@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.
- package/.turbo/turbo-build.log +5 -5
- package/dist/channel/client.d.ts +3 -3
- package/dist/channel/retriever.d.ts +1 -0
- package/dist/client.cjs +26 -10
- package/dist/client.cjs.map +1 -1
- package/dist/client.js +4230 -4103
- package/dist/client.js.map +1 -1
- package/dist/framer/adapter.d.ts +10 -8
- package/dist/framer/adapter.spec.d.ts +1 -0
- package/dist/framer/client.d.ts +2 -2
- package/dist/framer/frame.d.ts +3 -2
- package/dist/framer/writer.d.ts +4 -3
- package/dist/index.d.ts +1 -1
- package/dist/util/telem.d.ts +2 -2
- package/examples/node/liveStream.js +7 -2
- package/examples/node/package-lock.json +51 -38
- package/examples/node/package.json +1 -1
- package/examples/node/streamWrite.js +15 -8
- package/package.json +4 -3
- package/src/channel/client.ts +7 -4
- package/src/channel/retriever.ts +35 -8
- package/src/framer/adapter.spec.ts +118 -0
- package/src/framer/adapter.ts +104 -45
- package/src/framer/client.ts +2 -6
- package/src/framer/frame.spec.ts +102 -31
- package/src/framer/frame.ts +21 -4
- package/src/framer/iterator.ts +4 -4
- package/src/framer/streamer.ts +4 -4
- package/src/framer/writer.spec.ts +1 -1
- package/src/framer/writer.ts +19 -16
- package/src/index.ts +1 -1
- package/src/util/telem.ts +2 -2
package/src/framer/adapter.ts
CHANGED
|
@@ -7,12 +7,20 @@
|
|
|
7
7
|
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
|
-
import { type
|
|
11
|
-
|
|
10
|
+
import { type CrudeSeries, Series } from "@synnaxlabs/x";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
type Key,
|
|
14
|
+
type Name,
|
|
15
|
+
type Params,
|
|
16
|
+
type KeyOrName,
|
|
17
|
+
type Payload,
|
|
18
|
+
} from "@/channel/payload";
|
|
19
|
+
import { type Retriever, analyzeParams, retrieveRequired } from "@/channel/retriever";
|
|
12
20
|
import { ValidationError } from "@/errors";
|
|
13
|
-
import { type Frame } from "@/framer/frame";
|
|
21
|
+
import { type CrudeFrame, Frame } from "@/framer/frame";
|
|
14
22
|
|
|
15
|
-
export class
|
|
23
|
+
export class ReadFrameAdapter {
|
|
16
24
|
private adapter: Map<Key, Name> | null;
|
|
17
25
|
retriever: Retriever;
|
|
18
26
|
keys: Key[];
|
|
@@ -23,11 +31,8 @@ export class BackwardFrameAdapter {
|
|
|
23
31
|
this.keys = [];
|
|
24
32
|
}
|
|
25
33
|
|
|
26
|
-
static async open(
|
|
27
|
-
retriever
|
|
28
|
-
channels: Params,
|
|
29
|
-
): Promise<BackwardFrameAdapter> {
|
|
30
|
-
const adapter = new BackwardFrameAdapter(retriever);
|
|
34
|
+
static async open(retriever: Retriever, channels: Params): Promise<ReadFrameAdapter> {
|
|
35
|
+
const adapter = new ReadFrameAdapter(retriever);
|
|
31
36
|
await adapter.update(channels);
|
|
32
37
|
return adapter;
|
|
33
38
|
}
|
|
@@ -50,10 +55,10 @@ export class BackwardFrameAdapter {
|
|
|
50
55
|
this.keys = Array.from(this.adapter.keys());
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
adapt(
|
|
54
|
-
if (this.adapter == null) return
|
|
58
|
+
adapt(columnsOrData: Frame): Frame {
|
|
59
|
+
if (this.adapter == null) return columnsOrData;
|
|
55
60
|
const a = this.adapter;
|
|
56
|
-
return
|
|
61
|
+
return columnsOrData.map((k, arr) => {
|
|
57
62
|
if (typeof k === "number") {
|
|
58
63
|
const name = a.get(k);
|
|
59
64
|
if (name == null) throw new Error(`Channel ${k} not found`);
|
|
@@ -64,7 +69,7 @@ export class BackwardFrameAdapter {
|
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
export class
|
|
72
|
+
export class WriteFrameAdapter {
|
|
68
73
|
private adapter: Map<Name, Key> | null;
|
|
69
74
|
retriever: Retriever;
|
|
70
75
|
keys: Key[];
|
|
@@ -78,46 +83,100 @@ export class ForwardFrameAdapter {
|
|
|
78
83
|
static async open(
|
|
79
84
|
retriever: Retriever,
|
|
80
85
|
channels: Params,
|
|
81
|
-
): Promise<
|
|
82
|
-
const adapter = new
|
|
86
|
+
): Promise<WriteFrameAdapter> {
|
|
87
|
+
const adapter = new WriteFrameAdapter(retriever);
|
|
83
88
|
await adapter.update(channels);
|
|
84
89
|
return adapter;
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
async update(channels: Params): Promise<void> {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this.keys = normalized;
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const fetched = await this.retriever.retrieve(normalized);
|
|
95
|
-
const a = new Map<Name, Key>();
|
|
96
|
-
this.adapter = a;
|
|
97
|
-
normalized.forEach((name) => {
|
|
98
|
-
const channel = fetched.find((channel) => channel.name === name);
|
|
99
|
-
if (channel == null) throw new ValidationError(`Channel ${name} was not provided in the list of channels when opening the writer`);
|
|
100
|
-
a.set(channel.name, channel.key);
|
|
101
|
-
});
|
|
102
|
-
this.keys = fetched.map((c) => c.key);
|
|
93
|
+
const results = await retrieveRequired(this.retriever, channels);
|
|
94
|
+
this.adapter = new Map<Name, Key>(results.map((c) => [c.name, c.key]));
|
|
95
|
+
this.keys = results.map((c) => c.key);
|
|
103
96
|
}
|
|
104
97
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
98
|
+
private async fetchChannel(ch: Key | Name): Promise<Payload> {
|
|
99
|
+
const res = await this.retriever.retrieve(ch);
|
|
100
|
+
if (res.length === 0) throw new Error(`Channel ${ch} not found`);
|
|
101
|
+
return res[0];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async adapt(
|
|
105
|
+
columnsOrData: Params | Record<KeyOrName, CrudeSeries> | CrudeFrame,
|
|
106
|
+
series?: CrudeSeries | CrudeSeries[],
|
|
107
|
+
): Promise<Frame> {
|
|
108
|
+
if (typeof columnsOrData === "string" || typeof columnsOrData === "number") {
|
|
109
|
+
if (series == null)
|
|
110
|
+
throw new ValidationError(`
|
|
111
|
+
Received a single channel name or key but no series.
|
|
112
|
+
`);
|
|
113
|
+
if (Array.isArray(series)) {
|
|
114
|
+
if (series.length > 1) {
|
|
115
|
+
throw new ValidationError(`
|
|
116
|
+
Received a single channel name or key but multiple series.
|
|
117
|
+
`);
|
|
118
|
+
}
|
|
119
|
+
series = series[0] as CrudeSeries;
|
|
120
|
+
}
|
|
121
|
+
const pld = await this.fetchChannel(columnsOrData);
|
|
122
|
+
const s = new Series({ data: series as CrudeSeries, dataType: pld.dataType });
|
|
123
|
+
return new Frame(pld.key, s);
|
|
112
124
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
|
|
126
|
+
if (Array.isArray(columnsOrData)) {
|
|
127
|
+
if (series == null)
|
|
128
|
+
throw new ValidationError(`
|
|
129
|
+
Received an array of channel names or keys but no series.
|
|
130
|
+
`);
|
|
131
|
+
if (!Array.isArray(series))
|
|
132
|
+
throw new ValidationError(`
|
|
133
|
+
Received an array of channel names or keys but no array of series.
|
|
134
|
+
`);
|
|
135
|
+
const cols = [];
|
|
136
|
+
const data = [];
|
|
137
|
+
for (let i = 0; i < columnsOrData.length; i++) {
|
|
138
|
+
const pld = await this.fetchChannel(columnsOrData[i]);
|
|
139
|
+
if (i >= series.length) {
|
|
140
|
+
throw new ValidationError(`
|
|
141
|
+
Received an array of channel names or keys but not enough series.
|
|
142
|
+
`);
|
|
143
|
+
}
|
|
144
|
+
const s = new Series({
|
|
145
|
+
data: series[i] as CrudeSeries,
|
|
146
|
+
dataType: pld.dataType,
|
|
147
|
+
});
|
|
148
|
+
cols.push(pld.key);
|
|
149
|
+
data.push(s);
|
|
119
150
|
}
|
|
120
|
-
return
|
|
121
|
-
}
|
|
151
|
+
return new Frame(cols, data);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (columnsOrData instanceof Frame || columnsOrData instanceof Map) {
|
|
155
|
+
const fr = new Frame(columnsOrData);
|
|
156
|
+
if (this.adapter == null) return fr;
|
|
157
|
+
let cols: Key[] = [];
|
|
158
|
+
cols = fr.columns.map((col_) => {
|
|
159
|
+
const col = typeof col_ === "string" ? this.adapter?.get(col_) : col_;
|
|
160
|
+
if (col == null)
|
|
161
|
+
throw new ValidationError(`
|
|
162
|
+
Channel ${col_} was not provided in the list of channels when opening the writer
|
|
163
|
+
`);
|
|
164
|
+
return col;
|
|
165
|
+
});
|
|
166
|
+
return new Frame(cols, fr.series);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const cols = [];
|
|
170
|
+
const data = [];
|
|
171
|
+
const kvs = Object.entries(columnsOrData);
|
|
172
|
+
for (let i = 0; i < kvs.length; i++) {
|
|
173
|
+
const [k, v] = kvs[i];
|
|
174
|
+
const pld = await this.fetchChannel(k);
|
|
175
|
+
const s = new Series({ data: v, dataType: pld.dataType });
|
|
176
|
+
cols.push(pld.key);
|
|
177
|
+
data.push(s);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return new Frame(cols, data);
|
|
122
181
|
}
|
|
123
182
|
}
|
package/src/framer/client.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { type StreamClient } from "@synnaxlabs/freighter";
|
|
11
11
|
import {
|
|
12
|
-
type
|
|
12
|
+
type TypedArray,
|
|
13
13
|
type Series,
|
|
14
14
|
type TimeRange,
|
|
15
15
|
type CrudeTimeStamp,
|
|
@@ -84,11 +84,7 @@ export class Client {
|
|
|
84
84
|
* data type as the channel.
|
|
85
85
|
* @throws if the channel does not exist.
|
|
86
86
|
*/
|
|
87
|
-
async write(
|
|
88
|
-
to: KeyOrName,
|
|
89
|
-
start: CrudeTimeStamp,
|
|
90
|
-
data: NativeTypedArray,
|
|
91
|
-
): Promise<void> {
|
|
87
|
+
async write(to: KeyOrName, start: CrudeTimeStamp, data: TypedArray): Promise<void> {
|
|
92
88
|
const w = await this.newWriter({
|
|
93
89
|
start,
|
|
94
90
|
channels: to,
|
package/src/framer/frame.spec.ts
CHANGED
|
@@ -19,9 +19,9 @@ describe("framer.Frame", () => {
|
|
|
19
19
|
const f = new framer.Frame(
|
|
20
20
|
["a", "b", "c"],
|
|
21
21
|
[
|
|
22
|
-
new Series({data: new Float32Array([1, 2, 3])}),
|
|
23
|
-
new Series({data: new Float32Array([1, 2, 3])}),
|
|
24
|
-
new Series({data: new Float32Array([1, 2, 3])}),
|
|
22
|
+
new Series({ data: new Float32Array([1, 2, 3]) }),
|
|
23
|
+
new Series({ data: new Float32Array([1, 2, 3]) }),
|
|
24
|
+
new Series({ data: new Float32Array([1, 2, 3]) }),
|
|
25
25
|
],
|
|
26
26
|
);
|
|
27
27
|
expect(f.length).toEqual(9);
|
|
@@ -32,9 +32,9 @@ describe("framer.Frame", () => {
|
|
|
32
32
|
const f = new framer.Frame(
|
|
33
33
|
[12, 13, 14],
|
|
34
34
|
[
|
|
35
|
-
new Series({data: new Float32Array([1, 2, 3])}),
|
|
36
|
-
new Series({data: new Float32Array([1, 2, 3])}),
|
|
37
|
-
new Series({data: new Float32Array([1, 2, 3])}),
|
|
35
|
+
new Series({ data: new Float32Array([1, 2, 3]) }),
|
|
36
|
+
new Series({ data: new Float32Array([1, 2, 3]) }),
|
|
37
|
+
new Series({ data: new Float32Array([1, 2, 3]) }),
|
|
38
38
|
],
|
|
39
39
|
);
|
|
40
40
|
expect(f.length).toEqual(9);
|
|
@@ -42,25 +42,35 @@ describe("framer.Frame", () => {
|
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
test("from a single name and an array of arrays", () => {
|
|
45
|
-
const f = new framer.Frame("a", [
|
|
45
|
+
const f = new framer.Frame("a", [
|
|
46
|
+
new Series({ data: new Float32Array([1, 2, 3]) }),
|
|
47
|
+
]);
|
|
46
48
|
expect(f.length).toEqual(3);
|
|
47
49
|
expect(f.colType).toEqual("name");
|
|
48
50
|
});
|
|
49
51
|
|
|
50
52
|
test("from a single key and an array of arrays", () => {
|
|
51
|
-
const f = new framer.Frame(12, [
|
|
53
|
+
const f = new framer.Frame(12, [
|
|
54
|
+
new Series({ data: new Float32Array([1, 2, 3]) }),
|
|
55
|
+
]);
|
|
52
56
|
expect(f.length).toEqual(3);
|
|
53
57
|
expect(f.colType).toEqual("key");
|
|
54
58
|
});
|
|
55
59
|
|
|
56
60
|
test("from a single key and a single array", () => {
|
|
57
|
-
const f = new framer.Frame(
|
|
61
|
+
const f = new framer.Frame(
|
|
62
|
+
12,
|
|
63
|
+
new Series({ data: new Float32Array([1, 2, 3]) }),
|
|
64
|
+
);
|
|
58
65
|
expect(f.length).toEqual(3);
|
|
59
66
|
expect(f.colType).toEqual("key");
|
|
60
67
|
});
|
|
61
68
|
|
|
62
69
|
test("from a single name and a single array", () => {
|
|
63
|
-
const f = new framer.Frame(
|
|
70
|
+
const f = new framer.Frame(
|
|
71
|
+
"a",
|
|
72
|
+
new Series({ data: new Float32Array([1, 2, 3]) }),
|
|
73
|
+
);
|
|
64
74
|
expect(f.length).toEqual(3);
|
|
65
75
|
expect(f.colType).toEqual("name");
|
|
66
76
|
});
|
|
@@ -82,7 +92,7 @@ describe("framer.Frame", () => {
|
|
|
82
92
|
|
|
83
93
|
test("from record", () => {
|
|
84
94
|
const f = new framer.Frame({
|
|
85
|
-
a: new Series({data: new Float32Array([1, 2, 3])}),
|
|
95
|
+
a: new Series({ data: new Float32Array([1, 2, 3]) }),
|
|
86
96
|
});
|
|
87
97
|
expect(f.length.valueOf()).toEqual(3);
|
|
88
98
|
expect(f.columns.length).toEqual(1);
|
|
@@ -91,7 +101,7 @@ describe("framer.Frame", () => {
|
|
|
91
101
|
|
|
92
102
|
test("from map", () => {
|
|
93
103
|
const f = new framer.Frame(
|
|
94
|
-
new Map([[12, new Series({data: new Float32Array([1, 2, 3])})]]),
|
|
104
|
+
new Map([[12, new Series({ data: new Float32Array([1, 2, 3]) })]]),
|
|
95
105
|
);
|
|
96
106
|
expect(f.length).toEqual(3);
|
|
97
107
|
expect(f.columns.length).toEqual(1);
|
|
@@ -106,8 +116,8 @@ describe("framer.Frame", () => {
|
|
|
106
116
|
new framer.Frame(
|
|
107
117
|
["a", "b", "c"],
|
|
108
118
|
[
|
|
109
|
-
new Series({data: new Float32Array([1, 2, 3])}),
|
|
110
|
-
new Series({data: new Float32Array([1, 2, 3])}),
|
|
119
|
+
new Series({ data: new Float32Array([1, 2, 3]) }),
|
|
120
|
+
new Series({ data: new Float32Array([1, 2, 3]) }),
|
|
111
121
|
],
|
|
112
122
|
),
|
|
113
123
|
).toThrow();
|
|
@@ -119,12 +129,12 @@ describe("framer.Frame", () => {
|
|
|
119
129
|
it("should return false if a key has more than one array", () => {
|
|
120
130
|
const f = new framer.Frame(
|
|
121
131
|
new Map([
|
|
122
|
-
[12, [new Series({data: new Float32Array([1, 2, 3])})]],
|
|
132
|
+
[12, [new Series({ data: new Float32Array([1, 2, 3]) })]],
|
|
123
133
|
[
|
|
124
134
|
13,
|
|
125
135
|
[
|
|
126
|
-
new Series({data: new Float32Array([1, 2, 3])}),
|
|
127
|
-
new Series({data: new Float32Array([1, 2, 3])}),
|
|
136
|
+
new Series({ data: new Float32Array([1, 2, 3]) }),
|
|
137
|
+
new Series({ data: new Float32Array([1, 2, 3]) }),
|
|
128
138
|
],
|
|
129
139
|
],
|
|
130
140
|
]),
|
|
@@ -137,8 +147,8 @@ describe("framer.Frame", () => {
|
|
|
137
147
|
it("should return false if there is more than one key", () => {
|
|
138
148
|
const f = new framer.Frame(
|
|
139
149
|
new Map([
|
|
140
|
-
[12, [new Series({data: new Float32Array([1, 2, 3])})]],
|
|
141
|
-
[13, [new Series({data: new Float32Array([1, 2, 3])})]],
|
|
150
|
+
[12, [new Series({ data: new Float32Array([1, 2, 3]) })]],
|
|
151
|
+
[13, [new Series({ data: new Float32Array([1, 2, 3]) })]],
|
|
142
152
|
]),
|
|
143
153
|
);
|
|
144
154
|
expect(f.isHorizontal).toEqual(false);
|
|
@@ -164,7 +174,7 @@ describe("framer.Frame", () => {
|
|
|
164
174
|
new Series({
|
|
165
175
|
data: new Float32Array([1, 2, 3]),
|
|
166
176
|
timeRange: new TimeRange(500, 50000),
|
|
167
|
-
|
|
177
|
+
}),
|
|
168
178
|
],
|
|
169
179
|
],
|
|
170
180
|
]),
|
|
@@ -181,7 +191,7 @@ describe("framer.Frame", () => {
|
|
|
181
191
|
new Series({
|
|
182
192
|
data: new Float32Array([1, 2, 3]),
|
|
183
193
|
timeRange: new TimeRange(500, 50000),
|
|
184
|
-
|
|
194
|
+
}),
|
|
185
195
|
],
|
|
186
196
|
],
|
|
187
197
|
[
|
|
@@ -190,7 +200,7 @@ describe("framer.Frame", () => {
|
|
|
190
200
|
new Series({
|
|
191
201
|
data: new Float32Array([1, 2, 3]),
|
|
192
202
|
timeRange: new TimeRange(500, 50001),
|
|
193
|
-
|
|
203
|
+
}),
|
|
194
204
|
],
|
|
195
205
|
],
|
|
196
206
|
]),
|
|
@@ -201,7 +211,7 @@ describe("framer.Frame", () => {
|
|
|
201
211
|
|
|
202
212
|
describe("timeRange", () => {
|
|
203
213
|
describe("no key provided", () => {
|
|
204
|
-
it("should return the
|
|
214
|
+
it("should return the maximum time range of the frame", () => {
|
|
205
215
|
const f = new framer.Frame(
|
|
206
216
|
new Map([
|
|
207
217
|
[
|
|
@@ -210,7 +220,7 @@ describe("framer.Frame", () => {
|
|
|
210
220
|
new Series({
|
|
211
221
|
data: new Float32Array([1, 2, 3]),
|
|
212
222
|
timeRange: new TimeRange(40, 50000),
|
|
213
|
-
|
|
223
|
+
}),
|
|
214
224
|
],
|
|
215
225
|
],
|
|
216
226
|
[
|
|
@@ -219,7 +229,7 @@ describe("framer.Frame", () => {
|
|
|
219
229
|
new Series({
|
|
220
230
|
data: new Float32Array([1, 2, 3]),
|
|
221
231
|
timeRange: new TimeRange(500, 50001),
|
|
222
|
-
|
|
232
|
+
}),
|
|
223
233
|
],
|
|
224
234
|
],
|
|
225
235
|
]),
|
|
@@ -234,11 +244,11 @@ describe("framer.Frame", () => {
|
|
|
234
244
|
a: new Series({
|
|
235
245
|
data: new Float32Array([1, 2, 3]),
|
|
236
246
|
timeRange: new TimeRange(40, 50000),
|
|
237
|
-
|
|
247
|
+
}),
|
|
238
248
|
b: new Series({
|
|
239
249
|
data: new Float32Array([1, 2, 3]),
|
|
240
250
|
timeRange: new TimeRange(500, 50001),
|
|
241
|
-
|
|
251
|
+
}),
|
|
242
252
|
});
|
|
243
253
|
expect(f.timeRange("a")).toEqual(new TimeRange(40, 50000));
|
|
244
254
|
});
|
|
@@ -254,7 +264,7 @@ describe("framer.Frame", () => {
|
|
|
254
264
|
new Series({
|
|
255
265
|
data: new Float32Array([1, 2, 3]),
|
|
256
266
|
timeRange: new TimeRange(40, 50000),
|
|
257
|
-
|
|
267
|
+
}),
|
|
258
268
|
],
|
|
259
269
|
],
|
|
260
270
|
[
|
|
@@ -263,7 +273,7 @@ describe("framer.Frame", () => {
|
|
|
263
273
|
new Series({
|
|
264
274
|
data: new Float32Array([1, 2, 3]),
|
|
265
275
|
timeRange: new TimeRange(500, 50001),
|
|
266
|
-
|
|
276
|
+
}),
|
|
267
277
|
],
|
|
268
278
|
],
|
|
269
279
|
]),
|
|
@@ -283,7 +293,7 @@ describe("framer.Frame", () => {
|
|
|
283
293
|
new Series({
|
|
284
294
|
data: new Float32Array([1, 2, 3]),
|
|
285
295
|
timeRange: new TimeRange(40, 50000),
|
|
286
|
-
|
|
296
|
+
}),
|
|
287
297
|
],
|
|
288
298
|
],
|
|
289
299
|
[
|
|
@@ -292,7 +302,7 @@ describe("framer.Frame", () => {
|
|
|
292
302
|
new Series({
|
|
293
303
|
data: new Float32Array([1, 2, 3]),
|
|
294
304
|
timeRange: new TimeRange(500, 50001),
|
|
295
|
-
|
|
305
|
+
}),
|
|
296
306
|
],
|
|
297
307
|
],
|
|
298
308
|
]),
|
|
@@ -302,4 +312,65 @@ describe("framer.Frame", () => {
|
|
|
302
312
|
expect(pld.series?.[0].data.byteLength).toEqual(12);
|
|
303
313
|
});
|
|
304
314
|
});
|
|
315
|
+
|
|
316
|
+
describe("latest", () => {
|
|
317
|
+
it("should return the latest sample from each column in the frame", () => {
|
|
318
|
+
const f = new framer.Frame(
|
|
319
|
+
new Map([
|
|
320
|
+
[
|
|
321
|
+
12,
|
|
322
|
+
[
|
|
323
|
+
new Series({
|
|
324
|
+
data: new Float32Array([1, 2, 3]),
|
|
325
|
+
timeRange: new TimeRange(40, 50000),
|
|
326
|
+
}),
|
|
327
|
+
],
|
|
328
|
+
],
|
|
329
|
+
[
|
|
330
|
+
13,
|
|
331
|
+
[
|
|
332
|
+
new Series({
|
|
333
|
+
data: new Float32Array([1, 2, 3]),
|
|
334
|
+
timeRange: new TimeRange(500, 50001),
|
|
335
|
+
}),
|
|
336
|
+
],
|
|
337
|
+
],
|
|
338
|
+
]),
|
|
339
|
+
);
|
|
340
|
+
expect(f.latest()).toEqual({ 12: 3, 13: 3 });
|
|
341
|
+
});
|
|
342
|
+
it("should return the latest sample for each col in the frame - even with multiple series per col", () => {
|
|
343
|
+
const f = new framer.Frame(
|
|
344
|
+
new Map([
|
|
345
|
+
[
|
|
346
|
+
12,
|
|
347
|
+
[
|
|
348
|
+
new Series({
|
|
349
|
+
data: new Float32Array([1, 2, 3]),
|
|
350
|
+
timeRange: new TimeRange(40, 50000),
|
|
351
|
+
}),
|
|
352
|
+
new Series({
|
|
353
|
+
data: new Float32Array([4, 5, 6]),
|
|
354
|
+
timeRange: new TimeRange(40, 50000),
|
|
355
|
+
}),
|
|
356
|
+
],
|
|
357
|
+
],
|
|
358
|
+
[
|
|
359
|
+
13,
|
|
360
|
+
[
|
|
361
|
+
new Series({
|
|
362
|
+
data: new Float32Array([1, 2, 3]),
|
|
363
|
+
timeRange: new TimeRange(500, 50001),
|
|
364
|
+
}),
|
|
365
|
+
new Series({
|
|
366
|
+
data: new Float32Array([4, 5, 7]),
|
|
367
|
+
timeRange: new TimeRange(500, 50001),
|
|
368
|
+
}),
|
|
369
|
+
],
|
|
370
|
+
],
|
|
371
|
+
]),
|
|
372
|
+
);
|
|
373
|
+
expect(f.latest()).toEqual({ 12: 6, 13: 7 });
|
|
374
|
+
});
|
|
375
|
+
});
|
|
305
376
|
});
|
package/src/framer/frame.ts
CHANGED
|
@@ -7,7 +7,16 @@
|
|
|
7
7
|
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
Size,
|
|
12
|
+
Series,
|
|
13
|
+
TimeRange,
|
|
14
|
+
toArray,
|
|
15
|
+
DataType,
|
|
16
|
+
unique,
|
|
17
|
+
TimeStamp,
|
|
18
|
+
type TelemValue,
|
|
19
|
+
} from "@synnaxlabs/x";
|
|
11
20
|
import { z } from "zod";
|
|
12
21
|
|
|
13
22
|
import {
|
|
@@ -59,7 +68,7 @@ export type CrudeFrame =
|
|
|
59
68
|
*
|
|
60
69
|
* - A frame is weakly aligned if it meets the time range occupied by all arrays of a
|
|
61
70
|
* particular channel is the same for all channels in the frame. This means that the
|
|
62
|
-
* arrays for a particular channel can have gaps
|
|
71
|
+
* arrays for a particular channel can have gaps between them.
|
|
63
72
|
*
|
|
64
73
|
* - A strongly aligned frame means that all channels share the same rate/index and
|
|
65
74
|
* there are no gaps in time between arrays. Strongly aligned frames are natural
|
|
@@ -113,7 +122,7 @@ export class Frame {
|
|
|
113
122
|
Object.entries(columnsOrData).forEach(([k, v]) => {
|
|
114
123
|
const key = parseInt(k);
|
|
115
124
|
if (!isNaN(key)) return this.push(key, ...toArray(v));
|
|
116
|
-
else this.push(k, ...toArray(v))
|
|
125
|
+
else this.push(k, ...toArray(v));
|
|
117
126
|
});
|
|
118
127
|
return;
|
|
119
128
|
}
|
|
@@ -249,6 +258,14 @@ export class Frame {
|
|
|
249
258
|
);
|
|
250
259
|
}
|
|
251
260
|
|
|
261
|
+
latest(): Record<string, TelemValue> {
|
|
262
|
+
return Object.fromEntries(
|
|
263
|
+
this.columns
|
|
264
|
+
.map((c, i) => [c, this.series[i].at(-1)])
|
|
265
|
+
.filter(([_, v]) => v != null),
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
252
269
|
get timeRanges(): TimeRange[] {
|
|
253
270
|
return this.uniqueColumns.map((col) => this.timeRange(col));
|
|
254
271
|
}
|
|
@@ -403,7 +420,7 @@ export type FramePayload = z.infer<typeof frameZ>;
|
|
|
403
420
|
|
|
404
421
|
export const seriesFromPayload = (series: SeriesPayload): Series => {
|
|
405
422
|
const { dataType, data, timeRange, alignment } = series;
|
|
406
|
-
return new Series({data, dataType, timeRange, glBufferUsage: "static", alignment});
|
|
423
|
+
return new Series({ data, dataType, timeRange, glBufferUsage: "static", alignment });
|
|
407
424
|
};
|
|
408
425
|
|
|
409
426
|
export const seriesToPayload = (series: Series): SeriesPayload => {
|
package/src/framer/iterator.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { z } from "zod";
|
|
|
19
19
|
|
|
20
20
|
import { type Params } from "@/channel/payload";
|
|
21
21
|
import { type Retriever } from "@/channel/retriever";
|
|
22
|
-
import {
|
|
22
|
+
import { ReadFrameAdapter } from "@/framer/adapter";
|
|
23
23
|
import { Frame, frameZ } from "@/framer/frame";
|
|
24
24
|
import { StreamProxy } from "@/framer/streamProxy";
|
|
25
25
|
|
|
@@ -72,12 +72,12 @@ const resZ = z.object({
|
|
|
72
72
|
export class Iterator {
|
|
73
73
|
private static readonly ENDPOINT = "/frame/iterate";
|
|
74
74
|
private readonly stream: StreamProxy<typeof reqZ, typeof resZ>;
|
|
75
|
-
private readonly adapter:
|
|
75
|
+
private readonly adapter: ReadFrameAdapter;
|
|
76
76
|
value: Frame;
|
|
77
77
|
|
|
78
78
|
private constructor(
|
|
79
79
|
stream: Stream<typeof reqZ, typeof resZ>,
|
|
80
|
-
adapter:
|
|
80
|
+
adapter: ReadFrameAdapter,
|
|
81
81
|
) {
|
|
82
82
|
this.stream = new StreamProxy("Iterator", stream);
|
|
83
83
|
this.value = new Frame();
|
|
@@ -97,7 +97,7 @@ export class Iterator {
|
|
|
97
97
|
retriever: Retriever,
|
|
98
98
|
client: StreamClient,
|
|
99
99
|
): Promise<Iterator> {
|
|
100
|
-
const adapter = await
|
|
100
|
+
const adapter = await ReadFrameAdapter.open(retriever, channels);
|
|
101
101
|
const stream = await client.stream(Iterator.ENDPOINT, reqZ, resZ);
|
|
102
102
|
const iter = new Iterator(stream, adapter);
|
|
103
103
|
await iter.execute({ command: Command.Open, keys: adapter.keys, bounds: tr });
|
package/src/framer/streamer.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { z } from "zod";
|
|
|
13
13
|
|
|
14
14
|
import { type Key, type Params } from "@/channel/payload";
|
|
15
15
|
import { type Retriever } from "@/channel/retriever";
|
|
16
|
-
import {
|
|
16
|
+
import { ReadFrameAdapter } from "@/framer/adapter";
|
|
17
17
|
import { Frame, frameZ } from "@/framer/frame";
|
|
18
18
|
import { StreamProxy } from "@/framer/streamProxy";
|
|
19
19
|
|
|
@@ -31,11 +31,11 @@ const ENDPOINT = "/frame/stream";
|
|
|
31
31
|
|
|
32
32
|
export class Streamer implements AsyncIterator<Frame>, AsyncIterable<Frame> {
|
|
33
33
|
private readonly stream: StreamProxy<typeof reqZ, typeof resZ>;
|
|
34
|
-
private readonly adapter:
|
|
34
|
+
private readonly adapter: ReadFrameAdapter;
|
|
35
35
|
|
|
36
36
|
private constructor(
|
|
37
37
|
stream: Stream<typeof reqZ, typeof resZ>,
|
|
38
|
-
adapter:
|
|
38
|
+
adapter: ReadFrameAdapter,
|
|
39
39
|
) {
|
|
40
40
|
this.stream = new StreamProxy("Streamer", stream);
|
|
41
41
|
this.adapter = adapter;
|
|
@@ -51,7 +51,7 @@ export class Streamer implements AsyncIterator<Frame>, AsyncIterable<Frame> {
|
|
|
51
51
|
retriever: Retriever,
|
|
52
52
|
client: StreamClient,
|
|
53
53
|
): Promise<Streamer> {
|
|
54
|
-
const adapter = await
|
|
54
|
+
const adapter = await ReadFrameAdapter.open(retriever, channels);
|
|
55
55
|
const stream = await client.stream(ENDPOINT, reqZ, resZ);
|
|
56
56
|
const streamer = new Streamer(stream, adapter);
|
|
57
57
|
stream.send({ start: new TimeStamp(start), keys: adapter.keys });
|
|
@@ -43,7 +43,7 @@ describe("Writer", () => {
|
|
|
43
43
|
const writer = await client.telem.newWriter({ start: 0, channels: ch.key });
|
|
44
44
|
await expect(
|
|
45
45
|
writer.write("billy bob", randomSeries(10, DataType.FLOAT64)),
|
|
46
|
-
).rejects.toThrow("Channel billy bob
|
|
46
|
+
).rejects.toThrow("Channel billy bob not found");
|
|
47
47
|
await writer.close();
|
|
48
48
|
});
|
|
49
49
|
test("stream when mode is set ot persist only", async () => {
|