@saga-bus/transport-gcp-pubsub 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Dean Foran
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # @saga-bus/transport-gcp-pubsub
2
+
3
+ Google Cloud Pub/Sub transport for saga-bus.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @saga-bus/transport-gcp-pubsub @google-cloud/pubsub
9
+ # or
10
+ pnpm add @saga-bus/transport-gcp-pubsub @google-cloud/pubsub
11
+ ```
12
+
13
+ ## Features
14
+
15
+ - **Topic/Subscription Model**: GCP Pub/Sub native architecture
16
+ - **Message Ordering**: Optional ordering key support
17
+ - **Auto-Creation**: Automatically creates topics and subscriptions
18
+ - **Dead-Letter Queues**: Support for DLQ configuration
19
+ - **Authentication**: Google Cloud ADC and service account support
20
+
21
+ ## Quick Start
22
+
23
+ ```typescript
24
+ import { createBus } from "@saga-bus/core";
25
+ import { GcpPubSubTransport } from "@saga-bus/transport-gcp-pubsub";
26
+
27
+ const transport = new GcpPubSubTransport({
28
+ projectId: "my-gcp-project",
29
+ defaultTopic: "saga-events",
30
+ });
31
+
32
+ const bus = createBus({
33
+ transport,
34
+ // ... other config
35
+ });
36
+
37
+ await bus.start();
38
+ ```
39
+
40
+ ## Configuration
41
+
42
+ ```typescript
43
+ interface GcpPubSubTransportOptions {
44
+ /** Existing PubSub client instance */
45
+ pubsub?: PubSub;
46
+
47
+ /** Client config for creating new PubSub instance */
48
+ clientConfig?: ClientConfig;
49
+
50
+ /** Project ID (required if not in clientConfig) */
51
+ projectId?: string;
52
+
53
+ /** Default topic for publishing */
54
+ defaultTopic?: string;
55
+
56
+ /** Subscription name prefix (default: "saga-bus-") */
57
+ subscriptionPrefix?: string;
58
+
59
+ /** Whether to use ordering keys for message ordering (default: false) */
60
+ enableOrdering?: boolean;
61
+
62
+ /** Max messages to pull at once (default: 10) */
63
+ maxMessages?: number;
64
+
65
+ /** Ack deadline in seconds (default: 60) */
66
+ ackDeadlineSeconds?: number;
67
+
68
+ /** Whether to auto-create topics/subscriptions (default: true) */
69
+ autoCreate?: boolean;
70
+
71
+ /** Dead-letter topic for failed messages */
72
+ deadLetterTopic?: string;
73
+
74
+ /** Max delivery attempts before dead-letter (default: 5) */
75
+ maxDeliveryAttempts?: number;
76
+ }
77
+ ```
78
+
79
+ ## Examples
80
+
81
+ ### Basic Usage
82
+
83
+ ```typescript
84
+ import { GcpPubSubTransport } from "@saga-bus/transport-gcp-pubsub";
85
+
86
+ const transport = new GcpPubSubTransport({
87
+ projectId: "my-project",
88
+ defaultTopic: "saga-events",
89
+ });
90
+
91
+ await transport.start();
92
+
93
+ // Publish a message
94
+ await transport.publish(
95
+ { type: "OrderCreated", orderId: "123" },
96
+ { endpoint: "orders" }
97
+ );
98
+
99
+ // Subscribe to messages
100
+ await transport.subscribe(
101
+ { endpoint: "orders", group: "order-processor" },
102
+ async (envelope) => {
103
+ console.log("Received:", envelope.payload);
104
+ }
105
+ );
106
+ ```
107
+
108
+ ### With Message Ordering
109
+
110
+ ```typescript
111
+ const transport = new GcpPubSubTransport({
112
+ projectId: "my-project",
113
+ enableOrdering: true,
114
+ });
115
+
116
+ await transport.start();
117
+
118
+ // Messages with the same key will be delivered in order
119
+ await transport.publish(
120
+ { type: "OrderCreated", orderId: "123" },
121
+ { endpoint: "orders", key: "order-123" }
122
+ );
123
+
124
+ await transport.publish(
125
+ { type: "OrderShipped", orderId: "123" },
126
+ { endpoint: "orders", key: "order-123" }
127
+ );
128
+ ```
129
+
130
+ ### Using Existing PubSub Client
131
+
132
+ ```typescript
133
+ import { PubSub } from "@google-cloud/pubsub";
134
+
135
+ const pubsub = new PubSub({
136
+ projectId: "my-project",
137
+ keyFilename: "/path/to/service-account.json",
138
+ });
139
+
140
+ const transport = new GcpPubSubTransport({
141
+ pubsub,
142
+ defaultTopic: "saga-events",
143
+ });
144
+ ```
145
+
146
+ ### With Dead-Letter Topic
147
+
148
+ ```typescript
149
+ const transport = new GcpPubSubTransport({
150
+ projectId: "my-project",
151
+ deadLetterTopic: "saga-dlq",
152
+ maxDeliveryAttempts: 5,
153
+ });
154
+ ```
155
+
156
+ ## Message Format
157
+
158
+ Messages are published as JSON with attributes:
159
+
160
+ ```json
161
+ {
162
+ "data": {
163
+ "id": "msg-uuid",
164
+ "type": "OrderCreated",
165
+ "payload": { "type": "OrderCreated", "orderId": "123" },
166
+ "headers": {},
167
+ "timestamp": "2024-01-01T00:00:00.000Z",
168
+ "partitionKey": "order-123"
169
+ },
170
+ "attributes": {
171
+ "messageType": "OrderCreated",
172
+ "messageId": "msg-uuid",
173
+ "correlationId": "order-123"
174
+ },
175
+ "orderingKey": "order-123"
176
+ }
177
+ ```
178
+
179
+ ## Limitations
180
+
181
+ ### No Delayed Messages
182
+
183
+ GCP Pub/Sub does not support native delayed message delivery. Attempting to publish with `delayMs` will throw an error:
184
+
185
+ ```typescript
186
+ // This will throw an error
187
+ await transport.publish(message, { delayMs: 5000 });
188
+ // Error: GCP Pub/Sub does not support delayed messages.
189
+ // Use Cloud Scheduler or Cloud Tasks for delayed delivery.
190
+ ```
191
+
192
+ **Alternatives for delayed delivery:**
193
+
194
+ 1. **Cloud Scheduler**: Schedule messages at specific times
195
+ 2. **Cloud Tasks**: Queue tasks with delay
196
+ 3. **Cloud Functions with Pub/Sub**: Implement custom delay logic
197
+
198
+ ## Authentication
199
+
200
+ The transport uses Google Cloud's Application Default Credentials (ADC):
201
+
202
+ 1. **Local Development**: Use `gcloud auth application-default login`
203
+ 2. **Service Account**: Set `GOOGLE_APPLICATION_CREDENTIALS` environment variable
204
+ 3. **GCP Services**: Automatically uses metadata service
205
+
206
+ ```bash
207
+ # Local development
208
+ gcloud auth application-default login
209
+
210
+ # Or use service account
211
+ export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
212
+ ```
213
+
214
+ ## Testing
215
+
216
+ For testing, you can use the GCP Pub/Sub emulator:
217
+
218
+ ```bash
219
+ # Start emulator
220
+ gcloud beta emulators pubsub start --project=test-project
221
+
222
+ # Set environment variable
223
+ export PUBSUB_EMULATOR_HOST=localhost:8085
224
+ ```
225
+
226
+ ## License
227
+
228
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ GcpPubSubTransport: () => GcpPubSubTransport
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/GcpPubSubTransport.ts
28
+ var import_pubsub = require("@google-cloud/pubsub");
29
+ var import_crypto = require("crypto");
30
+ var GcpPubSubTransport = class {
31
+ pubsub = null;
32
+ options;
33
+ topics = /* @__PURE__ */ new Map();
34
+ subscriptions = [];
35
+ started = false;
36
+ constructor(options) {
37
+ if (!options.pubsub && !options.clientConfig && !options.projectId) {
38
+ throw new Error(
39
+ "Either pubsub, clientConfig, or projectId must be provided"
40
+ );
41
+ }
42
+ this.options = {
43
+ subscriptionPrefix: "saga-bus-",
44
+ enableOrdering: false,
45
+ maxMessages: 10,
46
+ ackDeadlineSeconds: 60,
47
+ autoCreate: true,
48
+ maxDeliveryAttempts: 5,
49
+ ...options
50
+ };
51
+ }
52
+ async start() {
53
+ if (this.started) return;
54
+ if (this.options.pubsub) {
55
+ this.pubsub = this.options.pubsub;
56
+ } else {
57
+ this.pubsub = new import_pubsub.PubSub({
58
+ projectId: this.options.projectId,
59
+ ...this.options.clientConfig
60
+ });
61
+ }
62
+ this.started = true;
63
+ }
64
+ async stop() {
65
+ if (!this.started) return;
66
+ for (const sub of this.subscriptions) {
67
+ await sub.close();
68
+ }
69
+ this.subscriptions.length = 0;
70
+ this.topics.clear();
71
+ if (!this.options.pubsub && this.pubsub) {
72
+ await this.pubsub.close();
73
+ }
74
+ this.pubsub = null;
75
+ this.started = false;
76
+ }
77
+ async subscribe(options, handler) {
78
+ if (!this.pubsub) throw new Error("Transport not started");
79
+ const { endpoint, group } = options;
80
+ const topicName = endpoint;
81
+ const subscriptionName = group ?? `${this.options.subscriptionPrefix}${endpoint}`;
82
+ let topic = this.topics.get(topicName);
83
+ if (!topic) {
84
+ topic = this.pubsub.topic(topicName);
85
+ if (this.options.autoCreate) {
86
+ const [exists] = await topic.exists();
87
+ if (!exists) await topic.create();
88
+ }
89
+ this.topics.set(topicName, topic);
90
+ }
91
+ const subscription = topic.subscription(subscriptionName);
92
+ if (this.options.autoCreate) {
93
+ const [exists] = await subscription.exists();
94
+ if (!exists) {
95
+ await subscription.create({
96
+ enableMessageOrdering: this.options.enableOrdering,
97
+ ackDeadlineSeconds: this.options.ackDeadlineSeconds
98
+ });
99
+ }
100
+ }
101
+ subscription.on("message", async (message) => {
102
+ try {
103
+ const rawEnvelope = JSON.parse(message.data.toString());
104
+ const envelope = {
105
+ id: rawEnvelope.id,
106
+ type: rawEnvelope.type,
107
+ payload: rawEnvelope.payload,
108
+ headers: rawEnvelope.headers,
109
+ timestamp: new Date(rawEnvelope.timestamp),
110
+ partitionKey: rawEnvelope.partitionKey
111
+ };
112
+ await handler(envelope);
113
+ message.ack();
114
+ } catch (error) {
115
+ console.error("[GcpPubSub] Message handler error:", error);
116
+ message.nack();
117
+ }
118
+ });
119
+ subscription.on("error", (error) => {
120
+ console.error("[GcpPubSub] Subscription error:", error);
121
+ });
122
+ this.subscriptions.push(subscription);
123
+ }
124
+ async publish(message, options) {
125
+ if (!this.pubsub) throw new Error("Transport not started");
126
+ const { endpoint, key, headers = {}, delayMs } = options;
127
+ if (delayMs && delayMs > 0) {
128
+ throw new Error(
129
+ "GCP Pub/Sub does not support delayed messages. Use Cloud Scheduler or Cloud Tasks for delayed delivery."
130
+ );
131
+ }
132
+ const topicName = endpoint ?? this.options.defaultTopic ?? message.type;
133
+ let topic = this.topics.get(topicName);
134
+ if (!topic) {
135
+ topic = this.pubsub.topic(topicName);
136
+ if (this.options.autoCreate) {
137
+ const [exists] = await topic.exists();
138
+ if (!exists) await topic.create();
139
+ }
140
+ this.topics.set(topicName, topic);
141
+ }
142
+ const envelope = {
143
+ id: (0, import_crypto.randomUUID)(),
144
+ type: message.type,
145
+ payload: message,
146
+ headers,
147
+ timestamp: /* @__PURE__ */ new Date(),
148
+ partitionKey: key
149
+ };
150
+ const attributes = {
151
+ messageType: message.type,
152
+ messageId: envelope.id
153
+ };
154
+ for (const [k, v] of Object.entries(headers)) {
155
+ if (typeof v === "string") {
156
+ attributes[k] = v;
157
+ }
158
+ }
159
+ if (key) {
160
+ attributes.correlationId = key;
161
+ }
162
+ const publishOptions = {};
163
+ if (this.options.enableOrdering && key) {
164
+ publishOptions.orderingKey = key;
165
+ }
166
+ await topic.publishMessage({
167
+ data: Buffer.from(JSON.stringify(envelope)),
168
+ attributes,
169
+ ...publishOptions
170
+ });
171
+ }
172
+ };
173
+ // Annotate the CommonJS export names for ESM import in node:
174
+ 0 && (module.exports = {
175
+ GcpPubSubTransport
176
+ });
177
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/GcpPubSubTransport.ts"],"sourcesContent":["export { GcpPubSubTransport } from \"./GcpPubSubTransport.js\";\nexport type { GcpPubSubTransportOptions } from \"./types.js\";\n","import { PubSub, Topic, Subscription, Message } from \"@google-cloud/pubsub\";\nimport type {\n Transport,\n TransportSubscribeOptions,\n TransportPublishOptions,\n MessageEnvelope,\n BaseMessage,\n} from \"@saga-bus/core\";\nimport type { GcpPubSubTransportOptions } from \"./types.js\";\nimport { randomUUID } from \"crypto\";\n\n/**\n * GCP Pub/Sub transport for saga-bus.\n *\n * @example\n * ```typescript\n * import { GcpPubSubTransport } from \"@saga-bus/transport-gcp-pubsub\";\n *\n * const transport = new GcpPubSubTransport({\n * projectId: \"my-project\",\n * defaultTopic: \"saga-events\",\n * enableOrdering: true,\n * });\n *\n * await transport.start();\n * ```\n */\nexport class GcpPubSubTransport implements Transport {\n private pubsub: PubSub | null = null;\n private readonly options: Required<\n Pick<\n GcpPubSubTransportOptions,\n | \"subscriptionPrefix\"\n | \"enableOrdering\"\n | \"maxMessages\"\n | \"ackDeadlineSeconds\"\n | \"autoCreate\"\n | \"maxDeliveryAttempts\"\n >\n > &\n GcpPubSubTransportOptions;\n\n private readonly topics = new Map<string, Topic>();\n private readonly subscriptions: Subscription[] = [];\n private started = false;\n\n constructor(options: GcpPubSubTransportOptions) {\n if (!options.pubsub && !options.clientConfig && !options.projectId) {\n throw new Error(\n \"Either pubsub, clientConfig, or projectId must be provided\"\n );\n }\n\n this.options = {\n subscriptionPrefix: \"saga-bus-\",\n enableOrdering: false,\n maxMessages: 10,\n ackDeadlineSeconds: 60,\n autoCreate: true,\n maxDeliveryAttempts: 5,\n ...options,\n };\n }\n\n async start(): Promise<void> {\n if (this.started) return;\n\n if (this.options.pubsub) {\n this.pubsub = this.options.pubsub;\n } else {\n this.pubsub = new PubSub({\n projectId: this.options.projectId,\n ...this.options.clientConfig,\n });\n }\n\n this.started = true;\n }\n\n async stop(): Promise<void> {\n if (!this.started) return;\n\n // Close all subscriptions\n for (const sub of this.subscriptions) {\n await sub.close();\n }\n this.subscriptions.length = 0;\n this.topics.clear();\n\n // Close client if we created it\n if (!this.options.pubsub && this.pubsub) {\n await this.pubsub.close();\n }\n this.pubsub = null;\n this.started = false;\n }\n\n async subscribe<TMessage extends BaseMessage>(\n options: TransportSubscribeOptions,\n handler: (envelope: MessageEnvelope<TMessage>) => Promise<void>\n ): Promise<void> {\n if (!this.pubsub) throw new Error(\"Transport not started\");\n\n const { endpoint, group } = options;\n const topicName = endpoint;\n const subscriptionName =\n group ?? `${this.options.subscriptionPrefix}${endpoint}`;\n\n // Get or create topic\n let topic = this.topics.get(topicName);\n if (!topic) {\n topic = this.pubsub.topic(topicName);\n if (this.options.autoCreate) {\n const [exists] = await topic.exists();\n if (!exists) await topic.create();\n }\n this.topics.set(topicName, topic);\n }\n\n // Get or create subscription\n const subscription = topic.subscription(subscriptionName);\n if (this.options.autoCreate) {\n const [exists] = await subscription.exists();\n if (!exists) {\n await subscription.create({\n enableMessageOrdering: this.options.enableOrdering,\n ackDeadlineSeconds: this.options.ackDeadlineSeconds,\n });\n }\n }\n\n // Start listening\n subscription.on(\"message\", async (message: Message) => {\n try {\n const rawEnvelope = JSON.parse(message.data.toString());\n const envelope: MessageEnvelope<TMessage> = {\n id: rawEnvelope.id,\n type: rawEnvelope.type,\n payload: rawEnvelope.payload as TMessage,\n headers: rawEnvelope.headers,\n timestamp: new Date(rawEnvelope.timestamp),\n partitionKey: rawEnvelope.partitionKey,\n };\n await handler(envelope);\n message.ack();\n } catch (error) {\n console.error(\"[GcpPubSub] Message handler error:\", error);\n message.nack();\n }\n });\n\n subscription.on(\"error\", (error) => {\n console.error(\"[GcpPubSub] Subscription error:\", error);\n });\n\n this.subscriptions.push(subscription);\n }\n\n async publish<TMessage extends BaseMessage>(\n message: TMessage,\n options: TransportPublishOptions\n ): Promise<void> {\n if (!this.pubsub) throw new Error(\"Transport not started\");\n\n const { endpoint, key, headers = {}, delayMs } = options;\n\n // GCP Pub/Sub doesn't support delayed messages natively\n if (delayMs && delayMs > 0) {\n throw new Error(\n \"GCP Pub/Sub does not support delayed messages. \" +\n \"Use Cloud Scheduler or Cloud Tasks for delayed delivery.\"\n );\n }\n\n const topicName = endpoint ?? this.options.defaultTopic ?? message.type;\n\n // Get or create topic\n let topic = this.topics.get(topicName);\n if (!topic) {\n topic = this.pubsub.topic(topicName);\n if (this.options.autoCreate) {\n const [exists] = await topic.exists();\n if (!exists) await topic.create();\n }\n this.topics.set(topicName, topic);\n }\n\n // Create envelope\n const envelope: MessageEnvelope<TMessage> = {\n id: randomUUID(),\n type: message.type,\n payload: message,\n headers: headers as Record<string, string>,\n timestamp: new Date(),\n partitionKey: key,\n };\n\n // Publish\n const attributes: Record<string, string> = {\n messageType: message.type,\n messageId: envelope.id,\n };\n\n // Add custom headers as attributes\n for (const [k, v] of Object.entries(headers)) {\n if (typeof v === \"string\") {\n attributes[k] = v;\n }\n }\n\n if (key) {\n attributes.correlationId = key;\n }\n\n const publishOptions: { orderingKey?: string } = {};\n if (this.options.enableOrdering && key) {\n publishOptions.orderingKey = key;\n }\n\n await topic.publishMessage({\n data: Buffer.from(JSON.stringify(envelope)),\n attributes,\n ...publishOptions,\n });\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,61 @@
1
+ import { Transport, BaseMessage, TransportSubscribeOptions, MessageEnvelope, TransportPublishOptions } from '@saga-bus/core';
2
+ import { PubSub, ClientConfig } from '@google-cloud/pubsub';
3
+
4
+ /**
5
+ * Configuration options for the GCP Pub/Sub transport.
6
+ */
7
+ interface GcpPubSubTransportOptions {
8
+ /** Existing PubSub client instance */
9
+ pubsub?: PubSub;
10
+ /** Client config for creating new PubSub instance */
11
+ clientConfig?: ClientConfig;
12
+ /** Project ID (required if not in clientConfig) */
13
+ projectId?: string;
14
+ /** Default topic for publishing */
15
+ defaultTopic?: string;
16
+ /** Subscription name prefix */
17
+ subscriptionPrefix?: string;
18
+ /** Whether to use ordering keys for message ordering */
19
+ enableOrdering?: boolean;
20
+ /** Max messages to pull at once */
21
+ maxMessages?: number;
22
+ /** Ack deadline in seconds */
23
+ ackDeadlineSeconds?: number;
24
+ /** Whether to auto-create topics/subscriptions */
25
+ autoCreate?: boolean;
26
+ /** Dead-letter topic for failed messages */
27
+ deadLetterTopic?: string;
28
+ /** Max delivery attempts before dead-letter */
29
+ maxDeliveryAttempts?: number;
30
+ }
31
+
32
+ /**
33
+ * GCP Pub/Sub transport for saga-bus.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * import { GcpPubSubTransport } from "@saga-bus/transport-gcp-pubsub";
38
+ *
39
+ * const transport = new GcpPubSubTransport({
40
+ * projectId: "my-project",
41
+ * defaultTopic: "saga-events",
42
+ * enableOrdering: true,
43
+ * });
44
+ *
45
+ * await transport.start();
46
+ * ```
47
+ */
48
+ declare class GcpPubSubTransport implements Transport {
49
+ private pubsub;
50
+ private readonly options;
51
+ private readonly topics;
52
+ private readonly subscriptions;
53
+ private started;
54
+ constructor(options: GcpPubSubTransportOptions);
55
+ start(): Promise<void>;
56
+ stop(): Promise<void>;
57
+ subscribe<TMessage extends BaseMessage>(options: TransportSubscribeOptions, handler: (envelope: MessageEnvelope<TMessage>) => Promise<void>): Promise<void>;
58
+ publish<TMessage extends BaseMessage>(message: TMessage, options: TransportPublishOptions): Promise<void>;
59
+ }
60
+
61
+ export { GcpPubSubTransport, type GcpPubSubTransportOptions };
@@ -0,0 +1,61 @@
1
+ import { Transport, BaseMessage, TransportSubscribeOptions, MessageEnvelope, TransportPublishOptions } from '@saga-bus/core';
2
+ import { PubSub, ClientConfig } from '@google-cloud/pubsub';
3
+
4
+ /**
5
+ * Configuration options for the GCP Pub/Sub transport.
6
+ */
7
+ interface GcpPubSubTransportOptions {
8
+ /** Existing PubSub client instance */
9
+ pubsub?: PubSub;
10
+ /** Client config for creating new PubSub instance */
11
+ clientConfig?: ClientConfig;
12
+ /** Project ID (required if not in clientConfig) */
13
+ projectId?: string;
14
+ /** Default topic for publishing */
15
+ defaultTopic?: string;
16
+ /** Subscription name prefix */
17
+ subscriptionPrefix?: string;
18
+ /** Whether to use ordering keys for message ordering */
19
+ enableOrdering?: boolean;
20
+ /** Max messages to pull at once */
21
+ maxMessages?: number;
22
+ /** Ack deadline in seconds */
23
+ ackDeadlineSeconds?: number;
24
+ /** Whether to auto-create topics/subscriptions */
25
+ autoCreate?: boolean;
26
+ /** Dead-letter topic for failed messages */
27
+ deadLetterTopic?: string;
28
+ /** Max delivery attempts before dead-letter */
29
+ maxDeliveryAttempts?: number;
30
+ }
31
+
32
+ /**
33
+ * GCP Pub/Sub transport for saga-bus.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * import { GcpPubSubTransport } from "@saga-bus/transport-gcp-pubsub";
38
+ *
39
+ * const transport = new GcpPubSubTransport({
40
+ * projectId: "my-project",
41
+ * defaultTopic: "saga-events",
42
+ * enableOrdering: true,
43
+ * });
44
+ *
45
+ * await transport.start();
46
+ * ```
47
+ */
48
+ declare class GcpPubSubTransport implements Transport {
49
+ private pubsub;
50
+ private readonly options;
51
+ private readonly topics;
52
+ private readonly subscriptions;
53
+ private started;
54
+ constructor(options: GcpPubSubTransportOptions);
55
+ start(): Promise<void>;
56
+ stop(): Promise<void>;
57
+ subscribe<TMessage extends BaseMessage>(options: TransportSubscribeOptions, handler: (envelope: MessageEnvelope<TMessage>) => Promise<void>): Promise<void>;
58
+ publish<TMessage extends BaseMessage>(message: TMessage, options: TransportPublishOptions): Promise<void>;
59
+ }
60
+
61
+ export { GcpPubSubTransport, type GcpPubSubTransportOptions };
package/dist/index.js ADDED
@@ -0,0 +1,150 @@
1
+ // src/GcpPubSubTransport.ts
2
+ import { PubSub } from "@google-cloud/pubsub";
3
+ import { randomUUID } from "crypto";
4
+ var GcpPubSubTransport = class {
5
+ pubsub = null;
6
+ options;
7
+ topics = /* @__PURE__ */ new Map();
8
+ subscriptions = [];
9
+ started = false;
10
+ constructor(options) {
11
+ if (!options.pubsub && !options.clientConfig && !options.projectId) {
12
+ throw new Error(
13
+ "Either pubsub, clientConfig, or projectId must be provided"
14
+ );
15
+ }
16
+ this.options = {
17
+ subscriptionPrefix: "saga-bus-",
18
+ enableOrdering: false,
19
+ maxMessages: 10,
20
+ ackDeadlineSeconds: 60,
21
+ autoCreate: true,
22
+ maxDeliveryAttempts: 5,
23
+ ...options
24
+ };
25
+ }
26
+ async start() {
27
+ if (this.started) return;
28
+ if (this.options.pubsub) {
29
+ this.pubsub = this.options.pubsub;
30
+ } else {
31
+ this.pubsub = new PubSub({
32
+ projectId: this.options.projectId,
33
+ ...this.options.clientConfig
34
+ });
35
+ }
36
+ this.started = true;
37
+ }
38
+ async stop() {
39
+ if (!this.started) return;
40
+ for (const sub of this.subscriptions) {
41
+ await sub.close();
42
+ }
43
+ this.subscriptions.length = 0;
44
+ this.topics.clear();
45
+ if (!this.options.pubsub && this.pubsub) {
46
+ await this.pubsub.close();
47
+ }
48
+ this.pubsub = null;
49
+ this.started = false;
50
+ }
51
+ async subscribe(options, handler) {
52
+ if (!this.pubsub) throw new Error("Transport not started");
53
+ const { endpoint, group } = options;
54
+ const topicName = endpoint;
55
+ const subscriptionName = group ?? `${this.options.subscriptionPrefix}${endpoint}`;
56
+ let topic = this.topics.get(topicName);
57
+ if (!topic) {
58
+ topic = this.pubsub.topic(topicName);
59
+ if (this.options.autoCreate) {
60
+ const [exists] = await topic.exists();
61
+ if (!exists) await topic.create();
62
+ }
63
+ this.topics.set(topicName, topic);
64
+ }
65
+ const subscription = topic.subscription(subscriptionName);
66
+ if (this.options.autoCreate) {
67
+ const [exists] = await subscription.exists();
68
+ if (!exists) {
69
+ await subscription.create({
70
+ enableMessageOrdering: this.options.enableOrdering,
71
+ ackDeadlineSeconds: this.options.ackDeadlineSeconds
72
+ });
73
+ }
74
+ }
75
+ subscription.on("message", async (message) => {
76
+ try {
77
+ const rawEnvelope = JSON.parse(message.data.toString());
78
+ const envelope = {
79
+ id: rawEnvelope.id,
80
+ type: rawEnvelope.type,
81
+ payload: rawEnvelope.payload,
82
+ headers: rawEnvelope.headers,
83
+ timestamp: new Date(rawEnvelope.timestamp),
84
+ partitionKey: rawEnvelope.partitionKey
85
+ };
86
+ await handler(envelope);
87
+ message.ack();
88
+ } catch (error) {
89
+ console.error("[GcpPubSub] Message handler error:", error);
90
+ message.nack();
91
+ }
92
+ });
93
+ subscription.on("error", (error) => {
94
+ console.error("[GcpPubSub] Subscription error:", error);
95
+ });
96
+ this.subscriptions.push(subscription);
97
+ }
98
+ async publish(message, options) {
99
+ if (!this.pubsub) throw new Error("Transport not started");
100
+ const { endpoint, key, headers = {}, delayMs } = options;
101
+ if (delayMs && delayMs > 0) {
102
+ throw new Error(
103
+ "GCP Pub/Sub does not support delayed messages. Use Cloud Scheduler or Cloud Tasks for delayed delivery."
104
+ );
105
+ }
106
+ const topicName = endpoint ?? this.options.defaultTopic ?? message.type;
107
+ let topic = this.topics.get(topicName);
108
+ if (!topic) {
109
+ topic = this.pubsub.topic(topicName);
110
+ if (this.options.autoCreate) {
111
+ const [exists] = await topic.exists();
112
+ if (!exists) await topic.create();
113
+ }
114
+ this.topics.set(topicName, topic);
115
+ }
116
+ const envelope = {
117
+ id: randomUUID(),
118
+ type: message.type,
119
+ payload: message,
120
+ headers,
121
+ timestamp: /* @__PURE__ */ new Date(),
122
+ partitionKey: key
123
+ };
124
+ const attributes = {
125
+ messageType: message.type,
126
+ messageId: envelope.id
127
+ };
128
+ for (const [k, v] of Object.entries(headers)) {
129
+ if (typeof v === "string") {
130
+ attributes[k] = v;
131
+ }
132
+ }
133
+ if (key) {
134
+ attributes.correlationId = key;
135
+ }
136
+ const publishOptions = {};
137
+ if (this.options.enableOrdering && key) {
138
+ publishOptions.orderingKey = key;
139
+ }
140
+ await topic.publishMessage({
141
+ data: Buffer.from(JSON.stringify(envelope)),
142
+ attributes,
143
+ ...publishOptions
144
+ });
145
+ }
146
+ };
147
+ export {
148
+ GcpPubSubTransport
149
+ };
150
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/GcpPubSubTransport.ts"],"sourcesContent":["import { PubSub, Topic, Subscription, Message } from \"@google-cloud/pubsub\";\nimport type {\n Transport,\n TransportSubscribeOptions,\n TransportPublishOptions,\n MessageEnvelope,\n BaseMessage,\n} from \"@saga-bus/core\";\nimport type { GcpPubSubTransportOptions } from \"./types.js\";\nimport { randomUUID } from \"crypto\";\n\n/**\n * GCP Pub/Sub transport for saga-bus.\n *\n * @example\n * ```typescript\n * import { GcpPubSubTransport } from \"@saga-bus/transport-gcp-pubsub\";\n *\n * const transport = new GcpPubSubTransport({\n * projectId: \"my-project\",\n * defaultTopic: \"saga-events\",\n * enableOrdering: true,\n * });\n *\n * await transport.start();\n * ```\n */\nexport class GcpPubSubTransport implements Transport {\n private pubsub: PubSub | null = null;\n private readonly options: Required<\n Pick<\n GcpPubSubTransportOptions,\n | \"subscriptionPrefix\"\n | \"enableOrdering\"\n | \"maxMessages\"\n | \"ackDeadlineSeconds\"\n | \"autoCreate\"\n | \"maxDeliveryAttempts\"\n >\n > &\n GcpPubSubTransportOptions;\n\n private readonly topics = new Map<string, Topic>();\n private readonly subscriptions: Subscription[] = [];\n private started = false;\n\n constructor(options: GcpPubSubTransportOptions) {\n if (!options.pubsub && !options.clientConfig && !options.projectId) {\n throw new Error(\n \"Either pubsub, clientConfig, or projectId must be provided\"\n );\n }\n\n this.options = {\n subscriptionPrefix: \"saga-bus-\",\n enableOrdering: false,\n maxMessages: 10,\n ackDeadlineSeconds: 60,\n autoCreate: true,\n maxDeliveryAttempts: 5,\n ...options,\n };\n }\n\n async start(): Promise<void> {\n if (this.started) return;\n\n if (this.options.pubsub) {\n this.pubsub = this.options.pubsub;\n } else {\n this.pubsub = new PubSub({\n projectId: this.options.projectId,\n ...this.options.clientConfig,\n });\n }\n\n this.started = true;\n }\n\n async stop(): Promise<void> {\n if (!this.started) return;\n\n // Close all subscriptions\n for (const sub of this.subscriptions) {\n await sub.close();\n }\n this.subscriptions.length = 0;\n this.topics.clear();\n\n // Close client if we created it\n if (!this.options.pubsub && this.pubsub) {\n await this.pubsub.close();\n }\n this.pubsub = null;\n this.started = false;\n }\n\n async subscribe<TMessage extends BaseMessage>(\n options: TransportSubscribeOptions,\n handler: (envelope: MessageEnvelope<TMessage>) => Promise<void>\n ): Promise<void> {\n if (!this.pubsub) throw new Error(\"Transport not started\");\n\n const { endpoint, group } = options;\n const topicName = endpoint;\n const subscriptionName =\n group ?? `${this.options.subscriptionPrefix}${endpoint}`;\n\n // Get or create topic\n let topic = this.topics.get(topicName);\n if (!topic) {\n topic = this.pubsub.topic(topicName);\n if (this.options.autoCreate) {\n const [exists] = await topic.exists();\n if (!exists) await topic.create();\n }\n this.topics.set(topicName, topic);\n }\n\n // Get or create subscription\n const subscription = topic.subscription(subscriptionName);\n if (this.options.autoCreate) {\n const [exists] = await subscription.exists();\n if (!exists) {\n await subscription.create({\n enableMessageOrdering: this.options.enableOrdering,\n ackDeadlineSeconds: this.options.ackDeadlineSeconds,\n });\n }\n }\n\n // Start listening\n subscription.on(\"message\", async (message: Message) => {\n try {\n const rawEnvelope = JSON.parse(message.data.toString());\n const envelope: MessageEnvelope<TMessage> = {\n id: rawEnvelope.id,\n type: rawEnvelope.type,\n payload: rawEnvelope.payload as TMessage,\n headers: rawEnvelope.headers,\n timestamp: new Date(rawEnvelope.timestamp),\n partitionKey: rawEnvelope.partitionKey,\n };\n await handler(envelope);\n message.ack();\n } catch (error) {\n console.error(\"[GcpPubSub] Message handler error:\", error);\n message.nack();\n }\n });\n\n subscription.on(\"error\", (error) => {\n console.error(\"[GcpPubSub] Subscription error:\", error);\n });\n\n this.subscriptions.push(subscription);\n }\n\n async publish<TMessage extends BaseMessage>(\n message: TMessage,\n options: TransportPublishOptions\n ): Promise<void> {\n if (!this.pubsub) throw new Error(\"Transport not started\");\n\n const { endpoint, key, headers = {}, delayMs } = options;\n\n // GCP Pub/Sub doesn't support delayed messages natively\n if (delayMs && delayMs > 0) {\n throw new Error(\n \"GCP Pub/Sub does not support delayed messages. \" +\n \"Use Cloud Scheduler or Cloud Tasks for delayed delivery.\"\n );\n }\n\n const topicName = endpoint ?? this.options.defaultTopic ?? message.type;\n\n // Get or create topic\n let topic = this.topics.get(topicName);\n if (!topic) {\n topic = this.pubsub.topic(topicName);\n if (this.options.autoCreate) {\n const [exists] = await topic.exists();\n if (!exists) await topic.create();\n }\n this.topics.set(topicName, topic);\n }\n\n // Create envelope\n const envelope: MessageEnvelope<TMessage> = {\n id: randomUUID(),\n type: message.type,\n payload: message,\n headers: headers as Record<string, string>,\n timestamp: new Date(),\n partitionKey: key,\n };\n\n // Publish\n const attributes: Record<string, string> = {\n messageType: message.type,\n messageId: envelope.id,\n };\n\n // Add custom headers as attributes\n for (const [k, v] of Object.entries(headers)) {\n if (typeof v === \"string\") {\n attributes[k] = v;\n }\n }\n\n if (key) {\n attributes.correlationId = key;\n }\n\n const publishOptions: { orderingKey?: string } = {};\n if (this.options.enableOrdering && key) {\n publishOptions.orderingKey = key;\n }\n\n await topic.publishMessage({\n data: Buffer.from(JSON.stringify(envelope)),\n attributes,\n ...publishOptions,\n });\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@saga-bus/transport-gcp-pubsub",
3
+ "version": "0.1.0",
4
+ "description": "Google Cloud Pub/Sub transport for saga-bus",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "dependencies": {
21
+ "@saga-bus/core": "0.1.0"
22
+ },
23
+ "peerDependencies": {
24
+ "@google-cloud/pubsub": ">=4.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^22.10.1",
28
+ "eslint": "^9.16.0",
29
+ "tsup": "^8.3.5",
30
+ "typescript": "^5.7.2",
31
+ "vitest": "^2.1.8",
32
+ "@repo/eslint-config": "0.0.0",
33
+ "@repo/typescript-config": "0.0.0"
34
+ },
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "dev": "tsup --watch",
38
+ "lint": "eslint src/",
39
+ "check-types": "tsc --noEmit",
40
+ "test": "vitest run",
41
+ "test:watch": "vitest"
42
+ }
43
+ }