pond-ts 0.2.0 → 0.4.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.
Files changed (97) hide show
  1. package/dist/LiveAggregation.d.ts +54 -0
  2. package/dist/LiveAggregation.d.ts.map +1 -0
  3. package/dist/LiveAggregation.js +240 -0
  4. package/dist/LiveAggregation.js.map +1 -0
  5. package/dist/LiveRollingAggregation.d.ts +43 -0
  6. package/dist/LiveRollingAggregation.d.ts.map +1 -0
  7. package/dist/LiveRollingAggregation.js +178 -0
  8. package/dist/LiveRollingAggregation.js.map +1 -0
  9. package/dist/LiveSeries.d.ts +63 -0
  10. package/dist/LiveSeries.d.ts.map +1 -0
  11. package/dist/LiveSeries.js +340 -0
  12. package/dist/LiveSeries.js.map +1 -0
  13. package/dist/LiveView.d.ts +59 -0
  14. package/dist/LiveView.d.ts.map +1 -0
  15. package/dist/LiveView.js +337 -0
  16. package/dist/LiveView.js.map +1 -0
  17. package/dist/Sequence.d.ts +2 -2
  18. package/dist/Sequence.d.ts.map +1 -1
  19. package/dist/Sequence.js +4 -57
  20. package/dist/Sequence.js.map +1 -1
  21. package/dist/TimeSeries.d.ts +133 -3
  22. package/dist/TimeSeries.d.ts.map +1 -1
  23. package/dist/TimeSeries.js +748 -444
  24. package/dist/TimeSeries.js.map +1 -1
  25. package/dist/index.d.ts +11 -2
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +4 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/reducers/avg.d.ts +3 -0
  30. package/dist/reducers/avg.d.ts.map +1 -0
  31. package/dist/reducers/avg.js +45 -0
  32. package/dist/reducers/avg.js.map +1 -0
  33. package/dist/reducers/count.d.ts +3 -0
  34. package/dist/reducers/count.d.ts.map +1 -0
  35. package/dist/reducers/count.js +35 -0
  36. package/dist/reducers/count.js.map +1 -0
  37. package/dist/reducers/difference.d.ts +3 -0
  38. package/dist/reducers/difference.d.ts.map +1 -0
  39. package/dist/reducers/difference.js +48 -0
  40. package/dist/reducers/difference.js.map +1 -0
  41. package/dist/reducers/first.d.ts +3 -0
  42. package/dist/reducers/first.d.ts.map +1 -0
  43. package/dist/reducers/first.js +23 -0
  44. package/dist/reducers/first.js.map +1 -0
  45. package/dist/reducers/index.d.ts +4 -0
  46. package/dist/reducers/index.d.ts.map +1 -0
  47. package/dist/reducers/index.js +35 -0
  48. package/dist/reducers/index.js.map +1 -0
  49. package/dist/reducers/keep.d.ts +3 -0
  50. package/dist/reducers/keep.d.ts.map +1 -0
  51. package/dist/reducers/keep.js +56 -0
  52. package/dist/reducers/keep.js.map +1 -0
  53. package/dist/reducers/last.d.ts +3 -0
  54. package/dist/reducers/last.d.ts.map +1 -0
  55. package/dist/reducers/last.js +23 -0
  56. package/dist/reducers/last.js.map +1 -0
  57. package/dist/reducers/max.d.ts +3 -0
  58. package/dist/reducers/max.d.ts.map +1 -0
  59. package/dist/reducers/max.js +25 -0
  60. package/dist/reducers/max.js.map +1 -0
  61. package/dist/reducers/median.d.ts +3 -0
  62. package/dist/reducers/median.d.ts.map +1 -0
  63. package/dist/reducers/median.js +39 -0
  64. package/dist/reducers/median.js.map +1 -0
  65. package/dist/reducers/min.d.ts +3 -0
  66. package/dist/reducers/min.d.ts.map +1 -0
  67. package/dist/reducers/min.js +25 -0
  68. package/dist/reducers/min.js.map +1 -0
  69. package/dist/reducers/percentile.d.ts +5 -0
  70. package/dist/reducers/percentile.d.ts.map +1 -0
  71. package/dist/reducers/percentile.js +56 -0
  72. package/dist/reducers/percentile.js.map +1 -0
  73. package/dist/reducers/rolling.d.ts +15 -0
  74. package/dist/reducers/rolling.d.ts.map +1 -0
  75. package/dist/reducers/rolling.js +84 -0
  76. package/dist/reducers/rolling.js.map +1 -0
  77. package/dist/reducers/stdev.d.ts +3 -0
  78. package/dist/reducers/stdev.d.ts.map +1 -0
  79. package/dist/reducers/stdev.js +58 -0
  80. package/dist/reducers/stdev.js.map +1 -0
  81. package/dist/reducers/sum.d.ts +3 -0
  82. package/dist/reducers/sum.d.ts.map +1 -0
  83. package/dist/reducers/sum.js +35 -0
  84. package/dist/reducers/sum.js.map +1 -0
  85. package/dist/reducers/types.d.ts +58 -0
  86. package/dist/reducers/types.d.ts.map +1 -0
  87. package/dist/reducers/types.js +2 -0
  88. package/dist/reducers/types.js.map +1 -0
  89. package/dist/types.d.ts +35 -4
  90. package/dist/types.d.ts.map +1 -1
  91. package/dist/utils/duration.d.ts +3 -0
  92. package/dist/utils/duration.d.ts.map +1 -0
  93. package/dist/utils/duration.js +25 -0
  94. package/dist/utils/duration.js.map +1 -0
  95. package/package.json +5 -2
  96. package/LICENSE +0 -21
  97. package/README.md +0 -415
@@ -1,10 +1,14 @@
1
+ var _a;
1
2
  import { BoundedSequence } from './BoundedSequence.js';
2
3
  import { parseTimestampString } from './calendar.js';
3
4
  import { Interval } from './Interval.js';
4
5
  import { Time } from './Time.js';
5
6
  import { TimeRange } from './TimeRange.js';
7
+ import { Event } from './Event.js';
6
8
  import { Sequence } from './Sequence.js';
7
9
  import { validateAndNormalize } from './validate.js';
10
+ import { parseDuration } from './utils/duration.js';
11
+ import { resolveReducer, } from './reducers/index.js';
8
12
  function isObjectRow(value) {
9
13
  return typeof value === 'object' && value !== null && !Array.isArray(value);
10
14
  }
@@ -89,6 +93,23 @@ function parseJsonRows(schema, rows, options = {}) {
89
93
  }));
90
94
  });
91
95
  }
96
+ function serializeJsonKey(kind, key, rowFormat) {
97
+ if (kind === 'time') {
98
+ return key.begin();
99
+ }
100
+ if (kind === 'timeRange') {
101
+ return rowFormat === 'object'
102
+ ? { start: key.begin(), end: key.end() }
103
+ : [key.begin(), key.end()];
104
+ }
105
+ const interval = key;
106
+ return rowFormat === 'object'
107
+ ? { value: interval.value, start: interval.begin(), end: interval.end() }
108
+ : [interval.value, interval.begin(), interval.end()];
109
+ }
110
+ function serializeJsonValue(value) {
111
+ return value === undefined ? null : value;
112
+ }
92
113
  function toRows(schema, events) {
93
114
  return events.map((event) => {
94
115
  const data = event.data();
@@ -100,6 +121,20 @@ function toRows(schema, events) {
100
121
  ]);
101
122
  });
102
123
  }
124
+ function toObjects(schema, events) {
125
+ const keyColumn = schema[0];
126
+ const dataColumns = schema.slice(1);
127
+ return events.map((event) => {
128
+ const row = {
129
+ [keyColumn.name]: event.key(),
130
+ };
131
+ const data = event.data();
132
+ for (const column of dataColumns) {
133
+ row[column.name] = data[column.name];
134
+ }
135
+ return Object.freeze(row);
136
+ });
137
+ }
103
138
  function isEventKey(value) {
104
139
  return (typeof value === 'object' &&
105
140
  value !== null &&
@@ -316,278 +351,60 @@ function bucketOverlapsHalfOpen(bucket, event) {
316
351
  function aggregateValues(operation, values) {
317
352
  const defined = values.filter((value) => value !== undefined);
318
353
  const numeric = defined.filter((value) => typeof value === 'number');
319
- switch (operation) {
320
- case 'count':
321
- return defined.length;
322
- case 'sum':
323
- return numeric.reduce((sum, value) => sum + value, 0);
324
- case 'avg':
325
- return numeric.length === 0
326
- ? undefined
327
- : numeric.reduce((sum, value) => sum + value, 0) / numeric.length;
328
- case 'min':
329
- return numeric.length === 0
330
- ? undefined
331
- : numeric.reduce((left, right) => (left <= right ? left : right));
332
- case 'max':
333
- return numeric.length === 0
334
- ? undefined
335
- : numeric.reduce((left, right) => (left >= right ? left : right));
336
- case 'first':
337
- return defined[0];
338
- case 'last':
339
- return defined[defined.length - 1];
340
- }
354
+ return resolveReducer(operation).reduce(defined, numeric);
341
355
  }
342
- function createAggregateBucketState(operation) {
343
- if (operation === 'count') {
344
- let definedCount = 0;
345
- return {
346
- add(value) {
347
- if (value !== undefined) {
348
- definedCount += 1;
349
- }
350
- },
351
- snapshot() {
352
- return definedCount;
353
- },
354
- };
355
- }
356
- if (operation === 'sum') {
357
- let numericSum = 0;
358
- return {
359
- add(value) {
360
- if (typeof value === 'number') {
361
- numericSum += value;
362
- }
363
- },
364
- snapshot() {
365
- return numericSum;
366
- },
367
- };
368
- }
369
- if (operation === 'avg') {
370
- let numericSum = 0;
371
- let numericCount = 0;
372
- return {
373
- add(value) {
374
- if (typeof value === 'number') {
375
- numericSum += value;
376
- numericCount += 1;
377
- }
378
- },
379
- snapshot() {
380
- return numericCount === 0 ? undefined : numericSum / numericCount;
381
- },
382
- };
383
- }
384
- if (operation === 'min') {
385
- let numericValue;
386
- return {
387
- add(value) {
388
- if (typeof value !== 'number') {
389
- return;
390
- }
391
- numericValue =
392
- numericValue === undefined || value < numericValue
393
- ? value
394
- : numericValue;
395
- },
396
- snapshot() {
397
- return numericValue;
398
- },
399
- };
400
- }
401
- if (operation === 'max') {
402
- let numericValue;
403
- return {
404
- add(value) {
405
- if (typeof value !== 'number') {
406
- return;
407
- }
408
- numericValue =
409
- numericValue === undefined || value > numericValue
410
- ? value
411
- : numericValue;
412
- },
413
- snapshot() {
414
- return numericValue;
415
- },
416
- };
417
- }
418
- if (operation === 'first') {
419
- let firstValue;
420
- return {
421
- add(value) {
422
- if (firstValue === undefined && value !== undefined) {
423
- firstValue = value;
424
- }
425
- },
426
- snapshot() {
427
- return firstValue;
428
- },
429
- };
430
- }
431
- if (operation === 'last') {
432
- let lastValue;
433
- return {
434
- add(value) {
435
- if (value !== undefined) {
436
- lastValue = value;
437
- }
438
- },
439
- snapshot() {
440
- return lastValue;
441
- },
442
- };
443
- }
444
- throw new TypeError(`unsupported aggregate reducer: ${operation}`);
356
+ function isBuiltInAggregateReducer(reducer) {
357
+ return typeof reducer === 'string';
445
358
  }
446
- function createRollingReducerState(operation) {
447
- const compact = (entries, head) => {
448
- if (head > 0 && head * 2 >= entries.length) {
449
- return [entries.slice(head), 0];
450
- }
451
- return [entries, head];
452
- };
453
- if (operation === 'count') {
454
- let definedCount = 0;
455
- return {
456
- add(_index, value) {
457
- if (value !== undefined) {
458
- definedCount += 1;
459
- }
460
- },
461
- remove(_index, value) {
462
- if (value !== undefined) {
463
- definedCount -= 1;
464
- }
465
- },
466
- snapshot() {
467
- return definedCount;
468
- },
469
- };
470
- }
471
- if (operation === 'sum') {
472
- let numericSum = 0;
473
- return {
474
- add(_index, value) {
475
- if (typeof value === 'number') {
476
- numericSum += value;
477
- }
478
- },
479
- remove(_index, value) {
480
- if (typeof value === 'number') {
481
- numericSum -= value;
482
- }
483
- },
484
- snapshot() {
485
- return numericSum;
486
- },
487
- };
488
- }
489
- if (operation === 'avg') {
490
- let numericSum = 0;
491
- let numericCount = 0;
492
- return {
493
- add(_index, value) {
494
- if (typeof value === 'number') {
495
- numericSum += value;
496
- numericCount += 1;
497
- }
498
- },
499
- remove(_index, value) {
500
- if (typeof value === 'number') {
501
- numericSum -= value;
502
- numericCount -= 1;
503
- }
504
- },
505
- snapshot() {
506
- return numericCount === 0 ? undefined : numericSum / numericCount;
507
- },
508
- };
509
- }
510
- if (operation === 'min' || operation === 'max') {
511
- let entries = [];
512
- let head = 0;
513
- return {
514
- add(index, value) {
515
- if (typeof value !== 'number') {
516
- return;
517
- }
518
- while (entries.length > head) {
519
- const last = entries[entries.length - 1];
520
- if (operation === 'min' ? last.value <= value : last.value >= value) {
521
- break;
522
- }
523
- entries.pop();
524
- }
525
- entries.push({ index, value });
526
- },
527
- remove(index, value) {
528
- if (typeof value !== 'number') {
529
- return;
530
- }
531
- if (entries[head]?.index === index) {
532
- head += 1;
533
- [entries, head] = compact(entries, head);
534
- }
535
- },
536
- snapshot() {
537
- return entries[head]?.value;
538
- },
539
- };
540
- }
541
- if (operation === 'first' || operation === 'last') {
542
- let entries = [];
543
- let head = 0;
544
- return {
545
- add(index, value) {
546
- if (value !== undefined) {
547
- entries.push({ index, value });
548
- }
549
- },
550
- remove(index, value) {
551
- if (value === undefined) {
552
- return;
553
- }
554
- if (entries[head]?.index === index) {
555
- head += 1;
556
- [entries, head] = compact(entries, head);
557
- }
558
- },
559
- snapshot() {
560
- return operation === 'first'
561
- ? entries[head]?.value
562
- : entries[entries.length - 1]?.value;
563
- },
564
- };
565
- }
566
- throw new TypeError(`unsupported rolling reducer: ${operation}`);
359
+ function applyAggregateReducer(reducer, values) {
360
+ return isBuiltInAggregateReducer(reducer)
361
+ ? aggregateValues(reducer, values)
362
+ : reducer(values);
567
363
  }
568
- function parseDurationInput(value) {
569
- if (typeof value === 'number') {
570
- if (!Number.isFinite(value) || value <= 0) {
571
- throw new TypeError('rolling window must be a positive finite number of milliseconds');
364
+ function isAggregateOutputSpec(value) {
365
+ return (typeof value === 'object' &&
366
+ value !== null &&
367
+ 'from' in value &&
368
+ 'using' in value);
369
+ }
370
+ function normalizeAggregateColumns(schema, mapping) {
371
+ const columnsByName = new Map(schema.slice(1).map((column) => [column.name, column]));
372
+ const normalized = [];
373
+ for (const [outputName, raw] of Object.entries(mapping)) {
374
+ const sourceName = isAggregateOutputSpec(raw) ? raw.from : outputName;
375
+ const sourceColumn = columnsByName.get(sourceName);
376
+ if (!sourceColumn) {
377
+ throw new TypeError(`aggregate mapping references unknown source column '${sourceName}'`);
572
378
  }
573
- return value;
379
+ if (sourceColumn.kind !== 'number' &&
380
+ sourceColumn.kind !== 'string' &&
381
+ sourceColumn.kind !== 'boolean') {
382
+ throw new TypeError(`aggregate source column '${sourceName}' must be a scalar value column`);
383
+ }
384
+ const reducer = isAggregateOutputSpec(raw) ? raw.using : raw;
385
+ if (typeof reducer !== 'string' && typeof reducer !== 'function') {
386
+ throw new TypeError(`aggregate reducer for '${outputName}' must be a built-in name or function`);
387
+ }
388
+ const explicitKind = isAggregateOutputSpec(raw) ? raw.kind : undefined;
389
+ const resolvedKind = explicitKind ??
390
+ (typeof reducer === 'string' &&
391
+ resolveReducer(reducer).outputKind === 'number'
392
+ ? 'number'
393
+ : sourceColumn.kind);
394
+ normalized.push({
395
+ output: outputName,
396
+ source: sourceName,
397
+ reducer,
398
+ kind: resolvedKind,
399
+ });
574
400
  }
575
- const match = /^(\d+)(ms|s|m|h|d)$/.exec(value);
576
- if (!match) {
577
- throw new TypeError(`unsupported duration '${value}'`);
578
- }
579
- const amount = Number(match[1]);
580
- const unit = match[2];
581
- const multiplier = unit === 'ms'
582
- ? 1
583
- : unit === 's'
584
- ? 1_000
585
- : unit === 'm'
586
- ? 60_000
587
- : unit === 'h'
588
- ? 3_600_000
589
- : 86_400_000;
590
- return amount * multiplier;
401
+ return normalized;
402
+ }
403
+ function createAggregateBucketState(operation) {
404
+ return resolveReducer(operation).bucketState();
405
+ }
406
+ function createRollingReducerState(operation) {
407
+ return resolveReducer(operation).rollingState();
591
408
  }
592
409
  function duplicateValueColumnNames(schemas) {
593
410
  const counts = new Map();
@@ -697,7 +514,7 @@ export class TimeSeries {
697
514
  * the supplied `parse.timeZone`, which defaults to `UTC`.
698
515
  */
699
516
  static fromJSON(input) {
700
- return new TimeSeries({
517
+ return new _a({
701
518
  name: input.name,
702
519
  schema: input.schema,
703
520
  rows: parseJsonRows(input.schema, input.rows, input.parse),
@@ -710,6 +527,48 @@ export class TimeSeries {
710
527
  this.events = validateAndNormalize(input);
711
528
  Object.freeze(this);
712
529
  }
530
+ /**
531
+ * Example: `series.toJSON({ rowFormat: "object" })`.
532
+ * Serializes the series into the JSON-friendly shape accepted by `TimeSeries.fromJSON(...)`.
533
+ *
534
+ * Timestamps are emitted as numbers to avoid time zone ambiguity. Missing payload values are
535
+ * emitted as `null`. By default rows are emitted as arrays; use `rowFormat: "object"` for rows
536
+ * keyed by schema column names.
537
+ */
538
+ toJSON(options = {}) {
539
+ const rowFormat = options.rowFormat ?? 'array';
540
+ const dataColumns = this.schema.slice(1);
541
+ if (rowFormat === 'object') {
542
+ const keyColumn = this.schema[0];
543
+ const rows = this.events.map((event) => {
544
+ const row = {
545
+ [keyColumn.name]: serializeJsonKey(keyColumn.kind, event.key(), rowFormat),
546
+ };
547
+ const data = event.data();
548
+ for (const column of dataColumns) {
549
+ row[column.name] = serializeJsonValue(data[column.name]);
550
+ }
551
+ return Object.freeze(row);
552
+ });
553
+ return {
554
+ name: this.name,
555
+ schema: this.schema,
556
+ rows,
557
+ };
558
+ }
559
+ const rows = this.events.map((event) => {
560
+ const data = event.data();
561
+ return Object.freeze([
562
+ serializeJsonKey(this.schema[0].kind, event.key(), rowFormat),
563
+ ...dataColumns.map((column) => serializeJsonValue(data[column.name])),
564
+ ]);
565
+ });
566
+ return {
567
+ name: this.name,
568
+ schema: this.schema,
569
+ rows,
570
+ };
571
+ }
713
572
  /**
714
573
  * Builds a series from event data that has already been validated and ordered by the caller.
715
574
  *
@@ -717,7 +576,7 @@ export class TimeSeries {
717
576
  * order and normalized key invariants.
718
577
  */
719
578
  static #fromTrustedEvents(name, schema, events) {
720
- const series = Object.create(TimeSeries.prototype);
579
+ const series = Object.create(_a.prototype);
721
580
  series.name = name;
722
581
  series.schema = Object.freeze(schema.slice());
723
582
  series.events = Object.freeze(events.slice());
@@ -731,6 +590,14 @@ export class TimeSeries {
731
590
  get rows() {
732
591
  return toRows(this.schema, this.events);
733
592
  }
593
+ /** Example: `series.toRows()`. Returns normalized row arrays using `Time`/`TimeRange`/`Interval` keys and `undefined` for missing payload values. */
594
+ toRows() {
595
+ return this.rows;
596
+ }
597
+ /** Example: `series.toObjects()`. Returns normalized schema-keyed object rows using temporal key objects and `undefined` for missing payload values. */
598
+ toObjects() {
599
+ return toObjects(this.schema, this.events);
600
+ }
734
601
  /** Example: `series.at(0)`. Returns the event at the supplied zero-based position, if present. */
735
602
  at(index) {
736
603
  return this.events[index];
@@ -748,7 +615,7 @@ export class TimeSeries {
748
615
  /** Example: `series.map(nextSchema, event => event)`. Maps each event into a new typed schema and returns a new series. */
749
616
  map(schema, mapper) {
750
617
  const mappedEvents = this.events.map((event, index) => mapper(event, index));
751
- return new TimeSeries({
618
+ return new _a({
752
619
  name: this.name,
753
620
  schema,
754
621
  rows: toRows(schema, mappedEvents),
@@ -762,9 +629,9 @@ export class TimeSeries {
762
629
  ]);
763
630
  const resultEvents = this.events.map((event) => event.asTime(options));
764
631
  if ((options.at ?? 'begin') === 'begin') {
765
- return TimeSeries.#fromTrustedEvents(this.name, schema, resultEvents);
632
+ return _a.#fromTrustedEvents(this.name, schema, resultEvents);
766
633
  }
767
- return new TimeSeries({
634
+ return new _a({
768
635
  name: this.name,
769
636
  schema,
770
637
  rows: toRows(schema, resultEvents),
@@ -777,7 +644,7 @@ export class TimeSeries {
777
644
  ...this.schema.slice(1),
778
645
  ]);
779
646
  const resultEvents = this.events.map((event) => event.asTimeRange());
780
- return TimeSeries.#fromTrustedEvents(this.name, schema, resultEvents);
647
+ return _a.#fromTrustedEvents(this.name, schema, resultEvents);
781
648
  }
782
649
  asInterval(value) {
783
650
  const schema = Object.freeze([
@@ -789,10 +656,13 @@ export class TimeSeries {
789
656
  ? event.asInterval(() => value(event, index))
790
657
  : event.asInterval(value);
791
658
  });
792
- return TimeSeries.#fromTrustedEvents(this.name, schema, nextEvents);
659
+ return _a.#fromTrustedEvents(this.name, schema, nextEvents);
793
660
  }
794
661
  join(other, options = {}) {
795
- const [left, right] = prepareSeriesForJoin([this, other], options);
662
+ const [left, right] = prepareSeriesForJoin([
663
+ this,
664
+ other,
665
+ ], options);
796
666
  const joinType = options.type ?? 'outer';
797
667
  if (left.firstColumnKind !== right.firstColumnKind) {
798
668
  throw new TypeError('cannot join series with different key kinds');
@@ -845,7 +715,7 @@ export class TimeSeries {
845
715
  rightIndex += 1;
846
716
  }
847
717
  }
848
- return TimeSeries.#fromTrustedEvents(left.name, resultSchema, joinedEvents);
718
+ return _a.#fromTrustedEvents(left.name, resultSchema, joinedEvents);
849
719
  }
850
720
  /**
851
721
  * Example: `series.align(Sequence.every("1m"))`.
@@ -877,7 +747,7 @@ export class TimeSeries {
877
747
  const range = options.range ?? this.timeRange();
878
748
  const resultSchema = makeAlignedSchema(this.schema);
879
749
  if (!range) {
880
- return new TimeSeries({
750
+ return new _a({
881
751
  name: this.name,
882
752
  schema: resultSchema,
883
753
  rows: [],
@@ -896,7 +766,7 @@ export class TimeSeries {
896
766
  for (let i = 0; i < intervals.length; i += 1) {
897
767
  const interval = intervals[i];
898
768
  const t = sampleTime(interval, sample);
899
- const data = this.#alignLinearAt(t, valueColumns, cursor);
769
+ const data = alignLinearAt(this, t, valueColumns, cursor);
900
770
  const row = new Array(resultColumns.length + 1);
901
771
  row[0] = interval;
902
772
  for (let j = 0; j < resultColumns.length; j += 1) {
@@ -909,7 +779,7 @@ export class TimeSeries {
909
779
  })()
910
780
  : intervals.map((interval) => {
911
781
  const t = sampleTime(interval, sample);
912
- const data = this.#alignHoldAt(t);
782
+ const data = alignHoldAt(this, t);
913
783
  return Object.freeze([
914
784
  interval,
915
785
  ...resultSchema
@@ -917,126 +787,452 @@ export class TimeSeries {
917
787
  .map((column) => data[column.name]),
918
788
  ]);
919
789
  });
920
- return new TimeSeries({
790
+ return new _a({
921
791
  name: this.name,
922
792
  schema: resultSchema,
923
793
  rows: alignedRows,
924
794
  });
925
795
  }
796
+ aggregate(sequence, mapping, options = {}) {
797
+ return aggregateInternal(this, sequence, mapping, options);
798
+ }
799
+ reduce(columnOrMapping, reducer) {
800
+ if (typeof columnOrMapping === 'string') {
801
+ const values = this.events.map((event) => {
802
+ const data = event.data();
803
+ return data[columnOrMapping];
804
+ });
805
+ return applyAggregateReducer(reducer, values);
806
+ }
807
+ const columns = normalizeAggregateColumns(this.schema, columnOrMapping);
808
+ const result = {};
809
+ for (const col of columns) {
810
+ const values = this.events.map((event) => {
811
+ const data = event.data();
812
+ return data[col.source];
813
+ });
814
+ result[col.output] = applyAggregateReducer(col.reducer, values);
815
+ }
816
+ return result;
817
+ }
818
+ groupBy(column, transform) {
819
+ const buckets = new Map();
820
+ for (const event of this.events) {
821
+ const raw = event.data()[column];
822
+ const key = raw === undefined ? 'undefined' : String(raw);
823
+ let bucket = buckets.get(key);
824
+ if (!bucket) {
825
+ bucket = [];
826
+ buckets.set(key, bucket);
827
+ }
828
+ bucket.push(event);
829
+ }
830
+ const buildGroup = (events) => new _a({
831
+ name: this.name,
832
+ schema: this.schema,
833
+ rows: toRows(this.schema, events),
834
+ });
835
+ if (transform) {
836
+ const result = new Map();
837
+ for (const [key, events] of buckets) {
838
+ result.set(key, transform(buildGroup(events), key));
839
+ }
840
+ return result;
841
+ }
842
+ const result = new Map();
843
+ for (const [key, events] of buckets) {
844
+ result.set(key, buildGroup(events));
845
+ }
846
+ return result;
847
+ }
926
848
  /**
927
- * Example: `series.aggregate(Sequence.every("1m"), { value: "avg" })`.
928
- * Aggregates events into sequence buckets using built-in reducer names.
849
+ * Example: `series.diff("requests")`.
850
+ * Computes per-event differences for the specified numeric columns.
851
+ * Non-specified columns pass through unchanged. The first event gets
852
+ * `undefined` in affected columns unless `{ drop: true }` is passed,
853
+ * which removes the first event entirely.
929
854
  *
930
- * Buckets use half-open membership semantics: `[begin, end)`. Point events contribute to the
931
- * bucket containing their timestamp. Interval-like events contribute to every bucket they
932
- * overlap under half-open overlap rules.
855
+ * Example: `series.diff(["requests", "cpu"])`.
856
+ * Multiple columns can be diffed in a single call.
933
857
  *
934
- * Defaults:
935
- * - `range`: `series.timeRange()`
858
+ * Example: `series.diff("requests", { drop: true })`.
859
+ * Drops the first event instead of keeping it with undefined values.
860
+ */
861
+ diff(columns, options) {
862
+ return this.#diffOrRate('diff', columns, options);
863
+ }
864
+ /**
865
+ * Example: `series.rate("requests")`.
866
+ * Computes the per-second rate of change for the specified numeric columns.
867
+ * Non-specified columns pass through unchanged. The first event gets
868
+ * `undefined` in affected columns unless `{ drop: true }` is passed,
869
+ * which removes the first event entirely.
870
+ *
871
+ * Example: `series.rate(["requests", "cpu"])`.
872
+ * Multiple columns can be rated in a single call.
936
873
  *
937
- * As with `align(...)`, `Sequence` defines the underlying grid and `range` selects which portion
938
- * of that grid is bounded. With `Sequence.every(...)`, the default grid anchor is Unix epoch `0`,
939
- * but the default aggregation range is always the source series extent. When a
940
- * `BoundedSequence` is supplied, its intervals are used directly.
874
+ * Example: `series.rate("requests", { drop: true })`.
875
+ * Drops the first event instead of keeping it with undefined values.
876
+ */
877
+ rate(columns, options) {
878
+ return this.#diffOrRate('rate', columns, options);
879
+ }
880
+ /**
881
+ * Example: `series.pctChange("requests")`.
882
+ * Computes the percentage change `(curr - prev) / prev` for the specified
883
+ * numeric columns. Non-specified columns pass through unchanged. The first
884
+ * event gets `undefined` in affected columns unless `{ drop: true }` is
885
+ * passed.
886
+ */
887
+ pctChange(columns, options) {
888
+ return this.#diffOrRate('pctChange', columns, options);
889
+ }
890
+ #diffOrRate(mode, columns, options) {
891
+ const cols = typeof columns === 'string' ? [columns] : columns;
892
+ const drop = options?.drop === true;
893
+ if (cols.length === 0) {
894
+ throw new Error(`${mode}() requires at least one column name`);
895
+ }
896
+ const targetSet = new Set(cols);
897
+ const outSchema = Object.freeze(this.schema.map((col, i) => {
898
+ if (i === 0)
899
+ return col;
900
+ if (targetSet.has(col.name)) {
901
+ return { ...col, kind: 'number', required: false };
902
+ }
903
+ return col;
904
+ }));
905
+ const events = this.events;
906
+ if (events.length === 0) {
907
+ return _a.#fromTrustedEvents(this.name, outSchema, []);
908
+ }
909
+ const resultEvents = [];
910
+ if (!drop) {
911
+ const firstData = { ...events[0].data() };
912
+ for (const col of cols) {
913
+ firstData[col] = undefined;
914
+ }
915
+ resultEvents.push(new Event(events[0].key(), firstData));
916
+ }
917
+ for (let i = 1; i < events.length; i++) {
918
+ const prev = events[i - 1];
919
+ const curr = events[i];
920
+ const data = { ...curr.data() };
921
+ const dt = mode === 'rate' ? (curr.begin() - prev.begin()) / 1000 : undefined;
922
+ for (const col of cols) {
923
+ const prevVal = prev.data()[col];
924
+ const currVal = data[col];
925
+ if (typeof currVal === 'number' && typeof prevVal === 'number') {
926
+ const delta = currVal - prevVal;
927
+ if (mode === 'pctChange') {
928
+ data[col] = prevVal !== 0 ? delta / prevVal : undefined;
929
+ }
930
+ else if (mode === 'rate') {
931
+ data[col] = dt !== 0 ? delta / dt : undefined;
932
+ }
933
+ else {
934
+ data[col] = delta;
935
+ }
936
+ }
937
+ else {
938
+ data[col] = undefined;
939
+ }
940
+ }
941
+ resultEvents.push(new Event(curr.key(), data));
942
+ }
943
+ return _a.#fromTrustedEvents(this.name, outSchema, resultEvents);
944
+ }
945
+ /**
946
+ * Example: `series.cumulative({ requests: "sum" })`.
947
+ * Computes running accumulations for the specified numeric columns.
948
+ * Non-accumulated columns pass through unchanged.
941
949
  *
942
- * Override `range` when you need multiple series aggregated over the same reporting window,
943
- * including leading or trailing empty buckets outside an individual series extent.
950
+ * Built-in accumulators: `"sum"`, `"max"`, `"min"`, `"count"`.
951
+ * Custom accumulators: `(acc: number, value: number) => number`.
952
+ */
953
+ cumulative(spec) {
954
+ const entries = Object.entries(spec);
955
+ if (entries.length === 0) {
956
+ throw new Error('cumulative() requires at least one column');
957
+ }
958
+ const targetSet = new Set(entries.map(([name]) => name));
959
+ const outSchema = Object.freeze(this.schema.map((col, i) => {
960
+ if (i === 0)
961
+ return col;
962
+ if (targetSet.has(col.name)) {
963
+ return { ...col, kind: 'number', required: false };
964
+ }
965
+ return col;
966
+ }));
967
+ const events = this.events;
968
+ if (events.length === 0) {
969
+ return _a.#fromTrustedEvents(this.name, outSchema, []);
970
+ }
971
+ const state = new Map();
972
+ for (const [name, reducer] of entries) {
973
+ if (typeof reducer === 'function') {
974
+ const fn = reducer;
975
+ state.set(name, {
976
+ acc: undefined,
977
+ apply: (acc, v) => (acc === undefined ? v : fn(acc, v)),
978
+ });
979
+ }
980
+ else {
981
+ switch (reducer) {
982
+ case 'sum':
983
+ state.set(name, {
984
+ acc: undefined,
985
+ apply: (acc, v) => (acc ?? 0) + v,
986
+ });
987
+ break;
988
+ case 'count':
989
+ state.set(name, { acc: undefined, apply: (acc) => (acc ?? 0) + 1 });
990
+ break;
991
+ case 'max':
992
+ state.set(name, {
993
+ acc: undefined,
994
+ apply: (acc, v) => (acc === undefined || v > acc ? v : acc),
995
+ });
996
+ break;
997
+ case 'min':
998
+ state.set(name, {
999
+ acc: undefined,
1000
+ apply: (acc, v) => (acc === undefined || v < acc ? v : acc),
1001
+ });
1002
+ break;
1003
+ }
1004
+ }
1005
+ }
1006
+ const resultEvents = [];
1007
+ for (const event of events) {
1008
+ const data = { ...event.data() };
1009
+ for (const [name, s] of state) {
1010
+ const raw = data[name];
1011
+ if (typeof raw === 'number') {
1012
+ s.acc = s.apply(s.acc, raw);
1013
+ data[name] = s.acc;
1014
+ }
1015
+ else {
1016
+ data[name] = s.acc;
1017
+ }
1018
+ }
1019
+ resultEvents.push(new Event(event.key(), data));
1020
+ }
1021
+ return _a.#fromTrustedEvents(this.name, outSchema, resultEvents);
1022
+ }
1023
+ /**
1024
+ * Example: `series.shift("value", 1)`.
1025
+ * Lags column values by N events (positive N) or leads them (negative N).
1026
+ * Vacated positions get `undefined`.
1027
+ */
1028
+ shift(columns, n) {
1029
+ const cols = typeof columns === 'string' ? [columns] : columns;
1030
+ if (cols.length === 0) {
1031
+ throw new Error('shift() requires at least one column name');
1032
+ }
1033
+ if (!Number.isInteger(n)) {
1034
+ throw new Error('shift() requires an integer offset');
1035
+ }
1036
+ const targetSet = new Set(cols);
1037
+ const outSchema = Object.freeze(this.schema.map((col, i) => {
1038
+ if (i === 0)
1039
+ return col;
1040
+ if (targetSet.has(col.name)) {
1041
+ return { ...col, kind: 'number', required: false };
1042
+ }
1043
+ return col;
1044
+ }));
1045
+ const events = this.events;
1046
+ if (events.length === 0) {
1047
+ return _a.#fromTrustedEvents(this.name, outSchema, []);
1048
+ }
1049
+ const resultEvents = [];
1050
+ for (let i = 0; i < events.length; i++) {
1051
+ const data = { ...events[i].data() };
1052
+ const srcIdx = i - n;
1053
+ for (const col of cols) {
1054
+ if (srcIdx >= 0 && srcIdx < events.length) {
1055
+ data[col] = events[srcIdx].data()[col];
1056
+ }
1057
+ else {
1058
+ data[col] = undefined;
1059
+ }
1060
+ }
1061
+ resultEvents.push(new Event(events[i].key(), data));
1062
+ }
1063
+ return _a.#fromTrustedEvents(this.name, outSchema, resultEvents);
1064
+ }
1065
+ /**
1066
+ * Example: `series.fill("hold")`.
1067
+ * Fills `undefined` values using the given strategy for all payload columns.
944
1068
  *
945
- * To align buckets to the beginning of the current series instead of epoch boundaries, override
946
- * the sequence anchor rather than the aggregation range:
1069
+ * Example: `series.fill({ cpu: "linear", host: "hold" })`.
1070
+ * Per-column fill strategies. Unmentioned columns are left as-is.
1071
+ * Strategy names: `"hold"` (forward fill), `"linear"` (time-interpolated),
1072
+ * `"zero"` (fill with 0). A non-string value is used as a literal fill value.
947
1073
  *
948
- * ```ts
949
- * const range = series.timeRange();
950
- * if (!range) {
951
- * throw new Error("empty series");
952
- * }
1074
+ * Example: `series.fill("hold", { limit: 3 })`.
1075
+ * Caps consecutive fills per column. After `limit` consecutive fills, further
1076
+ * `undefined` values are left as-is until a real value resets the counter.
953
1077
  *
954
- * const aggregated = series.aggregate(
955
- * Sequence.every("1m", { anchor: range.begin() }),
956
- * { value: "avg" },
957
- * );
958
- * ```
1078
+ * `"linear"` requires known values on both sides of a gap to interpolate.
1079
+ * Leading and trailing `undefined` runs are left unfilled.
959
1080
  */
960
- aggregate(sequence, mapping, options = {}) {
961
- const range = options.range ?? this.timeRange();
962
- const resultSchema = Object.freeze([
963
- { name: 'interval', kind: 'interval' },
964
- ...this.schema
965
- .slice(1)
966
- .filter((column) => column.name in mapping)
967
- .map((column) => {
968
- const operation = mapping[column.name];
969
- return {
970
- name: column.name,
971
- kind: operation === 'sum' ||
972
- operation === 'avg' ||
973
- operation === 'count'
974
- ? 'number'
975
- : column.kind,
976
- required: false,
977
- };
978
- }),
979
- ]);
980
- if (!range) {
981
- return new TimeSeries({
982
- name: this.name,
983
- schema: resultSchema,
984
- rows: [],
985
- });
1081
+ fill(strategy, options) {
1082
+ if (this.events.length === 0) {
1083
+ return this;
986
1084
  }
987
- const buckets = toBoundedSequence(sequence, range, 'begin').intervals();
988
- const resultColumns = resultSchema.slice(1);
989
- if (isTimeKeyed(this)) {
990
- const columns = resultColumns.map((column) => ({
991
- name: column.name,
992
- operation: mapping[column.name],
993
- }));
994
- let eventIndex = 0;
995
- const resultRows = buckets.map((bucket) => {
996
- const states = columns.map((column) => createAggregateBucketState(column.operation));
997
- while (eventIndex < this.events.length &&
998
- this.events[eventIndex].begin() < bucket.begin()) {
999
- eventIndex += 1;
1085
+ const colNames = this.schema.slice(1).map((c) => c.name);
1086
+ const specs = new Map();
1087
+ if (typeof strategy === 'string') {
1088
+ for (const name of colNames) {
1089
+ specs.set(name, { mode: strategy });
1090
+ }
1091
+ }
1092
+ else {
1093
+ const strategies = new Set([
1094
+ 'hold',
1095
+ 'bfill',
1096
+ 'linear',
1097
+ 'zero',
1098
+ ]);
1099
+ for (const [name, spec] of Object.entries(strategy)) {
1100
+ if (typeof spec === 'string' && strategies.has(spec)) {
1101
+ specs.set(name, { mode: spec });
1000
1102
  }
1001
- let scanIndex = eventIndex;
1002
- while (scanIndex < this.events.length &&
1003
- this.events[scanIndex].begin() < bucket.end()) {
1004
- const data = this.events[scanIndex].data();
1005
- for (let index = 0; index < columns.length; index += 1) {
1006
- const column = columns[index];
1007
- states[index].add(data[column.name]);
1103
+ else {
1104
+ specs.set(name, { mode: 'literal', value: spec });
1105
+ }
1106
+ }
1107
+ }
1108
+ const limit = options?.limit;
1109
+ const n = this.events.length;
1110
+ const columns = {};
1111
+ for (const name of colNames) {
1112
+ columns[name] = new Array(n);
1113
+ }
1114
+ for (let i = 0; i < n; i++) {
1115
+ const data = this.events[i].data();
1116
+ for (const name of colNames) {
1117
+ columns[name][i] = data[name];
1118
+ }
1119
+ }
1120
+ const times = new Array(n);
1121
+ for (let i = 0; i < n; i++) {
1122
+ times[i] = this.events[i].begin();
1123
+ }
1124
+ for (const [name, spec] of specs) {
1125
+ const col = columns[name];
1126
+ if (!col)
1127
+ continue;
1128
+ switch (spec.mode) {
1129
+ case 'hold': {
1130
+ let last;
1131
+ let consecutive = 0;
1132
+ for (let i = 0; i < n; i++) {
1133
+ if (col[i] !== undefined) {
1134
+ last = col[i];
1135
+ consecutive = 0;
1136
+ }
1137
+ else if (last !== undefined) {
1138
+ consecutive++;
1139
+ if (limit === undefined || consecutive <= limit) {
1140
+ col[i] = last;
1141
+ }
1142
+ }
1008
1143
  }
1009
- scanIndex += 1;
1144
+ break;
1010
1145
  }
1011
- eventIndex = scanIndex;
1012
- return Object.freeze([
1013
- bucket,
1014
- ...states.map((state) => state.snapshot()),
1015
- ]);
1016
- });
1017
- return new TimeSeries({
1018
- name: this.name,
1019
- schema: resultSchema,
1020
- rows: resultRows,
1021
- });
1146
+ case 'bfill': {
1147
+ let next;
1148
+ let consecutive = 0;
1149
+ for (let i = n - 1; i >= 0; i--) {
1150
+ if (col[i] !== undefined) {
1151
+ next = col[i];
1152
+ consecutive = 0;
1153
+ }
1154
+ else if (next !== undefined) {
1155
+ consecutive++;
1156
+ if (limit === undefined || consecutive <= limit) {
1157
+ col[i] = next;
1158
+ }
1159
+ }
1160
+ }
1161
+ break;
1162
+ }
1163
+ case 'zero': {
1164
+ let consecutive = 0;
1165
+ for (let i = 0; i < n; i++) {
1166
+ if (col[i] !== undefined) {
1167
+ consecutive = 0;
1168
+ }
1169
+ else {
1170
+ consecutive++;
1171
+ if (limit === undefined || consecutive <= limit) {
1172
+ col[i] = 0;
1173
+ }
1174
+ }
1175
+ }
1176
+ break;
1177
+ }
1178
+ case 'literal': {
1179
+ let consecutive = 0;
1180
+ for (let i = 0; i < n; i++) {
1181
+ if (col[i] !== undefined) {
1182
+ consecutive = 0;
1183
+ }
1184
+ else {
1185
+ consecutive++;
1186
+ if (limit === undefined || consecutive <= limit) {
1187
+ col[i] = spec.value;
1188
+ }
1189
+ }
1190
+ }
1191
+ break;
1192
+ }
1193
+ case 'linear': {
1194
+ let gapStart = -1;
1195
+ for (let i = 0; i < n; i++) {
1196
+ if (col[i] !== undefined) {
1197
+ if (gapStart >= 0 && gapStart > 0) {
1198
+ const before = col[gapStart - 1];
1199
+ const after = col[i];
1200
+ const t0 = times[gapStart - 1];
1201
+ const t1 = times[i];
1202
+ const span = t1 - t0;
1203
+ const gapLen = i - gapStart;
1204
+ for (let j = gapStart; j < i; j++) {
1205
+ const fillIndex = j - gapStart + 1;
1206
+ if (limit !== undefined && fillIndex > limit)
1207
+ break;
1208
+ if (span === 0) {
1209
+ col[j] = before;
1210
+ }
1211
+ else {
1212
+ const ratio = (times[j] - t0) / span;
1213
+ col[j] = before + (after - before) * ratio;
1214
+ }
1215
+ }
1216
+ }
1217
+ gapStart = -1;
1218
+ }
1219
+ else if (gapStart < 0) {
1220
+ gapStart = i;
1221
+ }
1222
+ }
1223
+ break;
1224
+ }
1225
+ }
1022
1226
  }
1023
- const resultRows = buckets.map((bucket) => {
1024
- const contributors = this.events.filter((event) => bucketOverlapsHalfOpen(bucket, event.key()));
1025
- const aggregated = resultColumns.map((column) => {
1026
- const operation = mapping[column.name];
1027
- const values = contributors.map((event) => {
1028
- const data = event.data();
1029
- return data[column.name];
1030
- });
1031
- return aggregateValues(operation, values);
1032
- });
1033
- return Object.freeze([bucket, ...aggregated]);
1034
- });
1035
- return new TimeSeries({
1036
- name: this.name,
1037
- schema: resultSchema,
1038
- rows: resultRows,
1039
- });
1227
+ const resultEvents = [];
1228
+ for (let i = 0; i < n; i++) {
1229
+ const data = {};
1230
+ for (const name of colNames) {
1231
+ data[name] = columns[name][i];
1232
+ }
1233
+ resultEvents.push(new Event(this.events[i].key(), data));
1234
+ }
1235
+ return _a.#fromTrustedEvents(this.name, this.schema, resultEvents);
1040
1236
  }
1041
1237
  rolling(sequenceOrWindow, windowOrMapping, mappingOrOptions, maybeOptions = {}) {
1042
1238
  const buildResultColumns = () => this.schema
@@ -1072,7 +1268,7 @@ export class TimeSeries {
1072
1268
  mappingOrOptions ??
1073
1269
  {};
1074
1270
  }
1075
- const windowMs = parseDurationInput(window);
1271
+ const windowMs = parseDuration(window);
1076
1272
  const alignment = options.alignment ?? 'trailing';
1077
1273
  const anchorInWindow = (candidate, anchor) => {
1078
1274
  if (alignment === 'trailing') {
@@ -1092,7 +1288,7 @@ export class TimeSeries {
1092
1288
  ...buildResultColumns(),
1093
1289
  ]);
1094
1290
  if (!range) {
1095
- return new TimeSeries({
1291
+ return new _a({
1096
1292
  name: this.name,
1097
1293
  schema: resultSchema,
1098
1294
  rows: [],
@@ -1103,16 +1299,16 @@ export class TimeSeries {
1103
1299
  const anchor = sampleTime(bucket, sample);
1104
1300
  const contributors = this.events.filter((candidate) => anchorInWindow(candidate.begin(), anchor));
1105
1301
  const aggregated = resultSchema.slice(1).map((column) => {
1106
- const operation = mapping[column.name];
1302
+ const reducer = mapping[column.name];
1107
1303
  const values = contributors.map((candidate) => {
1108
1304
  const data = candidate.data();
1109
1305
  return data[column.name];
1110
1306
  });
1111
- return aggregateValues(operation, values);
1307
+ return applyAggregateReducer(reducer, values);
1112
1308
  });
1113
1309
  return Object.freeze([bucket, ...aggregated]);
1114
1310
  });
1115
- return new TimeSeries({
1311
+ return new _a({
1116
1312
  name: this.name,
1117
1313
  schema: resultSchema,
1118
1314
  rows: resultRows,
@@ -1123,7 +1319,12 @@ export class TimeSeries {
1123
1319
  this.schema[0],
1124
1320
  ...resultColumns,
1125
1321
  ]);
1126
- const reducerStates = resultColumns.map((column) => createRollingReducerState(mapping[column.name]));
1322
+ const reducerStates = resultColumns.map((column) => {
1323
+ const reducer = mapping[column.name];
1324
+ return isBuiltInAggregateReducer(reducer)
1325
+ ? createRollingReducerState(reducer)
1326
+ : null;
1327
+ });
1127
1328
  const beginTimes = this.events.map((event) => event.begin());
1128
1329
  const resultRows = new Array(this.events.length);
1129
1330
  let windowStart = 0;
@@ -1131,19 +1332,38 @@ export class TimeSeries {
1131
1332
  const addEvent = (index) => {
1132
1333
  const event = this.events[index];
1133
1334
  const data = event.data();
1134
- for (let reducerIndex = 0; reducerIndex < reducerStates.length; reducerIndex++) {
1135
- const column = resultColumns[reducerIndex];
1136
- reducerStates[reducerIndex].add(index, data[column.name]);
1335
+ for (let i = 0; i < reducerStates.length; i++) {
1336
+ const state = reducerStates[i];
1337
+ if (state) {
1338
+ const column = resultColumns[i];
1339
+ state.add(index, data[column.name]);
1340
+ }
1137
1341
  }
1138
1342
  };
1139
1343
  const removeEvent = (index) => {
1140
1344
  const event = this.events[index];
1141
1345
  const data = event.data();
1142
- for (let reducerIndex = 0; reducerIndex < reducerStates.length; reducerIndex++) {
1143
- const column = resultColumns[reducerIndex];
1144
- reducerStates[reducerIndex].remove(index, data[column.name]);
1346
+ for (let i = 0; i < reducerStates.length; i++) {
1347
+ const state = reducerStates[i];
1348
+ if (state) {
1349
+ const column = resultColumns[i];
1350
+ state.remove(index, data[column.name]);
1351
+ }
1145
1352
  }
1146
1353
  };
1354
+ const snapshotWindow = () => resultColumns.map((column, i) => {
1355
+ const state = reducerStates[i];
1356
+ if (state)
1357
+ return state.snapshot();
1358
+ const reducer = mapping[column.name];
1359
+ const values = this.events
1360
+ .slice(windowStart, windowEnd)
1361
+ .map((event) => {
1362
+ const data = event.data();
1363
+ return data[column.name];
1364
+ });
1365
+ return applyAggregateReducer(reducer, values);
1366
+ });
1147
1367
  if (alignment === 'trailing') {
1148
1368
  for (let groupStart = 0; groupStart < this.events.length;) {
1149
1369
  const anchor = beginTimes[groupStart];
@@ -1163,7 +1383,7 @@ export class TimeSeries {
1163
1383
  removeEvent(windowStart);
1164
1384
  windowStart += 1;
1165
1385
  }
1166
- const aggregated = reducerStates.map((state) => state.snapshot());
1386
+ const aggregated = snapshotWindow();
1167
1387
  for (let index = groupStart; index < groupEnd; index++) {
1168
1388
  resultRows[index] = Object.freeze([
1169
1389
  this.events[index].key(),
@@ -1193,7 +1413,7 @@ export class TimeSeries {
1193
1413
  addEvent(windowEnd);
1194
1414
  windowEnd += 1;
1195
1415
  }
1196
- const aggregated = reducerStates.map((state) => state.snapshot());
1416
+ const aggregated = snapshotWindow();
1197
1417
  for (let index = groupStart; index < groupEnd; index++) {
1198
1418
  resultRows[index] = Object.freeze([
1199
1419
  this.events[index].key(),
@@ -1224,7 +1444,7 @@ export class TimeSeries {
1224
1444
  addEvent(windowEnd);
1225
1445
  windowEnd += 1;
1226
1446
  }
1227
- const aggregated = reducerStates.map((state) => state.snapshot());
1447
+ const aggregated = snapshotWindow();
1228
1448
  for (let index = groupStart; index < groupEnd; index++) {
1229
1449
  resultRows[index] = Object.freeze([
1230
1450
  this.events[index].key(),
@@ -1234,7 +1454,7 @@ export class TimeSeries {
1234
1454
  groupStart = groupEnd;
1235
1455
  }
1236
1456
  }
1237
- return new TimeSeries({
1457
+ return new _a({
1238
1458
  name: this.name,
1239
1459
  schema: resultSchema,
1240
1460
  rows: resultRows,
@@ -1299,7 +1519,7 @@ export class TimeSeries {
1299
1519
  .map((nextColumn) => nextEvent.data()[nextColumn.name]),
1300
1520
  ]);
1301
1521
  });
1302
- return new TimeSeries({
1522
+ return new _a({
1303
1523
  name: this.name,
1304
1524
  schema: resultSchema,
1305
1525
  rows: resultRows,
@@ -1337,7 +1557,7 @@ export class TimeSeries {
1337
1557
  .map((nextColumn) => nextEvent.data()[nextColumn.name]),
1338
1558
  ]);
1339
1559
  });
1340
- return new TimeSeries({
1560
+ return new _a({
1341
1561
  name: this.name,
1342
1562
  schema: resultSchema,
1343
1563
  rows: resultRows,
@@ -1347,7 +1567,7 @@ export class TimeSeries {
1347
1567
  throw new TypeError('movingAverage smoothing requires a window option');
1348
1568
  }
1349
1569
  const window = options.window;
1350
- const windowMs = parseDurationInput(window);
1570
+ const windowMs = parseDuration(window);
1351
1571
  const alignment = options.alignment ?? 'trailing';
1352
1572
  const resultValues = new Array(this.events.length);
1353
1573
  let windowStart = 0;
@@ -1431,7 +1651,7 @@ export class TimeSeries {
1431
1651
  .map((nextColumn) => nextEvent.data()[nextColumn.name]),
1432
1652
  ]);
1433
1653
  });
1434
- return new TimeSeries({
1654
+ return new _a({
1435
1655
  name: this.name,
1436
1656
  schema: resultSchema,
1437
1657
  rows: resultRows,
@@ -1439,11 +1659,11 @@ export class TimeSeries {
1439
1659
  }
1440
1660
  /** Example: `series.slice(0, 10)`. Returns a positional half-open slice of the series. */
1441
1661
  slice(beginIndex, endIndex) {
1442
- return TimeSeries.#fromTrustedEvents(this.name, this.schema, this.events.slice(beginIndex, endIndex));
1662
+ return _a.#fromTrustedEvents(this.name, this.schema, this.events.slice(beginIndex, endIndex));
1443
1663
  }
1444
1664
  /** Example: `series.filter(event => event.get("active"))`. Returns a new series containing only events that match the predicate. */
1445
1665
  filter(predicate) {
1446
- return TimeSeries.#fromTrustedEvents(this.name, this.schema, this.events.filter((event, index) => predicate(event, index)));
1666
+ return _a.#fromTrustedEvents(this.name, this.schema, this.events.filter((event, index) => predicate(event, index)));
1447
1667
  }
1448
1668
  /** Example: `series.find(event => event.get("value") > 0)`. Returns the first event that matches the predicate, if any. */
1449
1669
  find(predicate) {
@@ -1564,7 +1784,7 @@ export class TimeSeries {
1564
1784
  const trimmedEvents = this.events
1565
1785
  .map((event) => event.trim(range))
1566
1786
  .filter((event) => event !== undefined);
1567
- return TimeSeries.#fromTrustedEvents(this.name, this.schema, trimmedEvents);
1787
+ return _a.#fromTrustedEvents(this.name, this.schema, trimmedEvents);
1568
1788
  }
1569
1789
  /** Example: `series.before(Date.now())`. Returns the events ending strictly before the supplied temporal boundary. */
1570
1790
  before(boundary) {
@@ -1596,7 +1816,7 @@ export class TimeSeries {
1596
1816
  const selectedEvent = event.select(...keys);
1597
1817
  return selectedEvent;
1598
1818
  });
1599
- return TimeSeries.#fromTrustedEvents(this.name, resultSchema, resultEvents);
1819
+ return _a.#fromTrustedEvents(this.name, resultSchema, resultEvents);
1600
1820
  }
1601
1821
  /** Example: `series.rename({ cpu: "usage" })`. Returns a new series with payload field names renamed according to the supplied mapping. */
1602
1822
  rename(mapping) {
@@ -1614,7 +1834,7 @@ export class TimeSeries {
1614
1834
  const renamedEvent = event.rename(mapping);
1615
1835
  return renamedEvent;
1616
1836
  });
1617
- return TimeSeries.#fromTrustedEvents(this.name, resultSchema, resultEvents);
1837
+ return _a.#fromTrustedEvents(this.name, resultSchema, resultEvents);
1618
1838
  }
1619
1839
  collapse(keys, output, reducer, options) {
1620
1840
  const nextEvents = this.events.map((event) => {
@@ -1642,54 +1862,138 @@ export class TimeSeries {
1642
1862
  : 'string',
1643
1863
  },
1644
1864
  ]);
1645
- return TimeSeries.#fromTrustedEvents(this.name, resultSchema, nextEvents);
1865
+ return _a.#fromTrustedEvents(this.name, resultSchema, nextEvents);
1646
1866
  }
1647
1867
  /** Example: `series.length`. Returns the number of events in the series. */
1648
1868
  get length() {
1649
1869
  return this.events.length;
1650
1870
  }
1651
- #alignHoldAt(t) {
1652
- const event = this.atOrBefore(new Time(t));
1653
- return (event?.data() ?? {});
1871
+ }
1872
+ _a = TimeSeries;
1873
+ function aggregateInternal(series, sequence, mapping, options = {}) {
1874
+ const range = options.range ?? series.timeRange();
1875
+ const aggregateColumns = normalizeAggregateColumns(series.schema, mapping);
1876
+ const resultSchema = Object.freeze([
1877
+ { name: 'interval', kind: 'interval' },
1878
+ ...aggregateColumns.map((column) => ({
1879
+ name: column.output,
1880
+ kind: column.kind,
1881
+ required: false,
1882
+ })),
1883
+ ]);
1884
+ if (!range) {
1885
+ return new TimeSeries({
1886
+ name: series.name,
1887
+ schema: resultSchema,
1888
+ rows: [],
1889
+ });
1654
1890
  }
1655
- #alignLinearAt(t, valueColumns, cursor) {
1656
- const events = this.events;
1657
- const hasCursor = cursor !== undefined;
1658
- let index = hasCursor ? cursor.index : this.bisect(t);
1659
- if (hasCursor) {
1660
- while (index < events.length && events[index].begin() < t) {
1661
- index += 1;
1891
+ const buckets = toBoundedSequence(sequence, range, 'begin').intervals();
1892
+ const columns = aggregateColumns;
1893
+ if (isTimeKeyed(series)) {
1894
+ const builtInOnly = columns.every((column) => isBuiltInAggregateReducer(column.reducer));
1895
+ let eventIndex = 0;
1896
+ const resultRows = buckets.map((bucket) => {
1897
+ const states = builtInOnly
1898
+ ? columns.map((column) => createAggregateBucketState(column.reducer))
1899
+ : undefined;
1900
+ while (eventIndex < series.events.length &&
1901
+ series.events[eventIndex].begin() < bucket.begin()) {
1902
+ eventIndex += 1;
1662
1903
  }
1663
- cursor.index = index;
1664
- }
1665
- if (index < events.length && events[index].begin() === t) {
1666
- return events[index].data();
1667
- }
1668
- if (index === 0) {
1669
- return {};
1670
- }
1671
- const previous = events[index - 1];
1672
- const next = events[index];
1673
- if (!next || previous.begin() === next.begin()) {
1674
- return previous.data();
1675
- }
1676
- const ratio = (t - previous.begin()) / (next.begin() - previous.begin());
1677
- const result = {};
1678
- const previousData = previous.data();
1679
- const nextData = next.data();
1680
- for (const column of valueColumns) {
1681
- const previousValue = previousData[column.name];
1682
- const nextValue = nextData[column.name];
1683
- if (column.kind === 'number' &&
1684
- typeof previousValue === 'number' &&
1685
- typeof nextValue === 'number') {
1686
- result[column.name] =
1687
- previousValue + (nextValue - previousValue) * ratio;
1688
- continue;
1904
+ const bucketStart = eventIndex;
1905
+ let scanIndex = bucketStart;
1906
+ while (scanIndex < series.events.length &&
1907
+ series.events[scanIndex].begin() < bucket.end()) {
1908
+ if (states) {
1909
+ const data = series.events[scanIndex].data();
1910
+ for (let index = 0; index < columns.length; index += 1) {
1911
+ const column = columns[index];
1912
+ states[index].add(data[column.source]);
1913
+ }
1914
+ }
1915
+ scanIndex += 1;
1916
+ }
1917
+ eventIndex = scanIndex;
1918
+ if (states) {
1919
+ return Object.freeze([
1920
+ bucket,
1921
+ ...states.map((state) => state.snapshot()),
1922
+ ]);
1689
1923
  }
1690
- result[column.name] = previousValue;
1924
+ const contributors = series.events.slice(bucketStart, scanIndex);
1925
+ const aggregated = columns.map((column) => {
1926
+ const values = contributors.map((event) => {
1927
+ const data = event.data();
1928
+ return data[column.source];
1929
+ });
1930
+ return applyAggregateReducer(column.reducer, values);
1931
+ });
1932
+ return Object.freeze([bucket, ...aggregated]);
1933
+ });
1934
+ return new TimeSeries({
1935
+ name: series.name,
1936
+ schema: resultSchema,
1937
+ rows: resultRows,
1938
+ });
1939
+ }
1940
+ const resultRows = buckets.map((bucket) => {
1941
+ const contributors = series.events.filter((event) => bucketOverlapsHalfOpen(bucket, event.key()));
1942
+ const aggregated = columns.map((column) => {
1943
+ const values = contributors.map((event) => {
1944
+ const data = event.data();
1945
+ return data[column.source];
1946
+ });
1947
+ return applyAggregateReducer(column.reducer, values);
1948
+ });
1949
+ return Object.freeze([bucket, ...aggregated]);
1950
+ });
1951
+ return new TimeSeries({
1952
+ name: series.name,
1953
+ schema: resultSchema,
1954
+ rows: resultRows,
1955
+ });
1956
+ }
1957
+ function alignHoldAt(series, t) {
1958
+ const event = series.atOrBefore(new Time(t));
1959
+ return (event?.data() ?? {});
1960
+ }
1961
+ function alignLinearAt(series, t, valueColumns, cursor) {
1962
+ const events = series.events;
1963
+ const hasCursor = cursor !== undefined;
1964
+ let index = hasCursor ? cursor.index : series.bisect(t);
1965
+ if (hasCursor) {
1966
+ while (index < events.length && events[index].begin() < t) {
1967
+ index += 1;
1691
1968
  }
1692
- return result;
1969
+ cursor.index = index;
1970
+ }
1971
+ if (index < events.length && events[index].begin() === t) {
1972
+ return events[index].data();
1973
+ }
1974
+ if (index === 0) {
1975
+ return {};
1976
+ }
1977
+ const previous = events[index - 1];
1978
+ const next = events[index];
1979
+ if (!next || previous.begin() === next.begin()) {
1980
+ return previous.data();
1981
+ }
1982
+ const ratio = (t - previous.begin()) / (next.begin() - previous.begin());
1983
+ const result = {};
1984
+ const previousData = previous.data();
1985
+ const nextData = next.data();
1986
+ for (const column of valueColumns) {
1987
+ const previousValue = previousData[column.name];
1988
+ const nextValue = nextData[column.name];
1989
+ if (column.kind === 'number' &&
1990
+ typeof previousValue === 'number' &&
1991
+ typeof nextValue === 'number') {
1992
+ result[column.name] = previousValue + (nextValue - previousValue) * ratio;
1993
+ continue;
1994
+ }
1995
+ result[column.name] = previousValue;
1693
1996
  }
1997
+ return result;
1694
1998
  }
1695
1999
  //# sourceMappingURL=TimeSeries.js.map