@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/framer/iterator.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
10
|
import { EOF, type Stream } from "@synnaxlabs/freighter";
|
|
11
|
-
import { type z } from "zod
|
|
11
|
+
import { type z } from "zod";
|
|
12
12
|
|
|
13
13
|
export class StreamProxy<RQ extends z.ZodType, RS extends z.ZodType> {
|
|
14
14
|
readonly name: string;
|
|
@@ -15,13 +15,14 @@ import { type channel } from "@/channel";
|
|
|
15
15
|
import { Frame } from "@/framer/frame";
|
|
16
16
|
import {
|
|
17
17
|
HardenedStreamer,
|
|
18
|
-
|
|
18
|
+
ObservableStreamer,
|
|
19
19
|
type Streamer,
|
|
20
|
+
streamerConfigZ,
|
|
20
21
|
} from "@/framer/streamer";
|
|
21
|
-
import { newClient } from "@/setupspecs";
|
|
22
22
|
import { newVirtualChannel } from "@/testutil/channels";
|
|
23
|
+
import { newTestClient } from "@/testutil/client";
|
|
23
24
|
|
|
24
|
-
const client =
|
|
25
|
+
const client = newTestClient();
|
|
25
26
|
|
|
26
27
|
describe("Streamer", () => {
|
|
27
28
|
describe("standard", () => {
|
|
@@ -273,55 +274,60 @@ describe("Streamer", () => {
|
|
|
273
274
|
});
|
|
274
275
|
});
|
|
275
276
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (err) throw err;
|
|
289
|
-
}
|
|
290
|
-
this.updateMock(channels);
|
|
291
|
-
return Promise.resolve();
|
|
277
|
+
class MockStreamer implements Streamer {
|
|
278
|
+
keys: channel.Key[] = [];
|
|
279
|
+
updateMock = vi.fn();
|
|
280
|
+
readMock = vi.fn();
|
|
281
|
+
closeMock = vi.fn();
|
|
282
|
+
responses: [Frame, Error | null][] = [];
|
|
283
|
+
updateErrors: (Error | null)[] = [];
|
|
284
|
+
|
|
285
|
+
update(channels: channel.Params): Promise<void> {
|
|
286
|
+
if (this.updateErrors.length > 0) {
|
|
287
|
+
const err = this.updateErrors.shift()!;
|
|
288
|
+
if (err) throw err;
|
|
292
289
|
}
|
|
290
|
+
this.updateMock(channels);
|
|
291
|
+
return Promise.resolve();
|
|
292
|
+
}
|
|
293
293
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
294
|
+
close(): void {
|
|
295
|
+
this.closeMock();
|
|
296
|
+
}
|
|
297
297
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
298
|
+
async read(): Promise<Frame> {
|
|
299
|
+
this.readMock();
|
|
300
|
+
if (this.responses.length === 0) throw new EOF();
|
|
301
|
+
const [frame, err] = this.responses.shift()!;
|
|
302
|
+
if (err) throw err;
|
|
303
|
+
return frame;
|
|
304
|
+
}
|
|
305
305
|
|
|
306
|
-
|
|
306
|
+
async next(): Promise<IteratorResult<Frame, any>> {
|
|
307
|
+
try {
|
|
307
308
|
const fr = await this.read();
|
|
308
309
|
return { done: false, value: fr };
|
|
310
|
+
} catch (err) {
|
|
311
|
+
if (EOF.matches(err)) return { done: true, value: undefined };
|
|
312
|
+
throw err;
|
|
309
313
|
}
|
|
314
|
+
}
|
|
310
315
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
316
|
+
[Symbol.asyncIterator](): AsyncIterator<Frame, any, undefined> {
|
|
317
|
+
return this;
|
|
314
318
|
}
|
|
319
|
+
}
|
|
315
320
|
|
|
321
|
+
describe("hardened", () => {
|
|
316
322
|
it("should correctly call the underlying streamer methods", async () => {
|
|
317
323
|
const streamer = new MockStreamer();
|
|
318
324
|
const openMock = vi.fn();
|
|
319
|
-
const config = { channels: [1, 2, 3] };
|
|
325
|
+
const config = { channels: [1, 2, 3], useExperimentalCodec: false };
|
|
320
326
|
const fr = new Frame({ 1: new Series([1]) });
|
|
321
327
|
const hardened = await HardenedStreamer.open(
|
|
322
328
|
async (cfg) => {
|
|
323
329
|
openMock(cfg);
|
|
324
|
-
const cfg_ =
|
|
330
|
+
const cfg_ = streamerConfigZ.parse(cfg);
|
|
325
331
|
streamer.responses = [[fr, null]];
|
|
326
332
|
streamer.keys = cfg_.channels as channel.Key[];
|
|
327
333
|
return streamer;
|
|
@@ -329,7 +335,10 @@ describe("Streamer", () => {
|
|
|
329
335
|
{ channels: [1, 2, 3] },
|
|
330
336
|
);
|
|
331
337
|
expect(hardened.keys).toEqual([1, 2, 3]);
|
|
332
|
-
expect(openMock).toHaveBeenCalledWith(
|
|
338
|
+
expect(openMock).toHaveBeenCalledWith({
|
|
339
|
+
...config,
|
|
340
|
+
downsampleFactor: 1,
|
|
341
|
+
});
|
|
333
342
|
await hardened.update([1, 2, 3]);
|
|
334
343
|
expect(streamer.updateMock).toHaveBeenCalledWith([1, 2, 3]);
|
|
335
344
|
const fr2 = await hardened.read();
|
|
@@ -465,4 +474,144 @@ describe("Streamer", () => {
|
|
|
465
474
|
expect(openerMock).toHaveBeenCalledTimes(2);
|
|
466
475
|
});
|
|
467
476
|
});
|
|
477
|
+
|
|
478
|
+
describe("observable", () => {
|
|
479
|
+
it("should notify observers when frames are received", async () => {
|
|
480
|
+
const mockStreamer = new MockStreamer();
|
|
481
|
+
const frame1 = new Frame({ 1: new Series([1, 2, 3]) });
|
|
482
|
+
const frame2 = new Frame({ 1: new Series([4, 5, 6]) });
|
|
483
|
+
|
|
484
|
+
mockStreamer.responses = [
|
|
485
|
+
[frame1, null],
|
|
486
|
+
[frame2, null],
|
|
487
|
+
];
|
|
488
|
+
mockStreamer.keys = [1];
|
|
489
|
+
|
|
490
|
+
const observable = new ObservableStreamer(mockStreamer);
|
|
491
|
+
|
|
492
|
+
const receivedFrames: Frame[] = [];
|
|
493
|
+
observable.onChange((frame) => {
|
|
494
|
+
receivedFrames.push(frame);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
await expect.poll(() => receivedFrames.length).toBe(2);
|
|
498
|
+
expect(receivedFrames[0]).toEqual(frame1);
|
|
499
|
+
expect(receivedFrames[1]).toEqual(frame2);
|
|
500
|
+
|
|
501
|
+
await observable.close();
|
|
502
|
+
expect(mockStreamer.closeMock).toHaveBeenCalled();
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
test("should apply transform function to frames", async () => {
|
|
506
|
+
const mockStreamer = new MockStreamer();
|
|
507
|
+
const frame1 = new Frame({ 1: new Series([1, 2, 3]) });
|
|
508
|
+
const frame2 = new Frame({ 1: new Series([4, 5, 6]) });
|
|
509
|
+
|
|
510
|
+
mockStreamer.responses = [
|
|
511
|
+
[frame1, null],
|
|
512
|
+
[frame2, null],
|
|
513
|
+
];
|
|
514
|
+
mockStreamer.keys = [1];
|
|
515
|
+
|
|
516
|
+
const transform = (frame: Frame): [number, true] | [null, false] => {
|
|
517
|
+
try {
|
|
518
|
+
const data = Array.from(frame.get(1));
|
|
519
|
+
const firstValue = data[0] as number;
|
|
520
|
+
return [firstValue, true];
|
|
521
|
+
} catch {
|
|
522
|
+
return [null, false];
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const observable = new ObservableStreamer(mockStreamer, transform);
|
|
527
|
+
|
|
528
|
+
const receivedValues: number[] = [];
|
|
529
|
+
observable.onChange((value) => {
|
|
530
|
+
if (value !== null) receivedValues.push(value);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
await expect.poll(() => receivedValues.length).toBe(2);
|
|
534
|
+
expect(receivedValues[0]).toBe(1);
|
|
535
|
+
expect(receivedValues[1]).toBe(4);
|
|
536
|
+
|
|
537
|
+
await observable.close();
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test("should handle multiple observers", async () => {
|
|
541
|
+
const mockStreamer = new MockStreamer();
|
|
542
|
+
const frame1 = new Frame({ 1: new Series([10, 20]) });
|
|
543
|
+
|
|
544
|
+
mockStreamer.responses = [[frame1, null]];
|
|
545
|
+
mockStreamer.keys = [1];
|
|
546
|
+
|
|
547
|
+
const observable = new ObservableStreamer(mockStreamer);
|
|
548
|
+
|
|
549
|
+
const observer1Results: Frame[] = [];
|
|
550
|
+
const observer2Results: Frame[] = [];
|
|
551
|
+
|
|
552
|
+
observable.onChange((frame) => {
|
|
553
|
+
observer1Results.push(frame);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
observable.onChange((frame) => {
|
|
557
|
+
observer2Results.push(frame);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
await expect.poll(() => observer1Results.length).toBe(1);
|
|
561
|
+
expect(observer2Results).toHaveLength(1);
|
|
562
|
+
expect(observer1Results[0]).toEqual(frame1);
|
|
563
|
+
expect(observer2Results[0]).toEqual(frame1);
|
|
564
|
+
|
|
565
|
+
await observable.close();
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
test("should update channels on underlying streamer", async () => {
|
|
569
|
+
const mockStreamer = new MockStreamer();
|
|
570
|
+
mockStreamer.keys = [1, 2];
|
|
571
|
+
|
|
572
|
+
const observable = new ObservableStreamer(mockStreamer);
|
|
573
|
+
|
|
574
|
+
await observable.update([3, 4, 5]);
|
|
575
|
+
|
|
576
|
+
expect(mockStreamer.updateMock).toHaveBeenCalledWith([3, 4, 5]);
|
|
577
|
+
|
|
578
|
+
await observable.close();
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
test("should handle empty frame stream gracefully", async () => {
|
|
582
|
+
const mockStreamer = new MockStreamer();
|
|
583
|
+
mockStreamer.keys = [1];
|
|
584
|
+
|
|
585
|
+
const observable = new ObservableStreamer(mockStreamer);
|
|
586
|
+
|
|
587
|
+
const receivedFrames: Frame[] = [];
|
|
588
|
+
observable.onChange((frame) => {
|
|
589
|
+
receivedFrames.push(frame);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
await expect.poll(() => receivedFrames.length).toBe(0);
|
|
593
|
+
expect(receivedFrames).toHaveLength(0);
|
|
594
|
+
|
|
595
|
+
await observable.close();
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
test("should properly close and cleanup resources", async () => {
|
|
599
|
+
const mockStreamer = new MockStreamer();
|
|
600
|
+
const frame1 = new Frame({ 1: new Series([1]) });
|
|
601
|
+
|
|
602
|
+
mockStreamer.responses = [[frame1, null]];
|
|
603
|
+
mockStreamer.keys = [1];
|
|
604
|
+
|
|
605
|
+
const observable = new ObservableStreamer(mockStreamer);
|
|
606
|
+
|
|
607
|
+
const receivedFrames: Frame[] = [];
|
|
608
|
+
observable.onChange((frame) => {
|
|
609
|
+
receivedFrames.push(frame);
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
await observable.close();
|
|
613
|
+
|
|
614
|
+
expect(mockStreamer.closeMock).toHaveBeenCalled();
|
|
615
|
+
});
|
|
616
|
+
});
|
|
468
617
|
});
|
package/src/framer/streamer.ts
CHANGED
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
|
|
10
10
|
import { EOF, type Stream, type WebSocketClient } from "@synnaxlabs/freighter";
|
|
11
11
|
import { breaker, observe, TimeSpan } from "@synnaxlabs/x";
|
|
12
|
-
import { z } from "zod
|
|
12
|
+
import { z } from "zod";
|
|
13
13
|
|
|
14
14
|
import { type channel } from "@/channel";
|
|
15
|
+
import { paramsZ } from "@/channel/payload";
|
|
15
16
|
import { ReadAdapter } from "@/framer/adapter";
|
|
16
17
|
import { WSStreamerCodec } from "@/framer/codec";
|
|
17
18
|
import { Frame, frameZ } from "@/framer/frame";
|
|
18
19
|
import { StreamProxy } from "@/framer/streamProxy";
|
|
19
|
-
import { payloadZ } from "@/ranger/payload";
|
|
20
20
|
|
|
21
21
|
const reqZ = z.object({ keys: z.number().array(), downsampleFactor: z.number() });
|
|
22
22
|
|
|
@@ -36,17 +36,21 @@ export interface StreamerResponse extends z.infer<typeof resZ> {}
|
|
|
36
36
|
|
|
37
37
|
const ENDPOINT = "/frame/stream";
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
* Configuration options for creating a new streamer.
|
|
41
|
-
*/
|
|
42
|
-
export interface StreamerConfig {
|
|
39
|
+
const intermediateStreamerConfigZ = z.object({
|
|
43
40
|
/** The channels to stream data from. Can be channel keys, names, or payloads. */
|
|
44
|
-
channels:
|
|
41
|
+
channels: paramsZ,
|
|
45
42
|
/** Optional factor to downsample the data by. Defaults to 1 (no downsampling). */
|
|
46
|
-
downsampleFactor
|
|
47
|
-
/** Whether to use the experimental codec for streaming. Defaults to
|
|
48
|
-
useExperimentalCodec
|
|
49
|
-
}
|
|
43
|
+
downsampleFactor: z.number().optional().default(1),
|
|
44
|
+
/** Whether to use the experimental codec for streaming. Defaults to true. */
|
|
45
|
+
useExperimentalCodec: z.boolean().optional().default(false),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export const streamerConfigZ = intermediateStreamerConfigZ.or(
|
|
49
|
+
paramsZ.transform((channels) => intermediateStreamerConfigZ.parse({ channels })),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
export type StreamerConfig = z.input<typeof streamerConfigZ>;
|
|
53
|
+
type ParsedStreamerConfig = z.output<typeof streamerConfigZ>;
|
|
50
54
|
|
|
51
55
|
/**
|
|
52
56
|
* A streamer is used to stream frames of telemetry in real-time from a Synnax cluster.
|
|
@@ -85,25 +89,9 @@ export interface Streamer extends AsyncIterator<Frame>, AsyncIterable<Frame> {
|
|
|
85
89
|
* A function that opens a streamer.
|
|
86
90
|
*/
|
|
87
91
|
export interface StreamOpener {
|
|
88
|
-
(config: StreamerConfig
|
|
92
|
+
(config: StreamerConfig): Promise<Streamer>;
|
|
89
93
|
}
|
|
90
94
|
|
|
91
|
-
export const parseStreamerConfig = (
|
|
92
|
-
config: StreamerConfig | channel.Params,
|
|
93
|
-
): StreamerConfig => {
|
|
94
|
-
if (Array.isArray(config)) {
|
|
95
|
-
if (typeof config[0] === "object")
|
|
96
|
-
return {
|
|
97
|
-
channels: (config as channel.Payload[]).map((c) => c.key),
|
|
98
|
-
downsampleFactor: 1,
|
|
99
|
-
};
|
|
100
|
-
return { channels: config, downsampleFactor: 1 };
|
|
101
|
-
}
|
|
102
|
-
const parsed = payloadZ.safeParse(config);
|
|
103
|
-
if (parsed.success) return { channels: [parsed.data.key], downsampleFactor: 1 };
|
|
104
|
-
return config as StreamerConfig;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
95
|
/**
|
|
108
96
|
* Creates a function that opens streamers with the given retriever and client.
|
|
109
97
|
* @param retriever - The channel retriever to use for resolving channel information
|
|
@@ -113,7 +101,7 @@ export const parseStreamerConfig = (
|
|
|
113
101
|
export const createStreamOpener =
|
|
114
102
|
(retriever: channel.Retriever, client: WebSocketClient): StreamOpener =>
|
|
115
103
|
async (config) => {
|
|
116
|
-
const cfg =
|
|
104
|
+
const cfg = streamerConfigZ.parse(config);
|
|
117
105
|
const adapter = await ReadAdapter.open(retriever, cfg.channels);
|
|
118
106
|
if (cfg.useExperimentalCodec)
|
|
119
107
|
client = client.withCodec(new WSStreamerCodec(adapter.codec));
|
|
@@ -168,7 +156,8 @@ class CoreStreamer implements Streamer {
|
|
|
168
156
|
}
|
|
169
157
|
|
|
170
158
|
async update(channels: channel.Params): Promise<void> {
|
|
171
|
-
await this.adapter.update(channels);
|
|
159
|
+
const hasChanged = await this.adapter.update(channels);
|
|
160
|
+
if (!hasChanged) return;
|
|
172
161
|
this.stream.send({
|
|
173
162
|
keys: this.adapter.keys,
|
|
174
163
|
downsampleFactor: this.downsampleFactor,
|
|
@@ -193,15 +182,15 @@ export class HardenedStreamer implements Streamer {
|
|
|
193
182
|
private wrapped_: Streamer | null = null;
|
|
194
183
|
private readonly breaker: breaker.Breaker;
|
|
195
184
|
private readonly opener: StreamOpener;
|
|
196
|
-
private readonly config:
|
|
185
|
+
private readonly config: ParsedStreamerConfig;
|
|
197
186
|
|
|
198
187
|
private constructor(
|
|
199
188
|
opener: StreamOpener,
|
|
200
|
-
config: StreamerConfig
|
|
189
|
+
config: StreamerConfig,
|
|
201
190
|
breakerConfig: breaker.Config = {},
|
|
202
191
|
) {
|
|
203
192
|
this.opener = opener;
|
|
204
|
-
this.config =
|
|
193
|
+
this.config = streamerConfigZ.parse(config);
|
|
205
194
|
const {
|
|
206
195
|
maxRetries = 5000,
|
|
207
196
|
baseInterval = TimeSpan.seconds(1),
|
|
@@ -218,12 +207,11 @@ export class HardenedStreamer implements Streamer {
|
|
|
218
207
|
*/
|
|
219
208
|
static async open(
|
|
220
209
|
opener: StreamOpener,
|
|
221
|
-
config: StreamerConfig
|
|
210
|
+
config: StreamerConfig,
|
|
222
211
|
breakerConfig?: breaker.Config,
|
|
223
212
|
): Promise<HardenedStreamer> {
|
|
224
213
|
const h = new HardenedStreamer(opener, config, breakerConfig);
|
|
225
214
|
await h.runStreamer();
|
|
226
|
-
|
|
227
215
|
return h;
|
|
228
216
|
}
|
|
229
217
|
|
|
@@ -248,7 +236,7 @@ export class HardenedStreamer implements Streamer {
|
|
|
248
236
|
}
|
|
249
237
|
|
|
250
238
|
async update(channels: channel.Params): Promise<void> {
|
|
251
|
-
this.config.channels = channels;
|
|
239
|
+
this.config.channels = paramsZ.parse(channels);
|
|
252
240
|
try {
|
|
253
241
|
await this.wrapped.update(channels);
|
|
254
242
|
} catch {
|
|
@@ -314,6 +302,10 @@ export class ObservableStreamer<V = Frame>
|
|
|
314
302
|
this.closePromise = this.stream();
|
|
315
303
|
}
|
|
316
304
|
|
|
305
|
+
async update(channels: channel.Params): Promise<void> {
|
|
306
|
+
await this.streamer.update(channels);
|
|
307
|
+
}
|
|
308
|
+
|
|
317
309
|
async close(): Promise<void> {
|
|
318
310
|
this.streamer.close();
|
|
319
311
|
await this.closePromise;
|
|
@@ -12,12 +12,12 @@ import { describe, expect, it, test } from "vitest";
|
|
|
12
12
|
|
|
13
13
|
import { UnauthorizedError, ValidationError } from "@/errors";
|
|
14
14
|
import { ALWAYS_INDEX_PERSIST_ON_AUTO_COMMIT, WriterMode } from "@/framer/writer";
|
|
15
|
-
import { newClient } from "@/setupspecs";
|
|
16
15
|
import { newIndexedPair } from "@/testutil/channels";
|
|
16
|
+
import { newTestClient } from "@/testutil/client";
|
|
17
17
|
import { secondsLinspace } from "@/testutil/telem";
|
|
18
18
|
import { randomSeries } from "@/util/telem";
|
|
19
19
|
|
|
20
|
-
const client =
|
|
20
|
+
const client = newTestClient();
|
|
21
21
|
describe("Writer", () => {
|
|
22
22
|
describe("Writer", () => {
|
|
23
23
|
test("basic write", async () => {
|
|
@@ -34,7 +34,7 @@ describe("Writer", () => {
|
|
|
34
34
|
} finally {
|
|
35
35
|
await writer.close();
|
|
36
36
|
}
|
|
37
|
-
expect(true).
|
|
37
|
+
expect(true).toBe(true);
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
test("write to unknown channel key", async () => {
|
|
@@ -87,7 +87,7 @@ describe("Writer", () => {
|
|
|
87
87
|
} finally {
|
|
88
88
|
await writer.close();
|
|
89
89
|
}
|
|
90
|
-
expect(true).
|
|
90
|
+
expect(true).toBe(true);
|
|
91
91
|
|
|
92
92
|
const f = await client.read(
|
|
93
93
|
new TimeRange(TimeStamp.seconds(1), TimeStamp.seconds(11)),
|
|
@@ -113,7 +113,7 @@ describe("Writer", () => {
|
|
|
113
113
|
} finally {
|
|
114
114
|
await writer.close();
|
|
115
115
|
}
|
|
116
|
-
expect(true).
|
|
116
|
+
expect(true).toBe(true);
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
test("write with auto commit and a set interval", async () => {
|
|
@@ -133,7 +133,7 @@ describe("Writer", () => {
|
|
|
133
133
|
} finally {
|
|
134
134
|
await writer.close();
|
|
135
135
|
}
|
|
136
|
-
expect(true).
|
|
136
|
+
expect(true).toBe(true);
|
|
137
137
|
});
|
|
138
138
|
|
|
139
139
|
test("write with auto-commit off and incorrect data length validation error", async () => {
|