s3db.js 7.4.2 → 8.0.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/PLUGINS.md +3 -3
- package/dist/s3db.cjs.js +516 -349
- package/dist/s3db.cjs.min.js +1 -1
- package/dist/s3db.d.ts +10 -0
- package/dist/s3db.es.js +516 -349
- package/dist/s3db.es.min.js +1 -1
- package/dist/s3db.iife.js +517 -351
- package/dist/s3db.iife.min.js +1 -1
- package/mcp/README.md +1062 -0
- package/mcp/server.js +1 -1
- package/package.json +31 -29
- package/src/client.class.js +26 -5
- package/src/database.class.js +151 -0
- package/src/plugins/audit.plugin.js +143 -310
- package/src/plugins/cache/filesystem-cache.class.js +40 -11
- package/src/plugins/cache.plugin.js +95 -47
- package/src/plugins/fulltext.plugin.js +21 -0
- package/src/plugins/metrics.plugin.js +21 -6
- package/src/plugins/replicator.plugin.js +65 -70
- package/src/resource.class.js +3 -2
- package/src/s3db.d.ts +10 -0
package/dist/s3db.iife.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var S3DB = (function (exports, nanoid, zlib, promisePool, web, promises, crypto, lodashEs, jsonStableStringify, clientS3, flat, FastestValidator) {
|
|
1
|
+
var S3DB = (function (exports, nanoid, zlib, promisePool, web, promises, crypto, lodashEs, jsonStableStringify, nodeHttpHandler, clientS3, flat, FastestValidator) {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
@@ -1258,7 +1258,6 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
1258
1258
|
includeData: options.includeData !== false,
|
|
1259
1259
|
includePartitions: options.includePartitions !== false,
|
|
1260
1260
|
maxDataSize: options.maxDataSize || 1e4,
|
|
1261
|
-
// 10KB limit
|
|
1262
1261
|
...options
|
|
1263
1262
|
};
|
|
1264
1263
|
}
|
|
@@ -1279,327 +1278,193 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
1279
1278
|
metadata: "string|optional"
|
|
1280
1279
|
},
|
|
1281
1280
|
behavior: "body-overflow"
|
|
1282
|
-
// keyPrefix removido
|
|
1283
1281
|
}));
|
|
1284
1282
|
this.auditResource = ok ? auditResource : this.database.resources.audits || null;
|
|
1285
1283
|
if (!ok && !this.auditResource) return;
|
|
1286
|
-
this.
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
async onStart() {
|
|
1290
|
-
}
|
|
1291
|
-
async onStop() {
|
|
1292
|
-
}
|
|
1293
|
-
installDatabaseProxy() {
|
|
1294
|
-
if (this.database._auditProxyInstalled) {
|
|
1295
|
-
return;
|
|
1296
|
-
}
|
|
1297
|
-
const installEventListenersForResource = this.installEventListenersForResource.bind(this);
|
|
1298
|
-
this.database._originalCreateResource = this.database.createResource;
|
|
1299
|
-
this.database.createResource = async function(...args) {
|
|
1300
|
-
const resource = await this._originalCreateResource(...args);
|
|
1301
|
-
if (resource.name !== "audits") {
|
|
1302
|
-
installEventListenersForResource(resource);
|
|
1284
|
+
this.database.addHook("afterCreateResource", (context) => {
|
|
1285
|
+
if (context.resource.name !== "audits") {
|
|
1286
|
+
this.setupResourceAuditing(context.resource);
|
|
1303
1287
|
}
|
|
1304
|
-
|
|
1305
|
-
};
|
|
1306
|
-
this.database._auditProxyInstalled = true;
|
|
1307
|
-
}
|
|
1308
|
-
installEventListeners() {
|
|
1288
|
+
});
|
|
1309
1289
|
for (const resource of Object.values(this.database.resources)) {
|
|
1310
|
-
if (resource.name
|
|
1311
|
-
|
|
1290
|
+
if (resource.name !== "audits") {
|
|
1291
|
+
this.setupResourceAuditing(resource);
|
|
1312
1292
|
}
|
|
1313
|
-
this.installEventListenersForResource(resource);
|
|
1314
1293
|
}
|
|
1315
1294
|
}
|
|
1316
|
-
|
|
1295
|
+
async onStart() {
|
|
1296
|
+
}
|
|
1297
|
+
async onStop() {
|
|
1298
|
+
}
|
|
1299
|
+
setupResourceAuditing(resource) {
|
|
1317
1300
|
resource.on("insert", async (data) => {
|
|
1318
|
-
|
|
1319
|
-
const partitionValues = this.config.includePartitions ? this.getPartitionValues(data, resource) : null;
|
|
1320
|
-
const auditRecord = {
|
|
1321
|
-
id: `audit-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
|
|
1301
|
+
await this.logAudit({
|
|
1322
1302
|
resourceName: resource.name,
|
|
1323
1303
|
operation: "insert",
|
|
1324
|
-
recordId,
|
|
1325
|
-
userId: this.getCurrentUserId?.() || "system",
|
|
1326
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1304
|
+
recordId: data.id || "auto-generated",
|
|
1327
1305
|
oldData: null,
|
|
1328
|
-
newData: this.config.includeData
|
|
1329
|
-
partition: this.config.includePartitions ? this.getPrimaryPartition(
|
|
1330
|
-
partitionValues: this.config.includePartitions
|
|
1331
|
-
metadata: JSON.stringify({
|
|
1332
|
-
source: "audit-plugin",
|
|
1333
|
-
version: "2.0"
|
|
1334
|
-
})
|
|
1335
|
-
};
|
|
1336
|
-
this.logAudit(auditRecord).catch(() => {
|
|
1306
|
+
newData: this.config.includeData ? JSON.stringify(this.truncateData(data)) : null,
|
|
1307
|
+
partition: this.config.includePartitions && this.getPartitionValues(data, resource) ? this.getPrimaryPartition(this.getPartitionValues(data, resource)) : null,
|
|
1308
|
+
partitionValues: this.config.includePartitions && this.getPartitionValues(data, resource) ? JSON.stringify(this.getPartitionValues(data, resource)) : null
|
|
1337
1309
|
});
|
|
1338
1310
|
});
|
|
1339
1311
|
resource.on("update", async (data) => {
|
|
1340
|
-
const recordId = data.id;
|
|
1341
1312
|
let oldData = data.$before;
|
|
1342
1313
|
if (this.config.includeData && !oldData) {
|
|
1343
|
-
const [ok, err, fetched] = await try_fn_default(() => resource.get(
|
|
1314
|
+
const [ok, err, fetched] = await try_fn_default(() => resource.get(data.id));
|
|
1344
1315
|
if (ok) oldData = fetched;
|
|
1345
1316
|
}
|
|
1346
|
-
|
|
1347
|
-
const auditRecord = {
|
|
1348
|
-
id: `audit-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
|
|
1317
|
+
await this.logAudit({
|
|
1349
1318
|
resourceName: resource.name,
|
|
1350
1319
|
operation: "update",
|
|
1351
|
-
recordId,
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
partition: this.config.includePartitions ? this.getPrimaryPartition(partitionValues) : null,
|
|
1357
|
-
partitionValues: this.config.includePartitions ? partitionValues ? Object.keys(partitionValues).length > 0 ? JSON.stringify(partitionValues) : null : null : null,
|
|
1358
|
-
metadata: JSON.stringify({
|
|
1359
|
-
source: "audit-plugin",
|
|
1360
|
-
version: "2.0"
|
|
1361
|
-
})
|
|
1362
|
-
};
|
|
1363
|
-
this.logAudit(auditRecord).catch(() => {
|
|
1320
|
+
recordId: data.id,
|
|
1321
|
+
oldData: oldData && this.config.includeData ? JSON.stringify(this.truncateData(oldData)) : null,
|
|
1322
|
+
newData: this.config.includeData ? JSON.stringify(this.truncateData(data)) : null,
|
|
1323
|
+
partition: this.config.includePartitions && this.getPartitionValues(data, resource) ? this.getPrimaryPartition(this.getPartitionValues(data, resource)) : null,
|
|
1324
|
+
partitionValues: this.config.includePartitions && this.getPartitionValues(data, resource) ? JSON.stringify(this.getPartitionValues(data, resource)) : null
|
|
1364
1325
|
});
|
|
1365
1326
|
});
|
|
1366
1327
|
resource.on("delete", async (data) => {
|
|
1367
|
-
const recordId = data.id;
|
|
1368
1328
|
let oldData = data;
|
|
1369
1329
|
if (this.config.includeData && !oldData) {
|
|
1370
|
-
const [ok, err, fetched] = await try_fn_default(() => resource.get(
|
|
1330
|
+
const [ok, err, fetched] = await try_fn_default(() => resource.get(data.id));
|
|
1371
1331
|
if (ok) oldData = fetched;
|
|
1372
1332
|
}
|
|
1373
|
-
|
|
1374
|
-
const auditRecord = {
|
|
1375
|
-
id: `audit-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
|
|
1333
|
+
await this.logAudit({
|
|
1376
1334
|
resourceName: resource.name,
|
|
1377
1335
|
operation: "delete",
|
|
1378
|
-
recordId,
|
|
1379
|
-
|
|
1380
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1381
|
-
oldData: oldData && this.config.includeData === false ? null : oldData ? JSON.stringify(this.truncateData(oldData)) : null,
|
|
1336
|
+
recordId: data.id,
|
|
1337
|
+
oldData: oldData && this.config.includeData ? JSON.stringify(this.truncateData(oldData)) : null,
|
|
1382
1338
|
newData: null,
|
|
1383
|
-
partition: this.config.includePartitions ? this.getPrimaryPartition(
|
|
1384
|
-
partitionValues: this.config.includePartitions
|
|
1385
|
-
metadata: JSON.stringify({
|
|
1386
|
-
source: "audit-plugin",
|
|
1387
|
-
version: "2.0"
|
|
1388
|
-
})
|
|
1389
|
-
};
|
|
1390
|
-
this.logAudit(auditRecord).catch(() => {
|
|
1339
|
+
partition: oldData && this.config.includePartitions && this.getPartitionValues(oldData, resource) ? this.getPrimaryPartition(this.getPartitionValues(oldData, resource)) : null,
|
|
1340
|
+
partitionValues: oldData && this.config.includePartitions && this.getPartitionValues(oldData, resource) ? JSON.stringify(this.getPartitionValues(oldData, resource)) : null
|
|
1391
1341
|
});
|
|
1392
1342
|
});
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
return result;
|
|
1429
|
-
});
|
|
1343
|
+
}
|
|
1344
|
+
// Backward compatibility for tests
|
|
1345
|
+
installEventListenersForResource(resource) {
|
|
1346
|
+
return this.setupResourceAuditing(resource);
|
|
1347
|
+
}
|
|
1348
|
+
async logAudit(auditData) {
|
|
1349
|
+
if (!this.auditResource) {
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
const auditRecord = {
|
|
1353
|
+
id: `audit-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
|
|
1354
|
+
userId: this.getCurrentUserId?.() || "system",
|
|
1355
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1356
|
+
metadata: JSON.stringify({ source: "audit-plugin", version: "2.0" }),
|
|
1357
|
+
resourceName: auditData.resourceName,
|
|
1358
|
+
operation: auditData.operation,
|
|
1359
|
+
recordId: auditData.recordId
|
|
1360
|
+
};
|
|
1361
|
+
if (auditData.oldData !== null) {
|
|
1362
|
+
auditRecord.oldData = auditData.oldData;
|
|
1363
|
+
}
|
|
1364
|
+
if (auditData.newData !== null) {
|
|
1365
|
+
auditRecord.newData = auditData.newData;
|
|
1366
|
+
}
|
|
1367
|
+
if (auditData.partition !== null) {
|
|
1368
|
+
auditRecord.partition = auditData.partition;
|
|
1369
|
+
}
|
|
1370
|
+
if (auditData.partitionValues !== null) {
|
|
1371
|
+
auditRecord.partitionValues = auditData.partitionValues;
|
|
1372
|
+
}
|
|
1373
|
+
try {
|
|
1374
|
+
await this.auditResource.insert(auditRecord);
|
|
1375
|
+
} catch (error) {
|
|
1376
|
+
console.warn("Audit logging failed:", error.message);
|
|
1377
|
+
}
|
|
1430
1378
|
}
|
|
1431
1379
|
getPartitionValues(data, resource) {
|
|
1432
|
-
if (!
|
|
1433
|
-
const partitions = resource.config?.partitions || {};
|
|
1380
|
+
if (!this.config.includePartitions || !resource.partitions) return null;
|
|
1434
1381
|
const partitionValues = {};
|
|
1435
|
-
for (const [partitionName,
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
if (Object.keys(partitionData).length > 0) {
|
|
1445
|
-
partitionValues[partitionName] = partitionData;
|
|
1446
|
-
}
|
|
1382
|
+
for (const [partitionName, partitionConfig] of Object.entries(resource.partitions)) {
|
|
1383
|
+
const values = {};
|
|
1384
|
+
for (const field of Object.keys(partitionConfig.fields)) {
|
|
1385
|
+
values[field] = this.getNestedFieldValue(data, field);
|
|
1386
|
+
}
|
|
1387
|
+
if (Object.values(values).some((v) => v !== void 0 && v !== null)) {
|
|
1388
|
+
partitionValues[partitionName] = values;
|
|
1447
1389
|
}
|
|
1448
1390
|
}
|
|
1449
|
-
return partitionValues;
|
|
1391
|
+
return Object.keys(partitionValues).length > 0 ? partitionValues : null;
|
|
1450
1392
|
}
|
|
1451
1393
|
getNestedFieldValue(data, fieldPath) {
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
if (!currentLevel || typeof currentLevel !== "object" || !(key in currentLevel)) {
|
|
1394
|
+
const parts = fieldPath.split(".");
|
|
1395
|
+
let value = data;
|
|
1396
|
+
for (const part of parts) {
|
|
1397
|
+
if (value && typeof value === "object" && part in value) {
|
|
1398
|
+
value = value[part];
|
|
1399
|
+
} else {
|
|
1459
1400
|
return void 0;
|
|
1460
1401
|
}
|
|
1461
|
-
currentLevel = currentLevel[key];
|
|
1462
1402
|
}
|
|
1463
|
-
return
|
|
1403
|
+
return value;
|
|
1464
1404
|
}
|
|
1465
1405
|
getPrimaryPartition(partitionValues) {
|
|
1466
1406
|
if (!partitionValues) return null;
|
|
1467
1407
|
const partitionNames = Object.keys(partitionValues);
|
|
1468
1408
|
return partitionNames.length > 0 ? partitionNames[0] : null;
|
|
1469
1409
|
}
|
|
1470
|
-
async logAudit(auditRecord) {
|
|
1471
|
-
if (!auditRecord.id) {
|
|
1472
|
-
auditRecord.id = `audit-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1473
|
-
}
|
|
1474
|
-
const result = await this.auditResource.insert(auditRecord);
|
|
1475
|
-
return result;
|
|
1476
|
-
}
|
|
1477
1410
|
truncateData(data) {
|
|
1478
|
-
if (!
|
|
1479
|
-
const
|
|
1480
|
-
for (const [key, value] of Object.entries(data)) {
|
|
1481
|
-
if (!key.startsWith("_") && key !== "$overflow") {
|
|
1482
|
-
filteredData[key] = value;
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
const dataStr = JSON.stringify(filteredData);
|
|
1411
|
+
if (!this.config.includeData) return null;
|
|
1412
|
+
const dataStr = JSON.stringify(data);
|
|
1486
1413
|
if (dataStr.length <= this.config.maxDataSize) {
|
|
1487
|
-
return
|
|
1488
|
-
}
|
|
1489
|
-
let truncatedData = { ...filteredData };
|
|
1490
|
-
let currentSize = JSON.stringify(truncatedData).length;
|
|
1491
|
-
const metadataOverhead = JSON.stringify({
|
|
1492
|
-
_truncated: true,
|
|
1493
|
-
_originalSize: dataStr.length,
|
|
1494
|
-
_truncatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1495
|
-
}).length;
|
|
1496
|
-
const targetSize = this.config.maxDataSize - metadataOverhead;
|
|
1497
|
-
for (const [key, value] of Object.entries(truncatedData)) {
|
|
1498
|
-
if (typeof value === "string" && currentSize > targetSize) {
|
|
1499
|
-
const excess = currentSize - targetSize;
|
|
1500
|
-
const newLength = Math.max(0, value.length - excess - 3);
|
|
1501
|
-
if (newLength < value.length) {
|
|
1502
|
-
truncatedData[key] = value.substring(0, newLength) + "...";
|
|
1503
|
-
currentSize = JSON.stringify(truncatedData).length;
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1414
|
+
return data;
|
|
1506
1415
|
}
|
|
1507
1416
|
return {
|
|
1508
|
-
...
|
|
1417
|
+
...data,
|
|
1509
1418
|
_truncated: true,
|
|
1510
1419
|
_originalSize: dataStr.length,
|
|
1511
1420
|
_truncatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1512
1421
|
};
|
|
1513
1422
|
}
|
|
1514
|
-
// Utility methods for querying audit logs
|
|
1515
1423
|
async getAuditLogs(options = {}) {
|
|
1516
1424
|
if (!this.auditResource) return [];
|
|
1517
|
-
const
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
let filtered = allAudits.filter((audit) => {
|
|
1531
|
-
if (resourceName && audit.resourceName !== resourceName) return false;
|
|
1532
|
-
if (operation && audit.operation !== operation) return false;
|
|
1533
|
-
if (recordId && audit.recordId !== recordId) return false;
|
|
1534
|
-
if (userId && audit.userId !== userId) return false;
|
|
1535
|
-
if (partition && audit.partition !== partition) return false;
|
|
1536
|
-
if (startDate && new Date(audit.timestamp) < new Date(startDate)) return false;
|
|
1537
|
-
if (endDate && new Date(audit.timestamp) > new Date(endDate)) return false;
|
|
1538
|
-
return true;
|
|
1539
|
-
});
|
|
1540
|
-
filtered.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
1541
|
-
const deserialized = filtered.slice(offset, offset + limit).map((audit) => {
|
|
1542
|
-
const [okOld, , oldData] = typeof audit.oldData === "string" ? tryFnSync(() => JSON.parse(audit.oldData)) : [true, null, audit.oldData];
|
|
1543
|
-
const [okNew, , newData] = typeof audit.newData === "string" ? tryFnSync(() => JSON.parse(audit.newData)) : [true, null, audit.newData];
|
|
1544
|
-
const [okPart, , partitionValues] = audit.partitionValues && typeof audit.partitionValues === "string" ? tryFnSync(() => JSON.parse(audit.partitionValues)) : [true, null, audit.partitionValues];
|
|
1545
|
-
const [okMeta, , metadata] = audit.metadata && typeof audit.metadata === "string" ? tryFnSync(() => JSON.parse(audit.metadata)) : [true, null, audit.metadata];
|
|
1546
|
-
return {
|
|
1547
|
-
...audit,
|
|
1548
|
-
oldData: audit.oldData === null || audit.oldData === void 0 || audit.oldData === "null" ? null : okOld ? oldData : null,
|
|
1549
|
-
newData: audit.newData === null || audit.newData === void 0 || audit.newData === "null" ? null : okNew ? newData : null,
|
|
1550
|
-
partitionValues: okPart ? partitionValues : audit.partitionValues,
|
|
1551
|
-
metadata: okMeta ? metadata : audit.metadata
|
|
1552
|
-
};
|
|
1553
|
-
});
|
|
1554
|
-
return deserialized;
|
|
1555
|
-
});
|
|
1556
|
-
return ok ? result : [];
|
|
1425
|
+
const { resourceName, operation, recordId, partition, startDate, endDate, limit = 100 } = options;
|
|
1426
|
+
let query = {};
|
|
1427
|
+
if (resourceName) query.resourceName = resourceName;
|
|
1428
|
+
if (operation) query.operation = operation;
|
|
1429
|
+
if (recordId) query.recordId = recordId;
|
|
1430
|
+
if (partition) query.partition = partition;
|
|
1431
|
+
if (startDate || endDate) {
|
|
1432
|
+
query.timestamp = {};
|
|
1433
|
+
if (startDate) query.timestamp.$gte = startDate;
|
|
1434
|
+
if (endDate) query.timestamp.$lte = endDate;
|
|
1435
|
+
}
|
|
1436
|
+
const result = await this.auditResource.page({ query, limit });
|
|
1437
|
+
return result.items || [];
|
|
1557
1438
|
}
|
|
1558
1439
|
async getRecordHistory(resourceName, recordId) {
|
|
1559
|
-
return this.getAuditLogs({
|
|
1560
|
-
resourceName,
|
|
1561
|
-
recordId,
|
|
1562
|
-
limit: 1e3
|
|
1563
|
-
});
|
|
1440
|
+
return await this.getAuditLogs({ resourceName, recordId });
|
|
1564
1441
|
}
|
|
1565
1442
|
async getPartitionHistory(resourceName, partitionName, partitionValues) {
|
|
1566
|
-
return this.getAuditLogs({
|
|
1443
|
+
return await this.getAuditLogs({
|
|
1567
1444
|
resourceName,
|
|
1568
1445
|
partition: partitionName,
|
|
1569
|
-
|
|
1446
|
+
partitionValues: JSON.stringify(partitionValues)
|
|
1570
1447
|
});
|
|
1571
1448
|
}
|
|
1572
1449
|
async getAuditStats(options = {}) {
|
|
1573
|
-
const
|
|
1574
|
-
resourceName,
|
|
1575
|
-
startDate,
|
|
1576
|
-
endDate
|
|
1577
|
-
} = options;
|
|
1578
|
-
const allAudits = await this.getAuditLogs({
|
|
1579
|
-
resourceName,
|
|
1580
|
-
startDate,
|
|
1581
|
-
endDate,
|
|
1582
|
-
limit: 1e4
|
|
1583
|
-
});
|
|
1450
|
+
const logs = await this.getAuditLogs(options);
|
|
1584
1451
|
const stats = {
|
|
1585
|
-
total:
|
|
1452
|
+
total: logs.length,
|
|
1586
1453
|
byOperation: {},
|
|
1587
1454
|
byResource: {},
|
|
1588
1455
|
byPartition: {},
|
|
1589
1456
|
byUser: {},
|
|
1590
1457
|
timeline: {}
|
|
1591
1458
|
};
|
|
1592
|
-
for (const
|
|
1593
|
-
stats.byOperation[
|
|
1594
|
-
stats.byResource[
|
|
1595
|
-
if (
|
|
1596
|
-
stats.byPartition[
|
|
1597
|
-
}
|
|
1598
|
-
stats.byUser[audit.userId] = (stats.byUser[audit.userId] || 0) + 1;
|
|
1599
|
-
if (audit.timestamp) {
|
|
1600
|
-
const day = audit.timestamp.split("T")[0];
|
|
1601
|
-
stats.timeline[day] = (stats.timeline[day] || 0) + 1;
|
|
1459
|
+
for (const log of logs) {
|
|
1460
|
+
stats.byOperation[log.operation] = (stats.byOperation[log.operation] || 0) + 1;
|
|
1461
|
+
stats.byResource[log.resourceName] = (stats.byResource[log.resourceName] || 0) + 1;
|
|
1462
|
+
if (log.partition) {
|
|
1463
|
+
stats.byPartition[log.partition] = (stats.byPartition[log.partition] || 0) + 1;
|
|
1602
1464
|
}
|
|
1465
|
+
stats.byUser[log.userId] = (stats.byUser[log.userId] || 0) + 1;
|
|
1466
|
+
const date = log.timestamp.split("T")[0];
|
|
1467
|
+
stats.timeline[date] = (stats.timeline[date] || 0) + 1;
|
|
1603
1468
|
}
|
|
1604
1469
|
return stats;
|
|
1605
1470
|
}
|
|
@@ -7088,19 +6953,37 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
7088
6953
|
});
|
|
7089
6954
|
for (const file of cacheFiles) {
|
|
7090
6955
|
const filePath = path.join(this.directory, file);
|
|
7091
|
-
|
|
7092
|
-
await
|
|
6956
|
+
try {
|
|
6957
|
+
if (await this._fileExists(filePath)) {
|
|
6958
|
+
await promises.unlink(filePath);
|
|
6959
|
+
}
|
|
6960
|
+
} catch (error) {
|
|
6961
|
+
if (error.code !== "ENOENT") {
|
|
6962
|
+
throw error;
|
|
6963
|
+
}
|
|
7093
6964
|
}
|
|
7094
6965
|
if (this.enableMetadata) {
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
await
|
|
6966
|
+
try {
|
|
6967
|
+
const metadataPath = this._getMetadataPath(filePath);
|
|
6968
|
+
if (await this._fileExists(metadataPath)) {
|
|
6969
|
+
await promises.unlink(metadataPath);
|
|
6970
|
+
}
|
|
6971
|
+
} catch (error) {
|
|
6972
|
+
if (error.code !== "ENOENT") {
|
|
6973
|
+
throw error;
|
|
6974
|
+
}
|
|
7098
6975
|
}
|
|
7099
6976
|
}
|
|
7100
6977
|
if (this.enableBackup) {
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
await
|
|
6978
|
+
try {
|
|
6979
|
+
const backupPath = filePath + this.backupSuffix;
|
|
6980
|
+
if (await this._fileExists(backupPath)) {
|
|
6981
|
+
await promises.unlink(backupPath);
|
|
6982
|
+
}
|
|
6983
|
+
} catch (error) {
|
|
6984
|
+
if (error.code !== "ENOENT") {
|
|
6985
|
+
throw error;
|
|
6986
|
+
}
|
|
7104
6987
|
}
|
|
7105
6988
|
}
|
|
7106
6989
|
}
|
|
@@ -7112,6 +6995,12 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
7112
6995
|
}
|
|
7113
6996
|
return true;
|
|
7114
6997
|
} catch (error) {
|
|
6998
|
+
if (error.code === "ENOENT") {
|
|
6999
|
+
if (this.enableStats) {
|
|
7000
|
+
this.stats.clears++;
|
|
7001
|
+
}
|
|
7002
|
+
return true;
|
|
7003
|
+
}
|
|
7115
7004
|
if (this.enableStats) {
|
|
7116
7005
|
this.stats.errors++;
|
|
7117
7006
|
}
|
|
@@ -7597,11 +7486,11 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
7597
7486
|
await super.setup(database);
|
|
7598
7487
|
}
|
|
7599
7488
|
async onSetup() {
|
|
7600
|
-
if (this.config.driver) {
|
|
7489
|
+
if (this.config.driver && typeof this.config.driver === "object") {
|
|
7601
7490
|
this.driver = this.config.driver;
|
|
7602
|
-
} else if (this.config.
|
|
7491
|
+
} else if (this.config.driver === "memory") {
|
|
7603
7492
|
this.driver = new memory_cache_class_default(this.config.memoryOptions || {});
|
|
7604
|
-
} else if (this.config.
|
|
7493
|
+
} else if (this.config.driver === "filesystem") {
|
|
7605
7494
|
if (this.config.partitionAware) {
|
|
7606
7495
|
this.driver = new PartitionAwareFilesystemCache({
|
|
7607
7496
|
partitionStrategy: this.config.partitionStrategy,
|
|
@@ -7615,26 +7504,22 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
7615
7504
|
} else {
|
|
7616
7505
|
this.driver = new s3_cache_class_default({ client: this.database.client, ...this.config.s3Options || {} });
|
|
7617
7506
|
}
|
|
7618
|
-
this.
|
|
7507
|
+
this.installDatabaseHooks();
|
|
7619
7508
|
this.installResourceHooks();
|
|
7620
7509
|
}
|
|
7510
|
+
/**
|
|
7511
|
+
* Install database hooks to handle resource creation/updates
|
|
7512
|
+
*/
|
|
7513
|
+
installDatabaseHooks() {
|
|
7514
|
+
this.database.addHook("afterCreateResource", async ({ resource }) => {
|
|
7515
|
+
this.installResourceHooksForResource(resource);
|
|
7516
|
+
});
|
|
7517
|
+
}
|
|
7621
7518
|
async onStart() {
|
|
7622
7519
|
}
|
|
7623
7520
|
async onStop() {
|
|
7624
7521
|
}
|
|
7625
|
-
installDatabaseProxy
|
|
7626
|
-
if (this.database._cacheProxyInstalled) {
|
|
7627
|
-
return;
|
|
7628
|
-
}
|
|
7629
|
-
const installResourceHooks = this.installResourceHooks.bind(this);
|
|
7630
|
-
this.database._originalCreateResourceForCache = this.database.createResource;
|
|
7631
|
-
this.database.createResource = async function(...args) {
|
|
7632
|
-
const resource = await this._originalCreateResourceForCache(...args);
|
|
7633
|
-
installResourceHooks(resource);
|
|
7634
|
-
return resource;
|
|
7635
|
-
};
|
|
7636
|
-
this.database._cacheProxyInstalled = true;
|
|
7637
|
-
}
|
|
7522
|
+
// Remove the old installDatabaseProxy method
|
|
7638
7523
|
installResourceHooks() {
|
|
7639
7524
|
for (const resource of Object.values(this.database.resources)) {
|
|
7640
7525
|
this.installResourceHooksForResource(resource);
|
|
@@ -7673,7 +7558,12 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
7673
7558
|
"getAll",
|
|
7674
7559
|
"page",
|
|
7675
7560
|
"list",
|
|
7676
|
-
"get"
|
|
7561
|
+
"get",
|
|
7562
|
+
"exists",
|
|
7563
|
+
"content",
|
|
7564
|
+
"hasContent",
|
|
7565
|
+
"query",
|
|
7566
|
+
"getFromPartition"
|
|
7677
7567
|
];
|
|
7678
7568
|
for (const method of cacheMethods) {
|
|
7679
7569
|
resource.useMiddleware(method, async (ctx, next) => {
|
|
@@ -7686,9 +7576,26 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
7686
7576
|
} else if (method === "list" || method === "listIds" || method === "count") {
|
|
7687
7577
|
const { partition, partitionValues } = ctx.args[0] || {};
|
|
7688
7578
|
key = await resource.cacheKeyFor({ action: method, partition, partitionValues });
|
|
7579
|
+
} else if (method === "query") {
|
|
7580
|
+
const filter = ctx.args[0] || {};
|
|
7581
|
+
const options = ctx.args[1] || {};
|
|
7582
|
+
key = await resource.cacheKeyFor({
|
|
7583
|
+
action: method,
|
|
7584
|
+
params: { filter, options: { limit: options.limit, offset: options.offset } },
|
|
7585
|
+
partition: options.partition,
|
|
7586
|
+
partitionValues: options.partitionValues
|
|
7587
|
+
});
|
|
7588
|
+
} else if (method === "getFromPartition") {
|
|
7589
|
+
const { id, partitionName, partitionValues } = ctx.args[0] || {};
|
|
7590
|
+
key = await resource.cacheKeyFor({
|
|
7591
|
+
action: method,
|
|
7592
|
+
params: { id, partitionName },
|
|
7593
|
+
partition: partitionName,
|
|
7594
|
+
partitionValues
|
|
7595
|
+
});
|
|
7689
7596
|
} else if (method === "getAll") {
|
|
7690
7597
|
key = await resource.cacheKeyFor({ action: method });
|
|
7691
|
-
} else if (
|
|
7598
|
+
} else if (["get", "exists", "content", "hasContent"].includes(method)) {
|
|
7692
7599
|
key = await resource.cacheKeyFor({ action: method, params: { id: ctx.args[0] } });
|
|
7693
7600
|
}
|
|
7694
7601
|
if (this.driver instanceof PartitionAwareFilesystemCache) {
|
|
@@ -7697,6 +7604,14 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
7697
7604
|
const args = ctx.args[0] || {};
|
|
7698
7605
|
partition = args.partition;
|
|
7699
7606
|
partitionValues = args.partitionValues;
|
|
7607
|
+
} else if (method === "query") {
|
|
7608
|
+
const options = ctx.args[1] || {};
|
|
7609
|
+
partition = options.partition;
|
|
7610
|
+
partitionValues = options.partitionValues;
|
|
7611
|
+
} else if (method === "getFromPartition") {
|
|
7612
|
+
const { partitionName, partitionValues: pValues } = ctx.args[0] || {};
|
|
7613
|
+
partition = partitionName;
|
|
7614
|
+
partitionValues = pValues;
|
|
7700
7615
|
}
|
|
7701
7616
|
const [ok, err, result] = await try_fn_default(() => resource.cache._get(key, {
|
|
7702
7617
|
resource: resource.name,
|
|
@@ -7724,7 +7639,7 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
7724
7639
|
}
|
|
7725
7640
|
});
|
|
7726
7641
|
}
|
|
7727
|
-
const writeMethods = ["insert", "update", "delete", "deleteMany"];
|
|
7642
|
+
const writeMethods = ["insert", "update", "delete", "deleteMany", "setContent", "deleteContent", "replace"];
|
|
7728
7643
|
for (const method of writeMethods) {
|
|
7729
7644
|
resource.useMiddleware(method, async (ctx, next) => {
|
|
7730
7645
|
const result = await next();
|
|
@@ -7739,6 +7654,12 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
7739
7654
|
if (ok && full) data = full;
|
|
7740
7655
|
}
|
|
7741
7656
|
await this.clearCacheForResource(resource, data);
|
|
7657
|
+
} else if (method === "setContent" || method === "deleteContent") {
|
|
7658
|
+
const id = ctx.args[0]?.id || ctx.args[0];
|
|
7659
|
+
await this.clearCacheForResource(resource, { id });
|
|
7660
|
+
} else if (method === "replace") {
|
|
7661
|
+
const id = ctx.args[0];
|
|
7662
|
+
await this.clearCacheForResource(resource, { id, ...ctx.args[1] });
|
|
7742
7663
|
} else if (method === "deleteMany") {
|
|
7743
7664
|
await this.clearCacheForResource(resource);
|
|
7744
7665
|
}
|
|
@@ -7749,23 +7670,40 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
7749
7670
|
async clearCacheForResource(resource, data) {
|
|
7750
7671
|
if (!resource.cache) return;
|
|
7751
7672
|
const keyPrefix = `resource=${resource.name}`;
|
|
7752
|
-
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
const
|
|
7757
|
-
await resource.cache.clear(
|
|
7673
|
+
if (data && data.id) {
|
|
7674
|
+
const itemSpecificMethods = ["get", "exists", "content", "hasContent"];
|
|
7675
|
+
for (const method of itemSpecificMethods) {
|
|
7676
|
+
try {
|
|
7677
|
+
const specificKey = await this.generateCacheKey(resource, method, { id: data.id });
|
|
7678
|
+
await resource.cache.clear(specificKey.replace(".json.gz", ""));
|
|
7679
|
+
} catch (error) {
|
|
7758
7680
|
}
|
|
7759
|
-
}
|
|
7681
|
+
}
|
|
7682
|
+
if (this.config.includePartitions === true && resource.config?.partitions && Object.keys(resource.config.partitions).length > 0) {
|
|
7760
7683
|
const partitionValues = this.getPartitionValues(data, resource);
|
|
7761
7684
|
for (const [partitionName, values] of Object.entries(partitionValues)) {
|
|
7762
7685
|
if (values && Object.keys(values).length > 0 && Object.values(values).some((v) => v !== null && v !== void 0)) {
|
|
7763
|
-
|
|
7764
|
-
|
|
7686
|
+
try {
|
|
7687
|
+
const partitionKeyPrefix = join(keyPrefix, `partition=${partitionName}`);
|
|
7688
|
+
await resource.cache.clear(partitionKeyPrefix);
|
|
7689
|
+
} catch (error) {
|
|
7690
|
+
}
|
|
7765
7691
|
}
|
|
7766
7692
|
}
|
|
7767
7693
|
}
|
|
7768
7694
|
}
|
|
7695
|
+
try {
|
|
7696
|
+
await resource.cache.clear(keyPrefix);
|
|
7697
|
+
} catch (error) {
|
|
7698
|
+
const aggregateMethods = ["count", "list", "listIds", "getAll", "page", "query"];
|
|
7699
|
+
for (const method of aggregateMethods) {
|
|
7700
|
+
try {
|
|
7701
|
+
await resource.cache.clear(`${keyPrefix}/action=${method}`);
|
|
7702
|
+
await resource.cache.clear(`resource=${resource.name}/action=${method}`);
|
|
7703
|
+
} catch (methodError) {
|
|
7704
|
+
}
|
|
7705
|
+
}
|
|
7706
|
+
}
|
|
7769
7707
|
}
|
|
7770
7708
|
async generateCacheKey(resource, action, params = {}, partition = null, partitionValues = null) {
|
|
7771
7709
|
const keyParts = [
|
|
@@ -7787,7 +7725,7 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
7787
7725
|
return join(...keyParts) + ".json.gz";
|
|
7788
7726
|
}
|
|
7789
7727
|
async hashParams(params) {
|
|
7790
|
-
const sortedParams = Object.keys(params).sort().map((key) => `${key}:${params[key]}`).join("|") || "empty";
|
|
7728
|
+
const sortedParams = Object.keys(params).sort().map((key) => `${key}:${JSON.stringify(params[key])}`).join("|") || "empty";
|
|
7791
7729
|
return await sha256(sortedParams);
|
|
7792
7730
|
}
|
|
7793
7731
|
// Utility methods
|
|
@@ -7992,12 +7930,14 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
7992
7930
|
}));
|
|
7993
7931
|
this.indexResource = ok ? indexResource : database.resources.fulltext_indexes;
|
|
7994
7932
|
await this.loadIndexes();
|
|
7933
|
+
this.installDatabaseHooks();
|
|
7995
7934
|
this.installIndexingHooks();
|
|
7996
7935
|
}
|
|
7997
7936
|
async start() {
|
|
7998
7937
|
}
|
|
7999
7938
|
async stop() {
|
|
8000
7939
|
await this.saveIndexes();
|
|
7940
|
+
this.removeDatabaseHooks();
|
|
8001
7941
|
}
|
|
8002
7942
|
async loadIndexes() {
|
|
8003
7943
|
if (!this.indexResource) return;
|
|
@@ -8033,6 +7973,16 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
8033
7973
|
}
|
|
8034
7974
|
});
|
|
8035
7975
|
}
|
|
7976
|
+
installDatabaseHooks() {
|
|
7977
|
+
this.database.addHook("afterCreateResource", (resource) => {
|
|
7978
|
+
if (resource.name !== "fulltext_indexes") {
|
|
7979
|
+
this.installResourceHooks(resource);
|
|
7980
|
+
}
|
|
7981
|
+
});
|
|
7982
|
+
}
|
|
7983
|
+
removeDatabaseHooks() {
|
|
7984
|
+
this.database.removeHook("afterCreateResource", this.installResourceHooks.bind(this));
|
|
7985
|
+
}
|
|
8036
7986
|
installIndexingHooks() {
|
|
8037
7987
|
if (!this.database.plugins) {
|
|
8038
7988
|
this.database.plugins = {};
|
|
@@ -8395,6 +8345,7 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
8395
8345
|
this.errorsResource = database.resources.error_logs;
|
|
8396
8346
|
this.performanceResource = database.resources.performance_logs;
|
|
8397
8347
|
}
|
|
8348
|
+
this.installDatabaseHooks();
|
|
8398
8349
|
this.installMetricsHooks();
|
|
8399
8350
|
if (typeof process !== "undefined" && process.env.NODE_ENV !== "test") {
|
|
8400
8351
|
this.startFlushTimer();
|
|
@@ -8407,9 +8358,17 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
8407
8358
|
clearInterval(this.flushTimer);
|
|
8408
8359
|
this.flushTimer = null;
|
|
8409
8360
|
}
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
8361
|
+
this.removeDatabaseHooks();
|
|
8362
|
+
}
|
|
8363
|
+
installDatabaseHooks() {
|
|
8364
|
+
this.database.addHook("afterCreateResource", (resource) => {
|
|
8365
|
+
if (resource.name !== "metrics" && resource.name !== "error_logs" && resource.name !== "performance_logs") {
|
|
8366
|
+
this.installResourceHooks(resource);
|
|
8367
|
+
}
|
|
8368
|
+
});
|
|
8369
|
+
}
|
|
8370
|
+
removeDatabaseHooks() {
|
|
8371
|
+
this.database.removeHook("afterCreateResource", this.installResourceHooks.bind(this));
|
|
8413
8372
|
}
|
|
8414
8373
|
installMetricsHooks() {
|
|
8415
8374
|
for (const resource of Object.values(this.database.resources)) {
|
|
@@ -9510,6 +9469,72 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
9510
9469
|
}
|
|
9511
9470
|
var postgres_replicator_class_default = PostgresReplicator;
|
|
9512
9471
|
|
|
9472
|
+
/*
|
|
9473
|
+
this and http-lib folder
|
|
9474
|
+
|
|
9475
|
+
The MIT License
|
|
9476
|
+
|
|
9477
|
+
Copyright (c) 2015 John Hiesey
|
|
9478
|
+
|
|
9479
|
+
Permission is hereby granted, free of charge,
|
|
9480
|
+
to any person obtaining a copy of this software and
|
|
9481
|
+
associated documentation files (the "Software"), to
|
|
9482
|
+
deal in the Software without restriction, including
|
|
9483
|
+
without limitation the rights to use, copy, modify,
|
|
9484
|
+
merge, publish, distribute, sublicense, and/or sell
|
|
9485
|
+
copies of the Software, and to permit persons to whom
|
|
9486
|
+
the Software is furnished to do so,
|
|
9487
|
+
subject to the following conditions:
|
|
9488
|
+
|
|
9489
|
+
The above copyright notice and this permission notice
|
|
9490
|
+
shall be included in all copies or substantial portions of the Software.
|
|
9491
|
+
|
|
9492
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
9493
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
9494
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
9495
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
|
9496
|
+
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
9497
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
9498
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
9499
|
+
|
|
9500
|
+
*/
|
|
9501
|
+
|
|
9502
|
+
function Agent$1() {}
|
|
9503
|
+
Agent$1.defaultMaxSockets = 4;
|
|
9504
|
+
|
|
9505
|
+
/*
|
|
9506
|
+
this and http-lib folder
|
|
9507
|
+
|
|
9508
|
+
The MIT License
|
|
9509
|
+
|
|
9510
|
+
Copyright (c) 2015 John Hiesey
|
|
9511
|
+
|
|
9512
|
+
Permission is hereby granted, free of charge,
|
|
9513
|
+
to any person obtaining a copy of this software and
|
|
9514
|
+
associated documentation files (the "Software"), to
|
|
9515
|
+
deal in the Software without restriction, including
|
|
9516
|
+
without limitation the rights to use, copy, modify,
|
|
9517
|
+
merge, publish, distribute, sublicense, and/or sell
|
|
9518
|
+
copies of the Software, and to permit persons to whom
|
|
9519
|
+
the Software is furnished to do so,
|
|
9520
|
+
subject to the following conditions:
|
|
9521
|
+
|
|
9522
|
+
The above copyright notice and this permission notice
|
|
9523
|
+
shall be included in all copies or substantial portions of the Software.
|
|
9524
|
+
|
|
9525
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
9526
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
9527
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
9528
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
|
9529
|
+
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
9530
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
9531
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
9532
|
+
|
|
9533
|
+
*/
|
|
9534
|
+
|
|
9535
|
+
function Agent() {}
|
|
9536
|
+
Agent.defaultMaxSockets = 4;
|
|
9537
|
+
|
|
9513
9538
|
const S3_DEFAULT_REGION = "us-east-1";
|
|
9514
9539
|
const S3_DEFAULT_ENDPOINT = "https://s3.us-east-1.amazonaws.com";
|
|
9515
9540
|
class ConnectionString {
|
|
@@ -9577,19 +9602,38 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
9577
9602
|
id = null,
|
|
9578
9603
|
AwsS3Client,
|
|
9579
9604
|
connectionString,
|
|
9580
|
-
parallelism = 10
|
|
9605
|
+
parallelism = 10,
|
|
9606
|
+
httpClientOptions = {}
|
|
9581
9607
|
}) {
|
|
9582
9608
|
super();
|
|
9583
9609
|
this.verbose = verbose;
|
|
9584
9610
|
this.id = id ?? idGenerator();
|
|
9585
9611
|
this.parallelism = parallelism;
|
|
9586
9612
|
this.config = new ConnectionString(connectionString);
|
|
9613
|
+
this.httpClientOptions = {
|
|
9614
|
+
keepAlive: false,
|
|
9615
|
+
// Disabled for maximum creation speed
|
|
9616
|
+
maxSockets: 10,
|
|
9617
|
+
// Minimal sockets
|
|
9618
|
+
maxFreeSockets: 2,
|
|
9619
|
+
// Minimal pool
|
|
9620
|
+
timeout: 15e3,
|
|
9621
|
+
// Short timeout
|
|
9622
|
+
...httpClientOptions
|
|
9623
|
+
};
|
|
9587
9624
|
this.client = AwsS3Client || this.createClient();
|
|
9588
9625
|
}
|
|
9589
9626
|
createClient() {
|
|
9627
|
+
const httpAgent = new Agent$1(this.httpClientOptions);
|
|
9628
|
+
const httpsAgent = new Agent(this.httpClientOptions);
|
|
9629
|
+
const httpHandler = new nodeHttpHandler.NodeHttpHandler({
|
|
9630
|
+
httpAgent,
|
|
9631
|
+
httpsAgent
|
|
9632
|
+
});
|
|
9590
9633
|
let options = {
|
|
9591
9634
|
region: this.config.region,
|
|
9592
|
-
endpoint: this.config.endpoint
|
|
9635
|
+
endpoint: this.config.endpoint,
|
|
9636
|
+
requestHandler: httpHandler
|
|
9593
9637
|
};
|
|
9594
9638
|
if (this.config.forcePathStyle) options.forcePathStyle = true;
|
|
9595
9639
|
if (this.config.accessKeyId) {
|
|
@@ -13138,7 +13182,14 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
13138
13182
|
"delete",
|
|
13139
13183
|
"deleteMany",
|
|
13140
13184
|
"exists",
|
|
13141
|
-
"getMany"
|
|
13185
|
+
"getMany",
|
|
13186
|
+
"content",
|
|
13187
|
+
"hasContent",
|
|
13188
|
+
"query",
|
|
13189
|
+
"getFromPartition",
|
|
13190
|
+
"setContent",
|
|
13191
|
+
"deleteContent",
|
|
13192
|
+
"replace"
|
|
13142
13193
|
];
|
|
13143
13194
|
for (const method of this._middlewareMethods) {
|
|
13144
13195
|
this._middlewares.set(method, []);
|
|
@@ -13317,7 +13368,7 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
13317
13368
|
super();
|
|
13318
13369
|
this.version = "1";
|
|
13319
13370
|
this.s3dbVersion = (() => {
|
|
13320
|
-
const [ok, err, version] = try_fn_default(() => true ? "7.
|
|
13371
|
+
const [ok, err, version] = try_fn_default(() => true ? "7.5.0" : "latest");
|
|
13321
13372
|
return ok ? version : "latest";
|
|
13322
13373
|
})();
|
|
13323
13374
|
this.resources = {};
|
|
@@ -13330,6 +13381,7 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
13330
13381
|
this.cache = options.cache;
|
|
13331
13382
|
this.passphrase = options.passphrase || "secret";
|
|
13332
13383
|
this.versioningEnabled = options.versioningEnabled || false;
|
|
13384
|
+
this._initHooks();
|
|
13333
13385
|
let connectionString = options.connectionString;
|
|
13334
13386
|
if (!connectionString && (options.bucket || options.accessKeyId || options.secretAccessKey)) {
|
|
13335
13387
|
const { bucket, region, accessKeyId, secretAccessKey, endpoint, forcePathStyle } = options;
|
|
@@ -13807,6 +13859,131 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
13807
13859
|
} catch (err) {
|
|
13808
13860
|
}
|
|
13809
13861
|
}
|
|
13862
|
+
/**
|
|
13863
|
+
* Initialize hooks system for database operations
|
|
13864
|
+
* @private
|
|
13865
|
+
*/
|
|
13866
|
+
_initHooks() {
|
|
13867
|
+
this._hooks = /* @__PURE__ */ new Map();
|
|
13868
|
+
this._hookEvents = [
|
|
13869
|
+
"beforeConnect",
|
|
13870
|
+
"afterConnect",
|
|
13871
|
+
"beforeCreateResource",
|
|
13872
|
+
"afterCreateResource",
|
|
13873
|
+
"beforeUploadMetadata",
|
|
13874
|
+
"afterUploadMetadata",
|
|
13875
|
+
"beforeDisconnect",
|
|
13876
|
+
"afterDisconnect",
|
|
13877
|
+
"resourceCreated",
|
|
13878
|
+
"resourceUpdated"
|
|
13879
|
+
];
|
|
13880
|
+
for (const event of this._hookEvents) {
|
|
13881
|
+
this._hooks.set(event, []);
|
|
13882
|
+
}
|
|
13883
|
+
this._wrapHookableMethods();
|
|
13884
|
+
}
|
|
13885
|
+
/**
|
|
13886
|
+
* Wrap methods that can have hooks
|
|
13887
|
+
* @private
|
|
13888
|
+
*/
|
|
13889
|
+
_wrapHookableMethods() {
|
|
13890
|
+
if (this._hooksInstalled) return;
|
|
13891
|
+
this._originalConnect = this.connect.bind(this);
|
|
13892
|
+
this._originalCreateResource = this.createResource.bind(this);
|
|
13893
|
+
this._originalUploadMetadataFile = this.uploadMetadataFile.bind(this);
|
|
13894
|
+
this._originalDisconnect = this.disconnect.bind(this);
|
|
13895
|
+
this.connect = async (...args) => {
|
|
13896
|
+
await this._executeHooks("beforeConnect", { args });
|
|
13897
|
+
const result = await this._originalConnect(...args);
|
|
13898
|
+
await this._executeHooks("afterConnect", { result, args });
|
|
13899
|
+
return result;
|
|
13900
|
+
};
|
|
13901
|
+
this.createResource = async (config) => {
|
|
13902
|
+
await this._executeHooks("beforeCreateResource", { config });
|
|
13903
|
+
const resource = await this._originalCreateResource(config);
|
|
13904
|
+
await this._executeHooks("afterCreateResource", { resource, config });
|
|
13905
|
+
return resource;
|
|
13906
|
+
};
|
|
13907
|
+
this.uploadMetadataFile = async (...args) => {
|
|
13908
|
+
await this._executeHooks("beforeUploadMetadata", { args });
|
|
13909
|
+
const result = await this._originalUploadMetadataFile(...args);
|
|
13910
|
+
await this._executeHooks("afterUploadMetadata", { result, args });
|
|
13911
|
+
return result;
|
|
13912
|
+
};
|
|
13913
|
+
this.disconnect = async (...args) => {
|
|
13914
|
+
await this._executeHooks("beforeDisconnect", { args });
|
|
13915
|
+
const result = await this._originalDisconnect(...args);
|
|
13916
|
+
await this._executeHooks("afterDisconnect", { result, args });
|
|
13917
|
+
return result;
|
|
13918
|
+
};
|
|
13919
|
+
this._hooksInstalled = true;
|
|
13920
|
+
}
|
|
13921
|
+
/**
|
|
13922
|
+
* Add a hook for a specific database event
|
|
13923
|
+
* @param {string} event - Hook event name
|
|
13924
|
+
* @param {Function} fn - Hook function
|
|
13925
|
+
* @example
|
|
13926
|
+
* database.addHook('afterCreateResource', async ({ resource }) => {
|
|
13927
|
+
* console.log('Resource created:', resource.name);
|
|
13928
|
+
* });
|
|
13929
|
+
*/
|
|
13930
|
+
addHook(event, fn) {
|
|
13931
|
+
if (!this._hooks) this._initHooks();
|
|
13932
|
+
if (!this._hooks.has(event)) {
|
|
13933
|
+
throw new Error(`Unknown hook event: ${event}. Available events: ${this._hookEvents.join(", ")}`);
|
|
13934
|
+
}
|
|
13935
|
+
if (typeof fn !== "function") {
|
|
13936
|
+
throw new Error("Hook function must be a function");
|
|
13937
|
+
}
|
|
13938
|
+
this._hooks.get(event).push(fn);
|
|
13939
|
+
}
|
|
13940
|
+
/**
|
|
13941
|
+
* Execute hooks for a specific event
|
|
13942
|
+
* @param {string} event - Hook event name
|
|
13943
|
+
* @param {Object} context - Context data to pass to hooks
|
|
13944
|
+
* @private
|
|
13945
|
+
*/
|
|
13946
|
+
async _executeHooks(event, context = {}) {
|
|
13947
|
+
if (!this._hooks || !this._hooks.has(event)) return;
|
|
13948
|
+
const hooks = this._hooks.get(event);
|
|
13949
|
+
for (const hook of hooks) {
|
|
13950
|
+
try {
|
|
13951
|
+
await hook({ database: this, ...context });
|
|
13952
|
+
} catch (error) {
|
|
13953
|
+
this.emit("hookError", { event, error, context });
|
|
13954
|
+
}
|
|
13955
|
+
}
|
|
13956
|
+
}
|
|
13957
|
+
/**
|
|
13958
|
+
* Remove a hook for a specific event
|
|
13959
|
+
* @param {string} event - Hook event name
|
|
13960
|
+
* @param {Function} fn - Hook function to remove
|
|
13961
|
+
*/
|
|
13962
|
+
removeHook(event, fn) {
|
|
13963
|
+
if (!this._hooks || !this._hooks.has(event)) return;
|
|
13964
|
+
const hooks = this._hooks.get(event);
|
|
13965
|
+
const index = hooks.indexOf(fn);
|
|
13966
|
+
if (index > -1) {
|
|
13967
|
+
hooks.splice(index, 1);
|
|
13968
|
+
}
|
|
13969
|
+
}
|
|
13970
|
+
/**
|
|
13971
|
+
* Get all hooks for a specific event
|
|
13972
|
+
* @param {string} event - Hook event name
|
|
13973
|
+
* @returns {Function[]} Array of hook functions
|
|
13974
|
+
*/
|
|
13975
|
+
getHooks(event) {
|
|
13976
|
+
if (!this._hooks || !this._hooks.has(event)) return [];
|
|
13977
|
+
return [...this._hooks.get(event)];
|
|
13978
|
+
}
|
|
13979
|
+
/**
|
|
13980
|
+
* Clear all hooks for a specific event
|
|
13981
|
+
* @param {string} event - Hook event name
|
|
13982
|
+
*/
|
|
13983
|
+
clearHooks(event) {
|
|
13984
|
+
if (!this._hooks || !this._hooks.has(event)) return;
|
|
13985
|
+
this._hooks.get(event).length = 0;
|
|
13986
|
+
}
|
|
13810
13987
|
}
|
|
13811
13988
|
class S3db extends Database {
|
|
13812
13989
|
}
|
|
@@ -14539,6 +14716,10 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
14539
14716
|
}
|
|
14540
14717
|
return filtered;
|
|
14541
14718
|
}
|
|
14719
|
+
async getCompleteData(resource, data) {
|
|
14720
|
+
const [ok, err, completeRecord] = await try_fn_default(() => resource.get(data.id));
|
|
14721
|
+
return ok ? completeRecord : data;
|
|
14722
|
+
}
|
|
14542
14723
|
installEventListeners(resource, database, plugin) {
|
|
14543
14724
|
if (!resource || this.eventListenersInstalled.has(resource.name) || resource.name === this.config.replicatorLogResource) {
|
|
14544
14725
|
return;
|
|
@@ -14557,8 +14738,9 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
14557
14738
|
});
|
|
14558
14739
|
resource.on("update", async (data, beforeData) => {
|
|
14559
14740
|
const [ok, error] = await try_fn_default(async () => {
|
|
14560
|
-
const completeData =
|
|
14561
|
-
|
|
14741
|
+
const completeData = await plugin.getCompleteData(resource, data);
|
|
14742
|
+
const dataWithTimestamp = { ...completeData, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
14743
|
+
await plugin.processReplicatorEvent("update", resource.name, completeData.id, dataWithTimestamp, beforeData);
|
|
14562
14744
|
});
|
|
14563
14745
|
if (!ok) {
|
|
14564
14746
|
if (this.config.verbose) {
|
|
@@ -14580,67 +14762,55 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
14580
14762
|
});
|
|
14581
14763
|
this.eventListenersInstalled.add(resource.name);
|
|
14582
14764
|
}
|
|
14583
|
-
/**
|
|
14584
|
-
* Get complete data by always fetching the full record from the resource
|
|
14585
|
-
* This ensures we always have the complete data regardless of behavior or data size
|
|
14586
|
-
*/
|
|
14587
|
-
async getCompleteData(resource, data) {
|
|
14588
|
-
const [ok, err, completeRecord] = await try_fn_default(() => resource.get(data.id));
|
|
14589
|
-
return ok ? completeRecord : data;
|
|
14590
|
-
}
|
|
14591
14765
|
async setup(database) {
|
|
14592
14766
|
this.database = database;
|
|
14593
|
-
|
|
14594
|
-
await
|
|
14595
|
-
|
|
14596
|
-
|
|
14597
|
-
|
|
14598
|
-
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
|
|
14604
|
-
|
|
14605
|
-
|
|
14606
|
-
|
|
14607
|
-
|
|
14608
|
-
|
|
14609
|
-
|
|
14610
|
-
resourceName: "string",
|
|
14611
|
-
recordId: "string",
|
|
14612
|
-
data: "string",
|
|
14613
|
-
error: "string|optional",
|
|
14614
|
-
replicator: "string",
|
|
14615
|
-
timestamp: "string",
|
|
14616
|
-
status: "string"
|
|
14617
|
-
}
|
|
14618
|
-
});
|
|
14619
|
-
}
|
|
14620
|
-
});
|
|
14621
|
-
if (!logOk) {
|
|
14622
|
-
if (this.config.verbose) {
|
|
14623
|
-
console.warn(`[ReplicatorPlugin] Failed to create log resource ${this.config.replicatorLogResource}: ${logError.message}`);
|
|
14767
|
+
if (this.config.persistReplicatorLog) {
|
|
14768
|
+
const [ok, err, logResource] = await try_fn_default(() => database.createResource({
|
|
14769
|
+
name: this.config.replicatorLogResource || "replicator_logs",
|
|
14770
|
+
attributes: {
|
|
14771
|
+
id: "string|required",
|
|
14772
|
+
resource: "string|required",
|
|
14773
|
+
action: "string|required",
|
|
14774
|
+
data: "json",
|
|
14775
|
+
timestamp: "number|required",
|
|
14776
|
+
createdAt: "string|required"
|
|
14777
|
+
},
|
|
14778
|
+
behavior: "truncate-data"
|
|
14779
|
+
}));
|
|
14780
|
+
if (ok) {
|
|
14781
|
+
this.replicatorLogResource = logResource;
|
|
14782
|
+
} else {
|
|
14783
|
+
this.replicatorLogResource = database.resources[this.config.replicatorLogResource || "replicator_logs"];
|
|
14624
14784
|
}
|
|
14625
|
-
this.emit("replicator_log_resource_creation_error", {
|
|
14626
|
-
resourceName: this.config.replicatorLogResource,
|
|
14627
|
-
error: logError.message
|
|
14628
|
-
});
|
|
14629
14785
|
}
|
|
14630
|
-
await this.
|
|
14631
|
-
|
|
14632
|
-
|
|
14633
|
-
|
|
14634
|
-
if (resource) {
|
|
14786
|
+
await this.initializeReplicators(database);
|
|
14787
|
+
this.installDatabaseHooks();
|
|
14788
|
+
for (const resource of Object.values(database.resources)) {
|
|
14789
|
+
if (resource.name !== (this.config.replicatorLogResource || "replicator_logs")) {
|
|
14635
14790
|
this.installEventListeners(resource, database, this);
|
|
14636
14791
|
}
|
|
14637
|
-
return resource;
|
|
14638
|
-
};
|
|
14639
|
-
for (const resourceName in database.resources) {
|
|
14640
|
-
const resource = database.resources[resourceName];
|
|
14641
|
-
this.installEventListeners(resource, database, this);
|
|
14642
14792
|
}
|
|
14643
14793
|
}
|
|
14794
|
+
async start() {
|
|
14795
|
+
}
|
|
14796
|
+
async stop() {
|
|
14797
|
+
for (const replicator of this.replicators || []) {
|
|
14798
|
+
if (replicator && typeof replicator.cleanup === "function") {
|
|
14799
|
+
await replicator.cleanup();
|
|
14800
|
+
}
|
|
14801
|
+
}
|
|
14802
|
+
this.removeDatabaseHooks();
|
|
14803
|
+
}
|
|
14804
|
+
installDatabaseHooks() {
|
|
14805
|
+
this.database.addHook("afterCreateResource", (resource) => {
|
|
14806
|
+
if (resource.name !== (this.config.replicatorLogResource || "replicator_logs")) {
|
|
14807
|
+
this.installEventListeners(resource, this.database, this);
|
|
14808
|
+
}
|
|
14809
|
+
});
|
|
14810
|
+
}
|
|
14811
|
+
removeDatabaseHooks() {
|
|
14812
|
+
this.database.removeHook("afterCreateResource", this.installEventListeners.bind(this));
|
|
14813
|
+
}
|
|
14644
14814
|
createReplicator(driver, config, resources, client) {
|
|
14645
14815
|
return createReplicator(driver, config, resources, client);
|
|
14646
14816
|
}
|
|
@@ -14656,10 +14826,6 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
14656
14826
|
}
|
|
14657
14827
|
}
|
|
14658
14828
|
}
|
|
14659
|
-
async start() {
|
|
14660
|
-
}
|
|
14661
|
-
async stop() {
|
|
14662
|
-
}
|
|
14663
14829
|
async uploadMetadataFile(database) {
|
|
14664
14830
|
if (typeof database.uploadMetadataFile === "function") {
|
|
14665
14831
|
await database.uploadMetadataFile();
|
|
@@ -15062,4 +15228,4 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
15062
15228
|
|
|
15063
15229
|
return exports;
|
|
15064
15230
|
|
|
15065
|
-
})({}, nanoid, zlib, PromisePool, streams, promises, crypto, _, stringify, AWS, flat, FastestValidator);
|
|
15231
|
+
})({}, nanoid, zlib, PromisePool, streams, promises, crypto, _, stringify, nodeHttpHandler, AWS, flat, FastestValidator);
|