UNPKG

@mastra/core

Version:
1 lines 52.3 kB
{"version":3,"file":"event-emitter-80LrFIBS.cjs","names":["EventEmitter"],"sources":["../src/events/pubsub.ts","../src/events/event-emitter/batch-policy.ts","../src/events/event-emitter/ack-handle-buffer.ts","../src/events/event-emitter/index.ts"],"sourcesContent":["import type { Event, EventCallback, SubscribeOptions } from './types';\n\n/**\n * Delivery model for a PubSub implementation.\n *\n * - `pull`: consumers actively read from the broker (e.g. Redis Streams\n * XREADGROUP, GCP Pub/Sub streamingPull, SQS ReceiveMessage). Mastra runs\n * a long-lived `OrchestrationWorker` that owns a subscription loop.\n *\n * - `push`: events arrive without the consumer asking — either in-process\n * (EventEmitter dispatching to a registered listener) or out-of-process\n * (the broker POSTs to an HTTP endpoint, e.g. GCP Pub/Sub push, SNS,\n * EventBridge). Mastra wires the workflow handler directly to the pubsub\n * for in-process push, or relies on `POST /api/workers/events` for\n * broker push delivered over HTTP.\n */\nexport type PubSubDeliveryMode = 'pull' | 'push';\n\nexport abstract class PubSub {\n abstract publish(\n topic: string,\n event: Omit<Event, 'id' | 'createdAt'>,\n options?: { localOnly?: boolean },\n ): Promise<void>;\n abstract subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void>;\n abstract unsubscribe(topic: string, cb: EventCallback): Promise<void>;\n /**\n * Drain any buffered or in-flight deliveries before resolving.\n *\n * Best-effort: a `flush()` that resolves successfully does not guarantee\n * every subscriber callback succeeded — implementations surface per-event\n * delivery errors via their configured logger rather than re-throwing,\n * so a single failed callback does not mask later cleanup work.\n */\n abstract flush(): Promise<void>;\n\n /**\n * Delete all retained state for a topic (cached history, persistent stream\n * entries, consumer groups) once no more events will be published to it.\n *\n * Called by run lifecycles (durable agents, the evented workflow engine)\n * when a run reaches a terminal state, so per-run topics don't accumulate\n * forever on transports that retain messages (e.g. Redis Streams).\n *\n * Default implementation is a no-op: transports that don't retain anything\n * per topic (e.g. plain EventEmitter delivery) have nothing to clear.\n *\n * Best-effort contract: implementations should not throw — callers invoke\n * this fire-and-forget at cleanup boundaries, so failures should be logged\n * by the implementation rather than rejected.\n *\n * @param topic - The topic whose retained state should be deleted\n */\n clearTopic(_topic: string): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Delivery modes this PubSub implementation supports.\n *\n * Defaults to `['pull']` for backward compatibility — third-party\n * implementations that don't override this property are treated as\n * pull-mode, which preserves today's behavior.\n *\n * Implementations that deliver events without an active read loop (e.g.\n * EventEmitter, GCP Pub/Sub push subscriptions) should declare `'push'`.\n * Implementations that support both modes should declare both.\n */\n get supportedModes(): ReadonlyArray<PubSubDeliveryMode> {\n return ['pull'];\n }\n\n /**\n * Whether this implementation honors `options.batch` on `subscribe()`\n * natively. Defaults to `false`.\n *\n * Implementations that integrate batching internally (e.g. against their\n * own broker retention or via an `AckHandleBuffer`) override this getter\n * and return `true`.\n */\n get supportsNativeBatching(): boolean {\n return false;\n }\n\n /**\n * Get historical events for a topic.\n * Default implementation returns empty array (no history support).\n * Override in implementations that support event caching.\n *\n * @param topic - The topic to get history for\n * @param offset - Starting index (0-based), defaults to 0\n * @returns Array of events from the specified index\n */\n getHistory(_topic: string, _offset?: number): Promise<Event[]> {\n return Promise.resolve([]);\n }\n\n /**\n * Subscribe to a topic with automatic replay of cached events.\n * First replays any cached history, then subscribes to live events.\n * Default implementation falls back to regular subscribe (no replay).\n * Override in implementations that support event caching.\n *\n * @param topic - The topic to subscribe to\n * @param cb - Callback invoked for each event (both cached and live)\n */\n subscribeWithReplay(topic: string, cb: EventCallback): Promise<void> {\n return this.subscribe(topic, cb);\n }\n\n /**\n * Subscribe to a topic with replay starting from a specific index.\n * This is more efficient than full replay when the client knows their last position.\n * Default implementation falls back to subscribeWithReplay (full replay).\n * Override in implementations that support indexed event caching.\n *\n * @param topic - The topic to subscribe to\n * @param offset - Start replaying from this index (0-based)\n * @param cb - Callback invoked for each event\n */\n subscribeFromOffset(topic: string, _offset: number, cb: EventCallback): Promise<void> {\n return this.subscribeWithReplay(topic, cb);\n }\n}\n\n/**\n * Distributed leasing capability, separate from event delivery (`PubSub`).\n *\n * Used by the signals layer to elect a single owner across multiple\n * processes (e.g. serverless invocations) for a given resource — most\n * commonly a thread-key, where the owner is the process that will wake\n * and run the agent stream.\n *\n * Leasing is a distinct concern from pub/sub: a backend only implements\n * this when it can genuinely coordinate a lock (Redis via SET-NX, an\n * in-memory map for single-process). Backends that cannot lease simply do\n * not implement `LeaseProvider`; the signals runtime feature-detects and\n * falls back to {@link NoopLeaseProvider} (always-win / no-op), preserving\n * single-process behavior.\n */\nexport interface LeaseProvider {\n /**\n * Atomically try to acquire a lease on a key.\n *\n * Returns `{ acquired: true, owner }` if the caller claimed the lease,\n * or `{ acquired: false, owner }` where `owner` is the current holder\n * (so the caller can route follow-up work to them). `owner` may be\n * `undefined` if the holder could not be read (rare).\n *\n * @param key - The lease key (e.g. thread key)\n * @param owner - Identifier for the owner (e.g. runId) — used so the\n * same owner can call `acquireLease` idempotently and renew/release.\n * @param ttlMs - Time-to-live in milliseconds for the lease\n */\n acquireLease(key: string, owner: string, ttlMs: number): Promise<{ acquired: boolean; owner?: string }>;\n\n /**\n * Read the current owner of a lease, or `undefined` if no lease is held.\n */\n getLeaseOwner(key: string): Promise<string | undefined>;\n\n /**\n * Release a lease. No-op if the caller is not the current owner\n * (implementations should atomically check ownership before releasing\n * to avoid clobbering a renewal that happened concurrently).\n */\n releaseLease(key: string, owner: string): Promise<void>;\n\n /**\n * Renew an existing lease owned by `owner`, extending its TTL.\n *\n * Returns `true` if the renewal succeeded (caller still owns it),\n * `false` if the lease was lost (TTL expired or another owner took it).\n */\n renewLease(key: string, owner: string, ttlMs: number): Promise<boolean>;\n\n /**\n * Atomically hand a held lease from `fromOwner` to `toOwner`, refreshing\n * its TTL, without ever releasing the key in between.\n *\n * This is the gap-free primitive used when one owner finishes but a\n * follow-up owner must take over the *same* lease key immediately (e.g. a\n * thread run completes and a queued follow-up run drains on the same\n * thread). A naive release-then-acquire would briefly leave the key empty,\n * letting a racing process win the freed lease and start a competing run.\n *\n * Returns `true` if `fromOwner` still held the lease and ownership moved to\n * `toOwner`; `false` if the lease was already lost (expired or taken by a\n * third owner), in which case the caller should fall back to a fresh\n * `acquireLease`.\n *\n * Backends that cannot perform this atomically must still implement it —\n * as a best-effort `releaseLease(from)` followed by `acquireLease(to)` — and\n * document that the swap is non-atomic (a racing process can win the key in\n * the gap). Keeping it required means callers have a single code path and the\n * atomicity guarantee is an explicit per-backend decision rather than a\n * silent caller-side fallback.\n */\n transferLease(key: string, fromOwner: string, toOwner: string, ttlMs: number): Promise<boolean>;\n}\n\n/**\n * Duck-typed check for whether a value implements {@link LeaseProvider}.\n *\n * Uses structural detection rather than `instanceof` so it works across\n * package boundaries (e.g. a separately-published pubsub backend resolving\n * a different copy of `@mastra/core`).\n */\nexport function isLeaseProvider(value: unknown): value is LeaseProvider {\n if (!value || (typeof value !== 'object' && typeof value !== 'function')) return false;\n const candidate = value as Partial<LeaseProvider>;\n return (\n typeof candidate.acquireLease === 'function' &&\n typeof candidate.getLeaseOwner === 'function' &&\n typeof candidate.releaseLease === 'function' &&\n typeof candidate.renewLease === 'function' &&\n typeof candidate.transferLease === 'function'\n );\n}\n\n/**\n * Always-win / no-op {@link LeaseProvider}. Used by the signals runtime\n * when the configured pubsub does not implement `LeaseProvider` — this\n * preserves single-process behavior where every caller \"wins\" its own\n * lease race and release/renew are inert.\n */\nexport const NoopLeaseProvider: LeaseProvider = {\n acquireLease(_key: string, owner: string, _ttlMs: number): Promise<{ acquired: boolean; owner?: string }> {\n return Promise.resolve({ acquired: true, owner });\n },\n getLeaseOwner(_key: string): Promise<string | undefined> {\n return Promise.resolve(undefined);\n },\n releaseLease(_key: string, _owner: string): Promise<void> {\n return Promise.resolve();\n },\n renewLease(_key: string, _owner: string, _ttlMs: number): Promise<boolean> {\n return Promise.resolve(true);\n },\n transferLease(_key: string, _fromOwner: string, _toOwner: string, _ttlMs: number): Promise<boolean> {\n // Single-process: there is no competing holder, so the handoff always\n // \"succeeds\" — the next owner is free to proceed.\n return Promise.resolve(true);\n },\n};\n","import type { Event, SubscribeBatchOptions } from '../types';\n\n/**\n * Opaque timer handle. We don't care whether the runtime returns a number\n * (browser) or a `Timeout` (Node) — we only ever hand it back to\n * `clearTimeout`. Branding it keeps the type honest (you can't pass an\n * arbitrary number) while staying erasure-free at runtime.\n */\ndeclare const batchPolicyTimerHandleBrand: unique symbol;\nexport type BatchPolicyTimerHandle = { readonly [batchPolicyTimerHandleBrand]: true };\n\n/**\n * Injectable dependencies for `BatchPolicy`. Tests pass fake timers /\n * controllable clocks; production uses Node's `Date.now` / `setTimeout` /\n * `clearTimeout`.\n */\nexport interface BatchPolicyDeps {\n now: () => number;\n setTimeout: (cb: () => void, ms: number) => BatchPolicyTimerHandle;\n clearTimeout: (handle: BatchPolicyTimerHandle) => void;\n}\n\nconst defaultDeps: BatchPolicyDeps = {\n now: () => Date.now(),\n setTimeout: (cb, ms) => setTimeout(cb, ms) as unknown as BatchPolicyTimerHandle,\n clearTimeout: handle => clearTimeout(handle as unknown as Parameters<typeof clearTimeout>[0]),\n};\n\nexport type EnqueueDecision = 'flush-now' | 'wait';\n\nexport const DEFAULT_MAX_BUFFER_SIZE = 256;\nexport const DEFAULT_OVERFLOW: NonNullable<SubscribeBatchOptions['overflow']> = 'coalesce-or-drop-oldest';\n\n/**\n * Internal to `EventEmitterPubSub`. Embedded by `AckHandleBuffer` to decide\n * when a batched subscription should flush (size, time, coalesce, overflow).\n *\n * Not part of the public API — users configure batching via\n * `SubscribeBatchOptions` on `subscribe`.\n */\nexport class BatchPolicy {\n private readonly opts: SubscribeBatchOptions;\n private readonly deps: BatchPolicyDeps;\n private readonly maxBufferSize: number;\n private readonly overflow: NonNullable<SubscribeBatchOptions['overflow']>;\n\n private firstQueuedAt: number | null = null;\n private lastDeliveredAt: number = -Infinity;\n private size = 0;\n private timer: BatchPolicyTimerHandle | null = null;\n private flushHandler: (() => void | Promise<void>) | null = null;\n\n constructor(opts: SubscribeBatchOptions, deps: BatchPolicyDeps = defaultDeps) {\n this.opts = opts;\n this.deps = deps;\n this.maxBufferSize = opts.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE;\n this.overflow = opts.overflow ?? DEFAULT_OVERFLOW;\n }\n\n /** Bind the function invoked when the deadline timer fires. */\n bindFlushHandler(fn: () => void | Promise<void>): void {\n this.flushHandler = fn;\n }\n\n /**\n * Called by the integrator each time an event is enqueued.\n * Returns whether the integrator should flush immediately.\n */\n onEnqueue(event: Event): EnqueueDecision {\n this.size += 1;\n if (this.firstQueuedAt === null) {\n this.firstQueuedAt = this.deps.now();\n }\n\n const now = this.deps.now();\n const intervalFloor = this.lastDeliveredAt + (this.opts.minIntervalMs ?? 0);\n\n // Immediate event — bypass maxWait/maxSize gating, but still respect interval floor.\n if (this.opts.isImmediate?.(event)) {\n if (now >= intervalFloor) {\n return 'flush-now';\n }\n // Hold until the floor; reschedule timer there.\n this.scheduleAt(intervalFloor);\n return 'wait';\n }\n\n // Overflow trigger (regardless of interval — overflow is a budget enforcement).\n if (this.size >= this.maxBufferSize) {\n return 'flush-now';\n }\n\n // maxSize trigger — respects interval floor.\n if (this.opts.maxSize !== undefined && this.size >= this.opts.maxSize) {\n if (now >= intervalFloor) {\n return 'flush-now';\n }\n this.scheduleAt(intervalFloor);\n return 'wait';\n }\n\n // No immediate trigger — schedule the deadline if there is one.\n this.scheduleDeadline();\n return 'wait';\n }\n\n /**\n * Called by the integrator after a successful flush has delivered\n * `deliveredCount` events. Resets timer + firstQueuedAt.\n */\n onFlushed(deliveredCount: number): void {\n this.lastDeliveredAt = this.deps.now();\n this.size = Math.max(0, this.size - deliveredCount);\n this.firstQueuedAt = null;\n this.cancelTimer();\n }\n\n /**\n * Pure helper. Given the caller-owned queue contents, applies `coalesce`\n * and `overflow` to decide what to deliver and what to drop.\n * Order-preserving for kept events.\n */\n prepareBatch(events: Event[]): { delivered: Event[]; dropped: Event[] } {\n let working = events;\n\n // 1. Coalesce, if configured.\n if (this.opts.coalesce) {\n const coalesced = this.opts.coalesce(working);\n // Contract: `coalesce` MUST return a subset of `working` by reference\n // identity. Anything else (fresh objects, even with matching ids)\n // breaks ack routing downstream — AckHandleBuffer keys ack/nack by\n // event reference, and there's no way to map a manufactured event\n // back to the original transport handle. Detect the violation here\n // and discard the whole batch (treat all originals as dropped) rather\n // than silently deliver references with no ack/nack wired up.\n const inputRefs = new Set<Event>(working);\n const allInInput = coalesced.every(e => inputRefs.has(e));\n working = allInInput ? coalesced : [];\n }\n\n const keptRefs = new Set<Event>(working);\n const computeDropped = (): Event[] => events.filter(e => !keptRefs.has(e));\n\n // 2. Overflow handling — only if still over `maxBufferSize`.\n if (working.length <= this.maxBufferSize) {\n return { delivered: working, dropped: computeDropped() };\n }\n\n const overBy = working.length - this.maxBufferSize;\n const isImmediate = this.opts.isImmediate;\n\n let kept: Event[];\n let droppedFromOverflow: Event[];\n\n switch (this.overflow) {\n case 'drop-newest': {\n const splitFromEnd = this.takeWithoutDropping(working, overBy, isImmediate, /* fromEnd */ true);\n kept = splitFromEnd.kept;\n droppedFromOverflow = splitFromEnd.dropped;\n break;\n }\n case 'drop-oldest':\n case 'coalesce-or-drop-oldest':\n default: {\n const splitFromStart = this.takeWithoutDropping(working, overBy, isImmediate, /* fromEnd */ false);\n kept = splitFromStart.kept;\n droppedFromOverflow = splitFromStart.dropped;\n break;\n }\n }\n\n return { delivered: kept, dropped: [...computeDropped(), ...droppedFromOverflow] };\n }\n\n /** Stop the timer and clear policy state. */\n dispose(): void {\n this.cancelTimer();\n this.flushHandler = null;\n this.firstQueuedAt = null;\n this.size = 0;\n }\n\n private scheduleDeadline(): void {\n if (this.opts.maxWaitMs === undefined && this.opts.minIntervalMs === undefined) {\n // No time-based trigger — only `maxSize` / `isImmediate` can flush.\n return;\n }\n\n const firstQueuedAt = this.firstQueuedAt ?? this.deps.now();\n const deadline = this.opts.maxWaitMs !== undefined ? firstQueuedAt + this.opts.maxWaitMs : Number.POSITIVE_INFINITY;\n const floor = this.lastDeliveredAt + (this.opts.minIntervalMs ?? 0);\n const at = Math.max(deadline, floor);\n this.scheduleAt(at);\n }\n\n private scheduleAt(at: number): void {\n if (!isFinite(at)) {\n return;\n }\n this.cancelTimer();\n const delay = Math.max(0, at - this.deps.now());\n this.timer = this.deps.setTimeout(() => {\n this.timer = null;\n const handler = this.flushHandler;\n if (handler) {\n void handler();\n }\n }, delay);\n }\n\n private cancelTimer(): void {\n if (this.timer !== null) {\n this.deps.clearTimeout(this.timer);\n this.timer = null;\n }\n }\n\n /**\n * Drop `count` non-immediate items from the start (or end) of `items`.\n * Immediate items are never dropped — if every candidate is immediate,\n * fewer than `count` items are dropped.\n */\n private takeWithoutDropping(\n items: Event[],\n count: number,\n isImmediate: ((e: Event) => boolean) | undefined,\n fromEnd: boolean,\n ): { kept: Event[]; dropped: Event[] } {\n const dropped: Event[] = [];\n const result = [...items];\n let remaining = count;\n\n const order = fromEnd ? [...result.keys()].reverse() : [...result.keys()];\n\n for (const idx of order) {\n if (remaining === 0) break;\n const ev = result[idx]!;\n if (isImmediate?.(ev)) continue;\n dropped.push(ev);\n result[idx] = undefined as unknown as Event;\n remaining -= 1;\n }\n\n const kept = result.filter((e): e is Event => e !== undefined);\n return { kept, dropped };\n }\n}\n","import type { Event, EventCallback, SubscribeBatchOptions } from '../types';\nimport type { BatchPolicyDeps } from './batch-policy';\nimport { BatchPolicy } from './batch-policy';\n\ninterface Entry {\n event: Event;\n ack?: () => Promise<void>;\n nack?: () => Promise<void>;\n}\n\n/**\n * In-process queue used by `EventEmitterPubSub` to turn its\n * one-event-per-emit stream into batched callback invocations.\n * Owns a `BatchPolicy` that decides when to flush (size, time,\n * quiet-period) and holds (event, ack, nack) triples in publish\n * order until that decision fires.\n *\n * Extracted from `EventEmitterPubSub` only so the batching state\n * machine can be tested in isolation. Not a public extension point.\n * State is per-process; the queue dies with the process.\n */\nexport class AckHandleBuffer {\n private readonly policy: BatchPolicy;\n private queue: Entry[] = [];\n private flushing = false;\n private reflush = false;\n private disposed = false;\n\n constructor(\n private readonly cb: EventCallback,\n opts: SubscribeBatchOptions,\n deps?: BatchPolicyDeps,\n private readonly onError?: (err: unknown, ctx: { phase: 'cb' | 'ack-dropped' }) => void,\n ) {\n this.policy = new BatchPolicy(opts, deps);\n // The policy's deadline timer fires this handler fire-and-forget (it\n // discards the returned promise), so a rejection from `flush()` — e.g. a\n // user-supplied `coalesce` throwing inside `prepareBatch`, which lands\n // outside the per-event try/catch below — would otherwise escape as an\n // unhandled rejection on the timer path. The inline flush-now, group, and\n // explicit `flush()` paths each catch this already; route the timer path\n // through the same `onError` channel.\n this.policy.bindFlushHandler(() =>\n this.flush().catch(err => {\n this.onError?.(err, { phase: 'cb' });\n }),\n );\n }\n\n /**\n * Called by the adapter for each event arriving from the underlying transport.\n */\n async push(event: Event, ack?: () => Promise<void>, nack?: () => Promise<void>): Promise<void> {\n if (this.disposed) return;\n this.queue.push({ event, ack, nack });\n const decision = this.policy.onEnqueue(event);\n if (decision === 'flush-now') {\n await this.flush();\n }\n }\n\n /**\n * Drain the current queue regardless of policy state. Safe to call from\n * adapter `flush()` or external code that wants to force delivery.\n */\n async flush(): Promise<void> {\n // A flush-now request that lands while we're already draining is not\n // dropped — latch it so the current pass picks up the new events as\n // soon as it finishes its current snapshot, instead of forcing those\n // events to wait until the policy timer fires.\n if (this.flushing) {\n this.reflush = true;\n return;\n }\n // Empty buffer is a true no-op. `policy.onFlushed` bumps `lastDeliveredAt`,\n // which extends the `minIntervalMs` floor — calling it on every empty\n // flush silently corrupts the cadence for callers that flush() defensively.\n if (this.queue.length === 0) return;\n\n this.flushing = true;\n try {\n do {\n this.reflush = false;\n if (this.queue.length === 0) break;\n\n const snapshot = this.queue;\n this.queue = [];\n\n const events = snapshot.map(e => e.event);\n // Build a reverse index once so we don't pay O(n) per event looking up\n // the original Entry below.\n const byEvent = new Map<Event, Entry>();\n for (const e of snapshot) byEvent.set(e.event, e);\n\n const { delivered, dropped } = this.policy.prepareBatch(events);\n\n // Ack events that were coalesced or overflow-dropped — they should\n // not be redelivered. The transport's own ack is the right hook.\n for (const ev of dropped) {\n const entry = byEvent.get(ev);\n if (entry?.ack) {\n try {\n await entry.ack();\n } catch (err) {\n this.onError?.(err, { phase: 'ack-dropped' });\n }\n }\n }\n\n for (const ev of delivered) {\n // A cb may dispose the buffer mid-flush (e.g. subscriber tearing\n // itself down on a fatal event). Honor it immediately — don't keep\n // feeding events into a callback that asked to stop.\n if (this.disposed) break;\n const entry = byEvent.get(ev);\n try {\n // The declared EventCallback return type is `void`, but real\n // implementations frequently return a Promise. Await both kinds\n // so per-event isolation actually waits for the cb to settle.\n await (this.cb(ev, entry?.ack, entry?.nack) as void | Promise<void>);\n } catch (err) {\n this.onError?.(err, { phase: 'cb' });\n }\n }\n\n // `policy.size` was incremented once per push; decrement it by\n // everything that left the queue (delivered + dropped) so it doesn't\n // drift upward and trip maxSize prematurely.\n this.policy.onFlushed(delivered.length + dropped.length);\n } while (this.reflush && !this.disposed);\n } finally {\n this.flushing = false;\n }\n }\n\n dispose(): void {\n this.disposed = true;\n this.queue = [];\n this.policy.dispose();\n }\n}\n","import EventEmitter from 'node:events';\nimport type { IMastraLogger } from '../../logger';\nimport { PubSub } from '../pubsub';\nimport type { LeaseProvider, PubSubDeliveryMode } from '../pubsub';\nimport type { Event, EventCallback, SubscribeOptions } from '../types';\nimport { AckHandleBuffer } from './ack-handle-buffer';\n\nexport interface EventEmitterPubSubOptions {\n /**\n * Optional logger for surfacing batched-delivery errors. Falls back to\n * `console.error` when not provided.\n */\n logger?: IMastraLogger;\n}\n\n// Reused for the fan-out delivery path where ack/nack are no-ops: the process\n// is the broker, there is no transport-level redelivery to negotiate. Hoisted\n// to module scope so we don't allocate two new closures per emitted event.\nconst NOOP_ACK = async (): Promise<void> => {};\n\nexport class EventEmitterPubSub extends PubSub implements LeaseProvider {\n // EventEmitter dispatches synchronously to listeners, so it can serve both\n // a push consumer (no worker) and a pull-style worker that simply calls\n // `subscribe()` to register a listener. Both modes are advertised so the\n // default in-process setup keeps using OrchestrationWorker, while\n // genuinely push-only transports (GCP Pub/Sub push, SNS, EventBridge)\n // declare `['push']` only and skip the worker.\n override get supportedModes(): ReadonlyArray<PubSubDeliveryMode> {\n return ['pull', 'push'];\n }\n\n /**\n * `EventEmitterPubSub` is strictly in-process, so the `AckHandleBuffer`\n * queue it uses for batching shares the same lifetime as everything\n * else here. Nothing more durable is promised, and nothing less is\n * needed.\n */\n override get supportsNativeBatching(): boolean {\n return true;\n }\n\n private emitter: EventEmitter;\n\n // group → topic → callbacks[]\n private groups: Map<string, Map<string, EventCallback[]>> = new Map();\n // \"topic:group\" → round-robin counter\n private groupCounters: Map<string, number> = new Map();\n // \"topic:group\" → the single listener registered on the emitter for this group\n private groupListeners: Map<string, (event: Event) => void> = new Map();\n\n // Track pending nack redeliveries so flush() can wait and close() can cancel them\n private pendingNacks: Set<ReturnType<typeof setTimeout>> = new Set();\n\n // Track delivery attempts per message id\n private deliveryAttempts: Map<string, number> = new Map();\n\n // topic → (original callback → wrapped listener) for fan-out (non-group) subscribers.\n // Nested keying so the same callback registered on multiple topics keeps\n // a distinct wrapper per topic.\n private fanoutWrappers: Map<string, Map<EventCallback, (event: Event) => void>> = new Map();\n\n // topic → (original callback → buffer). Present only for subscribers that\n // opt into batching via `options.batch`. The buffer is the destination of\n // the emitter listener; it invokes the user cb according to its policy.\n private batchBuffers: Map<string, Map<EventCallback, AckHandleBuffer>> = new Map();\n\n private readonly logger?: IMastraLogger;\n\n constructor(existingEmitter?: EventEmitter, options: EventEmitterPubSubOptions = {}) {\n super();\n this.emitter = existingEmitter ?? new EventEmitter();\n this.logger = options.logger;\n }\n\n /**\n * Debug-hostile silent failures are the default for emitter listeners.\n * Surface buffer-side errors on a single channel so they're at least visible.\n */\n private logBufferError(topic: string, err: unknown, ctx: { phase: 'cb' | 'ack-dropped' }): void {\n const message = `[EventEmitterPubSub] batched ${ctx.phase} failed for ${topic}`;\n if (this.logger) {\n this.logger.error(message, err);\n } else {\n console.error(message, err);\n }\n }\n\n async publish(\n topic: string,\n event: Omit<Event, 'id' | 'createdAt'>,\n _options?: { localOnly?: boolean },\n ): Promise<void> {\n const id = crypto.randomUUID();\n const createdAt = new Date();\n this.emitter.emit(topic, {\n ...event,\n id,\n createdAt,\n deliveryAttempt: 1,\n });\n }\n\n async subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void> {\n if (options?.batch) {\n // Batched path: insert an AckHandleBuffer between the emitter and cb.\n // ack/nack are no-ops at this layer — the process is the broker.\n const buffer = new AckHandleBuffer(cb, options.batch, undefined, (err, ctx) => {\n this.logBufferError(topic, err, ctx);\n });\n let byCb = this.batchBuffers.get(topic);\n if (!byCb) {\n byCb = new Map();\n this.batchBuffers.set(topic, byCb);\n }\n byCb.set(cb, buffer);\n\n if (options.group) {\n // Group path: the group's member list keeps the original `cb` so\n // `unsubscribe(topic, cb)` and round-robin tracking work unchanged.\n // `deliverToGroup` checks `batchBuffers` and routes through the\n // buffer when present.\n this.subscribeWithGroup(topic, cb, options.group);\n } else {\n const wrapper = (event: Event) => {\n // Fire-and-forget — buffer.push can reject if the user-supplied\n // `coalesce` throws or any other policy step fails during an\n // inline flush-now. Surface those through the logger rather\n // than letting them become unhandled rejections.\n void buffer.push(event, NOOP_ACK, NOOP_ACK).catch(err => {\n this.logBufferError(topic, err, { phase: 'cb' });\n });\n };\n let byCbFanout = this.fanoutWrappers.get(topic);\n if (!byCbFanout) {\n byCbFanout = new Map();\n this.fanoutWrappers.set(topic, byCbFanout);\n }\n byCbFanout.set(cb, wrapper);\n this.emitter.on(topic, wrapper);\n }\n return;\n }\n\n if (options?.group) {\n this.subscribeWithGroup(topic, cb, options.group);\n } else {\n const wrapper = (event: Event) => {\n cb(event, NOOP_ACK, NOOP_ACK);\n };\n let byCb = this.fanoutWrappers.get(topic);\n if (!byCb) {\n byCb = new Map();\n this.fanoutWrappers.set(topic, byCb);\n }\n byCb.set(cb, wrapper);\n this.emitter.on(topic, wrapper);\n }\n }\n\n async unsubscribe(topic: string, cb: EventCallback): Promise<void> {\n // Tear down a batching buffer for this (topic, cb) pair, if one was set\n // up by `subscribe`. Done first so any in-flight emitter dispatches\n // ignore further events into a disposed buffer.\n const byCbBuffers = this.batchBuffers.get(topic);\n const buffer = byCbBuffers?.get(cb);\n if (buffer && byCbBuffers) {\n buffer.dispose();\n byCbBuffers.delete(cb);\n if (byCbBuffers.size === 0) this.batchBuffers.delete(topic);\n }\n\n // Check if this callback is in any group for this topic\n for (const [group, topicMap] of this.groups) {\n const members = topicMap.get(topic);\n if (members) {\n const idx = members.indexOf(cb);\n if (idx !== -1) {\n members.splice(idx, 1);\n // If group is now empty for this topic, remove the emitter listener\n if (members.length === 0) {\n topicMap.delete(topic);\n const listenerKey = `${topic}:${group}`;\n const listener = this.groupListeners.get(listenerKey);\n if (listener) {\n this.emitter.off(topic, listener);\n this.groupListeners.delete(listenerKey);\n this.groupCounters.delete(listenerKey);\n }\n }\n if (topicMap.size === 0) {\n this.groups.delete(group);\n }\n return;\n }\n }\n }\n\n // Not in a group — remove as fan-out listener\n const byCb = this.fanoutWrappers.get(topic);\n const wrapper = byCb?.get(cb);\n if (wrapper && byCb) {\n this.emitter.off(topic, wrapper);\n byCb.delete(cb);\n if (byCb.size === 0) this.fanoutWrappers.delete(topic);\n } else {\n this.emitter.off(topic, cb);\n }\n }\n\n async flush(): Promise<void> {\n // A batched cb can nack mid-delivery, which schedules a redelivery via\n // setTimeout(0). The redelivered event lands back in `batchBuffers`\n // (the buffer is the group member's destination) and may sit there\n // below maxSize/maxWaitMs thresholds. So we loop: drain buffers, wait\n // for pending nacks to fire, then check whether either side produced\n // more work. Stable-state termination requires both to be empty at the\n // top of a single iteration.\n while (true) {\n const drains: { topic: string; promise: Promise<void> }[] = [];\n for (const [topic, byCb] of this.batchBuffers.entries()) {\n for (const buffer of byCb.values()) {\n drains.push({ topic, promise: buffer.flush() });\n }\n }\n if (drains.length > 0) {\n // allSettled — a single throwing buffer should not block the rest from\n // flushing during shutdown. Rejections that propagate this far skipped\n // the per-event try/catch in AckHandleBuffer (e.g. a throwing coalesce)\n // and must be surfaced or they vanish at shutdown.\n const results = await Promise.allSettled(drains.map(d => d.promise));\n for (let i = 0; i < results.length; i++) {\n const result = results[i]!;\n if (result.status === 'rejected') {\n this.logBufferError(drains[i]!.topic, result.reason, { phase: 'cb' });\n }\n }\n }\n\n if (this.pendingNacks.size === 0) {\n // Nothing scheduled — and the drain above either did nothing or\n // produced no new pending nacks, so we're stable.\n return;\n }\n\n // Wait for the currently-scheduled nacks to fire. Each redelivery\n // may land in a buffer; loop and re-drain.\n await new Promise<void>(resolve => {\n const check = () => {\n if (this.pendingNacks.size === 0) {\n resolve();\n } else {\n setTimeout(check, 10);\n }\n };\n check();\n });\n }\n }\n\n /**\n * Clean up all listeners during graceful shutdown.\n */\n async close(): Promise<void> {\n // Cancel pending nack redeliveries\n for (const handle of this.pendingNacks) {\n clearTimeout(handle);\n }\n this.pendingNacks.clear();\n this.deliveryAttempts.clear();\n\n // Dispose every batching buffer so timers are cleared.\n for (const byCb of this.batchBuffers.values()) {\n for (const buffer of byCb.values()) {\n buffer.dispose();\n }\n }\n this.batchBuffers.clear();\n\n this.emitter.removeAllListeners();\n this.groups.clear();\n this.groupCounters.clear();\n this.groupListeners.clear();\n this.fanoutWrappers.clear();\n }\n\n private subscribeWithGroup(topic: string, cb: EventCallback, group: string): void {\n let topicMap = this.groups.get(group);\n if (!topicMap) {\n topicMap = new Map();\n this.groups.set(group, topicMap);\n }\n\n let members = topicMap.get(topic);\n if (!members) {\n members = [];\n topicMap.set(topic, members);\n }\n\n members.push(cb);\n\n // Register a single emitter listener per topic:group pair\n const listenerKey = `${topic}:${group}`;\n if (!this.groupListeners.has(listenerKey)) {\n const listener = (event: Event) => {\n this.deliverToGroup(topic, group, listenerKey, event);\n };\n\n this.groupListeners.set(listenerKey, listener);\n this.emitter.on(topic, listener);\n }\n }\n\n private deliverToGroup(topic: string, group: string, listenerKey: string, event: Event): void {\n const currentMembers = this.groups.get(group)?.get(topic);\n if (!currentMembers || currentMembers.length === 0) return;\n\n const counter = this.groupCounters.get(listenerKey) ?? 0;\n const idx = counter % currentMembers.length;\n this.groupCounters.set(listenerKey, counter + 1);\n\n // Track delivery attempts scoped per group listener, so ack/nack in one\n // group doesn't disturb another group's attempt counter for the same event.\n const attemptKey = `${listenerKey}:${event.id}`;\n const attempt = this.deliveryAttempts.get(attemptKey) ?? 1;\n const eventWithAttempt = { ...event, deliveryAttempt: attempt };\n\n const ack = async () => {\n // Message successfully processed — clean up attempt tracking\n this.deliveryAttempts.delete(attemptKey);\n };\n\n const nack = async () => {\n // Message processing failed — redeliver to the group after a short delay\n // Increment delivery attempt counter\n this.deliveryAttempts.set(attemptKey, attempt + 1);\n\n const handle = setTimeout(() => {\n this.pendingNacks.delete(handle);\n this.deliverToGroup(topic, group, listenerKey, event);\n }, 0);\n this.pendingNacks.add(handle);\n };\n\n const member = currentMembers[idx]!;\n // If this member opted into batching, route through its buffer.\n const buffer = this.batchBuffers.get(topic)?.get(member);\n if (buffer) {\n // Same rationale as the fan-out push above: surface rejections\n // through the logger so they don't escape as unhandled.\n void buffer.push(eventWithAttempt, ack, nack).catch(err => {\n this.logBufferError(topic, err, { phase: 'cb' });\n });\n } else {\n member(eventWithAttempt, ack, nack);\n }\n }\n\n // key → { owner, expiresAt }. In-process so a single Map is enough;\n // there is no other process to race against. The same owner can renew\n // their own lease; expired entries are reclaimed lazily on the next\n // acquireLease call.\n private leases: Map<string, { owner: string; expiresAt: number }> = new Map();\n\n acquireLease(key: string, owner: string, ttlMs: number): Promise<{ acquired: boolean; owner?: string }> {\n const now = Date.now();\n const existing = this.leases.get(key);\n if (existing && existing.expiresAt > now && existing.owner !== owner) {\n return Promise.resolve({ acquired: false, owner: existing.owner });\n }\n this.leases.set(key, { owner, expiresAt: now + ttlMs });\n return Promise.resolve({ acquired: true, owner });\n }\n\n getLeaseOwner(key: string): Promise<string | undefined> {\n const existing = this.leases.get(key);\n if (!existing) return Promise.resolve(undefined);\n if (existing.expiresAt <= Date.now()) {\n this.leases.delete(key);\n return Promise.resolve(undefined);\n }\n return Promise.resolve(existing.owner);\n }\n\n releaseLease(key: string, owner: string): Promise<void> {\n const existing = this.leases.get(key);\n if (existing && existing.owner === owner) {\n this.leases.delete(key);\n }\n return Promise.resolve();\n }\n\n renewLease(key: string, owner: string, ttlMs: number): Promise<boolean> {\n const existing = this.leases.get(key);\n if (!existing || existing.owner !== owner || existing.expiresAt <= Date.now()) {\n return Promise.resolve(false);\n }\n existing.expiresAt = Date.now() + ttlMs;\n return Promise.resolve(true);\n }\n\n // Atomic owner-guarded handoff: only the current owner can transfer, and the\n // key is never empty during the swap (mirrors the Redis GET==from -> SET to\n // Lua). Lets a finishing run hand the lease to its drain run without a\n // release/acquire gap, even on the in-process backend.\n transferLease(key: string, fromOwner: string, toOwner: string, ttlMs: number): Promise<boolean> {\n const existing = this.leases.get(key);\n if (!existing || existing.owner !== fromOwner || existing.expiresAt <= Date.now()) {\n return Promise.resolve(false);\n }\n this.leases.set(key, { owner: toOwner, expiresAt: Date.now() + ttlMs });\n return Promise.resolve(true);\n }\n}\n"],"mappings":"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