@synnaxlabs/client 0.38.1 → 0.39.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 (257) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/dist/access/payload.d.ts +6 -6
  3. package/dist/access/payload.d.ts.map +1 -1
  4. package/dist/access/policy/client.d.ts +5 -3
  5. package/dist/access/policy/client.d.ts.map +1 -1
  6. package/dist/access/policy/external.d.ts +1 -0
  7. package/dist/access/policy/external.d.ts.map +1 -1
  8. package/dist/access/policy/ontology.d.ts +5 -0
  9. package/dist/access/policy/ontology.d.ts.map +1 -0
  10. package/dist/access/policy/payload.d.ts +86 -89
  11. package/dist/access/policy/payload.d.ts.map +1 -1
  12. package/dist/access/policy/retriever.d.ts +7 -6
  13. package/dist/access/policy/retriever.d.ts.map +1 -1
  14. package/dist/access/policy/writer.d.ts +2 -2
  15. package/dist/access/policy/writer.d.ts.map +1 -1
  16. package/dist/auth/auth.d.ts +2 -1
  17. package/dist/auth/auth.d.ts.map +1 -1
  18. package/dist/channel/client.d.ts +6 -5
  19. package/dist/channel/client.d.ts.map +1 -1
  20. package/dist/channel/payload.d.ts +13 -11
  21. package/dist/channel/payload.d.ts.map +1 -1
  22. package/dist/channel/retriever.d.ts +9 -6
  23. package/dist/channel/retriever.d.ts.map +1 -1
  24. package/dist/channel/writer.d.ts +6 -4
  25. package/dist/channel/writer.d.ts.map +1 -1
  26. package/dist/client.cjs +30 -30
  27. package/dist/client.d.ts +4 -2
  28. package/dist/client.d.ts.map +1 -1
  29. package/dist/client.js +3376 -3423
  30. package/dist/connection/checker.d.ts +5 -4
  31. package/dist/connection/checker.d.ts.map +1 -1
  32. package/dist/control/state.d.ts +10 -8
  33. package/dist/control/state.d.ts.map +1 -1
  34. package/dist/errors.d.ts +5 -0
  35. package/dist/errors.d.ts.map +1 -1
  36. package/dist/framer/adapter.d.ts +14 -15
  37. package/dist/framer/adapter.d.ts.map +1 -1
  38. package/dist/framer/client.d.ts +13 -15
  39. package/dist/framer/client.d.ts.map +1 -1
  40. package/dist/framer/deleter.d.ts +3 -2
  41. package/dist/framer/deleter.d.ts.map +1 -1
  42. package/dist/framer/frame.d.ts +31 -27
  43. package/dist/framer/frame.d.ts.map +1 -1
  44. package/dist/framer/iterator.d.ts +4 -5
  45. package/dist/framer/iterator.d.ts.map +1 -1
  46. package/dist/framer/streamer.d.ts +5 -6
  47. package/dist/framer/streamer.d.ts.map +1 -1
  48. package/dist/framer/writer.d.ts +42 -39
  49. package/dist/framer/writer.d.ts.map +1 -1
  50. package/dist/hardware/device/client.d.ts +17 -12
  51. package/dist/hardware/device/client.d.ts.map +1 -1
  52. package/dist/hardware/device/payload.d.ts +19 -16
  53. package/dist/hardware/device/payload.d.ts.map +1 -1
  54. package/dist/hardware/rack/client.d.ts +15 -15
  55. package/dist/hardware/rack/client.d.ts.map +1 -1
  56. package/dist/hardware/rack/payload.d.ts +9 -8
  57. package/dist/hardware/rack/payload.d.ts.map +1 -1
  58. package/dist/hardware/task/client.d.ts +38 -29
  59. package/dist/hardware/task/client.d.ts.map +1 -1
  60. package/dist/hardware/task/payload.d.ts +58 -53
  61. package/dist/hardware/task/payload.d.ts.map +1 -1
  62. package/dist/label/client.d.ts +4 -3
  63. package/dist/label/client.d.ts.map +1 -1
  64. package/dist/label/payload.d.ts +4 -4
  65. package/dist/label/payload.d.ts.map +1 -1
  66. package/dist/label/retriever.d.ts.map +1 -1
  67. package/dist/label/writer.d.ts +13 -10
  68. package/dist/label/writer.d.ts.map +1 -1
  69. package/dist/ontology/client.d.ts +12 -10
  70. package/dist/ontology/client.d.ts.map +1 -1
  71. package/dist/ontology/group/client.d.ts +5 -4
  72. package/dist/ontology/group/client.d.ts.map +1 -1
  73. package/dist/ontology/group/group.d.ts +7 -5
  74. package/dist/ontology/group/group.d.ts.map +1 -1
  75. package/dist/ontology/group/payload.d.ts +6 -5
  76. package/dist/ontology/group/payload.d.ts.map +1 -1
  77. package/dist/ontology/group/writer.d.ts +8 -8
  78. package/dist/ontology/group/writer.d.ts.map +1 -1
  79. package/dist/ontology/payload.d.ts +72 -62
  80. package/dist/ontology/payload.d.ts.map +1 -1
  81. package/dist/ontology/writer.d.ts.map +1 -1
  82. package/dist/ranger/alias.d.ts +9 -10
  83. package/dist/ranger/alias.d.ts.map +1 -1
  84. package/dist/ranger/client.d.ts +18 -18
  85. package/dist/ranger/client.d.ts.map +1 -1
  86. package/dist/ranger/external.d.ts +1 -1
  87. package/dist/ranger/external.d.ts.map +1 -1
  88. package/dist/ranger/kv.d.ts +18 -14
  89. package/dist/ranger/kv.d.ts.map +1 -1
  90. package/dist/ranger/payload.d.ts +13 -13
  91. package/dist/ranger/payload.d.ts.map +1 -1
  92. package/dist/ranger/writer.d.ts +14 -14
  93. package/dist/ranger/writer.d.ts.map +1 -1
  94. package/dist/setupspecs.d.ts.map +1 -1
  95. package/dist/signals/observable.d.ts +3 -1
  96. package/dist/signals/observable.d.ts.map +1 -1
  97. package/dist/user/client.d.ts +5 -3
  98. package/dist/user/client.d.ts.map +1 -1
  99. package/dist/user/payload.d.ts +7 -6
  100. package/dist/user/payload.d.ts.map +1 -1
  101. package/dist/user/retriever.d.ts +2 -1
  102. package/dist/user/retriever.d.ts.map +1 -1
  103. package/dist/user/writer.d.ts +2 -2
  104. package/dist/user/writer.d.ts.map +1 -1
  105. package/dist/util/decodeJSONString.d.ts +3 -0
  106. package/dist/util/decodeJSONString.d.ts.map +1 -0
  107. package/dist/util/parseWithoutKeyConversion.d.ts +3 -0
  108. package/dist/util/parseWithoutKeyConversion.d.ts.map +1 -0
  109. package/dist/util/retrieve.d.ts +1 -1
  110. package/dist/util/retrieve.d.ts.map +1 -1
  111. package/dist/util/telem.d.ts.map +1 -1
  112. package/dist/util/zod.d.ts.map +1 -1
  113. package/dist/workspace/client.d.ts +6 -60
  114. package/dist/workspace/client.d.ts.map +1 -1
  115. package/dist/workspace/external.d.ts +3 -0
  116. package/dist/workspace/external.d.ts.map +1 -0
  117. package/dist/workspace/index.d.ts +1 -1
  118. package/dist/workspace/index.d.ts.map +1 -1
  119. package/dist/workspace/lineplot/client.d.ts +5 -44
  120. package/dist/workspace/lineplot/client.d.ts.map +1 -1
  121. package/dist/workspace/lineplot/external.d.ts +3 -0
  122. package/dist/workspace/lineplot/external.d.ts.map +1 -0
  123. package/dist/workspace/lineplot/index.d.ts +1 -1
  124. package/dist/workspace/lineplot/index.d.ts.map +1 -1
  125. package/dist/workspace/lineplot/payload.d.ts +45 -0
  126. package/dist/workspace/lineplot/payload.d.ts.map +1 -0
  127. package/dist/workspace/log/client.d.ts +5 -44
  128. package/dist/workspace/log/client.d.ts.map +1 -1
  129. package/dist/workspace/log/external.d.ts +3 -0
  130. package/dist/workspace/log/external.d.ts.map +1 -0
  131. package/dist/workspace/log/index.d.ts +1 -1
  132. package/dist/workspace/log/index.d.ts.map +1 -1
  133. package/dist/workspace/log/payload.d.ts +45 -0
  134. package/dist/workspace/log/payload.d.ts.map +1 -0
  135. package/dist/workspace/payload.d.ts +60 -0
  136. package/dist/workspace/payload.d.ts.map +1 -0
  137. package/dist/workspace/schematic/client.d.ts +5 -68
  138. package/dist/workspace/schematic/client.d.ts.map +1 -1
  139. package/dist/workspace/schematic/external.d.ts +3 -0
  140. package/dist/workspace/schematic/external.d.ts.map +1 -0
  141. package/dist/workspace/schematic/index.d.ts +1 -1
  142. package/dist/workspace/schematic/index.d.ts.map +1 -1
  143. package/dist/workspace/schematic/payload.d.ts +71 -0
  144. package/dist/workspace/schematic/payload.d.ts.map +1 -0
  145. package/dist/workspace/table/client.d.ts +5 -57
  146. package/dist/workspace/table/client.d.ts.map +1 -1
  147. package/dist/workspace/table/external.d.ts +3 -0
  148. package/dist/workspace/table/external.d.ts.map +1 -0
  149. package/dist/workspace/table/index.d.ts +1 -1
  150. package/dist/workspace/table/index.d.ts.map +1 -1
  151. package/dist/workspace/table/payload.d.ts +60 -0
  152. package/dist/workspace/table/payload.d.ts.map +1 -0
  153. package/examples/node/basicReadWrite.js +26 -26
  154. package/examples/node/liveStream.js +15 -15
  155. package/examples/node/seriesAndFrames.js +38 -38
  156. package/examples/node/streamWrite.js +47 -45
  157. package/package.json +15 -13
  158. package/src/access/payload.ts +12 -12
  159. package/src/access/policy/client.ts +13 -12
  160. package/src/access/policy/external.ts +1 -0
  161. package/src/access/policy/ontology.ts +17 -0
  162. package/src/access/policy/payload.ts +7 -19
  163. package/src/access/policy/policy.spec.ts +16 -16
  164. package/src/access/policy/retriever.ts +2 -1
  165. package/src/access/policy/writer.ts +4 -4
  166. package/src/auth/auth.spec.ts +27 -23
  167. package/src/auth/auth.ts +7 -11
  168. package/src/channel/batchRetriever.spec.ts +25 -22
  169. package/src/channel/client.ts +19 -21
  170. package/src/channel/payload.ts +16 -20
  171. package/src/channel/retriever.ts +20 -21
  172. package/src/channel/writer.ts +11 -13
  173. package/src/client.ts +6 -16
  174. package/src/connection/checker.ts +9 -11
  175. package/src/connection/connection.spec.ts +17 -5
  176. package/src/control/state.ts +8 -9
  177. package/src/errors.spec.ts +1 -1
  178. package/src/errors.ts +8 -0
  179. package/src/framer/adapter.spec.ts +28 -23
  180. package/src/framer/adapter.ts +37 -41
  181. package/src/framer/client.spec.ts +5 -11
  182. package/src/framer/client.ts +34 -38
  183. package/src/framer/deleter.ts +5 -6
  184. package/src/framer/frame.ts +62 -50
  185. package/src/framer/iterator.ts +11 -16
  186. package/src/framer/streamer.spec.ts +2 -10
  187. package/src/framer/streamer.ts +15 -19
  188. package/src/framer/writer.spec.ts +48 -7
  189. package/src/framer/writer.ts +39 -31
  190. package/src/hardware/device/client.ts +64 -39
  191. package/src/hardware/device/device.spec.ts +49 -33
  192. package/src/hardware/device/payload.ts +29 -29
  193. package/src/hardware/rack/client.ts +52 -65
  194. package/src/hardware/rack/payload.ts +9 -18
  195. package/src/hardware/rack/rack.spec.ts +12 -0
  196. package/src/hardware/task/client.ts +160 -131
  197. package/src/hardware/task/payload.ts +49 -68
  198. package/src/hardware/task/task.spec.ts +98 -81
  199. package/src/label/client.ts +12 -15
  200. package/src/label/payload.ts +3 -9
  201. package/src/label/retriever.ts +3 -7
  202. package/src/label/writer.ts +8 -15
  203. package/src/ontology/client.ts +17 -22
  204. package/src/ontology/group/client.ts +5 -5
  205. package/src/ontology/group/group.spec.ts +4 -4
  206. package/src/ontology/group/group.ts +10 -7
  207. package/src/ontology/group/payload.ts +11 -35
  208. package/src/ontology/group/writer.ts +22 -26
  209. package/src/ontology/ontology.spec.ts +15 -15
  210. package/src/ontology/payload.ts +67 -43
  211. package/src/ontology/writer.ts +16 -23
  212. package/src/ranger/alias.ts +25 -42
  213. package/src/ranger/client.ts +29 -38
  214. package/src/ranger/external.ts +1 -1
  215. package/src/ranger/kv.ts +9 -32
  216. package/src/ranger/payload.ts +14 -36
  217. package/src/ranger/ranger.spec.ts +1 -2
  218. package/src/ranger/writer.ts +8 -26
  219. package/src/signals/observable.ts +3 -4
  220. package/src/user/client.ts +8 -4
  221. package/src/user/payload.ts +5 -9
  222. package/src/user/retriever.ts +1 -1
  223. package/src/user/user.spec.ts +17 -15
  224. package/src/user/writer.ts +3 -10
  225. package/src/util/decodeJSONString.ts +13 -0
  226. package/src/util/parseWithoutKeyConversion.ts +19 -0
  227. package/src/util/retrieve.spec.ts +3 -13
  228. package/src/util/retrieve.ts +2 -12
  229. package/src/util/telem.ts +1 -1
  230. package/src/vite-env.d.ts +1 -0
  231. package/src/workspace/client.ts +30 -57
  232. package/src/workspace/external.ts +11 -0
  233. package/src/workspace/index.ts +1 -1
  234. package/src/workspace/lineplot/client.ts +22 -36
  235. package/src/workspace/lineplot/external.ts +11 -0
  236. package/src/workspace/lineplot/index.ts +1 -1
  237. package/src/workspace/lineplot/linePlot.spec.ts +1 -2
  238. package/src/workspace/lineplot/payload.ts +32 -0
  239. package/src/workspace/log/client.ts +25 -39
  240. package/src/workspace/log/external.ts +11 -0
  241. package/src/workspace/log/index.ts +1 -1
  242. package/src/workspace/log/log.spec.ts +5 -18
  243. package/src/workspace/log/payload.ts +32 -0
  244. package/src/workspace/payload.ts +36 -0
  245. package/src/workspace/schematic/client.ts +30 -56
  246. package/src/workspace/schematic/external.ts +11 -0
  247. package/src/workspace/schematic/index.ts +1 -1
  248. package/src/workspace/schematic/payload.ts +37 -0
  249. package/src/workspace/schematic/schematic.spec.ts +15 -6
  250. package/src/workspace/table/client.ts +27 -50
  251. package/src/workspace/table/external.ts +11 -0
  252. package/src/workspace/table/index.ts +1 -1
  253. package/src/workspace/table/payload.ts +36 -0
  254. package/src/workspace/workspace.spec.ts +1 -2
  255. package/dist/channel/creator.d.ts +0 -9
  256. package/dist/channel/creator.d.ts.map +0 -1
  257. package/src/channel/creator.ts +0 -37
@@ -12,7 +12,7 @@ import { DataType, Rate, TimeRange, TimeSpan, TimeStamp } from "@synnaxlabs/x/te
12
12
  import { describe, expect, test } from "vitest";
13
13
 
14
14
  import { type channel } from "@/channel";
15
- import { UnauthorizedError } from "@/errors";
15
+ import { UnauthorizedError, ValidationError } from "@/errors";
16
16
  import { ALWAYS_INDEX_PERSIST_ON_AUTO_COMMIT, WriterMode } from "@/framer/writer";
17
17
  import { newClient } from "@/setupspecs";
18
18
  import { randomSeries } from "@/util/telem";
@@ -22,7 +22,7 @@ const client = newClient();
22
22
  const newChannel = async (): Promise<channel.Channel> =>
23
23
  await client.channels.create({
24
24
  leaseholder: 1,
25
- name: `test-${id.id()}`,
25
+ name: `test-${id.create()}`,
26
26
  rate: Rate.hz(1),
27
27
  dataType: DataType.FLOAT64,
28
28
  });
@@ -40,6 +40,7 @@ describe("Writer", () => {
40
40
  }
41
41
  expect(true).toBeTruthy();
42
42
  });
43
+
43
44
  test("write to unknown channel key", async () => {
44
45
  const ch = await newChannel();
45
46
  const writer = await client.openWriter({ start: 0, channels: ch.key });
@@ -48,6 +49,7 @@ describe("Writer", () => {
48
49
  ).rejects.toThrow("Channel billy bob not found");
49
50
  await writer.close();
50
51
  });
52
+
51
53
  test("stream when mode is set ot persist only", async () => {
52
54
  const ch = await newChannel();
53
55
  const stream = await client.openStreamer(ch.key);
@@ -68,6 +70,7 @@ describe("Writer", () => {
68
70
  ]);
69
71
  expect(v).toEqual(123);
70
72
  });
73
+
71
74
  test("write with auto commit on", async () => {
72
75
  const ch = await newChannel();
73
76
  const writer = await client.openWriter({
@@ -85,6 +88,7 @@ describe("Writer", () => {
85
88
  const f = await client.read(new TimeRange(0, TimeStamp.seconds(10)), ch.key);
86
89
  expect(f.length).toEqual(10);
87
90
  });
91
+
88
92
  test("write with auto commit and alwaysPersist", async () => {
89
93
  const ch = await newChannel();
90
94
  const writer = await client.openWriter({
@@ -100,6 +104,7 @@ describe("Writer", () => {
100
104
  }
101
105
  expect(true).toBeTruthy();
102
106
  });
107
+
103
108
  test("write with auto commit and a set interval", async () => {
104
109
  const ch = await newChannel();
105
110
  const writer = await client.openWriter({
@@ -115,6 +120,41 @@ describe("Writer", () => {
115
120
  }
116
121
  expect(true).toBeTruthy();
117
122
  });
123
+
124
+ test("write with out of order timestamp", async () => {
125
+ const indexCh = await client.channels.create({
126
+ name: "idx",
127
+ dataType: DataType.TIMESTAMP,
128
+ isIndex: true,
129
+ });
130
+
131
+ const dataCh = await client.channels.create({
132
+ name: "data",
133
+ dataType: DataType.FLOAT64,
134
+ index: indexCh.key,
135
+ });
136
+
137
+ const writer = await client.openWriter({
138
+ start: TimeStamp.now(),
139
+ channels: [indexCh.key, dataCh.key],
140
+ enableAutoCommit: true,
141
+ });
142
+
143
+ let errAccumulated: boolean = false;
144
+ for (let i = 0; i < 10; i++) {
145
+ await new Promise((resolve) => setTimeout(resolve, 5));
146
+ errAccumulated = !(await writer.write({
147
+ [indexCh.key]: BigInt(i),
148
+ [dataCh.key]: i,
149
+ }));
150
+ if (errAccumulated) break;
151
+ }
152
+
153
+ expect(errAccumulated).toBeTruthy();
154
+
155
+ await expect(writer.close()).rejects.toThrow(ValidationError);
156
+ });
157
+
118
158
  test("write with errOnUnauthorized", async () => {
119
159
  const ch = await newChannel();
120
160
  const w1 = await client.openWriter({
@@ -123,14 +163,11 @@ describe("Writer", () => {
123
163
  });
124
164
 
125
165
  await expect(
126
- client.openWriter({
127
- start: 0,
128
- channels: ch.key,
129
- errOnUnauthorized: true,
130
- }),
166
+ client.openWriter({ start: 0, channels: ch.key, errOnUnauthorized: true }),
131
167
  ).rejects.toThrow(UnauthorizedError);
132
168
  await w1.close();
133
169
  });
170
+
134
171
  test("setAuthority", async () => {
135
172
  const ch = await newChannel();
136
173
  const w1 = await client.openWriter({
@@ -159,6 +196,7 @@ describe("Writer", () => {
159
196
  await w1.close();
160
197
  await w2.close();
161
198
  });
199
+
162
200
  test("setAuthority with name keys", async () => {
163
201
  const ch = await newChannel();
164
202
  const w1 = await client.openWriter({
@@ -187,6 +225,7 @@ describe("Writer", () => {
187
225
  await w1.close();
188
226
  await w2.close();
189
227
  });
228
+
190
229
  test("setAuthority with name-value pair", async () => {
191
230
  const ch = await newChannel();
192
231
  const w1 = await client.openWriter({
@@ -215,6 +254,7 @@ describe("Writer", () => {
215
254
  await w1.close();
216
255
  await w2.close();
217
256
  });
257
+
218
258
  test("setAuthority on all channels", async () => {
219
259
  const ch = await newChannel();
220
260
  const w1 = await client.openWriter({
@@ -244,6 +284,7 @@ describe("Writer", () => {
244
284
  await w2.close();
245
285
  });
246
286
  });
287
+
247
288
  describe("Client", () => {
248
289
  test("Client - basic write", async () => {
249
290
  const ch = await newChannel();
@@ -23,10 +23,9 @@ import {
23
23
  import { toArray } from "@synnaxlabs/x/toArray";
24
24
  import { z } from "zod";
25
25
 
26
- import { type KeyOrName, type KeysOrNames, type Params } from "@/channel/payload";
27
- import { type Retriever } from "@/channel/retriever";
28
- import { WriteFrameAdapter } from "@/framer/adapter";
29
- import { type CrudeFrame, frameZ } from "@/framer/frame";
26
+ import { channel } from "@/channel";
27
+ import { WriteAdapter } from "@/framer/adapter";
28
+ import { type Crude, frameZ } from "@/framer/frame";
30
29
  import { StreamProxy } from "@/framer/streamProxy";
31
30
 
32
31
  enum Command {
@@ -64,7 +63,7 @@ export const ALWAYS_INDEX_PERSIST_ON_AUTO_COMMIT: TimeSpan = new TimeSpan(-1);
64
63
  const netConfigZ = z.object({
65
64
  start: TimeStamp.z.optional(),
66
65
  controlSubject: control.subjectZ.optional(),
67
- keys: z.number().array().optional(),
66
+ keys: channel.keyZ.array().optional(),
68
67
  authorities: control.Authority.z.array().optional(),
69
68
  mode: z.nativeEnum(WriterMode).optional(),
70
69
  errOnUnauthorized: z.boolean().optional(),
@@ -72,7 +71,7 @@ const netConfigZ = z.object({
72
71
  autoIndexPersistInterval: TimeSpan.z.optional(),
73
72
  });
74
73
 
75
- type Config = z.infer<typeof netConfigZ>;
74
+ interface Config extends z.infer<typeof netConfigZ> {}
76
75
 
77
76
  const reqZ = z.object({
78
77
  command: z.nativeEnum(Command),
@@ -80,7 +79,7 @@ const reqZ = z.object({
80
79
  frame: frameZ.optional(),
81
80
  });
82
81
 
83
- type Request = z.infer<typeof reqZ>;
82
+ interface Request extends z.infer<typeof reqZ> {}
84
83
 
85
84
  const resZ = z.object({
86
85
  ack: z.boolean(),
@@ -88,11 +87,11 @@ const resZ = z.object({
88
87
  error: errorZ.optional().nullable(),
89
88
  });
90
89
 
91
- type Response = z.infer<typeof resZ>;
90
+ interface Response extends z.infer<typeof resZ> {}
92
91
 
93
92
  export interface WriterConfig {
94
93
  // channels denote the channels to write to.
95
- channels: Params;
94
+ channels: channel.Params;
96
95
  // start sets the starting timestamp for the first sample in the writer.
97
96
  start?: CrudeTimeStamp;
98
97
  // controlSubject sets the control subject of the writer.
@@ -158,23 +157,21 @@ export interface WriterConfig {
158
157
  export class Writer {
159
158
  private static readonly ENDPOINT = "/frame/write";
160
159
  private readonly stream: StreamProxy<typeof reqZ, typeof resZ>;
161
- private readonly adapter: WriteFrameAdapter;
160
+ private readonly adapter: WriteAdapter;
161
+ private errAccumulated: boolean = false;
162
162
 
163
- private constructor(
164
- stream: Stream<typeof reqZ, typeof resZ>,
165
- adapter: WriteFrameAdapter,
166
- ) {
163
+ private constructor(stream: Stream<typeof reqZ, typeof resZ>, adapter: WriteAdapter) {
167
164
  this.stream = new StreamProxy("Writer", stream);
168
165
  this.adapter = adapter;
169
166
  }
170
167
 
171
168
  static async _open(
172
- retriever: Retriever,
169
+ retriever: channel.Retriever,
173
170
  client: StreamClient,
174
171
  {
175
172
  channels,
176
173
  start = TimeStamp.now(),
177
- authorities = control.Authority.Absolute,
174
+ authorities = control.Authority.ABSOLUTE,
178
175
  controlSubject: subject,
179
176
  mode = WriterMode.PersistStream,
180
177
  errOnUnauthorized = false,
@@ -182,7 +179,7 @@ export class Writer {
182
179
  autoIndexPersistInterval = TimeSpan.SECOND,
183
180
  }: WriterConfig,
184
181
  ): Promise<Writer> {
185
- const adapter = await WriteFrameAdapter.open(retriever, channels);
182
+ const adapter = await WriteAdapter.open(retriever, channels);
186
183
  const stream = await client.stream(Writer.ENDPOINT, reqZ, resZ);
187
184
  const writer = new Writer(stream, adapter);
188
185
  await writer.execute({
@@ -201,14 +198,19 @@ export class Writer {
201
198
  return writer;
202
199
  }
203
200
 
204
- async write(channel: KeyOrName, data: CrudeSeries): Promise<boolean>;
205
-
206
- async write(channel: KeysOrNames, data: CrudeSeries[]): Promise<boolean>;
207
-
208
- async write(frame: CrudeFrame | Record<KeyOrName, CrudeSeries>): Promise<boolean>;
201
+ private async checkForAccumulatedError(): Promise<boolean> {
202
+ if (!this.errAccumulated && this.stream.received()) {
203
+ this.errAccumulated = true;
204
+ while (this.stream.received()) await this.stream.receive();
205
+ }
206
+ return this.errAccumulated;
207
+ }
209
208
 
209
+ async write(channel: channel.KeyOrName, data: CrudeSeries): Promise<boolean>;
210
+ async write(channel: channel.KeysOrNames, data: CrudeSeries[]): Promise<boolean>;
211
+ async write(frame: Crude | Record<channel.KeyOrName, CrudeSeries>): Promise<boolean>;
210
212
  async write(
211
- channelsOrData: Params | Record<KeyOrName, CrudeSeries> | CrudeFrame,
213
+ channelsOrData: channel.Params | Record<channel.KeyOrName, CrudeSeries> | Crude,
212
214
  series?: CrudeSeries | CrudeSeries[],
213
215
  ): Promise<boolean>;
214
216
 
@@ -227,9 +229,10 @@ export class Writer {
227
229
  * should acknowledge the error by calling the error method or closing the writer.
228
230
  */
229
231
  async write(
230
- channelsOrData: Params | Record<KeyOrName, CrudeSeries> | CrudeFrame,
232
+ channelsOrData: channel.Params | Record<channel.KeyOrName, CrudeSeries> | Crude,
231
233
  series?: CrudeSeries | CrudeSeries[],
232
234
  ): Promise<boolean> {
235
+ if (await this.checkForAccumulatedError()) return false;
233
236
  const frame = await this.adapter.adapt(channelsOrData, series);
234
237
  this.stream.send({ command: Command.Write, frame: frame.toPayload() });
235
238
  return true;
@@ -237,21 +240,27 @@ export class Writer {
237
240
 
238
241
  async setAuthority(value: number): Promise<boolean>;
239
242
 
240
- async setAuthority(key: KeyOrName, authority: control.Authority): Promise<boolean>;
243
+ async setAuthority(
244
+ key: channel.KeyOrName,
245
+ authority: control.Authority,
246
+ ): Promise<boolean>;
241
247
 
242
- async setAuthority(value: Record<KeyOrName, control.Authority>): Promise<boolean>;
248
+ async setAuthority(
249
+ value: Record<channel.KeyOrName, control.Authority>,
250
+ ): Promise<boolean>;
243
251
 
244
252
  async setAuthority(
245
- value: Record<KeyOrName, control.Authority> | KeyOrName | number,
253
+ value: Record<channel.KeyOrName, control.Authority> | channel.KeyOrName | number,
246
254
  authority?: control.Authority,
247
255
  ): Promise<boolean> {
256
+ if (await this.checkForAccumulatedError()) return false;
248
257
  let config: Config;
249
258
  if (typeof value === "number" && authority == null)
250
259
  config = { keys: [], authorities: [value] };
251
260
  else {
252
- let oValue: Record<KeyOrName, control.Authority>;
261
+ let oValue: Record<channel.KeyOrName, control.Authority>;
253
262
  if (typeof value === "string" || typeof value === "number")
254
- oValue = { [value]: authority } as Record<KeyOrName, control.Authority>;
263
+ oValue = { [value]: authority } as Record<channel.KeyOrName, control.Authority>;
255
264
  else oValue = value;
256
265
  oValue = await this.adapter.adaptObjectKeys(oValue);
257
266
  config = {
@@ -272,7 +281,7 @@ export class Writer {
272
281
  * After the caller acknowledges the error, they can attempt to commit again.
273
282
  */
274
283
  async commit(): Promise<boolean> {
275
- if (this.errorAccumulated) return false;
284
+ if (await this.checkForAccumulatedError()) return false;
276
285
  const res = await this.execute({ command: Command.Commit });
277
286
  return res.ack;
278
287
  }
@@ -282,7 +291,6 @@ export class Writer {
282
291
  * state, allowing the writer to be used again.
283
292
  */
284
293
  async error(): Promise<Error | null> {
285
- this.stream.send({ command: Command.Error });
286
294
  const res = await this.execute({ command: Command.Error });
287
295
  return res.error != null ? decodeError(res.error) : null;
288
296
  }
@@ -15,28 +15,30 @@ import { z } from "zod";
15
15
  import { type framer } from "@/framer";
16
16
  import {
17
17
  type Device,
18
- type DeviceKey,
19
- deviceKeyZ,
20
18
  deviceZ,
21
- type NewDevice,
22
- newDeviceZ,
19
+ type Key,
20
+ keyZ,
21
+ type New,
22
+ newZ,
23
+ ONTOLOGY_TYPE,
23
24
  } from "@/hardware/device/payload";
25
+ import { ontology } from "@/ontology";
24
26
  import { signals } from "@/signals";
25
27
  import { checkForMultipleOrNoResults } from "@/util/retrieve";
26
28
  import { nullableArrayZ } from "@/util/zod";
27
29
 
28
- const DEVICE_SET_NAME = "sy_device_set";
29
- const DEVICE_DELETE_NAME = "sy_device_delete";
30
+ const SET_CHANNEL_NAME = "sy_device_set";
31
+ const DELETE_CHANNEL_NAME = "sy_device_delete";
30
32
 
31
33
  const RETRIEVE_ENDPOINT = "/hardware/device/retrieve";
32
34
  const CREATE_ENDPOINT = "/hardware/device/create";
33
35
  const DELETE_ENDPOINT = "/hardware/device/delete";
34
36
 
35
- const createReqZ = z.object({ devices: newDeviceZ.array() });
37
+ const createReqZ = z.object({ devices: newZ.array() });
36
38
 
37
39
  const createResZ = z.object({ devices: deviceZ.array() });
38
40
 
39
- const deleteReqZ = z.object({ keys: deviceKeyZ.array() });
41
+ const deleteReqZ = z.object({ keys: keyZ.array() });
40
42
 
41
43
  const deleteResZ = z.object({});
42
44
 
@@ -44,21 +46,22 @@ const retrieveReqZ = z.object({
44
46
  search: z.string().optional(),
45
47
  limit: z.number().optional(),
46
48
  offset: z.number().optional(),
47
- keys: deviceKeyZ.array().optional(),
49
+ keys: keyZ.array().optional(),
48
50
  names: z.string().array().optional(),
49
51
  makes: z.string().array().optional(),
50
52
  });
51
53
 
52
- type RetrieveRequest = z.input<typeof retrieveReqZ>;
54
+ interface RetrieveRequest extends z.input<typeof retrieveReqZ> {}
53
55
 
54
- export type RetrieveOptions = Pick<RetrieveRequest, "limit" | "offset" | "makes">;
56
+ export interface RetrieveOptions
57
+ extends Pick<RetrieveRequest, "limit" | "offset" | "makes"> {}
55
58
 
56
- type PageOptions = Pick<RetrieveOptions, "makes">;
59
+ interface PageOptions extends Pick<RetrieveOptions, "makes"> {}
57
60
 
58
61
  const retrieveResZ = z.object({ devices: nullableArrayZ(deviceZ) });
59
62
 
60
- export class Client implements AsyncTermSearcher<string, DeviceKey, Device> {
61
- readonly type = "device";
63
+ export class Client implements AsyncTermSearcher<string, Key, Device> {
64
+ readonly type = ONTOLOGY_TYPE;
62
65
  private readonly client: UnaryClient;
63
66
  private readonly frameClient: framer.Client;
64
67
 
@@ -67,20 +70,29 @@ export class Client implements AsyncTermSearcher<string, DeviceKey, Device> {
67
70
  this.frameClient = frameClient;
68
71
  }
69
72
 
70
- async retrieve<P extends UnknownRecord = UnknownRecord>(
71
- key: string,
72
- options?: RetrieveOptions,
73
- ): Promise<Device<P>>;
74
-
75
- async retrieve<P extends UnknownRecord = UnknownRecord>(
73
+ async retrieve<
74
+ Properties extends UnknownRecord = UnknownRecord,
75
+ Make extends string = string,
76
+ Model extends string = string,
77
+ >(key: string, options?: RetrieveOptions): Promise<Device<Properties, Make, Model>>;
78
+
79
+ async retrieve<
80
+ Properties extends UnknownRecord = UnknownRecord,
81
+ Make extends string = string,
82
+ Model extends string = string,
83
+ >(
76
84
  keys: string[],
77
85
  options?: RetrieveOptions,
78
- ): Promise<Array<Device<P>>>;
86
+ ): Promise<Array<Device<Properties, Make, Model>>>;
79
87
 
80
- async retrieve<P extends UnknownRecord = UnknownRecord>(
88
+ async retrieve<
89
+ Properties extends UnknownRecord = UnknownRecord,
90
+ Make extends string = string,
91
+ Model extends string = string,
92
+ >(
81
93
  keys: string | string[],
82
94
  options?: RetrieveOptions,
83
- ): Promise<Device<P> | Array<Device<P>>> {
95
+ ): Promise<Device<Properties, Make, Model> | Array<Device<Properties, Make, Model>>> {
84
96
  const isSingle = !Array.isArray(keys);
85
97
  const res = await sendRequired(
86
98
  this.client,
@@ -90,7 +102,9 @@ export class Client implements AsyncTermSearcher<string, DeviceKey, Device> {
90
102
  retrieveResZ,
91
103
  );
92
104
  checkForMultipleOrNoResults("Device", keys, res.devices, isSingle);
93
- return (isSingle ? res.devices[0] : res.devices) as Device<P> | Array<Device<P>>;
105
+ return isSingle
106
+ ? (res.devices[0] as Device<Properties, Make, Model>)
107
+ : (res.devices as Array<Device<Properties, Make, Model>>);
94
108
  }
95
109
 
96
110
  async search(term: string, options?: RetrieveOptions): Promise<Device[]> {
@@ -117,17 +131,23 @@ export class Client implements AsyncTermSearcher<string, DeviceKey, Device> {
117
131
  ).devices;
118
132
  }
119
133
 
120
- async create<P extends UnknownRecord = UnknownRecord>(
121
- device: NewDevice<P>,
122
- ): Promise<Device<P>>;
123
-
124
- async create<P extends UnknownRecord = UnknownRecord>(
125
- devices: NewDevice<P>[],
126
- ): Promise<Device<P>[]>;
127
-
128
- async create<P extends UnknownRecord = UnknownRecord>(
129
- devices: NewDevice<P> | NewDevice<P>[],
130
- ): Promise<Device<P> | Device<P>[]> {
134
+ async create<
135
+ Properties extends UnknownRecord = UnknownRecord,
136
+ Make extends string = string,
137
+ Model extends string = string,
138
+ >(device: New<Properties, Make>): Promise<Device<Properties, Make, Model>>;
139
+ async create<
140
+ Properties extends UnknownRecord = UnknownRecord,
141
+ Make extends string = string,
142
+ Model extends string = string,
143
+ >(devices: New<Properties, Make>[]): Promise<Device<Properties, Make, Model>[]>;
144
+ async create<
145
+ Properties extends UnknownRecord = UnknownRecord,
146
+ Make extends string = string,
147
+ Model extends string = string,
148
+ >(
149
+ devices: New<Properties, Make> | New<Properties, Make>[],
150
+ ): Promise<Device<Properties, Make, Model> | Device<Properties, Make, Model>[]> {
131
151
  const isSingle = !Array.isArray(devices);
132
152
  const res = await sendRequired(
133
153
  this.client,
@@ -136,7 +156,9 @@ export class Client implements AsyncTermSearcher<string, DeviceKey, Device> {
136
156
  createReqZ,
137
157
  createResZ,
138
158
  );
139
- return isSingle ? (res.devices[0] as Device<P>) : (res.devices as Device<P>[]);
159
+ return isSingle
160
+ ? (res.devices[0] as Device<Properties, Make, Model>)
161
+ : (res.devices as Device<Properties, Make, Model>[]);
140
162
  }
141
163
 
142
164
  async delete(keys: string | string[]): Promise<void> {
@@ -152,15 +174,15 @@ export class Client implements AsyncTermSearcher<string, DeviceKey, Device> {
152
174
  async openDeviceTracker(): Promise<signals.Observable<string, Device>> {
153
175
  return await signals.openObservable<string, Device>(
154
176
  this.frameClient,
155
- DEVICE_SET_NAME,
156
- DEVICE_DELETE_NAME,
177
+ SET_CHANNEL_NAME,
178
+ DELETE_CHANNEL_NAME,
157
179
  decodeDeviceChanges,
158
180
  );
159
181
  }
160
182
 
161
183
  newSearcherWithOptions(
162
184
  options: RetrieveOptions,
163
- ): AsyncTermSearcher<string, DeviceKey, Device> {
185
+ ): AsyncTermSearcher<string, Key, Device> {
164
186
  return {
165
187
  type: this.type,
166
188
  search: async (term: string) => await this.search(term, options),
@@ -176,3 +198,6 @@ const decodeDeviceChanges: signals.Decoder<string, Device> = (variant, data) =>
176
198
  return data.toStrings().map((k) => ({ variant, key: k, value: undefined }));
177
199
  return data.parseJSON(deviceZ).map((d) => ({ variant, key: d.key, value: d }));
178
200
  };
201
+
202
+ export const ontologyID = (key: Key): ontology.ID =>
203
+ new ontology.ID({ type: ONTOLOGY_TYPE, key });
@@ -13,42 +13,58 @@ import { newClient } from "@/setupspecs";
13
13
 
14
14
  const client = newClient();
15
15
 
16
- describe("Device", () => {
17
- describe("Device", () => {
18
- describe("create", () => {
19
- it("should create a device on a rack", async () => {
20
- const rack = await client.hardware.racks.create({ name: "test" });
21
- const d = await client.hardware.devices.create({
22
- rack: rack.key,
23
- location: "Dev1",
24
- key: "SN222",
25
- name: "test",
26
- make: "ni",
27
- model: "dog",
28
- properties: { cat: "dog" },
29
- });
30
- expect(d.key).toEqual("SN222");
31
- expect(d.name).toBe("test");
32
- expect(d.make).toBe("ni");
16
+ describe("Device", async () => {
17
+ const testRack = await client.hardware.racks.create({ name: "test" });
18
+ describe("create", () => {
19
+ it("should create a device on a rack", async () => {
20
+ const d = await client.hardware.devices.create({
21
+ rack: testRack.key,
22
+ location: "Dev1",
23
+ key: "SN222",
24
+ name: "test",
25
+ make: "ni",
26
+ model: "dog",
27
+ properties: { cat: "dog" },
33
28
  });
29
+ expect(d.key).toEqual("SN222");
30
+ expect(d.name).toBe("test");
31
+ expect(d.make).toBe("ni");
34
32
  });
35
- describe("retrieve", () => {
36
- it("should retrieve a device by its key", async () => {
37
- const rack = await client.hardware.racks.create({ name: "test" });
38
- const d = await client.hardware.devices.create({
39
- key: "SN222",
40
- rack: rack.key,
41
- location: "Dev1",
42
- name: "test",
43
- make: "ni",
44
- model: "dog",
45
- properties: { cat: "dog" },
46
- });
47
- const retrieved = await client.hardware.devices.retrieve(d.key);
48
- expect(retrieved.key).toBe(d.key);
49
- expect(retrieved.name).toBe("test");
50
- expect(retrieved.make).toBe("ni");
33
+ });
34
+ it("should properly encode and decode properties", async () => {
35
+ const properties = {
36
+ rate: 10,
37
+ stateIndexChannel: 234,
38
+ inputChannels: { port1: 34214 },
39
+ outputChannels: [{ port2: 232 }],
40
+ };
41
+ const d = await client.hardware.devices.create({
42
+ key: "SN222",
43
+ rack: testRack.key,
44
+ location: "Dev1",
45
+ name: "test",
46
+ make: "ni",
47
+ model: "dog",
48
+ properties,
49
+ });
50
+ const retrieved = await client.hardware.devices.retrieve(d.key);
51
+ expect(retrieved.properties).toEqual(properties);
52
+ });
53
+ describe("retrieve", () => {
54
+ it("should retrieve a device by its key", async () => {
55
+ const d = await client.hardware.devices.create({
56
+ key: "SN222",
57
+ rack: testRack.key,
58
+ location: "Dev1",
59
+ name: "test",
60
+ make: "ni",
61
+ model: "dog",
62
+ properties: { cat: "dog" },
51
63
  });
64
+ const retrieved = await client.hardware.devices.retrieve(d.key);
65
+ expect(retrieved.key).toBe(d.key);
66
+ expect(retrieved.name).toBe("test");
67
+ expect(retrieved.make).toBe("ni");
52
68
  });
53
69
  });
54
70
  });
@@ -10,44 +10,44 @@
10
10
  import { binary, type UnknownRecord } from "@synnaxlabs/x";
11
11
  import { z } from "zod";
12
12
 
13
- import { rackKeyZ } from "@/hardware/rack/payload";
14
- import { ontology } from "@/ontology";
13
+ import { keyZ as rackKeyZ } from "@/hardware/rack/payload";
14
+ import { decodeJSONString } from "@/util/decodeJSONString";
15
15
 
16
- export const deviceKeyZ = z.string();
16
+ export const keyZ = z.string();
17
+ export type Key = z.infer<typeof keyZ>;
17
18
 
18
19
  export const deviceZ = z.object({
19
- key: deviceKeyZ,
20
+ key: keyZ,
20
21
  rack: rackKeyZ,
21
22
  name: z.string(),
22
23
  make: z.string(),
23
24
  model: z.string(),
24
25
  location: z.string(),
25
26
  configured: z.boolean().optional(),
26
- properties: z.record(z.unknown()).or(
27
- z.string().transform((c) => {
28
- if (c === "") return {};
29
- return binary.JSON_CODEC.decodeString(c);
30
- }),
31
- ) as z.ZodType<UnknownRecord>,
27
+ properties: z.record(z.unknown()).or(z.string().transform(decodeJSONString)),
32
28
  });
33
-
34
- export type Device<P extends UnknownRecord = UnknownRecord> = Omit<
35
- z.output<typeof deviceZ>,
36
- "properties"
37
- > & { properties: P };
38
-
39
- export type DeviceKey = z.infer<typeof deviceKeyZ>;
40
-
41
- export const newDeviceZ = deviceZ.extend({
29
+ export interface Device<
30
+ Properties extends UnknownRecord = UnknownRecord,
31
+ Make extends string = string,
32
+ Model extends string = string,
33
+ > extends Omit<z.output<typeof deviceZ>, "properties"> {
34
+ properties: Properties;
35
+ make: Make;
36
+ model: Model;
37
+ }
38
+
39
+ export const newZ = deviceZ.extend({
42
40
  properties: z.unknown().transform((c) => binary.JSON_CODEC.encodeString(c)),
43
41
  });
44
-
45
- export type NewDevice<P extends UnknownRecord = UnknownRecord> = Omit<
46
- z.input<typeof newDeviceZ>,
47
- "properties"
48
- > & { properties: P };
49
-
50
- export const ONTOLOGY_TYPE: ontology.ResourceType = "device";
51
-
52
- export const ontologyID = (key: DeviceKey): ontology.ID =>
53
- new ontology.ID({ type: ONTOLOGY_TYPE, key: key.toString() });
42
+ export interface New<
43
+ Properties extends UnknownRecord = UnknownRecord,
44
+ Make extends string = string,
45
+ Model extends string = string,
46
+ > extends Omit<z.input<typeof newZ>, "properties"> {
47
+ properties: Properties;
48
+ make: Make;
49
+ model: Model;
50
+ }
51
+
52
+ export const ONTOLOGY_TYPE = "device";
53
+ export type OntologyType = typeof ONTOLOGY_TYPE;