@mastra/core
Version:
Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.
1 lines • 16.9 kB
Source Map (JSON)
{"version":3,"sources":["../src/events/pubsub.ts","../src/events/event-emitter.ts"],"names":["EventEmitter"],"mappings":"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","file":"chunk-CVF4W47C.cjs","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(topic: string, event: Omit<Event, 'id' | 'createdAt'>): Promise<void>;\n abstract subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void>;\n abstract unsubscribe(topic: string, cb: EventCallback): Promise<void>;\n abstract flush(): Promise<void>;\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 * 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","import EventEmitter from 'node:events';\nimport { PubSub } from './pubsub';\nimport type { PubSubDeliveryMode } from './pubsub';\nimport type { Event, EventCallback, SubscribeOptions } from './types';\n\nexport class EventEmitterPubSub extends PubSub {\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 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 constructor(existingEmitter?: EventEmitter) {\n super();\n this.emitter = existingEmitter ?? new EventEmitter();\n }\n\n async publish(topic: string, event: Omit<Event, 'id' | 'createdAt'>): 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?.group) {\n this.subscribeWithGroup(topic, cb, options.group);\n } else {\n const wrapper = (event: Event) => {\n cb(\n event,\n async () => {},\n async () => {},\n );\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 // 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 // Wait for any pending nack redeliveries to fire\n if (this.pendingNacks.size > 0) {\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 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 currentMembers[idx]!(eventWithAttempt, ack, nack);\n }\n}\n"]}