@synnaxlabs/client 0.21.0 → 0.22.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.
Files changed (258) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/.vscode/settings.json +3 -0
  3. package/dist/auth/auth.d.ts +7 -6
  4. package/dist/auth/auth.d.ts.map +1 -1
  5. package/dist/channel/client.d.ts +14 -11
  6. package/dist/channel/client.d.ts.map +1 -1
  7. package/dist/channel/creator.d.ts +10 -0
  8. package/dist/channel/creator.d.ts.map +1 -0
  9. package/dist/channel/payload.d.ts +6 -2
  10. package/dist/channel/payload.d.ts.map +1 -1
  11. package/dist/channel/retriever.d.ts +65 -35
  12. package/dist/channel/retriever.d.ts.map +1 -1
  13. package/dist/channel/writer.d.ts +4 -3
  14. package/dist/client.cjs +18 -21
  15. package/dist/client.cjs.map +1 -1
  16. package/dist/client.d.ts +12 -10
  17. package/dist/client.d.ts.map +1 -1
  18. package/dist/client.js +6546 -6457
  19. package/dist/client.js.map +1 -1
  20. package/dist/connection/checker.d.ts +4 -3
  21. package/dist/connection/checker.d.ts.map +1 -1
  22. package/dist/control/authority.d.ts +2 -1
  23. package/dist/control/state.d.ts +6 -4
  24. package/dist/control/state.d.ts.map +1 -1
  25. package/dist/errors.d.ts +24 -20
  26. package/dist/errors.d.ts.map +1 -1
  27. package/dist/framer/adapter.d.ts +5 -4
  28. package/dist/framer/adapter.d.ts.map +1 -1
  29. package/dist/framer/client.d.ts +10 -9
  30. package/dist/framer/client.d.ts.map +1 -1
  31. package/dist/framer/frame.d.ts +13 -12
  32. package/dist/framer/frame.d.ts.map +1 -1
  33. package/dist/framer/iterator.d.ts +5 -4
  34. package/dist/framer/iterator.d.ts.map +1 -1
  35. package/dist/framer/streamProxy.d.ts +3 -2
  36. package/dist/framer/streamProxy.d.ts.map +1 -1
  37. package/dist/framer/streamer.d.ts +13 -4
  38. package/dist/framer/streamer.d.ts.map +1 -1
  39. package/dist/framer/writer.d.ts +42 -33
  40. package/dist/framer/writer.d.ts.map +1 -1
  41. package/dist/hardware/client.d.ts +4 -3
  42. package/dist/hardware/device/client.d.ts +107 -14
  43. package/dist/hardware/device/client.d.ts.map +1 -1
  44. package/dist/hardware/device/external.d.ts +0 -2
  45. package/dist/hardware/device/external.d.ts.map +1 -1
  46. package/dist/hardware/device/index.d.ts +1 -1
  47. package/dist/hardware/device/index.d.ts.map +1 -1
  48. package/dist/hardware/rack/client.d.ts +43 -20
  49. package/dist/hardware/rack/client.d.ts.map +1 -1
  50. package/dist/hardware/rack/external.d.ts +0 -2
  51. package/dist/hardware/rack/external.d.ts.map +1 -1
  52. package/dist/hardware/task/client.d.ts +197 -14
  53. package/dist/hardware/task/client.d.ts.map +1 -1
  54. package/dist/hardware/task/index.d.ts +1 -1
  55. package/dist/hardware/task/index.d.ts.map +1 -1
  56. package/dist/index.d.ts +4 -3
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/label/client.d.ts +8 -6
  59. package/dist/label/client.d.ts.map +1 -1
  60. package/dist/label/payload.d.ts +2 -1
  61. package/dist/label/retriever.d.ts +3 -2
  62. package/dist/label/writer.d.ts +4 -3
  63. package/dist/ontology/client.d.ts +82 -14
  64. package/dist/ontology/client.d.ts.map +1 -1
  65. package/dist/ontology/external.d.ts +0 -1
  66. package/dist/ontology/external.d.ts.map +1 -1
  67. package/dist/ontology/group/client.d.ts +3 -2
  68. package/dist/ontology/group/client.d.ts.map +1 -1
  69. package/dist/ontology/group/group.d.ts +1 -0
  70. package/dist/ontology/group/payload.d.ts +2 -1
  71. package/dist/ontology/group/writer.d.ts +4 -3
  72. package/dist/ontology/payload.d.ts +71 -60
  73. package/dist/ontology/payload.d.ts.map +1 -1
  74. package/dist/ontology/writer.d.ts +3 -2
  75. package/dist/ranger/active.d.ts +3 -2
  76. package/dist/ranger/alias.d.ts +7 -6
  77. package/dist/ranger/alias.d.ts.map +1 -1
  78. package/dist/ranger/client.d.ts +162 -11
  79. package/dist/ranger/client.d.ts.map +1 -1
  80. package/dist/ranger/external.d.ts +0 -1
  81. package/dist/ranger/external.d.ts.map +1 -1
  82. package/dist/ranger/kv.d.ts +5 -3
  83. package/dist/ranger/kv.d.ts.map +1 -1
  84. package/dist/ranger/payload.d.ts +57 -50
  85. package/dist/ranger/payload.d.ts.map +1 -1
  86. package/dist/ranger/range.d.ts +12 -10
  87. package/dist/ranger/range.d.ts.map +1 -1
  88. package/dist/ranger/writer.d.ts +3 -2
  89. package/dist/setupspecs.d.ts +2 -1
  90. package/dist/signals/observable.d.ts +8 -15
  91. package/dist/signals/observable.d.ts.map +1 -1
  92. package/dist/transport.d.ts +3 -2
  93. package/dist/transport.d.ts.map +1 -1
  94. package/dist/user/payload.d.ts +2 -1
  95. package/dist/util/retrieve.d.ts +24 -0
  96. package/dist/util/retrieve.d.ts.map +1 -0
  97. package/dist/util/retrieve.spec.d.ts +2 -0
  98. package/dist/util/retrieve.spec.d.ts.map +1 -0
  99. package/dist/util/telem.d.ts +2 -1
  100. package/dist/util/telem.d.ts.map +1 -1
  101. package/dist/util/zod.d.ts +4 -0
  102. package/dist/util/zod.d.ts.map +1 -0
  103. package/dist/workspace/client.d.ts +9 -6
  104. package/dist/workspace/client.d.ts.map +1 -1
  105. package/dist/workspace/lineplot/client.d.ts +6 -5
  106. package/dist/workspace/lineplot/client.d.ts.map +1 -1
  107. package/dist/workspace/lineplot/payload.d.ts +3 -2
  108. package/dist/workspace/lineplot/payload.d.ts.map +1 -1
  109. package/dist/workspace/lineplot/retriever.d.ts +3 -2
  110. package/dist/workspace/lineplot/retriever.d.ts.map +1 -1
  111. package/dist/workspace/lineplot/writer.d.ts +7 -6
  112. package/dist/workspace/lineplot/writer.d.ts.map +1 -1
  113. package/dist/workspace/payload.d.ts +3 -2
  114. package/dist/workspace/payload.d.ts.map +1 -1
  115. package/dist/workspace/retriever.d.ts +3 -2
  116. package/dist/workspace/schematic/client.d.ts +18 -0
  117. package/dist/workspace/schematic/client.d.ts.map +1 -0
  118. package/dist/workspace/schematic/external.d.ts.map +1 -0
  119. package/dist/workspace/schematic/index.d.ts +2 -0
  120. package/dist/workspace/schematic/index.d.ts.map +1 -0
  121. package/dist/workspace/{pid → schematic}/payload.d.ts +6 -5
  122. package/dist/workspace/schematic/payload.d.ts.map +1 -0
  123. package/dist/workspace/schematic/retriever.d.ts +10 -0
  124. package/dist/workspace/schematic/retriever.d.ts.map +1 -0
  125. package/dist/workspace/schematic/schematic.spec.d.ts +2 -0
  126. package/dist/workspace/schematic/schematic.spec.d.ts.map +1 -0
  127. package/dist/workspace/{pid → schematic}/writer.d.ts +11 -10
  128. package/dist/workspace/schematic/writer.d.ts.map +1 -0
  129. package/dist/workspace/writer.d.ts +5 -4
  130. package/dist/workspace/writer.d.ts.map +1 -1
  131. package/package.json +9 -8
  132. package/src/auth/auth.spec.ts +55 -15
  133. package/src/auth/auth.ts +41 -42
  134. package/src/channel/batchRetriever.spec.ts +37 -40
  135. package/src/channel/channel.spec.ts +4 -4
  136. package/src/channel/client.ts +42 -49
  137. package/src/channel/creator.ts +37 -0
  138. package/src/channel/payload.ts +2 -1
  139. package/src/channel/retriever.ts +55 -71
  140. package/src/client.ts +23 -20
  141. package/src/connection/checker.ts +1 -1
  142. package/src/connection/connection.spec.ts +1 -6
  143. package/src/control/state.ts +3 -1
  144. package/src/errors.ts +71 -67
  145. package/src/framer/adapter.spec.ts +33 -1
  146. package/src/framer/adapter.ts +10 -6
  147. package/src/framer/client.ts +11 -9
  148. package/src/framer/frame.spec.ts +1 -1
  149. package/src/framer/frame.ts +9 -6
  150. package/src/framer/iterator.spec.ts +1 -1
  151. package/src/framer/iterator.ts +1 -1
  152. package/src/framer/streamProxy.ts +12 -13
  153. package/src/framer/streamer.spec.ts +1 -1
  154. package/src/framer/streamer.ts +25 -1
  155. package/src/framer/writer.spec.ts +23 -11
  156. package/src/framer/writer.ts +6 -6
  157. package/src/hardware/device/client.ts +155 -28
  158. package/src/hardware/device/device.spec.ts +2 -2
  159. package/src/hardware/device/external.ts +0 -2
  160. package/src/hardware/device/index.ts +2 -2
  161. package/src/hardware/rack/client.ts +139 -56
  162. package/src/hardware/rack/external.ts +0 -2
  163. package/src/hardware/rack/rack.spec.ts +20 -1
  164. package/src/hardware/task/client.ts +324 -31
  165. package/src/hardware/task/index.ts +1 -1
  166. package/src/hardware/task/task.spec.ts +41 -0
  167. package/src/index.ts +3 -4
  168. package/src/label/client.ts +3 -2
  169. package/src/label/retriever.ts +1 -1
  170. package/src/label/writer.ts +1 -1
  171. package/src/ontology/client.ts +195 -41
  172. package/src/ontology/external.ts +0 -1
  173. package/src/ontology/group/client.ts +1 -2
  174. package/src/ontology/group/payload.ts +1 -1
  175. package/src/ontology/ontology.spec.ts +16 -0
  176. package/src/ontology/payload.ts +22 -13
  177. package/src/ranger/active.ts +5 -5
  178. package/src/ranger/alias.ts +2 -2
  179. package/src/ranger/client.ts +68 -17
  180. package/src/ranger/external.ts +0 -1
  181. package/src/ranger/kv.ts +6 -1
  182. package/src/ranger/payload.ts +6 -4
  183. package/src/ranger/range.ts +4 -1
  184. package/src/ranger/ranger.spec.ts +24 -2
  185. package/src/signals/observable.ts +24 -63
  186. package/src/transport.ts +2 -1
  187. package/src/util/retrieve.spec.ts +56 -0
  188. package/src/util/retrieve.ts +103 -0
  189. package/src/util/telem.ts +1 -1
  190. package/src/util/zod.ts +4 -0
  191. package/src/workspace/client.ts +6 -4
  192. package/src/workspace/lineplot/client.ts +3 -3
  193. package/src/workspace/lineplot/linePlot.spec.ts +11 -11
  194. package/src/workspace/lineplot/payload.ts +1 -1
  195. package/src/workspace/lineplot/retriever.ts +5 -13
  196. package/src/workspace/lineplot/writer.ts +8 -7
  197. package/src/workspace/payload.ts +6 -3
  198. package/src/workspace/retriever.ts +1 -1
  199. package/src/workspace/{pid → schematic}/client.ts +10 -10
  200. package/src/workspace/{pid → schematic}/external.ts +2 -2
  201. package/src/workspace/{pid → schematic}/index.ts +1 -1
  202. package/src/workspace/{pid → schematic}/payload.ts +4 -4
  203. package/src/workspace/{pid → schematic}/retriever.ts +10 -10
  204. package/src/workspace/{pid/pid.spec.ts → schematic/schematic.spec.ts} +35 -35
  205. package/src/workspace/{pid → schematic}/writer.ts +26 -25
  206. package/src/workspace/workspace.spec.ts +7 -7
  207. package/src/workspace/writer.ts +8 -2
  208. package/dist/hardware/device/payload.d.ts +0 -30
  209. package/dist/hardware/device/payload.d.ts.map +0 -1
  210. package/dist/hardware/device/retriever.d.ts +0 -10
  211. package/dist/hardware/device/retriever.d.ts.map +0 -1
  212. package/dist/hardware/device/writer.d.ts +0 -9
  213. package/dist/hardware/device/writer.d.ts.map +0 -1
  214. package/dist/hardware/rack/payload.d.ts +0 -26
  215. package/dist/hardware/rack/payload.d.ts.map +0 -1
  216. package/dist/hardware/rack/retriever.d.ts +0 -10
  217. package/dist/hardware/rack/retriever.d.ts.map +0 -1
  218. package/dist/hardware/rack/writer.d.ts +0 -9
  219. package/dist/hardware/rack/writer.d.ts.map +0 -1
  220. package/dist/hardware/task/external.d.ts +0 -4
  221. package/dist/hardware/task/external.d.ts.map +0 -1
  222. package/dist/hardware/task/payload.d.ts +0 -42
  223. package/dist/hardware/task/payload.d.ts.map +0 -1
  224. package/dist/hardware/task/retriever.d.ts +0 -29
  225. package/dist/hardware/task/retriever.d.ts.map +0 -1
  226. package/dist/hardware/task/writer.d.ts +0 -9
  227. package/dist/hardware/task/writer.d.ts.map +0 -1
  228. package/dist/ontology/retriever.d.ts +0 -13
  229. package/dist/ontology/retriever.d.ts.map +0 -1
  230. package/dist/ontology/signals.d.ts +0 -30
  231. package/dist/ontology/signals.d.ts.map +0 -1
  232. package/dist/ranger/retriever.d.ts +0 -11
  233. package/dist/ranger/retriever.d.ts.map +0 -1
  234. package/dist/workspace/pid/client.d.ts +0 -17
  235. package/dist/workspace/pid/client.d.ts.map +0 -1
  236. package/dist/workspace/pid/external.d.ts.map +0 -1
  237. package/dist/workspace/pid/index.d.ts +0 -2
  238. package/dist/workspace/pid/index.d.ts.map +0 -1
  239. package/dist/workspace/pid/payload.d.ts.map +0 -1
  240. package/dist/workspace/pid/pid.spec.d.ts +0 -2
  241. package/dist/workspace/pid/pid.spec.d.ts.map +0 -1
  242. package/dist/workspace/pid/retriever.d.ts +0 -9
  243. package/dist/workspace/pid/retriever.d.ts.map +0 -1
  244. package/dist/workspace/pid/writer.d.ts.map +0 -1
  245. package/src/hardware/device/payload.ts +0 -27
  246. package/src/hardware/device/retriever.ts +0 -71
  247. package/src/hardware/device/writer.ts +0 -59
  248. package/src/hardware/rack/payload.ts +0 -26
  249. package/src/hardware/rack/retriever.ts +0 -68
  250. package/src/hardware/rack/writer.ts +0 -59
  251. package/src/hardware/task/external.ts +0 -12
  252. package/src/hardware/task/payload.ts +0 -40
  253. package/src/hardware/task/retriever.ts +0 -70
  254. package/src/hardware/task/writer.ts +0 -65
  255. package/src/ontology/retriever.ts +0 -91
  256. package/src/ontology/signals.ts +0 -136
  257. package/src/ranger/retriever.ts +0 -50
  258. /package/dist/workspace/{pid → schematic}/external.d.ts +0 -0
@@ -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 { DataType, Rate, TimeRange, TimeSpan, TimeStamp } from "@synnaxlabs/x";
10
+ import { DataType, Rate, TimeRange, TimeSpan, TimeStamp } from "@synnaxlabs/x/telem";
11
11
  import { describe, expect, test } from "vitest";
12
12
 
13
13
  import { type channel } from "@/channel";
@@ -68,7 +68,11 @@ describe("Writer", () => {
68
68
  });
69
69
  test("write with auto commit on", async () => {
70
70
  const ch = await newChannel();
71
- const writer = await client.openWriter({ start: 0, channels: ch.key, enableAutoCommit: true });
71
+ const writer = await client.openWriter({
72
+ start: 0,
73
+ channels: ch.key,
74
+ enableAutoCommit: true,
75
+ });
72
76
  try {
73
77
  await writer.write(ch.key, randomSeries(10, ch.dataType));
74
78
  } finally {
@@ -76,31 +80,39 @@ describe("Writer", () => {
76
80
  }
77
81
  expect(true).toBeTruthy();
78
82
 
79
- const f = await client.read(new TimeRange(0, TimeStamp.seconds(10)), ch.key)
80
- expect(f.length).toEqual(10)
81
- })
83
+ const f = await client.read(new TimeRange(0, TimeStamp.seconds(10)), ch.key);
84
+ expect(f.length).toEqual(10);
85
+ });
82
86
  test("write with auto commit and alwaysPersist", async () => {
83
87
  const ch = await newChannel();
84
- const writer = await client.openWriter({ start: 0, channels: ch.key,
85
- enableAutoCommit: true, autoIndexPersistInterval: ALWAYS_INDEX_PERSIST_ON_AUTO_COMMIT});
88
+ const writer = await client.openWriter({
89
+ start: 0,
90
+ channels: ch.key,
91
+ enableAutoCommit: true,
92
+ autoIndexPersistInterval: ALWAYS_INDEX_PERSIST_ON_AUTO_COMMIT,
93
+ });
86
94
  try {
87
95
  await writer.write(ch.key, randomSeries(10, ch.dataType));
88
96
  } finally {
89
97
  await writer.close();
90
98
  }
91
99
  expect(true).toBeTruthy();
92
- })
100
+ });
93
101
  test("write with auto commit and a set interval", async () => {
94
102
  const ch = await newChannel();
95
- const writer = await client.openWriter({ start: 0, channels: ch.key,
96
- enableAutoCommit: true, autoIndexPersistInterval: TimeSpan.milliseconds(100)});
103
+ const writer = await client.openWriter({
104
+ start: 0,
105
+ channels: ch.key,
106
+ enableAutoCommit: true,
107
+ autoIndexPersistInterval: TimeSpan.milliseconds(100),
108
+ });
97
109
  try {
98
110
  await writer.write(ch.key, randomSeries(10, ch.dataType));
99
111
  } finally {
100
112
  await writer.close();
101
113
  }
102
114
  expect(true).toBeTruthy();
103
- })
115
+ });
104
116
  });
105
117
  describe("Client", () => {
106
118
  test("Client - basic write", async () => {
@@ -13,10 +13,10 @@ import { decodeError, errorZ } from "@synnaxlabs/freighter";
13
13
  import {
14
14
  TimeStamp,
15
15
  type CrudeTimeStamp,
16
- toArray,
17
16
  type CrudeSeries,
18
17
  TimeSpan,
19
- } from "@synnaxlabs/x";
18
+ } from "@synnaxlabs/x/telem";
19
+ import { toArray } from "@synnaxlabs/x/toArray";
20
20
  import { z } from "zod";
21
21
 
22
22
  import {
@@ -50,7 +50,7 @@ export enum WriterMode {
50
50
  StreamOnly = 3,
51
51
  }
52
52
 
53
- export const ALWAYS_INDEX_PERSIST_ON_AUTO_COMMIT : TimeSpan = new TimeSpan(-1);
53
+ export const ALWAYS_INDEX_PERSIST_ON_AUTO_COMMIT: TimeSpan = new TimeSpan(-1);
54
54
 
55
55
  const netConfigZ = z.object({
56
56
  start: TimeStamp.z.optional(),
@@ -95,11 +95,11 @@ export interface WriterConfig {
95
95
  // enableAutoCommit determines whether the writer will automatically commit.
96
96
  // If enableAutoCommit is true, then the writer will commit after each write, and
97
97
  // will flush that commit to index after the specified autoIndexPersistInterval.
98
- enableAutoCommit?: boolean
98
+ enableAutoCommit?: boolean;
99
99
  // autoIndexPersistInterval sets the interval at which commits to the index will be
100
100
  // persisted. To persist every commit to guarantee minimal loss of data, set
101
101
  // auto_index_persist_interval to AlwaysAutoIndexPersist.
102
- autoIndexPersistInterval?: TimeSpan
102
+ autoIndexPersistInterval?: TimeSpan;
103
103
  }
104
104
 
105
105
  /**
@@ -163,7 +163,7 @@ export class Writer {
163
163
  controlSubject: subject,
164
164
  mode = WriterMode.PersistStream,
165
165
  enableAutoCommit = false,
166
- autoIndexPersistInterval = TimeSpan.SECOND
166
+ autoIndexPersistInterval = TimeSpan.SECOND,
167
167
  }: WriterConfig,
168
168
  ): Promise<Writer> {
169
169
  const adapter = await WriteFrameAdapter.open(retriever, channels);
@@ -7,63 +7,190 @@
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 { AsyncTermSearcher, toArray } from "@synnaxlabs/x";
10
+ import { type UnaryClient, sendRequired } from "@synnaxlabs/freighter";
11
+ import { type UnknownRecord, toArray } from "@synnaxlabs/x";
12
+ import { binary } from "@synnaxlabs/x/binary";
13
+ import { type AsyncTermSearcher } from "@synnaxlabs/x/search";
14
+ import { z } from "zod";
11
15
 
12
16
  import { type framer } from "@/framer";
13
- import { type Device, deviceZ, DeviceKey } from "@/hardware/device/payload";
14
- import { type Retriever } from "@/hardware/device/retriever";
15
- import { type Writer } from "@/hardware/device/writer";
17
+ import { rackKeyZ } from "@/hardware/rack/client";
16
18
  import { signals } from "@/signals";
19
+ import { checkForMultipleOrNoResults } from "@/util/retrieve";
20
+ import { nullableArrayZ } from "@/util/zod";
17
21
 
18
22
  const DEVICE_SET_NAME = "sy_device_set";
19
23
  const DEVICE_DELETE_NAME = "sy_device_delete";
20
24
 
21
- export class Client implements AsyncTermSearcher<string, DeviceKey, Device>{
22
- private readonly retriever: Retriever;
23
- private readonly writer: Writer;
25
+ const RETRIEVE_ENDPOINT = "/hardware/device/retrieve";
26
+ const CREATE_ENDPOINT = "/hardware/device/create";
27
+ const DELETE_ENDPOINT = "/hardware/device/delete";
28
+
29
+ export const deviceKeyZ = z.string();
30
+
31
+ export const deviceZ = z.object({
32
+ key: deviceKeyZ,
33
+ rack: rackKeyZ,
34
+ name: z.string(),
35
+ make: z.string(),
36
+ model: z.string(),
37
+ location: z.string(),
38
+ configured: z.boolean().optional(),
39
+ properties: z.record(z.unknown()).or(
40
+ z.string().transform((c) => {
41
+ if (c === "") return {};
42
+ return binary.JSON_ECD.decodeString(c);
43
+ }),
44
+ ) as z.ZodType<UnknownRecord>,
45
+ });
46
+
47
+ export type Device<P extends UnknownRecord = UnknownRecord> = Omit<
48
+ z.output<typeof deviceZ>,
49
+ "properties"
50
+ > & { properties: P };
51
+
52
+ export type DeviceKey = z.infer<typeof deviceKeyZ>;
53
+
54
+ export const newDeviceZ = deviceZ.extend({
55
+ properties: z.unknown().transform((c) => binary.JSON_ECD.encodeString(c)),
56
+ });
57
+
58
+ export type NewDevice<P extends UnknownRecord = UnknownRecord> = Omit<
59
+ z.input<typeof newDeviceZ>,
60
+ "properties"
61
+ > & { properties: P };
62
+
63
+ const createReqZ = z.object({ devices: newDeviceZ.array() });
64
+
65
+ const createResZ = z.object({ devices: deviceZ.array() });
66
+
67
+ const deleteReqZ = z.object({ keys: deviceKeyZ.array() });
68
+
69
+ const deleteResZ = z.object({});
70
+
71
+ const retrieveReqZ = z.object({
72
+ search: z.string().optional(),
73
+ limit: z.number().optional(),
74
+ offset: z.number().optional(),
75
+ keys: deviceKeyZ.array().optional(),
76
+ names: z.string().array().optional(),
77
+ makes: z.string().array().optional(),
78
+ });
79
+
80
+ type RetrieveRequest = z.input<typeof retrieveReqZ>;
81
+
82
+ export type RetrieveOptions = Pick<RetrieveRequest, "limit" | "offset" | "makes">;
83
+
84
+ type PageOptions = Pick<RetrieveOptions, "makes">;
85
+
86
+ const retrieveResZ = z.object({ devices: nullableArrayZ(deviceZ) });
87
+
88
+ export class Client implements AsyncTermSearcher<string, DeviceKey, Device> {
89
+ readonly type = "device";
90
+ private readonly client: UnaryClient;
24
91
  private readonly frameClient: framer.Client;
25
92
 
26
- constructor(retriever: Retriever, writer: Writer, frameClient: framer.Client) {
27
- this.retriever = retriever;
28
- this.writer = writer;
93
+ constructor(client: UnaryClient, frameClient: framer.Client) {
94
+ this.client = client;
29
95
  this.frameClient = frameClient;
30
96
  }
31
97
 
32
- async create(device: Device): Promise<Device> {
33
- const res = await this.writer.create([device]);
34
- return res[0];
35
- }
98
+ async retrieve<P extends UnknownRecord = UnknownRecord>(
99
+ key: string,
100
+ options?: RetrieveOptions,
101
+ ): Promise<Device<P>>;
36
102
 
37
- async retrieve(key: string): Promise<Device>;
38
- async retrieve(keys: string[]): Promise<Device[]>;
103
+ async retrieve<P extends UnknownRecord = UnknownRecord>(
104
+ keys: string[],
105
+ options?: RetrieveOptions,
106
+ ): Promise<Array<Device<P>>>;
39
107
 
40
- async retrieve(keys: string | string[]): Promise<Device | Device[]> {
41
- const res = await this.retriever.retrieve(toArray(keys));
42
- return Array.isArray(keys) ? res : res[0];
108
+ async retrieve<P extends UnknownRecord = UnknownRecord>(
109
+ keys: string | string[],
110
+ options?: RetrieveOptions,
111
+ ): Promise<Device<P> | Array<Device<P>>> {
112
+ const isSingle = !Array.isArray(keys);
113
+ const res = await sendRequired(
114
+ this.client,
115
+ RETRIEVE_ENDPOINT,
116
+ { keys: toArray(keys), ...options },
117
+ retrieveReqZ,
118
+ retrieveResZ,
119
+ );
120
+ checkForMultipleOrNoResults("Device", keys, res.devices, isSingle);
121
+ return (isSingle ? res.devices[0] : res.devices) as Device<P> | Array<Device<P>>;
43
122
  }
44
123
 
45
- async search(term: string): Promise<Device[]> {
46
- const res = await this.retriever.search(term);
47
- return res;
124
+ async search(term: string, options?: RetrieveOptions): Promise<Device[]> {
125
+ return (
126
+ await sendRequired(
127
+ this.client,
128
+ RETRIEVE_ENDPOINT,
129
+ { search: term, ...options },
130
+ retrieveReqZ,
131
+ retrieveResZ,
132
+ )
133
+ ).devices;
48
134
  }
49
135
 
50
- async page(offset: number, limit: number): Promise<Device[]> {
51
- const res = await this.retriever.page(offset, limit);
52
- return res;
136
+ async page(offset: number, limit: number, options?: PageOptions): Promise<Device[]> {
137
+ return (
138
+ await sendRequired(
139
+ this.client,
140
+ RETRIEVE_ENDPOINT,
141
+ { offset, limit, ...options },
142
+ retrieveReqZ,
143
+ retrieveResZ,
144
+ )
145
+ ).devices;
53
146
  }
54
147
 
55
- async delete(keys: string[]): Promise<void> {
56
- await this.writer.delete(keys);
148
+ async create(device: NewDevice): Promise<Device>;
149
+
150
+ async create(devices: NewDevice[]): Promise<Device[]>;
151
+
152
+ async create(devices: NewDevice | NewDevice[]): Promise<Device | Device[]> {
153
+ const isSingle = !Array.isArray(devices);
154
+ const res = await sendRequired(
155
+ this.client,
156
+ CREATE_ENDPOINT,
157
+ { devices: toArray(devices) },
158
+ createReqZ,
159
+ createResZ,
160
+ );
161
+ return isSingle ? res.devices[0] : res.devices;
162
+ }
163
+
164
+ async delete(keys: string | string[]): Promise<void> {
165
+ await sendRequired(
166
+ this.client,
167
+ DELETE_ENDPOINT,
168
+ { keys: toArray(keys) },
169
+ deleteReqZ,
170
+ deleteResZ,
171
+ );
57
172
  }
58
173
 
59
174
  async openDeviceTracker(): Promise<signals.Observable<string, Device>> {
60
- return await signals.Observable.open<string, Device>(
175
+ return await signals.openObservable<string, Device>(
61
176
  this.frameClient,
62
177
  DEVICE_SET_NAME,
63
178
  DEVICE_DELETE_NAME,
64
179
  decodeDeviceChanges,
65
180
  );
66
181
  }
182
+
183
+ newSearcherWithOptions(
184
+ options: RetrieveOptions,
185
+ ): AsyncTermSearcher<string, DeviceKey, Device> {
186
+ return {
187
+ type: this.type,
188
+ search: async (term: string) => await this.search(term, options),
189
+ retrieve: async (keys: string[]) => await this.retrieve(keys, options),
190
+ page: async (offset: number, limit: number) =>
191
+ await this.page(offset, limit, options),
192
+ };
193
+ }
67
194
  }
68
195
 
69
196
  const decodeDeviceChanges: signals.Decoder<string, Device> = (variant, data) => {
@@ -25,7 +25,7 @@ describe("Device", () => {
25
25
  name: "test",
26
26
  make: "ni",
27
27
  model: "dog",
28
- properties: "dog",
28
+ properties: { cat: "dog" },
29
29
  });
30
30
  expect(d.key).toEqual("SN222");
31
31
  expect(d.name).toBe("test");
@@ -42,7 +42,7 @@ describe("Device", () => {
42
42
  name: "test",
43
43
  make: "ni",
44
44
  model: "dog",
45
- properties: "dog",
45
+ properties: { cat: "dog" },
46
46
  });
47
47
  const retrieved = await client.hardware.devices.retrieve(d.key);
48
48
  expect(retrieved.key).toBe(d.key);
@@ -8,5 +8,3 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  export * from "@/hardware/device/client";
11
- export * from "@/hardware/device/retriever";
12
- export * from "@/hardware/device/writer";
@@ -1,4 +1,4 @@
1
- // Copyright 2024 Synnax Labs, Inc.
1
+ // Copyright 2023 Synnax Labs, Inc.
2
2
  //
3
3
  // Use of this software is governed by the Business Source License included in the file
4
4
  // licenses/BSL.txt.
@@ -7,4 +7,4 @@
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
- export * as device from "@/hardware/device/external";
10
+ export * as device from "@/hardware/device/client";
@@ -7,105 +7,188 @@
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 { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
11
+ import { type UnknownRecord } from "@synnaxlabs/x";
12
+ import { type AsyncTermSearcher } from "@synnaxlabs/x/search";
13
+ import { toArray } from "@synnaxlabs/x/toArray";
14
+ import { z } from "zod";
15
+
10
16
  import { type framer } from "@/framer";
11
- import { RackKey, type NewRack, type RackPayload } from "@/hardware/rack/payload";
12
- import { type Retriever } from "@/hardware/rack/retriever";
13
- import { type Writer } from "@/hardware/rack/writer";
14
- import { type NewTask, type Task } from "@/hardware/task/payload";
15
- import { type Retriever as TaskRetriever } from "@/hardware/task/retriever";
16
- import { type Writer as TaskWriter } from "@/hardware/task/writer";
17
- import { AsyncTermSearcher, toArray } from "@synnaxlabs/x";
17
+ import { type task } from "@/hardware/task";
18
+ import { analyzeParams, checkForMultipleOrNoResults } from "@/util/retrieve";
19
+ import { nullableArrayZ } from "@/util/zod";
20
+
21
+ export const rackKeyZ = z.number();
22
+
23
+ export type RackKey = z.infer<typeof rackKeyZ>;
24
+
25
+ export const rackZ = z.object({
26
+ key: rackKeyZ,
27
+ name: z.string(),
28
+ });
29
+
30
+ export type RackPayload = z.infer<typeof rackZ>;
31
+
32
+ export const newRackZ = rackZ.partial({ key: true });
33
+
34
+ export type NewRack = z.input<typeof newRackZ>;
35
+
36
+ const RETRIEVE_ENDPOINT = "/hardware/rack/retrieve";
37
+ const CREATE_RACK_ENDPOINT = "/hardware/rack/create";
38
+ const DELETE_RACK_ENDPOINT = "/hardware/rack/delete";
39
+
40
+ const retrieveRackReqZ = z.object({
41
+ keys: rackKeyZ.array().optional(),
42
+ names: z.string().array().optional(),
43
+ search: z.string().optional(),
44
+ offset: z.number().optional(),
45
+ limit: z.number().optional(),
46
+ });
47
+
48
+ const retrieveRackResZ = z.object({
49
+ racks: nullableArrayZ(rackZ),
50
+ });
51
+
52
+ const createReqZ = z.object({
53
+ racks: newRackZ.array(),
54
+ });
55
+
56
+ const createResZ = z.object({
57
+ racks: rackZ.array(),
58
+ });
59
+
60
+ const deleteReqZ = z.object({
61
+ keys: rackKeyZ.array(),
62
+ });
63
+
64
+ const deleteResZ = z.object({});
18
65
 
19
66
  export class Client implements AsyncTermSearcher<string, RackKey, Rack> {
20
- private readonly retriever: Retriever;
21
- private readonly writer: Writer;
67
+ readonly type: string = "rack";
68
+ private readonly client: UnaryClient;
22
69
  private readonly frameClient: framer.Client;
23
- private readonly taskWriter: TaskWriter;
24
- private readonly taskRetriever: TaskRetriever;
70
+ private readonly tasks: task.Client;
25
71
 
26
72
  constructor(
27
- retriever: Retriever,
28
- writer: Writer,
73
+ client: UnaryClient,
29
74
  frameClient: framer.Client,
30
- taskWriter: TaskWriter,
31
- taskRetriever: TaskRetriever,
75
+ taskClient: task.Client,
32
76
  ) {
33
- this.retriever = retriever;
34
- this.writer = writer;
77
+ this.client = client;
35
78
  this.frameClient = frameClient;
36
- this.taskWriter = taskWriter;
37
- this.taskRetriever = taskRetriever;
79
+ this.tasks = taskClient;
38
80
  }
39
81
 
40
- async create(rack: NewRack): Promise<Rack> {
41
- const res = await this.writer.create([rack]);
42
- return this.sugar(res)[0];
82
+ async delete(keys: RackKey | RackKey[]): Promise<void> {
83
+ await sendRequired<typeof deleteReqZ, typeof deleteResZ>(
84
+ this.client,
85
+ DELETE_RACK_ENDPOINT,
86
+ { keys: toArray(keys) },
87
+ deleteReqZ,
88
+ deleteResZ,
89
+ );
90
+ }
91
+
92
+ async create(rack: NewRack): Promise<Rack>;
93
+
94
+ async create(racks: NewRack[]): Promise<Rack[]>;
95
+
96
+ async create(rack: NewRack | NewRack[]): Promise<Rack | Rack[]> {
97
+ const isSingle = !Array.isArray(rack);
98
+ const res = await sendRequired<typeof createReqZ, typeof createResZ>(
99
+ this.client,
100
+ CREATE_RACK_ENDPOINT,
101
+ { racks: toArray(rack) },
102
+ createReqZ,
103
+ createResZ,
104
+ );
105
+ const sugared = this.sugar(res.racks);
106
+ if (isSingle) return sugared[0];
107
+ return sugared;
43
108
  }
44
109
 
45
110
  async search(term: string): Promise<Rack[]> {
46
- const res = await this.retriever.search(term);
47
- return this.sugar(res);
111
+ const res = await sendRequired<typeof retrieveRackReqZ, typeof retrieveRackResZ>(
112
+ this.client,
113
+ RETRIEVE_ENDPOINT,
114
+ { search: term },
115
+ retrieveRackReqZ,
116
+ retrieveRackResZ,
117
+ );
118
+ return this.sugar(res.racks);
48
119
  }
49
120
 
50
121
  async page(offset: number, limit: number): Promise<Rack[]> {
51
- const res = await this.retriever.page(offset, limit);
52
- return this.sugar(res);
122
+ const res = await sendRequired<typeof retrieveRackReqZ, typeof retrieveRackResZ>(
123
+ this.client,
124
+ RETRIEVE_ENDPOINT,
125
+ { offset, limit },
126
+ retrieveRackReqZ,
127
+ retrieveRackResZ,
128
+ );
129
+ return this.sugar(res.racks);
53
130
  }
54
131
 
55
- async retrieve(key: RackKey): Promise<Rack>;
56
-
57
- async retrieve(keys: RackKey[]): Promise<Rack[]>;
58
-
59
- async retrieve(key: RackKey | RackKey[]): Promise<Rack | Rack[]> {
60
- const res = await this.retriever.retrieve(toArray(key));
61
- if (Array.isArray(key)) return this.sugar(res);
62
- return this.sugar(res)[0];
132
+ async retrieve(key: string | RackKey): Promise<Rack>;
133
+
134
+ async retrieve(keys: number[] | RackKey[]): Promise<Rack[]>;
135
+
136
+ async retrieve(
137
+ params: string | RackKey | string[] | RackKey[],
138
+ ): Promise<Rack | Rack[]> {
139
+ const { variant, normalized, single } = analyzeParams(params, {
140
+ string: "names",
141
+ number: "keys",
142
+ });
143
+ const res = await sendRequired<typeof retrieveRackReqZ, typeof retrieveRackResZ>(
144
+ this.client,
145
+ RETRIEVE_ENDPOINT,
146
+ { [variant]: normalized },
147
+ retrieveRackReqZ,
148
+ retrieveRackResZ,
149
+ );
150
+ const sugared = this.sugar(res.racks);
151
+ checkForMultipleOrNoResults("Rack", params, sugared, single);
152
+ return single ? sugared[0] : sugared;
63
153
  }
64
154
 
65
155
  private sugar(payloads: RackPayload[]): Rack[] {
66
- return payloads.map(
67
- (payload) =>
68
- new Rack(payload.key, payload.name, this.taskWriter, this.taskRetriever),
69
- );
156
+ return payloads.map(({ key, name }) => new Rack(key, name, this.tasks));
70
157
  }
71
158
  }
72
159
 
73
160
  export class Rack {
74
161
  key: number;
75
162
  name: string;
76
- private readonly writer: TaskWriter;
77
- private readonly tasks: TaskRetriever;
163
+ private readonly tasks: task.Client;
78
164
 
79
- constructor(
80
- key: number,
81
- name: string,
82
- _writer: TaskWriter,
83
- _retriever: TaskRetriever,
84
- ) {
165
+ constructor(key: number, name: string, client: task.Client) {
85
166
  this.key = key;
86
167
  this.name = name;
87
- this.writer = _writer;
88
- this.tasks = _retriever;
168
+ this.tasks = client;
89
169
  }
90
170
 
91
- async listTasks(): Promise<Task[]> {
92
- return await this.tasks.retrieve({ rack: this.key });
171
+ async listTasks(): Promise<task.Task[]> {
172
+ return await this.tasks.retrieve(this.key);
93
173
  }
94
174
 
95
- async retrieveTasks(): Promise<Task[]> {
96
- return [];
175
+ async retrieveTaskByName(name: string): Promise<task.Task> {
176
+ return await this.tasks.retrieveByName(name, this.key);
97
177
  }
98
178
 
99
- async createTask(task: NewTask): Promise<Task> {
179
+ async createTask<
180
+ C extends UnknownRecord,
181
+ D extends {} = UnknownRecord,
182
+ T extends string = string,
183
+ >(task: task.NewTask<C, T>): Promise<task.Task<C, D, T>> {
100
184
  task.key = (
101
185
  (BigInt(this.key) << 32n) +
102
186
  (BigInt(task.key ?? 0) & 0xffffffffn)
103
187
  ).toString();
104
- const res = await this.writer.create([task]);
105
- return res[0];
188
+ return await this.tasks.create<C, D, T>(task);
106
189
  }
107
190
 
108
191
  async deleteTask(task: bigint): Promise<void> {
109
- await this.writer.delete([task]);
192
+ await this.tasks.delete([task]);
110
193
  }
111
194
  }
@@ -8,5 +8,3 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  export * from "@/hardware/rack/client";
11
- export * from "@/hardware/rack/writer";
12
- export * from "@/hardware/rack/retriever";
@@ -8,7 +8,9 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  import { describe, expect, it } from "vitest";
11
+ import { ZodError } from "zod";
11
12
 
13
+ import { NotFoundError } from "@/errors";
12
14
  import { newClient } from "@/setupspecs";
13
15
 
14
16
  const client = newClient();
@@ -19,6 +21,10 @@ describe("Rack", () => {
19
21
  const r = await client.hardware.racks.create({ name: "test" });
20
22
  expect(r.key).toBeGreaterThan(0n);
21
23
  });
24
+ it("should return an error if the rack doesn't have a name", async () => {
25
+ // @ts-expect-error
26
+ await expect(client.hardware.racks.create({})).rejects.toThrow(ZodError);
27
+ });
22
28
  });
23
29
  describe("retrieve", () => {
24
30
  it("should retrieve a rack by its key", async () => {
@@ -27,6 +33,13 @@ describe("Rack", () => {
27
33
  expect(retrieved.key).toBe(r.key);
28
34
  expect(retrieved.name).toBe("test");
29
35
  });
36
+ it("should retrieve a rack by its name", async () => {
37
+ const name = `TimeStamp.now().toString()}-${Math.random()}`;
38
+ const r = await client.hardware.racks.create({ name });
39
+ const retrieved = await client.hardware.racks.retrieve(name);
40
+ expect(retrieved.key).toBe(r.key);
41
+ expect(retrieved.name).toEqual(name);
42
+ });
30
43
  });
31
44
  describe("tasks", () => {
32
45
  it("should list the tasks on a rack", async () => {
@@ -34,5 +47,11 @@ describe("Rack", () => {
34
47
  const tasks = await r.listTasks();
35
48
  expect(tasks).toHaveLength(0);
36
49
  });
37
- })
50
+ it("should throw an error if a task cannot be found by name", async () => {
51
+ const r = await client.hardware.racks.create({ name: "test" });
52
+ await expect(
53
+ async () => await r.retrieveTaskByName("nonexistent"),
54
+ ).rejects.toThrow(NotFoundError);
55
+ });
56
+ });
38
57
  });