@synnaxlabs/client 0.17.6 → 0.18.1

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,10 +1,10 @@
1
1
  import { type StreamClient } from "@synnaxlabs/freighter";
2
- import { type TypedArray, type Series, type TimeRange, type CrudeTimeStamp, TimeStamp } from "@synnaxlabs/x";
2
+ import { type Series, type TimeRange, type CrudeTimeStamp, type CrudeSeries } 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';
6
6
  import { Iterator } from './iterator';
7
- import { Streamer } from './streamer';
7
+ import { Streamer, type StreamerConfig } from './streamer';
8
8
  import { Writer, type WriterConfig } from './writer';
9
9
  export declare class Client {
10
10
  private readonly stream;
@@ -17,7 +17,7 @@ export declare class Client {
17
17
  * @param keys - A list of channel keys to iterate over.
18
18
  * @returns a new {@link TypedIterator}.
19
19
  */
20
- newIterator(tr: TimeRange, channels: Params): Promise<Iterator>;
20
+ openIterator(tr: TimeRange, channels: Params): Promise<Iterator>;
21
21
  /**
22
22
  * Opens a new writer on the given channels.
23
23
  *
@@ -26,18 +26,39 @@ export declare class Client {
26
26
  * for more information.
27
27
  * @returns a new {@link RecordWriter}.
28
28
  */
29
- newWriter({ start, channels, controlSubject, authorities, mode, }: WriterConfig): Promise<Writer>;
30
- newStreamer(params: Params, from?: TimeStamp): Promise<Streamer>;
29
+ openWriter(config: WriterConfig): Promise<Writer>;
30
+ /***
31
+ * Opens a new streamer on the given channels.
32
+ *
33
+ * @param channels - A key, name, list of keys, or list of names of the channels to
34
+ * stream values from.
35
+ * @throws a QueryError if any of the given channels do not exist.
36
+ * @returns a new {@link Streamer} that must be closed when done streaming, otherwise
37
+ * a network socket will remain open.
38
+ */
39
+ openStreamer(channels: Params): Promise<Streamer>;
40
+ /**
41
+ * Opens a new streamer with the provided configuration.
42
+ *
43
+ * @param config - Configuration parameters for the streamer.
44
+ * @param config.channels - The channels to stream values from. Can be a key, name,
45
+ * list of keys, or list of names.
46
+ * @param config.from - If this parameter is set and is before the current time,
47
+ * the streamer will first read and receive historical data from before this point
48
+ * and then will start reading new values.
49
+ *
50
+ */
51
+ openStreamer(config: StreamerConfig): Promise<Streamer>;
31
52
  /**
32
53
  * Writes telemetry to the given channel starting at the given timestamp.
33
54
  *
34
- * @param to - The key of the channel to write to.
55
+ * @param channel - The key of the channel to write to.
35
56
  * @param start - The starting timestamp of the first sample in data.
36
57
  * @param data - The telemetry to write. This telemetry must have the same
37
58
  * data type as the channel.
38
59
  * @throws if the channel does not exist.
39
60
  */
40
- write(to: KeyOrName, start: CrudeTimeStamp, data: TypedArray): Promise<void>;
61
+ write(channel: KeyOrName, start: CrudeTimeStamp, data: CrudeSeries): Promise<void>;
41
62
  read(tr: TimeRange, channel: KeyOrName): Promise<Series>;
42
63
  read(tr: TimeRange, channels: Params): Promise<Frame>;
43
64
  private readFrame;
@@ -1,4 +1,4 @@
1
- import { Size, Series, TimeRange, DataType, TimeStamp, type TelemValue } from "@synnaxlabs/x";
1
+ import { Size, Series, TimeRange, DataType, TimeStamp, type TelemValue, MultiSeries } 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;
@@ -98,13 +98,13 @@ export declare class Frame {
98
98
  */
99
99
  get isWeaklyAligned(): boolean;
100
100
  timeRange(col?: KeyOrName): TimeRange;
101
- latest(): Record<string, TelemValue>;
101
+ latest(): Record<string, TelemValue | undefined>;
102
102
  get timeRanges(): TimeRange[];
103
103
  /**
104
104
  * @returns lazy arrays matching the given channel key or name.
105
105
  * @param key the channel key or name.
106
106
  */
107
- get(key: KeyOrName): Series[];
107
+ get(key: KeyOrName): MultiSeries;
108
108
  /**
109
109
  * @returns a frame with the given channel keys or names.
110
110
  * @param keys the channel keys or names.
@@ -145,6 +145,8 @@ export declare class Frame {
145
145
  * @param fn a function that takes a channel key and typed array.
146
146
  */
147
147
  forEach(fn: (k: KeyOrName, arr: Series, i: number) => void): void;
148
+ at(index: number, required: true): Record<KeyOrName, TelemValue>;
149
+ at(index: number, required?: false): Record<KeyOrName, TelemValue | undefined>;
148
150
  /**
149
151
  * @returns a new frame containing all typed arrays in the current frame that pass
150
152
  * the provided filter function.
@@ -3,12 +3,16 @@ import { type CrudeTimeStamp } from "@synnaxlabs/x";
3
3
  import { type Key, type Params } from '../channel/payload';
4
4
  import { type Retriever } from '../channel/retriever';
5
5
  import { Frame } from './frame';
6
+ export interface StreamerConfig {
7
+ channels: Params;
8
+ from?: CrudeTimeStamp;
9
+ }
6
10
  export declare class Streamer implements AsyncIterator<Frame>, AsyncIterable<Frame> {
7
11
  private readonly stream;
8
12
  private readonly adapter;
9
13
  private constructor();
10
14
  get keys(): Key[];
11
- static _open(start: CrudeTimeStamp, channels: Params, retriever: Retriever, client: StreamClient): Promise<Streamer>;
15
+ static _open(retriever: Retriever, client: StreamClient, { channels, from }: StreamerConfig): Promise<Streamer>;
12
16
  next(): Promise<IteratorResult<Frame, any>>;
13
17
  read(): Promise<Frame>;
14
18
  update(params: Params): Promise<void>;
@@ -378,43 +378,43 @@ declare const resZ: z.ZodObject<{
378
378
  }>;
379
379
  type Response = z.infer<typeof resZ>;
380
380
  export interface WriterConfig {
381
- start: CrudeTimeStamp;
382
381
  channels: Params;
382
+ start?: CrudeTimeStamp;
383
383
  controlSubject?: ControlSubject;
384
384
  authorities?: Authority | Authority[];
385
385
  mode?: WriterMode;
386
386
  }
387
387
  /**
388
388
  * Writer is used to write telemetry to a set of channels in time order.
389
- * It should not be instantiated directly, and should instead be instantited via the
389
+ * It should not be instantiated directly, and should instead be instantiated via the
390
390
  * FramerClient {@link FrameClient#openWriter}.
391
391
  *
392
- * The writer is a streaming protocol that is heavily optimized for prerformance. This
393
- * comes at the cost of icnreased complexity, and should only be used directly when
394
- * writing large volumes of data (such as recording telemetry from a sensor or ingsting
395
- * data froma file). Simpler methods (such as the frame client's write method) should
392
+ * The writer is a streaming protocol that is heavily optimized for performance. This
393
+ * comes at the cost of increased complexity, and should only be used directly when
394
+ * writing large volumes of data (such as recording telemetry from a sensor or ingesting
395
+ * data from file). Simpler methods (such as the frame client's write method) should
396
396
  * be used for most use cases.
397
397
  *
398
398
  * The protocol is as follows:
399
399
  *
400
400
  * 1. The writer is opened with a starting timestamp and a list of channel keys. The
401
- * writer will fail to open if the starting timstamp overlaps with any existing telemetry
402
- * for any channels specified. If the writer opens successfuly, the caller is then
401
+ * writer will fail to open if the starting timestamp overlaps with any existing telemetry
402
+ * for any channels specified. If the writer opens successfully, the caller is then
403
403
  * free to write frames to the writer.
404
404
  *
405
405
  * 2. To write a frame, the caller can use the write method and follow the validation
406
406
  * rules described in its method's documentation. This process is asynchronous, meaning
407
407
  * that write calls may return before teh frame has been written to the cluster. This
408
408
  * also means that the writer can accumulate an error after write is called. If the writer
409
- * accumulates an erorr, all subsequent write and commit calls will return False. The
410
- * caller can check for errors by calling the error mehtod, which returns the accumulated
409
+ * accumulates an error, all subsequent write and commit calls will return False. The
410
+ * caller can check for errors by calling the error method, which returns the accumulated
411
411
  * error and resets the writer for future use. The caller can also check for errors by
412
412
  * closing the writer, which will throw any accumulated error.
413
413
  *
414
414
  * 3. To commit the written frames to the cluster, the caller can call the commit method.
415
415
  * Unlike write, commit is synchronous, meaning that it will not return until the frames
416
- * have been written to the cluster. If the writer has accumulated an erorr, commit will
417
- * return false. After the caller acknowledges the erorr, they can attempt to commit again.
416
+ * have been written to the cluster. If the writer has accumulated an error, commit will
417
+ * return false. After the caller acknowledges the error, they can attempt to commit again.
418
418
  * Commit can be called several times throughout a writer's lifetime, and will only
419
419
  * commit the frames that have been written since the last commit.
420
420
  *
@@ -427,7 +427,7 @@ export declare class Writer {
427
427
  private readonly stream;
428
428
  private readonly adapter;
429
429
  private constructor();
430
- static _open(retriever: Retriever, client: StreamClient, { channels, authorities, controlSubject: subject, start, mode, }: WriterConfig): Promise<Writer>;
430
+ static _open(retriever: Retriever, client: StreamClient, { channels, start, authorities, controlSubject: subject, mode, }: WriterConfig): Promise<Writer>;
431
431
  write(channel: KeyOrName, data: CrudeSeries): Promise<boolean>;
432
432
  write(channel: KeysOrNames, data: CrudeSeries[]): Promise<boolean>;
433
433
  write(frame: CrudeFrame): Promise<boolean>;
package/dist/index.d.ts CHANGED
@@ -9,8 +9,8 @@ export { Frame } from './framer/frame';
9
9
  export { ontology } from './ontology';
10
10
  export { control } from './control';
11
11
  export { Authority } from './control/authority';
12
- export { DataType, Density, Rate, Series, TimeRange, TimeSpan, TimeStamp, } from "@synnaxlabs/x";
13
- export type { TypedArray, CrudeDataType, CrudeDensity, CrudeRate, CrudeSize, CrudeTimeSpan, CrudeTimeStamp, SampleValue, TimeStampStringFormat, TZInfo, } from "@synnaxlabs/x";
12
+ export { DataType, Density, Rate, Series, TimeRange, TimeSpan, TimeStamp, MultiSeries, } from "@synnaxlabs/x";
13
+ export type { TypedArray, CrudeDataType, CrudeDensity, CrudeRate, CrudeSize, CrudeTimeSpan, CrudeTimeStamp, TelemValue, NumericTelemValue, TimeStampStringFormat, TZInfo, } from "@synnaxlabs/x";
14
14
  export { workspace } from './workspace';
15
15
  export { ranger } from './ranger';
16
16
  export { label } from './label';
@@ -24,15 +24,16 @@ const read_from = [
24
24
  "stream_write_example_data_2"
25
25
  ]
26
26
 
27
- const streamer = await client.telem.newStreamer(read_from);
27
+ const streamer = await client.telem.openStreamer(read_from);
28
28
 
29
29
  // It's very important that we close the streamer when we're done with it to release
30
30
  // network connections and other resources, so we wrap the streaming loop in a try-finally
31
31
  // block.
32
32
  try {
33
33
  // Loop through the frames in the streamer. Each iteration will block until a new
34
- // frame is available, and then we'll just print it out.
35
- for await (const frame of streamer) console.log(frame.latest());
34
+ // frame is available, and then we'll just print out the last sample for each
35
+ // channel in the frame.
36
+ for await (const frame of streamer) console.log(frame.at(-1));
36
37
  } finally {
37
38
  streamer.close();
38
39
  // Close the client when we're done with it.
@@ -9,7 +9,7 @@
9
9
  "version": "1.0.0",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
- "@synnaxlabs/client": "^0.17.5"
12
+ "@synnaxlabs/client": "^0.18.0"
13
13
  }
14
14
  },
15
15
  "node_modules/@grpc/grpc-js": {
@@ -3653,23 +3653,23 @@
3653
3653
  }
3654
3654
  },
3655
3655
  "node_modules/@synnaxlabs/client": {
3656
- "version": "0.17.5",
3657
- "resolved": "https://registry.npmjs.org/@synnaxlabs/client/-/client-0.17.5.tgz",
3658
- "integrity": "sha512-HyQ/lzX4lekh5pg84A59tNjMHAYact3uvV+DUUJma+C3w4fgl4hT0U1DZ3IABiI02pNJEaiahMszg+2E9g358A==",
3656
+ "version": "0.18.0",
3657
+ "resolved": "https://registry.npmjs.org/@synnaxlabs/client/-/client-0.18.0.tgz",
3658
+ "integrity": "sha512-o53rR0awYUCcdSCENzo+6VjsEEt30ExHG0B1FLOk3+865eIJkvazS4RrRYpitrIxuzAGIK12v0vZ/qj8sGzlfA==",
3659
3659
  "dependencies": {
3660
- "@synnaxlabs/freighter": "0.9.2",
3661
- "@synnaxlabs/x": "0.14.2",
3660
+ "@synnaxlabs/freighter": "0.9.3",
3661
+ "@synnaxlabs/x": "0.15.0",
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.2",
3668
- "resolved": "https://registry.npmjs.org/@synnaxlabs/freighter/-/freighter-0.9.2.tgz",
3669
- "integrity": "sha512-GJ63tGtSKlqiDLI+82keEIKwKmCsMI7y5ibI1DGKS/cXgeYJFUmG4BjJFM4HfVQxt56o94UP+qP4acZcLDZwGg==",
3667
+ "version": "0.9.3",
3668
+ "resolved": "https://registry.npmjs.org/@synnaxlabs/freighter/-/freighter-0.9.3.tgz",
3669
+ "integrity": "sha512-H8a4HePVykwlkQMw8yClJ/BpydMDieBD2HHC2bBcvfJKZ3w32uiAqvhqezy14aU2XgNG92hU65IBi2FT6esBng==",
3670
3670
  "dependencies": {
3671
3671
  "@synnaxlabs/alamos": "0.3.0",
3672
- "@synnaxlabs/x": "0.14.2",
3672
+ "@synnaxlabs/x": "0.15.0",
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.2",
3684
- "resolved": "https://registry.npmjs.org/@synnaxlabs/x/-/x-0.14.2.tgz",
3685
- "integrity": "sha512-Js5czEi7oMhaQkDe3xGuGLbho7xBMoNIWJmrXI20lu+VbB/FyO1wVtfcdIIIZOLc3tUAdNw9ceuZXMOieRoH1g==",
3683
+ "version": "0.15.0",
3684
+ "resolved": "https://registry.npmjs.org/@synnaxlabs/x/-/x-0.15.0.tgz",
3685
+ "integrity": "sha512-baowvPsc0oKeMscKHh/5u0WVRB7J3ylIYzkx8feyP67QkjE8PCQpf0gmdThMGg6r0HwUbubzTcD0N64TNvahWw==",
3686
3686
  "dependencies": {
3687
3687
  "async-mutex": "^0.4.0",
3688
3688
  "js-convert-case": "^4.2.0",
@@ -3886,9 +3886,9 @@
3886
3886
  }
3887
3887
  },
3888
3888
  "node_modules/@types/node": {
3889
- "version": "20.12.3",
3890
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.3.tgz",
3891
- "integrity": "sha512-sD+ia2ubTeWrOu+YMF+MTAB7E+O7qsMqAbMfW7DG3K1URwhZ5hN1pLlRVGbf4wDFzSfikL05M17EyorS86jShw==",
3889
+ "version": "20.12.4",
3890
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz",
3891
+ "integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==",
3892
3892
  "dependencies": {
3893
3893
  "undici-types": "~5.26.4"
3894
3894
  }
@@ -11,6 +11,6 @@
11
11
  "author": "",
12
12
  "license": "ISC",
13
13
  "dependencies": {
14
- "@synnaxlabs/client": "^0.17.5"
14
+ "@synnaxlabs/client": "^0.18.0"
15
15
  }
16
16
  }
@@ -0,0 +1,37 @@
1
+ import { Series } from "@synnaxlabs/client";
2
+
3
+ // Construct a series from an array of numbers. In this case, the series will
4
+ // automatically be of type float64.
5
+ let series = new Series([1, 2, 3, 4, 5]);
6
+
7
+ // Construct a series from an array of numbers, but this time we specify the type
8
+ // explicitly.
9
+ series = new Series({ data: [1, 2, 3, 4, 5], dataType: "float32" });
10
+
11
+ // Construct a series from an array of strings. In this case, the series will
12
+ // automatically be of type string.
13
+ series = new Series(["apple", "banana", "cherry"]);
14
+
15
+ // Construct a series from a Float32Array. This is the most efficient way to
16
+ // construct a series from a large amount of data.
17
+ series = new Series(new Float32Array([1, 2, 3, 4, 5]));
18
+
19
+ // Construct a series from a JSON object. This is useful when you have a series
20
+ // that has been serialized to JSON.
21
+ series = new Series([{ red: "cherry" }, { yellow: "banana" }, {orange: "orange" }]);
22
+
23
+ series = new Series([1, "a", 3, "b", 5]);
24
+
25
+ series = new Series([1, 2, 3, 4, 5]);
26
+
27
+ console.log(series.at(0)); // 1
28
+ console.log(series.at(-1)); // 5
29
+
30
+ series = new Series([1, 2, 3, 4, 5]);
31
+ // Is it a number? Is it a string? Who knows?
32
+ let v = series.at(0);
33
+
34
+ series = new Series([1, 2, 3, 4, 5]);
35
+ let easierSeries = series.as("number");
36
+ // Now we have a guarantee that this is a series of numbers.
37
+ v = easierSeries.at(0);
@@ -48,7 +48,7 @@ const roughRate = Rate.hz(40);
48
48
  // historical reads every 500 samples.
49
49
  const commitInterval = 500;
50
50
 
51
- const writer = await client.telem.newWriter({
51
+ const writer = await client.telem.openWriter({
52
52
  start,
53
53
  channels: [timeChannel.key, dataChannel1.key, dataChannel2.key]
54
54
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@synnaxlabs/client",
3
3
  "private": false,
4
- "version": "0.17.6",
4
+ "version": "0.18.1",
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,8 +18,8 @@
18
18
  "dependencies": {
19
19
  "async-mutex": "^0.4.0",
20
20
  "zod": "3.22.4",
21
- "@synnaxlabs/freighter": "0.9.2",
22
- "@synnaxlabs/x": "0.14.2"
21
+ "@synnaxlabs/freighter": "0.9.3",
22
+ "@synnaxlabs/x": "0.15.1"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^20.10.5",
@@ -29,8 +29,8 @@
29
29
  "vite": "^5.1.2",
30
30
  "vitest": "^1.2.2",
31
31
  "@synnaxlabs/tsconfig": "0.0.2",
32
- "@synnaxlabs/vite-plugin": "0.0.1",
33
- "eslint-config-synnaxlabs": "0.0.1"
32
+ "eslint-config-synnaxlabs": "0.0.1",
33
+ "@synnaxlabs/vite-plugin": "0.0.1"
34
34
  },
35
35
  "main": "dist/client.cjs",
36
36
  "module": "dist/client.js",
@@ -111,7 +111,7 @@ export class StateTracker implements observe.Observable<Transfer[]> {
111
111
  }
112
112
 
113
113
  static async open(client: FrameClient): Promise<StateTracker> {
114
- const streamer = await client.newStreamer("sy_node_1_control");
114
+ const streamer = await client.openStreamer("sy_node_1_control");
115
115
  return new StateTracker(streamer);
116
116
  }
117
117
 
@@ -41,8 +41,8 @@ describe("WriteFrameAdapter", () => {
41
41
  expect(res.series).toHaveLength(2);
42
42
  expect(res.get(timeCh.key)).toHaveLength(1);
43
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);
44
+ expect(res.get(timeCh.key).at(0)).toEqual(Number(ts));
45
+ expect(res.get(dataCh.key).at(0)).toEqual(1);
46
46
  });
47
47
 
48
48
  it("should correctly adapt a record of names to single values", async () => {
@@ -55,8 +55,8 @@ describe("WriteFrameAdapter", () => {
55
55
  expect(res2.series).toHaveLength(2);
56
56
  expect(res2.get(timeCh.key)).toHaveLength(1);
57
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);
58
+ expect(res2.get(timeCh.key).at(0)).toEqual(Number(ts));
59
+ expect(res2.get(dataCh.key).at(0)).toEqual(1);
60
60
  });
61
61
 
62
62
  it("should correctly adapt a single name to a single series", async () => {
@@ -64,7 +64,7 @@ describe("WriteFrameAdapter", () => {
64
64
  expect(res3.columns).toHaveLength(1);
65
65
  expect(res3.series).toHaveLength(1);
66
66
  expect(res3.get(dataCh.key)).toHaveLength(1);
67
- expect(res3.get(dataCh.key)[0].at(0)).toEqual(1);
67
+ expect(res3.get(dataCh.key).at(0)).toEqual(1);
68
68
  });
69
69
 
70
70
  it("should correctly adapt multiple names to multiple series", async () => {
@@ -75,8 +75,8 @@ describe("WriteFrameAdapter", () => {
75
75
  );
76
76
  expect(res4.get(timeCh.key)).toHaveLength(1);
77
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);
78
+ expect(res4.get(timeCh.key).at(0)).toEqual(Number(ts));
79
+ expect(res4.get(dataCh.key).at(0)).toEqual(1);
80
80
  });
81
81
 
82
82
  it("should correctly adapt a frame keyed by name", async () => {
@@ -88,8 +88,8 @@ describe("WriteFrameAdapter", () => {
88
88
  const res = await adapter.adapt(fr);
89
89
  expect(res.columns).toHaveLength(2);
90
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);
91
+ expect(res.get(timeCh.key).at(0)).toEqual(Number(ts));
92
+ expect(res.get(dataCh.key).at(0)).toEqual(1);
93
93
  });
94
94
 
95
95
  it("should not modify a frame keyed by key", async () => {
@@ -101,8 +101,8 @@ describe("WriteFrameAdapter", () => {
101
101
  const res = await adapter.adapt(fr);
102
102
  expect(res.columns).toHaveLength(2);
103
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);
104
+ expect(res.get(timeCh.key).at(0)).toEqual(Number(ts));
105
+ expect(res.get(dataCh.key).at(0)).toEqual(1);
106
106
  });
107
107
 
108
108
  it("should correctly adapt a map of series", async () => {
@@ -113,6 +113,6 @@ describe("WriteFrameAdapter", () => {
113
113
  expect(res.columns).toHaveLength(1);
114
114
  expect(res.series).toHaveLength(1);
115
115
  expect(res.get(timeCh.key)).toHaveLength(1);
116
- expect(res.get(timeCh.key)[0].at(0)).toEqual(Number(ts));
116
+ expect(res.get(timeCh.key).at(0)).toEqual(Number(ts));
117
117
  });
118
118
  });
@@ -9,19 +9,17 @@
9
9
 
10
10
  import { type StreamClient } from "@synnaxlabs/freighter";
11
11
  import {
12
- type TypedArray,
13
12
  type Series,
14
13
  type TimeRange,
15
14
  type CrudeTimeStamp,
16
- TimeStamp,
15
+ type CrudeSeries,
17
16
  } from "@synnaxlabs/x";
18
17
 
19
18
  import { type KeyOrName, type Params } from "@/channel/payload";
20
19
  import { type Retriever, analyzeParams } from "@/channel/retriever";
21
- import { Authority } from "@/control/authority";
22
20
  import { Frame } from "@/framer/frame";
23
21
  import { Iterator } from "@/framer/iterator";
24
- import { Streamer } from "@/framer/streamer";
22
+ import { Streamer, type StreamerConfig } from "@/framer/streamer";
25
23
  import { Writer, WriterMode, type WriterConfig } from "@/framer/writer";
26
24
 
27
25
  export class Client {
@@ -40,7 +38,7 @@ export class Client {
40
38
  * @param keys - A list of channel keys to iterate over.
41
39
  * @returns a new {@link TypedIterator}.
42
40
  */
43
- async newIterator(tr: TimeRange, channels: Params): Promise<Iterator> {
41
+ async openIterator(tr: TimeRange, channels: Params): Promise<Iterator> {
44
42
  return await Iterator._open(tr, channels, this.retriever, this.stream);
45
43
  }
46
44
 
@@ -52,46 +50,61 @@ export class Client {
52
50
  * for more information.
53
51
  * @returns a new {@link RecordWriter}.
54
52
  */
55
- async newWriter({
56
- start,
57
- channels,
58
- controlSubject,
59
- authorities = Authority.Absolute,
60
- mode = WriterMode.PersistStream,
61
- }: WriterConfig): Promise<Writer> {
62
- return await Writer._open(this.retriever, this.stream, {
63
- start: start ?? TimeStamp.now(),
64
- controlSubject,
65
- channels,
66
- authorities,
67
- mode,
68
- });
53
+ async openWriter(config: WriterConfig): Promise<Writer> {
54
+ return await Writer._open(this.retriever, this.stream, config);
69
55
  }
70
56
 
71
- async newStreamer(
72
- params: Params,
73
- from: TimeStamp = TimeStamp.now(),
74
- ): Promise<Streamer> {
75
- return await Streamer._open(from, params, this.retriever, this.stream);
57
+ /***
58
+ * Opens a new streamer on the given channels.
59
+ *
60
+ * @param channels - A key, name, list of keys, or list of names of the channels to
61
+ * stream values from.
62
+ * @throws a QueryError if any of the given channels do not exist.
63
+ * @returns a new {@link Streamer} that must be closed when done streaming, otherwise
64
+ * a network socket will remain open.
65
+ */
66
+ async openStreamer(channels: Params): Promise<Streamer>;
67
+
68
+ /**
69
+ * Opens a new streamer with the provided configuration.
70
+ *
71
+ * @param config - Configuration parameters for the streamer.
72
+ * @param config.channels - The channels to stream values from. Can be a key, name,
73
+ * list of keys, or list of names.
74
+ * @param config.from - If this parameter is set and is before the current time,
75
+ * the streamer will first read and receive historical data from before this point
76
+ * and then will start reading new values.
77
+ *
78
+ */
79
+ async openStreamer(config: StreamerConfig): Promise<Streamer>;
80
+
81
+ async openStreamer(config: StreamerConfig | Params): Promise<Streamer> {
82
+ const isObject = typeof config === "object";
83
+ if (Array.isArray(config) || !isObject) config = { channels: config as Params };
84
+ return await Streamer._open(this.retriever, this.stream, config as StreamerConfig);
76
85
  }
77
86
 
78
87
  /**
79
88
  * Writes telemetry to the given channel starting at the given timestamp.
80
89
  *
81
- * @param to - The key of the channel to write to.
90
+ * @param channel - The key of the channel to write to.
82
91
  * @param start - The starting timestamp of the first sample in data.
83
92
  * @param data - The telemetry to write. This telemetry must have the same
84
93
  * data type as the channel.
85
94
  * @throws if the channel does not exist.
86
95
  */
87
- async write(to: KeyOrName, start: CrudeTimeStamp, data: TypedArray): Promise<void> {
88
- const w = await this.newWriter({
96
+ async write(
97
+ channel: KeyOrName,
98
+ start: CrudeTimeStamp,
99
+ data: CrudeSeries,
100
+ ): Promise<void> {
101
+ const w = await this.openWriter({
89
102
  start,
90
- channels: to,
103
+ channels: channel,
91
104
  mode: WriterMode.PersistOnly,
92
105
  });
93
106
  try {
94
- await w.write(to, data);
107
+ await w.write(channel, data);
95
108
  await w.commit();
96
109
  } finally {
97
110
  await w.close();
@@ -110,7 +123,7 @@ export class Client {
110
123
  }
111
124
 
112
125
  private async readFrame(tr: TimeRange, params: Params): Promise<Frame> {
113
- const i = await this.newIterator(tr, params);
126
+ const i = await this.openIterator(tr, params);
114
127
  const frame = new Frame();
115
128
  try {
116
129
  for await (const f of i) frame.push(f);