@furlow/pipes 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/dist/index.js ADDED
@@ -0,0 +1,1282 @@
1
+ // src/http/index.ts
2
+ import axios from "axios";
3
+ var HttpPipe = class {
4
+ name;
5
+ type = "http";
6
+ client;
7
+ config;
8
+ requestCount = 0;
9
+ windowStart = Date.now();
10
+ constructor(options) {
11
+ this.name = options.name;
12
+ this.config = options.config;
13
+ this.client = axios.create({
14
+ baseURL: options.config.base_url,
15
+ timeout: this.parseDuration(options.config.timeout ?? "30s"),
16
+ headers: options.config.headers
17
+ });
18
+ if (options.config.auth) {
19
+ this.client.interceptors.request.use((config) => {
20
+ const auth = options.config.auth;
21
+ switch (auth.type) {
22
+ case "bearer":
23
+ config.headers.Authorization = `Bearer ${auth.token}`;
24
+ break;
25
+ case "basic":
26
+ const credentials = Buffer.from(`${auth.username}:${auth.password}`).toString("base64");
27
+ config.headers.Authorization = `Basic ${credentials}`;
28
+ break;
29
+ case "header":
30
+ const headerName = auth.header_name ?? "Authorization";
31
+ config.headers[headerName] = auth.token;
32
+ break;
33
+ }
34
+ return config;
35
+ });
36
+ }
37
+ }
38
+ isConnected() {
39
+ return true;
40
+ }
41
+ /**
42
+ * Make an HTTP request
43
+ */
44
+ async request(method, path, options = {}) {
45
+ if (this.config.rate_limit) {
46
+ const now = Date.now();
47
+ const windowMs = this.parseDuration(this.config.rate_limit.per);
48
+ if (now - this.windowStart >= windowMs) {
49
+ this.requestCount = 0;
50
+ this.windowStart = now;
51
+ }
52
+ if (this.requestCount >= this.config.rate_limit.requests) {
53
+ return {
54
+ success: false,
55
+ error: "Rate limit exceeded",
56
+ status: 429
57
+ };
58
+ }
59
+ this.requestCount++;
60
+ }
61
+ const config = {
62
+ method,
63
+ url: path,
64
+ data: options.body,
65
+ headers: options.headers,
66
+ params: options.params
67
+ };
68
+ try {
69
+ const response = await this.requestWithRetry(config);
70
+ return {
71
+ success: true,
72
+ data: response.data,
73
+ status: response.status,
74
+ headers: response.headers
75
+ };
76
+ } catch (error) {
77
+ if (axios.isAxiosError(error)) {
78
+ return {
79
+ success: false,
80
+ error: error.message,
81
+ status: error.response?.status,
82
+ data: error.response?.data
83
+ };
84
+ }
85
+ return {
86
+ success: false,
87
+ error: error instanceof Error ? error.message : "Unknown error"
88
+ };
89
+ }
90
+ }
91
+ /**
92
+ * Make a request with retry logic
93
+ */
94
+ async requestWithRetry(config, attempt = 0) {
95
+ try {
96
+ return await this.client.request(config);
97
+ } catch (error) {
98
+ const maxAttempts = this.config.retry?.attempts ?? 0;
99
+ const delay = this.parseDuration(this.config.retry?.delay ?? "1s");
100
+ if (attempt < maxAttempts && axios.isAxiosError(error)) {
101
+ if (!error.response || error.response.status >= 500) {
102
+ await new Promise((resolve) => setTimeout(resolve, delay));
103
+ return this.requestWithRetry(config, attempt + 1);
104
+ }
105
+ }
106
+ throw error;
107
+ }
108
+ }
109
+ /**
110
+ * Convenience methods
111
+ */
112
+ async get(path, options) {
113
+ return this.request("GET", path, options);
114
+ }
115
+ async post(path, body, options) {
116
+ return this.request("POST", path, { body, ...options });
117
+ }
118
+ async put(path, body, options) {
119
+ return this.request("PUT", path, { body, ...options });
120
+ }
121
+ async patch(path, body, options) {
122
+ return this.request("PATCH", path, { body, ...options });
123
+ }
124
+ async delete(path, options) {
125
+ return this.request("DELETE", path, options);
126
+ }
127
+ /**
128
+ * Parse duration string to milliseconds
129
+ */
130
+ parseDuration(duration) {
131
+ const match = duration.match(/^(\d+)(ms|s|m|h)?$/);
132
+ if (!match) return 3e4;
133
+ const value = parseInt(match[1], 10);
134
+ const unit = match[2] ?? "s";
135
+ switch (unit) {
136
+ case "ms":
137
+ return value;
138
+ case "s":
139
+ return value * 1e3;
140
+ case "m":
141
+ return value * 60 * 1e3;
142
+ case "h":
143
+ return value * 60 * 60 * 1e3;
144
+ default:
145
+ return value * 1e3;
146
+ }
147
+ }
148
+ };
149
+ function createHttpPipe(options) {
150
+ return new HttpPipe(options);
151
+ }
152
+
153
+ // src/websocket/index.ts
154
+ import WebSocket from "ws";
155
+ var WebSocketPipe = class {
156
+ name;
157
+ type = "websocket";
158
+ ws = null;
159
+ config;
160
+ reconnectAttempts = 0;
161
+ reconnecting = false;
162
+ heartbeatInterval = null;
163
+ messageHandlers = /* @__PURE__ */ new Map();
164
+ connected = false;
165
+ constructor(options) {
166
+ this.name = options.name;
167
+ this.config = options.config;
168
+ }
169
+ /**
170
+ * Connect to the WebSocket server
171
+ */
172
+ async connect() {
173
+ return new Promise((resolve, reject) => {
174
+ try {
175
+ const headers = this.config.headers;
176
+ this.ws = new WebSocket(this.config.url, { headers });
177
+ this.ws.on("open", () => {
178
+ this.connected = true;
179
+ this.reconnectAttempts = 0;
180
+ this.reconnecting = false;
181
+ this.startHeartbeat();
182
+ resolve();
183
+ });
184
+ this.ws.on("message", (data) => {
185
+ this.handleMessage(data);
186
+ });
187
+ this.ws.on("close", () => {
188
+ this.connected = false;
189
+ this.stopHeartbeat();
190
+ this.handleDisconnect();
191
+ });
192
+ this.ws.on("error", (error) => {
193
+ if (!this.connected) {
194
+ reject(error);
195
+ }
196
+ this.emit("error", error);
197
+ });
198
+ } catch (error) {
199
+ reject(error);
200
+ }
201
+ });
202
+ }
203
+ /**
204
+ * Disconnect from the server
205
+ */
206
+ async disconnect() {
207
+ this.stopHeartbeat();
208
+ this.reconnecting = false;
209
+ if (this.ws) {
210
+ this.ws.close();
211
+ this.ws = null;
212
+ }
213
+ this.connected = false;
214
+ }
215
+ /**
216
+ * Check if connected
217
+ */
218
+ isConnected() {
219
+ return this.connected && this.ws?.readyState === WebSocket.OPEN;
220
+ }
221
+ /**
222
+ * Send a message
223
+ */
224
+ send(data) {
225
+ if (!this.isConnected()) {
226
+ return false;
227
+ }
228
+ const message = typeof data === "string" ? data : JSON.stringify(data);
229
+ this.ws.send(message);
230
+ return true;
231
+ }
232
+ /**
233
+ * Send and wait for a response (request-response pattern)
234
+ */
235
+ async request(data, options = {}) {
236
+ const { timeout = 3e4, responseEvent = "response" } = options;
237
+ if (!this.isConnected()) {
238
+ return { success: false, error: "Not connected" };
239
+ }
240
+ return new Promise((resolve) => {
241
+ const timer = setTimeout(() => {
242
+ this.off(responseEvent, handler);
243
+ resolve({ success: false, error: "Request timeout" });
244
+ }, timeout);
245
+ const handler = (response) => {
246
+ clearTimeout(timer);
247
+ this.off(responseEvent, handler);
248
+ resolve({ success: true, data: response });
249
+ };
250
+ this.on(responseEvent, handler);
251
+ this.send(data);
252
+ });
253
+ }
254
+ /**
255
+ * Register a message handler
256
+ */
257
+ on(event, handler) {
258
+ const handlers = this.messageHandlers.get(event) ?? [];
259
+ handlers.push(handler);
260
+ this.messageHandlers.set(event, handlers);
261
+ }
262
+ /**
263
+ * Remove a message handler
264
+ */
265
+ off(event, handler) {
266
+ const handlers = this.messageHandlers.get(event) ?? [];
267
+ const index = handlers.indexOf(handler);
268
+ if (index !== -1) {
269
+ handlers.splice(index, 1);
270
+ }
271
+ }
272
+ /**
273
+ * Emit an event to handlers
274
+ */
275
+ emit(event, data) {
276
+ const handlers = this.messageHandlers.get(event) ?? [];
277
+ for (const handler of handlers) {
278
+ try {
279
+ handler(data);
280
+ } catch (error) {
281
+ console.error(`WebSocket handler error for "${event}":`, error);
282
+ }
283
+ }
284
+ }
285
+ /**
286
+ * Handle incoming messages
287
+ */
288
+ handleMessage(rawData) {
289
+ let data;
290
+ try {
291
+ const str = rawData.toString();
292
+ data = JSON.parse(str);
293
+ } catch {
294
+ data = rawData.toString();
295
+ }
296
+ this.emit("message", data);
297
+ if (typeof data === "object" && data !== null && "event" in data) {
298
+ const event = data.event;
299
+ this.emit(event, data);
300
+ }
301
+ }
302
+ /**
303
+ * Handle disconnection
304
+ */
305
+ handleDisconnect() {
306
+ if (!this.config.reconnect?.enabled || this.reconnecting) {
307
+ return;
308
+ }
309
+ const maxAttempts = this.config.reconnect.max_attempts ?? 10;
310
+ const delay = this.parseDuration(this.config.reconnect.delay ?? "5s");
311
+ if (this.reconnectAttempts >= maxAttempts) {
312
+ this.emit("reconnect_failed", { attempts: this.reconnectAttempts });
313
+ return;
314
+ }
315
+ this.reconnecting = true;
316
+ this.reconnectAttempts++;
317
+ setTimeout(async () => {
318
+ try {
319
+ await this.connect();
320
+ this.emit("reconnected", { attempts: this.reconnectAttempts });
321
+ } catch {
322
+ this.reconnecting = false;
323
+ this.handleDisconnect();
324
+ }
325
+ }, delay);
326
+ }
327
+ /**
328
+ * Start heartbeat
329
+ */
330
+ startHeartbeat() {
331
+ if (!this.config.heartbeat?.interval) return;
332
+ const interval = this.parseDuration(this.config.heartbeat.interval);
333
+ const message = this.config.heartbeat.message ?? "ping";
334
+ this.heartbeatInterval = setInterval(() => {
335
+ if (this.isConnected()) {
336
+ this.send(message);
337
+ }
338
+ }, interval);
339
+ }
340
+ /**
341
+ * Stop heartbeat
342
+ */
343
+ stopHeartbeat() {
344
+ if (this.heartbeatInterval) {
345
+ clearInterval(this.heartbeatInterval);
346
+ this.heartbeatInterval = null;
347
+ }
348
+ }
349
+ /**
350
+ * Parse duration string to milliseconds
351
+ */
352
+ parseDuration(duration) {
353
+ const match = duration.match(/^(\d+)(ms|s|m|h)?$/);
354
+ if (!match) return 5e3;
355
+ const value = parseInt(match[1], 10);
356
+ const unit = match[2] ?? "s";
357
+ switch (unit) {
358
+ case "ms":
359
+ return value;
360
+ case "s":
361
+ return value * 1e3;
362
+ case "m":
363
+ return value * 60 * 1e3;
364
+ case "h":
365
+ return value * 60 * 60 * 1e3;
366
+ default:
367
+ return value * 1e3;
368
+ }
369
+ }
370
+ };
371
+ function createWebSocketPipe(options) {
372
+ return new WebSocketPipe(options);
373
+ }
374
+
375
+ // src/webhook/index.ts
376
+ import { createHmac } from "crypto";
377
+ var WebhookPipe = class {
378
+ name;
379
+ type = "webhook";
380
+ config;
381
+ handlers = [];
382
+ constructor(options) {
383
+ this.name = options.name;
384
+ this.config = options.config;
385
+ }
386
+ isConnected() {
387
+ return true;
388
+ }
389
+ /**
390
+ * Get the webhook path
391
+ */
392
+ getPath() {
393
+ return this.config.path;
394
+ }
395
+ /**
396
+ * Get the expected method
397
+ */
398
+ getMethod() {
399
+ return this.config.method ?? "POST";
400
+ }
401
+ /**
402
+ * Register a handler for incoming webhooks
403
+ */
404
+ onWebhook(handler) {
405
+ this.handlers.push(handler);
406
+ return () => {
407
+ const index = this.handlers.indexOf(handler);
408
+ if (index !== -1) {
409
+ this.handlers.splice(index, 1);
410
+ }
411
+ };
412
+ }
413
+ /**
414
+ * Handle an incoming webhook request
415
+ */
416
+ async handleRequest(body, headers) {
417
+ if (this.config.verification) {
418
+ const valid = this.verifySignature(body, headers);
419
+ if (!valid) {
420
+ return { success: false, error: "Invalid signature", status: 401 };
421
+ }
422
+ }
423
+ for (const handler of this.handlers) {
424
+ try {
425
+ await handler(body, headers);
426
+ } catch (error) {
427
+ console.error(`Webhook handler error:`, error);
428
+ }
429
+ }
430
+ if (this.config.handlers) {
431
+ }
432
+ return { success: true, status: 200 };
433
+ }
434
+ /**
435
+ * Verify webhook signature
436
+ */
437
+ verifySignature(body, headers) {
438
+ if (!this.config.verification) return true;
439
+ const { type, secret, header, algorithm } = this.config.verification;
440
+ const signatureHeader = header ?? "x-signature";
441
+ const receivedSignature = headers[signatureHeader.toLowerCase()];
442
+ if (!receivedSignature || !secret) {
443
+ return false;
444
+ }
445
+ const bodyString = typeof body === "string" ? body : JSON.stringify(body);
446
+ switch (type) {
447
+ case "hmac": {
448
+ const algo = algorithm ?? "sha256";
449
+ const expectedSignature = createHmac(algo, secret).update(bodyString).digest("hex");
450
+ const cleanReceived = receivedSignature.replace(/^sha\d+=/, "");
451
+ return this.timingSafeEqual(cleanReceived, expectedSignature);
452
+ }
453
+ case "token": {
454
+ return receivedSignature === secret;
455
+ }
456
+ case "signature": {
457
+ return true;
458
+ }
459
+ default:
460
+ return true;
461
+ }
462
+ }
463
+ /**
464
+ * Timing-safe string comparison
465
+ */
466
+ timingSafeEqual(a, b) {
467
+ if (a.length !== b.length) {
468
+ return false;
469
+ }
470
+ let result = 0;
471
+ for (let i = 0; i < a.length; i++) {
472
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
473
+ }
474
+ return result === 0;
475
+ }
476
+ };
477
+ var WebhookSender = class {
478
+ /**
479
+ * Send a Discord webhook
480
+ */
481
+ static async sendDiscordWebhook(url, options) {
482
+ try {
483
+ const response = await fetch(url, {
484
+ method: "POST",
485
+ headers: { "Content-Type": "application/json" },
486
+ body: JSON.stringify(options)
487
+ });
488
+ if (!response.ok) {
489
+ return {
490
+ success: false,
491
+ error: `HTTP ${response.status}`,
492
+ status: response.status
493
+ };
494
+ }
495
+ return { success: true, status: response.status };
496
+ } catch (error) {
497
+ return {
498
+ success: false,
499
+ error: error instanceof Error ? error.message : "Unknown error"
500
+ };
501
+ }
502
+ }
503
+ /**
504
+ * Send a generic webhook
505
+ */
506
+ static async send(url, body, options = {}) {
507
+ try {
508
+ const response = await fetch(url, {
509
+ method: options.method ?? "POST",
510
+ headers: {
511
+ "Content-Type": "application/json",
512
+ ...options.headers
513
+ },
514
+ body: typeof body === "string" ? body : JSON.stringify(body)
515
+ });
516
+ let data;
517
+ try {
518
+ data = await response.json();
519
+ } catch {
520
+ data = await response.text();
521
+ }
522
+ return {
523
+ success: response.ok,
524
+ data,
525
+ status: response.status
526
+ };
527
+ } catch (error) {
528
+ return {
529
+ success: false,
530
+ error: error instanceof Error ? error.message : "Unknown error"
531
+ };
532
+ }
533
+ }
534
+ };
535
+ function createWebhookPipe(options) {
536
+ return new WebhookPipe(options);
537
+ }
538
+
539
+ // src/mqtt/index.ts
540
+ import mqtt from "mqtt";
541
+ var MqttPipe = class {
542
+ name;
543
+ type = "mqtt";
544
+ client = null;
545
+ config;
546
+ connected = false;
547
+ reconnectAttempts = 0;
548
+ messageHandlers = /* @__PURE__ */ new Map();
549
+ wildcardHandlers = /* @__PURE__ */ new Map();
550
+ constructor(options) {
551
+ this.name = options.name;
552
+ this.config = options.config;
553
+ }
554
+ /**
555
+ * Connect to the MQTT broker
556
+ */
557
+ async connect() {
558
+ return new Promise((resolve, reject) => {
559
+ const protocol = this.config.protocol ?? "mqtt";
560
+ const port = this.config.port ?? (protocol === "mqtts" || protocol === "wss" ? 8883 : 1883);
561
+ const url = `${protocol}://${this.config.broker}:${port}`;
562
+ const options = {
563
+ keepalive: this.config.keepalive ?? 60,
564
+ clean: this.config.clean ?? true,
565
+ reconnectPeriod: this.config.reconnect?.enabled !== false ? this.parseDuration(this.config.reconnect?.delay ?? "5s") : 0
566
+ };
567
+ if (this.config.auth) {
568
+ if (this.config.auth.username) options.username = this.config.auth.username;
569
+ if (this.config.auth.password) options.password = this.config.auth.password;
570
+ if (this.config.auth.clientId) options.clientId = this.config.auth.clientId;
571
+ }
572
+ if (this.config.will) {
573
+ options.will = {
574
+ topic: this.config.will.topic,
575
+ payload: Buffer.from(this.config.will.payload),
576
+ qos: this.config.will.qos ?? 0,
577
+ retain: this.config.will.retain ?? false
578
+ };
579
+ }
580
+ try {
581
+ this.client = mqtt.connect(url, options);
582
+ this.client.on("connect", () => {
583
+ this.connected = true;
584
+ this.reconnectAttempts = 0;
585
+ this.emit("connected", {});
586
+ resolve();
587
+ });
588
+ this.client.on("message", (topic, payload, packet) => {
589
+ this.handleMessage(topic, payload, packet);
590
+ });
591
+ this.client.on("error", (error) => {
592
+ if (!this.connected) {
593
+ reject(error);
594
+ }
595
+ this.emit("error", error);
596
+ });
597
+ this.client.on("close", () => {
598
+ this.connected = false;
599
+ this.emit("disconnected", {});
600
+ });
601
+ this.client.on("reconnect", () => {
602
+ this.reconnectAttempts++;
603
+ this.emit("reconnecting", { attempts: this.reconnectAttempts });
604
+ });
605
+ this.client.on("offline", () => {
606
+ this.connected = false;
607
+ this.emit("offline", {});
608
+ });
609
+ } catch (error) {
610
+ reject(error);
611
+ }
612
+ });
613
+ }
614
+ /**
615
+ * Disconnect from the broker
616
+ */
617
+ async disconnect() {
618
+ return new Promise((resolve) => {
619
+ if (this.client) {
620
+ this.client.end(false, {}, () => {
621
+ this.client = null;
622
+ this.connected = false;
623
+ resolve();
624
+ });
625
+ } else {
626
+ resolve();
627
+ }
628
+ });
629
+ }
630
+ /**
631
+ * Check if connected
632
+ */
633
+ isConnected() {
634
+ return this.connected && this.client?.connected === true;
635
+ }
636
+ /**
637
+ * Subscribe to a topic
638
+ */
639
+ async subscribe(topic, options = {}) {
640
+ if (!this.isConnected()) {
641
+ return { success: false, error: "Not connected" };
642
+ }
643
+ return new Promise((resolve) => {
644
+ this.client.subscribe(topic, { qos: options.qos ?? 0 }, (error, granted) => {
645
+ if (error) {
646
+ resolve({ success: false, error: error.message });
647
+ } else {
648
+ resolve({ success: true, data: granted });
649
+ }
650
+ });
651
+ });
652
+ }
653
+ /**
654
+ * Unsubscribe from a topic
655
+ */
656
+ async unsubscribe(topic) {
657
+ if (!this.isConnected()) {
658
+ return { success: false, error: "Not connected" };
659
+ }
660
+ return new Promise((resolve) => {
661
+ this.client.unsubscribe(topic, {}, (error) => {
662
+ if (error) {
663
+ resolve({ success: false, error: error.message });
664
+ } else {
665
+ resolve({ success: true });
666
+ }
667
+ });
668
+ });
669
+ }
670
+ /**
671
+ * Publish a message to a topic
672
+ */
673
+ async publish(topic, message, options = {}) {
674
+ if (!this.isConnected()) {
675
+ return { success: false, error: "Not connected" };
676
+ }
677
+ const payload = typeof message === "object" && !Buffer.isBuffer(message) ? JSON.stringify(message) : message;
678
+ const publishOptions = {
679
+ qos: options.qos ?? 0,
680
+ retain: options.retain ?? false
681
+ };
682
+ return new Promise((resolve) => {
683
+ this.client.publish(topic, payload, publishOptions, (error) => {
684
+ if (error) {
685
+ resolve({ success: false, error: error.message });
686
+ } else {
687
+ resolve({ success: true });
688
+ }
689
+ });
690
+ });
691
+ }
692
+ /**
693
+ * Register a handler for a specific topic
694
+ * Supports MQTT wildcards: + (single level), # (multi level)
695
+ */
696
+ on(topic, handler) {
697
+ if (topic.includes("+") || topic.includes("#")) {
698
+ const handlers = this.wildcardHandlers.get(topic) ?? [];
699
+ handlers.push(handler);
700
+ this.wildcardHandlers.set(topic, handlers);
701
+ } else {
702
+ const handlers = this.messageHandlers.get(topic) ?? [];
703
+ handlers.push(handler);
704
+ this.messageHandlers.set(topic, handlers);
705
+ }
706
+ }
707
+ /**
708
+ * Remove a handler
709
+ */
710
+ off(topic, handler) {
711
+ const map = topic.includes("+") || topic.includes("#") ? this.wildcardHandlers : this.messageHandlers;
712
+ const handlers = map.get(topic) ?? [];
713
+ const index = handlers.indexOf(handler);
714
+ if (index !== -1) {
715
+ handlers.splice(index, 1);
716
+ }
717
+ }
718
+ /**
719
+ * Emit to handlers
720
+ */
721
+ emit(event, data) {
722
+ const handlers = this.messageHandlers.get(event) ?? [];
723
+ for (const handler of handlers) {
724
+ try {
725
+ handler(event, data, {});
726
+ } catch (error) {
727
+ console.error(`MQTT handler error for "${event}":`, error);
728
+ }
729
+ }
730
+ }
731
+ /**
732
+ * Handle incoming messages
733
+ */
734
+ handleMessage(topic, payload, packet) {
735
+ let parsedPayload = payload;
736
+ try {
737
+ const str = payload.toString();
738
+ parsedPayload = JSON.parse(str);
739
+ } catch {
740
+ parsedPayload = payload.toString();
741
+ }
742
+ const exactHandlers = this.messageHandlers.get(topic) ?? [];
743
+ for (const handler of exactHandlers) {
744
+ try {
745
+ handler(topic, parsedPayload, packet);
746
+ } catch (error) {
747
+ console.error(`MQTT handler error for topic "${topic}":`, error);
748
+ }
749
+ }
750
+ for (const [pattern, handlers] of this.wildcardHandlers) {
751
+ if (this.topicMatches(pattern, topic)) {
752
+ for (const handler of handlers) {
753
+ try {
754
+ handler(topic, parsedPayload, packet);
755
+ } catch (error) {
756
+ console.error(`MQTT wildcard handler error for pattern "${pattern}":`, error);
757
+ }
758
+ }
759
+ }
760
+ }
761
+ this.emit("message", { topic, payload: parsedPayload, packet });
762
+ }
763
+ /**
764
+ * Check if a topic matches a pattern with MQTT wildcards
765
+ */
766
+ topicMatches(pattern, topic) {
767
+ const patternParts = pattern.split("/");
768
+ const topicParts = topic.split("/");
769
+ for (let i = 0; i < patternParts.length; i++) {
770
+ const patternPart = patternParts[i];
771
+ if (patternPart === "#") {
772
+ return true;
773
+ }
774
+ if (i >= topicParts.length) {
775
+ return false;
776
+ }
777
+ if (patternPart === "+") {
778
+ continue;
779
+ }
780
+ if (patternPart !== topicParts[i]) {
781
+ return false;
782
+ }
783
+ }
784
+ return patternParts.length === topicParts.length;
785
+ }
786
+ /**
787
+ * Parse duration string to milliseconds
788
+ */
789
+ parseDuration(duration) {
790
+ const match = duration.match(/^(\d+)(ms|s|m|h)?$/);
791
+ if (!match) return 5e3;
792
+ const value = parseInt(match[1], 10);
793
+ const unit = match[2] ?? "s";
794
+ switch (unit) {
795
+ case "ms":
796
+ return value;
797
+ case "s":
798
+ return value * 1e3;
799
+ case "m":
800
+ return value * 60 * 1e3;
801
+ case "h":
802
+ return value * 60 * 60 * 1e3;
803
+ default:
804
+ return value * 1e3;
805
+ }
806
+ }
807
+ };
808
+ function createMqttPipe(options) {
809
+ return new MqttPipe(options);
810
+ }
811
+
812
+ // src/tcp/index.ts
813
+ import { createConnection, createServer } from "net";
814
+
815
+ // src/tcp/udp.ts
816
+ import { createSocket } from "dgram";
817
+ var UdpPipe = class {
818
+ name;
819
+ type = "udp";
820
+ socket = null;
821
+ config;
822
+ bound = false;
823
+ messageHandlers = [];
824
+ eventHandlers = /* @__PURE__ */ new Map();
825
+ constructor(options) {
826
+ this.name = options.name;
827
+ this.config = options.config;
828
+ }
829
+ /**
830
+ * Bind to a port to receive messages
831
+ */
832
+ async bind(port) {
833
+ return new Promise((resolve, reject) => {
834
+ try {
835
+ this.socket = createSocket("udp4");
836
+ this.socket.on("message", (msg, rinfo) => {
837
+ this.handleMessage(msg, rinfo);
838
+ });
839
+ this.socket.on("error", (error) => {
840
+ if (!this.bound) {
841
+ reject(error);
842
+ }
843
+ this.emit("error", error);
844
+ });
845
+ this.socket.on("listening", () => {
846
+ this.bound = true;
847
+ const address = this.socket.address();
848
+ this.emit("listening", address);
849
+ resolve();
850
+ });
851
+ if (this.config.broadcast) {
852
+ this.socket.setBroadcast(true);
853
+ }
854
+ const bindPort = port ?? this.config.port;
855
+ const bindHost = this.config.host ?? "0.0.0.0";
856
+ this.socket.bind(bindPort, bindHost, () => {
857
+ if (this.config.multicast) {
858
+ this.socket.addMembership(this.config.multicast);
859
+ }
860
+ });
861
+ } catch (error) {
862
+ reject(error);
863
+ }
864
+ });
865
+ }
866
+ /**
867
+ * Close the socket
868
+ */
869
+ async disconnect() {
870
+ return new Promise((resolve) => {
871
+ if (this.socket) {
872
+ this.socket.close(() => {
873
+ this.socket = null;
874
+ this.bound = false;
875
+ resolve();
876
+ });
877
+ } else {
878
+ resolve();
879
+ }
880
+ });
881
+ }
882
+ /**
883
+ * Check if bound
884
+ */
885
+ isConnected() {
886
+ return this.bound;
887
+ }
888
+ /**
889
+ * Send data to a specific host and port
890
+ */
891
+ async send(data, host, port) {
892
+ if (!this.socket) {
893
+ this.socket = createSocket("udp4");
894
+ if (this.config.broadcast) {
895
+ this.socket.setBroadcast(true);
896
+ }
897
+ }
898
+ const buffer = typeof data === "string" ? Buffer.from(data) : data;
899
+ return new Promise((resolve) => {
900
+ this.socket.send(buffer, port, host, (error) => {
901
+ if (error) {
902
+ resolve({ success: false, error: error.message });
903
+ } else {
904
+ resolve({ success: true });
905
+ }
906
+ });
907
+ });
908
+ }
909
+ /**
910
+ * Broadcast data to all hosts on the network
911
+ */
912
+ async broadcast(data, port, address = "255.255.255.255") {
913
+ if (!this.socket) {
914
+ this.socket = createSocket("udp4");
915
+ this.socket.setBroadcast(true);
916
+ }
917
+ const buffer = typeof data === "string" ? Buffer.from(data) : data;
918
+ return new Promise((resolve) => {
919
+ this.socket.send(buffer, port, address, (error) => {
920
+ if (error) {
921
+ resolve({ success: false, error: error.message });
922
+ } else {
923
+ resolve({ success: true });
924
+ }
925
+ });
926
+ });
927
+ }
928
+ /**
929
+ * Send to multicast group
930
+ */
931
+ async multicast(data, port, group) {
932
+ const multicastGroup = group ?? this.config.multicast;
933
+ if (!multicastGroup) {
934
+ return { success: false, error: "No multicast group configured" };
935
+ }
936
+ return this.send(data, multicastGroup, port);
937
+ }
938
+ /**
939
+ * Register a message handler
940
+ */
941
+ onMessage(handler) {
942
+ this.messageHandlers.push(handler);
943
+ }
944
+ /**
945
+ * Remove a message handler
946
+ */
947
+ offMessage(handler) {
948
+ const index = this.messageHandlers.indexOf(handler);
949
+ if (index !== -1) {
950
+ this.messageHandlers.splice(index, 1);
951
+ }
952
+ }
953
+ /**
954
+ * Register an event handler
955
+ */
956
+ on(event, handler) {
957
+ const handlers = this.eventHandlers.get(event) ?? [];
958
+ handlers.push(handler);
959
+ this.eventHandlers.set(event, handlers);
960
+ }
961
+ /**
962
+ * Remove an event handler
963
+ */
964
+ off(event, handler) {
965
+ const handlers = this.eventHandlers.get(event) ?? [];
966
+ const index = handlers.indexOf(handler);
967
+ if (index !== -1) {
968
+ handlers.splice(index, 1);
969
+ }
970
+ }
971
+ /**
972
+ * Emit an event
973
+ */
974
+ emit(event, data) {
975
+ const handlers = this.eventHandlers.get(event) ?? [];
976
+ for (const handler of handlers) {
977
+ try {
978
+ handler(data);
979
+ } catch (error) {
980
+ console.error(`UDP handler error for "${event}":`, error);
981
+ }
982
+ }
983
+ }
984
+ /**
985
+ * Handle incoming messages
986
+ */
987
+ handleMessage(data, rinfo) {
988
+ const msg = { data, rinfo };
989
+ for (const handler of this.messageHandlers) {
990
+ try {
991
+ handler(msg);
992
+ } catch (error) {
993
+ console.error("UDP message handler error:", error);
994
+ }
995
+ }
996
+ this.emit("message", msg);
997
+ }
998
+ };
999
+ function createUdpPipe(options) {
1000
+ return new UdpPipe(options);
1001
+ }
1002
+
1003
+ // src/tcp/index.ts
1004
+ var TcpPipe = class {
1005
+ name;
1006
+ type = "tcp";
1007
+ socket = null;
1008
+ server = null;
1009
+ config;
1010
+ connected = false;
1011
+ reconnectAttempts = 0;
1012
+ reconnecting = false;
1013
+ dataHandlers = [];
1014
+ eventHandlers = /* @__PURE__ */ new Map();
1015
+ constructor(options) {
1016
+ this.name = options.name;
1017
+ this.config = options.config;
1018
+ }
1019
+ /**
1020
+ * Connect to a TCP server (client mode)
1021
+ */
1022
+ async connect() {
1023
+ return new Promise((resolve, reject) => {
1024
+ try {
1025
+ this.socket = createConnection({
1026
+ host: this.config.host,
1027
+ port: this.config.port
1028
+ });
1029
+ this.socket.setEncoding(this.config.encoding ?? "utf8");
1030
+ this.socket.on("connect", () => {
1031
+ this.connected = true;
1032
+ this.reconnectAttempts = 0;
1033
+ this.reconnecting = false;
1034
+ this.emit("connected");
1035
+ resolve();
1036
+ });
1037
+ this.socket.on("data", (data) => {
1038
+ this.handleData(data);
1039
+ });
1040
+ this.socket.on("close", () => {
1041
+ this.connected = false;
1042
+ this.emit("disconnected");
1043
+ this.handleDisconnect();
1044
+ });
1045
+ this.socket.on("error", (error) => {
1046
+ if (!this.connected) {
1047
+ reject(error);
1048
+ }
1049
+ this.emit("error", error);
1050
+ });
1051
+ this.socket.on("end", () => {
1052
+ this.connected = false;
1053
+ this.emit("end");
1054
+ });
1055
+ } catch (error) {
1056
+ reject(error);
1057
+ }
1058
+ });
1059
+ }
1060
+ /**
1061
+ * Start a TCP server (server mode)
1062
+ */
1063
+ async listen(port) {
1064
+ return new Promise((resolve, reject) => {
1065
+ try {
1066
+ this.server = createServer((socket) => {
1067
+ socket.setEncoding(this.config.encoding ?? "utf8");
1068
+ socket.on("data", (data) => {
1069
+ this.handleData(data);
1070
+ });
1071
+ socket.on("error", (error) => {
1072
+ this.emit("client_error", error);
1073
+ });
1074
+ this.emit("connection", socket);
1075
+ });
1076
+ this.server.on("error", (error) => {
1077
+ reject(error);
1078
+ });
1079
+ this.server.listen(port ?? this.config.port, () => {
1080
+ this.connected = true;
1081
+ this.emit("listening");
1082
+ resolve();
1083
+ });
1084
+ } catch (error) {
1085
+ reject(error);
1086
+ }
1087
+ });
1088
+ }
1089
+ /**
1090
+ * Disconnect / close
1091
+ */
1092
+ async disconnect() {
1093
+ this.reconnecting = false;
1094
+ return new Promise((resolve) => {
1095
+ if (this.socket) {
1096
+ this.socket.end(() => {
1097
+ this.socket?.destroy();
1098
+ this.socket = null;
1099
+ this.connected = false;
1100
+ resolve();
1101
+ });
1102
+ } else if (this.server) {
1103
+ this.server.close(() => {
1104
+ this.server = null;
1105
+ this.connected = false;
1106
+ resolve();
1107
+ });
1108
+ } else {
1109
+ resolve();
1110
+ }
1111
+ });
1112
+ }
1113
+ /**
1114
+ * Check if connected
1115
+ */
1116
+ isConnected() {
1117
+ return this.connected;
1118
+ }
1119
+ /**
1120
+ * Send data
1121
+ */
1122
+ async send(data) {
1123
+ if (!this.socket || !this.connected) {
1124
+ return { success: false, error: "Not connected" };
1125
+ }
1126
+ return new Promise((resolve) => {
1127
+ this.socket.write(data, (error) => {
1128
+ if (error) {
1129
+ resolve({ success: false, error: error.message });
1130
+ } else {
1131
+ resolve({ success: true });
1132
+ }
1133
+ });
1134
+ });
1135
+ }
1136
+ /**
1137
+ * Send and wait for response (request-response pattern)
1138
+ */
1139
+ async request(data, options = {}) {
1140
+ const { timeout = 3e4 } = options;
1141
+ if (!this.socket || !this.connected) {
1142
+ return { success: false, error: "Not connected" };
1143
+ }
1144
+ return new Promise((resolve) => {
1145
+ const timer = setTimeout(() => {
1146
+ this.offData(handler);
1147
+ resolve({ success: false, error: "Request timeout" });
1148
+ }, timeout);
1149
+ const handler = (response) => {
1150
+ clearTimeout(timer);
1151
+ this.offData(handler);
1152
+ resolve({ success: true, data: response });
1153
+ };
1154
+ this.onData(handler);
1155
+ this.send(data);
1156
+ });
1157
+ }
1158
+ /**
1159
+ * Register a data handler
1160
+ */
1161
+ onData(handler) {
1162
+ this.dataHandlers.push(handler);
1163
+ }
1164
+ /**
1165
+ * Remove a data handler
1166
+ */
1167
+ offData(handler) {
1168
+ const index = this.dataHandlers.indexOf(handler);
1169
+ if (index !== -1) {
1170
+ this.dataHandlers.splice(index, 1);
1171
+ }
1172
+ }
1173
+ /**
1174
+ * Register an event handler
1175
+ */
1176
+ on(event, handler) {
1177
+ const handlers = this.eventHandlers.get(event) ?? [];
1178
+ handlers.push(handler);
1179
+ this.eventHandlers.set(event, handlers);
1180
+ }
1181
+ /**
1182
+ * Remove an event handler
1183
+ */
1184
+ off(event, handler) {
1185
+ const handlers = this.eventHandlers.get(event) ?? [];
1186
+ const index = handlers.indexOf(handler);
1187
+ if (index !== -1) {
1188
+ handlers.splice(index, 1);
1189
+ }
1190
+ }
1191
+ /**
1192
+ * Emit an event
1193
+ */
1194
+ emit(event, data) {
1195
+ const handlers = this.eventHandlers.get(event) ?? [];
1196
+ for (const handler of handlers) {
1197
+ try {
1198
+ handler(data);
1199
+ } catch (error) {
1200
+ console.error(`TCP handler error for "${event}":`, error);
1201
+ }
1202
+ }
1203
+ }
1204
+ /**
1205
+ * Handle incoming data
1206
+ */
1207
+ handleData(data) {
1208
+ for (const handler of this.dataHandlers) {
1209
+ try {
1210
+ handler(data);
1211
+ } catch (error) {
1212
+ console.error("TCP data handler error:", error);
1213
+ }
1214
+ }
1215
+ this.emit("data", data);
1216
+ }
1217
+ /**
1218
+ * Handle disconnection with reconnect logic
1219
+ */
1220
+ handleDisconnect() {
1221
+ if (!this.config.reconnect?.enabled || this.reconnecting) {
1222
+ return;
1223
+ }
1224
+ const maxAttempts = this.config.reconnect.max_attempts ?? 10;
1225
+ const delay = this.parseDuration(this.config.reconnect.delay ?? "5s");
1226
+ if (this.reconnectAttempts >= maxAttempts) {
1227
+ this.emit("reconnect_failed", { attempts: this.reconnectAttempts });
1228
+ return;
1229
+ }
1230
+ this.reconnecting = true;
1231
+ this.reconnectAttempts++;
1232
+ setTimeout(async () => {
1233
+ try {
1234
+ await this.connect();
1235
+ this.emit("reconnected", { attempts: this.reconnectAttempts });
1236
+ } catch {
1237
+ this.reconnecting = false;
1238
+ this.handleDisconnect();
1239
+ }
1240
+ }, delay);
1241
+ }
1242
+ /**
1243
+ * Parse duration string to milliseconds
1244
+ */
1245
+ parseDuration(duration) {
1246
+ const match = duration.match(/^(\d+)(ms|s|m|h)?$/);
1247
+ if (!match) return 5e3;
1248
+ const value = parseInt(match[1], 10);
1249
+ const unit = match[2] ?? "s";
1250
+ switch (unit) {
1251
+ case "ms":
1252
+ return value;
1253
+ case "s":
1254
+ return value * 1e3;
1255
+ case "m":
1256
+ return value * 60 * 1e3;
1257
+ case "h":
1258
+ return value * 60 * 60 * 1e3;
1259
+ default:
1260
+ return value * 1e3;
1261
+ }
1262
+ }
1263
+ };
1264
+ function createTcpPipe(options) {
1265
+ return new TcpPipe(options);
1266
+ }
1267
+ export {
1268
+ HttpPipe,
1269
+ MqttPipe,
1270
+ TcpPipe,
1271
+ UdpPipe,
1272
+ WebSocketPipe,
1273
+ WebhookPipe,
1274
+ WebhookSender,
1275
+ createHttpPipe,
1276
+ createMqttPipe,
1277
+ createTcpPipe,
1278
+ createUdpPipe,
1279
+ createWebSocketPipe,
1280
+ createWebhookPipe
1281
+ };
1282
+ //# sourceMappingURL=index.js.map