@synnaxlabs/client 0.54.2 → 0.55.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 (46) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/dist/client.cjs +28 -34
  3. package/dist/client.js +5341 -5193
  4. package/dist/src/arc/arc.spec.d.ts +2 -0
  5. package/dist/src/arc/arc.spec.d.ts.map +1 -0
  6. package/dist/src/arc/graph/types.gen.d.ts +20 -20
  7. package/dist/src/arc/ir/types.gen.d.ts +145 -176
  8. package/dist/src/arc/ir/types.gen.d.ts.map +1 -1
  9. package/dist/src/arc/module/types.gen.d.ts +46 -65
  10. package/dist/src/arc/module/types.gen.d.ts.map +1 -1
  11. package/dist/src/arc/program/types.gen.d.ts +46 -65
  12. package/dist/src/arc/program/types.gen.d.ts.map +1 -1
  13. package/dist/src/arc/types.gen.d.ts +86 -105
  14. package/dist/src/arc/types.gen.d.ts.map +1 -1
  15. package/dist/src/auth/auth.d.ts.map +1 -1
  16. package/dist/src/channel/types.gen.d.ts.map +1 -1
  17. package/dist/src/client.d.ts +5 -0
  18. package/dist/src/client.d.ts.map +1 -1
  19. package/dist/src/connection/checker.d.ts +17 -2
  20. package/dist/src/connection/checker.d.ts.map +1 -1
  21. package/dist/src/control/state.d.ts.map +1 -1
  22. package/dist/src/framer/client.d.ts.map +1 -1
  23. package/dist/src/task/client.d.ts.map +1 -1
  24. package/package.json +10 -10
  25. package/src/arc/arc.spec.ts +44 -0
  26. package/src/arc/ir/types.gen.ts +101 -47
  27. package/src/auth/auth.ts +13 -1
  28. package/src/channel/channel.spec.ts +13 -0
  29. package/src/channel/types.gen.ts +1 -2
  30. package/src/client.ts +3 -0
  31. package/src/connection/checker.ts +44 -5
  32. package/src/connection/connection.spec.ts +67 -2
  33. package/src/control/state.ts +5 -4
  34. package/src/device/device.spec.ts +7 -5
  35. package/src/framer/client.ts +12 -0
  36. package/src/framer/writer.spec.ts +144 -1
  37. package/src/label/label.spec.ts +12 -0
  38. package/src/ontology/ontology.spec.ts +10 -0
  39. package/src/rack/rack.spec.ts +12 -1
  40. package/src/ranger/ranger.spec.ts +12 -0
  41. package/src/schematic/symbol/client.spec.ts +33 -9
  42. package/src/status/status.spec.ts +7 -6
  43. package/src/task/client.ts +7 -9
  44. package/src/task/task.spec.ts +15 -1
  45. package/src/view/view.spec.ts +9 -5
  46. package/src/workspace/workspace.spec.ts +14 -1
@@ -7,7 +7,7 @@
7
7
  // License, use of this software will be governed by the Apache License, Version 2.0,
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
- import { DataType, id, TimeRange, TimeSpan, TimeStamp } from "@synnaxlabs/x";
10
+ import { DataType, id, Series, TimeRange, TimeSpan, TimeStamp } from "@synnaxlabs/x";
11
11
  import { describe, expect, it, test } from "vitest";
12
12
 
13
13
  import { UnauthorizedError, ValidationError } from "@/errors";
@@ -263,4 +263,147 @@ describe("Writer", () => {
263
263
  expect(f.length).toEqual(10);
264
264
  });
265
265
  });
266
+
267
+ describe("Variable Channels", () => {
268
+ test("write and read string data", async () => {
269
+ const index = await client.channels.create({
270
+ name: id.create(),
271
+ isIndex: true,
272
+ dataType: DataType.TIMESTAMP,
273
+ leaseholder: 1,
274
+ });
275
+ const data = await client.channels.create({
276
+ name: id.create(),
277
+ index: index.key,
278
+ dataType: DataType.STRING,
279
+ leaseholder: 1,
280
+ });
281
+ const writer = await client.openWriter({
282
+ start: TimeStamp.seconds(1),
283
+ channels: [index, data],
284
+ });
285
+ try {
286
+ await writer.write({
287
+ [index.key]: secondsLinspace(1, 3),
288
+ [data.key]: new Series({
289
+ data: ["hello", "world", "foo"],
290
+ dataType: DataType.STRING,
291
+ }),
292
+ });
293
+ await writer.commit();
294
+ } finally {
295
+ await writer.close();
296
+ }
297
+ const f = await data.read(TimeRange.MAX);
298
+ expect(f.toStrings()).toEqual(["hello", "world", "foo"]);
299
+ });
300
+
301
+ test("write and read JSON data", async () => {
302
+ const index = await client.channels.create({
303
+ name: id.create(),
304
+ isIndex: true,
305
+ dataType: DataType.TIMESTAMP,
306
+ leaseholder: 1,
307
+ });
308
+ const data = await client.channels.create({
309
+ name: id.create(),
310
+ index: index.key,
311
+ dataType: DataType.JSON,
312
+ leaseholder: 1,
313
+ });
314
+ const writer = await client.openWriter({
315
+ start: TimeStamp.seconds(1),
316
+ channels: [index, data],
317
+ });
318
+ try {
319
+ await writer.write({
320
+ [index.key]: secondsLinspace(1, 2),
321
+ [data.key]: new Series({
322
+ data: [{ key: "value" }, { num: 42 }],
323
+ dataType: DataType.JSON,
324
+ }),
325
+ });
326
+ await writer.commit();
327
+ } finally {
328
+ await writer.close();
329
+ }
330
+ const f = await data.read(TimeRange.MAX);
331
+ expect(f.length).toEqual(2);
332
+ });
333
+
334
+ test("write mixed fixed and variable channels", async () => {
335
+ const index = await client.channels.create({
336
+ name: id.create(),
337
+ isIndex: true,
338
+ dataType: DataType.TIMESTAMP,
339
+ leaseholder: 1,
340
+ });
341
+ const floatCh = await client.channels.create({
342
+ name: id.create(),
343
+ index: index.key,
344
+ dataType: DataType.FLOAT64,
345
+ leaseholder: 1,
346
+ });
347
+ const strCh = await client.channels.create({
348
+ name: id.create(),
349
+ index: index.key,
350
+ dataType: DataType.STRING,
351
+ leaseholder: 1,
352
+ });
353
+ const writer = await client.openWriter({
354
+ start: TimeStamp.seconds(1),
355
+ channels: [index, floatCh, strCh],
356
+ });
357
+ try {
358
+ await writer.write({
359
+ [index.key]: secondsLinspace(1, 3),
360
+ [floatCh.key]: new Float64Array([1.1, 2.2, 3.3]),
361
+ [strCh.key]: new Series({
362
+ data: ["a", "b", "c"],
363
+ dataType: DataType.STRING,
364
+ }),
365
+ });
366
+ await writer.commit();
367
+ } finally {
368
+ await writer.close();
369
+ }
370
+ const floatData = await floatCh.read(TimeRange.MAX);
371
+ expect(floatData.length).toEqual(3);
372
+ const strData = await strCh.read(TimeRange.MAX);
373
+ expect(strData.toStrings()).toEqual(["a", "b", "c"]);
374
+ });
375
+
376
+ test("write strings with embedded newlines", async () => {
377
+ const index = await client.channels.create({
378
+ name: id.create(),
379
+ isIndex: true,
380
+ dataType: DataType.TIMESTAMP,
381
+ leaseholder: 1,
382
+ });
383
+ const data = await client.channels.create({
384
+ name: id.create(),
385
+ index: index.key,
386
+ dataType: DataType.STRING,
387
+ leaseholder: 1,
388
+ });
389
+ const writer = await client.openWriter({
390
+ start: TimeStamp.seconds(1),
391
+ channels: [index, data],
392
+ });
393
+ try {
394
+ await writer.write({
395
+ [index.key]: secondsLinspace(1, 2),
396
+ [data.key]: new Series({
397
+ data: ["line1\nline2", "no newline"],
398
+ dataType: DataType.STRING,
399
+ }),
400
+ });
401
+ await writer.commit();
402
+ } finally {
403
+ await writer.close();
404
+ }
405
+ const f = await data.read(TimeRange.MAX);
406
+ expect(f.toStrings()).toEqual(["line1\nline2", "no newline"]);
407
+ });
408
+ });
266
409
  });
@@ -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 { id } from "@synnaxlabs/x";
10
11
  import { describe, expect, it } from "vitest";
11
12
 
12
13
  import { label } from "@/label";
@@ -34,6 +35,17 @@ describe("Label", () => {
34
35
  const retrieved = await client.labels.retrieve({ key: v.key });
35
36
  expect(retrieved).toEqual(v);
36
37
  });
38
+ it("should retrieve labels by search term", async () => {
39
+ const prefix = `searchable-label-${id.create()}`;
40
+ const names = [`${prefix}-1`, `${prefix}-2`];
41
+ await client.labels.create(names.map((name) => ({ name, color: "#E774D0" })));
42
+ await expect
43
+ .poll(async () => {
44
+ const results = await client.labels.retrieve({ searchTerm: prefix });
45
+ return results.map((l) => l.name).sort();
46
+ })
47
+ .toEqual(names);
48
+ });
37
49
  });
38
50
 
39
51
  describe("delete", () => {
@@ -154,6 +154,16 @@ describe("Ontology", () => {
154
154
  expect(parents.length).toEqual(1);
155
155
  expect(parents[0].name).toEqual(name);
156
156
  });
157
+ test("retrieve by search term", async () => {
158
+ const name = randomName();
159
+ const g = await client.groups.create({ parent: ontology.ROOT_ID, name });
160
+ await expect
161
+ .poll(async () => {
162
+ const results = await client.ontology.retrieve({ searchTerm: name });
163
+ return results.find((r) => r.id.key === g.key);
164
+ })
165
+ .toMatchObject({ name, id: { type: "group", key: g.key } });
166
+ });
157
167
  });
158
168
 
159
169
  describe("write", () => {
@@ -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 { TimeStamp, zod } from "@synnaxlabs/x";
10
+ import { id, TimeStamp, zod } from "@synnaxlabs/x";
11
11
  import { describe, expect, it } from "vitest";
12
12
 
13
13
  import { type rack } from "@/rack";
@@ -77,6 +77,17 @@ describe("Rack", () => {
77
77
  expect(retrieved.key).toBe(r.key);
78
78
  expect(retrieved.name).toEqual(name);
79
79
  });
80
+ it("should retrieve racks by search term", async () => {
81
+ const prefix = `searchable-rack-${id.create()}`;
82
+ const names = [`${prefix}-1`, `${prefix}-2`];
83
+ await client.racks.create(names.map((name) => ({ name })));
84
+ await expect
85
+ .poll(async () => {
86
+ const results = await client.racks.retrieve({ searchTerm: prefix });
87
+ return results.map((r) => r.name).sort();
88
+ })
89
+ .toEqual(names);
90
+ });
80
91
  });
81
92
  describe("integrations", () => {
82
93
  it("should create a rack with integrations and retrieve them", async () => {
@@ -185,6 +185,18 @@ describe("range", () => {
185
185
  expect(retrieved.length).toBeGreaterThan(0);
186
186
  expect(retrieved.map((r) => r.key)).toContain(range.key);
187
187
  });
188
+ it("should retrieve ranges by search term", async () => {
189
+ const prefix = `searchable-range-${id.create()}`;
190
+ const timeRange = TimeStamp.now().spanRange(TimeSpan.seconds(1));
191
+ const names = [`${prefix}-1`, `${prefix}-2`];
192
+ await client.ranges.create(names.map((name) => ({ name, timeRange })));
193
+ await expect
194
+ .poll(async () => {
195
+ const results = await client.ranges.retrieve({ searchTerm: prefix });
196
+ return results.map((r) => r.name).sort();
197
+ })
198
+ .toEqual(names);
199
+ });
188
200
  });
189
201
 
190
202
  describe("retrieveParent", () => {
@@ -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 { id } from "@synnaxlabs/x";
10
11
  import { beforeAll, describe, expect, it } from "vitest";
11
12
 
12
13
  import { group } from "@/group";
@@ -75,22 +76,45 @@ describe("Symbol Client", () => {
75
76
  });
76
77
 
77
78
  it("should retrieve multiple symbols by keys", async () => {
78
- const created1 = await client.schematics.symbols.create({
79
- name: "Multi Test 1",
80
- data: { svg: "<svg></svg>", states: [], handles: [], variant: "sensor" },
81
- parent: group.ontologyID(symbolGroup.key),
82
- });
83
- const created2 = await client.schematics.symbols.create({
84
- name: "Multi Test 2",
85
- data: { svg: "<svg></svg>", states: [], handles: [], variant: "sensor" },
79
+ const created = await client.schematics.symbols.create({
80
+ symbols: [
81
+ {
82
+ name: "Multi Test 1",
83
+ data: { svg: "<svg></svg>", states: [], handles: [], variant: "sensor" },
84
+ },
85
+ {
86
+ name: "Multi Test 2",
87
+ data: { svg: "<svg></svg>", states: [], handles: [], variant: "sensor" },
88
+ },
89
+ ],
86
90
  parent: group.ontologyID(symbolGroup.key),
87
91
  });
88
92
 
89
93
  const retrieved = await client.schematics.symbols.retrieve({
90
- keys: [created1.key, created2.key],
94
+ keys: created.map((s) => s.key),
91
95
  });
92
96
  expect(retrieved).toHaveLength(2);
93
97
  });
98
+
99
+ it("should retrieve symbols by search term", async () => {
100
+ const prefix = `searchable-symbol-${id.create()}`;
101
+ const names = [`${prefix}-1`, `${prefix}-2`];
102
+ await client.schematics.symbols.create({
103
+ symbols: names.map((name) => ({
104
+ name,
105
+ data: { svg: "<svg></svg>", states: [], handles: [], variant: "sensor" },
106
+ })),
107
+ parent: group.ontologyID(symbolGroup.key),
108
+ });
109
+ await expect
110
+ .poll(async () => {
111
+ const results = await client.schematics.symbols.retrieve({
112
+ searchTerm: prefix,
113
+ });
114
+ return results.map((s) => s.name).sort();
115
+ })
116
+ .toEqual(names);
117
+ });
94
118
  });
95
119
 
96
120
  describe("rename", () => {
@@ -161,12 +161,13 @@ describe("Status", () => {
161
161
  time: TimeStamp.now(),
162
162
  });
163
163
 
164
- const results = await client.statuses.retrieve({
165
- searchTerm: uniqueName,
166
- });
167
-
168
- expect(results.length).toBeGreaterThanOrEqual(1);
169
- expect(results.some((s) => s.name === uniqueName)).toBe(true);
164
+ await expect
165
+ .poll(async () =>
166
+ (await client.statuses.retrieve({ searchTerm: uniqueName })).some(
167
+ (s) => s.name === uniqueName,
168
+ ),
169
+ )
170
+ .toBe(true);
170
171
  });
171
172
 
172
173
  it("should paginate results", async () => {
@@ -44,8 +44,6 @@ export const COMMAND_CHANNEL_NAME = "sy_task_cmd";
44
44
  export const SET_CHANNEL_NAME = "sy_task_set";
45
45
  export const DELETE_CHANNEL_NAME = "sy_task_delete";
46
46
 
47
- const NOT_CREATED_ERROR = new Error("Task not created");
48
-
49
47
  export const rackKey = (key: Key): RackKey => Number(BigInt(key) >> 32n);
50
48
 
51
49
  export const newKey = (rackKey: RackKey, taskKey: number = 0): Key =>
@@ -99,17 +97,17 @@ export class Task<S extends Schemas = Schemas> {
99
97
  private readonly rangeClient_?: ranger.Client;
100
98
 
101
99
  get frameClient(): framer.Client {
102
- if (this.frameClient_ == null) throw NOT_CREATED_ERROR;
100
+ if (this.frameClient_ == null) throw new Error("Task not created");
103
101
  return this.frameClient_;
104
102
  }
105
103
 
106
104
  get ontologyClient(): ontology.Client {
107
- if (this.ontologyClient_ == null) throw NOT_CREATED_ERROR;
105
+ if (this.ontologyClient_ == null) throw new Error("Task not created");
108
106
  return this.ontologyClient_;
109
107
  }
110
108
 
111
109
  get rangeClient(): ranger.Client {
112
- if (this.rangeClient_ == null) throw NOT_CREATED_ERROR;
110
+ if (this.rangeClient_ == null) throw new Error("Task not created");
113
111
  return this.rangeClient_;
114
112
  }
115
113
 
@@ -193,7 +191,7 @@ export class Task<S extends Schemas = Schemas> {
193
191
 
194
192
  async snapshottedTo(): Promise<ontology.Resource | null> {
195
193
  if (this.ontologyClient == null || this.rangeClient == null)
196
- throw NOT_CREATED_ERROR;
194
+ throw new Error("Task not created");
197
195
  if (!this.snapshot) return null;
198
196
  return await retrieveSnapshottedTo(this.key, this.ontologyClient);
199
197
  }
@@ -347,7 +345,7 @@ export class Client {
347
345
  }
348
346
 
349
347
  async retrieveSnapshottedTo(taskKey: Key): Promise<ontology.Resource | null> {
350
- if (this.ontologyClient == null) throw NOT_CREATED_ERROR;
348
+ if (this.ontologyClient == null) throw new Error("Task not created");
351
349
  return await retrieveSnapshottedTo(taskKey, this.ontologyClient);
352
350
  }
353
351
 
@@ -463,7 +461,7 @@ const executeCommands = async ({
463
461
  frameClient,
464
462
  commands,
465
463
  }: ExecuteCommandsInternalParams): Promise<string[]> => {
466
- if (frameClient == null) throw NOT_CREATED_ERROR;
464
+ if (frameClient == null) throw new Error("Task not created");
467
465
  const w = await frameClient.openWriter(COMMAND_CHANNEL_NAME);
468
466
  const cmds = commands.map((c) => ({ ...c, key: id.create() }));
469
467
  await w.write(COMMAND_CHANNEL_NAME, cmds);
@@ -512,7 +510,7 @@ const executeCommandsSync = async <StatusData extends z.ZodType = z.ZodNever>({
512
510
  statusDataZ,
513
511
  name: taskName,
514
512
  }: ExecuteCommandsSyncInternalParams<StatusData>): Promise<Status<StatusData>[]> => {
515
- if (frameClient == null) throw NOT_CREATED_ERROR;
513
+ if (frameClient == null) throw new Error("Task not created");
516
514
  const streamer = await frameClient.openStreamer(status.SET_CHANNEL_NAME);
517
515
  const cmdKeys = await executeCommands({ frameClient, commands });
518
516
  const parsedTimeout = new TimeSpan(timeout);
@@ -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 { TimeStamp } from "@synnaxlabs/x";
10
+ import { id, TimeStamp } from "@synnaxlabs/x";
11
11
  import { beforeAll, describe, expect, it } from "vitest";
12
12
  import { z } from "zod";
13
13
 
@@ -114,6 +114,20 @@ describe("Task", async () => {
114
114
  expect(retrieved.key).toBe(m.key);
115
115
  });
116
116
 
117
+ it("should retrieve tasks by search term", async () => {
118
+ const prefix = `searchable-task-${id.create()}`;
119
+ const names = [`${prefix}-1`, `${prefix}-2`];
120
+ await Promise.all(
121
+ names.map((name) => testRack.createTask({ name, config: {}, type: "ni" })),
122
+ );
123
+ await expect
124
+ .poll(async () => {
125
+ const results = await client.tasks.retrieve({ searchTerm: prefix });
126
+ return results.map((t) => t.name).sort();
127
+ })
128
+ .toEqual(names);
129
+ });
130
+
117
131
  describe("status", () => {
118
132
  it("should include task status when requested", async () => {
119
133
  const t = await testRack.createTask({
@@ -113,15 +113,19 @@ describe("View", () => {
113
113
 
114
114
  it("should search for views by name", async () => {
115
115
  const client = createTestClient();
116
+ const prefix = id.create();
117
+ const name = `${prefix} View`;
116
118
  await client.views.create({
117
- name: "Searchable View",
119
+ name,
118
120
  type: "lineplot",
119
121
  query: { channels: ["search"] },
120
122
  });
121
- const results = await client.views.retrieve({ searchTerm: "Searchable" });
122
- expect(results.length).toBeGreaterThan(0);
123
- expect(results[0].name).toContain("Searchable");
124
- expect(results[0].query).toEqual({ channels: ["search"] });
123
+ await expect
124
+ .poll(async () => {
125
+ const results = await client.views.retrieve({ searchTerm: prefix });
126
+ return results.find((v) => v.name === name);
127
+ })
128
+ .toMatchObject({ query: { channels: ["search"] } });
125
129
  });
126
130
  });
127
131
 
@@ -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 { uuid } from "@synnaxlabs/x";
10
+ import { id, uuid } from "@synnaxlabs/x";
11
11
  import { describe, expect, test } from "vitest";
12
12
 
13
13
  import { createTestClient } from "@/testutil/client";
@@ -58,6 +58,19 @@ describe("Workspace", () => {
58
58
  await expect(client.workspaces.retrieve(ws.key)).rejects.toThrow();
59
59
  });
60
60
  });
61
+ describe("retrieve", () => {
62
+ test("retrieve workspaces by search term", async () => {
63
+ const prefix = `searchable-workspace-${id.create()}`;
64
+ const names = [`${prefix}-1`, `${prefix}-2`];
65
+ await client.workspaces.create(names.map((name) => ({ name, layout: {} })));
66
+ await expect
67
+ .poll(async () => {
68
+ const results = await client.workspaces.retrieve({ searchTerm: prefix });
69
+ return results.map((w) => w.name).sort();
70
+ })
71
+ .toEqual(names);
72
+ });
73
+ });
61
74
  describe("case preservation", () => {
62
75
  test("should preserve key casing in layout field on create/retrieve cycle", async () => {
63
76
  const ws = await client.workspaces.create({