@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/database/index.js +145 -10
- package/dist/database/index.js.map +1 -1
- package/dist/index.js +239 -34
- package/dist/index.js.map +1 -1
- package/dist/mqtt/index.js +18 -3
- package/dist/mqtt/index.js.map +1 -1
- package/dist/tcp/index.d.ts +1 -0
- package/dist/tcp/index.js +44 -11
- package/dist/tcp/index.js.map +1 -1
- package/dist/webhook/index.js +6 -2
- package/dist/webhook/index.js.map +1 -1
- package/dist/websocket/index.d.ts +1 -0
- package/dist/websocket/index.js +26 -8
- package/dist/websocket/index.js.map +1 -1
- package/package.json +13 -13
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
|
-
|
|
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
|
-
|
|
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(
|
|
318
|
-
|
|
319
|
-
|
|
335
|
+
this.reconnectTimer = setTimeout(() => {
|
|
336
|
+
this.reconnectTimer = null;
|
|
337
|
+
this.connect().then(() => {
|
|
320
338
|
this.emit("reconnected", { attempts: this.reconnectAttempts });
|
|
321
|
-
}
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
1233
|
-
|
|
1234
|
-
|
|
1302
|
+
this.reconnectTimer = setTimeout(() => {
|
|
1303
|
+
this.reconnectTimer = null;
|
|
1304
|
+
this.connect().then(() => {
|
|
1235
1305
|
this.emit("reconnected", { attempts: this.reconnectAttempts });
|
|
1236
|
-
}
|
|
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
|
|
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
|
|
1405
|
-
const
|
|
1406
|
-
const
|
|
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
|
|
1438
|
-
const
|
|
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,
|
|
1496
|
-
const selectMatch = sql.match(
|
|
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
|
|
1499
|
-
|
|
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
|
}
|