@synnaxlabs/client 0.20.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 (262) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/.vscode/settings.json +3 -0
  3. package/README.md +13 -0
  4. package/dist/auth/auth.d.ts +7 -6
  5. package/dist/auth/auth.d.ts.map +1 -1
  6. package/dist/channel/client.d.ts +14 -11
  7. package/dist/channel/client.d.ts.map +1 -1
  8. package/dist/channel/creator.d.ts +10 -0
  9. package/dist/channel/creator.d.ts.map +1 -0
  10. package/dist/channel/payload.d.ts +6 -2
  11. package/dist/channel/payload.d.ts.map +1 -1
  12. package/dist/channel/retriever.d.ts +65 -35
  13. package/dist/channel/retriever.d.ts.map +1 -1
  14. package/dist/channel/writer.d.ts +4 -3
  15. package/dist/client.cjs +19 -22
  16. package/dist/client.cjs.map +1 -1
  17. package/dist/client.d.ts +12 -10
  18. package/dist/client.d.ts.map +1 -1
  19. package/dist/client.js +6781 -6675
  20. package/dist/client.js.map +1 -1
  21. package/dist/connection/checker.d.ts +4 -3
  22. package/dist/connection/checker.d.ts.map +1 -1
  23. package/dist/control/authority.d.ts +2 -1
  24. package/dist/control/state.d.ts +6 -4
  25. package/dist/control/state.d.ts.map +1 -1
  26. package/dist/errors.d.ts +24 -20
  27. package/dist/errors.d.ts.map +1 -1
  28. package/dist/framer/adapter.d.ts +5 -4
  29. package/dist/framer/adapter.d.ts.map +1 -1
  30. package/dist/framer/client.d.ts +12 -12
  31. package/dist/framer/client.d.ts.map +1 -1
  32. package/dist/framer/frame.d.ts +13 -12
  33. package/dist/framer/frame.d.ts.map +1 -1
  34. package/dist/framer/iterator.d.ts +5 -4
  35. package/dist/framer/iterator.d.ts.map +1 -1
  36. package/dist/framer/streamProxy.d.ts +3 -2
  37. package/dist/framer/streamProxy.d.ts.map +1 -1
  38. package/dist/framer/streamer.d.ts +13 -4
  39. package/dist/framer/streamer.d.ts.map +1 -1
  40. package/dist/framer/writer.d.ts +67 -33
  41. package/dist/framer/writer.d.ts.map +1 -1
  42. package/dist/hardware/client.d.ts +4 -3
  43. package/dist/hardware/device/client.d.ts +107 -14
  44. package/dist/hardware/device/client.d.ts.map +1 -1
  45. package/dist/hardware/device/external.d.ts +0 -2
  46. package/dist/hardware/device/external.d.ts.map +1 -1
  47. package/dist/hardware/device/index.d.ts +1 -1
  48. package/dist/hardware/device/index.d.ts.map +1 -1
  49. package/dist/hardware/rack/client.d.ts +43 -20
  50. package/dist/hardware/rack/client.d.ts.map +1 -1
  51. package/dist/hardware/rack/external.d.ts +0 -2
  52. package/dist/hardware/rack/external.d.ts.map +1 -1
  53. package/dist/hardware/task/client.d.ts +197 -14
  54. package/dist/hardware/task/client.d.ts.map +1 -1
  55. package/dist/hardware/task/index.d.ts +1 -1
  56. package/dist/hardware/task/index.d.ts.map +1 -1
  57. package/dist/index.d.ts +4 -3
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/label/client.d.ts +8 -6
  60. package/dist/label/client.d.ts.map +1 -1
  61. package/dist/label/payload.d.ts +2 -1
  62. package/dist/label/retriever.d.ts +3 -2
  63. package/dist/label/writer.d.ts +4 -3
  64. package/dist/ontology/client.d.ts +82 -14
  65. package/dist/ontology/client.d.ts.map +1 -1
  66. package/dist/ontology/external.d.ts +0 -1
  67. package/dist/ontology/external.d.ts.map +1 -1
  68. package/dist/ontology/group/client.d.ts +3 -2
  69. package/dist/ontology/group/client.d.ts.map +1 -1
  70. package/dist/ontology/group/group.d.ts +1 -0
  71. package/dist/ontology/group/payload.d.ts +2 -1
  72. package/dist/ontology/group/writer.d.ts +4 -3
  73. package/dist/ontology/payload.d.ts +71 -60
  74. package/dist/ontology/payload.d.ts.map +1 -1
  75. package/dist/ontology/writer.d.ts +3 -2
  76. package/dist/ranger/active.d.ts +3 -2
  77. package/dist/ranger/alias.d.ts +7 -6
  78. package/dist/ranger/alias.d.ts.map +1 -1
  79. package/dist/ranger/client.d.ts +162 -11
  80. package/dist/ranger/client.d.ts.map +1 -1
  81. package/dist/ranger/external.d.ts +0 -1
  82. package/dist/ranger/external.d.ts.map +1 -1
  83. package/dist/ranger/kv.d.ts +5 -3
  84. package/dist/ranger/kv.d.ts.map +1 -1
  85. package/dist/ranger/payload.d.ts +57 -50
  86. package/dist/ranger/payload.d.ts.map +1 -1
  87. package/dist/ranger/range.d.ts +12 -10
  88. package/dist/ranger/range.d.ts.map +1 -1
  89. package/dist/ranger/writer.d.ts +3 -2
  90. package/dist/setupspecs.d.ts +2 -1
  91. package/dist/signals/observable.d.ts +8 -15
  92. package/dist/signals/observable.d.ts.map +1 -1
  93. package/dist/transport.d.ts +3 -2
  94. package/dist/transport.d.ts.map +1 -1
  95. package/dist/user/payload.d.ts +2 -1
  96. package/dist/util/retrieve.d.ts +24 -0
  97. package/dist/util/retrieve.d.ts.map +1 -0
  98. package/dist/util/retrieve.spec.d.ts +2 -0
  99. package/dist/util/retrieve.spec.d.ts.map +1 -0
  100. package/dist/util/telem.d.ts +2 -1
  101. package/dist/util/telem.d.ts.map +1 -1
  102. package/dist/util/zod.d.ts +4 -0
  103. package/dist/util/zod.d.ts.map +1 -0
  104. package/dist/workspace/client.d.ts +9 -6
  105. package/dist/workspace/client.d.ts.map +1 -1
  106. package/dist/workspace/lineplot/client.d.ts +6 -5
  107. package/dist/workspace/lineplot/client.d.ts.map +1 -1
  108. package/dist/workspace/lineplot/payload.d.ts +3 -2
  109. package/dist/workspace/lineplot/payload.d.ts.map +1 -1
  110. package/dist/workspace/lineplot/retriever.d.ts +3 -2
  111. package/dist/workspace/lineplot/retriever.d.ts.map +1 -1
  112. package/dist/workspace/lineplot/writer.d.ts +7 -6
  113. package/dist/workspace/lineplot/writer.d.ts.map +1 -1
  114. package/dist/workspace/payload.d.ts +3 -2
  115. package/dist/workspace/payload.d.ts.map +1 -1
  116. package/dist/workspace/retriever.d.ts +3 -2
  117. package/dist/workspace/schematic/client.d.ts +18 -0
  118. package/dist/workspace/schematic/client.d.ts.map +1 -0
  119. package/dist/workspace/schematic/external.d.ts.map +1 -0
  120. package/dist/workspace/schematic/index.d.ts +2 -0
  121. package/dist/workspace/schematic/index.d.ts.map +1 -0
  122. package/dist/workspace/{pid → schematic}/payload.d.ts +6 -5
  123. package/dist/workspace/schematic/payload.d.ts.map +1 -0
  124. package/dist/workspace/schematic/retriever.d.ts +10 -0
  125. package/dist/workspace/schematic/retriever.d.ts.map +1 -0
  126. package/dist/workspace/schematic/schematic.spec.d.ts +2 -0
  127. package/dist/workspace/schematic/schematic.spec.d.ts.map +1 -0
  128. package/dist/workspace/{pid → schematic}/writer.d.ts +11 -10
  129. package/dist/workspace/schematic/writer.d.ts.map +1 -0
  130. package/dist/workspace/writer.d.ts +5 -4
  131. package/dist/workspace/writer.d.ts.map +1 -1
  132. package/examples/node/package-lock.json +29 -12
  133. package/examples/node/package.json +2 -2
  134. package/examples/node/streamWrite.js +8 -11
  135. package/package.json +10 -9
  136. package/src/auth/auth.spec.ts +55 -15
  137. package/src/auth/auth.ts +41 -42
  138. package/src/channel/batchRetriever.spec.ts +37 -40
  139. package/src/channel/channel.spec.ts +4 -4
  140. package/src/channel/client.ts +42 -49
  141. package/src/channel/creator.ts +37 -0
  142. package/src/channel/payload.ts +2 -1
  143. package/src/channel/retriever.ts +55 -71
  144. package/src/client.ts +23 -20
  145. package/src/connection/checker.ts +1 -1
  146. package/src/connection/connection.spec.ts +1 -6
  147. package/src/control/state.ts +3 -1
  148. package/src/errors.ts +71 -67
  149. package/src/framer/adapter.spec.ts +33 -1
  150. package/src/framer/adapter.ts +10 -6
  151. package/src/framer/client.ts +13 -12
  152. package/src/framer/frame.spec.ts +1 -1
  153. package/src/framer/frame.ts +9 -6
  154. package/src/framer/iterator.spec.ts +1 -1
  155. package/src/framer/iterator.ts +1 -1
  156. package/src/framer/streamProxy.ts +12 -13
  157. package/src/framer/streamer.spec.ts +1 -1
  158. package/src/framer/streamer.ts +25 -1
  159. package/src/framer/writer.spec.ts +49 -2
  160. package/src/framer/writer.ts +27 -2
  161. package/src/hardware/device/client.ts +155 -28
  162. package/src/hardware/device/device.spec.ts +2 -2
  163. package/src/hardware/device/external.ts +0 -2
  164. package/src/hardware/device/index.ts +2 -2
  165. package/src/hardware/rack/client.ts +139 -56
  166. package/src/hardware/rack/external.ts +0 -2
  167. package/src/hardware/rack/rack.spec.ts +20 -1
  168. package/src/hardware/task/client.ts +324 -31
  169. package/src/hardware/task/index.ts +1 -1
  170. package/src/hardware/task/task.spec.ts +41 -0
  171. package/src/index.ts +3 -4
  172. package/src/label/client.ts +3 -2
  173. package/src/label/retriever.ts +1 -1
  174. package/src/label/writer.ts +1 -1
  175. package/src/ontology/client.ts +195 -41
  176. package/src/ontology/external.ts +0 -1
  177. package/src/ontology/group/client.ts +1 -2
  178. package/src/ontology/group/payload.ts +1 -1
  179. package/src/ontology/ontology.spec.ts +16 -0
  180. package/src/ontology/payload.ts +22 -13
  181. package/src/ranger/active.ts +5 -5
  182. package/src/ranger/alias.ts +2 -2
  183. package/src/ranger/client.ts +68 -17
  184. package/src/ranger/external.ts +0 -1
  185. package/src/ranger/kv.ts +6 -1
  186. package/src/ranger/payload.ts +6 -4
  187. package/src/ranger/range.ts +4 -1
  188. package/src/ranger/ranger.spec.ts +24 -2
  189. package/src/signals/observable.ts +24 -63
  190. package/src/transport.ts +2 -1
  191. package/src/util/retrieve.spec.ts +56 -0
  192. package/src/util/retrieve.ts +103 -0
  193. package/src/util/telem.ts +1 -1
  194. package/src/util/zod.ts +4 -0
  195. package/src/workspace/client.ts +6 -4
  196. package/src/workspace/lineplot/client.ts +3 -3
  197. package/src/workspace/lineplot/linePlot.spec.ts +11 -11
  198. package/src/workspace/lineplot/payload.ts +1 -1
  199. package/src/workspace/lineplot/retriever.ts +5 -13
  200. package/src/workspace/lineplot/writer.ts +8 -7
  201. package/src/workspace/payload.ts +6 -3
  202. package/src/workspace/retriever.ts +1 -1
  203. package/src/workspace/{pid → schematic}/client.ts +10 -10
  204. package/src/workspace/{pid → schematic}/external.ts +2 -2
  205. package/src/workspace/{pid → schematic}/index.ts +1 -1
  206. package/src/workspace/{pid → schematic}/payload.ts +4 -4
  207. package/src/workspace/{pid → schematic}/retriever.ts +10 -10
  208. package/src/workspace/{pid/pid.spec.ts → schematic/schematic.spec.ts} +35 -35
  209. package/src/workspace/{pid → schematic}/writer.ts +26 -25
  210. package/src/workspace/workspace.spec.ts +7 -7
  211. package/src/workspace/writer.ts +8 -2
  212. package/dist/hardware/device/payload.d.ts +0 -30
  213. package/dist/hardware/device/payload.d.ts.map +0 -1
  214. package/dist/hardware/device/retriever.d.ts +0 -10
  215. package/dist/hardware/device/retriever.d.ts.map +0 -1
  216. package/dist/hardware/device/writer.d.ts +0 -9
  217. package/dist/hardware/device/writer.d.ts.map +0 -1
  218. package/dist/hardware/rack/payload.d.ts +0 -26
  219. package/dist/hardware/rack/payload.d.ts.map +0 -1
  220. package/dist/hardware/rack/retriever.d.ts +0 -10
  221. package/dist/hardware/rack/retriever.d.ts.map +0 -1
  222. package/dist/hardware/rack/writer.d.ts +0 -9
  223. package/dist/hardware/rack/writer.d.ts.map +0 -1
  224. package/dist/hardware/task/external.d.ts +0 -4
  225. package/dist/hardware/task/external.d.ts.map +0 -1
  226. package/dist/hardware/task/payload.d.ts +0 -42
  227. package/dist/hardware/task/payload.d.ts.map +0 -1
  228. package/dist/hardware/task/retriever.d.ts +0 -29
  229. package/dist/hardware/task/retriever.d.ts.map +0 -1
  230. package/dist/hardware/task/writer.d.ts +0 -9
  231. package/dist/hardware/task/writer.d.ts.map +0 -1
  232. package/dist/ontology/retriever.d.ts +0 -13
  233. package/dist/ontology/retriever.d.ts.map +0 -1
  234. package/dist/ontology/signals.d.ts +0 -30
  235. package/dist/ontology/signals.d.ts.map +0 -1
  236. package/dist/ranger/retriever.d.ts +0 -11
  237. package/dist/ranger/retriever.d.ts.map +0 -1
  238. package/dist/workspace/pid/client.d.ts +0 -17
  239. package/dist/workspace/pid/client.d.ts.map +0 -1
  240. package/dist/workspace/pid/external.d.ts.map +0 -1
  241. package/dist/workspace/pid/index.d.ts +0 -2
  242. package/dist/workspace/pid/index.d.ts.map +0 -1
  243. package/dist/workspace/pid/payload.d.ts.map +0 -1
  244. package/dist/workspace/pid/pid.spec.d.ts +0 -2
  245. package/dist/workspace/pid/pid.spec.d.ts.map +0 -1
  246. package/dist/workspace/pid/retriever.d.ts +0 -9
  247. package/dist/workspace/pid/retriever.d.ts.map +0 -1
  248. package/dist/workspace/pid/writer.d.ts.map +0 -1
  249. package/src/hardware/device/payload.ts +0 -27
  250. package/src/hardware/device/retriever.ts +0 -60
  251. package/src/hardware/device/writer.ts +0 -59
  252. package/src/hardware/rack/payload.ts +0 -26
  253. package/src/hardware/rack/retriever.ts +0 -68
  254. package/src/hardware/rack/writer.ts +0 -59
  255. package/src/hardware/task/external.ts +0 -12
  256. package/src/hardware/task/payload.ts +0 -40
  257. package/src/hardware/task/retriever.ts +0 -70
  258. package/src/hardware/task/writer.ts +0 -65
  259. package/src/ontology/retriever.ts +0 -91
  260. package/src/ontology/signals.ts +0 -139
  261. package/src/ranger/retriever.ts +0 -50
  262. /package/dist/workspace/{pid → schematic}/external.d.ts +0 -0
@@ -7,11 +7,11 @@
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, 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";
14
- import { WriterMode } from "@/framer/writer";
14
+ import { ALWAYS_INDEX_PERSIST_ON_AUTO_COMMIT, WriterMode } from "@/framer/writer";
15
15
  import { newClient } from "@/setupspecs";
16
16
  import { randomSeries } from "@/util/telem";
17
17
 
@@ -66,6 +66,53 @@ describe("Writer", () => {
66
66
  ]);
67
67
  expect(v).toEqual(123);
68
68
  });
69
+ test("write with auto commit on", async () => {
70
+ const ch = await newChannel();
71
+ const writer = await client.openWriter({
72
+ start: 0,
73
+ channels: ch.key,
74
+ enableAutoCommit: true,
75
+ });
76
+ try {
77
+ await writer.write(ch.key, randomSeries(10, ch.dataType));
78
+ } finally {
79
+ await writer.close();
80
+ }
81
+ expect(true).toBeTruthy();
82
+
83
+ const f = await client.read(new TimeRange(0, TimeStamp.seconds(10)), ch.key);
84
+ expect(f.length).toEqual(10);
85
+ });
86
+ test("write with auto commit and alwaysPersist", async () => {
87
+ const ch = await newChannel();
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
+ });
94
+ try {
95
+ await writer.write(ch.key, randomSeries(10, ch.dataType));
96
+ } finally {
97
+ await writer.close();
98
+ }
99
+ expect(true).toBeTruthy();
100
+ });
101
+ test("write with auto commit and a set interval", async () => {
102
+ const ch = await newChannel();
103
+ const writer = await client.openWriter({
104
+ start: 0,
105
+ channels: ch.key,
106
+ enableAutoCommit: true,
107
+ autoIndexPersistInterval: TimeSpan.milliseconds(100),
108
+ });
109
+ try {
110
+ await writer.write(ch.key, randomSeries(10, ch.dataType));
111
+ } finally {
112
+ await writer.close();
113
+ }
114
+ expect(true).toBeTruthy();
115
+ });
69
116
  });
70
117
  describe("Client", () => {
71
118
  test("Client - basic write", async () => {
@@ -13,9 +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
- } from "@synnaxlabs/x";
17
+ TimeSpan,
18
+ } from "@synnaxlabs/x/telem";
19
+ import { toArray } from "@synnaxlabs/x/toArray";
19
20
  import { z } from "zod";
20
21
 
21
22
  import {
@@ -49,12 +50,16 @@ export enum WriterMode {
49
50
  StreamOnly = 3,
50
51
  }
51
52
 
53
+ export const ALWAYS_INDEX_PERSIST_ON_AUTO_COMMIT: TimeSpan = new TimeSpan(-1);
54
+
52
55
  const netConfigZ = z.object({
53
56
  start: TimeStamp.z.optional(),
54
57
  controlSubject: controlSubjectZ.optional(),
55
58
  keys: z.number().array().optional(),
56
59
  authorities: Authority.z.array().optional(),
57
60
  mode: z.nativeEnum(WriterMode).optional(),
61
+ enableAutoCommit: z.boolean().optional(),
62
+ autoIndexPersistInterval: TimeSpan.z.optional(),
58
63
  });
59
64
 
60
65
  const reqZ = z.object({
@@ -74,11 +79,27 @@ const resZ = z.object({
74
79
  type Response = z.infer<typeof resZ>;
75
80
 
76
81
  export interface WriterConfig {
82
+ // channels denote the channels to write to.
77
83
  channels: Params;
84
+ // start sets the starting timestamp for the first sample in the writer.
78
85
  start?: CrudeTimeStamp;
86
+ // controlSubject sets the control subject of the writer.
79
87
  controlSubject?: ControlSubject;
88
+ // authorities set the control authority to set for each channel on the writer.
89
+ // Defaults to absolute authority. If not working with concurrent control,
90
+ // it's best to leave this as the default.
80
91
  authorities?: Authority | Authority[];
92
+ // mode sets the persistence and streaming mode of the writer. The default
93
+ // mode is WriterModePersistStream.
81
94
  mode?: WriterMode;
95
+ // enableAutoCommit determines whether the writer will automatically commit.
96
+ // If enableAutoCommit is true, then the writer will commit after each write, and
97
+ // will flush that commit to index after the specified autoIndexPersistInterval.
98
+ enableAutoCommit?: boolean;
99
+ // autoIndexPersistInterval sets the interval at which commits to the index will be
100
+ // persisted. To persist every commit to guarantee minimal loss of data, set
101
+ // auto_index_persist_interval to AlwaysAutoIndexPersist.
102
+ autoIndexPersistInterval?: TimeSpan;
82
103
  }
83
104
 
84
105
  /**
@@ -141,6 +162,8 @@ export class Writer {
141
162
  authorities = Authority.Absolute,
142
163
  controlSubject: subject,
143
164
  mode = WriterMode.PersistStream,
165
+ enableAutoCommit = false,
166
+ autoIndexPersistInterval = TimeSpan.SECOND,
144
167
  }: WriterConfig,
145
168
  ): Promise<Writer> {
146
169
  const adapter = await WriteFrameAdapter.open(retriever, channels);
@@ -154,6 +177,8 @@ export class Writer {
154
177
  controlSubject: subject,
155
178
  authorities: toArray(authorities),
156
179
  mode,
180
+ enableAutoCommit,
181
+ autoIndexPersistInterval,
157
182
  },
158
183
  });
159
184
  return writer;
@@ -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";