@synnaxlabs/x 0.40.0 → 0.41.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 (81) hide show
  1. package/.turbo/turbo-build.log +22 -22
  2. package/dist/{bounds-azUOoVVR.js → bounds-DQrjn60Q.js} +83 -86
  3. package/dist/bounds-M-SZ3X1Z.cjs +1 -0
  4. package/dist/bounds.cjs +1 -1
  5. package/dist/bounds.js +1 -1
  6. package/dist/box-0YrQibkB.cjs +1 -0
  7. package/dist/box-Cc8IzcNo.js +205 -0
  8. package/dist/box.cjs +1 -1
  9. package/dist/box.js +1 -1
  10. package/dist/index.cjs +3 -3
  11. package/dist/index.js +235 -198
  12. package/dist/location-BGl5Ddds.cjs +1 -0
  13. package/dist/{location-BuYbIFHD.js → location-C3aeu046.js} +16 -12
  14. package/dist/location.cjs +1 -1
  15. package/dist/location.js +1 -1
  16. package/dist/{position-DTrNGtrm.cjs → position-Cai5-wi1.cjs} +1 -1
  17. package/dist/{position-DemzGvAY.js → position-DIglP1l2.js} +2 -2
  18. package/dist/position.cjs +1 -1
  19. package/dist/position.js +1 -1
  20. package/dist/{scale-DpJM6__6.cjs → scale-DL9VFGhL.cjs} +1 -1
  21. package/dist/{scale-C0EllH-1.js → scale-DQwBWnwc.js} +4 -4
  22. package/dist/scale.cjs +1 -1
  23. package/dist/scale.js +1 -1
  24. package/dist/series-BMma2b5q.cjs +11 -0
  25. package/dist/{series-CXnO-P0V.js → series-D0zxMWxP.js} +285 -297
  26. package/dist/spatial.cjs +1 -1
  27. package/dist/spatial.js +6 -6
  28. package/dist/src/index.d.ts +1 -0
  29. package/dist/src/index.d.ts.map +1 -1
  30. package/dist/src/math/external.d.ts +3 -0
  31. package/dist/src/math/external.d.ts.map +1 -0
  32. package/dist/src/math/index.d.ts +1 -1
  33. package/dist/src/math/index.d.ts.map +1 -1
  34. package/dist/src/math/round.d.ts +40 -0
  35. package/dist/src/math/round.d.ts.map +1 -0
  36. package/dist/src/math/round.spec.d.ts +2 -0
  37. package/dist/src/math/round.spec.d.ts.map +1 -0
  38. package/dist/src/notation/index.d.ts +2 -0
  39. package/dist/src/notation/index.d.ts.map +1 -0
  40. package/dist/src/notation/notation.d.ts +33 -0
  41. package/dist/src/notation/notation.d.ts.map +1 -0
  42. package/dist/src/notation/notation.spec.d.ts +2 -0
  43. package/dist/src/notation/notation.spec.d.ts.map +1 -0
  44. package/dist/src/spatial/box/box.d.ts +4 -0
  45. package/dist/src/spatial/box/box.d.ts.map +1 -1
  46. package/dist/src/spatial/location/location.d.ts +2 -2
  47. package/dist/src/spatial/xy/xy.d.ts +10 -0
  48. package/dist/src/spatial/xy/xy.d.ts.map +1 -1
  49. package/dist/src/telem/series.d.ts.map +1 -1
  50. package/dist/src/telem/telem.d.ts +2 -0
  51. package/dist/src/telem/telem.d.ts.map +1 -1
  52. package/dist/telem.cjs +1 -1
  53. package/dist/telem.js +1 -1
  54. package/dist/{xy-DyQSETQZ.cjs → xy-B7065J2S.cjs} +1 -1
  55. package/dist/{xy-DHBO1dG_.js → xy-D_LqxaGt.js} +8 -4
  56. package/dist/xy.cjs +1 -1
  57. package/dist/xy.js +1 -1
  58. package/package.json +8 -8
  59. package/src/index.ts +1 -0
  60. package/src/math/external.ts +11 -0
  61. package/src/math/index.ts +1 -1
  62. package/src/math/round.spec.ts +81 -0
  63. package/src/math/round.ts +68 -0
  64. package/src/notation/index.ts +10 -0
  65. package/src/notation/notation.spec.ts +88 -0
  66. package/src/notation/notation.ts +61 -0
  67. package/src/spatial/box/box.spec.ts +3 -3
  68. package/src/spatial/box/box.ts +8 -0
  69. package/src/spatial/location/location.ts +4 -4
  70. package/src/spatial/position/position.spec.ts +10 -10
  71. package/src/spatial/xy/xy.spec.ts +8 -0
  72. package/src/spatial/xy/xy.ts +14 -0
  73. package/src/telem/series.spec.ts +39 -0
  74. package/src/telem/series.ts +15 -27
  75. package/src/telem/telem.ts +8 -0
  76. package/tsconfig.tsbuildinfo +1 -1
  77. package/dist/bounds-Dwq6ZFHm.cjs +0 -1
  78. package/dist/box-Bzya27QS.cjs +0 -1
  79. package/dist/box-DrsrRNSe.js +0 -201
  80. package/dist/location-BgpQ3rN2.cjs +0 -1
  81. package/dist/series-BgoCtU71.cjs +0 -11
@@ -0,0 +1,68 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { bounds } from "@/spatial/bounds";
11
+
12
+ const LARGE_SPAN_DECIMAL_PLACES = 2;
13
+ const MEDIUM_SPAN_DECIMAL_PLACES = 3;
14
+
15
+ // The number of additional decimal places to show past the precision of the span.
16
+ const EXTRA_DECIMAL_PLACES = 2;
17
+
18
+ /**
19
+ * Rounds a number based on the span of the provided bounds. The function adjusts the
20
+ * number of decimal places based on the magnitude of the bounds.
21
+ *
22
+ * @param value - The number to be rounded.
23
+ * @param bounds - The bounds object containing the min and max values that provide
24
+ * context for rounding.
25
+ * @returns The rounded number.
26
+ *
27
+ * Rules for decimal places:
28
+ * - For spans >= 1000: 2 decimal places
29
+ * - For spans >= 1: 3 decimal places
30
+ * - For spans < 1: 2 decimal places + 2 decimal places past the precision of the span
31
+ *
32
+ * Edge cases:
33
+ * - If the value is `NaN`, returns `NaN`
34
+ * - If the value is `Infinity` or `-Infinity`, returns the original value
35
+ * - If the bounds span is 0, returns the original value
36
+ *
37
+ * Examples:
38
+ * ```typescript
39
+ * // Large spans (>= 1000) use 2 decimal places
40
+ * roundBySpan(1234.5678, { start: 0, end: 2000 }); // 1200
41
+ *
42
+ * // Medium spans (>= 1) use 3 decimal places
43
+ * roundBySpan(1.23456, { start: 0, end: 2 }); // 1.235
44
+ *
45
+ * // Small spans (< 1) adapt based on the span
46
+ * roundBySpan(0.123456, { start: 0, end: 0.2 }); // 0.123 = 1 + 2 decimal places
47
+ * roundBySpan(0.0001234, { start: 0, end: 0.001 }); // 0.00012 = 3 + 2 decimal places
48
+ *
49
+ * // Edge cases
50
+ * roundBySpan(NaN, { start: 0, end: 1 }); // NaN
51
+ * roundBySpan(Infinity, { start: 0, end: 1 }); // Infinity
52
+ * roundBySpan(123, { start: 1, end: 1 }); // 123 (span is 0)
53
+ * ```
54
+ */
55
+ export const roundBySpan = (value: number, b: bounds.Bounds<number>): number => {
56
+ if (Number.isNaN(value) || !Number.isFinite(value)) return value;
57
+ const span = bounds.span(b);
58
+ if (span == 0) return value;
59
+ let decimalPlaces: number;
60
+ if (span >= 1000) decimalPlaces = LARGE_SPAN_DECIMAL_PLACES;
61
+ else if (span >= 1) decimalPlaces = MEDIUM_SPAN_DECIMAL_PLACES;
62
+ else {
63
+ const decimalPlacesInSpan = Math.ceil(-Math.log10(span));
64
+ decimalPlaces = decimalPlacesInSpan + EXTRA_DECIMAL_PLACES;
65
+ }
66
+ const multiplier = 10 ** decimalPlaces;
67
+ return Math.round(value * multiplier) / multiplier;
68
+ };
@@ -0,0 +1,10 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ export * as notation from "@/notation/notation";
@@ -0,0 +1,88 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { describe, expect, it } from "vitest";
11
+
12
+ import { notation } from "@/notation";
13
+
14
+ interface TestCase {
15
+ number: number;
16
+ precision: number;
17
+ expected: Record<notation.Notation, string>;
18
+ }
19
+
20
+ const TEST_CASES: TestCase[] = [
21
+ {
22
+ number: 12345.678,
23
+ precision: 1,
24
+ expected: { standard: "12345.7", scientific: "1.2ᴇ4", engineering: "12.3ᴇ3" },
25
+ },
26
+ {
27
+ number: 12345.678,
28
+ precision: 0,
29
+ expected: { standard: "12346", scientific: "1ᴇ4", engineering: "12ᴇ3" },
30
+ },
31
+ {
32
+ number: 0,
33
+ precision: 1,
34
+ expected: { standard: "0.0", scientific: "0.0ᴇ0", engineering: "0.0ᴇ0" },
35
+ },
36
+ {
37
+ number: 0,
38
+ precision: 0,
39
+ expected: { standard: "0", scientific: "0ᴇ0", engineering: "0ᴇ0" },
40
+ },
41
+ {
42
+ number: -1234.5678,
43
+ precision: 1,
44
+ expected: { standard: "-1234.6", scientific: "-1.2ᴇ3", engineering: "-1.2ᴇ3" },
45
+ },
46
+ {
47
+ number: -1234.5678,
48
+ precision: 0,
49
+ expected: { standard: "-1235", scientific: "-1ᴇ3", engineering: "-1ᴇ3" },
50
+ },
51
+ {
52
+ number: NaN,
53
+ precision: 0,
54
+ expected: { standard: "NaN", scientific: "NaN", engineering: "NaN" },
55
+ },
56
+ {
57
+ number: Infinity,
58
+ precision: 0,
59
+ expected: { standard: "∞", scientific: "∞", engineering: "∞" },
60
+ },
61
+ {
62
+ number: -Infinity,
63
+ precision: 0,
64
+ expected: { standard: "-∞", scientific: "-∞", engineering: "-∞" },
65
+ },
66
+ {
67
+ number: 0.0001234,
68
+ precision: 1,
69
+ expected: { standard: "0.0", scientific: "1.2ᴇ-4", engineering: "123.4ᴇ-6" },
70
+ },
71
+ {
72
+ number: 0.0001234,
73
+ precision: 0,
74
+ expected: { standard: "0", scientific: "1ᴇ-4", engineering: "123ᴇ-6" },
75
+ },
76
+ ];
77
+
78
+ describe("stringifyNumber", () => {
79
+ TEST_CASES.forEach(({ number, precision, expected }) =>
80
+ describe(`number: ${number}, precision: ${precision}`, () =>
81
+ notation.NOTATIONS.forEach((n) =>
82
+ it(`should format correctly in ${n} notation`, () => {
83
+ const result = notation.stringifyNumber(number, precision, n);
84
+ expect(result).toBe(expected[n]);
85
+ }),
86
+ )),
87
+ );
88
+ });
@@ -0,0 +1,61 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { z } from "zod";
11
+
12
+ export const NOTATIONS = ["standard", "scientific", "engineering"] as const;
13
+ export const notationZ = z.enum(NOTATIONS);
14
+ export type Notation = z.infer<typeof notationZ>;
15
+
16
+ /**
17
+ * Converts a number to a string representation with a specified precision and notation.
18
+ *
19
+ * @param value - The number to be converted.
20
+ * @param precision - The number of decimal places to include in the output. Must be between 0 and 20.
21
+ * @param notation - The notation to use for the conversion. Can be "standard", "scientific", or "engineering".
22
+ * @returns The string representation of the number.
23
+ *
24
+ * Edge cases:
25
+ * - If the value is `NaN`, returns "NaN".
26
+ * - If the value is `Infinity`, returns "∞".
27
+ * - If the value is `-Infinity`, returns "-∞".
28
+ *
29
+ * Examples:
30
+ *
31
+ * ```typescript
32
+ * stringifyNumber(1234.5678, 2, "standard"); // "1234.57"
33
+ * stringifyNumber(1234.5678, 2, "scientific"); // "1.23ᴇ3"
34
+ * stringifyNumber(1234.5678, 2, "engineering"); // "1.23ᴇ3"
35
+ * stringifyNumber(0.0001234, 4, "standard"); // "0.0001"
36
+ * stringifyNumber(0.0001234, 4, "scientific"); // "1.2340ᴇ-4"
37
+ * stringifyNumber(0.0001234, 4, "engineering"); // "123.4000ᴇ-6"
38
+ * stringifyNumber(NaN, 2, "standard"); // "NaN"
39
+ * stringifyNumber(Infinity, 2, "standard"); // "∞"
40
+ * stringifyNumber(-Infinity, 2, "standard"); // "-∞"
41
+ * ```
42
+ */
43
+ export const stringifyNumber = (
44
+ value: number,
45
+ precision: number,
46
+ notation: Notation,
47
+ ): string => {
48
+ if (Number.isNaN(value)) return "NaN";
49
+ if (value === Infinity) return "∞";
50
+ if (value === -Infinity) return "-∞";
51
+ if (notation === "standard") return value.toFixed(precision);
52
+ if (value === 0) {
53
+ if (precision === 0) return "0ᴇ0";
54
+ return `0.${"0".repeat(precision)}ᴇ0`;
55
+ }
56
+ let exp: number;
57
+ if (notation === "scientific") exp = Math.floor(Math.log10(Math.abs(value)));
58
+ else exp = Math.floor(Math.log10(Math.abs(value)) / 3) * 3;
59
+ const mantissa = value / 10 ** exp;
60
+ return `${mantissa.toFixed(precision)}ᴇ${exp}`;
61
+ };
@@ -7,7 +7,7 @@
7
7
  // License, use of this software will be governed by the Apache License, Version 2.0,
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
- import { describe, expect, it,test } from "vitest";
10
+ import { describe, expect, it, test } from "vitest";
11
11
 
12
12
  import * as box from "@/spatial/box/box";
13
13
  import * as location from "@/spatial/location/location";
@@ -102,8 +102,8 @@ describe("Box", () => {
102
102
  describe("xyLoc", () => {
103
103
  const v: location.XY[] = [
104
104
  location.BOTTOM_CENTER,
105
- location.LEFT_CENTER,
106
- location.RIGHT_CENTER,
105
+ location.CENTER_LEFT,
106
+ location.CENTER_RIGHT,
107
107
  location.TOP_CENTER,
108
108
  location.BOTTOM_LEFT,
109
109
  location.BOTTOM_RIGHT,
@@ -278,12 +278,20 @@ export const signedHeight = (b: Crude): number => {
278
278
 
279
279
  export const topLeft = (b: Crude): xy.XY => xyLoc(b, location.TOP_LEFT);
280
280
 
281
+ export const topCenter = (b: Crude): xy.XY => xyLoc(b, location.TOP_CENTER);
282
+
281
283
  export const topRight = (b: Crude): xy.XY => xyLoc(b, location.TOP_RIGHT);
282
284
 
283
285
  export const bottomLeft = (b: Crude): xy.XY => xyLoc(b, location.BOTTOM_LEFT);
284
286
 
287
+ export const bottomCenter = (b: Crude): xy.XY => xyLoc(b, location.BOTTOM_CENTER);
288
+
285
289
  export const bottomRight = (b: Crude): xy.XY => xyLoc(b, location.BOTTOM_RIGHT);
286
290
 
291
+ export const centerLeft = (b: Crude): xy.XY => xyLoc(b, location.CENTER_LEFT);
292
+
293
+ export const centerRight = (b: Crude): xy.XY => xyLoc(b, location.CENTER_RIGHT);
294
+
287
295
  export const right = (b: Crude): number => loc(b, "right");
288
296
 
289
297
  export const bottom = (b: Crude): number => loc(b, "bottom");
@@ -102,11 +102,11 @@ export const BOTTOM_RIGHT: CornerXY = Object.freeze({ x: "right", y: "bottom" })
102
102
  export const CENTER: XY = Object.freeze({ x: "center", y: "center" });
103
103
  export const TOP_CENTER: XY = Object.freeze({ x: "center", y: "top" });
104
104
  export const BOTTOM_CENTER: XY = Object.freeze({ x: "center", y: "bottom" });
105
- export const RIGHT_CENTER: XY = Object.freeze({ x: "right", y: "center" });
106
- export const LEFT_CENTER: XY = Object.freeze({ x: "left", y: "center" });
105
+ export const CENTER_RIGHT: XY = Object.freeze({ x: "right", y: "center" });
106
+ export const CENTER_LEFT: XY = Object.freeze({ x: "left", y: "center" });
107
107
  export const XY_LOCATIONS: readonly XY[] = Object.freeze([
108
- LEFT_CENTER,
109
- RIGHT_CENTER,
108
+ CENTER_LEFT,
109
+ CENTER_RIGHT,
110
110
  TOP_CENTER,
111
111
  BOTTOM_CENTER,
112
112
  TOP_LEFT,
@@ -24,7 +24,7 @@ describe("position", () => {
24
24
  target: box.construct(45, 55, 10, 10),
25
25
  dialog: box.construct(0, 0, 20, 20),
26
26
  },
27
- location.LEFT_CENTER,
27
+ location.CENTER_LEFT,
28
28
  ];
29
29
 
30
30
  const SPEC_CASE_2: Spec = [
@@ -54,7 +54,7 @@ describe("position", () => {
54
54
  dialog: box.construct(0, 0, 20, 20),
55
55
  initial: "left",
56
56
  },
57
- location.LEFT_CENTER,
57
+ location.CENTER_LEFT,
58
58
  ];
59
59
 
60
60
  const SPEC_CASE_5: Spec = [
@@ -64,7 +64,7 @@ describe("position", () => {
64
64
  dialog: box.construct(0, 0, 20, 20),
65
65
  initial: "right",
66
66
  },
67
- location.RIGHT_CENTER,
67
+ location.CENTER_RIGHT,
68
68
  ];
69
69
 
70
70
  // Target is in bottom right corner
@@ -74,7 +74,7 @@ describe("position", () => {
74
74
  target: box.construct(90, 90, 10, 10),
75
75
  dialog: box.construct(0, 0, 20, 20),
76
76
  },
77
- location.LEFT_CENTER,
77
+ location.CENTER_LEFT,
78
78
  ];
79
79
 
80
80
  // Target is in the top left corner
@@ -186,12 +186,12 @@ describe("position", () => {
186
186
  [location.TOP_CENTER, "start", location.BOTTOM_LEFT],
187
187
  [location.TOP_CENTER, "center", location.BOTTOM_CENTER],
188
188
  [location.TOP_CENTER, "end", location.BOTTOM_RIGHT],
189
- [location.LEFT_CENTER, "start", location.BOTTOM_RIGHT],
190
- [location.LEFT_CENTER, "center", location.RIGHT_CENTER],
191
- [location.LEFT_CENTER, "end", location.TOP_RIGHT],
192
- [location.RIGHT_CENTER, "start", location.BOTTOM_LEFT],
193
- [location.RIGHT_CENTER, "center", location.LEFT_CENTER],
194
- [location.RIGHT_CENTER, "end", location.TOP_LEFT],
189
+ [location.CENTER_LEFT, "start", location.BOTTOM_RIGHT],
190
+ [location.CENTER_LEFT, "center", location.CENTER_RIGHT],
191
+ [location.CENTER_LEFT, "end", location.TOP_RIGHT],
192
+ [location.CENTER_RIGHT, "start", location.BOTTOM_LEFT],
193
+ [location.CENTER_RIGHT, "center", location.CENTER_LEFT],
194
+ [location.CENTER_RIGHT, "end", location.TOP_LEFT],
195
195
  [location.BOTTOM_LEFT, "start", location.TOP_RIGHT],
196
196
  [location.BOTTOM_LEFT, "center", location.TOP_CENTER],
197
197
  [location.BOTTOM_LEFT, "end", location.TOP_LEFT],
@@ -143,4 +143,12 @@ describe("XY", () => {
143
143
  ]);
144
144
  });
145
145
  });
146
+
147
+ describe("swap", () => {
148
+ it("should swap x and y coordinates", () => {
149
+ expect(xy.swap([1, 2])).toEqual({ x: 2, y: 1 });
150
+ expect(xy.swap({ x: 3, y: 4 })).toEqual({ x: 4, y: 3 });
151
+ expect(xy.swap({ width: 5, height: 6 })).toEqual({ x: 6, y: 5 });
152
+ });
153
+ });
146
154
  });
@@ -279,3 +279,17 @@ export const calculateMiters = (path: XY[], offset: number): XY[] => {
279
279
  }
280
280
  return miters;
281
281
  };
282
+
283
+ /**
284
+ * Swaps the x and y coordinates of a point.
285
+ * @param a - The coordinate to swap. Can be provided in any supported format (couple, object, dimensions, etc.)
286
+ * @returns A new XY coordinate with the x and y values swapped.
287
+ * @example
288
+ * swap([1, 2]) // returns { x: 2, y: 1 }
289
+ * swap({ x: 3, y: 4 }) // returns { x: 4, y: 3 }
290
+ * swap({ width: 5, height: 6 }) // returns { x: 6, y: 5 }
291
+ */
292
+ export const swap = (a: Crude): XY => {
293
+ const xy = construct(a);
294
+ return { x: xy.y, y: xy.x };
295
+ };
@@ -1064,4 +1064,43 @@ describe("MultiSeries", () => {
1064
1064
  expect(multi.timeRange).toEqual(new TimeRange(1, 4));
1065
1065
  });
1066
1066
  });
1067
+
1068
+ describe("as", () => {
1069
+ it("should correctly cast a numeric series to number type", () => {
1070
+ const a = new Series(new Float32Array([1, 2, 3]));
1071
+ const b = new Series(new Float32Array([4, 5, 6]));
1072
+ const multi = new MultiSeries([a, b]);
1073
+ const asNum = multi.as("number");
1074
+ expect(asNum.at(0)).toEqual(1);
1075
+ expect(asNum.at(5)).toEqual(6);
1076
+ expect(Array.from(asNum)).toEqual([1, 2, 3, 4, 5, 6]);
1077
+ });
1078
+
1079
+ it("should correctly cast a string series to string type", () => {
1080
+ const a = new Series(["apple", "banana"]);
1081
+ const b = new Series(["carrot", "date"]);
1082
+ const multi = new MultiSeries([a, b]);
1083
+ const asStr = multi.as("string");
1084
+ expect(asStr.at(0)).toEqual("apple");
1085
+ expect(asStr.at(3)).toEqual("date");
1086
+ expect(Array.from(asStr)).toEqual(["apple", "banana", "carrot", "date"]);
1087
+ });
1088
+
1089
+ it("should correctly cast a bigint series to bigint type", () => {
1090
+ const a = new Series([1n, 2n]);
1091
+ const b = new Series([3n, 4n]);
1092
+ const multi = new MultiSeries([a, b]);
1093
+ const asBigInt = multi.as("bigint");
1094
+ expect(asBigInt.at(0)).toEqual(1n);
1095
+ expect(asBigInt.at(3)).toEqual(4n);
1096
+ expect(Array.from(asBigInt)).toEqual([1n, 2n, 3n, 4n]);
1097
+ });
1098
+
1099
+ it("should throw an error when trying to cast to an incompatible type", () => {
1100
+ const a = new Series(new Float32Array([1, 2, 3]));
1101
+ const b = new Series(new Float32Array([4, 5, 6]));
1102
+ const multi = new MultiSeries([a, b]);
1103
+ expect(() => multi.as("string")).toThrow();
1104
+ });
1105
+ });
1067
1106
  });
@@ -126,6 +126,17 @@ const nullArrayZ = z
126
126
 
127
127
  const NEW_LINE = 10;
128
128
 
129
+ type JSType = "string" | "number" | "bigint";
130
+
131
+ const checkAsType = (jsType: JSType, dataType: DataType) => {
132
+ if (jsType === "string" && !dataType.isVariable)
133
+ throw new Error(`cannot convert series of type ${dataType.toString()} to string`);
134
+ if (jsType === "number" && !dataType.isNumeric)
135
+ throw new Error(`cannot convert series of type ${dataType.toString()} to number`);
136
+ if (jsType === "bigint" && !dataType.usesBigInt)
137
+ throw new Error(`cannot convert series of type ${dataType.toString()} to bigint`);
138
+ };
139
+
129
140
  /**
130
141
  * Series is a strongly typed array of telemetry samples backed by an underlying binary
131
142
  * buffer.
@@ -696,28 +707,8 @@ export class Series<T extends TelemValue = TelemValue> {
696
707
  as(jsType: "bigint"): Series<bigint>;
697
708
 
698
709
  as<T extends TelemValue>(jsType: "string" | "number" | "bigint"): Series<T> {
699
- if (jsType === "string") {
700
- if (!this.dataType.equals(DataType.STRING))
701
- throw new Error(
702
- `cannot convert series of type ${this.dataType.toString()} to string`,
703
- );
704
- return this as unknown as Series<T>;
705
- }
706
- if (jsType === "number") {
707
- if (!this.dataType.isNumeric)
708
- throw new Error(
709
- `cannot convert series of type ${this.dataType.toString()} to number`,
710
- );
711
- return this as unknown as Series<T>;
712
- }
713
- if (jsType === "bigint") {
714
- if (!this.dataType.equals(DataType.INT64))
715
- throw new Error(
716
- `cannot convert series of type ${this.dataType.toString()} to bigint`,
717
- );
718
- return this as unknown as Series<T>;
719
- }
720
- throw new Error(`cannot convert series to ${jsType as string}`);
710
+ checkAsType(jsType, this.dataType);
711
+ return this as unknown as Series<T>;
721
712
  }
722
713
 
723
714
  get digest(): SeriesDigest {
@@ -950,11 +941,8 @@ export class MultiSeries<T extends TelemValue = TelemValue> implements Iterable<
950
941
 
951
942
  as(jsType: "bigint"): MultiSeries<bigint>;
952
943
 
953
- as<T extends TelemValue>(dataType: CrudeDataType): MultiSeries<T> {
954
- if (!new DataType(dataType).equals(this.dataType))
955
- throw new Error(
956
- `cannot convert series of type ${this.dataType.toString()} to ${dataType.toString()}`,
957
- );
944
+ as<T extends TelemValue>(jsType: "string" | "number" | "bigint"): MultiSeries<T> {
945
+ checkAsType(jsType, this.dataType);
958
946
  return this as unknown as MultiSeries<T>;
959
947
  }
960
948
 
@@ -360,6 +360,14 @@ export class TimeStamp implements Stringer {
360
360
  return Number(this.valueOf()) / Number(TimeStamp.MILLISECOND.valueOf());
361
361
  }
362
362
 
363
+ get microseconds(): number {
364
+ return Number(this.valueOf()) / Number(TimeStamp.MICROSECOND.valueOf());
365
+ }
366
+
367
+ get nanoseconds(): number {
368
+ return Number(this.valueOf());
369
+ }
370
+
363
371
  /** @returns the integer year that the timestamp corresponds to. */
364
372
  get year(): number {
365
373
  return this.date().getFullYear();