s3db.js 10.0.4 → 10.0.5
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/s3db-cli.js +0 -0
- package/dist/s3db.cjs.js +99 -11
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +99 -11
- package/dist/s3db.es.js.map +1 -1
- package/mcp/README.md +1728 -0
- package/package.json +24 -22
- package/src/plugins/eventual-consistency.plugin.js +122 -10
package/dist/s3db.es.js
CHANGED
|
@@ -5348,79 +5348,163 @@ class EventualConsistencyPlugin extends Plugin {
|
|
|
5348
5348
|
recordCount: a.recordCount
|
|
5349
5349
|
}));
|
|
5350
5350
|
}
|
|
5351
|
+
/**
|
|
5352
|
+
* Fill gaps in analytics data with zeros for continuous time series
|
|
5353
|
+
* @private
|
|
5354
|
+
* @param {Array} data - Sparse analytics data
|
|
5355
|
+
* @param {string} period - Period type ('hour', 'day', 'month')
|
|
5356
|
+
* @param {string} startDate - Start date (ISO format)
|
|
5357
|
+
* @param {string} endDate - End date (ISO format)
|
|
5358
|
+
* @returns {Array} Complete time series with gaps filled
|
|
5359
|
+
*/
|
|
5360
|
+
_fillGaps(data, period, startDate, endDate) {
|
|
5361
|
+
if (!data || data.length === 0) {
|
|
5362
|
+
data = [];
|
|
5363
|
+
}
|
|
5364
|
+
const dataMap = /* @__PURE__ */ new Map();
|
|
5365
|
+
data.forEach((item) => {
|
|
5366
|
+
dataMap.set(item.cohort, item);
|
|
5367
|
+
});
|
|
5368
|
+
const result = [];
|
|
5369
|
+
const emptyRecord = {
|
|
5370
|
+
count: 0,
|
|
5371
|
+
sum: 0,
|
|
5372
|
+
avg: 0,
|
|
5373
|
+
min: 0,
|
|
5374
|
+
max: 0,
|
|
5375
|
+
recordCount: 0
|
|
5376
|
+
};
|
|
5377
|
+
if (period === "hour") {
|
|
5378
|
+
const start = /* @__PURE__ */ new Date(startDate + "T00:00:00Z");
|
|
5379
|
+
const end = /* @__PURE__ */ new Date(endDate + "T23:59:59Z");
|
|
5380
|
+
for (let dt = new Date(start); dt <= end; dt.setHours(dt.getHours() + 1)) {
|
|
5381
|
+
const cohort = dt.toISOString().substring(0, 13);
|
|
5382
|
+
result.push(dataMap.get(cohort) || { cohort, ...emptyRecord });
|
|
5383
|
+
}
|
|
5384
|
+
} else if (period === "day") {
|
|
5385
|
+
const start = new Date(startDate);
|
|
5386
|
+
const end = new Date(endDate);
|
|
5387
|
+
for (let dt = new Date(start); dt <= end; dt.setDate(dt.getDate() + 1)) {
|
|
5388
|
+
const cohort = dt.toISOString().substring(0, 10);
|
|
5389
|
+
result.push(dataMap.get(cohort) || { cohort, ...emptyRecord });
|
|
5390
|
+
}
|
|
5391
|
+
} else if (period === "month") {
|
|
5392
|
+
const startYear = parseInt(startDate.substring(0, 4));
|
|
5393
|
+
const startMonth = parseInt(startDate.substring(5, 7));
|
|
5394
|
+
const endYear = parseInt(endDate.substring(0, 4));
|
|
5395
|
+
const endMonth = parseInt(endDate.substring(5, 7));
|
|
5396
|
+
for (let year = startYear; year <= endYear; year++) {
|
|
5397
|
+
const firstMonth = year === startYear ? startMonth : 1;
|
|
5398
|
+
const lastMonth = year === endYear ? endMonth : 12;
|
|
5399
|
+
for (let month = firstMonth; month <= lastMonth; month++) {
|
|
5400
|
+
const cohort = `${year}-${month.toString().padStart(2, "0")}`;
|
|
5401
|
+
result.push(dataMap.get(cohort) || { cohort, ...emptyRecord });
|
|
5402
|
+
}
|
|
5403
|
+
}
|
|
5404
|
+
}
|
|
5405
|
+
return result;
|
|
5406
|
+
}
|
|
5351
5407
|
/**
|
|
5352
5408
|
* Get analytics for entire month, broken down by days
|
|
5353
5409
|
* @param {string} resourceName - Resource name
|
|
5354
5410
|
* @param {string} field - Field name
|
|
5355
5411
|
* @param {string} month - Month in YYYY-MM format
|
|
5412
|
+
* @param {Object} options - Options
|
|
5413
|
+
* @param {boolean} options.fillGaps - Fill missing days with zeros (default: false)
|
|
5356
5414
|
* @returns {Promise<Array>} Daily analytics for the month
|
|
5357
5415
|
*/
|
|
5358
|
-
async getMonthByDay(resourceName, field, month) {
|
|
5416
|
+
async getMonthByDay(resourceName, field, month, options = {}) {
|
|
5359
5417
|
const year = parseInt(month.substring(0, 4));
|
|
5360
5418
|
const monthNum = parseInt(month.substring(5, 7));
|
|
5361
5419
|
const firstDay = new Date(year, monthNum - 1, 1);
|
|
5362
5420
|
const lastDay = new Date(year, monthNum, 0);
|
|
5363
5421
|
const startDate = firstDay.toISOString().substring(0, 10);
|
|
5364
5422
|
const endDate = lastDay.toISOString().substring(0, 10);
|
|
5365
|
-
|
|
5423
|
+
const data = await this.getAnalytics(resourceName, field, {
|
|
5366
5424
|
period: "day",
|
|
5367
5425
|
startDate,
|
|
5368
5426
|
endDate
|
|
5369
5427
|
});
|
|
5428
|
+
if (options.fillGaps) {
|
|
5429
|
+
return this._fillGaps(data, "day", startDate, endDate);
|
|
5430
|
+
}
|
|
5431
|
+
return data;
|
|
5370
5432
|
}
|
|
5371
5433
|
/**
|
|
5372
5434
|
* Get analytics for entire day, broken down by hours
|
|
5373
5435
|
* @param {string} resourceName - Resource name
|
|
5374
5436
|
* @param {string} field - Field name
|
|
5375
5437
|
* @param {string} date - Date in YYYY-MM-DD format
|
|
5438
|
+
* @param {Object} options - Options
|
|
5439
|
+
* @param {boolean} options.fillGaps - Fill missing hours with zeros (default: false)
|
|
5376
5440
|
* @returns {Promise<Array>} Hourly analytics for the day
|
|
5377
5441
|
*/
|
|
5378
|
-
async getDayByHour(resourceName, field, date) {
|
|
5379
|
-
|
|
5442
|
+
async getDayByHour(resourceName, field, date, options = {}) {
|
|
5443
|
+
const data = await this.getAnalytics(resourceName, field, {
|
|
5380
5444
|
period: "hour",
|
|
5381
5445
|
date
|
|
5382
5446
|
});
|
|
5447
|
+
if (options.fillGaps) {
|
|
5448
|
+
return this._fillGaps(data, "hour", date, date);
|
|
5449
|
+
}
|
|
5450
|
+
return data;
|
|
5383
5451
|
}
|
|
5384
5452
|
/**
|
|
5385
5453
|
* Get analytics for last N days, broken down by days
|
|
5386
5454
|
* @param {string} resourceName - Resource name
|
|
5387
5455
|
* @param {string} field - Field name
|
|
5388
5456
|
* @param {number} days - Number of days to look back (default: 7)
|
|
5457
|
+
* @param {Object} options - Options
|
|
5458
|
+
* @param {boolean} options.fillGaps - Fill missing days with zeros (default: false)
|
|
5389
5459
|
* @returns {Promise<Array>} Daily analytics
|
|
5390
5460
|
*/
|
|
5391
|
-
async getLastNDays(resourceName, field, days = 7) {
|
|
5461
|
+
async getLastNDays(resourceName, field, days = 7, options = {}) {
|
|
5392
5462
|
const dates = Array.from({ length: days }, (_, i) => {
|
|
5393
5463
|
const date = /* @__PURE__ */ new Date();
|
|
5394
5464
|
date.setDate(date.getDate() - i);
|
|
5395
5465
|
return date.toISOString().substring(0, 10);
|
|
5396
5466
|
}).reverse();
|
|
5397
|
-
|
|
5467
|
+
const data = await this.getAnalytics(resourceName, field, {
|
|
5398
5468
|
period: "day",
|
|
5399
5469
|
startDate: dates[0],
|
|
5400
5470
|
endDate: dates[dates.length - 1]
|
|
5401
5471
|
});
|
|
5472
|
+
if (options.fillGaps) {
|
|
5473
|
+
return this._fillGaps(data, "day", dates[0], dates[dates.length - 1]);
|
|
5474
|
+
}
|
|
5475
|
+
return data;
|
|
5402
5476
|
}
|
|
5403
5477
|
/**
|
|
5404
5478
|
* Get analytics for entire year, broken down by months
|
|
5405
5479
|
* @param {string} resourceName - Resource name
|
|
5406
5480
|
* @param {string} field - Field name
|
|
5407
5481
|
* @param {number} year - Year (e.g., 2025)
|
|
5482
|
+
* @param {Object} options - Options
|
|
5483
|
+
* @param {boolean} options.fillGaps - Fill missing months with zeros (default: false)
|
|
5408
5484
|
* @returns {Promise<Array>} Monthly analytics for the year
|
|
5409
5485
|
*/
|
|
5410
|
-
async getYearByMonth(resourceName, field, year) {
|
|
5411
|
-
|
|
5486
|
+
async getYearByMonth(resourceName, field, year, options = {}) {
|
|
5487
|
+
const data = await this.getAnalytics(resourceName, field, {
|
|
5412
5488
|
period: "month",
|
|
5413
5489
|
year
|
|
5414
5490
|
});
|
|
5491
|
+
if (options.fillGaps) {
|
|
5492
|
+
const startDate = `${year}-01`;
|
|
5493
|
+
const endDate = `${year}-12`;
|
|
5494
|
+
return this._fillGaps(data, "month", startDate, endDate);
|
|
5495
|
+
}
|
|
5496
|
+
return data;
|
|
5415
5497
|
}
|
|
5416
5498
|
/**
|
|
5417
5499
|
* Get analytics for entire month, broken down by hours
|
|
5418
5500
|
* @param {string} resourceName - Resource name
|
|
5419
5501
|
* @param {string} field - Field name
|
|
5420
5502
|
* @param {string} month - Month in YYYY-MM format (or 'last' for previous month)
|
|
5503
|
+
* @param {Object} options - Options
|
|
5504
|
+
* @param {boolean} options.fillGaps - Fill missing hours with zeros (default: false)
|
|
5421
5505
|
* @returns {Promise<Array>} Hourly analytics for the month (up to 24*31=744 records)
|
|
5422
5506
|
*/
|
|
5423
|
-
async getMonthByHour(resourceName, field, month) {
|
|
5507
|
+
async getMonthByHour(resourceName, field, month, options = {}) {
|
|
5424
5508
|
let year, monthNum;
|
|
5425
5509
|
if (month === "last") {
|
|
5426
5510
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -5435,11 +5519,15 @@ class EventualConsistencyPlugin extends Plugin {
|
|
|
5435
5519
|
const lastDay = new Date(year, monthNum, 0);
|
|
5436
5520
|
const startDate = firstDay.toISOString().substring(0, 10);
|
|
5437
5521
|
const endDate = lastDay.toISOString().substring(0, 10);
|
|
5438
|
-
|
|
5522
|
+
const data = await this.getAnalytics(resourceName, field, {
|
|
5439
5523
|
period: "hour",
|
|
5440
5524
|
startDate,
|
|
5441
5525
|
endDate
|
|
5442
5526
|
});
|
|
5527
|
+
if (options.fillGaps) {
|
|
5528
|
+
return this._fillGaps(data, "hour", startDate, endDate);
|
|
5529
|
+
}
|
|
5530
|
+
return data;
|
|
5443
5531
|
}
|
|
5444
5532
|
/**
|
|
5445
5533
|
* Get top records by volume
|
|
@@ -11560,7 +11648,7 @@ class Database extends EventEmitter {
|
|
|
11560
11648
|
this.id = idGenerator(7);
|
|
11561
11649
|
this.version = "1";
|
|
11562
11650
|
this.s3dbVersion = (() => {
|
|
11563
|
-
const [ok, err, version] = tryFn(() => true ? "10.0.
|
|
11651
|
+
const [ok, err, version] = tryFn(() => true ? "10.0.5" : "latest");
|
|
11564
11652
|
return ok ? version : "latest";
|
|
11565
11653
|
})();
|
|
11566
11654
|
this.resources = {};
|