@synnaxlabs/client 0.36.0 → 0.38.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 (75) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/dist/access/policy/payload.d.ts +10 -10
  3. package/dist/channel/client.d.ts +13 -1
  4. package/dist/channel/client.d.ts.map +1 -1
  5. package/dist/channel/payload.d.ts +14 -0
  6. package/dist/channel/payload.d.ts.map +1 -1
  7. package/dist/client.cjs +30 -30
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +2226 -2157
  10. package/dist/framer/frame.d.ts.map +1 -1
  11. package/dist/hardware/device/payload.d.ts +2 -2
  12. package/dist/hardware/device/payload.d.ts.map +1 -1
  13. package/dist/hardware/task/client.d.ts.map +1 -1
  14. package/dist/hardware/task/payload.d.ts +4 -4
  15. package/dist/hardware/task/payload.d.ts.map +1 -1
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/label/client.d.ts.map +1 -1
  19. package/dist/ontology/group/group.d.ts.map +1 -1
  20. package/dist/ontology/group/payload.d.ts.map +1 -1
  21. package/dist/ontology/payload.d.ts.map +1 -1
  22. package/dist/ranger/client.d.ts.map +1 -1
  23. package/dist/ranger/payload.d.ts +2 -2
  24. package/dist/ranger/payload.d.ts.map +1 -1
  25. package/dist/transport.d.ts.map +1 -1
  26. package/dist/util/retrieve.d.ts.map +1 -1
  27. package/dist/util/zod.d.ts +1 -1
  28. package/dist/util/zod.d.ts.map +1 -1
  29. package/dist/workspace/client.d.ts +2 -2
  30. package/dist/workspace/client.d.ts.map +1 -1
  31. package/dist/workspace/lineplot/client.d.ts +1 -1
  32. package/dist/workspace/lineplot/client.d.ts.map +1 -1
  33. package/dist/workspace/log/client.d.ts +1 -1
  34. package/dist/workspace/log/client.d.ts.map +1 -1
  35. package/dist/workspace/schematic/client.d.ts +1 -1
  36. package/dist/workspace/schematic/client.d.ts.map +1 -1
  37. package/dist/workspace/table/client.d.ts +1 -1
  38. package/dist/workspace/table/client.d.ts.map +1 -1
  39. package/examples/node/package-lock.json +56 -5509
  40. package/examples/node/package.json +1 -1
  41. package/package.json +11 -15
  42. package/src/access/policy/policy.spec.ts +1 -4
  43. package/src/channel/batchRetriever.spec.ts +4 -0
  44. package/src/channel/channel.spec.ts +108 -37
  45. package/src/channel/client.ts +45 -2
  46. package/src/channel/payload.ts +5 -0
  47. package/src/client.ts +1 -1
  48. package/src/framer/frame.spec.ts +0 -1
  49. package/src/framer/frame.ts +4 -5
  50. package/src/framer/streamer.spec.ts +193 -6
  51. package/src/hardware/task/client.ts +3 -2
  52. package/src/index.ts +1 -1
  53. package/src/label/client.ts +2 -2
  54. package/src/ontology/group/group.ts +3 -5
  55. package/src/ontology/group/payload.ts +1 -0
  56. package/src/ontology/payload.ts +1 -1
  57. package/src/ranger/client.ts +6 -11
  58. package/src/ranger/payload.ts +2 -2
  59. package/src/transport.ts +4 -4
  60. package/src/util/retrieve.ts +2 -2
  61. package/src/util/zod.ts +4 -1
  62. package/src/workspace/client.ts +6 -5
  63. package/src/workspace/lineplot/client.ts +5 -4
  64. package/src/workspace/log/client.ts +4 -3
  65. package/src/workspace/schematic/client.ts +5 -4
  66. package/src/workspace/table/client.ts +4 -3
  67. package/vite.config.ts +5 -12
  68. package/api/client.api.md +0 -3473
  69. package/api-extractor.json +0 -7
  70. package/dist/hardware/task/ni/types.d.ts +0 -14495
  71. package/dist/hardware/task/ni/types.d.ts.map +0 -1
  72. package/dist/workspace/lineplot/payload.d.ts +0 -23
  73. package/dist/workspace/lineplot/payload.d.ts.map +0 -1
  74. package/src/hardware/task/ni/types.ts +0 -1716
  75. package/src/workspace/lineplot/payload.ts +0 -30
@@ -11,6 +11,6 @@
11
11
  "author": "",
12
12
  "license": "ISC",
13
13
  "dependencies": {
14
- "@synnaxlabs/client": "^0.31.0"
14
+ "@synnaxlabs/client": "^0.38.0"
15
15
  }
16
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synnaxlabs/client",
3
- "version": "0.36.0",
3
+ "version": "0.38.0",
4
4
  "description": "The Synnax Client Library",
5
5
  "keywords": [
6
6
  "synnax",
@@ -25,22 +25,21 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "async-mutex": "^0.5.0",
28
+ "uuid": "^11.0.5",
28
29
  "zod": "^3.24.1",
29
- "@synnaxlabs/x": "0.36.0",
30
- "@synnaxlabs/freighter": "0.36.0"
30
+ "@synnaxlabs/freighter": "0.38.0",
31
+ "@synnaxlabs/x": "0.38.0"
31
32
  },
32
33
  "devDependencies": {
33
- "@types/node": "^22.7.5",
34
+ "@types/node": "^22.10.6",
34
35
  "@types/uuid": "^10.0.0",
35
- "@vitest/coverage-v8": "^2.1.8",
36
- "eslint": "^9.16.0",
37
- "typescript": "^5.6.3",
38
- "uuid": "^10.0.0",
39
- "vite": "^6.0.3",
36
+ "eslint": "^9.18.0",
37
+ "typescript": "^5.7.3",
38
+ "vite": "^6.0.7",
40
39
  "vitest": "^2.1.8",
40
+ "@synnaxlabs/vite-plugin": "0.0.1",
41
41
  "@synnaxlabs/tsconfig": "0.0.2",
42
- "eslint-config-synnaxlabs": "0.0.1",
43
- "@synnaxlabs/vite-plugin": "0.0.1"
42
+ "eslint-config-synnaxlabs": "0.0.1"
44
43
  },
45
44
  "type": "module",
46
45
  "types": "dist/index.d.ts",
@@ -49,10 +48,7 @@
49
48
  "build": "tsc --noEmit && vite build",
50
49
  "watch": "tsc --noEmit && vite build --watch",
51
50
  "test": "vitest",
52
- "cov": "vitest --coverage",
53
51
  "lint": "eslint --cache",
54
- "fix": "eslint --cache --fix",
55
- "genApi": "tsc --noEmit && vite build && npx api-extractor run --local",
56
- "checkApi": "tsc --noEmit && vite build && npx api-extractor run"
52
+ "fix": "eslint --cache --fix"
57
53
  }
58
54
  }
@@ -298,10 +298,7 @@ describe("privilege", async () => {
298
298
  password: "pwd1",
299
299
  });
300
300
  await expect(
301
- client2.user.create({
302
- username: id.id(),
303
- password: id.id(),
304
- }),
301
+ client2.user.create({ username: id.id(), password: id.id() }),
305
302
  ).rejects.toThrow(AuthError);
306
303
 
307
304
  const policy = await client.access.policy.create({
@@ -56,6 +56,8 @@ describe("channelRetriever", () => {
56
56
  leaseholder: 1,
57
57
  index: 0,
58
58
  virtual: false,
59
+ expression: "",
60
+ requires: [],
59
61
  }));
60
62
  });
61
63
  const retriever = new DebouncedBatchRetriever(base, 10);
@@ -83,6 +85,8 @@ describe("channelRetriever", () => {
83
85
  leaseholder: 1,
84
86
  index: 0,
85
87
  virtual: false,
88
+ expression: "",
89
+ requires: [],
86
90
  }));
87
91
  });
88
92
  const retriever = new DebouncedBatchRetriever(base, 10);
@@ -8,7 +8,7 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  import { DataType, Rate, TimeStamp } from "@synnaxlabs/x/telem";
11
- import { describe, expect, it, test } from "vitest";
11
+ import { beforeAll, describe, expect, it, test } from "vitest";
12
12
 
13
13
  import { Channel } from "@/channel/client";
14
14
  import { NotFoundError, QueryError } from "@/errors";
@@ -31,6 +31,27 @@ describe("Channel", () => {
31
31
  expect(channel.dataType).toEqual(DataType.FLOAT32);
32
32
  });
33
33
 
34
+ test("create calculated", async () => {
35
+ let chOne = new Channel({
36
+ name: "test",
37
+ isIndex: true,
38
+ dataType: DataType.TIMESTAMP,
39
+ });
40
+ chOne = await client.channels.create(chOne);
41
+ let calculatedCH = new Channel({
42
+ name: "test2",
43
+ virtual: true,
44
+ dataType: DataType.FLOAT32,
45
+ expression: "test * 2",
46
+ requires: [chOne.key],
47
+ });
48
+ calculatedCH = await client.channels.create(calculatedCH);
49
+ expect(calculatedCH.key).not.toEqual(0);
50
+ expect(calculatedCH.virtual).toEqual(true);
51
+ expect(calculatedCH.expression).toEqual("test * 2");
52
+ expect(calculatedCH.requires).toEqual([chOne.key]);
53
+ });
54
+
34
55
  test("create index and indexed pair", async () => {
35
56
  const one = await client.channels.create({
36
57
  name: "Time",
@@ -48,18 +69,8 @@ describe("Channel", () => {
48
69
 
49
70
  test("create many", async () => {
50
71
  const channels = await client.channels.create([
51
- {
52
- name: "test1",
53
- leaseholder: 1,
54
- rate: Rate.hz(1),
55
- dataType: DataType.FLOAT32,
56
- },
57
- {
58
- name: "test2",
59
- leaseholder: 1,
60
- rate: Rate.hz(1),
61
- dataType: DataType.FLOAT32,
62
- },
72
+ { name: "test1", leaseholder: 1, rate: Rate.hz(1), dataType: DataType.FLOAT32 },
73
+ { name: "test2", leaseholder: 1, rate: Rate.hz(1), dataType: DataType.FLOAT32 },
63
74
  ]);
64
75
  expect(channels.length).toEqual(2);
65
76
  expect(channels[0].name).toEqual("test1");
@@ -116,12 +127,7 @@ describe("Channel", () => {
116
127
  dataType: DataType.FLOAT32,
117
128
  });
118
129
  const channelTwo = await client.channels.create(
119
- {
120
- name,
121
- leaseholder: 1,
122
- rate: Rate.hz(1),
123
- dataType: DataType.FLOAT32,
124
- },
130
+ { name, leaseholder: 1, rate: Rate.hz(1), dataType: DataType.FLOAT32 },
125
131
  { retrieveIfNameExists: true },
126
132
  );
127
133
  expect(channelTwo.key).toEqual(channel.key);
@@ -155,12 +161,7 @@ describe("Channel", () => {
155
161
  });
156
162
  const channelTwo = await client.channels.create(
157
163
  [
158
- {
159
- name,
160
- leaseholder: 1,
161
- rate: Rate.hz(1),
162
- dataType: DataType.FLOAT32,
163
- },
164
+ { name, leaseholder: 1, rate: Rate.hz(1), dataType: DataType.FLOAT32 },
164
165
  {
165
166
  name: `${name}-2`,
166
167
  leaseholder: 1,
@@ -253,18 +254,8 @@ describe("Channel", () => {
253
254
  });
254
255
  test("multiple rename", async () => {
255
256
  const channels = await client.channels.create([
256
- {
257
- name: "test1",
258
- leaseholder: 1,
259
- rate: Rate.hz(1),
260
- dataType: DataType.FLOAT32,
261
- },
262
- {
263
- name: "test2",
264
- leaseholder: 1,
265
- rate: Rate.hz(1),
266
- dataType: DataType.FLOAT32,
267
- },
257
+ { name: "test1", leaseholder: 1, rate: Rate.hz(1), dataType: DataType.FLOAT32 },
258
+ { name: "test2", leaseholder: 1, rate: Rate.hz(1), dataType: DataType.FLOAT32 },
268
259
  ]);
269
260
  // Retrieve channels here to ensure we check for cache invalidation
270
261
  const initial = await client.channels.retrieve(channels.map((c) => c.key));
@@ -279,4 +270,84 @@ describe("Channel", () => {
279
270
  expect(renamed[1].name).toEqual("test4");
280
271
  });
281
272
  });
273
+
274
+ describe("update", () => {
275
+ let idxCH: Channel;
276
+ beforeAll(async () => {
277
+ idxCH = await client.channels.create({
278
+ name: "idx",
279
+ dataType: DataType.TIMESTAMP,
280
+ isIndex: true,
281
+ });
282
+ });
283
+ test("update virtual channel expression", async () => {
284
+ const channel = await client.channels.create({
285
+ name: "virtual-calc",
286
+ dataType: DataType.FLOAT32,
287
+ virtual: true,
288
+ expression: "return 1",
289
+ requires: [idxCH.key],
290
+ });
291
+
292
+ const updated = await client.channels.create({
293
+ key: channel.key,
294
+ name: channel.name,
295
+ dataType: channel.dataType,
296
+ virtual: true,
297
+ expression: "return 2",
298
+ requires: [idxCH.key],
299
+ });
300
+
301
+ const channelsWithName = await client.channels.retrieve(["virtual-calc"]);
302
+ expect(channelsWithName.length).toEqual(1);
303
+
304
+ expect(updated.expression).toEqual("return 2");
305
+
306
+ const retrieved = await client.channels.retrieve(channel.key);
307
+ expect(retrieved.expression).toEqual("return 2");
308
+ });
309
+
310
+ test("update calculated channel name", async () => {
311
+ const channel = await client.channels.create({
312
+ name: "virtual-calc",
313
+ dataType: DataType.FLOAT32,
314
+ virtual: true,
315
+ expression: "return 1",
316
+ requires: [idxCH.key],
317
+ });
318
+
319
+ const updated = await client.channels.create({
320
+ key: channel.key,
321
+ name: "new-name",
322
+ dataType: channel.dataType,
323
+ virtual: true,
324
+ expression: channel.expression,
325
+ requires: [idxCH.key],
326
+ });
327
+ expect(updated.name).toEqual("new-name");
328
+
329
+ const retrieved = await client.channels.retrieve(channel.key);
330
+ expect(retrieved.name).toEqual("new-name");
331
+ });
332
+
333
+ test("should not allow updates to non-virtual channels", async () => {
334
+ const channel = await client.channels.create({
335
+ name: "regular-channel",
336
+ leaseholder: 1,
337
+ rate: Rate.hz(1),
338
+ dataType: DataType.FLOAT32,
339
+ });
340
+
341
+ const _updated = await client.channels.create({
342
+ key: channel.key,
343
+ name: "new-name",
344
+ leaseholder: channel.leaseholder,
345
+ rate: channel.rate,
346
+ dataType: channel.dataType,
347
+ });
348
+
349
+ const retrieved = await client.channels.retrieve(channel.key);
350
+ expect(retrieved.name).toEqual("regular-channel");
351
+ });
352
+ });
282
353
  });
@@ -25,6 +25,7 @@ import {
25
25
  type Key,
26
26
  type KeyOrName,
27
27
  type NewPayload,
28
+ ontologyID as payloadOntologyID,
28
29
  type Params,
29
30
  type Payload,
30
31
  payload,
@@ -40,7 +41,7 @@ import {
40
41
  import { type Writer } from "@/channel/writer";
41
42
  import { ValidationError } from "@/errors";
42
43
  import { type framer } from "@/framer";
43
- import { ontology } from "@/ontology";
44
+ import { type ontology } from "@/ontology";
44
45
  import { group } from "@/ontology/group";
45
46
  import { checkForMultipleOrNoResults } from "@/util/retrieve";
46
47
 
@@ -107,6 +108,15 @@ export class Channel {
107
108
  * database, but can still be used for streaming purposes.
108
109
  */
109
110
  readonly virtual: boolean;
111
+ /**
112
+ * Only used for calculated channels. Specifies the python expression to evaluate
113
+ * the calculated value
114
+ */
115
+ readonly expression: string;
116
+ /**
117
+ * Only used for calculated channels. Specifies the channels required for calculation
118
+ */
119
+ readonly requires: Key[];
110
120
 
111
121
  constructor({
112
122
  dataType,
@@ -120,6 +130,8 @@ export class Channel {
120
130
  virtual = false,
121
131
  frameClient,
122
132
  alias,
133
+ expression = "",
134
+ requires = [],
123
135
  }: NewPayload & {
124
136
  frameClient?: framer.Client;
125
137
  density?: CrudeDensity;
@@ -134,6 +146,8 @@ export class Channel {
134
146
  this.internal = internal;
135
147
  this.alias = alias;
136
148
  this.virtual = virtual;
149
+ this.expression = expression;
150
+ this.requires = requires ?? [];
137
151
  this._frameClient = frameClient ?? null;
138
152
  }
139
153
 
@@ -158,14 +172,21 @@ export class Channel {
158
172
  index: this.index,
159
173
  isIndex: this.isIndex,
160
174
  internal: this.internal,
175
+ virtual: this.virtual,
176
+ expression: this.expression,
177
+ requires: this.requires,
161
178
  });
162
179
  }
163
180
 
181
+ get isCalculated(): boolean {
182
+ return isCalculated(this.payload);
183
+ }
184
+
164
185
  /***
165
186
  * @returns the ontology ID of the channel
166
187
  */
167
188
  get ontologyID(): ontology.ID {
168
- return new ontology.ID({ type: "channel", key: this.key.toString() });
189
+ return payloadOntologyID(this.key);
169
190
  }
170
191
 
171
192
  /**
@@ -421,3 +442,25 @@ export class Client implements AsyncTermSearcher<string, Key, Channel> {
421
442
  return new group.Group(res.group.name, res.group.key);
422
443
  }
423
444
  }
445
+
446
+ export const isCalculated = ({ virtual, expression }: Payload): boolean =>
447
+ virtual && expression !== "";
448
+
449
+ export const resolveCalculatedIndex = async (
450
+ retrieve: (key: Key) => Promise<Payload>,
451
+ channel: Payload,
452
+ ): Promise<Key | null> => {
453
+ if (!isCalculated(channel)) return channel.index;
454
+ for (const required of channel.requires) {
455
+ const requiredChannel = await retrieve(required);
456
+ if (!requiredChannel.virtual) return requiredChannel.index;
457
+ }
458
+ for (const required of channel.requires) {
459
+ const requiredChannel = await retrieve(required);
460
+ if (isCalculated(requiredChannel)) {
461
+ const index = await resolveCalculatedIndex(retrieve, requiredChannel);
462
+ if (index != null) return index;
463
+ }
464
+ }
465
+ return null;
466
+ };
@@ -11,6 +11,7 @@ import { DataType, Rate } from "@synnaxlabs/x/telem";
11
11
  import { z } from "zod";
12
12
 
13
13
  import { ontology } from "@/ontology";
14
+ import { nullableArrayZ } from "@/util/zod";
14
15
 
15
16
  export const keyZ = z.number();
16
17
  export type Key = number;
@@ -32,6 +33,8 @@ export const payload = z.object({
32
33
  internal: z.boolean(),
33
34
  virtual: z.boolean(),
34
35
  alias: z.string().optional(),
36
+ expression: z.string().default(""),
37
+ requires: nullableArrayZ(keyZ),
35
38
  });
36
39
 
37
40
  export type Payload = z.infer<typeof payload>;
@@ -44,6 +47,8 @@ export const newPayload = payload.extend({
44
47
  isIndex: z.boolean().optional(),
45
48
  internal: z.boolean().optional().default(false),
46
49
  virtual: z.boolean().optional().default(false),
50
+ expression: z.string().optional().default(""),
51
+ requires: nullableArrayZ(keyZ).optional().default([]),
47
52
  });
48
53
 
49
54
  export type NewPayload = z.input<typeof newPayload>;
package/src/client.ts CHANGED
@@ -7,6 +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 { breaker } from "@synnaxlabs/x";
10
11
  import { TimeSpan, TimeStamp } from "@synnaxlabs/x/telem";
11
12
  import { URL } from "@synnaxlabs/x/url";
12
13
  import { z } from "zod";
@@ -28,7 +29,6 @@ import { ranger } from "@/ranger";
28
29
  import { Transport } from "@/transport";
29
30
  import { user } from "@/user";
30
31
  import { workspace } from "@/workspace";
31
- import { breaker } from "@synnaxlabs/x";
32
32
 
33
33
  export const synnaxPropsZ = z.object({
34
34
  host: z
@@ -11,7 +11,6 @@ import { DataType, Series, TimeRange } from "@synnaxlabs/x/telem";
11
11
  import { describe, expect, it, test } from "vitest";
12
12
 
13
13
  import { framer } from "@/framer";
14
- import { equal } from "assert";
15
14
 
16
15
  describe("framer.Frame", () => {
17
16
  describe("construction", () => {
@@ -7,18 +7,17 @@
7
7
  // License, use of this software will be governed by the Apache License, Version 2.0,
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
+ import { toArray, unique } from "@synnaxlabs/x";
10
11
  import {
11
12
  MultiSeries,
12
13
  Series,
13
- SeriesDigest,
14
+ type SeriesDigest,
14
15
  type SeriesPayload,
15
16
  Size,
16
17
  type TelemValue,
17
18
  TimeRange,
18
19
  TimeStamp,
19
20
  } from "@synnaxlabs/x/telem";
20
- import { toArray } from "@synnaxlabs/x/toArray";
21
- import { unique } from "@synnaxlabs/x/unique";
22
21
  import { z } from "zod";
23
22
 
24
23
  import {
@@ -177,7 +176,7 @@ export class Frame {
177
176
  * error otherwise.
178
177
  */
179
178
  get uniqueKeys(): Keys {
180
- return unique(this.keys);
179
+ return unique.unique(this.keys);
181
180
  }
182
181
 
183
182
  /**
@@ -194,7 +193,7 @@ export class Frame {
194
193
  * otherwise.
195
194
  */
196
195
  get uniqueNames(): Names {
197
- return unique(this.names);
196
+ return unique.unique(this.names);
198
197
  }
199
198
 
200
199
  /**