@synnaxlabs/x 0.54.2 → 0.56.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.
- package/.turbo/turbo-build.log +10 -13
- package/dist/src/array/nullable.d.ts +1 -1
- package/dist/src/array/nullable.d.ts.map +1 -1
- package/dist/src/caseconv/caseconv.d.ts.map +1 -1
- package/dist/src/compare/compare.d.ts +14 -0
- package/dist/src/compare/compare.d.ts.map +1 -1
- package/dist/src/debounce/debounce.d.ts +7 -2
- package/dist/src/debounce/debounce.d.ts.map +1 -1
- package/dist/src/destructor/destructor.d.ts +1 -0
- package/dist/src/destructor/destructor.d.ts.map +1 -1
- package/dist/src/errors/errors.d.ts +6 -10
- package/dist/src/errors/errors.d.ts.map +1 -1
- package/dist/src/index.d.ts +4 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/notation/external.d.ts +3 -0
- package/dist/src/notation/external.d.ts.map +1 -0
- package/dist/src/notation/index.d.ts +1 -1
- package/dist/src/notation/notation.d.ts +5 -9
- package/dist/src/notation/notation.d.ts.map +1 -1
- package/dist/src/notation/types.gen.d.ts +9 -0
- package/dist/src/notation/types.gen.d.ts.map +1 -0
- package/dist/src/primitive/primitive.d.ts +16 -0
- package/dist/src/primitive/primitive.d.ts.map +1 -1
- package/dist/src/record/record.d.ts +8 -1
- package/dist/src/record/record.d.ts.map +1 -1
- package/dist/src/require/index.d.ts +2 -0
- package/dist/src/require/index.d.ts.map +1 -0
- package/dist/src/require/require.d.ts +2 -0
- package/dist/src/require/require.d.ts.map +1 -0
- package/dist/src/spatial/base.d.ts +1 -103
- package/dist/src/spatial/base.d.ts.map +1 -1
- package/dist/src/spatial/bounds/bounds.d.ts +3 -3
- package/dist/src/spatial/bounds/bounds.d.ts.map +1 -1
- package/dist/src/spatial/box/box.d.ts +7 -13
- package/dist/src/spatial/box/box.d.ts.map +1 -1
- package/dist/src/spatial/direction/direction.d.ts +17 -16
- package/dist/src/spatial/direction/direction.d.ts.map +1 -1
- package/dist/src/spatial/external.d.ts +1 -2
- package/dist/src/spatial/external.d.ts.map +1 -1
- package/dist/src/spatial/location/location.d.ts +28 -28
- package/dist/src/spatial/location/location.d.ts.map +1 -1
- package/dist/src/spatial/scale/scale.d.ts +2 -2
- package/dist/src/spatial/scale/scale.d.ts.map +1 -1
- package/dist/src/spatial/sticky/sticky.d.ts +15 -15
- package/dist/src/spatial/sticky/sticky.d.ts.map +1 -1
- package/dist/src/spatial/types.gen.d.ts +179 -2
- package/dist/src/spatial/types.gen.d.ts.map +1 -1
- package/dist/src/spatial/xy/xy.d.ts +4 -4
- package/dist/src/spatial/xy/xy.d.ts.map +1 -1
- package/dist/src/status/status.d.ts +11 -0
- package/dist/src/status/status.d.ts.map +1 -1
- package/dist/src/strings/strings.d.ts +9 -0
- package/dist/src/strings/strings.d.ts.map +1 -1
- package/dist/src/telem/clockSkew.d.ts +17 -0
- package/dist/src/telem/clockSkew.d.ts.map +1 -0
- package/dist/src/telem/clockSkew.spec.d.ts +2 -0
- package/dist/src/telem/clockSkew.spec.d.ts.map +1 -0
- package/dist/src/telem/external.d.ts +2 -0
- package/dist/src/telem/external.d.ts.map +1 -1
- package/dist/src/telem/series.d.ts +6 -3
- package/dist/src/telem/series.d.ts.map +1 -1
- package/dist/src/telem/telem.d.ts +48 -39
- package/dist/src/telem/telem.d.ts.map +1 -1
- package/dist/src/telem/types.gen.d.ts +19 -0
- package/dist/src/telem/types.gen.d.ts.map +1 -0
- package/dist/src/text/external.d.ts +3 -0
- package/dist/src/text/external.d.ts.map +1 -0
- package/dist/src/text/index.d.ts +2 -0
- package/dist/src/text/index.d.ts.map +1 -0
- package/dist/src/text/types.d.ts +21 -0
- package/dist/src/text/types.d.ts.map +1 -0
- package/dist/src/text/types.gen.d.ts +13 -0
- package/dist/src/text/types.gen.d.ts.map +1 -0
- package/dist/src/throttle/index.d.ts +2 -0
- package/dist/src/throttle/index.d.ts.map +1 -0
- package/dist/src/throttle/throttle.d.ts +3 -0
- package/dist/src/throttle/throttle.d.ts.map +1 -0
- package/dist/src/throttle/throttle.spec.d.ts +2 -0
- package/dist/src/throttle/throttle.spec.d.ts.map +1 -0
- package/dist/src/zod/parse.d.ts.map +1 -1
- package/dist/x.cjs +10 -13
- package/dist/x.js +2271 -2084
- package/package.json +12 -12
- package/src/array/nullable.ts +1 -4
- package/src/caseconv/caseconv.spec.ts +71 -0
- package/src/caseconv/caseconv.ts +15 -2
- package/src/compare/compare.spec.ts +115 -0
- package/src/compare/compare.ts +29 -0
- package/src/debounce/debounce.spec.ts +258 -24
- package/src/debounce/debounce.ts +49 -30
- package/src/deep/copy.spec.ts +13 -0
- package/src/deep/difference.ts +1 -1
- package/src/destructor/destructor.ts +2 -0
- package/src/errors/errors.spec.ts +30 -0
- package/src/errors/errors.ts +29 -17
- package/src/index.ts +4 -1
- package/src/notation/external.ts +11 -0
- package/src/notation/index.ts +1 -1
- package/src/notation/notation.spec.ts +260 -2
- package/src/notation/notation.ts +25 -7
- package/src/notation/types.gen.ts +16 -0
- package/src/primitive/primitive.spec.ts +58 -5
- package/src/primitive/primitive.ts +22 -0
- package/src/record/record.spec.ts +26 -0
- package/src/record/record.ts +20 -5
- package/src/require/index.ts +10 -0
- package/src/require/require.ts +10 -0
- package/src/spatial/base.ts +1 -93
- package/src/spatial/bounds/bounds.ts +10 -10
- package/src/spatial/box/box.ts +5 -5
- package/src/spatial/direction/direction.ts +16 -17
- package/src/spatial/external.ts +1 -2
- package/src/spatial/location/location.ts +19 -17
- package/src/spatial/scale/scale.ts +2 -2
- package/src/spatial/sticky/sticky.spec.ts +2 -2
- package/src/spatial/sticky/sticky.ts +6 -13
- package/src/spatial/types.gen.ts +140 -0
- package/src/spatial/xy/xy.ts +7 -7
- package/src/status/status.spec.ts +65 -0
- package/src/status/status.ts +20 -0
- package/src/strings/strings.spec.ts +19 -0
- package/src/strings/strings.ts +16 -0
- package/src/telem/clockSkew.spec.ts +58 -0
- package/src/telem/clockSkew.ts +46 -0
- package/src/telem/external.ts +9 -0
- package/src/telem/series.spec.ts +235 -4
- package/src/telem/series.ts +172 -58
- package/src/telem/telem.spec.ts +147 -9
- package/src/telem/telem.ts +101 -84
- package/src/telem/types.gen.ts +28 -0
- package/src/text/external.ts +11 -0
- package/src/text/index.ts +10 -0
- package/src/text/types.gen.ts +16 -0
- package/src/text/types.ts +37 -0
- package/src/{worker → throttle}/index.ts +1 -1
- package/src/throttle/throttle.spec.ts +147 -0
- package/src/throttle/throttle.ts +44 -0
- package/src/zod/parse.ts +2 -3
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/src/spatial/spatial.d.ts +0 -3
- package/dist/src/spatial/spatial.d.ts.map +0 -1
- package/dist/src/worker/index.d.ts +0 -2
- package/dist/src/worker/index.d.ts.map +0 -1
- package/dist/src/worker/worker.d.ts +0 -33
- package/dist/src/worker/worker.d.ts.map +0 -1
- package/dist/src/worker/worker.spec.d.ts +0 -2
- package/dist/src/worker/worker.spec.d.ts.map +0 -1
- package/src/spatial/spatial.ts +0 -44
- package/src/worker/worker.spec.ts +0 -41
- package/src/worker/worker.ts +0 -86
package/src/telem/series.spec.ts
CHANGED
|
@@ -141,7 +141,7 @@ describe("Series", () => {
|
|
|
141
141
|
const s = new Series({ data: [{ a: 1, b: "apple" }] });
|
|
142
142
|
expect(s.dataType.equals(DataType.JSON));
|
|
143
143
|
expect(s.length).toEqual(1);
|
|
144
|
-
expect(s.
|
|
144
|
+
expect(s.at(0)).toEqual({ a: 1, b: "apple" });
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
it("should correctly interpret a bigint as an int64", () => {
|
|
@@ -214,8 +214,8 @@ describe("Series", () => {
|
|
|
214
214
|
|
|
215
215
|
it("should convert encoded keys to snake_case", () => {
|
|
216
216
|
const a = new Series({ data: [{ aB: 1, bC: "apple" }], dataType: DataType.JSON });
|
|
217
|
-
|
|
218
|
-
expect(
|
|
217
|
+
expect(a.length).toEqual(1);
|
|
218
|
+
expect(a.at(0)).toEqual({ aB: 1, bC: "apple" });
|
|
219
219
|
});
|
|
220
220
|
|
|
221
221
|
it("should throw an error when an empty JS array is provided and no data type is provided", () => {
|
|
@@ -369,6 +369,155 @@ describe("Series", () => {
|
|
|
369
369
|
});
|
|
370
370
|
});
|
|
371
371
|
|
|
372
|
+
describe("asString", () => {
|
|
373
|
+
it("should return the string value for a string series", () => {
|
|
374
|
+
const series = new Series({
|
|
375
|
+
data: ["apple", "banana"],
|
|
376
|
+
dataType: DataType.STRING,
|
|
377
|
+
});
|
|
378
|
+
expect(series.asString(0, true)).toEqual("apple");
|
|
379
|
+
expect(series.asString(1, true)).toEqual("banana");
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it("should return the raw JSON string without parsing for a JSON series", () => {
|
|
383
|
+
const series = new Series({
|
|
384
|
+
data: [{ a_b: 1, c_d: "apple" }],
|
|
385
|
+
dataType: DataType.JSON,
|
|
386
|
+
});
|
|
387
|
+
const result = series.asString(0, true);
|
|
388
|
+
expect(result).toEqual('{"a_b":1,"c_d":"apple"}');
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it("should preserve raw snake_case keys while at() converts them to camelCase", () => {
|
|
392
|
+
const series = new Series({
|
|
393
|
+
data: [{ user_id: 1, first_name: "alice" }],
|
|
394
|
+
dataType: DataType.JSON,
|
|
395
|
+
});
|
|
396
|
+
expect(series.asString(0, true)).toEqual('{"user_id":1,"first_name":"alice"}');
|
|
397
|
+
expect(series.at(0, true)).toEqual({ userId: 1, firstName: "alice" });
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it("should decode UTF-8 bytes for a BYTES series", () => {
|
|
401
|
+
const payload = new TextEncoder().encode("hello world");
|
|
402
|
+
const buf = new ArrayBuffer(4 + payload.byteLength);
|
|
403
|
+
new DataView(buf).setUint32(0, payload.byteLength, true);
|
|
404
|
+
new Uint8Array(buf).set(payload, 4);
|
|
405
|
+
const series = new Series({ data: buf, dataType: DataType.BYTES });
|
|
406
|
+
expect(series.asString(0, true)).toEqual("hello world");
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it("should emit the U+FFFD replacement character for invalid UTF-8 in a BYTES series", () => {
|
|
410
|
+
const payload = new Uint8Array([0xff, 0xfe]);
|
|
411
|
+
const buf = new ArrayBuffer(4 + payload.byteLength);
|
|
412
|
+
new DataView(buf).setUint32(0, payload.byteLength, true);
|
|
413
|
+
new Uint8Array(buf).set(payload, 4);
|
|
414
|
+
const series = new Series({ data: buf, dataType: DataType.BYTES });
|
|
415
|
+
expect(series.asString(0, true)).toEqual("��");
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it("should return a string representation for a numeric series", () => {
|
|
419
|
+
const series = new Series({
|
|
420
|
+
data: new Float32Array([3.5, 7]),
|
|
421
|
+
dataType: DataType.FLOAT32,
|
|
422
|
+
});
|
|
423
|
+
expect(series.asString(0, true)).toEqual("3.5");
|
|
424
|
+
expect(series.asString(1, true)).toEqual("7");
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("should return the shortest f32-roundtrippable decimal for typical FLOAT32 values", () => {
|
|
428
|
+
const series = new Series({
|
|
429
|
+
data: new Float32Array([1.234, 0.1, 100.5, -1.234, 3.14, -0]),
|
|
430
|
+
dataType: DataType.FLOAT32,
|
|
431
|
+
});
|
|
432
|
+
expect(series.asString(0, true)).toEqual("1.234");
|
|
433
|
+
expect(series.asString(1, true)).toEqual("0.1");
|
|
434
|
+
expect(series.asString(2, true)).toEqual("100.5");
|
|
435
|
+
expect(series.asString(3, true)).toEqual("-1.234");
|
|
436
|
+
expect(series.asString(4, true)).toEqual("3.14");
|
|
437
|
+
expect(series.asString(5, true)).toEqual("0");
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it("should stringify integer-valued FLOAT32 values without decimals", () => {
|
|
441
|
+
const series = new Series({
|
|
442
|
+
data: new Float32Array([0, 1, -1, 1000000, -1000000]),
|
|
443
|
+
dataType: DataType.FLOAT32,
|
|
444
|
+
});
|
|
445
|
+
expect(series.asString(0, true)).toEqual("0");
|
|
446
|
+
expect(series.asString(1, true)).toEqual("1");
|
|
447
|
+
expect(series.asString(2, true)).toEqual("-1");
|
|
448
|
+
expect(series.asString(3, true)).toEqual("1000000");
|
|
449
|
+
expect(series.asString(4, true)).toEqual("-1000000");
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it("should preserve f32 representation for very small FLOAT32 values", () => {
|
|
453
|
+
const subnormal = Math.fround(1e-40);
|
|
454
|
+
const series = new Series({
|
|
455
|
+
data: new Float32Array([Number.MIN_VALUE, subnormal]),
|
|
456
|
+
dataType: DataType.FLOAT32,
|
|
457
|
+
});
|
|
458
|
+
// Number.MIN_VALUE (5e-324) underflows to 0 in f32.
|
|
459
|
+
expect(series.asString(0, true)).toEqual("0");
|
|
460
|
+
expect(Math.fround(parseFloat(series.asString(1, true)))).toBe(subnormal);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it("should preserve f32 representation for very large FLOAT32 values", () => {
|
|
464
|
+
const maxF32 = Math.fround(3.4e38);
|
|
465
|
+
const large = Math.fround(1e30);
|
|
466
|
+
const series = new Series({
|
|
467
|
+
data: new Float32Array([maxF32, large]),
|
|
468
|
+
dataType: DataType.FLOAT32,
|
|
469
|
+
});
|
|
470
|
+
expect(Math.fround(parseFloat(series.asString(0, true)))).toBe(maxF32);
|
|
471
|
+
expect(Math.fround(parseFloat(series.asString(1, true)))).toBe(large);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it("should stringify non-finite FLOAT32 values", () => {
|
|
475
|
+
const series = new Series({
|
|
476
|
+
data: new Float32Array([NaN, Infinity, -Infinity]),
|
|
477
|
+
dataType: DataType.FLOAT32,
|
|
478
|
+
});
|
|
479
|
+
expect(series.asString(0, true)).toEqual("NaN");
|
|
480
|
+
expect(series.asString(1, true)).toEqual("Infinity");
|
|
481
|
+
expect(series.asString(2, true)).toEqual("-Infinity");
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it("should produce idempotent stringification under FLOAT32 round-trip", () => {
|
|
485
|
+
const cases = [1.234, 0.1, 100.5, -1.234, 1.2339999675750732, Math.PI, 1e-20];
|
|
486
|
+
for (const v of cases) {
|
|
487
|
+
const s1 = new Series({
|
|
488
|
+
data: new Float32Array([v]),
|
|
489
|
+
dataType: DataType.FLOAT32,
|
|
490
|
+
}).asString(0, true);
|
|
491
|
+
const s2 = new Series({
|
|
492
|
+
data: new Float32Array([parseFloat(s1)]),
|
|
493
|
+
dataType: DataType.FLOAT32,
|
|
494
|
+
}).asString(0, true);
|
|
495
|
+
expect(s2).toBe(s1);
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it("should return undefined when index is out of bounds", () => {
|
|
500
|
+
const series = new Series({
|
|
501
|
+
data: ["apple"],
|
|
502
|
+
dataType: DataType.STRING,
|
|
503
|
+
});
|
|
504
|
+
expect(series.asString(5)).toBeUndefined();
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it("should throw when index is out of bounds and required is true", () => {
|
|
508
|
+
const series = new Series({
|
|
509
|
+
data: ["apple"],
|
|
510
|
+
dataType: DataType.STRING,
|
|
511
|
+
});
|
|
512
|
+
expect(() => series.asString(5, true)).toThrow();
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it("should return the UUID string for a UUID series", () => {
|
|
516
|
+
const series = new Series({ data: SAMPLE_UUID_BYTES, dataType: DataType.UUID });
|
|
517
|
+
expect(series.asString(0, true)).toEqual("123e4567-e89b-40d3-8056-426614174000");
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
|
|
372
521
|
describe("atAlignment", () => {
|
|
373
522
|
it("should return the value at a particular alignment", () => {
|
|
374
523
|
const series = new Series({
|
|
@@ -382,6 +531,40 @@ describe("Series", () => {
|
|
|
382
531
|
});
|
|
383
532
|
});
|
|
384
533
|
|
|
534
|
+
describe("bigint sampleOffset precision", () => {
|
|
535
|
+
it("preserves precision in at when sampleOffset is a bigint above 2^53", () => {
|
|
536
|
+
const offset = 1778020940471336960n;
|
|
537
|
+
const series = new Series({
|
|
538
|
+
data: new Float32Array([0, 1, 2]),
|
|
539
|
+
dataType: DataType.FLOAT32,
|
|
540
|
+
sampleOffset: offset,
|
|
541
|
+
});
|
|
542
|
+
expect(series.at(0)).toBe(offset);
|
|
543
|
+
expect(series.at(1)).toBe(offset + 1n);
|
|
544
|
+
expect(series.at(2)).toBe(offset + 2n);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it("preserves precision in max when sampleOffset is a bigint above 2^53", () => {
|
|
548
|
+
const offset = 1778020940471336960n;
|
|
549
|
+
const series = new Series({
|
|
550
|
+
data: new Float32Array([0, 1, 2]),
|
|
551
|
+
dataType: DataType.FLOAT32,
|
|
552
|
+
sampleOffset: offset,
|
|
553
|
+
});
|
|
554
|
+
expect(series.max).toBe(offset + 2n);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it("preserves precision in min when sampleOffset is a bigint above 2^53", () => {
|
|
558
|
+
const offset = 1778020940471336960n;
|
|
559
|
+
const series = new Series({
|
|
560
|
+
data: new Float32Array([0, 1, 2]),
|
|
561
|
+
dataType: DataType.FLOAT32,
|
|
562
|
+
sampleOffset: offset,
|
|
563
|
+
});
|
|
564
|
+
expect(series.min).toBe(offset);
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
|
|
385
568
|
describe("slice", () => {
|
|
386
569
|
it("should slice a lazy array", () => {
|
|
387
570
|
const a = new Series({
|
|
@@ -502,7 +685,7 @@ describe("Series", () => {
|
|
|
502
685
|
});
|
|
503
686
|
|
|
504
687
|
it("should recompute the length of a variable density array", () => {
|
|
505
|
-
const series = Series.alloc({ capacity:
|
|
688
|
+
const series = Series.alloc({ capacity: 18, dataType: DataType.STRING });
|
|
506
689
|
expect(series.length).toEqual(0);
|
|
507
690
|
const writeOne = new Series({ data: ["apple"] });
|
|
508
691
|
expect(series.write(writeOne)).toEqual(1);
|
|
@@ -689,6 +872,54 @@ describe("Series", () => {
|
|
|
689
872
|
{ a: 3, b: "carrot" },
|
|
690
873
|
]);
|
|
691
874
|
});
|
|
875
|
+
|
|
876
|
+
it("should handle a single JSON value", () => {
|
|
877
|
+
const s = new Series([{ key: "val" }]);
|
|
878
|
+
expect(s.length).toEqual(1);
|
|
879
|
+
expect(s.at(0)).toEqual({ key: "val" });
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
it("should handle empty JSON objects", () => {
|
|
883
|
+
const s = new Series([{}, {}, {}]);
|
|
884
|
+
expect(s.length).toEqual(3);
|
|
885
|
+
expect(s.at(0)).toEqual({});
|
|
886
|
+
expect(s.at(2)).toEqual({});
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
it("should support negative indexing", () => {
|
|
890
|
+
const s = new Series([{ a: 1 }, { a: 2 }, { a: 3 }]);
|
|
891
|
+
expect(s.at(-1)).toEqual({ a: 3 });
|
|
892
|
+
expect(s.at(-2)).toEqual({ a: 2 });
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
it("should handle an empty JSON series", () => {
|
|
896
|
+
const s = new Series({ data: new ArrayBuffer(0), dataType: DataType.JSON });
|
|
897
|
+
expect(s.length).toEqual(0);
|
|
898
|
+
expect(Array.from(s)).toEqual([]);
|
|
899
|
+
});
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
describe("bytes series", () => {
|
|
903
|
+
it("should handle an empty bytes series", () => {
|
|
904
|
+
const s = new Series({ data: new ArrayBuffer(0), dataType: DataType.BYTES });
|
|
905
|
+
expect(s.length).toEqual(0);
|
|
906
|
+
expect(Array.from(s)).toEqual([]);
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
it("should correctly compute the length of a bytes series from a length-prefixed buffer", () => {
|
|
910
|
+
const payload1 = new Uint8Array([1, 2, 3]);
|
|
911
|
+
const payload2 = new Uint8Array([4, 5]);
|
|
912
|
+
const totalBytes = 4 + payload1.byteLength + 4 + payload2.byteLength;
|
|
913
|
+
const buf = new ArrayBuffer(totalBytes);
|
|
914
|
+
const view = new DataView(buf);
|
|
915
|
+
const bytes = new Uint8Array(buf);
|
|
916
|
+
view.setUint32(0, payload1.byteLength, true);
|
|
917
|
+
bytes.set(payload1, 4);
|
|
918
|
+
view.setUint32(4 + payload1.byteLength, payload2.byteLength, true);
|
|
919
|
+
bytes.set(payload2, 4 + payload1.byteLength + 4);
|
|
920
|
+
const s = new Series({ data: buf, dataType: DataType.BYTES });
|
|
921
|
+
expect(s.length).toEqual(2);
|
|
922
|
+
});
|
|
692
923
|
});
|
|
693
924
|
|
|
694
925
|
describe("binarySearch", () => {
|
package/src/telem/series.ts
CHANGED
|
@@ -45,6 +45,17 @@ interface GL {
|
|
|
45
45
|
|
|
46
46
|
interface IterableIterator<T> extends Iterator<T>, Iterable<T> {}
|
|
47
47
|
|
|
48
|
+
/** Shortest decimal string that round-trips through f32 — JS analogue of Go's strconv.FormatFloat(_, 'g', -1, 32). */
|
|
49
|
+
const stringifyFloat32 = (value: number): string => {
|
|
50
|
+
const f32 = Math.fround(value);
|
|
51
|
+
if (!Number.isFinite(f32)) return f32.toString();
|
|
52
|
+
for (let p = 1; p <= 9; p++) {
|
|
53
|
+
const parsed = parseFloat(f32.toPrecision(p));
|
|
54
|
+
if (Math.fround(parsed) === f32) return parsed.toString();
|
|
55
|
+
}
|
|
56
|
+
return f32.toString();
|
|
57
|
+
};
|
|
58
|
+
|
|
48
59
|
/** A condensed set of information describing the layout of a series. */
|
|
49
60
|
export interface SeriesDigest {
|
|
50
61
|
key: string;
|
|
@@ -114,7 +125,7 @@ const nullArrayZ = z
|
|
|
114
125
|
.union([z.null(), z.undefined()])
|
|
115
126
|
.transform(() => new Uint8Array().buffer);
|
|
116
127
|
|
|
117
|
-
const
|
|
128
|
+
const UINT32_SIZE = 4;
|
|
118
129
|
|
|
119
130
|
type JSType = "string" | "number" | "bigint";
|
|
120
131
|
|
|
@@ -193,12 +204,14 @@ export class Series<T extends TelemValue = TelemValue>
|
|
|
193
204
|
timeRange: TimeRange.z.optional(),
|
|
194
205
|
dataType: DataType.z,
|
|
195
206
|
alignment: z.coerce.bigint().optional(),
|
|
196
|
-
data: z
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
207
|
+
data: z
|
|
208
|
+
.union([
|
|
209
|
+
stringArrayZ,
|
|
210
|
+
nullArrayZ,
|
|
211
|
+
z.instanceof(ArrayBuffer),
|
|
212
|
+
z.instanceof(Uint8Array),
|
|
213
|
+
])
|
|
214
|
+
.default(() => new Uint8Array().buffer),
|
|
202
215
|
glBufferUsage: glBufferUsageZ.default("static").optional(),
|
|
203
216
|
});
|
|
204
217
|
|
|
@@ -294,7 +307,7 @@ export class Series<T extends TelemValue = TelemValue>
|
|
|
294
307
|
data,
|
|
295
308
|
} = props;
|
|
296
309
|
if (isSeries(data)) {
|
|
297
|
-
const data_ = data
|
|
310
|
+
const data_ = data;
|
|
298
311
|
this.key = data_.key;
|
|
299
312
|
this.dataType = data_.dataType;
|
|
300
313
|
this.sampleOffset = data_.sampleOffset;
|
|
@@ -319,7 +332,7 @@ export class Series<T extends TelemValue = TelemValue>
|
|
|
319
332
|
"cannot infer data type from an ArrayBuffer instance when constructing a Series. Please provide a data type.",
|
|
320
333
|
);
|
|
321
334
|
else if (isArray || isSingle) {
|
|
322
|
-
let first: TelemValue | unknown = data
|
|
335
|
+
let first: TelemValue | unknown = data;
|
|
323
336
|
if (!isSingle) {
|
|
324
337
|
if (data.length === 0)
|
|
325
338
|
throw new Error(
|
|
@@ -358,12 +371,42 @@ export class Series<T extends TelemValue = TelemValue>
|
|
|
358
371
|
data_ = data_.map((v) => new TimeStamp(v as CrudeTimeStamp).valueOf());
|
|
359
372
|
if (this.dataType.equals(DataType.STRING)) {
|
|
360
373
|
this.cachedLength = data_.length;
|
|
361
|
-
|
|
374
|
+
const encoded = (data_ as string[]).map((s) => new TextEncoder().encode(s));
|
|
375
|
+
const totalBytes = encoded.reduce(
|
|
376
|
+
(acc, e) => acc + UINT32_SIZE + e.byteLength,
|
|
377
|
+
0,
|
|
378
|
+
);
|
|
379
|
+
const buf = new ArrayBuffer(totalBytes);
|
|
380
|
+
const view = new DataView(buf);
|
|
381
|
+
const bytes = new Uint8Array(buf);
|
|
382
|
+
let offset = 0;
|
|
383
|
+
for (const e of encoded) {
|
|
384
|
+
view.setUint32(offset, e.byteLength, true);
|
|
385
|
+
offset += UINT32_SIZE;
|
|
386
|
+
bytes.set(e, offset);
|
|
387
|
+
offset += e.byteLength;
|
|
388
|
+
}
|
|
389
|
+
this._data = buf;
|
|
362
390
|
} else if (this.dataType.equals(DataType.JSON)) {
|
|
363
391
|
this.cachedLength = data_.length;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
)
|
|
392
|
+
const encoded = data_.map((d) =>
|
|
393
|
+
new TextEncoder().encode(binary.JSON_CODEC.encodeString(d)),
|
|
394
|
+
);
|
|
395
|
+
const totalBytes = encoded.reduce(
|
|
396
|
+
(acc, e) => acc + UINT32_SIZE + e.byteLength,
|
|
397
|
+
0,
|
|
398
|
+
);
|
|
399
|
+
const buf = new ArrayBuffer(totalBytes);
|
|
400
|
+
const view = new DataView(buf);
|
|
401
|
+
const bytes = new Uint8Array(buf);
|
|
402
|
+
let offset = 0;
|
|
403
|
+
for (const e of encoded) {
|
|
404
|
+
view.setUint32(offset, e.byteLength, true);
|
|
405
|
+
offset += UINT32_SIZE;
|
|
406
|
+
bytes.set(e, offset);
|
|
407
|
+
offset += e.byteLength;
|
|
408
|
+
}
|
|
409
|
+
this._data = buf;
|
|
367
410
|
} else if (this.dataType.usesBigInt && typeof first === "number")
|
|
368
411
|
this._data = new this.dataType.Array(
|
|
369
412
|
data_.map((v) => BigInt(Math.round(v as number))),
|
|
@@ -456,14 +499,25 @@ export class Series<T extends TelemValue = TelemValue>
|
|
|
456
499
|
private writeVariable(other: Series): number {
|
|
457
500
|
if (this.writePos === FULL_BUFFER) return 0;
|
|
458
501
|
const available = this.byteCapacity.valueOf() - this.writePos;
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
502
|
+
const otherBuf = other.buffer;
|
|
503
|
+
const otherByteLen = other.byteLength.valueOf();
|
|
504
|
+
const view = new DataView(otherBuf);
|
|
505
|
+
let offset = 0;
|
|
506
|
+
let samplesWritten = 0;
|
|
507
|
+
while (offset + UINT32_SIZE <= otherByteLen) {
|
|
508
|
+
const sampleLen = view.getUint32(offset, true);
|
|
509
|
+
const recordSize = UINT32_SIZE + sampleLen;
|
|
510
|
+
if (offset + recordSize > available) break;
|
|
511
|
+
offset += recordSize;
|
|
512
|
+
samplesWritten++;
|
|
465
513
|
}
|
|
466
|
-
return
|
|
514
|
+
if (offset === 0) return 0;
|
|
515
|
+
const toWrite = other.subBytes(0, offset);
|
|
516
|
+
this.writeToUnderlyingData(toWrite);
|
|
517
|
+
this.writePos += offset;
|
|
518
|
+
this.cachedLength = (this.cachedLength ?? 0) + samplesWritten;
|
|
519
|
+
this._cachedIndexes = undefined;
|
|
520
|
+
return samplesWritten;
|
|
467
521
|
}
|
|
468
522
|
|
|
469
523
|
private writeFixed(other: Series): number {
|
|
@@ -511,8 +565,21 @@ export class Series<T extends TelemValue = TelemValue>
|
|
|
511
565
|
* @returns An array of string representations of the series values.
|
|
512
566
|
*/
|
|
513
567
|
toStrings(): string[] {
|
|
514
|
-
if (this.dataType.isVariable)
|
|
515
|
-
|
|
568
|
+
if (this.dataType.isVariable) {
|
|
569
|
+
const result: string[] = [];
|
|
570
|
+
const buf = this.buffer;
|
|
571
|
+
const byteLen = this.byteLength.valueOf();
|
|
572
|
+
const view = new DataView(buf);
|
|
573
|
+
const decoder = new TextDecoder();
|
|
574
|
+
let offset = 0;
|
|
575
|
+
while (offset + UINT32_SIZE <= byteLen) {
|
|
576
|
+
const len = view.getUint32(offset, true);
|
|
577
|
+
offset += UINT32_SIZE;
|
|
578
|
+
result.push(decoder.decode(new Uint8Array(buf, offset, len)));
|
|
579
|
+
offset += len;
|
|
580
|
+
}
|
|
581
|
+
return result;
|
|
582
|
+
}
|
|
516
583
|
return Array.from(this).map((d) => d.toString());
|
|
517
584
|
}
|
|
518
585
|
|
|
@@ -560,7 +627,7 @@ export class Series<T extends TelemValue = TelemValue>
|
|
|
560
627
|
|
|
561
628
|
/**
|
|
562
629
|
* Returns the number of samples in this array.
|
|
563
|
-
* For variable length data types, this is calculated by
|
|
630
|
+
* For variable length data types, this is calculated by scanning uint32 length prefixes.
|
|
564
631
|
* @returns The number of samples in the series.
|
|
565
632
|
*/
|
|
566
633
|
get length(): number {
|
|
@@ -575,12 +642,18 @@ export class Series<T extends TelemValue = TelemValue>
|
|
|
575
642
|
if (!this.dataType.isVariable)
|
|
576
643
|
throw new Error("cannot calculate length of a non-variable length data type");
|
|
577
644
|
let cl = 0;
|
|
578
|
-
const ci: number[] = [
|
|
579
|
-
|
|
580
|
-
|
|
645
|
+
const ci: number[] = [];
|
|
646
|
+
const buf = this.buffer;
|
|
647
|
+
const byteLen = this.byteLength.valueOf();
|
|
648
|
+
const view = new DataView(buf);
|
|
649
|
+
let offset = 0;
|
|
650
|
+
while (offset + UINT32_SIZE <= byteLen) {
|
|
651
|
+
const len = view.getUint32(offset, true);
|
|
652
|
+
offset += UINT32_SIZE;
|
|
653
|
+
ci.push(offset);
|
|
654
|
+
offset += len;
|
|
581
655
|
cl++;
|
|
582
|
-
|
|
583
|
-
});
|
|
656
|
+
}
|
|
584
657
|
this._cachedIndexes = ci;
|
|
585
658
|
this.cachedLength = cl;
|
|
586
659
|
return cl;
|
|
@@ -634,7 +707,7 @@ export class Series<T extends TelemValue = TelemValue>
|
|
|
634
707
|
throw new Error("cannot calculate maximum on a variable length data type");
|
|
635
708
|
if (this.writePos === 0) return -Infinity;
|
|
636
709
|
this.cachedMax ??= this.calcRawMax();
|
|
637
|
-
return
|
|
710
|
+
return this.applyOffset(this.cachedMax);
|
|
638
711
|
}
|
|
639
712
|
|
|
640
713
|
private calcRawMin(): math.Numeric {
|
|
@@ -660,7 +733,7 @@ export class Series<T extends TelemValue = TelemValue>
|
|
|
660
733
|
throw new Error("cannot calculate minimum on a variable length data type");
|
|
661
734
|
if (this.writePos === 0) return Infinity;
|
|
662
735
|
this.cachedMin ??= this.calcRawMin();
|
|
663
|
-
return
|
|
736
|
+
return this.applyOffset(this.cachedMin);
|
|
664
737
|
}
|
|
665
738
|
|
|
666
739
|
/** @returns the bounds of the series. */
|
|
@@ -717,7 +790,12 @@ export class Series<T extends TelemValue = TelemValue>
|
|
|
717
790
|
at(index: number, required?: false): T | undefined;
|
|
718
791
|
|
|
719
792
|
at(index: number, required: boolean = false): T | undefined {
|
|
720
|
-
if (this.dataType.isVariable)
|
|
793
|
+
if (this.dataType.isVariable) {
|
|
794
|
+
const str = this.atVariable(index, required);
|
|
795
|
+
if (str == null) return undefined;
|
|
796
|
+
if (this.dataType.equals(DataType.STRING)) return str as T;
|
|
797
|
+
return caseconv.snakeToCamel(JSON.parse(str)) as T;
|
|
798
|
+
}
|
|
721
799
|
if (this.dataType.equals(DataType.UUID)) return this.atUUID(index, required) as T;
|
|
722
800
|
if (index < 0) index = this.length + index;
|
|
723
801
|
const v = this.data[index];
|
|
@@ -725,7 +803,16 @@ export class Series<T extends TelemValue = TelemValue>
|
|
|
725
803
|
if (required === true) throw new Error(`[series] - no value at index ${index}`);
|
|
726
804
|
return undefined;
|
|
727
805
|
}
|
|
728
|
-
return
|
|
806
|
+
return this.applyOffset(v) as T;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// For huge bigint offsets (i64 timestamps narrowed to float32 for GL), math.add would
|
|
810
|
+
// coerce through Number() and lose precision above 2^53; do the addition in bigint
|
|
811
|
+
// space when the offset is a bigint.
|
|
812
|
+
private applyOffset(v: math.Numeric): math.Numeric {
|
|
813
|
+
if (typeof this.sampleOffset === "bigint" && typeof v === "number")
|
|
814
|
+
return BigInt(Math.round(v)) + this.sampleOffset;
|
|
815
|
+
return math.add(v, this.sampleOffset);
|
|
729
816
|
}
|
|
730
817
|
|
|
731
818
|
private atUUID(index: number, required: boolean): string | undefined {
|
|
@@ -740,33 +827,56 @@ export class Series<T extends TelemValue = TelemValue>
|
|
|
740
827
|
return uuidString;
|
|
741
828
|
}
|
|
742
829
|
|
|
743
|
-
|
|
830
|
+
asString(index: number, required: true): string;
|
|
831
|
+
|
|
832
|
+
asString(index: number, required?: false): string | undefined;
|
|
833
|
+
|
|
834
|
+
asString(index: number, required: boolean = false): string | undefined {
|
|
835
|
+
if (this.dataType.isVariable) return this.atVariable(index, required);
|
|
836
|
+
if (this.dataType.equals(DataType.UUID)) return this.atUUID(index, required);
|
|
837
|
+
const v = this.at(index, required as true);
|
|
838
|
+
if (v == null) return undefined;
|
|
839
|
+
if (this.dataType.equals(DataType.FLOAT32)) return stringifyFloat32(v as number);
|
|
840
|
+
return String(v);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
private atVariable(index: number, required: boolean): string | undefined {
|
|
744
844
|
let start = 0;
|
|
745
|
-
let
|
|
845
|
+
let len = 0;
|
|
846
|
+
const buf = this.buffer;
|
|
847
|
+
const view = new DataView(buf);
|
|
746
848
|
if (this._cachedIndexes != null) {
|
|
849
|
+
if (index < 0) index = this._cachedIndexes.length + index;
|
|
850
|
+
if (index < 0 || index >= this._cachedIndexes.length) {
|
|
851
|
+
if (required) throw new Error(`[series] - no value at index ${index}`);
|
|
852
|
+
return undefined;
|
|
853
|
+
}
|
|
747
854
|
start = this._cachedIndexes[index];
|
|
748
|
-
|
|
855
|
+
len = view.getUint32(start - UINT32_SIZE, true);
|
|
749
856
|
} else {
|
|
750
857
|
if (index < 0) index = this.length + index;
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
858
|
+
const byteLen = this.byteLength.valueOf();
|
|
859
|
+
let offset = 0;
|
|
860
|
+
let found = false;
|
|
861
|
+
while (offset + UINT32_SIZE <= byteLen) {
|
|
862
|
+
const sampleLen = view.getUint32(offset, true);
|
|
863
|
+
offset += UINT32_SIZE;
|
|
864
|
+
if (index === 0) {
|
|
865
|
+
start = offset;
|
|
866
|
+
len = sampleLen;
|
|
867
|
+
found = true;
|
|
868
|
+
break;
|
|
759
869
|
}
|
|
760
|
-
|
|
761
|
-
|
|
870
|
+
offset += sampleLen;
|
|
871
|
+
index--;
|
|
872
|
+
}
|
|
873
|
+
if (!found) {
|
|
762
874
|
if (required) throw new Error(`[series] - no value at index ${index}`);
|
|
763
875
|
return undefined;
|
|
764
876
|
}
|
|
765
877
|
}
|
|
766
|
-
const slice =
|
|
767
|
-
|
|
768
|
-
return new TextDecoder().decode(slice) as T;
|
|
769
|
-
return caseconv.snakeToCamel(JSON.parse(new TextDecoder().decode(slice))) as T;
|
|
878
|
+
const slice = new Uint8Array(buf, start, len);
|
|
879
|
+
return new TextDecoder().decode(slice);
|
|
770
880
|
}
|
|
771
881
|
|
|
772
882
|
/**
|
|
@@ -1069,8 +1179,9 @@ class SubIterator<T> implements Iterator<T> {
|
|
|
1069
1179
|
|
|
1070
1180
|
class StringSeriesIterator implements Iterator<string> {
|
|
1071
1181
|
private readonly series: Series;
|
|
1072
|
-
private
|
|
1182
|
+
private byteOffset: number;
|
|
1073
1183
|
private readonly decoder: TextDecoder;
|
|
1184
|
+
private readonly view: DataView;
|
|
1074
1185
|
|
|
1075
1186
|
constructor(series: Series) {
|
|
1076
1187
|
if (!series.dataType.isVariable)
|
|
@@ -1078,18 +1189,21 @@ class StringSeriesIterator implements Iterator<string> {
|
|
|
1078
1189
|
"cannot create a variable series iterator for a non-variable series",
|
|
1079
1190
|
);
|
|
1080
1191
|
this.series = series;
|
|
1081
|
-
this.
|
|
1192
|
+
this.byteOffset = 0;
|
|
1082
1193
|
this.decoder = new TextDecoder();
|
|
1194
|
+
this.view = new DataView(series.buffer);
|
|
1083
1195
|
}
|
|
1084
1196
|
|
|
1085
1197
|
next(): IteratorResult<string> {
|
|
1086
|
-
const
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
const
|
|
1090
|
-
|
|
1091
|
-
this.
|
|
1092
|
-
|
|
1198
|
+
const byteLen = this.series.byteLength.valueOf();
|
|
1199
|
+
if (this.byteOffset + UINT32_SIZE > byteLen)
|
|
1200
|
+
return { done: true, value: undefined };
|
|
1201
|
+
const len = this.view.getUint32(this.byteOffset, true);
|
|
1202
|
+
this.byteOffset += UINT32_SIZE;
|
|
1203
|
+
const s = this.decoder.decode(
|
|
1204
|
+
new Uint8Array(this.series.buffer, this.byteOffset, len),
|
|
1205
|
+
);
|
|
1206
|
+
this.byteOffset += len;
|
|
1093
1207
|
return { done: false, value: s };
|
|
1094
1208
|
}
|
|
1095
1209
|
}
|