@blokjs/trigger-pubsub 0.2.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.
@@ -0,0 +1,220 @@
1
+ /**
2
+ * AzureServiceBusAdapter - Azure Service Bus adapter for PubSubTrigger
3
+ *
4
+ * Uses @azure/service-bus for Azure Service Bus connectivity.
5
+ * Requires: npm install @azure/service-bus
6
+ *
7
+ * Environment variables:
8
+ * - AZURE_SERVICE_BUS_CONNECTION_STRING: Azure Service Bus connection string
9
+ * - AZURE_SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE: Fully qualified namespace (if using DefaultAzureCredential)
10
+ */
11
+
12
+ import type { PubSubTriggerOpts } from "@blokjs/helper";
13
+ import { v4 as uuid } from "uuid";
14
+ import type { PubSubAdapter, PubSubMessage } from "../PubSubTrigger";
15
+
16
+ /**
17
+ * Azure Service Bus configuration
18
+ */
19
+ export interface AzureServiceBusConfig {
20
+ connectionString?: string;
21
+ fullyQualifiedNamespace?: string;
22
+ }
23
+
24
+ /**
25
+ * AzureServiceBusAdapter - Azure Service Bus implementation
26
+ */
27
+ export class AzureServiceBusAdapter implements PubSubAdapter {
28
+ readonly provider = "azure" as const;
29
+
30
+ private client: any;
31
+ private receivers: Map<string, any> = new Map();
32
+ private connected = false;
33
+ private config: AzureServiceBusConfig;
34
+
35
+ constructor(config?: AzureServiceBusConfig) {
36
+ this.config = {
37
+ connectionString: config?.connectionString || process.env.AZURE_SERVICE_BUS_CONNECTION_STRING,
38
+ fullyQualifiedNamespace:
39
+ config?.fullyQualifiedNamespace || process.env.AZURE_SERVICE_BUS_FULLY_QUALIFIED_NAMESPACE,
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Connect to Azure Service Bus
45
+ */
46
+ async connect(): Promise<void> {
47
+ if (this.connected) return;
48
+
49
+ try {
50
+ // Dynamic import of @azure/service-bus
51
+ const { ServiceBusClient } = await import("@azure/service-bus");
52
+
53
+ if (this.config.connectionString) {
54
+ this.client = new ServiceBusClient(this.config.connectionString);
55
+ } else if (this.config.fullyQualifiedNamespace) {
56
+ // Would need @azure/identity for DefaultAzureCredential
57
+ throw new Error("Managed identity authentication requires @azure/identity package");
58
+ } else {
59
+ throw new Error("Either connectionString or fullyQualifiedNamespace is required");
60
+ }
61
+
62
+ this.connected = true;
63
+ console.log("[AzureServiceBusAdapter] Connected to Azure Service Bus");
64
+ } catch (error) {
65
+ throw new Error(
66
+ `Failed to connect to Azure Service Bus: ${(error as Error).message}. ` +
67
+ `Make sure @azure/service-bus is installed: npm install @azure/service-bus`,
68
+ );
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Disconnect from Azure Service Bus
74
+ */
75
+ async disconnect(): Promise<void> {
76
+ if (!this.connected) return;
77
+
78
+ try {
79
+ // Close all receivers
80
+ for (const [name, receiver] of this.receivers) {
81
+ await receiver.close();
82
+ }
83
+ this.receivers.clear();
84
+
85
+ await this.client.close();
86
+ this.connected = false;
87
+ console.log("[AzureServiceBusAdapter] Disconnected from Azure Service Bus");
88
+ } catch (error) {
89
+ console.error(`[AzureServiceBusAdapter] Error disconnecting: ${(error as Error).message}`);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Subscribe to an Azure Service Bus topic/subscription or queue
95
+ */
96
+ async subscribe(config: PubSubTriggerOpts, handler: (message: PubSubMessage) => Promise<void>): Promise<void> {
97
+ if (!this.connected) {
98
+ throw new Error("Not connected to Azure Service Bus. Call connect() first.");
99
+ }
100
+
101
+ let receiver: any;
102
+
103
+ // Determine if this is a topic subscription or a queue
104
+ if (config.subscription && config.topic) {
105
+ // Topic with subscription
106
+ receiver = this.client.createReceiver(config.topic, config.subscription, {
107
+ receiveMode: config.ack !== false ? "peekLock" : "receiveAndDelete",
108
+ });
109
+ } else {
110
+ // Queue
111
+ receiver = this.client.createReceiver(config.subscription || config.topic, {
112
+ receiveMode: config.ack !== false ? "peekLock" : "receiveAndDelete",
113
+ });
114
+ }
115
+
116
+ const subscriptionKey = `${config.topic}/${config.subscription}`;
117
+
118
+ // Message handler
119
+ const processMessage = async (sbMessage: any) => {
120
+ // Parse message body
121
+ let body: unknown;
122
+ try {
123
+ if (typeof sbMessage.body === "string") {
124
+ body = JSON.parse(sbMessage.body);
125
+ } else {
126
+ body = sbMessage.body;
127
+ }
128
+ } catch {
129
+ body = sbMessage.body;
130
+ }
131
+
132
+ // Extract application properties as attributes
133
+ const attributes: Record<string, string> = {};
134
+ if (sbMessage.applicationProperties) {
135
+ for (const [key, value] of Object.entries(sbMessage.applicationProperties)) {
136
+ attributes[key] = String(value);
137
+ }
138
+ }
139
+
140
+ // Create pub/sub message
141
+ const pubsubMessage: PubSubMessage = {
142
+ id: sbMessage.messageId || uuid(),
143
+ body,
144
+ attributes,
145
+ raw: sbMessage,
146
+ topic: config.topic,
147
+ subscription: config.subscription,
148
+ publishTime: sbMessage.enqueuedTimeUtc ? new Date(sbMessage.enqueuedTimeUtc) : new Date(),
149
+ ack: async () => {
150
+ await receiver.completeMessage(sbMessage);
151
+ },
152
+ nack: async () => {
153
+ await receiver.abandonMessage(sbMessage);
154
+ },
155
+ };
156
+
157
+ // Process message
158
+ try {
159
+ await handler(pubsubMessage);
160
+ } catch (error) {
161
+ console.error(`[AzureServiceBusAdapter] Error processing message: ${(error as Error).message}`);
162
+ }
163
+ };
164
+
165
+ // Error handler
166
+ const processError = async (error: Error) => {
167
+ console.error(`[AzureServiceBusAdapter] Error: ${error.message}`);
168
+ };
169
+
170
+ // Subscribe to messages
171
+ receiver.subscribe({
172
+ processMessage,
173
+ processError,
174
+ });
175
+
176
+ this.receivers.set(subscriptionKey, receiver);
177
+
178
+ console.log(`[AzureServiceBusAdapter] Subscribed to: ${subscriptionKey}`);
179
+ }
180
+
181
+ /**
182
+ * Unsubscribe from Azure Service Bus
183
+ */
184
+ async unsubscribe(subscriptionKey: string): Promise<void> {
185
+ const receiver = this.receivers.get(subscriptionKey);
186
+ if (receiver) {
187
+ await receiver.close();
188
+ this.receivers.delete(subscriptionKey);
189
+ console.log(`[AzureServiceBusAdapter] Unsubscribed from: ${subscriptionKey}`);
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Check if connected to Azure Service Bus
195
+ */
196
+ isConnected(): boolean {
197
+ return this.connected;
198
+ }
199
+
200
+ /**
201
+ * Health check - verify Azure Service Bus connectivity
202
+ */
203
+ async healthCheck(): Promise<boolean> {
204
+ if (!this.connected) return false;
205
+
206
+ try {
207
+ // Create a temporary receiver to test connectivity
208
+ const testReceiver = this.client.createReceiver("$default", {
209
+ receiveMode: "peekLock",
210
+ });
211
+ await testReceiver.close();
212
+ return true;
213
+ } catch {
214
+ // Queue might not exist but connection is healthy if we got here
215
+ return this.connected;
216
+ }
217
+ }
218
+ }
219
+
220
+ export default AzureServiceBusAdapter;
@@ -0,0 +1,196 @@
1
+ /**
2
+ * GCPPubSubAdapter - Google Cloud Pub/Sub adapter for PubSubTrigger
3
+ *
4
+ * Uses @google-cloud/pubsub for GCP Pub/Sub connectivity.
5
+ * Requires: npm install @google-cloud/pubsub
6
+ *
7
+ * Environment variables:
8
+ * - GOOGLE_CLOUD_PROJECT: GCP project ID
9
+ * - GOOGLE_APPLICATION_CREDENTIALS: Path to service account key file (optional if using default credentials)
10
+ * - PUBSUB_EMULATOR_HOST: Pub/Sub emulator host for local development (optional)
11
+ */
12
+
13
+ import type { PubSubTriggerOpts } from "@blokjs/helper";
14
+ import { v4 as uuid } from "uuid";
15
+ import type { PubSubAdapter, PubSubMessage } from "../PubSubTrigger";
16
+
17
+ /**
18
+ * GCP Pub/Sub configuration
19
+ */
20
+ export interface GCPPubSubConfig {
21
+ projectId?: string;
22
+ credentials?: {
23
+ client_email: string;
24
+ private_key: string;
25
+ };
26
+ }
27
+
28
+ /**
29
+ * GCPPubSubAdapter - Google Cloud Pub/Sub implementation
30
+ */
31
+ export class GCPPubSubAdapter implements PubSubAdapter {
32
+ readonly provider = "gcp" as const;
33
+
34
+ private client: any;
35
+ private subscriptions: Map<string, any> = new Map();
36
+ private connected = false;
37
+ private config: GCPPubSubConfig;
38
+
39
+ constructor(config?: GCPPubSubConfig) {
40
+ this.config = {
41
+ projectId: config?.projectId || process.env.GOOGLE_CLOUD_PROJECT,
42
+ credentials: config?.credentials,
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Connect to GCP Pub/Sub
48
+ */
49
+ async connect(): Promise<void> {
50
+ if (this.connected) return;
51
+
52
+ try {
53
+ // Dynamic import of @google-cloud/pubsub
54
+ const { PubSub } = await import("@google-cloud/pubsub");
55
+
56
+ this.client = new PubSub({
57
+ projectId: this.config.projectId,
58
+ credentials: this.config.credentials,
59
+ });
60
+
61
+ this.connected = true;
62
+ console.log(`[GCPPubSubAdapter] Connected to GCP Pub/Sub: ${this.config.projectId}`);
63
+ } catch (error) {
64
+ throw new Error(
65
+ `Failed to connect to GCP Pub/Sub: ${(error as Error).message}. ` +
66
+ `Make sure @google-cloud/pubsub is installed: npm install @google-cloud/pubsub`,
67
+ );
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Disconnect from GCP Pub/Sub
73
+ */
74
+ async disconnect(): Promise<void> {
75
+ if (!this.connected) return;
76
+
77
+ try {
78
+ // Close all subscriptions
79
+ for (const [name, subscription] of this.subscriptions) {
80
+ await subscription.close();
81
+ }
82
+ this.subscriptions.clear();
83
+
84
+ await this.client.close();
85
+ this.connected = false;
86
+ console.log("[GCPPubSubAdapter] Disconnected from GCP Pub/Sub");
87
+ } catch (error) {
88
+ console.error(`[GCPPubSubAdapter] Error disconnecting: ${(error as Error).message}`);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Subscribe to a GCP Pub/Sub topic
94
+ */
95
+ async subscribe(config: PubSubTriggerOpts, handler: (message: PubSubMessage) => Promise<void>): Promise<void> {
96
+ if (!this.connected) {
97
+ throw new Error("Not connected to GCP Pub/Sub. Call connect() first.");
98
+ }
99
+
100
+ const subscriptionName = config.subscription;
101
+
102
+ // Get the subscription
103
+ const subscription = this.client.subscription(subscriptionName, {
104
+ flowControl: {
105
+ maxMessages: config.maxMessages || 10,
106
+ },
107
+ ackDeadline: config.ackDeadline || 30,
108
+ });
109
+
110
+ // Message handler
111
+ const messageHandler = async (gcpMessage: any) => {
112
+ // Parse message data
113
+ let body: unknown;
114
+ try {
115
+ const data = gcpMessage.data.toString();
116
+ body = JSON.parse(data);
117
+ } catch {
118
+ body = gcpMessage.data.toString();
119
+ }
120
+
121
+ // Create pub/sub message
122
+ const pubsubMessage: PubSubMessage = {
123
+ id: gcpMessage.id || uuid(),
124
+ body,
125
+ attributes: gcpMessage.attributes || {},
126
+ raw: gcpMessage,
127
+ topic: config.topic,
128
+ subscription: subscriptionName,
129
+ publishTime: gcpMessage.publishTime ? new Date(gcpMessage.publishTime) : new Date(),
130
+ ack: async () => {
131
+ gcpMessage.ack();
132
+ },
133
+ nack: async () => {
134
+ gcpMessage.nack();
135
+ },
136
+ };
137
+
138
+ // Process message
139
+ try {
140
+ await handler(pubsubMessage);
141
+ } catch (error) {
142
+ console.error(`[GCPPubSubAdapter] Error processing message: ${(error as Error).message}`);
143
+ }
144
+ };
145
+
146
+ // Error handler
147
+ const errorHandler = (error: Error) => {
148
+ console.error(`[GCPPubSubAdapter] Subscription error: ${error.message}`);
149
+ };
150
+
151
+ // Attach listeners
152
+ subscription.on("message", messageHandler);
153
+ subscription.on("error", errorHandler);
154
+
155
+ // Store subscription reference
156
+ this.subscriptions.set(subscriptionName, subscription);
157
+
158
+ console.log(`[GCPPubSubAdapter] Subscribed to: ${subscriptionName}`);
159
+ }
160
+
161
+ /**
162
+ * Unsubscribe from a GCP Pub/Sub subscription
163
+ */
164
+ async unsubscribe(subscriptionName: string): Promise<void> {
165
+ const subscription = this.subscriptions.get(subscriptionName);
166
+ if (subscription) {
167
+ await subscription.close();
168
+ this.subscriptions.delete(subscriptionName);
169
+ console.log(`[GCPPubSubAdapter] Unsubscribed from: ${subscriptionName}`);
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Check if connected to GCP Pub/Sub
175
+ */
176
+ isConnected(): boolean {
177
+ return this.connected;
178
+ }
179
+
180
+ /**
181
+ * Health check - verify GCP Pub/Sub connectivity
182
+ */
183
+ async healthCheck(): Promise<boolean> {
184
+ if (!this.connected) return false;
185
+
186
+ try {
187
+ // List subscriptions as a health check
188
+ const [subscriptions] = await this.client.getSubscriptions({ pageSize: 1 });
189
+ return true;
190
+ } catch {
191
+ return false;
192
+ }
193
+ }
194
+ }
195
+
196
+ export default GCPPubSubAdapter;
package/src/index.ts ADDED
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @blokjs/trigger-pubsub
3
+ *
4
+ * Pub/Sub-based trigger for Blok workflows.
5
+ * Supports multiple pub/sub providers:
6
+ * - Google Cloud Pub/Sub
7
+ * - AWS SNS/SQS
8
+ * - Azure Service Bus
9
+ *
10
+ * @example GCP Pub/Sub
11
+ * ```typescript
12
+ * import { PubSubTrigger, GCPPubSubAdapter } from "@blokjs/trigger-pubsub";
13
+ *
14
+ * class MyPubSubTrigger extends PubSubTrigger {
15
+ * protected adapter = new GCPPubSubAdapter({
16
+ * projectId: "my-project",
17
+ * });
18
+ *
19
+ * protected nodes = myNodes;
20
+ * protected workflows = myWorkflows;
21
+ * }
22
+ *
23
+ * const trigger = new MyPubSubTrigger();
24
+ * await trigger.listen();
25
+ * ```
26
+ *
27
+ * @example AWS SNS/SQS
28
+ * ```typescript
29
+ * import { PubSubTrigger, AWSSNSAdapter } from "@blokjs/trigger-pubsub";
30
+ *
31
+ * class MyPubSubTrigger extends PubSubTrigger {
32
+ * protected adapter = new AWSSNSAdapter({
33
+ * region: "us-east-1",
34
+ * });
35
+ * // ...
36
+ * }
37
+ * ```
38
+ *
39
+ * @example Azure Service Bus
40
+ * ```typescript
41
+ * import { PubSubTrigger, AzureServiceBusAdapter } from "@blokjs/trigger-pubsub";
42
+ *
43
+ * class MyPubSubTrigger extends PubSubTrigger {
44
+ * protected adapter = new AzureServiceBusAdapter({
45
+ * connectionString: process.env.AZURE_SERVICE_BUS_CONNECTION_STRING,
46
+ * });
47
+ * // ...
48
+ * }
49
+ * ```
50
+ */
51
+
52
+ // Core exports
53
+ export {
54
+ PubSubTrigger,
55
+ type PubSubAdapter,
56
+ type PubSubMessage,
57
+ } from "./PubSubTrigger";
58
+
59
+ // Adapters
60
+ export { GCPPubSubAdapter, type GCPPubSubConfig } from "./adapters/GCPPubSubAdapter";
61
+ export { AWSSNSAdapter, type AWSSNSConfig } from "./adapters/AWSSNSAdapter";
62
+ export { AzureServiceBusAdapter, type AzureServiceBusConfig } from "./adapters/AzureServiceBusAdapter";
63
+
64
+ // Re-export types from helper for convenience
65
+ export type {
66
+ PubSubProvider,
67
+ PubSubTriggerOpts,
68
+ } from "@blokjs/helper";
@@ -0,0 +1,8 @@
1
+ PROJECT_NAME=trigger-pubsub-server
2
+ PROJECT_VERSION=0.0.1
3
+ PORT=4006
4
+ WORKFLOWS_PATH=PROJECT_PATH/workflows
5
+ NODES_PATH=PROJECT_PATH/src/nodes
6
+ CONSOLE_LOG_ACTIVE=true
7
+ APP_NAME=blok-pubsub
8
+ DISABLE_TRIGGER_RUN=false # Set to true to disable trigger run and use this project as a module
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "blok-pubsub-trigger",
3
+ "version": "0.1.0",
4
+ "description": "Pub/Sub trigger for Blok workflows",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=18.0.0"
8
+ },
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "author": "",
12
+ "license": "MIT",
13
+ "scripts": {
14
+ "dev": "bun --watch run src/index.ts",
15
+ "start": "bun run dist/index.js",
16
+ "build": "rimraf ./dist && tsc",
17
+ "test": "vitest run",
18
+ "test:dev": "vitest"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^22.15.21",
22
+ "@types/uuid": "^11.0.0",
23
+ "rimraf": "^6.1.2",
24
+ "typescript": "^5.8.3",
25
+ "vitest": "^4.0.18"
26
+ },
27
+ "dependencies": {
28
+ "@blokjs/api-call": "workspace:*",
29
+ "@blokjs/helper": "workspace:*",
30
+ "@blokjs/if-else": "workspace:*",
31
+ "@blokjs/runner": "workspace:*",
32
+ "@blokjs/shared": "workspace:*",
33
+ "@blokjs/trigger-pubsub": "workspace:*",
34
+ "@opentelemetry/api": "^1.9.0",
35
+ "@opentelemetry/exporter-prometheus": "^0.57.2",
36
+ "@opentelemetry/resources": "^1.30.1",
37
+ "@opentelemetry/sdk-metrics": "^1.30.1",
38
+ "@opentelemetry/sdk-trace-base": "^1.30.1",
39
+ "@opentelemetry/semantic-conventions": "^1.39.0",
40
+ "uuid": "^11.1.0",
41
+ "zod": "^3.24.2"
42
+ },
43
+ "private": true
44
+ }
@@ -0,0 +1,10 @@
1
+ import ApiCall from "@blokjs/api-call";
2
+ import IfElse from "@blokjs/if-else";
3
+ import type { BlokService } from "@blokjs/runner";
4
+
5
+ const nodes: Record<string, BlokService<unknown>> = {
6
+ "@blokjs/api-call": ApiCall,
7
+ "@blokjs/if-else": IfElse,
8
+ };
9
+
10
+ export default nodes;
@@ -0,0 +1,8 @@
1
+ import type Workflows from "./runner/types/Workflows";
2
+ import onMessage from "./workflows/messages/on-message";
3
+
4
+ const workflows: Workflows = {
5
+ "on-message": onMessage,
6
+ };
7
+
8
+ export default workflows;
@@ -0,0 +1,41 @@
1
+ import { DefaultLogger } from "@blokjs/runner";
2
+ import { type Span, metrics, trace } from "@opentelemetry/api";
3
+ import PubSubServer from "./runner/PubSubServer";
4
+
5
+ export default class App {
6
+ private pubsubServer: PubSubServer = <PubSubServer>{};
7
+ protected trigger_initializer = 0;
8
+ protected initializer = 0;
9
+ protected tracer = trace.getTracer(
10
+ process.env.PROJECT_NAME || "trigger-pubsub-server",
11
+ process.env.PROJECT_VERSION || "0.0.1",
12
+ );
13
+ private logger = new DefaultLogger();
14
+ protected app_cold_start = metrics.getMeter("default").createGauge("initialization", {
15
+ description: "Application cold start",
16
+ });
17
+
18
+ constructor() {
19
+ this.initializer = performance.now();
20
+ this.pubsubServer = new PubSubServer();
21
+ }
22
+
23
+ async run() {
24
+ this.tracer.startActiveSpan("initialization", async (span: Span) => {
25
+ await this.pubsubServer.listen();
26
+ this.initializer = performance.now() - this.initializer;
27
+
28
+ this.logger.log(`Pub/Sub trigger initialized in ${this.initializer.toFixed(2)}ms`);
29
+ this.app_cold_start.record(this.initializer, {
30
+ pid: process.pid,
31
+ env: process.env.NODE_ENV,
32
+ app: process.env.APP_NAME,
33
+ });
34
+ span.end();
35
+ });
36
+ }
37
+ }
38
+
39
+ if (process.env.DISABLE_TRIGGER_RUN !== "true") {
40
+ new App().run();
41
+ }
@@ -0,0 +1,39 @@
1
+ import { GCPPubSubAdapter, PubSubTrigger } from "@blokjs/trigger-pubsub";
2
+ import nodes from "../Nodes";
3
+ import workflows from "../Workflows";
4
+
5
+ /**
6
+ * PubSubServer - Concrete Pub/Sub trigger implementation
7
+ *
8
+ * This server extends the abstract PubSubTrigger and provides:
9
+ * - A specific adapter (GCP Pub/Sub by default, can be changed to AWS or Azure)
10
+ * - Node and workflow registries
11
+ *
12
+ * To change the provider, replace:
13
+ * - GCPPubSubAdapter with AWSSNSAdapter or AzureServiceBusAdapter
14
+ * - Update the adapter configuration accordingly
15
+ *
16
+ * @example AWS SNS/SQS
17
+ * ```typescript
18
+ * import { AWSSNSAdapter } from "@blokjs/trigger-pubsub";
19
+ * protected adapter = new AWSSNSAdapter({
20
+ * region: process.env.AWS_REGION || "us-east-1",
21
+ * });
22
+ * ```
23
+ *
24
+ * @example Azure Service Bus
25
+ * ```typescript
26
+ * import { AzureServiceBusAdapter } from "@blokjs/trigger-pubsub";
27
+ * protected adapter = new AzureServiceBusAdapter({
28
+ * connectionString: process.env.AZURE_SERVICE_BUS_CONNECTION_STRING || "",
29
+ * });
30
+ * ```
31
+ */
32
+ export default class PubSubServer extends PubSubTrigger {
33
+ protected adapter = new GCPPubSubAdapter({
34
+ projectId: process.env.GCP_PROJECT_ID || "my-project",
35
+ });
36
+
37
+ protected nodes = nodes;
38
+ protected workflows = workflows;
39
+ }
@@ -0,0 +1,7 @@
1
+ import type { HelperResponse } from "@blokjs/helper";
2
+
3
+ type Workflows = {
4
+ [key: string]: HelperResponse;
5
+ };
6
+
7
+ export default Workflows;
@@ -0,0 +1,44 @@
1
+ import { type Step, Workflow } from "@blokjs/helper";
2
+
3
+ /**
4
+ * Example Pub/Sub workflow - triggered when a message is received
5
+ *
6
+ * The message data is available in ctx.request:
7
+ * - ctx.request.body: The message payload
8
+ * - ctx.request.headers: Message attributes
9
+ * - ctx.request.params.topic: The topic name
10
+ * - ctx.request.params.subscription: The subscription name
11
+ * - ctx.request.params.messageId: Unique message ID
12
+ *
13
+ * Additional metadata is available in ctx.vars._pubsub_message:
14
+ * - topic: Topic name
15
+ * - subscription: Subscription name
16
+ * - publishTime: When the message was published (ISO string)
17
+ * - attributes: JSON string of message attributes
18
+ */
19
+ const step: Step = Workflow({
20
+ name: "On Pub/Sub Message",
21
+ version: "1.0.0",
22
+ description: "Handles incoming Pub/Sub messages",
23
+ })
24
+ .addTrigger("pubsub", {
25
+ provider: "gcp",
26
+ topic: "my-topic",
27
+ subscription: "my-subscription",
28
+ })
29
+ .addStep({
30
+ name: "log-message",
31
+ node: "@blokjs/api-call",
32
+ type: "module",
33
+ inputs: {
34
+ url: "https://httpbin.org/post",
35
+ method: "POST",
36
+ body: {
37
+ message: "js/ctx.request.body",
38
+ topic: "js/ctx.request.params.topic",
39
+ messageId: "js/ctx.request.params.messageId",
40
+ },
41
+ },
42
+ });
43
+
44
+ export default step;