@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
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
10
|
import { DataType, Rate, TimeStamp } from "@synnaxlabs/x";
|
|
11
|
-
import { describe, test, expect } from "vitest";
|
|
11
|
+
import { describe, test, expect, it } from "vitest";
|
|
12
12
|
|
|
13
13
|
import { type channel } from "@/channel";
|
|
14
14
|
import { newClient } from "@/setupspecs";
|
|
@@ -26,9 +26,9 @@ const newChannel = async (): Promise<channel.Channel> =>
|
|
|
26
26
|
describe("Streamer", () => {
|
|
27
27
|
test("happy path", async () => {
|
|
28
28
|
const ch = await newChannel();
|
|
29
|
-
const streamer = await client.telem.
|
|
29
|
+
const streamer = await client.telem.openStreamer(ch.key);
|
|
30
30
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
31
|
-
const writer = await client.telem.
|
|
31
|
+
const writer = await client.telem.openWriter({
|
|
32
32
|
start: TimeStamp.now(),
|
|
33
33
|
channels: ch.key,
|
|
34
34
|
});
|
|
@@ -38,6 +38,18 @@ describe("Streamer", () => {
|
|
|
38
38
|
await writer.close();
|
|
39
39
|
}
|
|
40
40
|
const d = await streamer.read();
|
|
41
|
-
expect(d.get(ch.key)
|
|
41
|
+
expect(Array.from(d.get(ch.key))).toEqual([1, 2, 3]);
|
|
42
|
+
});
|
|
43
|
+
test("open with config", async () => {
|
|
44
|
+
const ch = await newChannel();
|
|
45
|
+
await expect(
|
|
46
|
+
client.telem.openStreamer({
|
|
47
|
+
channels: ch.key,
|
|
48
|
+
from: TimeStamp.now(),
|
|
49
|
+
}),
|
|
50
|
+
).resolves.not.toThrow();
|
|
51
|
+
});
|
|
52
|
+
it("should not throw an error when the streamer is opened with zero channels", async () => {
|
|
53
|
+
await expect(client.telem.openStreamer([])).resolves.not.toThrow();
|
|
42
54
|
});
|
|
43
55
|
});
|
package/src/framer/streamer.ts
CHANGED
|
@@ -29,6 +29,11 @@ const resZ = z.object({
|
|
|
29
29
|
|
|
30
30
|
const ENDPOINT = "/frame/stream";
|
|
31
31
|
|
|
32
|
+
export interface StreamerConfig {
|
|
33
|
+
channels: Params;
|
|
34
|
+
from?: CrudeTimeStamp;
|
|
35
|
+
}
|
|
36
|
+
|
|
32
37
|
export class Streamer implements AsyncIterator<Frame>, AsyncIterable<Frame> {
|
|
33
38
|
private readonly stream: StreamProxy<typeof reqZ, typeof resZ>;
|
|
34
39
|
private readonly adapter: ReadFrameAdapter;
|
|
@@ -46,15 +51,14 @@ export class Streamer implements AsyncIterator<Frame>, AsyncIterable<Frame> {
|
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
static async _open(
|
|
49
|
-
start: CrudeTimeStamp,
|
|
50
|
-
channels: Params,
|
|
51
54
|
retriever: Retriever,
|
|
52
55
|
client: StreamClient,
|
|
56
|
+
{ channels, from }: StreamerConfig,
|
|
53
57
|
): Promise<Streamer> {
|
|
54
58
|
const adapter = await ReadFrameAdapter.open(retriever, channels);
|
|
55
59
|
const stream = await client.stream(ENDPOINT, reqZ, resZ);
|
|
56
60
|
const streamer = new Streamer(stream, adapter);
|
|
57
|
-
stream.send({ start: new TimeStamp(
|
|
61
|
+
stream.send({ start: new TimeStamp(from), keys: adapter.keys });
|
|
58
62
|
return streamer;
|
|
59
63
|
}
|
|
60
64
|
|
|
@@ -29,7 +29,7 @@ describe("Writer", () => {
|
|
|
29
29
|
describe("Writer", () => {
|
|
30
30
|
test("basic write", async () => {
|
|
31
31
|
const ch = await newChannel();
|
|
32
|
-
const writer = await client.telem.
|
|
32
|
+
const writer = await client.telem.openWriter({ start: 0, channels: ch.key });
|
|
33
33
|
try {
|
|
34
34
|
await writer.write(ch.key, randomSeries(10, ch.dataType));
|
|
35
35
|
await writer.commit();
|
|
@@ -40,7 +40,7 @@ describe("Writer", () => {
|
|
|
40
40
|
});
|
|
41
41
|
test("write to unknown channel key", async () => {
|
|
42
42
|
const ch = await newChannel();
|
|
43
|
-
const writer = await client.telem.
|
|
43
|
+
const writer = await client.telem.openWriter({ start: 0, channels: ch.key });
|
|
44
44
|
await expect(
|
|
45
45
|
writer.write("billy bob", randomSeries(10, DataType.FLOAT64)),
|
|
46
46
|
).rejects.toThrow("Channel billy bob not found");
|
|
@@ -48,8 +48,8 @@ describe("Writer", () => {
|
|
|
48
48
|
});
|
|
49
49
|
test("stream when mode is set ot persist only", async () => {
|
|
50
50
|
const ch = await newChannel();
|
|
51
|
-
const stream = await client.telem.
|
|
52
|
-
const writer = await client.telem.
|
|
51
|
+
const stream = await client.telem.openStreamer(ch.key);
|
|
52
|
+
const writer = await client.telem.openWriter({
|
|
53
53
|
start: 0,
|
|
54
54
|
channels: ch.key,
|
|
55
55
|
mode: WriterMode.PersistOnly,
|
package/src/framer/writer.ts
CHANGED
|
@@ -74,8 +74,8 @@ const resZ = z.object({
|
|
|
74
74
|
type Response = z.infer<typeof resZ>;
|
|
75
75
|
|
|
76
76
|
export interface WriterConfig {
|
|
77
|
-
start: CrudeTimeStamp;
|
|
78
77
|
channels: Params;
|
|
78
|
+
start?: CrudeTimeStamp;
|
|
79
79
|
controlSubject?: ControlSubject;
|
|
80
80
|
authorities?: Authority | Authority[];
|
|
81
81
|
mode?: WriterMode;
|
|
@@ -83,35 +83,35 @@ export interface WriterConfig {
|
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* Writer is used to write telemetry to a set of channels in time order.
|
|
86
|
-
* It should not be instantiated directly, and should instead be
|
|
86
|
+
* It should not be instantiated directly, and should instead be instantiated via the
|
|
87
87
|
* FramerClient {@link FrameClient#openWriter}.
|
|
88
88
|
*
|
|
89
|
-
* The writer is a streaming protocol that is heavily optimized for
|
|
90
|
-
* comes at the cost of
|
|
91
|
-
* writing large volumes of data (such as recording telemetry from a sensor or
|
|
92
|
-
* data
|
|
89
|
+
* The writer is a streaming protocol that is heavily optimized for performance. This
|
|
90
|
+
* comes at the cost of increased complexity, and should only be used directly when
|
|
91
|
+
* writing large volumes of data (such as recording telemetry from a sensor or ingesting
|
|
92
|
+
* data from file). Simpler methods (such as the frame client's write method) should
|
|
93
93
|
* be used for most use cases.
|
|
94
94
|
*
|
|
95
95
|
* The protocol is as follows:
|
|
96
96
|
*
|
|
97
97
|
* 1. The writer is opened with a starting timestamp and a list of channel keys. The
|
|
98
|
-
* writer will fail to open if the starting
|
|
99
|
-
* for any channels specified. If the writer opens
|
|
98
|
+
* writer will fail to open if the starting timestamp overlaps with any existing telemetry
|
|
99
|
+
* for any channels specified. If the writer opens successfully, the caller is then
|
|
100
100
|
* free to write frames to the writer.
|
|
101
101
|
*
|
|
102
102
|
* 2. To write a frame, the caller can use the write method and follow the validation
|
|
103
103
|
* rules described in its method's documentation. This process is asynchronous, meaning
|
|
104
104
|
* that write calls may return before teh frame has been written to the cluster. This
|
|
105
105
|
* also means that the writer can accumulate an error after write is called. If the writer
|
|
106
|
-
* accumulates an
|
|
107
|
-
* caller can check for errors by calling the error
|
|
106
|
+
* accumulates an error, all subsequent write and commit calls will return False. The
|
|
107
|
+
* caller can check for errors by calling the error method, which returns the accumulated
|
|
108
108
|
* error and resets the writer for future use. The caller can also check for errors by
|
|
109
109
|
* closing the writer, which will throw any accumulated error.
|
|
110
110
|
*
|
|
111
111
|
* 3. To commit the written frames to the cluster, the caller can call the commit method.
|
|
112
112
|
* Unlike write, commit is synchronous, meaning that it will not return until the frames
|
|
113
|
-
* have been written to the cluster. If the writer has accumulated an
|
|
114
|
-
* return false. After the caller acknowledges the
|
|
113
|
+
* have been written to the cluster. If the writer has accumulated an error, commit will
|
|
114
|
+
* return false. After the caller acknowledges the error, they can attempt to commit again.
|
|
115
115
|
* Commit can be called several times throughout a writer's lifetime, and will only
|
|
116
116
|
* commit the frames that have been written since the last commit.
|
|
117
117
|
*
|
|
@@ -137,10 +137,10 @@ export class Writer {
|
|
|
137
137
|
client: StreamClient,
|
|
138
138
|
{
|
|
139
139
|
channels,
|
|
140
|
+
start = TimeStamp.now(),
|
|
140
141
|
authorities = Authority.Absolute,
|
|
141
142
|
controlSubject: subject,
|
|
142
|
-
|
|
143
|
-
mode,
|
|
143
|
+
mode = WriterMode.PersistStream,
|
|
144
144
|
}: WriterConfig,
|
|
145
145
|
): Promise<Writer> {
|
|
146
146
|
const adapter = await WriteFrameAdapter.open(retriever, channels);
|
package/src/index.ts
CHANGED
|
@@ -35,6 +35,7 @@ export {
|
|
|
35
35
|
TimeRange,
|
|
36
36
|
TimeSpan,
|
|
37
37
|
TimeStamp,
|
|
38
|
+
MultiSeries,
|
|
38
39
|
} from "@synnaxlabs/x";
|
|
39
40
|
export type {
|
|
40
41
|
TypedArray,
|
|
@@ -44,7 +45,8 @@ export type {
|
|
|
44
45
|
CrudeSize,
|
|
45
46
|
CrudeTimeSpan,
|
|
46
47
|
CrudeTimeStamp,
|
|
47
|
-
|
|
48
|
+
TelemValue,
|
|
49
|
+
NumericTelemValue,
|
|
48
50
|
TimeStampStringFormat,
|
|
49
51
|
TZInfo,
|
|
50
52
|
} from "@synnaxlabs/x";
|
package/src/ontology/signals.ts
CHANGED
|
@@ -72,15 +72,15 @@ export class ChangeTracker {
|
|
|
72
72
|
if (allResources.length > 0) this.resourceObs.notify(resSets.concat(resDeletes));
|
|
73
73
|
const relSets = this.parseRelationshipSets(frame);
|
|
74
74
|
const relDeletes = this.parseRelationshipDeletes(frame);
|
|
75
|
-
const
|
|
76
|
-
if (
|
|
75
|
+
const allRelationships = relSets.concat(relDeletes);
|
|
76
|
+
if (allRelationships.length > 0)
|
|
77
|
+
this.relationshipObs.notify(relSets.concat(relDeletes));
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
private parseRelationshipSets(frame: Frame): RelationshipChange[] {
|
|
80
81
|
const relationships = frame.get(RELATIONSHIP_SET_NAME);
|
|
81
82
|
if (relationships.length === 0) return [];
|
|
82
|
-
|
|
83
|
-
return relationships[0].toStrings().map((rel) => ({
|
|
83
|
+
return Array.from(relationships.as("string")).map((rel) => ({
|
|
84
84
|
variant: "set",
|
|
85
85
|
key: parseRelationship(rel),
|
|
86
86
|
value: undefined,
|
|
@@ -90,8 +90,7 @@ export class ChangeTracker {
|
|
|
90
90
|
private parseRelationshipDeletes(frame: Frame): RelationshipChange[] {
|
|
91
91
|
const relationships = frame.get(RELATIONSHIP_DELETE_NAME);
|
|
92
92
|
if (relationships.length === 0) return [];
|
|
93
|
-
|
|
94
|
-
return relationships[0].toStrings().map((rel) => ({
|
|
93
|
+
return Array.from(relationships.as("string")).map((rel) => ({
|
|
95
94
|
variant: "delete",
|
|
96
95
|
key: parseRelationship(rel),
|
|
97
96
|
}));
|
|
@@ -101,7 +100,7 @@ export class ChangeTracker {
|
|
|
101
100
|
const sets = frame.get(RESOURCE_SET_NAME);
|
|
102
101
|
if (sets.length === 0) return [];
|
|
103
102
|
// We should only ever get one series of sets
|
|
104
|
-
const ids = sets
|
|
103
|
+
const ids = Array.from(sets.as("string")).map((id: string) => new ID(id));
|
|
105
104
|
try {
|
|
106
105
|
const resources = await this.retriever.retrieve(ids);
|
|
107
106
|
return resources.map((resource) => ({
|
|
@@ -122,13 +121,14 @@ export class ChangeTracker {
|
|
|
122
121
|
const deletes = frame.get(RESOURCE_DELETE_NAME);
|
|
123
122
|
if (deletes.length === 0) return [];
|
|
124
123
|
// We should only ever get one series of deletes
|
|
125
|
-
return deletes
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
return Array.from(deletes.as("string")).map((str) => ({
|
|
125
|
+
variant: "delete",
|
|
126
|
+
key: new ID(str),
|
|
127
|
+
}));
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
static async open(client: FrameClient, retriever: Retriever): Promise<ChangeTracker> {
|
|
131
|
-
const streamer = await client.
|
|
131
|
+
const streamer = await client.openStreamer([
|
|
132
132
|
RESOURCE_SET_NAME,
|
|
133
133
|
RESOURCE_DELETE_NAME,
|
|
134
134
|
RELATIONSHIP_SET_NAME,
|
|
@@ -55,11 +55,11 @@ export class Observable<K, V>
|
|
|
55
55
|
const changes: Array<change.Change<K, V>> = [];
|
|
56
56
|
if (this.deleteChannel != null) {
|
|
57
57
|
const deletes = frame.get(this.deleteChannel);
|
|
58
|
-
changes.push(...deletes.flatMap((s) => this.decoder("delete", s)));
|
|
58
|
+
changes.push(...deletes.series.flatMap((s) => this.decoder("delete", s)));
|
|
59
59
|
}
|
|
60
60
|
if (this.setChannel != null) {
|
|
61
61
|
const sets = frame.get(this.setChannel);
|
|
62
|
-
changes.push(...sets.flatMap((s) => this.decoder("set", s)));
|
|
62
|
+
changes.push(...sets.series.flatMap((s) => this.decoder("set", s)));
|
|
63
63
|
}
|
|
64
64
|
this.base.notify(changes);
|
|
65
65
|
}
|
|
@@ -71,7 +71,7 @@ export class Observable<K, V>
|
|
|
71
71
|
deleteChannel: channel.Key | channel.Name,
|
|
72
72
|
ecd: Decoder<K, V>,
|
|
73
73
|
): Promise<Observable<K, V>> {
|
|
74
|
-
const stream = await client.
|
|
74
|
+
const stream = await client.openStreamer([
|
|
75
75
|
setChannel,
|
|
76
76
|
deleteChannel,
|
|
77
77
|
] as channel.Keys);
|