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.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
- return await this.getAnalytics(resourceName, field, {
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
- return await this.getAnalytics(resourceName, field, {
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
- return await this.getAnalytics(resourceName, field, {
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
- return await this.getAnalytics(resourceName, field, {
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
- return await this.getAnalytics(resourceName, field, {
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.4" : "latest");
11651
+ const [ok, err, version] = tryFn(() => true ? "10.0.5" : "latest");
11564
11652
  return ok ? version : "latest";
11565
11653
  })();
11566
11654
  this.resources = {};