@synnaxlabs/client 0.17.6 → 0.18.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 +5 -5
- package/dist/client.cjs +14 -14
- package/dist/client.cjs.map +1 -1
- package/dist/client.js +2497 -2352
- package/dist/client.js.map +1 -1
- package/dist/framer/client.d.ts +28 -7
- package/dist/framer/frame.d.ts +5 -3
- package/dist/framer/streamer.d.ts +5 -1
- package/dist/framer/writer.d.ts +13 -13
- package/dist/index.d.ts +2 -2
- package/examples/node/seriesAndFrames.js +0 -0
- package/package.json +5 -5
- package/src/control/state.ts +1 -1
- package/src/framer/adapter.spec.ts +12 -12
- package/src/framer/client.ts +43 -30
- package/src/framer/frame.spec.ts +106 -0
- package/src/framer/frame.ts +20 -13
- package/src/framer/iterator.spec.ts +3 -3
- package/src/framer/streamer.spec.ts +16 -4
- package/src/framer/streamer.ts +7 -3
- package/src/framer/writer.spec.ts +4 -4
- package/src/framer/writer.ts +14 -14
- package/src/index.ts +3 -1
- package/src/ontology/signals.ts +11 -11
- package/src/signals/observable.ts +3 -3
package/dist/framer/client.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { type StreamClient } from "@synnaxlabs/freighter";
|
|
2
|
-
import { type
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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
|
|
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(
|
|
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;
|
package/dist/framer/frame.d.ts
CHANGED
|
@@ -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):
|
|
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(
|
|
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>;
|
package/dist/framer/writer.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
393
|
-
* comes at the cost of
|
|
394
|
-
* writing large volumes of data (such as recording telemetry from a sensor or
|
|
395
|
-
* data
|
|
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
|
|
402
|
-
* for any channels specified. If the writer opens
|
|
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
|
|
410
|
-
* caller can check for errors by calling the error
|
|
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
|
|
417
|
-
* return false. After the caller acknowledges the
|
|
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,
|
|
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,
|
|
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';
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synnaxlabs/client",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.18.0",
|
|
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.
|
|
22
|
-
"@synnaxlabs/x": "0.
|
|
21
|
+
"@synnaxlabs/freighter": "0.9.3",
|
|
22
|
+
"@synnaxlabs/x": "0.15.0"
|
|
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
|
-
"
|
|
33
|
-
"
|
|
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",
|
package/src/control/state.ts
CHANGED
|
@@ -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.
|
|
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)
|
|
45
|
-
expect(res.get(dataCh.key)
|
|
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)
|
|
59
|
-
expect(res2.get(dataCh.key)
|
|
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)
|
|
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)
|
|
79
|
-
expect(res4.get(dataCh.key)
|
|
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)
|
|
92
|
-
expect(res.get(dataCh.key)
|
|
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)
|
|
105
|
-
expect(res.get(dataCh.key)
|
|
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)
|
|
116
|
+
expect(res.get(timeCh.key).at(0)).toEqual(Number(ts));
|
|
117
117
|
});
|
|
118
118
|
});
|
package/src/framer/client.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
56
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
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(
|
|
88
|
-
|
|
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:
|
|
103
|
+
channels: channel,
|
|
91
104
|
mode: WriterMode.PersistOnly,
|
|
92
105
|
});
|
|
93
106
|
try {
|
|
94
|
-
await w.write(
|
|
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.
|
|
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);
|
package/src/framer/frame.spec.ts
CHANGED
|
@@ -400,4 +400,110 @@ describe("framer.Frame", () => {
|
|
|
400
400
|
expect(f.latest()).toEqual({ 12: 3, 13: 3 });
|
|
401
401
|
});
|
|
402
402
|
});
|
|
403
|
+
|
|
404
|
+
describe("sample access", () => {
|
|
405
|
+
it("should return the sample at the given index", () => {
|
|
406
|
+
const f = new framer.Frame(
|
|
407
|
+
new Map([
|
|
408
|
+
[
|
|
409
|
+
12,
|
|
410
|
+
[
|
|
411
|
+
new Series({
|
|
412
|
+
data: new Float32Array([1, 2, 3]),
|
|
413
|
+
timeRange: new TimeRange(40, 50000),
|
|
414
|
+
}),
|
|
415
|
+
],
|
|
416
|
+
],
|
|
417
|
+
[
|
|
418
|
+
13,
|
|
419
|
+
[
|
|
420
|
+
new Series({
|
|
421
|
+
data: new Float32Array([1, 2, 3]),
|
|
422
|
+
timeRange: new TimeRange(500, 50001),
|
|
423
|
+
}),
|
|
424
|
+
],
|
|
425
|
+
],
|
|
426
|
+
]),
|
|
427
|
+
);
|
|
428
|
+
expect(f.get(12).at(0)).toEqual(1);
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
describe("at", () => {
|
|
433
|
+
it("should return the sample at the given index", () => {
|
|
434
|
+
const f = new framer.Frame(
|
|
435
|
+
new Map([
|
|
436
|
+
[
|
|
437
|
+
12,
|
|
438
|
+
[
|
|
439
|
+
new Series({
|
|
440
|
+
data: new Float32Array([1, 2, 3]),
|
|
441
|
+
timeRange: new TimeRange(40, 50000),
|
|
442
|
+
}),
|
|
443
|
+
],
|
|
444
|
+
],
|
|
445
|
+
[
|
|
446
|
+
13,
|
|
447
|
+
[
|
|
448
|
+
new Series({
|
|
449
|
+
data: new Float32Array([1, 2, 3]),
|
|
450
|
+
timeRange: new TimeRange(500, 50001),
|
|
451
|
+
}),
|
|
452
|
+
],
|
|
453
|
+
],
|
|
454
|
+
]),
|
|
455
|
+
);
|
|
456
|
+
expect(f.at(0)).toEqual({ 12: 1, 13: 1 });
|
|
457
|
+
});
|
|
458
|
+
it("should throw an error if required is true and the index is out of bounds", () => {
|
|
459
|
+
const f = new framer.Frame(
|
|
460
|
+
new Map([
|
|
461
|
+
[
|
|
462
|
+
12,
|
|
463
|
+
[
|
|
464
|
+
new Series({
|
|
465
|
+
data: new Float32Array([1, 2, 3]),
|
|
466
|
+
timeRange: new TimeRange(40, 50000),
|
|
467
|
+
}),
|
|
468
|
+
],
|
|
469
|
+
],
|
|
470
|
+
[
|
|
471
|
+
13,
|
|
472
|
+
[
|
|
473
|
+
new Series({
|
|
474
|
+
data: new Float32Array([1, 2, 3]),
|
|
475
|
+
timeRange: new TimeRange(500, 50001),
|
|
476
|
+
}),
|
|
477
|
+
],
|
|
478
|
+
],
|
|
479
|
+
]),
|
|
480
|
+
);
|
|
481
|
+
expect(() => f.at(3, true)).toThrow();
|
|
482
|
+
});
|
|
483
|
+
it("should return undefined if required is false and the index is out of bounds", () => {
|
|
484
|
+
const f = new framer.Frame(
|
|
485
|
+
new Map([
|
|
486
|
+
[
|
|
487
|
+
12,
|
|
488
|
+
[
|
|
489
|
+
new Series({
|
|
490
|
+
data: new Float32Array([1, 2, 3]),
|
|
491
|
+
timeRange: new TimeRange(40, 50000),
|
|
492
|
+
}),
|
|
493
|
+
],
|
|
494
|
+
],
|
|
495
|
+
[
|
|
496
|
+
13,
|
|
497
|
+
[
|
|
498
|
+
new Series({
|
|
499
|
+
data: new Float32Array([1, 2]),
|
|
500
|
+
timeRange: new TimeRange(500, 50001),
|
|
501
|
+
}),
|
|
502
|
+
],
|
|
503
|
+
],
|
|
504
|
+
]),
|
|
505
|
+
);
|
|
506
|
+
expect(f.at(2)).toEqual({ 12: 3, 13: undefined });
|
|
507
|
+
});
|
|
508
|
+
});
|
|
403
509
|
});
|
package/src/framer/frame.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
unique,
|
|
17
17
|
TimeStamp,
|
|
18
18
|
type TelemValue,
|
|
19
|
+
MultiSeries,
|
|
19
20
|
} from "@synnaxlabs/x";
|
|
20
21
|
import { z } from "zod";
|
|
21
22
|
|
|
@@ -34,6 +35,7 @@ const columnType = (columns: Params): ColumnType => {
|
|
|
34
35
|
const arrKeys = toArray(columns);
|
|
35
36
|
if (arrKeys.length === 0) return null;
|
|
36
37
|
if (typeof arrKeys[0] === "number") return "key";
|
|
38
|
+
if (!isNaN(parseInt(arrKeys[0]))) return "key";
|
|
37
39
|
return "name";
|
|
38
40
|
};
|
|
39
41
|
|
|
@@ -252,18 +254,11 @@ export class Frame {
|
|
|
252
254
|
}
|
|
253
255
|
const group = this.get(col);
|
|
254
256
|
if (group == null) return TimeRange.ZERO;
|
|
255
|
-
return
|
|
256
|
-
group[0].timeRange.start,
|
|
257
|
-
group[group.length - 1].timeRange.end,
|
|
258
|
-
);
|
|
257
|
+
return group.timeRange;
|
|
259
258
|
}
|
|
260
259
|
|
|
261
|
-
latest(): Record<string, TelemValue> {
|
|
262
|
-
return
|
|
263
|
-
this.columns
|
|
264
|
-
.map((c, i) => [c, this.series[i].at(-1)])
|
|
265
|
-
.filter(([_, v]) => v != null),
|
|
266
|
-
);
|
|
260
|
+
latest(): Record<string, TelemValue | undefined> {
|
|
261
|
+
return this.at(-1);
|
|
267
262
|
}
|
|
268
263
|
|
|
269
264
|
get timeRanges(): TimeRange[] {
|
|
@@ -274,7 +269,7 @@ export class Frame {
|
|
|
274
269
|
* @returns lazy arrays matching the given channel key or name.
|
|
275
270
|
* @param key the channel key or name.
|
|
276
271
|
*/
|
|
277
|
-
get(key: KeyOrName):
|
|
272
|
+
get(key: KeyOrName): MultiSeries;
|
|
278
273
|
|
|
279
274
|
/**
|
|
280
275
|
* @returns a frame with the given channel keys or names.
|
|
@@ -282,9 +277,9 @@ export class Frame {
|
|
|
282
277
|
*/
|
|
283
278
|
get(keys: Keys | Names): Frame;
|
|
284
279
|
|
|
285
|
-
get(key: KeyOrName | Keys | Names):
|
|
280
|
+
get(key: KeyOrName | Keys | Names): MultiSeries | Frame {
|
|
286
281
|
if (Array.isArray(key)) return this.filter((k) => (key as Keys).includes(k as Key));
|
|
287
|
-
return this.series.filter((_, i) => this.columns[i] === key);
|
|
282
|
+
return new MultiSeries(this.series.filter((_, i) => this.columns[i] === key));
|
|
288
283
|
}
|
|
289
284
|
|
|
290
285
|
/**
|
|
@@ -364,6 +359,18 @@ export class Frame {
|
|
|
364
359
|
});
|
|
365
360
|
}
|
|
366
361
|
|
|
362
|
+
at(index: number, required: true): Record<KeyOrName, TelemValue>;
|
|
363
|
+
|
|
364
|
+
at(index: number, required?: false): Record<KeyOrName, TelemValue | undefined>;
|
|
365
|
+
|
|
366
|
+
at(index: number, required = false): Record<KeyOrName, TelemValue | undefined> {
|
|
367
|
+
const res: Record<KeyOrName, TelemValue> = {};
|
|
368
|
+
this.uniqueColumns.forEach((k) => {
|
|
369
|
+
res[k] = this.get(k).at(index, required as true);
|
|
370
|
+
});
|
|
371
|
+
return res;
|
|
372
|
+
}
|
|
373
|
+
|
|
367
374
|
/**
|
|
368
375
|
* @returns a new frame containing all typed arrays in the current frame that pass
|
|
369
376
|
* the provided filter function.
|
|
@@ -28,7 +28,7 @@ const newChannel = async (): Promise<channel.Channel> => {
|
|
|
28
28
|
describe("Iterator", () => {
|
|
29
29
|
test("happy path", async () => {
|
|
30
30
|
const ch = await newChannel();
|
|
31
|
-
const writer = await client.telem.
|
|
31
|
+
const writer = await client.telem.openWriter({
|
|
32
32
|
start: TimeStamp.SECOND,
|
|
33
33
|
channels: ch.key,
|
|
34
34
|
});
|
|
@@ -42,7 +42,7 @@ describe("Iterator", () => {
|
|
|
42
42
|
await writer.close();
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const iter = await client.telem.
|
|
45
|
+
const iter = await client.telem.openIterator(
|
|
46
46
|
new TimeRange(TimeSpan.ZERO, TimeSpan.seconds(4)),
|
|
47
47
|
[ch.key],
|
|
48
48
|
);
|
|
@@ -52,7 +52,7 @@ describe("Iterator", () => {
|
|
|
52
52
|
let c = 0;
|
|
53
53
|
while (await iter.next(TimeSpan.seconds(1))) {
|
|
54
54
|
c++;
|
|
55
|
-
expect(iter.value.get(ch.key)
|
|
55
|
+
expect(iter.value.get(ch.key)).toHaveLength(25);
|
|
56
56
|
}
|
|
57
57
|
expect(c).toEqual(3);
|
|
58
58
|
} finally {
|