@synnaxlabs/client 0.43.1 → 0.44.2

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.
Files changed (245) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/dist/access/payload.d.ts +1 -1
  3. package/dist/access/payload.d.ts.map +1 -1
  4. package/dist/access/policy/client.d.ts +263 -6
  5. package/dist/access/policy/client.d.ts.map +1 -1
  6. package/dist/access/policy/external.d.ts +0 -1
  7. package/dist/access/policy/external.d.ts.map +1 -1
  8. package/dist/access/policy/payload.d.ts +105 -93
  9. package/dist/access/policy/payload.d.ts.map +1 -1
  10. package/dist/auth/auth.d.ts +1 -1
  11. package/dist/auth/auth.d.ts.map +1 -1
  12. package/dist/channel/client.d.ts +23 -17
  13. package/dist/channel/client.d.ts.map +1 -1
  14. package/dist/channel/payload.d.ts +151 -21
  15. package/dist/channel/payload.d.ts.map +1 -1
  16. package/dist/channel/retriever.d.ts +9 -16
  17. package/dist/channel/retriever.d.ts.map +1 -1
  18. package/dist/channel/writer.d.ts +1 -1
  19. package/dist/channel/writer.d.ts.map +1 -1
  20. package/dist/client.cjs +27 -135
  21. package/dist/client.d.ts +3 -3
  22. package/dist/client.d.ts.map +1 -1
  23. package/dist/client.js +8657 -28963
  24. package/dist/connection/checker.d.ts +1 -1
  25. package/dist/connection/checker.d.ts.map +1 -1
  26. package/dist/control/client.d.ts +1 -0
  27. package/dist/control/client.d.ts.map +1 -1
  28. package/dist/control/state.d.ts +6 -6
  29. package/dist/control/state.d.ts.map +1 -1
  30. package/dist/errors.d.ts +18 -5
  31. package/dist/errors.d.ts.map +1 -1
  32. package/dist/framer/adapter.d.ts +3 -3
  33. package/dist/framer/adapter.d.ts.map +1 -1
  34. package/dist/framer/client.d.ts +4 -13
  35. package/dist/framer/client.d.ts.map +1 -1
  36. package/dist/framer/codec.d.ts +1 -1
  37. package/dist/framer/codec.d.ts.map +1 -1
  38. package/dist/framer/deleter.d.ts +5 -5
  39. package/dist/framer/deleter.d.ts.map +1 -1
  40. package/dist/framer/frame.d.ts +5 -7
  41. package/dist/framer/frame.d.ts.map +1 -1
  42. package/dist/framer/streamProxy.d.ts +1 -1
  43. package/dist/framer/streamProxy.d.ts.map +1 -1
  44. package/dist/framer/streamer.d.ts +235 -20
  45. package/dist/framer/streamer.d.ts.map +1 -1
  46. package/dist/framer/writer.d.ts +302 -33
  47. package/dist/framer/writer.d.ts.map +1 -1
  48. package/dist/hardware/device/client.d.ts +49 -28
  49. package/dist/hardware/device/client.d.ts.map +1 -1
  50. package/dist/hardware/device/payload.d.ts +126 -46
  51. package/dist/hardware/device/payload.d.ts.map +1 -1
  52. package/dist/hardware/rack/client.d.ts +78 -22
  53. package/dist/hardware/rack/client.d.ts.map +1 -1
  54. package/dist/hardware/rack/payload.d.ts +99 -56
  55. package/dist/hardware/rack/payload.d.ts.map +1 -1
  56. package/dist/hardware/task/client.d.ts +100 -41
  57. package/dist/hardware/task/client.d.ts.map +1 -1
  58. package/dist/hardware/task/payload.d.ts +83 -61
  59. package/dist/hardware/task/payload.d.ts.map +1 -1
  60. package/dist/index.d.ts +2 -2
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/label/client.d.ts +138 -20
  63. package/dist/label/client.d.ts.map +1 -1
  64. package/dist/label/external.d.ts +0 -2
  65. package/dist/label/external.d.ts.map +1 -1
  66. package/dist/label/payload.d.ts +4 -5
  67. package/dist/label/payload.d.ts.map +1 -1
  68. package/dist/ontology/client.d.ts +45 -135
  69. package/dist/ontology/client.d.ts.map +1 -1
  70. package/dist/ontology/group/group.d.ts +3 -3
  71. package/dist/ontology/group/group.d.ts.map +1 -1
  72. package/dist/ontology/group/payload.d.ts +3 -27
  73. package/dist/ontology/group/payload.d.ts.map +1 -1
  74. package/dist/ontology/payload.d.ts +114 -243
  75. package/dist/ontology/payload.d.ts.map +1 -1
  76. package/dist/ontology/writer.d.ts +4 -4
  77. package/dist/ontology/writer.d.ts.map +1 -1
  78. package/dist/ranger/alias.d.ts +15 -5
  79. package/dist/ranger/alias.d.ts.map +1 -1
  80. package/dist/ranger/client.d.ts +91 -30
  81. package/dist/ranger/client.d.ts.map +1 -1
  82. package/dist/ranger/external.d.ts +1 -1
  83. package/dist/ranger/external.d.ts.map +1 -1
  84. package/dist/ranger/kv.d.ts +11 -12
  85. package/dist/ranger/kv.d.ts.map +1 -1
  86. package/dist/ranger/payload.d.ts +19 -44
  87. package/dist/ranger/payload.d.ts.map +1 -1
  88. package/dist/ranger/writer.d.ts +22 -19
  89. package/dist/ranger/writer.d.ts.map +1 -1
  90. package/dist/testutil/client.d.ts +4 -0
  91. package/dist/testutil/client.d.ts.map +1 -0
  92. package/dist/user/client.d.ts +59 -6
  93. package/dist/user/client.d.ts.map +1 -1
  94. package/dist/user/payload.d.ts +4 -6
  95. package/dist/user/payload.d.ts.map +1 -1
  96. package/dist/user/retriever.d.ts +2 -2
  97. package/dist/user/retriever.d.ts.map +1 -1
  98. package/dist/util/decodeJSONString.d.ts +2 -2
  99. package/dist/util/decodeJSONString.d.ts.map +1 -1
  100. package/dist/util/parseWithoutKeyConversion.d.ts +2 -2
  101. package/dist/util/parseWithoutKeyConversion.d.ts.map +1 -1
  102. package/dist/util/retrieve.d.ts +1 -1
  103. package/dist/util/retrieve.d.ts.map +1 -1
  104. package/dist/util/zod.d.ts +1 -1
  105. package/dist/util/zod.d.ts.map +1 -1
  106. package/dist/workspace/client.d.ts +17 -6
  107. package/dist/workspace/client.d.ts.map +1 -1
  108. package/dist/workspace/lineplot/client.d.ts +2 -2
  109. package/dist/workspace/lineplot/client.d.ts.map +1 -1
  110. package/dist/workspace/lineplot/payload.d.ts +8 -9
  111. package/dist/workspace/lineplot/payload.d.ts.map +1 -1
  112. package/dist/workspace/log/client.d.ts +2 -2
  113. package/dist/workspace/log/client.d.ts.map +1 -1
  114. package/dist/workspace/log/payload.d.ts +8 -9
  115. package/dist/workspace/log/payload.d.ts.map +1 -1
  116. package/dist/workspace/payload.d.ts +10 -11
  117. package/dist/workspace/payload.d.ts.map +1 -1
  118. package/dist/workspace/schematic/client.d.ts +2 -2
  119. package/dist/workspace/schematic/client.d.ts.map +1 -1
  120. package/dist/workspace/schematic/payload.d.ts +10 -11
  121. package/dist/workspace/schematic/payload.d.ts.map +1 -1
  122. package/dist/workspace/table/client.d.ts +2 -2
  123. package/dist/workspace/table/client.d.ts.map +1 -1
  124. package/dist/workspace/table/payload.d.ts +10 -11
  125. package/dist/workspace/table/payload.d.ts.map +1 -1
  126. package/examples/node/package-lock.json +47 -39
  127. package/examples/node/package.json +2 -1
  128. package/examples/node/streamWrite.js +5 -11
  129. package/package.json +14 -13
  130. package/src/access/payload.ts +1 -1
  131. package/src/access/policy/client.ts +87 -32
  132. package/src/access/policy/external.ts +0 -1
  133. package/src/access/policy/payload.ts +4 -4
  134. package/src/access/policy/policy.spec.ts +86 -83
  135. package/src/auth/auth.spec.ts +29 -18
  136. package/src/auth/auth.ts +1 -1
  137. package/src/channel/batchRetriever.spec.ts +4 -9
  138. package/src/channel/channel.spec.ts +24 -6
  139. package/src/channel/client.ts +52 -51
  140. package/src/channel/payload.ts +15 -16
  141. package/src/channel/retriever.ts +26 -41
  142. package/src/channel/writer.ts +7 -4
  143. package/src/client.ts +4 -4
  144. package/src/connection/checker.ts +1 -1
  145. package/src/connection/connection.spec.ts +31 -23
  146. package/src/control/client.ts +2 -2
  147. package/src/control/state.spec.ts +3 -3
  148. package/src/control/state.ts +1 -1
  149. package/src/errors.spec.ts +9 -5
  150. package/src/errors.ts +28 -15
  151. package/src/framer/adapter.spec.ts +118 -9
  152. package/src/framer/adapter.ts +24 -11
  153. package/src/framer/client.spec.ts +125 -2
  154. package/src/framer/client.ts +41 -47
  155. package/src/framer/codec.ts +1 -1
  156. package/src/framer/deleter.spec.ts +2 -2
  157. package/src/framer/deleter.ts +1 -1
  158. package/src/framer/frame.ts +1 -4
  159. package/src/framer/iterator.spec.ts +8 -8
  160. package/src/framer/iterator.ts +1 -1
  161. package/src/framer/streamProxy.ts +1 -1
  162. package/src/framer/streamer.spec.ts +185 -36
  163. package/src/framer/streamer.ts +28 -36
  164. package/src/framer/writer.spec.ts +7 -6
  165. package/src/framer/writer.ts +97 -111
  166. package/src/hardware/device/client.ts +45 -131
  167. package/src/hardware/device/device.spec.ts +163 -52
  168. package/src/hardware/device/payload.ts +10 -21
  169. package/src/hardware/rack/client.ts +87 -105
  170. package/src/hardware/rack/payload.ts +4 -13
  171. package/src/hardware/rack/rack.spec.ts +28 -35
  172. package/src/hardware/task/client.ts +335 -291
  173. package/src/hardware/task/payload.ts +86 -62
  174. package/src/hardware/task/task.spec.ts +208 -32
  175. package/src/index.ts +2 -1
  176. package/src/label/client.ts +100 -95
  177. package/src/label/external.ts +0 -2
  178. package/src/label/label.spec.ts +8 -6
  179. package/src/label/payload.ts +3 -4
  180. package/src/ontology/client.ts +41 -324
  181. package/src/ontology/group/group.spec.ts +2 -2
  182. package/src/ontology/group/group.ts +4 -5
  183. package/src/ontology/group/payload.ts +2 -25
  184. package/src/ontology/group/writer.ts +1 -1
  185. package/src/ontology/ontology.spec.ts +355 -41
  186. package/src/ontology/payload.ts +77 -112
  187. package/src/ontology/writer.ts +8 -17
  188. package/src/ranger/alias.ts +45 -37
  189. package/src/ranger/client.ts +144 -149
  190. package/src/ranger/external.ts +1 -1
  191. package/src/ranger/kv.ts +9 -27
  192. package/src/ranger/payload.ts +23 -37
  193. package/src/ranger/ranger.spec.ts +37 -56
  194. package/src/ranger/writer.ts +1 -1
  195. package/src/{signals/index.ts → testutil/client.ts} +11 -1
  196. package/src/user/client.ts +122 -47
  197. package/src/user/payload.ts +2 -5
  198. package/src/user/retriever.ts +1 -1
  199. package/src/user/user.spec.ts +31 -31
  200. package/src/user/writer.ts +1 -1
  201. package/src/util/decodeJSONString.ts +3 -3
  202. package/src/util/parseWithoutKeyConversion.ts +2 -2
  203. package/src/util/retrieve.ts +1 -1
  204. package/src/util/zod.ts +1 -1
  205. package/src/workspace/client.ts +20 -36
  206. package/src/workspace/lineplot/client.ts +5 -7
  207. package/src/workspace/lineplot/lineplot.spec.ts +2 -2
  208. package/src/workspace/lineplot/payload.ts +4 -7
  209. package/src/workspace/log/client.ts +5 -7
  210. package/src/workspace/log/log.spec.ts +2 -2
  211. package/src/workspace/log/payload.ts +4 -7
  212. package/src/workspace/payload.ts +4 -7
  213. package/src/workspace/schematic/client.ts +5 -7
  214. package/src/workspace/schematic/payload.ts +4 -7
  215. package/src/workspace/schematic/schematic.spec.ts +2 -2
  216. package/src/workspace/table/client.ts +5 -7
  217. package/src/workspace/table/payload.ts +4 -7
  218. package/src/workspace/table/table.spec.ts +2 -2
  219. package/src/workspace/workspace.spec.ts +2 -2
  220. package/dist/access/policy/ontology.d.ts +0 -5
  221. package/dist/access/policy/ontology.d.ts.map +0 -1
  222. package/dist/access/policy/retriever.d.ts +0 -40
  223. package/dist/access/policy/retriever.d.ts.map +0 -1
  224. package/dist/access/policy/writer.d.ts +0 -9
  225. package/dist/access/policy/writer.d.ts.map +0 -1
  226. package/dist/label/retriever.d.ts +0 -14
  227. package/dist/label/retriever.d.ts.map +0 -1
  228. package/dist/label/writer.d.ts +0 -54
  229. package/dist/label/writer.d.ts.map +0 -1
  230. package/dist/setupspecs.d.ts +0 -5
  231. package/dist/setupspecs.d.ts.map +0 -1
  232. package/dist/signals/external.d.ts +0 -2
  233. package/dist/signals/external.d.ts.map +0 -1
  234. package/dist/signals/index.d.ts +0 -2
  235. package/dist/signals/index.d.ts.map +0 -1
  236. package/dist/signals/observable.d.ts +0 -12
  237. package/dist/signals/observable.d.ts.map +0 -1
  238. package/src/access/policy/ontology.ts +0 -17
  239. package/src/access/policy/retriever.ts +0 -44
  240. package/src/access/policy/writer.ts +0 -65
  241. package/src/label/retriever.ts +0 -63
  242. package/src/label/writer.ts +0 -95
  243. package/src/setupspecs.ts +0 -27
  244. package/src/signals/external.ts +0 -10
  245. package/src/signals/observable.ts +0 -42
@@ -7,13 +7,12 @@
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, type UnknownRecord } from "@synnaxlabs/x";
11
- import { describe, expect, it } from "vitest";
10
+ import { id, unique } from "@synnaxlabs/x";
11
+ import { beforeAll, describe, expect, it } from "vitest";
12
12
 
13
- import { NotFoundError } from "@/errors";
14
- import { newClient } from "@/setupspecs";
13
+ import { createTestClient } from "@/testutil/client";
15
14
 
16
- const client = newClient();
15
+ const client = createTestClient();
17
16
 
18
17
  describe("Device", async () => {
19
18
  const testRack = await client.hardware.racks.create({ name: "test" });
@@ -50,7 +49,8 @@ describe("Device", async () => {
50
49
  model: "dog",
51
50
  properties,
52
51
  });
53
- const retrieved = await client.hardware.devices.retrieve(d.key);
52
+ const retrieved = await client.hardware.devices.retrieve({ key: d.key });
53
+ expect(retrieved.key).toEqual(d.key);
54
54
  expect(retrieved.properties).toEqual(properties);
55
55
  });
56
56
  describe("retrieve", () => {
@@ -64,7 +64,7 @@ describe("Device", async () => {
64
64
  model: "dog",
65
65
  properties: { cat: "dog" },
66
66
  });
67
- const retrieved = await client.hardware.devices.retrieve(d.key);
67
+ const retrieved = await client.hardware.devices.retrieve({ key: d.key });
68
68
  expect(retrieved.key).toBe(d.key);
69
69
  expect(retrieved.name).toBe("test");
70
70
  expect(retrieved.make).toBe("ni");
@@ -88,27 +88,13 @@ describe("Device", async () => {
88
88
  model: "dog",
89
89
  properties: { cat: "dog" },
90
90
  });
91
- const retrieved = await client.hardware.devices.retrieve([d1.key, d2.key]);
91
+ const retrieved = await client.hardware.devices.retrieve({
92
+ keys: [d1.key, d2.key],
93
+ });
92
94
  expect(retrieved.length).toBe(2);
93
95
  expect(retrieved[0].key).toBe(d1.key);
94
96
  expect(retrieved[1].key).toBe(d2.key);
95
97
  });
96
- it("should handle ignoreNotFound option", async () => {
97
- // Test multiple device retrieval
98
- const results = await client.hardware.devices.retrieve(
99
- ["nonexistent_key1", "nonexistent_key2"],
100
- { ignoreNotFound: true },
101
- );
102
- expect(results).toEqual([]);
103
- });
104
-
105
- it("should throw an error when device not found and ignoreNotFound is false", async () => {
106
- await expect(
107
- client.hardware.devices.retrieve(["nonexistent_key"], {
108
- ignoreNotFound: false,
109
- }),
110
- ).rejects.toThrow(NotFoundError);
111
- });
112
98
 
113
99
  describe("state", () => {
114
100
  it("should not include state by default", async () => {
@@ -122,11 +108,11 @@ describe("Device", async () => {
122
108
  properties: { cat: "dog" },
123
109
  });
124
110
 
125
- const retrieved = await client.hardware.devices.retrieve(d.key);
126
- expect(retrieved.state).toBeUndefined();
111
+ const retrieved = await client.hardware.devices.retrieve({ key: d.key });
112
+ expect(retrieved.status).toBeUndefined();
127
113
  });
128
114
 
129
- it("should include state when includeState is true", async () => {
115
+ it("should include status when includeStatus is true", async () => {
130
116
  const d = await client.hardware.devices.create({
131
117
  key: id.create(),
132
118
  rack: testRack.key,
@@ -139,12 +125,13 @@ describe("Device", async () => {
139
125
 
140
126
  await expect
141
127
  .poll(async () => {
142
- const { state } = await client.hardware.devices.retrieve(d.key, {
143
- includeState: true,
128
+ const { status } = await client.hardware.devices.retrieve({
129
+ key: d.key,
130
+ includeStatus: true,
144
131
  });
145
- return state !== undefined;
132
+ return status != null;
146
133
  })
147
- .toBeTruthy();
134
+ .toBe(true);
148
135
  });
149
136
 
150
137
  it("should include state for multiple devices", async () => {
@@ -170,22 +157,17 @@ describe("Device", async () => {
170
157
 
171
158
  await expect
172
159
  .poll(async () => {
173
- const retrievedDevices = await client.hardware.devices.retrieve(
174
- [d1.key, d2.key],
175
- { includeState: true },
176
- );
160
+ const retrievedDevices = await client.hardware.devices.retrieve({
161
+ keys: [d1.key, d2.key],
162
+ includeStatus: true,
163
+ });
177
164
  if (retrievedDevices.length !== 2) return false;
178
- return retrievedDevices.every(({ state }) => state !== undefined);
165
+ return retrievedDevices.every(({ status }) => status !== undefined);
179
166
  })
180
- .toBeTruthy();
167
+ .toBe(true);
181
168
  });
182
169
 
183
170
  it("should handle state with type-safe details", async () => {
184
- interface DeviceStateDetails {
185
- status: string;
186
- temperature: number;
187
- }
188
-
189
171
  const key = id.create();
190
172
  await client.hardware.devices.create({
191
173
  key,
@@ -199,19 +181,148 @@ describe("Device", async () => {
199
181
 
200
182
  await expect
201
183
  .poll(async () => {
202
- const retrieved = await client.hardware.devices.retrieve<
203
- UnknownRecord,
204
- string,
205
- string,
206
- DeviceStateDetails
207
- >(key, { includeState: true });
184
+ const retrieved = await client.hardware.devices.retrieve({
185
+ key,
186
+ includeStatus: true,
187
+ });
208
188
  return (
209
- retrieved.state !== undefined &&
210
- retrieved.state.variant === "info" &&
211
- retrieved.state.key === key
189
+ retrieved.status !== undefined &&
190
+ retrieved.status.variant === "info" &&
191
+ retrieved.status.details.device === key
212
192
  );
213
193
  })
214
- .toBeTruthy();
194
+ .toBe(true);
195
+ });
196
+ });
197
+
198
+ describe("request object format", () => {
199
+ const testDevices: Array<{
200
+ key: string;
201
+ name: string;
202
+ make: string;
203
+ model: string;
204
+ location: string;
205
+ }> = [];
206
+
207
+ beforeAll(async () => {
208
+ const deviceConfigs = [
209
+ { name: "sensor1", make: "ni", model: "pxi-6281", location: "Lab1" },
210
+ { name: "sensor2", make: "ni", model: "pxi-6284", location: "Lab2" },
211
+ { name: "actuator1", make: "labjack", model: "t7", location: "Lab1" },
212
+ { name: "actuator2", make: "labjack", model: "t4", location: "Lab3" },
213
+ { name: "controller", make: "opc", model: "server", location: "Lab2" },
214
+ ];
215
+
216
+ for (const config of deviceConfigs) {
217
+ const key = id.create();
218
+ await client.hardware.devices.create({
219
+ key,
220
+ rack: testRack.key,
221
+ location: config.location,
222
+ name: config.name,
223
+ make: config.make,
224
+ model: config.model,
225
+ properties: { test: true },
226
+ });
227
+ testDevices.push({ key, ...config });
228
+ }
229
+ });
230
+
231
+ it("should retrieve devices by names", async () => {
232
+ const result = await client.hardware.devices.retrieve({
233
+ names: ["sensor1", "actuator1"],
234
+ });
235
+ expect(result.length).toBeGreaterThanOrEqual(2);
236
+ expect(unique.unique(result.map((d) => d.name).sort())).toEqual([
237
+ "actuator1",
238
+ "sensor1",
239
+ ]);
240
+ });
241
+
242
+ it("should retrieve devices by makes", async () => {
243
+ const result = await client.hardware.devices.retrieve({
244
+ makes: ["ni"],
245
+ });
246
+ expect(result.length).toBeGreaterThanOrEqual(2);
247
+ expect(result.every((d) => d.make === "ni")).toBe(true);
248
+ });
249
+
250
+ it("should retrieve devices by models", async () => {
251
+ const result = await client.hardware.devices.retrieve({
252
+ models: ["pxi-6281", "t7"],
253
+ });
254
+ expect(result.length).toBeGreaterThanOrEqual(2);
255
+ expect(unique.unique(result.map((d) => d.model).sort())).toEqual([
256
+ "pxi-6281",
257
+ "t7",
258
+ ]);
259
+ });
260
+
261
+ it("should retrieve devices by locations", async () => {
262
+ const result = await client.hardware.devices.retrieve({
263
+ locations: ["Lab1"],
264
+ });
265
+ expect(result.length).toBeGreaterThanOrEqual(2);
266
+ expect(result.every((d) => d.location === "Lab1")).toBe(true);
267
+ });
268
+
269
+ it("should retrieve devices by racks", async () => {
270
+ const result = await client.hardware.devices.retrieve({
271
+ racks: [testRack.key],
272
+ });
273
+ expect(result.length).toBeGreaterThanOrEqual(5);
274
+ expect(result.every((d) => d.rack === testRack.key)).toBe(true);
275
+ });
276
+
277
+ it("should retrieve devices by search term", async () => {
278
+ const result = await client.hardware.devices.retrieve({
279
+ searchTerm: "sensor1",
280
+ });
281
+ expect(result.length).toBeGreaterThanOrEqual(2);
282
+ expect(result.every((d) => d.name.includes("sensor"))).toBe(true);
283
+ });
284
+
285
+ it("should support pagination with limit and offset", async () => {
286
+ const firstPage = await client.hardware.devices.retrieve({
287
+ racks: [testRack.key],
288
+ limit: 2,
289
+ offset: 0,
290
+ });
291
+ expect(firstPage).toHaveLength(2);
292
+
293
+ const secondPage = await client.hardware.devices.retrieve({
294
+ racks: [testRack.key],
295
+ limit: 2,
296
+ offset: 2,
297
+ });
298
+ expect(secondPage).toHaveLength(2);
299
+
300
+ const firstPageKeys = firstPage.map((d) => d.key);
301
+ const secondPageKeys = secondPage.map((d) => d.key);
302
+ expect(firstPageKeys.every((key) => !secondPageKeys.includes(key))).toBe(true);
303
+ });
304
+
305
+ it("should support combined filters", async () => {
306
+ const result = await client.hardware.devices.retrieve({
307
+ makes: ["ni"],
308
+ locations: ["Lab1", "Lab2"],
309
+ includeStatus: true,
310
+ });
311
+ expect(result.length).toBeGreaterThanOrEqual(1);
312
+ expect(
313
+ result.every((d) => d.make === "ni" && ["Lab1", "Lab2"].includes(d.location)),
314
+ ).toBe(true);
315
+
316
+ await expect
317
+ .poll(async () => {
318
+ const devices = await client.hardware.devices.retrieve({
319
+ makes: ["ni"],
320
+ locations: ["Lab1", "Lab2"],
321
+ includeStatus: true,
322
+ });
323
+ return devices.every((d) => d.status !== undefined);
324
+ })
325
+ .toBe(true);
215
326
  });
216
327
  });
217
328
  });
@@ -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, status, type UnknownRecord, unknownRecordZ, zod } from "@synnaxlabs/x";
11
- import { z } from "zod/v4";
10
+ import { binary, record, status, zod } from "@synnaxlabs/x";
11
+ import { z } from "zod";
12
12
 
13
13
  import { keyZ as rackKeyZ } from "@/hardware/rack/payload";
14
14
  import { decodeJSONString } from "@/util/decodeJSONString";
@@ -16,16 +16,9 @@ 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: status.variantZ.or(z.literal("").transform<status.Variant>(() => "info")),
22
- details: unknownRecordZ.or(z.string().transform(decodeJSONString)),
23
- });
19
+ export const statusZ = status.statusZ(z.object({ rack: rackKeyZ, device: keyZ }));
24
20
 
25
- export interface State<Details extends {} = UnknownRecord>
26
- extends Omit<z.infer<typeof stateZ>, "details"> {
27
- details: Details;
28
- }
21
+ export interface Status extends z.infer<typeof statusZ> {}
29
22
 
30
23
  export const deviceZ = z.object({
31
24
  key: keyZ,
@@ -35,27 +28,26 @@ export const deviceZ = z.object({
35
28
  model: z.string(),
36
29
  location: z.string(),
37
30
  configured: z.boolean().optional(),
38
- properties: unknownRecordZ.or(z.string().transform(decodeJSONString)),
39
- state: zod.nullToUndefined(stateZ),
31
+ properties: record.unknownZ.or(z.string().transform(decodeJSONString)),
32
+ status: zod.nullToUndefined(statusZ),
40
33
  });
41
34
 
42
35
  export interface Device<
43
- Properties extends UnknownRecord = UnknownRecord,
36
+ Properties extends record.Unknown = record.Unknown,
44
37
  Make extends string = string,
45
38
  Model extends string = string,
46
- StateDetails extends {} = UnknownRecord,
47
- > extends Omit<z.infer<typeof deviceZ>, "properties" | "state"> {
39
+ > extends Omit<z.infer<typeof deviceZ>, "properties" | "status"> {
48
40
  properties: Properties;
49
41
  make: Make;
50
42
  model: Model;
51
- state?: State<StateDetails>;
43
+ status?: Status;
52
44
  }
53
45
 
54
46
  export const newZ = deviceZ.extend({
55
47
  properties: z.unknown().transform((c) => binary.JSON_CODEC.encodeString(c)),
56
48
  });
57
49
  export interface New<
58
- Properties extends UnknownRecord = UnknownRecord,
50
+ Properties extends record.Unknown = record.Unknown,
59
51
  Make extends string = string,
60
52
  Model extends string = string,
61
53
  > extends Omit<z.input<typeof newZ>, "properties"> {
@@ -63,6 +55,3 @@ export interface New<
63
55
  make: Make;
64
56
  model: Model;
65
57
  }
66
-
67
- export const ONTOLOGY_TYPE = "device";
68
- export type OntologyType = typeof ONTOLOGY_TYPE;
@@ -8,73 +8,81 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
11
- import { type UnknownRecord } from "@synnaxlabs/x";
12
11
  import { array } from "@synnaxlabs/x/array";
13
- import { type AsyncTermSearcher } from "@synnaxlabs/x/search";
14
- import { z } from "zod/v4";
12
+ import { z } from "zod";
15
13
 
16
- import { framer } from "@/framer";
17
14
  import {
18
15
  type Key,
19
16
  keyZ,
20
17
  type New,
21
18
  newZ,
22
- ONTOLOGY_TYPE,
23
19
  type Payload,
24
20
  rackZ,
25
- type State,
26
- stateZ,
21
+ type Status,
27
22
  } from "@/hardware/rack/payload";
28
23
  import { type task } from "@/hardware/task";
29
- import { ontology } from "@/ontology";
30
- import { analyzeParams, checkForMultipleOrNoResults } from "@/util/retrieve";
24
+ import { type ontology } from "@/ontology";
25
+ import { checkForMultipleOrNoResults } from "@/util/retrieve";
31
26
  import { nullableArrayZ } from "@/util/zod";
32
27
 
33
28
  const RETRIEVE_ENDPOINT = "/hardware/rack/retrieve";
34
29
  const CREATE_ENDPOINT = "/hardware/rack/create";
35
30
  const DELETE_ENDPOINT = "/hardware/rack/delete";
36
31
 
37
- const STATE_CHANNEL_NAME = "sy_rack_state";
32
+ export const STATUS_CHANNEL_NAME = "sy_rack_status";
33
+ export const SET_CHANNEL_NAME = "sy_rack_set";
34
+ export const DELETE_CHANNEL_NAME = "sy_rack_delete";
38
35
 
39
36
  const retrieveReqZ = z.object({
40
37
  keys: keyZ.array().optional(),
41
38
  names: z.string().array().optional(),
42
- search: z.string().optional(),
39
+ searchTerm: z.string().optional(),
43
40
  embedded: z.boolean().optional(),
44
41
  hostIsNode: z.boolean().optional(),
45
42
  limit: z.number().optional(),
46
43
  offset: z.number().optional(),
47
- includeState: z.boolean().optional(),
44
+ includeStatus: z.boolean().optional(),
48
45
  });
49
-
50
46
  const retrieveResZ = z.object({ racks: nullableArrayZ(rackZ) });
51
47
 
52
- const createReqZ = z.object({ racks: newZ.array() });
48
+ const singleRetrieveArgsZ = z.union([
49
+ z
50
+ .object({
51
+ key: keyZ,
52
+ includeStatus: z.boolean().optional(),
53
+ })
54
+ .transform(({ key, includeStatus }) => ({ keys: [key], includeStatus })),
55
+ z
56
+ .object({
57
+ name: z.string(),
58
+ includeStatus: z.boolean().optional(),
59
+ })
60
+ .transform(({ name, includeStatus }) => ({ names: [name], includeStatus })),
61
+ ]);
62
+ export type SingleRetrieveArgs = z.input<typeof singleRetrieveArgsZ>;
63
+
64
+ const multiRetrieveArgsZ = retrieveReqZ;
65
+
66
+ export type MultiRetrieveArgs = z.input<typeof multiRetrieveArgsZ>;
67
+
68
+ const retrieveArgsZ = z.union([singleRetrieveArgsZ, multiRetrieveArgsZ]);
53
69
 
70
+ export type RetrieveArgs = z.input<typeof retrieveArgsZ>;
71
+
72
+ const createReqZ = z.object({ racks: newZ.array() });
54
73
  const createResZ = z.object({ racks: rackZ.array() });
55
74
 
56
75
  const deleteReqZ = z.object({ keys: keyZ.array() });
57
-
58
76
  const deleteResZ = z.object({});
59
77
 
60
- export interface RetrieveOptions {
61
- includeState?: boolean;
62
- }
63
-
64
- export class Client implements AsyncTermSearcher<string, Key, Payload> {
65
- readonly type = ONTOLOGY_TYPE;
78
+ export class Client {
79
+ readonly type = "rack";
66
80
  private readonly client: UnaryClient;
67
81
  private readonly tasks: task.Client;
68
- private readonly frameClient: framer.Client;
69
82
 
70
- constructor(
71
- client: UnaryClient,
72
- taskClient: task.Client,
73
- frameClient: framer.Client,
74
- ) {
83
+ constructor(client: UnaryClient, taskClient: task.Client) {
75
84
  this.client = client;
76
85
  this.tasks = taskClient;
77
- this.frameClient = frameClient;
78
86
  }
79
87
 
80
88
  async delete(keys: Key | Key[]): Promise<void> {
@@ -99,117 +107,91 @@ export class Client implements AsyncTermSearcher<string, Key, Payload> {
99
107
  createResZ,
100
108
  );
101
109
  const sugared = this.sugar(res.racks);
102
- if (isSingle) return sugared[0];
103
- return sugared;
104
- }
105
-
106
- async search(term: string): Promise<Rack[]> {
107
- const res = await sendRequired<typeof retrieveReqZ, typeof retrieveResZ>(
108
- this.client,
109
- RETRIEVE_ENDPOINT,
110
- { search: term },
111
- retrieveReqZ,
112
- retrieveResZ,
113
- );
114
- return this.sugar(res.racks);
115
- }
116
-
117
- async page(offset: number, limit: number): Promise<Rack[]> {
118
- const res = await sendRequired<typeof retrieveReqZ, typeof retrieveResZ>(
119
- this.client,
120
- RETRIEVE_ENDPOINT,
121
- { offset, limit },
122
- retrieveReqZ,
123
- retrieveResZ,
124
- );
125
- return this.sugar(res.racks);
110
+ return isSingle ? sugared[0] : sugared;
126
111
  }
127
112
 
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[]> {
134
- const { variant, normalized, single } = analyzeParams(racks, {
135
- string: "names",
136
- number: "keys",
137
- });
138
- const res = await sendRequired<typeof retrieveReqZ, typeof retrieveResZ>(
113
+ async retrieve(args: SingleRetrieveArgs): Promise<Rack>;
114
+ async retrieve(args: MultiRetrieveArgs): Promise<Rack[]>;
115
+ async retrieve(args: RetrieveArgs): Promise<Rack | Rack[]> {
116
+ const isSingle = "key" in args || "name" in args;
117
+ const res = await sendRequired(
139
118
  this.client,
140
119
  RETRIEVE_ENDPOINT,
141
- {
142
- [variant]: normalized,
143
- includeState: options?.includeState,
144
- },
145
- retrieveReqZ,
120
+ args,
121
+ retrieveArgsZ,
146
122
  retrieveResZ,
147
123
  );
148
124
  const sugared = this.sugar(res.racks);
149
- checkForMultipleOrNoResults("Rack", racks, sugared, single);
150
- return single ? sugared[0] : sugared;
125
+ checkForMultipleOrNoResults("Rack", args, sugared, isSingle);
126
+ return isSingle ? sugared[0] : sugared;
151
127
  }
152
128
 
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
-
165
- private sugar(payloads: Payload[]): Rack[] {
166
- return payloads.map(
167
- ({ key, name, state }) => new Rack(key, name, this.tasks, state),
168
- );
129
+ sugar(payload: Payload): Rack;
130
+ sugar(payloads: Payload[]): Rack[];
131
+ sugar(payloads: Payload | Payload[]): Rack | Rack[] {
132
+ const isSingle = !Array.isArray(payloads);
133
+ const sugared = array
134
+ .toArray(payloads)
135
+ .map(({ key, name, status }) => new Rack(key, name, this.tasks, status));
136
+ return isSingle ? sugared[0] : sugared;
169
137
  }
170
138
  }
171
139
 
172
140
  export class Rack {
173
141
  key: Key;
174
142
  name: string;
175
- state?: State;
143
+ status?: Status;
176
144
  private readonly tasks: task.Client;
177
145
 
178
- constructor(key: Key, name: string, taskClient: task.Client, state?: State) {
146
+ constructor(key: Key, name: string, taskClient: task.Client, status?: Status) {
179
147
  this.key = key;
180
148
  this.name = name;
181
149
  this.tasks = taskClient;
182
- this.state = state;
150
+ this.status = status;
183
151
  }
184
152
 
185
153
  async listTasks(): Promise<task.Task[]> {
186
- return await this.tasks.retrieve(this.key);
154
+ return await this.tasks.retrieve({ rack: this.key });
187
155
  }
188
156
 
189
- async retrieveTaskByName(name: string): Promise<task.Task> {
190
- return await this.tasks.retrieveByName(name, this.key);
191
- }
192
-
193
- async retrieveTaskByType(type: string): Promise<task.Task[]> {
194
- return await this.tasks.retrieveByType(type, this.key);
195
- }
157
+ async createTask(task: task.New): Promise<task.Task>;
158
+ async createTask<
159
+ Type extends z.ZodLiteral<string> = z.ZodLiteral<string>,
160
+ Config extends z.ZodType = z.ZodType,
161
+ StatusData extends z.ZodType = z.ZodType,
162
+ >(
163
+ task: task.New<Type, Config>,
164
+ schemas: task.Schemas<Type, Config, StatusData>,
165
+ ): Promise<task.Task<Type, Config, StatusData>>;
196
166
 
197
167
  async createTask<
198
- Config extends UnknownRecord,
199
- Details extends {} = UnknownRecord,
200
- Type extends string = string,
201
- >(task: task.New<Config, Type>): Promise<task.Task<Config, Details, Type>> {
168
+ Type extends z.ZodLiteral<string> = z.ZodLiteral<string>,
169
+ Config extends z.ZodType = z.ZodType,
170
+ StatusData extends z.ZodType = z.ZodType,
171
+ >(
172
+ task: task.New<Type, Config>,
173
+ schemas?: task.Schemas<Type, Config, StatusData>,
174
+ ): Promise<task.Task<Type, Config, StatusData>> {
202
175
  task.key = (
203
176
  (BigInt(this.key) << 32n) +
204
177
  (BigInt(task.key ?? 0) & 0xffffffffn)
205
178
  ).toString();
206
- return await this.tasks.create<Config, Details, Type>(task);
179
+ return await this.tasks.create(
180
+ task,
181
+ schemas as Required<task.Schemas<Type, Config, StatusData>>,
182
+ );
207
183
  }
208
184
 
209
- async deleteTask(task: bigint): Promise<void> {
185
+ async deleteTask(task: task.Key): Promise<void> {
210
186
  await this.tasks.delete([task]);
211
187
  }
188
+
189
+ get payload(): Payload {
190
+ return { key: this.key, name: this.name, status: this.status };
191
+ }
212
192
  }
213
193
 
214
- export const ontologyID = (key: Key): ontology.ID =>
215
- new ontology.ID({ type: ONTOLOGY_TYPE, key: key.toString() });
194
+ export const ontologyID = (key: Key): ontology.ID => ({
195
+ type: "rack",
196
+ key: key.toString(),
197
+ });
@@ -8,31 +8,22 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  import { status, zod } from "@synnaxlabs/x";
11
- import { TimeStamp } from "@synnaxlabs/x/telem";
12
- import { z } from "zod/v4";
11
+ import { z } from "zod";
13
12
 
14
13
  export const keyZ = z.uint32();
15
14
  export type Key = z.infer<typeof keyZ>;
16
15
 
17
- export const stateZ = z.object({
18
- key: keyZ,
19
- variant: status.variantZ.or(z.literal("").transform<status.Variant>(() => "info")),
20
- message: z.string(),
21
- lastReceived: TimeStamp.z,
22
- });
16
+ export const statusZ = status.statusZ(z.object({ rack: keyZ }));
23
17
 
24
- export interface State extends z.infer<typeof stateZ> {}
18
+ export interface Status extends z.infer<typeof statusZ> {}
25
19
 
26
20
  export const rackZ = z.object({
27
21
  key: keyZ,
28
22
  name: z.string(),
29
- state: zod.nullToUndefined(stateZ),
23
+ status: zod.nullToUndefined(statusZ),
30
24
  });
31
25
 
32
26
  export interface Payload extends z.infer<typeof rackZ> {}
33
27
 
34
28
  export const newZ = rackZ.partial({ key: true });
35
29
  export interface New extends z.input<typeof newZ> {}
36
-
37
- export const ONTOLOGY_TYPE = "rack";
38
- export type OntologyType = typeof ONTOLOGY_TYPE;