@synnaxlabs/client 0.48.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 (294) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/dist/client.cjs +31 -31
  3. package/dist/client.js +4836 -4644
  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 +8 -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/streamer.d.ts +24 -21
  67. package/dist/src/framer/streamer.d.ts.map +1 -1
  68. package/dist/src/framer/writer.d.ts +13 -13
  69. package/dist/src/index.d.ts +4 -5
  70. package/dist/src/index.d.ts.map +1 -1
  71. package/dist/src/label/access.spec.d.ts +2 -0
  72. package/dist/src/label/access.spec.d.ts.map +1 -0
  73. package/dist/src/label/client.d.ts +20 -11
  74. package/dist/src/label/client.d.ts.map +1 -1
  75. package/dist/src/ontology/client.d.ts +6 -6
  76. package/dist/src/ontology/client.d.ts.map +1 -1
  77. package/dist/src/ontology/group/access.spec.d.ts +2 -0
  78. package/dist/src/ontology/group/access.spec.d.ts.map +1 -0
  79. package/dist/src/ontology/group/client.d.ts +2 -2
  80. package/dist/src/ontology/group/client.d.ts.map +1 -1
  81. package/dist/src/ontology/group/payload.d.ts +1 -2
  82. package/dist/src/ontology/group/payload.d.ts.map +1 -1
  83. package/dist/src/ontology/payload.d.ts +23 -17
  84. package/dist/src/ontology/payload.d.ts.map +1 -1
  85. package/dist/src/ontology/writer.d.ts +10 -10
  86. package/dist/src/ontology/writer.d.ts.map +1 -1
  87. package/dist/src/rack/access.spec.d.ts +2 -0
  88. package/dist/src/rack/access.spec.d.ts.map +1 -0
  89. package/dist/src/{hardware/rack → rack}/client.d.ts +15 -8
  90. package/dist/src/rack/client.d.ts.map +1 -0
  91. package/dist/src/rack/external.d.ts.map +1 -0
  92. package/dist/src/rack/index.d.ts.map +1 -0
  93. package/dist/src/{hardware/rack → rack}/payload.d.ts +1 -1
  94. package/dist/src/rack/payload.d.ts.map +1 -0
  95. package/dist/src/rack/rack.spec.d.ts.map +1 -0
  96. package/dist/src/ranger/access.spec.d.ts +2 -0
  97. package/dist/src/ranger/access.spec.d.ts.map +1 -0
  98. package/dist/src/ranger/alias.d.ts +1 -8
  99. package/dist/src/ranger/alias.d.ts.map +1 -1
  100. package/dist/src/ranger/client.d.ts +12 -5
  101. package/dist/src/ranger/client.d.ts.map +1 -1
  102. package/dist/src/ranger/kv.d.ts +0 -3
  103. package/dist/src/ranger/kv.d.ts.map +1 -1
  104. package/dist/src/ranger/writer.d.ts +2 -2
  105. package/dist/src/ranger/writer.d.ts.map +1 -1
  106. package/dist/src/status/access.spec.d.ts +2 -0
  107. package/dist/src/status/access.spec.d.ts.map +1 -0
  108. package/dist/src/status/client.d.ts +4 -4
  109. package/dist/src/status/client.d.ts.map +1 -1
  110. package/dist/src/status/payload.d.ts +9 -2
  111. package/dist/src/status/payload.d.ts.map +1 -1
  112. package/dist/src/task/access.spec.d.ts +2 -0
  113. package/dist/src/task/access.spec.d.ts.map +1 -0
  114. package/dist/src/{hardware/task → task}/client.d.ts +26 -15
  115. package/dist/src/task/client.d.ts.map +1 -0
  116. package/dist/src/task/external.d.ts +3 -0
  117. package/dist/src/task/external.d.ts.map +1 -0
  118. package/dist/src/task/index.d.ts.map +1 -0
  119. package/dist/src/{hardware/task → task}/payload.d.ts +45 -6
  120. package/dist/src/task/payload.d.ts.map +1 -0
  121. package/dist/src/task/task.spec.d.ts.map +1 -0
  122. package/dist/src/testutil/access.d.ts +4 -0
  123. package/dist/src/testutil/access.d.ts.map +1 -0
  124. package/dist/src/transport.d.ts.map +1 -1
  125. package/dist/src/user/access.spec.d.ts +2 -0
  126. package/dist/src/user/access.spec.d.ts.map +1 -0
  127. package/dist/src/user/client.d.ts +10 -1
  128. package/dist/src/user/client.d.ts.map +1 -1
  129. package/dist/src/user/external.d.ts +1 -1
  130. package/dist/src/user/external.d.ts.map +1 -1
  131. package/dist/src/user/payload.d.ts.map +1 -1
  132. package/dist/src/workspace/access.spec.d.ts +2 -0
  133. package/dist/src/workspace/access.spec.d.ts.map +1 -0
  134. package/dist/src/workspace/client.d.ts +10 -5
  135. package/dist/src/workspace/client.d.ts.map +1 -1
  136. package/dist/src/workspace/lineplot/access.spec.d.ts +2 -0
  137. package/dist/src/workspace/lineplot/access.spec.d.ts.map +1 -0
  138. package/dist/src/workspace/lineplot/client.d.ts +8 -1
  139. package/dist/src/workspace/lineplot/client.d.ts.map +1 -1
  140. package/dist/src/workspace/log/access.spec.d.ts +2 -0
  141. package/dist/src/workspace/log/access.spec.d.ts.map +1 -0
  142. package/dist/src/workspace/log/client.d.ts +8 -1
  143. package/dist/src/workspace/log/client.d.ts.map +1 -1
  144. package/dist/src/workspace/schematic/access.spec.d.ts +2 -0
  145. package/dist/src/workspace/schematic/access.spec.d.ts.map +1 -0
  146. package/dist/src/workspace/schematic/client.d.ts +8 -1
  147. package/dist/src/workspace/schematic/client.d.ts.map +1 -1
  148. package/dist/src/workspace/schematic/symbol/access.spec.d.ts +2 -0
  149. package/dist/src/workspace/schematic/symbol/access.spec.d.ts.map +1 -0
  150. package/dist/src/workspace/schematic/symbol/client.d.ts +1 -5
  151. package/dist/src/workspace/schematic/symbol/client.d.ts.map +1 -1
  152. package/dist/src/workspace/schematic/symbol/payload.d.ts +2 -2
  153. package/dist/src/workspace/table/access.spec.d.ts +2 -0
  154. package/dist/src/workspace/table/access.spec.d.ts.map +1 -0
  155. package/dist/src/workspace/table/client.d.ts +8 -1
  156. package/dist/src/workspace/table/client.d.ts.map +1 -1
  157. package/package.json +3 -3
  158. package/src/access/client.ts +5 -2
  159. package/src/access/enforce.spec.ts +189 -0
  160. package/src/access/enforce.ts +84 -0
  161. package/src/access/external.ts +3 -0
  162. package/src/access/payload.ts +1 -13
  163. package/src/access/policy/access.spec.ts +147 -0
  164. package/src/access/policy/client.ts +21 -25
  165. package/src/access/policy/payload.ts +9 -5
  166. package/src/access/role/client.ts +135 -0
  167. package/src/access/role/external.ts +11 -0
  168. package/src/{hardware → access/role}/index.ts +1 -1
  169. package/src/access/role/payload.ts +32 -0
  170. package/src/access/role/role.spec.ts +95 -0
  171. package/src/arc/access.spec.ts +143 -0
  172. package/src/arc/client.ts +7 -31
  173. package/src/arc/payload.ts +4 -0
  174. package/src/auth/auth.ts +33 -11
  175. package/src/channel/access.spec.ts +116 -0
  176. package/src/channel/channel.spec.ts +63 -73
  177. package/src/channel/client.ts +2 -8
  178. package/src/channel/payload.spec.ts +171 -0
  179. package/src/channel/payload.ts +35 -7
  180. package/src/channel/retriever.ts +10 -11
  181. package/src/channel/writer.ts +3 -7
  182. package/src/client.ts +14 -18
  183. package/src/device/access.spec.ts +159 -0
  184. package/src/{hardware/device → device}/client.ts +12 -21
  185. package/src/{hardware/device → device}/device.spec.ts +70 -34
  186. package/src/device/external.ts +11 -0
  187. package/src/{hardware/rack → device}/index.ts +1 -1
  188. package/src/{hardware/device → device}/payload.ts +3 -3
  189. package/src/errors.ts +2 -0
  190. package/src/framer/adapter.spec.ts +14 -14
  191. package/src/framer/client.spec.ts +14 -20
  192. package/src/framer/client.ts +3 -5
  193. package/src/framer/deleter.spec.ts +1 -1
  194. package/src/framer/frame.spec.ts +131 -0
  195. package/src/framer/frame.ts +10 -2
  196. package/src/framer/iterator.ts +3 -3
  197. package/src/framer/streamer.spec.ts +100 -12
  198. package/src/framer/streamer.ts +29 -9
  199. package/src/framer/writer.spec.ts +5 -5
  200. package/src/index.ts +4 -5
  201. package/src/label/access.spec.ts +109 -0
  202. package/src/label/client.ts +10 -14
  203. package/src/ontology/client.ts +4 -6
  204. package/src/ontology/group/access.spec.ts +77 -0
  205. package/src/ontology/group/client.ts +3 -7
  206. package/src/ontology/group/group.spec.ts +18 -0
  207. package/src/ontology/group/payload.ts +2 -2
  208. package/src/ontology/ontology.spec.ts +2 -0
  209. package/src/ontology/payload.ts +18 -2
  210. package/src/ontology/writer.ts +3 -7
  211. package/src/rack/access.spec.ts +102 -0
  212. package/src/{hardware/rack → rack}/client.ts +14 -19
  213. package/src/{hardware/device/index.ts → rack/external.ts} +2 -1
  214. package/src/{hardware/external.ts → rack/index.ts} +1 -1
  215. package/src/{hardware/rack → rack}/payload.ts +2 -2
  216. package/src/{hardware/rack → rack}/rack.spec.ts +43 -17
  217. package/src/ranger/access.spec.ts +115 -0
  218. package/src/ranger/alias.ts +6 -14
  219. package/src/ranger/client.ts +13 -14
  220. package/src/ranger/kv.ts +7 -9
  221. package/src/ranger/ranger.spec.ts +4 -4
  222. package/src/ranger/writer.ts +3 -7
  223. package/src/status/access.spec.ts +129 -0
  224. package/src/status/client.ts +5 -9
  225. package/src/status/payload.ts +3 -2
  226. package/src/task/access.spec.ts +131 -0
  227. package/src/{hardware/task → task}/client.ts +50 -25
  228. package/src/task/external.ts +11 -0
  229. package/src/{hardware/task → task}/index.ts +1 -1
  230. package/src/{hardware/task → task}/payload.ts +22 -3
  231. package/src/{hardware/task → task}/task.spec.ts +197 -34
  232. package/src/testutil/access.ts +34 -0
  233. package/src/testutil/channels.ts +3 -3
  234. package/src/transport.ts +1 -3
  235. package/src/user/access.spec.ts +107 -0
  236. package/src/user/client.ts +10 -12
  237. package/src/user/external.ts +12 -1
  238. package/src/user/payload.ts +3 -5
  239. package/src/workspace/access.spec.ts +108 -0
  240. package/src/workspace/client.ts +11 -27
  241. package/src/workspace/lineplot/access.spec.ts +134 -0
  242. package/src/workspace/lineplot/client.ts +8 -13
  243. package/src/workspace/log/access.spec.ts +134 -0
  244. package/src/workspace/log/client.ts +8 -13
  245. package/src/workspace/schematic/access.spec.ts +134 -0
  246. package/src/workspace/schematic/client.ts +9 -18
  247. package/src/workspace/schematic/symbol/access.spec.ts +172 -0
  248. package/src/workspace/schematic/symbol/client.ts +6 -17
  249. package/src/workspace/schematic/symbol/payload.ts +1 -1
  250. package/src/workspace/table/access.spec.ts +134 -0
  251. package/src/workspace/table/client.ts +8 -13
  252. package/dist/src/access/policy/policy.spec.d.ts +0 -2
  253. package/dist/src/access/policy/policy.spec.d.ts.map +0 -1
  254. package/dist/src/hardware/client.d.ts +0 -10
  255. package/dist/src/hardware/client.d.ts.map +0 -1
  256. package/dist/src/hardware/device/client.d.ts.map +0 -1
  257. package/dist/src/hardware/device/device.spec.d.ts.map +0 -1
  258. package/dist/src/hardware/device/external.d.ts.map +0 -1
  259. package/dist/src/hardware/device/index.d.ts.map +0 -1
  260. package/dist/src/hardware/device/payload.d.ts.map +0 -1
  261. package/dist/src/hardware/external.d.ts +0 -2
  262. package/dist/src/hardware/external.d.ts.map +0 -1
  263. package/dist/src/hardware/index.d.ts +0 -2
  264. package/dist/src/hardware/index.d.ts.map +0 -1
  265. package/dist/src/hardware/rack/client.d.ts.map +0 -1
  266. package/dist/src/hardware/rack/external.d.ts.map +0 -1
  267. package/dist/src/hardware/rack/index.d.ts.map +0 -1
  268. package/dist/src/hardware/rack/payload.d.ts.map +0 -1
  269. package/dist/src/hardware/rack/rack.spec.d.ts.map +0 -1
  270. package/dist/src/hardware/task/client.d.ts.map +0 -1
  271. package/dist/src/hardware/task/external.d.ts.map +0 -1
  272. package/dist/src/hardware/task/index.d.ts.map +0 -1
  273. package/dist/src/hardware/task/payload.d.ts.map +0 -1
  274. package/dist/src/hardware/task/task.spec.d.ts.map +0 -1
  275. package/dist/src/user/retriever.d.ts +0 -16
  276. package/dist/src/user/retriever.d.ts.map +0 -1
  277. package/dist/src/user/writer.d.ts +0 -11
  278. package/dist/src/user/writer.d.ts.map +0 -1
  279. package/src/access/policy/policy.spec.ts +0 -329
  280. package/src/hardware/client.ts +0 -24
  281. package/src/hardware/device/external.ts +0 -11
  282. package/src/hardware/rack/external.ts +0 -11
  283. package/src/hardware/task/external.ts +0 -11
  284. package/src/user/retriever.ts +0 -41
  285. package/src/user/writer.ts +0 -84
  286. /package/dist/src/{hardware/device → access/role}/external.d.ts +0 -0
  287. /package/dist/src/{hardware/device → device}/device.spec.d.ts +0 -0
  288. /package/dist/src/{hardware/rack → device}/external.d.ts +0 -0
  289. /package/dist/src/{hardware/device → device}/index.d.ts +0 -0
  290. /package/dist/src/{hardware/task → rack}/external.d.ts +0 -0
  291. /package/dist/src/{hardware/rack → rack}/index.d.ts +0 -0
  292. /package/dist/src/{hardware/rack → rack}/rack.spec.d.ts +0 -0
  293. /package/dist/src/{hardware/task → task}/index.d.ts +0 -0
  294. /package/dist/src/{hardware/task → task}/task.spec.d.ts +0 -0
@@ -7,16 +7,17 @@
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, TimeStamp } from "@synnaxlabs/x";
10
+ import { TimeStamp } from "@synnaxlabs/x";
11
11
  import { beforeAll, describe, expect, it } from "vitest";
12
12
 
13
- import { task } from "@/hardware/task";
13
+ import { ontology } from "@/ontology";
14
+ import { task } from "@/task";
14
15
  import { createTestClient } from "@/testutil/client";
15
16
 
16
17
  const client = createTestClient();
17
18
 
18
19
  describe("Task", async () => {
19
- const testRack = await client.hardware.racks.create({ name: "test" });
20
+ const testRack = await client.racks.create({ name: "test" });
20
21
  describe("create", () => {
21
22
  it("should create a task on a rack", async () => {
22
23
  const m = await testRack.createTask({
@@ -44,6 +45,31 @@ describe("Task", async () => {
44
45
  });
45
46
  expect(m.config).toStrictEqual(config);
46
47
  });
48
+ it("should create a task with a custom status", async () => {
49
+ const m = await testRack.createTask({
50
+ name: "task-with-status",
51
+ config: { test: true },
52
+ type: "ni",
53
+ status: {
54
+ variant: "success",
55
+ message: "Custom task status",
56
+ description: "Task is running",
57
+ time: TimeStamp.now(),
58
+ details: { running: true, data: { customData: true } },
59
+ },
60
+ });
61
+ expect(m.key).not.toHaveLength(0);
62
+ const retrieved = await client.tasks.retrieve({
63
+ key: m.key,
64
+ includeStatus: true,
65
+ });
66
+ expect(retrieved.status).toBeDefined();
67
+ expect(retrieved.status?.variant).toBe("success");
68
+ expect(retrieved.status?.message).toBe("Custom task status");
69
+ expect(retrieved.status?.description).toBe("Task is running");
70
+ expect(retrieved.status?.details?.task).toBe(m.key);
71
+ expect(retrieved.status?.details?.running).toBe(true);
72
+ });
47
73
  });
48
74
  describe("update", () => {
49
75
  it("should update a task if the key is provided", async () => {
@@ -52,12 +78,12 @@ describe("Task", async () => {
52
78
  config: { a: "dog" },
53
79
  type: "ni",
54
80
  });
55
- const updated = await client.hardware.tasks.create({
81
+ const updated = await client.tasks.create({
56
82
  ...m,
57
83
  name: "updated",
58
84
  });
59
85
  expect(updated.name).toBe("updated");
60
- const retrieved = await client.hardware.tasks.retrieve({ key: m.key });
86
+ const retrieved = await client.tasks.retrieve({ key: m.key });
61
87
  expect(retrieved.name).toBe("updated");
62
88
  });
63
89
  });
@@ -68,7 +94,7 @@ describe("Task", async () => {
68
94
  config: { a: "dog" },
69
95
  type: "ni",
70
96
  });
71
- const retrieved = await client.hardware.tasks.retrieve({ key: m.key });
97
+ const retrieved = await client.tasks.retrieve({ key: m.key });
72
98
  expect(retrieved.key).toBe(m.key);
73
99
  expect(retrieved.name).toBe("test");
74
100
  expect(retrieved.config).toStrictEqual({ a: "dog" });
@@ -78,31 +104,29 @@ describe("Task", async () => {
78
104
  it("should retrieve a task by its name", async () => {
79
105
  const name = `test-${Date.now()}-${Math.random()}`;
80
106
  const m = await testRack.createTask({ name, config: { a: "dog" }, type: "ni" });
81
- const retrieved = await client.hardware.tasks.retrieve({ name });
107
+ const retrieved = await client.tasks.retrieve({ name });
82
108
  expect(retrieved.key).toBe(m.key);
83
109
  });
84
110
 
85
- describe("state", () => {
111
+ describe("status", () => {
86
112
  it("should include task status when requested", async () => {
87
113
  const t = await testRack.createTask({
88
114
  name: "test",
89
115
  config: { a: "dog" },
90
116
  type: "ni",
91
117
  });
92
- const w = await client.openWriter([task.STATUS_CHANNEL_NAME]);
93
118
  const communicatedStatus: task.Status = {
94
- key: id.create(),
119
+ key: ontology.idToString(task.ontologyID(t.key)),
95
120
  name: "test",
96
121
  variant: "success",
97
122
  details: { task: t.key, running: false, data: {} },
98
123
  message: "test",
99
124
  time: TimeStamp.now(),
100
125
  };
101
- await w.write(task.STATUS_CHANNEL_NAME, [communicatedStatus]);
102
- await w.close();
126
+ await client.statuses.set(communicatedStatus);
103
127
  await expect
104
128
  .poll(async () => {
105
- const retrieved = await client.hardware.tasks.retrieve({
129
+ const retrieved = await client.tasks.retrieve({
106
130
  key: t.key,
107
131
  includeStatus: true,
108
132
  });
@@ -117,7 +141,7 @@ describe("Task", async () => {
117
141
  let secondRack: any;
118
142
 
119
143
  beforeAll(async () => {
120
- secondRack = await client.hardware.racks.create({ name: "test-rack-2" });
144
+ secondRack = await client.racks.create({ name: "test-rack-2" });
121
145
 
122
146
  const taskConfigs = [
123
147
  { name: "sensor_task1", type: "ni", rack: testRack },
@@ -138,7 +162,7 @@ describe("Task", async () => {
138
162
  });
139
163
 
140
164
  it("should retrieve tasks by rack", async () => {
141
- const result = await client.hardware.tasks.retrieve({
165
+ const result = await client.tasks.retrieve({
142
166
  rack: testRack.key,
143
167
  });
144
168
  expect(result.length).toBeGreaterThanOrEqual(3);
@@ -147,7 +171,7 @@ describe("Task", async () => {
147
171
 
148
172
  it("should retrieve tasks by multiple keys", async () => {
149
173
  const keysToQuery = testTasks.slice(0, 2).map((t) => t.key);
150
- const result = await client.hardware.tasks.retrieve({
174
+ const result = await client.tasks.retrieve({
151
175
  keys: keysToQuery,
152
176
  });
153
177
  expect(result).toHaveLength(2);
@@ -156,7 +180,7 @@ describe("Task", async () => {
156
180
 
157
181
  it("should retrieve tasks by multiple names", async () => {
158
182
  const namesToQuery = ["sensor_task1", "actuator_task1"];
159
- const result = await client.hardware.tasks.retrieve({
183
+ const result = await client.tasks.retrieve({
160
184
  names: namesToQuery,
161
185
  });
162
186
  expect(result.length).toBeGreaterThanOrEqual(2);
@@ -164,7 +188,7 @@ describe("Task", async () => {
164
188
  });
165
189
 
166
190
  it("should retrieve tasks by types", async () => {
167
- const result = await client.hardware.tasks.retrieve({
191
+ const result = await client.tasks.retrieve({
168
192
  types: ["ni"],
169
193
  });
170
194
  expect(result.length).toBeGreaterThanOrEqual(2);
@@ -173,7 +197,7 @@ describe("Task", async () => {
173
197
 
174
198
  it("should retrieve tasks by multiple types", async () => {
175
199
  const typesToQuery = ["ni", "labjack"];
176
- const result = await client.hardware.tasks.retrieve({
200
+ const result = await client.tasks.retrieve({
177
201
  types: typesToQuery,
178
202
  });
179
203
  expect(result.length).toBeGreaterThanOrEqual(4);
@@ -181,7 +205,7 @@ describe("Task", async () => {
181
205
  });
182
206
 
183
207
  it("should support pagination with limit and offset", async () => {
184
- const firstPage = await client.hardware.tasks.retrieve({
208
+ const firstPage = await client.tasks.retrieve({
185
209
  rack: testRack.key,
186
210
  limit: 2,
187
211
  offset: 0,
@@ -189,7 +213,7 @@ describe("Task", async () => {
189
213
  expect(firstPage.length).toBeLessThanOrEqual(2);
190
214
 
191
215
  if (firstPage.length === 2) {
192
- const secondPage = await client.hardware.tasks.retrieve({
216
+ const secondPage = await client.tasks.retrieve({
193
217
  rack: testRack.key,
194
218
  limit: 2,
195
219
  offset: 2,
@@ -204,7 +228,7 @@ describe("Task", async () => {
204
228
  });
205
229
 
206
230
  it("should support combined filters", async () => {
207
- const result = await client.hardware.tasks.retrieve({
231
+ const result = await client.tasks.retrieve({
208
232
  rack: testRack.key,
209
233
  types: ["ni"],
210
234
  includeStatus: true,
@@ -214,7 +238,7 @@ describe("Task", async () => {
214
238
 
215
239
  await expect
216
240
  .poll(async () => {
217
- const tasks = await client.hardware.tasks.retrieve({
241
+ const tasks = await client.tasks.retrieve({
218
242
  rack: testRack.key,
219
243
  types: ["ni"],
220
244
  includeStatus: true,
@@ -225,14 +249,14 @@ describe("Task", async () => {
225
249
  });
226
250
 
227
251
  it("should handle empty results gracefully", async () => {
228
- const result = await client.hardware.tasks.retrieve({
252
+ const result = await client.tasks.retrieve({
229
253
  types: ["nonexistent_type"],
230
254
  });
231
255
  expect(result).toEqual([]);
232
256
  });
233
257
 
234
258
  it("should combine rack and type filters", async () => {
235
- const result = await client.hardware.tasks.retrieve({
259
+ const result = await client.tasks.retrieve({
236
260
  rack: secondRack.key,
237
261
  types: ["labjack"],
238
262
  });
@@ -241,14 +265,14 @@ describe("Task", async () => {
241
265
  });
242
266
 
243
267
  it("should handle limit without offset", async () => {
244
- const result = await client.hardware.tasks.retrieve({
268
+ const result = await client.tasks.retrieve({
245
269
  limit: 1,
246
270
  });
247
271
  expect(result).toHaveLength(1);
248
272
  });
249
273
 
250
274
  it("should retrieve tasks with includeStatus in request object", async () => {
251
- const result = await client.hardware.tasks.retrieve({
275
+ const result = await client.tasks.retrieve({
252
276
  rack: testRack.key,
253
277
  includeStatus: true,
254
278
  });
@@ -256,7 +280,7 @@ describe("Task", async () => {
256
280
 
257
281
  await expect
258
282
  .poll(async () => {
259
- const tasks = await client.hardware.tasks.retrieve({
283
+ const tasks = await client.tasks.retrieve({
260
284
  rack: testRack.key,
261
285
  includeStatus: true,
262
286
  });
@@ -272,19 +296,19 @@ describe("Task", async () => {
272
296
  config: { test: true },
273
297
  });
274
298
 
275
- const snapshotTask = await client.hardware.tasks.copy(
299
+ const snapshotTask = await client.tasks.copy(
276
300
  regularTask.key,
277
301
  "snapshot_test_task",
278
302
  true,
279
303
  );
280
304
 
281
- const snapshotOnlyResult = await client.hardware.tasks.retrieve({
305
+ const snapshotOnlyResult = await client.tasks.retrieve({
282
306
  snapshot: true,
283
307
  });
284
308
  expect(snapshotOnlyResult.some((t) => t.key === snapshotTask.key)).toBe(true);
285
309
  expect(snapshotOnlyResult.every((t) => t.snapshot === true)).toBe(true);
286
310
 
287
- const regularOnlyResult = await client.hardware.tasks.retrieve({
311
+ const regularOnlyResult = await client.tasks.retrieve({
288
312
  snapshot: false,
289
313
  });
290
314
  expect(regularOnlyResult.some((t) => t.key === regularTask.key)).toBe(true);
@@ -298,7 +322,7 @@ describe("Task", async () => {
298
322
  config: { test: true },
299
323
  });
300
324
 
301
- const result = await client.hardware.tasks.retrieve({
325
+ const result = await client.tasks.retrieve({
302
326
  rack: testRack.key,
303
327
  types: ["ni"],
304
328
  snapshot: false,
@@ -311,6 +335,35 @@ describe("Task", async () => {
311
335
  });
312
336
  });
313
337
 
338
+ describe("list", () => {
339
+ it("should list all tasks excluding internal tasks", async () => {
340
+ const tasks = await client.tasks.list();
341
+ expect(Array.isArray(tasks)).toBe(true);
342
+ expect(tasks.every((t) => t.internal === false)).toBe(true);
343
+ });
344
+
345
+ it("should list tasks on a specific rack", async () => {
346
+ const task1 = await testRack.createTask({
347
+ name: `list-test-${Date.now()}`,
348
+ config: { test: true },
349
+ type: "ni",
350
+ });
351
+ const tasks = await client.tasks.list(testRack.key);
352
+ expect(tasks.some((t) => t.key === task1.key)).toBe(true);
353
+ expect(tasks.every((t) => task.rackKey(t.key) === testRack.key)).toBe(true);
354
+ });
355
+
356
+ it("should exclude internal tasks by default", async () => {
357
+ const allTasks = await client.tasks.list();
358
+ const internalTasks = await client.tasks.retrieve({
359
+ internal: true,
360
+ });
361
+ const allTaskKeys = allTasks.map((t) => t.key);
362
+ const internalTaskKeys = internalTasks.map((t) => t.key);
363
+ expect(internalTaskKeys.every((key) => !allTaskKeys.includes(key))).toBe(true);
364
+ });
365
+ });
366
+
314
367
  describe("copy", () => {
315
368
  it("should correctly copy the task", async () => {
316
369
  const m = await testRack.createTask({
@@ -318,12 +371,122 @@ describe("Task", async () => {
318
371
  config: { a: "dog" },
319
372
  type: "ni",
320
373
  });
321
- const copy = await client.hardware.tasks.copy(m.key, "New Name", false);
374
+ const copy = await client.tasks.copy(m.key, "New Name", false);
322
375
  expect(copy.name).toBe("New Name");
323
376
  expect(copy.config).toStrictEqual({ a: "dog" });
324
377
  });
325
378
  });
326
379
 
380
+ describe("lifecycle methods", () => {
381
+ it("should start a task", async () => {
382
+ const t = await testRack.createTask({
383
+ name: "lifecycle-start-test",
384
+ config: {},
385
+ type: "ni",
386
+ });
387
+ const streamer = await client.openStreamer(task.COMMAND_CHANNEL_NAME);
388
+ await t.start();
389
+ await expect
390
+ .poll<Promise<task.Command>>(async () => {
391
+ const fr = await streamer.read();
392
+ const sample = fr.at(-1)[task.COMMAND_CHANNEL_NAME];
393
+ return task.commandZ.parse(sample);
394
+ })
395
+ .toMatchObject({ task: t.key, type: "start" });
396
+ streamer.close();
397
+ });
398
+
399
+ it("should stop a task", async () => {
400
+ const t = await testRack.createTask({
401
+ name: "lifecycle-stop-test",
402
+ config: {},
403
+ type: "ni",
404
+ });
405
+ const streamer = await client.openStreamer(task.COMMAND_CHANNEL_NAME);
406
+ await t.stop();
407
+ await expect
408
+ .poll<Promise<task.Command>>(async () => {
409
+ const fr = await streamer.read();
410
+ const sample = fr.at(-1)[task.COMMAND_CHANNEL_NAME];
411
+ return task.commandZ.parse(sample);
412
+ })
413
+ .toMatchObject({ task: t.key, type: "stop" });
414
+ streamer.close();
415
+ });
416
+
417
+ it("should run a task with automatic start and stop", async () => {
418
+ const t = await testRack.createTask({
419
+ name: "lifecycle-run-test",
420
+ config: {},
421
+ type: "ni",
422
+ });
423
+ const streamer = await client.openStreamer(task.COMMAND_CHANNEL_NAME);
424
+ let executedCallback = false;
425
+
426
+ await t.run(async () => {
427
+ executedCallback = true;
428
+ });
429
+
430
+ expect(executedCallback).toBe(true);
431
+
432
+ // Should have received both start and stop commands
433
+ const commands: task.Command[] = [];
434
+ await expect
435
+ .poll(async () => {
436
+ try {
437
+ const fr = await streamer.read();
438
+ const samples = fr.get(task.COMMAND_CHANNEL_NAME);
439
+ for (const sample of samples) commands.push(task.commandZ.parse(sample));
440
+
441
+ return (
442
+ commands.some((c) => c.task === t.key && c.type === "start") &&
443
+ commands.some((c) => c.task === t.key && c.type === "stop")
444
+ );
445
+ } catch {
446
+ return false;
447
+ }
448
+ })
449
+ .toBe(true);
450
+
451
+ streamer.close();
452
+ });
453
+
454
+ it("should stop task even if callback throws error", async () => {
455
+ const t = await testRack.createTask({
456
+ name: "lifecycle-run-error-test",
457
+ config: {},
458
+ type: "ni",
459
+ });
460
+ const streamer = await client.openStreamer(task.COMMAND_CHANNEL_NAME);
461
+
462
+ await expect(
463
+ t.run(async () => {
464
+ throw new Error("Test error");
465
+ }),
466
+ ).rejects.toThrow("Test error");
467
+
468
+ // Should still have received stop command
469
+ const stopCommands: task.Command[] = [];
470
+ await expect
471
+ .poll(async () => {
472
+ try {
473
+ const fr = await streamer.read();
474
+ const samples = fr.get(task.COMMAND_CHANNEL_NAME);
475
+ for (const sample of samples) {
476
+ const cmd = task.commandZ.parse(sample);
477
+ if (cmd.task === t.key && cmd.type === "stop") stopCommands.push(cmd);
478
+ }
479
+ return stopCommands.length > 0;
480
+ } catch {
481
+ return false;
482
+ }
483
+ })
484
+ .toBe(true);
485
+
486
+ streamer.close();
487
+ });
488
+ });
489
+
327
490
  describe("executeCommand", () => {
328
491
  it("should execute a command on a task", async () => {
329
492
  const type = "testCmd";
@@ -334,7 +497,7 @@ describe("Task", async () => {
334
497
  type: "ni",
335
498
  });
336
499
  const streamer = await client.openStreamer(task.COMMAND_CHANNEL_NAME);
337
- const key = await client.hardware.tasks.executeCommand({
500
+ const key = await client.tasks.executeCommand({
338
501
  task: t.key,
339
502
  type,
340
503
  args,
@@ -0,0 +1,34 @@
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
+
12
+ import { policy } from "@/access/policy";
13
+ import { role } from "@/access/role";
14
+ import type Synnax from "@/client";
15
+ import { createTestClient } from "@/testutil/client";
16
+
17
+ export const createTestClientWithPolicy = async (client: Synnax, pol: policy.New) => {
18
+ const username = id.create();
19
+ const u = await client.users.create({
20
+ username,
21
+ password: "test",
22
+ firstName: "test",
23
+ lastName: "test",
24
+ });
25
+ const p = await client.access.policies.create(pol);
26
+ const r = await client.access.roles.create({
27
+ name: "test",
28
+ description: "test",
29
+ });
30
+ await client.ontology.addChildren(role.ontologyID(r.key), policy.ontologyID(p.key));
31
+ await client.access.roles.assign({ user: u.key, role: r.key });
32
+ const userClient = createTestClient({ username, password: "test" });
33
+ return userClient;
34
+ };
@@ -17,13 +17,13 @@ export const newIndexedPair = async (
17
17
  ): Promise<[channel.Channel, channel.Channel]> => {
18
18
  const index = await client.channels.create({
19
19
  leaseholder: 1,
20
- name: `test-${id.create()}`,
20
+ name: id.create(),
21
21
  dataType: DataType.TIMESTAMP,
22
22
  isIndex: true,
23
23
  });
24
24
  const data = await client.channels.create({
25
25
  leaseholder: 1,
26
- name: `test-${id.create()}`,
26
+ name: id.create(),
27
27
  dataType: DataType.FLOAT64,
28
28
  index: index.key,
29
29
  });
@@ -32,7 +32,7 @@ export const newIndexedPair = async (
32
32
 
33
33
  export const newVirtualChannel = async (client: Synnax): Promise<channel.Channel> => {
34
34
  const ch = await client.channels.create({
35
- name: `test-${id.create()}`,
35
+ name: id.create(),
36
36
  dataType: DataType.FLOAT64,
37
37
  virtual: true,
38
38
  });
package/src/transport.ts CHANGED
@@ -16,8 +16,6 @@ import {
16
16
  } from "@synnaxlabs/freighter";
17
17
  import { binary, type breaker, type URL } from "@synnaxlabs/x";
18
18
 
19
- const baseAPIEndpoint = "/api/v1/";
20
-
21
19
  export class Transport {
22
20
  readonly url: URL;
23
21
  readonly unary: UnaryClient;
@@ -26,7 +24,7 @@ export class Transport {
26
24
 
27
25
  constructor(url: URL, breakerCfg: breaker.Config = {}, secure: boolean = false) {
28
26
  this.secure = secure;
29
- this.url = url.child(baseAPIEndpoint);
27
+ this.url = url.child("/api/v1/");
30
28
  const codec = new binary.JSONCodec();
31
29
  this.unary = unaryWithBreaker(
32
30
  new HTTPClient(this.url, codec, this.secure),
@@ -0,0 +1,107 @@
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, NotFoundError } from "@/errors";
14
+ import { createTestClientWithPolicy } from "@/testutil/access";
15
+ import { createTestClient } from "@/testutil/client";
16
+ import { user } from "@/user";
17
+
18
+ const client = createTestClient();
19
+
20
+ describe("user", () => {
21
+ describe("access control", () => {
22
+ it("should deny access when no retrieve policy exists", async () => {
23
+ const userClient = await createTestClientWithPolicy(client, {
24
+ name: "test",
25
+ objects: [],
26
+ actions: [],
27
+ });
28
+ const randomUser = await client.users.create({
29
+ username: id.create(),
30
+ password: "test",
31
+ });
32
+ await expect(userClient.users.retrieve({ key: randomUser.key })).rejects.toThrow(
33
+ AuthError,
34
+ );
35
+ });
36
+
37
+ it("should allow the caller to retrieve users with the correct policy", async () => {
38
+ const userClient = await createTestClientWithPolicy(client, {
39
+ name: "test",
40
+ objects: [user.ontologyID("")],
41
+ actions: ["retrieve"],
42
+ });
43
+ const randomUser = await client.users.create({
44
+ username: id.create(),
45
+ password: "test",
46
+ });
47
+ const retrieved = await userClient.users.retrieve({ key: randomUser.key });
48
+ expect(retrieved.key).toBe(randomUser.key);
49
+ expect(retrieved.username).toBe(randomUser.username);
50
+ });
51
+
52
+ it("should allow the caller to create users with the correct policy", async () => {
53
+ const userClient = await createTestClientWithPolicy(client, {
54
+ name: "test",
55
+ objects: [user.ontologyID("")],
56
+ actions: ["create"],
57
+ });
58
+ await userClient.users.create({
59
+ username: id.create(),
60
+ password: "test",
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: [user.ontologyID("")],
68
+ actions: [],
69
+ });
70
+ await expect(
71
+ userClient.users.create({
72
+ username: id.create(),
73
+ password: "test",
74
+ }),
75
+ ).rejects.toThrow(AuthError);
76
+ });
77
+
78
+ it("should allow the caller to delete users with the correct policy", async () => {
79
+ const userClient = await createTestClientWithPolicy(client, {
80
+ name: "test",
81
+ objects: [user.ontologyID("")],
82
+ actions: ["delete"],
83
+ });
84
+ const randomUser = await client.users.create({
85
+ username: id.create(),
86
+ password: "test",
87
+ });
88
+ await userClient.users.delete(randomUser.key);
89
+ await expect(userClient.users.retrieve({ key: randomUser.key })).rejects.toThrow(
90
+ NotFoundError,
91
+ );
92
+ });
93
+
94
+ it("should deny access when no delete policy exists", async () => {
95
+ const userClient = await createTestClientWithPolicy(client, {
96
+ name: "test",
97
+ objects: [user.ontologyID("")],
98
+ actions: [],
99
+ });
100
+ const randomUser = await client.users.create({
101
+ username: id.create(),
102
+ password: "test",
103
+ });
104
+ await expect(userClient.users.delete(randomUser.key)).rejects.toThrow(AuthError);
105
+ });
106
+ });
107
+ });