pond-ts 0.2.0 → 0.3.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 (70) hide show
  1. package/README.md +14 -0
  2. package/dist/TimeSeries.d.ts +132 -2
  3. package/dist/TimeSeries.d.ts.map +1 -1
  4. package/dist/TimeSeries.js +745 -418
  5. package/dist/TimeSeries.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/reducers/avg.d.ts +3 -0
  9. package/dist/reducers/avg.d.ts.map +1 -0
  10. package/dist/reducers/avg.js +45 -0
  11. package/dist/reducers/avg.js.map +1 -0
  12. package/dist/reducers/count.d.ts +3 -0
  13. package/dist/reducers/count.d.ts.map +1 -0
  14. package/dist/reducers/count.js +35 -0
  15. package/dist/reducers/count.js.map +1 -0
  16. package/dist/reducers/difference.d.ts +3 -0
  17. package/dist/reducers/difference.d.ts.map +1 -0
  18. package/dist/reducers/difference.js +48 -0
  19. package/dist/reducers/difference.js.map +1 -0
  20. package/dist/reducers/first.d.ts +3 -0
  21. package/dist/reducers/first.d.ts.map +1 -0
  22. package/dist/reducers/first.js +23 -0
  23. package/dist/reducers/first.js.map +1 -0
  24. package/dist/reducers/index.d.ts +4 -0
  25. package/dist/reducers/index.d.ts.map +1 -0
  26. package/dist/reducers/index.js +35 -0
  27. package/dist/reducers/index.js.map +1 -0
  28. package/dist/reducers/keep.d.ts +3 -0
  29. package/dist/reducers/keep.d.ts.map +1 -0
  30. package/dist/reducers/keep.js +56 -0
  31. package/dist/reducers/keep.js.map +1 -0
  32. package/dist/reducers/last.d.ts +3 -0
  33. package/dist/reducers/last.d.ts.map +1 -0
  34. package/dist/reducers/last.js +23 -0
  35. package/dist/reducers/last.js.map +1 -0
  36. package/dist/reducers/max.d.ts +3 -0
  37. package/dist/reducers/max.d.ts.map +1 -0
  38. package/dist/reducers/max.js +25 -0
  39. package/dist/reducers/max.js.map +1 -0
  40. package/dist/reducers/median.d.ts +3 -0
  41. package/dist/reducers/median.d.ts.map +1 -0
  42. package/dist/reducers/median.js +39 -0
  43. package/dist/reducers/median.js.map +1 -0
  44. package/dist/reducers/min.d.ts +3 -0
  45. package/dist/reducers/min.d.ts.map +1 -0
  46. package/dist/reducers/min.js +25 -0
  47. package/dist/reducers/min.js.map +1 -0
  48. package/dist/reducers/percentile.d.ts +5 -0
  49. package/dist/reducers/percentile.d.ts.map +1 -0
  50. package/dist/reducers/percentile.js +56 -0
  51. package/dist/reducers/percentile.js.map +1 -0
  52. package/dist/reducers/rolling.d.ts +15 -0
  53. package/dist/reducers/rolling.d.ts.map +1 -0
  54. package/dist/reducers/rolling.js +84 -0
  55. package/dist/reducers/rolling.js.map +1 -0
  56. package/dist/reducers/stdev.d.ts +3 -0
  57. package/dist/reducers/stdev.d.ts.map +1 -0
  58. package/dist/reducers/stdev.js +58 -0
  59. package/dist/reducers/stdev.js.map +1 -0
  60. package/dist/reducers/sum.d.ts +3 -0
  61. package/dist/reducers/sum.d.ts.map +1 -0
  62. package/dist/reducers/sum.js +35 -0
  63. package/dist/reducers/sum.js.map +1 -0
  64. package/dist/reducers/types.d.ts +58 -0
  65. package/dist/reducers/types.d.ts.map +1 -0
  66. package/dist/reducers/types.js +2 -0
  67. package/dist/reducers/types.js.map +1 -0
  68. package/dist/types.d.ts +28 -4
  69. package/dist/types.d.ts.map +1 -1
  70. package/package.json +1 -1
@@ -1,10 +1,13 @@
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 { resolveReducer, } from './reducers/index.js';
8
11
  function isObjectRow(value) {
9
12
  return typeof value === 'object' && value !== null && !Array.isArray(value);
10
13
  }
@@ -89,6 +92,23 @@ function parseJsonRows(schema, rows, options = {}) {
89
92
  }));
90
93
  });
91
94
  }
95
+ function serializeJsonKey(kind, key, rowFormat) {
96
+ if (kind === 'time') {
97
+ return key.begin();
98
+ }
99
+ if (kind === 'timeRange') {
100
+ return rowFormat === 'object'
101
+ ? { start: key.begin(), end: key.end() }
102
+ : [key.begin(), key.end()];
103
+ }
104
+ const interval = key;
105
+ return rowFormat === 'object'
106
+ ? { value: interval.value, start: interval.begin(), end: interval.end() }
107
+ : [interval.value, interval.begin(), interval.end()];
108
+ }
109
+ function serializeJsonValue(value) {
110
+ return value === undefined ? null : value;
111
+ }
92
112
  function toRows(schema, events) {
93
113
  return events.map((event) => {
94
114
  const data = event.data();
@@ -100,6 +120,20 @@ function toRows(schema, events) {
100
120
  ]);
101
121
  });
102
122
  }
123
+ function toObjects(schema, events) {
124
+ const keyColumn = schema[0];
125
+ const dataColumns = schema.slice(1);
126
+ return events.map((event) => {
127
+ const row = {
128
+ [keyColumn.name]: event.key(),
129
+ };
130
+ const data = event.data();
131
+ for (const column of dataColumns) {
132
+ row[column.name] = data[column.name];
133
+ }
134
+ return Object.freeze(row);
135
+ });
136
+ }
103
137
  function isEventKey(value) {
104
138
  return (typeof value === 'object' &&
105
139
  value !== null &&
@@ -316,254 +350,60 @@ function bucketOverlapsHalfOpen(bucket, event) {
316
350
  function aggregateValues(operation, values) {
317
351
  const defined = values.filter((value) => value !== undefined);
318
352
  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];
353
+ return resolveReducer(operation).reduce(defined, numeric);
354
+ }
355
+ function isBuiltInAggregateReducer(reducer) {
356
+ return typeof reducer === 'string';
357
+ }
358
+ function applyAggregateReducer(reducer, values) {
359
+ return isBuiltInAggregateReducer(reducer)
360
+ ? aggregateValues(reducer, values)
361
+ : reducer(values);
362
+ }
363
+ function isAggregateOutputSpec(value) {
364
+ return (typeof value === 'object' &&
365
+ value !== null &&
366
+ 'from' in value &&
367
+ 'using' in value);
368
+ }
369
+ function normalizeAggregateColumns(schema, mapping) {
370
+ const columnsByName = new Map(schema.slice(1).map((column) => [column.name, column]));
371
+ const normalized = [];
372
+ for (const [outputName, raw] of Object.entries(mapping)) {
373
+ const sourceName = isAggregateOutputSpec(raw) ? raw.from : outputName;
374
+ const sourceColumn = columnsByName.get(sourceName);
375
+ if (!sourceColumn) {
376
+ throw new TypeError(`aggregate mapping references unknown source column '${sourceName}'`);
377
+ }
378
+ if (sourceColumn.kind !== 'number' &&
379
+ sourceColumn.kind !== 'string' &&
380
+ sourceColumn.kind !== 'boolean') {
381
+ throw new TypeError(`aggregate source column '${sourceName}' must be a scalar value column`);
382
+ }
383
+ const reducer = isAggregateOutputSpec(raw) ? raw.using : raw;
384
+ if (typeof reducer !== 'string' && typeof reducer !== 'function') {
385
+ throw new TypeError(`aggregate reducer for '${outputName}' must be a built-in name or function`);
386
+ }
387
+ const explicitKind = isAggregateOutputSpec(raw) ? raw.kind : undefined;
388
+ const resolvedKind = explicitKind ??
389
+ (typeof reducer === 'string' &&
390
+ resolveReducer(reducer).outputKind === 'number'
391
+ ? 'number'
392
+ : sourceColumn.kind);
393
+ normalized.push({
394
+ output: outputName,
395
+ source: sourceName,
396
+ reducer,
397
+ kind: resolvedKind,
398
+ });
340
399
  }
400
+ return normalized;
341
401
  }
342
402
  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}`);
403
+ return resolveReducer(operation).bucketState();
445
404
  }
446
405
  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}`);
406
+ return resolveReducer(operation).rollingState();
567
407
  }
568
408
  function parseDurationInput(value) {
569
409
  if (typeof value === 'number') {
@@ -697,7 +537,7 @@ export class TimeSeries {
697
537
  * the supplied `parse.timeZone`, which defaults to `UTC`.
698
538
  */
699
539
  static fromJSON(input) {
700
- return new TimeSeries({
540
+ return new _a({
701
541
  name: input.name,
702
542
  schema: input.schema,
703
543
  rows: parseJsonRows(input.schema, input.rows, input.parse),
@@ -710,6 +550,48 @@ export class TimeSeries {
710
550
  this.events = validateAndNormalize(input);
711
551
  Object.freeze(this);
712
552
  }
553
+ /**
554
+ * Example: `series.toJSON({ rowFormat: "object" })`.
555
+ * Serializes the series into the JSON-friendly shape accepted by `TimeSeries.fromJSON(...)`.
556
+ *
557
+ * Timestamps are emitted as numbers to avoid time zone ambiguity. Missing payload values are
558
+ * emitted as `null`. By default rows are emitted as arrays; use `rowFormat: "object"` for rows
559
+ * keyed by schema column names.
560
+ */
561
+ toJSON(options = {}) {
562
+ const rowFormat = options.rowFormat ?? 'array';
563
+ const dataColumns = this.schema.slice(1);
564
+ if (rowFormat === 'object') {
565
+ const keyColumn = this.schema[0];
566
+ const rows = this.events.map((event) => {
567
+ const row = {
568
+ [keyColumn.name]: serializeJsonKey(keyColumn.kind, event.key(), rowFormat),
569
+ };
570
+ const data = event.data();
571
+ for (const column of dataColumns) {
572
+ row[column.name] = serializeJsonValue(data[column.name]);
573
+ }
574
+ return Object.freeze(row);
575
+ });
576
+ return {
577
+ name: this.name,
578
+ schema: this.schema,
579
+ rows,
580
+ };
581
+ }
582
+ const rows = this.events.map((event) => {
583
+ const data = event.data();
584
+ return Object.freeze([
585
+ serializeJsonKey(this.schema[0].kind, event.key(), rowFormat),
586
+ ...dataColumns.map((column) => serializeJsonValue(data[column.name])),
587
+ ]);
588
+ });
589
+ return {
590
+ name: this.name,
591
+ schema: this.schema,
592
+ rows,
593
+ };
594
+ }
713
595
  /**
714
596
  * Builds a series from event data that has already been validated and ordered by the caller.
715
597
  *
@@ -717,7 +599,7 @@ export class TimeSeries {
717
599
  * order and normalized key invariants.
718
600
  */
719
601
  static #fromTrustedEvents(name, schema, events) {
720
- const series = Object.create(TimeSeries.prototype);
602
+ const series = Object.create(_a.prototype);
721
603
  series.name = name;
722
604
  series.schema = Object.freeze(schema.slice());
723
605
  series.events = Object.freeze(events.slice());
@@ -731,6 +613,14 @@ export class TimeSeries {
731
613
  get rows() {
732
614
  return toRows(this.schema, this.events);
733
615
  }
616
+ /** Example: `series.toRows()`. Returns normalized row arrays using `Time`/`TimeRange`/`Interval` keys and `undefined` for missing payload values. */
617
+ toRows() {
618
+ return this.rows;
619
+ }
620
+ /** Example: `series.toObjects()`. Returns normalized schema-keyed object rows using temporal key objects and `undefined` for missing payload values. */
621
+ toObjects() {
622
+ return toObjects(this.schema, this.events);
623
+ }
734
624
  /** Example: `series.at(0)`. Returns the event at the supplied zero-based position, if present. */
735
625
  at(index) {
736
626
  return this.events[index];
@@ -748,7 +638,7 @@ export class TimeSeries {
748
638
  /** Example: `series.map(nextSchema, event => event)`. Maps each event into a new typed schema and returns a new series. */
749
639
  map(schema, mapper) {
750
640
  const mappedEvents = this.events.map((event, index) => mapper(event, index));
751
- return new TimeSeries({
641
+ return new _a({
752
642
  name: this.name,
753
643
  schema,
754
644
  rows: toRows(schema, mappedEvents),
@@ -762,9 +652,9 @@ export class TimeSeries {
762
652
  ]);
763
653
  const resultEvents = this.events.map((event) => event.asTime(options));
764
654
  if ((options.at ?? 'begin') === 'begin') {
765
- return TimeSeries.#fromTrustedEvents(this.name, schema, resultEvents);
655
+ return _a.#fromTrustedEvents(this.name, schema, resultEvents);
766
656
  }
767
- return new TimeSeries({
657
+ return new _a({
768
658
  name: this.name,
769
659
  schema,
770
660
  rows: toRows(schema, resultEvents),
@@ -777,7 +667,7 @@ export class TimeSeries {
777
667
  ...this.schema.slice(1),
778
668
  ]);
779
669
  const resultEvents = this.events.map((event) => event.asTimeRange());
780
- return TimeSeries.#fromTrustedEvents(this.name, schema, resultEvents);
670
+ return _a.#fromTrustedEvents(this.name, schema, resultEvents);
781
671
  }
782
672
  asInterval(value) {
783
673
  const schema = Object.freeze([
@@ -789,10 +679,13 @@ export class TimeSeries {
789
679
  ? event.asInterval(() => value(event, index))
790
680
  : event.asInterval(value);
791
681
  });
792
- return TimeSeries.#fromTrustedEvents(this.name, schema, nextEvents);
682
+ return _a.#fromTrustedEvents(this.name, schema, nextEvents);
793
683
  }
794
684
  join(other, options = {}) {
795
- const [left, right] = prepareSeriesForJoin([this, other], options);
685
+ const [left, right] = prepareSeriesForJoin([
686
+ this,
687
+ other,
688
+ ], options);
796
689
  const joinType = options.type ?? 'outer';
797
690
  if (left.firstColumnKind !== right.firstColumnKind) {
798
691
  throw new TypeError('cannot join series with different key kinds');
@@ -845,7 +738,7 @@ export class TimeSeries {
845
738
  rightIndex += 1;
846
739
  }
847
740
  }
848
- return TimeSeries.#fromTrustedEvents(left.name, resultSchema, joinedEvents);
741
+ return _a.#fromTrustedEvents(left.name, resultSchema, joinedEvents);
849
742
  }
850
743
  /**
851
744
  * Example: `series.align(Sequence.every("1m"))`.
@@ -877,7 +770,7 @@ export class TimeSeries {
877
770
  const range = options.range ?? this.timeRange();
878
771
  const resultSchema = makeAlignedSchema(this.schema);
879
772
  if (!range) {
880
- return new TimeSeries({
773
+ return new _a({
881
774
  name: this.name,
882
775
  schema: resultSchema,
883
776
  rows: [],
@@ -896,7 +789,7 @@ export class TimeSeries {
896
789
  for (let i = 0; i < intervals.length; i += 1) {
897
790
  const interval = intervals[i];
898
791
  const t = sampleTime(interval, sample);
899
- const data = this.#alignLinearAt(t, valueColumns, cursor);
792
+ const data = alignLinearAt(this, t, valueColumns, cursor);
900
793
  const row = new Array(resultColumns.length + 1);
901
794
  row[0] = interval;
902
795
  for (let j = 0; j < resultColumns.length; j += 1) {
@@ -909,7 +802,7 @@ export class TimeSeries {
909
802
  })()
910
803
  : intervals.map((interval) => {
911
804
  const t = sampleTime(interval, sample);
912
- const data = this.#alignHoldAt(t);
805
+ const data = alignHoldAt(this, t);
913
806
  return Object.freeze([
914
807
  interval,
915
808
  ...resultSchema
@@ -917,126 +810,452 @@ export class TimeSeries {
917
810
  .map((column) => data[column.name]),
918
811
  ]);
919
812
  });
920
- return new TimeSeries({
813
+ return new _a({
921
814
  name: this.name,
922
815
  schema: resultSchema,
923
816
  rows: alignedRows,
924
817
  });
925
818
  }
819
+ aggregate(sequence, mapping, options = {}) {
820
+ return aggregateInternal(this, sequence, mapping, options);
821
+ }
822
+ reduce(columnOrMapping, reducer) {
823
+ if (typeof columnOrMapping === 'string') {
824
+ const values = this.events.map((event) => {
825
+ const data = event.data();
826
+ return data[columnOrMapping];
827
+ });
828
+ return applyAggregateReducer(reducer, values);
829
+ }
830
+ const columns = normalizeAggregateColumns(this.schema, columnOrMapping);
831
+ const result = {};
832
+ for (const col of columns) {
833
+ const values = this.events.map((event) => {
834
+ const data = event.data();
835
+ return data[col.source];
836
+ });
837
+ result[col.output] = applyAggregateReducer(col.reducer, values);
838
+ }
839
+ return result;
840
+ }
841
+ groupBy(column, transform) {
842
+ const buckets = new Map();
843
+ for (const event of this.events) {
844
+ const raw = event.data()[column];
845
+ const key = raw === undefined ? 'undefined' : String(raw);
846
+ let bucket = buckets.get(key);
847
+ if (!bucket) {
848
+ bucket = [];
849
+ buckets.set(key, bucket);
850
+ }
851
+ bucket.push(event);
852
+ }
853
+ const buildGroup = (events) => new _a({
854
+ name: this.name,
855
+ schema: this.schema,
856
+ rows: toRows(this.schema, events),
857
+ });
858
+ if (transform) {
859
+ const result = new Map();
860
+ for (const [key, events] of buckets) {
861
+ result.set(key, transform(buildGroup(events), key));
862
+ }
863
+ return result;
864
+ }
865
+ const result = new Map();
866
+ for (const [key, events] of buckets) {
867
+ result.set(key, buildGroup(events));
868
+ }
869
+ return result;
870
+ }
926
871
  /**
927
- * Example: `series.aggregate(Sequence.every("1m"), { value: "avg" })`.
928
- * Aggregates events into sequence buckets using built-in reducer names.
872
+ * Example: `series.diff("requests")`.
873
+ * Computes per-event differences for the specified numeric columns.
874
+ * Non-specified columns pass through unchanged. The first event gets
875
+ * `undefined` in affected columns unless `{ drop: true }` is passed,
876
+ * which removes the first event entirely.
929
877
  *
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.
878
+ * Example: `series.diff(["requests", "cpu"])`.
879
+ * Multiple columns can be diffed in a single call.
933
880
  *
934
- * Defaults:
935
- * - `range`: `series.timeRange()`
881
+ * Example: `series.diff("requests", { drop: true })`.
882
+ * Drops the first event instead of keeping it with undefined values.
883
+ */
884
+ diff(columns, options) {
885
+ return this.#diffOrRate('diff', columns, options);
886
+ }
887
+ /**
888
+ * Example: `series.rate("requests")`.
889
+ * Computes the per-second rate of change for the specified numeric columns.
890
+ * Non-specified columns pass through unchanged. The first event gets
891
+ * `undefined` in affected columns unless `{ drop: true }` is passed,
892
+ * which removes the first event entirely.
936
893
  *
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.
894
+ * Example: `series.rate(["requests", "cpu"])`.
895
+ * Multiple columns can be rated in a single call.
941
896
  *
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.
897
+ * Example: `series.rate("requests", { drop: true })`.
898
+ * Drops the first event instead of keeping it with undefined values.
899
+ */
900
+ rate(columns, options) {
901
+ return this.#diffOrRate('rate', columns, options);
902
+ }
903
+ /**
904
+ * Example: `series.pctChange("requests")`.
905
+ * Computes the percentage change `(curr - prev) / prev` for the specified
906
+ * numeric columns. Non-specified columns pass through unchanged. The first
907
+ * event gets `undefined` in affected columns unless `{ drop: true }` is
908
+ * passed.
909
+ */
910
+ pctChange(columns, options) {
911
+ return this.#diffOrRate('pctChange', columns, options);
912
+ }
913
+ #diffOrRate(mode, columns, options) {
914
+ const cols = typeof columns === 'string' ? [columns] : columns;
915
+ const drop = options?.drop === true;
916
+ if (cols.length === 0) {
917
+ throw new Error(`${mode}() requires at least one column name`);
918
+ }
919
+ const targetSet = new Set(cols);
920
+ const outSchema = Object.freeze(this.schema.map((col, i) => {
921
+ if (i === 0)
922
+ return col;
923
+ if (targetSet.has(col.name)) {
924
+ return { ...col, kind: 'number', required: false };
925
+ }
926
+ return col;
927
+ }));
928
+ const events = this.events;
929
+ if (events.length === 0) {
930
+ return _a.#fromTrustedEvents(this.name, outSchema, []);
931
+ }
932
+ const resultEvents = [];
933
+ if (!drop) {
934
+ const firstData = { ...events[0].data() };
935
+ for (const col of cols) {
936
+ firstData[col] = undefined;
937
+ }
938
+ resultEvents.push(new Event(events[0].key(), firstData));
939
+ }
940
+ for (let i = 1; i < events.length; i++) {
941
+ const prev = events[i - 1];
942
+ const curr = events[i];
943
+ const data = { ...curr.data() };
944
+ const dt = mode === 'rate' ? (curr.begin() - prev.begin()) / 1000 : undefined;
945
+ for (const col of cols) {
946
+ const prevVal = prev.data()[col];
947
+ const currVal = data[col];
948
+ if (typeof currVal === 'number' && typeof prevVal === 'number') {
949
+ const delta = currVal - prevVal;
950
+ if (mode === 'pctChange') {
951
+ data[col] = prevVal !== 0 ? delta / prevVal : undefined;
952
+ }
953
+ else if (mode === 'rate') {
954
+ data[col] = dt !== 0 ? delta / dt : undefined;
955
+ }
956
+ else {
957
+ data[col] = delta;
958
+ }
959
+ }
960
+ else {
961
+ data[col] = undefined;
962
+ }
963
+ }
964
+ resultEvents.push(new Event(curr.key(), data));
965
+ }
966
+ return _a.#fromTrustedEvents(this.name, outSchema, resultEvents);
967
+ }
968
+ /**
969
+ * Example: `series.cumulative({ requests: "sum" })`.
970
+ * Computes running accumulations for the specified numeric columns.
971
+ * Non-accumulated columns pass through unchanged.
972
+ *
973
+ * Built-in accumulators: `"sum"`, `"max"`, `"min"`, `"count"`.
974
+ * Custom accumulators: `(acc: number, value: number) => number`.
975
+ */
976
+ cumulative(spec) {
977
+ const entries = Object.entries(spec);
978
+ if (entries.length === 0) {
979
+ throw new Error('cumulative() requires at least one column');
980
+ }
981
+ const targetSet = new Set(entries.map(([name]) => name));
982
+ const outSchema = Object.freeze(this.schema.map((col, i) => {
983
+ if (i === 0)
984
+ return col;
985
+ if (targetSet.has(col.name)) {
986
+ return { ...col, kind: 'number', required: false };
987
+ }
988
+ return col;
989
+ }));
990
+ const events = this.events;
991
+ if (events.length === 0) {
992
+ return _a.#fromTrustedEvents(this.name, outSchema, []);
993
+ }
994
+ const state = new Map();
995
+ for (const [name, reducer] of entries) {
996
+ if (typeof reducer === 'function') {
997
+ const fn = reducer;
998
+ state.set(name, {
999
+ acc: undefined,
1000
+ apply: (acc, v) => (acc === undefined ? v : fn(acc, v)),
1001
+ });
1002
+ }
1003
+ else {
1004
+ switch (reducer) {
1005
+ case 'sum':
1006
+ state.set(name, {
1007
+ acc: undefined,
1008
+ apply: (acc, v) => (acc ?? 0) + v,
1009
+ });
1010
+ break;
1011
+ case 'count':
1012
+ state.set(name, { acc: undefined, apply: (acc) => (acc ?? 0) + 1 });
1013
+ break;
1014
+ case 'max':
1015
+ state.set(name, {
1016
+ acc: undefined,
1017
+ apply: (acc, v) => (acc === undefined || v > acc ? v : acc),
1018
+ });
1019
+ break;
1020
+ case 'min':
1021
+ state.set(name, {
1022
+ acc: undefined,
1023
+ apply: (acc, v) => (acc === undefined || v < acc ? v : acc),
1024
+ });
1025
+ break;
1026
+ }
1027
+ }
1028
+ }
1029
+ const resultEvents = [];
1030
+ for (const event of events) {
1031
+ const data = { ...event.data() };
1032
+ for (const [name, s] of state) {
1033
+ const raw = data[name];
1034
+ if (typeof raw === 'number') {
1035
+ s.acc = s.apply(s.acc, raw);
1036
+ data[name] = s.acc;
1037
+ }
1038
+ else {
1039
+ data[name] = s.acc;
1040
+ }
1041
+ }
1042
+ resultEvents.push(new Event(event.key(), data));
1043
+ }
1044
+ return _a.#fromTrustedEvents(this.name, outSchema, resultEvents);
1045
+ }
1046
+ /**
1047
+ * Example: `series.shift("value", 1)`.
1048
+ * Lags column values by N events (positive N) or leads them (negative N).
1049
+ * Vacated positions get `undefined`.
1050
+ */
1051
+ shift(columns, n) {
1052
+ const cols = typeof columns === 'string' ? [columns] : columns;
1053
+ if (cols.length === 0) {
1054
+ throw new Error('shift() requires at least one column name');
1055
+ }
1056
+ if (!Number.isInteger(n)) {
1057
+ throw new Error('shift() requires an integer offset');
1058
+ }
1059
+ const targetSet = new Set(cols);
1060
+ const outSchema = Object.freeze(this.schema.map((col, i) => {
1061
+ if (i === 0)
1062
+ return col;
1063
+ if (targetSet.has(col.name)) {
1064
+ return { ...col, kind: 'number', required: false };
1065
+ }
1066
+ return col;
1067
+ }));
1068
+ const events = this.events;
1069
+ if (events.length === 0) {
1070
+ return _a.#fromTrustedEvents(this.name, outSchema, []);
1071
+ }
1072
+ const resultEvents = [];
1073
+ for (let i = 0; i < events.length; i++) {
1074
+ const data = { ...events[i].data() };
1075
+ const srcIdx = i - n;
1076
+ for (const col of cols) {
1077
+ if (srcIdx >= 0 && srcIdx < events.length) {
1078
+ data[col] = events[srcIdx].data()[col];
1079
+ }
1080
+ else {
1081
+ data[col] = undefined;
1082
+ }
1083
+ }
1084
+ resultEvents.push(new Event(events[i].key(), data));
1085
+ }
1086
+ return _a.#fromTrustedEvents(this.name, outSchema, resultEvents);
1087
+ }
1088
+ /**
1089
+ * Example: `series.fill("hold")`.
1090
+ * Fills `undefined` values using the given strategy for all payload columns.
944
1091
  *
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:
1092
+ * Example: `series.fill({ cpu: "linear", host: "hold" })`.
1093
+ * Per-column fill strategies. Unmentioned columns are left as-is.
1094
+ * Strategy names: `"hold"` (forward fill), `"linear"` (time-interpolated),
1095
+ * `"zero"` (fill with 0). A non-string value is used as a literal fill value.
947
1096
  *
948
- * ```ts
949
- * const range = series.timeRange();
950
- * if (!range) {
951
- * throw new Error("empty series");
952
- * }
1097
+ * Example: `series.fill("hold", { limit: 3 })`.
1098
+ * Caps consecutive fills per column. After `limit` consecutive fills, further
1099
+ * `undefined` values are left as-is until a real value resets the counter.
953
1100
  *
954
- * const aggregated = series.aggregate(
955
- * Sequence.every("1m", { anchor: range.begin() }),
956
- * { value: "avg" },
957
- * );
958
- * ```
1101
+ * `"linear"` requires known values on both sides of a gap to interpolate.
1102
+ * Leading and trailing `undefined` runs are left unfilled.
959
1103
  */
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
- });
1104
+ fill(strategy, options) {
1105
+ if (this.events.length === 0) {
1106
+ return this;
986
1107
  }
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;
1108
+ const colNames = this.schema.slice(1).map((c) => c.name);
1109
+ const specs = new Map();
1110
+ if (typeof strategy === 'string') {
1111
+ for (const name of colNames) {
1112
+ specs.set(name, { mode: strategy });
1113
+ }
1114
+ }
1115
+ else {
1116
+ const strategies = new Set([
1117
+ 'hold',
1118
+ 'bfill',
1119
+ 'linear',
1120
+ 'zero',
1121
+ ]);
1122
+ for (const [name, spec] of Object.entries(strategy)) {
1123
+ if (typeof spec === 'string' && strategies.has(spec)) {
1124
+ specs.set(name, { mode: spec });
1000
1125
  }
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]);
1126
+ else {
1127
+ specs.set(name, { mode: 'literal', value: spec });
1128
+ }
1129
+ }
1130
+ }
1131
+ const limit = options?.limit;
1132
+ const n = this.events.length;
1133
+ const columns = {};
1134
+ for (const name of colNames) {
1135
+ columns[name] = new Array(n);
1136
+ }
1137
+ for (let i = 0; i < n; i++) {
1138
+ const data = this.events[i].data();
1139
+ for (const name of colNames) {
1140
+ columns[name][i] = data[name];
1141
+ }
1142
+ }
1143
+ const times = new Array(n);
1144
+ for (let i = 0; i < n; i++) {
1145
+ times[i] = this.events[i].begin();
1146
+ }
1147
+ for (const [name, spec] of specs) {
1148
+ const col = columns[name];
1149
+ if (!col)
1150
+ continue;
1151
+ switch (spec.mode) {
1152
+ case 'hold': {
1153
+ let last;
1154
+ let consecutive = 0;
1155
+ for (let i = 0; i < n; i++) {
1156
+ if (col[i] !== undefined) {
1157
+ last = col[i];
1158
+ consecutive = 0;
1159
+ }
1160
+ else if (last !== undefined) {
1161
+ consecutive++;
1162
+ if (limit === undefined || consecutive <= limit) {
1163
+ col[i] = last;
1164
+ }
1165
+ }
1008
1166
  }
1009
- scanIndex += 1;
1167
+ break;
1010
1168
  }
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
- });
1169
+ case 'bfill': {
1170
+ let next;
1171
+ let consecutive = 0;
1172
+ for (let i = n - 1; i >= 0; i--) {
1173
+ if (col[i] !== undefined) {
1174
+ next = col[i];
1175
+ consecutive = 0;
1176
+ }
1177
+ else if (next !== undefined) {
1178
+ consecutive++;
1179
+ if (limit === undefined || consecutive <= limit) {
1180
+ col[i] = next;
1181
+ }
1182
+ }
1183
+ }
1184
+ break;
1185
+ }
1186
+ case 'zero': {
1187
+ let consecutive = 0;
1188
+ for (let i = 0; i < n; i++) {
1189
+ if (col[i] !== undefined) {
1190
+ consecutive = 0;
1191
+ }
1192
+ else {
1193
+ consecutive++;
1194
+ if (limit === undefined || consecutive <= limit) {
1195
+ col[i] = 0;
1196
+ }
1197
+ }
1198
+ }
1199
+ break;
1200
+ }
1201
+ case 'literal': {
1202
+ let consecutive = 0;
1203
+ for (let i = 0; i < n; i++) {
1204
+ if (col[i] !== undefined) {
1205
+ consecutive = 0;
1206
+ }
1207
+ else {
1208
+ consecutive++;
1209
+ if (limit === undefined || consecutive <= limit) {
1210
+ col[i] = spec.value;
1211
+ }
1212
+ }
1213
+ }
1214
+ break;
1215
+ }
1216
+ case 'linear': {
1217
+ let gapStart = -1;
1218
+ for (let i = 0; i < n; i++) {
1219
+ if (col[i] !== undefined) {
1220
+ if (gapStart >= 0 && gapStart > 0) {
1221
+ const before = col[gapStart - 1];
1222
+ const after = col[i];
1223
+ const t0 = times[gapStart - 1];
1224
+ const t1 = times[i];
1225
+ const span = t1 - t0;
1226
+ const gapLen = i - gapStart;
1227
+ for (let j = gapStart; j < i; j++) {
1228
+ const fillIndex = j - gapStart + 1;
1229
+ if (limit !== undefined && fillIndex > limit)
1230
+ break;
1231
+ if (span === 0) {
1232
+ col[j] = before;
1233
+ }
1234
+ else {
1235
+ const ratio = (times[j] - t0) / span;
1236
+ col[j] = before + (after - before) * ratio;
1237
+ }
1238
+ }
1239
+ }
1240
+ gapStart = -1;
1241
+ }
1242
+ else if (gapStart < 0) {
1243
+ gapStart = i;
1244
+ }
1245
+ }
1246
+ break;
1247
+ }
1248
+ }
1022
1249
  }
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
- });
1250
+ const resultEvents = [];
1251
+ for (let i = 0; i < n; i++) {
1252
+ const data = {};
1253
+ for (const name of colNames) {
1254
+ data[name] = columns[name][i];
1255
+ }
1256
+ resultEvents.push(new Event(this.events[i].key(), data));
1257
+ }
1258
+ return _a.#fromTrustedEvents(this.name, this.schema, resultEvents);
1040
1259
  }
1041
1260
  rolling(sequenceOrWindow, windowOrMapping, mappingOrOptions, maybeOptions = {}) {
1042
1261
  const buildResultColumns = () => this.schema
@@ -1092,7 +1311,7 @@ export class TimeSeries {
1092
1311
  ...buildResultColumns(),
1093
1312
  ]);
1094
1313
  if (!range) {
1095
- return new TimeSeries({
1314
+ return new _a({
1096
1315
  name: this.name,
1097
1316
  schema: resultSchema,
1098
1317
  rows: [],
@@ -1103,16 +1322,16 @@ export class TimeSeries {
1103
1322
  const anchor = sampleTime(bucket, sample);
1104
1323
  const contributors = this.events.filter((candidate) => anchorInWindow(candidate.begin(), anchor));
1105
1324
  const aggregated = resultSchema.slice(1).map((column) => {
1106
- const operation = mapping[column.name];
1325
+ const reducer = mapping[column.name];
1107
1326
  const values = contributors.map((candidate) => {
1108
1327
  const data = candidate.data();
1109
1328
  return data[column.name];
1110
1329
  });
1111
- return aggregateValues(operation, values);
1330
+ return applyAggregateReducer(reducer, values);
1112
1331
  });
1113
1332
  return Object.freeze([bucket, ...aggregated]);
1114
1333
  });
1115
- return new TimeSeries({
1334
+ return new _a({
1116
1335
  name: this.name,
1117
1336
  schema: resultSchema,
1118
1337
  rows: resultRows,
@@ -1123,7 +1342,12 @@ export class TimeSeries {
1123
1342
  this.schema[0],
1124
1343
  ...resultColumns,
1125
1344
  ]);
1126
- const reducerStates = resultColumns.map((column) => createRollingReducerState(mapping[column.name]));
1345
+ const reducerStates = resultColumns.map((column) => {
1346
+ const reducer = mapping[column.name];
1347
+ return isBuiltInAggregateReducer(reducer)
1348
+ ? createRollingReducerState(reducer)
1349
+ : null;
1350
+ });
1127
1351
  const beginTimes = this.events.map((event) => event.begin());
1128
1352
  const resultRows = new Array(this.events.length);
1129
1353
  let windowStart = 0;
@@ -1131,19 +1355,38 @@ export class TimeSeries {
1131
1355
  const addEvent = (index) => {
1132
1356
  const event = this.events[index];
1133
1357
  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]);
1358
+ for (let i = 0; i < reducerStates.length; i++) {
1359
+ const state = reducerStates[i];
1360
+ if (state) {
1361
+ const column = resultColumns[i];
1362
+ state.add(index, data[column.name]);
1363
+ }
1137
1364
  }
1138
1365
  };
1139
1366
  const removeEvent = (index) => {
1140
1367
  const event = this.events[index];
1141
1368
  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]);
1369
+ for (let i = 0; i < reducerStates.length; i++) {
1370
+ const state = reducerStates[i];
1371
+ if (state) {
1372
+ const column = resultColumns[i];
1373
+ state.remove(index, data[column.name]);
1374
+ }
1145
1375
  }
1146
1376
  };
1377
+ const snapshotWindow = () => resultColumns.map((column, i) => {
1378
+ const state = reducerStates[i];
1379
+ if (state)
1380
+ return state.snapshot();
1381
+ const reducer = mapping[column.name];
1382
+ const values = this.events
1383
+ .slice(windowStart, windowEnd)
1384
+ .map((event) => {
1385
+ const data = event.data();
1386
+ return data[column.name];
1387
+ });
1388
+ return applyAggregateReducer(reducer, values);
1389
+ });
1147
1390
  if (alignment === 'trailing') {
1148
1391
  for (let groupStart = 0; groupStart < this.events.length;) {
1149
1392
  const anchor = beginTimes[groupStart];
@@ -1163,7 +1406,7 @@ export class TimeSeries {
1163
1406
  removeEvent(windowStart);
1164
1407
  windowStart += 1;
1165
1408
  }
1166
- const aggregated = reducerStates.map((state) => state.snapshot());
1409
+ const aggregated = snapshotWindow();
1167
1410
  for (let index = groupStart; index < groupEnd; index++) {
1168
1411
  resultRows[index] = Object.freeze([
1169
1412
  this.events[index].key(),
@@ -1193,7 +1436,7 @@ export class TimeSeries {
1193
1436
  addEvent(windowEnd);
1194
1437
  windowEnd += 1;
1195
1438
  }
1196
- const aggregated = reducerStates.map((state) => state.snapshot());
1439
+ const aggregated = snapshotWindow();
1197
1440
  for (let index = groupStart; index < groupEnd; index++) {
1198
1441
  resultRows[index] = Object.freeze([
1199
1442
  this.events[index].key(),
@@ -1224,7 +1467,7 @@ export class TimeSeries {
1224
1467
  addEvent(windowEnd);
1225
1468
  windowEnd += 1;
1226
1469
  }
1227
- const aggregated = reducerStates.map((state) => state.snapshot());
1470
+ const aggregated = snapshotWindow();
1228
1471
  for (let index = groupStart; index < groupEnd; index++) {
1229
1472
  resultRows[index] = Object.freeze([
1230
1473
  this.events[index].key(),
@@ -1234,7 +1477,7 @@ export class TimeSeries {
1234
1477
  groupStart = groupEnd;
1235
1478
  }
1236
1479
  }
1237
- return new TimeSeries({
1480
+ return new _a({
1238
1481
  name: this.name,
1239
1482
  schema: resultSchema,
1240
1483
  rows: resultRows,
@@ -1299,7 +1542,7 @@ export class TimeSeries {
1299
1542
  .map((nextColumn) => nextEvent.data()[nextColumn.name]),
1300
1543
  ]);
1301
1544
  });
1302
- return new TimeSeries({
1545
+ return new _a({
1303
1546
  name: this.name,
1304
1547
  schema: resultSchema,
1305
1548
  rows: resultRows,
@@ -1337,7 +1580,7 @@ export class TimeSeries {
1337
1580
  .map((nextColumn) => nextEvent.data()[nextColumn.name]),
1338
1581
  ]);
1339
1582
  });
1340
- return new TimeSeries({
1583
+ return new _a({
1341
1584
  name: this.name,
1342
1585
  schema: resultSchema,
1343
1586
  rows: resultRows,
@@ -1431,7 +1674,7 @@ export class TimeSeries {
1431
1674
  .map((nextColumn) => nextEvent.data()[nextColumn.name]),
1432
1675
  ]);
1433
1676
  });
1434
- return new TimeSeries({
1677
+ return new _a({
1435
1678
  name: this.name,
1436
1679
  schema: resultSchema,
1437
1680
  rows: resultRows,
@@ -1439,11 +1682,11 @@ export class TimeSeries {
1439
1682
  }
1440
1683
  /** Example: `series.slice(0, 10)`. Returns a positional half-open slice of the series. */
1441
1684
  slice(beginIndex, endIndex) {
1442
- return TimeSeries.#fromTrustedEvents(this.name, this.schema, this.events.slice(beginIndex, endIndex));
1685
+ return _a.#fromTrustedEvents(this.name, this.schema, this.events.slice(beginIndex, endIndex));
1443
1686
  }
1444
1687
  /** Example: `series.filter(event => event.get("active"))`. Returns a new series containing only events that match the predicate. */
1445
1688
  filter(predicate) {
1446
- return TimeSeries.#fromTrustedEvents(this.name, this.schema, this.events.filter((event, index) => predicate(event, index)));
1689
+ return _a.#fromTrustedEvents(this.name, this.schema, this.events.filter((event, index) => predicate(event, index)));
1447
1690
  }
1448
1691
  /** Example: `series.find(event => event.get("value") > 0)`. Returns the first event that matches the predicate, if any. */
1449
1692
  find(predicate) {
@@ -1564,7 +1807,7 @@ export class TimeSeries {
1564
1807
  const trimmedEvents = this.events
1565
1808
  .map((event) => event.trim(range))
1566
1809
  .filter((event) => event !== undefined);
1567
- return TimeSeries.#fromTrustedEvents(this.name, this.schema, trimmedEvents);
1810
+ return _a.#fromTrustedEvents(this.name, this.schema, trimmedEvents);
1568
1811
  }
1569
1812
  /** Example: `series.before(Date.now())`. Returns the events ending strictly before the supplied temporal boundary. */
1570
1813
  before(boundary) {
@@ -1596,7 +1839,7 @@ export class TimeSeries {
1596
1839
  const selectedEvent = event.select(...keys);
1597
1840
  return selectedEvent;
1598
1841
  });
1599
- return TimeSeries.#fromTrustedEvents(this.name, resultSchema, resultEvents);
1842
+ return _a.#fromTrustedEvents(this.name, resultSchema, resultEvents);
1600
1843
  }
1601
1844
  /** Example: `series.rename({ cpu: "usage" })`. Returns a new series with payload field names renamed according to the supplied mapping. */
1602
1845
  rename(mapping) {
@@ -1614,7 +1857,7 @@ export class TimeSeries {
1614
1857
  const renamedEvent = event.rename(mapping);
1615
1858
  return renamedEvent;
1616
1859
  });
1617
- return TimeSeries.#fromTrustedEvents(this.name, resultSchema, resultEvents);
1860
+ return _a.#fromTrustedEvents(this.name, resultSchema, resultEvents);
1618
1861
  }
1619
1862
  collapse(keys, output, reducer, options) {
1620
1863
  const nextEvents = this.events.map((event) => {
@@ -1642,54 +1885,138 @@ export class TimeSeries {
1642
1885
  : 'string',
1643
1886
  },
1644
1887
  ]);
1645
- return TimeSeries.#fromTrustedEvents(this.name, resultSchema, nextEvents);
1888
+ return _a.#fromTrustedEvents(this.name, resultSchema, nextEvents);
1646
1889
  }
1647
1890
  /** Example: `series.length`. Returns the number of events in the series. */
1648
1891
  get length() {
1649
1892
  return this.events.length;
1650
1893
  }
1651
- #alignHoldAt(t) {
1652
- const event = this.atOrBefore(new Time(t));
1653
- return (event?.data() ?? {});
1894
+ }
1895
+ _a = TimeSeries;
1896
+ function aggregateInternal(series, sequence, mapping, options = {}) {
1897
+ const range = options.range ?? series.timeRange();
1898
+ const aggregateColumns = normalizeAggregateColumns(series.schema, mapping);
1899
+ const resultSchema = Object.freeze([
1900
+ { name: 'interval', kind: 'interval' },
1901
+ ...aggregateColumns.map((column) => ({
1902
+ name: column.output,
1903
+ kind: column.kind,
1904
+ required: false,
1905
+ })),
1906
+ ]);
1907
+ if (!range) {
1908
+ return new TimeSeries({
1909
+ name: series.name,
1910
+ schema: resultSchema,
1911
+ rows: [],
1912
+ });
1654
1913
  }
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;
1914
+ const buckets = toBoundedSequence(sequence, range, 'begin').intervals();
1915
+ const columns = aggregateColumns;
1916
+ if (isTimeKeyed(series)) {
1917
+ const builtInOnly = columns.every((column) => isBuiltInAggregateReducer(column.reducer));
1918
+ let eventIndex = 0;
1919
+ const resultRows = buckets.map((bucket) => {
1920
+ const states = builtInOnly
1921
+ ? columns.map((column) => createAggregateBucketState(column.reducer))
1922
+ : undefined;
1923
+ while (eventIndex < series.events.length &&
1924
+ series.events[eventIndex].begin() < bucket.begin()) {
1925
+ eventIndex += 1;
1662
1926
  }
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;
1927
+ const bucketStart = eventIndex;
1928
+ let scanIndex = bucketStart;
1929
+ while (scanIndex < series.events.length &&
1930
+ series.events[scanIndex].begin() < bucket.end()) {
1931
+ if (states) {
1932
+ const data = series.events[scanIndex].data();
1933
+ for (let index = 0; index < columns.length; index += 1) {
1934
+ const column = columns[index];
1935
+ states[index].add(data[column.source]);
1936
+ }
1937
+ }
1938
+ scanIndex += 1;
1939
+ }
1940
+ eventIndex = scanIndex;
1941
+ if (states) {
1942
+ return Object.freeze([
1943
+ bucket,
1944
+ ...states.map((state) => state.snapshot()),
1945
+ ]);
1689
1946
  }
1690
- result[column.name] = previousValue;
1947
+ const contributors = series.events.slice(bucketStart, scanIndex);
1948
+ const aggregated = columns.map((column) => {
1949
+ const values = contributors.map((event) => {
1950
+ const data = event.data();
1951
+ return data[column.source];
1952
+ });
1953
+ return applyAggregateReducer(column.reducer, values);
1954
+ });
1955
+ return Object.freeze([bucket, ...aggregated]);
1956
+ });
1957
+ return new TimeSeries({
1958
+ name: series.name,
1959
+ schema: resultSchema,
1960
+ rows: resultRows,
1961
+ });
1962
+ }
1963
+ const resultRows = buckets.map((bucket) => {
1964
+ const contributors = series.events.filter((event) => bucketOverlapsHalfOpen(bucket, event.key()));
1965
+ const aggregated = columns.map((column) => {
1966
+ const values = contributors.map((event) => {
1967
+ const data = event.data();
1968
+ return data[column.source];
1969
+ });
1970
+ return applyAggregateReducer(column.reducer, values);
1971
+ });
1972
+ return Object.freeze([bucket, ...aggregated]);
1973
+ });
1974
+ return new TimeSeries({
1975
+ name: series.name,
1976
+ schema: resultSchema,
1977
+ rows: resultRows,
1978
+ });
1979
+ }
1980
+ function alignHoldAt(series, t) {
1981
+ const event = series.atOrBefore(new Time(t));
1982
+ return (event?.data() ?? {});
1983
+ }
1984
+ function alignLinearAt(series, t, valueColumns, cursor) {
1985
+ const events = series.events;
1986
+ const hasCursor = cursor !== undefined;
1987
+ let index = hasCursor ? cursor.index : series.bisect(t);
1988
+ if (hasCursor) {
1989
+ while (index < events.length && events[index].begin() < t) {
1990
+ index += 1;
1691
1991
  }
1692
- return result;
1992
+ cursor.index = index;
1993
+ }
1994
+ if (index < events.length && events[index].begin() === t) {
1995
+ return events[index].data();
1996
+ }
1997
+ if (index === 0) {
1998
+ return {};
1999
+ }
2000
+ const previous = events[index - 1];
2001
+ const next = events[index];
2002
+ if (!next || previous.begin() === next.begin()) {
2003
+ return previous.data();
2004
+ }
2005
+ const ratio = (t - previous.begin()) / (next.begin() - previous.begin());
2006
+ const result = {};
2007
+ const previousData = previous.data();
2008
+ const nextData = next.data();
2009
+ for (const column of valueColumns) {
2010
+ const previousValue = previousData[column.name];
2011
+ const nextValue = nextData[column.name];
2012
+ if (column.kind === 'number' &&
2013
+ typeof previousValue === 'number' &&
2014
+ typeof nextValue === 'number') {
2015
+ result[column.name] = previousValue + (nextValue - previousValue) * ratio;
2016
+ continue;
2017
+ }
2018
+ result[column.name] = previousValue;
1693
2019
  }
2020
+ return result;
1694
2021
  }
1695
2022
  //# sourceMappingURL=TimeSeries.js.map