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