@synnaxlabs/client 0.26.7 → 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.
- package/.turbo/turbo-build.log +7 -7
- package/README.md +36 -10
- package/api/client.api.md +3121 -0
- package/api-extractor.json +7 -0
- package/dist/access/client.d.ts +0 -1
- package/dist/access/payload.d.ts +0 -1
- package/dist/auth/auth.d.ts +0 -1
- package/dist/channel/client.d.ts +6 -2
- package/dist/channel/client.d.ts.map +1 -1
- package/dist/channel/creator.d.ts +0 -1
- package/dist/channel/payload.d.ts +4 -1
- package/dist/channel/payload.d.ts.map +1 -1
- package/dist/channel/retriever.d.ts +0 -1
- package/dist/channel/writer.d.ts +0 -1
- package/dist/client.cjs +23 -19
- package/dist/client.d.ts +10 -7
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2326 -1904
- package/dist/connection/checker.d.ts +21 -2
- package/dist/connection/checker.d.ts.map +1 -1
- package/dist/control/client.d.ts +0 -1
- package/dist/control/client.d.ts.map +1 -1
- package/dist/control/state.d.ts +0 -1
- package/dist/errors.d.ts +0 -1
- package/dist/framer/adapter.d.ts +0 -1
- package/dist/framer/client.d.ts +0 -1
- package/dist/framer/client.d.ts.map +1 -1
- package/dist/framer/deleter.d.ts +0 -1
- package/dist/framer/frame.d.ts +11 -12
- package/dist/framer/iterator.d.ts +0 -1
- package/dist/framer/streamProxy.d.ts +0 -1
- package/dist/framer/streamer.d.ts +0 -1
- package/dist/framer/writer.d.ts +0 -1
- package/dist/framer/writer.d.ts.map +1 -1
- package/dist/hardware/client.d.ts +0 -1
- package/dist/hardware/device/client.d.ts +0 -1
- package/dist/hardware/device/payload.d.ts +0 -1
- package/dist/hardware/device/payload.d.ts.map +1 -1
- package/dist/hardware/rack/client.d.ts +0 -1
- package/dist/hardware/rack/payload.d.ts +0 -1
- package/dist/hardware/rack/payload.d.ts.map +1 -1
- package/dist/hardware/task/client.d.ts +12 -3
- package/dist/hardware/task/client.d.ts.map +1 -1
- package/dist/hardware/task/ni/types.d.ts +14495 -0
- package/dist/hardware/task/ni/types.d.ts.map +1 -0
- package/dist/hardware/task/payload.d.ts +6 -1
- package/dist/hardware/task/payload.d.ts.map +1 -1
- package/dist/label/client.d.ts +8 -6
- package/dist/label/client.d.ts.map +1 -1
- package/dist/label/payload.d.ts +0 -1
- package/dist/label/retriever.d.ts +0 -1
- package/dist/label/retriever.d.ts.map +1 -1
- package/dist/label/writer.d.ts +32 -2
- package/dist/label/writer.d.ts.map +1 -1
- package/dist/ontology/client.d.ts +136 -12
- package/dist/ontology/client.d.ts.map +1 -1
- package/dist/ontology/group/client.d.ts +0 -1
- package/dist/ontology/group/group.d.ts +0 -1
- package/dist/ontology/group/payload.d.ts +0 -1
- package/dist/ontology/group/writer.d.ts +0 -1
- package/dist/ontology/group/writer.d.ts.map +1 -1
- package/dist/ontology/payload.d.ts +6 -3
- package/dist/ontology/payload.d.ts.map +1 -1
- package/dist/ontology/writer.d.ts +4 -5
- package/dist/ontology/writer.d.ts.map +1 -1
- package/dist/ranger/alias.d.ts +0 -1
- package/dist/ranger/client.d.ts +57 -14
- package/dist/ranger/client.d.ts.map +1 -1
- package/dist/ranger/external.d.ts +1 -1
- package/dist/ranger/external.d.ts.map +1 -1
- package/dist/ranger/kv.d.ts +42 -5
- package/dist/ranger/kv.d.ts.map +1 -1
- package/dist/ranger/payload.d.ts +5 -2
- package/dist/ranger/payload.d.ts.map +1 -1
- package/dist/ranger/writer.d.ts +107 -2
- package/dist/ranger/writer.d.ts.map +1 -1
- package/dist/setupspecs.d.ts +0 -1
- package/dist/signals/observable.d.ts +0 -1
- package/dist/transport.d.ts +0 -1
- package/dist/user/client.d.ts +0 -1
- package/dist/user/payload.d.ts +0 -1
- package/dist/util/retrieve.d.ts +0 -1
- package/dist/util/telem.d.ts +0 -1
- package/dist/util/zod.d.ts +0 -1
- package/dist/workspace/client.d.ts +0 -1
- package/dist/workspace/lineplot/client.d.ts +0 -1
- package/dist/workspace/lineplot/payload.d.ts +0 -1
- package/dist/workspace/lineplot/retriever.d.ts +0 -1
- package/dist/workspace/lineplot/writer.d.ts +0 -1
- package/dist/workspace/payload.d.ts +0 -1
- package/dist/workspace/retriever.d.ts +0 -1
- package/dist/workspace/schematic/client.d.ts +0 -1
- package/dist/workspace/schematic/payload.d.ts +0 -1
- package/dist/workspace/schematic/retriever.d.ts +0 -1
- package/dist/workspace/schematic/writer.d.ts +0 -1
- package/dist/workspace/writer.d.ts +0 -1
- package/package.json +14 -12
- package/src/access/access.spec.ts +11 -11
- package/src/channel/batchRetriever.spec.ts +2 -0
- package/src/channel/channel.spec.ts +51 -31
- package/src/channel/client.ts +7 -0
- package/src/channel/payload.ts +1 -0
- package/src/client.ts +17 -8
- package/src/connection/checker.ts +58 -1
- package/src/connection/connection.spec.ts +43 -3
- package/src/control/client.ts +9 -0
- package/src/errors.spec.ts +9 -0
- package/src/framer/client.ts +0 -1
- package/src/framer/frame.spec.ts +2 -2
- package/src/framer/frame.ts +22 -22
- package/src/framer/writer.ts +2 -1
- package/src/hardware/device/payload.ts +9 -0
- package/src/hardware/rack/payload.ts +9 -0
- package/src/hardware/task/client.ts +82 -6
- package/src/hardware/task/ni/types.ts +1716 -0
- package/src/hardware/task/payload.ts +10 -0
- package/src/hardware/task/task.spec.ts +45 -30
- package/src/label/client.ts +49 -19
- package/src/label/label.spec.ts +9 -0
- package/src/label/retriever.ts +2 -1
- package/src/label/writer.ts +11 -3
- package/src/ontology/client.ts +227 -14
- package/src/ontology/group/writer.ts +10 -12
- package/src/ontology/ontology.spec.ts +3 -5
- package/src/ontology/payload.ts +5 -1
- package/src/ontology/writer.ts +26 -12
- package/src/ranger/client.ts +223 -41
- package/src/ranger/external.ts +1 -1
- package/src/ranger/kv.ts +50 -11
- package/src/ranger/payload.ts +9 -5
- package/src/ranger/ranger.spec.ts +114 -49
- package/src/ranger/writer.ts +7 -2
- package/src/vite-env.d.ts +1 -1
- package/vite.config.ts +6 -1
- package/dist/ranger/active.d.ts +0 -11
- package/dist/ranger/active.d.ts.map +0 -1
- package/dist/ranger/range.d.ts +0 -32
- package/dist/ranger/range.d.ts.map +0 -1
- package/src/ranger/active.ts +0 -74
- package/src/ranger/range.ts +0 -98
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
// Copyright 2024 Synnax Labs, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Use of this software is governed by the Business Source License included in the file
|
|
4
|
+
// licenses/BSL.txt.
|
|
5
|
+
//
|
|
6
|
+
// As of the Change Date specified in that file, in accordance with the Business Source
|
|
7
|
+
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
|
+
// included in the file licenses/APL.txt.
|
|
9
|
+
|
|
1
10
|
import { binary, observe, UnknownRecord } from "@synnaxlabs/x";
|
|
2
11
|
import { z } from "zod";
|
|
3
12
|
|
|
@@ -46,6 +55,7 @@ export const taskZ = z.object({
|
|
|
46
55
|
}),
|
|
47
56
|
) as z.ZodType<UnknownRecord>,
|
|
48
57
|
state: stateZ.optional().nullable(),
|
|
58
|
+
snapshot: z.boolean().optional(),
|
|
49
59
|
});
|
|
50
60
|
|
|
51
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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
|
|
91
|
+
const m = await r.createTask({
|
|
65
92
|
name: "test",
|
|
66
93
|
config: { a: "dog" },
|
|
67
94
|
type: "ni",
|
|
68
95
|
});
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
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" });
|
package/src/label/client.ts
CHANGED
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
10
|
import { type UnaryClient } from "@synnaxlabs/freighter";
|
|
11
|
+
import { observe } from "@synnaxlabs/x";
|
|
11
12
|
import { type AsyncTermSearcher } from "@synnaxlabs/x/search";
|
|
12
13
|
|
|
13
14
|
import { type framer } from "@/framer";
|
|
14
15
|
import { type Key, type Label, labelZ } from "@/label/payload";
|
|
15
16
|
import { Retriever } from "@/label/retriever";
|
|
16
|
-
import { type NewLabelPayload,Writer } from "@/label/writer";
|
|
17
|
-
import {
|
|
17
|
+
import { type NewLabelPayload, SetOptions, Writer } from "@/label/writer";
|
|
18
|
+
import { ontology } from "@/ontology";
|
|
18
19
|
import { signals } from "@/signals";
|
|
19
20
|
|
|
20
21
|
const LABEL_SET_NAME = "sy_label_set";
|
|
@@ -25,11 +26,17 @@ export class Client implements AsyncTermSearcher<string, Key, Label> {
|
|
|
25
26
|
private readonly retriever: Retriever;
|
|
26
27
|
private readonly writer: Writer;
|
|
27
28
|
private readonly frameClient: framer.Client;
|
|
29
|
+
private readonly ontology: ontology.Client;
|
|
28
30
|
|
|
29
|
-
constructor(
|
|
31
|
+
constructor(
|
|
32
|
+
client: UnaryClient,
|
|
33
|
+
frameClient: framer.Client,
|
|
34
|
+
ontology: ontology.Client,
|
|
35
|
+
) {
|
|
30
36
|
this.writer = new Writer(client);
|
|
31
37
|
this.retriever = new Retriever(client);
|
|
32
38
|
this.frameClient = frameClient;
|
|
39
|
+
this.ontology = ontology;
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
async search(term: string): Promise<Label[]> {
|
|
@@ -46,16 +53,20 @@ export class Client implements AsyncTermSearcher<string, Key, Label> {
|
|
|
46
53
|
return isMany ? res : res[0];
|
|
47
54
|
}
|
|
48
55
|
|
|
49
|
-
async retrieveFor(id: ontology.
|
|
50
|
-
return await this.retriever.retrieveFor(id);
|
|
56
|
+
async retrieveFor(id: ontology.CrudeID): Promise<Label[]> {
|
|
57
|
+
return await this.retriever.retrieveFor(new ontology.ID(id));
|
|
51
58
|
}
|
|
52
59
|
|
|
53
|
-
async label(
|
|
54
|
-
|
|
60
|
+
async label(
|
|
61
|
+
id: ontology.CrudeID,
|
|
62
|
+
labels: Key[],
|
|
63
|
+
opts: SetOptions = {},
|
|
64
|
+
): Promise<void> {
|
|
65
|
+
await this.writer.set(new ontology.ID(id), labels, opts);
|
|
55
66
|
}
|
|
56
67
|
|
|
57
|
-
async removeLabels(id: ontology.
|
|
58
|
-
await this.writer.remove(id, labels);
|
|
68
|
+
async removeLabels(id: ontology.CrudeID, labels: Key[]): Promise<void> {
|
|
69
|
+
await this.writer.remove(new ontology.ID(id), labels);
|
|
59
70
|
}
|
|
60
71
|
|
|
61
72
|
async page(offset: number, limit: number): Promise<Label[]> {
|
|
@@ -88,17 +99,36 @@ export class Client implements AsyncTermSearcher<string, Key, Label> {
|
|
|
88
99
|
decodeChanges,
|
|
89
100
|
);
|
|
90
101
|
}
|
|
102
|
+
|
|
103
|
+
async trackLabelsOf(
|
|
104
|
+
id: ontology.CrudeID,
|
|
105
|
+
): Promise<observe.ObservableAsyncCloseable<Label[]>> {
|
|
106
|
+
const wrapper = new observe.Observer<Label[]>();
|
|
107
|
+
const initial = (await this.retrieveFor(id)).map((l) => ({
|
|
108
|
+
id: new ontology.ID({ key: l.key, type: "label" }),
|
|
109
|
+
key: l.key,
|
|
110
|
+
name: l.name,
|
|
111
|
+
data: l,
|
|
112
|
+
}));
|
|
113
|
+
const base = await this.ontology.openDependentTracker({
|
|
114
|
+
target: new ontology.ID(id),
|
|
115
|
+
dependents: initial,
|
|
116
|
+
relationshipType: "labeled_by",
|
|
117
|
+
});
|
|
118
|
+
base.onChange((resources: ontology.Resource[]) =>
|
|
119
|
+
wrapper.notify(
|
|
120
|
+
resources.map((r) => ({
|
|
121
|
+
key: r.id.key,
|
|
122
|
+
color: r.data?.color as string,
|
|
123
|
+
name: r.data?.name as string,
|
|
124
|
+
})),
|
|
125
|
+
),
|
|
126
|
+
);
|
|
127
|
+
return wrapper;
|
|
128
|
+
}
|
|
91
129
|
}
|
|
92
130
|
|
|
93
131
|
const decodeChanges: signals.Decoder<string, Label> = (variant, data) => {
|
|
94
|
-
if (variant === "delete")
|
|
95
|
-
|
|
96
|
-
variant,
|
|
97
|
-
key: v,
|
|
98
|
-
}));
|
|
99
|
-
return data.parseJSON(labelZ).map((l) => ({
|
|
100
|
-
variant,
|
|
101
|
-
key: l.key,
|
|
102
|
-
value: l,
|
|
103
|
-
}));
|
|
132
|
+
if (variant === "delete") return data.toUUIDs().map((v) => ({ variant, key: v }));
|
|
133
|
+
return data.parseJSON(labelZ).map((l) => ({ variant, key: l.key, value: l }));
|
|
104
134
|
};
|
package/src/label/label.spec.ts
CHANGED
|
@@ -47,5 +47,14 @@ describe("Label", () => {
|
|
|
47
47
|
expect(labels).toHaveLength(1);
|
|
48
48
|
expect(labels[0].key).toEqual(l2.key);
|
|
49
49
|
});
|
|
50
|
+
it("should replace the labels on an item", async () => {
|
|
51
|
+
const l1 = await client.labels.create({ name: "Label One", color: "#E774D)" });
|
|
52
|
+
const l2 = await client.labels.create({ name: "Label Two", color: "#E774D)" });
|
|
53
|
+
await client.labels.label(label.ontologyID(l1.key), [l2.key]);
|
|
54
|
+
await client.labels.label(label.ontologyID(l1.key), [l1.key], { replace: true });
|
|
55
|
+
const labels = await client.labels.retrieveFor(label.ontologyID(l1.key));
|
|
56
|
+
expect(labels).toHaveLength(1);
|
|
57
|
+
expect(labels[0].key).toEqual(l1.key);
|
|
58
|
+
});
|
|
50
59
|
});
|
|
51
60
|
});
|
package/src/label/retriever.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { z } from "zod";
|
|
|
13
13
|
|
|
14
14
|
import { keyZ, type Label, labelZ, type Params } from "@/label/payload";
|
|
15
15
|
import { ontology } from "@/ontology";
|
|
16
|
+
import { nullableArrayZ } from "@/util/zod";
|
|
16
17
|
|
|
17
18
|
const reqZ = z.object({
|
|
18
19
|
keys: keyZ.array().optional(),
|
|
@@ -25,7 +26,7 @@ const reqZ = z.object({
|
|
|
25
26
|
type Request = z.infer<typeof reqZ>;
|
|
26
27
|
|
|
27
28
|
const resZ = z.object({
|
|
28
|
-
labels: labelZ
|
|
29
|
+
labels: nullableArrayZ(labelZ),
|
|
29
30
|
});
|
|
30
31
|
|
|
31
32
|
export class Retriever {
|
package/src/label/writer.ts
CHANGED
|
@@ -33,9 +33,13 @@ const deleteReqZ = z.object({
|
|
|
33
33
|
const setReqZ = z.object({
|
|
34
34
|
id: ontology.idZ,
|
|
35
35
|
labels: keyZ.array(),
|
|
36
|
+
replace: z.boolean().optional(),
|
|
36
37
|
});
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
type SetReq = z.infer<typeof setReqZ>;
|
|
40
|
+
export type SetOptions = Pick<SetReq, "replace">;
|
|
41
|
+
|
|
42
|
+
const removeReqZ = setReqZ.omit({ replace: true });
|
|
39
43
|
|
|
40
44
|
const emptyResZ = z.object({});
|
|
41
45
|
|
|
@@ -72,11 +76,15 @@ export class Writer {
|
|
|
72
76
|
);
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
async set(
|
|
79
|
+
async set(
|
|
80
|
+
id: ontology.ID,
|
|
81
|
+
labels: Key[],
|
|
82
|
+
{ replace }: SetOptions = {},
|
|
83
|
+
): Promise<void> {
|
|
76
84
|
await sendRequired<typeof setReqZ, typeof emptyResZ>(
|
|
77
85
|
this.client,
|
|
78
86
|
SET_ENDPOINT,
|
|
79
|
-
{ id, labels },
|
|
87
|
+
{ id, labels, replace },
|
|
80
88
|
setReqZ,
|
|
81
89
|
emptyResZ,
|
|
82
90
|
);
|
package/src/ontology/client.ts
CHANGED
|
@@ -17,14 +17,17 @@ import { framer } from "@/framer";
|
|
|
17
17
|
import { Frame } from "@/framer/frame";
|
|
18
18
|
import { group } from "@/ontology/group";
|
|
19
19
|
import {
|
|
20
|
+
CrudeID,
|
|
20
21
|
ID,
|
|
21
22
|
IDPayload,
|
|
22
23
|
idZ,
|
|
23
24
|
parseRelationship,
|
|
24
25
|
RelationshipChange,
|
|
26
|
+
RelationshipDirection,
|
|
25
27
|
type Resource,
|
|
26
28
|
ResourceChange,
|
|
27
29
|
resourceSchemaZ,
|
|
30
|
+
resourceTypeZ,
|
|
28
31
|
} from "@/ontology/payload";
|
|
29
32
|
import { Writer } from "@/ontology/writer";
|
|
30
33
|
|
|
@@ -39,20 +42,21 @@ const retrieveReqZ = z.object({
|
|
|
39
42
|
term: z.string().optional(),
|
|
40
43
|
limit: z.number().optional(),
|
|
41
44
|
offset: z.number().optional(),
|
|
45
|
+
types: resourceTypeZ.array().optional(),
|
|
42
46
|
});
|
|
43
47
|
|
|
44
48
|
type RetrieveRequest = z.infer<typeof retrieveReqZ>;
|
|
45
49
|
|
|
46
50
|
export type RetrieveOptions = Pick<
|
|
47
51
|
RetrieveRequest,
|
|
48
|
-
"includeSchema" | "excludeFieldData"
|
|
52
|
+
"includeSchema" | "excludeFieldData" | "types"
|
|
49
53
|
>;
|
|
50
54
|
|
|
51
55
|
const retrieveResZ = z.object({
|
|
52
56
|
resources: resourceSchemaZ.array(),
|
|
53
57
|
});
|
|
54
58
|
|
|
55
|
-
const parseIDs = (ids:
|
|
59
|
+
export const parseIDs = (ids: CrudeID | CrudeID[] | string | string[]): IDPayload[] =>
|
|
56
60
|
toArray(ids).map((id) => new ID(id).payload);
|
|
57
61
|
|
|
58
62
|
/** The core client class for executing queries against a Synnax cluster ontology */
|
|
@@ -70,16 +74,50 @@ export class Client implements AsyncTermSearcher<string, string, Resource> {
|
|
|
70
74
|
this.framer = framer;
|
|
71
75
|
}
|
|
72
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Executes a fuzzy search on the ontology for resources with names/fields similar to the
|
|
79
|
+
* given term.
|
|
80
|
+
*
|
|
81
|
+
* @param term The search term.
|
|
82
|
+
* @param options Additional options for the search.
|
|
83
|
+
* @param options.includeSchema Whether to include the schema of the resources in the
|
|
84
|
+
* results.
|
|
85
|
+
* @param options.excludeFieldData Whether to exclude the field data of the resources in
|
|
86
|
+
* the results.
|
|
87
|
+
* @returns A list of resources that match the search term.
|
|
88
|
+
*/
|
|
73
89
|
async search(term: string, options?: RetrieveOptions): Promise<Resource[]> {
|
|
74
90
|
return await this.execRetrieve({ term, ...options });
|
|
75
91
|
}
|
|
76
92
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Retrieves the resource in the ontology with the given ID.
|
|
95
|
+
* @param id The ID of the resource to retrieve.
|
|
96
|
+
* @param options Additional options for the retrieval.
|
|
97
|
+
* @param options.includeSchema Whether to include the schema of the resource in the
|
|
98
|
+
* results.
|
|
99
|
+
* @param options.excludeFieldData Whether to exclude the field data of the resource in
|
|
100
|
+
* the results.
|
|
101
|
+
* @returns The resource with the given ID.
|
|
102
|
+
* @throws {QueryError} If no resource is found with the given ID.
|
|
103
|
+
*/
|
|
104
|
+
async retrieve(id: CrudeID, options?: RetrieveOptions): Promise<Resource>;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Retrieves the resources in the ontology with the given IDs.
|
|
108
|
+
* @param ids The IDs of the resources to retrieve.
|
|
109
|
+
* @param options Additional options for the retrieval.
|
|
110
|
+
* @param options.includeSchema Whether to include the schema of the resources in the
|
|
111
|
+
* results.
|
|
112
|
+
* @param options.excludeFieldData Whether to exclude the field data of the resources in
|
|
113
|
+
* the results.
|
|
114
|
+
* @returns The resources with the given IDs.
|
|
115
|
+
* @throws {QueryError} If no resource is found with any of the given IDs.
|
|
116
|
+
*/
|
|
117
|
+
async retrieve(ids: CrudeID[], options?: RetrieveOptions): Promise<Resource[]>;
|
|
80
118
|
|
|
81
119
|
async retrieve(
|
|
82
|
-
ids:
|
|
120
|
+
ids: CrudeID | CrudeID[],
|
|
83
121
|
options?: RetrieveOptions,
|
|
84
122
|
): Promise<Resource | Resource[]> {
|
|
85
123
|
const resources = await this.execRetrieve({ ids: parseIDs(ids), ...options });
|
|
@@ -89,6 +127,15 @@ export class Client implements AsyncTermSearcher<string, string, Resource> {
|
|
|
89
127
|
return resources[0];
|
|
90
128
|
}
|
|
91
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Retrieves resources from the ontology in a paginated manner.
|
|
132
|
+
*
|
|
133
|
+
* @param offset - The offset of the page (i.e. how many resources to skip before
|
|
134
|
+
* returning results).
|
|
135
|
+
* @param limit - The maximum number of resources to return.
|
|
136
|
+
* @param options - Additional options for the retrieval.
|
|
137
|
+
* @returns A list of resources in the ontology.
|
|
138
|
+
*/
|
|
92
139
|
async page(
|
|
93
140
|
offset: number,
|
|
94
141
|
limit: number,
|
|
@@ -97,36 +144,88 @@ export class Client implements AsyncTermSearcher<string, string, Resource> {
|
|
|
97
144
|
return await this.execRetrieve({ offset, limit, ...options });
|
|
98
145
|
}
|
|
99
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Retrieves the children of the resources with the given IDs.
|
|
149
|
+
* @param ids - The IDs of the resources whose children to retrieve.
|
|
150
|
+
* @param options - Additional options for the retrieval.
|
|
151
|
+
* @param options.includeSchema - Whether to include the schema of the children in the
|
|
152
|
+
* results.
|
|
153
|
+
* @param options.excludeFieldData - Whether to exclude the field data of the children in
|
|
154
|
+
* the results.
|
|
155
|
+
* @returns The children of the resources with the given IDs.
|
|
156
|
+
*/
|
|
100
157
|
async retrieveChildren(
|
|
101
|
-
ids:
|
|
158
|
+
ids: CrudeID | CrudeID[],
|
|
102
159
|
options?: RetrieveOptions,
|
|
103
160
|
): Promise<Resource[]> {
|
|
104
161
|
return await this.execRetrieve({ ids: parseIDs(ids), children: true, ...options });
|
|
105
162
|
}
|
|
106
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Retrieves the parents of the resources with the given IDs.
|
|
166
|
+
*
|
|
167
|
+
* @param ids the IDs of the resources whose parents to retrieve
|
|
168
|
+
* @param options additional options for the retrieval
|
|
169
|
+
* @param options.includeSchema whether to include the schema of the parents in the results
|
|
170
|
+
* @param options.excludeFieldData whether to exclude the field data of the parents in the results
|
|
171
|
+
* @returns the parents of the resources with the given IDs
|
|
172
|
+
*/
|
|
107
173
|
async retrieveParents(
|
|
108
|
-
ids:
|
|
174
|
+
ids: CrudeID | CrudeID[],
|
|
109
175
|
options?: RetrieveOptions,
|
|
110
176
|
): Promise<Resource[]> {
|
|
111
177
|
return await this.execRetrieve({ ids: parseIDs(ids), parents: true, ...options });
|
|
112
178
|
}
|
|
113
179
|
|
|
114
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Adds children to a resource in the ontology.
|
|
182
|
+
* @param id The ID of the resource to add children to.
|
|
183
|
+
* @param children The IDs of the children to add.
|
|
184
|
+
*/
|
|
185
|
+
async addChildren(id: CrudeID, ...children: CrudeID[]): Promise<void> {
|
|
115
186
|
return await this.writer.addChildren(id, ...children);
|
|
116
187
|
}
|
|
117
188
|
|
|
118
|
-
|
|
189
|
+
/**
|
|
190
|
+
* Removes children from a resource in the ontology.
|
|
191
|
+
* @param id The ID of the resource to remove children from.
|
|
192
|
+
* @param children The IDs of the children
|
|
193
|
+
* to remove.
|
|
194
|
+
*/
|
|
195
|
+
async removeChildren(id: CrudeID, ...children: CrudeID[]): Promise<void> {
|
|
119
196
|
return await this.writer.removeChildren(id, ...children);
|
|
120
197
|
}
|
|
121
198
|
|
|
122
|
-
|
|
199
|
+
/**
|
|
200
|
+
* Moves children from one resource to another in the ontology.
|
|
201
|
+
* @param from The ID of the resource to move children from.
|
|
202
|
+
* @param to The ID of the resource to move children to.
|
|
203
|
+
* @param children The IDs of the children to move.
|
|
204
|
+
*/
|
|
205
|
+
async moveChildren(
|
|
206
|
+
from: CrudeID,
|
|
207
|
+
to: CrudeID,
|
|
208
|
+
...children: CrudeID[]
|
|
209
|
+
): Promise<void> {
|
|
123
210
|
return await this.writer.moveChildren(from, to, ...children);
|
|
124
211
|
}
|
|
125
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Opens an observable that can be used to subscribe to changes in both the ontology's
|
|
215
|
+
* resources and relationships.
|
|
216
|
+
* @see ChangeTracker for more information.
|
|
217
|
+
* @returns An observable that emits changes to the ontology's resources and relationships.
|
|
218
|
+
*/
|
|
126
219
|
async openChangeTracker(): Promise<ChangeTracker> {
|
|
127
220
|
return await ChangeTracker.open(this.framer, this);
|
|
128
221
|
}
|
|
129
222
|
|
|
223
|
+
async openDependentTracker(
|
|
224
|
+
props: DependentTrackerProps,
|
|
225
|
+
): Promise<observe.ObservableAsyncCloseable<Resource[]>> {
|
|
226
|
+
return await DependentTracker.open(props, this.framer, this);
|
|
227
|
+
}
|
|
228
|
+
|
|
130
229
|
newSearcherWithOptions(
|
|
131
230
|
options: RetrieveOptions,
|
|
132
231
|
): AsyncTermSearcher<string, string, Resource> {
|
|
@@ -155,13 +254,21 @@ const RESOURCE_DELETE_NAME = "sy_ontology_resource_delete";
|
|
|
155
254
|
const RELATIONSHIP_SET_NAME = "sy_ontology_relationship_set";
|
|
156
255
|
const RELATIONSHIP_DELETE_NAME = "sy_ontology_relationship_delete";
|
|
157
256
|
|
|
257
|
+
/**
|
|
258
|
+
* A class that tracks changes to the ontology's resources and relationships.
|
|
259
|
+
*/
|
|
158
260
|
export class ChangeTracker {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
261
|
+
/**
|
|
262
|
+
* An observable that emits changes to the ontology's relationships.
|
|
263
|
+
*/
|
|
162
264
|
readonly relationships: observe.Observable<RelationshipChange[]>;
|
|
265
|
+
/**
|
|
266
|
+
* An observable that emits changes to the ontology's resources.
|
|
267
|
+
*/
|
|
163
268
|
readonly resources: observe.Observable<ResourceChange[]>;
|
|
164
269
|
|
|
270
|
+
private readonly resourceObs: observe.Observer<ResourceChange[]>;
|
|
271
|
+
private readonly relationshipObs: observe.Observer<RelationshipChange[]>;
|
|
165
272
|
private readonly streamer: framer.Streamer;
|
|
166
273
|
private readonly client: Client;
|
|
167
274
|
private readonly closePromise: Promise<void>;
|
|
@@ -256,3 +363,109 @@ export class ChangeTracker {
|
|
|
256
363
|
return new ChangeTracker(streamer, retriever);
|
|
257
364
|
}
|
|
258
365
|
}
|
|
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
|
+
|
|
378
|
+
/**
|
|
379
|
+
* A class that tracks a resource (called the 'target' resource) and related resources
|
|
380
|
+
* (called 'dependents') of a particular type (called the 'type') in a Synnax cluster
|
|
381
|
+
* ontology.
|
|
382
|
+
*/
|
|
383
|
+
export class DependentTracker
|
|
384
|
+
extends observe.Observer<Resource[]>
|
|
385
|
+
implements observe.ObservableAsyncCloseable<Resource[]>
|
|
386
|
+
{
|
|
387
|
+
private readonly internal: ChangeTracker;
|
|
388
|
+
private readonly target: ID;
|
|
389
|
+
private readonly relDir: RelationshipDirection;
|
|
390
|
+
private readonly resourceType?: string;
|
|
391
|
+
private dependents: Resource[];
|
|
392
|
+
private readonly client: Client;
|
|
393
|
+
private readonly relType: string;
|
|
394
|
+
|
|
395
|
+
private constructor(
|
|
396
|
+
{
|
|
397
|
+
target,
|
|
398
|
+
dependents,
|
|
399
|
+
relationshipType = "parent",
|
|
400
|
+
relationshipDirection = "from",
|
|
401
|
+
resourceType,
|
|
402
|
+
}: DependentTrackerProps,
|
|
403
|
+
internal: ChangeTracker,
|
|
404
|
+
client: Client,
|
|
405
|
+
) {
|
|
406
|
+
super();
|
|
407
|
+
this.resourceType = resourceType;
|
|
408
|
+
this.internal = internal;
|
|
409
|
+
this.target = target;
|
|
410
|
+
this.dependents = dependents;
|
|
411
|
+
if (this.resourceType != null)
|
|
412
|
+
this.dependents = this.dependents.filter((r) => r.id.type === this.resourceType);
|
|
413
|
+
this.client = client;
|
|
414
|
+
this.relType = relationshipType;
|
|
415
|
+
this.relDir = relationshipDirection;
|
|
416
|
+
this.internal.resources.onChange(this.handleResourceChange);
|
|
417
|
+
this.internal.relationships.onChange(this.handleRelationshipChange);
|
|
418
|
+
}
|
|
419
|
+
static async open(
|
|
420
|
+
props: DependentTrackerProps,
|
|
421
|
+
framer: framer.Client,
|
|
422
|
+
client: Client,
|
|
423
|
+
): Promise<DependentTracker> {
|
|
424
|
+
const internal = await ChangeTracker.open(framer, client);
|
|
425
|
+
return new DependentTracker(props, internal, client);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private handleResourceChange = (changes: ResourceChange[]): void => {
|
|
429
|
+
this.dependents = this.dependents.map((child) => {
|
|
430
|
+
const change = changes.find((c) => c.key.toString() == child.id.toString());
|
|
431
|
+
if (change == null || change.variant === "delete") return child;
|
|
432
|
+
return change.value;
|
|
433
|
+
});
|
|
434
|
+
this.notify(this.dependents);
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
private handleRelationshipChange = (changes: RelationshipChange[]): void => {
|
|
438
|
+
const deletes = changes.filter(
|
|
439
|
+
(c) =>
|
|
440
|
+
c.variant === "delete" &&
|
|
441
|
+
c.key[this.relDir].toString() === this.target.toString() &&
|
|
442
|
+
(this.resourceType == null ||
|
|
443
|
+
c.key[oppositeDirection(this.relDir)].type === this.resourceType),
|
|
444
|
+
);
|
|
445
|
+
this.dependents = this.dependents.filter(
|
|
446
|
+
(child) =>
|
|
447
|
+
!deletes.some(
|
|
448
|
+
(del) =>
|
|
449
|
+
del.key.to.toString() === child.id.toString() &&
|
|
450
|
+
del.key.type === this.relType,
|
|
451
|
+
),
|
|
452
|
+
);
|
|
453
|
+
const sets = changes.filter(
|
|
454
|
+
(c) =>
|
|
455
|
+
c.variant === "set" &&
|
|
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),
|
|
460
|
+
);
|
|
461
|
+
if (sets.length === 0) return this.notify(this.dependents);
|
|
462
|
+
this.client.retrieve(sets.map((s) => s.key.to)).then((resources) => {
|
|
463
|
+
this.dependents = this.dependents.concat(resources);
|
|
464
|
+
this.notify(this.dependents);
|
|
465
|
+
});
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
async close(): Promise<void> {
|
|
469
|
+
await this.internal.close();
|
|
470
|
+
}
|
|
471
|
+
}
|
|
@@ -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
|
|
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
|
-
|
|
58
|
-
|
|
57
|
+
await sendRequired(
|
|
58
|
+
this.client,
|
|
59
59
|
Writer.ENDPOINT_RENAME,
|
|
60
|
-
|
|
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
|
-
|
|
69
|
-
|
|
67
|
+
await sendRequired(
|
|
68
|
+
this.client,
|
|
70
69
|
Writer.ENDPOINT_DELETE,
|
|
71
|
-
|
|
70
|
+
{ keys },
|
|
72
71
|
deleteReqZ,
|
|
73
72
|
z.object({}),
|
|
74
73
|
);
|
|
75
|
-
if (err != null) throw err;
|
|
76
74
|
}
|
|
77
75
|
}
|