@synnaxlabs/client 0.47.0 → 0.49.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 (311) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/dist/client.cjs +35 -35
  3. package/dist/client.js +6264 -5984
  4. package/dist/eslint.config.d.ts +3 -2
  5. package/dist/eslint.config.d.ts.map +1 -1
  6. package/dist/src/access/client.d.ts +3 -1
  7. package/dist/src/access/client.d.ts.map +1 -1
  8. package/dist/src/access/enforce.d.ts +35 -0
  9. package/dist/src/access/enforce.d.ts.map +1 -0
  10. package/dist/src/access/enforce.spec.d.ts +2 -0
  11. package/dist/src/access/enforce.spec.d.ts.map +1 -0
  12. package/dist/src/access/external.d.ts +3 -0
  13. package/dist/src/access/external.d.ts.map +1 -1
  14. package/dist/src/access/payload.d.ts +0 -6
  15. package/dist/src/access/payload.d.ts.map +1 -1
  16. package/dist/src/access/policy/access.spec.d.ts +2 -0
  17. package/dist/src/access/policy/access.spec.d.ts.map +1 -0
  18. package/dist/src/access/policy/client.d.ts +485 -31
  19. package/dist/src/access/policy/client.d.ts.map +1 -1
  20. package/dist/src/access/policy/payload.d.ts +36 -113
  21. package/dist/src/access/policy/payload.d.ts.map +1 -1
  22. package/dist/src/access/role/client.d.ts +135 -0
  23. package/dist/src/access/role/client.d.ts.map +1 -0
  24. package/dist/src/access/role/external.d.ts.map +1 -0
  25. package/dist/src/access/role/index.d.ts +2 -0
  26. package/dist/src/access/role/index.d.ts.map +1 -0
  27. package/dist/src/access/role/payload.d.ts +27 -0
  28. package/dist/src/access/role/payload.d.ts.map +1 -0
  29. package/dist/src/access/role/role.spec.d.ts +2 -0
  30. package/dist/src/access/role/role.spec.d.ts.map +1 -0
  31. package/dist/src/arc/access.spec.d.ts +2 -0
  32. package/dist/src/arc/access.spec.d.ts.map +1 -0
  33. package/dist/src/arc/client.d.ts +5 -14
  34. package/dist/src/arc/client.d.ts.map +1 -1
  35. package/dist/src/arc/payload.d.ts +11 -2
  36. package/dist/src/arc/payload.d.ts.map +1 -1
  37. package/dist/src/auth/auth.d.ts +5 -3
  38. package/dist/src/auth/auth.d.ts.map +1 -1
  39. package/dist/src/channel/access.spec.d.ts +2 -0
  40. package/dist/src/channel/access.spec.d.ts.map +1 -0
  41. package/dist/src/channel/client.d.ts +0 -1
  42. package/dist/src/channel/client.d.ts.map +1 -1
  43. package/dist/src/channel/payload.d.ts +19 -8
  44. package/dist/src/channel/payload.d.ts.map +1 -1
  45. package/dist/src/channel/payload.spec.d.ts +2 -0
  46. package/dist/src/channel/payload.spec.d.ts.map +1 -0
  47. package/dist/src/channel/retriever.d.ts +4 -6
  48. package/dist/src/channel/retriever.d.ts.map +1 -1
  49. package/dist/src/channel/writer.d.ts.map +1 -1
  50. package/dist/src/client.d.ts +18 -10
  51. package/dist/src/client.d.ts.map +1 -1
  52. package/dist/src/connection/checker.d.ts +2 -3
  53. package/dist/src/connection/checker.d.ts.map +1 -1
  54. package/dist/src/connection.spec.d.ts +2 -0
  55. package/dist/src/connection.spec.d.ts.map +1 -0
  56. package/dist/src/device/access.spec.d.ts +2 -0
  57. package/dist/src/device/access.spec.d.ts.map +1 -0
  58. package/dist/src/{hardware/device → device}/client.d.ts +14 -7
  59. package/dist/src/device/client.d.ts.map +1 -0
  60. package/dist/src/device/device.spec.d.ts.map +1 -0
  61. package/dist/src/device/external.d.ts.map +1 -0
  62. package/dist/src/device/index.d.ts.map +1 -0
  63. package/dist/src/{hardware/device → device}/payload.d.ts +1 -1
  64. package/dist/src/device/payload.d.ts.map +1 -0
  65. package/dist/src/errors.d.ts +3 -0
  66. package/dist/src/errors.d.ts.map +1 -1
  67. package/dist/src/framer/adapter.d.ts +2 -2
  68. package/dist/src/framer/adapter.d.ts.map +1 -1
  69. package/dist/src/framer/client.d.ts +8 -1
  70. package/dist/src/framer/client.d.ts.map +1 -1
  71. package/dist/src/framer/frame.d.ts +11 -5
  72. package/dist/src/framer/frame.d.ts.map +1 -1
  73. package/dist/src/framer/iterator.d.ts +3 -3
  74. package/dist/src/framer/streamer.d.ts +24 -21
  75. package/dist/src/framer/streamer.d.ts.map +1 -1
  76. package/dist/src/framer/writer.d.ts +13 -13
  77. package/dist/src/index.d.ts +6 -7
  78. package/dist/src/index.d.ts.map +1 -1
  79. package/dist/src/label/access.spec.d.ts +2 -0
  80. package/dist/src/label/access.spec.d.ts.map +1 -0
  81. package/dist/src/label/client.d.ts +20 -11
  82. package/dist/src/label/client.d.ts.map +1 -1
  83. package/dist/src/ontology/client.d.ts +6 -6
  84. package/dist/src/ontology/client.d.ts.map +1 -1
  85. package/dist/src/ontology/group/access.spec.d.ts +2 -0
  86. package/dist/src/ontology/group/access.spec.d.ts.map +1 -0
  87. package/dist/src/ontology/group/client.d.ts +2 -2
  88. package/dist/src/ontology/group/client.d.ts.map +1 -1
  89. package/dist/src/ontology/group/payload.d.ts +1 -2
  90. package/dist/src/ontology/group/payload.d.ts.map +1 -1
  91. package/dist/src/ontology/payload.d.ts +23 -17
  92. package/dist/src/ontology/payload.d.ts.map +1 -1
  93. package/dist/src/ontology/writer.d.ts +10 -10
  94. package/dist/src/ontology/writer.d.ts.map +1 -1
  95. package/dist/src/rack/access.spec.d.ts +2 -0
  96. package/dist/src/rack/access.spec.d.ts.map +1 -0
  97. package/dist/src/{hardware/rack → rack}/client.d.ts +15 -8
  98. package/dist/src/rack/client.d.ts.map +1 -0
  99. package/dist/src/rack/external.d.ts.map +1 -0
  100. package/dist/src/rack/index.d.ts.map +1 -0
  101. package/dist/src/{hardware/rack → rack}/payload.d.ts +1 -1
  102. package/dist/src/rack/payload.d.ts.map +1 -0
  103. package/dist/src/rack/rack.spec.d.ts.map +1 -0
  104. package/dist/src/ranger/access.spec.d.ts +2 -0
  105. package/dist/src/ranger/access.spec.d.ts.map +1 -0
  106. package/dist/src/ranger/alias.d.ts +1 -8
  107. package/dist/src/ranger/alias.d.ts.map +1 -1
  108. package/dist/src/ranger/client.d.ts +12 -5
  109. package/dist/src/ranger/client.d.ts.map +1 -1
  110. package/dist/src/ranger/kv.d.ts +0 -3
  111. package/dist/src/ranger/kv.d.ts.map +1 -1
  112. package/dist/src/ranger/writer.d.ts +2 -2
  113. package/dist/src/ranger/writer.d.ts.map +1 -1
  114. package/dist/src/status/access.spec.d.ts +2 -0
  115. package/dist/src/status/access.spec.d.ts.map +1 -0
  116. package/dist/src/status/client.d.ts +4 -4
  117. package/dist/src/status/client.d.ts.map +1 -1
  118. package/dist/src/status/payload.d.ts +9 -2
  119. package/dist/src/status/payload.d.ts.map +1 -1
  120. package/dist/src/task/access.spec.d.ts +2 -0
  121. package/dist/src/task/access.spec.d.ts.map +1 -0
  122. package/dist/src/{hardware/task → task}/client.d.ts +26 -15
  123. package/dist/src/task/client.d.ts.map +1 -0
  124. package/dist/src/task/external.d.ts +3 -0
  125. package/dist/src/task/external.d.ts.map +1 -0
  126. package/dist/src/task/index.d.ts.map +1 -0
  127. package/dist/src/{hardware/task → task}/payload.d.ts +45 -6
  128. package/dist/src/task/payload.d.ts.map +1 -0
  129. package/dist/src/task/task.spec.d.ts.map +1 -0
  130. package/dist/src/testutil/access.d.ts +4 -0
  131. package/dist/src/testutil/access.d.ts.map +1 -0
  132. package/dist/src/testutil/client.d.ts +3 -3
  133. package/dist/src/testutil/client.d.ts.map +1 -1
  134. package/dist/src/transport.d.ts.map +1 -1
  135. package/dist/src/user/access.spec.d.ts +2 -0
  136. package/dist/src/user/access.spec.d.ts.map +1 -0
  137. package/dist/src/user/client.d.ts +10 -1
  138. package/dist/src/user/client.d.ts.map +1 -1
  139. package/dist/src/user/external.d.ts +1 -1
  140. package/dist/src/user/external.d.ts.map +1 -1
  141. package/dist/src/user/payload.d.ts.map +1 -1
  142. package/dist/src/workspace/access.spec.d.ts +2 -0
  143. package/dist/src/workspace/access.spec.d.ts.map +1 -0
  144. package/dist/src/workspace/client.d.ts +10 -5
  145. package/dist/src/workspace/client.d.ts.map +1 -1
  146. package/dist/src/workspace/lineplot/access.spec.d.ts +2 -0
  147. package/dist/src/workspace/lineplot/access.spec.d.ts.map +1 -0
  148. package/dist/src/workspace/lineplot/client.d.ts +8 -1
  149. package/dist/src/workspace/lineplot/client.d.ts.map +1 -1
  150. package/dist/src/workspace/log/access.spec.d.ts +2 -0
  151. package/dist/src/workspace/log/access.spec.d.ts.map +1 -0
  152. package/dist/src/workspace/log/client.d.ts +8 -1
  153. package/dist/src/workspace/log/client.d.ts.map +1 -1
  154. package/dist/src/workspace/schematic/access.spec.d.ts +2 -0
  155. package/dist/src/workspace/schematic/access.spec.d.ts.map +1 -0
  156. package/dist/src/workspace/schematic/client.d.ts +8 -1
  157. package/dist/src/workspace/schematic/client.d.ts.map +1 -1
  158. package/dist/src/workspace/schematic/symbol/access.spec.d.ts +2 -0
  159. package/dist/src/workspace/schematic/symbol/access.spec.d.ts.map +1 -0
  160. package/dist/src/workspace/schematic/symbol/client.d.ts +1 -5
  161. package/dist/src/workspace/schematic/symbol/client.d.ts.map +1 -1
  162. package/dist/src/workspace/schematic/symbol/payload.d.ts +2 -2
  163. package/dist/src/workspace/table/access.spec.d.ts +2 -0
  164. package/dist/src/workspace/table/access.spec.d.ts.map +1 -0
  165. package/dist/src/workspace/table/client.d.ts +8 -1
  166. package/dist/src/workspace/table/client.d.ts.map +1 -1
  167. package/eslint.config.ts +3 -1
  168. package/package.json +8 -8
  169. package/src/access/client.ts +5 -2
  170. package/src/access/enforce.spec.ts +189 -0
  171. package/src/access/enforce.ts +84 -0
  172. package/src/access/external.ts +3 -0
  173. package/src/access/payload.ts +1 -13
  174. package/src/access/policy/access.spec.ts +147 -0
  175. package/src/access/policy/client.ts +21 -25
  176. package/src/access/policy/payload.ts +9 -5
  177. package/src/access/role/client.ts +135 -0
  178. package/src/access/role/external.ts +11 -0
  179. package/src/{hardware → access/role}/index.ts +1 -1
  180. package/src/access/role/payload.ts +32 -0
  181. package/src/access/role/role.spec.ts +95 -0
  182. package/src/arc/access.spec.ts +143 -0
  183. package/src/arc/client.ts +7 -31
  184. package/src/arc/payload.ts +4 -0
  185. package/src/auth/auth.spec.ts +13 -13
  186. package/src/auth/auth.ts +33 -11
  187. package/src/channel/access.spec.ts +116 -0
  188. package/src/channel/channel.spec.ts +63 -73
  189. package/src/channel/client.ts +2 -8
  190. package/src/channel/payload.spec.ts +171 -0
  191. package/src/channel/payload.ts +37 -8
  192. package/src/channel/retriever.ts +10 -11
  193. package/src/channel/writer.ts +3 -7
  194. package/src/client.ts +38 -28
  195. package/src/connection/checker.ts +10 -10
  196. package/src/connection/connection.spec.ts +13 -13
  197. package/src/connection.spec.ts +145 -0
  198. package/src/device/access.spec.ts +159 -0
  199. package/src/{hardware/device → device}/client.ts +12 -21
  200. package/src/{hardware/device → device}/device.spec.ts +70 -34
  201. package/src/device/external.ts +11 -0
  202. package/src/{hardware/rack → device}/index.ts +1 -1
  203. package/src/{hardware/device → device}/payload.ts +3 -3
  204. package/src/errors.ts +2 -0
  205. package/src/framer/adapter.spec.ts +351 -13
  206. package/src/framer/adapter.ts +23 -13
  207. package/src/framer/client.spec.ts +14 -20
  208. package/src/framer/client.ts +3 -5
  209. package/src/framer/deleter.spec.ts +1 -1
  210. package/src/framer/frame.spec.ts +427 -0
  211. package/src/framer/frame.ts +30 -3
  212. package/src/framer/iterator.ts +4 -4
  213. package/src/framer/streamer.spec.ts +155 -10
  214. package/src/framer/streamer.ts +35 -12
  215. package/src/framer/writer.spec.ts +5 -5
  216. package/src/index.ts +13 -7
  217. package/src/label/access.spec.ts +109 -0
  218. package/src/label/client.ts +10 -14
  219. package/src/ontology/client.ts +4 -6
  220. package/src/ontology/group/access.spec.ts +77 -0
  221. package/src/ontology/group/client.ts +3 -7
  222. package/src/ontology/group/group.spec.ts +18 -0
  223. package/src/ontology/group/payload.ts +2 -2
  224. package/src/ontology/ontology.spec.ts +2 -0
  225. package/src/ontology/payload.ts +18 -2
  226. package/src/ontology/writer.ts +3 -7
  227. package/src/rack/access.spec.ts +102 -0
  228. package/src/{hardware/rack → rack}/client.ts +14 -19
  229. package/src/{hardware/device/index.ts → rack/external.ts} +2 -1
  230. package/src/{hardware/external.ts → rack/index.ts} +1 -1
  231. package/src/{hardware/rack → rack}/payload.ts +2 -2
  232. package/src/{hardware/rack → rack}/rack.spec.ts +43 -17
  233. package/src/ranger/access.spec.ts +115 -0
  234. package/src/ranger/alias.ts +6 -14
  235. package/src/ranger/client.ts +13 -14
  236. package/src/ranger/kv.ts +7 -9
  237. package/src/ranger/ranger.spec.ts +4 -4
  238. package/src/ranger/writer.ts +3 -7
  239. package/src/status/access.spec.ts +129 -0
  240. package/src/status/client.ts +5 -9
  241. package/src/status/payload.ts +3 -2
  242. package/src/task/access.spec.ts +131 -0
  243. package/src/{hardware/task → task}/client.ts +50 -25
  244. package/src/task/external.ts +11 -0
  245. package/src/{hardware/task → task}/index.ts +1 -1
  246. package/src/{hardware/task → task}/payload.ts +22 -3
  247. package/src/{hardware/task → task}/task.spec.ts +197 -34
  248. package/src/testutil/access.ts +34 -0
  249. package/src/testutil/channels.ts +3 -3
  250. package/src/testutil/client.ts +4 -4
  251. package/src/transport.ts +1 -3
  252. package/src/user/access.spec.ts +107 -0
  253. package/src/user/client.ts +10 -12
  254. package/src/user/external.ts +12 -1
  255. package/src/user/payload.ts +3 -5
  256. package/src/workspace/access.spec.ts +108 -0
  257. package/src/workspace/client.ts +11 -27
  258. package/src/workspace/lineplot/access.spec.ts +134 -0
  259. package/src/workspace/lineplot/client.ts +8 -13
  260. package/src/workspace/log/access.spec.ts +134 -0
  261. package/src/workspace/log/client.ts +8 -13
  262. package/src/workspace/schematic/access.spec.ts +134 -0
  263. package/src/workspace/schematic/client.ts +9 -18
  264. package/src/workspace/schematic/symbol/access.spec.ts +172 -0
  265. package/src/workspace/schematic/symbol/client.ts +6 -17
  266. package/src/workspace/schematic/symbol/payload.ts +1 -1
  267. package/src/workspace/table/access.spec.ts +134 -0
  268. package/src/workspace/table/client.ts +8 -13
  269. package/dist/src/access/policy/policy.spec.d.ts +0 -2
  270. package/dist/src/access/policy/policy.spec.d.ts.map +0 -1
  271. package/dist/src/hardware/client.d.ts +0 -10
  272. package/dist/src/hardware/client.d.ts.map +0 -1
  273. package/dist/src/hardware/device/client.d.ts.map +0 -1
  274. package/dist/src/hardware/device/device.spec.d.ts.map +0 -1
  275. package/dist/src/hardware/device/external.d.ts.map +0 -1
  276. package/dist/src/hardware/device/index.d.ts.map +0 -1
  277. package/dist/src/hardware/device/payload.d.ts.map +0 -1
  278. package/dist/src/hardware/external.d.ts +0 -2
  279. package/dist/src/hardware/external.d.ts.map +0 -1
  280. package/dist/src/hardware/index.d.ts +0 -2
  281. package/dist/src/hardware/index.d.ts.map +0 -1
  282. package/dist/src/hardware/rack/client.d.ts.map +0 -1
  283. package/dist/src/hardware/rack/external.d.ts.map +0 -1
  284. package/dist/src/hardware/rack/index.d.ts.map +0 -1
  285. package/dist/src/hardware/rack/payload.d.ts.map +0 -1
  286. package/dist/src/hardware/rack/rack.spec.d.ts.map +0 -1
  287. package/dist/src/hardware/task/client.d.ts.map +0 -1
  288. package/dist/src/hardware/task/external.d.ts.map +0 -1
  289. package/dist/src/hardware/task/index.d.ts.map +0 -1
  290. package/dist/src/hardware/task/payload.d.ts.map +0 -1
  291. package/dist/src/hardware/task/task.spec.d.ts.map +0 -1
  292. package/dist/src/user/retriever.d.ts +0 -16
  293. package/dist/src/user/retriever.d.ts.map +0 -1
  294. package/dist/src/user/writer.d.ts +0 -11
  295. package/dist/src/user/writer.d.ts.map +0 -1
  296. package/src/access/policy/policy.spec.ts +0 -329
  297. package/src/hardware/client.ts +0 -24
  298. package/src/hardware/device/external.ts +0 -11
  299. package/src/hardware/rack/external.ts +0 -11
  300. package/src/hardware/task/external.ts +0 -11
  301. package/src/user/retriever.ts +0 -41
  302. package/src/user/writer.ts +0 -84
  303. /package/dist/src/{hardware/device → access/role}/external.d.ts +0 -0
  304. /package/dist/src/{hardware/device → device}/device.spec.d.ts +0 -0
  305. /package/dist/src/{hardware/rack → device}/external.d.ts +0 -0
  306. /package/dist/src/{hardware/device → device}/index.d.ts +0 -0
  307. /package/dist/src/{hardware/task → rack}/external.d.ts +0 -0
  308. /package/dist/src/{hardware/rack → rack}/index.d.ts +0 -0
  309. /package/dist/src/{hardware/rack → rack}/rack.spec.d.ts +0 -0
  310. /package/dist/src/{hardware/task → task}/index.d.ts +0 -0
  311. /package/dist/src/{hardware/task → task}/task.spec.d.ts +0 -0
@@ -0,0 +1,77 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { id } from "@synnaxlabs/x";
11
+ import { describe, expect, it } from "vitest";
12
+
13
+ import { AuthError } from "@/errors";
14
+ import { ontology } from "@/ontology";
15
+ import { group } from "@/ontology/group";
16
+ import { createTestClientWithPolicy } from "@/testutil/access";
17
+ import { createTestClient } from "@/testutil/client";
18
+
19
+ const client = createTestClient();
20
+
21
+ describe("group", () => {
22
+ describe("access control", () => {
23
+ it("should allow the caller to create groups with the correct policy", async () => {
24
+ const userClient = await createTestClientWithPolicy(client, {
25
+ name: "test",
26
+ objects: [group.ontologyID("")],
27
+ actions: ["create"],
28
+ });
29
+ await userClient.ontology.groups.create({
30
+ parent: ontology.ROOT_ID,
31
+ name: `test-${id.create()}`,
32
+ });
33
+ });
34
+
35
+ it("should deny access when no create policy exists", async () => {
36
+ const userClient = await createTestClientWithPolicy(client, {
37
+ name: "test",
38
+ objects: [group.ontologyID("")],
39
+ actions: [],
40
+ });
41
+ await expect(
42
+ userClient.ontology.groups.create({
43
+ parent: ontology.ROOT_ID,
44
+ name: `test-${id.create()}`,
45
+ }),
46
+ ).rejects.toThrow(AuthError);
47
+ });
48
+
49
+ it("should allow the caller to delete groups with the correct policy", async () => {
50
+ const userClient = await createTestClientWithPolicy(client, {
51
+ name: "test",
52
+ objects: [group.ontologyID("")],
53
+ actions: ["delete", "retrieve"],
54
+ });
55
+ const randomGroup = await client.ontology.groups.create({
56
+ parent: ontology.ROOT_ID,
57
+ name: `test-${id.create()}`,
58
+ });
59
+ await userClient.ontology.groups.delete(randomGroup.key);
60
+ });
61
+
62
+ it("should deny access when no delete policy exists", async () => {
63
+ const userClient = await createTestClientWithPolicy(client, {
64
+ name: "test",
65
+ objects: [group.ontologyID("")],
66
+ actions: [],
67
+ });
68
+ const randomGroup = await client.ontology.groups.create({
69
+ parent: ontology.ROOT_ID,
70
+ name: `test-${id.create()}`,
71
+ });
72
+ await expect(userClient.ontology.groups.delete(randomGroup.key)).rejects.toThrow(
73
+ AuthError,
74
+ );
75
+ });
76
+ });
77
+ });
@@ -29,10 +29,6 @@ const renameReqZ = z.object({ key: keyZ, name: z.string() });
29
29
 
30
30
  const deleteReqZ = z.object({ keys: z.array(keyZ) });
31
31
 
32
- const CREATE_ENDPOINT = "/ontology/create-group";
33
- const RENAME_ENDPOINT = "/ontology/rename-group";
34
- const DELETE_ENDPOINT = "/ontology/delete-group";
35
-
36
32
  export interface CreateArgs extends z.infer<typeof createReqZ> {}
37
33
 
38
34
  export class Client {
@@ -45,7 +41,7 @@ export class Client {
45
41
  async create(args: CreateArgs): Promise<Group> {
46
42
  const res = await sendRequired(
47
43
  this.client,
48
- CREATE_ENDPOINT,
44
+ "/ontology/create-group",
49
45
  args,
50
46
  createReqZ,
51
47
  resZ,
@@ -56,7 +52,7 @@ export class Client {
56
52
  async rename(key: Key, name: string): Promise<void> {
57
53
  await sendRequired(
58
54
  this.client,
59
- RENAME_ENDPOINT,
55
+ "/ontology/rename-group",
60
56
  { key, name },
61
57
  renameReqZ,
62
58
  z.object({}),
@@ -66,7 +62,7 @@ export class Client {
66
62
  async delete(keys: Key | Key[]): Promise<void> {
67
63
  await sendRequired(
68
64
  this.client,
69
- DELETE_ENDPOINT,
65
+ "/ontology/delete-group",
70
66
  { keys: array.toArray(keys) },
71
67
  deleteReqZ,
72
68
  z.object({}),
@@ -7,6 +7,7 @@
7
7
  // License, use of this software will be governed by the Apache License, Version 2.0,
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
+ import { id } from "@synnaxlabs/x";
10
11
  import { describe, expect, it } from "vitest";
11
12
 
12
13
  import { NotFoundError } from "@/errors";
@@ -23,6 +24,23 @@ describe("Group", () => {
23
24
  const g = await client.ontology.groups.create({ parent: ontology.ROOT_ID, name });
24
25
  expect(g.name).toEqual(name);
25
26
  });
27
+ it("should update an existing group", async () => {
28
+ const parent = await client.ontology.groups.create({
29
+ parent: ontology.ROOT_ID,
30
+ name: `test-parent-key${id.create()}`,
31
+ });
32
+ const g = await client.ontology.groups.create({
33
+ parent: group.ontologyID(parent.key),
34
+ name: `original-name-${id.create()}`,
35
+ });
36
+ await client.ontology.groups.create({
37
+ parent: group.ontologyID(parent.key),
38
+ key: g.key,
39
+ name: "updated-name",
40
+ });
41
+ const g2 = await client.ontology.retrieve(group.ontologyID(g.key));
42
+ expect(g2.name).toEqual("updated-name");
43
+ });
26
44
  });
27
45
  describe("rename", () => {
28
46
  it("should correctly rename a group", async () => {
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { z } from "zod";
11
11
 
12
- import { type ID as OntologyID } from "@/ontology/payload";
12
+ import { createIDFactory } from "@/ontology/payload";
13
13
 
14
14
  export const keyZ = z.uuid();
15
15
  export type Key = z.infer<typeof keyZ>;
@@ -21,4 +21,4 @@ export type Params = Key | Name | Keys | Names;
21
21
  export const groupZ = z.object({ key: keyZ, name: nameZ });
22
22
  export interface Group extends z.infer<typeof groupZ> {}
23
23
 
24
- export const ontologyID = (key: Key): OntologyID => ({ type: "group", key });
24
+ export const ontologyID = createIDFactory("group");
@@ -202,11 +202,13 @@ describe("Ontology", () => {
202
202
  });
203
203
  const oldRootLength = (await client.ontology.retrieveChildren(ontology.ROOT_ID))
204
204
  .length;
205
+
205
206
  await client.ontology.moveChildren(
206
207
  ontology.ROOT_ID,
207
208
  group.ontologyID(g.key),
208
209
  group.ontologyID(g2.key),
209
210
  );
211
+
210
212
  const children = await client.ontology.retrieveChildren(group.ontologyID(g.key));
211
213
  expect(children.length).toEqual(1);
212
214
  const newRootLength = (await client.ontology.retrieveChildren(ontology.ROOT_ID))
@@ -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 { array, type change, record } from "@synnaxlabs/x";
10
+ import { array, type change, primitive, record } from "@synnaxlabs/x";
11
11
  import { z } from "zod";
12
12
 
13
13
  export type ResourceChange = change.Change<ID, Resource>;
@@ -22,7 +22,6 @@ export interface RelationshipDelete extends change.Delete<Relationship, undefine
22
22
  export const resourceTypeZ = z.enum([
23
23
  "label",
24
24
  "log",
25
- "allow_all",
26
25
  "builtin",
27
26
  "cluster",
28
27
  "channel",
@@ -39,6 +38,7 @@ export const resourceTypeZ = z.enum([
39
38
  "device",
40
39
  "task",
41
40
  "policy",
41
+ "role",
42
42
  "table",
43
43
  "arc",
44
44
  "schematic_symbol",
@@ -57,6 +57,22 @@ export type ID = z.infer<typeof idZ>;
57
57
 
58
58
  export const ROOT_ID: ID = { type: "builtin", key: "root" };
59
59
 
60
+ export interface CreateID<K extends record.Key> {
61
+ (key: K): ID;
62
+ (keys: K[]): ID[];
63
+ (keys: K | K[]): ID | ID[];
64
+ }
65
+
66
+ export const createIDFactory = <K extends record.Key>(
67
+ type: ResourceType,
68
+ ): CreateID<K> => {
69
+ const id = (key: K) => ({ type, key: primitive.isZero(key) ? "" : key.toString() });
70
+ return ((key: K | K[]) => {
71
+ if (Array.isArray(key)) return key.map(id);
72
+ return id(key);
73
+ }) as CreateID<K>;
74
+ };
75
+
60
76
  export interface IDToString {
61
77
  (id: ID | string): string;
62
78
  (ids: (ID | string)[]): string[];
@@ -12,10 +12,6 @@ import { z } from "zod";
12
12
 
13
13
  import { type ID, idZ } from "@/ontology/payload";
14
14
 
15
- const ADD_CHILDREN_ENDPOINT = "/ontology/add-children";
16
- const REMOVE_CHILDREN_ENDPOINT = "/ontology/remove-children";
17
- const MOVE_CHILDREN_ENDPOINT = "/ontology/move-children";
18
-
19
15
  export const addRemoveChildrenReqZ = z.object({ id: idZ, children: idZ.array() });
20
16
  export const moveChildrenReqZ = z.object({ from: idZ, to: idZ, children: idZ.array() });
21
17
  export const emptyResZ = z.object({});
@@ -30,7 +26,7 @@ export class Writer {
30
26
  async addChildren(id: ID, ...children: ID[]): Promise<void> {
31
27
  await sendRequired<typeof addRemoveChildrenReqZ, typeof emptyResZ>(
32
28
  this.client,
33
- ADD_CHILDREN_ENDPOINT,
29
+ "/ontology/add-children",
34
30
  { id, children },
35
31
  addRemoveChildrenReqZ,
36
32
  emptyResZ,
@@ -40,7 +36,7 @@ export class Writer {
40
36
  async removeChildren(id: ID, ...children: ID[]): Promise<void> {
41
37
  await sendRequired<typeof addRemoveChildrenReqZ, typeof emptyResZ>(
42
38
  this.client,
43
- REMOVE_CHILDREN_ENDPOINT,
39
+ "/ontology/remove-children",
44
40
  { id, children },
45
41
  addRemoveChildrenReqZ,
46
42
  emptyResZ,
@@ -50,7 +46,7 @@ export class Writer {
50
46
  async moveChildren(from: ID, to: ID, ...children: ID[]): Promise<void> {
51
47
  await sendRequired<typeof moveChildrenReqZ, typeof emptyResZ>(
52
48
  this.client,
53
- MOVE_CHILDREN_ENDPOINT,
49
+ "/ontology/move-children",
54
50
  { from, to, children },
55
51
  moveChildrenReqZ,
56
52
  emptyResZ,
@@ -0,0 +1,102 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { describe, expect, it } from "vitest";
11
+
12
+ import { AuthError, NotFoundError } from "@/errors";
13
+ import { rack } from "@/rack";
14
+ import { createTestClientWithPolicy } from "@/testutil/access";
15
+ import { createTestClient } from "@/testutil/client";
16
+
17
+ const client = createTestClient();
18
+
19
+ describe("rack", () => {
20
+ describe("access control", () => {
21
+ it("should deny access when no retrieve policy exists", async () => {
22
+ const userClient = await createTestClientWithPolicy(client, {
23
+ name: "test",
24
+ objects: [],
25
+ actions: [],
26
+ });
27
+ const randomRack = await client.racks.create({
28
+ name: "test",
29
+ });
30
+ await expect(userClient.racks.retrieve({ key: randomRack.key })).rejects.toThrow(
31
+ AuthError,
32
+ );
33
+ });
34
+
35
+ it("should allow the caller to retrieve racks with the correct policy", async () => {
36
+ const userClient = await createTestClientWithPolicy(client, {
37
+ name: "test",
38
+ objects: [rack.ontologyID(0)],
39
+ actions: ["retrieve"],
40
+ });
41
+ const randomRack = await client.racks.create({
42
+ name: "test",
43
+ });
44
+ const retrieved = await userClient.racks.retrieve({
45
+ key: randomRack.key,
46
+ });
47
+ expect(retrieved.key).toBe(randomRack.key);
48
+ expect(retrieved.name).toBe(randomRack.name);
49
+ });
50
+
51
+ it("should allow the caller to create racks with the correct policy", async () => {
52
+ const userClient = await createTestClientWithPolicy(client, {
53
+ name: "test",
54
+ objects: [rack.ontologyID(0)],
55
+ actions: ["create"],
56
+ });
57
+ await userClient.racks.create({
58
+ name: "test",
59
+ });
60
+ });
61
+
62
+ it("should deny access when no create policy exists", async () => {
63
+ const userClient = await createTestClientWithPolicy(client, {
64
+ name: "test",
65
+ objects: [rack.ontologyID(0)],
66
+ actions: [],
67
+ });
68
+ await expect(
69
+ userClient.racks.create({
70
+ name: "test",
71
+ }),
72
+ ).rejects.toThrow(AuthError);
73
+ });
74
+
75
+ it("should allow the caller to delete racks with the correct policy", async () => {
76
+ const userClient = await createTestClientWithPolicy(client, {
77
+ name: "test",
78
+ objects: [rack.ontologyID(0)],
79
+ actions: ["delete", "retrieve"],
80
+ });
81
+ const randomRack = await client.racks.create({
82
+ name: "test",
83
+ });
84
+ await userClient.racks.delete(randomRack.key);
85
+ await expect(userClient.racks.retrieve({ key: randomRack.key })).rejects.toThrow(
86
+ NotFoundError,
87
+ );
88
+ });
89
+
90
+ it("should deny access when no delete policy exists", async () => {
91
+ const userClient = await createTestClientWithPolicy(client, {
92
+ name: "test",
93
+ objects: [rack.ontologyID(0)],
94
+ actions: [],
95
+ });
96
+ const randomRack = await client.racks.create({
97
+ name: "test",
98
+ });
99
+ await expect(userClient.racks.delete(randomRack.key)).rejects.toThrow(AuthError);
100
+ });
101
+ });
102
+ });
@@ -11,6 +11,7 @@ import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
11
11
  import { array } from "@synnaxlabs/x";
12
12
  import { z } from "zod";
13
13
 
14
+ import { ontology } from "@/ontology";
14
15
  import {
15
16
  type Key,
16
17
  keyZ,
@@ -19,16 +20,10 @@ import {
19
20
  type Payload,
20
21
  rackZ,
21
22
  type Status,
22
- } from "@/hardware/rack/payload";
23
- import { type task } from "@/hardware/task";
24
- import { type ontology } from "@/ontology";
23
+ } from "@/rack/payload";
24
+ import { type task } from "@/task";
25
25
  import { checkForMultipleOrNoResults } from "@/util/retrieve";
26
26
 
27
- const RETRIEVE_ENDPOINT = "/hardware/rack/retrieve";
28
- const CREATE_ENDPOINT = "/hardware/rack/create";
29
- const DELETE_ENDPOINT = "/hardware/rack/delete";
30
-
31
- export const STATUS_CHANNEL_NAME = "sy_rack_status";
32
27
  export const SET_CHANNEL_NAME = "sy_rack_set";
33
28
  export const DELETE_CHANNEL_NAME = "sy_rack_delete";
34
29
 
@@ -38,8 +33,8 @@ const retrieveReqZ = z.object({
38
33
  searchTerm: z.string().optional(),
39
34
  embedded: z.boolean().optional(),
40
35
  hostIsNode: z.boolean().optional(),
41
- limit: z.number().optional(),
42
- offset: z.number().optional(),
36
+ limit: z.int().optional(),
37
+ offset: z.int().optional(),
43
38
  includeStatus: z.boolean().optional(),
44
39
  });
45
40
  const retrieveResZ = z.object({ racks: array.nullableZ(rackZ) });
@@ -86,7 +81,7 @@ export class Client {
86
81
  async delete(keys: Key | Key[]): Promise<void> {
87
82
  await sendRequired<typeof deleteReqZ, typeof deleteResZ>(
88
83
  this.client,
89
- DELETE_ENDPOINT,
84
+ "/rack/delete",
90
85
  { keys: array.toArray(keys) },
91
86
  deleteReqZ,
92
87
  deleteResZ,
@@ -99,7 +94,7 @@ export class Client {
99
94
  const isSingle = !Array.isArray(rack);
100
95
  const res = await sendRequired<typeof createReqZ, typeof createResZ>(
101
96
  this.client,
102
- CREATE_ENDPOINT,
97
+ "/rack/create",
103
98
  { racks: array.toArray(rack) },
104
99
  createReqZ,
105
100
  createResZ,
@@ -114,7 +109,7 @@ export class Client {
114
109
  const isSingle = "key" in args || "name" in args;
115
110
  const res = await sendRequired(
116
111
  this.client,
117
- RETRIEVE_ENDPOINT,
112
+ "/rack/retrieve",
118
113
  args,
119
114
  retrieveArgsZ,
120
115
  retrieveResZ,
@@ -158,7 +153,7 @@ export class Rack {
158
153
  Config extends z.ZodType = z.ZodType,
159
154
  StatusData extends z.ZodType = z.ZodType,
160
155
  >(
161
- task: task.New<Type, Config>,
156
+ task: task.New<Type, Config, StatusData>,
162
157
  schemas: task.Schemas<Type, Config, StatusData>,
163
158
  ): Promise<task.Task<Type, Config, StatusData>>;
164
159
 
@@ -167,7 +162,7 @@ export class Rack {
167
162
  Config extends z.ZodType = z.ZodType,
168
163
  StatusData extends z.ZodType = z.ZodType,
169
164
  >(
170
- task: task.New<Type, Config>,
165
+ task: task.New<Type, Config, StatusData>,
171
166
  schemas?: task.Schemas<Type, Config, StatusData>,
172
167
  ): Promise<task.Task<Type, Config, StatusData>> {
173
168
  task.key = (
@@ -189,7 +184,7 @@ export class Rack {
189
184
  }
190
185
  }
191
186
 
192
- export const ontologyID = (key: Key): ontology.ID => ({
193
- type: "rack",
194
- key: key.toString(),
195
- });
187
+ export const ontologyID = ontology.createIDFactory<Key>("rack");
188
+ export const TYPE_ONTOLOGY_ID = ontologyID(0);
189
+
190
+ export const statusKey = (key: Key): string => ontology.idToString(ontologyID(key));
@@ -7,4 +7,5 @@
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 * from "@/rack/client";
11
+ export * from "@/rack/payload";
@@ -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 * from "@/hardware/client";
10
+ export * as rack from "@/rack/external";
@@ -13,8 +13,8 @@ import { z } from "zod";
13
13
  export const keyZ = z.uint32();
14
14
  export type Key = z.infer<typeof keyZ>;
15
15
 
16
- export const statusDetailsSchema = z.object({ rack: keyZ });
17
- export const statusZ = status.statusZ(statusDetailsSchema);
16
+ export const statusDetailsZ = z.object({ rack: keyZ });
17
+ export const statusZ = status.statusZ(statusDetailsZ);
18
18
 
19
19
  export interface Status extends z.infer<typeof statusZ> {}
20
20
 
@@ -7,10 +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 { TimeStamp } from "@synnaxlabs/x";
10
11
  import { describe, expect, it } from "vitest";
11
12
  import { ZodError } from "zod";
12
13
 
13
- import { type rack } from "@/hardware/rack";
14
+ import { type rack } from "@/rack";
14
15
  import { createTestClient } from "@/testutil/client";
15
16
 
16
17
  const client = createTestClient();
@@ -18,55 +19,80 @@ const client = createTestClient();
18
19
  describe("Rack", () => {
19
20
  describe("create", () => {
20
21
  it("should create a single rack", async () => {
21
- const r = await client.hardware.racks.create({ name: "test" });
22
+ const r = await client.racks.create({ name: "test" });
22
23
  expect(r.key).toBeGreaterThan(0n);
23
24
  });
24
25
  it("should return an error if the rack doesn't have a name", async () => {
25
26
  // @ts-expect-error - Testing for error
26
- await expect(client.hardware.racks.create({})).rejects.toThrow(ZodError);
27
+ await expect(client.racks.create({})).rejects.toThrow(ZodError);
28
+ });
29
+ it("should create a rack with a custom status", async () => {
30
+ const customStatus: rack.Status = {
31
+ key: "",
32
+ name: "",
33
+ variant: "success",
34
+ message: "Custom rack status",
35
+ description: "Rack is running",
36
+ time: TimeStamp.now(),
37
+ details: { rack: 0 },
38
+ };
39
+ const r = await client.racks.create({
40
+ name: "rack-with-status",
41
+ status: customStatus,
42
+ });
43
+ expect(r.key).toBeGreaterThan(0n);
44
+ const retrieved = await client.racks.retrieve({
45
+ key: r.key,
46
+ includeStatus: true,
47
+ });
48
+ expect(retrieved.status).toBeDefined();
49
+ expect(retrieved.status?.variant).toBe("success");
50
+ expect(retrieved.status?.message).toBe("Custom rack status");
51
+ expect(retrieved.status?.description).toBe("Rack is running");
52
+ expect(retrieved.status?.details?.rack).toBe(r.key);
27
53
  });
28
54
  });
29
55
  describe("update", () => {
30
56
  it("should update a rack if the key is provided", async () => {
31
- const r = await client.hardware.racks.create({ name: "test" });
32
- const updated = await client.hardware.racks.create({
57
+ const r = await client.racks.create({ name: "test" });
58
+ const updated = await client.racks.create({
33
59
  key: r.key,
34
60
  name: "updated",
35
61
  });
36
62
  expect(updated.name).toBe("updated");
37
- const retrieved = await client.hardware.racks.retrieve({ key: r.key });
63
+ const retrieved = await client.racks.retrieve({ key: r.key });
38
64
  expect(retrieved.name).toBe("updated");
39
65
  });
40
66
  });
41
67
  describe("retrieve", () => {
42
68
  it("should retrieve a rack by its key", async () => {
43
- const r = await client.hardware.racks.create({ name: "test" });
44
- const retrieved = await client.hardware.racks.retrieve({ key: r.key });
69
+ const r = await client.racks.create({ name: "test" });
70
+ const retrieved = await client.racks.retrieve({ key: r.key });
45
71
  expect(retrieved.key).toBe(r.key);
46
72
  expect(retrieved.name).toBe("test");
47
73
  });
48
74
  it("should retrieve a rack by its name", async () => {
49
- const name = `TimeStamp.now().toString()}-${Math.random()}`;
50
- const r = await client.hardware.racks.create({ name });
51
- const retrieved = await client.hardware.racks.retrieve({ name });
75
+ const name = `${TimeStamp.now().toString()}-${Math.random()}`;
76
+ const r = await client.racks.create({ name });
77
+ const retrieved = await client.racks.retrieve({ name });
52
78
  expect(retrieved.key).toBe(r.key);
53
79
  expect(retrieved.name).toEqual(name);
54
80
  });
55
81
  });
56
82
  describe("tasks", () => {
57
83
  it("should list the tasks on a rack", async () => {
58
- const r = await client.hardware.racks.create({ name: "test" });
84
+ const r = await client.racks.create({ name: "test" });
59
85
  const tasks = await r.listTasks();
60
86
  expect(tasks).toHaveLength(0);
61
87
  });
62
88
  });
63
89
  describe("status", () => {
64
90
  it("should include the rack's status when includeStatus is true", async () => {
65
- const r = await client.hardware.racks.create({ name: "test" });
91
+ const r = await client.racks.create({ name: "test" });
66
92
  let status: rack.Status | undefined;
67
93
  await expect
68
94
  .poll(async () => {
69
- const retrieved = await client.hardware.racks.retrieve({
95
+ const retrieved = await client.racks.retrieve({
70
96
  key: r.key,
71
97
  includeStatus: true,
72
98
  });
@@ -77,12 +103,12 @@ describe("Rack", () => {
77
103
  expect(status?.details?.rack).toBe(r.key);
78
104
  });
79
105
  it("should include the status for multiple racks", async () => {
80
- const r1 = await client.hardware.racks.create({ name: "test1" });
81
- const r2 = await client.hardware.racks.create({ name: "test2" });
106
+ const r1 = await client.racks.create({ name: "test1" });
107
+ const r2 = await client.racks.create({ name: "test2" });
82
108
  let statuses: (rack.Status | undefined)[] = [];
83
109
  await expect
84
110
  .poll(async () => {
85
- const retrieved = await client.hardware.racks.retrieve({
111
+ const retrieved = await client.racks.retrieve({
86
112
  keys: [r1.key, r2.key],
87
113
  includeStatus: true,
88
114
  });