@synnaxlabs/client 0.47.0 → 0.48.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 (39) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/dist/client.cjs +35 -35
  3. package/dist/client.js +4567 -4479
  4. package/dist/eslint.config.d.ts +3 -2
  5. package/dist/eslint.config.d.ts.map +1 -1
  6. package/dist/src/channel/payload.d.ts +1 -0
  7. package/dist/src/channel/payload.d.ts.map +1 -1
  8. package/dist/src/client.d.ts +9 -5
  9. package/dist/src/client.d.ts.map +1 -1
  10. package/dist/src/connection/checker.d.ts +2 -3
  11. package/dist/src/connection/checker.d.ts.map +1 -1
  12. package/dist/src/connection.spec.d.ts +2 -0
  13. package/dist/src/connection.spec.d.ts.map +1 -0
  14. package/dist/src/framer/adapter.d.ts +2 -2
  15. package/dist/src/framer/adapter.d.ts.map +1 -1
  16. package/dist/src/framer/frame.d.ts +1 -0
  17. package/dist/src/framer/frame.d.ts.map +1 -1
  18. package/dist/src/framer/streamer.d.ts.map +1 -1
  19. package/dist/src/index.d.ts +2 -2
  20. package/dist/src/index.d.ts.map +1 -1
  21. package/dist/src/testutil/client.d.ts +3 -3
  22. package/dist/src/testutil/client.d.ts.map +1 -1
  23. package/eslint.config.ts +3 -1
  24. package/package.json +8 -8
  25. package/src/auth/auth.spec.ts +13 -13
  26. package/src/channel/payload.ts +2 -1
  27. package/src/client.ts +24 -10
  28. package/src/connection/checker.ts +10 -10
  29. package/src/connection/connection.spec.ts +13 -13
  30. package/src/connection.spec.ts +145 -0
  31. package/src/framer/adapter.spec.ts +339 -1
  32. package/src/framer/adapter.ts +23 -13
  33. package/src/framer/frame.spec.ts +296 -0
  34. package/src/framer/frame.ts +20 -1
  35. package/src/framer/iterator.ts +1 -1
  36. package/src/framer/streamer.spec.ts +57 -0
  37. package/src/framer/streamer.ts +6 -3
  38. package/src/index.ts +9 -2
  39. package/src/testutil/client.ts +4 -4
@@ -543,4 +543,300 @@ describe("framer.Frame", () => {
543
543
  expect(digest[13][0]).toEqual(s3.digest);
544
544
  });
545
545
  });
546
+
547
+ describe("mapFilter", () => {
548
+ it("should filter out items based on the keep boolean", () => {
549
+ const f = new framer.Frame(
550
+ new Map([
551
+ [
552
+ 12,
553
+ [
554
+ new Series({
555
+ data: new Float32Array([1, 2, 3]),
556
+ timeRange: new TimeRange(40, 50000),
557
+ }),
558
+ ],
559
+ ],
560
+ [
561
+ 13,
562
+ [
563
+ new Series({
564
+ data: new Float32Array([4, 5, 6]),
565
+ timeRange: new TimeRange(500, 50001),
566
+ }),
567
+ ],
568
+ ],
569
+ [
570
+ 14,
571
+ [
572
+ new Series({
573
+ data: new Float32Array([7, 8, 9]),
574
+ timeRange: new TimeRange(600, 60000),
575
+ }),
576
+ ],
577
+ ],
578
+ ]),
579
+ );
580
+ const filtered = f.mapFilter((k, arr) => [k, arr, k !== 13]);
581
+ expect(filtered.columns).toEqual([12, 14]);
582
+ expect(filtered.series.length).toEqual(2);
583
+ });
584
+
585
+ it("should map keys to new values", () => {
586
+ const f = new framer.Frame(
587
+ new Map([
588
+ [
589
+ 12,
590
+ [
591
+ new Series({
592
+ data: new Float32Array([1, 2, 3]),
593
+ timeRange: new TimeRange(40, 50000),
594
+ }),
595
+ ],
596
+ ],
597
+ [
598
+ 13,
599
+ [
600
+ new Series({
601
+ data: new Float32Array([4, 5, 6]),
602
+ timeRange: new TimeRange(500, 50001),
603
+ }),
604
+ ],
605
+ ],
606
+ ]),
607
+ );
608
+ const mapped = f.mapFilter((k, arr) => {
609
+ const newKey = typeof k === "number" ? k + 100 : k;
610
+ return [newKey, arr, true];
611
+ });
612
+ expect(mapped.columns).toEqual([112, 113]);
613
+ expect(mapped.series.length).toEqual(2);
614
+ });
615
+
616
+ it("should map series to new series", () => {
617
+ const f = new framer.Frame(
618
+ new Map([
619
+ [
620
+ 12,
621
+ [
622
+ new Series({
623
+ data: new Float32Array([1, 2, 3]),
624
+ timeRange: new TimeRange(40, 50000),
625
+ }),
626
+ ],
627
+ ],
628
+ ]),
629
+ );
630
+ const mapped = f.mapFilter((k, arr) => {
631
+ const newData = new Float32Array(arr.length);
632
+ for (let j = 0; j < arr.length; j++) newData[j] = (arr.at(j) as number) * 2;
633
+ const newArr = new Series({
634
+ data: newData,
635
+ timeRange: arr.timeRange,
636
+ });
637
+ return [k, newArr, true];
638
+ });
639
+ expect(mapped.get(12).at(0)).toEqual(2);
640
+ expect(mapped.get(12).at(1)).toEqual(4);
641
+ expect(mapped.get(12).at(2)).toEqual(6);
642
+ });
643
+
644
+ it("should both map and filter simultaneously", () => {
645
+ const f = new framer.Frame(
646
+ new Map([
647
+ [
648
+ 12,
649
+ [
650
+ new Series({
651
+ data: new Float32Array([1, 2, 3]),
652
+ timeRange: new TimeRange(40, 50000),
653
+ }),
654
+ ],
655
+ ],
656
+ [
657
+ 13,
658
+ [
659
+ new Series({
660
+ data: new Float32Array([4, 5, 6]),
661
+ timeRange: new TimeRange(500, 50001),
662
+ }),
663
+ ],
664
+ ],
665
+ [
666
+ 14,
667
+ [
668
+ new Series({
669
+ data: new Float32Array([7, 8, 9]),
670
+ timeRange: new TimeRange(600, 60000),
671
+ }),
672
+ ],
673
+ ],
674
+ ]),
675
+ );
676
+ const result = f.mapFilter((k, arr) => {
677
+ const keep = typeof k === "number" && k % 2 === 0;
678
+ const newKey = typeof k === "number" ? k + 1 : k;
679
+ return [newKey, arr, keep];
680
+ });
681
+ expect(result.columns).toEqual([13, 15]);
682
+ expect(result.series.length).toEqual(2);
683
+ });
684
+
685
+ it("should handle empty frames", () => {
686
+ const f = new framer.Frame();
687
+ const result = f.mapFilter((k, arr) => [k, arr, true]);
688
+ expect(result.columns).toEqual([]);
689
+ expect(result.series.length).toEqual(0);
690
+ });
691
+
692
+ it("should handle filtering out all items", () => {
693
+ const f = new framer.Frame(
694
+ new Map([
695
+ [
696
+ 12,
697
+ [
698
+ new Series({
699
+ data: new Float32Array([1, 2, 3]),
700
+ timeRange: new TimeRange(40, 50000),
701
+ }),
702
+ ],
703
+ ],
704
+ [
705
+ 13,
706
+ [
707
+ new Series({
708
+ data: new Float32Array([4, 5, 6]),
709
+ timeRange: new TimeRange(500, 50001),
710
+ }),
711
+ ],
712
+ ],
713
+ ]),
714
+ );
715
+ const result = f.mapFilter((k, arr) => [k, arr, false]);
716
+ expect(result.columns).toEqual([]);
717
+ expect(result.series.length).toEqual(0);
718
+ });
719
+
720
+ it("should keep all items when keep is always true", () => {
721
+ const f = new framer.Frame(
722
+ new Map([
723
+ [
724
+ 12,
725
+ [
726
+ new Series({
727
+ data: new Float32Array([1, 2, 3]),
728
+ timeRange: new TimeRange(40, 50000),
729
+ }),
730
+ ],
731
+ ],
732
+ [
733
+ 13,
734
+ [
735
+ new Series({
736
+ data: new Float32Array([4, 5, 6]),
737
+ timeRange: new TimeRange(500, 50001),
738
+ }),
739
+ ],
740
+ ],
741
+ ]),
742
+ );
743
+ const result = f.mapFilter((k, arr) => [k, arr, true]);
744
+ expect(result.columns).toEqual([12, 13]);
745
+ expect(result.series.length).toEqual(2);
746
+ });
747
+
748
+ it("should use the index parameter correctly", () => {
749
+ const f = new framer.Frame(
750
+ new Map([
751
+ [
752
+ 12,
753
+ [
754
+ new Series({
755
+ data: new Float32Array([1, 2, 3]),
756
+ timeRange: new TimeRange(40, 50000),
757
+ }),
758
+ ],
759
+ ],
760
+ [
761
+ 13,
762
+ [
763
+ new Series({
764
+ data: new Float32Array([4, 5, 6]),
765
+ timeRange: new TimeRange(500, 50001),
766
+ }),
767
+ ],
768
+ ],
769
+ [
770
+ 14,
771
+ [
772
+ new Series({
773
+ data: new Float32Array([7, 8, 9]),
774
+ timeRange: new TimeRange(600, 60000),
775
+ }),
776
+ ],
777
+ ],
778
+ ]),
779
+ );
780
+ // Keep only items at even indices
781
+ const result = f.mapFilter((k, arr, i) => [k, arr, i % 2 === 0]);
782
+ expect(result.columns).toEqual([12, 14]);
783
+ expect(result.series.length).toEqual(2);
784
+ });
785
+
786
+ it("should work with string keys", () => {
787
+ const f = new framer.Frame({
788
+ a: new Series({
789
+ data: new Float32Array([1, 2, 3]),
790
+ timeRange: new TimeRange(40, 50000),
791
+ }),
792
+ b: new Series({
793
+ data: new Float32Array([4, 5, 6]),
794
+ timeRange: new TimeRange(500, 50001),
795
+ }),
796
+ c: new Series({
797
+ data: new Float32Array([7, 8, 9]),
798
+ timeRange: new TimeRange(600, 60000),
799
+ }),
800
+ });
801
+ const result = f.mapFilter((k, arr) => {
802
+ const newKey = k === "a" ? "x" : k;
803
+ const keep = k === "a" || k === "c";
804
+ return [newKey, arr, keep];
805
+ });
806
+ expect(result.columns).toEqual(["x", "c"]);
807
+ expect(result.series.length).toEqual(2);
808
+ });
809
+
810
+ it("should handle frames with multiple series per channel", () => {
811
+ const f = new framer.Frame(
812
+ new Map([
813
+ [
814
+ 12,
815
+ [
816
+ new Series({
817
+ data: new Float32Array([1, 2, 3]),
818
+ timeRange: new TimeRange(40, 50000),
819
+ }),
820
+ new Series({
821
+ data: new Float32Array([4, 5, 6]),
822
+ timeRange: new TimeRange(50001, 60000),
823
+ }),
824
+ ],
825
+ ],
826
+ [
827
+ 13,
828
+ [
829
+ new Series({
830
+ data: new Float32Array([7, 8, 9]),
831
+ timeRange: new TimeRange(500, 50001),
832
+ }),
833
+ ],
834
+ ],
835
+ ]),
836
+ );
837
+ const result = f.mapFilter((k, arr) => [k, arr, k === 12]);
838
+ expect(result.columns).toEqual([12, 12]);
839
+ expect(result.series.length).toEqual(2);
840
+ });
841
+ });
546
842
  });
@@ -305,7 +305,11 @@ export class Frame {
305
305
 
306
306
  push(keyOrFrame: channel.KeyOrName | Frame, ...v: Series[]): void {
307
307
  if (keyOrFrame instanceof Frame) {
308
- if (this.colType !== null && keyOrFrame.colType !== this.colType)
308
+ if (
309
+ keyOrFrame.colType != null &&
310
+ this.colType !== null &&
311
+ keyOrFrame.colType !== this.colType
312
+ )
309
313
  throw new ValidationError("keyVariant must match");
310
314
  this.series.push(...keyOrFrame.series);
311
315
  (this.columns as channel.Keys).push(...(keyOrFrame.columns as channel.Keys));
@@ -355,6 +359,21 @@ export class Frame {
355
359
  return frame;
356
360
  }
357
361
 
362
+ mapFilter(
363
+ fn: (
364
+ k: channel.KeyOrName,
365
+ arr: Series,
366
+ i: number,
367
+ ) => [channel.KeyOrName, Series, boolean],
368
+ ): Frame {
369
+ const frame = new Frame();
370
+ this.forEach((k, arr, i) => {
371
+ const [newK, newArr, keep] = fn(k, arr, i);
372
+ if (keep) frame.push(newK, newArr);
373
+ });
374
+ return frame;
375
+ }
376
+
358
377
  /**
359
378
  * Iterates over all series in the current frame.
360
379
  *
@@ -111,7 +111,7 @@ export class Iterator {
111
111
  const iter = new Iterator(stream, adapter);
112
112
  await iter.execute({
113
113
  command: Command.Open,
114
- keys: adapter.keys,
114
+ keys: Array.from(adapter.keys),
115
115
  bounds: new TimeRange(tr),
116
116
  chunkSize: opts.chunkSize ?? 1e5,
117
117
  });
@@ -175,6 +175,7 @@ describe("Streamer", () => {
175
175
  streamer.close();
176
176
  }
177
177
  });
178
+
178
179
  test("calculated channel with constant", async () => {
179
180
  // Create an index channel for timestamps
180
181
  const timeChannel = await client.channels.create({
@@ -274,6 +275,62 @@ describe("Streamer", () => {
274
275
  streamer.close();
275
276
  }
276
277
  });
278
+
279
+ describe("legacy calculations", async () => {
280
+ it("should correctly execute a calculation with a requires field", async () => {
281
+ const timeChannel = await client.channels.create({
282
+ name: "calc_test_time",
283
+ isIndex: true,
284
+ dataType: DataType.TIMESTAMP,
285
+ });
286
+
287
+ const [channelA, channelB] = await client.channels.create([
288
+ {
289
+ name: id.create(),
290
+ dataType: DataType.FLOAT64,
291
+ index: timeChannel.key,
292
+ },
293
+ {
294
+ name: id.create(),
295
+ dataType: DataType.FLOAT64,
296
+ index: timeChannel.key,
297
+ },
298
+ ]);
299
+
300
+ const calcChannel = await client.channels.create({
301
+ name: "test_calc",
302
+ dataType: DataType.FLOAT64,
303
+ virtual: true,
304
+ expression: `return ${channelA.name} + ${channelB.name}`,
305
+ requires: [channelA.key, channelB.key],
306
+ });
307
+
308
+ const streamer = await client.openStreamer(calcChannel.key);
309
+ await sleep.sleep(TimeSpan.milliseconds(10));
310
+
311
+ const startTime = TimeStamp.now();
312
+ const writer = await client.openWriter({
313
+ start: startTime,
314
+ channels: [timeChannel.key, channelA.key, channelB.key],
315
+ });
316
+
317
+ try {
318
+ await writer.write({
319
+ [timeChannel.key]: [startTime],
320
+ [channelA.key]: new Float64Array([2.5]),
321
+ [channelB.key]: new Float64Array([2.5]),
322
+ });
323
+
324
+ const frame = await streamer.read();
325
+
326
+ const calcData = Array.from(frame.get(calcChannel.key));
327
+ expect(calcData).toEqual([5.0]);
328
+ } finally {
329
+ await writer.close();
330
+ streamer.close();
331
+ }
332
+ });
333
+ });
277
334
  });
278
335
  });
279
336
 
@@ -106,7 +106,10 @@ export const createStreamOpener =
106
106
  client = client.withCodec(new WSStreamerCodec(adapter.codec));
107
107
  const stream = await client.stream("/frame/stream", reqZ, resZ);
108
108
  const streamer = new CoreStreamer(stream, adapter);
109
- stream.send({ keys: adapter.keys, downsampleFactor: cfg.downsampleFactor ?? 1 });
109
+ stream.send({
110
+ keys: Array.from(adapter.keys),
111
+ downsampleFactor: cfg.downsampleFactor,
112
+ });
110
113
  const [, err] = await stream.receive();
111
114
  if (err != null) throw err;
112
115
  return streamer;
@@ -137,7 +140,7 @@ class CoreStreamer implements Streamer {
137
140
  }
138
141
 
139
142
  get keys(): channel.Key[] {
140
- return this.adapter.keys;
143
+ return Array.from(this.adapter.keys);
141
144
  }
142
145
 
143
146
  async next(): Promise<IteratorResult<Frame, any>> {
@@ -158,7 +161,7 @@ class CoreStreamer implements Streamer {
158
161
  const hasChanged = await this.adapter.update(channels);
159
162
  if (!hasChanged) return;
160
163
  this.stream.send({
161
- keys: this.adapter.keys,
164
+ keys: Array.from(this.adapter.keys),
162
165
  downsampleFactor: this.downsampleFactor,
163
166
  });
164
167
  }
package/src/index.ts CHANGED
@@ -12,7 +12,14 @@ export { policy } from "@/access/policy";
12
12
  export { arc } from "@/arc";
13
13
  export { channel } from "@/channel";
14
14
  export { Channel, isCalculated } from "@/channel/client";
15
- export { default as Synnax, type SynnaxProps, synnaxPropsZ } from "@/client";
15
+ export {
16
+ checkConnection,
17
+ type CheckConnectionParams,
18
+ newConnectionChecker,
19
+ default as Synnax,
20
+ type SynnaxParams,
21
+ synnaxParamsZ,
22
+ } from "@/client";
16
23
  export * from "@/connection";
17
24
  export { control } from "@/control";
18
25
  export {
@@ -37,7 +44,7 @@ export { ontology } from "@/ontology";
37
44
  export { group } from "@/ontology/group";
38
45
  export { ranger } from "@/ranger";
39
46
  export { status } from "@/status";
40
- export { createTestClient, TEST_CLIENT_PROPS } from "@/testutil/client";
47
+ export { createTestClient, TEST_CLIENT_PARAMS } from "@/testutil/client";
41
48
  export { user } from "@/user";
42
49
  export { workspace } from "@/workspace";
43
50
  export { lineplot } from "@/workspace/lineplot";
@@ -9,9 +9,9 @@
9
9
 
10
10
  import { TimeSpan } from "@synnaxlabs/x";
11
11
 
12
- import Synnax, { type SynnaxProps } from "@/client";
12
+ import Synnax, { type SynnaxParams } from "@/client";
13
13
 
14
- export const TEST_CLIENT_PROPS: SynnaxProps = {
14
+ export const TEST_CLIENT_PARAMS: SynnaxParams = {
15
15
  host: "localhost",
16
16
  port: 9090,
17
17
  username: "synnax",
@@ -23,5 +23,5 @@ export const TEST_CLIENT_PROPS: SynnaxProps = {
23
23
  },
24
24
  };
25
25
 
26
- export const createTestClient = (props?: Partial<SynnaxProps>): Synnax =>
27
- new Synnax({ ...TEST_CLIENT_PROPS, ...props });
26
+ export const createTestClient = (params?: Partial<SynnaxParams>): Synnax =>
27
+ new Synnax({ ...TEST_CLIENT_PARAMS, ...params });