@synnaxlabs/client 0.27.0 → 0.28.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 (39) hide show
  1. package/.turbo/turbo-build.log +5 -5
  2. package/api/client.api.md +52 -8
  3. package/dist/client.cjs +22 -18
  4. package/dist/client.d.ts +4 -0
  5. package/dist/client.d.ts.map +1 -1
  6. package/dist/client.js +1880 -1719
  7. package/dist/connection/checker.d.ts +21 -1
  8. package/dist/connection/checker.d.ts.map +1 -1
  9. package/dist/framer/client.d.ts.map +1 -1
  10. package/dist/hardware/task/client.d.ts +12 -2
  11. package/dist/hardware/task/client.d.ts.map +1 -1
  12. package/dist/hardware/task/ni/types.d.ts +14495 -0
  13. package/dist/hardware/task/ni/types.d.ts.map +1 -0
  14. package/dist/hardware/task/payload.d.ts +6 -0
  15. package/dist/hardware/task/payload.d.ts.map +1 -1
  16. package/dist/ontology/client.d.ts +16 -5
  17. package/dist/ontology/client.d.ts.map +1 -1
  18. package/dist/ontology/group/writer.d.ts.map +1 -1
  19. package/dist/ontology/payload.d.ts +1 -2
  20. package/dist/ontology/payload.d.ts.map +1 -1
  21. package/dist/ranger/client.d.ts +3 -1
  22. package/dist/ranger/client.d.ts.map +1 -1
  23. package/package.json +5 -5
  24. package/src/client.ts +13 -5
  25. package/src/connection/checker.ts +58 -1
  26. package/src/connection/connection.spec.ts +43 -3
  27. package/src/framer/client.ts +0 -1
  28. package/src/hardware/task/client.ts +82 -6
  29. package/src/hardware/task/ni/types.ts +1716 -0
  30. package/src/hardware/task/payload.ts +1 -0
  31. package/src/hardware/task/task.spec.ts +45 -30
  32. package/src/label/client.ts +5 -5
  33. package/src/ontology/client.ts +43 -31
  34. package/src/ontology/group/writer.ts +10 -12
  35. package/src/ontology/ontology.spec.ts +3 -5
  36. package/src/ontology/payload.ts +1 -1
  37. package/src/ranger/client.ts +46 -40
  38. package/src/vite-env.d.ts +1 -1
  39. package/vite.config.ts +5 -0
@@ -55,6 +55,7 @@ export const taskZ = z.object({
55
55
  }),
56
56
  ) as z.ZodType<UnknownRecord>,
57
57
  state: stateZ.optional().nullable(),
58
+ snapshot: z.boolean().optional(),
58
59
  });
59
60
 
60
61
  export const newTaskZ = taskZ.omit({ key: true }).extend({
@@ -44,46 +44,61 @@ describe("Hardware", () => {
44
44
  expect(retrieved.config).toStrictEqual({ a: "dog" });
45
45
  expect(retrieved.type).toBe("ni");
46
46
  });
47
- });
48
- describe("retrieveByName", () => {
49
- it("should retrieve a task by its name", async () => {
50
- const name = `test-${Date.now()}-${Math.random()}`;
51
- const r = await client.hardware.racks.create({ name });
52
- const m = await r.createTask({
53
- name,
54
- config: { a: "dog" },
55
- type: "ni",
47
+ describe("retrieveByName", () => {
48
+ it("should retrieve a task by its name", async () => {
49
+ const name = `test-${Date.now()}-${Math.random()}`;
50
+ const r = await client.hardware.racks.create({ name });
51
+ const m = await r.createTask({
52
+ name,
53
+ config: { a: "dog" },
54
+ type: "ni",
55
+ });
56
+ const retrieved = await client.hardware.tasks.retrieveByName(name);
57
+ expect(retrieved.key).toBe(m.key);
58
+ });
59
+ });
60
+ describe("retrieve with state", () => {
61
+ it("should also send the tasks state", async () => {
62
+ const r = await client.hardware.racks.create({ name: "test" });
63
+ const t = await r.createTask({
64
+ name: "test",
65
+ config: { a: "dog" },
66
+ type: "ni",
67
+ });
68
+ const w = await client.openWriter(["sy_task_state"]);
69
+ interface StateDetails {
70
+ dog: string;
71
+ }
72
+ const state: task.State<StateDetails> = {
73
+ key: id.id(),
74
+ task: t.key,
75
+ variant: "success",
76
+ };
77
+ expect(await w.write("sy_task_state", [state])).toBeTruthy();
78
+ await w.close();
79
+ const retrieved = await client.hardware.tasks.retrieve(t.key, {
80
+ includeState: true,
81
+ });
82
+ expect(retrieved.state).not.toBeNull();
83
+ expect(retrieved.state?.variant).toBe(state.variant);
56
84
  });
57
- const retrieved = await client.hardware.tasks.retrieveByName(name);
58
- expect(retrieved.key).toBe(m.key);
59
85
  });
60
86
  });
61
- describe("retrieve with state", () => {
62
- it("should also send the tasks state", async () => {
87
+
88
+ describe("copy", () => {
89
+ it("should correctly copy the task", async () => {
63
90
  const r = await client.hardware.racks.create({ name: "test" });
64
- const t = await r.createTask({
91
+ const m = await r.createTask({
65
92
  name: "test",
66
93
  config: { a: "dog" },
67
94
  type: "ni",
68
95
  });
69
- const w = await client.openWriter(["sy_task_state"]);
70
- interface StateDetails {
71
- dog: string;
72
- }
73
- const state: task.State<StateDetails> = {
74
- key: id.id(),
75
- task: t.key,
76
- variant: "success",
77
- };
78
- expect(await w.write("sy_task_state", [state])).toBeTruthy();
79
- await w.close();
80
- const retrieved = await client.hardware.tasks.retrieve(t.key, {
81
- includeState: true,
82
- });
83
- expect(retrieved.state).not.toBeNull();
84
- expect(retrieved.state?.variant).toBe(state.variant);
96
+ const copy = await client.hardware.tasks.copy(m.key, "New Name", false);
97
+ expect(copy.name).toBe("New Name");
98
+ expect(copy.config).toStrictEqual({ a: "dog" });
85
99
  });
86
100
  });
101
+
87
102
  describe("list", () => {
88
103
  it("should list all tasks", async () => {
89
104
  const t = await client.hardware.racks.create({ name: "test" });
@@ -110,11 +110,11 @@ export class Client implements AsyncTermSearcher<string, Key, Label> {
110
110
  name: l.name,
111
111
  data: l,
112
112
  }));
113
- const base = await this.ontology.openDependentTracker(
114
- new ontology.ID(id),
115
- initial,
116
- "labeled_by",
117
- );
113
+ const base = await this.ontology.openDependentTracker({
114
+ target: new ontology.ID(id),
115
+ dependents: initial,
116
+ relationshipType: "labeled_by",
117
+ });
118
118
  base.onChange((resources: ontology.Resource[]) =>
119
119
  wrapper.notify(
120
120
  resources.map((r) => ({
@@ -27,6 +27,7 @@ import {
27
27
  type Resource,
28
28
  ResourceChange,
29
29
  resourceSchemaZ,
30
+ resourceTypeZ,
30
31
  } from "@/ontology/payload";
31
32
  import { Writer } from "@/ontology/writer";
32
33
 
@@ -41,13 +42,14 @@ const retrieveReqZ = z.object({
41
42
  term: z.string().optional(),
42
43
  limit: z.number().optional(),
43
44
  offset: z.number().optional(),
45
+ types: resourceTypeZ.array().optional(),
44
46
  });
45
47
 
46
48
  type RetrieveRequest = z.infer<typeof retrieveReqZ>;
47
49
 
48
50
  export type RetrieveOptions = Pick<
49
51
  RetrieveRequest,
50
- "includeSchema" | "excludeFieldData"
52
+ "includeSchema" | "excludeFieldData" | "types"
51
53
  >;
52
54
 
53
55
  const retrieveResZ = z.object({
@@ -219,19 +221,9 @@ export class Client implements AsyncTermSearcher<string, string, Resource> {
219
221
  }
220
222
 
221
223
  async openDependentTracker(
222
- parent: ID,
223
- initial: Resource[],
224
- type: string = "parent",
225
- direction: RelationshipDirection = "from",
224
+ props: DependentTrackerProps,
226
225
  ): Promise<observe.ObservableAsyncCloseable<Resource[]>> {
227
- return await DependentTracker.open(
228
- parent,
229
- this,
230
- this.framer,
231
- initial,
232
- type,
233
- direction,
234
- );
226
+ return await DependentTracker.open(props, this.framer, this);
235
227
  }
236
228
 
237
229
  newSearcherWithOptions(
@@ -372,6 +364,17 @@ export class ChangeTracker {
372
364
  }
373
365
  }
374
366
 
367
+ const oppositeDirection = (dir: RelationshipDirection): RelationshipDirection =>
368
+ dir === "from" ? "to" : "from";
369
+
370
+ interface DependentTrackerProps {
371
+ target: ID;
372
+ dependents: Resource[];
373
+ relationshipType?: string;
374
+ relationshipDirection?: RelationshipDirection;
375
+ resourceType?: string;
376
+ }
377
+
375
378
  /**
376
379
  * A class that tracks a resource (called the 'target' resource) and related resources
377
380
  * (called 'dependents') of a particular type (called the 'type') in a Synnax cluster
@@ -383,39 +386,43 @@ export class DependentTracker
383
386
  {
384
387
  private readonly internal: ChangeTracker;
385
388
  private readonly target: ID;
386
- private readonly direction: RelationshipDirection;
389
+ private readonly relDir: RelationshipDirection;
390
+ private readonly resourceType?: string;
387
391
  private dependents: Resource[];
388
392
  private readonly client: Client;
389
- private readonly type: string;
393
+ private readonly relType: string;
390
394
 
391
395
  private constructor(
392
- target: ID,
396
+ {
397
+ target,
398
+ dependents,
399
+ relationshipType = "parent",
400
+ relationshipDirection = "from",
401
+ resourceType,
402
+ }: DependentTrackerProps,
393
403
  internal: ChangeTracker,
394
- dependents: Resource[],
395
404
  client: Client,
396
- type: string = "parent",
397
- direction: RelationshipDirection = "from",
398
405
  ) {
399
406
  super();
407
+ this.resourceType = resourceType;
400
408
  this.internal = internal;
401
409
  this.target = target;
402
410
  this.dependents = dependents;
411
+ if (this.resourceType != null)
412
+ this.dependents = this.dependents.filter((r) => r.id.type === this.resourceType);
403
413
  this.client = client;
404
- this.type = type;
405
- this.direction = direction;
414
+ this.relType = relationshipType;
415
+ this.relDir = relationshipDirection;
406
416
  this.internal.resources.onChange(this.handleResourceChange);
407
417
  this.internal.relationships.onChange(this.handleRelationshipChange);
408
418
  }
409
419
  static async open(
410
- from: ID,
411
- client: Client,
420
+ props: DependentTrackerProps,
412
421
  framer: framer.Client,
413
- initial: Resource[],
414
- type: string = "parent",
415
- direction: RelationshipDirection = "from",
422
+ client: Client,
416
423
  ): Promise<DependentTracker> {
417
424
  const internal = await ChangeTracker.open(framer, client);
418
- return new DependentTracker(from, internal, initial, client, type, direction);
425
+ return new DependentTracker(props, internal, client);
419
426
  }
420
427
 
421
428
  private handleResourceChange = (changes: ResourceChange[]): void => {
@@ -431,20 +438,25 @@ export class DependentTracker
431
438
  const deletes = changes.filter(
432
439
  (c) =>
433
440
  c.variant === "delete" &&
434
- c.key[this.direction].toString() === this.target.toString(),
441
+ c.key[this.relDir].toString() === this.target.toString() &&
442
+ (this.resourceType == null ||
443
+ c.key[oppositeDirection(this.relDir)].type === this.resourceType),
435
444
  );
436
445
  this.dependents = this.dependents.filter(
437
446
  (child) =>
438
447
  !deletes.some(
439
448
  (del) =>
440
- del.key.to.toString() === child.id.toString() && del.key.type === this.type,
449
+ del.key.to.toString() === child.id.toString() &&
450
+ del.key.type === this.relType,
441
451
  ),
442
452
  );
443
453
  const sets = changes.filter(
444
454
  (c) =>
445
455
  c.variant === "set" &&
446
- c.key.type === this.type &&
447
- c.key[this.direction].toString() === this.target.toString(),
456
+ c.key.type === this.relType &&
457
+ c.key[this.relDir].toString() === this.target.toString() &&
458
+ (this.resourceType == null ||
459
+ c.key[oppositeDirection(this.relDir)].type === this.resourceType),
448
460
  );
449
461
  if (sets.length === 0) return this.notify(this.dependents);
450
462
  this.client.retrieve(sets.map((s) => s.key.to)).then((resources) => {
@@ -7,10 +7,10 @@
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 { type UnaryClient } from "@synnaxlabs/freighter";
10
+ import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
11
11
  import { z } from "zod";
12
12
 
13
- import { groupZ,type Payload } from "@/ontology/group/payload";
13
+ import { groupZ, type Payload } from "@/ontology/group/payload";
14
14
  import { type ID, idZ } from "@/ontology/payload";
15
15
 
16
16
  const resZ = z.object({
@@ -43,35 +43,33 @@ export class Writer {
43
43
  }
44
44
 
45
45
  async create(parent: ID, name: string, key?: string): Promise<Payload> {
46
- const [res, err] = await this.client.send(
46
+ const res = await sendRequired(
47
+ this.client,
47
48
  Writer.ENDPOINT,
48
49
  { parent, name, key },
49
50
  createReqZ,
50
51
  resZ,
51
52
  );
52
- if (err != null) throw err;
53
53
  return res.group;
54
54
  }
55
55
 
56
56
  async rename(key: string, name: string): Promise<void> {
57
- const req = { key, name };
58
- const [, err] = await this.client.send(
57
+ await sendRequired(
58
+ this.client,
59
59
  Writer.ENDPOINT_RENAME,
60
- req,
60
+ { key, name },
61
61
  renameReqZ,
62
62
  z.object({}),
63
63
  );
64
- if (err != null) throw err;
65
64
  }
66
65
 
67
66
  async delete(keys: string[]): Promise<void> {
68
- const req = { keys };
69
- const [, err] = await this.client.send(
67
+ await sendRequired(
68
+ this.client,
70
69
  Writer.ENDPOINT_DELETE,
71
- req,
70
+ { keys },
72
71
  deleteReqZ,
73
72
  z.object({}),
74
73
  );
75
- if (err != null) throw err;
76
74
  }
77
75
  }
@@ -104,11 +104,9 @@ describe("Ontology", () => {
104
104
  });
105
105
  it("should correctly propagate resource changes to the ontology", async () => {
106
106
  const change = await client.ontology.openChangeTracker();
107
- const p = new Promise<ontology.ResourceChange[]>((resolve) => {
108
- change.resources.onChange((changes) => {
109
- resolve(changes);
110
- });
111
- });
107
+ const p = new Promise<ontology.ResourceChange[]>((resolve) =>
108
+ change.resources.onChange((changes) => resolve(changes)),
109
+ );
112
110
  await client.ontology.groups.create(ontology.Root, randomName());
113
111
  const c = await p;
114
112
  expect(c.length).toBeGreaterThan(0);
@@ -17,7 +17,7 @@ export type RelationshipChange = change.Change<Relationship, undefined>;
17
17
  export type RelationshipSet = change.Set<Relationship, undefined>;
18
18
  export type RelationshipDelete = change.Delete<Relationship, undefined>;
19
19
 
20
- const resourceTypeZ = z.union([
20
+ export const resourceTypeZ = z.union([
21
21
  z.literal("label"),
22
22
  z.literal("builtin"),
23
23
  z.literal("cluster"),
@@ -128,6 +128,7 @@ export class Range {
128
128
  const res = (
129
129
  await this.ontologyClient.retrieveChildren(this.ontologyID, {
130
130
  excludeFieldData: true,
131
+ types: ["range"],
131
132
  })
132
133
  ).map((r) => r.id.key);
133
134
  return await this.rangeClient.retrieve(res);
@@ -159,12 +160,13 @@ export class Range {
159
160
  const id = new ontology.ID({ key: r.key, type: "range" });
160
161
  return { id, key: id.toString(), name: r.name, data: r.payload };
161
162
  });
162
- const base = await this.ontologyClient.openDependentTracker(
163
- this.ontologyID,
164
- initial,
165
- );
166
- base.onChange((resources: Resource[]) =>
167
- wrapper.notify(this.rangeClient.resourcesToRanges(resources)),
163
+ const base = await this.ontologyClient.openDependentTracker({
164
+ target: this.ontologyID,
165
+ dependents: initial,
166
+ resourceType: "range",
167
+ });
168
+ base.onChange((r: Resource[]) =>
169
+ wrapper.notify(this.rangeClient.resourcesToRanges(r)),
168
170
  );
169
171
  wrapper.setCloser(async () => await base.close());
170
172
  return wrapper;
@@ -176,12 +178,11 @@ export class Range {
176
178
  if (p == null) return null;
177
179
  const id = new ontology.ID({ key: p.key, type: "range" });
178
180
  const resourceP = { id, key: id.toString(), name: p.name, data: p.payload };
179
- const base = await this.ontologyClient.openDependentTracker(
180
- this.ontologyID,
181
- [resourceP],
182
- "parent",
183
- "to",
184
- );
181
+ const base = await this.ontologyClient.openDependentTracker({
182
+ target: this.ontologyID,
183
+ dependents: [resourceP],
184
+ relationshipDirection: "to",
185
+ });
185
186
  base.onChange((resources: Resource[]) => {
186
187
  const ranges = this.rangeClient.resourcesToRanges(resources);
187
188
  if (ranges.length === 0) return;
@@ -244,7 +245,7 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
244
245
  options?: CreateOptions,
245
246
  ): Promise<Range | Range[]> {
246
247
  const single = !Array.isArray(ranges);
247
- const res = this.sugar(await this.writer.create(toArray(ranges), options));
248
+ const res = this.sugarMany(await this.writer.create(toArray(ranges), options));
248
249
  return single ? res[0] : res;
249
250
  }
250
251
 
@@ -257,11 +258,11 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
257
258
  }
258
259
 
259
260
  async search(term: string): Promise<Range[]> {
260
- return this.sugar(await this.execRetrieve({ term }));
261
+ return this.sugarMany(await this.execRetrieve({ term }));
261
262
  }
262
263
 
263
264
  async page(offset: number, limit: number): Promise<Range[]> {
264
- return this.sugar(await this.execRetrieve({ offset, limit }));
265
+ return this.sugarMany(await this.execRetrieve({ offset, limit }));
265
266
  }
266
267
 
267
268
  async retrieve(range: CrudeTimeRange): Promise<Range[]>;
@@ -296,7 +297,7 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
296
297
  retrieveReqZ,
297
298
  retrieveResZ,
298
299
  );
299
- return this.sugar(ranges);
300
+ return this.sugarMany(ranges);
300
301
  }
301
302
 
302
303
  async retrieveParent(range: Key): Promise<Range | null> {
@@ -310,22 +311,24 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
310
311
  return await this.retrieve(first.id.key);
311
312
  }
312
313
 
313
- sugar(payloads: Payload[]): Range[] {
314
- return payloads.map((payload) => {
315
- return new Range(
316
- payload.name,
317
- payload.timeRange,
318
- payload.key,
319
- payload.color,
320
- this.frameClient,
321
- new KV(payload.key, this.unaryClient, this.frameClient),
322
- new Aliaser(payload.key, this.frameClient, this.unaryClient),
323
- this.channels,
324
- this.labelClient,
325
- this.ontologyClient,
326
- this,
327
- );
328
- });
314
+ sugarOne(payload: Payload): Range {
315
+ return new Range(
316
+ payload.name,
317
+ payload.timeRange,
318
+ payload.key,
319
+ payload.color,
320
+ this.frameClient,
321
+ new KV(payload.key, this.unaryClient, this.frameClient),
322
+ new Aliaser(payload.key, this.frameClient, this.unaryClient),
323
+ this.channels,
324
+ this.labelClient,
325
+ this.ontologyClient,
326
+ this,
327
+ );
328
+ }
329
+
330
+ sugarMany(payloads: Payload[]): Range[] {
331
+ return payloads.map((payload) => this.sugarOne(payload));
329
332
  }
330
333
 
331
334
  async openTracker(): Promise<signals.Observable<string, Range>> {
@@ -336,19 +339,22 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
336
339
  (variant, data) => {
337
340
  if (variant === "delete")
338
341
  return data.toStrings().map((k) => ({ variant, key: k, value: undefined }));
339
- const sugared = this.sugar(data.parseJSON(payloadZ));
342
+ const sugared = this.sugarMany(data.parseJSON(payloadZ));
340
343
  return sugared.map((r) => ({ variant, key: r.key, value: r }));
341
344
  },
342
345
  );
343
346
  }
344
347
 
345
348
  resourcesToRanges(resources: Resource[]): Range[] {
346
- return this.sugar(
347
- resources.map((r) => ({
348
- key: r.id.key,
349
- name: r.data?.name as string,
350
- timeRange: new TimeRange(r.data?.timeRange as CrudeTimeRange),
351
- })),
352
- );
349
+ return resources.map((r) => this.resourceToRange(r));
350
+ }
351
+
352
+ resourceToRange(resource: Resource): Range {
353
+ return this.sugarOne({
354
+ key: resource.id.key,
355
+ name: resource.data?.name as string,
356
+ timeRange: new TimeRange(resource.data?.timeRange as CrudeTimeRange),
357
+ color: resource.data?.color as string,
358
+ });
353
359
  }
354
360
  }
package/src/vite-env.d.ts CHANGED
@@ -7,5 +7,5 @@
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
-
11
10
  /// <reference types="vite/client" />
11
+ declare const __VERSION__: string;
package/vite.config.ts CHANGED
@@ -10,7 +10,12 @@
10
10
  import { lib } from "@synnaxlabs/vite-plugin";
11
11
  import { defineConfig } from "vite";
12
12
 
13
+ import packageJSON from "./package.json";
14
+
13
15
  export default defineConfig({
16
+ define: {
17
+ __VERSION__: JSON.stringify(packageJSON.version),
18
+ },
14
19
  plugins: [lib({ name: "client" })],
15
20
  build: {
16
21
  rollupOptions: {