@furlow/pipes 1.0.2 → 1.0.4

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 CHANGED
@@ -159,6 +159,7 @@ var WebSocketPipe = class {
159
159
  config;
160
160
  reconnectAttempts = 0;
161
161
  reconnecting = false;
162
+ reconnectTimer = null;
162
163
  heartbeatInterval = null;
163
164
  messageHandlers = /* @__PURE__ */ new Map();
164
165
  connected = false;
@@ -206,6 +207,10 @@ var WebSocketPipe = class {
206
207
  async disconnect() {
207
208
  this.stopHeartbeat();
208
209
  this.reconnecting = false;
210
+ if (this.reconnectTimer) {
211
+ clearTimeout(this.reconnectTimer);
212
+ this.reconnectTimer = null;
213
+ }
209
214
  if (this.ws) {
210
215
  this.ws.close();
211
216
  this.ws = null;
@@ -238,13 +243,21 @@ var WebSocketPipe = class {
238
243
  return { success: false, error: "Not connected" };
239
244
  }
240
245
  return new Promise((resolve) => {
241
- const timer = setTimeout(() => {
246
+ let resolved = false;
247
+ const cleanup = () => {
242
248
  this.off(responseEvent, handler);
249
+ };
250
+ const timer = setTimeout(() => {
251
+ if (resolved) return;
252
+ resolved = true;
253
+ cleanup();
243
254
  resolve({ success: false, error: "Request timeout" });
244
255
  }, timeout);
245
256
  const handler = (response) => {
257
+ if (resolved) return;
258
+ resolved = true;
246
259
  clearTimeout(timer);
247
- this.off(responseEvent, handler);
260
+ cleanup();
248
261
  resolve({ success: true, data: response });
249
262
  };
250
263
  this.on(responseEvent, handler);
@@ -276,7 +289,12 @@ var WebSocketPipe = class {
276
289
  const handlers = this.messageHandlers.get(event) ?? [];
277
290
  for (const handler of handlers) {
278
291
  try {
279
- handler(data);
292
+ const result = handler(data);
293
+ if (result instanceof Promise) {
294
+ result.catch((error) => {
295
+ console.error(`WebSocket async handler error for "${event}":`, error);
296
+ });
297
+ }
280
298
  } catch (error) {
281
299
  console.error(`WebSocket handler error for "${event}":`, error);
282
300
  }
@@ -314,14 +332,14 @@ var WebSocketPipe = class {
314
332
  }
315
333
  this.reconnecting = true;
316
334
  this.reconnectAttempts++;
317
- setTimeout(async () => {
318
- try {
319
- await this.connect();
335
+ this.reconnectTimer = setTimeout(() => {
336
+ this.reconnectTimer = null;
337
+ this.connect().then(() => {
320
338
  this.emit("reconnected", { attempts: this.reconnectAttempts });
321
- } catch {
339
+ }).catch(() => {
322
340
  this.reconnecting = false;
323
341
  this.handleDisconnect();
324
- }
342
+ });
325
343
  }, delay);
326
344
  }
327
345
  /**
@@ -454,10 +472,14 @@ var WebhookPipe = class {
454
472
  return receivedSignature === secret;
455
473
  }
456
474
  case "signature": {
457
- return true;
475
+ const signatureValue = headers[signatureHeader.toLowerCase()];
476
+ if (!signatureValue) {
477
+ return false;
478
+ }
479
+ return this.timingSafeEqual(signatureValue, secret);
458
480
  }
459
481
  default:
460
- return true;
482
+ return false;
461
483
  }
462
484
  }
463
485
  /**
@@ -722,7 +744,12 @@ var MqttPipe = class {
722
744
  const handlers = this.messageHandlers.get(event) ?? [];
723
745
  for (const handler of handlers) {
724
746
  try {
725
- handler(event, data, {});
747
+ const result = handler(event, data, {});
748
+ if (result instanceof Promise) {
749
+ result.catch((error) => {
750
+ console.error(`MQTT async handler error for "${event}":`, error);
751
+ });
752
+ }
726
753
  } catch (error) {
727
754
  console.error(`MQTT handler error for "${event}":`, error);
728
755
  }
@@ -742,7 +769,12 @@ var MqttPipe = class {
742
769
  const exactHandlers = this.messageHandlers.get(topic) ?? [];
743
770
  for (const handler of exactHandlers) {
744
771
  try {
745
- handler(topic, parsedPayload, packet);
772
+ const result = handler(topic, parsedPayload, packet);
773
+ if (result instanceof Promise) {
774
+ result.catch((error) => {
775
+ console.error(`MQTT async handler error for topic "${topic}":`, error);
776
+ });
777
+ }
746
778
  } catch (error) {
747
779
  console.error(`MQTT handler error for topic "${topic}":`, error);
748
780
  }
@@ -751,7 +783,12 @@ var MqttPipe = class {
751
783
  if (this.topicMatches(pattern, topic)) {
752
784
  for (const handler of handlers) {
753
785
  try {
754
- handler(topic, parsedPayload, packet);
786
+ const result = handler(topic, parsedPayload, packet);
787
+ if (result instanceof Promise) {
788
+ result.catch((error) => {
789
+ console.error(`MQTT async wildcard handler error for pattern "${pattern}":`, error);
790
+ });
791
+ }
755
792
  } catch (error) {
756
793
  console.error(`MQTT wildcard handler error for pattern "${pattern}":`, error);
757
794
  }
@@ -975,7 +1012,12 @@ var UdpPipe = class {
975
1012
  const handlers = this.eventHandlers.get(event) ?? [];
976
1013
  for (const handler of handlers) {
977
1014
  try {
978
- handler(data);
1015
+ const result = handler(data);
1016
+ if (result instanceof Promise) {
1017
+ result.catch((error) => {
1018
+ console.error(`UDP async handler error for "${event}":`, error);
1019
+ });
1020
+ }
979
1021
  } catch (error) {
980
1022
  console.error(`UDP handler error for "${event}":`, error);
981
1023
  }
@@ -988,7 +1030,12 @@ var UdpPipe = class {
988
1030
  const msg = { data, rinfo };
989
1031
  for (const handler of this.messageHandlers) {
990
1032
  try {
991
- handler(msg);
1033
+ const result = handler(msg);
1034
+ if (result instanceof Promise) {
1035
+ result.catch((error) => {
1036
+ console.error("UDP async message handler error:", error);
1037
+ });
1038
+ }
992
1039
  } catch (error) {
993
1040
  console.error("UDP message handler error:", error);
994
1041
  }
@@ -1010,6 +1057,7 @@ var TcpPipe = class {
1010
1057
  connected = false;
1011
1058
  reconnectAttempts = 0;
1012
1059
  reconnecting = false;
1060
+ reconnectTimer = null;
1013
1061
  dataHandlers = [];
1014
1062
  eventHandlers = /* @__PURE__ */ new Map();
1015
1063
  constructor(options) {
@@ -1091,6 +1139,10 @@ var TcpPipe = class {
1091
1139
  */
1092
1140
  async disconnect() {
1093
1141
  this.reconnecting = false;
1142
+ if (this.reconnectTimer) {
1143
+ clearTimeout(this.reconnectTimer);
1144
+ this.reconnectTimer = null;
1145
+ }
1094
1146
  return new Promise((resolve) => {
1095
1147
  if (this.socket) {
1096
1148
  this.socket.end(() => {
@@ -1142,13 +1194,21 @@ var TcpPipe = class {
1142
1194
  return { success: false, error: "Not connected" };
1143
1195
  }
1144
1196
  return new Promise((resolve) => {
1145
- const timer = setTimeout(() => {
1197
+ let resolved = false;
1198
+ const cleanup = () => {
1146
1199
  this.offData(handler);
1200
+ };
1201
+ const timer = setTimeout(() => {
1202
+ if (resolved) return;
1203
+ resolved = true;
1204
+ cleanup();
1147
1205
  resolve({ success: false, error: "Request timeout" });
1148
1206
  }, timeout);
1149
1207
  const handler = (response) => {
1208
+ if (resolved) return;
1209
+ resolved = true;
1150
1210
  clearTimeout(timer);
1151
- this.offData(handler);
1211
+ cleanup();
1152
1212
  resolve({ success: true, data: response });
1153
1213
  };
1154
1214
  this.onData(handler);
@@ -1195,7 +1255,12 @@ var TcpPipe = class {
1195
1255
  const handlers = this.eventHandlers.get(event) ?? [];
1196
1256
  for (const handler of handlers) {
1197
1257
  try {
1198
- handler(data);
1258
+ const result = handler(data);
1259
+ if (result instanceof Promise) {
1260
+ result.catch((error) => {
1261
+ console.error(`TCP async handler error for "${event}":`, error);
1262
+ });
1263
+ }
1199
1264
  } catch (error) {
1200
1265
  console.error(`TCP handler error for "${event}":`, error);
1201
1266
  }
@@ -1207,7 +1272,12 @@ var TcpPipe = class {
1207
1272
  handleData(data) {
1208
1273
  for (const handler of this.dataHandlers) {
1209
1274
  try {
1210
- handler(data);
1275
+ const result = handler(data);
1276
+ if (result instanceof Promise) {
1277
+ result.catch((error) => {
1278
+ console.error("TCP async data handler error:", error);
1279
+ });
1280
+ }
1211
1281
  } catch (error) {
1212
1282
  console.error("TCP data handler error:", error);
1213
1283
  }
@@ -1229,14 +1299,14 @@ var TcpPipe = class {
1229
1299
  }
1230
1300
  this.reconnecting = true;
1231
1301
  this.reconnectAttempts++;
1232
- setTimeout(async () => {
1233
- try {
1234
- await this.connect();
1302
+ this.reconnectTimer = setTimeout(() => {
1303
+ this.reconnectTimer = null;
1304
+ this.connect().then(() => {
1235
1305
  this.emit("reconnected", { attempts: this.reconnectAttempts });
1236
- } catch {
1306
+ }).catch(() => {
1237
1307
  this.reconnecting = false;
1238
1308
  this.handleDisconnect();
1239
- }
1309
+ });
1240
1310
  }, delay);
1241
1311
  }
1242
1312
  /**
@@ -1266,6 +1336,12 @@ function createTcpPipe(options) {
1266
1336
  }
1267
1337
 
1268
1338
  // src/database/index.ts
1339
+ function escapeIdentifier(name) {
1340
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
1341
+ throw new Error(`Invalid SQL identifier: ${name}`);
1342
+ }
1343
+ return `"${name}"`;
1344
+ }
1269
1345
  var DatabasePipe = class {
1270
1346
  name;
1271
1347
  type = "database";
@@ -1289,6 +1365,18 @@ var DatabasePipe = class {
1289
1365
  const BetterSqlite3 = (await import("better-sqlite3")).default;
1290
1366
  const connectionString = typeof this.config.connection === "string" ? this.config.connection : ":memory:";
1291
1367
  this.db = new BetterSqlite3(connectionString);
1368
+ } else if (this.config.adapter === "postgres") {
1369
+ const pgModuleName = "pg";
1370
+ const pg = await new Function("m", "return import(m)")(pgModuleName).catch(() => null);
1371
+ if (!pg) {
1372
+ throw new Error('PostgreSQL adapter requires the "pg" package. Install it with: npm install pg');
1373
+ }
1374
+ const { Pool } = pg;
1375
+ const connectionString = typeof this.config.connection === "string" ? this.config.connection : void 0;
1376
+ const connectionOptions = typeof this.config.connection === "object" ? this.config.connection : void 0;
1377
+ this.db = new PostgresWrapper(
1378
+ connectionString ? new Pool({ connectionString }) : new Pool(connectionOptions)
1379
+ );
1292
1380
  } else if (this.config.adapter === "memory") {
1293
1381
  this.db = new MemoryDatabase();
1294
1382
  } else {
@@ -1315,6 +1403,8 @@ var DatabasePipe = class {
1315
1403
  try {
1316
1404
  if (this.config.adapter === "sqlite" && this.db) {
1317
1405
  this.db.close();
1406
+ } else if (this.config.adapter === "postgres" && this.db) {
1407
+ await this.db.close();
1318
1408
  }
1319
1409
  this.db = null;
1320
1410
  this.connected = false;
@@ -1351,6 +1441,9 @@ var DatabasePipe = class {
1351
1441
  const result = stmt.run(...params);
1352
1442
  return { success: true, data: result };
1353
1443
  }
1444
+ } else if (this.config.adapter === "postgres") {
1445
+ const result = await this.db.query(sql, params);
1446
+ return { success: true, data: result };
1354
1447
  } else if (this.config.adapter === "memory") {
1355
1448
  const result = this.db.query(sql, params);
1356
1449
  return { success: true, data: result };
@@ -1372,11 +1465,15 @@ var DatabasePipe = class {
1372
1465
  const columns = Object.keys(data);
1373
1466
  const values = Object.values(data);
1374
1467
  const placeholders = columns.map(() => "?").join(", ");
1375
- const sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
1468
+ const escapedTable = escapeIdentifier(table);
1469
+ const escapedColumns = columns.map((c) => escapeIdentifier(c)).join(", ");
1470
+ const sql = `INSERT INTO ${escapedTable} (${escapedColumns}) VALUES (${placeholders})`;
1376
1471
  let result = {};
1377
1472
  if (this.config.adapter === "sqlite") {
1378
1473
  const stmt = this.db.prepare(sql);
1379
1474
  result = stmt.run(...values);
1475
+ } else if (this.config.adapter === "postgres") {
1476
+ result = await this.db.insert(table, data);
1380
1477
  } else if (this.config.adapter === "memory") {
1381
1478
  result = this.db.insert(table, data);
1382
1479
  }
@@ -1401,14 +1498,17 @@ var DatabasePipe = class {
1401
1498
  return { success: false, error: "Not connected" };
1402
1499
  }
1403
1500
  try {
1404
- const setClauses = Object.keys(data).map((key) => `${key} = ?`).join(", ");
1405
- const whereClauses = Object.keys(where).map((key) => `${key} = ?`).join(" AND ");
1406
- const sql = `UPDATE ${table} SET ${setClauses} WHERE ${whereClauses}`;
1501
+ const escapedTable = escapeIdentifier(table);
1502
+ const setClauses = Object.keys(data).map((key) => `${escapeIdentifier(key)} = ?`).join(", ");
1503
+ const whereClauses = Object.keys(where).map((key) => `${escapeIdentifier(key)} = ?`).join(" AND ");
1504
+ const sql = `UPDATE ${escapedTable} SET ${setClauses} WHERE ${whereClauses}`;
1407
1505
  const params = [...Object.values(data), ...Object.values(where)];
1408
1506
  let result = {};
1409
1507
  if (this.config.adapter === "sqlite") {
1410
1508
  const stmt = this.db.prepare(sql);
1411
1509
  result = stmt.run(...params);
1510
+ } else if (this.config.adapter === "postgres") {
1511
+ result = await this.db.update(table, where, data);
1412
1512
  } else if (this.config.adapter === "memory") {
1413
1513
  result = this.db.update(table, where, data);
1414
1514
  }
@@ -1434,13 +1534,16 @@ var DatabasePipe = class {
1434
1534
  return { success: false, error: "Not connected" };
1435
1535
  }
1436
1536
  try {
1437
- const whereClauses = Object.keys(where).map((key) => `${key} = ?`).join(" AND ");
1438
- const sql = `DELETE FROM ${table} WHERE ${whereClauses}`;
1537
+ const escapedTable = escapeIdentifier(table);
1538
+ const whereClauses = Object.keys(where).map((key) => `${escapeIdentifier(key)} = ?`).join(" AND ");
1539
+ const sql = `DELETE FROM ${escapedTable} WHERE ${whereClauses}`;
1439
1540
  const params = Object.values(where);
1440
1541
  let result = {};
1441
1542
  if (this.config.adapter === "sqlite") {
1442
1543
  const stmt = this.db.prepare(sql);
1443
1544
  result = stmt.run(...params);
1545
+ } else if (this.config.adapter === "postgres") {
1546
+ result = await this.db.delete(table, where);
1444
1547
  } else if (this.config.adapter === "memory") {
1445
1548
  result = this.db.delete(table, where);
1446
1549
  }
@@ -1492,14 +1595,68 @@ var DatabasePipe = class {
1492
1595
  var MemoryDatabase = class {
1493
1596
  tables = /* @__PURE__ */ new Map();
1494
1597
  autoIncrements = /* @__PURE__ */ new Map();
1495
- query(sql, _params) {
1496
- const selectMatch = sql.match(/SELECT \* FROM (\w+)/i);
1598
+ query(sql, params) {
1599
+ const selectMatch = sql.match(
1600
+ /SELECT\s+(\*|[\w,\s]+)\s+FROM\s+["']?(\w+)["']?(?:\s+WHERE\s+(.+?))?(?:\s+ORDER\s+BY\s+([\w\s,]+?)(?:\s+(ASC|DESC))?)?(?:\s+LIMIT\s+(\d+))?(?:\s+OFFSET\s+(\d+))?$/i
1601
+ );
1497
1602
  if (selectMatch) {
1498
- const table = selectMatch[1];
1499
- return this.tables.get(table) ?? [];
1603
+ const [, columns, table, whereClause, orderBy, orderDir, limitStr, offsetStr] = selectMatch;
1604
+ let rows = [...this.tables.get(table) ?? []];
1605
+ if (whereClause) {
1606
+ const where = this.parseWhereClause(whereClause, params);
1607
+ rows = rows.filter((row) => this.matchesWhere(row, where));
1608
+ }
1609
+ if (orderBy) {
1610
+ const col = orderBy.trim();
1611
+ const direction = orderDir?.toUpperCase() === "DESC" ? -1 : 1;
1612
+ rows.sort((a, b) => {
1613
+ const aVal = a[col];
1614
+ const bVal = b[col];
1615
+ if (aVal === bVal) return 0;
1616
+ if (aVal === null || aVal === void 0) return direction;
1617
+ if (bVal === null || bVal === void 0) return -direction;
1618
+ return (aVal < bVal ? -1 : 1) * direction;
1619
+ });
1620
+ }
1621
+ if (offsetStr) {
1622
+ const offset = parseInt(offsetStr, 10);
1623
+ rows = rows.slice(offset);
1624
+ }
1625
+ if (limitStr) {
1626
+ const limit = parseInt(limitStr, 10);
1627
+ rows = rows.slice(0, limit);
1628
+ }
1629
+ if (columns !== "*") {
1630
+ const cols = columns.split(",").map((c) => c.trim());
1631
+ rows = rows.map((row) => {
1632
+ const result = {};
1633
+ for (const col of cols) {
1634
+ result[col] = row[col];
1635
+ }
1636
+ return result;
1637
+ });
1638
+ }
1639
+ return rows;
1500
1640
  }
1641
+ console.warn(`MemoryDatabase: Unsupported query: ${sql}`);
1501
1642
  return [];
1502
1643
  }
1644
+ /**
1645
+ * Parse a simple WHERE clause into key-value pairs
1646
+ * Supports: col = ? AND col2 = ?
1647
+ */
1648
+ parseWhereClause(whereClause, params) {
1649
+ const where = {};
1650
+ let paramIndex = 0;
1651
+ const conditions = whereClause.split(/\s+AND\s+/i);
1652
+ for (const condition of conditions) {
1653
+ const match = condition.match(/["']?(\w+)["']?\s*=\s*\?/);
1654
+ if (match) {
1655
+ where[match[1]] = params[paramIndex++];
1656
+ }
1657
+ }
1658
+ return where;
1659
+ }
1503
1660
  insert(table, data) {
1504
1661
  if (!this.tables.has(table)) {
1505
1662
  this.tables.set(table, []);
@@ -1537,6 +1694,54 @@ var MemoryDatabase = class {
1537
1694
  return true;
1538
1695
  }
1539
1696
  };
1697
+ var PostgresWrapper = class {
1698
+ pool;
1699
+ constructor(pool) {
1700
+ this.pool = pool;
1701
+ }
1702
+ async query(sql, params) {
1703
+ let paramIndex = 0;
1704
+ const pgSql = sql.replace(/\?/g, () => `$${++paramIndex}`);
1705
+ const result = await this.pool.query(pgSql, params);
1706
+ return result.rows;
1707
+ }
1708
+ async insert(table, data) {
1709
+ const columns = Object.keys(data);
1710
+ const values = Object.values(data);
1711
+ const placeholders = columns.map((_, i) => `$${i + 1}`).join(", ");
1712
+ const escapedTable = escapeIdentifier(table);
1713
+ const escapedColumns = columns.map((c) => escapeIdentifier(c)).join(", ");
1714
+ const result = await this.pool.query(
1715
+ `INSERT INTO ${escapedTable} (${escapedColumns}) VALUES (${placeholders}) RETURNING id`,
1716
+ values
1717
+ );
1718
+ const row = result.rows[0];
1719
+ return { lastInsertRowid: row?.id };
1720
+ }
1721
+ async update(table, where, data) {
1722
+ const escapedTable = escapeIdentifier(table);
1723
+ const setClauses = Object.keys(data).map((key, i) => `${escapeIdentifier(key)} = $${i + 1}`).join(", ");
1724
+ const whereClauses = Object.keys(where).map((key, i) => `${escapeIdentifier(key)} = $${Object.keys(data).length + i + 1}`).join(" AND ");
1725
+ const params = [...Object.values(data), ...Object.values(where)];
1726
+ const result = await this.pool.query(
1727
+ `UPDATE ${escapedTable} SET ${setClauses} WHERE ${whereClauses}`,
1728
+ params
1729
+ );
1730
+ return { changes: result.rowCount ?? 0 };
1731
+ }
1732
+ async delete(table, where) {
1733
+ const escapedTable = escapeIdentifier(table);
1734
+ const whereClauses = Object.keys(where).map((key, i) => `${escapeIdentifier(key)} = $${i + 1}`).join(" AND ");
1735
+ const result = await this.pool.query(
1736
+ `DELETE FROM ${escapedTable} WHERE ${whereClauses}`,
1737
+ Object.values(where)
1738
+ );
1739
+ return { changes: result.rowCount ?? 0 };
1740
+ }
1741
+ async close() {
1742
+ await this.pool.end();
1743
+ }
1744
+ };
1540
1745
  function createDatabasePipe(options) {
1541
1746
  return new DatabasePipe(options);
1542
1747
  }