@synnaxlabs/client 0.40.0 → 0.41.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 +6 -6
- package/dist/client.cjs +26 -26
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2392 -2358
- package/dist/control/state.d.ts.map +1 -1
- package/dist/framer/deleter.d.ts.map +1 -1
- package/dist/framer/frame.d.ts.map +1 -1
- package/dist/framer/writer.d.ts.map +1 -1
- package/dist/hardware/device/client.d.ts +20 -7
- package/dist/hardware/device/client.d.ts.map +1 -1
- package/dist/hardware/device/payload.d.ts +64 -1
- package/dist/hardware/device/payload.d.ts.map +1 -1
- package/dist/hardware/rack/client.d.ts +12 -5
- package/dist/hardware/rack/client.d.ts.map +1 -1
- package/dist/hardware/rack/payload.d.ts +109 -0
- package/dist/hardware/rack/payload.d.ts.map +1 -1
- package/dist/hardware/task/payload.d.ts +1 -1
- package/dist/ranger/client.d.ts +3 -0
- package/dist/ranger/client.d.ts.map +1 -1
- package/dist/ranger/payload.d.ts.map +1 -1
- package/dist/ranger/writer.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/client.ts +1 -1
- package/src/control/state.spec.ts +1 -2
- package/src/framer/streamer.spec.ts +0 -9
- package/src/hardware/device/client.ts +42 -10
- package/src/hardware/device/device.spec.ts +109 -1
- package/src/hardware/device/payload.ts +15 -1
- package/src/hardware/rack/client.ts +48 -8
- package/src/hardware/rack/payload.ts +16 -1
- package/src/hardware/rack/rack.spec.ts +36 -0
- package/src/hardware/task/task.spec.ts +8 -5
- package/src/ranger/client.ts +3 -2
|
@@ -27,7 +27,6 @@ describe("Streamer", () => {
|
|
|
27
27
|
test("happy path", async () => {
|
|
28
28
|
const ch = await newChannel();
|
|
29
29
|
const streamer = await client.openStreamer(ch.key);
|
|
30
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
31
30
|
const writer = await client.openWriter({
|
|
32
31
|
start: TimeStamp.now(),
|
|
33
32
|
channels: ch.key,
|
|
@@ -56,7 +55,6 @@ describe("Streamer", () => {
|
|
|
56
55
|
channels: ch.key,
|
|
57
56
|
downsampleFactor: 1,
|
|
58
57
|
});
|
|
59
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
60
58
|
const writer = await client.openWriter({
|
|
61
59
|
start: TimeStamp.now(),
|
|
62
60
|
channels: ch.key,
|
|
@@ -75,7 +73,6 @@ describe("Streamer", () => {
|
|
|
75
73
|
channels: ch.key,
|
|
76
74
|
downsampleFactor: 2,
|
|
77
75
|
});
|
|
78
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
79
76
|
const writer = await client.openWriter({
|
|
80
77
|
start: TimeStamp.now(),
|
|
81
78
|
channels: ch.key,
|
|
@@ -94,7 +91,6 @@ describe("Streamer", () => {
|
|
|
94
91
|
channels: ch.key,
|
|
95
92
|
downsampleFactor: 10,
|
|
96
93
|
});
|
|
97
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
98
94
|
const writer = await client.openWriter({
|
|
99
95
|
start: TimeStamp.now(),
|
|
100
96
|
channels: ch.key,
|
|
@@ -145,9 +141,6 @@ describe("Streamer - Calculated Channels", () => {
|
|
|
145
141
|
// Set up streamer to listen for calculated results
|
|
146
142
|
const streamer = await client.openStreamer(calcChannel.key);
|
|
147
143
|
|
|
148
|
-
// Give streamer time to initialize
|
|
149
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
150
|
-
|
|
151
144
|
// Write test data
|
|
152
145
|
const startTime = TimeStamp.now();
|
|
153
146
|
const writer = await client.openWriter({
|
|
@@ -200,7 +193,6 @@ describe("Streamer - Calculated Channels", () => {
|
|
|
200
193
|
});
|
|
201
194
|
|
|
202
195
|
const streamer = await client.openStreamer(calcChannel.key);
|
|
203
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
204
196
|
|
|
205
197
|
const startTime = TimeStamp.now();
|
|
206
198
|
const writer = await client.openWriter({
|
|
@@ -254,7 +246,6 @@ describe("Streamer - Calculated Channels", () => {
|
|
|
254
246
|
});
|
|
255
247
|
|
|
256
248
|
const streamer = await client.openStreamer(calcChannel.key);
|
|
257
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
258
249
|
|
|
259
250
|
const startTime = TimeStamp.now();
|
|
260
251
|
const writer = await client.openWriter({
|
|
@@ -12,7 +12,7 @@ import { toArray, type UnknownRecord } from "@synnaxlabs/x";
|
|
|
12
12
|
import { type AsyncTermSearcher } from "@synnaxlabs/x/search";
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import { framer } from "@/framer";
|
|
16
16
|
import {
|
|
17
17
|
type Device,
|
|
18
18
|
deviceZ,
|
|
@@ -21,7 +21,10 @@ import {
|
|
|
21
21
|
type New,
|
|
22
22
|
newZ,
|
|
23
23
|
ONTOLOGY_TYPE,
|
|
24
|
+
type State,
|
|
25
|
+
stateZ,
|
|
24
26
|
} from "@/hardware/device/payload";
|
|
27
|
+
import { keyZ as rackKeyZ } from "@/hardware/rack/payload";
|
|
25
28
|
import { ontology } from "@/ontology";
|
|
26
29
|
import { signals } from "@/signals";
|
|
27
30
|
import { checkForMultipleOrNoResults } from "@/util/retrieve";
|
|
@@ -29,6 +32,7 @@ import { nullableArrayZ } from "@/util/zod";
|
|
|
29
32
|
|
|
30
33
|
const SET_CHANNEL_NAME = "sy_device_set";
|
|
31
34
|
const DELETE_CHANNEL_NAME = "sy_device_delete";
|
|
35
|
+
const STATE_CHANNEL_NAME = "sy_device_state";
|
|
32
36
|
|
|
33
37
|
const RETRIEVE_ENDPOINT = "/hardware/device/retrieve";
|
|
34
38
|
const CREATE_ENDPOINT = "/hardware/device/create";
|
|
@@ -43,19 +47,26 @@ const deleteReqZ = z.object({ keys: keyZ.array() });
|
|
|
43
47
|
const deleteResZ = z.object({});
|
|
44
48
|
|
|
45
49
|
const retrieveReqZ = z.object({
|
|
46
|
-
search: z.string().optional(),
|
|
47
|
-
limit: z.number().optional(),
|
|
48
|
-
offset: z.number().optional(),
|
|
49
50
|
keys: keyZ.array().optional(),
|
|
50
51
|
names: z.string().array().optional(),
|
|
51
52
|
makes: z.string().array().optional(),
|
|
53
|
+
models: z.string().array().optional(),
|
|
54
|
+
locations: z.string().array().optional(),
|
|
55
|
+
racks: rackKeyZ.array().optional(),
|
|
56
|
+
search: z.string().optional(),
|
|
57
|
+
limit: z.number().optional(),
|
|
58
|
+
offset: z.number().optional(),
|
|
52
59
|
ignoreNotFound: z.boolean().optional(),
|
|
60
|
+
includeState: z.boolean().optional(),
|
|
53
61
|
});
|
|
54
62
|
|
|
55
63
|
interface RetrieveRequest extends z.input<typeof retrieveReqZ> {}
|
|
56
64
|
|
|
57
65
|
export interface RetrieveOptions
|
|
58
|
-
extends Pick<
|
|
66
|
+
extends Pick<
|
|
67
|
+
RetrieveRequest,
|
|
68
|
+
"limit" | "offset" | "makes" | "ignoreNotFound" | "includeState"
|
|
69
|
+
> {}
|
|
59
70
|
|
|
60
71
|
interface PageOptions extends Pick<RetrieveOptions, "makes"> {}
|
|
61
72
|
|
|
@@ -75,26 +86,33 @@ export class Client implements AsyncTermSearcher<string, Key, Device> {
|
|
|
75
86
|
Properties extends UnknownRecord = UnknownRecord,
|
|
76
87
|
Make extends string = string,
|
|
77
88
|
Model extends string = string,
|
|
78
|
-
|
|
89
|
+
StateDetails extends {} = UnknownRecord,
|
|
90
|
+
>(
|
|
91
|
+
key: string,
|
|
92
|
+
options?: RetrieveOptions,
|
|
93
|
+
): Promise<Device<Properties, Make, Model, StateDetails>>;
|
|
79
94
|
|
|
80
95
|
async retrieve<
|
|
81
96
|
Properties extends UnknownRecord = UnknownRecord,
|
|
82
97
|
Make extends string = string,
|
|
83
98
|
Model extends string = string,
|
|
99
|
+
StateDetails extends {} = UnknownRecord,
|
|
84
100
|
>(
|
|
85
101
|
keys: string[],
|
|
86
102
|
options?: RetrieveOptions,
|
|
87
|
-
): Promise<Array<Device<Properties, Make, Model>>>;
|
|
103
|
+
): Promise<Array<Device<Properties, Make, Model, StateDetails>>>;
|
|
88
104
|
|
|
89
105
|
async retrieve<
|
|
90
106
|
Properties extends UnknownRecord = UnknownRecord,
|
|
91
107
|
Make extends string = string,
|
|
92
108
|
Model extends string = string,
|
|
109
|
+
StateDetails extends {} = UnknownRecord,
|
|
93
110
|
>(
|
|
94
111
|
keys: string | string[],
|
|
95
112
|
options?: RetrieveOptions,
|
|
96
113
|
): Promise<
|
|
97
|
-
|
|
114
|
+
| Device<Properties, Make, Model, StateDetails>
|
|
115
|
+
| Array<Device<Properties, Make, Model, StateDetails>>
|
|
98
116
|
> {
|
|
99
117
|
const isSingle = !Array.isArray(keys);
|
|
100
118
|
const res = await sendRequired(
|
|
@@ -106,8 +124,8 @@ export class Client implements AsyncTermSearcher<string, Key, Device> {
|
|
|
106
124
|
);
|
|
107
125
|
checkForMultipleOrNoResults("Device", keys, res.devices, isSingle);
|
|
108
126
|
return isSingle
|
|
109
|
-
? (res.devices[0] as Device<Properties, Make, Model>)
|
|
110
|
-
: (res.devices as Array<Device<Properties, Make, Model>>);
|
|
127
|
+
? (res.devices[0] as Device<Properties, Make, Model, StateDetails>)
|
|
128
|
+
: (res.devices as Array<Device<Properties, Make, Model, StateDetails>>);
|
|
111
129
|
}
|
|
112
130
|
|
|
113
131
|
async search(term: string, options?: RetrieveOptions): Promise<Device[]> {
|
|
@@ -183,6 +201,20 @@ export class Client implements AsyncTermSearcher<string, Key, Device> {
|
|
|
183
201
|
);
|
|
184
202
|
}
|
|
185
203
|
|
|
204
|
+
async openStateObserver<Details extends {} = UnknownRecord>(): Promise<
|
|
205
|
+
framer.ObservableStreamer<State<Details>[]>
|
|
206
|
+
> {
|
|
207
|
+
return new framer.ObservableStreamer<State<Details>[]>(
|
|
208
|
+
await this.frameClient.openStreamer(STATE_CHANNEL_NAME),
|
|
209
|
+
(frame) => {
|
|
210
|
+
const s = frame.get(STATE_CHANNEL_NAME);
|
|
211
|
+
if (s.length === 0) return [null, false];
|
|
212
|
+
const states = s.parseJSON(stateZ);
|
|
213
|
+
return [states as State<Details>[], true];
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
186
218
|
newSearcherWithOptions(
|
|
187
219
|
options: RetrieveOptions,
|
|
188
220
|
): AsyncTermSearcher<string, Key, Device> {
|
|
@@ -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 { id } from "@synnaxlabs/x";
|
|
10
|
+
import { id, type UnknownRecord } from "@synnaxlabs/x";
|
|
11
11
|
import { describe, expect, it } from "vitest";
|
|
12
12
|
|
|
13
13
|
import { NotFoundError } from "@/errors";
|
|
@@ -108,5 +108,113 @@ describe("Device", async () => {
|
|
|
108
108
|
}),
|
|
109
109
|
).rejects.toThrow(NotFoundError);
|
|
110
110
|
});
|
|
111
|
+
|
|
112
|
+
describe("state", () => {
|
|
113
|
+
it("should not include state by default", async () => {
|
|
114
|
+
const d = await client.hardware.devices.create({
|
|
115
|
+
key: "SN_STATE_TEST1",
|
|
116
|
+
rack: testRack.key,
|
|
117
|
+
location: "Dev1",
|
|
118
|
+
name: "state_test1",
|
|
119
|
+
make: "ni",
|
|
120
|
+
model: "dog",
|
|
121
|
+
properties: { cat: "dog" },
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const retrieved = await client.hardware.devices.retrieve(d.key);
|
|
125
|
+
expect(retrieved.state?.key).toHaveLength(0);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should include state when includeState is true", async () => {
|
|
129
|
+
const d = await client.hardware.devices.create({
|
|
130
|
+
key: "SN_STATE_TEST2",
|
|
131
|
+
rack: testRack.key,
|
|
132
|
+
location: "Dev1",
|
|
133
|
+
name: "state_test2",
|
|
134
|
+
make: "ni",
|
|
135
|
+
model: "dog",
|
|
136
|
+
properties: { cat: "dog" },
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const retrieved = await client.hardware.devices.retrieve(d.key, {
|
|
140
|
+
includeState: true,
|
|
141
|
+
});
|
|
142
|
+
expect(retrieved.state).toBeDefined();
|
|
143
|
+
if (retrieved.state) {
|
|
144
|
+
expect(retrieved.state.variant).toBeDefined();
|
|
145
|
+
expect(retrieved.state.key).toBeDefined();
|
|
146
|
+
expect(retrieved.state.details).toBeDefined();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("should include state for multiple devices", async () => {
|
|
151
|
+
const d1 = await client.hardware.devices.create({
|
|
152
|
+
key: "SN_STATE_TEST3",
|
|
153
|
+
rack: testRack.key,
|
|
154
|
+
location: "Dev1",
|
|
155
|
+
name: "state_test3",
|
|
156
|
+
make: "ni",
|
|
157
|
+
model: "dog",
|
|
158
|
+
properties: { cat: "dog" },
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const d2 = await client.hardware.devices.create({
|
|
162
|
+
key: "SN_STATE_TEST4",
|
|
163
|
+
rack: testRack.key,
|
|
164
|
+
location: "Dev2",
|
|
165
|
+
name: "state_test4",
|
|
166
|
+
make: "ni",
|
|
167
|
+
model: "dog",
|
|
168
|
+
properties: { cat: "dog" },
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const retrieved = await client.hardware.devices.retrieve([d1.key, d2.key], {
|
|
172
|
+
includeState: true,
|
|
173
|
+
});
|
|
174
|
+
expect(retrieved).toHaveLength(2);
|
|
175
|
+
retrieved.forEach((device) => {
|
|
176
|
+
expect(device.state).toBeDefined();
|
|
177
|
+
if (device.state) {
|
|
178
|
+
expect(device.state.variant).toBeDefined();
|
|
179
|
+
expect(device.state.key).toBeDefined();
|
|
180
|
+
expect(device.state.details).toBeDefined();
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should handle state with type-safe details", async () => {
|
|
186
|
+
interface DeviceStateDetails {
|
|
187
|
+
status: string;
|
|
188
|
+
temperature: number;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const key = id.create();
|
|
192
|
+
await client.hardware.devices.create({
|
|
193
|
+
key,
|
|
194
|
+
rack: testRack.key,
|
|
195
|
+
location: "Dev1",
|
|
196
|
+
name: "state_test5",
|
|
197
|
+
make: "ni",
|
|
198
|
+
model: "dog",
|
|
199
|
+
properties: { cat: "dog" },
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
await expect
|
|
203
|
+
.poll(async () => {
|
|
204
|
+
const retrieved = await client.hardware.devices.retrieve<
|
|
205
|
+
UnknownRecord,
|
|
206
|
+
string,
|
|
207
|
+
string,
|
|
208
|
+
DeviceStateDetails
|
|
209
|
+
>(key, { includeState: true });
|
|
210
|
+
return (
|
|
211
|
+
retrieved.state !== undefined &&
|
|
212
|
+
retrieved.state.variant === "info" &&
|
|
213
|
+
retrieved.state.key === key
|
|
214
|
+
);
|
|
215
|
+
})
|
|
216
|
+
.toBeTruthy();
|
|
217
|
+
});
|
|
218
|
+
});
|
|
111
219
|
});
|
|
112
220
|
});
|
|
@@ -16,6 +16,17 @@ import { decodeJSONString } from "@/util/decodeJSONString";
|
|
|
16
16
|
export const keyZ = z.string();
|
|
17
17
|
export type Key = z.infer<typeof keyZ>;
|
|
18
18
|
|
|
19
|
+
export const stateZ = z.object({
|
|
20
|
+
key: keyZ,
|
|
21
|
+
variant: z.string(),
|
|
22
|
+
details: z.record(z.unknown()).or(z.string().transform(decodeJSONString)),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export interface State<Details extends {} = UnknownRecord>
|
|
26
|
+
extends Omit<z.infer<typeof stateZ>, "details"> {
|
|
27
|
+
details: Details;
|
|
28
|
+
}
|
|
29
|
+
|
|
19
30
|
export const deviceZ = z.object({
|
|
20
31
|
key: keyZ,
|
|
21
32
|
rack: rackKeyZ,
|
|
@@ -25,16 +36,19 @@ export const deviceZ = z.object({
|
|
|
25
36
|
location: z.string(),
|
|
26
37
|
configured: z.boolean().optional(),
|
|
27
38
|
properties: z.record(z.unknown()).or(z.string().transform(decodeJSONString)),
|
|
39
|
+
state: stateZ.optional(),
|
|
28
40
|
});
|
|
29
41
|
|
|
30
42
|
export interface Device<
|
|
31
43
|
Properties extends UnknownRecord = UnknownRecord,
|
|
32
44
|
Make extends string = string,
|
|
33
45
|
Model extends string = string,
|
|
34
|
-
|
|
46
|
+
StateDetails extends {} = UnknownRecord,
|
|
47
|
+
> extends Omit<z.output<typeof deviceZ>, "properties" | "state"> {
|
|
35
48
|
properties: Properties;
|
|
36
49
|
make: Make;
|
|
37
50
|
model: Model;
|
|
51
|
+
state?: State<StateDetails>;
|
|
38
52
|
}
|
|
39
53
|
|
|
40
54
|
export const newZ = deviceZ.extend({
|
|
@@ -13,6 +13,7 @@ import { type AsyncTermSearcher } from "@synnaxlabs/x/search";
|
|
|
13
13
|
import { toArray } from "@synnaxlabs/x/toArray";
|
|
14
14
|
import { z } from "zod";
|
|
15
15
|
|
|
16
|
+
import { framer } from "@/framer";
|
|
16
17
|
import {
|
|
17
18
|
type Key,
|
|
18
19
|
keyZ,
|
|
@@ -21,6 +22,8 @@ import {
|
|
|
21
22
|
ONTOLOGY_TYPE,
|
|
22
23
|
type Payload,
|
|
23
24
|
rackZ,
|
|
25
|
+
type State,
|
|
26
|
+
stateZ,
|
|
24
27
|
} from "@/hardware/rack/payload";
|
|
25
28
|
import { type task } from "@/hardware/task";
|
|
26
29
|
import { ontology } from "@/ontology";
|
|
@@ -31,12 +34,17 @@ const RETRIEVE_ENDPOINT = "/hardware/rack/retrieve";
|
|
|
31
34
|
const CREATE_ENDPOINT = "/hardware/rack/create";
|
|
32
35
|
const DELETE_ENDPOINT = "/hardware/rack/delete";
|
|
33
36
|
|
|
37
|
+
const STATE_CHANNEL_NAME = "sy_rack_state";
|
|
38
|
+
|
|
34
39
|
const retrieveReqZ = z.object({
|
|
35
40
|
keys: keyZ.array().optional(),
|
|
36
41
|
names: z.string().array().optional(),
|
|
37
42
|
search: z.string().optional(),
|
|
38
|
-
|
|
43
|
+
embedded: z.boolean().optional(),
|
|
44
|
+
hostIsNode: z.boolean().optional(),
|
|
39
45
|
limit: z.number().optional(),
|
|
46
|
+
offset: z.number().optional(),
|
|
47
|
+
includeState: z.boolean().optional(),
|
|
40
48
|
});
|
|
41
49
|
|
|
42
50
|
const retrieveResZ = z.object({ racks: nullableArrayZ(rackZ) });
|
|
@@ -49,14 +57,24 @@ const deleteReqZ = z.object({ keys: keyZ.array() });
|
|
|
49
57
|
|
|
50
58
|
const deleteResZ = z.object({});
|
|
51
59
|
|
|
60
|
+
export interface RetrieveOptions {
|
|
61
|
+
includeState?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
52
64
|
export class Client implements AsyncTermSearcher<string, Key, Payload> {
|
|
53
65
|
readonly type = ONTOLOGY_TYPE;
|
|
54
66
|
private readonly client: UnaryClient;
|
|
55
67
|
private readonly tasks: task.Client;
|
|
68
|
+
private readonly frameClient: framer.Client;
|
|
56
69
|
|
|
57
|
-
constructor(
|
|
70
|
+
constructor(
|
|
71
|
+
client: UnaryClient,
|
|
72
|
+
taskClient: task.Client,
|
|
73
|
+
frameClient: framer.Client,
|
|
74
|
+
) {
|
|
58
75
|
this.client = client;
|
|
59
76
|
this.tasks = taskClient;
|
|
77
|
+
this.frameClient = frameClient;
|
|
60
78
|
}
|
|
61
79
|
|
|
62
80
|
async delete(keys: Key | Key[]): Promise<void> {
|
|
@@ -107,9 +125,12 @@ export class Client implements AsyncTermSearcher<string, Key, Payload> {
|
|
|
107
125
|
return this.sugar(res.racks);
|
|
108
126
|
}
|
|
109
127
|
|
|
110
|
-
async retrieve(key: string | Key): Promise<Rack>;
|
|
111
|
-
async retrieve(keys: Key[]): Promise<Rack[]>;
|
|
112
|
-
async retrieve(
|
|
128
|
+
async retrieve(key: string | Key, options?: RetrieveOptions): Promise<Rack>;
|
|
129
|
+
async retrieve(keys: Key[], options?: RetrieveOptions): Promise<Rack[]>;
|
|
130
|
+
async retrieve(
|
|
131
|
+
racks: string | Key | Key[],
|
|
132
|
+
options?: RetrieveOptions,
|
|
133
|
+
): Promise<Rack | Rack[]> {
|
|
113
134
|
const { variant, normalized, single } = analyzeParams(racks, {
|
|
114
135
|
string: "names",
|
|
115
136
|
number: "keys",
|
|
@@ -117,7 +138,10 @@ export class Client implements AsyncTermSearcher<string, Key, Payload> {
|
|
|
117
138
|
const res = await sendRequired<typeof retrieveReqZ, typeof retrieveResZ>(
|
|
118
139
|
this.client,
|
|
119
140
|
RETRIEVE_ENDPOINT,
|
|
120
|
-
{
|
|
141
|
+
{
|
|
142
|
+
[variant]: normalized,
|
|
143
|
+
includeState: options?.includeState,
|
|
144
|
+
},
|
|
121
145
|
retrieveReqZ,
|
|
122
146
|
retrieveResZ,
|
|
123
147
|
);
|
|
@@ -126,20 +150,36 @@ export class Client implements AsyncTermSearcher<string, Key, Payload> {
|
|
|
126
150
|
return single ? sugared[0] : sugared;
|
|
127
151
|
}
|
|
128
152
|
|
|
153
|
+
async openStateObserver(): Promise<framer.ObservableStreamer<State[]>> {
|
|
154
|
+
return new framer.ObservableStreamer<State[]>(
|
|
155
|
+
await this.frameClient.openStreamer(STATE_CHANNEL_NAME),
|
|
156
|
+
(fr) => {
|
|
157
|
+
const data = fr.get(STATE_CHANNEL_NAME);
|
|
158
|
+
if (data.length === 0) return [[], false];
|
|
159
|
+
const states = data.parseJSON(stateZ);
|
|
160
|
+
return [states, true];
|
|
161
|
+
},
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
129
165
|
private sugar(payloads: Payload[]): Rack[] {
|
|
130
|
-
return payloads.map(
|
|
166
|
+
return payloads.map(
|
|
167
|
+
({ key, name, state }) => new Rack(key, name, this.tasks, state),
|
|
168
|
+
);
|
|
131
169
|
}
|
|
132
170
|
}
|
|
133
171
|
|
|
134
172
|
export class Rack {
|
|
135
173
|
key: Key;
|
|
136
174
|
name: string;
|
|
175
|
+
state?: State;
|
|
137
176
|
private readonly tasks: task.Client;
|
|
138
177
|
|
|
139
|
-
constructor(key: Key, name: string, taskClient: task.Client) {
|
|
178
|
+
constructor(key: Key, name: string, taskClient: task.Client, state?: State) {
|
|
140
179
|
this.key = key;
|
|
141
180
|
this.name = name;
|
|
142
181
|
this.tasks = taskClient;
|
|
182
|
+
this.state = state;
|
|
143
183
|
}
|
|
144
184
|
|
|
145
185
|
async listTasks(): Promise<task.Task[]> {
|
|
@@ -8,12 +8,27 @@
|
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
10
|
import { zod } from "@synnaxlabs/x";
|
|
11
|
+
import { TimeStamp } from "@synnaxlabs/x/telem";
|
|
11
12
|
import { z } from "zod";
|
|
12
13
|
|
|
13
14
|
export const keyZ = zod.uint32;
|
|
14
15
|
export type Key = z.infer<typeof keyZ>;
|
|
15
16
|
|
|
16
|
-
export const
|
|
17
|
+
export const stateZ = z.object({
|
|
18
|
+
key: keyZ,
|
|
19
|
+
variant: z.string(),
|
|
20
|
+
message: z.string(),
|
|
21
|
+
lastReceived: TimeStamp.z.optional(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export interface State extends z.infer<typeof stateZ> {}
|
|
25
|
+
|
|
26
|
+
export const rackZ = z.object({
|
|
27
|
+
key: keyZ,
|
|
28
|
+
name: z.string(),
|
|
29
|
+
state: stateZ.optional(),
|
|
30
|
+
});
|
|
31
|
+
|
|
17
32
|
export interface Payload extends z.infer<typeof rackZ> {}
|
|
18
33
|
|
|
19
34
|
export const newZ = rackZ.partial({ key: true });
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
|
+
import { TimeStamp } from "@synnaxlabs/x";
|
|
10
11
|
import { describe, expect, it } from "vitest";
|
|
11
12
|
import { ZodError } from "zod";
|
|
12
13
|
|
|
@@ -66,4 +67,39 @@ describe("Rack", () => {
|
|
|
66
67
|
).rejects.toThrow(NotFoundError);
|
|
67
68
|
});
|
|
68
69
|
});
|
|
70
|
+
describe("state", () => {
|
|
71
|
+
it("should include state when includeState is true", async () => {
|
|
72
|
+
const r = await client.hardware.racks.create({ name: "test" });
|
|
73
|
+
await expect
|
|
74
|
+
.poll(async () => {
|
|
75
|
+
const retrieved = await client.hardware.racks.retrieve(r.key, {
|
|
76
|
+
includeState: true,
|
|
77
|
+
});
|
|
78
|
+
return (
|
|
79
|
+
retrieved.state !== undefined &&
|
|
80
|
+
retrieved.state.lastReceived instanceof TimeStamp &&
|
|
81
|
+
retrieved.state.key === r.key
|
|
82
|
+
);
|
|
83
|
+
})
|
|
84
|
+
.toBeTruthy();
|
|
85
|
+
});
|
|
86
|
+
it("should include state for multiple racks", async () => {
|
|
87
|
+
const r1 = await client.hardware.racks.create({ name: "test1" });
|
|
88
|
+
const r2 = await client.hardware.racks.create({ name: "test2" });
|
|
89
|
+
|
|
90
|
+
await expect
|
|
91
|
+
.poll(async () => {
|
|
92
|
+
const retrieved = await client.hardware.racks.retrieve([r1.key, r2.key], {
|
|
93
|
+
includeState: true,
|
|
94
|
+
});
|
|
95
|
+
return retrieved.every(
|
|
96
|
+
(rack) =>
|
|
97
|
+
rack.state !== undefined &&
|
|
98
|
+
rack.state.lastReceived instanceof TimeStamp &&
|
|
99
|
+
rack.state.key === rack.key,
|
|
100
|
+
);
|
|
101
|
+
})
|
|
102
|
+
.toBeTruthy();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
69
105
|
});
|
|
@@ -100,11 +100,14 @@ describe("Task", async () => {
|
|
|
100
100
|
};
|
|
101
101
|
expect(await w.write("sy_task_state", [state])).toBeTruthy();
|
|
102
102
|
await w.close();
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
await expect
|
|
104
|
+
.poll(async () => {
|
|
105
|
+
const retrieved = await client.hardware.tasks.retrieve(t.key, {
|
|
106
|
+
includeState: true,
|
|
107
|
+
});
|
|
108
|
+
return retrieved.state?.variant === state.variant;
|
|
109
|
+
})
|
|
110
|
+
.toBeTruthy();
|
|
108
111
|
});
|
|
109
112
|
});
|
|
110
113
|
});
|
package/src/ranger/client.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { z } from "zod";
|
|
|
17
17
|
import { type channel } from "@/channel";
|
|
18
18
|
import { MultipleFoundError, NotFoundError, QueryError } from "@/errors";
|
|
19
19
|
import { type framer } from "@/framer";
|
|
20
|
-
import {
|
|
20
|
+
import { label } from "@/label";
|
|
21
21
|
import { ontology } from "@/ontology";
|
|
22
22
|
import { type Alias, Aliaser } from "@/ranger/alias";
|
|
23
23
|
import { KV } from "@/ranger/kv";
|
|
@@ -195,6 +195,7 @@ const retrieveReqZ = z.object({
|
|
|
195
195
|
overlapsWith: TimeRange.z.optional(),
|
|
196
196
|
limit: z.number().int().optional(),
|
|
197
197
|
offset: z.number().int().optional(),
|
|
198
|
+
hasLabels: label.keyZ.array().optional(),
|
|
198
199
|
});
|
|
199
200
|
|
|
200
201
|
export interface RetrieveRequest extends z.infer<typeof retrieveReqZ> {}
|
|
@@ -321,7 +322,7 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
|
|
|
321
322
|
"sy_range_delete",
|
|
322
323
|
(variant, data) => {
|
|
323
324
|
if (variant === "delete")
|
|
324
|
-
return data.
|
|
325
|
+
return data.toUUIDs().map((k) => ({ variant, key: k, value: undefined }));
|
|
325
326
|
const sugared = this.sugarMany(data.parseJSON(payloadZ));
|
|
326
327
|
return sugared.map((r) => ({ variant, key: r.key, value: r }));
|
|
327
328
|
},
|