@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
@@ -0,0 +1,116 @@
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 { DataType, id } from "@synnaxlabs/x";
11
+ import { describe, expect, it } from "vitest";
12
+
13
+ import { channel } from "@/channel";
14
+ import { AuthError, NotFoundError } from "@/errors";
15
+ import { createTestClientWithPolicy } from "@/testutil/access";
16
+ import { createTestClient } from "@/testutil/client";
17
+
18
+ const client = createTestClient();
19
+
20
+ describe("channel", () => {
21
+ describe("access control", () => {
22
+ it("should deny access when no matching policy exists", async () => {
23
+ const userClient = await createTestClientWithPolicy(client, {
24
+ name: "test",
25
+ objects: [],
26
+ actions: [],
27
+ });
28
+ const randomChannel = await client.channels.create({
29
+ name: id.create(),
30
+ dataType: DataType.FLOAT32,
31
+ virtual: true,
32
+ });
33
+ await expect(userClient.channels.retrieve(randomChannel.key)).rejects.toThrow(
34
+ AuthError,
35
+ );
36
+ });
37
+
38
+ it("should allow the caller to retrieve channels with the correct policy", async () => {
39
+ const userClient = await createTestClientWithPolicy(client, {
40
+ name: "test",
41
+ objects: [channel.ontologyID(0)],
42
+ actions: ["retrieve"],
43
+ });
44
+ const randomChannel = await client.channels.create({
45
+ name: id.create(),
46
+ dataType: DataType.FLOAT32,
47
+ virtual: true,
48
+ });
49
+ const retrieved = await userClient.channels.retrieve(randomChannel.key);
50
+ expect(retrieved.key).toBe(randomChannel.key);
51
+ expect(retrieved.name).toBe(randomChannel.name);
52
+ expect(retrieved.dataType.equals(randomChannel.dataType)).toBe(true);
53
+ });
54
+
55
+ it("should allow the caller to create channels with the correct policy", async () => {
56
+ const userClient = await createTestClientWithPolicy(client, {
57
+ name: "test",
58
+ objects: [channel.ontologyID(0)],
59
+ actions: ["create"],
60
+ });
61
+ await userClient.channels.create({
62
+ name: id.create(),
63
+ dataType: DataType.FLOAT32,
64
+ virtual: true,
65
+ });
66
+ });
67
+
68
+ it("should deny access when no create policy exists", async () => {
69
+ const userClient = await createTestClientWithPolicy(client, {
70
+ name: "test",
71
+ objects: [channel.ontologyID(0)],
72
+ actions: ["retrieve"],
73
+ });
74
+ await expect(
75
+ userClient.channels.create({
76
+ name: id.create(),
77
+ dataType: DataType.FLOAT32,
78
+ virtual: true,
79
+ }),
80
+ ).rejects.toThrow(AuthError);
81
+ });
82
+
83
+ it("should allow the caller to delete channels with the correct policy", async () => {
84
+ const userClient = await createTestClientWithPolicy(client, {
85
+ name: "test",
86
+ objects: [channel.ontologyID(0)],
87
+ actions: ["delete"],
88
+ });
89
+ const randomChannel = await client.channels.create({
90
+ name: id.create(),
91
+ dataType: DataType.FLOAT32,
92
+ virtual: true,
93
+ });
94
+ await userClient.channels.delete(randomChannel.key);
95
+ await expect(userClient.channels.retrieve(randomChannel.key)).rejects.toThrow(
96
+ NotFoundError,
97
+ );
98
+ });
99
+
100
+ it("should deny access when no delete policy exists", async () => {
101
+ const userClient = await createTestClientWithPolicy(client, {
102
+ name: "test",
103
+ objects: [channel.ontologyID(0)],
104
+ actions: ["retrieve"],
105
+ });
106
+ const randomChannel = await client.channels.create({
107
+ name: id.create(),
108
+ dataType: DataType.FLOAT32,
109
+ virtual: true,
110
+ });
111
+ await expect(userClient.channels.delete(randomChannel.key)).rejects.toThrow(
112
+ AuthError,
113
+ );
114
+ });
115
+ });
116
+ });
@@ -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 { DataType, id, TimeStamp } from "@synnaxlabs/x";
10
+ import { DataType, id } from "@synnaxlabs/x";
11
11
  import { describe, expect, it, test } from "vitest";
12
12
 
13
13
  import { Channel } from "@/channel/client";
@@ -19,12 +19,13 @@ const client = createTestClient();
19
19
  describe("Channel", () => {
20
20
  describe("create", () => {
21
21
  test("create one", async () => {
22
+ const name = id.create();
22
23
  const channel = await client.channels.create({
23
- name: "test",
24
+ name,
24
25
  dataType: DataType.FLOAT32,
25
26
  virtual: true,
26
27
  });
27
- expect(channel.name, "test").toEqual("test");
28
+ expect(channel.name).toEqual(name);
28
29
  expect(channel.leaseholder).toEqual(1);
29
30
  expect(channel.virtual).toBe(true);
30
31
  expect(channel.dataType).toEqual(DataType.FLOAT32);
@@ -39,7 +40,7 @@ describe("Channel", () => {
39
40
  });
40
41
  chOne = await client.channels.create(chOne);
41
42
  let calculatedCH = new Channel({
42
- name: "test2",
43
+ name: id.create(),
43
44
  virtual: true,
44
45
  dataType: DataType.FLOAT32,
45
46
  expression: `return ${chOne.name} * 2`,
@@ -53,12 +54,15 @@ describe("Channel", () => {
53
54
  test("create calculated, missing required channel", async () => {
54
55
  try {
55
56
  await client.channels.create({
56
- name: "test",
57
+ name: id.create(),
57
58
  virtual: true,
58
59
  dataType: DataType.FLOAT32,
59
- expression: "test * 2",
60
+ expression: `return 2`,
60
61
  });
61
62
  } catch (e) {
63
+ expect((e as Error).message).toContain(
64
+ "calculated channels must require at least one channel",
65
+ );
62
66
  expect(PathError.matches(e)).toBe(true);
63
67
  expect((e as PathError).path).toEqual(["requires"]);
64
68
  expect((e as PathError).error.message).contain(
@@ -69,13 +73,13 @@ describe("Channel", () => {
69
73
 
70
74
  test("create index and indexed pair", async () => {
71
75
  const one = await client.channels.create({
72
- name: "Time",
76
+ name: id.create(),
73
77
  isIndex: true,
74
78
  dataType: DataType.TIMESTAMP,
75
79
  });
76
80
  expect(one.key).not.toEqual(0);
77
81
  const two = await client.channels.create({
78
- name: "test",
82
+ name: id.create(),
79
83
  index: one.key,
80
84
  dataType: DataType.FLOAT32,
81
85
  });
@@ -83,36 +87,37 @@ describe("Channel", () => {
83
87
  });
84
88
 
85
89
  test("create many", async () => {
90
+ const names = [id.create(), id.create()];
86
91
  const channels = await client.channels.create([
87
- { name: "test1", leaseholder: 1, virtual: true, dataType: DataType.FLOAT32 },
88
- { name: "test2", leaseholder: 1, virtual: true, dataType: DataType.FLOAT32 },
92
+ { name: names[0], leaseholder: 1, virtual: true, dataType: DataType.FLOAT32 },
93
+ { name: names[1], leaseholder: 1, virtual: true, dataType: DataType.FLOAT32 },
89
94
  ]);
90
95
  expect(channels.length).toEqual(2);
91
- expect(channels[0].name).toEqual("test1");
92
- expect(channels[1].name).toEqual("test2");
96
+ expect(channels[0].name).toEqual(names[0]);
97
+ expect(channels[1].name).toEqual(names[1]);
93
98
  });
94
99
 
95
100
  test("create instances of channels", async () => {
96
101
  const timeIndexChannel = await client.channels.create({
97
- name: "time",
102
+ name: id.create(),
98
103
  dataType: DataType.TIMESTAMP,
99
104
  isIndex: true,
100
105
  });
101
106
 
102
107
  const sensorOne = new Channel({
103
- name: "sensor_one",
108
+ name: id.create(),
104
109
  dataType: DataType.FLOAT32,
105
110
  index: timeIndexChannel.key,
106
111
  });
107
112
 
108
113
  const sensorTwo = new Channel({
109
- name: "sensor_two",
114
+ name: id.create(),
110
115
  dataType: DataType.FLOAT32,
111
116
  index: timeIndexChannel.key,
112
117
  });
113
118
 
114
119
  const sensorThree = new Channel({
115
- name: "sensor_three",
120
+ name: id.create(),
116
121
  dataType: DataType.FLOAT32,
117
122
  index: timeIndexChannel.key,
118
123
  });
@@ -122,7 +127,7 @@ describe("Channel", () => {
122
127
  describe("virtual", () => {
123
128
  it("should create a virtual channel", async () => {
124
129
  const channel = await client.channels.create({
125
- name: "test",
130
+ name: id.create(),
126
131
  dataType: DataType.JSON,
127
132
  virtual: true,
128
133
  });
@@ -134,7 +139,7 @@ describe("Channel", () => {
134
139
 
135
140
  describe("retrieveIfNameExists", () => {
136
141
  it("should retrieve the existing channel when it exists", async () => {
137
- const name = `test-${Math.random()}-${TimeStamp.now().valueOf()}`;
142
+ const name = id.create();
138
143
  const channel = await client.channels.create({
139
144
  name,
140
145
  leaseholder: 1,
@@ -148,16 +153,15 @@ describe("Channel", () => {
148
153
  expect(channelTwo.key).toEqual(channel.key);
149
154
  });
150
155
  it("should create a new channel when it does not exist", async () => {
151
- const name = `test-${Math.random()}-${TimeStamp.now().valueOf()}`;
152
156
  const channel = await client.channels.create({
153
- name,
157
+ name: id.create(),
154
158
  leaseholder: 1,
155
159
  virtual: true,
156
160
  dataType: DataType.FLOAT32,
157
161
  });
158
162
  const channelTwo = await client.channels.create(
159
163
  {
160
- name: `${name}-2`,
164
+ name: id.create(),
161
165
  leaseholder: 1,
162
166
  virtual: true,
163
167
  dataType: DataType.FLOAT32,
@@ -167,7 +171,7 @@ describe("Channel", () => {
167
171
  expect(channelTwo.key).not.toEqual(channel.key);
168
172
  });
169
173
  it("should retrieve and create the correct channels when creating many", async () => {
170
- const name = `test-${Math.random()}-${TimeStamp.now().valueOf()}`;
174
+ const name = id.create();
171
175
  const channel = await client.channels.create({
172
176
  name,
173
177
  leaseholder: 1,
@@ -178,7 +182,7 @@ describe("Channel", () => {
178
182
  [
179
183
  { name, leaseholder: 1, virtual: true, dataType: DataType.FLOAT32 },
180
184
  {
181
- name: `${name}-2`,
185
+ name: id.create(),
182
186
  leaseholder: 1,
183
187
  virtual: true,
184
188
  dataType: DataType.FLOAT32,
@@ -196,13 +200,13 @@ describe("Channel", () => {
196
200
  describe("retrieve", () => {
197
201
  test("retrieve by key", async () => {
198
202
  const channel = await client.channels.create({
199
- name: "test",
203
+ name: id.create(),
200
204
  leaseholder: 1,
201
205
  virtual: true,
202
206
  dataType: DataType.FLOAT32,
203
207
  });
204
208
  const retrieved = await client.channels.retrieve(channel.key);
205
- expect(retrieved.name).toEqual("test");
209
+ expect(retrieved.name).toEqual(channel.name);
206
210
  expect(retrieved.leaseholder).toEqual(1);
207
211
  expect(retrieved.virtual).toEqual(true);
208
212
  expect(retrieved.dataType).toEqual(DataType.FLOAT32);
@@ -213,26 +217,28 @@ describe("Channel", () => {
213
217
  ).rejects.toThrow(NotFoundError);
214
218
  });
215
219
  test("retrieve by name", async () => {
216
- const retrieved = await client.channels.retrieve(["test"]);
217
- expect(retrieved.length).toBeGreaterThan(0);
218
- retrieved.forEach((ch) => expect(ch.name).toEqual("test"));
220
+ const name = id.create();
221
+ await client.channels.create({
222
+ name,
223
+ leaseholder: 1,
224
+ virtual: true,
225
+ dataType: DataType.FLOAT32,
226
+ });
227
+ const retrieved = await client.channels.retrieve([name]);
228
+ expect(retrieved.length).toBe(1);
229
+ expect(retrieved[0].name).toEqual(name);
219
230
  });
220
231
  test("retrieve by key - not found", async () => {
221
232
  await expect(
222
233
  async () => await client.channels.retrieve("1-1000"),
223
234
  ).rejects.toThrow(NotFoundError);
224
235
  });
225
- test("retrieve by name", async () => {
226
- const retrieved = await client.channels.retrieve(["test"]);
227
- expect(retrieved.length).toBeGreaterThan(0);
228
- retrieved.forEach((ch) => expect(ch.name).toEqual("test"));
229
- });
230
236
  });
231
237
 
232
238
  describe("delete", async () => {
233
239
  test("delete by key", async () => {
234
240
  const channel = await client.channels.create({
235
- name: "test",
241
+ name: id.create(),
236
242
  leaseholder: 1,
237
243
  virtual: true,
238
244
  dataType: DataType.FLOAT32,
@@ -244,12 +250,12 @@ describe("Channel", () => {
244
250
  });
245
251
  test("delete by name", async () => {
246
252
  const channel = await client.channels.create({
247
- name: "test",
253
+ name: id.create(),
248
254
  leaseholder: 1,
249
255
  virtual: true,
250
256
  dataType: DataType.FLOAT32,
251
257
  });
252
- await client.channels.delete(["test"]);
258
+ await client.channels.delete([channel.name]);
253
259
  await expect(
254
260
  async () => await client.channels.retrieve(channel.key),
255
261
  ).rejects.toThrow(NotFoundError);
@@ -258,40 +264,43 @@ describe("Channel", () => {
258
264
  describe("rename", async () => {
259
265
  test("single rename", async () => {
260
266
  const channel = await client.channels.create({
261
- name: "test",
267
+ name: id.create(),
262
268
  leaseholder: 1,
263
269
  virtual: true,
264
270
  dataType: DataType.FLOAT32,
265
271
  });
266
- await client.channels.rename(channel.key, "test2");
272
+ const newName = id.create();
273
+ await client.channels.rename(channel.key, newName);
267
274
  const renamed = await client.channels.retrieve(channel.key);
268
- expect(renamed.name).toEqual("test2");
275
+ expect(renamed.name).toEqual(newName);
269
276
  });
270
277
  test("multiple rename", async () => {
278
+ const names = [id.create(), id.create()];
271
279
  const channels = await client.channels.create([
272
- { name: "test1", leaseholder: 1, virtual: true, dataType: DataType.FLOAT32 },
273
- { name: "test2", leaseholder: 1, virtual: true, dataType: DataType.FLOAT32 },
280
+ { name: names[0], leaseholder: 1, virtual: true, dataType: DataType.FLOAT32 },
281
+ { name: names[1], leaseholder: 1, virtual: true, dataType: DataType.FLOAT32 },
274
282
  ]);
275
283
  // Retrieve channels here to ensure we check for cache invalidation
276
284
  const initial = await client.channels.retrieve(channels.map((c) => c.key));
277
- expect(initial[0].name).toEqual("test1");
278
- expect(initial[1].name).toEqual("test2");
285
+ expect(initial[0].name).toEqual(names[0]);
286
+ expect(initial[1].name).toEqual(names[1]);
287
+ const newNames = [id.create(), id.create()];
279
288
  await client.channels.rename(
280
289
  channels.map((c) => c.key),
281
- ["test3", "test4"],
290
+ newNames,
282
291
  );
283
292
  const renamed = await client.channels.retrieve(channels.map((c) => c.key));
284
- expect(renamed[0].name).toEqual("test3");
285
- expect(renamed[1].name).toEqual("test4");
293
+ expect(renamed[0].name).toEqual(newNames[0]);
294
+ expect(renamed[1].name).toEqual(newNames[1]);
286
295
  });
287
296
  });
288
297
 
289
298
  describe("update calculations", () => {
290
299
  test("update virtual channel expression", async () => {
291
300
  const channel = await client.channels.create({
292
- name: "virtual-calc",
293
- dataType: DataType.FLOAT32,
301
+ name: id.create(),
294
302
  virtual: true,
303
+ dataType: DataType.INT64,
295
304
  expression: "return 1",
296
305
  });
297
306
 
@@ -299,11 +308,12 @@ describe("Channel", () => {
299
308
  key: channel.key,
300
309
  name: channel.name,
301
310
  dataType: channel.dataType,
311
+ leaseholder: channel.leaseholder,
302
312
  virtual: true,
303
313
  expression: "return 2",
304
314
  });
305
315
 
306
- const channelsWithName = await client.channels.retrieve(["virtual-calc"]);
316
+ const channelsWithName = await client.channels.retrieve([channel.name]);
307
317
  expect(channelsWithName.length).toEqual(1);
308
318
 
309
319
  expect(updated.expression).toEqual("return 2");
@@ -314,7 +324,7 @@ describe("Channel", () => {
314
324
 
315
325
  test("update calculated channel name", async () => {
316
326
  const channel = await client.channels.create({
317
- name: "virtual-calc",
327
+ name: id.create(),
318
328
  dataType: DataType.FLOAT32,
319
329
  virtual: true,
320
330
  expression: "return 1",
@@ -322,35 +332,15 @@ describe("Channel", () => {
322
332
 
323
333
  const updated = await client.channels.create({
324
334
  key: channel.key,
325
- name: "new-name",
335
+ name: id.create(),
326
336
  dataType: channel.dataType,
327
337
  virtual: true,
328
338
  expression: channel.expression,
329
339
  });
330
- expect(updated.name).toEqual("new-name");
331
-
332
- const retrieved = await client.channels.retrieve(channel.key);
333
- expect(retrieved.name).toEqual("new-name");
334
- });
335
-
336
- test("should not allow updates to non-virtual channels", async () => {
337
- const channel = await client.channels.create({
338
- name: "regular-channel",
339
- leaseholder: 1,
340
- virtual: true,
341
- dataType: DataType.FLOAT32,
342
- });
343
-
344
- const _updated = await client.channels.create({
345
- key: channel.key,
346
- name: "new-name",
347
- leaseholder: channel.leaseholder,
348
- virtual: true,
349
- dataType: channel.dataType,
350
- });
340
+ expect(updated.name).toEqual(updated.name);
351
341
 
352
342
  const retrieved = await client.channels.retrieve(channel.key);
353
- expect(retrieved.name).toEqual("regular-channel");
343
+ expect(retrieved.name).toEqual(updated.name);
354
344
  });
355
345
  });
356
346
  });
@@ -26,6 +26,7 @@ import {
26
26
  keyZ,
27
27
  type Name,
28
28
  type New,
29
+ ontologyID,
29
30
  type Operation,
30
31
  type Params,
31
32
  type Payload,
@@ -223,8 +224,6 @@ export class Channel {
223
224
 
224
225
  export const CALCULATION_STATUS_CHANNEL_NAME = "sy_calculation_status";
225
226
 
226
- const RETRIEVE_GROUP_ENDPOINT = "/channel/retrieve-group";
227
-
228
227
  const retrieveGroupReqZ = z.object({});
229
228
 
230
229
  const retrieveGroupResZ = z.object({ group: group.groupZ });
@@ -427,7 +426,7 @@ export class Client {
427
426
  async retrieveGroup(): Promise<group.Group> {
428
427
  const res = await sendRequired(
429
428
  this.client,
430
- RETRIEVE_GROUP_ENDPOINT,
429
+ "/channel/retrieve-group",
431
430
  {},
432
431
  retrieveGroupReqZ,
433
432
  retrieveGroupResZ,
@@ -442,11 +441,6 @@ export const isCalculated = ({ virtual, expression }: Payload): boolean =>
442
441
  export const isLegacyCalculated = (pld: Payload): boolean =>
443
442
  isCalculated(pld) && pld.requires.length > 0;
444
443
 
445
- export const ontologyID = (key: Key): ontology.ID => ({
446
- type: "channel",
447
- key: key.toString(),
448
- });
449
-
450
444
  export const resolveLegacyCalculatedIndex = async (
451
445
  retrieve: (key: Key) => Promise<Payload | null>,
452
446
  channel: Payload,
@@ -0,0 +1,171 @@
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 { DataType } from "@synnaxlabs/x";
11
+ import { describe, expect, it } from "vitest";
12
+
13
+ import { escapeInvalidName, nameZ, newZ } from "@/channel/payload";
14
+
15
+ describe("nameZ", () => {
16
+ describe("valid names", () => {
17
+ const validNames = [
18
+ ["temperature", "lowercase letters"],
19
+ ["Pressure", "capitalized word"],
20
+ ["sensor1", "letters followed by digit"],
21
+ ["sensor_temp", "letters with underscore"],
22
+ ["temp123", "letters followed by digits"],
23
+ ["Sensor_temp", "capitalized with underscore"],
24
+ ["temp123_sensor_temp", "complex valid name"],
25
+ ["_private", "underscore prefix"],
26
+ ["__double", "double underscore prefix"],
27
+ ["a", "single letter"],
28
+ ["A", "single capital letter"],
29
+ ["_", "single underscore"],
30
+ ["_1", "underscore followed by digit"],
31
+ ];
32
+ validNames.forEach(([name, description]) => {
33
+ it(`should accept ${name} (${description})`, () => {
34
+ const result = nameZ.safeParse(name);
35
+ expect(result.success).toBe(true);
36
+ });
37
+ });
38
+ });
39
+
40
+ describe("invalid names", () => {
41
+ it("should reject empty string", () => {
42
+ const result = nameZ.safeParse("");
43
+ expect(result.success).toBe(false);
44
+ expect(result.error?.issues[0].message).toContain("Name must not be empty");
45
+ });
46
+
47
+ it("should reject name starting with digit", () => {
48
+ const result = nameZ.safeParse("1sensor");
49
+ expect(result.success).toBe(false);
50
+ // Regex validation covers both "cannot start with digit" and "invalid characters"
51
+ expect(result.error?.issues[0].message).toContain(
52
+ "can only contain letters, digits, and underscores",
53
+ );
54
+ });
55
+
56
+ it("should reject name with spaces", () => {
57
+ const result = nameZ.safeParse("my channel");
58
+ expect(result.success).toBe(false);
59
+ expect(result.error?.issues[0].message).toContain(
60
+ "can only contain letters, digits, and underscores",
61
+ );
62
+ });
63
+
64
+ it("should reject name with special characters", () => {
65
+ const result = nameZ.safeParse("sensor!");
66
+ expect(result.success).toBe(false);
67
+ });
68
+
69
+ it("should reject name with hyphens", () => {
70
+ const result = nameZ.safeParse("sensor-temp");
71
+ expect(result.success).toBe(false);
72
+ });
73
+
74
+ it("should reject name with dots", () => {
75
+ const result = nameZ.safeParse("sensor.temp");
76
+ expect(result.success).toBe(false);
77
+ });
78
+
79
+ it("should reject name with parentheses", () => {
80
+ const result = nameZ.safeParse("sensor(1)");
81
+ expect(result.success).toBe(false);
82
+ });
83
+
84
+ it("should reject name with brackets", () => {
85
+ const result = nameZ.safeParse("sensor[0]");
86
+ expect(result.success).toBe(false);
87
+ });
88
+ });
89
+ });
90
+
91
+ describe("newZ", () => {
92
+ const validNewChannel = {
93
+ name: "temperature_sensor",
94
+ dataType: DataType.FLOAT32,
95
+ virtual: true,
96
+ };
97
+
98
+ describe("name validation", () => {
99
+ it("should accept valid channel names", () => {
100
+ const result = newZ.safeParse(validNewChannel);
101
+ expect(result.success).toBe(true);
102
+ });
103
+
104
+ it("should reject empty name", () => {
105
+ const result = newZ.safeParse({ ...validNewChannel, name: "" });
106
+ expect(result.success).toBe(false);
107
+ });
108
+
109
+ it("should reject name starting with digit", () => {
110
+ const result = newZ.safeParse({ ...validNewChannel, name: "1sensor" });
111
+ expect(result.success).toBe(false);
112
+ });
113
+
114
+ it("should reject name with spaces", () => {
115
+ const result = newZ.safeParse({ ...validNewChannel, name: "my channel" });
116
+ expect(result.success).toBe(false);
117
+ });
118
+
119
+ it("should reject name with special characters", () => {
120
+ const result = newZ.safeParse({ ...validNewChannel, name: "sensor-temp" });
121
+ expect(result.success).toBe(false);
122
+ });
123
+
124
+ it("should accept name with underscores", () => {
125
+ const result = newZ.safeParse({
126
+ ...validNewChannel,
127
+ name: "sensor_temp_123",
128
+ });
129
+ expect(result.success).toBe(true);
130
+ });
131
+
132
+ it("should accept name starting with underscore", () => {
133
+ const result = newZ.safeParse({ ...validNewChannel, name: "_private_sensor" });
134
+ expect(result.success).toBe(true);
135
+ });
136
+ });
137
+ });
138
+ describe("escapeInvalidName", () => {
139
+ it("should escape invalid name", () => {
140
+ const result = escapeInvalidName("sensor-temp");
141
+ expect(result).toBe("sensor_temp");
142
+ });
143
+ it("should escape name starting with digit", () => {
144
+ const result = escapeInvalidName("1sensor");
145
+ expect(result).toBe("_1sensor");
146
+ });
147
+ it("should escape name with spaces", () => {
148
+ const result = escapeInvalidName("my channel");
149
+ expect(result).toBe("my_channel");
150
+ });
151
+ it("should escape name with special characters", () => {
152
+ const result = escapeInvalidName("sensor!");
153
+ expect(result).toBe("sensor_");
154
+ });
155
+ it("should escape name with hyphens", () => {
156
+ const result = escapeInvalidName("sensor-temp");
157
+ expect(result).toBe("sensor_temp");
158
+ });
159
+ it("should escape name with dots", () => {
160
+ const result = escapeInvalidName("sensor.temp");
161
+ expect(result).toBe("sensor_temp");
162
+ });
163
+ it("should allow an empty string by default", () => {
164
+ const result = escapeInvalidName("");
165
+ expect(result).toBe("");
166
+ });
167
+ it("should change empty string to underscore when changeEmptyToUnderscore is true", () => {
168
+ const result = escapeInvalidName("", true);
169
+ expect(result).toBe("_");
170
+ });
171
+ });