@synnaxlabs/client 0.26.1 → 0.27.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 +3077 -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 +17 -17
- package/dist/client.d.ts +6 -7
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2006 -1745
- package/dist/connection/checker.d.ts +0 -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/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 +0 -1
- package/dist/hardware/task/payload.d.ts +0 -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 +124 -11
- 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/payload.d.ts +5 -1
- 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 +55 -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 +4 -3
- package/src/control/client.ts +9 -0
- package/src/errors.spec.ts +9 -0
- 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/payload.ts +9 -0
- 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 +214 -13
- package/src/ontology/payload.ts +4 -0
- package/src/ontology/writer.ts +26 -12
- package/src/ranger/client.ts +204 -28
- 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/vite.config.ts +1 -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
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
|
+
new ontology.ID(id),
|
|
115
|
+
initial,
|
|
116
|
+
"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,11 +17,13 @@ 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,
|
|
@@ -52,7 +54,7 @@ const retrieveResZ = z.object({
|
|
|
52
54
|
resources: resourceSchemaZ.array(),
|
|
53
55
|
});
|
|
54
56
|
|
|
55
|
-
const parseIDs = (ids:
|
|
57
|
+
export const parseIDs = (ids: CrudeID | CrudeID[] | string | string[]): IDPayload[] =>
|
|
56
58
|
toArray(ids).map((id) => new ID(id).payload);
|
|
57
59
|
|
|
58
60
|
/** The core client class for executing queries against a Synnax cluster ontology */
|
|
@@ -70,16 +72,50 @@ export class Client implements AsyncTermSearcher<string, string, Resource> {
|
|
|
70
72
|
this.framer = framer;
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Executes a fuzzy search on the ontology for resources with names/fields similar to the
|
|
77
|
+
* given term.
|
|
78
|
+
*
|
|
79
|
+
* @param term The search term.
|
|
80
|
+
* @param options Additional options for the search.
|
|
81
|
+
* @param options.includeSchema Whether to include the schema of the resources in the
|
|
82
|
+
* results.
|
|
83
|
+
* @param options.excludeFieldData Whether to exclude the field data of the resources in
|
|
84
|
+
* the results.
|
|
85
|
+
* @returns A list of resources that match the search term.
|
|
86
|
+
*/
|
|
73
87
|
async search(term: string, options?: RetrieveOptions): Promise<Resource[]> {
|
|
74
88
|
return await this.execRetrieve({ term, ...options });
|
|
75
89
|
}
|
|
76
90
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Retrieves the resource in the ontology with the given ID.
|
|
93
|
+
* @param id The ID of the resource to retrieve.
|
|
94
|
+
* @param options Additional options for the retrieval.
|
|
95
|
+
* @param options.includeSchema Whether to include the schema of the resource in the
|
|
96
|
+
* results.
|
|
97
|
+
* @param options.excludeFieldData Whether to exclude the field data of the resource in
|
|
98
|
+
* the results.
|
|
99
|
+
* @returns The resource with the given ID.
|
|
100
|
+
* @throws {QueryError} If no resource is found with the given ID.
|
|
101
|
+
*/
|
|
102
|
+
async retrieve(id: CrudeID, options?: RetrieveOptions): Promise<Resource>;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Retrieves the resources in the ontology with the given IDs.
|
|
106
|
+
* @param ids The IDs of the resources to retrieve.
|
|
107
|
+
* @param options Additional options for the retrieval.
|
|
108
|
+
* @param options.includeSchema Whether to include the schema of the resources in the
|
|
109
|
+
* results.
|
|
110
|
+
* @param options.excludeFieldData Whether to exclude the field data of the resources in
|
|
111
|
+
* the results.
|
|
112
|
+
* @returns The resources with the given IDs.
|
|
113
|
+
* @throws {QueryError} If no resource is found with any of the given IDs.
|
|
114
|
+
*/
|
|
115
|
+
async retrieve(ids: CrudeID[], options?: RetrieveOptions): Promise<Resource[]>;
|
|
80
116
|
|
|
81
117
|
async retrieve(
|
|
82
|
-
ids:
|
|
118
|
+
ids: CrudeID | CrudeID[],
|
|
83
119
|
options?: RetrieveOptions,
|
|
84
120
|
): Promise<Resource | Resource[]> {
|
|
85
121
|
const resources = await this.execRetrieve({ ids: parseIDs(ids), ...options });
|
|
@@ -89,6 +125,15 @@ export class Client implements AsyncTermSearcher<string, string, Resource> {
|
|
|
89
125
|
return resources[0];
|
|
90
126
|
}
|
|
91
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Retrieves resources from the ontology in a paginated manner.
|
|
130
|
+
*
|
|
131
|
+
* @param offset - The offset of the page (i.e. how many resources to skip before
|
|
132
|
+
* returning results).
|
|
133
|
+
* @param limit - The maximum number of resources to return.
|
|
134
|
+
* @param options - Additional options for the retrieval.
|
|
135
|
+
* @returns A list of resources in the ontology.
|
|
136
|
+
*/
|
|
92
137
|
async page(
|
|
93
138
|
offset: number,
|
|
94
139
|
limit: number,
|
|
@@ -97,36 +142,98 @@ export class Client implements AsyncTermSearcher<string, string, Resource> {
|
|
|
97
142
|
return await this.execRetrieve({ offset, limit, ...options });
|
|
98
143
|
}
|
|
99
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Retrieves the children of the resources with the given IDs.
|
|
147
|
+
* @param ids - The IDs of the resources whose children to retrieve.
|
|
148
|
+
* @param options - Additional options for the retrieval.
|
|
149
|
+
* @param options.includeSchema - Whether to include the schema of the children in the
|
|
150
|
+
* results.
|
|
151
|
+
* @param options.excludeFieldData - Whether to exclude the field data of the children in
|
|
152
|
+
* the results.
|
|
153
|
+
* @returns The children of the resources with the given IDs.
|
|
154
|
+
*/
|
|
100
155
|
async retrieveChildren(
|
|
101
|
-
ids:
|
|
156
|
+
ids: CrudeID | CrudeID[],
|
|
102
157
|
options?: RetrieveOptions,
|
|
103
158
|
): Promise<Resource[]> {
|
|
104
159
|
return await this.execRetrieve({ ids: parseIDs(ids), children: true, ...options });
|
|
105
160
|
}
|
|
106
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Retrieves the parents of the resources with the given IDs.
|
|
164
|
+
*
|
|
165
|
+
* @param ids the IDs of the resources whose parents to retrieve
|
|
166
|
+
* @param options additional options for the retrieval
|
|
167
|
+
* @param options.includeSchema whether to include the schema of the parents in the results
|
|
168
|
+
* @param options.excludeFieldData whether to exclude the field data of the parents in the results
|
|
169
|
+
* @returns the parents of the resources with the given IDs
|
|
170
|
+
*/
|
|
107
171
|
async retrieveParents(
|
|
108
|
-
ids:
|
|
172
|
+
ids: CrudeID | CrudeID[],
|
|
109
173
|
options?: RetrieveOptions,
|
|
110
174
|
): Promise<Resource[]> {
|
|
111
175
|
return await this.execRetrieve({ ids: parseIDs(ids), parents: true, ...options });
|
|
112
176
|
}
|
|
113
177
|
|
|
114
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Adds children to a resource in the ontology.
|
|
180
|
+
* @param id The ID of the resource to add children to.
|
|
181
|
+
* @param children The IDs of the children to add.
|
|
182
|
+
*/
|
|
183
|
+
async addChildren(id: CrudeID, ...children: CrudeID[]): Promise<void> {
|
|
115
184
|
return await this.writer.addChildren(id, ...children);
|
|
116
185
|
}
|
|
117
186
|
|
|
118
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Removes children from a resource in the ontology.
|
|
189
|
+
* @param id The ID of the resource to remove children from.
|
|
190
|
+
* @param children The IDs of the children
|
|
191
|
+
* to remove.
|
|
192
|
+
*/
|
|
193
|
+
async removeChildren(id: CrudeID, ...children: CrudeID[]): Promise<void> {
|
|
119
194
|
return await this.writer.removeChildren(id, ...children);
|
|
120
195
|
}
|
|
121
196
|
|
|
122
|
-
|
|
197
|
+
/**
|
|
198
|
+
* Moves children from one resource to another in the ontology.
|
|
199
|
+
* @param from The ID of the resource to move children from.
|
|
200
|
+
* @param to The ID of the resource to move children to.
|
|
201
|
+
* @param children The IDs of the children to move.
|
|
202
|
+
*/
|
|
203
|
+
async moveChildren(
|
|
204
|
+
from: CrudeID,
|
|
205
|
+
to: CrudeID,
|
|
206
|
+
...children: CrudeID[]
|
|
207
|
+
): Promise<void> {
|
|
123
208
|
return await this.writer.moveChildren(from, to, ...children);
|
|
124
209
|
}
|
|
125
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Opens an observable that can be used to subscribe to changes in both the ontology's
|
|
213
|
+
* resources and relationships.
|
|
214
|
+
* @see ChangeTracker for more information.
|
|
215
|
+
* @returns An observable that emits changes to the ontology's resources and relationships.
|
|
216
|
+
*/
|
|
126
217
|
async openChangeTracker(): Promise<ChangeTracker> {
|
|
127
218
|
return await ChangeTracker.open(this.framer, this);
|
|
128
219
|
}
|
|
129
220
|
|
|
221
|
+
async openDependentTracker(
|
|
222
|
+
parent: ID,
|
|
223
|
+
initial: Resource[],
|
|
224
|
+
type: string = "parent",
|
|
225
|
+
direction: RelationshipDirection = "from",
|
|
226
|
+
): Promise<observe.ObservableAsyncCloseable<Resource[]>> {
|
|
227
|
+
return await DependentTracker.open(
|
|
228
|
+
parent,
|
|
229
|
+
this,
|
|
230
|
+
this.framer,
|
|
231
|
+
initial,
|
|
232
|
+
type,
|
|
233
|
+
direction,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
130
237
|
newSearcherWithOptions(
|
|
131
238
|
options: RetrieveOptions,
|
|
132
239
|
): AsyncTermSearcher<string, string, Resource> {
|
|
@@ -155,13 +262,21 @@ const RESOURCE_DELETE_NAME = "sy_ontology_resource_delete";
|
|
|
155
262
|
const RELATIONSHIP_SET_NAME = "sy_ontology_relationship_set";
|
|
156
263
|
const RELATIONSHIP_DELETE_NAME = "sy_ontology_relationship_delete";
|
|
157
264
|
|
|
265
|
+
/**
|
|
266
|
+
* A class that tracks changes to the ontology's resources and relationships.
|
|
267
|
+
*/
|
|
158
268
|
export class ChangeTracker {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
269
|
+
/**
|
|
270
|
+
* An observable that emits changes to the ontology's relationships.
|
|
271
|
+
*/
|
|
162
272
|
readonly relationships: observe.Observable<RelationshipChange[]>;
|
|
273
|
+
/**
|
|
274
|
+
* An observable that emits changes to the ontology's resources.
|
|
275
|
+
*/
|
|
163
276
|
readonly resources: observe.Observable<ResourceChange[]>;
|
|
164
277
|
|
|
278
|
+
private readonly resourceObs: observe.Observer<ResourceChange[]>;
|
|
279
|
+
private readonly relationshipObs: observe.Observer<RelationshipChange[]>;
|
|
165
280
|
private readonly streamer: framer.Streamer;
|
|
166
281
|
private readonly client: Client;
|
|
167
282
|
private readonly closePromise: Promise<void>;
|
|
@@ -256,3 +371,89 @@ export class ChangeTracker {
|
|
|
256
371
|
return new ChangeTracker(streamer, retriever);
|
|
257
372
|
}
|
|
258
373
|
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* A class that tracks a resource (called the 'target' resource) and related resources
|
|
377
|
+
* (called 'dependents') of a particular type (called the 'type') in a Synnax cluster
|
|
378
|
+
* ontology.
|
|
379
|
+
*/
|
|
380
|
+
export class DependentTracker
|
|
381
|
+
extends observe.Observer<Resource[]>
|
|
382
|
+
implements observe.ObservableAsyncCloseable<Resource[]>
|
|
383
|
+
{
|
|
384
|
+
private readonly internal: ChangeTracker;
|
|
385
|
+
private readonly target: ID;
|
|
386
|
+
private readonly direction: RelationshipDirection;
|
|
387
|
+
private dependents: Resource[];
|
|
388
|
+
private readonly client: Client;
|
|
389
|
+
private readonly type: string;
|
|
390
|
+
|
|
391
|
+
private constructor(
|
|
392
|
+
target: ID,
|
|
393
|
+
internal: ChangeTracker,
|
|
394
|
+
dependents: Resource[],
|
|
395
|
+
client: Client,
|
|
396
|
+
type: string = "parent",
|
|
397
|
+
direction: RelationshipDirection = "from",
|
|
398
|
+
) {
|
|
399
|
+
super();
|
|
400
|
+
this.internal = internal;
|
|
401
|
+
this.target = target;
|
|
402
|
+
this.dependents = dependents;
|
|
403
|
+
this.client = client;
|
|
404
|
+
this.type = type;
|
|
405
|
+
this.direction = direction;
|
|
406
|
+
this.internal.resources.onChange(this.handleResourceChange);
|
|
407
|
+
this.internal.relationships.onChange(this.handleRelationshipChange);
|
|
408
|
+
}
|
|
409
|
+
static async open(
|
|
410
|
+
from: ID,
|
|
411
|
+
client: Client,
|
|
412
|
+
framer: framer.Client,
|
|
413
|
+
initial: Resource[],
|
|
414
|
+
type: string = "parent",
|
|
415
|
+
direction: RelationshipDirection = "from",
|
|
416
|
+
): Promise<DependentTracker> {
|
|
417
|
+
const internal = await ChangeTracker.open(framer, client);
|
|
418
|
+
return new DependentTracker(from, internal, initial, client, type, direction);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private handleResourceChange = (changes: ResourceChange[]): void => {
|
|
422
|
+
this.dependents = this.dependents.map((child) => {
|
|
423
|
+
const change = changes.find((c) => c.key.toString() == child.id.toString());
|
|
424
|
+
if (change == null || change.variant === "delete") return child;
|
|
425
|
+
return change.value;
|
|
426
|
+
});
|
|
427
|
+
this.notify(this.dependents);
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
private handleRelationshipChange = (changes: RelationshipChange[]): void => {
|
|
431
|
+
const deletes = changes.filter(
|
|
432
|
+
(c) =>
|
|
433
|
+
c.variant === "delete" &&
|
|
434
|
+
c.key[this.direction].toString() === this.target.toString(),
|
|
435
|
+
);
|
|
436
|
+
this.dependents = this.dependents.filter(
|
|
437
|
+
(child) =>
|
|
438
|
+
!deletes.some(
|
|
439
|
+
(del) =>
|
|
440
|
+
del.key.to.toString() === child.id.toString() && del.key.type === this.type,
|
|
441
|
+
),
|
|
442
|
+
);
|
|
443
|
+
const sets = changes.filter(
|
|
444
|
+
(c) =>
|
|
445
|
+
c.variant === "set" &&
|
|
446
|
+
c.key.type === this.type &&
|
|
447
|
+
c.key[this.direction].toString() === this.target.toString(),
|
|
448
|
+
);
|
|
449
|
+
if (sets.length === 0) return this.notify(this.dependents);
|
|
450
|
+
this.client.retrieve(sets.map((s) => s.key.to)).then((resources) => {
|
|
451
|
+
this.dependents = this.dependents.concat(resources);
|
|
452
|
+
this.notify(this.dependents);
|
|
453
|
+
});
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
async close(): Promise<void> {
|
|
457
|
+
await this.internal.close();
|
|
458
|
+
}
|
|
459
|
+
}
|
package/src/ontology/payload.ts
CHANGED
|
@@ -53,6 +53,8 @@ export const stringIDZ = z.string().transform((v) => {
|
|
|
53
53
|
|
|
54
54
|
export const crudeIDZ = z.union([stringIDZ, idZ]);
|
|
55
55
|
|
|
56
|
+
export type CrudeID = { type: ResourceType; key: string } | string;
|
|
57
|
+
|
|
56
58
|
export class ID {
|
|
57
59
|
type: ResourceType;
|
|
58
60
|
key: string;
|
|
@@ -119,6 +121,8 @@ export type Resource<T extends UnknownRecord = UnknownRecord> = Omit<
|
|
|
119
121
|
"data"
|
|
120
122
|
> & { data?: T | null };
|
|
121
123
|
|
|
124
|
+
export type RelationshipDirection = "from" | "to";
|
|
125
|
+
|
|
122
126
|
export const relationshipSchemaZ = z.object({
|
|
123
127
|
from: ID.z,
|
|
124
128
|
type: z.string(),
|
package/src/ontology/writer.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
|
|
13
|
-
import { type ID,idZ } from "@/ontology/payload";
|
|
13
|
+
import { type CrudeID, ID, idZ } from "@/ontology/payload";
|
|
14
14
|
|
|
15
15
|
const ENDPOINTS = {
|
|
16
16
|
ADD_CHILDREN: "/ontology/add-children",
|
|
@@ -36,28 +36,42 @@ export class Writer {
|
|
|
36
36
|
this.client = client;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
async addChildren(id:
|
|
39
|
+
async addChildren(id: CrudeID, ...children: CrudeID[]): Promise<void> {
|
|
40
40
|
await sendRequired<typeof addRemoveChildrenReqZ, z.ZodTypeAny>(
|
|
41
|
-
this.client,
|
|
42
|
-
ENDPOINTS.ADD_CHILDREN,
|
|
43
|
-
{ id, children },
|
|
44
|
-
addRemoveChildrenReqZ,
|
|
45
|
-
z.object({})
|
|
41
|
+
this.client,
|
|
42
|
+
ENDPOINTS.ADD_CHILDREN,
|
|
43
|
+
{ id: new ID(id).payload, children: children.map((c) => new ID(c).payload) },
|
|
44
|
+
addRemoveChildrenReqZ,
|
|
45
|
+
z.object({}),
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
async removeChildren(id:
|
|
49
|
+
async removeChildren(id: CrudeID, ...children: CrudeID[]): Promise<void> {
|
|
50
50
|
await sendRequired<typeof addRemoveChildrenReqZ, z.ZodTypeAny>(
|
|
51
51
|
this.client,
|
|
52
52
|
ENDPOINTS.REMOVE_CHILDREN,
|
|
53
|
-
{ id, children },
|
|
53
|
+
{ id: new ID(id).payload, children: children.map((c) => new ID(c).payload) },
|
|
54
54
|
addRemoveChildrenReqZ,
|
|
55
55
|
z.object({}),
|
|
56
56
|
);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
async moveChildren(
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
async moveChildren(
|
|
60
|
+
from: CrudeID,
|
|
61
|
+
to: CrudeID,
|
|
62
|
+
...children: CrudeID[]
|
|
63
|
+
): Promise<void> {
|
|
64
|
+
const req = {
|
|
65
|
+
from: new ID(from).payload,
|
|
66
|
+
to: new ID(to).payload,
|
|
67
|
+
children: children.map((c) => new ID(c).payload),
|
|
68
|
+
};
|
|
69
|
+
await sendRequired(
|
|
70
|
+
this.client,
|
|
71
|
+
ENDPOINTS.MOVE_CHILDREN,
|
|
72
|
+
req,
|
|
73
|
+
moveChildrenReqZ,
|
|
74
|
+
z.object({}),
|
|
75
|
+
);
|
|
62
76
|
}
|
|
63
77
|
}
|