@synnaxlabs/x 0.7.0 → 0.9.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 (66) hide show
  1. package/.turbo/turbo-build.log +8 -7
  2. package/dist/binary/index.d.ts +1 -1
  3. package/dist/change/change.d.ts +4 -2
  4. package/dist/change/index.d.ts +1 -1
  5. package/dist/compare/index.d.ts +1 -1
  6. package/dist/deep/delete.d.ts +1 -1
  7. package/dist/deep/external.d.ts +7 -7
  8. package/dist/deep/index.d.ts +1 -1
  9. package/dist/deep/merge.d.ts +1 -1
  10. package/dist/deep/path.d.ts +37 -0
  11. package/dist/deep/path.spec.d.ts +1 -0
  12. package/dist/destructor.d.ts +1 -0
  13. package/dist/index.d.ts +1 -0
  14. package/dist/kv/index.d.ts +1 -1
  15. package/dist/mock/index.d.ts +1 -1
  16. package/dist/observe/index.d.ts +1 -1
  17. package/dist/observe/observe.d.ts +1 -1
  18. package/dist/runtime/external.d.ts +2 -2
  19. package/dist/runtime/index.d.ts +1 -1
  20. package/dist/shallowCopy.d.ts +1 -0
  21. package/dist/spatial/bounds.d.ts +48 -9
  22. package/dist/spatial/box.d.ts +9 -35
  23. package/dist/spatial/dimensions.d.ts +1 -1
  24. package/dist/spatial/direction.d.ts +1 -1
  25. package/dist/spatial/external.d.ts +8 -8
  26. package/dist/spatial/index.d.ts +1 -1
  27. package/dist/spatial/location.d.ts +1 -1
  28. package/dist/spatial/position.d.ts +1 -1
  29. package/dist/spatial/scale.d.ts +5 -5
  30. package/dist/spatial/spatial.d.ts +1 -1
  31. package/dist/spatial/xy.d.ts +5 -2
  32. package/dist/telem/generate.d.ts +1 -1
  33. package/dist/telem/index.d.ts +3 -3
  34. package/dist/telem/series.d.ts +31 -5
  35. package/dist/telem/telem.d.ts +3 -0
  36. package/dist/toArray.d.ts +1 -0
  37. package/dist/x.cjs.js +289 -60
  38. package/dist/x.cjs.js.map +1 -1
  39. package/dist/x.es.js +289 -60
  40. package/dist/x.es.js.map +1 -1
  41. package/package.json +6 -6
  42. package/src/change/change.ts +13 -11
  43. package/src/deep/delete.ts +1 -1
  44. package/src/deep/external.ts +2 -1
  45. package/src/deep/memo.ts +15 -0
  46. package/src/deep/path.spec.ts +82 -0
  47. package/src/deep/path.ts +95 -0
  48. package/src/destructor.ts +2 -0
  49. package/src/index.ts +1 -0
  50. package/src/observe/observe.ts +2 -2
  51. package/src/shallowCopy.ts +6 -0
  52. package/src/spatial/bounds.spec.ts +218 -0
  53. package/src/spatial/bounds.ts +155 -19
  54. package/src/spatial/box.ts +7 -0
  55. package/src/spatial/dimensions.ts +5 -0
  56. package/src/spatial/direction.ts +8 -8
  57. package/src/spatial/location.ts +11 -0
  58. package/src/spatial/xy.spec.ts +8 -0
  59. package/src/spatial/xy.ts +32 -9
  60. package/src/telem/series.spec.ts +54 -46
  61. package/src/telem/series.ts +110 -57
  62. package/src/telem/telem.spec.ts +17 -1
  63. package/src/telem/telem.ts +33 -0
  64. package/src/toArray.ts +3 -0
  65. package/dist/deep/key.d.ts +0 -30
  66. package/src/deep/key.ts +0 -46
@@ -8,6 +8,7 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  import { type Bounds, bounds, type CrudeBounds } from "@/spatial/base";
11
+ import { resolveObjectURL } from "buffer";
11
12
 
12
13
  export { type Bounds, bounds };
13
14
 
@@ -40,34 +41,60 @@ export const DECIMAL = { lower: 0, upper: 1 };
40
41
 
41
42
  export const CLIP = { lower: -1, upper: 1 };
42
43
 
43
- export const equals = (a?: Bounds, b?: Bounds): boolean =>
44
- a?.lower === b?.lower && a?.upper === b?.upper;
44
+ export const equals = (_a?: Bounds, _b?: Bounds): boolean => {
45
+ if (_a == null && _b == null) return true;
46
+ if (_a == null || _b == null) return false;
47
+ const a = construct(_a);
48
+ const b = construct(_b);
49
+ return a?.lower === b?.lower && a?.upper === b?.upper;
50
+ }
45
51
 
46
52
  export const makeValid = (a: Bounds): Bounds => {
47
53
  if (a.lower > a.upper) return { lower: a.upper, upper: a.lower };
48
54
  return a;
49
55
  };
50
56
 
51
- export const clamp = (bounds: Bounds, target: number): number => {
52
- if (target < bounds.lower) return bounds.lower;
53
- if (target >= bounds.upper) return bounds.upper - 1;
57
+ export const clamp = (bounds: Crude, target: number): number => {
58
+ const _bounds = construct(bounds);
59
+ if (target < _bounds.lower) return _bounds.lower;
60
+ if (target >= _bounds.upper) return _bounds.upper - 1;
54
61
  return target;
55
62
  };
56
63
 
57
- export const contains = (bounds: Bounds, target: number): boolean =>
58
- target >= bounds.lower && target < bounds.upper;
59
-
60
- export const overlapsWith = (a: Bounds, b: Bounds): boolean =>
61
- contains(a, a.lower) || contains(b, b.upper - 1);
62
-
63
- export const span = (a: Bounds): number => a.upper - a.lower;
64
-
65
- export const isZero = (a: Bounds): boolean => a.lower === 0 && a.upper === 0;
66
-
67
- export const spanIsZero = (a: Bounds): boolean => span(a) === 0;
68
-
69
- export const isFinite = (a: Bounds): boolean =>
70
- Number.isFinite(a.lower) && Number.isFinite(a.upper);
64
+ export const contains = (bounds: Crude, target: number | CrudeBounds): boolean => {
65
+ const _bounds = construct(bounds);
66
+ if (typeof target === "number") return target >= _bounds.lower && target < _bounds.upper;
67
+ const _target = construct(target);
68
+ return _target.lower >= _bounds.lower && _target.upper <= _bounds.upper;
69
+ }
70
+
71
+ export const overlapsWith = (a: Crude, b: Crude): boolean => {
72
+ const _a = construct(a);
73
+ const _b = construct(b);
74
+ if (_a.lower ==_b.lower) return true;
75
+ if (_b.upper == _a.lower || _b.lower == _a.upper) return false;
76
+ return contains(_a, _b.upper)
77
+ || contains(_a, _b.lower)
78
+ || contains(_b, _a.upper)
79
+ || contains(_b, _a.lower);
80
+ }
81
+
82
+ export const span = (a: Crude): number => {
83
+ const _a = construct(a);
84
+ return _a.upper - _a.lower;
85
+ }
86
+
87
+ export const isZero = (a: Crude): boolean => {
88
+ const _a = construct(a);
89
+ return _a.lower === 0 && _a.upper === 0;
90
+ }
91
+
92
+ export const spanIsZero = (a: Crude): boolean => span(a) === 0;
93
+
94
+ export const isFinite = (a: Crude): boolean => {
95
+ const _a = construct(a);
96
+ return Number.isFinite(_a.lower) && Number.isFinite(_a.upper);
97
+ }
71
98
 
72
99
  export const max = (bounds: Crude[]): Bounds => ({
73
100
  lower: Math.min(...bounds.map((b) => construct(b).lower)),
@@ -78,3 +105,112 @@ export const min = (bounds: Crude[]): Bounds => ({
78
105
  lower: Math.max(...bounds.map((b) => construct(b).lower)),
79
106
  upper: Math.min(...bounds.map((b) => construct(b).upper)),
80
107
  });
108
+
109
+ export const constructArray = (bounds: Crude): number[] => {
110
+ const _bounds = construct(bounds);
111
+ return Array.from({ length: span(bounds) }, (_, i) => i + _bounds.lower);
112
+ }
113
+
114
+ export const findInsertPosition = (bounds: Crude[], target: number): { index: number, position: number } => {
115
+ const _bounds = bounds.map(construct);
116
+ const index = _bounds.findIndex((b, i) => contains(b, target) || target < _bounds[i].lower);
117
+ if (index === -1) return { index: bounds.length, position: 0 };
118
+ const b = _bounds[index];
119
+ if (contains(b, target)) return { index, position: target - b.lower };
120
+ return {index: index, position: 0};
121
+ }
122
+
123
+
124
+ /**
125
+ * A plan for inserting a new bound into an ordered array of bounds.
126
+ */
127
+ export interface InsertionPlan {
128
+ /** How much to increase the lower bound of the new bound or decrease the upper bound
129
+ * of the previous bound. */
130
+ removeBefore: number;
131
+ /** How much to decrease the upper bound of the new bound or increase the lower bound
132
+ * of the next bound. */
133
+ removeAfter: number;
134
+ /** The index at which to insert the new bound. */
135
+ insertInto: number;
136
+ /** The number of bounds to remove from the array. */
137
+ deleteInBetween: number;
138
+ }
139
+
140
+ const ZERO_PLAN: InsertionPlan = {
141
+ removeBefore: 0,
142
+ removeAfter: 0,
143
+ insertInto: 0,
144
+ deleteInBetween: 0,
145
+ }
146
+
147
+ /**
148
+ * Build a plan for inserting a new bound into an ordered array of bounds. This function
149
+ * is particularly useful for inserting a new array into a sorted array of array of arrays
150
+ * that may overlap. The plan is used to determine how to splice the new array into the
151
+ * existing array. The following are important constraints:
152
+ *
153
+ *
154
+ * 1. If the new bound is entirely contained within an existing bound, the new bound
155
+ * is not inserted and the plan is null.
156
+ *
157
+ * @param bounds - An ordered array of bounds, where each bound is valid (i.e., lower <= upper)
158
+ * and the lower bound of each bound is less than the upper bound of the next bound.
159
+ * @param value - The new bound to insert.
160
+ * @returns A plan for inserting the new bound into the array of bounds, or null if the
161
+ * new bound is entirely contained within an existing bound. See the {@link InsertionPlan}
162
+ * type for more details.
163
+ */
164
+ export const buildInsertionPlan = (bounds: Crude[], value: Crude):InsertionPlan | null => {
165
+ const _bounds = bounds.map(construct);
166
+ const _target = construct(value);
167
+ // No bounds to insert into, so just insert the new bound at the beginning of the array.
168
+ if (_bounds.length === 0) return ZERO_PLAN;
169
+ const lower = findInsertPosition(bounds, _target.lower);
170
+ const upper = findInsertPosition(bounds, _target.upper);
171
+ // Greater than all bounds,
172
+ if (lower.index == bounds.length) return { ...ZERO_PLAN, insertInto: bounds.length };
173
+ // Less than all bounds,
174
+ if (upper.index == 0) return {
175
+ ...ZERO_PLAN,
176
+ removeAfter: upper.position
177
+ }
178
+ if (lower.index === upper.index) {
179
+ // The case where the bound is entirely contained within an existing bound.
180
+ if (lower.position !== 0 && upper.position !== 0)
181
+ return null;
182
+ return {
183
+ removeAfter: upper.position,
184
+ removeBefore: lower.position,
185
+ insertInto: lower.index,
186
+ deleteInBetween: 0,
187
+ }
188
+ }
189
+ let deleteInBetween = (upper.index - lower.index)
190
+ let insertInto = lower.index;
191
+ let removeBefore = span(_bounds[lower.index]) - lower.position;
192
+ // If we're overlapping with the previous bound, we need to slice out one less
193
+ // and insert one further up.
194
+ if (lower.position != 0) {
195
+ deleteInBetween -= 1;
196
+ insertInto += 1;
197
+ // We're not overlapping with the previous bound, so don't need to remove anything
198
+ } else removeBefore = 0;
199
+ return {
200
+ removeBefore,
201
+ removeAfter: upper.position,
202
+ insertInto,
203
+ deleteInBetween,
204
+ }
205
+ }
206
+
207
+
208
+ export const insert = (bounds: Crude[], value: Crude): Crude[] => {
209
+ const plan = buildInsertionPlan(bounds, value);
210
+ if (plan == null) return bounds;
211
+ const _target = construct(value);
212
+ _target.lower += plan.removeBefore;
213
+ _target.upper -= plan.removeAfter;
214
+ bounds.splice(plan.insertInto, plan.deleteInBetween, _target);
215
+ return bounds.map(construct);
216
+ }
@@ -115,6 +115,11 @@ export const construct = (
115
115
  return b;
116
116
  };
117
117
 
118
+ export const resize = (
119
+ b: Box,
120
+ dims: dimensions.Dimensions,
121
+ ): Box => construct(b.one, dims);
122
+
118
123
  /**
119
124
  * Checks if a box contains a point or another box.
120
125
  *
@@ -184,6 +189,8 @@ export const dim = (
184
189
  return signed ? dim : Math.abs(dim);
185
190
  };
186
191
 
192
+
193
+
187
194
  /** @returns the pont corresponding to the given corner of the box. */
188
195
  export const xyLoc = (b: Crude, l: location.XY): xy.XY => {
189
196
  const b_ = construct(b);
@@ -62,3 +62,8 @@ export const min = (crude: Crude[]): Dimensions => ({
62
62
  width: Math.min(...crude.map((c) => construct(c).width)),
63
63
  height: Math.min(...crude.map((c) => construct(c).height)),
64
64
  });
65
+
66
+ export const scale = (ca: Crude, factor: number): Dimensions => {
67
+ const a = construct(ca);
68
+ return { width: a.width * factor, height: a.height * factor };
69
+ }
@@ -32,16 +32,16 @@ export const construct = (c: Crude): Direction => {
32
32
  else return "x";
33
33
  };
34
34
 
35
- export const swap = (direction: Direction): Direction =>
36
- direction === "x" ? "y" : "x";
35
+ export const swap = (direction: CrudeDirection): Direction =>
36
+ construct(direction) === "x" ? "y" : "x";
37
37
 
38
- export const dimension = (direction: Direction): Dimension =>
39
- direction === "x" ? "width" : "height";
38
+ export const dimension = (direction: CrudeDirection): Dimension =>
39
+ construct(direction) === "x" ? "width" : "height";
40
40
 
41
- export const location = (direction: Direction): Location =>
42
- direction === "x" ? "left" : "top";
41
+ export const location = (direction: CrudeDirection): Location =>
42
+ construct(direction) === "x" ? "left" : "top";
43
43
 
44
44
  export const isDirection = (c: unknown): c is Direction => crude.safeParse(c).success;
45
45
 
46
- export const signedDimension = (direction: Direction): SignedDimension =>
47
- direction === "x" ? "signedWidth" : "signedHeight";
46
+ export const signedDimension = (direction: CrudeDirection): SignedDimension =>
47
+ construct(direction) === "x" ? "signedWidth" : "signedHeight";
@@ -52,6 +52,14 @@ const SWAPPED: Record<Location, Location> = {
52
52
  center: "center",
53
53
  };
54
54
 
55
+ const ROTATE_90: Record<Location, Location> = {
56
+ top: "left",
57
+ right: "top",
58
+ bottom: "right",
59
+ left: "bottom",
60
+ center: "center",
61
+ };
62
+
55
63
  export const crude = crudeLocation;
56
64
 
57
65
  export type Crude = CrudeLocation;
@@ -65,6 +73,8 @@ export const construct = (cl: Crude): Location => {
65
73
 
66
74
  export const swap = (cl: Crude): Location => SWAPPED[construct(cl)];
67
75
 
76
+ export const rotate90 = (cl: Crude): Location => ROTATE_90[construct(cl)];
77
+
68
78
  export const direction = (cl: Crude): Direction => {
69
79
  const l = construct(cl);
70
80
  if (l === "top" || l === "bottom") return "y";
@@ -76,6 +86,7 @@ export const corner = z.object({ x: xLocation, y: yLocation });
76
86
 
77
87
  export type XY = z.infer<typeof xy>;
78
88
  export type CornerXY = z.infer<typeof corner>;
89
+ export type CornerXYString = "topLeft" | "topRight" | "bottomLeft" | "bottomRight";
79
90
 
80
91
  export const TOP_LEFT: CornerXY = { x: "left", y: "top" };
81
92
  export const TOP_RIGHT: CornerXY = { x: "right", y: "top" };
@@ -46,6 +46,14 @@ test("translate", () => {
46
46
  expect(p.x).toEqual(6);
47
47
  expect(p.y).toEqual(7);
48
48
  });
49
+
50
+ test("translate multiple", () => {
51
+ let p = xy.construct([1, 2]);
52
+ p = xy.translate(p, [5, 5], [2, 2]);
53
+ expect(p.x).toEqual(8);
54
+ expect(p.y).toEqual(9);
55
+ });
56
+
49
57
  describe("equals", () => {
50
58
  const TESTS: Array<[xy.Crude, xy.Crude, boolean]> = [
51
59
  [[1, 1], { x: 1, y: 1 }, true],
package/src/spatial/xy.ts CHANGED
@@ -25,6 +25,7 @@ export { clientXY, xy, type ClientXY as Client, type XY };
25
25
 
26
26
  /** A crude representation of a {@link XY} coordinate as a zod schema. */
27
27
  export const crudeZ = z.union([
28
+ z.number(),
28
29
  xy,
29
30
  numberCouple,
30
31
  dimensions,
@@ -38,17 +39,19 @@ export type Crude = z.infer<typeof crudeZ>;
38
39
  /**
39
40
  * @constructs XY
40
41
  * @param x - A crude representation of the XY coordinate as a number, number couple,
41
- * dimensions, signed dimensions, or client XY.
42
+ * dimensions, signed dimensions, or mouse event. If it's a mouse event, the clientX and
43
+ * clientY coordinates are preferred over the x and y coordinates.
42
44
  * @param y - If x is a number, the y coordinate. If x is a number and this argument is
43
45
  * not given, the y coordinate is assumed to be the same as the x coordinate.
44
46
  */
45
- export const construct = (x: Crude | number, y?: number): XY => {
47
+ export const construct = (x: Crude, y?: number): XY => {
48
+ // The order in which we execute these checks is very important.
46
49
  if (typeof x === "number") return { x, y: y ?? x };
47
50
  if (Array.isArray(x)) return { x: x[0], y: x[1] };
48
51
  if ("signedWidth" in x) return { x: x.signedWidth, y: x.signedHeight };
49
52
  if ("clientX" in x) return { x: x.clientX, y: x.clientY };
50
53
  if ("width" in x) return { x: x.width, y: x.height };
51
- return { ...x };
54
+ return { x: x.x, y: x.y };
52
55
  };
53
56
 
54
57
  /** An x and y coordinate of zero */
@@ -63,11 +66,12 @@ export const INFINITY = { x: Infinity, y: Infinity };
63
66
  /** An x and y coordinate of NaN */
64
67
  export const NAN = { x: NaN, y: NaN };
65
68
 
66
- /** @returns true if the two XY coordinates are semntically equal. */
67
- export const equals = (a: Crude, b: Crude): boolean => {
69
+ /** @returns true if the two XY coordinates are semantically equal. */
70
+ export const equals = (a: Crude, b: Crude, threshold: number = 0): boolean => {
68
71
  const a_ = construct(a);
69
72
  const b_ = construct(b);
70
- return a_.x === b_.x && a_.y === b_.y;
73
+ if (threshold === 0) return a_.x === b_.x && a_.y === b_.y;
74
+ return Math.abs(a_.x - b_.x) <= threshold && Math.abs(a_.y - b_.y) <= threshold;
71
75
  };
72
76
 
73
77
  /** Is zero is true if the XY coordinate has a semantic x and y value of zero. */
@@ -94,15 +98,28 @@ export const translateY = (c: Crude, y: number): XY => {
94
98
  return { x: p.x, y: p.y + y };
95
99
  };
96
100
 
101
+ type TranslateOverloadOne = (a: Crude, b: Crude, ...cb: Crude[]) => XY;
102
+ type TranslateOverloadTwo = (a: Crude, direction: Direction, value: number) => XY;
103
+
97
104
  /**
98
105
  * @returns the given coordinate translated by an arbitrary number of translation
99
106
  * coordinates.
100
107
  */
101
- export const translate = (a: Crude, b: Crude, ...cb: Crude[]): XY =>
102
- [a, b, ...cb].reduce((p: XY, c) => {
103
- const xy = construct(c);
108
+ export const translate: TranslateOverloadOne & TranslateOverloadTwo = (
109
+ a,
110
+ b,
111
+ v,
112
+ ...cb
113
+ ): XY => {
114
+ if (typeof b === "string" && typeof v === "number") {
115
+ if (b === "x") return translateX(a, v);
116
+ return translateY(a, v);
117
+ }
118
+ return [a, b, v ?? ZERO, ...cb].reduce((p: XY, c) => {
119
+ const xy = construct(c as Crude);
104
120
  return { x: p.x + xy.x, y: p.y + xy.y };
105
121
  }, ZERO);
122
+ };
106
123
 
107
124
  /**
108
125
  * @returns the given coordinate the given direction set to the given value.
@@ -151,6 +168,12 @@ export const isNan = (a: Crude): boolean => {
151
168
  return Number.isNaN(xy.x) || Number.isNaN(xy.y);
152
169
  };
153
170
 
171
+ /** @returns true if both the x and y coordinates of the given coordinate are finite. */
172
+ export const isFinite = (a: Crude): boolean => {
173
+ const xy = construct(a);
174
+ return Number.isFinite(xy.x) && Number.isFinite(xy.y);
175
+ }
176
+
154
177
  /** @returns the coordinate represented as a couple of the form [x, y]. */
155
178
  export const couple = (a: Crude): NumberCouple => {
156
179
  const xy = construct(a);
@@ -17,42 +17,42 @@ import { DataType, Rate, Size, TimeRange, TimeStamp } from "@/telem/telem";
17
17
  describe("Series", () => {
18
18
  describe("construction", () => {
19
19
  test("valid from native", () => {
20
- const a = new Series(new Float32Array([1, 2, 3]));
20
+ const a = new Series({data: new Float32Array([1, 2, 3])});
21
21
  expect(a.dataType.toString()).toBe(DataType.FLOAT32.toString());
22
22
  expect(a.length).toEqual(3);
23
23
  expect(a.byteLength).toEqual(Size.bytes(12));
24
24
  expect(a.byteCap).toEqual(Size.bytes(12));
25
25
  expect(a.cap).toEqual(3);
26
- const b = new Series(new BigInt64Array([BigInt(1)]));
26
+ const b = new Series({data: new BigInt64Array([BigInt(1)])});
27
27
  expect(b.dataType.toString()).toBe(DataType.INT64.toString());
28
- const c = new Series(new BigInt64Array([BigInt(1)]), DataType.TIMESTAMP);
28
+ const c = new Series({data: new BigInt64Array([BigInt(1)]), dataType: DataType.TIMESTAMP});
29
29
  expect(c.dataType.toString()).toBe(DataType.TIMESTAMP.toString());
30
30
  });
31
31
 
32
32
  test("from buffer without data type provided", () => {
33
33
  expect(() => {
34
34
  // eslint-disable-next-line no-new
35
- new Series(new ArrayBuffer(4));
35
+ new Series({data: new ArrayBuffer(4)});
36
36
  }).toThrow();
37
37
  });
38
38
 
39
39
  test("from buffer with data type provided", () => {
40
- const a = new Series(new ArrayBuffer(4), DataType.FLOAT32);
40
+ const a = new Series({data: new ArrayBuffer(4), dataType: DataType.FLOAT32});
41
41
  expect(a.dataType.toString()).toBe(DataType.FLOAT32.toString());
42
42
  });
43
43
 
44
44
  test("with time range", () => {
45
- const a = new Series(
46
- new Float32Array([1, 2, 3]),
47
- DataType.FLOAT32,
48
- new TimeRange(1, 2),
49
- );
45
+ const a = new Series({
46
+ data: new Float32Array([1, 2, 3]),
47
+ dataType: DataType.FLOAT32,
48
+ timeRange: new TimeRange(1, 2),
49
+ });
50
50
  expect(a.timeRange.span.valueOf()).toBe(1);
51
51
  });
52
52
 
53
53
  describe("allocation", () => {
54
54
  it("should allocate a lazy array", () => {
55
- const arr = Series.alloc(10, DataType.FLOAT32);
55
+ const arr = Series.alloc({length: 10, dataType: DataType.FLOAT32});
56
56
  expect(arr.byteCap).toEqual(Size.bytes(40));
57
57
  expect(arr.cap).toEqual(10);
58
58
  expect(arr.length).toEqual(0);
@@ -60,33 +60,32 @@ describe("Series", () => {
60
60
  });
61
61
  it("should throw an error when attempting to allocate an array of lenght 0", () => {
62
62
  expect(() => {
63
- Series.alloc(0, DataType.FLOAT32);
63
+ Series.alloc({length: 0, dataType: DataType.FLOAT32});
64
64
  }).toThrow();
65
65
  });
66
66
  });
67
67
  });
68
68
 
69
- test("at", () => {
69
+ describe("at", () => {
70
70
  it("should return the value at the given index and add the sample offset", () => {
71
- const arr = new Series(
72
- new Float32Array([1, 2, 3]),
73
- DataType.FLOAT32,
74
- undefined,
75
- 2,
76
- );
71
+ const arr = new Series({
72
+ data: new Float32Array([1, 2, 3]),
73
+ dataType: DataType.FLOAT32,
74
+ sampleOffset: 2,
75
+ });
77
76
  expect(arr.at(0)).toEqual(3);
78
77
  expect(arr.at(1)).toEqual(4);
79
78
  expect(arr.at(2)).toEqual(5);
80
79
  });
81
80
  it("should return undefined when the index is out of bounds", () => {
82
- const arr = new Series(new Float32Array([1, 2, 3]), DataType.FLOAT32);
81
+ const arr = new Series({data: new Float32Array([1, 2, 3]), dataType: DataType.FLOAT32});
83
82
  expect(arr.at(3)).toBeUndefined();
84
83
  });
85
84
  });
86
85
 
87
86
  describe("slice", () => {
88
87
  it("should slice a lazy array", () => {
89
- const a = new Series(new Float32Array([1, 2, 3]), DataType.FLOAT32);
88
+ const a = new Series({data: new Float32Array([1, 2, 3]), dataType: DataType.FLOAT32});
90
89
  const b = a.slice(1, 2);
91
90
  expect(b.dataType.toString()).toBe(DataType.FLOAT32.toString());
92
91
  expect(b.data).toEqual(new Float32Array([2]));
@@ -99,12 +98,12 @@ describe("Series", () => {
99
98
 
100
99
  describe("min and max", () => {
101
100
  it("should return a min and max of zero on an allocated array", () => {
102
- const arr = Series.alloc(10, DataType.FLOAT32);
101
+ const arr = Series.alloc({length: 10, dataType: DataType.FLOAT32});
103
102
  expect(arr.max).toEqual(-Infinity);
104
103
  expect(arr.min).toEqual(Infinity);
105
104
  });
106
105
  it("should correctly calculate the min and max of a lazy array", () => {
107
- const arr = new Series(new Float32Array([1, 2, 3]), DataType.FLOAT32);
106
+ const arr = new Series({data: new Float32Array([1, 2, 3]), dataType: DataType.FLOAT32});
108
107
  expect(arr.max).toEqual(3);
109
108
  expect(arr.min).toEqual(1);
110
109
  });
@@ -112,21 +111,21 @@ describe("Series", () => {
112
111
 
113
112
  describe("conversion", () => {
114
113
  test("from float64 to float32", () => {
115
- const a = new Series(new Float64Array([1, 2, 3]), DataType.FLOAT64);
114
+ const a = new Series({data: new Float64Array([1, 2, 3]), dataType: DataType.FLOAT64});
116
115
  const b = a.convert(DataType.FLOAT32);
117
116
  expect(b.dataType.toString()).toBe(DataType.FLOAT32.toString());
118
117
  expect(b.data).toEqual(new Float32Array([1, 2, 3]));
119
118
  });
120
119
 
121
120
  test("from int64 to int32", () => {
122
- const a = new Series(new BigInt64Array([BigInt(1), BigInt(2), BigInt(3)]));
121
+ const a = new Series({data: new BigInt64Array([BigInt(1), BigInt(2), BigInt(3)])});
123
122
  const b = a.convert(DataType.INT32);
124
123
  expect(b.dataType.toString()).toBe(DataType.INT32.toString());
125
124
  expect(b.data).toEqual(new Int32Array([1, 2, 3]));
126
125
  });
127
126
 
128
127
  test("from float32 to int64", () => {
129
- const a = new Series(new Float32Array([1, 2, 3]), DataType.FLOAT32);
128
+ const a = new Series({data: new Float32Array([1, 2, 3]), dataType: DataType.FLOAT32});
130
129
  const b = a.convert(DataType.INT64);
131
130
  expect(b.dataType.toString()).toBe(DataType.INT64.toString());
132
131
  expect(b.data).toEqual(new BigInt64Array([BigInt(1), BigInt(2), BigInt(3)]));
@@ -135,35 +134,35 @@ describe("Series", () => {
135
134
 
136
135
  describe("writing", () => {
137
136
  it("should correctly write to an allocated lazy array", () => {
138
- const arr = Series.alloc(10, DataType.FLOAT32);
137
+ const arr = Series.alloc({length: 10, dataType: DataType.FLOAT32});
139
138
  expect(arr.byteCap).toEqual(Size.bytes(40));
140
139
  expect(arr.length).toEqual(0);
141
- const writeOne = new Series(new Float32Array([1]));
140
+ const writeOne = new Series({data:new Float32Array([1])});
142
141
  expect(arr.write(writeOne)).toEqual(1);
143
142
  expect(arr.length).toEqual(1);
144
- const writeTwo = new Series(new Float32Array([2, 3]));
143
+ const writeTwo = new Series({data: new Float32Array([2, 3])});
145
144
  expect(arr.write(writeTwo)).toEqual(2);
146
145
  expect(arr.length).toEqual(3);
147
146
  });
148
147
  it("should recompute cached max and min correctly", () => {
149
- const arr = Series.alloc(10, DataType.FLOAT32);
148
+ const arr = Series.alloc({length: 10, dataType: DataType.FLOAT32});
150
149
  arr.enrich();
151
- const writeTwo = new Series(new Float32Array([2, 3]));
150
+ const writeTwo = new Series({data: new Float32Array([2, 3])});
152
151
  arr.write(writeTwo);
153
152
  expect(arr.max).toEqual(3);
154
153
  expect(arr.min).toEqual(2);
155
154
  });
156
155
  it("should correctly adjust the sample offset of a written array", () => {
157
- const arr = Series.alloc(2, DataType.FLOAT32, TimeRange.ZERO, -3);
158
- const writeOne = new Series(new Float32Array([-2]));
156
+ const arr = Series.alloc({length: 2, dataType: DataType.FLOAT32, timeRange: TimeRange.ZERO, sampleOffset: -3});
157
+ const writeOne = new Series({data: new Float32Array([-2])});
159
158
  expect(arr.write(writeOne)).toEqual(1);
160
159
  expect(arr.min).toEqual(-5);
161
- const writeTwo = new Series(
162
- new Float32Array([1]),
163
- DataType.FLOAT32,
164
- TimeRange.ZERO,
165
- -1,
166
- );
160
+ const writeTwo = new Series({
161
+ data: new Float32Array([1]),
162
+ dataType: DataType.FLOAT32,
163
+ timeRange: TimeRange.ZERO,
164
+ sampleOffset: -1,
165
+ });
167
166
  expect(arr.write(writeTwo)).toEqual(1);
168
167
  expect(arr.min).toEqual(-5);
169
168
  expect(arr.max).toEqual(-2);
@@ -193,7 +192,7 @@ describe("Series", () => {
193
192
 
194
193
  describe("webgl buffering", () => {
195
194
  it("should correctly buffer a new lazy array", () => {
196
- const arr = new Series(new Float32Array([1, 2, 3]), DataType.FLOAT32);
195
+ const arr = new Series({data: new Float32Array([1, 2, 3]), dataType: DataType.FLOAT32});
197
196
  const controller = new MockGLBufferController();
198
197
  arr.updateGLBuffer(controller);
199
198
  expect(controller.createBufferMock).toHaveBeenCalledTimes(1);
@@ -206,7 +205,7 @@ describe("Series", () => {
206
205
  expect(buf).toEqual(new Float32Array([1, 2, 3]));
207
206
  });
208
207
  it("should correctly update a buffer when writing to an allocated array", () => {
209
- const arr = Series.alloc(10, DataType.FLOAT32);
208
+ const arr = Series.alloc({length: 10, dataType: DataType.FLOAT32});
210
209
  const controller = new MockGLBufferController();
211
210
  arr.updateGLBuffer(controller);
212
211
  expect(controller.createBufferMock).toHaveBeenCalledTimes(1);
@@ -216,7 +215,7 @@ describe("Series", () => {
216
215
  let buf = controller.buffers[arr.glBuffer as number];
217
216
  expect(buf).toBeDefined();
218
217
  expect(buf.byteLength).toEqual(0);
219
- const writeOne = new Series(new Float32Array([1]));
218
+ const writeOne = new Series({data: new Float32Array([1])});
220
219
  arr.write(writeOne);
221
220
  arr.updateGLBuffer(controller);
222
221
  expect(controller.bufferDataMock).toHaveBeenCalledTimes(1);
@@ -224,7 +223,7 @@ describe("Series", () => {
224
223
  buf = controller.buffers[arr.glBuffer as number];
225
224
  expect(buf.byteLength).toEqual(arr.byteCap.valueOf());
226
225
  expect(new Float32Array(buf)[0]).toEqual(1);
227
- const writeTwo = new Series(new Float32Array([2, 3]));
226
+ const writeTwo = new Series({data: new Float32Array([2, 3])});
228
227
  arr.write(writeTwo);
229
228
  arr.updateGLBuffer(controller);
230
229
  expect(controller.bufferDataMock).not.toHaveBeenCalledTimes(2);
@@ -239,7 +238,7 @@ describe("Series", () => {
239
238
 
240
239
  describe("acquire", () => {
241
240
  it("should increase the reference count and buffer gl data", () => {
242
- const s = new Series(new Float32Array([1, 2, 3]));
241
+ const s = new Series({data: new Float32Array([1, 2, 3])});
243
242
  expect(s.refCount).toEqual(0);
244
243
  const control = new MockGLBufferController();
245
244
  s.acquire(control);
@@ -259,7 +258,7 @@ describe("Series", () => {
259
258
  expect(outStrings).toEqual(["apple", "banana", "carrot"]);
260
259
  });
261
260
  it("should throw an error if the series is not of type string", () => {
262
- const s = new Series(new Float32Array([1, 2, 3]));
261
+ const s = new Series({data: new Float32Array([1, 2, 3])});
263
262
  expect(() => {
264
263
  s.toStrings();
265
264
  }).toThrow();
@@ -278,7 +277,6 @@ describe("Series", () => {
278
277
  { a: 3, b: "carrot" },
279
278
  ]);
280
279
  const outJSON = s.parseJSON(schema);
281
- print(outJSON);
282
280
  expect(outJSON).toEqual([
283
281
  { a: 1, b: "apple" },
284
282
  { a: 2, b: "banana" },
@@ -286,4 +284,14 @@ describe("Series", () => {
286
284
  ]);
287
285
  });
288
286
  });
287
+
288
+ describe("binarySearch", () => {
289
+ it("should correctly binary search a pre-allocated array", () => {
290
+ const arr = Series.alloc({length: 10, dataType: DataType.FLOAT32});
291
+ const writeOne = new Series({data: new Float32Array([1, 2, 3, 4, 5])});
292
+ arr.write(writeOne);
293
+ expect(arr.binarySearch(3)).toEqual(2);
294
+ expect(arr.binarySearch(6)).toEqual(5);
295
+ })
296
+ })
289
297
  });