@synnaxlabs/client 0.48.0 → 0.49.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 (300) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/dist/client.cjs +33 -31
  3. package/dist/client.js +6522 -6167
  4. package/dist/src/access/client.d.ts +3 -1
  5. package/dist/src/access/client.d.ts.map +1 -1
  6. package/dist/src/access/enforce.d.ts +35 -0
  7. package/dist/src/access/enforce.d.ts.map +1 -0
  8. package/dist/src/access/enforce.spec.d.ts +2 -0
  9. package/dist/src/access/enforce.spec.d.ts.map +1 -0
  10. package/dist/src/access/external.d.ts +3 -0
  11. package/dist/src/access/external.d.ts.map +1 -1
  12. package/dist/src/access/payload.d.ts +0 -6
  13. package/dist/src/access/payload.d.ts.map +1 -1
  14. package/dist/src/access/policy/access.spec.d.ts +2 -0
  15. package/dist/src/access/policy/access.spec.d.ts.map +1 -0
  16. package/dist/src/access/policy/client.d.ts +485 -31
  17. package/dist/src/access/policy/client.d.ts.map +1 -1
  18. package/dist/src/access/policy/payload.d.ts +36 -113
  19. package/dist/src/access/policy/payload.d.ts.map +1 -1
  20. package/dist/src/access/role/client.d.ts +135 -0
  21. package/dist/src/access/role/client.d.ts.map +1 -0
  22. package/dist/src/access/role/external.d.ts.map +1 -0
  23. package/dist/src/access/role/index.d.ts +2 -0
  24. package/dist/src/access/role/index.d.ts.map +1 -0
  25. package/dist/src/access/role/payload.d.ts +27 -0
  26. package/dist/src/access/role/payload.d.ts.map +1 -0
  27. package/dist/src/access/role/role.spec.d.ts +2 -0
  28. package/dist/src/access/role/role.spec.d.ts.map +1 -0
  29. package/dist/src/arc/access.spec.d.ts +2 -0
  30. package/dist/src/arc/access.spec.d.ts.map +1 -0
  31. package/dist/src/arc/client.d.ts +5 -14
  32. package/dist/src/arc/client.d.ts.map +1 -1
  33. package/dist/src/arc/payload.d.ts +11 -2
  34. package/dist/src/arc/payload.d.ts.map +1 -1
  35. package/dist/src/auth/auth.d.ts +5 -3
  36. package/dist/src/auth/auth.d.ts.map +1 -1
  37. package/dist/src/channel/access.spec.d.ts +2 -0
  38. package/dist/src/channel/access.spec.d.ts.map +1 -0
  39. package/dist/src/channel/client.d.ts +0 -1
  40. package/dist/src/channel/client.d.ts.map +1 -1
  41. package/dist/src/channel/payload.d.ts +18 -8
  42. package/dist/src/channel/payload.d.ts.map +1 -1
  43. package/dist/src/channel/payload.spec.d.ts +2 -0
  44. package/dist/src/channel/payload.spec.d.ts.map +1 -0
  45. package/dist/src/channel/retriever.d.ts +4 -6
  46. package/dist/src/channel/retriever.d.ts.map +1 -1
  47. package/dist/src/channel/writer.d.ts.map +1 -1
  48. package/dist/src/client.d.ts +9 -5
  49. package/dist/src/client.d.ts.map +1 -1
  50. package/dist/src/device/access.spec.d.ts +2 -0
  51. package/dist/src/device/access.spec.d.ts.map +1 -0
  52. package/dist/src/{hardware/device → device}/client.d.ts +14 -7
  53. package/dist/src/device/client.d.ts.map +1 -0
  54. package/dist/src/device/device.spec.d.ts.map +1 -0
  55. package/dist/src/device/external.d.ts.map +1 -0
  56. package/dist/src/device/index.d.ts.map +1 -0
  57. package/dist/src/{hardware/device → device}/payload.d.ts +1 -1
  58. package/dist/src/device/payload.d.ts.map +1 -0
  59. package/dist/src/errors.d.ts +3 -0
  60. package/dist/src/errors.d.ts.map +1 -1
  61. package/dist/src/framer/client.d.ts +11 -1
  62. package/dist/src/framer/client.d.ts.map +1 -1
  63. package/dist/src/framer/frame.d.ts +10 -5
  64. package/dist/src/framer/frame.d.ts.map +1 -1
  65. package/dist/src/framer/iterator.d.ts +3 -3
  66. package/dist/src/framer/reader.d.ts +16 -0
  67. package/dist/src/framer/reader.d.ts.map +1 -0
  68. package/dist/src/framer/reader.spec.d.ts +2 -0
  69. package/dist/src/framer/reader.spec.d.ts.map +1 -0
  70. package/dist/src/framer/streamer.d.ts +24 -21
  71. package/dist/src/framer/streamer.d.ts.map +1 -1
  72. package/dist/src/framer/writer.d.ts +13 -13
  73. package/dist/src/index.d.ts +4 -5
  74. package/dist/src/index.d.ts.map +1 -1
  75. package/dist/src/label/access.spec.d.ts +2 -0
  76. package/dist/src/label/access.spec.d.ts.map +1 -0
  77. package/dist/src/label/client.d.ts +20 -11
  78. package/dist/src/label/client.d.ts.map +1 -1
  79. package/dist/src/ontology/client.d.ts +6 -6
  80. package/dist/src/ontology/client.d.ts.map +1 -1
  81. package/dist/src/ontology/group/access.spec.d.ts +2 -0
  82. package/dist/src/ontology/group/access.spec.d.ts.map +1 -0
  83. package/dist/src/ontology/group/client.d.ts +2 -2
  84. package/dist/src/ontology/group/client.d.ts.map +1 -1
  85. package/dist/src/ontology/group/payload.d.ts +1 -2
  86. package/dist/src/ontology/group/payload.d.ts.map +1 -1
  87. package/dist/src/ontology/payload.d.ts +23 -17
  88. package/dist/src/ontology/payload.d.ts.map +1 -1
  89. package/dist/src/ontology/writer.d.ts +10 -10
  90. package/dist/src/ontology/writer.d.ts.map +1 -1
  91. package/dist/src/rack/access.spec.d.ts +2 -0
  92. package/dist/src/rack/access.spec.d.ts.map +1 -0
  93. package/dist/src/{hardware/rack → rack}/client.d.ts +15 -8
  94. package/dist/src/rack/client.d.ts.map +1 -0
  95. package/dist/src/rack/external.d.ts.map +1 -0
  96. package/dist/src/rack/index.d.ts.map +1 -0
  97. package/dist/src/{hardware/rack → rack}/payload.d.ts +1 -1
  98. package/dist/src/rack/payload.d.ts.map +1 -0
  99. package/dist/src/rack/rack.spec.d.ts.map +1 -0
  100. package/dist/src/ranger/access.spec.d.ts +2 -0
  101. package/dist/src/ranger/access.spec.d.ts.map +1 -0
  102. package/dist/src/ranger/alias.d.ts +1 -8
  103. package/dist/src/ranger/alias.d.ts.map +1 -1
  104. package/dist/src/ranger/client.d.ts +12 -5
  105. package/dist/src/ranger/client.d.ts.map +1 -1
  106. package/dist/src/ranger/kv.d.ts +0 -3
  107. package/dist/src/ranger/kv.d.ts.map +1 -1
  108. package/dist/src/ranger/writer.d.ts +2 -2
  109. package/dist/src/ranger/writer.d.ts.map +1 -1
  110. package/dist/src/status/access.spec.d.ts +2 -0
  111. package/dist/src/status/access.spec.d.ts.map +1 -0
  112. package/dist/src/status/client.d.ts +4 -4
  113. package/dist/src/status/client.d.ts.map +1 -1
  114. package/dist/src/status/payload.d.ts +9 -2
  115. package/dist/src/status/payload.d.ts.map +1 -1
  116. package/dist/src/task/access.spec.d.ts +2 -0
  117. package/dist/src/task/access.spec.d.ts.map +1 -0
  118. package/dist/src/{hardware/task → task}/client.d.ts +26 -15
  119. package/dist/src/task/client.d.ts.map +1 -0
  120. package/dist/src/task/external.d.ts +3 -0
  121. package/dist/src/task/external.d.ts.map +1 -0
  122. package/dist/src/task/index.d.ts.map +1 -0
  123. package/dist/src/{hardware/task → task}/payload.d.ts +45 -6
  124. package/dist/src/task/payload.d.ts.map +1 -0
  125. package/dist/src/task/task.spec.d.ts.map +1 -0
  126. package/dist/src/testutil/access.d.ts +4 -0
  127. package/dist/src/testutil/access.d.ts.map +1 -0
  128. package/dist/src/transport.d.ts.map +1 -1
  129. package/dist/src/user/access.spec.d.ts +2 -0
  130. package/dist/src/user/access.spec.d.ts.map +1 -0
  131. package/dist/src/user/client.d.ts +10 -1
  132. package/dist/src/user/client.d.ts.map +1 -1
  133. package/dist/src/user/external.d.ts +1 -1
  134. package/dist/src/user/external.d.ts.map +1 -1
  135. package/dist/src/user/payload.d.ts.map +1 -1
  136. package/dist/src/workspace/access.spec.d.ts +2 -0
  137. package/dist/src/workspace/access.spec.d.ts.map +1 -0
  138. package/dist/src/workspace/client.d.ts +10 -5
  139. package/dist/src/workspace/client.d.ts.map +1 -1
  140. package/dist/src/workspace/lineplot/access.spec.d.ts +2 -0
  141. package/dist/src/workspace/lineplot/access.spec.d.ts.map +1 -0
  142. package/dist/src/workspace/lineplot/client.d.ts +8 -1
  143. package/dist/src/workspace/lineplot/client.d.ts.map +1 -1
  144. package/dist/src/workspace/log/access.spec.d.ts +2 -0
  145. package/dist/src/workspace/log/access.spec.d.ts.map +1 -0
  146. package/dist/src/workspace/log/client.d.ts +8 -1
  147. package/dist/src/workspace/log/client.d.ts.map +1 -1
  148. package/dist/src/workspace/schematic/access.spec.d.ts +2 -0
  149. package/dist/src/workspace/schematic/access.spec.d.ts.map +1 -0
  150. package/dist/src/workspace/schematic/client.d.ts +8 -1
  151. package/dist/src/workspace/schematic/client.d.ts.map +1 -1
  152. package/dist/src/workspace/schematic/symbol/access.spec.d.ts +2 -0
  153. package/dist/src/workspace/schematic/symbol/access.spec.d.ts.map +1 -0
  154. package/dist/src/workspace/schematic/symbol/client.d.ts +1 -5
  155. package/dist/src/workspace/schematic/symbol/client.d.ts.map +1 -1
  156. package/dist/src/workspace/schematic/symbol/payload.d.ts +2 -2
  157. package/dist/src/workspace/table/access.spec.d.ts +2 -0
  158. package/dist/src/workspace/table/access.spec.d.ts.map +1 -0
  159. package/dist/src/workspace/table/client.d.ts +8 -1
  160. package/dist/src/workspace/table/client.d.ts.map +1 -1
  161. package/package.json +3 -3
  162. package/src/access/client.ts +5 -2
  163. package/src/access/enforce.spec.ts +189 -0
  164. package/src/access/enforce.ts +84 -0
  165. package/src/access/external.ts +3 -0
  166. package/src/access/payload.ts +1 -13
  167. package/src/access/policy/access.spec.ts +147 -0
  168. package/src/access/policy/client.ts +21 -25
  169. package/src/access/policy/payload.ts +9 -5
  170. package/src/access/role/client.ts +135 -0
  171. package/src/access/role/external.ts +11 -0
  172. package/src/{hardware → access/role}/index.ts +1 -1
  173. package/src/access/role/payload.ts +32 -0
  174. package/src/access/role/role.spec.ts +95 -0
  175. package/src/arc/access.spec.ts +143 -0
  176. package/src/arc/client.ts +7 -31
  177. package/src/arc/payload.ts +4 -0
  178. package/src/auth/auth.ts +33 -11
  179. package/src/channel/access.spec.ts +116 -0
  180. package/src/channel/channel.spec.ts +63 -73
  181. package/src/channel/client.ts +2 -8
  182. package/src/channel/payload.spec.ts +171 -0
  183. package/src/channel/payload.ts +35 -7
  184. package/src/channel/retriever.ts +10 -11
  185. package/src/channel/writer.ts +3 -7
  186. package/src/client.ts +14 -18
  187. package/src/device/access.spec.ts +159 -0
  188. package/src/{hardware/device → device}/client.ts +12 -21
  189. package/src/{hardware/device → device}/device.spec.ts +70 -34
  190. package/src/device/external.ts +11 -0
  191. package/src/{hardware/rack → device}/index.ts +1 -1
  192. package/src/{hardware/device → device}/payload.ts +3 -3
  193. package/src/errors.ts +2 -0
  194. package/src/framer/adapter.spec.ts +14 -14
  195. package/src/framer/client.spec.ts +14 -20
  196. package/src/framer/client.ts +15 -20
  197. package/src/framer/deleter.spec.ts +1 -1
  198. package/src/framer/frame.spec.ts +131 -0
  199. package/src/framer/frame.ts +10 -2
  200. package/src/framer/iterator.ts +3 -3
  201. package/src/framer/reader.spec.ts +736 -0
  202. package/src/framer/reader.ts +265 -0
  203. package/src/framer/streamer.spec.ts +100 -12
  204. package/src/framer/streamer.ts +29 -9
  205. package/src/framer/writer.spec.ts +5 -5
  206. package/src/index.ts +4 -5
  207. package/src/label/access.spec.ts +109 -0
  208. package/src/label/client.ts +10 -14
  209. package/src/ontology/client.ts +4 -6
  210. package/src/ontology/group/access.spec.ts +77 -0
  211. package/src/ontology/group/client.ts +3 -7
  212. package/src/ontology/group/group.spec.ts +18 -0
  213. package/src/ontology/group/payload.ts +2 -2
  214. package/src/ontology/ontology.spec.ts +2 -0
  215. package/src/ontology/payload.ts +18 -2
  216. package/src/ontology/writer.ts +3 -7
  217. package/src/rack/access.spec.ts +102 -0
  218. package/src/{hardware/rack → rack}/client.ts +14 -19
  219. package/src/{hardware/device/index.ts → rack/external.ts} +2 -1
  220. package/src/{hardware/external.ts → rack/index.ts} +1 -1
  221. package/src/{hardware/rack → rack}/payload.ts +2 -2
  222. package/src/{hardware/rack → rack}/rack.spec.ts +43 -17
  223. package/src/ranger/access.spec.ts +115 -0
  224. package/src/ranger/alias.ts +6 -14
  225. package/src/ranger/client.ts +13 -14
  226. package/src/ranger/kv.ts +7 -9
  227. package/src/ranger/ranger.spec.ts +4 -4
  228. package/src/ranger/writer.ts +3 -7
  229. package/src/status/access.spec.ts +129 -0
  230. package/src/status/client.ts +5 -9
  231. package/src/status/payload.ts +3 -2
  232. package/src/task/access.spec.ts +131 -0
  233. package/src/{hardware/task → task}/client.ts +50 -25
  234. package/src/task/external.ts +11 -0
  235. package/src/{hardware/task → task}/index.ts +1 -1
  236. package/src/{hardware/task → task}/payload.ts +22 -3
  237. package/src/{hardware/task → task}/task.spec.ts +197 -34
  238. package/src/testutil/access.ts +34 -0
  239. package/src/testutil/channels.ts +3 -3
  240. package/src/transport.ts +1 -3
  241. package/src/user/access.spec.ts +107 -0
  242. package/src/user/client.ts +10 -12
  243. package/src/user/external.ts +12 -1
  244. package/src/user/payload.ts +3 -5
  245. package/src/workspace/access.spec.ts +108 -0
  246. package/src/workspace/client.ts +11 -27
  247. package/src/workspace/lineplot/access.spec.ts +134 -0
  248. package/src/workspace/lineplot/client.ts +8 -13
  249. package/src/workspace/log/access.spec.ts +134 -0
  250. package/src/workspace/log/client.ts +8 -13
  251. package/src/workspace/schematic/access.spec.ts +134 -0
  252. package/src/workspace/schematic/client.ts +9 -18
  253. package/src/workspace/schematic/symbol/access.spec.ts +172 -0
  254. package/src/workspace/schematic/symbol/client.ts +6 -17
  255. package/src/workspace/schematic/symbol/payload.ts +1 -1
  256. package/src/workspace/table/access.spec.ts +134 -0
  257. package/src/workspace/table/client.ts +8 -13
  258. package/dist/src/access/policy/policy.spec.d.ts +0 -2
  259. package/dist/src/access/policy/policy.spec.d.ts.map +0 -1
  260. package/dist/src/hardware/client.d.ts +0 -10
  261. package/dist/src/hardware/client.d.ts.map +0 -1
  262. package/dist/src/hardware/device/client.d.ts.map +0 -1
  263. package/dist/src/hardware/device/device.spec.d.ts.map +0 -1
  264. package/dist/src/hardware/device/external.d.ts.map +0 -1
  265. package/dist/src/hardware/device/index.d.ts.map +0 -1
  266. package/dist/src/hardware/device/payload.d.ts.map +0 -1
  267. package/dist/src/hardware/external.d.ts +0 -2
  268. package/dist/src/hardware/external.d.ts.map +0 -1
  269. package/dist/src/hardware/index.d.ts +0 -2
  270. package/dist/src/hardware/index.d.ts.map +0 -1
  271. package/dist/src/hardware/rack/client.d.ts.map +0 -1
  272. package/dist/src/hardware/rack/external.d.ts.map +0 -1
  273. package/dist/src/hardware/rack/index.d.ts.map +0 -1
  274. package/dist/src/hardware/rack/payload.d.ts.map +0 -1
  275. package/dist/src/hardware/rack/rack.spec.d.ts.map +0 -1
  276. package/dist/src/hardware/task/client.d.ts.map +0 -1
  277. package/dist/src/hardware/task/external.d.ts.map +0 -1
  278. package/dist/src/hardware/task/index.d.ts.map +0 -1
  279. package/dist/src/hardware/task/payload.d.ts.map +0 -1
  280. package/dist/src/hardware/task/task.spec.d.ts.map +0 -1
  281. package/dist/src/user/retriever.d.ts +0 -16
  282. package/dist/src/user/retriever.d.ts.map +0 -1
  283. package/dist/src/user/writer.d.ts +0 -11
  284. package/dist/src/user/writer.d.ts.map +0 -1
  285. package/src/access/policy/policy.spec.ts +0 -329
  286. package/src/hardware/client.ts +0 -24
  287. package/src/hardware/device/external.ts +0 -11
  288. package/src/hardware/rack/external.ts +0 -11
  289. package/src/hardware/task/external.ts +0 -11
  290. package/src/user/retriever.ts +0 -41
  291. package/src/user/writer.ts +0 -84
  292. /package/dist/src/{hardware/device → access/role}/external.d.ts +0 -0
  293. /package/dist/src/{hardware/device → device}/device.spec.d.ts +0 -0
  294. /package/dist/src/{hardware/rack → device}/external.d.ts +0 -0
  295. /package/dist/src/{hardware/device → device}/index.d.ts +0 -0
  296. /package/dist/src/{hardware/task → rack}/external.d.ts +0 -0
  297. /package/dist/src/{hardware/rack → rack}/index.d.ts +0 -0
  298. /package/dist/src/{hardware/rack → rack}/rack.spec.d.ts +0 -0
  299. /package/dist/src/{hardware/task → task}/index.d.ts +0 -0
  300. /package/dist/src/{hardware/task → task}/task.spec.d.ts +0 -0
@@ -0,0 +1,109 @@
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 { label } from "@/label";
14
+ import { createTestClientWithPolicy } from "@/testutil/access";
15
+ import { createTestClient } from "@/testutil/client";
16
+
17
+ const client = createTestClient();
18
+
19
+ describe("label", () => {
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 randomLabel = await client.labels.create({
28
+ name: "test",
29
+ color: "#E774D0",
30
+ });
31
+ await expect(
32
+ userClient.labels.retrieve({ key: randomLabel.key }),
33
+ ).rejects.toThrow(AuthError);
34
+ });
35
+
36
+ it("should allow the caller to retrieve labels with the correct policy", async () => {
37
+ const userClient = await createTestClientWithPolicy(client, {
38
+ name: "test",
39
+ objects: [label.ontologyID("")],
40
+ actions: ["retrieve"],
41
+ });
42
+ const randomLabel = await client.labels.create({
43
+ name: "test",
44
+ color: "#E774D0",
45
+ });
46
+ const retrieved = await userClient.labels.retrieve({ key: randomLabel.key });
47
+ expect(retrieved.key).toBe(randomLabel.key);
48
+ expect(retrieved.name).toBe(randomLabel.name);
49
+ expect(retrieved.color).toBe(randomLabel.color);
50
+ });
51
+
52
+ it("should allow the caller to create labels with the correct policy", async () => {
53
+ const userClient = await createTestClientWithPolicy(client, {
54
+ name: "test",
55
+ objects: [label.ontologyID("")],
56
+ actions: ["create"],
57
+ });
58
+ await userClient.labels.create({
59
+ name: "test",
60
+ color: "#E774D0",
61
+ });
62
+ });
63
+
64
+ it("should deny access when no create policy exists", async () => {
65
+ const userClient = await createTestClientWithPolicy(client, {
66
+ name: "test",
67
+ objects: [label.ontologyID("")],
68
+ actions: [],
69
+ });
70
+ await expect(
71
+ userClient.labels.create({
72
+ name: "test",
73
+ color: "#E774D0",
74
+ }),
75
+ ).rejects.toThrow(AuthError);
76
+ });
77
+
78
+ it("should allow the caller to delete labels with the correct policy", async () => {
79
+ const userClient = await createTestClientWithPolicy(client, {
80
+ name: "test",
81
+ objects: [label.ontologyID("")],
82
+ actions: ["delete"],
83
+ });
84
+ const randomLabel = await client.labels.create({
85
+ name: "test",
86
+ color: "#E774D0",
87
+ });
88
+ await userClient.labels.delete(randomLabel.key);
89
+ await expect(
90
+ userClient.labels.retrieve({ key: randomLabel.key }),
91
+ ).rejects.toThrow(NotFoundError);
92
+ });
93
+
94
+ it("should deny access when no delete policy exists", async () => {
95
+ const userClient = await createTestClientWithPolicy(client, {
96
+ name: "test",
97
+ objects: [label.ontologyID("")],
98
+ actions: [],
99
+ });
100
+ const randomLabel = await client.labels.create({
101
+ name: "test",
102
+ color: "#E774D0",
103
+ });
104
+ await expect(userClient.labels.delete(randomLabel.key)).rejects.toThrow(
105
+ AuthError,
106
+ );
107
+ });
108
+ });
109
+ });
@@ -36,18 +36,13 @@ export interface SetOptions extends Pick<SetReq, "replace"> {}
36
36
  const removeReqZ = setReqZ.omit({ replace: true });
37
37
  const emptyResZ = z.object({});
38
38
 
39
- const CREATE_ENDPOINT = "/label/create";
40
- const DELETE_ENDPOINT = "/label/delete";
41
- const SET_ENDPOINT = "/label/set";
42
- const REMOVE_ENDPOINT = "/label/remove";
43
- const RETRIEVE_ENDPOINT = "/label/retrieve";
44
-
45
39
  const retrieveRequestZ = z.object({
46
40
  keys: keyZ.array().optional(),
41
+ names: z.string().array().optional(),
47
42
  for: ontology.idZ.optional(),
48
43
  searchTerm: z.string().optional(),
49
- offset: z.number().optional(),
50
- limit: z.number().optional(),
44
+ offset: z.int().optional(),
45
+ limit: z.int().optional(),
51
46
  });
52
47
 
53
48
  const singleRetrieveArgsZ = z
@@ -76,7 +71,7 @@ export class Client {
76
71
  const isSingle = "key" in args;
77
72
  const res = await sendRequired(
78
73
  this.client,
79
- RETRIEVE_ENDPOINT,
74
+ "/label/retrieve",
80
75
  args,
81
76
  retrieveArgsZ,
82
77
  retrieveResponseZ,
@@ -88,7 +83,7 @@ export class Client {
88
83
  async label(id: ontology.ID, labels: Key[], opts: SetOptions = {}): Promise<void> {
89
84
  await sendRequired<typeof setReqZ, typeof emptyResZ>(
90
85
  this.client,
91
- SET_ENDPOINT,
86
+ "/label/set",
92
87
  { id, labels, replace: opts.replace },
93
88
  setReqZ,
94
89
  emptyResZ,
@@ -98,7 +93,7 @@ export class Client {
98
93
  async remove(id: ontology.ID, labels: Key[]): Promise<void> {
99
94
  await sendRequired<typeof removeReqZ, typeof emptyResZ>(
100
95
  this.client,
101
- REMOVE_ENDPOINT,
96
+ "/label/remove",
102
97
  { id, labels },
103
98
  removeReqZ,
104
99
  emptyResZ,
@@ -111,7 +106,7 @@ export class Client {
111
106
  const isMany = Array.isArray(labels);
112
107
  const res = await sendRequired<typeof createReqZ, typeof createResZ>(
113
108
  this.client,
114
- CREATE_ENDPOINT,
109
+ "/label/create",
115
110
  { labels: array.toArray(labels) },
116
111
  createReqZ,
117
112
  createResZ,
@@ -122,7 +117,7 @@ export class Client {
122
117
  async delete(keys: Key | Key[]): Promise<void> {
123
118
  await sendRequired<typeof deleteReqZ, typeof emptyResZ>(
124
119
  this.client,
125
- DELETE_ENDPOINT,
120
+ "/label/delete",
126
121
  { keys: array.toArray(keys) },
127
122
  deleteReqZ,
128
123
  emptyResZ,
@@ -130,4 +125,5 @@ export class Client {
130
125
  }
131
126
  }
132
127
 
133
- export const ontologyID = (key: Key): ontology.ID => ({ type: "label", key });
128
+ export const ontologyID = ontology.createIDFactory<Key>("label");
129
+ export const TYPE_ONTOLOGY_ID = ontologyID("");
@@ -25,17 +25,15 @@ import {
25
25
  } from "@/ontology/payload";
26
26
  import { Writer } from "@/ontology/writer";
27
27
 
28
- const RETRIEVE_ENDPOINT = "/ontology/retrieve";
29
-
30
28
  const retrieveReqZ = z.object({
31
29
  ids: idZ.array().optional(),
32
30
  children: z.boolean().optional(),
33
31
  parents: z.boolean().optional(),
34
32
  excludeFieldData: z.boolean().optional(),
35
- searchTerm: z.string().optional(),
36
- limit: z.number().optional(),
37
- offset: z.number().optional(),
38
33
  types: resourceTypeZ.array().optional(),
34
+ searchTerm: z.string().optional(),
35
+ limit: z.int().optional(),
36
+ offset: z.int().optional(),
39
37
  });
40
38
  export interface RetrieveRequest extends z.infer<typeof retrieveReqZ> {}
41
39
 
@@ -174,7 +172,7 @@ export class Client {
174
172
  private async execRetrieve(request: RetrieveRequest): Promise<Resource[]> {
175
173
  const { resources } = await sendRequired(
176
174
  this.client,
177
- RETRIEVE_ENDPOINT,
175
+ "/ontology/retrieve",
178
176
  request,
179
177
  retrieveReqZ,
180
178
  retrieveResZ,
@@ -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";