@stepflowjs/trigger-websocket 0.0.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.
@@ -0,0 +1,74 @@
1
+ import { Trigger, TriggerHandler } from '@stepflowjs/core';
2
+
3
+ interface WebSocketTriggerConfig {
4
+ /** WebSocket server URL to connect to */
5
+ url: string;
6
+ /** WebSocket sub-protocols */
7
+ protocols?: string[];
8
+ /** Custom headers for connection */
9
+ headers?: Record<string, string>;
10
+ /** Auto-reconnect on disconnect */
11
+ reconnect?: boolean;
12
+ /** Reconnect interval in milliseconds */
13
+ reconnectInterval?: number;
14
+ /** Maximum reconnect attempts */
15
+ maxReconnectAttempts?: number;
16
+ /** Message parsing type */
17
+ messageType?: "json" | "text";
18
+ }
19
+ /**
20
+ * WebSocket trigger for Stepflow workflows
21
+ *
22
+ * Connects to a WebSocket server and triggers workflows on incoming messages.
23
+ * Supports auto-reconnect with configurable retry logic.
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const trigger = new WebSocketTrigger({
28
+ * url: 'wss://api.example.com/events',
29
+ * messageType: 'json',
30
+ * reconnect: true,
31
+ * reconnectInterval: 5000,
32
+ * maxReconnectAttempts: 10,
33
+ * });
34
+ *
35
+ * await trigger.start(async (event) => {
36
+ * await stepflow.trigger('process-event', event.data);
37
+ * });
38
+ * ```
39
+ */
40
+ declare class WebSocketTrigger implements Trigger<WebSocketTriggerConfig> {
41
+ readonly config: WebSocketTriggerConfig;
42
+ readonly type = "websocket";
43
+ private ws?;
44
+ private handler?;
45
+ private reconnectAttempts;
46
+ private shouldReconnect;
47
+ private reconnectTimeout?;
48
+ constructor(config: WebSocketTriggerConfig);
49
+ /**
50
+ * Start the trigger with a handler function
51
+ * @param handler Function to call when WebSocket messages are received
52
+ */
53
+ start(handler: TriggerHandler): Promise<void>;
54
+ /**
55
+ * Connect to the WebSocket server
56
+ * @private
57
+ */
58
+ private connect;
59
+ /**
60
+ * Attempt to reconnect to the WebSocket server
61
+ * @private
62
+ */
63
+ private attemptReconnect;
64
+ /**
65
+ * Stop the trigger and close the WebSocket connection
66
+ */
67
+ stop(): Promise<void>;
68
+ /**
69
+ * Health check - returns true if WebSocket is connected
70
+ */
71
+ healthCheck(): Promise<boolean>;
72
+ }
73
+
74
+ export { WebSocketTrigger, type WebSocketTriggerConfig };
package/dist/index.js ADDED
@@ -0,0 +1,138 @@
1
+ // src/index.ts
2
+ import WebSocket from "ws";
3
+ var WebSocketTrigger = class {
4
+ constructor(config) {
5
+ this.config = config;
6
+ }
7
+ type = "websocket";
8
+ ws;
9
+ handler;
10
+ reconnectAttempts = 0;
11
+ shouldReconnect = true;
12
+ reconnectTimeout;
13
+ /**
14
+ * Start the trigger with a handler function
15
+ * @param handler Function to call when WebSocket messages are received
16
+ */
17
+ async start(handler) {
18
+ this.handler = handler;
19
+ this.shouldReconnect = true;
20
+ await this.connect();
21
+ }
22
+ /**
23
+ * Connect to the WebSocket server
24
+ * @private
25
+ */
26
+ async connect() {
27
+ return new Promise((resolve, reject) => {
28
+ try {
29
+ const options = {};
30
+ if (this.config.headers) {
31
+ options.headers = this.config.headers;
32
+ }
33
+ this.ws = new WebSocket(
34
+ this.config.url,
35
+ this.config.protocols,
36
+ options
37
+ );
38
+ this.ws.on("open", () => {
39
+ this.reconnectAttempts = 0;
40
+ resolve();
41
+ });
42
+ this.ws.on("message", async (rawData) => {
43
+ try {
44
+ let data;
45
+ const messageStr = rawData.toString();
46
+ if (this.config.messageType === "text") {
47
+ data = messageStr;
48
+ } else {
49
+ try {
50
+ data = JSON.parse(messageStr);
51
+ } catch (error) {
52
+ console.error(
53
+ "Failed to parse WebSocket message as JSON:",
54
+ error
55
+ );
56
+ data = messageStr;
57
+ }
58
+ }
59
+ const triggerEvent = {
60
+ id: crypto.randomUUID(),
61
+ type: this.type,
62
+ source: this.config.url,
63
+ data,
64
+ metadata: {
65
+ messageType: this.config.messageType || "json"
66
+ },
67
+ timestamp: /* @__PURE__ */ new Date()
68
+ };
69
+ await this.handler?.(triggerEvent);
70
+ } catch (error) {
71
+ console.error("Error handling WebSocket message:", error);
72
+ }
73
+ });
74
+ this.ws.on("close", () => {
75
+ if (this.shouldReconnect && this.config.reconnect !== false) {
76
+ this.attemptReconnect();
77
+ }
78
+ });
79
+ this.ws.on("error", (error) => {
80
+ console.error("WebSocket error:", error);
81
+ if (this.reconnectAttempts === 0) {
82
+ reject(error);
83
+ }
84
+ });
85
+ } catch (error) {
86
+ reject(error);
87
+ }
88
+ });
89
+ }
90
+ /**
91
+ * Attempt to reconnect to the WebSocket server
92
+ * @private
93
+ */
94
+ attemptReconnect() {
95
+ const maxAttempts = this.config.maxReconnectAttempts ?? 10;
96
+ const interval = this.config.reconnectInterval ?? 5e3;
97
+ if (this.reconnectAttempts < maxAttempts) {
98
+ this.reconnectAttempts++;
99
+ console.log(
100
+ `WebSocket reconnect attempt ${this.reconnectAttempts}/${maxAttempts}`
101
+ );
102
+ this.reconnectTimeout = setTimeout(() => {
103
+ this.connect().catch((error) => {
104
+ console.error("WebSocket reconnect failed:", error);
105
+ });
106
+ }, interval);
107
+ } else {
108
+ console.error(
109
+ `WebSocket max reconnect attempts (${maxAttempts}) reached`
110
+ );
111
+ }
112
+ }
113
+ /**
114
+ * Stop the trigger and close the WebSocket connection
115
+ */
116
+ async stop() {
117
+ this.shouldReconnect = false;
118
+ this.handler = void 0;
119
+ if (this.reconnectTimeout) {
120
+ clearTimeout(this.reconnectTimeout);
121
+ this.reconnectTimeout = void 0;
122
+ }
123
+ if (this.ws) {
124
+ this.ws.close();
125
+ this.ws = void 0;
126
+ }
127
+ }
128
+ /**
129
+ * Health check - returns true if WebSocket is connected
130
+ */
131
+ async healthCheck() {
132
+ return this.ws?.readyState === WebSocket.OPEN;
133
+ }
134
+ };
135
+ export {
136
+ WebSocketTrigger
137
+ };
138
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Trigger, TriggerHandler, TriggerEvent } from \"@stepflowjs/core\";\nimport WebSocket from \"ws\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface WebSocketTriggerConfig {\n /** WebSocket server URL to connect to */\n url: string;\n /** WebSocket sub-protocols */\n protocols?: string[];\n /** Custom headers for connection */\n headers?: Record<string, string>;\n /** Auto-reconnect on disconnect */\n reconnect?: boolean;\n /** Reconnect interval in milliseconds */\n reconnectInterval?: number;\n /** Maximum reconnect attempts */\n maxReconnectAttempts?: number;\n /** Message parsing type */\n messageType?: \"json\" | \"text\";\n}\n\n// ============================================================================\n// WebSocketTrigger Implementation\n// ============================================================================\n\n/**\n * WebSocket trigger for Stepflow workflows\n *\n * Connects to a WebSocket server and triggers workflows on incoming messages.\n * Supports auto-reconnect with configurable retry logic.\n *\n * @example\n * ```typescript\n * const trigger = new WebSocketTrigger({\n * url: 'wss://api.example.com/events',\n * messageType: 'json',\n * reconnect: true,\n * reconnectInterval: 5000,\n * maxReconnectAttempts: 10,\n * });\n *\n * await trigger.start(async (event) => {\n * await stepflow.trigger('process-event', event.data);\n * });\n * ```\n */\nexport class WebSocketTrigger implements Trigger<WebSocketTriggerConfig> {\n readonly type = \"websocket\";\n private ws?: WebSocket;\n private handler?: TriggerHandler;\n private reconnectAttempts = 0;\n private shouldReconnect = true;\n private reconnectTimeout?: NodeJS.Timeout;\n\n constructor(readonly config: WebSocketTriggerConfig) {}\n\n /**\n * Start the trigger with a handler function\n * @param handler Function to call when WebSocket messages are received\n */\n async start(handler: TriggerHandler): Promise<void> {\n this.handler = handler;\n this.shouldReconnect = true;\n await this.connect();\n }\n\n /**\n * Connect to the WebSocket server\n * @private\n */\n private async connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n // Create WebSocket connection\n const options: WebSocket.ClientOptions = {};\n if (this.config.headers) {\n options.headers = this.config.headers;\n }\n\n this.ws = new WebSocket(\n this.config.url,\n this.config.protocols,\n options,\n );\n\n // Handle connection open\n this.ws.on(\"open\", () => {\n this.reconnectAttempts = 0;\n resolve();\n });\n\n // Handle incoming messages\n this.ws.on(\"message\", async (rawData: WebSocket.RawData) => {\n try {\n let data: unknown;\n\n // Parse message based on messageType\n const messageStr = rawData.toString();\n if (this.config.messageType === \"text\") {\n data = messageStr;\n } else {\n // Default to JSON parsing\n try {\n data = JSON.parse(messageStr);\n } catch (error) {\n console.error(\n \"Failed to parse WebSocket message as JSON:\",\n error,\n );\n data = messageStr;\n }\n }\n\n // Create trigger event\n const triggerEvent: TriggerEvent = {\n id: crypto.randomUUID(),\n type: this.type,\n source: this.config.url,\n data,\n metadata: {\n messageType: this.config.messageType || \"json\",\n },\n timestamp: new Date(),\n };\n\n // Invoke handler\n await this.handler?.(triggerEvent);\n } catch (error) {\n console.error(\"Error handling WebSocket message:\", error);\n }\n });\n\n // Handle connection close\n this.ws.on(\"close\", () => {\n if (this.shouldReconnect && this.config.reconnect !== false) {\n this.attemptReconnect();\n }\n });\n\n // Handle errors\n this.ws.on(\"error\", (error) => {\n console.error(\"WebSocket error:\", error);\n // If connection fails during initial connect, reject the promise\n if (this.reconnectAttempts === 0) {\n reject(error);\n }\n });\n } catch (error) {\n reject(error);\n }\n });\n }\n\n /**\n * Attempt to reconnect to the WebSocket server\n * @private\n */\n private attemptReconnect(): void {\n const maxAttempts = this.config.maxReconnectAttempts ?? 10;\n const interval = this.config.reconnectInterval ?? 5000;\n\n if (this.reconnectAttempts < maxAttempts) {\n this.reconnectAttempts++;\n console.log(\n `WebSocket reconnect attempt ${this.reconnectAttempts}/${maxAttempts}`,\n );\n\n this.reconnectTimeout = setTimeout(() => {\n this.connect().catch((error) => {\n console.error(\"WebSocket reconnect failed:\", error);\n });\n }, interval);\n } else {\n console.error(\n `WebSocket max reconnect attempts (${maxAttempts}) reached`,\n );\n }\n }\n\n /**\n * Stop the trigger and close the WebSocket connection\n */\n async stop(): Promise<void> {\n this.shouldReconnect = false;\n this.handler = undefined;\n\n // Clear any pending reconnect timeout\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = undefined;\n }\n\n // Close WebSocket connection\n if (this.ws) {\n this.ws.close();\n this.ws = undefined;\n }\n }\n\n /**\n * Health check - returns true if WebSocket is connected\n */\n async healthCheck(): Promise<boolean> {\n return this.ws?.readyState === WebSocket.OPEN;\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@stepflowjs/trigger-websocket",
3
+ "version": "0.0.1",
4
+ "description": "WebSocket trigger for Stepflow with auto-reconnect",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "dependencies": {
19
+ "ws": "^8.18.0",
20
+ "@stepflowjs/core": "0.0.1"
21
+ },
22
+ "devDependencies": {
23
+ "@types/ws": "^8.5.0",
24
+ "tsup": "^8.5.1",
25
+ "vitest": "^4.0.17"
26
+ },
27
+ "peerDependencies": {
28
+ "typescript": "^5.0.0"
29
+ },
30
+ "license": "MIT",
31
+ "author": "Stepflow Contributors",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://stepflow-production.up.railway.app",
35
+ "directory": "packages/triggers/websocket"
36
+ },
37
+ "homepage": "https://stepflow-production.up.railway.app",
38
+ "bugs": {
39
+ "url": "https://stepflow-production.up.railway.app"
40
+ },
41
+ "keywords": [
42
+ "stepflow",
43
+ "trigger",
44
+ "websocket",
45
+ "realtime",
46
+ "workflow",
47
+ "orchestration"
48
+ ],
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "scripts": {
53
+ "build": "tsup",
54
+ "dev": "tsup --watch",
55
+ "typecheck": "tsc --noEmit",
56
+ "test": "vitest",
57
+ "clean": "rm -rf dist"
58
+ }
59
+ }