@synnaxlabs/x 0.32.0 → 0.34.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 (124) hide show
  1. package/.turbo/turbo-build.log +32 -32
  2. package/dist/binary.cjs +1 -1
  3. package/dist/binary.js +1 -1
  4. package/dist/bounds-CFI9wDXn.js +171 -0
  5. package/dist/bounds-DzCDHgdE.cjs +1 -0
  6. package/dist/bounds.cjs +1 -1
  7. package/dist/bounds.js +1 -1
  8. package/dist/box-DVCNGsJG.js +201 -0
  9. package/dist/box-Mf8E1Ypp.cjs +1 -0
  10. package/dist/box.cjs +1 -1
  11. package/dist/box.js +1 -1
  12. package/dist/caseconv.cjs +1 -1
  13. package/dist/caseconv.js +1 -1
  14. package/dist/compare.js +1 -1
  15. package/dist/deep.cjs +1 -1
  16. package/dist/deep.js +46 -48
  17. package/dist/{direction-DZbN47uL.cjs → direction-D7qoo_GJ.cjs} +1 -1
  18. package/dist/direction.cjs +1 -1
  19. package/dist/external-B3XSLDq5.cjs +1 -0
  20. package/dist/{external-CO221aaF.js → external-sVtvYJS6.js} +3 -3
  21. package/dist/{index-B5THJ1eb.js → index-BBa2mWG1.js} +1 -1
  22. package/dist/{index-DgaYJC35.cjs → index-CYxQwEdX.cjs} +1 -1
  23. package/dist/{index-B3BUDIdi.js → index-HQonyH7n.js} +1 -2
  24. package/dist/index-YsO0EMN8.cjs +1 -0
  25. package/dist/{index-Duv1uH08.js → index-eue4dSQX.js} +14 -10
  26. package/dist/index.cjs +2 -2
  27. package/dist/index.js +162 -145
  28. package/dist/{location-DjcaXEps.js → location-CI9x53qR.js} +8 -8
  29. package/dist/{location-gPB1RtfA.cjs → location-DetomF8Z.cjs} +1 -1
  30. package/dist/location.cjs +1 -1
  31. package/dist/location.js +1 -1
  32. package/dist/path-BBCx3K6k.cjs +1 -0
  33. package/dist/{path-B-1-i3qC.js → path-CmnoH3RC.js} +26 -26
  34. package/dist/{position-DkON65EZ.js → position-CFc9RsSn.js} +2 -2
  35. package/dist/{position-C71OiHiw.cjs → position-DKhPhvPh.cjs} +1 -1
  36. package/dist/position.cjs +1 -1
  37. package/dist/position.js +1 -1
  38. package/dist/runtime.cjs +1 -1
  39. package/dist/runtime.js +1 -1
  40. package/dist/{scale-COPgp55a.cjs → scale-CT61XD_X.cjs} +1 -1
  41. package/dist/{scale-qw6vRO4s.js → scale-DNQE1LMm.js} +25 -3
  42. package/dist/scale.cjs +1 -1
  43. package/dist/scale.js +1 -1
  44. package/dist/series-DWLXo7J6.cjs +11 -0
  45. package/dist/{series-B5eA90Ci.js → series-sjWkW8qe.js} +627 -466
  46. package/dist/spatial.cjs +1 -1
  47. package/dist/spatial.js +5 -5
  48. package/dist/src/binary/encoder.d.ts.map +1 -1
  49. package/dist/src/caseconv/caseconv.d.ts.map +1 -1
  50. package/dist/src/clamp/clamp.d.ts.map +1 -1
  51. package/dist/src/compare/compare.d.ts.map +1 -1
  52. package/dist/src/debounce/debounce.d.ts.map +1 -1
  53. package/dist/src/deep/difference.d.ts.map +1 -1
  54. package/dist/src/deep/merge.d.ts.map +1 -1
  55. package/dist/src/deep/path.d.ts +2 -2
  56. package/dist/src/deep/path.d.ts.map +1 -1
  57. package/dist/src/math/math.d.ts +26 -6
  58. package/dist/src/math/math.d.ts.map +1 -1
  59. package/dist/src/math/math.spec.d.ts +2 -0
  60. package/dist/src/math/math.spec.d.ts.map +1 -0
  61. package/dist/src/migrate/migrate.d.ts.map +1 -1
  62. package/dist/src/record.d.ts +4 -0
  63. package/dist/src/record.d.ts.map +1 -1
  64. package/dist/src/spatial/bounds/bounds.d.ts +208 -4
  65. package/dist/src/spatial/bounds/bounds.d.ts.map +1 -1
  66. package/dist/src/spatial/box/box.d.ts +4 -4
  67. package/dist/src/spatial/box/box.d.ts.map +1 -1
  68. package/dist/src/spatial/scale/scale.d.ts +177 -5
  69. package/dist/src/spatial/scale/scale.d.ts.map +1 -1
  70. package/dist/src/strings/strings.d.ts +14 -0
  71. package/dist/src/strings/strings.d.ts.map +1 -1
  72. package/dist/src/telem/generate.d.ts.map +1 -1
  73. package/dist/src/telem/series.d.ts +35 -10
  74. package/dist/src/telem/series.d.ts.map +1 -1
  75. package/dist/src/telem/telem.d.ts +12 -10
  76. package/dist/src/telem/telem.d.ts.map +1 -1
  77. package/dist/src/url/url.d.ts.map +1 -1
  78. package/dist/telem.cjs +1 -1
  79. package/dist/telem.js +1 -1
  80. package/dist/url.cjs +1 -1
  81. package/dist/url.js +1 -1
  82. package/dist/{zodutil-BWvwKcpb.cjs → zodutil-C6RYzvXd.cjs} +1 -1
  83. package/dist/{zodutil-qNM8aVYC.js → zodutil-Tmuc4CNq.js} +1 -1
  84. package/dist/zodutil.cjs +1 -1
  85. package/dist/zodutil.js +1 -1
  86. package/package.json +10 -9
  87. package/src/binary/encoder.ts +1 -2
  88. package/src/caseconv/caseconv.ts +8 -15
  89. package/src/clamp/clamp.ts +1 -1
  90. package/src/compare/compare.ts +1 -3
  91. package/src/debounce/debounce.ts +1 -2
  92. package/src/deep/difference.ts +3 -9
  93. package/src/deep/merge.ts +8 -15
  94. package/src/deep/path.spec.ts +1 -1
  95. package/src/deep/path.ts +4 -4
  96. package/src/math/math.spec.ts +149 -0
  97. package/src/math/math.ts +61 -10
  98. package/src/migrate/migrate.ts +4 -5
  99. package/src/record.ts +5 -0
  100. package/src/runtime/os.ts +2 -2
  101. package/src/spatial/bounds/bounds.spec.ts +135 -270
  102. package/src/spatial/bounds/bounds.ts +296 -29
  103. package/src/spatial/box/box.ts +13 -12
  104. package/src/spatial/direction/direction.ts +1 -1
  105. package/src/spatial/location/location.ts +5 -5
  106. package/src/spatial/scale/scale.ts +196 -12
  107. package/src/strings/strings.spec.ts +33 -1
  108. package/src/strings/strings.ts +52 -0
  109. package/src/telem/generate.ts +1 -3
  110. package/src/telem/series.spec.ts +235 -0
  111. package/src/telem/series.ts +310 -100
  112. package/src/telem/telem.spec.ts +27 -11
  113. package/src/telem/telem.ts +56 -36
  114. package/src/url/url.ts +9 -12
  115. package/src/zodutil/zodutil.spec.ts +5 -7
  116. package/tsconfig.tsbuildinfo +1 -1
  117. package/dist/bounds-CpboA0q6.js +0 -127
  118. package/dist/bounds-ZZc1c-_Z.cjs +0 -1
  119. package/dist/box-BQID-0jO.cjs +0 -1
  120. package/dist/box-xRqO6NvI.js +0 -202
  121. package/dist/external-B-DoBvh7.cjs +0 -1
  122. package/dist/index-xk130iQA.cjs +0 -1
  123. package/dist/path-577Fmn5N.cjs +0 -1
  124. package/dist/series-CJ65b1Uz.cjs +0 -11
@@ -13,6 +13,7 @@ import { binary } from "@/binary";
13
13
  import { caseconv } from "@/caseconv";
14
14
  import { compare } from "@/compare";
15
15
  import { id } from "@/id";
16
+ import { type math } from "@/math";
16
17
  import { bounds } from "@/spatial";
17
18
  import {
18
19
  type GLBufferController,
@@ -25,7 +26,6 @@ import {
25
26
  type CrudeTimeStamp,
26
27
  DataType,
27
28
  isTelemValue,
28
- type NumericTelemValue,
29
29
  type Rate,
30
30
  Size,
31
31
  type TelemValue,
@@ -43,10 +43,12 @@ interface GL {
43
43
  bufferUsage: GLBufferUsage;
44
44
  }
45
45
 
46
+ export interface IterableIterator<T> extends Iterator<T>, Iterable<T> {}
47
+
46
48
  export interface SeriesDigest {
47
49
  key: string;
48
50
  dataType: string;
49
- sampleOffset: NumericTelemValue;
51
+ sampleOffset: math.Numeric;
50
52
  alignment: bounds.Bounds<bigint>;
51
53
  timeRange?: string;
52
54
  length: number;
@@ -56,7 +58,7 @@ export interface SeriesDigest {
56
58
  interface BaseSeriesProps {
57
59
  dataType?: CrudeDataType;
58
60
  timeRange?: TimeRange;
59
- sampleOffset?: NumericTelemValue;
61
+ sampleOffset?: math.Numeric;
60
62
  glBufferUsage?: GLBufferUsage;
61
63
  alignment?: bigint;
62
64
  key?: string;
@@ -101,6 +103,11 @@ export interface SeriesMemInfo {
101
103
  glBuffer: boolean;
102
104
  }
103
105
 
106
+ const noopIterableIterator: IterableIterator<never> = {
107
+ [Symbol.iterator]: () => noopIterableIterator,
108
+ next: () => ({ done: true, value: undefined }),
109
+ };
110
+
104
111
  const stringArrayZ = z.string().transform(
105
112
  (s) =>
106
113
  new Uint8Array(
@@ -114,6 +121,8 @@ const nullArrayZ = z
114
121
  .union([z.null(), z.undefined()])
115
122
  .transform(() => new Uint8Array().buffer as ArrayBuffer);
116
123
 
124
+ const NEW_LINE = 10;
125
+
117
126
  /**
118
127
  * Series is a strongly typed array of telemetry samples backed by an underlying binary
119
128
  * buffer.
@@ -128,7 +137,7 @@ export class Series<T extends TelemValue = TelemValue> {
128
137
  * downwards. Typically used to convert arrays to lower precision while preserving
129
138
  * the relative range of actual values.
130
139
  */
131
- sampleOffset: NumericTelemValue;
140
+ sampleOffset: math.Numeric;
132
141
  /**
133
142
  * Stores information about the buffer state of this array into a WebGL buffer.
134
143
  */
@@ -138,14 +147,17 @@ export class Series<T extends TelemValue = TelemValue> {
138
147
  readonly _timeRange?: TimeRange;
139
148
  readonly alignment: bigint = 0n;
140
149
  /** A cached minimum value. */
141
- private _cachedMin?: NumericTelemValue;
150
+ private _cachedMin?: math.Numeric;
142
151
  /** A cached maximum value. */
143
- private _cachedMax?: NumericTelemValue;
152
+ private _cachedMax?: math.Numeric;
144
153
  /** The write position of the buffer. */
145
154
  private writePos: number = FULL_BUFFER;
146
155
  /** Tracks the number of entities currently using this array. */
147
156
  private _refCount: number = 0;
157
+ /** Caches the length of the array for variable length data types. */
148
158
  private _cachedLength?: number;
159
+ /** Caches the indexes of the array for variable length data types. */
160
+ private _cachedIndexes?: number[];
149
161
 
150
162
  static readonly crudeZ = z.object({
151
163
  timeRange: TimeRange.z.optional(),
@@ -193,37 +205,35 @@ export class Series<T extends TelemValue = TelemValue> {
193
205
  const isArray = Array.isArray(data);
194
206
 
195
207
  if (dataType != null) this.dataType = new DataType(dataType);
196
- else {
197
- if (data instanceof ArrayBuffer)
198
- throw new Error(
199
- "cannot infer data type from an ArrayBuffer instance when constructing a Series. Please provide a data type.",
200
- );
201
- else if (isArray || isSingle) {
202
- let first: TelemValue | unknown = data as TelemValue;
203
- if (!isSingle) {
204
- if (data.length === 0)
205
- throw new Error(
206
- "cannot infer data type from a zero length JS array when constructing a Series. Please provide a data type.",
207
- );
208
- first = data[0];
209
- }
210
- if (typeof first === "string") this.dataType = DataType.STRING;
211
- else if (typeof first === "number") this.dataType = DataType.FLOAT64;
212
- else if (typeof first === "bigint") this.dataType = DataType.INT64;
213
- else if (typeof first === "boolean") this.dataType = DataType.BOOLEAN;
214
- else if (
215
- first instanceof TimeStamp ||
216
- first instanceof Date ||
217
- first instanceof TimeStamp
218
- )
219
- this.dataType = DataType.TIMESTAMP;
220
- else if (typeof first === "object") this.dataType = DataType.JSON;
221
- else
208
+ else if (data instanceof ArrayBuffer)
209
+ throw new Error(
210
+ "cannot infer data type from an ArrayBuffer instance when constructing a Series. Please provide a data type.",
211
+ );
212
+ else if (isArray || isSingle) {
213
+ let first: TelemValue | unknown = data as TelemValue;
214
+ if (!isSingle) {
215
+ if (data.length === 0)
222
216
  throw new Error(
223
- `cannot infer data type of ${typeof first} when constructing a Series from a JS array`,
217
+ "cannot infer data type from a zero length JS array when constructing a Series. Please provide a data type.",
224
218
  );
225
- } else this.dataType = new DataType(data);
226
- }
219
+ first = data[0];
220
+ }
221
+ if (typeof first === "string") this.dataType = DataType.STRING;
222
+ else if (typeof first === "number") this.dataType = DataType.FLOAT64;
223
+ else if (typeof first === "bigint") this.dataType = DataType.INT64;
224
+ else if (typeof first === "boolean") this.dataType = DataType.BOOLEAN;
225
+ else if (
226
+ first instanceof TimeStamp ||
227
+ first instanceof Date ||
228
+ first instanceof TimeStamp
229
+ )
230
+ this.dataType = DataType.TIMESTAMP;
231
+ else if (typeof first === "object") this.dataType = DataType.JSON;
232
+ else
233
+ throw new Error(
234
+ `cannot infer data type of ${typeof first} when constructing a Series from a JS array`,
235
+ );
236
+ } else this.dataType = new DataType(data);
227
237
 
228
238
  if (!isArray && !isSingle) this._data = data;
229
239
  else {
@@ -237,11 +247,11 @@ export class Series<T extends TelemValue = TelemValue> {
237
247
  data_ = data_.map((v) => new TimeStamp(v as CrudeTimeStamp).valueOf());
238
248
  if (this.dataType.equals(DataType.STRING)) {
239
249
  this._cachedLength = data_.length;
240
- this._data = new TextEncoder().encode(data_.join("\n") + "\n");
250
+ this._data = new TextEncoder().encode(`${data_.join("\n")}\n`);
241
251
  } else if (this.dataType.equals(DataType.JSON)) {
242
252
  this._cachedLength = data_.length;
243
253
  this._data = new TextEncoder().encode(
244
- data_.map((d) => binary.JSON_CODEC.encodeString(d)).join("\n") + "\n",
254
+ `${data_.map((d) => binary.JSON_CODEC.encodeString(d)).join("\n")}\n`,
245
255
  );
246
256
  } else this._data = new this.dataType.Array(data_ as number[] & bigint[]).buffer;
247
257
  }
@@ -274,9 +284,8 @@ export class Series<T extends TelemValue = TelemValue> {
274
284
  static generateTimestamps(length: number, rate: Rate, start: TimeStamp): Series {
275
285
  const timeRange = start.spanRange(rate.span(length));
276
286
  const data = new BigInt64Array(length);
277
- for (let i = 0; i < length; i++) {
287
+ for (let i = 0; i < length; i++)
278
288
  data[i] = BigInt(start.add(rate.span(i)).valueOf());
279
- }
280
289
  return new Series({ data, dataType: DataType.TIMESTAMP, timeRange });
281
290
  }
282
291
 
@@ -285,13 +294,13 @@ export class Series<T extends TelemValue = TelemValue> {
285
294
  }
286
295
 
287
296
  static fromStrings(data: string[], timeRange?: TimeRange): Series {
288
- const buffer = new TextEncoder().encode(data.join("\n") + "\n");
297
+ const buffer = new TextEncoder().encode(`${data.join("\n")}\n`);
289
298
  return new Series({ data: buffer, dataType: DataType.STRING, timeRange });
290
299
  }
291
300
 
292
301
  static fromJSON<T>(data: T[], timeRange?: TimeRange): Series {
293
302
  const buffer = new TextEncoder().encode(
294
- data.map((d) => binary.JSON_CODEC.encodeString(d)).join("\n") + "\n",
303
+ `${data.map((d) => binary.JSON_CODEC.encodeString(d)).join("\n")}\n`,
295
304
  );
296
305
  return new Series({ data: buffer, dataType: DataType.JSON, timeRange });
297
306
  }
@@ -320,25 +329,46 @@ export class Series<T extends TelemValue = TelemValue> {
320
329
  write(other: Series): number {
321
330
  if (!other.dataType.equals(this.dataType))
322
331
  throw new Error("buffer must be of the same type as this array");
332
+ if (this.dataType.isVariable) return this.writeVariable(other);
333
+ return this.writeFixed(other);
334
+ }
335
+
336
+ private writeVariable(other: Series): number {
337
+ if (this.writePos === FULL_BUFFER) return 0;
338
+ const available = this.byteCapacity.valueOf() - this.writePos;
339
+ const toWrite = other.subBytes(0, available);
340
+ this.writeToUnderlyingData(toWrite);
341
+ this.writePos += toWrite.byteLength.valueOf();
342
+ if (this._cachedLength != null) {
343
+ this._cachedLength += toWrite.length;
344
+ this.calculateCachedLength();
345
+ }
346
+ return toWrite.length;
347
+ }
323
348
 
324
- // We've filled the entire underlying buffer
349
+ private writeFixed(other: Series): number {
325
350
  if (this.writePos === FULL_BUFFER) return 0;
326
351
  const available = this.capacity - this.writePos;
352
+ const toWrite = other.sub(0, available);
353
+ this.writeToUnderlyingData(toWrite);
354
+ this._cachedLength = undefined;
355
+ this.maybeRecomputeMinMax(toWrite);
356
+ this.writePos += toWrite.length;
357
+ return toWrite.length;
358
+ }
327
359
 
328
- const toWrite = available < other.length ? other.slice(0, available) : other;
360
+ private writeToUnderlyingData(data: Series) {
329
361
  this.underlyingData.set(
330
- toWrite.data as unknown as ArrayLike<bigint> & ArrayLike<number>,
362
+ data.data as unknown as ArrayLike<bigint> & ArrayLike<number>,
331
363
  this.writePos,
332
364
  );
333
- this.maybeRecomputeMinMax(toWrite);
334
- this._cachedLength = undefined;
335
- this.writePos += toWrite.length;
336
- return toWrite.length;
337
365
  }
338
366
 
339
367
  /** @returns the underlying buffer backing this array. */
340
368
  get buffer(): ArrayBufferLike {
341
- return this._data;
369
+ if (this._data instanceof ArrayBuffer || this._data instanceof SharedArrayBuffer)
370
+ return this._data;
371
+ return (this._data as TypedArray).buffer;
342
372
  }
343
373
 
344
374
  private get underlyingData(): TypedArray {
@@ -354,7 +384,7 @@ export class Series<T extends TelemValue = TelemValue> {
354
384
  toStrings(): string[] {
355
385
  if (!this.dataType.matches(DataType.STRING, DataType.UUID))
356
386
  throw new Error("cannot convert non-string series to strings");
357
- return new TextDecoder().decode(this.buffer).split("\n").slice(0, -1);
387
+ return new TextDecoder().decode(this.underlyingData).split("\n").slice(0, -1);
358
388
  }
359
389
 
360
390
  toUUIDs(): string[] {
@@ -364,7 +394,7 @@ export class Series<T extends TelemValue = TelemValue> {
364
394
  const r = Array(this.length);
365
395
 
366
396
  for (let i = 0; i < this.length; i++) {
367
- const v = this.buffer.slice(i * den, (i + 1) * den);
397
+ const v = this.underlyingData.slice(i * den, (i + 1) * den);
368
398
  const id = Array.from(new Uint8Array(v), (b) => b.toString(16).padStart(2, "0"))
369
399
  .join("")
370
400
  .replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, "$1-$2-$3-$4-$5");
@@ -377,7 +407,7 @@ export class Series<T extends TelemValue = TelemValue> {
377
407
  if (!this.dataType.equals(DataType.JSON))
378
408
  throw new Error("cannot convert non-string series to strings");
379
409
  return new TextDecoder()
380
- .decode(this.buffer)
410
+ .decode(this.underlyingData)
381
411
  .split("\n")
382
412
  .slice(0, -1)
383
413
  .map((s) => schema.parse(binary.JSON_CODEC.decodeString(s)));
@@ -391,17 +421,19 @@ export class Series<T extends TelemValue = TelemValue> {
391
421
 
392
422
  /** @returns the capacity of the series in bytes. */
393
423
  get byteCapacity(): Size {
394
- return new Size(this.buffer.byteLength);
424
+ return new Size(this.underlyingData.byteLength);
395
425
  }
396
426
 
397
427
  /** @returns the capacity of the series in samples. */
398
428
  get capacity(): number {
429
+ if (this.dataType.isVariable) return this.byteCapacity.valueOf();
399
430
  return this.dataType.density.length(this.byteCapacity);
400
431
  }
401
432
 
402
433
  /** @returns the length of the series in bytes. */
403
434
  get byteLength(): Size {
404
435
  if (this.writePos === FULL_BUFFER) return this.byteCapacity;
436
+ if (this.dataType.isVariable) return new Size(this.writePos);
405
437
  return this.dataType.density.size(this.writePos);
406
438
  }
407
439
 
@@ -417,9 +449,13 @@ export class Series<T extends TelemValue = TelemValue> {
417
449
  if (!this.dataType.isVariable)
418
450
  throw new Error("cannot calculate length of a non-variable length data type");
419
451
  let cl = 0;
420
- this.data.forEach((v) => {
421
- if (v === 10) cl++;
452
+ const ci: number[] = [0];
453
+ this.data.forEach((v, i) => {
454
+ if (v !== NEW_LINE) return;
455
+ cl++;
456
+ ci.push(i + 1);
422
457
  });
458
+ this._cachedIndexes = ci;
423
459
  this._cachedLength = cl;
424
460
  return cl;
425
461
  }
@@ -433,12 +469,11 @@ export class Series<T extends TelemValue = TelemValue> {
433
469
  * WARNING: This method is expensive and copies the entire underlying array. There
434
470
  * also may be untimely precision issues when converting between data types.
435
471
  */
436
- convert(target: DataType, sampleOffset: NumericTelemValue = 0): Series {
472
+ convert(target: DataType, sampleOffset: math.Numeric = 0): Series {
437
473
  if (this.dataType.equals(target)) return this;
438
474
  const data = new target.Array(this.length);
439
- for (let i = 0; i < this.length; i++) {
475
+ for (let i = 0; i < this.length; i++)
440
476
  data[i] = convertDataType(this.dataType, target, this.data[i], sampleOffset);
441
- }
442
477
  return new Series({
443
478
  data: data.buffer,
444
479
  dataType: target,
@@ -449,11 +484,11 @@ export class Series<T extends TelemValue = TelemValue> {
449
484
  });
450
485
  }
451
486
 
452
- private calcRawMax(): NumericTelemValue {
487
+ private calcRawMax(): math.Numeric {
453
488
  if (this.length === 0) return -Infinity;
454
- if (this.dataType.equals(DataType.TIMESTAMP)) {
489
+ if (this.dataType.equals(DataType.TIMESTAMP))
455
490
  this._cachedMax = this.data[this.data.length - 1];
456
- } else if (this.dataType.usesBigInt) {
491
+ else if (this.dataType.usesBigInt) {
457
492
  const d = this.data as BigInt64Array;
458
493
  this._cachedMax = d.reduce((a, b) => (a > b ? a : b));
459
494
  } else {
@@ -464,19 +499,18 @@ export class Series<T extends TelemValue = TelemValue> {
464
499
  }
465
500
 
466
501
  /** @returns the maximum value in the array */
467
- get max(): NumericTelemValue {
502
+ get max(): math.Numeric {
468
503
  if (this.dataType.isVariable)
469
504
  throw new Error("cannot calculate maximum on a variable length data type");
470
505
  if (this.writePos === 0) return -Infinity;
471
- else if (this._cachedMax == null) this._cachedMax = this.calcRawMax();
506
+ this._cachedMax ??= this.calcRawMax();
472
507
  return addSamples(this._cachedMax, this.sampleOffset);
473
508
  }
474
509
 
475
- private calcRawMin(): NumericTelemValue {
510
+ private calcRawMin(): math.Numeric {
476
511
  if (this.length === 0) return Infinity;
477
- if (this.dataType.equals(DataType.TIMESTAMP)) {
478
- this._cachedMin = this.data[0];
479
- } else if (this.dataType.usesBigInt) {
512
+ if (this.dataType.equals(DataType.TIMESTAMP)) this._cachedMin = this.data[0];
513
+ else if (this.dataType.usesBigInt) {
480
514
  const d = this.data as BigInt64Array;
481
515
  this._cachedMin = d.reduce((a, b) => (a < b ? a : b));
482
516
  } else {
@@ -487,11 +521,11 @@ export class Series<T extends TelemValue = TelemValue> {
487
521
  }
488
522
 
489
523
  /** @returns the minimum value in the array */
490
- get min(): NumericTelemValue {
524
+ get min(): math.Numeric {
491
525
  if (this.dataType.isVariable)
492
526
  throw new Error("cannot calculate minimum on a variable length data type");
493
527
  if (this.writePos === 0) return Infinity;
494
- else if (this._cachedMin == null) this._cachedMin = this.calcRawMin();
528
+ this._cachedMin ??= this.calcRawMin();
495
529
  return addSamples(this._cachedMin, this.sampleOffset);
496
530
  }
497
531
 
@@ -517,10 +551,23 @@ export class Series<T extends TelemValue = TelemValue> {
517
551
  _ = this.min;
518
552
  }
519
553
 
520
- get range(): NumericTelemValue {
554
+ get range(): math.Numeric {
521
555
  return addSamples(this.max, -this.min);
522
556
  }
523
557
 
558
+ atAlignment(alignment: bigint, required: true): T;
559
+
560
+ atAlignment(alignment: bigint, required?: false): T | undefined;
561
+
562
+ atAlignment(alignment: bigint, required?: boolean): T | undefined {
563
+ const index = Number(alignment - this.alignment);
564
+ if (index < 0 || index >= this.length) {
565
+ if (required === true) throw new Error(`[series] - no value at index ${index}`);
566
+ return undefined;
567
+ }
568
+ return this.at(index, required as true);
569
+ }
570
+
524
571
  at(index: number, required: true): T;
525
572
 
526
573
  at(index: number, required?: false): T | undefined;
@@ -537,24 +584,28 @@ export class Series<T extends TelemValue = TelemValue> {
537
584
  }
538
585
 
539
586
  private atVariable(index: number, required: boolean): T | undefined {
540
- if (index < 0) index = this.length + index;
541
587
  let start = 0;
542
588
  let end = 0;
543
- for (let i = 0; i < this.data.length; i++) {
544
- if (this.data[i] === 10) {
545
- if (index === 0) {
546
- end = i;
547
- break;
589
+ if (this._cachedIndexes != null) {
590
+ start = this._cachedIndexes[index];
591
+ end = this._cachedIndexes[index + 1] - 1;
592
+ } else {
593
+ if (index < 0) index = this.length + index;
594
+ for (let i = 0; i < this.data.length; i++)
595
+ if (this.data[i] === NEW_LINE) {
596
+ if (index === 0) {
597
+ end = i;
598
+ break;
599
+ }
600
+ start = i + 1;
601
+ index--;
548
602
  }
549
- start = i + 1;
550
- index--;
603
+ if (end === 0) end = this.data.length;
604
+ if (start >= end || index > 0) {
605
+ if (required) throw new Error(`[series] - no value at index ${index}`);
606
+ return undefined;
551
607
  }
552
608
  }
553
- if (end === 0) end = this.data.length;
554
- if (start >= end || index > 0) {
555
- if (required) throw new Error(`[series] - no value at index ${index}`);
556
- return undefined;
557
- }
558
609
  const slice = this.data.slice(start, end);
559
610
  if (this.dataType.equals(DataType.STRING))
560
611
  return new TextDecoder().decode(slice) as unknown as T;
@@ -568,13 +619,13 @@ export class Series<T extends TelemValue = TelemValue> {
568
619
  * The underlying array must be sorted. If it is not, the behavior of this method is undefined.
569
620
  * @param value the value to search for.
570
621
  */
571
- binarySearch(value: NumericTelemValue): number {
622
+ binarySearch(value: math.Numeric): number {
572
623
  let left = 0;
573
624
  let right = this.length - 1;
574
625
  const cf = compare.newF(value);
575
626
  while (left <= right) {
576
627
  const mid = Math.floor((left + right) / 2);
577
- const cmp = cf(this.at(mid, true) as NumericTelemValue, value);
628
+ const cmp = cf(this.at(mid, true) as math.Numeric, value);
578
629
  if (cmp === 0) return mid;
579
630
  if (cmp < 0) left = mid + 1;
580
631
  else right = mid - 1;
@@ -599,9 +650,8 @@ export class Series<T extends TelemValue = TelemValue> {
599
650
 
600
651
  // This means we only need to buffer part of the array.
601
652
  if (this.writePos !== FULL_BUFFER) {
602
- if (prevBuffer === 0) {
653
+ if (prevBuffer === 0)
603
654
  gl.bufferData(gl.ARRAY_BUFFER, this.byteCapacity.valueOf(), gl.STATIC_DRAW);
604
- }
605
655
  const byteOffset = this.dataType.density.size(prevBuffer).valueOf();
606
656
  const slice = this.underlyingData.slice(this.gl.prevBuffer, this.writePos);
607
657
  gl.bufferSubData(gl.ARRAY_BUFFER, byteOffset, slice.buffer);
@@ -610,7 +660,7 @@ export class Series<T extends TelemValue = TelemValue> {
610
660
  // This means we can buffer the entire array in a single go.
611
661
  gl.bufferData(
612
662
  gl.ARRAY_BUFFER,
613
- this.buffer,
663
+ this.underlyingData,
614
664
  bufferUsage === "static" ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW,
615
665
  );
616
666
  this.gl.prevBuffer = FULL_BUFFER;
@@ -690,17 +740,51 @@ export class Series<T extends TelemValue = TelemValue> {
690
740
  [Symbol.iterator](): Iterator<T> {
691
741
  if (this.dataType.isVariable) {
692
742
  const s = new StringSeriesIterator(this);
693
- if (this.dataType.equals(DataType.JSON)) {
743
+ if (this.dataType.equals(DataType.JSON))
694
744
  return new JSONSeriesIterator(s) as Iterator<T>;
695
- }
696
745
  return s as Iterator<T>;
697
746
  }
698
747
  return new FixedSeriesIterator(this) as Iterator<T>;
699
748
  }
700
749
 
701
750
  slice(start: number, end?: number): Series {
751
+ return this.sliceSub(false, start, end);
752
+ }
753
+
754
+ sub(start: number, end?: number): Series {
755
+ return this.sliceSub(true, start, end);
756
+ }
757
+
758
+ subIterator(start: number, end?: number): IterableIterator<T> {
759
+ return new SubIterator(this, start, end ?? this.length);
760
+ }
761
+
762
+ subAlignmentIterator(start: bigint, end: bigint): IterableIterator<T> {
763
+ return new SubIterator(
764
+ this,
765
+ Number(start - this.alignment),
766
+ Number(end - this.alignment),
767
+ );
768
+ }
769
+
770
+ private subBytes(start: number, end?: number): Series {
771
+ if (start >= 0 && (end == null || end >= this.byteLength.valueOf())) return this;
772
+ const data = this.data.subarray(start, end);
773
+ return new Series({
774
+ data,
775
+ dataType: this.dataType,
776
+ timeRange: this._timeRange,
777
+ sampleOffset: this.sampleOffset,
778
+ glBufferUsage: this.gl.bufferUsage,
779
+ alignment: this.alignment + BigInt(start),
780
+ });
781
+ }
782
+
783
+ private sliceSub(sub: boolean, start: number, end?: number): Series {
702
784
  if (start <= 0 && (end == null || end >= this.length)) return this;
703
- const data = this.data.slice(start, end);
785
+ let data: TypedArray;
786
+ if (sub) data = this.data.subarray(start, end);
787
+ else data = this.data.slice(start, end);
704
788
  return new Series({
705
789
  data,
706
790
  dataType: this.dataType,
@@ -723,6 +807,28 @@ export class Series<T extends TelemValue = TelemValue> {
723
807
  }
724
808
  }
725
809
 
810
+ class SubIterator<T> implements Iterator<T>, Iterable<T> {
811
+ private readonly series: Series;
812
+ private readonly end: number;
813
+ private index: number;
814
+
815
+ constructor(series: Series, start: number, end: number) {
816
+ this.series = series;
817
+ const b = bounds.construct(0, series.length);
818
+ this.end = bounds.clamp(b, end);
819
+ this.index = bounds.clamp(b, start);
820
+ }
821
+
822
+ next(): IteratorResult<T> {
823
+ if (this.index >= this.end) return { done: true, value: undefined };
824
+ return { done: false, value: this.series.at(this.index++, true) as T };
825
+ }
826
+
827
+ [Symbol.iterator](): Iterator<T> {
828
+ return this;
829
+ }
830
+ }
831
+
726
832
  class StringSeriesIterator implements Iterator<string> {
727
833
  private readonly series: Series;
728
834
  private index: number;
@@ -741,7 +847,7 @@ class StringSeriesIterator implements Iterator<string> {
741
847
  next(): IteratorResult<string> {
742
848
  const start = this.index;
743
849
  const data = this.series.data;
744
- while (this.index < data.length && data[this.index] !== 10) this.index++;
850
+ while (this.index < data.length && data[this.index] !== NEW_LINE) this.index++;
745
851
  const end = this.index;
746
852
  if (start === end) return { done: true, value: undefined };
747
853
  this.index++;
@@ -777,7 +883,7 @@ class JSONSeriesIterator implements Iterator<unknown> {
777
883
  [Symbol.toStringTag] = "JSONSeriesIterator";
778
884
  }
779
885
 
780
- class FixedSeriesIterator implements Iterator<NumericTelemValue> {
886
+ class FixedSeriesIterator implements Iterator<math.Numeric> {
781
887
  series: Series;
782
888
  index: number;
783
889
  constructor(series: Series) {
@@ -785,25 +891,22 @@ class FixedSeriesIterator implements Iterator<NumericTelemValue> {
785
891
  this.index = 0;
786
892
  }
787
893
 
788
- next(): IteratorResult<NumericTelemValue> {
894
+ next(): IteratorResult<math.Numeric> {
789
895
  if (this.index >= this.series.length) return { done: true, value: undefined };
790
896
  return {
791
897
  done: false,
792
- value: this.series.at(this.index++, true) as NumericTelemValue,
898
+ value: this.series.at(this.index++, true) as math.Numeric,
793
899
  };
794
900
  }
795
901
 
796
- [Symbol.iterator](): Iterator<NumericTelemValue> {
902
+ [Symbol.iterator](): Iterator<math.Numeric> {
797
903
  return this;
798
904
  }
799
905
 
800
906
  [Symbol.toStringTag] = "SeriesIterator";
801
907
  }
802
908
 
803
- export const addSamples = (
804
- a: NumericTelemValue,
805
- b: NumericTelemValue,
806
- ): NumericTelemValue => {
909
+ export const addSamples = (a: math.Numeric, b: math.Numeric): math.Numeric => {
807
910
  if (typeof a === "bigint" && typeof b === "bigint") return a + b;
808
911
  if (typeof a === "number" && typeof b === "number") return a + b;
809
912
  if (b === 0) return a;
@@ -851,6 +954,19 @@ export class MultiSeries<T extends TelemValue = TelemValue> implements Iterable<
851
954
  );
852
955
  }
853
956
 
957
+ get alignment(): bigint {
958
+ if (this.series.length === 0) return 0n;
959
+ return this.series[0].alignment;
960
+ }
961
+
962
+ get alignmentBounds(): bounds.Bounds<bigint> {
963
+ if (this.series.length === 0) return bounds.construct(0n, 0n);
964
+ return bounds.construct(
965
+ this.series[0].alignmentBounds.lower,
966
+ this.series[this.series.length - 1].alignmentBounds.upper,
967
+ );
968
+ }
969
+
854
970
  push(series: Series<T>): void {
855
971
  this.series.push(series);
856
972
  }
@@ -859,6 +975,22 @@ export class MultiSeries<T extends TelemValue = TelemValue> implements Iterable<
859
975
  return this.series.reduce((a, b) => a + b.length, 0);
860
976
  }
861
977
 
978
+ atAlignment(alignment: bigint, required: true): T;
979
+
980
+ atAlignment(alignment: bigint, required?: false): T | undefined;
981
+
982
+ atAlignment(alignment: bigint, required?: boolean): T | undefined {
983
+ if (this.series.length === 0) {
984
+ if (required) throw new Error(`[series] - no value at alignment ${alignment}`);
985
+ return undefined;
986
+ }
987
+ for (const ser of this.series)
988
+ if (bounds.contains(ser.alignmentBounds, alignment))
989
+ return ser.atAlignment(alignment, required as true);
990
+ if (required) throw new Error(`[series] - no value at alignment ${alignment}`);
991
+ return undefined;
992
+ }
993
+
862
994
  at(index: number, required: true): T;
863
995
 
864
996
  at(index: number, required?: false): T | undefined;
@@ -873,6 +1005,51 @@ export class MultiSeries<T extends TelemValue = TelemValue> implements Iterable<
873
1005
  return undefined;
874
1006
  }
875
1007
 
1008
+ subIterator(start: number, end?: number): IterableIterator<T> {
1009
+ return new MultiSubIterator(this, start, end ?? this.length);
1010
+ }
1011
+
1012
+ subAlignmentIterator(start: bigint, end: bigint): IterableIterator<T> {
1013
+ if (start >= this.alignmentBounds.upper || end <= this.alignmentBounds.lower)
1014
+ return noopIterableIterator;
1015
+ let startIdx = 0;
1016
+ for (let i = 0; i < this.series.length; i++) {
1017
+ const ser = this.series[i];
1018
+ if (start < ser.alignment) break;
1019
+ else if (start >= ser.alignmentBounds.upper) startIdx += ser.length;
1020
+ else if (bounds.contains(ser.alignmentBounds, start)) {
1021
+ startIdx += Number(start - ser.alignment);
1022
+ break;
1023
+ }
1024
+ }
1025
+ let endIdx = 0;
1026
+ for (let i = 0; i < this.series.length; i++) {
1027
+ const ser = this.series[i];
1028
+ if (end < ser.alignment) break;
1029
+ else if (end >= ser.alignmentBounds.upper) endIdx += ser.length;
1030
+ else if (bounds.contains(ser.alignmentBounds, end)) {
1031
+ endIdx += Number(end - ser.alignment);
1032
+ break;
1033
+ }
1034
+ }
1035
+ return new MultiSubIterator(this, startIdx, endIdx);
1036
+ }
1037
+
1038
+ subAlignmentSpanIterator(start: bigint, span: number): IterableIterator<T> {
1039
+ if (start >= this.alignmentBounds.upper) return noopIterableIterator;
1040
+ let startIdx = 0;
1041
+ for (let i = 0; i < this.series.length; i++) {
1042
+ const ser = this.series[i];
1043
+ if (start < ser.alignment) break;
1044
+ else if (start >= ser.alignmentBounds.upper) startIdx += ser.length;
1045
+ else if (bounds.contains(ser.alignmentBounds, start)) {
1046
+ startIdx += Number(start - ser.alignment);
1047
+ break;
1048
+ }
1049
+ }
1050
+ return new MultiSubIterator(this, startIdx, startIdx + span);
1051
+ }
1052
+
876
1053
  get byteLength(): Size {
877
1054
  return new Size(this.series.reduce((a, b) => a + b.byteLength.valueOf(), 0));
878
1055
  }
@@ -887,6 +1064,16 @@ export class MultiSeries<T extends TelemValue = TelemValue> implements Iterable<
887
1064
  return new this.dataType.Array(buf);
888
1065
  }
889
1066
 
1067
+ traverseAlignment(start: bigint, dist: bigint): bigint {
1068
+ const b = this.series.map((s) => s.alignmentBounds);
1069
+ return bounds.traverse(b, start, dist);
1070
+ }
1071
+
1072
+ distance(start: bigint, end: bigint): bigint {
1073
+ const b = this.series.map((s) => s.alignmentBounds);
1074
+ return bounds.distance(b, start, end);
1075
+ }
1076
+
890
1077
  [Symbol.iterator](): Iterator<T> {
891
1078
  if (this.series.length === 0)
892
1079
  return {
@@ -925,4 +1112,27 @@ class MultiSeriesIterator<T extends TelemValue = TelemValue> implements Iterator
925
1112
  [Symbol.toStringTag] = "MultiSeriesIterator";
926
1113
  }
927
1114
 
1115
+ class MultiSubIterator<T extends TelemValue = TelemValue>
1116
+ implements IterableIterator<T>
1117
+ {
1118
+ private readonly series: MultiSeries<T>;
1119
+ private index: number;
1120
+ private end: number;
1121
+
1122
+ constructor(series: MultiSeries<T>, start: number, end: number) {
1123
+ this.series = series;
1124
+ this.end = end;
1125
+ this.index = start;
1126
+ }
1127
+
1128
+ next(): IteratorResult<T> {
1129
+ if (this.index >= this.end) return { done: true, value: undefined };
1130
+ return { done: false, value: this.series.at(this.index++, true) as T };
1131
+ }
1132
+
1133
+ [Symbol.iterator](): Iterator<T> {
1134
+ return this;
1135
+ }
1136
+ }
1137
+
928
1138
  export type SeriesPayload = z.infer<typeof Series.crudeZ>;