@synnaxlabs/client 0.42.3 → 0.43.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/.vscode/settings.json +2 -2
- package/CONTRIBUTING.md +6 -5
- package/README.md +7 -8
- package/dist/access/payload.d.ts +1 -1
- package/dist/access/payload.d.ts.map +1 -1
- package/dist/access/policy/payload.d.ts +9 -9
- package/dist/access/policy/payload.d.ts.map +1 -1
- package/dist/access/policy/retriever.d.ts +3 -3
- package/dist/access/policy/retriever.d.ts.map +1 -1
- package/dist/auth/auth.d.ts +2 -2
- package/dist/auth/auth.d.ts.map +1 -1
- package/dist/channel/client.d.ts +1 -0
- package/dist/channel/client.d.ts.map +1 -1
- package/dist/channel/payload.d.ts +21 -8
- package/dist/channel/payload.d.ts.map +1 -1
- package/dist/channel/retriever.d.ts +5 -5
- package/dist/channel/retriever.d.ts.map +1 -1
- package/dist/channel/writer.d.ts +3 -3
- package/dist/channel/writer.d.ts.map +1 -1
- package/dist/client.cjs +135 -39
- package/dist/client.d.ts +8 -8
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +28499 -9313
- package/dist/connection/checker.d.ts +5 -5
- package/dist/connection/checker.d.ts.map +1 -1
- package/dist/control/state.d.ts +46 -3
- package/dist/control/state.d.ts.map +1 -1
- package/dist/framer/adapter.d.ts +2 -2
- package/dist/framer/adapter.d.ts.map +1 -1
- package/dist/framer/client.d.ts +2 -0
- package/dist/framer/client.d.ts.map +1 -1
- package/dist/framer/codec.d.ts +3 -3
- package/dist/framer/codec.d.ts.map +1 -1
- package/dist/framer/deleter.d.ts +8 -8
- package/dist/framer/deleter.d.ts.map +1 -1
- package/dist/framer/frame.d.ts +17 -17
- package/dist/framer/frame.d.ts.map +1 -1
- package/dist/framer/streamProxy.d.ts +3 -3
- package/dist/framer/streamProxy.d.ts.map +1 -1
- package/dist/framer/streamer.d.ts +103 -22
- package/dist/framer/streamer.d.ts.map +1 -1
- package/dist/framer/writer.d.ts +25 -25
- package/dist/framer/writer.d.ts.map +1 -1
- package/dist/hardware/device/client.d.ts +3 -3
- package/dist/hardware/device/client.d.ts.map +1 -1
- package/dist/hardware/device/payload.d.ts +65 -18
- package/dist/hardware/device/payload.d.ts.map +1 -1
- package/dist/hardware/rack/client.d.ts.map +1 -1
- package/dist/hardware/rack/payload.d.ts +87 -30
- package/dist/hardware/rack/payload.d.ts.map +1 -1
- package/dist/hardware/task/client.d.ts +3 -3
- package/dist/hardware/task/client.d.ts.map +1 -1
- package/dist/hardware/task/payload.d.ts +20 -21
- package/dist/hardware/task/payload.d.ts.map +1 -1
- package/dist/label/payload.d.ts +2 -2
- package/dist/label/payload.d.ts.map +1 -1
- package/dist/label/writer.d.ts +4 -4
- package/dist/label/writer.d.ts.map +1 -1
- package/dist/ontology/client.d.ts +3 -3
- package/dist/ontology/client.d.ts.map +1 -1
- package/dist/ontology/group/payload.d.ts +2 -2
- package/dist/ontology/group/payload.d.ts.map +1 -1
- package/dist/ontology/payload.d.ts +25 -25
- package/dist/ontology/payload.d.ts.map +1 -1
- package/dist/ranger/client.d.ts +8 -8
- package/dist/ranger/client.d.ts.map +1 -1
- package/dist/ranger/kv.d.ts +6 -6
- package/dist/ranger/kv.d.ts.map +1 -1
- package/dist/ranger/payload.d.ts +15 -15
- package/dist/ranger/payload.d.ts.map +1 -1
- package/dist/ranger/writer.d.ts +10 -10
- package/dist/ranger/writer.d.ts.map +1 -1
- package/dist/testutil/{indexedPair.d.ts → channels.d.ts} +1 -1
- package/dist/testutil/channels.d.ts.map +1 -0
- package/dist/user/payload.d.ts +3 -3
- 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/retrieve.d.ts +6 -6
- package/dist/util/retrieve.d.ts.map +1 -1
- package/dist/util/zod.d.ts +2 -2
- package/dist/util/zod.d.ts.map +1 -1
- package/dist/workspace/client.d.ts.map +1 -1
- package/dist/workspace/lineplot/client.d.ts.map +1 -1
- package/dist/workspace/lineplot/lineplot.spec.d.ts +2 -0
- package/dist/workspace/lineplot/lineplot.spec.d.ts.map +1 -0
- package/dist/workspace/lineplot/payload.d.ts +5 -5
- package/dist/workspace/lineplot/payload.d.ts.map +1 -1
- package/dist/workspace/log/client.d.ts.map +1 -1
- package/dist/workspace/log/payload.d.ts +5 -5
- package/dist/workspace/log/payload.d.ts.map +1 -1
- package/dist/workspace/payload.d.ts +6 -6
- package/dist/workspace/payload.d.ts.map +1 -1
- package/dist/workspace/schematic/client.d.ts.map +1 -1
- package/dist/workspace/schematic/payload.d.ts +7 -7
- package/dist/workspace/schematic/payload.d.ts.map +1 -1
- package/dist/workspace/table/client.d.ts.map +1 -1
- package/dist/workspace/table/payload.d.ts +6 -6
- package/dist/workspace/table/payload.d.ts.map +1 -1
- package/package.json +11 -12
- package/src/access/payload.ts +1 -1
- package/src/access/policy/client.ts +3 -3
- package/src/access/policy/payload.ts +1 -1
- package/src/access/policy/retriever.ts +1 -1
- package/src/access/policy/writer.ts +7 -7
- package/src/auth/auth.ts +1 -1
- package/src/channel/client.ts +6 -4
- package/src/channel/payload.ts +10 -18
- package/src/channel/retriever.ts +2 -2
- package/src/channel/writer.ts +11 -2
- package/src/client.ts +3 -3
- package/src/connection/checker.ts +1 -1
- package/src/connection/connection.spec.ts +1 -1
- package/src/control/client.ts +1 -1
- package/src/control/state.ts +4 -5
- package/src/errors.spec.ts +2 -3
- package/src/errors.ts +2 -2
- package/src/framer/adapter.ts +2 -2
- package/src/framer/client.ts +4 -3
- package/src/framer/codec.spec.ts +2 -2
- package/src/framer/codec.ts +5 -9
- package/src/framer/deleter.spec.ts +1 -1
- package/src/framer/deleter.ts +1 -1
- package/src/framer/frame.ts +15 -15
- package/src/framer/iterator.spec.ts +1 -1
- package/src/framer/iterator.ts +1 -1
- package/src/framer/streamProxy.ts +4 -4
- package/src/framer/streamer.spec.ts +420 -215
- package/src/framer/streamer.ts +119 -21
- package/src/framer/writer.spec.ts +1 -1
- package/src/framer/writer.ts +15 -8
- package/src/hardware/device/client.ts +5 -5
- package/src/hardware/device/device.spec.ts +28 -30
- package/src/hardware/device/payload.ts +5 -5
- package/src/hardware/rack/client.ts +4 -4
- package/src/hardware/rack/payload.ts +6 -6
- package/src/hardware/rack/rack.spec.ts +1 -1
- package/src/hardware/task/client.ts +21 -19
- package/src/hardware/task/payload.ts +8 -6
- package/src/label/payload.ts +1 -1
- package/src/label/retriever.ts +3 -3
- package/src/label/writer.ts +4 -4
- package/src/ontology/client.ts +4 -4
- package/src/ontology/group/payload.ts +3 -3
- package/src/ontology/group/writer.ts +1 -1
- package/src/ontology/payload.ts +2 -2
- package/src/ontology/writer.ts +1 -1
- package/src/ranger/alias.ts +1 -1
- package/src/ranger/client.ts +6 -4
- package/src/ranger/kv.ts +4 -4
- package/src/ranger/payload.ts +3 -3
- package/src/ranger/writer.ts +1 -1
- package/src/user/client.ts +3 -3
- package/src/user/payload.ts +1 -1
- package/src/user/retriever.ts +1 -1
- package/src/user/writer.ts +4 -4
- package/src/util/retrieve.spec.ts +7 -4
- package/src/util/retrieve.ts +10 -10
- package/src/util/zod.ts +3 -3
- package/src/workspace/client.ts +5 -5
- package/src/workspace/lineplot/client.ts +5 -5
- package/src/workspace/lineplot/{linePlot.spec.ts → lineplot.spec.ts} +2 -2
- package/src/workspace/lineplot/payload.ts +1 -1
- package/src/workspace/log/client.ts +5 -5
- package/src/workspace/log/log.spec.ts +2 -2
- package/src/workspace/log/payload.ts +1 -1
- package/src/workspace/payload.ts +1 -1
- package/src/workspace/schematic/client.ts +5 -5
- package/src/workspace/schematic/payload.ts +1 -1
- package/src/workspace/schematic/schematic.spec.ts +3 -3
- package/src/workspace/table/client.ts +5 -5
- package/src/workspace/table/payload.ts +1 -1
- package/src/workspace/table/table.spec.ts +2 -2
- package/src/workspace/workspace.spec.ts +2 -2
- package/tsconfig.json +3 -5
- package/dist/testutil/indexedPair.d.ts.map +0 -1
- package/dist/workspace/lineplot/linePlot.spec.d.ts +0 -2
- package/dist/workspace/lineplot/linePlot.spec.d.ts.map +0 -1
- /package/src/testutil/{indexedPair.ts → channels.ts} +0 -0
package/src/framer/streamer.ts
CHANGED
|
@@ -9,59 +9,129 @@
|
|
|
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/v4";
|
|
13
13
|
|
|
14
14
|
import { type channel } from "@/channel";
|
|
15
15
|
import { ReadAdapter } from "@/framer/adapter";
|
|
16
16
|
import { WSStreamerCodec } from "@/framer/codec";
|
|
17
17
|
import { Frame, frameZ } from "@/framer/frame";
|
|
18
18
|
import { StreamProxy } from "@/framer/streamProxy";
|
|
19
|
+
import { payloadZ } from "@/ranger/payload";
|
|
19
20
|
|
|
20
|
-
const reqZ = z.object({ keys: z.number().array(),
|
|
21
|
+
const reqZ = z.object({ keys: z.number().array(), downsampleFactor: z.number() });
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Request interface for streaming frames from a Synnax cluster.
|
|
25
|
+
* Contains the keys of channels to stream from and a downsample factor.
|
|
26
|
+
*/
|
|
27
|
+
export interface StreamerRequest extends z.infer<typeof reqZ> {}
|
|
23
28
|
|
|
24
29
|
const resZ = z.object({ frame: frameZ });
|
|
25
30
|
|
|
26
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Response interface for streaming frames from a Synnax cluster.
|
|
33
|
+
* Contains a frame of telemetry data.
|
|
34
|
+
*/
|
|
35
|
+
export interface StreamerResponse extends z.infer<typeof resZ> {}
|
|
27
36
|
|
|
28
37
|
const ENDPOINT = "/frame/stream";
|
|
29
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Configuration options for creating a new streamer.
|
|
41
|
+
*/
|
|
30
42
|
export interface StreamerConfig {
|
|
43
|
+
/** The channels to stream data from. Can be channel keys, names, or payloads. */
|
|
31
44
|
channels: channel.Params;
|
|
32
|
-
|
|
45
|
+
/** Optional factor to downsample the data by. Defaults to 1 (no downsampling). */
|
|
46
|
+
downsampleFactor?: number;
|
|
47
|
+
/** Whether to use the experimental codec for streaming. Defaults to false. */
|
|
33
48
|
useExperimentalCodec?: boolean;
|
|
34
49
|
}
|
|
35
50
|
|
|
51
|
+
/**
|
|
52
|
+
* A streamer is used to stream frames of telemetry in real-time from a Synnax cluster.
|
|
53
|
+
* It should not be constructed directly, and should instead be created using the
|
|
54
|
+
* client's openStreamer method.
|
|
55
|
+
*
|
|
56
|
+
* To open a streamer, use the openStreamer method on the client and pass it in the list
|
|
57
|
+
* of channels you'd like to receive data from. Once the streamer has been opened, call
|
|
58
|
+
* the `read` method to read the next frame of telemetry, or use the streamer as an
|
|
59
|
+
* async iterator to iterate over the frames of telemetry as they are received.
|
|
60
|
+
*
|
|
61
|
+
* The list of channels being streamed can be updated at any time by using the `update`
|
|
62
|
+
* method.
|
|
63
|
+
*
|
|
64
|
+
* Once done, call the `close` method to close the streamer and free all associated
|
|
65
|
+
* resources. We recommend using the streamer within a try-finally block to ensure
|
|
66
|
+
* that it is closed properly in the event of an error.
|
|
67
|
+
*
|
|
68
|
+
* For details documentation, see https://docs.synnaxlabs.com/reference/typescript-client/stream-data
|
|
69
|
+
*/
|
|
36
70
|
export interface Streamer extends AsyncIterator<Frame>, AsyncIterable<Frame> {
|
|
71
|
+
/** The keys of the channels currently being streamed from. */
|
|
37
72
|
keys: channel.Key[];
|
|
73
|
+
/**
|
|
74
|
+
* Update the list of channels being streamed from. This replaces the list of channels
|
|
75
|
+
* being streamed from with the new list of channels.
|
|
76
|
+
*/
|
|
38
77
|
update: (channels: channel.Params) => Promise<void>;
|
|
78
|
+
/** Close the streamer and free all associated resources. */
|
|
39
79
|
close: () => void;
|
|
80
|
+
/** Read the next frame of telemetry. */
|
|
40
81
|
read: () => Promise<Frame>;
|
|
41
82
|
}
|
|
42
83
|
|
|
84
|
+
/**
|
|
85
|
+
* A function that opens a streamer.
|
|
86
|
+
*/
|
|
43
87
|
export interface StreamOpener {
|
|
44
88
|
(config: StreamerConfig | channel.Params): Promise<Streamer>;
|
|
45
89
|
}
|
|
46
90
|
|
|
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
|
+
/**
|
|
108
|
+
* Creates a function that opens streamers with the given retriever and client.
|
|
109
|
+
* @param retriever - The channel retriever to use for resolving channel information
|
|
110
|
+
* @param client - The WebSocket client to use for streaming
|
|
111
|
+
* @returns A function that opens streamers with the given configuration
|
|
112
|
+
*/
|
|
47
113
|
export const createStreamOpener =
|
|
48
114
|
(retriever: channel.Retriever, client: WebSocketClient): StreamOpener =>
|
|
49
115
|
async (config) => {
|
|
50
|
-
|
|
51
|
-
if (Array.isArray(config) || typeof config !== "object")
|
|
52
|
-
cfg = { channels: config as channel.Params, downSampleFactor: 1 };
|
|
53
|
-
else cfg = config as StreamerConfig;
|
|
116
|
+
const cfg = parseStreamerConfig(config);
|
|
54
117
|
const adapter = await ReadAdapter.open(retriever, cfg.channels);
|
|
55
118
|
if (cfg.useExperimentalCodec)
|
|
56
119
|
client = client.withCodec(new WSStreamerCodec(adapter.codec));
|
|
57
120
|
const stream = await client.stream(ENDPOINT, reqZ, resZ);
|
|
58
121
|
const streamer = new CoreStreamer(stream, adapter);
|
|
59
|
-
stream.send({ keys: adapter.keys,
|
|
122
|
+
stream.send({ keys: adapter.keys, downsampleFactor: cfg.downsampleFactor ?? 1 });
|
|
60
123
|
const [, err] = await stream.receive();
|
|
61
124
|
if (err != null) throw err;
|
|
62
125
|
return streamer;
|
|
63
126
|
};
|
|
64
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Opens a new streamer with the given configuration.
|
|
130
|
+
* @param retriever - The channel retriever to use for resolving channel information
|
|
131
|
+
* @param client - The WebSocket client to use for streaming
|
|
132
|
+
* @param config - The configuration for the streamer
|
|
133
|
+
* @returns A promise that resolves to a new streamer
|
|
134
|
+
*/
|
|
65
135
|
export const openStreamer = async (
|
|
66
136
|
retriever: channel.Retriever,
|
|
67
137
|
client: WebSocketClient,
|
|
@@ -101,7 +171,7 @@ class CoreStreamer implements Streamer {
|
|
|
101
171
|
await this.adapter.update(channels);
|
|
102
172
|
this.stream.send({
|
|
103
173
|
keys: this.adapter.keys,
|
|
104
|
-
|
|
174
|
+
downsampleFactor: this.downsampleFactor,
|
|
105
175
|
});
|
|
106
176
|
}
|
|
107
177
|
|
|
@@ -114,30 +184,46 @@ class CoreStreamer implements Streamer {
|
|
|
114
184
|
}
|
|
115
185
|
}
|
|
116
186
|
|
|
187
|
+
/**
|
|
188
|
+
* A hardened streamer that automatically reconnects on failure.
|
|
189
|
+
* This streamer wraps a regular streamer and adds automatic reconnection
|
|
190
|
+
* logic when the connection is lost or errors occur.
|
|
191
|
+
*/
|
|
117
192
|
export class HardenedStreamer implements Streamer {
|
|
118
193
|
private wrapped_: Streamer | null = null;
|
|
119
194
|
private readonly breaker: breaker.Breaker;
|
|
120
195
|
private readonly opener: StreamOpener;
|
|
121
196
|
private readonly config: StreamerConfig;
|
|
122
197
|
|
|
123
|
-
private constructor(
|
|
198
|
+
private constructor(
|
|
199
|
+
opener: StreamOpener,
|
|
200
|
+
config: StreamerConfig | channel.Params,
|
|
201
|
+
breakerConfig: breaker.Config = {},
|
|
202
|
+
) {
|
|
124
203
|
this.opener = opener;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
});
|
|
204
|
+
this.config = parseStreamerConfig(config);
|
|
205
|
+
const {
|
|
206
|
+
maxRetries = 5000,
|
|
207
|
+
baseInterval = TimeSpan.seconds(1),
|
|
208
|
+
scale = 1,
|
|
209
|
+
} = breakerConfig ?? {};
|
|
210
|
+
this.breaker = new breaker.Breaker({ maxRetries, baseInterval, scale });
|
|
133
211
|
}
|
|
134
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Opens a new hardened streamer with the given configuration.
|
|
215
|
+
* @param opener - The function to use for opening streamers
|
|
216
|
+
* @param config - The configuration for the streamer
|
|
217
|
+
* @returns A promise that resolves to a new hardened streamer
|
|
218
|
+
*/
|
|
135
219
|
static async open(
|
|
136
220
|
opener: StreamOpener,
|
|
137
221
|
config: StreamerConfig | channel.Params,
|
|
222
|
+
breakerConfig?: breaker.Config,
|
|
138
223
|
): Promise<HardenedStreamer> {
|
|
139
|
-
const h = new HardenedStreamer(opener, config);
|
|
224
|
+
const h = new HardenedStreamer(opener, config, breakerConfig);
|
|
140
225
|
await h.runStreamer();
|
|
226
|
+
|
|
141
227
|
return h;
|
|
142
228
|
}
|
|
143
229
|
|
|
@@ -151,6 +237,7 @@ export class HardenedStreamer implements Streamer {
|
|
|
151
237
|
} catch (e) {
|
|
152
238
|
this.wrapped_ = null;
|
|
153
239
|
if (!(await this.breaker.wait())) throw e;
|
|
240
|
+
console.error("failed to open streamer", e);
|
|
154
241
|
continue;
|
|
155
242
|
}
|
|
156
243
|
}
|
|
@@ -203,6 +290,10 @@ export class HardenedStreamer implements Streamer {
|
|
|
203
290
|
}
|
|
204
291
|
}
|
|
205
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Wraps a standard streamer to implement an observable interface for handling changes
|
|
295
|
+
* to channel values through an onChange handler.
|
|
296
|
+
*/
|
|
206
297
|
export class ObservableStreamer<V = Frame>
|
|
207
298
|
extends observe.Observer<Frame, V>
|
|
208
299
|
implements observe.ObservableAsyncCloseable<V>
|
|
@@ -210,6 +301,13 @@ export class ObservableStreamer<V = Frame>
|
|
|
210
301
|
private readonly streamer: Streamer;
|
|
211
302
|
private readonly closePromise: Promise<void>;
|
|
212
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Creates a new observable streamer.
|
|
306
|
+
* @param streamer - The streamer to wrap
|
|
307
|
+
* @param transform - An optional transform function to apply to each frame
|
|
308
|
+
* @template V - The type of the transformed value. Only relevant if transform is
|
|
309
|
+
* provided. Defaults to Frame.
|
|
310
|
+
*/
|
|
213
311
|
constructor(streamer: Streamer, transform?: observe.Transform<Frame, V>) {
|
|
214
312
|
super(transform);
|
|
215
313
|
this.streamer = streamer;
|
|
@@ -13,7 +13,7 @@ import { describe, expect, it, test } from "vitest";
|
|
|
13
13
|
import { UnauthorizedError, ValidationError } from "@/errors";
|
|
14
14
|
import { ALWAYS_INDEX_PERSIST_ON_AUTO_COMMIT, WriterMode } from "@/framer/writer";
|
|
15
15
|
import { newClient } from "@/setupspecs";
|
|
16
|
-
import { newIndexedPair } from "@/testutil/
|
|
16
|
+
import { newIndexedPair } from "@/testutil/channels";
|
|
17
17
|
import { secondsLinspace } from "@/testutil/telem";
|
|
18
18
|
import { randomSeries } from "@/util/telem";
|
|
19
19
|
|
package/src/framer/writer.ts
CHANGED
|
@@ -8,21 +8,20 @@
|
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
10
|
import { EOF, type Stream, type WebSocketClient } from "@synnaxlabs/freighter";
|
|
11
|
-
import { control, errors } from "@synnaxlabs/x";
|
|
11
|
+
import { array, control, errors } from "@synnaxlabs/x";
|
|
12
12
|
import {
|
|
13
13
|
type CrudeSeries,
|
|
14
14
|
type CrudeTimeStamp,
|
|
15
15
|
TimeSpan,
|
|
16
16
|
TimeStamp,
|
|
17
17
|
} from "@synnaxlabs/x/telem";
|
|
18
|
-
import {
|
|
19
|
-
import { z } from "zod";
|
|
18
|
+
import { z } from "zod/v4";
|
|
20
19
|
|
|
21
20
|
import { channel } from "@/channel";
|
|
22
21
|
import { SynnaxError } from "@/errors";
|
|
23
22
|
import { WriteAdapter } from "@/framer/adapter";
|
|
24
23
|
import { WSWriterCodec } from "@/framer/codec";
|
|
25
|
-
import { type
|
|
24
|
+
import { type CrudeFrame, frameZ } from "@/framer/frame";
|
|
26
25
|
|
|
27
26
|
export enum WriterCommand {
|
|
28
27
|
Open = 0,
|
|
@@ -194,7 +193,7 @@ export class Writer {
|
|
|
194
193
|
start: new TimeStamp(start),
|
|
195
194
|
keys: adapter.keys,
|
|
196
195
|
controlSubject: subject,
|
|
197
|
-
authorities: toArray(authorities),
|
|
196
|
+
authorities: array.toArray(authorities),
|
|
198
197
|
mode: constructWriterMode(mode),
|
|
199
198
|
errOnUnauthorized,
|
|
200
199
|
enableAutoCommit,
|
|
@@ -206,9 +205,14 @@ export class Writer {
|
|
|
206
205
|
|
|
207
206
|
async write(channel: channel.KeyOrName, data: CrudeSeries): Promise<void>;
|
|
208
207
|
async write(channel: channel.KeysOrNames, data: CrudeSeries[]): Promise<void>;
|
|
209
|
-
async write(frame: Crude | Record<channel.KeyOrName, CrudeSeries>): Promise<void>;
|
|
210
208
|
async write(
|
|
211
|
-
|
|
209
|
+
frame: CrudeFrame | Record<channel.KeyOrName, CrudeSeries>,
|
|
210
|
+
): Promise<void>;
|
|
211
|
+
async write(
|
|
212
|
+
channelsOrData:
|
|
213
|
+
| channel.Params
|
|
214
|
+
| Record<channel.KeyOrName, CrudeSeries>
|
|
215
|
+
| CrudeFrame,
|
|
212
216
|
series?: CrudeSeries | CrudeSeries[],
|
|
213
217
|
): Promise<void>;
|
|
214
218
|
|
|
@@ -227,7 +231,10 @@ export class Writer {
|
|
|
227
231
|
* should acknowledge the error by calling the error method or closing the writer.
|
|
228
232
|
*/
|
|
229
233
|
async write(
|
|
230
|
-
channelsOrData:
|
|
234
|
+
channelsOrData:
|
|
235
|
+
| channel.Params
|
|
236
|
+
| Record<channel.KeyOrName, CrudeSeries>
|
|
237
|
+
| CrudeFrame,
|
|
231
238
|
series?: CrudeSeries | CrudeSeries[],
|
|
232
239
|
): Promise<void> {
|
|
233
240
|
if (this.closeErr != null) throw this.closeErr;
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
10
|
import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
|
|
11
|
-
import {
|
|
11
|
+
import { array, type UnknownRecord } from "@synnaxlabs/x";
|
|
12
12
|
import { type AsyncTermSearcher } from "@synnaxlabs/x/search";
|
|
13
|
-
import { z } from "zod";
|
|
13
|
+
import { z } from "zod/v4";
|
|
14
14
|
|
|
15
15
|
import { framer } from "@/framer";
|
|
16
16
|
import {
|
|
@@ -118,7 +118,7 @@ export class Client implements AsyncTermSearcher<string, Key, Device> {
|
|
|
118
118
|
const res = await sendRequired(
|
|
119
119
|
this.client,
|
|
120
120
|
RETRIEVE_ENDPOINT,
|
|
121
|
-
{ keys: toArray(keys), ...options },
|
|
121
|
+
{ keys: array.toArray(keys), ...options },
|
|
122
122
|
retrieveReqZ,
|
|
123
123
|
retrieveResZ,
|
|
124
124
|
);
|
|
@@ -173,7 +173,7 @@ export class Client implements AsyncTermSearcher<string, Key, Device> {
|
|
|
173
173
|
const res = await sendRequired(
|
|
174
174
|
this.client,
|
|
175
175
|
CREATE_ENDPOINT,
|
|
176
|
-
{ devices: toArray(devices) },
|
|
176
|
+
{ devices: array.toArray(devices) },
|
|
177
177
|
createReqZ,
|
|
178
178
|
createResZ,
|
|
179
179
|
);
|
|
@@ -186,7 +186,7 @@ export class Client implements AsyncTermSearcher<string, Key, Device> {
|
|
|
186
186
|
await sendRequired(
|
|
187
187
|
this.client,
|
|
188
188
|
DELETE_ENDPOINT,
|
|
189
|
-
{ keys: toArray(keys) },
|
|
189
|
+
{ keys: array.toArray(keys) },
|
|
190
190
|
deleteReqZ,
|
|
191
191
|
deleteResZ,
|
|
192
192
|
);
|
|
@@ -19,16 +19,17 @@ describe("Device", async () => {
|
|
|
19
19
|
const testRack = await client.hardware.racks.create({ name: "test" });
|
|
20
20
|
describe("create", () => {
|
|
21
21
|
it("should create a device on a rack", async () => {
|
|
22
|
+
const key = id.create();
|
|
22
23
|
const d = await client.hardware.devices.create({
|
|
23
24
|
rack: testRack.key,
|
|
24
25
|
location: "Dev1",
|
|
25
|
-
key
|
|
26
|
+
key,
|
|
26
27
|
name: "test",
|
|
27
28
|
make: "ni",
|
|
28
29
|
model: "dog",
|
|
29
30
|
properties: { cat: "dog" },
|
|
30
31
|
});
|
|
31
|
-
expect(d.key).toEqual(
|
|
32
|
+
expect(d.key).toEqual(key);
|
|
32
33
|
expect(d.name).toBe("test");
|
|
33
34
|
expect(d.make).toBe("ni");
|
|
34
35
|
});
|
|
@@ -41,7 +42,7 @@ describe("Device", async () => {
|
|
|
41
42
|
outputChannels: [{ port2: 232 }],
|
|
42
43
|
};
|
|
43
44
|
const d = await client.hardware.devices.create({
|
|
44
|
-
key:
|
|
45
|
+
key: id.create(),
|
|
45
46
|
rack: testRack.key,
|
|
46
47
|
location: "Dev1",
|
|
47
48
|
name: "test",
|
|
@@ -55,7 +56,7 @@ describe("Device", async () => {
|
|
|
55
56
|
describe("retrieve", () => {
|
|
56
57
|
it("should retrieve a device by its key", async () => {
|
|
57
58
|
const d = await client.hardware.devices.create({
|
|
58
|
-
key:
|
|
59
|
+
key: id.create(),
|
|
59
60
|
rack: testRack.key,
|
|
60
61
|
location: "Dev1",
|
|
61
62
|
name: "test",
|
|
@@ -112,7 +113,7 @@ describe("Device", async () => {
|
|
|
112
113
|
describe("state", () => {
|
|
113
114
|
it("should not include state by default", async () => {
|
|
114
115
|
const d = await client.hardware.devices.create({
|
|
115
|
-
key:
|
|
116
|
+
key: id.create(),
|
|
116
117
|
rack: testRack.key,
|
|
117
118
|
location: "Dev1",
|
|
118
119
|
name: "state_test1",
|
|
@@ -122,12 +123,12 @@ describe("Device", async () => {
|
|
|
122
123
|
});
|
|
123
124
|
|
|
124
125
|
const retrieved = await client.hardware.devices.retrieve(d.key);
|
|
125
|
-
expect(retrieved.state
|
|
126
|
+
expect(retrieved.state).toBeUndefined();
|
|
126
127
|
});
|
|
127
128
|
|
|
128
129
|
it("should include state when includeState is true", async () => {
|
|
129
130
|
const d = await client.hardware.devices.create({
|
|
130
|
-
key:
|
|
131
|
+
key: id.create(),
|
|
131
132
|
rack: testRack.key,
|
|
132
133
|
location: "Dev1",
|
|
133
134
|
name: "state_test2",
|
|
@@ -136,20 +137,19 @@ describe("Device", async () => {
|
|
|
136
137
|
properties: { cat: "dog" },
|
|
137
138
|
});
|
|
138
139
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
140
|
+
await expect
|
|
141
|
+
.poll(async () => {
|
|
142
|
+
const { state } = await client.hardware.devices.retrieve(d.key, {
|
|
143
|
+
includeState: true,
|
|
144
|
+
});
|
|
145
|
+
return state !== undefined;
|
|
146
|
+
})
|
|
147
|
+
.toBeTruthy();
|
|
148
148
|
});
|
|
149
149
|
|
|
150
150
|
it("should include state for multiple devices", async () => {
|
|
151
151
|
const d1 = await client.hardware.devices.create({
|
|
152
|
-
key:
|
|
152
|
+
key: id.create(),
|
|
153
153
|
rack: testRack.key,
|
|
154
154
|
location: "Dev1",
|
|
155
155
|
name: "state_test3",
|
|
@@ -159,7 +159,7 @@ describe("Device", async () => {
|
|
|
159
159
|
});
|
|
160
160
|
|
|
161
161
|
const d2 = await client.hardware.devices.create({
|
|
162
|
-
key:
|
|
162
|
+
key: id.create(),
|
|
163
163
|
rack: testRack.key,
|
|
164
164
|
location: "Dev2",
|
|
165
165
|
name: "state_test4",
|
|
@@ -168,18 +168,16 @@ describe("Device", async () => {
|
|
|
168
168
|
properties: { cat: "dog" },
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
});
|
|
171
|
+
await expect
|
|
172
|
+
.poll(async () => {
|
|
173
|
+
const retrievedDevices = await client.hardware.devices.retrieve(
|
|
174
|
+
[d1.key, d2.key],
|
|
175
|
+
{ includeState: true },
|
|
176
|
+
);
|
|
177
|
+
if (retrievedDevices.length !== 2) return false;
|
|
178
|
+
return retrievedDevices.every(({ state }) => state !== undefined);
|
|
179
|
+
})
|
|
180
|
+
.toBeTruthy();
|
|
183
181
|
});
|
|
184
182
|
|
|
185
183
|
it("should handle state with type-safe details", async () => {
|
|
@@ -7,8 +7,8 @@
|
|
|
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 { binary, type UnknownRecord, unknownRecordZ } from "@synnaxlabs/x";
|
|
11
|
-
import { z } from "zod";
|
|
10
|
+
import { binary, status, type UnknownRecord, unknownRecordZ, zod } from "@synnaxlabs/x";
|
|
11
|
+
import { z } from "zod/v4";
|
|
12
12
|
|
|
13
13
|
import { keyZ as rackKeyZ } from "@/hardware/rack/payload";
|
|
14
14
|
import { decodeJSONString } from "@/util/decodeJSONString";
|
|
@@ -18,7 +18,7 @@ export type Key = z.infer<typeof keyZ>;
|
|
|
18
18
|
|
|
19
19
|
export const stateZ = z.object({
|
|
20
20
|
key: keyZ,
|
|
21
|
-
variant: z.
|
|
21
|
+
variant: status.variantZ.or(z.literal("").transform<status.Variant>(() => "info")),
|
|
22
22
|
details: unknownRecordZ.or(z.string().transform(decodeJSONString)),
|
|
23
23
|
});
|
|
24
24
|
|
|
@@ -36,7 +36,7 @@ export const deviceZ = z.object({
|
|
|
36
36
|
location: z.string(),
|
|
37
37
|
configured: z.boolean().optional(),
|
|
38
38
|
properties: unknownRecordZ.or(z.string().transform(decodeJSONString)),
|
|
39
|
-
state:
|
|
39
|
+
state: zod.nullToUndefined(stateZ),
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
export interface Device<
|
|
@@ -44,7 +44,7 @@ export interface Device<
|
|
|
44
44
|
Make extends string = string,
|
|
45
45
|
Model extends string = string,
|
|
46
46
|
StateDetails extends {} = UnknownRecord,
|
|
47
|
-
> extends Omit<z.
|
|
47
|
+
> extends Omit<z.infer<typeof deviceZ>, "properties" | "state"> {
|
|
48
48
|
properties: Properties;
|
|
49
49
|
make: Make;
|
|
50
50
|
model: Model;
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
|
|
11
11
|
import { type UnknownRecord } from "@synnaxlabs/x";
|
|
12
|
+
import { array } from "@synnaxlabs/x/array";
|
|
12
13
|
import { type AsyncTermSearcher } from "@synnaxlabs/x/search";
|
|
13
|
-
import {
|
|
14
|
-
import { z } from "zod";
|
|
14
|
+
import { z } from "zod/v4";
|
|
15
15
|
|
|
16
16
|
import { framer } from "@/framer";
|
|
17
17
|
import {
|
|
@@ -81,7 +81,7 @@ export class Client implements AsyncTermSearcher<string, Key, Payload> {
|
|
|
81
81
|
await sendRequired<typeof deleteReqZ, typeof deleteResZ>(
|
|
82
82
|
this.client,
|
|
83
83
|
DELETE_ENDPOINT,
|
|
84
|
-
{ keys: toArray(keys) },
|
|
84
|
+
{ keys: array.toArray(keys) },
|
|
85
85
|
deleteReqZ,
|
|
86
86
|
deleteResZ,
|
|
87
87
|
);
|
|
@@ -94,7 +94,7 @@ export class Client implements AsyncTermSearcher<string, Key, Payload> {
|
|
|
94
94
|
const res = await sendRequired<typeof createReqZ, typeof createResZ>(
|
|
95
95
|
this.client,
|
|
96
96
|
CREATE_ENDPOINT,
|
|
97
|
-
{ racks: toArray(rack) },
|
|
97
|
+
{ racks: array.toArray(rack) },
|
|
98
98
|
createReqZ,
|
|
99
99
|
createResZ,
|
|
100
100
|
);
|
|
@@ -7,18 +7,18 @@
|
|
|
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 { zod } from "@synnaxlabs/x";
|
|
10
|
+
import { status, zod } from "@synnaxlabs/x";
|
|
11
11
|
import { TimeStamp } from "@synnaxlabs/x/telem";
|
|
12
|
-
import { z } from "zod";
|
|
12
|
+
import { z } from "zod/v4";
|
|
13
13
|
|
|
14
|
-
export const keyZ =
|
|
14
|
+
export const keyZ = z.uint32();
|
|
15
15
|
export type Key = z.infer<typeof keyZ>;
|
|
16
16
|
|
|
17
17
|
export const stateZ = z.object({
|
|
18
18
|
key: keyZ,
|
|
19
|
-
variant: z.
|
|
19
|
+
variant: status.variantZ.or(z.literal("").transform<status.Variant>(() => "info")),
|
|
20
20
|
message: z.string(),
|
|
21
|
-
lastReceived: TimeStamp.z
|
|
21
|
+
lastReceived: TimeStamp.z,
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
export interface State extends z.infer<typeof stateZ> {}
|
|
@@ -26,7 +26,7 @@ export interface State extends z.infer<typeof stateZ> {}
|
|
|
26
26
|
export const rackZ = z.object({
|
|
27
27
|
key: keyZ,
|
|
28
28
|
name: z.string(),
|
|
29
|
-
state:
|
|
29
|
+
state: zod.nullToUndefined(stateZ),
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
export interface Payload extends z.infer<typeof rackZ> {}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { TimeStamp } from "@synnaxlabs/x";
|
|
11
11
|
import { describe, expect, it } from "vitest";
|
|
12
|
-
import { ZodError } from "zod";
|
|
12
|
+
import { ZodError } from "zod/v4";
|
|
13
13
|
|
|
14
14
|
import { NotFoundError } from "@/errors";
|
|
15
15
|
import { newClient } from "@/setupspecs";
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
|
|
11
11
|
import { id } from "@synnaxlabs/x";
|
|
12
|
+
import { array } from "@synnaxlabs/x/array";
|
|
12
13
|
import { type UnknownRecord } from "@synnaxlabs/x/record";
|
|
13
14
|
import { type AsyncTermSearcher } from "@synnaxlabs/x/search";
|
|
14
15
|
import { type CrudeTimeSpan, TimeSpan } from "@synnaxlabs/x/telem";
|
|
15
|
-
import {
|
|
16
|
-
import { z } from "zod";
|
|
16
|
+
import { z } from "zod/v4";
|
|
17
17
|
|
|
18
18
|
import { framer } from "@/framer";
|
|
19
19
|
import { keyZ as rackKeyZ } from "@/hardware/rack/payload";
|
|
@@ -264,7 +264,7 @@ export class Client implements AsyncTermSearcher<string, Key, Payload> {
|
|
|
264
264
|
const res = await sendRequired<typeof createReqZ, typeof createResZ>(
|
|
265
265
|
this.client,
|
|
266
266
|
CREATE_ENDPOINT,
|
|
267
|
-
{ tasks: toArray(task) },
|
|
267
|
+
{ tasks: array.toArray(task) },
|
|
268
268
|
createReqZ,
|
|
269
269
|
createResZ,
|
|
270
270
|
);
|
|
@@ -276,7 +276,7 @@ export class Client implements AsyncTermSearcher<string, Key, Payload> {
|
|
|
276
276
|
await sendRequired<typeof deleteReqZ, typeof deleteResZ>(
|
|
277
277
|
this.client,
|
|
278
278
|
DELETE_ENDPOINT,
|
|
279
|
-
{ keys: toArray(keys) },
|
|
279
|
+
{ keys: array.toArray(keys) },
|
|
280
280
|
deleteReqZ,
|
|
281
281
|
deleteResZ,
|
|
282
282
|
);
|
|
@@ -393,21 +393,23 @@ export class Client implements AsyncTermSearcher<string, Key, Payload> {
|
|
|
393
393
|
|
|
394
394
|
sugar(payloads: Payload | Payload[]): Task | Task[] {
|
|
395
395
|
const isSingle = !Array.isArray(payloads);
|
|
396
|
-
const res =
|
|
397
|
-
(
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
396
|
+
const res = array
|
|
397
|
+
.toArray(payloads)
|
|
398
|
+
.map(
|
|
399
|
+
({ key, name, type, config, state, internal, snapshot }) =>
|
|
400
|
+
new Task(
|
|
401
|
+
key,
|
|
402
|
+
name,
|
|
403
|
+
type,
|
|
404
|
+
config,
|
|
405
|
+
internal,
|
|
406
|
+
snapshot,
|
|
407
|
+
state,
|
|
408
|
+
this.frameClient,
|
|
409
|
+
this.ontologyClient,
|
|
410
|
+
this.rangeClient,
|
|
411
|
+
),
|
|
412
|
+
);
|
|
411
413
|
return isSingle ? res[0] : res;
|
|
412
414
|
}
|
|
413
415
|
|