@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,11 +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 { array } from "@synnaxlabs/x/array";
11
10
  import { TimeRange } from "@synnaxlabs/x/telem";
12
- import { z } from "zod/v4";
11
+ import { z } from "zod";
13
12
 
14
- export const keyZ = z.string().uuid();
13
+ import { label } from "@/label";
14
+
15
+ export const keyZ = z.uuid();
15
16
  export type Key = z.infer<typeof keyZ>;
16
17
  export const nameZ = z.string().min(1);
17
18
  export type Name = z.infer<typeof nameZ>;
@@ -24,40 +25,25 @@ export const payloadZ = z.object({
24
25
  name: nameZ,
25
26
  timeRange: TimeRange.z,
26
27
  color: z.string().optional(),
28
+ labels: label.labelZ
29
+ .array()
30
+ .or(z.null().transform(() => undefined))
31
+ .optional(),
32
+ get parent(): z.ZodUnion<readonly [z.ZodNull, typeof payloadZ]> {
33
+ // Using as unknown is bad, but unfortunately resolving the output type of this
34
+ // transform is nearly impossible.
35
+ return payloadZ
36
+ .optional()
37
+ .nullable()
38
+ .transform((p) => (p === undefined ? null : p)) as unknown as z.ZodUnion<
39
+ readonly [z.ZodNull, typeof payloadZ]
40
+ >;
41
+ },
27
42
  });
28
- export interface Payload extends z.infer<typeof payloadZ> {}
29
-
30
- export const newZ = payloadZ.partial({ key: true });
31
- export interface New extends z.input<typeof newZ> {}
32
43
 
33
- export type ParamAnalysisResult =
34
- | { single: true; variant: "keys"; normalized: Keys; actual: Key; empty: never }
35
- | { single: true; variant: "names"; normalized: Names; actual: Name; empty: never }
36
- | { single: false; variant: "keys"; normalized: Keys; actual: Keys; empty: boolean }
37
- | {
38
- single: false;
39
- variant: "names";
40
- normalized: Names;
41
- actual: Names;
42
- empty: boolean;
43
- };
44
+ export type Payload = z.infer<typeof payloadZ>;
44
45
 
45
- export const analyzeParams = (ranges: Params): ParamAnalysisResult => {
46
- const normal = array.toArray(ranges) as Keys | Names;
47
- const empty = normal.length === 0;
48
- let isKey = false;
49
- if (!empty) isKey = keyZ.safeParse(normal[0]).success;
50
- return {
51
- single: !Array.isArray(ranges),
52
- variant: isKey ? "keys" : "names",
53
- normalized: normal,
54
- actual: ranges,
55
- empty,
56
- } as ParamAnalysisResult;
57
- };
58
-
59
- export const ONTOLOGY_TYPE = "range";
60
- export type OntologyType = typeof ONTOLOGY_TYPE;
61
-
62
- export const ALIAS_ONTOLOGY_TYPE = "range-alias";
63
- export type AliasOntologyType = typeof ALIAS_ONTOLOGY_TYPE;
46
+ export const newZ = payloadZ
47
+ .omit({ parent: true, labels: true })
48
+ .partial({ key: true });
49
+ export interface New extends z.input<typeof newZ> {}
@@ -7,15 +7,14 @@
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 { type change } from "@synnaxlabs/x";
11
10
  import { DataType, TimeSpan, TimeStamp } from "@synnaxlabs/x/telem";
12
11
  import { describe, expect, it } from "vitest";
13
12
 
14
13
  import { NotFoundError } from "@/errors";
15
14
  import { type ranger } from "@/ranger";
16
- import { newClient } from "@/setupspecs";
15
+ import { createTestClient } from "@/testutil/client";
17
16
 
18
- const client = newClient();
17
+ const client = createTestClient();
19
18
 
20
19
  describe("Ranger", () => {
21
20
  describe("create", () => {
@@ -143,25 +142,6 @@ describe("Ranger", () => {
143
142
  });
144
143
  });
145
144
 
146
- describe("page", () => {
147
- it("should page through ranges", async () => {
148
- await client.ranges.create({
149
- name: "My New One Second Range",
150
- timeRange: TimeStamp.now().spanRange(TimeSpan.seconds(1)),
151
- });
152
- await client.ranges.create({
153
- name: "My New Two Second Range",
154
- timeRange: TimeStamp.now().spanRange(TimeSpan.seconds(2)),
155
- });
156
- const ranges = await client.ranges.page(0, 1);
157
- expect(ranges.length).toEqual(1);
158
- const keys = ranges.map((r) => r.key);
159
- const next = await client.ranges.page(1, 1);
160
- expect(next.length).toEqual(1);
161
- expect(next.map((r) => r.key)).not.toContain(keys[0]);
162
- });
163
- });
164
-
165
145
  describe("KV", () => {
166
146
  it("should set, get, and delete a single key", async () => {
167
147
  const rng = await client.ranges.create({
@@ -196,43 +176,44 @@ describe("Ranger", () => {
196
176
  const res = await rng.kv.list();
197
177
  expect(res).toEqual({ foo: "bar", baz: "qux" });
198
178
  });
179
+ });
199
180
 
200
- describe("observable", () => {
201
- it("should listen to key-value sets on the range", async () => {
202
- const rng = await client.ranges.create({
203
- name: "My New One Second Range",
204
- timeRange: TimeStamp.now().spanRange(TimeSpan.seconds(1)),
205
- });
206
- const obs = await rng.kv.openTracker();
207
- const res = new Promise<change.Change<string, ranger.KVPair>[]>((resolve) => {
208
- obs.onChange((pair) => resolve(pair));
209
- });
210
- await rng.kv.set("foo", "bar");
211
- const pair = await res;
212
- expect(pair.length).toBeGreaterThan(0);
213
- expect(pair[0].value?.range).toEqual(rng.key);
214
- expect(pair[0].value?.key).toEqual("foo");
215
- expect(pair[0].value?.value).toEqual("bar");
181
+ describe("label", () => {
182
+ it("should set and get a label for the range", async () => {
183
+ const rng = await client.ranges.create({
184
+ name: "My New One Second Range",
185
+ timeRange: TimeStamp.now().spanRange(TimeSpan.seconds(1)),
216
186
  });
217
- it("should listen to key-value deletes on the range", async () => {
218
- const rng = await client.ranges.create({
219
- name: "My New One Second Range",
220
- timeRange: TimeStamp.now().spanRange(TimeSpan.seconds(1)),
221
- });
222
- await rng.kv.set("foo", "bar");
223
- const obs = await rng.kv.openTracker();
224
- const res = new Promise<change.Change<string, ranger.KVPair>[]>((resolve) => {
225
- obs.onChange((changes) => {
226
- if (changes.every((c) => c.variant === "delete")) resolve(changes);
227
- });
228
- });
229
- await rng.kv.delete("foo");
230
- const pair = await res;
231
- expect(pair.length).toBeGreaterThan(0);
232
- expect(pair[0].value?.range).toEqual(rng.key);
233
- expect(pair[0].value?.key).toEqual("foo");
234
- expect(pair[0].value?.value).toHaveLength(0);
187
+ const label = await client.labels.create({
188
+ name: "My New Label",
189
+ color: "#E774D0",
190
+ });
191
+ await rng.addLabel(label.key);
192
+ const newRange = await client.ranges.retrieve({
193
+ keys: [rng.key],
194
+ includeLabels: true,
195
+ });
196
+ expect(newRange[0].labels).toHaveLength(1);
197
+ expect(newRange[0].labels?.[0]).toEqual(label);
198
+ });
199
+ });
200
+
201
+ describe("parent", () => {
202
+ it("should set and get a parent for the range", async () => {
203
+ const parent = await client.ranges.create({
204
+ name: "My New One Second Range",
205
+ timeRange: TimeStamp.now().spanRange(TimeSpan.seconds(1)),
206
+ });
207
+ const child = await client.ranges.create({
208
+ name: "My New One Second Range",
209
+ timeRange: TimeStamp.now().spanRange(TimeSpan.seconds(1)),
210
+ });
211
+ await client.ontology.addChildren(parent.ontologyID, child.ontologyID);
212
+ const newParent = await client.ranges.retrieve({
213
+ keys: [child.key],
214
+ includeParent: true,
235
215
  });
216
+ expect(newParent[0].parent).toEqual(parent.payload);
236
217
  });
237
218
  });
238
219
 
@@ -8,7 +8,7 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
11
- import { z } from "zod/v4";
11
+ import { z } from "zod";
12
12
 
13
13
  import { ontology } from "@/ontology";
14
14
  import { keyZ, nameZ, type New, newZ, type Payload, payloadZ } from "@/ranger/payload";
@@ -7,4 +7,14 @@
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 signals from "@/signals/external";
10
+ import Synnax, { type SynnaxProps } from "@/client";
11
+
12
+ export const TEST_CLIENT_PROPS: SynnaxProps = {
13
+ host: "localhost",
14
+ port: 9090,
15
+ username: "synnax",
16
+ password: "seldon",
17
+ };
18
+
19
+ export const createTestClient = (props?: Partial<SynnaxProps>): Synnax =>
20
+ new Synnax({ ...TEST_CLIENT_PROPS, ...props });
@@ -7,79 +7,154 @@
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 { type UnaryClient } from "@synnaxlabs/freighter";
10
+ import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
11
11
  import { array } from "@synnaxlabs/x";
12
+ import { z } from "zod";
12
13
 
13
14
  import { MultipleFoundError, NotFoundError } from "@/errors";
14
- import { ontology } from "@/ontology";
15
- import { type Key, type New, ONTOLOGY_TYPE, type User } from "@/user/payload";
16
- import { Retriever } from "@/user/retriever";
17
- import { Writer } from "@/user/writer";
15
+ import { type ontology } from "@/ontology";
16
+ import { type Key, keyZ, type New, newZ, type User, userZ } from "@/user/payload";
17
+ import { nullableArrayZ } from "@/util/zod";
18
+
19
+ const retrieveRequestZ = z.object({
20
+ keys: keyZ.array().optional(),
21
+ usernames: z.string().array().optional(),
22
+ });
23
+
24
+ const keyRetrieveRequestZ = z
25
+ .object({
26
+ key: keyZ,
27
+ })
28
+ .transform(({ key }) => ({ keys: [key] }));
29
+
30
+ const usernameRetrieveRequestZ = z
31
+ .object({
32
+ username: z.string(),
33
+ })
34
+ .transform(({ username }) => ({ usernames: [username] }));
35
+
36
+ const usernamesRetrieveRequestZ = z
37
+ .object({
38
+ usernames: z.string().array(),
39
+ })
40
+ .transform(({ usernames }) => ({ usernames }));
41
+
42
+ export type KeyRetrieveRequest = z.input<typeof keyRetrieveRequestZ>;
43
+ export type UsernameRetrieveRequest = z.input<typeof usernameRetrieveRequestZ>;
44
+ export type UsernamesRetrieveRequest = z.input<typeof usernamesRetrieveRequestZ>;
45
+
46
+ const retrieveArgsZ = z.union([
47
+ keyRetrieveRequestZ,
48
+ usernameRetrieveRequestZ,
49
+ usernamesRetrieveRequestZ,
50
+ retrieveRequestZ,
51
+ ]);
52
+
53
+ export type RetrieveArgs = z.input<typeof retrieveArgsZ>;
54
+
55
+ export interface RetrieveRequest extends z.infer<typeof retrieveRequestZ> {}
56
+
57
+ const retrieveResZ = z.object({ users: nullableArrayZ(userZ) });
58
+
59
+ const createReqZ = z.object({ users: newZ.array() });
60
+ const createResZ = z.object({ users: userZ.array() });
61
+ const changeUsernameReqZ = z.object({ key: keyZ, username: z.string().min(1) });
62
+ const changeUsernameResZ = z.object({});
63
+ const renameReqZ = z.object({
64
+ key: keyZ,
65
+ firstName: z.string().optional(),
66
+ lastName: z.string().optional(),
67
+ });
68
+ const renameResZ = z.object({});
69
+ const deleteReqZ = z.object({ keys: keyZ.array() });
70
+ const deleteResZ = z.object({});
71
+
72
+ const RETRIEVE_ENDPOINT = "/user/retrieve";
73
+ const CREATE_ENDPOINT = "/user/create";
74
+ const CHANGE_USERNAME_ENDPOINT = "/user/change-username";
75
+ const RENAME_ENDPOINT = "/user/rename";
76
+ const DELETE_ENDPOINT = "/user/delete";
18
77
 
19
78
  export class Client {
20
- private readonly reader: Retriever;
21
- private readonly writer: Writer;
79
+ private readonly client: UnaryClient;
22
80
 
23
81
  constructor(client: UnaryClient) {
24
- this.writer = new Writer(client);
25
- this.reader = new Retriever(client);
82
+ this.client = client;
26
83
  }
27
84
 
28
85
  async create(user: New): Promise<User>;
29
-
30
86
  async create(users: New[]): Promise<User[]>;
31
-
32
87
  async create(users: New | New[]): Promise<User | User[]> {
33
88
  const isMany = Array.isArray(users);
34
- const res = await this.writer.create(users);
35
- return isMany ? res : res[0];
89
+ const res = await sendRequired<typeof createReqZ, typeof createResZ>(
90
+ this.client,
91
+ CREATE_ENDPOINT,
92
+ { users: array.toArray(users) },
93
+ createReqZ,
94
+ createResZ,
95
+ );
96
+ return isMany ? res.users : res.users[0];
36
97
  }
37
98
 
38
99
  async changeUsername(key: Key, newUsername: string): Promise<void> {
39
- await this.writer.changeUsername(key, newUsername);
40
- }
41
-
42
- async retrieve(key: Key): Promise<User>;
43
-
44
- async retrieve(keys: Key[]): Promise<User[]>;
45
-
46
- async retrieve(keys: Key | Key[]): Promise<User | User[]> {
47
- const isMany = Array.isArray(keys);
48
- const res = await this.reader.retrieve({ keys: array.toArray(keys) });
49
- if (isMany) return res;
50
- if (res.length === 0) throw new NotFoundError(`No user with key ${keys} found`);
51
- if (res.length > 1)
52
- throw new MultipleFoundError(`Multiple users found with key ${keys}`);
53
- return res[0];
100
+ await sendRequired<typeof changeUsernameReqZ, typeof changeUsernameResZ>(
101
+ this.client,
102
+ CHANGE_USERNAME_ENDPOINT,
103
+ { key, username: newUsername },
104
+ changeUsernameReqZ,
105
+ changeUsernameResZ,
106
+ );
54
107
  }
55
108
 
56
- async retrieveByName(username: string): Promise<User>;
57
-
58
- async retrieveByName(usernames: string[]): Promise<User[]>;
59
-
60
- async retrieveByName(usernames: string | string[]): Promise<User | User[]> {
61
- const isMany = Array.isArray(usernames);
62
- const res = await this.reader.retrieve({ usernames: array.toArray(usernames) });
63
- if (isMany) return res;
64
- if (res.length === 0)
65
- throw new NotFoundError(`No user with username ${usernames} found`);
66
- if (res.length > 1)
67
- throw new MultipleFoundError(`Multiple users found with username ${usernames}`);
68
- return res[0];
109
+ async retrieve(args: KeyRetrieveRequest): Promise<User>;
110
+ async retrieve(args: UsernameRetrieveRequest): Promise<User>;
111
+ async retrieve(args: RetrieveArgs): Promise<User[]>;
112
+ async retrieve(args: RetrieveArgs): Promise<User | User[]> {
113
+ const isSingle = "key" in args || "username" in args;
114
+ const res = await sendRequired<typeof retrieveArgsZ, typeof retrieveResZ>(
115
+ this.client,
116
+ RETRIEVE_ENDPOINT,
117
+ args,
118
+ retrieveArgsZ,
119
+ retrieveResZ,
120
+ );
121
+
122
+ if (!isSingle) return res.users;
123
+
124
+ if (res.users.length === 0) {
125
+ const identifier =
126
+ "key" in args ? `key ${args.key}` : `username ${args.username}`;
127
+ throw new NotFoundError(`No user with ${identifier} found`);
128
+ }
129
+ if (res.users.length > 1) {
130
+ const identifier =
131
+ "key" in args ? `key ${args.key}` : `username ${args.username}`;
132
+ throw new MultipleFoundError(`Multiple users found with ${identifier}`);
133
+ }
134
+ return res.users[0];
69
135
  }
70
136
 
71
137
  async rename(key: Key, firstName?: string, lastName?: string): Promise<void> {
72
- await this.writer.rename(key, firstName, lastName);
138
+ await sendRequired<typeof renameReqZ, typeof renameResZ>(
139
+ this.client,
140
+ RENAME_ENDPOINT,
141
+ { key, firstName, lastName },
142
+ renameReqZ,
143
+ renameResZ,
144
+ );
73
145
  }
74
146
 
75
147
  async delete(key: Key): Promise<void>;
76
-
77
148
  async delete(keys: Key[]): Promise<void>;
78
-
79
149
  async delete(keys: Key | Key[]): Promise<void> {
80
- await this.writer.delete(keys);
150
+ await sendRequired<typeof deleteReqZ, typeof deleteResZ>(
151
+ this.client,
152
+ DELETE_ENDPOINT,
153
+ { keys: array.toArray(keys) },
154
+ deleteReqZ,
155
+ deleteResZ,
156
+ );
81
157
  }
82
158
  }
83
159
 
84
- export const ontologyID = (key: Key): ontology.ID =>
85
- new ontology.ID({ type: ONTOLOGY_TYPE, key });
160
+ export const ontologyID = (key: Key): ontology.ID => ({ type: "user", key });
@@ -7,9 +7,9 @@
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 { z } from "zod/v4";
10
+ import { z } from "zod";
11
11
 
12
- export const keyZ = z.string().uuid();
12
+ export const keyZ = z.uuid();
13
13
  export type Key = z.infer<typeof keyZ>;
14
14
 
15
15
  export const userZ = z.object({
@@ -29,6 +29,3 @@ export const newZ = userZ
29
29
  .omit({ rootUser: true })
30
30
  .extend({ password: z.string().min(1) });
31
31
  export interface New extends z.infer<typeof newZ> {}
32
-
33
- export const ONTOLOGY_TYPE = "user";
34
- export type OntologyType = typeof ONTOLOGY_TYPE;
@@ -8,7 +8,7 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
11
- import { z } from "zod/v4";
11
+ import { z } from "zod";
12
12
 
13
13
  import { keyZ, type User, userZ } from "@/user/payload";
14
14
  import { nullableArrayZ } from "@/util/zod";
@@ -11,7 +11,7 @@ import { id } from "@synnaxlabs/x";
11
11
  import { describe, expect, test } from "vitest";
12
12
 
13
13
  import { AuthError, NotFoundError } from "@/errors";
14
- import { newClient } from "@/setupspecs";
14
+ import { createTestClient } from "@/testutil/client";
15
15
  import { type user } from "@/user";
16
16
 
17
17
  interface SortType {
@@ -20,7 +20,7 @@ interface SortType {
20
20
 
21
21
  const sort = (a: SortType, b: SortType) => a.username.localeCompare(b.username);
22
22
 
23
- const client = newClient();
23
+ const client = createTestClient();
24
24
 
25
25
  const userOne: user.New = {
26
26
  username: id.create(),
@@ -102,22 +102,22 @@ describe("User", () => {
102
102
  describe("by name", () => {
103
103
  describe("one", () => {
104
104
  test("found", async () => {
105
- const res = await client.user.retrieveByName(userOne.username);
105
+ const res = await client.user.retrieve({ username: userOne.username });
106
106
  expect(res.username).toEqual(userOne.username);
107
107
  expect(res.key).toEqual(userOne.key);
108
108
  expect(res.firstName).toEqual(userOne.firstName);
109
109
  expect(res.lastName).toEqual(userOne.lastName);
110
110
  });
111
111
  test("not found", async () =>
112
- await expect(client.user.retrieveByName(id.create())).rejects.toThrow(
112
+ await expect(client.user.retrieve({ username: id.create() })).rejects.toThrow(
113
113
  NotFoundError,
114
114
  ));
115
115
  });
116
116
  describe("many", () => {
117
117
  test("found", async () => {
118
- const res = await client.user.retrieveByName(
119
- userArray.map((u) => u.username),
120
- );
118
+ const res = await client.user.retrieve({
119
+ usernames: userArray.map((u) => u.username),
120
+ });
121
121
  expect(res.sort(sort)).toHaveLength(2);
122
122
  res.forEach((u, i) => {
123
123
  expect(u.username).toEqual(userArray[i].username);
@@ -127,14 +127,13 @@ describe("User", () => {
127
127
  });
128
128
  });
129
129
  test("not found", async () => {
130
- const res = await client.user.retrieveByName([id.create()]);
130
+ const res = await client.user.retrieve({ usernames: [id.create()] });
131
131
  expect(res).toEqual([]);
132
132
  });
133
133
  test("extra names getting deleted", async () => {
134
- const res = await client.user.retrieveByName([
135
- ...userArray.map((u) => u.username),
136
- id.create(),
137
- ]);
134
+ const res = await client.user.retrieve({
135
+ usernames: [...userArray.map((u) => u.username), id.create()],
136
+ });
138
137
  expect(res.sort(sort)).toHaveLength(2);
139
138
  res.forEach((u, i) => {
140
139
  expect(u.username).toEqual(userArray[i].username);
@@ -144,7 +143,7 @@ describe("User", () => {
144
143
  });
145
144
  });
146
145
  test("calling with no names", async () => {
147
- const res = await client.user.retrieveByName([]);
146
+ const res = await client.user.retrieve({ usernames: [] });
148
147
  const usernames = res.map((u) => u.username);
149
148
  expect(usernames).toContain(userOne.username);
150
149
  expect(usernames).toContain(userTwo.username);
@@ -156,7 +155,7 @@ describe("User", () => {
156
155
  describe("by key", () => {
157
156
  describe("one", () => {
158
157
  test("found", async () => {
159
- const res = await client.user.retrieve(userOne.key as string);
158
+ const res = await client.user.retrieve({ key: userOne.key as string });
160
159
  expect(res.username).toEqual(userOne.username);
161
160
  expect(res.key).toEqual(userOne.key);
162
161
  expect(res.firstName).toEqual(userOne.firstName);
@@ -166,16 +165,18 @@ describe("User", () => {
166
165
  await expect(
167
166
  client.user.delete(userOne.key as string),
168
167
  ).resolves.toBeUndefined();
169
- await expect(client.user.retrieve(userOne.key as string)).rejects.toThrow(
170
- NotFoundError,
171
- );
168
+ await expect(
169
+ client.user.retrieve({ key: userOne.key as string }),
170
+ ).rejects.toThrow(NotFoundError);
172
171
  const u = await client.user.create(userOne);
173
172
  userOne.key = u.key;
174
173
  });
175
174
  });
176
175
  describe("many", () => {
177
176
  test("found", async () => {
178
- const res = await client.user.retrieve(userArray.map((u) => u.key as string));
177
+ const res = await client.user.retrieve({
178
+ keys: userArray.map((u) => u.key as string),
179
+ });
179
180
  expect(res.sort(sort)).toHaveLength(2);
180
181
  res.forEach((u, i) => {
181
182
  expect(u.username).toEqual(userArray[i].username);
@@ -188,14 +189,13 @@ describe("User", () => {
188
189
  for (const u of userArray)
189
190
  await expect(client.user.delete(u.key as string)).resolves.toBeUndefined();
190
191
  await expect(
191
- client.user.retrieve(userArray.map((u) => u.key as string)),
192
+ client.user.retrieve({ keys: userArray.map((u) => u.key as string) }),
192
193
  ).rejects.toThrow(NotFoundError);
193
- // cleanup
194
194
  const users = await client.user.create(userArray);
195
195
  users.forEach((u, i) => (userArray[i].key = u.key));
196
196
  });
197
197
  test("all", async () => {
198
- const res = await client.user.retrieve([]);
198
+ const res = await client.user.retrieve({ keys: [] });
199
199
  const usernames = res.map((u) => u.username);
200
200
  expect(usernames).toContain(userOne.username);
201
201
  expect(usernames).toContain(userTwo.username);
@@ -211,7 +211,7 @@ describe("User", () => {
211
211
  await expect(
212
212
  client.user.changeUsername(userOne.key as string, newUsername),
213
213
  ).resolves.toBeUndefined();
214
- const res = await client.user.retrieveByName(newUsername);
214
+ const res = await client.user.retrieve({ username: newUsername });
215
215
  expect(res.username).toEqual(newUsername);
216
216
  expect(res.key).not.toEqual("");
217
217
  expect(res.firstName).toEqual(userOne.firstName);
@@ -240,7 +240,7 @@ describe("User", () => {
240
240
  await expect(
241
241
  client.user.rename(userOne.key as string, "Thomas", "Jefferson"),
242
242
  ).resolves.toBeUndefined();
243
- const res = await client.user.retrieve(userOne.key as string);
243
+ const res = await client.user.retrieve({ key: userOne.key as string });
244
244
  expect(res.username).toEqual(userOne.username);
245
245
  expect(res.key).toEqual(userOne.key);
246
246
  expect(res.firstName).toEqual("Thomas");
@@ -252,7 +252,7 @@ describe("User", () => {
252
252
  await expect(
253
253
  client.user.rename(userOne.key as string, "James"),
254
254
  ).resolves.toBeUndefined();
255
- const res = await client.user.retrieve(userOne.key as string);
255
+ const res = await client.user.retrieve({ key: userOne.key as string });
256
256
  expect(res.username).toEqual(userOne.username);
257
257
  expect(res.key).toEqual(userOne.key);
258
258
  expect(res.firstName).toEqual("James");
@@ -263,16 +263,16 @@ describe("User", () => {
263
263
  describe("Delete", () => {
264
264
  test("one that exists", async () => {
265
265
  await expect(client.user.delete(userOne.key as string)).resolves.toBeUndefined();
266
- await expect(client.user.retrieve(userOne.key as string)).rejects.toThrow(
267
- NotFoundError,
268
- );
266
+ await expect(
267
+ client.user.retrieve({ key: userOne.key as string }),
268
+ ).rejects.toThrow(NotFoundError);
269
269
  });
270
270
  test("many that exist", async () => {
271
271
  await expect(
272
272
  client.user.delete(userArray.map((u) => u.key as string)),
273
273
  ).resolves.toBeUndefined();
274
274
  await expect(
275
- client.user.retrieve(userArray.map((u) => u.key as string)),
275
+ client.user.retrieve({ keys: userArray.map((u) => u.key as string) }),
276
276
  ).rejects.toThrow(NotFoundError);
277
277
  });
278
278
  test("one that doesn't exist", async () => {
@@ -282,9 +282,9 @@ describe("User", () => {
282
282
  await expect(
283
283
  client.user.delete([userOne.key as string, userTwo.key as string]),
284
284
  ).resolves.toBeUndefined();
285
- await expect(client.user.retrieve(userTwo.key as string)).rejects.toThrow(
286
- NotFoundError,
287
- );
285
+ await expect(
286
+ client.user.retrieve({ key: userTwo.key as string }),
287
+ ).rejects.toThrow(NotFoundError);
288
288
  });
289
289
  });
290
290
  });
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
11
11
  import { array } from "@synnaxlabs/x/array";
12
- import { z } from "zod/v4";
12
+ import { z } from "zod";
13
13
 
14
14
  import { type Key, keyZ, type New, newZ, type User, userZ } from "@/user/payload";
15
15
 
@@ -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 { binary, type UnknownRecord, unknownRecordZ } from "@synnaxlabs/x";
10
+ import { binary, record } from "@synnaxlabs/x";
11
11
 
12
- export const decodeJSONString = (s: string): UnknownRecord =>
13
- s ? binary.JSON_CODEC.decodeString(s, unknownRecordZ) : {};
12
+ export const decodeJSONString = (s: string): record.Unknown =>
13
+ s ? binary.JSON_CODEC.decodeString(s, record.unknownZ) : {};