UNPKG

@mastra/core

Version:
1 lines 17.9 kB
{"version":3,"sources":["../src/events/caching-pubsub.ts"],"names":["PubSub","isLeaseProvider"],"mappings":";;;;;AAuDO,IAAM,aAAA,GAAN,cAA4BA,wBAAA,CAAO;AAAA,EAMxC,WAAA,CACmB,KAAA,EACA,KAAA,EACjB,OAAA,GAAgC,EAAC,EACjC;AACA,IAAA,KAAA,EAAM;AAJW,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA;AACA,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA;AAIjB,IAAA,IAAA,CAAK,SAAA,GAAY,QAAQ,SAAA,IAAa,SAAA;AACtC,IAAA,IAAA,CAAK,SAAS,OAAA,CAAQ,MAAA;AAAA,EACxB;AAAA,EAPmB,KAAA;AAAA,EACA,KAAA;AAAA,EAPF,SAAA;AAAA,EACA,MAAA;AAAA;AAAA,EAET,WAAA,uBAAkB,GAAA,EAAkC;AAAA,EAY5D,IAAI,sBAAA,GAAkC;AACpC,IAAA,OAAO,KAAK,KAAA,CAAM,sBAAA;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKQ,QAAA,CAAS,SAAiB,KAAA,EAAsB;AACtD,IAAA,IAAI,KAAK,MAAA,EAAQ;AACf,MAAA,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,OAAA,EAAS,KAAK,CAAA;AAAA,IAClC,CAAA,MAAO;AACL,MAAA,OAAA,CAAQ,KAAA,CAAM,SAAS,KAAK,CAAA;AAAA,IAC9B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeQ,SAAS,KAAA,EAAsB;AACrC,IAAA,OAAO,KAAA,CAAM,UAAU,MAAA,GAAY,CAAA,EAAA,EAAK,MAAM,KAAK,CAAA,CAAA,GAAK,CAAA,GAAA,EAAM,KAAA,CAAM,EAAE,CAAA,CAAA;AAAA,EACxE;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAY,KAAA,EAAuB;AACzC,IAAA,OAAO,CAAA,EAAG,IAAA,CAAK,SAAS,CAAA,EAAG,KAAK,CAAA,CAAA;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,KAAA,EAAuB;AAC3C,IAAA,OAAO,CAAA,EAAG,IAAA,CAAK,SAAS,CAAA,EAAG,KAAK,CAAA,QAAA,CAAA;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,OAAA,CACJ,KAAA,EACA,KAAA,EACA,OAAA,EACe;AACf,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,WAAA,CAAY,KAAK,CAAA;AACvC,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,aAAA,CAAc,KAAK,CAAA;AAE3C,IAAA,IAAI,KAAA;AACJ,IAAA,IAAI,WAAA,GAAc,KAAA;AAClB,IAAA,IAAI;AAEF,MAAA,KAAA,GAAS,MAAM,IAAA,CAAK,KAAA,CAAM,SAAA,CAAU,UAAU,CAAA,GAAK,CAAA;AAAA,IACrD,SAAS,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,QAAA,CAAS,CAAA,gDAAA,EAAmD,KAAK,CAAA,CAAA,EAAI,KAAK,CAAA;AAC/E,MAAA,WAAA,GAAc,IAAA;AAAA,IAChB;AAKA,IAAA,MAAM,SAAA,GAAmB;AAAA,MACvB,GAAG,KAAA;AAAA,MACH,EAAA,EAAI,OAAO,UAAA,EAAW;AAAA,MACtB,SAAA,sBAAe,IAAA,EAAK;AAAA,MACpB,GAAI,KAAA,KAAU,MAAA,GAAY,EAAE,KAAA,KAAU;AAAC,KACzC;AAEA,IAAA,IAAI,CAAC,WAAA,EAAa;AAChB,MAAA,IAAI;AAEF,QAAA,MAAM,IAAA,CAAK,KAAA,CAAM,QAAA,CAAS,QAAA,EAAU,SAAS,CAAA;AAAA,MAC/C,SAAS,KAAA,EAAO;AACd,QAAA,IAAA,CAAK,QAAA,CAAS,CAAA,0CAAA,EAA6C,KAAK,CAAA,CAAA,EAAI,KAAK,CAAA;AAAA,MAC3E;AAAA,IACF;AAGA,IAAA,MAAM,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,KAAA,EAAO,WAAW,OAAO,CAAA;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAA,CAAU,KAAA,EAAe,EAAA,EAAmB,OAAA,EAA2C;AAC3F,IAAA,MAAM,IAAA,CAAK,KAAA,CAAM,SAAA,CAAU,KAAA,EAAO,IAAI,OAAO,CAAA;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,mBAAA,CAAoB,KAAA,EAAe,EAAA,EAAkC;AACzE,IAAA,OAAO,IAAA,CAAK,mBAAA,CAAoB,KAAA,EAAO,CAAA,EAAG,EAAE,CAAA;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,mBAAA,CAAoB,KAAA,EAAe,MAAA,EAAgB,EAAA,EAAkC;AAEzF,IAAA,IAAI,aAAA,GAAgB,IAAA;AACpB,IAAA,MAAM,SAID,EAAC;AACN,IAAA,IAAI,aAAA,GAAgB,EAAA;AAEpB,IAAA,MAAM,SAAA,GAA2B,CAAC,KAAA,EAAO,GAAA,EAAK,IAAA,KAAS;AAErD,MAAA,IAAI,OAAO,KAAA,CAAM,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,QAAQ,MAAA,EAAQ;AAC3D,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,aAAA,EAAe;AACjB,QAAA,MAAA,CAAO,IAAA,CAAK,EAAE,KAAA,EAAO,GAAA,EAAK,MAAM,CAAA;AAChC,QAAA;AAAA,MACF;AAKA,MAAA,MAAM,UAAU,OAAO,KAAA,CAAM,eAAA,KAAoB,QAAA,IAAY,MAAM,eAAA,GAAkB,CAAA;AACrF,MAAA,IAAI,OAAO,MAAM,KAAA,KAAU,QAAA,IAAY,MAAM,KAAA,IAAS,aAAA,IAAiB,CAAC,OAAA,EAAS;AAC/E,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,OAAO,KAAA,CAAM,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,QAAQ,aAAA,EAAe;AAClE,QAAA,aAAA,GAAgB,KAAA,CAAM,KAAA;AAAA,MACxB;AACA,MAAA,EAAA,CAAG,KAAA,EAAO,KAAK,IAAI,CAAA;AAAA,IACrB,CAAA;AAEA,IAAA,IAAA,CAAK,WAAA,CAAY,GAAA,CAAI,EAAA,EAAI,SAAS,CAAA;AAClC,IAAA,MAAM,IAAA,CAAK,KAAA,CAAM,SAAA,CAAU,KAAA,EAAO,SAAS,CAAA;AAE3C,IAAA,IAAI;AAEF,MAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,MAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,UAAA,CAAW,OAAO,MAAM,CAAA;AACnD,MAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAC3B,QAAA,MAAM,GAAA,GAAM,IAAA,CAAK,QAAA,CAAS,KAAK,CAAA;AAC/B,QAAA,IAAA,CAAK,IAAI,GAAG,CAAA;AACZ,QAAA,IAAI,OAAO,KAAA,CAAM,KAAA,KAAU,QAAA,EAAU;AACnC,UAAA,aAAA,GAAgB,KAAA,CAAM,KAAA;AAAA,QACxB;AACA,QAAA,EAAA,CAAG,KAAK,CAAA;AAAA,MACV;AAGA,MAAA,KAAA,MAAW,EAAE,KAAA,EAAO,GAAA,EAAK,IAAA,MAAU,MAAA,EAAQ;AACzC,QAAA,MAAM,GAAA,GAAM,IAAA,CAAK,QAAA,CAAS,KAAK,CAAA;AAC/B,QAAA,IAAI,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA,EAAG;AACjB,UAAA;AAAA,QACF;AACA,QAAA,IAAA,CAAK,IAAI,GAAG,CAAA;AACZ,QAAA,IAAI,OAAO,KAAA,CAAM,KAAA,KAAU,QAAA,EAAU;AACnC,UAAA,aAAA,GAAgB,KAAA,CAAM,KAAA;AAAA,QACxB;AACA,QAAA,EAAA,CAAG,KAAA,EAAO,KAAK,IAAI,CAAA;AAAA,MACrB;AAGA,MAAA,aAAA,GAAgB,KAAA;AAChB,MAAA,MAAA,CAAO,MAAA,GAAS,CAAA;AAAA,IAClB,SAAS,KAAA,EAAO;AAEd,MAAA,IAAA,CAAK,WAAA,CAAY,OAAO,EAAE,CAAA;AAC1B,MAAA,MAAM,KAAK,KAAA,CAAM,WAAA,CAAY,OAAO,SAAS,CAAA,CAAE,MAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AAC7D,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAA,CAAY,KAAA,EAAe,EAAA,EAAkC;AACjE,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,WAAA,CAAY,GAAA,CAAI,EAAE,CAAA,IAAK,EAAA;AAC9C,IAAA,IAAA,CAAK,WAAA,CAAY,OAAO,EAAE,CAAA;AAC1B,IAAA,MAAM,IAAA,CAAK,KAAA,CAAM,WAAA,CAAY,KAAA,EAAO,SAAS,CAAA;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAA,CAAW,KAAA,EAAe,MAAA,GAAiB,CAAA,EAAqB;AACpE,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,WAAA,CAAY,KAAK,CAAA;AACvC,IAAA,MAAM,SAAS,MAAM,IAAA,CAAK,KAAA,CAAM,UAAA,CAAW,UAAU,MAAM,CAAA;AAC3D,IAAA,OAAO,MAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAA,GAAuB;AAC3B,IAAA,MAAM,IAAA,CAAK,MAAM,KAAA,EAAM;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,gBAAA,GAA8C;AAC5C,IAAA,OAAOC,iCAAA,CAAgB,IAAA,CAAK,KAAK,CAAA,GAAI,KAAK,KAAA,GAAQ,MAAA;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,WAAW,KAAA,EAA8B;AAC7C,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,WAAA,CAAY,KAAK,CAAA;AACvC,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,aAAA,CAAc,KAAK,CAAA;AAC3C,IAAA,MAAM,OAAA,CAAQ,GAAA,CAAI,CAAC,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,QAAQ,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,UAAU,CAAC,CAAC,CAAA;AAAA,EAChF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAA,GAAmB;AACjB,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AACF;AAcO,SAAS,WAAA,CAAY,MAAA,EAAgB,KAAA,EAA0B,OAAA,EAA+C;AACnH,EAAA,OAAO,IAAI,aAAA,CAAc,MAAA,EAAQ,KAAA,EAAO,OAAO,CAAA;AACjD","file":"chunk-OXETR7NG.cjs","sourcesContent":["import type { MastraServerCache } from '../cache/base';\nimport type { IMastraLogger } from '../logger';\nimport { isLeaseProvider, PubSub } from './pubsub';\nimport type { LeaseProvider } from './pubsub';\nimport type { Event, EventCallback, SubscribeOptions } from './types';\n\n/**\n * Options for CachingPubSub\n */\nexport interface CachingPubSubOptions {\n /**\n * Optional prefix for cache keys to namespace events.\n * Defaults to 'pubsub:'.\n */\n keyPrefix?: string;\n /**\n * Optional logger for structured logging.\n * Falls back to console.error if not provided.\n */\n logger?: IMastraLogger;\n}\n\n/**\n * A PubSub decorator that adds event caching and replay capabilities.\n *\n * Wraps any PubSub implementation and uses MastraServerCache to:\n * - Cache all published events per topic\n * - Enable replay of cached events for late subscribers\n *\n * This enables resumable streams - clients can disconnect and reconnect\n * without missing events.\n *\n * ## Batching\n *\n * `CachingPubSub` is transparent to `options.batch`: `subscribe()` forwards\n * the option to the inner PubSub, and `supportsNativeBatching` mirrors the\n * inner's value. Wrapping a non-native inner with `{ batch: {...} }` results\n * in unbatched delivery — use an inner that returns\n * `supportsNativeBatching === true` (e.g. `EventEmitterPubSub`) if you need\n * batched delivery.\n *\n * @example\n * ```typescript\n * import { EventEmitterPubSub, CachingPubSub } from '@mastra/core/events';\n * import { InMemoryServerCache } from '@mastra/core/cache';\n *\n * const cache = new InMemoryServerCache();\n * const pubsub = new CachingPubSub(new EventEmitterPubSub(), cache);\n *\n * // Subscribe with replay - receives cached events first, then live\n * await pubsub.subscribeWithReplay('my-topic', (event) => {\n * console.log(event);\n * });\n * ```\n */\nexport class CachingPubSub extends PubSub {\n private readonly keyPrefix: string;\n private readonly logger?: IMastraLogger;\n /** Maps original callbacks to their wrapped versions for proper unsubscribe */\n private callbackMap = new Map<EventCallback, EventCallback>();\n\n constructor(\n private readonly inner: PubSub,\n private readonly cache: MastraServerCache,\n options: CachingPubSubOptions = {},\n ) {\n super();\n this.keyPrefix = options.keyPrefix ?? 'pubsub:';\n this.logger = options.logger;\n }\n\n get supportsNativeBatching(): boolean {\n return this.inner.supportsNativeBatching;\n }\n\n /**\n * Log an error message using the configured logger or console.error.\n */\n private logError(message: string, error: unknown): void {\n if (this.logger) {\n this.logger.error(message, error);\n } else {\n console.error(message, error);\n }\n }\n\n /**\n * Stable key used to deduplicate an event across the cache-replay and\n * live-delivery paths.\n *\n * We cannot dedup on `event.id`: `CachingPubSub.publish` assigns the id and\n * caches the event with it, but inner PubSub implementations\n * (EventEmitterPubSub, UnixSocketPubSub, …) regenerate `id` inside their own\n * `publish`, so the cached copy and the live copy of the SAME publish carry\n * different ids. The sequential `index` is assigned here and is preserved by\n * every inner implementation, so it matches across both paths. Events without\n * an index are never cached (see `publish`), so they can't be replay/live\n * duplicated — falling back to `id` for them is safe.\n */\n private dedupKey(event: Event): string {\n return event.index !== undefined ? `i:${event.index}` : `id:${event.id}`;\n }\n\n /**\n * Get the cache key for a topic's event list\n */\n private getCacheKey(topic: string): string {\n return `${this.keyPrefix}${topic}`;\n }\n\n /**\n * Get the cache key for a topic's index counter\n */\n private getCounterKey(topic: string): string {\n return `${this.keyPrefix}${topic}:counter`;\n }\n\n /**\n * Publish an event to a topic.\n * The event is cached with a sequential index before being published to the inner PubSub.\n *\n * Uses atomic increment for index assignment to prevent race conditions\n * when multiple events are published concurrently.\n */\n async publish(\n topic: string,\n event: Omit<Event, 'id' | 'createdAt' | 'index'>,\n options?: { localOnly?: boolean },\n ): Promise<void> {\n const cacheKey = this.getCacheKey(topic);\n const counterKey = this.getCounterKey(topic);\n\n let index: number | undefined;\n let indexFailed = false;\n try {\n // Atomically get next index (increment returns value after incrementing, so subtract 1 for 0-based index)\n index = (await this.cache.increment(counterKey)) - 1;\n } catch (error) {\n this.logError(`[CachingPubSub] Failed to increment counter for ${topic}`, error);\n indexFailed = true;\n }\n\n // On counter failure leave `index` undefined rather than defaulting to 0:\n // downstream consumers that key off `index` (e.g. replay-from-offset)\n // would otherwise see colliding indices across failed publishes.\n const fullEvent: Event = {\n ...event,\n id: crypto.randomUUID(),\n createdAt: new Date(),\n ...(index !== undefined ? { index } : {}),\n };\n\n if (!indexFailed) {\n try {\n // Cache BEFORE live publish so late-joining observers never miss events\n await this.cache.listPush(cacheKey, fullEvent);\n } catch (error) {\n this.logError(`[CachingPubSub] Failed to cache event for ${topic}`, error);\n }\n }\n\n // Always publish to inner PubSub — cache failure must not block live delivery\n await this.inner.publish(topic, fullEvent, options);\n }\n\n /**\n * Subscribe to live events on a topic (no replay).\n */\n async subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void> {\n await this.inner.subscribe(topic, cb, options);\n }\n\n /**\n * Subscribe to a topic with automatic replay of cached events.\n * Delegates to {@link subscribeFromOffset} with offset 0.\n */\n async subscribeWithReplay(topic: string, cb: EventCallback): Promise<void> {\n return this.subscribeFromOffset(topic, 0, cb);\n }\n\n /**\n * Subscribe to a topic with replay starting from a specific index.\n * More efficient than full replay when the client knows their last position.\n *\n * Order of operations:\n * 1. Subscribe to live events FIRST — buffer deliveries during bootstrap\n * 2. Fetch and deliver cached history in order\n * 3. Drain the buffer, skipping events already delivered via history\n * 4. Switch to passthrough with an index watermark for steady-state dedup\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 async subscribeFromOffset(topic: string, offset: number, cb: EventCallback): Promise<void> {\n // --- Phase 1: subscribe live, buffer everything during bootstrap ---\n let bootstrapping = true;\n const buffer: Array<{\n event: Event;\n ack?: Parameters<EventCallback>[1];\n nack?: Parameters<EventCallback>[2];\n }> = [];\n let lastDelivered = -1;\n\n const wrappedCb: EventCallback = (event, ack, nack) => {\n // Drop events strictly before the requested offset on the live path.\n if (typeof event.index === 'number' && event.index < offset) {\n return;\n }\n\n if (bootstrapping) {\n buffer.push({ event, ack, nack });\n return;\n }\n\n // Steady-state: skip events we already delivered via history or buffer drain.\n // Allow nack-redelivered messages through — they carry the same index but\n // deliveryAttempt > 1, and the consumer must see them to retry processing.\n const isRetry = typeof event.deliveryAttempt === 'number' && event.deliveryAttempt > 1;\n if (typeof event.index === 'number' && event.index <= lastDelivered && !isRetry) {\n return;\n }\n\n if (typeof event.index === 'number' && event.index > lastDelivered) {\n lastDelivered = event.index;\n }\n cb(event, ack, nack);\n };\n\n this.callbackMap.set(cb, wrappedCb);\n await this.inner.subscribe(topic, wrappedCb);\n\n try {\n // --- Phase 2: fetch and deliver cached history ---\n const seen = new Set<string>();\n const history = await this.getHistory(topic, offset);\n for (const event of history) {\n const key = this.dedupKey(event);\n seen.add(key);\n if (typeof event.index === 'number') {\n lastDelivered = event.index;\n }\n cb(event);\n }\n\n // --- Phase 3: drain buffer, suppressing duplicates history already covered ---\n for (const { event, ack, nack } of buffer) {\n const key = this.dedupKey(event);\n if (seen.has(key)) {\n continue;\n }\n seen.add(key);\n if (typeof event.index === 'number') {\n lastDelivered = event.index;\n }\n cb(event, ack, nack);\n }\n\n // --- Phase 4: flip to passthrough ---\n bootstrapping = false;\n buffer.length = 0;\n } catch (error) {\n // Rollback: unsubscribe wrappedCb so it doesn't strand in bootstrap mode\n this.callbackMap.delete(cb);\n await this.inner.unsubscribe(topic, wrappedCb).catch(() => {});\n throw error;\n }\n }\n\n /**\n * Unsubscribe from a topic.\n */\n async unsubscribe(topic: string, cb: EventCallback): Promise<void> {\n const wrappedCb = this.callbackMap.get(cb) ?? cb;\n this.callbackMap.delete(cb);\n await this.inner.unsubscribe(topic, wrappedCb);\n }\n\n /**\n * Get historical events for a topic from cache.\n */\n async getHistory(topic: string, offset: number = 0): Promise<Event[]> {\n const cacheKey = this.getCacheKey(topic);\n const events = await this.cache.listFromTo(cacheKey, offset);\n return events as Event[];\n }\n\n /**\n * Flush any pending operations on the inner PubSub.\n */\n async flush(): Promise<void> {\n await this.inner.flush();\n }\n\n /**\n * Expose the inner's {@link LeaseProvider} when it has one, otherwise\n * `undefined`. Leasing is a capability of the underlying backend\n * (e.g. Redis), not of the caching decorator itself — so rather than\n * unconditionally declaring lease methods (which would make\n * {@link isLeaseProvider} report `true` even when the inner can't\n * coordinate a lock), we surface the inner's capability directly. The\n * signals runtime unwraps this so wrapping with caching preserves real\n * distributed lease semantics without faking them.\n */\n getLeaseProvider(): LeaseProvider | undefined {\n return isLeaseProvider(this.inner) ? this.inner : undefined;\n }\n\n /**\n * Clear cached events for a specific topic.\n * Call this when a stream completes to free memory.\n * Also clears the index counter.\n */\n async clearTopic(topic: string): Promise<void> {\n const cacheKey = this.getCacheKey(topic);\n const counterKey = this.getCounterKey(topic);\n await Promise.all([this.cache.delete(cacheKey), this.cache.delete(counterKey)]);\n }\n\n /**\n * Get the inner PubSub instance.\n * Useful for accessing implementation-specific methods like close().\n */\n getInner(): PubSub {\n return this.inner;\n }\n}\n\n/**\n * Factory function to wrap a PubSub with caching capabilities.\n *\n * @example\n * ```typescript\n * import { withCaching, EventEmitterPubSub } from '@mastra/core/events';\n * import { InMemoryServerCache } from '@mastra/core/cache';\n *\n * const cache = new InMemoryServerCache();\n * const pubsub = withCaching(new EventEmitterPubSub(), cache);\n * ```\n */\nexport function withCaching(pubsub: PubSub, cache: MastraServerCache, options?: CachingPubSubOptions): CachingPubSub {\n return new CachingPubSub(pubsub, cache, options);\n}\n"]}