@mastra/google-cloud-pubsub 1.1.0 → 1.1.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @mastra/inngest
2
2
 
3
+ ## 1.1.1-alpha.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Honor the `localOnly` publish option so in-process subscribers can receive events without round-tripping through the broker. ([#17836](https://github.com/mastra-ai/mastra/pull/17836))
8
+
9
+ This matches the contract already implemented by `UnixSocketPubSub` in `@mastra/core`: when `Mastra` tags an internal workflow event as `localOnly`, the payload is delivered by reference to local subscribers and the broker is skipped entirely. Live runtime values like `MastraModelOutput` instances now keep their prototypes when the evented agent loop runs against a Redis Streams or Google Cloud Pub/Sub broker, fixing `output.consumeStream is not a function` style failures.
10
+
11
+ - Updated dependencies [[`5c4e9a4`](https://github.com/mastra-ai/mastra/commit/5c4e9a4cfb2216bb3ea7f8988ad3727f3b92bb3a), [`25961e3`](https://github.com/mastra-ai/mastra/commit/25961e3260ff3b1464637af8fcdb36210551c39f), [`7b29f33`](https://github.com/mastra-ai/mastra/commit/7b29f332a357a83e555f29e718e5f2fab9979943), [`24912b1`](https://github.com/mastra-ai/mastra/commit/24912b1f855d29ec36af4ef4bde1f7417e20cdf5), [`7686216`](https://github.com/mastra-ai/mastra/commit/7686216f37e74568feddec17cef3c3d24e10e60a), [`975c59a`](https://github.com/mastra-ai/mastra/commit/975c59ae363ee275fc55062392e1ffd2cbccbd53), [`d95f394`](https://github.com/mastra-ai/mastra/commit/d95f394fd24c8411886930d727679c4d5252aa26), [`f3f0c9d`](https://github.com/mastra-ai/mastra/commit/f3f0c9d7c878db5a13177871ce3523a14f14b311)]:
12
+ - @mastra/core@1.46.0-alpha.4
13
+
14
+ ## 1.1.1-alpha.0
15
+
16
+ ### Patch Changes
17
+
18
+ - Fixed a startup race where concurrent subscribers to the same ungrouped topic could fail to attach. When a producer's `agent.stream()` and a consumer's `agent.observe()` subscribe to a fresh run topic within Google Cloud Pub/Sub's subscription-creation window, both raced to create the same subscription. The loser received an `ALREADY_EXISTS` error and, for ungrouped topics, fell through and threw `Failed to subscribe to topic`, killing the observe attach. Concurrent `init()` calls are now coalesced into a single create attempt, and an `ALREADY_EXISTS` result attaches to the existing subscription regardless of whether a group is set. ([#18252](https://github.com/mastra-ai/mastra/pull/18252))
19
+
20
+ - Updated dependencies [[`5bd72d2`](https://github.com/mastra-ai/mastra/commit/5bd72d255f45b5ea8ab342643bd463814a980a24), [`1cc9ee1`](https://github.com/mastra-ai/mastra/commit/1cc9ee1ba51db53020a735626d33017a60b4b5b3), [`417baae`](https://github.com/mastra-ai/mastra/commit/417baae40b995db5819c845036947f0c27dc1c00), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`30ebaf0`](https://github.com/mastra-ai/mastra/commit/30ebaf07bed5f4d30f2f257836c15d1bf7e40aae), [`5704634`](https://github.com/mastra-ai/mastra/commit/5704634b22133167dea337a942a34f57aaa3fa14), [`417baae`](https://github.com/mastra-ai/mastra/commit/417baae40b995db5819c845036947f0c27dc1c00), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`c0eda2b`](https://github.com/mastra-ai/mastra/commit/c0eda2bcd91a228427314b12c91d8b147f3a739f), [`c0eda2b`](https://github.com/mastra-ai/mastra/commit/c0eda2bcd91a228427314b12c91d8b147f3a739f), [`b13925b`](https://github.com/mastra-ai/mastra/commit/b13925bfa91aa8700f56fa54a9ce707ee7e4ba62), [`bf94ec6`](https://github.com/mastra-ai/mastra/commit/bf94ec68192d9f16e46ef7e5ac36370aeeddf35d), [`a29f371`](https://github.com/mastra-ai/mastra/commit/a29f371aef629ac8562661524a497127e93b5131), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`073f910`](https://github.com/mastra-ai/mastra/commit/073f910481e7d94b95ba3830f96531774ae95d33), [`ebbe1d3`](https://github.com/mastra-ai/mastra/commit/ebbe1d31a965a3adb0e728758f326b8122b4b55f), [`1f97ce5`](https://github.com/mastra-ai/mastra/commit/1f97ce5695463bebb4eaacf501da6fb403e20885), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`64f58c0`](https://github.com/mastra-ai/mastra/commit/64f58c04e78b40137497d47f781e897e416f22a5), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`ebbe1d3`](https://github.com/mastra-ai/mastra/commit/ebbe1d31a965a3adb0e728758f326b8122b4b55f), [`417baae`](https://github.com/mastra-ai/mastra/commit/417baae40b995db5819c845036947f0c27dc1c00), [`8e25a78`](https://github.com/mastra-ai/mastra/commit/8e25a78e0597575f0b0729bae8c5e190c84869b5), [`417baae`](https://github.com/mastra-ai/mastra/commit/417baae40b995db5819c845036947f0c27dc1c00), [`a5b22d3`](https://github.com/mastra-ai/mastra/commit/a5b22d314d62a68d801886a8d3d0eb6c089473db), [`417baae`](https://github.com/mastra-ai/mastra/commit/417baae40b995db5819c845036947f0c27dc1c00), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858)]:
21
+ - @mastra/core@1.46.0-alpha.0
22
+
3
23
  ## 1.1.0
4
24
 
5
25
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -10,6 +10,14 @@ var GoogleCloudPubSub = class extends events.PubSub {
10
10
  ackBuffer = {};
11
11
  activeSubscriptions = {};
12
12
  activeCbs = {};
13
+ // `localOnly` publishes never touch Google Cloud — they are delivered to
14
+ // same-process subscribers only. Tracks live callbacks per (normalized) topic
15
+ // so we can fan out without going through PubSub.
16
+ localCallbacks = /* @__PURE__ */ new Map();
17
+ // Coalesces concurrent init() calls for the same subscription so racing
18
+ // subscribers (e.g. a producer stream and a consumer observe on the same
19
+ // run topic) share a single createTopic/createSubscription attempt.
20
+ inFlightInit = {};
13
21
  // Tracks the actual anonymous message listener registered on each subscription,
14
22
  // so we can remove it cleanly on the final unsubscribe.
15
23
  messageListeners = {};
@@ -36,30 +44,40 @@ var GoogleCloudPubSub = class extends events.PubSub {
36
44
  }
37
45
  }
38
46
  async init(topicName, group) {
39
- try {
40
- await this.pubsub.createTopic(topicName);
41
- } catch {
47
+ const subscriptionKey = group ? `${topicName}:${group}` : topicName;
48
+ if (this.inFlightInit[subscriptionKey]) {
49
+ return this.inFlightInit[subscriptionKey];
42
50
  }
43
51
  const subscriptionName = this.getSubscriptionName(topicName, group);
44
- const subscriptionKey = group ? `${topicName}:${group}` : topicName;
45
- try {
46
- const [sub] = await this.pubsub.topic(topicName).createSubscription(subscriptionName, {
47
- enableMessageOrdering: true,
48
- enableExactlyOnceDelivery: topicName === "workflows" || !!group
49
- });
50
- this.activeSubscriptions[subscriptionKey] = sub;
51
- return sub;
52
- } catch {
53
- if (group) {
54
- try {
55
- const sub = this.pubsub.subscription(subscriptionName);
56
- this.activeSubscriptions[subscriptionKey] = sub;
57
- return sub;
58
- } catch {
52
+ const initPromise = (async () => {
53
+ try {
54
+ await this.pubsub.createTopic(topicName);
55
+ } catch {
56
+ }
57
+ try {
58
+ const [sub] = await this.pubsub.topic(topicName).createSubscription(subscriptionName, {
59
+ enableMessageOrdering: true,
60
+ enableExactlyOnceDelivery: topicName === "workflows" || !!group
61
+ });
62
+ this.activeSubscriptions[subscriptionKey] = sub;
63
+ return sub;
64
+ } catch (error) {
65
+ const alreadyExists = error?.code === 6;
66
+ if (alreadyExists || group) {
67
+ try {
68
+ const sub = this.pubsub.subscription(subscriptionName);
69
+ this.activeSubscriptions[subscriptionKey] = sub;
70
+ return sub;
71
+ } catch {
72
+ }
59
73
  }
60
74
  }
61
- }
62
- return void 0;
75
+ return void 0;
76
+ })().finally(() => {
77
+ delete this.inFlightInit[subscriptionKey];
78
+ });
79
+ this.inFlightInit[subscriptionKey] = initPromise;
80
+ return initPromise;
63
81
  }
64
82
  async destroy(topicName) {
65
83
  const subName = this.getSubscriptionName(topicName);
@@ -69,7 +87,7 @@ var GoogleCloudPubSub = class extends events.PubSub {
69
87
  await this.pubsub.subscription(subName).delete();
70
88
  await this.pubsub.topic(topicName).delete();
71
89
  }
72
- async publish(topicName, event) {
90
+ async publish(topicName, event, options) {
73
91
  if (topicName.startsWith("workflow.events.")) {
74
92
  const parts = topicName.split(".");
75
93
  if (parts[parts.length - 2] === "v2") {
@@ -78,6 +96,10 @@ var GoogleCloudPubSub = class extends events.PubSub {
78
96
  topicName = "workflow.events.v1";
79
97
  }
80
98
  }
99
+ if (options?.localOnly) {
100
+ await this.deliverLocal(topicName, event);
101
+ return;
102
+ }
81
103
  let topic = this.pubsub.topic(topicName);
82
104
  try {
83
105
  await topic.publishMessage({
@@ -104,6 +126,12 @@ var GoogleCloudPubSub = class extends events.PubSub {
104
126
  }
105
127
  const group = options?.group;
106
128
  const subscriptionKey = group ? `${topic}:${group}` : topic;
129
+ let localSet = this.localCallbacks.get(topic);
130
+ if (!localSet) {
131
+ localSet = /* @__PURE__ */ new Set();
132
+ this.localCallbacks.set(topic, localSet);
133
+ }
134
+ localSet.add(cb);
107
135
  const subscription = this.activeSubscriptions[subscriptionKey] ?? await this.init(topic, group);
108
136
  if (!subscription) {
109
137
  throw new Error(`Failed to subscribe to topic: ${topic}`);
@@ -152,6 +180,18 @@ var GoogleCloudPubSub = class extends events.PubSub {
152
180
  });
153
181
  }
154
182
  async unsubscribe(topic, cb) {
183
+ if (topic.startsWith("workflow.events.")) {
184
+ const parts = topic.split(".");
185
+ if (parts[parts.length - 2] === "v2") {
186
+ topic = "workflow.events.v2";
187
+ } else {
188
+ topic = "workflow.events.v1";
189
+ }
190
+ }
191
+ const localSet = this.localCallbacks.get(topic);
192
+ if (localSet?.delete(cb) && localSet.size === 0) {
193
+ this.localCallbacks.delete(topic);
194
+ }
155
195
  const keysToCheck = [topic];
156
196
  for (const key of Object.keys(this.activeCbs)) {
157
197
  if (key.startsWith(`${topic}:`) && !keysToCheck.includes(key)) {
@@ -180,6 +220,34 @@ var GoogleCloudPubSub = class extends events.PubSub {
180
220
  async flush() {
181
221
  await Promise.all(Object.values(this.ackBuffer));
182
222
  }
223
+ /**
224
+ * Fan a `localOnly` event out to in-process subscribers without going through
225
+ * Google Cloud. The payload is delivered by reference, so live class instances
226
+ * and functions on the event survive intact.
227
+ */
228
+ async deliverLocal(topicName, event) {
229
+ const callbacks = this.localCallbacks.get(topicName);
230
+ if (!callbacks || callbacks.size === 0) return;
231
+ const localEvent = {
232
+ ...event,
233
+ id: crypto.randomUUID(),
234
+ createdAt: /* @__PURE__ */ new Date(),
235
+ deliveryAttempt: 1
236
+ };
237
+ for (const cb of [...callbacks]) {
238
+ try {
239
+ cb(
240
+ localEvent,
241
+ async () => {
242
+ },
243
+ async () => {
244
+ }
245
+ );
246
+ } catch (error) {
247
+ console.error("Error delivering local event", error);
248
+ }
249
+ }
250
+ }
183
251
  };
184
252
 
185
253
  exports.GoogleCloudPubSub = GoogleCloudPubSub;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["PubSub","PubSubClient","activeCbs","cb"],"mappings":"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","file":"index.cjs","sourcesContent":["import { PubSub as PubSubClient } from '@google-cloud/pubsub';\nimport type { ClientConfig, Message, Subscription } from '@google-cloud/pubsub';\nimport { PubSub } from '@mastra/core/events';\nimport type { Event, EventCallback, SubscribeOptions } from '@mastra/core/events';\n\nexport class GoogleCloudPubSub extends PubSub {\n private instanceId: string;\n private pubsub: PubSubClient;\n private ackBuffer: Record<string, Promise<any>> = {};\n private activeSubscriptions: Record<string, Subscription> = {};\n private activeCbs: Record<string, Set<EventCallback>> = {};\n // Tracks the actual anonymous message listener registered on each subscription,\n // so we can remove it cleanly on the final unsubscribe.\n private messageListeners: Record<string, (message: Message) => void> = {};\n\n constructor(config: ClientConfig) {\n super();\n this.pubsub = new PubSubClient(config);\n this.instanceId = crypto.randomUUID();\n }\n\n getSubscriptionName(topic: string, group?: string) {\n if (group) {\n return `${topic}-${group}`;\n }\n return `${topic}-${this.instanceId}`;\n }\n\n async ackMessage(topic: string, message: Message) {\n try {\n const ackResponse = Promise.race([message.ackWithResponse(), new Promise(resolve => setTimeout(resolve, 5000))]);\n this.ackBuffer[topic + '-' + message.id] = ackResponse.catch(() => {});\n await ackResponse;\n delete this.ackBuffer[topic + '-' + message.id];\n } catch (e) {\n console.error('Error acking message', e);\n }\n }\n\n async init(topicName: string, group?: string) {\n try {\n await this.pubsub.createTopic(topicName);\n } catch {\n // no-op\n }\n const subscriptionName = this.getSubscriptionName(topicName, group);\n const subscriptionKey = group ? `${topicName}:${group}` : topicName;\n try {\n const [sub] = await this.pubsub.topic(topicName).createSubscription(subscriptionName, {\n enableMessageOrdering: true,\n enableExactlyOnceDelivery: topicName === 'workflows' || !!group,\n });\n this.activeSubscriptions[subscriptionKey] = sub;\n return sub;\n } catch {\n // Subscription may already exist (e.g. shared group subscription created by another process).\n // Get the existing subscription instead.\n if (group) {\n try {\n const sub = this.pubsub.subscription(subscriptionName);\n this.activeSubscriptions[subscriptionKey] = sub;\n return sub;\n } catch {\n // no-op\n }\n }\n }\n\n return undefined;\n }\n\n async destroy(topicName: string) {\n const subName = this.getSubscriptionName(topicName);\n delete this.activeSubscriptions[topicName];\n this.pubsub.subscription(subName).removeAllListeners();\n await this.pubsub.subscription(subName).close();\n await this.pubsub.subscription(subName).delete();\n await this.pubsub.topic(topicName).delete();\n }\n\n async publish(topicName: string, event: Omit<Event, 'id' | 'createdAt'>): Promise<void> {\n if (topicName.startsWith('workflow.events.')) {\n const parts = topicName.split('.');\n if (parts[parts.length - 2] === 'v2') {\n topicName = 'workflow.events.v2';\n } else {\n topicName = 'workflow.events.v1';\n }\n }\n\n let topic = this.pubsub.topic(topicName);\n\n try {\n await topic.publishMessage({\n data: Buffer.from(JSON.stringify(event)),\n orderingKey: 'workflows',\n });\n } catch (e: any) {\n if (e.code === 5) {\n await this.pubsub.createTopic(topicName);\n await this.publish(topicName, event);\n } else {\n throw e;\n }\n }\n }\n\n async subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void> {\n if (topic.startsWith('workflow.events.')) {\n const parts = topic.split('.');\n if (parts[parts.length - 2] === 'v2') {\n topic = 'workflow.events.v2';\n } else {\n topic = 'workflow.events.v1';\n }\n }\n\n const group = options?.group;\n // Use a composite key when group is set so grouped and non-grouped subscriptions\n // on the same topic don't collide\n const subscriptionKey = group ? `${topic}:${group}` : topic;\n\n // Update tracked callbacks\n const subscription = this.activeSubscriptions[subscriptionKey] ?? (await this.init(topic, group));\n if (!subscription) {\n throw new Error(`Failed to subscribe to topic: ${topic}`);\n }\n\n this.activeSubscriptions[subscriptionKey] = subscription;\n\n const activeCbs = this.activeCbs[subscriptionKey] ?? new Set();\n activeCbs.add(cb);\n this.activeCbs[subscriptionKey] = activeCbs;\n\n if (subscription.isOpen) {\n return;\n }\n\n const messageListener = async (message: Message) => {\n const event = JSON.parse(message.data.toString()) as Event;\n event.id = message.id;\n event.createdAt = message.publishTime;\n event.deliveryAttempt = message.deliveryAttempt ?? 1;\n\n try {\n const activeCbs = this.activeCbs[subscriptionKey] ?? [];\n for (const cb of activeCbs) {\n cb(\n event,\n async () => {\n try {\n await this.ackMessage(subscriptionKey, message);\n } catch (e) {\n console.error('Error acking message', e);\n }\n },\n async () => {\n try {\n message.nack();\n } catch (e) {\n console.error('Error nacking message', e);\n }\n },\n );\n }\n } catch (error) {\n console.error('Error processing event', error);\n }\n };\n\n this.messageListeners[subscriptionKey] = messageListener;\n subscription.on('message', messageListener);\n\n subscription.on('error', async error => {\n console.error('subscription error', error);\n });\n }\n\n async unsubscribe(topic: string, cb: EventCallback): Promise<void> {\n // Check both grouped and non-grouped subscription keys for this callback\n const keysToCheck = [topic];\n for (const key of Object.keys(this.activeCbs)) {\n if (key.startsWith(`${topic}:`) && !keysToCheck.includes(key)) {\n keysToCheck.push(key);\n }\n }\n\n for (const subscriptionKey of keysToCheck) {\n const activeCbs = this.activeCbs[subscriptionKey];\n if (activeCbs?.has(cb)) {\n activeCbs.delete(cb);\n\n if (activeCbs.size === 0) {\n const subscription = this.activeSubscriptions[subscriptionKey];\n const listener = this.messageListeners[subscriptionKey];\n if (subscription) {\n if (listener) subscription.removeListener('message', listener);\n await subscription.close();\n }\n delete this.activeSubscriptions[subscriptionKey];\n delete this.activeCbs[subscriptionKey];\n delete this.messageListeners[subscriptionKey];\n }\n return;\n }\n }\n }\n\n async flush(): Promise<void> {\n await Promise.all(Object.values(this.ackBuffer));\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["PubSub","PubSubClient","activeCbs","cb"],"mappings":"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","file":"index.cjs","sourcesContent":["import { PubSub as PubSubClient } from '@google-cloud/pubsub';\nimport type { ClientConfig, Message, Subscription } from '@google-cloud/pubsub';\nimport { PubSub } from '@mastra/core/events';\nimport type { Event, EventCallback, SubscribeOptions } from '@mastra/core/events';\n\nexport class GoogleCloudPubSub extends PubSub {\n private instanceId: string;\n private pubsub: PubSubClient;\n private ackBuffer: Record<string, Promise<any>> = {};\n private activeSubscriptions: Record<string, Subscription> = {};\n private activeCbs: Record<string, Set<EventCallback>> = {};\n // `localOnly` publishes never touch Google Cloud — they are delivered to\n // same-process subscribers only. Tracks live callbacks per (normalized) topic\n // so we can fan out without going through PubSub.\n private localCallbacks: Map<string, Set<EventCallback>> = new Map();\n // Coalesces concurrent init() calls for the same subscription so racing\n // subscribers (e.g. a producer stream and a consumer observe on the same\n // run topic) share a single createTopic/createSubscription attempt.\n private inFlightInit: Record<string, Promise<Subscription | undefined>> = {};\n // Tracks the actual anonymous message listener registered on each subscription,\n // so we can remove it cleanly on the final unsubscribe.\n private messageListeners: Record<string, (message: Message) => void> = {};\n\n constructor(config: ClientConfig) {\n super();\n this.pubsub = new PubSubClient(config);\n this.instanceId = crypto.randomUUID();\n }\n\n getSubscriptionName(topic: string, group?: string) {\n if (group) {\n return `${topic}-${group}`;\n }\n return `${topic}-${this.instanceId}`;\n }\n\n async ackMessage(topic: string, message: Message) {\n try {\n const ackResponse = Promise.race([message.ackWithResponse(), new Promise(resolve => setTimeout(resolve, 5000))]);\n this.ackBuffer[topic + '-' + message.id] = ackResponse.catch(() => {});\n await ackResponse;\n delete this.ackBuffer[topic + '-' + message.id];\n } catch (e) {\n console.error('Error acking message', e);\n }\n }\n\n async init(topicName: string, group?: string): Promise<Subscription | undefined> {\n const subscriptionKey = group ? `${topicName}:${group}` : topicName;\n\n // Reuse an in-flight init so concurrent subscribers don't race to create the\n // same subscription. The promise is registered synchronously below (before any\n // await), so a second caller arriving during the create window reuses it.\n if (this.inFlightInit[subscriptionKey]) {\n return this.inFlightInit[subscriptionKey];\n }\n\n const subscriptionName = this.getSubscriptionName(topicName, group);\n const initPromise = (async (): Promise<Subscription | undefined> => {\n try {\n await this.pubsub.createTopic(topicName);\n } catch {\n // no-op\n }\n try {\n const [sub] = await this.pubsub.topic(topicName).createSubscription(subscriptionName, {\n enableMessageOrdering: true,\n enableExactlyOnceDelivery: topicName === 'workflows' || !!group,\n });\n this.activeSubscriptions[subscriptionKey] = sub;\n return sub;\n } catch (error) {\n // The subscription may already exist: created concurrently by a racing\n // subscriber (ALREADY_EXISTS / gRPC code 6), shared by another process via\n // a group, or surviving a previous process. In all of these cases attach to\n // the existing subscription instead of failing. Ungrouped subscriptions hit\n // this on the concurrent-create race, so we must not gate it on `group`.\n const alreadyExists = (error as { code?: number } | undefined)?.code === 6;\n if (alreadyExists || group) {\n try {\n const sub = this.pubsub.subscription(subscriptionName);\n this.activeSubscriptions[subscriptionKey] = sub;\n return sub;\n } catch {\n // no-op\n }\n }\n }\n return undefined;\n })().finally(() => {\n delete this.inFlightInit[subscriptionKey];\n });\n\n this.inFlightInit[subscriptionKey] = initPromise;\n return initPromise;\n }\n\n async destroy(topicName: string) {\n const subName = this.getSubscriptionName(topicName);\n delete this.activeSubscriptions[topicName];\n this.pubsub.subscription(subName).removeAllListeners();\n await this.pubsub.subscription(subName).close();\n await this.pubsub.subscription(subName).delete();\n await this.pubsub.topic(topicName).delete();\n }\n\n async publish(\n topicName: string,\n event: Omit<Event, 'id' | 'createdAt'>,\n options?: { localOnly?: boolean },\n ): Promise<void> {\n if (topicName.startsWith('workflow.events.')) {\n const parts = topicName.split('.');\n if (parts[parts.length - 2] === 'v2') {\n topicName = 'workflow.events.v2';\n } else {\n topicName = 'workflow.events.v1';\n }\n }\n\n // `localOnly` events stay entirely within the publishing process. They are\n // never serialized through Google Cloud, so live methods on payload values\n // (e.g. `MastraModelOutput.getFullOutput`) survive intact. The agent's\n // execution-workflow relies on this: the run result is delivered via\n // `workflows-finish` and includes the `MastraModelOutput` instance —\n // round-tripping it through Pub/Sub would strip its methods.\n if (options?.localOnly) {\n await this.deliverLocal(topicName, event);\n return;\n }\n\n let topic = this.pubsub.topic(topicName);\n\n try {\n await topic.publishMessage({\n data: Buffer.from(JSON.stringify(event)),\n orderingKey: 'workflows',\n });\n } catch (e: any) {\n if (e.code === 5) {\n await this.pubsub.createTopic(topicName);\n await this.publish(topicName, event);\n } else {\n throw e;\n }\n }\n }\n\n async subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void> {\n if (topic.startsWith('workflow.events.')) {\n const parts = topic.split('.');\n if (parts[parts.length - 2] === 'v2') {\n topic = 'workflow.events.v2';\n } else {\n topic = 'workflow.events.v1';\n }\n }\n\n const group = options?.group;\n // Use a composite key when group is set so grouped and non-grouped subscriptions\n // on the same topic don't collide\n const subscriptionKey = group ? `${topic}:${group}` : topic;\n\n // Register callback for `localOnly` delivery. Local delivery bypasses Google\n // Cloud entirely so live class instances on the payload (e.g. Date, Map,\n // Error, MastraModelOutput) keep their prototypes.\n let localSet = this.localCallbacks.get(topic);\n if (!localSet) {\n localSet = new Set();\n this.localCallbacks.set(topic, localSet);\n }\n localSet.add(cb);\n\n // Update tracked callbacks\n const subscription = this.activeSubscriptions[subscriptionKey] ?? (await this.init(topic, group));\n if (!subscription) {\n throw new Error(`Failed to subscribe to topic: ${topic}`);\n }\n\n this.activeSubscriptions[subscriptionKey] = subscription;\n\n const activeCbs = this.activeCbs[subscriptionKey] ?? new Set();\n activeCbs.add(cb);\n this.activeCbs[subscriptionKey] = activeCbs;\n\n if (subscription.isOpen) {\n return;\n }\n\n const messageListener = async (message: Message) => {\n const event = JSON.parse(message.data.toString()) as Event;\n event.id = message.id;\n event.createdAt = message.publishTime;\n event.deliveryAttempt = message.deliveryAttempt ?? 1;\n\n try {\n const activeCbs = this.activeCbs[subscriptionKey] ?? [];\n for (const cb of activeCbs) {\n cb(\n event,\n async () => {\n try {\n await this.ackMessage(subscriptionKey, message);\n } catch (e) {\n console.error('Error acking message', e);\n }\n },\n async () => {\n try {\n message.nack();\n } catch (e) {\n console.error('Error nacking message', e);\n }\n },\n );\n }\n } catch (error) {\n console.error('Error processing event', error);\n }\n };\n\n this.messageListeners[subscriptionKey] = messageListener;\n subscription.on('message', messageListener);\n\n subscription.on('error', async error => {\n console.error('subscription error', error);\n });\n }\n\n async unsubscribe(topic: string, cb: EventCallback): Promise<void> {\n if (topic.startsWith('workflow.events.')) {\n const parts = topic.split('.');\n if (parts[parts.length - 2] === 'v2') {\n topic = 'workflow.events.v2';\n } else {\n topic = 'workflow.events.v1';\n }\n }\n\n // Drop from the local-delivery set; if nobody is left, tear down the bucket.\n const localSet = this.localCallbacks.get(topic);\n if (localSet?.delete(cb) && localSet.size === 0) {\n this.localCallbacks.delete(topic);\n }\n\n // Check both grouped and non-grouped subscription keys for this callback\n const keysToCheck = [topic];\n for (const key of Object.keys(this.activeCbs)) {\n if (key.startsWith(`${topic}:`) && !keysToCheck.includes(key)) {\n keysToCheck.push(key);\n }\n }\n\n for (const subscriptionKey of keysToCheck) {\n const activeCbs = this.activeCbs[subscriptionKey];\n if (activeCbs?.has(cb)) {\n activeCbs.delete(cb);\n\n if (activeCbs.size === 0) {\n const subscription = this.activeSubscriptions[subscriptionKey];\n const listener = this.messageListeners[subscriptionKey];\n if (subscription) {\n if (listener) subscription.removeListener('message', listener);\n await subscription.close();\n }\n delete this.activeSubscriptions[subscriptionKey];\n delete this.activeCbs[subscriptionKey];\n delete this.messageListeners[subscriptionKey];\n }\n return;\n }\n }\n }\n\n async flush(): Promise<void> {\n await Promise.all(Object.values(this.ackBuffer));\n }\n\n /**\n * Fan a `localOnly` event out to in-process subscribers without going through\n * Google Cloud. The payload is delivered by reference, so live class instances\n * and functions on the event survive intact.\n */\n private async deliverLocal(topicName: string, event: Omit<Event, 'id' | 'createdAt'>): Promise<void> {\n const callbacks = this.localCallbacks.get(topicName);\n if (!callbacks || callbacks.size === 0) return;\n\n const localEvent: Event = {\n ...event,\n id: crypto.randomUUID(),\n createdAt: new Date(),\n deliveryAttempt: 1,\n };\n\n for (const cb of [...callbacks]) {\n try {\n cb(\n localEvent,\n async () => {},\n async () => {},\n );\n } catch (error) {\n console.error('Error delivering local event', error);\n }\n }\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -7,15 +7,25 @@ export declare class GoogleCloudPubSub extends PubSub {
7
7
  private ackBuffer;
8
8
  private activeSubscriptions;
9
9
  private activeCbs;
10
+ private localCallbacks;
11
+ private inFlightInit;
10
12
  private messageListeners;
11
13
  constructor(config: ClientConfig);
12
14
  getSubscriptionName(topic: string, group?: string): string;
13
15
  ackMessage(topic: string, message: Message): Promise<void>;
14
16
  init(topicName: string, group?: string): Promise<Subscription | undefined>;
15
17
  destroy(topicName: string): Promise<void>;
16
- publish(topicName: string, event: Omit<Event, 'id' | 'createdAt'>): Promise<void>;
18
+ publish(topicName: string, event: Omit<Event, 'id' | 'createdAt'>, options?: {
19
+ localOnly?: boolean;
20
+ }): Promise<void>;
17
21
  subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void>;
18
22
  unsubscribe(topic: string, cb: EventCallback): Promise<void>;
19
23
  flush(): Promise<void>;
24
+ /**
25
+ * Fan a `localOnly` event out to in-process subscribers without going through
26
+ * Google Cloud. The payload is delivered by reference, so live class instances
27
+ * and functions on the event survive intact.
28
+ */
29
+ private deliverLocal;
20
30
  }
21
31
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAElF,qBAAa,iBAAkB,SAAQ,MAAM;IAC3C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,mBAAmB,CAAoC;IAC/D,OAAO,CAAC,SAAS,CAA0C;IAG3D,OAAO,CAAC,gBAAgB,CAAkD;gBAE9D,MAAM,EAAE,YAAY;IAMhC,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM;IAO3C,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAW1C,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM;IAgCtC,OAAO,CAAC,SAAS,EAAE,MAAM;IASzB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BjF,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuEtF,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IA8B5D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAElF,qBAAa,iBAAkB,SAAQ,MAAM;IAC3C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,mBAAmB,CAAoC;IAC/D,OAAO,CAAC,SAAS,CAA0C;IAI3D,OAAO,CAAC,cAAc,CAA8C;IAIpE,OAAO,CAAC,YAAY,CAAyD;IAG7E,OAAO,CAAC,gBAAgB,CAAkD;gBAE9D,MAAM,EAAE,YAAY;IAMhC,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM;IAO3C,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAW1C,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAkD1E,OAAO,CAAC,SAAS,EAAE,MAAM;IASzB,OAAO,CACX,SAAS,EAAE,MAAM,EACjB,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;IAsCV,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiFtF,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IA6C5D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;;;OAIG;YACW,YAAY;CAuB3B"}
package/dist/index.js CHANGED
@@ -8,6 +8,14 @@ var GoogleCloudPubSub = class extends PubSub {
8
8
  ackBuffer = {};
9
9
  activeSubscriptions = {};
10
10
  activeCbs = {};
11
+ // `localOnly` publishes never touch Google Cloud — they are delivered to
12
+ // same-process subscribers only. Tracks live callbacks per (normalized) topic
13
+ // so we can fan out without going through PubSub.
14
+ localCallbacks = /* @__PURE__ */ new Map();
15
+ // Coalesces concurrent init() calls for the same subscription so racing
16
+ // subscribers (e.g. a producer stream and a consumer observe on the same
17
+ // run topic) share a single createTopic/createSubscription attempt.
18
+ inFlightInit = {};
11
19
  // Tracks the actual anonymous message listener registered on each subscription,
12
20
  // so we can remove it cleanly on the final unsubscribe.
13
21
  messageListeners = {};
@@ -34,30 +42,40 @@ var GoogleCloudPubSub = class extends PubSub {
34
42
  }
35
43
  }
36
44
  async init(topicName, group) {
37
- try {
38
- await this.pubsub.createTopic(topicName);
39
- } catch {
45
+ const subscriptionKey = group ? `${topicName}:${group}` : topicName;
46
+ if (this.inFlightInit[subscriptionKey]) {
47
+ return this.inFlightInit[subscriptionKey];
40
48
  }
41
49
  const subscriptionName = this.getSubscriptionName(topicName, group);
42
- const subscriptionKey = group ? `${topicName}:${group}` : topicName;
43
- try {
44
- const [sub] = await this.pubsub.topic(topicName).createSubscription(subscriptionName, {
45
- enableMessageOrdering: true,
46
- enableExactlyOnceDelivery: topicName === "workflows" || !!group
47
- });
48
- this.activeSubscriptions[subscriptionKey] = sub;
49
- return sub;
50
- } catch {
51
- if (group) {
52
- try {
53
- const sub = this.pubsub.subscription(subscriptionName);
54
- this.activeSubscriptions[subscriptionKey] = sub;
55
- return sub;
56
- } catch {
50
+ const initPromise = (async () => {
51
+ try {
52
+ await this.pubsub.createTopic(topicName);
53
+ } catch {
54
+ }
55
+ try {
56
+ const [sub] = await this.pubsub.topic(topicName).createSubscription(subscriptionName, {
57
+ enableMessageOrdering: true,
58
+ enableExactlyOnceDelivery: topicName === "workflows" || !!group
59
+ });
60
+ this.activeSubscriptions[subscriptionKey] = sub;
61
+ return sub;
62
+ } catch (error) {
63
+ const alreadyExists = error?.code === 6;
64
+ if (alreadyExists || group) {
65
+ try {
66
+ const sub = this.pubsub.subscription(subscriptionName);
67
+ this.activeSubscriptions[subscriptionKey] = sub;
68
+ return sub;
69
+ } catch {
70
+ }
57
71
  }
58
72
  }
59
- }
60
- return void 0;
73
+ return void 0;
74
+ })().finally(() => {
75
+ delete this.inFlightInit[subscriptionKey];
76
+ });
77
+ this.inFlightInit[subscriptionKey] = initPromise;
78
+ return initPromise;
61
79
  }
62
80
  async destroy(topicName) {
63
81
  const subName = this.getSubscriptionName(topicName);
@@ -67,7 +85,7 @@ var GoogleCloudPubSub = class extends PubSub {
67
85
  await this.pubsub.subscription(subName).delete();
68
86
  await this.pubsub.topic(topicName).delete();
69
87
  }
70
- async publish(topicName, event) {
88
+ async publish(topicName, event, options) {
71
89
  if (topicName.startsWith("workflow.events.")) {
72
90
  const parts = topicName.split(".");
73
91
  if (parts[parts.length - 2] === "v2") {
@@ -76,6 +94,10 @@ var GoogleCloudPubSub = class extends PubSub {
76
94
  topicName = "workflow.events.v1";
77
95
  }
78
96
  }
97
+ if (options?.localOnly) {
98
+ await this.deliverLocal(topicName, event);
99
+ return;
100
+ }
79
101
  let topic = this.pubsub.topic(topicName);
80
102
  try {
81
103
  await topic.publishMessage({
@@ -102,6 +124,12 @@ var GoogleCloudPubSub = class extends PubSub {
102
124
  }
103
125
  const group = options?.group;
104
126
  const subscriptionKey = group ? `${topic}:${group}` : topic;
127
+ let localSet = this.localCallbacks.get(topic);
128
+ if (!localSet) {
129
+ localSet = /* @__PURE__ */ new Set();
130
+ this.localCallbacks.set(topic, localSet);
131
+ }
132
+ localSet.add(cb);
105
133
  const subscription = this.activeSubscriptions[subscriptionKey] ?? await this.init(topic, group);
106
134
  if (!subscription) {
107
135
  throw new Error(`Failed to subscribe to topic: ${topic}`);
@@ -150,6 +178,18 @@ var GoogleCloudPubSub = class extends PubSub {
150
178
  });
151
179
  }
152
180
  async unsubscribe(topic, cb) {
181
+ if (topic.startsWith("workflow.events.")) {
182
+ const parts = topic.split(".");
183
+ if (parts[parts.length - 2] === "v2") {
184
+ topic = "workflow.events.v2";
185
+ } else {
186
+ topic = "workflow.events.v1";
187
+ }
188
+ }
189
+ const localSet = this.localCallbacks.get(topic);
190
+ if (localSet?.delete(cb) && localSet.size === 0) {
191
+ this.localCallbacks.delete(topic);
192
+ }
153
193
  const keysToCheck = [topic];
154
194
  for (const key of Object.keys(this.activeCbs)) {
155
195
  if (key.startsWith(`${topic}:`) && !keysToCheck.includes(key)) {
@@ -178,6 +218,34 @@ var GoogleCloudPubSub = class extends PubSub {
178
218
  async flush() {
179
219
  await Promise.all(Object.values(this.ackBuffer));
180
220
  }
221
+ /**
222
+ * Fan a `localOnly` event out to in-process subscribers without going through
223
+ * Google Cloud. The payload is delivered by reference, so live class instances
224
+ * and functions on the event survive intact.
225
+ */
226
+ async deliverLocal(topicName, event) {
227
+ const callbacks = this.localCallbacks.get(topicName);
228
+ if (!callbacks || callbacks.size === 0) return;
229
+ const localEvent = {
230
+ ...event,
231
+ id: crypto.randomUUID(),
232
+ createdAt: /* @__PURE__ */ new Date(),
233
+ deliveryAttempt: 1
234
+ };
235
+ for (const cb of [...callbacks]) {
236
+ try {
237
+ cb(
238
+ localEvent,
239
+ async () => {
240
+ },
241
+ async () => {
242
+ }
243
+ );
244
+ } catch (error) {
245
+ console.error("Error delivering local event", error);
246
+ }
247
+ }
248
+ }
181
249
  };
182
250
 
183
251
  export { GoogleCloudPubSub };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["PubSubClient","activeCbs","cb"],"mappings":"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","file":"index.js","sourcesContent":["import { PubSub as PubSubClient } from '@google-cloud/pubsub';\nimport type { ClientConfig, Message, Subscription } from '@google-cloud/pubsub';\nimport { PubSub } from '@mastra/core/events';\nimport type { Event, EventCallback, SubscribeOptions } from '@mastra/core/events';\n\nexport class GoogleCloudPubSub extends PubSub {\n private instanceId: string;\n private pubsub: PubSubClient;\n private ackBuffer: Record<string, Promise<any>> = {};\n private activeSubscriptions: Record<string, Subscription> = {};\n private activeCbs: Record<string, Set<EventCallback>> = {};\n // Tracks the actual anonymous message listener registered on each subscription,\n // so we can remove it cleanly on the final unsubscribe.\n private messageListeners: Record<string, (message: Message) => void> = {};\n\n constructor(config: ClientConfig) {\n super();\n this.pubsub = new PubSubClient(config);\n this.instanceId = crypto.randomUUID();\n }\n\n getSubscriptionName(topic: string, group?: string) {\n if (group) {\n return `${topic}-${group}`;\n }\n return `${topic}-${this.instanceId}`;\n }\n\n async ackMessage(topic: string, message: Message) {\n try {\n const ackResponse = Promise.race([message.ackWithResponse(), new Promise(resolve => setTimeout(resolve, 5000))]);\n this.ackBuffer[topic + '-' + message.id] = ackResponse.catch(() => {});\n await ackResponse;\n delete this.ackBuffer[topic + '-' + message.id];\n } catch (e) {\n console.error('Error acking message', e);\n }\n }\n\n async init(topicName: string, group?: string) {\n try {\n await this.pubsub.createTopic(topicName);\n } catch {\n // no-op\n }\n const subscriptionName = this.getSubscriptionName(topicName, group);\n const subscriptionKey = group ? `${topicName}:${group}` : topicName;\n try {\n const [sub] = await this.pubsub.topic(topicName).createSubscription(subscriptionName, {\n enableMessageOrdering: true,\n enableExactlyOnceDelivery: topicName === 'workflows' || !!group,\n });\n this.activeSubscriptions[subscriptionKey] = sub;\n return sub;\n } catch {\n // Subscription may already exist (e.g. shared group subscription created by another process).\n // Get the existing subscription instead.\n if (group) {\n try {\n const sub = this.pubsub.subscription(subscriptionName);\n this.activeSubscriptions[subscriptionKey] = sub;\n return sub;\n } catch {\n // no-op\n }\n }\n }\n\n return undefined;\n }\n\n async destroy(topicName: string) {\n const subName = this.getSubscriptionName(topicName);\n delete this.activeSubscriptions[topicName];\n this.pubsub.subscription(subName).removeAllListeners();\n await this.pubsub.subscription(subName).close();\n await this.pubsub.subscription(subName).delete();\n await this.pubsub.topic(topicName).delete();\n }\n\n async publish(topicName: string, event: Omit<Event, 'id' | 'createdAt'>): Promise<void> {\n if (topicName.startsWith('workflow.events.')) {\n const parts = topicName.split('.');\n if (parts[parts.length - 2] === 'v2') {\n topicName = 'workflow.events.v2';\n } else {\n topicName = 'workflow.events.v1';\n }\n }\n\n let topic = this.pubsub.topic(topicName);\n\n try {\n await topic.publishMessage({\n data: Buffer.from(JSON.stringify(event)),\n orderingKey: 'workflows',\n });\n } catch (e: any) {\n if (e.code === 5) {\n await this.pubsub.createTopic(topicName);\n await this.publish(topicName, event);\n } else {\n throw e;\n }\n }\n }\n\n async subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void> {\n if (topic.startsWith('workflow.events.')) {\n const parts = topic.split('.');\n if (parts[parts.length - 2] === 'v2') {\n topic = 'workflow.events.v2';\n } else {\n topic = 'workflow.events.v1';\n }\n }\n\n const group = options?.group;\n // Use a composite key when group is set so grouped and non-grouped subscriptions\n // on the same topic don't collide\n const subscriptionKey = group ? `${topic}:${group}` : topic;\n\n // Update tracked callbacks\n const subscription = this.activeSubscriptions[subscriptionKey] ?? (await this.init(topic, group));\n if (!subscription) {\n throw new Error(`Failed to subscribe to topic: ${topic}`);\n }\n\n this.activeSubscriptions[subscriptionKey] = subscription;\n\n const activeCbs = this.activeCbs[subscriptionKey] ?? new Set();\n activeCbs.add(cb);\n this.activeCbs[subscriptionKey] = activeCbs;\n\n if (subscription.isOpen) {\n return;\n }\n\n const messageListener = async (message: Message) => {\n const event = JSON.parse(message.data.toString()) as Event;\n event.id = message.id;\n event.createdAt = message.publishTime;\n event.deliveryAttempt = message.deliveryAttempt ?? 1;\n\n try {\n const activeCbs = this.activeCbs[subscriptionKey] ?? [];\n for (const cb of activeCbs) {\n cb(\n event,\n async () => {\n try {\n await this.ackMessage(subscriptionKey, message);\n } catch (e) {\n console.error('Error acking message', e);\n }\n },\n async () => {\n try {\n message.nack();\n } catch (e) {\n console.error('Error nacking message', e);\n }\n },\n );\n }\n } catch (error) {\n console.error('Error processing event', error);\n }\n };\n\n this.messageListeners[subscriptionKey] = messageListener;\n subscription.on('message', messageListener);\n\n subscription.on('error', async error => {\n console.error('subscription error', error);\n });\n }\n\n async unsubscribe(topic: string, cb: EventCallback): Promise<void> {\n // Check both grouped and non-grouped subscription keys for this callback\n const keysToCheck = [topic];\n for (const key of Object.keys(this.activeCbs)) {\n if (key.startsWith(`${topic}:`) && !keysToCheck.includes(key)) {\n keysToCheck.push(key);\n }\n }\n\n for (const subscriptionKey of keysToCheck) {\n const activeCbs = this.activeCbs[subscriptionKey];\n if (activeCbs?.has(cb)) {\n activeCbs.delete(cb);\n\n if (activeCbs.size === 0) {\n const subscription = this.activeSubscriptions[subscriptionKey];\n const listener = this.messageListeners[subscriptionKey];\n if (subscription) {\n if (listener) subscription.removeListener('message', listener);\n await subscription.close();\n }\n delete this.activeSubscriptions[subscriptionKey];\n delete this.activeCbs[subscriptionKey];\n delete this.messageListeners[subscriptionKey];\n }\n return;\n }\n }\n }\n\n async flush(): Promise<void> {\n await Promise.all(Object.values(this.ackBuffer));\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["PubSubClient","activeCbs","cb"],"mappings":";;;;AAKO,IAAM,iBAAA,GAAN,cAAgC,MAAA,CAAO;AAAA,EACpC,UAAA;AAAA,EACA,MAAA;AAAA,EACA,YAA0C,EAAC;AAAA,EAC3C,sBAAoD,EAAC;AAAA,EACrD,YAAgD,EAAC;AAAA;AAAA;AAAA;AAAA,EAIjD,cAAA,uBAAsD,GAAA,EAAI;AAAA;AAAA;AAAA;AAAA,EAI1D,eAAkE,EAAC;AAAA;AAAA;AAAA,EAGnE,mBAA+D,EAAC;AAAA,EAExE,YAAY,MAAA,EAAsB;AAChC,IAAA,KAAA,EAAM;AACN,IAAA,IAAA,CAAK,MAAA,GAAS,IAAIA,QAAA,CAAa,MAAM,CAAA;AACrC,IAAA,IAAA,CAAK,UAAA,GAAa,OAAO,UAAA,EAAW;AAAA,EACtC;AAAA,EAEA,mBAAA,CAAoB,OAAe,KAAA,EAAgB;AACjD,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,OAAO,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA;AAAA,IAC1B;AACA,IAAA,OAAO,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,IAAA,CAAK,UAAU,CAAA,CAAA;AAAA,EACpC;AAAA,EAEA,MAAM,UAAA,CAAW,KAAA,EAAe,OAAA,EAAkB;AAChD,IAAA,IAAI;AACF,MAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,IAAA,CAAK,CAAC,QAAQ,eAAA,EAAgB,EAAG,IAAI,OAAA,CAAQ,aAAW,UAAA,CAAW,OAAA,EAAS,GAAI,CAAC,CAAC,CAAC,CAAA;AAC/G,MAAA,IAAA,CAAK,SAAA,CAAU,QAAQ,GAAA,GAAM,OAAA,CAAQ,EAAE,CAAA,GAAI,WAAA,CAAY,MAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AACrE,MAAA,MAAM,WAAA;AACN,MAAA,OAAO,IAAA,CAAK,SAAA,CAAU,KAAA,GAAQ,GAAA,GAAM,QAAQ,EAAE,CAAA;AAAA,IAChD,SAAS,CAAA,EAAG;AACV,MAAA,OAAA,CAAQ,KAAA,CAAM,wBAAwB,CAAC,CAAA;AAAA,IACzC;AAAA,EACF;AAAA,EAEA,MAAM,IAAA,CAAK,SAAA,EAAmB,KAAA,EAAmD;AAC/E,IAAA,MAAM,kBAAkB,KAAA,GAAQ,CAAA,EAAG,SAAS,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,GAAK,SAAA;AAK1D,IAAA,IAAI,IAAA,CAAK,YAAA,CAAa,eAAe,CAAA,EAAG;AACtC,MAAA,OAAO,IAAA,CAAK,aAAa,eAAe,CAAA;AAAA,IAC1C;AAEA,IAAA,MAAM,gBAAA,GAAmB,IAAA,CAAK,mBAAA,CAAoB,SAAA,EAAW,KAAK,CAAA;AAClE,IAAA,MAAM,eAAe,YAA+C;AAClE,MAAA,IAAI;AACF,QAAA,MAAM,IAAA,CAAK,MAAA,CAAO,WAAA,CAAY,SAAS,CAAA;AAAA,MACzC,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,IAAI;AACF,QAAA,MAAM,CAAC,GAAG,CAAA,GAAI,MAAM,IAAA,CAAK,OAAO,KAAA,CAAM,SAAS,CAAA,CAAE,kBAAA,CAAmB,gBAAA,EAAkB;AAAA,UACpF,qBAAA,EAAuB,IAAA;AAAA,UACvB,yBAAA,EAA2B,SAAA,KAAc,WAAA,IAAe,CAAC,CAAC;AAAA,SAC3D,CAAA;AACD,QAAA,IAAA,CAAK,mBAAA,CAAoB,eAAe,CAAA,GAAI,GAAA;AAC5C,QAAA,OAAO,GAAA;AAAA,MACT,SAAS,KAAA,EAAO;AAMd,QAAA,MAAM,aAAA,GAAiB,OAAyC,IAAA,KAAS,CAAA;AACzE,QAAA,IAAI,iBAAiB,KAAA,EAAO;AAC1B,UAAA,IAAI;AACF,YAAA,MAAM,GAAA,GAAM,IAAA,CAAK,MAAA,CAAO,YAAA,CAAa,gBAAgB,CAAA;AACrD,YAAA,IAAA,CAAK,mBAAA,CAAoB,eAAe,CAAA,GAAI,GAAA;AAC5C,YAAA,OAAO,GAAA;AAAA,UACT,CAAA,CAAA,MAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF;AACA,MAAA,OAAO,MAAA;AAAA,IACT,CAAA,GAAG,CAAE,OAAA,CAAQ,MAAM;AACjB,MAAA,OAAO,IAAA,CAAK,aAAa,eAAe,CAAA;AAAA,IAC1C,CAAC,CAAA;AAED,IAAA,IAAA,CAAK,YAAA,CAAa,eAAe,CAAA,GAAI,WAAA;AACrC,IAAA,OAAO,WAAA;AAAA,EACT;AAAA,EAEA,MAAM,QAAQ,SAAA,EAAmB;AAC/B,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,mBAAA,CAAoB,SAAS,CAAA;AAClD,IAAA,OAAO,IAAA,CAAK,oBAAoB,SAAS,CAAA;AACzC,IAAA,IAAA,CAAK,MAAA,CAAO,YAAA,CAAa,OAAO,CAAA,CAAE,kBAAA,EAAmB;AACrD,IAAA,MAAM,IAAA,CAAK,MAAA,CAAO,YAAA,CAAa,OAAO,EAAE,KAAA,EAAM;AAC9C,IAAA,MAAM,IAAA,CAAK,MAAA,CAAO,YAAA,CAAa,OAAO,EAAE,MAAA,EAAO;AAC/C,IAAA,MAAM,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,SAAS,EAAE,MAAA,EAAO;AAAA,EAC5C;AAAA,EAEA,MAAM,OAAA,CACJ,SAAA,EACA,KAAA,EACA,OAAA,EACe;AACf,IAAA,IAAI,SAAA,CAAU,UAAA,CAAW,kBAAkB,CAAA,EAAG;AAC5C,MAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,KAAA,CAAM,GAAG,CAAA;AACjC,MAAA,IAAI,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,MAAM,IAAA,EAAM;AACpC,QAAA,SAAA,GAAY,oBAAA;AAAA,MACd,CAAA,MAAO;AACL,QAAA,SAAA,GAAY,oBAAA;AAAA,MACd;AAAA,IACF;AAQA,IAAA,IAAI,SAAS,SAAA,EAAW;AACtB,MAAA,MAAM,IAAA,CAAK,YAAA,CAAa,SAAA,EAAW,KAAK,CAAA;AACxC,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,KAAA,GAAQ,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,SAAS,CAAA;AAEvC,IAAA,IAAI;AACF,MAAA,MAAM,MAAM,cAAA,CAAe;AAAA,QACzB,MAAM,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,QACvC,WAAA,EAAa;AAAA,OACd,CAAA;AAAA,IACH,SAAS,CAAA,EAAQ;AACf,MAAA,IAAI,CAAA,CAAE,SAAS,CAAA,EAAG;AAChB,QAAA,MAAM,IAAA,CAAK,MAAA,CAAO,WAAA,CAAY,SAAS,CAAA;AACvC,QAAA,MAAM,IAAA,CAAK,OAAA,CAAQ,SAAA,EAAW,KAAK,CAAA;AAAA,MACrC,CAAA,MAAO;AACL,QAAA,MAAM,CAAA;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,SAAA,CAAU,KAAA,EAAe,EAAA,EAAmB,OAAA,EAA2C;AAC3F,IAAA,IAAI,KAAA,CAAM,UAAA,CAAW,kBAAkB,CAAA,EAAG;AACxC,MAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,GAAG,CAAA;AAC7B,MAAA,IAAI,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,MAAM,IAAA,EAAM;AACpC,QAAA,KAAA,GAAQ,oBAAA;AAAA,MACV,CAAA,MAAO;AACL,QAAA,KAAA,GAAQ,oBAAA;AAAA,MACV;AAAA,IACF;AAEA,IAAA,MAAM,QAAQ,OAAA,EAAS,KAAA;AAGvB,IAAA,MAAM,kBAAkB,KAAA,GAAQ,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,GAAK,KAAA;AAKtD,IAAA,IAAI,QAAA,GAAW,IAAA,CAAK,cAAA,CAAe,GAAA,CAAI,KAAK,CAAA;AAC5C,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,QAAA,uBAAe,GAAA,EAAI;AACnB,MAAA,IAAA,CAAK,cAAA,CAAe,GAAA,CAAI,KAAA,EAAO,QAAQ,CAAA;AAAA,IACzC;AACA,IAAA,QAAA,CAAS,IAAI,EAAE,CAAA;AAGf,IAAA,MAAM,YAAA,GAAe,KAAK,mBAAA,CAAoB,eAAe,KAAM,MAAM,IAAA,CAAK,IAAA,CAAK,KAAA,EAAO,KAAK,CAAA;AAC/F,IAAA,IAAI,CAAC,YAAA,EAAc;AACjB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,8BAAA,EAAiC,KAAK,CAAA,CAAE,CAAA;AAAA,IAC1D;AAEA,IAAA,IAAA,CAAK,mBAAA,CAAoB,eAAe,CAAA,GAAI,YAAA;AAE5C,IAAA,MAAM,YAAY,IAAA,CAAK,SAAA,CAAU,eAAe,CAAA,wBAAS,GAAA,EAAI;AAC7D,IAAA,SAAA,CAAU,IAAI,EAAE,CAAA;AAChB,IAAA,IAAA,CAAK,SAAA,CAAU,eAAe,CAAA,GAAI,SAAA;AAElC,IAAA,IAAI,aAAa,MAAA,EAAQ;AACvB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,eAAA,GAAkB,OAAO,OAAA,KAAqB;AAClD,MAAA,MAAM,QAAQ,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK,UAAU,CAAA;AAChD,MAAA,KAAA,CAAM,KAAK,OAAA,CAAQ,EAAA;AACnB,MAAA,KAAA,CAAM,YAAY,OAAA,CAAQ,WAAA;AAC1B,MAAA,KAAA,CAAM,eAAA,GAAkB,QAAQ,eAAA,IAAmB,CAAA;AAEnD,MAAA,IAAI;AACF,QAAA,MAAMC,UAAAA,GAAY,IAAA,CAAK,SAAA,CAAU,eAAe,KAAK,EAAC;AACtD,QAAA,KAAA,MAAWC,OAAMD,UAAAA,EAAW;AAC1B,UAAAC,GAAAA;AAAA,YACE,KAAA;AAAA,YACA,YAAY;AACV,cAAA,IAAI;AACF,gBAAA,MAAM,IAAA,CAAK,UAAA,CAAW,eAAA,EAAiB,OAAO,CAAA;AAAA,cAChD,SAAS,CAAA,EAAG;AACV,gBAAA,OAAA,CAAQ,KAAA,CAAM,wBAAwB,CAAC,CAAA;AAAA,cACzC;AAAA,YACF,CAAA;AAAA,YACA,YAAY;AACV,cAAA,IAAI;AACF,gBAAA,OAAA,CAAQ,IAAA,EAAK;AAAA,cACf,SAAS,CAAA,EAAG;AACV,gBAAA,OAAA,CAAQ,KAAA,CAAM,yBAAyB,CAAC,CAAA;AAAA,cAC1C;AAAA,YACF;AAAA,WACF;AAAA,QACF;AAAA,MACF,SAAS,KAAA,EAAO;AACd,QAAA,OAAA,CAAQ,KAAA,CAAM,0BAA0B,KAAK,CAAA;AAAA,MAC/C;AAAA,IACF,CAAA;AAEA,IAAA,IAAA,CAAK,gBAAA,CAAiB,eAAe,CAAA,GAAI,eAAA;AACzC,IAAA,YAAA,CAAa,EAAA,CAAG,WAAW,eAAe,CAAA;AAE1C,IAAA,YAAA,CAAa,EAAA,CAAG,OAAA,EAAS,OAAM,KAAA,KAAS;AACtC,MAAA,OAAA,CAAQ,KAAA,CAAM,sBAAsB,KAAK,CAAA;AAAA,IAC3C,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,WAAA,CAAY,KAAA,EAAe,EAAA,EAAkC;AACjE,IAAA,IAAI,KAAA,CAAM,UAAA,CAAW,kBAAkB,CAAA,EAAG;AACxC,MAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,GAAG,CAAA;AAC7B,MAAA,IAAI,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,MAAM,IAAA,EAAM;AACpC,QAAA,KAAA,GAAQ,oBAAA;AAAA,MACV,CAAA,MAAO;AACL,QAAA,KAAA,GAAQ,oBAAA;AAAA,MACV;AAAA,IACF;AAGA,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,cAAA,CAAe,GAAA,CAAI,KAAK,CAAA;AAC9C,IAAA,IAAI,UAAU,MAAA,CAAO,EAAE,CAAA,IAAK,QAAA,CAAS,SAAS,CAAA,EAAG;AAC/C,MAAA,IAAA,CAAK,cAAA,CAAe,OAAO,KAAK,CAAA;AAAA,IAClC;AAGA,IAAA,MAAM,WAAA,GAAc,CAAC,KAAK,CAAA;AAC1B,IAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,SAAS,CAAA,EAAG;AAC7C,MAAA,IAAI,GAAA,CAAI,UAAA,CAAW,CAAA,EAAG,KAAK,CAAA,CAAA,CAAG,KAAK,CAAC,WAAA,CAAY,QAAA,CAAS,GAAG,CAAA,EAAG;AAC7D,QAAA,WAAA,CAAY,KAAK,GAAG,CAAA;AAAA,MACtB;AAAA,IACF;AAEA,IAAA,KAAA,MAAW,mBAAmB,WAAA,EAAa;AACzC,MAAA,MAAM,SAAA,GAAY,IAAA,CAAK,SAAA,CAAU,eAAe,CAAA;AAChD,MAAA,IAAI,SAAA,EAAW,GAAA,CAAI,EAAE,CAAA,EAAG;AACtB,QAAA,SAAA,CAAU,OAAO,EAAE,CAAA;AAEnB,QAAA,IAAI,SAAA,CAAU,SAAS,CAAA,EAAG;AACxB,UAAA,MAAM,YAAA,GAAe,IAAA,CAAK,mBAAA,CAAoB,eAAe,CAAA;AAC7D,UAAA,MAAM,QAAA,GAAW,IAAA,CAAK,gBAAA,CAAiB,eAAe,CAAA;AACtD,UAAA,IAAI,YAAA,EAAc;AAChB,YAAA,IAAI,QAAA,EAAU,YAAA,CAAa,cAAA,CAAe,SAAA,EAAW,QAAQ,CAAA;AAC7D,YAAA,MAAM,aAAa,KAAA,EAAM;AAAA,UAC3B;AACA,UAAA,OAAO,IAAA,CAAK,oBAAoB,eAAe,CAAA;AAC/C,UAAA,OAAO,IAAA,CAAK,UAAU,eAAe,CAAA;AACrC,UAAA,OAAO,IAAA,CAAK,iBAAiB,eAAe,CAAA;AAAA,QAC9C;AACA,QAAA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,KAAA,GAAuB;AAC3B,IAAA,MAAM,QAAQ,GAAA,CAAI,MAAA,CAAO,MAAA,CAAO,IAAA,CAAK,SAAS,CAAC,CAAA;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,YAAA,CAAa,SAAA,EAAmB,KAAA,EAAuD;AACnG,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,cAAA,CAAe,GAAA,CAAI,SAAS,CAAA;AACnD,IAAA,IAAI,CAAC,SAAA,IAAa,SAAA,CAAU,IAAA,KAAS,CAAA,EAAG;AAExC,IAAA,MAAM,UAAA,GAAoB;AAAA,MACxB,GAAG,KAAA;AAAA,MACH,EAAA,EAAI,OAAO,UAAA,EAAW;AAAA,MACtB,SAAA,sBAAe,IAAA,EAAK;AAAA,MACpB,eAAA,EAAiB;AAAA,KACnB;AAEA,IAAA,KAAA,MAAW,EAAA,IAAM,CAAC,GAAG,SAAS,CAAA,EAAG;AAC/B,MAAA,IAAI;AACF,QAAA,EAAA;AAAA,UACE,UAAA;AAAA,UACA,YAAY;AAAA,UAAC,CAAA;AAAA,UACb,YAAY;AAAA,UAAC;AAAA,SACf;AAAA,MACF,SAAS,KAAA,EAAO;AACd,QAAA,OAAA,CAAQ,KAAA,CAAM,gCAAgC,KAAK,CAAA;AAAA,MACrD;AAAA,IACF;AAAA,EACF;AACF","file":"index.js","sourcesContent":["import { PubSub as PubSubClient } from '@google-cloud/pubsub';\nimport type { ClientConfig, Message, Subscription } from '@google-cloud/pubsub';\nimport { PubSub } from '@mastra/core/events';\nimport type { Event, EventCallback, SubscribeOptions } from '@mastra/core/events';\n\nexport class GoogleCloudPubSub extends PubSub {\n private instanceId: string;\n private pubsub: PubSubClient;\n private ackBuffer: Record<string, Promise<any>> = {};\n private activeSubscriptions: Record<string, Subscription> = {};\n private activeCbs: Record<string, Set<EventCallback>> = {};\n // `localOnly` publishes never touch Google Cloud — they are delivered to\n // same-process subscribers only. Tracks live callbacks per (normalized) topic\n // so we can fan out without going through PubSub.\n private localCallbacks: Map<string, Set<EventCallback>> = new Map();\n // Coalesces concurrent init() calls for the same subscription so racing\n // subscribers (e.g. a producer stream and a consumer observe on the same\n // run topic) share a single createTopic/createSubscription attempt.\n private inFlightInit: Record<string, Promise<Subscription | undefined>> = {};\n // Tracks the actual anonymous message listener registered on each subscription,\n // so we can remove it cleanly on the final unsubscribe.\n private messageListeners: Record<string, (message: Message) => void> = {};\n\n constructor(config: ClientConfig) {\n super();\n this.pubsub = new PubSubClient(config);\n this.instanceId = crypto.randomUUID();\n }\n\n getSubscriptionName(topic: string, group?: string) {\n if (group) {\n return `${topic}-${group}`;\n }\n return `${topic}-${this.instanceId}`;\n }\n\n async ackMessage(topic: string, message: Message) {\n try {\n const ackResponse = Promise.race([message.ackWithResponse(), new Promise(resolve => setTimeout(resolve, 5000))]);\n this.ackBuffer[topic + '-' + message.id] = ackResponse.catch(() => {});\n await ackResponse;\n delete this.ackBuffer[topic + '-' + message.id];\n } catch (e) {\n console.error('Error acking message', e);\n }\n }\n\n async init(topicName: string, group?: string): Promise<Subscription | undefined> {\n const subscriptionKey = group ? `${topicName}:${group}` : topicName;\n\n // Reuse an in-flight init so concurrent subscribers don't race to create the\n // same subscription. The promise is registered synchronously below (before any\n // await), so a second caller arriving during the create window reuses it.\n if (this.inFlightInit[subscriptionKey]) {\n return this.inFlightInit[subscriptionKey];\n }\n\n const subscriptionName = this.getSubscriptionName(topicName, group);\n const initPromise = (async (): Promise<Subscription | undefined> => {\n try {\n await this.pubsub.createTopic(topicName);\n } catch {\n // no-op\n }\n try {\n const [sub] = await this.pubsub.topic(topicName).createSubscription(subscriptionName, {\n enableMessageOrdering: true,\n enableExactlyOnceDelivery: topicName === 'workflows' || !!group,\n });\n this.activeSubscriptions[subscriptionKey] = sub;\n return sub;\n } catch (error) {\n // The subscription may already exist: created concurrently by a racing\n // subscriber (ALREADY_EXISTS / gRPC code 6), shared by another process via\n // a group, or surviving a previous process. In all of these cases attach to\n // the existing subscription instead of failing. Ungrouped subscriptions hit\n // this on the concurrent-create race, so we must not gate it on `group`.\n const alreadyExists = (error as { code?: number } | undefined)?.code === 6;\n if (alreadyExists || group) {\n try {\n const sub = this.pubsub.subscription(subscriptionName);\n this.activeSubscriptions[subscriptionKey] = sub;\n return sub;\n } catch {\n // no-op\n }\n }\n }\n return undefined;\n })().finally(() => {\n delete this.inFlightInit[subscriptionKey];\n });\n\n this.inFlightInit[subscriptionKey] = initPromise;\n return initPromise;\n }\n\n async destroy(topicName: string) {\n const subName = this.getSubscriptionName(topicName);\n delete this.activeSubscriptions[topicName];\n this.pubsub.subscription(subName).removeAllListeners();\n await this.pubsub.subscription(subName).close();\n await this.pubsub.subscription(subName).delete();\n await this.pubsub.topic(topicName).delete();\n }\n\n async publish(\n topicName: string,\n event: Omit<Event, 'id' | 'createdAt'>,\n options?: { localOnly?: boolean },\n ): Promise<void> {\n if (topicName.startsWith('workflow.events.')) {\n const parts = topicName.split('.');\n if (parts[parts.length - 2] === 'v2') {\n topicName = 'workflow.events.v2';\n } else {\n topicName = 'workflow.events.v1';\n }\n }\n\n // `localOnly` events stay entirely within the publishing process. They are\n // never serialized through Google Cloud, so live methods on payload values\n // (e.g. `MastraModelOutput.getFullOutput`) survive intact. The agent's\n // execution-workflow relies on this: the run result is delivered via\n // `workflows-finish` and includes the `MastraModelOutput` instance —\n // round-tripping it through Pub/Sub would strip its methods.\n if (options?.localOnly) {\n await this.deliverLocal(topicName, event);\n return;\n }\n\n let topic = this.pubsub.topic(topicName);\n\n try {\n await topic.publishMessage({\n data: Buffer.from(JSON.stringify(event)),\n orderingKey: 'workflows',\n });\n } catch (e: any) {\n if (e.code === 5) {\n await this.pubsub.createTopic(topicName);\n await this.publish(topicName, event);\n } else {\n throw e;\n }\n }\n }\n\n async subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void> {\n if (topic.startsWith('workflow.events.')) {\n const parts = topic.split('.');\n if (parts[parts.length - 2] === 'v2') {\n topic = 'workflow.events.v2';\n } else {\n topic = 'workflow.events.v1';\n }\n }\n\n const group = options?.group;\n // Use a composite key when group is set so grouped and non-grouped subscriptions\n // on the same topic don't collide\n const subscriptionKey = group ? `${topic}:${group}` : topic;\n\n // Register callback for `localOnly` delivery. Local delivery bypasses Google\n // Cloud entirely so live class instances on the payload (e.g. Date, Map,\n // Error, MastraModelOutput) keep their prototypes.\n let localSet = this.localCallbacks.get(topic);\n if (!localSet) {\n localSet = new Set();\n this.localCallbacks.set(topic, localSet);\n }\n localSet.add(cb);\n\n // Update tracked callbacks\n const subscription = this.activeSubscriptions[subscriptionKey] ?? (await this.init(topic, group));\n if (!subscription) {\n throw new Error(`Failed to subscribe to topic: ${topic}`);\n }\n\n this.activeSubscriptions[subscriptionKey] = subscription;\n\n const activeCbs = this.activeCbs[subscriptionKey] ?? new Set();\n activeCbs.add(cb);\n this.activeCbs[subscriptionKey] = activeCbs;\n\n if (subscription.isOpen) {\n return;\n }\n\n const messageListener = async (message: Message) => {\n const event = JSON.parse(message.data.toString()) as Event;\n event.id = message.id;\n event.createdAt = message.publishTime;\n event.deliveryAttempt = message.deliveryAttempt ?? 1;\n\n try {\n const activeCbs = this.activeCbs[subscriptionKey] ?? [];\n for (const cb of activeCbs) {\n cb(\n event,\n async () => {\n try {\n await this.ackMessage(subscriptionKey, message);\n } catch (e) {\n console.error('Error acking message', e);\n }\n },\n async () => {\n try {\n message.nack();\n } catch (e) {\n console.error('Error nacking message', e);\n }\n },\n );\n }\n } catch (error) {\n console.error('Error processing event', error);\n }\n };\n\n this.messageListeners[subscriptionKey] = messageListener;\n subscription.on('message', messageListener);\n\n subscription.on('error', async error => {\n console.error('subscription error', error);\n });\n }\n\n async unsubscribe(topic: string, cb: EventCallback): Promise<void> {\n if (topic.startsWith('workflow.events.')) {\n const parts = topic.split('.');\n if (parts[parts.length - 2] === 'v2') {\n topic = 'workflow.events.v2';\n } else {\n topic = 'workflow.events.v1';\n }\n }\n\n // Drop from the local-delivery set; if nobody is left, tear down the bucket.\n const localSet = this.localCallbacks.get(topic);\n if (localSet?.delete(cb) && localSet.size === 0) {\n this.localCallbacks.delete(topic);\n }\n\n // Check both grouped and non-grouped subscription keys for this callback\n const keysToCheck = [topic];\n for (const key of Object.keys(this.activeCbs)) {\n if (key.startsWith(`${topic}:`) && !keysToCheck.includes(key)) {\n keysToCheck.push(key);\n }\n }\n\n for (const subscriptionKey of keysToCheck) {\n const activeCbs = this.activeCbs[subscriptionKey];\n if (activeCbs?.has(cb)) {\n activeCbs.delete(cb);\n\n if (activeCbs.size === 0) {\n const subscription = this.activeSubscriptions[subscriptionKey];\n const listener = this.messageListeners[subscriptionKey];\n if (subscription) {\n if (listener) subscription.removeListener('message', listener);\n await subscription.close();\n }\n delete this.activeSubscriptions[subscriptionKey];\n delete this.activeCbs[subscriptionKey];\n delete this.messageListeners[subscriptionKey];\n }\n return;\n }\n }\n }\n\n async flush(): Promise<void> {\n await Promise.all(Object.values(this.ackBuffer));\n }\n\n /**\n * Fan a `localOnly` event out to in-process subscribers without going through\n * Google Cloud. The payload is delivered by reference, so live class instances\n * and functions on the event survive intact.\n */\n private async deliverLocal(topicName: string, event: Omit<Event, 'id' | 'createdAt'>): Promise<void> {\n const callbacks = this.localCallbacks.get(topicName);\n if (!callbacks || callbacks.size === 0) return;\n\n const localEvent: Event = {\n ...event,\n id: crypto.randomUUID(),\n createdAt: new Date(),\n deliveryAttempt: 1,\n };\n\n for (const cb of [...callbacks]) {\n try {\n cb(\n localEvent,\n async () => {},\n async () => {},\n );\n } catch (error) {\n console.error('Error delivering local event', error);\n }\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/google-cloud-pubsub",
3
- "version": "1.1.0",
3
+ "version": "1.1.1-alpha.1",
4
4
  "description": "Mastra Google Cloud PubSub integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,6 +30,8 @@
30
30
  "zod": "^4.4.3"
31
31
  },
32
32
  "devDependencies": {
33
+ "@ai-sdk/openai-v5": "npm:@ai-sdk/openai@2.0.106",
34
+ "@copilotkit/aimock": "^1.29.0",
33
35
  "@types/node": "22.19.21",
34
36
  "@vitest/coverage-v8": "4.1.8",
35
37
  "@vitest/ui": "4.1.8",
@@ -38,11 +40,12 @@
38
40
  "tsup": "^8.5.1",
39
41
  "typescript": "^6.0.3",
40
42
  "vitest": "4.1.8",
41
- "@internal/lint": "0.0.107",
43
+ "@internal/ai-sdk-v5": "0.0.54",
42
44
  "@internal/types-builder": "0.0.82",
43
- "@mastra/deployer": "1.45.0",
44
- "@mastra/core": "1.45.0",
45
- "@mastra/libsql": "1.14.0"
45
+ "@internal/lint": "0.0.107",
46
+ "@mastra/core": "1.46.0-alpha.4",
47
+ "@mastra/libsql": "1.14.1-alpha.1",
48
+ "@mastra/deployer": "1.46.0-alpha.4"
46
49
  },
47
50
  "peerDependencies": {
48
51
  "@mastra/core": ">=1.13.2-0 <2.0.0-0"