s3db.js 11.1.0 → 11.2.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/SECURITY.md +76 -0
- package/dist/s3db.cjs.js +144 -3
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +144 -3
- package/dist/s3db.es.js.map +1 -1
- package/package.json +2 -1
- package/src/plugins/eventual-consistency/analytics.js +164 -2
- package/src/plugins/eventual-consistency/install.js +1 -1
- package/src/plugins/eventual-consistency/utils.js +64 -0
package/dist/s3db.es.js
CHANGED
|
@@ -5399,6 +5399,38 @@ function groupByCohort(transactions, cohortField) {
|
|
|
5399
5399
|
}
|
|
5400
5400
|
return groups;
|
|
5401
5401
|
}
|
|
5402
|
+
function ensureCohortHour(transaction, timezone = "UTC", verbose = false) {
|
|
5403
|
+
if (transaction.cohortHour) {
|
|
5404
|
+
return transaction;
|
|
5405
|
+
}
|
|
5406
|
+
if (transaction.timestamp) {
|
|
5407
|
+
const date = new Date(transaction.timestamp);
|
|
5408
|
+
const cohortInfo = getCohortInfo(date, timezone, verbose);
|
|
5409
|
+
if (verbose) {
|
|
5410
|
+
console.log(
|
|
5411
|
+
`[EventualConsistency] Transaction ${transaction.id} missing cohortHour, calculated from timestamp: ${cohortInfo.hour}`
|
|
5412
|
+
);
|
|
5413
|
+
}
|
|
5414
|
+
transaction.cohortHour = cohortInfo.hour;
|
|
5415
|
+
if (!transaction.cohortWeek) {
|
|
5416
|
+
transaction.cohortWeek = cohortInfo.week;
|
|
5417
|
+
}
|
|
5418
|
+
if (!transaction.cohortMonth) {
|
|
5419
|
+
transaction.cohortMonth = cohortInfo.month;
|
|
5420
|
+
}
|
|
5421
|
+
} else if (verbose) {
|
|
5422
|
+
console.warn(
|
|
5423
|
+
`[EventualConsistency] Transaction ${transaction.id} missing both cohortHour and timestamp, cannot calculate cohort`
|
|
5424
|
+
);
|
|
5425
|
+
}
|
|
5426
|
+
return transaction;
|
|
5427
|
+
}
|
|
5428
|
+
function ensureCohortHours(transactions, timezone = "UTC", verbose = false) {
|
|
5429
|
+
if (!transactions || !Array.isArray(transactions)) {
|
|
5430
|
+
return transactions;
|
|
5431
|
+
}
|
|
5432
|
+
return transactions.map((txn) => ensureCohortHour(txn, timezone, verbose));
|
|
5433
|
+
}
|
|
5402
5434
|
|
|
5403
5435
|
function createPartitionConfig() {
|
|
5404
5436
|
const partitions = {
|
|
@@ -6523,7 +6555,10 @@ async function getAnalytics(resourceName, field, options, fieldHandlers) {
|
|
|
6523
6555
|
if (!handler.analyticsResource) {
|
|
6524
6556
|
throw new Error("Analytics not enabled for this plugin");
|
|
6525
6557
|
}
|
|
6526
|
-
const { period = "day", date, startDate, endDate, month, year, breakdown = false } = options;
|
|
6558
|
+
const { period = "day", date, startDate, endDate, month, year, breakdown = false, recordId } = options;
|
|
6559
|
+
if (recordId) {
|
|
6560
|
+
return await getAnalyticsForRecord(resourceName, field, recordId, options, handler);
|
|
6561
|
+
}
|
|
6527
6562
|
const [ok, err, allAnalytics] = await tryFn(
|
|
6528
6563
|
() => handler.analyticsResource.list()
|
|
6529
6564
|
);
|
|
@@ -6562,6 +6597,105 @@ async function getAnalytics(resourceName, field, options, fieldHandlers) {
|
|
|
6562
6597
|
recordCount: a.recordCount
|
|
6563
6598
|
}));
|
|
6564
6599
|
}
|
|
6600
|
+
async function getAnalyticsForRecord(resourceName, field, recordId, options, handler) {
|
|
6601
|
+
const { period = "day", date, startDate, endDate, month, year } = options;
|
|
6602
|
+
const [okTrue, errTrue, appliedTransactions] = await tryFn(
|
|
6603
|
+
() => handler.transactionResource.query({
|
|
6604
|
+
originalId: recordId,
|
|
6605
|
+
applied: true
|
|
6606
|
+
})
|
|
6607
|
+
);
|
|
6608
|
+
const [okFalse, errFalse, pendingTransactions] = await tryFn(
|
|
6609
|
+
() => handler.transactionResource.query({
|
|
6610
|
+
originalId: recordId,
|
|
6611
|
+
applied: false
|
|
6612
|
+
})
|
|
6613
|
+
);
|
|
6614
|
+
let allTransactions = [
|
|
6615
|
+
...okTrue && appliedTransactions ? appliedTransactions : [],
|
|
6616
|
+
...okFalse && pendingTransactions ? pendingTransactions : []
|
|
6617
|
+
];
|
|
6618
|
+
if (allTransactions.length === 0) {
|
|
6619
|
+
return [];
|
|
6620
|
+
}
|
|
6621
|
+
allTransactions = ensureCohortHours(allTransactions, handler.config?.cohort?.timezone || "UTC", false);
|
|
6622
|
+
let filtered = allTransactions;
|
|
6623
|
+
if (date) {
|
|
6624
|
+
if (period === "hour") {
|
|
6625
|
+
filtered = filtered.filter((t) => t.cohortHour && t.cohortHour.startsWith(date));
|
|
6626
|
+
} else if (period === "day") {
|
|
6627
|
+
filtered = filtered.filter((t) => t.cohortDate === date);
|
|
6628
|
+
} else if (period === "month") {
|
|
6629
|
+
filtered = filtered.filter((t) => t.cohortMonth && t.cohortMonth.startsWith(date));
|
|
6630
|
+
}
|
|
6631
|
+
} else if (startDate && endDate) {
|
|
6632
|
+
if (period === "hour") {
|
|
6633
|
+
filtered = filtered.filter((t) => t.cohortHour && t.cohortHour >= startDate && t.cohortHour <= endDate);
|
|
6634
|
+
} else if (period === "day") {
|
|
6635
|
+
filtered = filtered.filter((t) => t.cohortDate && t.cohortDate >= startDate && t.cohortDate <= endDate);
|
|
6636
|
+
} else if (period === "month") {
|
|
6637
|
+
filtered = filtered.filter((t) => t.cohortMonth && t.cohortMonth >= startDate && t.cohortMonth <= endDate);
|
|
6638
|
+
}
|
|
6639
|
+
} else if (month) {
|
|
6640
|
+
if (period === "hour") {
|
|
6641
|
+
filtered = filtered.filter((t) => t.cohortHour && t.cohortHour.startsWith(month));
|
|
6642
|
+
} else if (period === "day") {
|
|
6643
|
+
filtered = filtered.filter((t) => t.cohortDate && t.cohortDate.startsWith(month));
|
|
6644
|
+
}
|
|
6645
|
+
} else if (year) {
|
|
6646
|
+
if (period === "hour") {
|
|
6647
|
+
filtered = filtered.filter((t) => t.cohortHour && t.cohortHour.startsWith(String(year)));
|
|
6648
|
+
} else if (period === "day") {
|
|
6649
|
+
filtered = filtered.filter((t) => t.cohortDate && t.cohortDate.startsWith(String(year)));
|
|
6650
|
+
} else if (period === "month") {
|
|
6651
|
+
filtered = filtered.filter((t) => t.cohortMonth && t.cohortMonth.startsWith(String(year)));
|
|
6652
|
+
}
|
|
6653
|
+
}
|
|
6654
|
+
const cohortField = period === "hour" ? "cohortHour" : period === "day" ? "cohortDate" : "cohortMonth";
|
|
6655
|
+
const aggregated = aggregateTransactionsByCohort(filtered, cohortField);
|
|
6656
|
+
return aggregated;
|
|
6657
|
+
}
|
|
6658
|
+
function aggregateTransactionsByCohort(transactions, cohortField) {
|
|
6659
|
+
const groups = {};
|
|
6660
|
+
for (const txn of transactions) {
|
|
6661
|
+
const cohort = txn[cohortField];
|
|
6662
|
+
if (!cohort) continue;
|
|
6663
|
+
if (!groups[cohort]) {
|
|
6664
|
+
groups[cohort] = {
|
|
6665
|
+
cohort,
|
|
6666
|
+
count: 0,
|
|
6667
|
+
sum: 0,
|
|
6668
|
+
min: Infinity,
|
|
6669
|
+
max: -Infinity,
|
|
6670
|
+
recordCount: /* @__PURE__ */ new Set(),
|
|
6671
|
+
operations: {}
|
|
6672
|
+
};
|
|
6673
|
+
}
|
|
6674
|
+
const group = groups[cohort];
|
|
6675
|
+
const signedValue = txn.operation === "sub" ? -txn.value : txn.value;
|
|
6676
|
+
group.count++;
|
|
6677
|
+
group.sum += signedValue;
|
|
6678
|
+
group.min = Math.min(group.min, signedValue);
|
|
6679
|
+
group.max = Math.max(group.max, signedValue);
|
|
6680
|
+
group.recordCount.add(txn.originalId);
|
|
6681
|
+
const op = txn.operation;
|
|
6682
|
+
if (!group.operations[op]) {
|
|
6683
|
+
group.operations[op] = { count: 0, sum: 0 };
|
|
6684
|
+
}
|
|
6685
|
+
group.operations[op].count++;
|
|
6686
|
+
group.operations[op].sum += signedValue;
|
|
6687
|
+
}
|
|
6688
|
+
return Object.values(groups).map((g) => ({
|
|
6689
|
+
cohort: g.cohort,
|
|
6690
|
+
count: g.count,
|
|
6691
|
+
sum: g.sum,
|
|
6692
|
+
avg: g.sum / g.count,
|
|
6693
|
+
min: g.min === Infinity ? 0 : g.min,
|
|
6694
|
+
max: g.max === -Infinity ? 0 : g.max,
|
|
6695
|
+
recordCount: g.recordCount.size,
|
|
6696
|
+
operations: g.operations
|
|
6697
|
+
})).sort((a, b) => a.cohort.localeCompare(b.cohort));
|
|
6698
|
+
}
|
|
6565
6699
|
async function getMonthByDay(resourceName, field, month, options, fieldHandlers) {
|
|
6566
6700
|
const year = parseInt(month.substring(0, 4));
|
|
6567
6701
|
const monthNum = parseInt(month.substring(5, 7));
|
|
@@ -6596,6 +6730,8 @@ async function getLastNDays(resourceName, field, days, options, fieldHandlers) {
|
|
|
6596
6730
|
return date.toISOString().substring(0, 10);
|
|
6597
6731
|
}).reverse();
|
|
6598
6732
|
const data = await getAnalytics(resourceName, field, {
|
|
6733
|
+
...options,
|
|
6734
|
+
// ✅ Include all options (recordId, etc.)
|
|
6599
6735
|
period: "day",
|
|
6600
6736
|
startDate: dates[0],
|
|
6601
6737
|
endDate: dates[dates.length - 1]
|
|
@@ -6789,6 +6925,8 @@ async function getLastNHours(resourceName, field, hours = 24, options, fieldHand
|
|
|
6789
6925
|
const startHour = hoursAgo.toISOString().substring(0, 13);
|
|
6790
6926
|
const endHour = now.toISOString().substring(0, 13);
|
|
6791
6927
|
const data = await getAnalytics(resourceName, field, {
|
|
6928
|
+
...options,
|
|
6929
|
+
// ✅ Include all options (recordId, etc.)
|
|
6792
6930
|
period: "hour",
|
|
6793
6931
|
startDate: startHour,
|
|
6794
6932
|
endDate: endHour
|
|
@@ -6836,6 +6974,8 @@ async function getLastNMonths(resourceName, field, months = 12, options, fieldHa
|
|
|
6836
6974
|
const startDate = monthsAgo.toISOString().substring(0, 7);
|
|
6837
6975
|
const endDate = now.toISOString().substring(0, 7);
|
|
6838
6976
|
const data = await getAnalytics(resourceName, field, {
|
|
6977
|
+
...options,
|
|
6978
|
+
// ✅ Include all options (recordId, etc.)
|
|
6839
6979
|
period: "month",
|
|
6840
6980
|
startDate,
|
|
6841
6981
|
endDate
|
|
@@ -7028,7 +7168,8 @@ async function completeFieldSetup(handler, database, config, plugin) {
|
|
|
7028
7168
|
operation: "string|required",
|
|
7029
7169
|
timestamp: "string|required",
|
|
7030
7170
|
cohortDate: "string|required",
|
|
7031
|
-
cohortHour: "string|
|
|
7171
|
+
cohortHour: "string|optional",
|
|
7172
|
+
// ✅ FIX BUG #2: Changed from required to optional for migration compatibility
|
|
7032
7173
|
cohortWeek: "string|optional",
|
|
7033
7174
|
cohortMonth: "string|optional",
|
|
7034
7175
|
source: "string|optional",
|
|
@@ -13606,7 +13747,7 @@ class Database extends EventEmitter {
|
|
|
13606
13747
|
this.id = idGenerator(7);
|
|
13607
13748
|
this.version = "1";
|
|
13608
13749
|
this.s3dbVersion = (() => {
|
|
13609
|
-
const [ok, err, version] = tryFn(() => true ? "11.
|
|
13750
|
+
const [ok, err, version] = tryFn(() => true ? "11.2.0" : "latest");
|
|
13610
13751
|
return ok ? version : "latest";
|
|
13611
13752
|
})();
|
|
13612
13753
|
this.resources = {};
|