@synnaxlabs/client 0.17.3 → 0.17.5

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.
@@ -1,21 +1,23 @@
1
- import { type Key, type Params } from '../channel/payload';
1
+ import { type CrudeSeries } from "@synnaxlabs/x";
2
+ import { type Key, type Params, type KeyOrName } from '../channel/payload';
2
3
  import { type Retriever } from '../channel/retriever';
3
- import { type Frame } from './frame';
4
- export declare class BackwardFrameAdapter {
4
+ import { type CrudeFrame, Frame } from './frame';
5
+ export declare class ReadFrameAdapter {
5
6
  private adapter;
6
7
  retriever: Retriever;
7
8
  keys: Key[];
8
9
  private constructor();
9
- static open(retriever: Retriever, channels: Params): Promise<BackwardFrameAdapter>;
10
+ static open(retriever: Retriever, channels: Params): Promise<ReadFrameAdapter>;
10
11
  update(channels: Params): Promise<void>;
11
- adapt(fr: Frame): Frame;
12
+ adapt(columnsOrData: Frame): Frame;
12
13
  }
13
- export declare class ForwardFrameAdapter {
14
+ export declare class WriteFrameAdapter {
14
15
  private adapter;
15
16
  retriever: Retriever;
16
17
  keys: Key[];
17
18
  private constructor();
18
- static open(retriever: Retriever, channels: Params): Promise<ForwardFrameAdapter>;
19
+ static open(retriever: Retriever, channels: Params): Promise<WriteFrameAdapter>;
19
20
  update(channels: Params): Promise<void>;
20
- adapt(fr: Frame): Frame;
21
+ private fetchChannel;
22
+ adapt(columnsOrData: Params | Record<KeyOrName, CrudeSeries> | CrudeFrame, series?: CrudeSeries | CrudeSeries[]): Promise<Frame>;
21
23
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { type StreamClient } from "@synnaxlabs/freighter";
2
- import { type NativeTypedArray, type Series, type TimeRange, type CrudeTimeStamp, TimeStamp } from "@synnaxlabs/x";
2
+ import { type TypedArray, type Series, type TimeRange, type CrudeTimeStamp, TimeStamp } from "@synnaxlabs/x";
3
3
  import { type KeyOrName, type Params } from '../channel/payload';
4
4
  import { type Retriever } from '../channel/retriever';
5
5
  import { Frame } from './frame';
@@ -37,7 +37,7 @@ export declare class Client {
37
37
  * data type as the channel.
38
38
  * @throws if the channel does not exist.
39
39
  */
40
- write(to: KeyOrName, start: CrudeTimeStamp, data: NativeTypedArray): Promise<void>;
40
+ write(to: KeyOrName, start: CrudeTimeStamp, data: TypedArray): Promise<void>;
41
41
  read(tr: TimeRange, channel: KeyOrName): Promise<Series>;
42
42
  read(tr: TimeRange, channels: Params): Promise<Frame>;
43
43
  private readFrame;
@@ -1,4 +1,4 @@
1
- import { Size, Series, TimeRange, DataType, TimeStamp } from "@synnaxlabs/x";
1
+ import { Size, Series, TimeRange, DataType, TimeStamp, type TelemValue } from "@synnaxlabs/x";
2
2
  import { z } from "zod";
3
3
  import { type KeyOrName, type Keys, type Names, type Params } from '../channel/payload';
4
4
  type ColumnType = "key" | "name" | null;
@@ -14,7 +14,7 @@ export type CrudeFrame = Frame | FramePayload | Map<KeyOrName, Series[] | Series
14
14
  *
15
15
  * - A frame is weakly aligned if it meets the time range occupied by all arrays of a
16
16
  * particular channel is the same for all channels in the frame. This means that the
17
- * arrays for a particular channel can have gaps betwen them.
17
+ * arrays for a particular channel can have gaps between them.
18
18
  *
19
19
  * - A strongly aligned frame means that all channels share the same rate/index and
20
20
  * there are no gaps in time between arrays. Strongly aligned frames are natural
@@ -98,6 +98,7 @@ export declare class Frame {
98
98
  */
99
99
  get isWeaklyAligned(): boolean;
100
100
  timeRange(col?: KeyOrName): TimeRange;
101
+ latest(): Record<string, TelemValue>;
101
102
  get timeRanges(): TimeRange[];
102
103
  /**
103
104
  * @returns lazy arrays matching the given channel key or name.
@@ -1,7 +1,7 @@
1
1
  import type { StreamClient } from "@synnaxlabs/freighter";
2
- import { type NativeTypedArray, TimeStamp, type CrudeTimeStamp } from "@synnaxlabs/x";
2
+ import { TimeStamp, type CrudeTimeStamp, type CrudeSeries } from "@synnaxlabs/x";
3
3
  import { z } from "zod";
4
- import { type Key, type KeyOrName, type Params } from '../channel/payload';
4
+ import { type KeysOrNames, type Key, type KeyOrName, type Params } from '../channel/payload';
5
5
  import { type Retriever } from '../channel/retriever';
6
6
  import { Authority } from '../control/authority';
7
7
  import { type Subject as ControlSubject } from '../control/state';
@@ -428,7 +428,8 @@ export declare class Writer {
428
428
  private readonly adapter;
429
429
  private constructor();
430
430
  static _open(retriever: Retriever, client: StreamClient, { channels, authorities, controlSubject: subject, start, mode, }: WriterConfig): Promise<Writer>;
431
- write(channel: KeyOrName, data: NativeTypedArray): Promise<boolean>;
431
+ write(channel: KeyOrName, data: CrudeSeries): Promise<boolean>;
432
+ write(channel: KeysOrNames, data: CrudeSeries[]): Promise<boolean>;
432
433
  write(frame: CrudeFrame): Promise<boolean>;
433
434
  setAuthority(value: Record<Key, Authority>): Promise<boolean>;
434
435
  setMode(mode: WriterMode): Promise<boolean>;
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export { ontology } from './ontology';
10
10
  export { control } from './control';
11
11
  export { Authority } from './control/authority';
12
12
  export { DataType, Density, Rate, Series, TimeRange, TimeSpan, TimeStamp, } from "@synnaxlabs/x";
13
- export type { NativeTypedArray, CrudeDataType, CrudeDensity, CrudeRate, CrudeSize, CrudeTimeSpan, CrudeTimeStamp, SampleValue, TimeStampStringFormat, TZInfo, } from "@synnaxlabs/x";
13
+ export type { TypedArray, CrudeDataType, CrudeDensity, CrudeRate, CrudeSize, CrudeTimeSpan, CrudeTimeStamp, SampleValue, TimeStampStringFormat, TZInfo, } from "@synnaxlabs/x";
14
14
  export { workspace } from './workspace';
15
15
  export { ranger } from './ranger';
16
16
  export { label } from './label';
@@ -1,2 +1,2 @@
1
- import { DataType, NativeTypedArray } from "@synnaxlabs/x";
2
- export declare const randomSeries: (length: number, dataType: DataType) => NativeTypedArray;
1
+ import { type DataType, type TypedArray } from "@synnaxlabs/x";
2
+ export declare const randomSeries: (length: number, dataType: DataType) => TypedArray;
@@ -18,7 +18,11 @@ const client = new Synnax({
18
18
  });
19
19
 
20
20
  // We can just specify the names of the channels we'd like to stream from.
21
- const read_from = ["stream_write_example_time", "stream_write_example_data"]
21
+ const read_from = [
22
+ "stream_write_example_time",
23
+ "stream_write_example_data_1",
24
+ "stream_write_example_data_2"
25
+ ]
22
26
 
23
27
  const streamer = await client.telem.newStreamer(read_from);
24
28
 
@@ -31,7 +35,8 @@ try {
31
35
  for await (const frame of streamer)
32
36
  console.log({
33
37
  time: new TimeStamp(frame.get("stream_write_example_time")[0].at(0)).toString(),
34
- data: frame.get("stream_write_example_data")[0].at(0)
38
+ data1: frame.get("stream_write_example_data_1")[0].at(0),
39
+ data2: frame.get("stream_write_example_data_2")[0].at(0)
35
40
  })
36
41
  } finally {
37
42
  streamer.close();
@@ -9,13 +9,13 @@
9
9
  "version": "1.0.0",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
- "@synnaxlabs/client": "^0.17.2"
12
+ "@synnaxlabs/client": "^0.17.4"
13
13
  }
14
14
  },
15
15
  "node_modules/@grpc/grpc-js": {
16
- "version": "1.10.5",
17
- "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.5.tgz",
18
- "integrity": "sha512-9IS/wB9YESGysRzJ+OFcb/bDf7OmtLQ4l93LsT1/cJnqzTKHUJBwXm/dc0dLio4C/DMomx01dTZiQb7pau0hrw==",
16
+ "version": "1.10.6",
17
+ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.6.tgz",
18
+ "integrity": "sha512-xP58G7wDQ4TCmN/cMUHh00DS7SRDv/+lC+xFLrTkMIN8h55X5NhZMLYbvy7dSELP15qlI6hPhNCRWVMtZMwqLA==",
19
19
  "dependencies": {
20
20
  "@grpc/proto-loader": "^0.7.10",
21
21
  "@js-sdsl/ordered-map": "^4.4.2"
@@ -3017,9 +3017,9 @@
3017
3017
  }
3018
3018
  },
3019
3019
  "node_modules/@opentelemetry/propagation-utils": {
3020
- "version": "0.30.7",
3021
- "resolved": "https://registry.npmjs.org/@opentelemetry/propagation-utils/-/propagation-utils-0.30.7.tgz",
3022
- "integrity": "sha512-QkxOkuCQdq8YgJstEMF4ntSyr0ivCrcQc49uvO2pyccrniu2DwA+JD071aM4BXfNVSCeOuhIyW/3QPiZYl4zdA==",
3020
+ "version": "0.30.8",
3021
+ "resolved": "https://registry.npmjs.org/@opentelemetry/propagation-utils/-/propagation-utils-0.30.8.tgz",
3022
+ "integrity": "sha512-ZKjsUm//SvL8I9JS+bJpAXGpe0Fb+sO+AiWS0fb7EKKLEm3GoNAO7CDMs8GMZBZ91ElR3tBjdUKf/9MzUdYHBA==",
3023
3023
  "engines": {
3024
3024
  "node": ">=14"
3025
3025
  },
@@ -3122,12 +3122,12 @@
3122
3122
  }
3123
3123
  },
3124
3124
  "node_modules/@opentelemetry/resource-detector-alibaba-cloud": {
3125
- "version": "0.28.7",
3126
- "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.28.7.tgz",
3127
- "integrity": "sha512-7o/waBJ08JrKED4blHGyBPIN1HMM1KEvhbO1HmdA+tsUqsGwZdTjsdMKFW7hc1TvAu4AQEnuvMy/Q5OByVr95A==",
3125
+ "version": "0.28.8",
3126
+ "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.28.8.tgz",
3127
+ "integrity": "sha512-njdK7S90OX99H0nlDh45vmGU3Bn46JSNjciU18NefyU6R3Dq5ZUU13DWxSlrCBWAiH/+SFJyHQMFsXRnTOFp6w==",
3128
3128
  "dependencies": {
3129
3129
  "@opentelemetry/resources": "^1.0.0",
3130
- "@opentelemetry/semantic-conventions": "^1.0.0"
3130
+ "@opentelemetry/semantic-conventions": "^1.22.0"
3131
3131
  },
3132
3132
  "engines": {
3133
3133
  "node": ">=14"
@@ -3137,13 +3137,13 @@
3137
3137
  }
3138
3138
  },
3139
3139
  "node_modules/@opentelemetry/resource-detector-aws": {
3140
- "version": "1.4.0",
3141
- "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-1.4.0.tgz",
3142
- "integrity": "sha512-Cn8eQ/heLqrNrPuHGG7xUkk//VQt4hzVIPurmLlCI0wrDV6HR+yykBvRkJBuSdLzbjeQ/qNbGel9OvTmA6PBQA==",
3140
+ "version": "1.4.1",
3141
+ "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-1.4.1.tgz",
3142
+ "integrity": "sha512-QsPJwXDxlt+IWQazST7renk9KDOAK3hsywb0Mw6gEwgEoe12EvdVcT+mCknTc+hu5WzxiTmFMtnn1TWab7To1g==",
3143
3143
  "dependencies": {
3144
3144
  "@opentelemetry/core": "^1.0.0",
3145
3145
  "@opentelemetry/resources": "^1.0.0",
3146
- "@opentelemetry/semantic-conventions": "^1.0.0"
3146
+ "@opentelemetry/semantic-conventions": "^1.22.0"
3147
3147
  },
3148
3148
  "engines": {
3149
3149
  "node": ">=14"
@@ -3153,12 +3153,12 @@
3153
3153
  }
3154
3154
  },
3155
3155
  "node_modules/@opentelemetry/resource-detector-container": {
3156
- "version": "0.3.7",
3157
- "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.3.7.tgz",
3158
- "integrity": "sha512-AYqwffGVuGLuzzVOQMLNHTztwyvsep9noxN9HTQ/grwmJSWZ6851kNx+W735K7v6GZEDmXeLpBn+J3TeqKQUJA==",
3156
+ "version": "0.3.8",
3157
+ "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.3.8.tgz",
3158
+ "integrity": "sha512-YusLYSo8Rr1yKEhakaJ2XgbfbLses5t8YsSRHhO0b6SLliZOFq9ymxoWgtuwyabNCqMMJJYuvrE3Nq0AL6sbcQ==",
3159
3159
  "dependencies": {
3160
3160
  "@opentelemetry/resources": "^1.0.0",
3161
- "@opentelemetry/semantic-conventions": "^1.0.0"
3161
+ "@opentelemetry/semantic-conventions": "^1.22.0"
3162
3162
  },
3163
3163
  "engines": {
3164
3164
  "node": ">=14"
@@ -3168,9 +3168,9 @@
3168
3168
  }
3169
3169
  },
3170
3170
  "node_modules/@opentelemetry/resource-detector-gcp": {
3171
- "version": "0.29.7",
3172
- "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.29.7.tgz",
3173
- "integrity": "sha512-uUHKfoOgBCZCEPCU6FWnRrbYuz1miaeIfos0Xe38YuR06vQvddhqZ0tewYunJpfECfKEcjSjY0eDe2QIRLMkXw==",
3171
+ "version": "0.29.8",
3172
+ "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.29.8.tgz",
3173
+ "integrity": "sha512-jvFtsnrQI7CLDxehp6BxtudbZ3a0HZ+Dj/vFy251280efc15gQdlvFUqXbKBw6ODt/3o38ilw8e0qePJbm2eAg==",
3174
3174
  "dependencies": {
3175
3175
  "@opentelemetry/core": "^1.0.0",
3176
3176
  "@opentelemetry/resources": "^1.0.0",
@@ -3653,23 +3653,23 @@
3653
3653
  }
3654
3654
  },
3655
3655
  "node_modules/@synnaxlabs/client": {
3656
- "version": "0.17.2",
3657
- "resolved": "https://registry.npmjs.org/@synnaxlabs/client/-/client-0.17.2.tgz",
3658
- "integrity": "sha512-9r8C7KbWBHluBRnu8qw34hf+u8JW1CkM6VmAT+51N+m0z2AZA0pbK01MNNIjLQAZ1bAdKpzc57BxqSzCwIceGQ==",
3656
+ "version": "0.17.4",
3657
+ "resolved": "https://registry.npmjs.org/@synnaxlabs/client/-/client-0.17.4.tgz",
3658
+ "integrity": "sha512-fQ2VvuHMI43BO3x2E7Nl51ZhBOyMbSWeGrid/+TkGNVYyyhVh8oDQE0O5n5mRr6h+kLeKwAv3fH84zDQXTow6w==",
3659
3659
  "dependencies": {
3660
- "@synnaxlabs/freighter": "0.9.1",
3661
- "@synnaxlabs/x": "0.14.1",
3660
+ "@synnaxlabs/freighter": "0.9.2",
3661
+ "@synnaxlabs/x": "0.14.2",
3662
3662
  "async-mutex": "^0.4.0",
3663
3663
  "zod": "3.22.4"
3664
3664
  }
3665
3665
  },
3666
3666
  "node_modules/@synnaxlabs/freighter": {
3667
- "version": "0.9.1",
3668
- "resolved": "https://registry.npmjs.org/@synnaxlabs/freighter/-/freighter-0.9.1.tgz",
3669
- "integrity": "sha512-0ulbbzZ5XixGJgy/ri6ecl9qNrU2NrSj5Sb+vfSb3438CXeTmSY+Q1aJ8/Ksk5sl97AHbtKIGZyuV75AHX6RHw==",
3667
+ "version": "0.9.2",
3668
+ "resolved": "https://registry.npmjs.org/@synnaxlabs/freighter/-/freighter-0.9.2.tgz",
3669
+ "integrity": "sha512-GJ63tGtSKlqiDLI+82keEIKwKmCsMI7y5ibI1DGKS/cXgeYJFUmG4BjJFM4HfVQxt56o94UP+qP4acZcLDZwGg==",
3670
3670
  "dependencies": {
3671
3671
  "@synnaxlabs/alamos": "0.3.0",
3672
- "@synnaxlabs/x": "0.14.1",
3672
+ "@synnaxlabs/x": "0.14.2",
3673
3673
  "js-convert-case": "^4.2.0",
3674
3674
  "node-fetch": "2.6.11",
3675
3675
  "ws": "^8.15.1",
@@ -3680,9 +3680,9 @@
3680
3680
  }
3681
3681
  },
3682
3682
  "node_modules/@synnaxlabs/x": {
3683
- "version": "0.14.1",
3684
- "resolved": "https://registry.npmjs.org/@synnaxlabs/x/-/x-0.14.1.tgz",
3685
- "integrity": "sha512-+rLxNBvFwZjU0JVRslzGoB5OCixITlRIsfibRMGV/P5lUzylA1BKBiAsjrg8i4gWtTx3pQUyrunTFbiTe0HWmA==",
3683
+ "version": "0.14.2",
3684
+ "resolved": "https://registry.npmjs.org/@synnaxlabs/x/-/x-0.14.2.tgz",
3685
+ "integrity": "sha512-Js5czEi7oMhaQkDe3xGuGLbho7xBMoNIWJmrXI20lu+VbB/FyO1wVtfcdIIIZOLc3tUAdNw9ceuZXMOieRoH1g==",
3686
3686
  "dependencies": {
3687
3687
  "async-mutex": "^0.4.0",
3688
3688
  "js-convert-case": "^4.2.0",
@@ -4414,14 +4414,15 @@
4414
4414
  }
4415
4415
  },
4416
4416
  "node_modules/gaxios": {
4417
- "version": "6.3.0",
4418
- "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.3.0.tgz",
4419
- "integrity": "sha512-p+ggrQw3fBwH2F5N/PAI4k/G/y1art5OxKpb2J2chwNNHM4hHuAOtivjPuirMF4KNKwTTUal/lPfL2+7h2mEcg==",
4417
+ "version": "6.4.0",
4418
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.4.0.tgz",
4419
+ "integrity": "sha512-apAloYrY4dlBGlhauDAYSZveafb5U6+L9titing1wox6BvWM0TSXBp603zTrLpyLMGkrcFgohnUN150dFN/zOA==",
4420
4420
  "dependencies": {
4421
4421
  "extend": "^3.0.2",
4422
4422
  "https-proxy-agent": "^7.0.1",
4423
4423
  "is-stream": "^2.0.0",
4424
- "node-fetch": "^2.6.9"
4424
+ "node-fetch": "^2.6.9",
4425
+ "uuid": "^9.0.1"
4425
4426
  },
4426
4427
  "engines": {
4427
4428
  "node": ">=14"
@@ -4857,6 +4858,18 @@
4857
4858
  "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
4858
4859
  "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
4859
4860
  },
4861
+ "node_modules/uuid": {
4862
+ "version": "9.0.1",
4863
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
4864
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
4865
+ "funding": [
4866
+ "https://github.com/sponsors/broofa",
4867
+ "https://github.com/sponsors/ctavan"
4868
+ ],
4869
+ "bin": {
4870
+ "uuid": "dist/bin/uuid"
4871
+ }
4872
+ },
4860
4873
  "node_modules/webidl-conversions": {
4861
4874
  "version": "3.0.1",
4862
4875
  "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@@ -11,6 +11,6 @@
11
11
  "author": "",
12
12
  "license": "ISC",
13
13
  "dependencies": {
14
- "@synnaxlabs/client": "^0.17.2"
14
+ "@synnaxlabs/client": "^0.17.4"
15
15
  }
16
16
  }
@@ -23,12 +23,18 @@ const timeChannel = await client.channels.create({
23
23
  }, { retrieveIfNameExists: true });
24
24
 
25
25
  // Create a data channel that will be used to store our fake sensor data.
26
- const dataChannel = await client.channels.create({
27
- name: "stream_write_example_data",
26
+ const dataChannel1 = await client.channels.create({
27
+ name: "stream_write_example_data_1",
28
28
  dataType: "float32",
29
29
  index: timeChannel.key,
30
30
  }, { retrieveIfNameExists: true });
31
31
 
32
+ const dataChannel2 = await client.channels.create({
33
+ name: "stream_write_example_data_2",
34
+ dataType: "int32",
35
+ index: timeChannel.key,
36
+ }, { retrieveIfNameExists: true });
37
+
32
38
 
33
39
  // We'll start our write at the current time. This timestamps should be the same as or
34
40
  // just before the first timestamp we write.
@@ -44,7 +50,7 @@ const commitInterval = 500;
44
50
 
45
51
  const writer = await client.telem.newWriter({
46
52
  start,
47
- channels: [timeChannel.key, dataChannel.key]
53
+ channels: [timeChannel.key, dataChannel1.key, dataChannel2.key]
48
54
  });
49
55
 
50
56
  try {
@@ -53,12 +59,13 @@ try {
53
59
  await new Promise(resolve => setTimeout(resolve, roughRate.period.milliseconds));
54
60
  i++;
55
61
  const timestamp = TimeStamp.now();
56
- const data = Math.sin(i / 10);
57
- const fr = new framer.Frame({
58
- [timeChannel.key]: new Series({ data: new timeChannel.dataType.Array([timestamp]) }),
59
- [dataChannel.key]: new Series({ data: new dataChannel.dataType.Array([data]) })
62
+ const data2= i % 2;
63
+ const data1 = Math.sin(i / 10);
64
+ await writer.write({
65
+ [timeChannel.key]: timestamp,
66
+ [dataChannel1.key]: data1,
67
+ [dataChannel2.key]: data2,
60
68
  });
61
- await writer.write(fr);
62
69
 
63
70
  if (i % 60 == 0)
64
71
  console.log(`Writing sample ${i} at ${timestamp.toISOString()}`)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@synnaxlabs/client",
3
3
  "private": false,
4
- "version": "0.17.3",
4
+ "version": "0.17.5",
5
5
  "type": "module",
6
6
  "description": "The Client Library for Synnax",
7
7
  "repository": "https://github.com/synnaxlabs/synnax/tree/main/client/ts",
@@ -18,12 +18,13 @@
18
18
  "dependencies": {
19
19
  "async-mutex": "^0.4.0",
20
20
  "zod": "3.22.4",
21
- "@synnaxlabs/freighter": "0.9.1",
22
- "@synnaxlabs/x": "0.14.1"
21
+ "@synnaxlabs/freighter": "0.9.2",
22
+ "@synnaxlabs/x": "0.14.2"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^20.10.5",
26
26
  "@vitest/coverage-v8": "^1.2.2",
27
+ "eslint": "^8.55.0",
27
28
  "typescript": "^5.3.3",
28
29
  "vite": "^5.1.2",
29
30
  "vitest": "^1.2.2",
@@ -11,7 +11,7 @@ import { type UnaryClient } from "@synnaxlabs/freighter";
11
11
  import {
12
12
  DataType,
13
13
  Rate,
14
- type NativeTypedArray,
14
+ type TypedArray,
15
15
  type CrudeDensity,
16
16
  type Series,
17
17
  type TimeRange,
@@ -158,7 +158,7 @@ export class Channel {
158
158
  * @param start - The starting timestamp of the first sample in data.
159
159
  * @param data - THe telemetry to write to the channel.
160
160
  */
161
- async write(start: CrudeTimeStamp, data: NativeTypedArray): Promise<void> {
161
+ async write(start: CrudeTimeStamp, data: TypedArray): Promise<void> {
162
162
  return await this.framer.write(this.key, start, data);
163
163
  }
164
164
  }
@@ -170,7 +170,7 @@ export class Channel {
170
170
  */
171
171
  export class Client implements AsyncTermSearcher<string, Key, Channel> {
172
172
  private readonly frameClient: framer.Client;
173
- private readonly retriever: Retriever;
173
+ readonly retriever: Retriever;
174
174
  private readonly creator: Creator;
175
175
  private readonly client: UnaryClient;
176
176
 
@@ -247,7 +247,10 @@ export class Client implements AsyncTermSearcher<string, Key, Channel> {
247
247
  */
248
248
  async create(channels: NewPayload[], options?: CreateOptions): Promise<Channel[]>;
249
249
 
250
- async create(channels: NewPayload | NewPayload[], options: CreateOptions = {}): Promise<Channel | Channel[]> {
250
+ async create(
251
+ channels: NewPayload | NewPayload[],
252
+ options: CreateOptions = {},
253
+ ): Promise<Channel | Channel[]> {
251
254
  const { retrieveIfNameExists = false } = options;
252
255
  const single = !Array.isArray(channels);
253
256
  let toCreate = toArray(channels);
@@ -8,6 +8,7 @@
8
8
  // included in the file licenses/APL.txt.
9
9
  import type { UnaryClient } from "@synnaxlabs/freighter";
10
10
  import { debounce, toArray } from "@synnaxlabs/x";
11
+ import { Mutex } from "async-mutex";
11
12
  import { z } from "zod";
12
13
 
13
14
  import {
@@ -21,7 +22,7 @@ import {
21
22
  type Payload,
22
23
  payload,
23
24
  } from "@/channel/payload";
24
- import { Mutex } from "async-mutex";
25
+ import { QueryError, ValidationError } from "@/errors";
25
26
 
26
27
  const reqZ = z.object({
27
28
  leaseholder: z.number().optional(),
@@ -59,15 +60,20 @@ export class ClusterRetriever implements Retriever {
59
60
 
60
61
  async retrieve(channels: Params, rangeKey?: string): Promise<Payload[]> {
61
62
  const { variant, normalized } = analyzeParams(channels);
62
- return await this.execute({ [variant]: normalized, rangeKey});
63
+ return await this.execute({ [variant]: normalized, rangeKey });
63
64
  }
64
65
 
65
66
  async page(offset: number, limit: number, rangeKey?: string): Promise<Payload[]> {
66
- return await this.execute({ offset, limit, rangeKey});
67
+ return await this.execute({ offset, limit, rangeKey });
67
68
  }
68
69
 
69
70
  private async execute(request: Request): Promise<Payload[]> {
70
- const [res, err] = await this.client.send(ClusterRetriever.ENDPOINT, request, reqZ, resZ);
71
+ const [res, err] = await this.client.send(
72
+ ClusterRetriever.ENDPOINT,
73
+ request,
74
+ reqZ,
75
+ resZ,
76
+ );
71
77
  if (err != null) throw err;
72
78
  return res.channels;
73
79
  }
@@ -148,10 +154,17 @@ export type ParamAnalysisResult =
148
154
  };
149
155
 
150
156
  export const analyzeParams = (channels: Params): ParamAnalysisResult => {
151
- const normal = (toArray(channels) as KeysOrNames).filter((c) => c != 0);
157
+ let normal = (toArray(channels) as KeysOrNames).filter((c) => c !== 0);
158
+ let variant: "names" | "keys" = "keys";
159
+ if (typeof normal[0] === "string") {
160
+ if (isNaN(parseInt(normal[0]))) variant = "names";
161
+ else {
162
+ normal = normal.map((v) => parseInt(v as string));
163
+ }
164
+ }
152
165
  return {
153
166
  single: !Array.isArray(channels),
154
- variant: typeof normal[0] === "number" ? "keys" : "names",
167
+ variant,
155
168
  normalized: normal,
156
169
  actual: channels,
157
170
  } as const as ParamAnalysisResult;
@@ -187,8 +200,7 @@ export class DebouncedBatchRetriever implements Retriever {
187
200
  async retrieve(channels: Params): Promise<Payload[]> {
188
201
  const { normalized, variant } = analyzeParams(channels);
189
202
  // Bypass on name fetches for now.
190
- if (variant === "names")
191
- return await this.wrapped.retrieve(normalized);
203
+ if (variant === "names") return await this.wrapped.retrieve(normalized);
192
204
  // eslint-disable-next-line @typescript-eslint/promise-function-async
193
205
  const a = new Promise<Payload[]>((resolve, reject) => {
194
206
  void this.mu.runExclusive(() => {
@@ -216,3 +228,18 @@ export class DebouncedBatchRetriever implements Retriever {
216
228
  });
217
229
  }
218
230
  }
231
+
232
+ export const retrieveRequired = async (
233
+ r: Retriever,
234
+ params: Params,
235
+ ): Promise<Payload[]> => {
236
+ const { normalized } = analyzeParams(params);
237
+ const results = await r.retrieve(normalized);
238
+ const notFound: KeyOrName[] = [];
239
+ normalized.forEach((v) => {
240
+ if (results.find((c) => c.name === v || c.key === v) == null) notFound.push(v);
241
+ });
242
+ if (notFound.length > 0)
243
+ throw new QueryError(`Could not find channels: ${JSON.stringify(notFound)}`);
244
+ return results;
245
+ };
@@ -0,0 +1,118 @@
1
+ import { DataType, Series, TimeStamp } from "@synnaxlabs/x";
2
+ import { beforeAll, describe, expect, it } from "vitest";
3
+
4
+ import { type channel } from "@/channel";
5
+ import { WriteFrameAdapter } from "@/framer/adapter";
6
+ import { Frame } from "@/index";
7
+ import { newClient } from "@/setupspecs";
8
+
9
+ const client = newClient();
10
+
11
+ describe("WriteFrameAdapter", () => {
12
+ let timeCh: channel.Channel;
13
+ let dataCh: channel.Channel;
14
+ let adapter: WriteFrameAdapter;
15
+
16
+ beforeAll(async () => {
17
+ timeCh = await client.channels.create({
18
+ name: `time-${Math.random()}-${TimeStamp.now().toString()}`,
19
+ dataType: DataType.TIMESTAMP,
20
+ isIndex: true,
21
+ });
22
+ dataCh = await client.channels.create({
23
+ name: `data-${Math.random()}-${TimeStamp.now().toString()}`,
24
+ dataType: DataType.FLOAT32,
25
+ index: timeCh.key,
26
+ });
27
+
28
+ adapter = await WriteFrameAdapter.open(client.channels.retriever, [
29
+ timeCh.key,
30
+ dataCh.key,
31
+ ]);
32
+ });
33
+
34
+ it("should correctly adapt a record of keys to single values", async () => {
35
+ const ts = TimeStamp.now().valueOf();
36
+ const res = await adapter.adapt({
37
+ [timeCh.key]: ts,
38
+ [dataCh.key]: 1,
39
+ });
40
+ expect(res.columns).toHaveLength(2);
41
+ expect(res.series).toHaveLength(2);
42
+ expect(res.get(timeCh.key)).toHaveLength(1);
43
+ expect(res.get(dataCh.key)).toHaveLength(1);
44
+ expect(res.get(timeCh.key)[0].at(0)).toEqual(Number(ts));
45
+ expect(res.get(dataCh.key)[0].at(0)).toEqual(1);
46
+ });
47
+
48
+ it("should correctly adapt a record of names to single values", async () => {
49
+ const ts = TimeStamp.now().valueOf();
50
+ const res2 = await adapter.adapt({
51
+ [timeCh.name]: ts,
52
+ [dataCh.name]: 1,
53
+ });
54
+ expect(res2.columns).toHaveLength(2);
55
+ expect(res2.series).toHaveLength(2);
56
+ expect(res2.get(timeCh.key)).toHaveLength(1);
57
+ expect(res2.get(dataCh.key)).toHaveLength(1);
58
+ expect(res2.get(timeCh.key)[0].at(0)).toEqual(Number(ts));
59
+ expect(res2.get(dataCh.key)[0].at(0)).toEqual(1);
60
+ });
61
+
62
+ it("should correctly adapt a single name to a single series", async () => {
63
+ const res3 = await adapter.adapt(dataCh.name, new Series(1));
64
+ expect(res3.columns).toHaveLength(1);
65
+ expect(res3.series).toHaveLength(1);
66
+ expect(res3.get(dataCh.key)).toHaveLength(1);
67
+ expect(res3.get(dataCh.key)[0].at(0)).toEqual(1);
68
+ });
69
+
70
+ it("should correctly adapt multiple names to multiple series", async () => {
71
+ const ts = TimeStamp.now().valueOf();
72
+ const res4 = await adapter.adapt(
73
+ [timeCh.name, dataCh.name],
74
+ [new Series(ts), new Series(1)],
75
+ );
76
+ expect(res4.get(timeCh.key)).toHaveLength(1);
77
+ expect(res4.get(dataCh.key)).toHaveLength(1);
78
+ expect(res4.get(timeCh.key)[0].at(0)).toEqual(Number(ts));
79
+ expect(res4.get(dataCh.key)[0].at(0)).toEqual(1);
80
+ });
81
+
82
+ it("should correctly adapt a frame keyed by name", async () => {
83
+ const ts = TimeStamp.now().valueOf();
84
+ const fr = new Frame({
85
+ [timeCh.name]: new Series(ts),
86
+ [dataCh.name]: new Series(1),
87
+ });
88
+ const res = await adapter.adapt(fr);
89
+ expect(res.columns).toHaveLength(2);
90
+ expect(res.series).toHaveLength(2);
91
+ expect(res.get(timeCh.key)[0].at(0)).toEqual(Number(ts));
92
+ expect(res.get(dataCh.key)[0].at(0)).toEqual(1);
93
+ });
94
+
95
+ it("should not modify a frame keyed by key", async () => {
96
+ const ts = TimeStamp.now().valueOf();
97
+ const fr = new Frame({
98
+ [timeCh.key]: new Series(ts),
99
+ [dataCh.key]: new Series(1),
100
+ });
101
+ const res = await adapter.adapt(fr);
102
+ expect(res.columns).toHaveLength(2);
103
+ expect(res.series).toHaveLength(2);
104
+ expect(res.get(timeCh.key)[0].at(0)).toEqual(Number(ts));
105
+ expect(res.get(dataCh.key)[0].at(0)).toEqual(1);
106
+ });
107
+
108
+ it("should correctly adapt a map of series", async () => {
109
+ const ts = TimeStamp.now().valueOf();
110
+ const m = new Map();
111
+ m.set(timeCh.key, new Series(ts));
112
+ const res = await adapter.adapt(m);
113
+ expect(res.columns).toHaveLength(1);
114
+ expect(res.series).toHaveLength(1);
115
+ expect(res.get(timeCh.key)).toHaveLength(1);
116
+ expect(res.get(timeCh.key)[0].at(0)).toEqual(Number(ts));
117
+ });
118
+ });