@synnaxlabs/client 0.47.0 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/dist/client.cjs +35 -35
  3. package/dist/client.js +6264 -5984
  4. package/dist/eslint.config.d.ts +3 -2
  5. package/dist/eslint.config.d.ts.map +1 -1
  6. package/dist/src/access/client.d.ts +3 -1
  7. package/dist/src/access/client.d.ts.map +1 -1
  8. package/dist/src/access/enforce.d.ts +35 -0
  9. package/dist/src/access/enforce.d.ts.map +1 -0
  10. package/dist/src/access/enforce.spec.d.ts +2 -0
  11. package/dist/src/access/enforce.spec.d.ts.map +1 -0
  12. package/dist/src/access/external.d.ts +3 -0
  13. package/dist/src/access/external.d.ts.map +1 -1
  14. package/dist/src/access/payload.d.ts +0 -6
  15. package/dist/src/access/payload.d.ts.map +1 -1
  16. package/dist/src/access/policy/access.spec.d.ts +2 -0
  17. package/dist/src/access/policy/access.spec.d.ts.map +1 -0
  18. package/dist/src/access/policy/client.d.ts +485 -31
  19. package/dist/src/access/policy/client.d.ts.map +1 -1
  20. package/dist/src/access/policy/payload.d.ts +36 -113
  21. package/dist/src/access/policy/payload.d.ts.map +1 -1
  22. package/dist/src/access/role/client.d.ts +135 -0
  23. package/dist/src/access/role/client.d.ts.map +1 -0
  24. package/dist/src/access/role/external.d.ts.map +1 -0
  25. package/dist/src/access/role/index.d.ts +2 -0
  26. package/dist/src/access/role/index.d.ts.map +1 -0
  27. package/dist/src/access/role/payload.d.ts +27 -0
  28. package/dist/src/access/role/payload.d.ts.map +1 -0
  29. package/dist/src/access/role/role.spec.d.ts +2 -0
  30. package/dist/src/access/role/role.spec.d.ts.map +1 -0
  31. package/dist/src/arc/access.spec.d.ts +2 -0
  32. package/dist/src/arc/access.spec.d.ts.map +1 -0
  33. package/dist/src/arc/client.d.ts +5 -14
  34. package/dist/src/arc/client.d.ts.map +1 -1
  35. package/dist/src/arc/payload.d.ts +11 -2
  36. package/dist/src/arc/payload.d.ts.map +1 -1
  37. package/dist/src/auth/auth.d.ts +5 -3
  38. package/dist/src/auth/auth.d.ts.map +1 -1
  39. package/dist/src/channel/access.spec.d.ts +2 -0
  40. package/dist/src/channel/access.spec.d.ts.map +1 -0
  41. package/dist/src/channel/client.d.ts +0 -1
  42. package/dist/src/channel/client.d.ts.map +1 -1
  43. package/dist/src/channel/payload.d.ts +19 -8
  44. package/dist/src/channel/payload.d.ts.map +1 -1
  45. package/dist/src/channel/payload.spec.d.ts +2 -0
  46. package/dist/src/channel/payload.spec.d.ts.map +1 -0
  47. package/dist/src/channel/retriever.d.ts +4 -6
  48. package/dist/src/channel/retriever.d.ts.map +1 -1
  49. package/dist/src/channel/writer.d.ts.map +1 -1
  50. package/dist/src/client.d.ts +18 -10
  51. package/dist/src/client.d.ts.map +1 -1
  52. package/dist/src/connection/checker.d.ts +2 -3
  53. package/dist/src/connection/checker.d.ts.map +1 -1
  54. package/dist/src/connection.spec.d.ts +2 -0
  55. package/dist/src/connection.spec.d.ts.map +1 -0
  56. package/dist/src/device/access.spec.d.ts +2 -0
  57. package/dist/src/device/access.spec.d.ts.map +1 -0
  58. package/dist/src/{hardware/device → device}/client.d.ts +14 -7
  59. package/dist/src/device/client.d.ts.map +1 -0
  60. package/dist/src/device/device.spec.d.ts.map +1 -0
  61. package/dist/src/device/external.d.ts.map +1 -0
  62. package/dist/src/device/index.d.ts.map +1 -0
  63. package/dist/src/{hardware/device → device}/payload.d.ts +1 -1
  64. package/dist/src/device/payload.d.ts.map +1 -0
  65. package/dist/src/errors.d.ts +3 -0
  66. package/dist/src/errors.d.ts.map +1 -1
  67. package/dist/src/framer/adapter.d.ts +2 -2
  68. package/dist/src/framer/adapter.d.ts.map +1 -1
  69. package/dist/src/framer/client.d.ts +8 -1
  70. package/dist/src/framer/client.d.ts.map +1 -1
  71. package/dist/src/framer/frame.d.ts +11 -5
  72. package/dist/src/framer/frame.d.ts.map +1 -1
  73. package/dist/src/framer/iterator.d.ts +3 -3
  74. package/dist/src/framer/streamer.d.ts +24 -21
  75. package/dist/src/framer/streamer.d.ts.map +1 -1
  76. package/dist/src/framer/writer.d.ts +13 -13
  77. package/dist/src/index.d.ts +6 -7
  78. package/dist/src/index.d.ts.map +1 -1
  79. package/dist/src/label/access.spec.d.ts +2 -0
  80. package/dist/src/label/access.spec.d.ts.map +1 -0
  81. package/dist/src/label/client.d.ts +20 -11
  82. package/dist/src/label/client.d.ts.map +1 -1
  83. package/dist/src/ontology/client.d.ts +6 -6
  84. package/dist/src/ontology/client.d.ts.map +1 -1
  85. package/dist/src/ontology/group/access.spec.d.ts +2 -0
  86. package/dist/src/ontology/group/access.spec.d.ts.map +1 -0
  87. package/dist/src/ontology/group/client.d.ts +2 -2
  88. package/dist/src/ontology/group/client.d.ts.map +1 -1
  89. package/dist/src/ontology/group/payload.d.ts +1 -2
  90. package/dist/src/ontology/group/payload.d.ts.map +1 -1
  91. package/dist/src/ontology/payload.d.ts +23 -17
  92. package/dist/src/ontology/payload.d.ts.map +1 -1
  93. package/dist/src/ontology/writer.d.ts +10 -10
  94. package/dist/src/ontology/writer.d.ts.map +1 -1
  95. package/dist/src/rack/access.spec.d.ts +2 -0
  96. package/dist/src/rack/access.spec.d.ts.map +1 -0
  97. package/dist/src/{hardware/rack → rack}/client.d.ts +15 -8
  98. package/dist/src/rack/client.d.ts.map +1 -0
  99. package/dist/src/rack/external.d.ts.map +1 -0
  100. package/dist/src/rack/index.d.ts.map +1 -0
  101. package/dist/src/{hardware/rack → rack}/payload.d.ts +1 -1
  102. package/dist/src/rack/payload.d.ts.map +1 -0
  103. package/dist/src/rack/rack.spec.d.ts.map +1 -0
  104. package/dist/src/ranger/access.spec.d.ts +2 -0
  105. package/dist/src/ranger/access.spec.d.ts.map +1 -0
  106. package/dist/src/ranger/alias.d.ts +1 -8
  107. package/dist/src/ranger/alias.d.ts.map +1 -1
  108. package/dist/src/ranger/client.d.ts +12 -5
  109. package/dist/src/ranger/client.d.ts.map +1 -1
  110. package/dist/src/ranger/kv.d.ts +0 -3
  111. package/dist/src/ranger/kv.d.ts.map +1 -1
  112. package/dist/src/ranger/writer.d.ts +2 -2
  113. package/dist/src/ranger/writer.d.ts.map +1 -1
  114. package/dist/src/status/access.spec.d.ts +2 -0
  115. package/dist/src/status/access.spec.d.ts.map +1 -0
  116. package/dist/src/status/client.d.ts +4 -4
  117. package/dist/src/status/client.d.ts.map +1 -1
  118. package/dist/src/status/payload.d.ts +9 -2
  119. package/dist/src/status/payload.d.ts.map +1 -1
  120. package/dist/src/task/access.spec.d.ts +2 -0
  121. package/dist/src/task/access.spec.d.ts.map +1 -0
  122. package/dist/src/{hardware/task → task}/client.d.ts +26 -15
  123. package/dist/src/task/client.d.ts.map +1 -0
  124. package/dist/src/task/external.d.ts +3 -0
  125. package/dist/src/task/external.d.ts.map +1 -0
  126. package/dist/src/task/index.d.ts.map +1 -0
  127. package/dist/src/{hardware/task → task}/payload.d.ts +45 -6
  128. package/dist/src/task/payload.d.ts.map +1 -0
  129. package/dist/src/task/task.spec.d.ts.map +1 -0
  130. package/dist/src/testutil/access.d.ts +4 -0
  131. package/dist/src/testutil/access.d.ts.map +1 -0
  132. package/dist/src/testutil/client.d.ts +3 -3
  133. package/dist/src/testutil/client.d.ts.map +1 -1
  134. package/dist/src/transport.d.ts.map +1 -1
  135. package/dist/src/user/access.spec.d.ts +2 -0
  136. package/dist/src/user/access.spec.d.ts.map +1 -0
  137. package/dist/src/user/client.d.ts +10 -1
  138. package/dist/src/user/client.d.ts.map +1 -1
  139. package/dist/src/user/external.d.ts +1 -1
  140. package/dist/src/user/external.d.ts.map +1 -1
  141. package/dist/src/user/payload.d.ts.map +1 -1
  142. package/dist/src/workspace/access.spec.d.ts +2 -0
  143. package/dist/src/workspace/access.spec.d.ts.map +1 -0
  144. package/dist/src/workspace/client.d.ts +10 -5
  145. package/dist/src/workspace/client.d.ts.map +1 -1
  146. package/dist/src/workspace/lineplot/access.spec.d.ts +2 -0
  147. package/dist/src/workspace/lineplot/access.spec.d.ts.map +1 -0
  148. package/dist/src/workspace/lineplot/client.d.ts +8 -1
  149. package/dist/src/workspace/lineplot/client.d.ts.map +1 -1
  150. package/dist/src/workspace/log/access.spec.d.ts +2 -0
  151. package/dist/src/workspace/log/access.spec.d.ts.map +1 -0
  152. package/dist/src/workspace/log/client.d.ts +8 -1
  153. package/dist/src/workspace/log/client.d.ts.map +1 -1
  154. package/dist/src/workspace/schematic/access.spec.d.ts +2 -0
  155. package/dist/src/workspace/schematic/access.spec.d.ts.map +1 -0
  156. package/dist/src/workspace/schematic/client.d.ts +8 -1
  157. package/dist/src/workspace/schematic/client.d.ts.map +1 -1
  158. package/dist/src/workspace/schematic/symbol/access.spec.d.ts +2 -0
  159. package/dist/src/workspace/schematic/symbol/access.spec.d.ts.map +1 -0
  160. package/dist/src/workspace/schematic/symbol/client.d.ts +1 -5
  161. package/dist/src/workspace/schematic/symbol/client.d.ts.map +1 -1
  162. package/dist/src/workspace/schematic/symbol/payload.d.ts +2 -2
  163. package/dist/src/workspace/table/access.spec.d.ts +2 -0
  164. package/dist/src/workspace/table/access.spec.d.ts.map +1 -0
  165. package/dist/src/workspace/table/client.d.ts +8 -1
  166. package/dist/src/workspace/table/client.d.ts.map +1 -1
  167. package/eslint.config.ts +3 -1
  168. package/package.json +8 -8
  169. package/src/access/client.ts +5 -2
  170. package/src/access/enforce.spec.ts +189 -0
  171. package/src/access/enforce.ts +84 -0
  172. package/src/access/external.ts +3 -0
  173. package/src/access/payload.ts +1 -13
  174. package/src/access/policy/access.spec.ts +147 -0
  175. package/src/access/policy/client.ts +21 -25
  176. package/src/access/policy/payload.ts +9 -5
  177. package/src/access/role/client.ts +135 -0
  178. package/src/access/role/external.ts +11 -0
  179. package/src/{hardware → access/role}/index.ts +1 -1
  180. package/src/access/role/payload.ts +32 -0
  181. package/src/access/role/role.spec.ts +95 -0
  182. package/src/arc/access.spec.ts +143 -0
  183. package/src/arc/client.ts +7 -31
  184. package/src/arc/payload.ts +4 -0
  185. package/src/auth/auth.spec.ts +13 -13
  186. package/src/auth/auth.ts +33 -11
  187. package/src/channel/access.spec.ts +116 -0
  188. package/src/channel/channel.spec.ts +63 -73
  189. package/src/channel/client.ts +2 -8
  190. package/src/channel/payload.spec.ts +171 -0
  191. package/src/channel/payload.ts +37 -8
  192. package/src/channel/retriever.ts +10 -11
  193. package/src/channel/writer.ts +3 -7
  194. package/src/client.ts +38 -28
  195. package/src/connection/checker.ts +10 -10
  196. package/src/connection/connection.spec.ts +13 -13
  197. package/src/connection.spec.ts +145 -0
  198. package/src/device/access.spec.ts +159 -0
  199. package/src/{hardware/device → device}/client.ts +12 -21
  200. package/src/{hardware/device → device}/device.spec.ts +70 -34
  201. package/src/device/external.ts +11 -0
  202. package/src/{hardware/rack → device}/index.ts +1 -1
  203. package/src/{hardware/device → device}/payload.ts +3 -3
  204. package/src/errors.ts +2 -0
  205. package/src/framer/adapter.spec.ts +351 -13
  206. package/src/framer/adapter.ts +23 -13
  207. package/src/framer/client.spec.ts +14 -20
  208. package/src/framer/client.ts +3 -5
  209. package/src/framer/deleter.spec.ts +1 -1
  210. package/src/framer/frame.spec.ts +427 -0
  211. package/src/framer/frame.ts +30 -3
  212. package/src/framer/iterator.ts +4 -4
  213. package/src/framer/streamer.spec.ts +155 -10
  214. package/src/framer/streamer.ts +35 -12
  215. package/src/framer/writer.spec.ts +5 -5
  216. package/src/index.ts +13 -7
  217. package/src/label/access.spec.ts +109 -0
  218. package/src/label/client.ts +10 -14
  219. package/src/ontology/client.ts +4 -6
  220. package/src/ontology/group/access.spec.ts +77 -0
  221. package/src/ontology/group/client.ts +3 -7
  222. package/src/ontology/group/group.spec.ts +18 -0
  223. package/src/ontology/group/payload.ts +2 -2
  224. package/src/ontology/ontology.spec.ts +2 -0
  225. package/src/ontology/payload.ts +18 -2
  226. package/src/ontology/writer.ts +3 -7
  227. package/src/rack/access.spec.ts +102 -0
  228. package/src/{hardware/rack → rack}/client.ts +14 -19
  229. package/src/{hardware/device/index.ts → rack/external.ts} +2 -1
  230. package/src/{hardware/external.ts → rack/index.ts} +1 -1
  231. package/src/{hardware/rack → rack}/payload.ts +2 -2
  232. package/src/{hardware/rack → rack}/rack.spec.ts +43 -17
  233. package/src/ranger/access.spec.ts +115 -0
  234. package/src/ranger/alias.ts +6 -14
  235. package/src/ranger/client.ts +13 -14
  236. package/src/ranger/kv.ts +7 -9
  237. package/src/ranger/ranger.spec.ts +4 -4
  238. package/src/ranger/writer.ts +3 -7
  239. package/src/status/access.spec.ts +129 -0
  240. package/src/status/client.ts +5 -9
  241. package/src/status/payload.ts +3 -2
  242. package/src/task/access.spec.ts +131 -0
  243. package/src/{hardware/task → task}/client.ts +50 -25
  244. package/src/task/external.ts +11 -0
  245. package/src/{hardware/task → task}/index.ts +1 -1
  246. package/src/{hardware/task → task}/payload.ts +22 -3
  247. package/src/{hardware/task → task}/task.spec.ts +197 -34
  248. package/src/testutil/access.ts +34 -0
  249. package/src/testutil/channels.ts +3 -3
  250. package/src/testutil/client.ts +4 -4
  251. package/src/transport.ts +1 -3
  252. package/src/user/access.spec.ts +107 -0
  253. package/src/user/client.ts +10 -12
  254. package/src/user/external.ts +12 -1
  255. package/src/user/payload.ts +3 -5
  256. package/src/workspace/access.spec.ts +108 -0
  257. package/src/workspace/client.ts +11 -27
  258. package/src/workspace/lineplot/access.spec.ts +134 -0
  259. package/src/workspace/lineplot/client.ts +8 -13
  260. package/src/workspace/log/access.spec.ts +134 -0
  261. package/src/workspace/log/client.ts +8 -13
  262. package/src/workspace/schematic/access.spec.ts +134 -0
  263. package/src/workspace/schematic/client.ts +9 -18
  264. package/src/workspace/schematic/symbol/access.spec.ts +172 -0
  265. package/src/workspace/schematic/symbol/client.ts +6 -17
  266. package/src/workspace/schematic/symbol/payload.ts +1 -1
  267. package/src/workspace/table/access.spec.ts +134 -0
  268. package/src/workspace/table/client.ts +8 -13
  269. package/dist/src/access/policy/policy.spec.d.ts +0 -2
  270. package/dist/src/access/policy/policy.spec.d.ts.map +0 -1
  271. package/dist/src/hardware/client.d.ts +0 -10
  272. package/dist/src/hardware/client.d.ts.map +0 -1
  273. package/dist/src/hardware/device/client.d.ts.map +0 -1
  274. package/dist/src/hardware/device/device.spec.d.ts.map +0 -1
  275. package/dist/src/hardware/device/external.d.ts.map +0 -1
  276. package/dist/src/hardware/device/index.d.ts.map +0 -1
  277. package/dist/src/hardware/device/payload.d.ts.map +0 -1
  278. package/dist/src/hardware/external.d.ts +0 -2
  279. package/dist/src/hardware/external.d.ts.map +0 -1
  280. package/dist/src/hardware/index.d.ts +0 -2
  281. package/dist/src/hardware/index.d.ts.map +0 -1
  282. package/dist/src/hardware/rack/client.d.ts.map +0 -1
  283. package/dist/src/hardware/rack/external.d.ts.map +0 -1
  284. package/dist/src/hardware/rack/index.d.ts.map +0 -1
  285. package/dist/src/hardware/rack/payload.d.ts.map +0 -1
  286. package/dist/src/hardware/rack/rack.spec.d.ts.map +0 -1
  287. package/dist/src/hardware/task/client.d.ts.map +0 -1
  288. package/dist/src/hardware/task/external.d.ts.map +0 -1
  289. package/dist/src/hardware/task/index.d.ts.map +0 -1
  290. package/dist/src/hardware/task/payload.d.ts.map +0 -1
  291. package/dist/src/hardware/task/task.spec.d.ts.map +0 -1
  292. package/dist/src/user/retriever.d.ts +0 -16
  293. package/dist/src/user/retriever.d.ts.map +0 -1
  294. package/dist/src/user/writer.d.ts +0 -11
  295. package/dist/src/user/writer.d.ts.map +0 -1
  296. package/src/access/policy/policy.spec.ts +0 -329
  297. package/src/hardware/client.ts +0 -24
  298. package/src/hardware/device/external.ts +0 -11
  299. package/src/hardware/rack/external.ts +0 -11
  300. package/src/hardware/task/external.ts +0 -11
  301. package/src/user/retriever.ts +0 -41
  302. package/src/user/writer.ts +0 -84
  303. /package/dist/src/{hardware/device → access/role}/external.d.ts +0 -0
  304. /package/dist/src/{hardware/device → device}/device.spec.d.ts +0 -0
  305. /package/dist/src/{hardware/rack → device}/external.d.ts +0 -0
  306. /package/dist/src/{hardware/device → device}/index.d.ts +0 -0
  307. /package/dist/src/{hardware/task → rack}/external.d.ts +0 -0
  308. /package/dist/src/{hardware/rack → rack}/index.d.ts +0 -0
  309. /package/dist/src/{hardware/rack → rack}/rack.spec.d.ts +0 -0
  310. /package/dist/src/{hardware/task → task}/index.d.ts +0 -0
  311. /package/dist/src/{hardware/task → task}/task.spec.d.ts +0 -0
@@ -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 * as rack from "@/hardware/rack/external";
10
+ export * as device from "@/device/external";
@@ -10,14 +10,14 @@
10
10
  import { binary, record, status, zod } from "@synnaxlabs/x";
11
11
  import { z } from "zod";
12
12
 
13
- import { keyZ as rackKeyZ } from "@/hardware/rack/payload";
13
+ import { keyZ as rackKeyZ } from "@/rack/payload";
14
14
  import { decodeJSONString } from "@/util/decodeJSONString";
15
15
 
16
16
  export const keyZ = z.string();
17
17
  export type Key = z.infer<typeof keyZ>;
18
18
 
19
- export const statusDetailsSchema = z.object({ rack: rackKeyZ, device: keyZ });
20
- export const statusZ = status.statusZ(statusDetailsSchema);
19
+ export const statusDetailsZ = z.object({ rack: rackKeyZ, device: keyZ });
20
+ export const statusZ = status.statusZ(statusDetailsZ);
21
21
 
22
22
  export interface Status extends z.infer<typeof statusZ> {}
23
23
 
package/src/errors.ts CHANGED
@@ -51,6 +51,8 @@ export class InvalidTokenError extends AuthError.sub("invalid_token") {}
51
51
 
52
52
  export class ExpiredTokenError extends AuthError.sub("expired_token") {}
53
53
 
54
+ export class AccessDeniedError extends AuthError.sub("access_denied") {}
55
+
54
56
  /**
55
57
  * UnexpectedError is raised when an unexpected error occurs.
56
58
  */
@@ -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, Series, TimeStamp } from "@synnaxlabs/x";
10
+ import { DataType, id, Series, TimeStamp } from "@synnaxlabs/x";
11
11
  import { beforeAll, describe, expect, it } from "vitest";
12
12
 
13
13
  import { type channel } from "@/channel";
@@ -24,12 +24,12 @@ describe("WriteFrameAdapter", () => {
24
24
 
25
25
  beforeAll(async () => {
26
26
  timeCh = await client.channels.create({
27
- name: `time-${Math.random()}-${TimeStamp.now().toString()}`,
27
+ name: id.create(),
28
28
  dataType: DataType.TIMESTAMP,
29
29
  isIndex: true,
30
30
  });
31
31
  dataCh = await client.channels.create({
32
- name: `data-${Math.random()}-${TimeStamp.now().toString()}`,
32
+ name: id.create(),
33
33
  dataType: DataType.FLOAT32,
34
34
  index: timeCh.key,
35
35
  });
@@ -118,7 +118,7 @@ describe("WriteFrameAdapter", () => {
118
118
 
119
119
  it("should correctly adapt a name and JSON value", async () => {
120
120
  const jsonChannel = await client.channels.create({
121
- name: `json-${Math.random()}-${TimeStamp.now().toString()}`,
121
+ name: id.create(),
122
122
  dataType: DataType.JSON,
123
123
  virtual: true,
124
124
  });
@@ -134,7 +134,7 @@ describe("WriteFrameAdapter", () => {
134
134
 
135
135
  it("should correctly adapt a name and a json typed series", async () => {
136
136
  const jsonChannel = await client.channels.create({
137
- name: `json-${Math.random()}-${TimeStamp.now().toString()}`,
137
+ name: id.create(),
138
138
  dataType: DataType.JSON,
139
139
  virtual: true,
140
140
  });
@@ -150,7 +150,7 @@ describe("WriteFrameAdapter", () => {
150
150
 
151
151
  it("should correctly adapt a numeric value to a BigInt keyed by key", async () => {
152
152
  const bigIntCh = await client.channels.create({
153
- name: `bigint-${Math.random()}-${TimeStamp.now().toString()}`,
153
+ name: id.create(),
154
154
  dataType: DataType.INT64,
155
155
  virtual: true,
156
156
  });
@@ -176,7 +176,7 @@ describe("WriteFrameAdapter", () => {
176
176
 
177
177
  it("should return true when adding a new channel", async () => {
178
178
  const newCh = await client.channels.create({
179
- name: `new-${Math.random()}-${TimeStamp.now().toString()}`,
179
+ name: id.create(),
180
180
  dataType: DataType.FLOAT32,
181
181
  index: timeCh.key,
182
182
  });
@@ -191,7 +191,7 @@ describe("WriteFrameAdapter", () => {
191
191
 
192
192
  it("should return true when replacing channels", async () => {
193
193
  const newCh = await client.channels.create({
194
- name: `replacement-${Math.random()}-${TimeStamp.now().toString()}`,
194
+ name: id.create(),
195
195
  dataType: DataType.FLOAT32,
196
196
  index: timeCh.key,
197
197
  });
@@ -213,22 +213,28 @@ describe("WriteFrameAdapter", () => {
213
213
  });
214
214
  });
215
215
 
216
- describe("ReadAdapter", () => {
216
+ describe("ReadFrameAdapter", () => {
217
217
  let timeCh: channel.Channel;
218
218
  let dataCh: channel.Channel;
219
+ let extraCh: channel.Channel;
219
220
  let adapter: ReadAdapter;
220
221
 
221
222
  beforeAll(async () => {
222
223
  timeCh = await client.channels.create({
223
- name: `read-time-${Math.random()}-${TimeStamp.now().toString()}`,
224
+ name: id.create(),
224
225
  dataType: DataType.TIMESTAMP,
225
226
  isIndex: true,
226
227
  });
227
228
  dataCh = await client.channels.create({
228
- name: `read-data-${Math.random()}-${TimeStamp.now().toString()}`,
229
+ name: id.create(),
229
230
  dataType: DataType.FLOAT32,
230
231
  index: timeCh.key,
231
232
  });
233
+ extraCh = await client.channels.create({
234
+ name: id.create(),
235
+ dataType: DataType.FLOAT64,
236
+ index: timeCh.key,
237
+ });
232
238
 
233
239
  adapter = await ReadAdapter.open(client.channels.retriever, [
234
240
  timeCh.key,
@@ -236,6 +242,338 @@ describe("ReadAdapter", () => {
236
242
  ]);
237
243
  });
238
244
 
245
+ describe("adapt", () => {
246
+ describe("with keys (no conversion)", () => {
247
+ describe("hot path - exact channel match", () => {
248
+ it("should return frame unchanged when all channels match", () => {
249
+ // HOT PATH: Frame has exactly the channels registered with adapter
250
+ const ts = TimeStamp.now().valueOf();
251
+ const inputFrame = new Frame({
252
+ [timeCh.key]: new Series([ts]),
253
+ [dataCh.key]: new Series([1.5]),
254
+ });
255
+
256
+ const result = adapter.adapt(inputFrame);
257
+
258
+ // Frame should be returned unchanged (zero allocations)
259
+ expect(result).toBe(inputFrame); // Same object reference
260
+ expect(result.columns).toHaveLength(2);
261
+ expect(result.has(timeCh.key)).toBe(true);
262
+ expect(result.has(dataCh.key)).toBe(true);
263
+ expect(result.get(timeCh.key).at(0)).toEqual(ts);
264
+ expect(result.get(dataCh.key).at(0)).toEqual(1.5);
265
+ });
266
+
267
+ it("should preserve series data types in hot path", () => {
268
+ const ts = TimeStamp.now().valueOf();
269
+ const inputFrame = new Frame({
270
+ [timeCh.key]: new Series({ data: [ts], dataType: DataType.TIMESTAMP }),
271
+ [dataCh.key]: new Series({ data: [1.5], dataType: DataType.FLOAT32 }),
272
+ });
273
+
274
+ const result = adapter.adapt(inputFrame);
275
+
276
+ // Data types should be preserved
277
+ expect(result.get(timeCh.key).dataType).toEqual(DataType.TIMESTAMP);
278
+ expect(result.get(dataCh.key).dataType).toEqual(DataType.FLOAT32);
279
+ });
280
+ });
281
+
282
+ describe("cold path - filtering needed", () => {
283
+ it("should filter out extra channels in key mode", () => {
284
+ // COLD PATH: Frame has extra channels not in adapter
285
+ const ts = TimeStamp.now().valueOf();
286
+ const inputFrame = new Frame({
287
+ [timeCh.key]: new Series([ts]),
288
+ [dataCh.key]: new Series([1.5]),
289
+ [extraCh.key]: new Series([999.0]), // Extra channel
290
+ });
291
+
292
+ const result = adapter.adapt(inputFrame);
293
+
294
+ // Should filter out extraCh
295
+ expect(result).not.toBe(inputFrame); // Different object (filtered)
296
+ expect(result.columns).toHaveLength(2);
297
+ expect(result.has(timeCh.key)).toBe(true);
298
+ expect(result.has(dataCh.key)).toBe(true);
299
+ expect(result.has(extraCh.key)).toBe(false);
300
+ });
301
+
302
+ it("should handle partial matches in key mode", () => {
303
+ // Frame has some matching and some extra channels
304
+ const ts = TimeStamp.now().valueOf();
305
+ const inputFrame = new Frame({
306
+ [timeCh.key]: new Series([ts]),
307
+ [extraCh.key]: new Series([999.0]),
308
+ });
309
+
310
+ const result = adapter.adapt(inputFrame);
311
+
312
+ expect(result.columns).toHaveLength(1);
313
+ expect(result.has(timeCh.key)).toBe(true);
314
+ expect(result.has(extraCh.key)).toBe(false);
315
+ });
316
+
317
+ it("should return empty frame when no channels match in key mode", () => {
318
+ const inputFrame = new Frame({
319
+ [extraCh.key]: new Series([999.0]),
320
+ });
321
+
322
+ const result = adapter.adapt(inputFrame);
323
+
324
+ expect(result.columns).toHaveLength(0);
325
+ expect(result.series).toHaveLength(0);
326
+ });
327
+ });
328
+ });
329
+
330
+ describe("with names (conversion)", () => {
331
+ let nameAdapter: ReadAdapter;
332
+
333
+ beforeAll(async () => {
334
+ // Create adapter with channel names (triggers key-to-name mapping)
335
+ nameAdapter = await ReadAdapter.open(client.channels.retriever, [
336
+ timeCh.name,
337
+ dataCh.name,
338
+ ]);
339
+ });
340
+
341
+ describe("hot path - exact match, only convert", () => {
342
+ it("should convert channel keys to names when all channels match", () => {
343
+ // HOT PATH: Frame has exactly the channels in adapter
344
+ const ts = TimeStamp.now().valueOf();
345
+ const inputFrame = new Frame({
346
+ [timeCh.key]: new Series([ts]),
347
+ [dataCh.key]: new Series([2.5]),
348
+ });
349
+
350
+ const result = nameAdapter.adapt(inputFrame);
351
+
352
+ // Output should have names instead of keys (one allocation for conversion)
353
+ expect(result.columns).toHaveLength(2);
354
+ expect(result.has(timeCh.name)).toBe(true);
355
+ expect(result.has(dataCh.name)).toBe(true);
356
+ expect(result.get(timeCh.name).at(0)).toEqual(ts);
357
+ expect(result.get(dataCh.name).at(0)).toEqual(2.5);
358
+ });
359
+
360
+ it("should handle multiple values in hot path", () => {
361
+ const ts = TimeStamp.now().valueOf();
362
+ const inputFrame = new Frame({
363
+ [timeCh.key]: new Series([ts, ts + 1000n]),
364
+ [dataCh.key]: new Series([1.0, 2.0]),
365
+ });
366
+
367
+ const result = nameAdapter.adapt(inputFrame);
368
+
369
+ expect(result.columns).toHaveLength(2);
370
+ expect(result.get(timeCh.name)).toHaveLength(2);
371
+ expect(result.get(dataCh.name)).toHaveLength(2);
372
+ expect(result.get(timeCh.name).at(0)).toEqual(ts);
373
+ expect(result.get(timeCh.name).at(1)).toEqual(ts + 1000n);
374
+ expect(result.get(dataCh.name).at(0)).toEqual(1.0);
375
+ expect(result.get(dataCh.name).at(1)).toEqual(2.0);
376
+ });
377
+
378
+ it("should preserve data types during name conversion", () => {
379
+ const ts = TimeStamp.now().valueOf();
380
+ const inputFrame = new Frame({
381
+ [timeCh.key]: new Series({ data: [ts], dataType: DataType.TIMESTAMP }),
382
+ [dataCh.key]: new Series({ data: [3.5], dataType: DataType.FLOAT32 }),
383
+ });
384
+
385
+ const result = nameAdapter.adapt(inputFrame);
386
+
387
+ expect(result.get(timeCh.name).dataType).toEqual(DataType.TIMESTAMP);
388
+ expect(result.get(dataCh.name).dataType).toEqual(DataType.FLOAT32);
389
+ });
390
+ });
391
+
392
+ describe("cold path - filter and convert", () => {
393
+ it("should filter out extra channels while converting", async () => {
394
+ // COLD PATH: Frame has extra channels that need filtering
395
+ const ts = TimeStamp.now().valueOf();
396
+ const inputFrame = new Frame({
397
+ [timeCh.key]: new Series([ts]),
398
+ [dataCh.key]: new Series([1.5]),
399
+ [extraCh.key]: new Series([999.0]), // Extra channel
400
+ });
401
+
402
+ const result = nameAdapter.adapt(inputFrame);
403
+
404
+ // Should filter extraCh and convert remaining keys to names
405
+ expect(result.columns).toHaveLength(2);
406
+ expect(result.has(timeCh.name)).toBe(true);
407
+ expect(result.has(dataCh.name)).toBe(true);
408
+ expect(result.has(extraCh.key)).toBe(false);
409
+ });
410
+
411
+ it("should handle partial matches while converting", async () => {
412
+ const filterAdapter = await ReadAdapter.open(client.channels.retriever, [
413
+ timeCh.name,
414
+ ]);
415
+
416
+ const ts = TimeStamp.now().valueOf();
417
+ const inputFrame = new Frame({
418
+ [timeCh.key]: new Series([ts]),
419
+ [extraCh.key]: new Series([999.0]),
420
+ });
421
+
422
+ const result = filterAdapter.adapt(inputFrame);
423
+
424
+ expect(result.columns).toHaveLength(1);
425
+ expect(result.has(timeCh.name)).toBe(true);
426
+ expect(result.has(extraCh.key)).toBe(false);
427
+ expect(result.get(timeCh.name).at(0)).toEqual(ts);
428
+ });
429
+
430
+ it("should return empty frame when no channels match", async () => {
431
+ const filterAdapter = await ReadAdapter.open(client.channels.retriever, [
432
+ timeCh.name,
433
+ ]);
434
+
435
+ const inputFrame = new Frame({
436
+ [extraCh.key]: new Series([999.0]),
437
+ });
438
+
439
+ const result = filterAdapter.adapt(inputFrame);
440
+
441
+ expect(result.columns).toHaveLength(0);
442
+ expect(result.series).toHaveLength(0);
443
+ });
444
+ });
445
+ });
446
+
447
+ describe("edge cases", () => {
448
+ it("should handle empty frames", () => {
449
+ const inputFrame = new Frame({});
450
+
451
+ const result = adapter.adapt(inputFrame);
452
+
453
+ expect(result.columns).toHaveLength(0);
454
+ expect(result.series).toHaveLength(0);
455
+ });
456
+
457
+ it("should handle frames with empty series", () => {
458
+ const inputFrame = new Frame({
459
+ [timeCh.key]: new Series({ data: [], dataType: DataType.TIMESTAMP }),
460
+ [dataCh.key]: new Series({ data: [], dataType: DataType.FLOAT32 }),
461
+ });
462
+
463
+ const result = adapter.adapt(inputFrame);
464
+
465
+ expect(result.columns).toHaveLength(2);
466
+ expect(result.get(timeCh.key)).toHaveLength(0);
467
+ expect(result.get(dataCh.key)).toHaveLength(0);
468
+ });
469
+ });
470
+
471
+ describe("data integrity", () => {
472
+ it("should preserve series values across multiple data types", async () => {
473
+ const int64Ch = await client.channels.create({
474
+ name: id.create(),
475
+ dataType: DataType.INT64,
476
+ index: timeCh.key,
477
+ });
478
+
479
+ const testAdapter = await ReadAdapter.open(client.channels.retriever, [
480
+ timeCh.key,
481
+ dataCh.key,
482
+ int64Ch.key,
483
+ ]);
484
+
485
+ const ts = TimeStamp.now().valueOf();
486
+ const inputFrame = new Frame({
487
+ [timeCh.key]: new Series([ts, ts + 1000n]),
488
+ [dataCh.key]: new Series([1.5, 2.5]),
489
+ [int64Ch.key]: new Series([100n, 200n]),
490
+ });
491
+
492
+ const result = testAdapter.adapt(inputFrame);
493
+
494
+ // Verify all values preserved
495
+ expect(result.get(timeCh.key).at(0)).toEqual(ts);
496
+ expect(result.get(timeCh.key).at(1)).toEqual(ts + 1000n);
497
+ expect(result.get(dataCh.key).at(0)).toEqual(1.5);
498
+ expect(result.get(dataCh.key).at(1)).toEqual(2.5);
499
+ expect(result.get(int64Ch.key).at(0)).toEqual(100n);
500
+ expect(result.get(int64Ch.key).at(1)).toEqual(200n);
501
+ });
502
+
503
+ it("should preserve series lengths after filtering", () => {
504
+ const ts = TimeStamp.now().valueOf();
505
+ const inputFrame = new Frame({
506
+ [timeCh.key]: new Series([ts, ts + 1000n, ts + 2000n]),
507
+ [dataCh.key]: new Series([1.0, 2.0, 3.0]),
508
+ [extraCh.key]: new Series([999.0, 888.0, 777.0]),
509
+ });
510
+
511
+ const result = adapter.adapt(inputFrame);
512
+
513
+ // Lengths should be preserved for included channels
514
+ expect(result.get(timeCh.key)).toHaveLength(3);
515
+ expect(result.get(dataCh.key)).toHaveLength(3);
516
+ });
517
+
518
+ it("should preserve series order", () => {
519
+ const ts = TimeStamp.now().valueOf();
520
+ // Create frame with explicit column order
521
+ const inputFrame = new Frame(
522
+ [dataCh.key, timeCh.key],
523
+ [new Series([1.0, 2.0, 3.0]), new Series([ts, ts + 1000n, ts + 2000n])],
524
+ );
525
+
526
+ const result = adapter.adapt(inputFrame);
527
+
528
+ // Order should be preserved (dataCh first, then timeCh)
529
+ expect(result.columns[0]).toEqual(dataCh.key);
530
+ expect(result.columns[1]).toEqual(timeCh.key);
531
+ });
532
+ });
533
+
534
+ describe("state management", () => {
535
+ it("should handle multiple sequential updates correctly", async () => {
536
+ // Start with NAME mode to enable filtering
537
+ const newAdapter = await ReadAdapter.open(client.channels.retriever, [
538
+ timeCh.name,
539
+ ]);
540
+
541
+ // Initial state: only timeCh registered
542
+ const ts = TimeStamp.now().valueOf();
543
+ const inputFrame = new Frame({
544
+ [timeCh.key]: new Series([ts]),
545
+ [dataCh.key]: new Series([1.5]),
546
+ });
547
+
548
+ // Should filter out dataCh and convert timeCh key to name
549
+ let result = newAdapter.adapt(inputFrame);
550
+ expect(result.columns).toHaveLength(1);
551
+ expect(result.has(timeCh.name)).toBe(true);
552
+ expect(result.has(dataCh.name)).toBe(false);
553
+
554
+ // Update to include dataCh
555
+ await newAdapter.update([timeCh.name, dataCh.name]);
556
+
557
+ // Should now include both channels (converted to names)
558
+ result = newAdapter.adapt(inputFrame);
559
+ expect(result.columns).toHaveLength(2);
560
+ expect(result.has(timeCh.name)).toBe(true);
561
+ expect(result.has(dataCh.name)).toBe(true);
562
+ });
563
+ });
564
+
565
+ describe("codec integration", () => {
566
+ it("should update codec when channels change", async () => {
567
+ const codecAdapter = await ReadAdapter.open(client.channels.retriever, [
568
+ timeCh.key,
569
+ ]);
570
+ expect(codecAdapter.keys).toHaveLength(1);
571
+ await codecAdapter.update([timeCh.key, dataCh.key]);
572
+ expect(codecAdapter.keys).toHaveLength(2);
573
+ });
574
+ });
575
+ });
576
+
239
577
  describe("update", () => {
240
578
  it("should return false when updating with the same channels", async () => {
241
579
  const hasChanged = await adapter.update([timeCh.key, dataCh.key]);
@@ -244,7 +582,7 @@ describe("ReadAdapter", () => {
244
582
 
245
583
  it("should return true when adding a new channel", async () => {
246
584
  const newCh = await client.channels.create({
247
- name: `read-new-${Math.random()}-${TimeStamp.now().toString()}`,
585
+ name: id.create(),
248
586
  dataType: DataType.FLOAT32,
249
587
  index: timeCh.key,
250
588
  });
@@ -259,7 +597,7 @@ describe("ReadAdapter", () => {
259
597
 
260
598
  it("should return true when replacing channels", async () => {
261
599
  const newCh = await client.channels.create({
262
- name: `read-replacement-${Math.random()}-${TimeStamp.now().toString()}`,
600
+ name: id.create(),
263
601
  dataType: DataType.FLOAT32,
264
602
  index: timeCh.key,
265
603
  });
@@ -17,13 +17,13 @@ import { type CrudeFrame, Frame } from "@/framer/frame";
17
17
  export class ReadAdapter {
18
18
  private adapter: Map<channel.Key, channel.Name> | null;
19
19
  retriever: channel.Retriever;
20
- keys: channel.Key[];
20
+ keys: Set<channel.Key>;
21
21
  codec: Codec;
22
22
 
23
23
  private constructor(retriever: channel.Retriever) {
24
24
  this.retriever = retriever;
25
25
  this.adapter = null;
26
- this.keys = [];
26
+ this.keys = new Set();
27
27
  this.codec = new Codec();
28
28
  }
29
29
 
@@ -40,7 +40,10 @@ export class ReadAdapter {
40
40
  const { variant, normalized } = channel.analyzeParams(channels);
41
41
  const fetched = await this.retriever.retrieve(normalized);
42
42
  const newKeys = fetched.map((c) => c.key);
43
- if (compare.uniqueUnorderedPrimitiveArrays(this.keys, newKeys) === compare.EQUAL)
43
+ if (
44
+ compare.uniqueUnorderedPrimitiveArrays(Array.from(this.keys), newKeys) ===
45
+ compare.EQUAL
46
+ )
44
47
  return false;
45
48
  this.codec.update(
46
49
  newKeys,
@@ -48,7 +51,7 @@ export class ReadAdapter {
48
51
  );
49
52
  if (variant === "keys") {
50
53
  this.adapter = null;
51
- this.keys = normalized as channel.Key[];
54
+ this.keys = new Set(normalized as channel.Key[]);
52
55
  return true;
53
56
  }
54
57
  const a = new Map<channel.Key, channel.Name>();
@@ -58,20 +61,27 @@ export class ReadAdapter {
58
61
  if (channel == null) throw new Error(`Channel ${name} not found`);
59
62
  a.set(channel.key, channel.name);
60
63
  });
61
- this.keys = Array.from(this.adapter.keys());
64
+ this.keys = new Set(this.adapter.keys());
62
65
  return true;
63
66
  }
64
67
 
65
- adapt(columnsOrData: Frame): Frame {
66
- if (this.adapter == null) return columnsOrData;
68
+ adapt(frm: Frame): Frame {
69
+ if (this.adapter == null) {
70
+ let shouldFilter = false;
71
+ frm.forEach((k) => {
72
+ if (!this.keys.has(k as channel.Key)) shouldFilter = true;
73
+ });
74
+ if (shouldFilter) return frm.filter((k) => this.keys.has(k as channel.Key));
75
+ return frm;
76
+ }
67
77
  const a = this.adapter;
68
- return columnsOrData.map((k, arr) => {
69
- if (typeof k === "number") {
70
- const name = a.get(k);
71
- if (name == null) throw new Error(`Channel ${k} not found`);
72
- return [name, arr];
78
+ return frm.mapFilter((col, arr) => {
79
+ if (typeof col === "number") {
80
+ const name = a.get(col);
81
+ if (name == null) return [col, arr, false];
82
+ return [name, arr, true];
73
83
  }
74
- return [k, arr];
84
+ return [col, arr, true];
75
85
  });
76
86
  }
77
87
  }
@@ -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 { TimeSpan, TimeStamp } from "@synnaxlabs/x";
10
+ import { id, TimeSpan, TimeStamp } from "@synnaxlabs/x";
11
11
  import { describe, expect, it } from "vitest";
12
12
 
13
13
  import { createTestClient } from "@/testutil/client";
@@ -17,14 +17,13 @@ const client = createTestClient();
17
17
  describe("Client", () => {
18
18
  describe("read + write", () => {
19
19
  it("should correctly write and read a frame of data", async () => {
20
- const rand = `${TimeStamp.now().toString()}${Math.random()}`;
21
20
  const time = await client.channels.create({
22
- name: `time-${rand}`,
21
+ name: id.create(),
23
22
  dataType: "timestamp",
24
23
  isIndex: true,
25
24
  });
26
25
  const data = await client.channels.create({
27
- name: `data-${rand}`,
26
+ name: id.create(),
28
27
  dataType: "float32",
29
28
  index: time.key,
30
29
  });
@@ -38,14 +37,13 @@ describe("Client", () => {
38
37
  expect(Array.from(frame.get(data.key))).toEqual([1]);
39
38
  });
40
39
  it("should correctly write a single series of data", async () => {
41
- const rand = `${TimeStamp.now().toString()}${Math.random()}`;
42
40
  const time = await client.channels.create({
43
- name: `time-${rand}`,
41
+ name: id.create(),
44
42
  dataType: "timestamp",
45
43
  isIndex: true,
46
44
  });
47
45
  const data = await client.channels.create({
48
- name: `data-${rand}`,
46
+ name: id.create(),
49
47
  dataType: "float32",
50
48
  index: time.key,
51
49
  });
@@ -62,14 +60,13 @@ describe("Client", () => {
62
60
  });
63
61
  describe("readLatestN", () => {
64
62
  it("should correctly read the latest N samples from a single channel", async () => {
65
- const rand = `${TimeStamp.now().toString()}${Math.random()}`;
66
63
  const time = await client.channels.create({
67
- name: `time-${rand}`,
64
+ name: id.create(),
68
65
  dataType: "timestamp",
69
66
  isIndex: true,
70
67
  });
71
68
  const data = await client.channels.create({
72
- name: `data-${rand}`,
69
+ name: id.create(),
73
70
  dataType: "float32",
74
71
  index: time.key,
75
72
  });
@@ -94,19 +91,18 @@ describe("Client", () => {
94
91
  });
95
92
 
96
93
  it("should correctly read the latest N samples from multiple channels", async () => {
97
- const rand = `${TimeStamp.now().toString()}${Math.random()}`;
98
94
  const time = await client.channels.create({
99
- name: `time-${rand}`,
95
+ name: id.create(),
100
96
  dataType: "timestamp",
101
97
  isIndex: true,
102
98
  });
103
99
  const data1 = await client.channels.create({
104
- name: `data1-${rand}`,
100
+ name: id.create(),
105
101
  dataType: "float32",
106
102
  index: time.key,
107
103
  });
108
104
  const data2 = await client.channels.create({
109
- name: `data2-${rand}`,
105
+ name: id.create(),
110
106
  dataType: "float32",
111
107
  index: time.key,
112
108
  });
@@ -142,14 +138,13 @@ describe("Client", () => {
142
138
  });
143
139
 
144
140
  it("should return empty series when no data exists", async () => {
145
- const rand = `${TimeStamp.now().toString()}${Math.random()}`;
146
141
  const time = await client.channels.create({
147
- name: `time-${rand}`,
142
+ name: id.create(),
148
143
  dataType: "timestamp",
149
144
  isIndex: true,
150
145
  });
151
146
  const data = await client.channels.create({
152
- name: `data-${rand}`,
147
+ name: id.create(),
153
148
  dataType: "float32",
154
149
  index: time.key,
155
150
  });
@@ -159,14 +154,13 @@ describe("Client", () => {
159
154
  });
160
155
 
161
156
  it("should correctly handle N larger than available data", async () => {
162
- const rand = `${TimeStamp.now().toString()}${Math.random()}`;
163
157
  const time = await client.channels.create({
164
- name: `time-${rand}`,
158
+ name: id.create(),
165
159
  dataType: "timestamp",
166
160
  isIndex: true,
167
161
  });
168
162
  const data = await client.channels.create({
169
- name: `data-${rand}`,
163
+ name: id.create(),
170
164
  dataType: "float32",
171
165
  index: time.key,
172
166
  });