@synnaxlabs/client 0.54.2 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +10 -13
- package/dist/client.cjs +60 -42
- package/dist/client.js +8037 -6265
- package/dist/src/access/policy/client.d.ts +70 -80
- package/dist/src/access/policy/client.d.ts.map +1 -1
- package/dist/src/access/policy/types.gen.d.ts +18 -20
- package/dist/src/access/policy/types.gen.d.ts.map +1 -1
- package/dist/src/access/role/client.d.ts.map +1 -1
- package/dist/src/access/role/types.gen.d.ts +2 -2
- package/dist/src/actions/actions.d.ts +68 -0
- package/dist/src/actions/actions.d.ts.map +1 -0
- package/dist/src/actions/actions.spec.d.ts +2 -0
- package/dist/src/actions/actions.spec.d.ts.map +1 -0
- package/dist/src/actions/external.d.ts +2 -0
- package/dist/src/actions/external.d.ts.map +1 -0
- package/dist/src/actions/index.d.ts +2 -0
- package/dist/src/actions/index.d.ts.map +1 -0
- package/dist/src/arc/arc.spec.d.ts +2 -0
- package/dist/src/arc/arc.spec.d.ts.map +1 -0
- package/dist/src/arc/client.d.ts.map +1 -1
- package/dist/src/arc/compiler/types.gen.d.ts +1 -1
- package/dist/src/arc/compiler/types.gen.d.ts.map +1 -1
- package/dist/src/arc/graph/types.gen.d.ts +40 -40
- package/dist/src/arc/graph/types.gen.d.ts.map +1 -1
- package/dist/src/arc/ir/types.gen.d.ts +202 -233
- package/dist/src/arc/ir/types.gen.d.ts.map +1 -1
- package/dist/src/arc/module/types.gen.d.ts +63 -82
- package/dist/src/arc/module/types.gen.d.ts.map +1 -1
- package/dist/src/arc/program/types.gen.d.ts +63 -82
- package/dist/src/arc/program/types.gen.d.ts.map +1 -1
- package/dist/src/arc/types/types.gen.d.ts +11 -11
- package/dist/src/arc/types/types.gen.d.ts.map +1 -1
- package/dist/src/arc/types.gen.d.ts +139 -158
- package/dist/src/arc/types.gen.d.ts.map +1 -1
- package/dist/src/auth/auth.d.ts +3 -3
- package/dist/src/auth/auth.d.ts.map +1 -1
- package/dist/src/channel/client.d.ts +2 -2
- package/dist/src/channel/client.d.ts.map +1 -1
- package/dist/src/channel/retriever.d.ts +5 -8
- package/dist/src/channel/retriever.d.ts.map +1 -1
- package/dist/src/channel/types.gen.d.ts +3 -3
- package/dist/src/channel/types.gen.d.ts.map +1 -1
- package/dist/src/channel/writer.d.ts.map +1 -1
- package/dist/src/client.d.ts +5 -0
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/connection/checker.d.ts +17 -2
- package/dist/src/connection/checker.d.ts.map +1 -1
- package/dist/src/control/state.d.ts.map +1 -1
- package/dist/src/device/client.d.ts.map +1 -1
- package/dist/src/device/types.gen.d.ts +6 -8
- package/dist/src/device/types.gen.d.ts.map +1 -1
- package/dist/src/errors.d.ts +2 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/framer/adapter.d.ts.map +1 -1
- package/dist/src/framer/client.d.ts +2 -2
- package/dist/src/framer/client.d.ts.map +1 -1
- package/dist/src/framer/codec.d.ts +9 -1
- package/dist/src/framer/codec.d.ts.map +1 -1
- package/dist/src/framer/deleter.d.ts.map +1 -1
- package/dist/src/framer/frame.d.ts +1 -1
- package/dist/src/framer/iterator.d.ts +84 -3
- package/dist/src/framer/iterator.d.ts.map +1 -1
- package/dist/src/framer/streamProxy.d.ts.map +1 -1
- package/dist/src/framer/streamer.d.ts +1 -3
- package/dist/src/framer/streamer.d.ts.map +1 -1
- package/dist/src/framer/types.gen.d.ts +18 -0
- package/dist/src/framer/types.gen.d.ts.map +1 -1
- package/dist/src/framer/writer.d.ts +8 -8
- package/dist/src/framer/writer.d.ts.map +1 -1
- package/dist/src/group/client.d.ts +1 -2
- package/dist/src/group/client.d.ts.map +1 -1
- package/dist/src/group/types.gen.d.ts +2 -2
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/label/client.d.ts +5 -8
- package/dist/src/label/client.d.ts.map +1 -1
- package/dist/src/lineplot/client.d.ts.map +1 -1
- package/dist/src/lineplot/types.gen.d.ts +2 -2
- package/dist/src/log/client.d.ts.map +1 -1
- package/dist/src/log/types.gen.d.ts +2 -2
- package/dist/src/ontology/client.d.ts +1 -3
- package/dist/src/ontology/client.d.ts.map +1 -1
- package/dist/src/ontology/payload.d.ts +12 -16
- package/dist/src/ontology/payload.d.ts.map +1 -1
- package/dist/src/ontology/types.gen.d.ts +1 -2
- package/dist/src/ontology/types.gen.d.ts.map +1 -1
- package/dist/src/ontology/writer.d.ts +5 -10
- package/dist/src/ontology/writer.d.ts.map +1 -1
- package/dist/src/rack/client.d.ts.map +1 -1
- package/dist/src/rack/types.gen.d.ts +3 -3
- package/dist/src/ranger/alias/client.d.ts.map +1 -1
- package/dist/src/ranger/client.d.ts.map +1 -1
- package/dist/src/ranger/kv/client.d.ts.map +1 -1
- package/dist/src/ranger/types.gen.d.ts +6 -6
- package/dist/src/ranger/types.gen.d.ts.map +1 -1
- package/dist/src/ranger/writer.d.ts +2 -3
- package/dist/src/ranger/writer.d.ts.map +1 -1
- package/dist/src/schematic/actions.d.ts +147 -0
- package/dist/src/schematic/actions.d.ts.map +1 -0
- package/dist/src/schematic/actions.gen.d.ts +484 -0
- package/dist/src/schematic/actions.gen.d.ts.map +1 -0
- package/dist/src/schematic/actions.spec.d.ts +2 -0
- package/dist/src/schematic/actions.spec.d.ts.map +1 -0
- package/dist/src/schematic/client.d.ts +53 -2
- package/dist/src/schematic/client.d.ts.map +1 -1
- package/dist/src/schematic/external.d.ts +2 -0
- package/dist/src/schematic/external.d.ts.map +1 -1
- package/dist/src/schematic/symbol/client.d.ts.map +1 -1
- package/dist/src/schematic/symbol/types.gen.d.ts +48 -58
- package/dist/src/schematic/symbol/types.gen.d.ts.map +1 -1
- package/dist/src/schematic/types.gen.d.ts +131 -5
- package/dist/src/schematic/types.gen.d.ts.map +1 -1
- package/dist/src/status/client.d.ts.map +1 -1
- package/dist/src/status/payload.d.ts +3 -3
- package/dist/src/table/actions.d.ts +156 -0
- package/dist/src/table/actions.d.ts.map +1 -0
- package/dist/src/table/actions.gen.d.ts +587 -0
- package/dist/src/table/actions.gen.d.ts.map +1 -0
- package/dist/src/table/client.d.ts +28 -2
- package/dist/src/table/client.d.ts.map +1 -1
- package/dist/src/table/external.d.ts +2 -0
- package/dist/src/table/external.d.ts.map +1 -1
- package/dist/src/table/types.gen.d.ts +71 -4
- package/dist/src/table/types.gen.d.ts.map +1 -1
- package/dist/src/task/client.d.ts.map +1 -1
- package/dist/src/task/types.gen.d.ts +7 -7
- package/dist/src/task/types.gen.d.ts.map +1 -1
- package/dist/src/user/client.d.ts +2 -2
- package/dist/src/user/client.d.ts.map +1 -1
- package/dist/src/user/types.gen.d.ts +2 -2
- package/dist/src/view/client.d.ts.map +1 -1
- package/dist/src/view/types.gen.d.ts +2 -2
- package/dist/src/workspace/client.d.ts.map +1 -1
- package/dist/src/workspace/types.gen.d.ts +3 -3
- package/dist/src/workspace/types.gen.d.ts.map +1 -1
- package/package.json +12 -11
- package/src/access/policy/client.ts +4 -7
- package/src/access/role/client.ts +6 -26
- package/src/actions/actions.spec.ts +229 -0
- package/src/actions/actions.ts +104 -0
- package/src/actions/external.ts +10 -0
- package/src/actions/index.ts +10 -0
- package/src/arc/arc.spec.ts +44 -0
- package/src/arc/client.ts +3 -7
- package/src/arc/compiler/types.gen.ts +2 -1
- package/src/arc/ir/types.gen.ts +102 -48
- package/src/arc/lsp.spec.ts +3 -7
- package/src/arc/types/types.gen.ts +3 -3
- package/src/auth/auth.spec.ts +12 -13
- package/src/auth/auth.ts +48 -34
- package/src/channel/batchRetriever.spec.ts +13 -4
- package/src/channel/channel.spec.ts +13 -0
- package/src/channel/client.ts +8 -6
- package/src/channel/retriever.ts +7 -16
- package/src/channel/types.gen.ts +1 -2
- package/src/channel/writer.ts +4 -20
- package/src/client.ts +3 -0
- package/src/connection/checker.ts +48 -10
- package/src/connection/connection.spec.ts +64 -2
- package/src/control/state.ts +5 -4
- package/src/device/client.ts +5 -8
- package/src/device/device.spec.ts +7 -5
- package/src/device/types.gen.ts +4 -4
- package/src/errors.ts +8 -9
- package/src/framer/adapter.ts +2 -4
- package/src/framer/client.ts +12 -0
- package/src/framer/codec.spec.ts +53 -3
- package/src/framer/codec.ts +58 -25
- package/src/framer/deleter.ts +2 -8
- package/src/framer/iterator.ts +42 -39
- package/src/framer/streamProxy.ts +11 -12
- package/src/framer/streamer.spec.ts +1 -1
- package/src/framer/streamer.ts +2 -7
- package/src/framer/types.gen.ts +20 -0
- package/src/framer/writer.spec.ts +221 -1
- package/src/framer/writer.ts +53 -28
- package/src/group/client.ts +4 -7
- package/src/index.ts +3 -2
- package/src/label/client.ts +6 -16
- package/src/label/label.spec.ts +12 -0
- package/src/lineplot/client.ts +6 -21
- package/src/log/client.ts +6 -21
- package/src/ontology/client.ts +2 -3
- package/src/ontology/ontology.spec.ts +10 -0
- package/src/ontology/types.gen.ts +0 -1
- package/src/ontology/writer.ts +4 -7
- package/src/rack/client.ts +4 -7
- package/src/rack/rack.spec.ts +12 -1
- package/src/ranger/alias/client.ts +6 -11
- package/src/ranger/client.ts +2 -3
- package/src/ranger/kv/client.ts +4 -7
- package/src/ranger/ranger.spec.ts +12 -0
- package/src/ranger/writer.ts +4 -17
- package/src/schematic/access.spec.ts +6 -6
- package/src/schematic/actions.gen.ts +200 -0
- package/src/schematic/actions.spec.ts +699 -0
- package/src/schematic/actions.ts +168 -0
- package/src/schematic/client.ts +34 -30
- package/src/schematic/external.ts +2 -0
- package/src/schematic/schematic.spec.ts +233 -69
- package/src/schematic/symbol/client.spec.ts +33 -9
- package/src/schematic/symbol/client.ts +6 -11
- package/src/schematic/symbol/types.gen.ts +1 -10
- package/src/schematic/types.gen.ts +55 -6
- package/src/status/client.ts +4 -10
- package/src/status/status.spec.ts +7 -6
- package/src/table/access.spec.ts +0 -6
- package/src/table/actions.gen.ts +243 -0
- package/src/table/actions.ts +255 -0
- package/src/table/client.ts +21 -25
- package/src/table/external.ts +2 -0
- package/src/table/table.spec.ts +588 -43
- package/src/table/types.gen.ts +58 -5
- package/src/task/client.ts +14 -20
- package/src/task/task.spec.ts +15 -1
- package/src/task/types.gen.ts +8 -6
- package/src/user/client.ts +6 -11
- package/src/view/client.ts +4 -7
- package/src/view/view.spec.ts +9 -5
- package/src/workspace/client.ts +6 -16
- package/src/workspace/types.gen.ts +2 -1
- package/src/workspace/workspace.spec.ts +14 -1
|
@@ -7,8 +7,9 @@
|
|
|
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 {
|
|
11
|
-
import {
|
|
10
|
+
import { type UnaryClient } from "@synnaxlabs/freighter";
|
|
11
|
+
import { TimeSpan, TimeStamp, URL } from "@synnaxlabs/x";
|
|
12
|
+
import { describe, expect, it, vi } from "vitest";
|
|
12
13
|
import { z } from "zod";
|
|
13
14
|
|
|
14
15
|
import { auth } from "@/auth";
|
|
@@ -87,4 +88,65 @@ describe("connectivity", () => {
|
|
|
87
88
|
expect(state.clientVersion).toBe("0.0.0");
|
|
88
89
|
});
|
|
89
90
|
});
|
|
91
|
+
describe("clock skew", () => {
|
|
92
|
+
const createMockClient = (nodeTime: TimeStamp): UnaryClient => ({
|
|
93
|
+
send: vi.fn().mockResolvedValue({
|
|
94
|
+
clusterKey: "test-cluster",
|
|
95
|
+
nodeVersion: __VERSION__,
|
|
96
|
+
nodeTime,
|
|
97
|
+
}),
|
|
98
|
+
use: vi.fn(),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should detect clock skew exceeding threshold", async () => {
|
|
102
|
+
const farFuture = TimeStamp.now().add(TimeSpan.hours(1));
|
|
103
|
+
const checker = new connection.Checker(
|
|
104
|
+
createMockClient(farFuture),
|
|
105
|
+
TimeSpan.seconds(30),
|
|
106
|
+
__VERSION__,
|
|
107
|
+
undefined,
|
|
108
|
+
TimeSpan.seconds(1),
|
|
109
|
+
);
|
|
110
|
+
const state = await checker.check();
|
|
111
|
+
expect(state.clockSkewExceeded).toBe(true);
|
|
112
|
+
expect(state.clockSkew.valueOf()).not.toBe(0n);
|
|
113
|
+
checker.stop();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should not flag skew within threshold", async () => {
|
|
117
|
+
const now = TimeStamp.now();
|
|
118
|
+
const checker = new connection.Checker(
|
|
119
|
+
createMockClient(now),
|
|
120
|
+
TimeSpan.seconds(30),
|
|
121
|
+
__VERSION__,
|
|
122
|
+
undefined,
|
|
123
|
+
TimeSpan.seconds(1),
|
|
124
|
+
);
|
|
125
|
+
const state = await checker.check();
|
|
126
|
+
expect(state.clockSkewExceeded).toBe(false);
|
|
127
|
+
checker.stop();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("should fire onChange when clockSkewExceeded changes", async () => {
|
|
131
|
+
let callCount = 0;
|
|
132
|
+
const farFuture = TimeStamp.now().add(TimeSpan.hours(1));
|
|
133
|
+
const checker = new connection.Checker(
|
|
134
|
+
createMockClient(farFuture),
|
|
135
|
+
TimeSpan.seconds(30),
|
|
136
|
+
__VERSION__,
|
|
137
|
+
undefined,
|
|
138
|
+
TimeSpan.seconds(1),
|
|
139
|
+
);
|
|
140
|
+
// Wait for the constructor's initial check to complete
|
|
141
|
+
await checker.check();
|
|
142
|
+
checker.onChange(() => {
|
|
143
|
+
callCount++;
|
|
144
|
+
});
|
|
145
|
+
// Trigger another check - skewExceeded stays true, status stays connected,
|
|
146
|
+
// so onChange should not fire
|
|
147
|
+
await checker.check();
|
|
148
|
+
expect(callCount).toBe(0);
|
|
149
|
+
checker.stop();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
90
152
|
});
|
package/src/control/state.ts
CHANGED
|
@@ -46,13 +46,14 @@ export class StateTracker
|
|
|
46
46
|
implements observe.ObservableAsyncCloseable<Transfer[]>
|
|
47
47
|
{
|
|
48
48
|
readonly states: Map<channel.Key, State>;
|
|
49
|
-
private readonly codec: binary.
|
|
49
|
+
private readonly codec: binary.JSONCodec;
|
|
50
50
|
|
|
51
51
|
constructor(streamer: framer.Streamer) {
|
|
52
52
|
super(streamer, (frame) => {
|
|
53
|
-
const
|
|
54
|
-
this.
|
|
55
|
-
|
|
53
|
+
const raw = Array.from(frame.series[0].as("string"));
|
|
54
|
+
const updates: Update[] = raw.map((r) => this.codec.decodeString(r, updateZ));
|
|
55
|
+
updates.forEach((u) => this.merge(u));
|
|
56
|
+
return [updates.flatMap((u) => u.transfers), true];
|
|
56
57
|
});
|
|
57
58
|
this.states = new Map();
|
|
58
59
|
this.codec = new binary.JSONCodec();
|
package/src/device/client.ts
CHANGED
|
@@ -7,7 +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 {
|
|
10
|
+
import { type UnaryClient } from "@synnaxlabs/freighter";
|
|
11
11
|
import { array, type record, zod } from "@synnaxlabs/x";
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
|
|
@@ -122,10 +122,9 @@ export class Client {
|
|
|
122
122
|
async retrieve(
|
|
123
123
|
args: RetrieveArgs & { schemas?: DeviceSchemas },
|
|
124
124
|
): Promise<Device | Array<Device>> {
|
|
125
|
-
const { schemas, ...rest } = args
|
|
125
|
+
const { schemas, ...rest } = args;
|
|
126
126
|
const isSingle = typeof rest === "object" && "key" in rest;
|
|
127
|
-
const res = await
|
|
128
|
-
this.client,
|
|
127
|
+
const res = await this.client.send(
|
|
129
128
|
"/device/retrieve",
|
|
130
129
|
rest,
|
|
131
130
|
retrieveArgsZ,
|
|
@@ -166,8 +165,7 @@ export class Client {
|
|
|
166
165
|
schemas?: DeviceSchemas,
|
|
167
166
|
): Promise<Device | Device[]> {
|
|
168
167
|
const isSingle = !Array.isArray(devices);
|
|
169
|
-
const res = await
|
|
170
|
-
this.client,
|
|
168
|
+
const res = await this.client.send(
|
|
171
169
|
"/device/create",
|
|
172
170
|
{ devices: array.toArray(devices) },
|
|
173
171
|
createReqZ(schemas),
|
|
@@ -177,8 +175,7 @@ export class Client {
|
|
|
177
175
|
}
|
|
178
176
|
|
|
179
177
|
async delete(keys: Key | Key[]): Promise<void> {
|
|
180
|
-
await
|
|
181
|
-
this.client,
|
|
178
|
+
await this.client.send(
|
|
182
179
|
"/device/delete",
|
|
183
180
|
{ keys: array.toArray(keys) },
|
|
184
181
|
deleteReqZ,
|
|
@@ -312,11 +312,13 @@ describe("Device", async () => {
|
|
|
312
312
|
});
|
|
313
313
|
|
|
314
314
|
it("should retrieve devices by search term", async () => {
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
315
|
+
const sensor1 = testDevices.find((d) => d.name === "sensor1")!;
|
|
316
|
+
await expect
|
|
317
|
+
.poll(async () => {
|
|
318
|
+
const result = await client.devices.retrieve({ searchTerm: "sensor1" });
|
|
319
|
+
return result.find((d) => d.key === sensor1.key);
|
|
320
|
+
})
|
|
321
|
+
.toMatchObject({ name: "sensor1", key: sensor1.key });
|
|
320
322
|
});
|
|
321
323
|
|
|
322
324
|
it("should support pagination with limit and offset", async () => {
|
package/src/device/types.gen.ts
CHANGED
|
@@ -15,18 +15,18 @@ import { z } from "zod";
|
|
|
15
15
|
import { ontology } from "@/ontology";
|
|
16
16
|
import { rack } from "@/rack";
|
|
17
17
|
|
|
18
|
+
export const keyZ = z.string();
|
|
19
|
+
export type Key = z.infer<typeof keyZ>;
|
|
20
|
+
|
|
18
21
|
/** StatusDetails contains device-specific status details identifying the device and its associated rack. */
|
|
19
22
|
export const statusDetailsZ = z.object({
|
|
20
23
|
/** rack is the key of the rack this device belongs to. */
|
|
21
24
|
rack: rack.keyZ,
|
|
22
25
|
/** device is the device identifier. */
|
|
23
|
-
device:
|
|
26
|
+
device: keyZ,
|
|
24
27
|
});
|
|
25
28
|
export interface StatusDetails extends z.infer<typeof statusDetailsZ> {}
|
|
26
29
|
|
|
27
|
-
export const keyZ = z.string();
|
|
28
|
-
export type Key = z.infer<typeof keyZ>;
|
|
29
|
-
|
|
30
30
|
export const statusZ = status.statusZ({ details: statusDetailsZ });
|
|
31
31
|
export type Status = z.infer<typeof statusZ>;
|
|
32
32
|
|
package/src/errors.ts
CHANGED
|
@@ -157,15 +157,14 @@ export const validateFieldNotNull = (
|
|
|
157
157
|
};
|
|
158
158
|
|
|
159
159
|
export const errorsMiddleware: Middleware = async (ctx, next) => {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
new Unreachable({
|
|
160
|
+
try {
|
|
161
|
+
return await next(ctx);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
if (err instanceof Unreachable)
|
|
164
|
+
throw new Unreachable({
|
|
166
165
|
message: `Cannot reach Core at ${err.url.host}:${err.url.port}`,
|
|
167
166
|
url: err.url,
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
|
|
167
|
+
});
|
|
168
|
+
throw err;
|
|
169
|
+
}
|
|
171
170
|
};
|
package/src/framer/adapter.ts
CHANGED
|
@@ -164,16 +164,14 @@ export class WriteAdapter {
|
|
|
164
164
|
throw new ValidationError(`
|
|
165
165
|
Received a single channel name or key but no series.
|
|
166
166
|
`);
|
|
167
|
-
if (Array.isArray(series))
|
|
167
|
+
if (Array.isArray(series))
|
|
168
168
|
if (series.some((s) => s instanceof Series || Array.isArray(s)))
|
|
169
169
|
throw new ValidationError(`
|
|
170
170
|
Received a single channel name or key but multiple series.
|
|
171
171
|
`);
|
|
172
172
|
|
|
173
|
-
series = series as CrudeSeries;
|
|
174
|
-
}
|
|
175
173
|
const pld = await this.fetchChannel(columnsOrData);
|
|
176
|
-
const s = new Series({ data: series
|
|
174
|
+
const s = new Series({ data: series, dataType: pld.dataType });
|
|
177
175
|
return new Frame(pld.key, s);
|
|
178
176
|
}
|
|
179
177
|
|
package/src/framer/client.ts
CHANGED
|
@@ -180,6 +180,18 @@ export class Client {
|
|
|
180
180
|
|
|
181
181
|
async readLatest(channels: channel.Params, n: number): Promise<Frame>;
|
|
182
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Reads the latest n samples from the given channel(s).
|
|
185
|
+
*
|
|
186
|
+
* If fewer than n samples are available, returns only the samples that
|
|
187
|
+
* exist.
|
|
188
|
+
*
|
|
189
|
+
* @param channels - A single channel key/name or an array of channel
|
|
190
|
+
* keys/names.
|
|
191
|
+
* @param n - The maximum number of samples to read. Defaults to 1.
|
|
192
|
+
* @returns A MultiSeries when a single channel is provided, or a Frame when
|
|
193
|
+
* multiple channels are provided.
|
|
194
|
+
*/
|
|
183
195
|
async readLatest(
|
|
184
196
|
channels: channel.Params,
|
|
185
197
|
n: number = 1,
|
package/src/framer/codec.spec.ts
CHANGED
|
@@ -17,11 +17,13 @@ import { framer } from "@/framer";
|
|
|
17
17
|
import {
|
|
18
18
|
Codec,
|
|
19
19
|
HIGH_PERF_SPECIAL_CHAR,
|
|
20
|
-
|
|
20
|
+
LOW_PERF_SPECIAL_CHAR,
|
|
21
|
+
WSIteratorCodec,
|
|
21
22
|
WSWriterCodec,
|
|
22
23
|
} from "@/framer/codec";
|
|
23
24
|
import { Frame } from "@/framer/frame";
|
|
24
|
-
import {
|
|
25
|
+
import { type IteratorResponse } from "@/framer/iterator";
|
|
26
|
+
import { IteratorResponseVariant, WriterCommand } from "@/framer/types.gen";
|
|
25
27
|
import { type WriteRequest } from "@/framer/writer";
|
|
26
28
|
|
|
27
29
|
describe("encoder", () => {
|
|
@@ -325,9 +327,57 @@ describe("encoder", () => {
|
|
|
325
327
|
const codec = new WSWriterCodec(baseCodec);
|
|
326
328
|
const msg: WebsocketMessage<WriteRequest> = { type: "data", payload: writeReq };
|
|
327
329
|
const encoded = codec.encode(msg);
|
|
328
|
-
expect(encoded[0]).toEqual(
|
|
330
|
+
expect(encoded[0]).toEqual(LOW_PERF_SPECIAL_CHAR);
|
|
329
331
|
const decoded = codec.decode(encoded);
|
|
330
332
|
expect(decoded).toEqual(msg);
|
|
331
333
|
});
|
|
332
334
|
});
|
|
335
|
+
|
|
336
|
+
describe("websocket iterator codec", () => {
|
|
337
|
+
it("should JSON-encode an iterator request without a special char prefix", () => {
|
|
338
|
+
const baseCodec = new Codec([1], [DataType.INT32]);
|
|
339
|
+
const codec = new WSIteratorCodec(baseCodec);
|
|
340
|
+
const msg: WebsocketMessage<unknown> = {
|
|
341
|
+
type: "data",
|
|
342
|
+
payload: { command: 1, span: 0 },
|
|
343
|
+
};
|
|
344
|
+
const encoded = codec.encode(msg);
|
|
345
|
+
// Iterator requests are sent as raw JSON; no special-char prefix.
|
|
346
|
+
// Confirm the first byte is JSON ('{' or '[').
|
|
347
|
+
expect([0x7b, 0x5b]).toContain(encoded[0]);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it("should binary-decode a data variant response and synthesize variant=Data", () => {
|
|
351
|
+
const baseCodec = new Codec([1], [DataType.INT32]);
|
|
352
|
+
const fr = new framer.Frame([1], [new Series(new Int32Array([1, 2, 3]))]);
|
|
353
|
+
const codec = new WSIteratorCodec(baseCodec);
|
|
354
|
+
const encoded = new Uint8Array(baseCodec.encode(fr.toPayload(), 1));
|
|
355
|
+
encoded[0] = HIGH_PERF_SPECIAL_CHAR;
|
|
356
|
+
const decoded = codec.decode(encoded) as WebsocketMessage<IteratorResponse>;
|
|
357
|
+
expect(decoded.payload?.variant).toEqual(IteratorResponseVariant.Data);
|
|
358
|
+
const decodedFr = new Frame(decoded.payload?.frame);
|
|
359
|
+
expect(decodedFr.series[0].data).toEqual(fr.series[0].data);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it("should JSON-decode an ack variant response prefixed with the low-perf char", () => {
|
|
363
|
+
const baseCodec = new Codec([1], [DataType.INT32]);
|
|
364
|
+
const codec = new WSIteratorCodec(baseCodec);
|
|
365
|
+
const ackMsg: WebsocketMessage<IteratorResponse> = {
|
|
366
|
+
type: "data",
|
|
367
|
+
payload: {
|
|
368
|
+
variant: IteratorResponseVariant.Ack,
|
|
369
|
+
ack: true,
|
|
370
|
+
command: 1,
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
// Simulate what the server sends: LOW_PERF_SPECIAL_CHAR + JSON-encoded message.
|
|
374
|
+
const json = new TextEncoder().encode(JSON.stringify(ackMsg));
|
|
375
|
+
const encoded = new Uint8Array(json.byteLength + 1);
|
|
376
|
+
encoded[0] = LOW_PERF_SPECIAL_CHAR;
|
|
377
|
+
encoded.set(json, 1);
|
|
378
|
+
const decoded = codec.decode(encoded) as WebsocketMessage<IteratorResponse>;
|
|
379
|
+
expect(decoded.payload?.variant).toEqual(IteratorResponseVariant.Ack);
|
|
380
|
+
expect(decoded.payload?.ack).toEqual(true);
|
|
381
|
+
});
|
|
382
|
+
});
|
|
333
383
|
});
|
package/src/framer/codec.ts
CHANGED
|
@@ -21,7 +21,7 @@ import { type channel } from "@/channel";
|
|
|
21
21
|
import { ValidationError } from "@/errors";
|
|
22
22
|
import { type Frame, type Payload } from "@/framer/frame";
|
|
23
23
|
import { type StreamerResponse } from "@/framer/streamer";
|
|
24
|
-
import { WriterCommand } from "@/framer/types.gen";
|
|
24
|
+
import { IteratorResponseVariant, WriterCommand } from "@/framer/types.gen";
|
|
25
25
|
import { type WriteRequest } from "@/framer/writer";
|
|
26
26
|
|
|
27
27
|
const seriesPldLength = (series: SeriesPayload): number =>
|
|
@@ -63,7 +63,7 @@ interface CodecState {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
export class Codec {
|
|
66
|
-
contentType: string =
|
|
66
|
+
contentType: string = CONTENT_TYPE;
|
|
67
67
|
private states: Map<number, CodecState> = new Map();
|
|
68
68
|
private currState: CodecState | undefined;
|
|
69
69
|
private seqNum: number = 0;
|
|
@@ -269,36 +269,26 @@ export class Codec {
|
|
|
269
269
|
index += ALIGNMENT_SIZE;
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (
|
|
275
|
-
if (index >= view.byteLength) return;
|
|
276
|
-
const frameKey = view.getUint32(index, true);
|
|
277
|
-
if (frameKey !== k) return;
|
|
278
|
-
index += KEY_SIZE;
|
|
279
|
-
returnFrame.keys.push(k);
|
|
280
|
-
}
|
|
281
|
-
const dataType = state.keyDataTypes.get(k) as DataType;
|
|
272
|
+
const decodeSeries = (k: channel.Key): boolean => {
|
|
273
|
+
const dataType = state.keyDataTypes.get(k);
|
|
274
|
+
if (dataType == null) return false;
|
|
282
275
|
currSize = 0;
|
|
283
276
|
if (!sizeFlag) {
|
|
284
|
-
if (index + DATA_LENGTH_SIZE > view.byteLength) return;
|
|
277
|
+
if (index + DATA_LENGTH_SIZE > view.byteLength) return false;
|
|
285
278
|
currSize = view.getUint32(index, true);
|
|
286
279
|
index += DATA_LENGTH_SIZE;
|
|
287
280
|
} else currSize = sizeRepresentation;
|
|
288
281
|
|
|
289
282
|
let dataByteLength = currSize;
|
|
290
283
|
if (!dataType.isVariable) dataByteLength *= dataType.density.valueOf();
|
|
291
|
-
if (index + dataByteLength > view.byteLength)
|
|
292
|
-
returnFrame.keys.splice(i, 1);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
284
|
+
if (index + dataByteLength > view.byteLength) return false;
|
|
295
285
|
const currSeries: SeriesPayload = {
|
|
296
286
|
dataType,
|
|
297
287
|
data: src.slice(index, index + dataByteLength).buffer,
|
|
298
288
|
};
|
|
299
289
|
index += dataByteLength;
|
|
300
290
|
if (!equalTimeRangesFlag && !timeRangesZeroFlag) {
|
|
301
|
-
if (index + TIMESTAMP_SIZE * 2 > view.byteLength) return;
|
|
291
|
+
if (index + TIMESTAMP_SIZE * 2 > view.byteLength) return false;
|
|
302
292
|
const start = view.getBigUint64(index, true);
|
|
303
293
|
index += TIMESTAMP_SIZE;
|
|
304
294
|
const end = view.getBigUint64(index, true);
|
|
@@ -312,7 +302,7 @@ export class Codec {
|
|
|
312
302
|
else currSeries.timeRange = new TimeRange({ start: 0n, end: 0n });
|
|
313
303
|
|
|
314
304
|
if (!equalAlignmentsFlag && !zeroAlignmentsFlag) {
|
|
315
|
-
if (index + ALIGNMENT_SIZE > view.byteLength) return;
|
|
305
|
+
if (index + ALIGNMENT_SIZE > view.byteLength) return false;
|
|
316
306
|
currAlignment = view.getBigUint64(index, true);
|
|
317
307
|
index += ALIGNMENT_SIZE;
|
|
318
308
|
currSeries.alignment = currAlignment;
|
|
@@ -320,16 +310,27 @@ export class Codec {
|
|
|
320
310
|
else currSeries.alignment = 0n;
|
|
321
311
|
|
|
322
312
|
returnFrame.series.push(currSeries);
|
|
323
|
-
|
|
313
|
+
returnFrame.keys.push(k);
|
|
314
|
+
return true;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
if (channelFlag) state.keys.forEach((k) => decodeSeries(k));
|
|
318
|
+
else
|
|
319
|
+
while (index < view.byteLength) {
|
|
320
|
+
if (index + KEY_SIZE > view.byteLength) break;
|
|
321
|
+
const frameKey = view.getUint32(index, true);
|
|
322
|
+
index += KEY_SIZE;
|
|
323
|
+
if (!decodeSeries(frameKey)) break;
|
|
324
|
+
}
|
|
324
325
|
return returnFrame;
|
|
325
326
|
}
|
|
326
327
|
}
|
|
327
328
|
|
|
328
|
-
export const
|
|
329
|
-
const LOW_PERF_SPECIAL_CHAR_BUF = new Uint8Array([
|
|
329
|
+
export const LOW_PERF_SPECIAL_CHAR = 254;
|
|
330
|
+
const LOW_PERF_SPECIAL_CHAR_BUF = new Uint8Array([LOW_PERF_SPECIAL_CHAR]);
|
|
330
331
|
export const HIGH_PERF_SPECIAL_CHAR = 255;
|
|
331
332
|
const HIGH_PERF_SPECIAL_CHAR_BUF = new Uint8Array([HIGH_PERF_SPECIAL_CHAR]);
|
|
332
|
-
const CONTENT_TYPE = "application/
|
|
333
|
+
const CONTENT_TYPE = "application/vnd.synnax.frame";
|
|
333
334
|
|
|
334
335
|
export class WSWriterCodec implements binary.Codec {
|
|
335
336
|
contentType = CONTENT_TYPE;
|
|
@@ -358,7 +359,7 @@ export class WSWriterCodec implements binary.Codec {
|
|
|
358
359
|
decode<P extends z.ZodType>(data: Uint8Array | ArrayBuffer, schema?: P): z.infer<P> {
|
|
359
360
|
const dv = new DataView(data instanceof Uint8Array ? data.buffer : data);
|
|
360
361
|
const codec = dv.getUint8(0);
|
|
361
|
-
if (codec ===
|
|
362
|
+
if (codec === LOW_PERF_SPECIAL_CHAR)
|
|
362
363
|
return this.lowPerfCodec.decode(data.slice(1), schema);
|
|
363
364
|
const v: WebsocketMessage<WriteRequest> = { type: "data" };
|
|
364
365
|
const frame = this.base.decode(data, 1);
|
|
@@ -384,7 +385,7 @@ export class WSStreamerCodec implements binary.Codec {
|
|
|
384
385
|
decode<P extends z.ZodType>(data: Uint8Array | ArrayBuffer, schema?: P): z.infer<P> {
|
|
385
386
|
const dv = new DataView(data instanceof Uint8Array ? data.buffer : data);
|
|
386
387
|
const codec = dv.getUint8(0);
|
|
387
|
-
if (codec ===
|
|
388
|
+
if (codec === LOW_PERF_SPECIAL_CHAR)
|
|
388
389
|
return this.lowPerfCodec.decode(data.slice(1), schema);
|
|
389
390
|
const v: WebsocketMessage<StreamerResponse> = {
|
|
390
391
|
type: "data",
|
|
@@ -393,3 +394,35 @@ export class WSStreamerCodec implements binary.Codec {
|
|
|
393
394
|
return v as z.infer<P>;
|
|
394
395
|
}
|
|
395
396
|
}
|
|
397
|
+
|
|
398
|
+
export class WSIteratorCodec implements binary.Codec {
|
|
399
|
+
contentType = CONTENT_TYPE;
|
|
400
|
+
private base: Codec;
|
|
401
|
+
private lowPerfCodec: binary.Codec;
|
|
402
|
+
|
|
403
|
+
constructor(base: Codec) {
|
|
404
|
+
this.base = base;
|
|
405
|
+
this.lowPerfCodec = binary.JSON_CODEC;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
encode(payload: unknown): Uint8Array<ArrayBuffer> {
|
|
409
|
+
return this.lowPerfCodec.encode(payload);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
decode<P extends z.ZodType>(data: Uint8Array | ArrayBuffer, schema?: P): z.infer<P> {
|
|
413
|
+
const dv = new DataView(data instanceof Uint8Array ? data.buffer : data);
|
|
414
|
+
const codec = dv.getUint8(0);
|
|
415
|
+
if (codec === LOW_PERF_SPECIAL_CHAR)
|
|
416
|
+
return this.lowPerfCodec.decode(data.slice(1), schema);
|
|
417
|
+
const v: WebsocketMessage<unknown> = {
|
|
418
|
+
type: "data",
|
|
419
|
+
payload: {
|
|
420
|
+
variant: IteratorResponseVariant.Data,
|
|
421
|
+
ack: false,
|
|
422
|
+
command: 0,
|
|
423
|
+
frame: this.base.decode(data, 1),
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
return v as z.infer<P>;
|
|
427
|
+
}
|
|
428
|
+
}
|
package/src/framer/deleter.ts
CHANGED
|
@@ -7,7 +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 {
|
|
10
|
+
import { type UnaryClient } from "@synnaxlabs/freighter";
|
|
11
11
|
import { TimeRange } from "@synnaxlabs/x";
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
|
|
@@ -33,12 +33,6 @@ export class Deleter {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
async delete(props: Request): Promise<void> {
|
|
36
|
-
await
|
|
37
|
-
this.client,
|
|
38
|
-
"/frame/delete",
|
|
39
|
-
props,
|
|
40
|
-
reqZ,
|
|
41
|
-
resZ,
|
|
42
|
-
);
|
|
36
|
+
await this.client.send("/frame/delete", props, reqZ, resZ);
|
|
43
37
|
}
|
|
44
38
|
}
|