@synnaxlabs/client 0.43.0 → 0.44.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.
- package/.turbo/turbo-build.log +7 -7
- package/dist/access/payload.d.ts +1 -1
- package/dist/access/payload.d.ts.map +1 -1
- package/dist/access/policy/client.d.ts +263 -6
- package/dist/access/policy/client.d.ts.map +1 -1
- package/dist/access/policy/external.d.ts +0 -1
- package/dist/access/policy/external.d.ts.map +1 -1
- package/dist/access/policy/payload.d.ts +105 -93
- package/dist/access/policy/payload.d.ts.map +1 -1
- package/dist/auth/auth.d.ts +1 -1
- package/dist/auth/auth.d.ts.map +1 -1
- package/dist/channel/client.d.ts +12 -13
- package/dist/channel/client.d.ts.map +1 -1
- package/dist/channel/payload.d.ts +77 -19
- package/dist/channel/payload.d.ts.map +1 -1
- package/dist/channel/retriever.d.ts +9 -16
- package/dist/channel/retriever.d.ts.map +1 -1
- package/dist/channel/writer.d.ts +1 -1
- package/dist/channel/writer.d.ts.map +1 -1
- package/dist/client.cjs +27 -135
- package/dist/client.d.ts +3 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +8619 -28938
- package/dist/connection/checker.d.ts +1 -1
- package/dist/connection/checker.d.ts.map +1 -1
- package/dist/control/client.d.ts +1 -0
- package/dist/control/client.d.ts.map +1 -1
- package/dist/control/state.d.ts +6 -6
- package/dist/control/state.d.ts.map +1 -1
- package/dist/errors.d.ts +18 -5
- package/dist/errors.d.ts.map +1 -1
- package/dist/framer/adapter.d.ts +3 -3
- package/dist/framer/adapter.d.ts.map +1 -1
- package/dist/framer/client.d.ts +4 -13
- package/dist/framer/client.d.ts.map +1 -1
- package/dist/framer/codec.d.ts +1 -1
- package/dist/framer/codec.d.ts.map +1 -1
- package/dist/framer/deleter.d.ts +5 -5
- package/dist/framer/deleter.d.ts.map +1 -1
- package/dist/framer/frame.d.ts +5 -7
- package/dist/framer/frame.d.ts.map +1 -1
- package/dist/framer/streamProxy.d.ts +1 -1
- package/dist/framer/streamProxy.d.ts.map +1 -1
- package/dist/framer/streamer.d.ts +139 -20
- package/dist/framer/streamer.d.ts.map +1 -1
- package/dist/framer/writer.d.ts +222 -33
- package/dist/framer/writer.d.ts.map +1 -1
- package/dist/hardware/device/client.d.ts +49 -28
- package/dist/hardware/device/client.d.ts.map +1 -1
- package/dist/hardware/device/payload.d.ts +126 -46
- package/dist/hardware/device/payload.d.ts.map +1 -1
- package/dist/hardware/rack/client.d.ts +78 -22
- package/dist/hardware/rack/client.d.ts.map +1 -1
- package/dist/hardware/rack/payload.d.ts +99 -56
- package/dist/hardware/rack/payload.d.ts.map +1 -1
- package/dist/hardware/task/client.d.ts +100 -41
- package/dist/hardware/task/client.d.ts.map +1 -1
- package/dist/hardware/task/payload.d.ts +83 -61
- package/dist/hardware/task/payload.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/label/client.d.ts +138 -20
- package/dist/label/client.d.ts.map +1 -1
- package/dist/label/external.d.ts +0 -2
- package/dist/label/external.d.ts.map +1 -1
- package/dist/label/payload.d.ts +4 -5
- package/dist/label/payload.d.ts.map +1 -1
- package/dist/ontology/client.d.ts +45 -135
- package/dist/ontology/client.d.ts.map +1 -1
- package/dist/ontology/group/group.d.ts +3 -3
- package/dist/ontology/group/group.d.ts.map +1 -1
- package/dist/ontology/group/payload.d.ts +3 -27
- package/dist/ontology/group/payload.d.ts.map +1 -1
- package/dist/ontology/payload.d.ts +113 -243
- package/dist/ontology/payload.d.ts.map +1 -1
- package/dist/ontology/writer.d.ts +4 -4
- package/dist/ontology/writer.d.ts.map +1 -1
- package/dist/ranger/alias.d.ts +11 -5
- package/dist/ranger/alias.d.ts.map +1 -1
- package/dist/ranger/client.d.ts +87 -30
- package/dist/ranger/client.d.ts.map +1 -1
- package/dist/ranger/external.d.ts +1 -1
- package/dist/ranger/external.d.ts.map +1 -1
- package/dist/ranger/kv.d.ts +10 -12
- package/dist/ranger/kv.d.ts.map +1 -1
- package/dist/ranger/payload.d.ts +23 -44
- package/dist/ranger/payload.d.ts.map +1 -1
- package/dist/ranger/writer.d.ts +22 -19
- package/dist/ranger/writer.d.ts.map +1 -1
- package/dist/testutil/client.d.ts +4 -0
- package/dist/testutil/client.d.ts.map +1 -0
- package/dist/user/client.d.ts +59 -6
- package/dist/user/client.d.ts.map +1 -1
- package/dist/user/payload.d.ts +4 -6
- package/dist/user/payload.d.ts.map +1 -1
- package/dist/user/retriever.d.ts +2 -2
- package/dist/user/retriever.d.ts.map +1 -1
- package/dist/util/decodeJSONString.d.ts +2 -2
- package/dist/util/decodeJSONString.d.ts.map +1 -1
- package/dist/util/parseWithoutKeyConversion.d.ts +2 -2
- package/dist/util/parseWithoutKeyConversion.d.ts.map +1 -1
- package/dist/util/retrieve.d.ts +1 -1
- package/dist/util/retrieve.d.ts.map +1 -1
- package/dist/util/zod.d.ts +1 -1
- package/dist/util/zod.d.ts.map +1 -1
- package/dist/workspace/client.d.ts +17 -6
- package/dist/workspace/client.d.ts.map +1 -1
- package/dist/workspace/lineplot/client.d.ts +2 -2
- package/dist/workspace/lineplot/client.d.ts.map +1 -1
- package/dist/workspace/lineplot/payload.d.ts +8 -9
- package/dist/workspace/lineplot/payload.d.ts.map +1 -1
- package/dist/workspace/log/client.d.ts +2 -2
- package/dist/workspace/log/client.d.ts.map +1 -1
- package/dist/workspace/log/payload.d.ts +8 -9
- package/dist/workspace/log/payload.d.ts.map +1 -1
- package/dist/workspace/payload.d.ts +10 -11
- package/dist/workspace/payload.d.ts.map +1 -1
- package/dist/workspace/schematic/client.d.ts +2 -2
- package/dist/workspace/schematic/client.d.ts.map +1 -1
- package/dist/workspace/schematic/payload.d.ts +10 -11
- package/dist/workspace/schematic/payload.d.ts.map +1 -1
- package/dist/workspace/table/client.d.ts +2 -2
- package/dist/workspace/table/client.d.ts.map +1 -1
- package/dist/workspace/table/payload.d.ts +10 -11
- package/dist/workspace/table/payload.d.ts.map +1 -1
- package/examples/node/package-lock.json +47 -39
- package/examples/node/package.json +2 -1
- package/examples/node/streamWrite.js +5 -11
- package/package.json +14 -13
- package/src/access/payload.ts +1 -1
- package/src/access/policy/client.ts +87 -32
- package/src/access/policy/external.ts +0 -1
- package/src/access/policy/payload.ts +4 -4
- package/src/access/policy/policy.spec.ts +86 -83
- package/src/auth/auth.spec.ts +29 -18
- package/src/auth/auth.ts +1 -1
- package/src/channel/batchRetriever.spec.ts +4 -9
- package/src/channel/channel.spec.ts +24 -6
- package/src/channel/client.ts +31 -46
- package/src/channel/payload.ts +13 -14
- package/src/channel/retriever.ts +26 -41
- package/src/channel/writer.ts +3 -3
- package/src/client.ts +4 -4
- package/src/connection/checker.ts +1 -1
- package/src/connection/connection.spec.ts +31 -23
- package/src/control/client.ts +2 -2
- package/src/control/state.spec.ts +3 -3
- package/src/control/state.ts +1 -1
- package/src/errors.spec.ts +9 -5
- package/src/errors.ts +28 -15
- package/src/framer/adapter.spec.ts +118 -9
- package/src/framer/adapter.ts +24 -11
- package/src/framer/client.spec.ts +125 -2
- package/src/framer/client.ts +41 -47
- package/src/framer/codec.ts +1 -1
- package/src/framer/deleter.spec.ts +2 -2
- package/src/framer/deleter.ts +1 -1
- package/src/framer/frame.ts +1 -4
- package/src/framer/iterator.spec.ts +8 -8
- package/src/framer/iterator.ts +1 -1
- package/src/framer/streamProxy.ts +1 -1
- package/src/framer/streamer.spec.ts +185 -36
- package/src/framer/streamer.ts +28 -36
- package/src/framer/writer.spec.ts +6 -6
- package/src/framer/writer.ts +97 -111
- package/src/hardware/device/client.ts +45 -131
- package/src/hardware/device/device.spec.ts +163 -52
- package/src/hardware/device/payload.ts +10 -21
- package/src/hardware/rack/client.ts +87 -105
- package/src/hardware/rack/payload.ts +4 -13
- package/src/hardware/rack/rack.spec.ts +28 -35
- package/src/hardware/task/client.ts +335 -291
- package/src/hardware/task/payload.ts +86 -62
- package/src/hardware/task/task.spec.ts +208 -32
- package/src/index.ts +2 -1
- package/src/label/client.ts +100 -95
- package/src/label/external.ts +0 -2
- package/src/label/label.spec.ts +8 -6
- package/src/label/payload.ts +3 -4
- package/src/ontology/client.ts +41 -324
- package/src/ontology/group/group.spec.ts +2 -2
- package/src/ontology/group/group.ts +4 -5
- package/src/ontology/group/payload.ts +2 -25
- package/src/ontology/group/writer.ts +1 -1
- package/src/ontology/ontology.spec.ts +355 -41
- package/src/ontology/payload.ts +74 -112
- package/src/ontology/writer.ts +8 -17
- package/src/ranger/alias.ts +19 -37
- package/src/ranger/client.ts +118 -150
- package/src/ranger/external.ts +9 -1
- package/src/ranger/kv.ts +6 -27
- package/src/ranger/payload.ts +21 -37
- package/src/ranger/ranger.spec.ts +37 -56
- package/src/ranger/writer.ts +1 -1
- package/src/{signals/index.ts → testutil/client.ts} +11 -1
- package/src/user/client.ts +122 -47
- package/src/user/payload.ts +2 -5
- package/src/user/retriever.ts +1 -1
- package/src/user/user.spec.ts +31 -31
- package/src/user/writer.ts +1 -1
- package/src/util/decodeJSONString.ts +3 -3
- package/src/util/parseWithoutKeyConversion.ts +2 -2
- package/src/util/retrieve.ts +1 -1
- package/src/util/zod.ts +1 -1
- package/src/workspace/client.ts +20 -36
- package/src/workspace/lineplot/client.ts +5 -7
- package/src/workspace/lineplot/lineplot.spec.ts +2 -2
- package/src/workspace/lineplot/payload.ts +4 -7
- package/src/workspace/log/client.ts +5 -7
- package/src/workspace/log/log.spec.ts +2 -2
- package/src/workspace/log/payload.ts +4 -7
- package/src/workspace/payload.ts +4 -7
- package/src/workspace/schematic/client.ts +5 -7
- package/src/workspace/schematic/payload.ts +4 -7
- package/src/workspace/schematic/schematic.spec.ts +2 -2
- package/src/workspace/table/client.ts +5 -7
- package/src/workspace/table/payload.ts +4 -7
- package/src/workspace/table/table.spec.ts +2 -2
- package/src/workspace/workspace.spec.ts +2 -2
- package/dist/access/policy/ontology.d.ts +0 -5
- package/dist/access/policy/ontology.d.ts.map +0 -1
- package/dist/access/policy/retriever.d.ts +0 -40
- package/dist/access/policy/retriever.d.ts.map +0 -1
- package/dist/access/policy/writer.d.ts +0 -9
- package/dist/access/policy/writer.d.ts.map +0 -1
- package/dist/label/retriever.d.ts +0 -14
- package/dist/label/retriever.d.ts.map +0 -1
- package/dist/label/writer.d.ts +0 -54
- package/dist/label/writer.d.ts.map +0 -1
- package/dist/setupspecs.d.ts +0 -5
- package/dist/setupspecs.d.ts.map +0 -1
- package/dist/signals/external.d.ts +0 -2
- package/dist/signals/external.d.ts.map +0 -1
- package/dist/signals/index.d.ts +0 -2
- package/dist/signals/index.d.ts.map +0 -1
- package/dist/signals/observable.d.ts +0 -12
- package/dist/signals/observable.d.ts.map +0 -1
- package/src/access/policy/ontology.ts +0 -17
- package/src/access/policy/retriever.ts +0 -44
- package/src/access/policy/writer.ts +0 -65
- package/src/label/retriever.ts +0 -63
- package/src/label/writer.ts +0 -95
- package/src/setupspecs.ts +0 -27
- package/src/signals/external.ts +0 -10
- package/src/signals/observable.ts +0 -42
package/src/errors.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
10
|
import { type Middleware, Unreachable } from "@synnaxlabs/freighter";
|
|
11
|
-
import { errors } from "@synnaxlabs/x";
|
|
11
|
+
import { array, errors } from "@synnaxlabs/x";
|
|
12
|
+
import { z } from "zod";
|
|
12
13
|
|
|
13
14
|
export class SynnaxError extends errors.createTyped("sy") {}
|
|
14
15
|
|
|
@@ -17,14 +18,24 @@ export class SynnaxError extends errors.createTyped("sy") {}
|
|
|
17
18
|
*/
|
|
18
19
|
export class ValidationError extends SynnaxError.sub("validation") {}
|
|
19
20
|
|
|
20
|
-
export class
|
|
21
|
-
readonly
|
|
22
|
-
readonly
|
|
21
|
+
export class PathError extends ValidationError.sub("path") {
|
|
22
|
+
readonly path: string[];
|
|
23
|
+
readonly error: Error;
|
|
24
|
+
static readonly encodedSchema = z.object({
|
|
25
|
+
path: z.string().array(),
|
|
26
|
+
error: errors.payloadZ,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
constructor(path: string | string[], error: Error) {
|
|
30
|
+
const arrPath = array.toArray(path);
|
|
31
|
+
super(`${arrPath.join(".")}: ${error.message}`);
|
|
32
|
+
this.path = arrPath.flatMap((p) => p.split("."));
|
|
33
|
+
this.error = error;
|
|
34
|
+
}
|
|
23
35
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.message = message;
|
|
36
|
+
static decode(payload: errors.Payload): PathError {
|
|
37
|
+
const decoded = PathError.encodedSchema.parse(JSON.parse(payload.data));
|
|
38
|
+
return new PathError(decoded.path, errors.decode(decoded.error) as Error);
|
|
28
39
|
}
|
|
29
40
|
}
|
|
30
41
|
|
|
@@ -80,6 +91,12 @@ export class ControlError extends SynnaxError.sub("control") {}
|
|
|
80
91
|
|
|
81
92
|
export class UnauthorizedError extends ControlError.sub("unauthorized") {}
|
|
82
93
|
|
|
94
|
+
export class DisconnectedError extends SynnaxError.sub("disconnected") {
|
|
95
|
+
constructor(message: string = "Operation failed because no cluster is connected.") {
|
|
96
|
+
super(message);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
83
100
|
/**
|
|
84
101
|
* Raised when time-series data is not contiguous.
|
|
85
102
|
*/
|
|
@@ -88,11 +105,7 @@ export class ContiguityError extends SynnaxError.sub("contiguity") {}
|
|
|
88
105
|
const decode = (payload: errors.Payload): Error | null => {
|
|
89
106
|
if (!payload.type.startsWith(SynnaxError.TYPE)) return null;
|
|
90
107
|
if (payload.type.startsWith(ValidationError.TYPE)) {
|
|
91
|
-
if (payload.type ===
|
|
92
|
-
const values = payload.data.split(": ");
|
|
93
|
-
if (values.length < 2) return new ValidationError(payload.data);
|
|
94
|
-
return new FieldError(values[0], values[1]);
|
|
95
|
-
}
|
|
108
|
+
if (payload.type === PathError.TYPE) return PathError.decode(payload);
|
|
96
109
|
return new ValidationError(payload.data);
|
|
97
110
|
}
|
|
98
111
|
|
|
@@ -128,7 +141,7 @@ const decode = (payload: errors.Payload): Error | null => {
|
|
|
128
141
|
};
|
|
129
142
|
|
|
130
143
|
const encode = (): errors.Payload => {
|
|
131
|
-
throw new
|
|
144
|
+
throw new errors.NotImplemented();
|
|
132
145
|
};
|
|
133
146
|
|
|
134
147
|
errors.register({ encode, decode });
|
|
@@ -138,7 +151,7 @@ export const validateFieldNotNull = (
|
|
|
138
151
|
value: unknown,
|
|
139
152
|
message: string = "must be provided",
|
|
140
153
|
): void => {
|
|
141
|
-
if (value == null) throw new
|
|
154
|
+
if (value == null) throw new PathError(key, new ValidationError(message));
|
|
142
155
|
};
|
|
143
156
|
|
|
144
157
|
export const errorsMiddleware: Middleware = async (ctx, next) => {
|
|
@@ -11,11 +11,11 @@ import { DataType, Series, TimeStamp } from "@synnaxlabs/x/telem";
|
|
|
11
11
|
import { beforeAll, describe, expect, it } from "vitest";
|
|
12
12
|
|
|
13
13
|
import { type channel } from "@/channel";
|
|
14
|
-
import { WriteAdapter } from "@/framer/adapter";
|
|
14
|
+
import { ReadAdapter, WriteAdapter } from "@/framer/adapter";
|
|
15
15
|
import { Frame } from "@/index";
|
|
16
|
-
import {
|
|
16
|
+
import { newTestClient } from "@/testutil/client";
|
|
17
17
|
|
|
18
|
-
const client =
|
|
18
|
+
const client = newTestClient();
|
|
19
19
|
|
|
20
20
|
describe("WriteFrameAdapter", () => {
|
|
21
21
|
let timeCh: channel.Channel;
|
|
@@ -160,14 +160,123 @@ describe("WriteFrameAdapter", () => {
|
|
|
160
160
|
expect(res.get(bigIntCh.key).at(0)).toEqual(12n);
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
-
describe("
|
|
163
|
+
describe("adaptParams", () => {
|
|
164
164
|
it("should correctly adapt generic object keys", async () => {
|
|
165
|
-
const res = await adapter.
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
const res = await adapter.adaptParams([timeCh.name, dataCh.name]);
|
|
166
|
+
expect(res).toContain(timeCh.key);
|
|
167
|
+
expect(res).toContain(dataCh.key);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe("update", () => {
|
|
172
|
+
it("should return false when updating with the same channels", async () => {
|
|
173
|
+
const hasChanged = await adapter.update([timeCh.key, dataCh.key]);
|
|
174
|
+
expect(hasChanged).toBe(false);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should return true when adding a new channel", async () => {
|
|
178
|
+
const newCh = await client.channels.create({
|
|
179
|
+
name: `new-${Math.random()}-${TimeStamp.now().toString()}`,
|
|
180
|
+
dataType: DataType.FLOAT32,
|
|
181
|
+
index: timeCh.key,
|
|
182
|
+
});
|
|
183
|
+
const hasChanged = await adapter.update([timeCh.key, dataCh.key, newCh.key]);
|
|
184
|
+
expect(hasChanged).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should return true when removing a channel", async () => {
|
|
188
|
+
const hasChanged = await adapter.update([timeCh.key]);
|
|
189
|
+
expect(hasChanged).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should return true when replacing channels", async () => {
|
|
193
|
+
const newCh = await client.channels.create({
|
|
194
|
+
name: `replacement-${Math.random()}-${TimeStamp.now().toString()}`,
|
|
195
|
+
dataType: DataType.FLOAT32,
|
|
196
|
+
index: timeCh.key,
|
|
197
|
+
});
|
|
198
|
+
const hasChanged = await adapter.update([timeCh.key, newCh.key]);
|
|
199
|
+
expect(hasChanged).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("should return false when updating with same channels in different order", async () => {
|
|
203
|
+
await adapter.update([timeCh.key, dataCh.key]);
|
|
204
|
+
const hasChanged = await adapter.update([dataCh.key, timeCh.key]);
|
|
205
|
+
expect(hasChanged).toBe(false);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("should return false when updating with channel names that resolve to same keys", async () => {
|
|
209
|
+
await adapter.update([timeCh.key, dataCh.key]);
|
|
210
|
+
const hasChanged = await adapter.update([timeCh.name, dataCh.name]);
|
|
211
|
+
expect(hasChanged).toBe(false);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe("ReadAdapter", () => {
|
|
217
|
+
let timeCh: channel.Channel;
|
|
218
|
+
let dataCh: channel.Channel;
|
|
219
|
+
let adapter: ReadAdapter;
|
|
220
|
+
|
|
221
|
+
beforeAll(async () => {
|
|
222
|
+
timeCh = await client.channels.create({
|
|
223
|
+
name: `read-time-${Math.random()}-${TimeStamp.now().toString()}`,
|
|
224
|
+
dataType: DataType.TIMESTAMP,
|
|
225
|
+
isIndex: true,
|
|
226
|
+
});
|
|
227
|
+
dataCh = await client.channels.create({
|
|
228
|
+
name: `read-data-${Math.random()}-${TimeStamp.now().toString()}`,
|
|
229
|
+
dataType: DataType.FLOAT32,
|
|
230
|
+
index: timeCh.key,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
adapter = await ReadAdapter.open(client.channels.retriever, [
|
|
234
|
+
timeCh.key,
|
|
235
|
+
dataCh.key,
|
|
236
|
+
]);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe("update", () => {
|
|
240
|
+
it("should return false when updating with the same channels", async () => {
|
|
241
|
+
const hasChanged = await adapter.update([timeCh.key, dataCh.key]);
|
|
242
|
+
expect(hasChanged).toBe(false);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("should return true when adding a new channel", async () => {
|
|
246
|
+
const newCh = await client.channels.create({
|
|
247
|
+
name: `read-new-${Math.random()}-${TimeStamp.now().toString()}`,
|
|
248
|
+
dataType: DataType.FLOAT32,
|
|
249
|
+
index: timeCh.key,
|
|
250
|
+
});
|
|
251
|
+
const hasChanged = await adapter.update([timeCh.key, dataCh.key, newCh.key]);
|
|
252
|
+
expect(hasChanged).toBe(true);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("should return true when removing a channel", async () => {
|
|
256
|
+
const hasChanged = await adapter.update([timeCh.key]);
|
|
257
|
+
expect(hasChanged).toBe(true);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it("should return true when replacing channels", async () => {
|
|
261
|
+
const newCh = await client.channels.create({
|
|
262
|
+
name: `read-replacement-${Math.random()}-${TimeStamp.now().toString()}`,
|
|
263
|
+
dataType: DataType.FLOAT32,
|
|
264
|
+
index: timeCh.key,
|
|
168
265
|
});
|
|
169
|
-
|
|
170
|
-
expect(
|
|
266
|
+
const hasChanged = await adapter.update([timeCh.key, newCh.key]);
|
|
267
|
+
expect(hasChanged).toBe(true);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("should return false when updating with same channels in different order", async () => {
|
|
271
|
+
await adapter.update([timeCh.key, dataCh.key]);
|
|
272
|
+
const hasChanged = await adapter.update([dataCh.key, timeCh.key]);
|
|
273
|
+
expect(hasChanged).toBe(false);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("should return false when updating with channel names that resolve to same keys", async () => {
|
|
277
|
+
await adapter.update([timeCh.key, dataCh.key]);
|
|
278
|
+
const hasChanged = await adapter.update([timeCh.name, dataCh.name]);
|
|
279
|
+
expect(hasChanged).toBe(false);
|
|
171
280
|
});
|
|
172
281
|
});
|
|
173
282
|
});
|
package/src/framer/adapter.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
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 { compare } from "@synnaxlabs/x";
|
|
10
11
|
import { type CrudeSeries, Series } from "@synnaxlabs/x/telem";
|
|
11
12
|
|
|
12
13
|
import { channel } from "@/channel";
|
|
@@ -36,17 +37,20 @@ export class ReadAdapter {
|
|
|
36
37
|
return adapter;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
async update(channels: channel.Params): Promise<
|
|
40
|
+
async update(channels: channel.Params): Promise<boolean> {
|
|
40
41
|
const { variant, normalized } = channel.analyzeParams(channels);
|
|
41
42
|
const fetched = await this.retriever.retrieve(normalized);
|
|
43
|
+
const newKeys = fetched.map((c) => c.key);
|
|
44
|
+
if (compare.uniqueUnorderedPrimitiveArrays(this.keys, newKeys) === compare.EQUAL)
|
|
45
|
+
return false;
|
|
42
46
|
this.codec.update(
|
|
43
|
-
|
|
47
|
+
newKeys,
|
|
44
48
|
fetched.map((c) => c.dataType),
|
|
45
49
|
);
|
|
46
50
|
if (variant === "keys") {
|
|
47
51
|
this.adapter = null;
|
|
48
52
|
this.keys = normalized as channel.Key[];
|
|
49
|
-
return;
|
|
53
|
+
return true;
|
|
50
54
|
}
|
|
51
55
|
const a = new Map<channel.Key, channel.Name>();
|
|
52
56
|
this.adapter = a;
|
|
@@ -56,6 +60,7 @@ export class ReadAdapter {
|
|
|
56
60
|
a.set(channel.key, channel.name);
|
|
57
61
|
});
|
|
58
62
|
this.keys = Array.from(this.adapter.keys());
|
|
63
|
+
return true;
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
adapt(columnsOrData: Frame): Frame {
|
|
@@ -94,24 +99,32 @@ export class WriteAdapter {
|
|
|
94
99
|
return adapter;
|
|
95
100
|
}
|
|
96
101
|
|
|
97
|
-
async
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return
|
|
102
|
+
async adaptParams(data: channel.Params): Promise<channel.Keys> {
|
|
103
|
+
const arrParams = channel.paramsZ.parse(data);
|
|
104
|
+
const keys = await Promise.all(
|
|
105
|
+
arrParams.map(async (p) => await this.adaptToKey(p)),
|
|
106
|
+
);
|
|
107
|
+
return keys;
|
|
103
108
|
}
|
|
104
109
|
|
|
105
|
-
async update(channels: channel.Params): Promise<
|
|
110
|
+
async update(channels: channel.Params): Promise<boolean> {
|
|
106
111
|
const results = await channel.retrieveRequired(this.retriever, channels);
|
|
112
|
+
const newKeys = results.map((c) => c.key);
|
|
113
|
+
const previousKeySet = new Set(this.keys);
|
|
114
|
+
const newKeySet = new Set(newKeys);
|
|
115
|
+
const hasAddedKeys = !newKeySet.isSubsetOf(previousKeySet);
|
|
116
|
+
const hasRemovedKeys = !previousKeySet.isSubsetOf(newKeySet);
|
|
117
|
+
const hasChanged = hasAddedKeys || hasRemovedKeys;
|
|
118
|
+
if (!hasChanged) return false;
|
|
107
119
|
this.adapter = new Map<channel.Name, channel.Key>(
|
|
108
120
|
results.map((c) => [c.name, c.key]),
|
|
109
121
|
);
|
|
110
|
-
this.keys =
|
|
122
|
+
this.keys = newKeys;
|
|
111
123
|
this.codec.update(
|
|
112
124
|
this.keys,
|
|
113
125
|
results.map((c) => c.dataType),
|
|
114
126
|
);
|
|
127
|
+
return true;
|
|
115
128
|
}
|
|
116
129
|
|
|
117
130
|
private async fetchChannel(
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
import { TimeSpan, TimeStamp } from "@synnaxlabs/x";
|
|
11
11
|
import { describe, expect, it } from "vitest";
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { newTestClient } from "@/testutil/client";
|
|
14
14
|
|
|
15
|
-
const client =
|
|
15
|
+
const client = newTestClient();
|
|
16
16
|
|
|
17
17
|
describe("Client", () => {
|
|
18
18
|
describe("read + write", () => {
|
|
@@ -60,4 +60,127 @@ describe("Client", () => {
|
|
|
60
60
|
expect(group.name).toEqual("Channels");
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
|
+
describe("readLatestN", () => {
|
|
64
|
+
it("should correctly read the latest N samples from a single channel", async () => {
|
|
65
|
+
const rand = `${TimeStamp.now().toString()}${Math.random()}`;
|
|
66
|
+
const time = await client.channels.create({
|
|
67
|
+
name: `time-${rand}`,
|
|
68
|
+
dataType: "timestamp",
|
|
69
|
+
isIndex: true,
|
|
70
|
+
});
|
|
71
|
+
const data = await client.channels.create({
|
|
72
|
+
name: `data-${rand}`,
|
|
73
|
+
dataType: "float32",
|
|
74
|
+
index: time.key,
|
|
75
|
+
});
|
|
76
|
+
const start = TimeStamp.now();
|
|
77
|
+
const timeData = [
|
|
78
|
+
start,
|
|
79
|
+
start.add(TimeSpan.seconds(1)),
|
|
80
|
+
start.add(TimeSpan.seconds(2)),
|
|
81
|
+
start.add(TimeSpan.seconds(3)),
|
|
82
|
+
start.add(TimeSpan.seconds(4)),
|
|
83
|
+
start.add(TimeSpan.seconds(5)),
|
|
84
|
+
start.add(TimeSpan.seconds(6)),
|
|
85
|
+
start.add(TimeSpan.seconds(7)),
|
|
86
|
+
start.add(TimeSpan.seconds(8)),
|
|
87
|
+
start.add(TimeSpan.seconds(9)),
|
|
88
|
+
];
|
|
89
|
+
const dataValues = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
90
|
+
await client.write(start, { [time.key]: timeData, [data.key]: dataValues });
|
|
91
|
+
|
|
92
|
+
const result = await client.readLatest(data.key, 3);
|
|
93
|
+
expect(Array.from(result)).toEqual([8, 9, 10]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should correctly read the latest N samples from multiple channels", async () => {
|
|
97
|
+
const rand = `${TimeStamp.now().toString()}${Math.random()}`;
|
|
98
|
+
const time = await client.channels.create({
|
|
99
|
+
name: `time-${rand}`,
|
|
100
|
+
dataType: "timestamp",
|
|
101
|
+
isIndex: true,
|
|
102
|
+
});
|
|
103
|
+
const data1 = await client.channels.create({
|
|
104
|
+
name: `data1-${rand}`,
|
|
105
|
+
dataType: "float32",
|
|
106
|
+
index: time.key,
|
|
107
|
+
});
|
|
108
|
+
const data2 = await client.channels.create({
|
|
109
|
+
name: `data2-${rand}`,
|
|
110
|
+
dataType: "float32",
|
|
111
|
+
index: time.key,
|
|
112
|
+
});
|
|
113
|
+
const start = TimeStamp.now();
|
|
114
|
+
const timeData = [
|
|
115
|
+
start,
|
|
116
|
+
start.add(TimeSpan.seconds(1)),
|
|
117
|
+
start.add(TimeSpan.seconds(2)),
|
|
118
|
+
start.add(TimeSpan.seconds(3)),
|
|
119
|
+
start.add(TimeSpan.seconds(4)),
|
|
120
|
+
start.add(TimeSpan.seconds(5)),
|
|
121
|
+
start.add(TimeSpan.seconds(6)),
|
|
122
|
+
start.add(TimeSpan.seconds(7)),
|
|
123
|
+
start.add(TimeSpan.seconds(8)),
|
|
124
|
+
start.add(TimeSpan.seconds(9)),
|
|
125
|
+
];
|
|
126
|
+
const data1Values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
127
|
+
const data2Values = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
|
|
128
|
+
await client.write(start, {
|
|
129
|
+
[time.key]: timeData,
|
|
130
|
+
[data1.key]: data1Values,
|
|
131
|
+
[data2.key]: data2Values,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const frame = await client.readLatest([time.key, data1.key, data2.key], 3);
|
|
135
|
+
expect(Array.from(frame.get(data1.key))).toEqual([8, 9, 10]);
|
|
136
|
+
expect(Array.from(frame.get(data2.key))).toEqual([80, 90, 100]);
|
|
137
|
+
expect(Array.from(frame.get(time.key))).toEqual([
|
|
138
|
+
timeData[7].valueOf(),
|
|
139
|
+
timeData[8].valueOf(),
|
|
140
|
+
timeData[9].valueOf(),
|
|
141
|
+
]);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should return empty series when no data exists", async () => {
|
|
145
|
+
const rand = `${TimeStamp.now().toString()}${Math.random()}`;
|
|
146
|
+
const time = await client.channels.create({
|
|
147
|
+
name: `time-${rand}`,
|
|
148
|
+
dataType: "timestamp",
|
|
149
|
+
isIndex: true,
|
|
150
|
+
});
|
|
151
|
+
const data = await client.channels.create({
|
|
152
|
+
name: `data-${rand}`,
|
|
153
|
+
dataType: "float32",
|
|
154
|
+
index: time.key,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const result = await client.readLatest(data.key, 5);
|
|
158
|
+
expect(Array.from(result)).toEqual([]);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should correctly handle N larger than available data", async () => {
|
|
162
|
+
const rand = `${TimeStamp.now().toString()}${Math.random()}`;
|
|
163
|
+
const time = await client.channels.create({
|
|
164
|
+
name: `time-${rand}`,
|
|
165
|
+
dataType: "timestamp",
|
|
166
|
+
isIndex: true,
|
|
167
|
+
});
|
|
168
|
+
const data = await client.channels.create({
|
|
169
|
+
name: `data-${rand}`,
|
|
170
|
+
dataType: "float32",
|
|
171
|
+
index: time.key,
|
|
172
|
+
});
|
|
173
|
+
const start = TimeStamp.now();
|
|
174
|
+
const timeData = [
|
|
175
|
+
start,
|
|
176
|
+
start.add(TimeSpan.seconds(1)),
|
|
177
|
+
start.add(TimeSpan.seconds(2)),
|
|
178
|
+
];
|
|
179
|
+
const dataValues = [1, 2, 3];
|
|
180
|
+
await client.write(start, { [time.key]: timeData, [data.key]: dataValues });
|
|
181
|
+
|
|
182
|
+
const result = await client.readLatest(data.key, 10);
|
|
183
|
+
expect(Array.from(result)).toEqual([1, 2, 3]);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
63
186
|
});
|
package/src/framer/client.ts
CHANGED
|
@@ -13,32 +13,22 @@ import {
|
|
|
13
13
|
type CrudeTimeRange,
|
|
14
14
|
type CrudeTimeStamp,
|
|
15
15
|
type MultiSeries,
|
|
16
|
-
|
|
16
|
+
TimeRange,
|
|
17
17
|
TimeSpan,
|
|
18
18
|
} from "@synnaxlabs/x";
|
|
19
19
|
|
|
20
20
|
import { channel } from "@/channel";
|
|
21
21
|
import { Deleter } from "@/framer/deleter";
|
|
22
|
-
import { Frame
|
|
23
|
-
import { Iterator, type IteratorConfig } from "@/framer/iterator";
|
|
22
|
+
import { Frame } from "@/framer/frame";
|
|
23
|
+
import { AUTO_SPAN, Iterator, type IteratorConfig } from "@/framer/iterator";
|
|
24
24
|
import { openStreamer, type Streamer, type StreamerConfig } from "@/framer/streamer";
|
|
25
25
|
import { Writer, type WriterConfig, WriterMode } from "@/framer/writer";
|
|
26
|
-
import { ontology } from "@/ontology";
|
|
27
|
-
|
|
28
|
-
export const ontologyID = (key: channel.Key): ontology.ID =>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
config: T | channel.Params,
|
|
33
|
-
): T => {
|
|
34
|
-
if (
|
|
35
|
-
Array.isArray(config) ||
|
|
36
|
-
typeof config !== "object" ||
|
|
37
|
-
(typeof config === "object" && "key" in config)
|
|
38
|
-
)
|
|
39
|
-
return { channels: config } as T;
|
|
40
|
-
return config;
|
|
41
|
-
};
|
|
26
|
+
import { type ontology } from "@/ontology";
|
|
27
|
+
|
|
28
|
+
export const ontologyID = (key: channel.Key): ontology.ID => ({
|
|
29
|
+
type: "framer",
|
|
30
|
+
key: key.toString(),
|
|
31
|
+
});
|
|
42
32
|
|
|
43
33
|
export class Client {
|
|
44
34
|
private readonly streamClient: WebSocketClient;
|
|
@@ -78,25 +68,10 @@ export class Client {
|
|
|
78
68
|
* writerConfig for more detail.
|
|
79
69
|
* @returns a new {@link Writer}.
|
|
80
70
|
*/
|
|
81
|
-
async openWriter(config: WriterConfig
|
|
82
|
-
return await Writer._open(
|
|
83
|
-
this.retriever,
|
|
84
|
-
this.streamClient,
|
|
85
|
-
normalizeConfig<WriterConfig>(config),
|
|
86
|
-
);
|
|
71
|
+
async openWriter(config: WriterConfig): Promise<Writer> {
|
|
72
|
+
return await Writer._open(this.retriever, this.streamClient, config);
|
|
87
73
|
}
|
|
88
74
|
|
|
89
|
-
/***
|
|
90
|
-
* Opens a new streamer on the given channels.
|
|
91
|
-
*
|
|
92
|
-
* @param channels - A key, name, list of keys, or list of names of the channels to
|
|
93
|
-
* stream values from.
|
|
94
|
-
* @throws a QueryError if any of the given channels do not exist.
|
|
95
|
-
* @returns a new {@link Streamer} that must be closed when done streaming, otherwise
|
|
96
|
-
* a network socket will remain open.
|
|
97
|
-
*/
|
|
98
|
-
async openStreamer(channels: channel.Params): Promise<Streamer>;
|
|
99
|
-
|
|
100
75
|
/**
|
|
101
76
|
* Opens a new streamer with the provided configuration.
|
|
102
77
|
*
|
|
@@ -108,17 +83,8 @@ export class Client {
|
|
|
108
83
|
* and then will start reading new values.
|
|
109
84
|
*
|
|
110
85
|
*/
|
|
111
|
-
async openStreamer(config: StreamerConfig): Promise<Streamer
|
|
112
|
-
|
|
113
|
-
/** Overload to provide interface compatibility with @see StreamOpener */
|
|
114
|
-
async openStreamer(config: StreamerConfig | channel.Params): Promise<Streamer>;
|
|
115
|
-
|
|
116
|
-
async openStreamer(config: StreamerConfig | channel.Params): Promise<Streamer> {
|
|
117
|
-
return await openStreamer(
|
|
118
|
-
this.retriever,
|
|
119
|
-
this.streamClient,
|
|
120
|
-
normalizeConfig<StreamerConfig>(config),
|
|
121
|
-
);
|
|
86
|
+
async openStreamer(config: StreamerConfig): Promise<Streamer> {
|
|
87
|
+
return await openStreamer(this.retriever, this.streamClient, config);
|
|
122
88
|
}
|
|
123
89
|
|
|
124
90
|
async write(
|
|
@@ -205,6 +171,34 @@ export class Client {
|
|
|
205
171
|
return frame;
|
|
206
172
|
}
|
|
207
173
|
|
|
174
|
+
async readLatest(channel: channel.KeyOrName, n: number): Promise<MultiSeries>;
|
|
175
|
+
|
|
176
|
+
async readLatest(channels: channel.Params, n: number): Promise<Frame>;
|
|
177
|
+
|
|
178
|
+
async readLatest(
|
|
179
|
+
channels: channel.Params,
|
|
180
|
+
n: number = 1,
|
|
181
|
+
): Promise<MultiSeries | Frame> {
|
|
182
|
+
const { single } = channel.analyzeParams(channels);
|
|
183
|
+
const fr = await this.readLatestNFrame(channels, n);
|
|
184
|
+
if (single) return fr.get(channels as channel.KeyOrName);
|
|
185
|
+
return fr;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private async readLatestNFrame(channels: channel.Params, n: number): Promise<Frame> {
|
|
189
|
+
const i = await this.openIterator(TimeRange.MAX, channels, { chunkSize: n });
|
|
190
|
+
const frame = new Frame();
|
|
191
|
+
if (n > 0)
|
|
192
|
+
try {
|
|
193
|
+
await i.seekLast();
|
|
194
|
+
await i.prev(AUTO_SPAN);
|
|
195
|
+
frame.push(i.value);
|
|
196
|
+
} finally {
|
|
197
|
+
await i.close();
|
|
198
|
+
}
|
|
199
|
+
return frame;
|
|
200
|
+
}
|
|
201
|
+
|
|
208
202
|
async delete(channels: channel.Params, timeRange: TimeRange): Promise<void> {
|
|
209
203
|
const { normalized, variant } = channel.analyzeParams(channels);
|
|
210
204
|
if (variant === "keys")
|
package/src/framer/codec.ts
CHANGED
|
@@ -11,12 +11,12 @@ import { TimeRange, TimeStamp } from "@synnaxlabs/x/telem";
|
|
|
11
11
|
import { describe, expect, test } from "vitest";
|
|
12
12
|
|
|
13
13
|
import { NotFoundError, UnauthorizedError } from "@/errors";
|
|
14
|
-
import { newClient } from "@/setupspecs";
|
|
15
14
|
import { newIndexedPair } from "@/testutil/channels";
|
|
15
|
+
import { newTestClient } from "@/testutil/client";
|
|
16
16
|
import { secondsLinspace } from "@/testutil/telem";
|
|
17
17
|
import { randomSeries } from "@/util/telem";
|
|
18
18
|
|
|
19
|
-
const client =
|
|
19
|
+
const client = newTestClient();
|
|
20
20
|
|
|
21
21
|
describe("Deleter", () => {
|
|
22
22
|
test("Client - basic delete", async () => {
|
package/src/framer/deleter.ts
CHANGED
package/src/framer/frame.ts
CHANGED
|
@@ -18,14 +18,11 @@ import {
|
|
|
18
18
|
TimeRange,
|
|
19
19
|
TimeStamp,
|
|
20
20
|
} from "@synnaxlabs/x/telem";
|
|
21
|
-
import { z } from "zod
|
|
21
|
+
import { z } from "zod";
|
|
22
22
|
|
|
23
23
|
import { type channel } from "@/channel";
|
|
24
24
|
import { UnexpectedError, ValidationError } from "@/errors";
|
|
25
25
|
|
|
26
|
-
export const ONTOLOGY_TYPE = "framer";
|
|
27
|
-
export type OntologyType = typeof ONTOLOGY_TYPE;
|
|
28
|
-
|
|
29
26
|
type ColumnType = "key" | "name" | null;
|
|
30
27
|
|
|
31
28
|
export interface Digest extends Record<channel.KeyOrName, SeriesDigest[]> {}
|
|
@@ -11,12 +11,12 @@ import { TimeRange, TimeSpan, TimeStamp } from "@synnaxlabs/x/telem";
|
|
|
11
11
|
import { describe, expect, test } from "vitest";
|
|
12
12
|
|
|
13
13
|
import { AUTO_SPAN } from "@/framer/iterator";
|
|
14
|
-
import { newClient } from "@/setupspecs";
|
|
15
14
|
import { newIndexedPair } from "@/testutil/channels";
|
|
15
|
+
import { newTestClient } from "@/testutil/client";
|
|
16
16
|
import { secondsLinspace } from "@/testutil/telem";
|
|
17
17
|
import { randomSeries } from "@/util/telem";
|
|
18
18
|
|
|
19
|
-
const client =
|
|
19
|
+
const client = newTestClient();
|
|
20
20
|
|
|
21
21
|
describe("Iterator", () => {
|
|
22
22
|
test("happy path", async () => {
|
|
@@ -50,7 +50,7 @@ describe("Iterator", () => {
|
|
|
50
50
|
);
|
|
51
51
|
|
|
52
52
|
try {
|
|
53
|
-
expect(await iter.seekFirst()).
|
|
53
|
+
expect(await iter.seekFirst()).toBe(true);
|
|
54
54
|
let c = 0;
|
|
55
55
|
while (await iter.next(TimeSpan.seconds(1))) {
|
|
56
56
|
c++;
|
|
@@ -78,24 +78,24 @@ describe("Iterator", () => {
|
|
|
78
78
|
const iter = await client.openIterator(TimeRange.MAX, channels, { chunkSize: 4 });
|
|
79
79
|
|
|
80
80
|
try {
|
|
81
|
-
expect(await iter.seekFirst()).
|
|
81
|
+
expect(await iter.seekFirst()).toBe(true);
|
|
82
82
|
|
|
83
|
-
expect(await iter.next(AUTO_SPAN)).
|
|
83
|
+
expect(await iter.next(AUTO_SPAN)).toBe(true);
|
|
84
84
|
expect(iter.value.get(idx_ch.key).data).toEqual(
|
|
85
85
|
new BigInt64Array(secondsLinspace(1, 4).map((v) => v.valueOf())),
|
|
86
86
|
);
|
|
87
87
|
|
|
88
|
-
expect(await iter.next(AUTO_SPAN)).
|
|
88
|
+
expect(await iter.next(AUTO_SPAN)).toBe(true);
|
|
89
89
|
expect(iter.value.get(idx_ch.key).data).toEqual(
|
|
90
90
|
new BigInt64Array(secondsLinspace(5, 4).map((v) => v.valueOf())),
|
|
91
91
|
);
|
|
92
92
|
|
|
93
|
-
expect(await iter.next(AUTO_SPAN)).
|
|
93
|
+
expect(await iter.next(AUTO_SPAN)).toBe(true);
|
|
94
94
|
expect(iter.value.get(idx_ch.key).data).toEqual(
|
|
95
95
|
new BigInt64Array(secondsLinspace(9, 2).map((v) => v.valueOf())),
|
|
96
96
|
);
|
|
97
97
|
|
|
98
|
-
expect(await iter.next(AUTO_SPAN)).
|
|
98
|
+
expect(await iter.next(AUTO_SPAN)).toBe(false);
|
|
99
99
|
} finally {
|
|
100
100
|
await iter.close();
|
|
101
101
|
}
|