@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
@@ -544,6 +544,137 @@ describe("framer.Frame", () => {
544
544
  });
545
545
  });
546
546
 
547
+ describe("forEachUnique", () => {
548
+ it("should iterate over all unique columns in the frame", () => {
549
+ const f = new framer.Frame(
550
+ new Map([
551
+ [
552
+ 12,
553
+ [
554
+ new Series({
555
+ data: new Float32Array([1, 2, 3]),
556
+ timeRange: new TimeRange(1, 2),
557
+ }),
558
+ ],
559
+ ],
560
+ [
561
+ 13,
562
+ [
563
+ new Series({
564
+ data: new Float32Array([4, 5, 6]),
565
+ timeRange: new TimeRange(4, 6),
566
+ }),
567
+ ],
568
+ ],
569
+ [
570
+ 14,
571
+ [
572
+ new Series({
573
+ data: new Float32Array([7, 8, 9]),
574
+ timeRange: new TimeRange(7, 9),
575
+ }),
576
+ ],
577
+ ],
578
+ ]),
579
+ );
580
+ f.push(
581
+ 12,
582
+ new Series({
583
+ data: new Float32Array([10, 11, 12]),
584
+ timeRange: new TimeRange(10, 12),
585
+ }),
586
+ );
587
+ f.push(
588
+ 13,
589
+ new Series({
590
+ data: new Float32Array([13, 14, 15]),
591
+ timeRange: new TimeRange(13, 15),
592
+ }),
593
+ );
594
+ f.push(
595
+ 14,
596
+ new Series({
597
+ data: new Float32Array([7, 8, 9]),
598
+ timeRange: new TimeRange(7, 9),
599
+ }),
600
+ );
601
+ const firstValues = new Set<number>();
602
+ f.forEachUnique((_, ms) => {
603
+ firstValues.add(ms.at(0) as number);
604
+ });
605
+ expect(firstValues).toEqual(new Set([1, 4, 7]));
606
+ });
607
+ it("should get all data from the frame", () => {
608
+ const f = new framer.Frame(
609
+ new Map([
610
+ [
611
+ 12,
612
+ [
613
+ new Series({
614
+ data: new Float32Array([1, 2, 3]),
615
+ timeRange: new TimeRange(1, 2),
616
+ }),
617
+ ],
618
+ ],
619
+ ]),
620
+ );
621
+ f.push(
622
+ 12,
623
+ new Series({
624
+ data: new Float32Array([10, 11, 12]),
625
+ timeRange: new TimeRange(10, 12),
626
+ }),
627
+ );
628
+ f.push(
629
+ 13,
630
+ new Series({
631
+ data: new Float32Array([13, 14, 15]),
632
+ timeRange: new TimeRange(13, 15),
633
+ }),
634
+ );
635
+ const data: number[] = [];
636
+ f.forEachUnique((key, ms) => {
637
+ if (key !== 12) return;
638
+ for (let i = 0; i < ms.length; i++) data.push(ms.at(i, true) as number);
639
+ });
640
+ expect(data).toEqual([1, 2, 3, 10, 11, 12]);
641
+ });
642
+ it("should allow use of an index parameter", () => {
643
+ const f = new framer.Frame(
644
+ new Map([
645
+ [
646
+ 12,
647
+ [
648
+ new Series({
649
+ data: new Float32Array([1, 2, 3]),
650
+ timeRange: new TimeRange(1, 2),
651
+ }),
652
+ ],
653
+ ],
654
+ ]),
655
+ );
656
+ f.push(
657
+ 12,
658
+ new Series({
659
+ data: new Float32Array([10, 11, 12]),
660
+ timeRange: new TimeRange(10, 12),
661
+ }),
662
+ );
663
+ f.push(
664
+ 13,
665
+ new Series({
666
+ data: new Float32Array([13, 14, 15]),
667
+ timeRange: new TimeRange(13, 15),
668
+ }),
669
+ );
670
+ let currentIndex = 0;
671
+ f.forEachUnique((_, __, i) => {
672
+ expect(i).toEqual(currentIndex);
673
+ currentIndex++;
674
+ });
675
+ expect(currentIndex).toEqual(2);
676
+ });
677
+ });
547
678
  describe("mapFilter", () => {
548
679
  it("should filter out items based on the keep boolean", () => {
549
680
  const f = new framer.Frame(
@@ -386,6 +386,14 @@ export class Frame {
386
386
  });
387
387
  }
388
388
 
389
+ /**
390
+ * Iterates over all unique columns in the frame.
391
+ * @param fn a function that takes a channel key, multi-series, and index.
392
+ */
393
+ forEachUnique(fn: (k: channel.KeyOrName, ms: MultiSeries, i: number) => void): void {
394
+ this.uniqueColumns.forEach((k, i) => fn(k, this.get(k), i));
395
+ }
396
+
389
397
  at(index: number, required: true): Record<channel.KeyOrName, TelemValue>;
390
398
 
391
399
  at(
@@ -457,11 +465,11 @@ export class Frame {
457
465
  export const frameZ = z.object({
458
466
  keys: z.union([
459
467
  z.null().transform<number[]>(() => []),
460
- z.number().array().optional().default([]),
468
+ z.number().array().default([]),
461
469
  ]),
462
470
  series: z.union([
463
471
  z.null().transform<z.infer<typeof Series.crudeZ>[]>(() => []),
464
- Series.crudeZ.array().optional().default([]),
472
+ Series.crudeZ.array().default([]),
465
473
  ]),
466
474
  });
467
475
 
@@ -196,9 +196,9 @@ export class Iterator {
196
196
  }
197
197
 
198
198
  /**
199
- * @returns true if the iterator value contains a valid segment, and fale otherwise.
200
- * valid most commonly returns false when the iterator is exhausted or has
201
- * accumulated an error.
199
+ * @returns true if the iterator value contains a valid segment, and false otherwise.
200
+ * valid most commonly returns false when the iterator is exhausted or has accumulated
201
+ * an error.
202
202
  */
203
203
  async valid(): Promise<boolean> {
204
204
  return await this.execute({ command: Command.Valid });
@@ -8,8 +8,7 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  import { EOF, Unreachable } from "@synnaxlabs/freighter";
11
- import { id, sleep } from "@synnaxlabs/x";
12
- import { DataType, Series, TimeSpan, TimeStamp } from "@synnaxlabs/x/telem";
11
+ import { DataType, id, Rate, Series, sleep, TimeSpan, TimeStamp } from "@synnaxlabs/x";
13
12
  import { describe, expect, it, test, vi } from "vitest";
14
13
 
15
14
  import { type channel } from "@/channel";
@@ -114,11 +113,98 @@ describe("Streamer", () => {
114
113
  });
115
114
  });
116
115
 
116
+ describe("throttling", () => {
117
+ test("throttle at 60Hz", async () => {
118
+ const ch = await newVirtualChannel(client);
119
+ const streamer = await client.openStreamer({
120
+ channels: ch.key,
121
+ throttleRate: 60,
122
+ });
123
+ const writer = await client.openWriter({
124
+ start: TimeStamp.now(),
125
+ channels: ch.key,
126
+ });
127
+ try {
128
+ const startTime = Date.now();
129
+ // Write data rapidly
130
+ for (let i = 0; i < 10; i++) {
131
+ await writer.write(ch.key, new Float64Array([i]));
132
+ await sleep.sleep(TimeSpan.milliseconds(5));
133
+ }
134
+
135
+ // Read frames - should be throttled
136
+ const receivedFrames: Frame[] = [];
137
+ const timeout = Date.now() + 500;
138
+ while (Date.now() < timeout)
139
+ try {
140
+ const frame = await Promise.race([
141
+ streamer.read(),
142
+ sleep.sleep(TimeSpan.milliseconds(100)).then(() => null),
143
+ ]);
144
+ if (frame) receivedFrames.push(frame);
145
+ else break;
146
+ } catch {
147
+ break;
148
+ }
149
+
150
+ expect(receivedFrames.length).toBeGreaterThan(0);
151
+ const elapsed = Date.now() - startTime;
152
+ // Should take at least the throttle period
153
+ expect(elapsed).toBeGreaterThanOrEqual(16); // ~1/60Hz
154
+ } finally {
155
+ await writer.close();
156
+ streamer.close();
157
+ }
158
+ });
159
+
160
+ test("no throttling with rate of 0", async () => {
161
+ const ch = await newVirtualChannel(client);
162
+ const streamer = await client.openStreamer({
163
+ channels: ch.key,
164
+ throttleRate: 0,
165
+ });
166
+ const writer = await client.openWriter({
167
+ start: TimeStamp.now(),
168
+ channels: ch.key,
169
+ });
170
+ try {
171
+ await writer.write(ch.key, new Float64Array([1, 2, 3]));
172
+ const d = await streamer.read();
173
+ expect(Array.from(d.get(ch.key))).toEqual([1, 2, 3]);
174
+ } finally {
175
+ await writer.close();
176
+ streamer.close();
177
+ }
178
+ });
179
+
180
+ test("combine throttling and downsampling", async () => {
181
+ const ch = await newVirtualChannel(client);
182
+ const streamer = await client.openStreamer({
183
+ channels: ch.key,
184
+ downsampleFactor: 2,
185
+ throttleRate: 10,
186
+ });
187
+ const writer = await client.openWriter({
188
+ start: TimeStamp.now(),
189
+ channels: ch.key,
190
+ });
191
+ try {
192
+ await writer.write(ch.key, new Float64Array([1, 2, 3, 4, 5, 6]));
193
+ const d = await streamer.read();
194
+ // Should be downsampled to [1, 3, 5] and throttled
195
+ expect(Array.from(d.get(ch.key))).toEqual([1, 3, 5]);
196
+ } finally {
197
+ await writer.close();
198
+ streamer.close();
199
+ }
200
+ });
201
+ });
202
+
117
203
  describe("calculations", () => {
118
204
  test("basic calculated channel streaming", async () => {
119
205
  // Create a timestamp index channel
120
206
  const timeChannel = await client.channels.create({
121
- name: "calc_test_time",
207
+ name: id.create(),
122
208
  isIndex: true,
123
209
  dataType: DataType.TIMESTAMP,
124
210
  });
@@ -139,7 +225,7 @@ describe("Streamer", () => {
139
225
 
140
226
  // Create calculated channel that adds the two source channels
141
227
  const calcChannel = await client.channels.create({
142
- name: "test_calc",
228
+ name: id.create(),
143
229
  dataType: DataType.FLOAT64,
144
230
  virtual: true,
145
231
  expression: `return ${channelA.name} + ${channelB.name}`,
@@ -193,7 +279,7 @@ describe("Streamer", () => {
193
279
 
194
280
  // Create calculated channel that adds 5
195
281
  const calcChannel = await client.channels.create({
196
- name: "calc_const_channel",
282
+ name: id.create(),
197
283
  dataType: DataType.FLOAT64,
198
284
  virtual: true,
199
285
  expression: `return ${baseChannel.name} + 5`,
@@ -232,23 +318,24 @@ describe("Streamer", () => {
232
318
  test("calculated channel with multiple operations", async () => {
233
319
  // Create timestamp channel
234
320
  const timeChannel = await client.channels.create({
235
- name: "calc_multi_time",
321
+ name: id.create(),
236
322
  isIndex: true,
237
323
  dataType: DataType.TIMESTAMP,
238
324
  });
239
325
 
240
326
  // Create source channels
327
+ const names = [id.create(), id.create()];
241
328
  const [channelA, channelB] = await client.channels.create([
242
- { name: "multi_test_a", dataType: DataType.FLOAT64, index: timeChannel.key },
243
- { name: "multi_test_b", dataType: DataType.FLOAT64, index: timeChannel.key },
329
+ { name: names[0], dataType: DataType.FLOAT64, index: timeChannel.key },
330
+ { name: names[1], dataType: DataType.FLOAT64, index: timeChannel.key },
244
331
  ]);
245
332
 
246
333
  // Create calculated channel with multiple operations
247
334
  const calcChannel = await client.channels.create({
248
- name: "multi_calc",
335
+ name: id.create(),
249
336
  dataType: DataType.FLOAT64,
250
337
  virtual: true,
251
- expression: "return (multi_test_a * 2) + (multi_test_b / 2)",
338
+ expression: `return (${names[0]} * 2) + (${names[1]} / 2)`,
252
339
  });
253
340
 
254
341
  const streamer = await client.openStreamer(calcChannel.key);
@@ -279,7 +366,7 @@ describe("Streamer", () => {
279
366
  describe("legacy calculations", async () => {
280
367
  it("should correctly execute a calculation with a requires field", async () => {
281
368
  const timeChannel = await client.channels.create({
282
- name: "calc_test_time",
369
+ name: id.create(),
283
370
  isIndex: true,
284
371
  dataType: DataType.TIMESTAMP,
285
372
  });
@@ -298,7 +385,7 @@ describe("Streamer", () => {
298
385
  ]);
299
386
 
300
387
  const calcChannel = await client.channels.create({
301
- name: "test_calc",
388
+ name: id.create(),
302
389
  dataType: DataType.FLOAT64,
303
390
  virtual: true,
304
391
  expression: `return ${channelA.name} + ${channelB.name}`,
@@ -398,6 +485,7 @@ describe("Streamer", () => {
398
485
  expect(openMock).toHaveBeenCalledWith({
399
486
  ...config,
400
487
  downsampleFactor: 1,
488
+ throttleRate: new Rate(0),
401
489
  });
402
490
  await hardened.update([1, 2, 3]);
403
491
  expect(streamer.updateMock).toHaveBeenCalledWith([1, 2, 3]);
@@ -8,7 +8,7 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  import { EOF, type Stream, type WebSocketClient } from "@synnaxlabs/freighter";
11
- import { breaker, observe, TimeSpan } from "@synnaxlabs/x";
11
+ import { breaker, observe, Rate, TimeSpan } from "@synnaxlabs/x";
12
12
  import { z } from "zod";
13
13
 
14
14
  import { type channel } from "@/channel";
@@ -18,7 +18,11 @@ import { WSStreamerCodec } from "@/framer/codec";
18
18
  import { Frame, frameZ } from "@/framer/frame";
19
19
  import { StreamProxy } from "@/framer/streamProxy";
20
20
 
21
- const reqZ = z.object({ keys: z.number().array(), downsampleFactor: z.number() });
21
+ const reqZ = z.object({
22
+ keys: z.number().array(),
23
+ downsampleFactor: z.int(),
24
+ throttleRate: Rate.z.optional(),
25
+ });
22
26
 
23
27
  /**
24
28
  * Request interface for streaming frames from a Synnax cluster.
@@ -38,10 +42,12 @@ const intermediateStreamerConfigZ = z.object({
38
42
  /** The channels to stream data from. Can be channel keys, names, or payloads. */
39
43
  channels: paramsZ,
40
44
  /** Optional factor to downsample the data by. Defaults to 1 (no downsampling). */
41
- downsampleFactor: z.number().optional().default(1),
42
- /** useHighPerformanceCodec sets whether the writer will use the synnax frame
43
- /* encoder as opposed to the standard JSON encoding mechanisms for frames. */
44
- useHighPerformanceCodec: z.boolean().optional().default(true),
45
+ downsampleFactor: z.int().default(1),
46
+ /** Optional throttle rate in Hz to limit the rate of frames sent to the client. Defaults to 0 (no throttling). */
47
+ throttleRate: Rate.z.default(new Rate(0)),
48
+ /** useHighPerformanceCodec sets whether the writer will use the Synnax frame encoder
49
+ as opposed to the standard JSON encoding mechanisms for frames. */
50
+ useHighPerformanceCodec: z.boolean().default(true),
45
51
  });
46
52
 
47
53
  export const streamerConfigZ = intermediateStreamerConfigZ.or(
@@ -105,10 +111,16 @@ export const createStreamOpener =
105
111
  if (cfg.useHighPerformanceCodec)
106
112
  client = client.withCodec(new WSStreamerCodec(adapter.codec));
107
113
  const stream = await client.stream("/frame/stream", reqZ, resZ);
108
- const streamer = new CoreStreamer(stream, adapter);
114
+ const streamer = new CoreStreamer(
115
+ stream,
116
+ adapter,
117
+ cfg.downsampleFactor,
118
+ cfg.throttleRate,
119
+ );
109
120
  stream.send({
110
121
  keys: Array.from(adapter.keys),
111
122
  downsampleFactor: cfg.downsampleFactor,
123
+ throttleRate: cfg.throttleRate,
112
124
  });
113
125
  const [, err] = await stream.receive();
114
126
  if (err != null) throw err;
@@ -132,11 +144,18 @@ class CoreStreamer implements Streamer {
132
144
  private readonly stream: StreamProxy<typeof reqZ, typeof resZ>;
133
145
  private readonly adapter: ReadAdapter;
134
146
  private readonly downsampleFactor: number;
147
+ private readonly throttleRate: Rate;
135
148
 
136
- constructor(stream: Stream<typeof reqZ, typeof resZ>, adapter: ReadAdapter) {
149
+ constructor(
150
+ stream: Stream<typeof reqZ, typeof resZ>,
151
+ adapter: ReadAdapter,
152
+ downsampleFactor: number = 1,
153
+ throttleRate: Rate = new Rate(0),
154
+ ) {
137
155
  this.stream = new StreamProxy("Streamer", stream);
138
156
  this.adapter = adapter;
139
- this.downsampleFactor = 1;
157
+ this.downsampleFactor = downsampleFactor;
158
+ this.throttleRate = throttleRate;
140
159
  }
141
160
 
142
161
  get keys(): channel.Key[] {
@@ -163,6 +182,7 @@ class CoreStreamer implements Streamer {
163
182
  this.stream.send({
164
183
  keys: Array.from(this.adapter.keys),
165
184
  downsampleFactor: this.downsampleFactor,
185
+ throttleRate: this.throttleRate,
166
186
  });
167
187
  }
168
188
 
@@ -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, TimeRange, TimeSpan, TimeStamp } from "@synnaxlabs/x";
10
+ import { DataType, id, TimeRange, TimeSpan, TimeStamp } from "@synnaxlabs/x";
11
11
  import { describe, expect, it, test } from "vitest";
12
12
 
13
13
  import { UnauthorizedError, ValidationError } from "@/errors";
@@ -42,8 +42,8 @@ describe("Writer", () => {
42
42
  const channels = await newIndexedPair(client);
43
43
  const writer = await client.openWriter({ start: TimeStamp.now(), channels });
44
44
  await expect(
45
- writer.write("billy bob", randomSeries(10, DataType.FLOAT64)),
46
- ).rejects.toThrow('Channel "billy bob" not found');
45
+ writer.write("nonexistent_channel", randomSeries(10, DataType.FLOAT64)),
46
+ ).rejects.toThrow('Channel "nonexistent_channel" not found');
47
47
  await writer.close();
48
48
  });
49
49
 
@@ -152,13 +152,13 @@ describe("Writer", () => {
152
152
 
153
153
  test("write with out of order timestamp", async () => {
154
154
  const indexCh = await client.channels.create({
155
- name: "idx",
155
+ name: id.create(),
156
156
  dataType: DataType.TIMESTAMP,
157
157
  isIndex: true,
158
158
  });
159
159
 
160
160
  const dataCh = await client.channels.create({
161
- name: "data",
161
+ name: id.create(),
162
162
  dataType: DataType.FLOAT64,
163
163
  index: indexCh.key,
164
164
  });
package/src/index.ts CHANGED
@@ -8,7 +8,6 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  export { access } from "@/access";
11
- export { policy } from "@/access/policy";
12
11
  export { arc } from "@/arc";
13
12
  export { channel } from "@/channel";
14
13
  export { Channel, isCalculated } from "@/channel/client";
@@ -22,6 +21,7 @@ export {
22
21
  } from "@/client";
23
22
  export * from "@/connection";
24
23
  export { control } from "@/control";
24
+ export { device } from "@/device";
25
25
  export {
26
26
  AuthError,
27
27
  ContiguityError,
@@ -35,15 +35,14 @@ export {
35
35
  } from "@/errors";
36
36
  export { framer } from "@/framer";
37
37
  export { Frame } from "@/framer/frame";
38
- export { hardware } from "@/hardware";
39
- export { device } from "@/hardware/device";
40
- export { rack } from "@/hardware/rack";
41
- export { task } from "@/hardware/task";
42
38
  export { label } from "@/label";
43
39
  export { ontology } from "@/ontology";
44
40
  export { group } from "@/ontology/group";
41
+ export { rack } from "@/rack";
45
42
  export { ranger } from "@/ranger";
46
43
  export { status } from "@/status";
44
+ export { task } from "@/task";
45
+ export { createTestClientWithPolicy } from "@/testutil/access";
47
46
  export { createTestClient, TEST_CLIENT_PARAMS } from "@/testutil/client";
48
47
  export { user } from "@/user";
49
48
  export { workspace } from "@/workspace";
@@ -0,0 +1,109 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { describe, expect, it } from "vitest";
11
+
12
+ import { AuthError, NotFoundError } from "@/errors";
13
+ import { label } from "@/label";
14
+ import { createTestClientWithPolicy } from "@/testutil/access";
15
+ import { createTestClient } from "@/testutil/client";
16
+
17
+ const client = createTestClient();
18
+
19
+ describe("label", () => {
20
+ describe("access control", () => {
21
+ it("should deny access when no retrieve policy exists", async () => {
22
+ const userClient = await createTestClientWithPolicy(client, {
23
+ name: "test",
24
+ objects: [],
25
+ actions: [],
26
+ });
27
+ const randomLabel = await client.labels.create({
28
+ name: "test",
29
+ color: "#E774D0",
30
+ });
31
+ await expect(
32
+ userClient.labels.retrieve({ key: randomLabel.key }),
33
+ ).rejects.toThrow(AuthError);
34
+ });
35
+
36
+ it("should allow the caller to retrieve labels with the correct policy", async () => {
37
+ const userClient = await createTestClientWithPolicy(client, {
38
+ name: "test",
39
+ objects: [label.ontologyID("")],
40
+ actions: ["retrieve"],
41
+ });
42
+ const randomLabel = await client.labels.create({
43
+ name: "test",
44
+ color: "#E774D0",
45
+ });
46
+ const retrieved = await userClient.labels.retrieve({ key: randomLabel.key });
47
+ expect(retrieved.key).toBe(randomLabel.key);
48
+ expect(retrieved.name).toBe(randomLabel.name);
49
+ expect(retrieved.color).toBe(randomLabel.color);
50
+ });
51
+
52
+ it("should allow the caller to create labels with the correct policy", async () => {
53
+ const userClient = await createTestClientWithPolicy(client, {
54
+ name: "test",
55
+ objects: [label.ontologyID("")],
56
+ actions: ["create"],
57
+ });
58
+ await userClient.labels.create({
59
+ name: "test",
60
+ color: "#E774D0",
61
+ });
62
+ });
63
+
64
+ it("should deny access when no create policy exists", async () => {
65
+ const userClient = await createTestClientWithPolicy(client, {
66
+ name: "test",
67
+ objects: [label.ontologyID("")],
68
+ actions: [],
69
+ });
70
+ await expect(
71
+ userClient.labels.create({
72
+ name: "test",
73
+ color: "#E774D0",
74
+ }),
75
+ ).rejects.toThrow(AuthError);
76
+ });
77
+
78
+ it("should allow the caller to delete labels with the correct policy", async () => {
79
+ const userClient = await createTestClientWithPolicy(client, {
80
+ name: "test",
81
+ objects: [label.ontologyID("")],
82
+ actions: ["delete"],
83
+ });
84
+ const randomLabel = await client.labels.create({
85
+ name: "test",
86
+ color: "#E774D0",
87
+ });
88
+ await userClient.labels.delete(randomLabel.key);
89
+ await expect(
90
+ userClient.labels.retrieve({ key: randomLabel.key }),
91
+ ).rejects.toThrow(NotFoundError);
92
+ });
93
+
94
+ it("should deny access when no delete policy exists", async () => {
95
+ const userClient = await createTestClientWithPolicy(client, {
96
+ name: "test",
97
+ objects: [label.ontologyID("")],
98
+ actions: [],
99
+ });
100
+ const randomLabel = await client.labels.create({
101
+ name: "test",
102
+ color: "#E774D0",
103
+ });
104
+ await expect(userClient.labels.delete(randomLabel.key)).rejects.toThrow(
105
+ AuthError,
106
+ );
107
+ });
108
+ });
109
+ });