@synnaxlabs/x 0.14.2 → 0.15.1

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.
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-misused-new */
1
2
  // Copyright 2023 Synnax Labs, Inc.
2
3
  //
3
4
  // Use of this software is governed by the Business Source License included in the file
@@ -26,10 +27,9 @@ import {
26
27
  isTelemValue,
27
28
  TimeSpan,
28
29
  type CrudeTimeStamp,
30
+ type NumericTelemValue,
29
31
  } from "@/telem/telem";
30
32
 
31
- export type SampleValue = number | bigint;
32
-
33
33
  interface GL {
34
34
  control: GLBufferController | null;
35
35
  buffer: WebGLBuffer | null;
@@ -40,7 +40,7 @@ interface GL {
40
40
  export interface SeriesDigest {
41
41
  key: string;
42
42
  dataType: string;
43
- sampleOffset: SampleValue;
43
+ sampleOffset: NumericTelemValue;
44
44
  alignment: bounds.Bounds;
45
45
  timeRange?: string;
46
46
  length: number;
@@ -50,7 +50,7 @@ export interface SeriesDigest {
50
50
  interface BaseSeriesProps {
51
51
  dataType?: CrudeDataType;
52
52
  timeRange?: TimeRange;
53
- sampleOffset?: SampleValue;
53
+ sampleOffset?: NumericTelemValue;
54
54
  glBufferUsage?: GLBufferUsage;
55
55
  alignment?: number;
56
56
  key?: string;
@@ -99,7 +99,7 @@ export interface SeriesMemInfo {
99
99
  * Series is a strongly typed array of telemetry samples backed by an underlying binary
100
100
  * buffer.
101
101
  */
102
- export class Series {
102
+ export class Series<T extends TelemValue = TelemValue> {
103
103
  key: string = "";
104
104
  /** The data type of the array */
105
105
  readonly dataType: DataType;
@@ -108,7 +108,7 @@ export class Series {
108
108
  * downwards. Typically used to convert arrays to lower precision while preserving
109
109
  * the relative range of actual values.
110
110
  */
111
- sampleOffset: SampleValue;
111
+ sampleOffset: NumericTelemValue;
112
112
  /**
113
113
  * Stores information about the buffer state of this array into a WebGL buffer.
114
114
  */
@@ -118,9 +118,9 @@ export class Series {
118
118
  readonly _timeRange?: TimeRange;
119
119
  readonly alignment: number = 0;
120
120
  /** A cached minimum value. */
121
- private _cachedMin?: SampleValue;
121
+ private _cachedMin?: NumericTelemValue;
122
122
  /** A cached maximum value. */
123
- private _cachedMax?: SampleValue;
123
+ private _cachedMax?: NumericTelemValue;
124
124
  /** The write position of the buffer. */
125
125
  private writePos: number = FULL_BUFFER;
126
126
  /** Tracks the number of entities currently using this array. */
@@ -138,6 +138,7 @@ export class Series {
138
138
  key = nanoid(),
139
139
  } = props;
140
140
  const { data } = props;
141
+
141
142
  if (data instanceof Series) {
142
143
  this.key = data.key;
143
144
  this.dataType = data.dataType;
@@ -375,6 +376,8 @@ export class Series {
375
376
  }
376
377
 
377
378
  private calculateCachedLength(): number {
379
+ if (!this.dataType.isVariable)
380
+ throw new Error("cannot calculate length of a non-variable length data type");
378
381
  let cl = 0;
379
382
  this.data.forEach((v) => {
380
383
  if (v === 10) cl++;
@@ -392,7 +395,7 @@ export class Series {
392
395
  * WARNING: This method is expensive and copies the entire underlying array. There
393
396
  * also may be untimely precision issues when converting between data types.
394
397
  */
395
- convert(target: DataType, sampleOffset: SampleValue = 0): Series {
398
+ convert(target: DataType, sampleOffset: NumericTelemValue = 0): Series {
396
399
  if (this.dataType.equals(target)) return this;
397
400
  const data = new target.Array(this.length);
398
401
  for (let i = 0; i < this.length; i++) {
@@ -408,7 +411,7 @@ export class Series {
408
411
  });
409
412
  }
410
413
 
411
- private calcRawMax(): SampleValue {
414
+ private calcRawMax(): NumericTelemValue {
412
415
  if (this.length === 0) return -Infinity;
413
416
  if (this.dataType.equals(DataType.TIMESTAMP)) {
414
417
  this._cachedMax = this.data[this.data.length - 1];
@@ -423,7 +426,7 @@ export class Series {
423
426
  }
424
427
 
425
428
  /** @returns the maximum value in the array */
426
- get max(): SampleValue {
429
+ get max(): NumericTelemValue {
427
430
  if (this.dataType.isVariable)
428
431
  throw new Error("cannot calculate maximum on a variable length data type");
429
432
  if (this.writePos === 0) return -Infinity;
@@ -431,7 +434,7 @@ export class Series {
431
434
  return addSamples(this._cachedMax, this.sampleOffset);
432
435
  }
433
436
 
434
- private calcRawMin(): SampleValue {
437
+ private calcRawMin(): NumericTelemValue {
435
438
  if (this.length === 0) return Infinity;
436
439
  if (this.dataType.equals(DataType.TIMESTAMP)) {
437
440
  this._cachedMin = this.data[0];
@@ -446,7 +449,7 @@ export class Series {
446
449
  }
447
450
 
448
451
  /** @returns the minimum value in the array */
449
- get min(): SampleValue {
452
+ get min(): NumericTelemValue {
450
453
  if (this.dataType.isVariable)
451
454
  throw new Error("cannot calculate minimum on a variable length data type");
452
455
  if (this.writePos === 0) return Infinity;
@@ -476,22 +479,48 @@ export class Series {
476
479
  _ = this.min;
477
480
  }
478
481
 
479
- get range(): SampleValue {
482
+ get range(): NumericTelemValue {
480
483
  return addSamples(this.max, -this.min);
481
484
  }
482
485
 
483
- at(index: number, required: true): SampleValue;
486
+ at(index: number, required: true): T;
484
487
 
485
- at(index: number, required?: false): SampleValue | undefined;
488
+ at(index: number, required?: false): T | undefined;
486
489
 
487
- at(index: number, required?: boolean): SampleValue | undefined {
490
+ at(index: number, required?: boolean): T | undefined {
491
+ if (this.dataType.isVariable) return this.atVariable(index, required ?? false);
488
492
  if (index < 0) index = this.length + index;
489
493
  const v = this.data[index];
490
494
  if (v == null) {
491
495
  if (required === true) throw new Error(`[series] - no value at index ${index}`);
492
496
  return undefined;
493
497
  }
494
- return addSamples(v, this.sampleOffset);
498
+ return addSamples(v, this.sampleOffset) as T;
499
+ }
500
+
501
+ private atVariable(index: number, required: boolean): T | undefined {
502
+ if (index < 0) index = this.length + index;
503
+ let start = 0;
504
+ let end = 0;
505
+ for (let i = 0; i < this.data.length; i++) {
506
+ if (this.data[i] === 10) {
507
+ if (index === 0) {
508
+ end = i;
509
+ break;
510
+ }
511
+ start = i + 1;
512
+ index--;
513
+ }
514
+ }
515
+ if (end === 0) end = this.data.length;
516
+ if (start >= end || index > 0) {
517
+ if (required) throw new Error(`[series] - no value at index ${index}`);
518
+ return undefined;
519
+ }
520
+ const slice = this.data.slice(start, end);
521
+ if (this.dataType.equals(DataType.STRING))
522
+ return new TextDecoder().decode(slice) as unknown as T;
523
+ return JSON.parse(new TextDecoder().decode(slice)) as unknown as T;
495
524
  }
496
525
 
497
526
  /**
@@ -499,13 +528,13 @@ export class Series {
499
528
  * The underlying array must be sorted. If it is not, the behavior of this method is undefined.
500
529
  * @param value the value to search for.
501
530
  */
502
- binarySearch(value: SampleValue): number {
531
+ binarySearch(value: NumericTelemValue): number {
503
532
  let left = 0;
504
533
  let right = this.length - 1;
505
534
  const cf = compare.newF(value);
506
535
  while (left <= right) {
507
536
  const mid = Math.floor((left + right) / 2);
508
- const cmp = cf(this.at(mid, true), value);
537
+ const cmp = cf(this.at(mid, true) as NumericTelemValue, value);
509
538
  if (cmp === 0) return mid;
510
539
  if (cmp < 0) left = mid + 1;
511
540
  else right = mid - 1;
@@ -548,6 +577,37 @@ export class Series {
548
577
  }
549
578
  }
550
579
 
580
+ as(jsType: "string"): Series<string>;
581
+
582
+ as(jsType: "number"): Series<number>;
583
+
584
+ as(jsType: "bigint"): Series<bigint>;
585
+
586
+ as<T extends TelemValue>(jsType: "string" | "number" | "bigint"): Series<T> {
587
+ if (jsType === "string") {
588
+ if (!this.dataType.equals(DataType.STRING))
589
+ throw new Error(
590
+ `cannot convert series of type ${this.dataType.toString()} to string`,
591
+ );
592
+ return this as unknown as Series<T>;
593
+ }
594
+ if (jsType === "number") {
595
+ if (!this.dataType.isNumeric)
596
+ throw new Error(
597
+ `cannot convert series of type ${this.dataType.toString()} to number`,
598
+ );
599
+ return this as unknown as Series<T>;
600
+ }
601
+ if (jsType === "bigint") {
602
+ if (!this.dataType.equals(DataType.INT64))
603
+ throw new Error(
604
+ `cannot convert series of type ${this.dataType.toString()} to bigint`,
605
+ );
606
+ return this as unknown as Series<T>;
607
+ }
608
+ throw new Error(`cannot convert series to ${jsType as string}`);
609
+ }
610
+
551
611
  get digest(): SeriesDigest {
552
612
  return {
553
613
  key: this.key,
@@ -587,6 +647,17 @@ export class Series {
587
647
  return this.gl.buffer;
588
648
  }
589
649
 
650
+ [Symbol.iterator](): Iterator<T> {
651
+ if (this.dataType.isVariable) {
652
+ const s = new StringSeriesIterator(this);
653
+ if (this.dataType.equals(DataType.JSON)) {
654
+ return new JSONSeriesIterator(s) as Iterator<T>;
655
+ }
656
+ return s as Iterator<T>;
657
+ }
658
+ return new FixedSeriesIterator(this) as Iterator<T>;
659
+ }
660
+
590
661
  slice(start: number, end?: number): Series {
591
662
  if (start <= 0 && (end == null || end >= this.length)) return this;
592
663
  const data = this.data.slice(start, end);
@@ -612,8 +683,187 @@ export class Series {
612
683
  }
613
684
  }
614
685
 
615
- export const addSamples = (a: SampleValue, b: SampleValue): SampleValue => {
686
+ class StringSeriesIterator implements Iterator<string> {
687
+ private readonly series: Series;
688
+ private index: number;
689
+ private readonly decoder: TextDecoder;
690
+
691
+ constructor(series: Series) {
692
+ if (!series.dataType.isVariable)
693
+ throw new Error(
694
+ "cannot create a variable series iterator for a non-variable series",
695
+ );
696
+ this.series = series;
697
+ this.index = 0;
698
+ this.decoder = new TextDecoder();
699
+ }
700
+
701
+ next(): IteratorResult<string> {
702
+ const start = this.index;
703
+ const data = this.series.data;
704
+ while (this.index < data.length && data[this.index] !== 10) this.index++;
705
+ const end = this.index;
706
+ if (start === end) return { done: true, value: undefined };
707
+ this.index++;
708
+ const s = this.decoder.decode(this.series.buffer.slice(start, end));
709
+ return { done: false, value: s };
710
+ }
711
+
712
+ [Symbol.iterator](): Iterator<TelemValue> {
713
+ return this;
714
+ }
715
+ }
716
+
717
+ class JSONSeriesIterator implements Iterator<unknown> {
718
+ private readonly wrapped: Iterator<string>;
719
+
720
+ constructor(wrapped: Iterator<string>) {
721
+ this.wrapped = wrapped;
722
+ }
723
+
724
+ next(): IteratorResult<object> {
725
+ const next = this.wrapped.next();
726
+ if (next.done === true) return { done: true, value: undefined };
727
+ return { done: false, value: JSON.parse(next.value) };
728
+ }
729
+
730
+ [Symbol.iterator](): Iterator<object> {
731
+ return this;
732
+ }
733
+
734
+ [Symbol.toStringTag] = "JSONSeriesIterator";
735
+ }
736
+
737
+ class FixedSeriesIterator implements Iterator<NumericTelemValue> {
738
+ series: Series;
739
+ index: number;
740
+ constructor(series: Series) {
741
+ this.series = series;
742
+ this.index = 0;
743
+ }
744
+
745
+ next(): IteratorResult<NumericTelemValue> {
746
+ if (this.index >= this.series.length) return { done: true, value: undefined };
747
+ return {
748
+ done: false,
749
+ value: this.series.at(this.index++, true) as NumericTelemValue,
750
+ };
751
+ }
752
+
753
+ [Symbol.iterator](): Iterator<NumericTelemValue> {
754
+ return this;
755
+ }
756
+
757
+ [Symbol.toStringTag] = "SeriesIterator";
758
+ }
759
+
760
+ export const addSamples = (
761
+ a: NumericTelemValue,
762
+ b: NumericTelemValue,
763
+ ): NumericTelemValue => {
616
764
  if (typeof a === "bigint" && typeof b === "bigint") return a + b;
617
765
  if (typeof a === "number" && typeof b === "number") return a + b;
766
+ if (b === 0) return a;
767
+ if (a === 0) return b;
618
768
  return Number(a) + Number(b);
619
769
  };
770
+
771
+ export class MultiSeries<T extends TelemValue = TelemValue> implements Iterable<T> {
772
+ readonly series: Array<Series<T>>;
773
+
774
+ constructor(series: Array<Series<T>>) {
775
+ if (series.length !== 0) {
776
+ const type = series[0].dataType;
777
+ for (let i = 1; i < series.length; i++)
778
+ if (!series[i].dataType.equals(type))
779
+ throw new Error("[multi-series] - series must have the same data type");
780
+ }
781
+ this.series = series;
782
+ }
783
+
784
+ as(jsType: "string"): MultiSeries<string>;
785
+
786
+ as(jsType: "number"): MultiSeries<number>;
787
+
788
+ as(jsType: "bigint"): MultiSeries<bigint>;
789
+
790
+ as<T extends TelemValue>(dataType: CrudeDataType): MultiSeries<T> {
791
+ if (!new DataType(dataType).equals(this.dataType))
792
+ throw new Error(
793
+ `cannot convert series of type ${this.dataType.toString()} to ${dataType.toString()}`,
794
+ );
795
+ return this as unknown as MultiSeries<T>;
796
+ }
797
+
798
+ get dataType(): DataType {
799
+ if (this.series.length === 0) return DataType.UNKNOWN;
800
+ return this.series[0].dataType;
801
+ }
802
+
803
+ get timeRange(): TimeRange {
804
+ if (this.series.length === 0) return TimeRange.ZERO;
805
+ return new TimeRange(
806
+ this.series[0].timeRange.start,
807
+ this.series[this.series.length - 1].timeRange.end,
808
+ );
809
+ }
810
+
811
+ push(series: Series<T>): void {
812
+ this.series.push(series);
813
+ }
814
+
815
+ get length(): number {
816
+ return this.series.reduce((a, b) => a + b.length, 0);
817
+ }
818
+
819
+ at(index: number, required: true): T;
820
+
821
+ at(index: number, required?: false): T | undefined;
822
+
823
+ at(index: number, required: boolean = false): T | undefined {
824
+ if (index < 0) index = this.length + index;
825
+ for (const ser of this.series) {
826
+ if (index < ser.length) return ser.at(index, required as true);
827
+ index -= ser.length;
828
+ }
829
+ if (required) throw new Error(`[series] - no value at index ${index}`);
830
+ return undefined;
831
+ }
832
+
833
+ [Symbol.iterator](): Iterator<T> {
834
+ if (this.series.length === 0)
835
+ return {
836
+ next(): IteratorResult<T> {
837
+ return { done: true, value: undefined };
838
+ },
839
+ };
840
+ return new MultiSeriesIterator<T>(this.series);
841
+ }
842
+ }
843
+
844
+ class MultiSeriesIterator<T extends TelemValue = TelemValue> implements Iterator<T> {
845
+ private readonly series: Array<Series<T>>;
846
+ private seriesIndex: number;
847
+ private internal: Iterator<T>;
848
+
849
+ constructor(series: Array<Series<T>>) {
850
+ this.series = series;
851
+ this.seriesIndex = 0;
852
+ this.internal = series[0][Symbol.iterator]();
853
+ }
854
+
855
+ next(): IteratorResult<T> {
856
+ const next = this.internal.next();
857
+ if (next.done === false) return next;
858
+ if (this.seriesIndex === this.series.length - 1)
859
+ return { done: true, value: undefined };
860
+ this.internal = this.series[++this.seriesIndex][Symbol.iterator]();
861
+ return this.next();
862
+ }
863
+
864
+ [Symbol.iterator](): Iterator<TelemValue | unknown> {
865
+ return this;
866
+ }
867
+
868
+ [Symbol.toStringTag] = "MultiSeriesIterator";
869
+ }
@@ -1049,6 +1049,18 @@ export class DataType extends String implements Stringer {
1049
1049
  return this.equals(DataType.JSON) || this.equals(DataType.STRING);
1050
1050
  }
1051
1051
 
1052
+ get isNumeric(): boolean {
1053
+ return !this.isVariable && !this.equals(DataType.UUID);
1054
+ }
1055
+
1056
+ get isInteger(): boolean {
1057
+ return this.toString().startsWith("int");
1058
+ }
1059
+
1060
+ get isFloat(): boolean {
1061
+ return this.toString().startsWith("float");
1062
+ }
1063
+
1052
1064
  get density(): Density {
1053
1065
  const v = DataType.DENSITIES.get(this.toString());
1054
1066
  if (v == null) throw new Error(`unable to find density for ${this.valueOf()}`);
@@ -1390,7 +1402,7 @@ type TypedArrayConstructor =
1390
1402
  | Int16ArrayConstructor
1391
1403
  | Int32ArrayConstructor
1392
1404
  | BigInt64ArrayConstructor;
1393
- type NumericTelemValue = number | bigint;
1405
+ export type NumericTelemValue = number | bigint;
1394
1406
  export type TelemValue =
1395
1407
  | number
1396
1408
  | bigint