@synnaxlabs/client 0.20.0 → 0.22.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 (262) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/.vscode/settings.json +3 -0
  3. package/README.md +13 -0
  4. package/dist/auth/auth.d.ts +7 -6
  5. package/dist/auth/auth.d.ts.map +1 -1
  6. package/dist/channel/client.d.ts +14 -11
  7. package/dist/channel/client.d.ts.map +1 -1
  8. package/dist/channel/creator.d.ts +10 -0
  9. package/dist/channel/creator.d.ts.map +1 -0
  10. package/dist/channel/payload.d.ts +6 -2
  11. package/dist/channel/payload.d.ts.map +1 -1
  12. package/dist/channel/retriever.d.ts +65 -35
  13. package/dist/channel/retriever.d.ts.map +1 -1
  14. package/dist/channel/writer.d.ts +4 -3
  15. package/dist/client.cjs +19 -22
  16. package/dist/client.cjs.map +1 -1
  17. package/dist/client.d.ts +12 -10
  18. package/dist/client.d.ts.map +1 -1
  19. package/dist/client.js +6781 -6675
  20. package/dist/client.js.map +1 -1
  21. package/dist/connection/checker.d.ts +4 -3
  22. package/dist/connection/checker.d.ts.map +1 -1
  23. package/dist/control/authority.d.ts +2 -1
  24. package/dist/control/state.d.ts +6 -4
  25. package/dist/control/state.d.ts.map +1 -1
  26. package/dist/errors.d.ts +24 -20
  27. package/dist/errors.d.ts.map +1 -1
  28. package/dist/framer/adapter.d.ts +5 -4
  29. package/dist/framer/adapter.d.ts.map +1 -1
  30. package/dist/framer/client.d.ts +12 -12
  31. package/dist/framer/client.d.ts.map +1 -1
  32. package/dist/framer/frame.d.ts +13 -12
  33. package/dist/framer/frame.d.ts.map +1 -1
  34. package/dist/framer/iterator.d.ts +5 -4
  35. package/dist/framer/iterator.d.ts.map +1 -1
  36. package/dist/framer/streamProxy.d.ts +3 -2
  37. package/dist/framer/streamProxy.d.ts.map +1 -1
  38. package/dist/framer/streamer.d.ts +13 -4
  39. package/dist/framer/streamer.d.ts.map +1 -1
  40. package/dist/framer/writer.d.ts +67 -33
  41. package/dist/framer/writer.d.ts.map +1 -1
  42. package/dist/hardware/client.d.ts +4 -3
  43. package/dist/hardware/device/client.d.ts +107 -14
  44. package/dist/hardware/device/client.d.ts.map +1 -1
  45. package/dist/hardware/device/external.d.ts +0 -2
  46. package/dist/hardware/device/external.d.ts.map +1 -1
  47. package/dist/hardware/device/index.d.ts +1 -1
  48. package/dist/hardware/device/index.d.ts.map +1 -1
  49. package/dist/hardware/rack/client.d.ts +43 -20
  50. package/dist/hardware/rack/client.d.ts.map +1 -1
  51. package/dist/hardware/rack/external.d.ts +0 -2
  52. package/dist/hardware/rack/external.d.ts.map +1 -1
  53. package/dist/hardware/task/client.d.ts +197 -14
  54. package/dist/hardware/task/client.d.ts.map +1 -1
  55. package/dist/hardware/task/index.d.ts +1 -1
  56. package/dist/hardware/task/index.d.ts.map +1 -1
  57. package/dist/index.d.ts +4 -3
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/label/client.d.ts +8 -6
  60. package/dist/label/client.d.ts.map +1 -1
  61. package/dist/label/payload.d.ts +2 -1
  62. package/dist/label/retriever.d.ts +3 -2
  63. package/dist/label/writer.d.ts +4 -3
  64. package/dist/ontology/client.d.ts +82 -14
  65. package/dist/ontology/client.d.ts.map +1 -1
  66. package/dist/ontology/external.d.ts +0 -1
  67. package/dist/ontology/external.d.ts.map +1 -1
  68. package/dist/ontology/group/client.d.ts +3 -2
  69. package/dist/ontology/group/client.d.ts.map +1 -1
  70. package/dist/ontology/group/group.d.ts +1 -0
  71. package/dist/ontology/group/payload.d.ts +2 -1
  72. package/dist/ontology/group/writer.d.ts +4 -3
  73. package/dist/ontology/payload.d.ts +71 -60
  74. package/dist/ontology/payload.d.ts.map +1 -1
  75. package/dist/ontology/writer.d.ts +3 -2
  76. package/dist/ranger/active.d.ts +3 -2
  77. package/dist/ranger/alias.d.ts +7 -6
  78. package/dist/ranger/alias.d.ts.map +1 -1
  79. package/dist/ranger/client.d.ts +162 -11
  80. package/dist/ranger/client.d.ts.map +1 -1
  81. package/dist/ranger/external.d.ts +0 -1
  82. package/dist/ranger/external.d.ts.map +1 -1
  83. package/dist/ranger/kv.d.ts +5 -3
  84. package/dist/ranger/kv.d.ts.map +1 -1
  85. package/dist/ranger/payload.d.ts +57 -50
  86. package/dist/ranger/payload.d.ts.map +1 -1
  87. package/dist/ranger/range.d.ts +12 -10
  88. package/dist/ranger/range.d.ts.map +1 -1
  89. package/dist/ranger/writer.d.ts +3 -2
  90. package/dist/setupspecs.d.ts +2 -1
  91. package/dist/signals/observable.d.ts +8 -15
  92. package/dist/signals/observable.d.ts.map +1 -1
  93. package/dist/transport.d.ts +3 -2
  94. package/dist/transport.d.ts.map +1 -1
  95. package/dist/user/payload.d.ts +2 -1
  96. package/dist/util/retrieve.d.ts +24 -0
  97. package/dist/util/retrieve.d.ts.map +1 -0
  98. package/dist/util/retrieve.spec.d.ts +2 -0
  99. package/dist/util/retrieve.spec.d.ts.map +1 -0
  100. package/dist/util/telem.d.ts +2 -1
  101. package/dist/util/telem.d.ts.map +1 -1
  102. package/dist/util/zod.d.ts +4 -0
  103. package/dist/util/zod.d.ts.map +1 -0
  104. package/dist/workspace/client.d.ts +9 -6
  105. package/dist/workspace/client.d.ts.map +1 -1
  106. package/dist/workspace/lineplot/client.d.ts +6 -5
  107. package/dist/workspace/lineplot/client.d.ts.map +1 -1
  108. package/dist/workspace/lineplot/payload.d.ts +3 -2
  109. package/dist/workspace/lineplot/payload.d.ts.map +1 -1
  110. package/dist/workspace/lineplot/retriever.d.ts +3 -2
  111. package/dist/workspace/lineplot/retriever.d.ts.map +1 -1
  112. package/dist/workspace/lineplot/writer.d.ts +7 -6
  113. package/dist/workspace/lineplot/writer.d.ts.map +1 -1
  114. package/dist/workspace/payload.d.ts +3 -2
  115. package/dist/workspace/payload.d.ts.map +1 -1
  116. package/dist/workspace/retriever.d.ts +3 -2
  117. package/dist/workspace/schematic/client.d.ts +18 -0
  118. package/dist/workspace/schematic/client.d.ts.map +1 -0
  119. package/dist/workspace/schematic/external.d.ts.map +1 -0
  120. package/dist/workspace/schematic/index.d.ts +2 -0
  121. package/dist/workspace/schematic/index.d.ts.map +1 -0
  122. package/dist/workspace/{pid → schematic}/payload.d.ts +6 -5
  123. package/dist/workspace/schematic/payload.d.ts.map +1 -0
  124. package/dist/workspace/schematic/retriever.d.ts +10 -0
  125. package/dist/workspace/schematic/retriever.d.ts.map +1 -0
  126. package/dist/workspace/schematic/schematic.spec.d.ts +2 -0
  127. package/dist/workspace/schematic/schematic.spec.d.ts.map +1 -0
  128. package/dist/workspace/{pid → schematic}/writer.d.ts +11 -10
  129. package/dist/workspace/schematic/writer.d.ts.map +1 -0
  130. package/dist/workspace/writer.d.ts +5 -4
  131. package/dist/workspace/writer.d.ts.map +1 -1
  132. package/examples/node/package-lock.json +29 -12
  133. package/examples/node/package.json +2 -2
  134. package/examples/node/streamWrite.js +8 -11
  135. package/package.json +10 -9
  136. package/src/auth/auth.spec.ts +55 -15
  137. package/src/auth/auth.ts +41 -42
  138. package/src/channel/batchRetriever.spec.ts +37 -40
  139. package/src/channel/channel.spec.ts +4 -4
  140. package/src/channel/client.ts +42 -49
  141. package/src/channel/creator.ts +37 -0
  142. package/src/channel/payload.ts +2 -1
  143. package/src/channel/retriever.ts +55 -71
  144. package/src/client.ts +23 -20
  145. package/src/connection/checker.ts +1 -1
  146. package/src/connection/connection.spec.ts +1 -6
  147. package/src/control/state.ts +3 -1
  148. package/src/errors.ts +71 -67
  149. package/src/framer/adapter.spec.ts +33 -1
  150. package/src/framer/adapter.ts +10 -6
  151. package/src/framer/client.ts +13 -12
  152. package/src/framer/frame.spec.ts +1 -1
  153. package/src/framer/frame.ts +9 -6
  154. package/src/framer/iterator.spec.ts +1 -1
  155. package/src/framer/iterator.ts +1 -1
  156. package/src/framer/streamProxy.ts +12 -13
  157. package/src/framer/streamer.spec.ts +1 -1
  158. package/src/framer/streamer.ts +25 -1
  159. package/src/framer/writer.spec.ts +49 -2
  160. package/src/framer/writer.ts +27 -2
  161. package/src/hardware/device/client.ts +155 -28
  162. package/src/hardware/device/device.spec.ts +2 -2
  163. package/src/hardware/device/external.ts +0 -2
  164. package/src/hardware/device/index.ts +2 -2
  165. package/src/hardware/rack/client.ts +139 -56
  166. package/src/hardware/rack/external.ts +0 -2
  167. package/src/hardware/rack/rack.spec.ts +20 -1
  168. package/src/hardware/task/client.ts +324 -31
  169. package/src/hardware/task/index.ts +1 -1
  170. package/src/hardware/task/task.spec.ts +41 -0
  171. package/src/index.ts +3 -4
  172. package/src/label/client.ts +3 -2
  173. package/src/label/retriever.ts +1 -1
  174. package/src/label/writer.ts +1 -1
  175. package/src/ontology/client.ts +195 -41
  176. package/src/ontology/external.ts +0 -1
  177. package/src/ontology/group/client.ts +1 -2
  178. package/src/ontology/group/payload.ts +1 -1
  179. package/src/ontology/ontology.spec.ts +16 -0
  180. package/src/ontology/payload.ts +22 -13
  181. package/src/ranger/active.ts +5 -5
  182. package/src/ranger/alias.ts +2 -2
  183. package/src/ranger/client.ts +68 -17
  184. package/src/ranger/external.ts +0 -1
  185. package/src/ranger/kv.ts +6 -1
  186. package/src/ranger/payload.ts +6 -4
  187. package/src/ranger/range.ts +4 -1
  188. package/src/ranger/ranger.spec.ts +24 -2
  189. package/src/signals/observable.ts +24 -63
  190. package/src/transport.ts +2 -1
  191. package/src/util/retrieve.spec.ts +56 -0
  192. package/src/util/retrieve.ts +103 -0
  193. package/src/util/telem.ts +1 -1
  194. package/src/util/zod.ts +4 -0
  195. package/src/workspace/client.ts +6 -4
  196. package/src/workspace/lineplot/client.ts +3 -3
  197. package/src/workspace/lineplot/linePlot.spec.ts +11 -11
  198. package/src/workspace/lineplot/payload.ts +1 -1
  199. package/src/workspace/lineplot/retriever.ts +5 -13
  200. package/src/workspace/lineplot/writer.ts +8 -7
  201. package/src/workspace/payload.ts +6 -3
  202. package/src/workspace/retriever.ts +1 -1
  203. package/src/workspace/{pid → schematic}/client.ts +10 -10
  204. package/src/workspace/{pid → schematic}/external.ts +2 -2
  205. package/src/workspace/{pid → schematic}/index.ts +1 -1
  206. package/src/workspace/{pid → schematic}/payload.ts +4 -4
  207. package/src/workspace/{pid → schematic}/retriever.ts +10 -10
  208. package/src/workspace/{pid/pid.spec.ts → schematic/schematic.spec.ts} +35 -35
  209. package/src/workspace/{pid → schematic}/writer.ts +26 -25
  210. package/src/workspace/workspace.spec.ts +7 -7
  211. package/src/workspace/writer.ts +8 -2
  212. package/dist/hardware/device/payload.d.ts +0 -30
  213. package/dist/hardware/device/payload.d.ts.map +0 -1
  214. package/dist/hardware/device/retriever.d.ts +0 -10
  215. package/dist/hardware/device/retriever.d.ts.map +0 -1
  216. package/dist/hardware/device/writer.d.ts +0 -9
  217. package/dist/hardware/device/writer.d.ts.map +0 -1
  218. package/dist/hardware/rack/payload.d.ts +0 -26
  219. package/dist/hardware/rack/payload.d.ts.map +0 -1
  220. package/dist/hardware/rack/retriever.d.ts +0 -10
  221. package/dist/hardware/rack/retriever.d.ts.map +0 -1
  222. package/dist/hardware/rack/writer.d.ts +0 -9
  223. package/dist/hardware/rack/writer.d.ts.map +0 -1
  224. package/dist/hardware/task/external.d.ts +0 -4
  225. package/dist/hardware/task/external.d.ts.map +0 -1
  226. package/dist/hardware/task/payload.d.ts +0 -42
  227. package/dist/hardware/task/payload.d.ts.map +0 -1
  228. package/dist/hardware/task/retriever.d.ts +0 -29
  229. package/dist/hardware/task/retriever.d.ts.map +0 -1
  230. package/dist/hardware/task/writer.d.ts +0 -9
  231. package/dist/hardware/task/writer.d.ts.map +0 -1
  232. package/dist/ontology/retriever.d.ts +0 -13
  233. package/dist/ontology/retriever.d.ts.map +0 -1
  234. package/dist/ontology/signals.d.ts +0 -30
  235. package/dist/ontology/signals.d.ts.map +0 -1
  236. package/dist/ranger/retriever.d.ts +0 -11
  237. package/dist/ranger/retriever.d.ts.map +0 -1
  238. package/dist/workspace/pid/client.d.ts +0 -17
  239. package/dist/workspace/pid/client.d.ts.map +0 -1
  240. package/dist/workspace/pid/external.d.ts.map +0 -1
  241. package/dist/workspace/pid/index.d.ts +0 -2
  242. package/dist/workspace/pid/index.d.ts.map +0 -1
  243. package/dist/workspace/pid/payload.d.ts.map +0 -1
  244. package/dist/workspace/pid/pid.spec.d.ts +0 -2
  245. package/dist/workspace/pid/pid.spec.d.ts.map +0 -1
  246. package/dist/workspace/pid/retriever.d.ts +0 -9
  247. package/dist/workspace/pid/retriever.d.ts.map +0 -1
  248. package/dist/workspace/pid/writer.d.ts.map +0 -1
  249. package/src/hardware/device/payload.ts +0 -27
  250. package/src/hardware/device/retriever.ts +0 -60
  251. package/src/hardware/device/writer.ts +0 -59
  252. package/src/hardware/rack/payload.ts +0 -26
  253. package/src/hardware/rack/retriever.ts +0 -68
  254. package/src/hardware/rack/writer.ts +0 -59
  255. package/src/hardware/task/external.ts +0 -12
  256. package/src/hardware/task/payload.ts +0 -40
  257. package/src/hardware/task/retriever.ts +0 -70
  258. package/src/hardware/task/writer.ts +0 -65
  259. package/src/ontology/retriever.ts +0 -91
  260. package/src/ontology/signals.ts +0 -139
  261. package/src/ranger/retriever.ts +0 -50
  262. /package/dist/workspace/{pid → schematic}/external.d.ts +0 -0
@@ -7,83 +7,108 @@
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";
11
- import { type AsyncTermSearcher } from "@synnaxlabs/x";
10
+ import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
11
+ import { type AsyncTermSearcher } from "@synnaxlabs/x/search";
12
12
 
13
- import { type Client as FrameClient } from "@/framer/client";
13
+ import { QueryError } from "@/errors";
14
+ import { framer } from "@/framer";
14
15
  import { group } from "@/ontology/group";
15
- import { type ID, type Resource } from "@/ontology/payload";
16
- import { Retriever } from "@/ontology/retriever";
16
+ import {
17
+ ID,
18
+ IDPayload,
19
+ RelationshipChange,
20
+ ResourceChange,
21
+ idZ,
22
+ parseRelationship,
23
+ resourceSchemaZ,
24
+ type Resource,
25
+ } from "@/ontology/payload";
17
26
  import { Writer } from "@/ontology/writer";
27
+ import { z } from "zod";
28
+ import { observe, toArray } from "@synnaxlabs/x";
29
+ import { Frame } from "@/framer/frame";
18
30
 
19
- import { QueryError } from "@/errors";
31
+ const RETRIEVE_ENDPOINT = "/ontology/retrieve";
32
+
33
+ const retrieveReqZ = z.object({
34
+ ids: idZ.array().optional(),
35
+ children: z.boolean().optional(),
36
+ parents: z.boolean().optional(),
37
+ includeSchema: z.boolean().optional(),
38
+ excludeFieldData: z.boolean().optional(),
39
+ term: z.string().optional(),
40
+ limit: z.number().optional(),
41
+ offset: z.number().optional(),
42
+ });
43
+
44
+ type RetrieveRequest = z.infer<typeof retrieveReqZ>;
45
+
46
+ export type RetrieveOptions = Pick<
47
+ RetrieveRequest,
48
+ "includeSchema" | "excludeFieldData"
49
+ >;
20
50
 
21
- import { ChangeTracker } from "@/ontology/signals";
51
+ const retrieveResZ = z.object({
52
+ resources: resourceSchemaZ.array(),
53
+ });
54
+
55
+ const parseIDs = (ids: ID | ID[] | string | string[]): IDPayload[] =>
56
+ toArray(ids).map((id) => new ID(id).payload);
22
57
 
23
58
  /** The core client class for executing queries against a Synnax cluster ontology */
24
59
  export class Client implements AsyncTermSearcher<string, string, Resource> {
60
+ readonly type: string = "ontology";
25
61
  groups: group.Client;
26
- retriever: Retriever;
62
+ private readonly client: UnaryClient;
27
63
  private readonly writer: Writer;
28
- private readonly framer: FrameClient;
64
+ private readonly framer: framer.Client;
29
65
 
30
- constructor(unary: UnaryClient, framer: FrameClient) {
31
- this.retriever = new Retriever(unary);
66
+ constructor(unary: UnaryClient, framer: framer.Client) {
67
+ this.client = unary;
32
68
  this.writer = new Writer(unary);
33
69
  this.groups = new group.Client(unary);
34
70
  this.framer = framer;
35
71
  }
36
72
 
37
- async search(term: string): Promise<Resource[]> {
38
- return await this.retriever.search(term);
73
+ async search(term: string, options?: RetrieveOptions): Promise<Resource[]> {
74
+ return await this.execRetrieve({ term, ...options });
39
75
  }
40
76
 
41
- async retrieve(
42
- id: ID | string,
43
- includeSchema?: boolean,
44
- includeFieldData?: boolean,
45
- ): Promise<Resource>;
77
+ async retrieve(id: ID | string, options?: RetrieveOptions): Promise<Resource>;
46
78
 
47
- async retrieve(
48
- ids: ID[] | string[],
49
- includeSchema?: boolean,
50
- includeFieldData?: boolean,
51
- ): Promise<Resource[]>;
79
+ async retrieve(ids: ID[] | string[], options?: RetrieveOptions): Promise<Resource[]>;
52
80
 
53
81
  async retrieve(
54
82
  ids: ID | ID[] | string | string[],
55
- includeSchema?: boolean,
56
- includeFieldData?: boolean,
83
+ options?: RetrieveOptions,
57
84
  ): Promise<Resource | Resource[]> {
58
- const resources = await this.retriever.retrieve(
59
- ids,
60
- includeSchema,
61
- includeFieldData,
62
- );
85
+ const resources = await this.execRetrieve({ ids: parseIDs(ids), ...options });
63
86
  if (Array.isArray(ids)) return resources;
64
87
  if (resources.length === 0)
65
88
  throw new QueryError(`No resource found with ID ${ids.toString()}`);
66
89
  return resources[0];
67
90
  }
68
91
 
69
- async page(offset: number, limit: number): Promise<Resource[]> {
70
- return [];
92
+ async page(
93
+ offset: number,
94
+ limit: number,
95
+ options?: RetrieveOptions,
96
+ ): Promise<Resource[]> {
97
+ return await this.execRetrieve({ offset, limit, ...options });
71
98
  }
72
99
 
73
100
  async retrieveChildren(
74
- IDs: ID | ID[],
75
- includeSchema?: boolean,
76
- includeFieldData?: boolean,
101
+ ids: ID | ID[],
102
+ options?: RetrieveOptions,
77
103
  ): Promise<Resource[]> {
78
- return await this.retriever.retrieveChildren(IDs, includeSchema, includeFieldData);
104
+ return await this.execRetrieve({ ids: parseIDs(ids), children: true, ...options });
79
105
  }
80
106
 
81
107
  async retrieveParents(
82
- IDs: ID | ID[],
83
- includeSchema?: boolean,
84
- includeFieldData?: boolean,
108
+ ids: ID | ID[],
109
+ options?: RetrieveOptions,
85
110
  ): Promise<Resource[]> {
86
- return await this.retriever.retrieveParents(IDs, includeSchema, includeFieldData);
111
+ return await this.execRetrieve({ ids: parseIDs(ids), parents: true, ...options });
87
112
  }
88
113
 
89
114
  async addChildren(id: ID, ...children: ID[]): Promise<void> {
@@ -99,6 +124,135 @@ export class Client implements AsyncTermSearcher<string, string, Resource> {
99
124
  }
100
125
 
101
126
  async openChangeTracker(): Promise<ChangeTracker> {
102
- return await ChangeTracker.open(this.framer, this.retriever);
127
+ return await ChangeTracker.open(this.framer, this);
128
+ }
129
+
130
+ newSearcherWithOptions(
131
+ options: RetrieveOptions,
132
+ ): AsyncTermSearcher<string, string, Resource> {
133
+ return {
134
+ type: this.type,
135
+ search: (term: string) => this.search(term, options),
136
+ retrieve: (ids: string[]) => this.retrieve(ids, options),
137
+ page: (offset: number, limit: number) => this.page(offset, limit, options),
138
+ };
139
+ }
140
+
141
+ private async execRetrieve(request: RetrieveRequest): Promise<Resource[]> {
142
+ const { resources } = await sendRequired(
143
+ this.client,
144
+ RETRIEVE_ENDPOINT,
145
+ request,
146
+ retrieveReqZ,
147
+ retrieveResZ,
148
+ );
149
+ return resources;
150
+ }
151
+ }
152
+
153
+ const RESOURCE_SET_NAME = "sy_ontology_resource_set";
154
+ const RESOURCE_DELETE_NAME = "sy_ontology_resource_delete";
155
+ const RELATIONSHIP_SET_NAME = "sy_ontology_relationship_set";
156
+ const RELATIONSHIP_DELETE_NAME = "sy_ontology_relationship_delete";
157
+
158
+ export class ChangeTracker {
159
+ private readonly resourceObs: observe.Observer<ResourceChange[]>;
160
+ private readonly relationshipObs: observe.Observer<RelationshipChange[]>;
161
+
162
+ readonly relationships: observe.Observable<RelationshipChange[]>;
163
+ readonly resources: observe.Observable<ResourceChange[]>;
164
+
165
+ private readonly streamer: framer.Streamer;
166
+ private readonly client: Client;
167
+ private readonly closePromise: Promise<void>;
168
+
169
+ constructor(streamer: framer.Streamer, client: Client) {
170
+ this.relationshipObs = new observe.Observer<RelationshipChange[]>();
171
+ this.relationships = this.relationshipObs;
172
+ this.resourceObs = new observe.Observer<ResourceChange[]>();
173
+ this.resources = this.resourceObs;
174
+ this.client = client;
175
+ this.streamer = streamer;
176
+ this.closePromise = this.start();
177
+ }
178
+
179
+ async close(): Promise<void> {
180
+ this.streamer.close();
181
+ await this.closePromise;
182
+ }
183
+
184
+ private async start(): Promise<void> {
185
+ for await (const frame of this.streamer) {
186
+ await this.update(frame);
187
+ }
188
+ }
189
+
190
+ private async update(frame: Frame): Promise<void> {
191
+ const resSets = await this.parseResourceSets(frame);
192
+ const resDeletes = this.parseResourceDeletes(frame);
193
+ const allResources = resSets.concat(resDeletes);
194
+ if (allResources.length > 0) this.resourceObs.notify(resSets.concat(resDeletes));
195
+ const relSets = this.parseRelationshipSets(frame);
196
+ const relDeletes = this.parseRelationshipDeletes(frame);
197
+ const allRelationships = relSets.concat(relDeletes);
198
+ if (allRelationships.length > 0)
199
+ this.relationshipObs.notify(relSets.concat(relDeletes));
200
+ }
201
+
202
+ private parseRelationshipSets(frame: Frame): RelationshipChange[] {
203
+ const relationships = frame.get(RELATIONSHIP_SET_NAME);
204
+ if (relationships.length === 0) return [];
205
+ return Array.from(relationships.as("string")).map((rel) => ({
206
+ variant: "set",
207
+ key: parseRelationship(rel),
208
+ value: undefined,
209
+ }));
210
+ }
211
+
212
+ private parseRelationshipDeletes(frame: Frame): RelationshipChange[] {
213
+ const relationships = frame.get(RELATIONSHIP_DELETE_NAME);
214
+ if (relationships.length === 0) return [];
215
+ return Array.from(relationships.as("string")).map((rel) => ({
216
+ variant: "delete",
217
+ key: parseRelationship(rel),
218
+ }));
219
+ }
220
+
221
+ private async parseResourceSets(frame: Frame): Promise<ResourceChange[]> {
222
+ const sets = frame.get(RESOURCE_SET_NAME);
223
+ if (sets.length === 0) return [];
224
+ // We should only ever get one series of sets
225
+ const ids = Array.from(sets.as("string")).map((id: string) => new ID(id));
226
+ try {
227
+ const resources = await this.client.retrieve(ids);
228
+ return resources.map((resource) => ({
229
+ variant: "set",
230
+ key: resource.id,
231
+ value: resource,
232
+ }));
233
+ } catch (e) {
234
+ if (e instanceof QueryError) return [];
235
+ throw e;
236
+ }
237
+ }
238
+
239
+ private parseResourceDeletes(frame: Frame): ResourceChange[] {
240
+ const deletes = frame.get(RESOURCE_DELETE_NAME);
241
+ if (deletes.length === 0) return [];
242
+ // We should only ever get one series of deletes
243
+ return Array.from(deletes.as("string")).map((str) => ({
244
+ variant: "delete",
245
+ key: new ID(str),
246
+ }));
247
+ }
248
+
249
+ static async open(client: framer.Client, retriever: Client): Promise<ChangeTracker> {
250
+ const streamer = await client.openStreamer([
251
+ RESOURCE_SET_NAME,
252
+ RESOURCE_DELETE_NAME,
253
+ RELATIONSHIP_SET_NAME,
254
+ RELATIONSHIP_DELETE_NAME,
255
+ ]);
256
+ return new ChangeTracker(streamer, retriever);
103
257
  }
104
258
  }
@@ -9,4 +9,3 @@
9
9
 
10
10
  export * from "@/ontology/client";
11
11
  export * from "@/ontology/payload";
12
- export * from "@/ontology/signals";
@@ -9,11 +9,10 @@
9
9
 
10
10
  import { type UnaryClient } from "@synnaxlabs/freighter";
11
11
 
12
- import { type ID } from "@/ontology/payload";
13
-
14
12
  import { Group } from "@/ontology/group/group";
15
13
  import { type Payload } from "@/ontology/group/payload";
16
14
  import { Writer } from "@/ontology/group/writer";
15
+ import { type ID } from "@/ontology/payload";
17
16
 
18
17
  export class Client {
19
18
  private readonly creator: Writer;
@@ -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 { toArray } from "@synnaxlabs/x";
10
+ import { toArray } from "@synnaxlabs/x/toArray";
11
11
  import { z } from "zod";
12
12
 
13
13
  export const keyZ = z.string().uuid();
@@ -42,6 +42,22 @@ describe("Ontology", () => {
42
42
  expect(parents.length).toEqual(1);
43
43
  expect(parents[0].name).toEqual(name);
44
44
  });
45
+ });
46
+ describe("page", () => {
47
+ it("should return a page of resources", async () => {
48
+ for (let i = 0; i < 10; i++)
49
+ await client.ontology.groups.create(ontology.Root, randomName());
50
+ const page = await client.ontology.page(0, 5);
51
+ expect(page.length).toEqual(5);
52
+ const page2 = await client.ontology.page(5, 5);
53
+ expect(page2.length).toEqual(5);
54
+ const page1Keys = page.map((r) => r.key);
55
+ const page2Keys = page2.map((r) => r.key);
56
+ const intersection = page1Keys.filter((key) => page2Keys.includes(key));
57
+ expect(intersection.length).toEqual(0);
58
+ });
59
+ });
60
+ describe("write", () => {
45
61
  test("add children", async () => {
46
62
  const name = randomName();
47
63
  const g = await client.ontology.groups.create(ontology.Root, name);
@@ -7,8 +7,16 @@
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 { change } from "@synnaxlabs/x";
10
11
  import { z } from "zod";
11
12
 
13
+ export type ResourceChange = change.Change<ID, Resource>;
14
+ export type ResourceSet = change.Set<ID, Resource>;
15
+ export type ResourceDelete = change.Delete<ID, Resource>;
16
+ export type RelationshipChange = change.Change<Relationship, undefined>;
17
+ export type RelationshipSet = change.Set<Relationship, undefined>;
18
+ export type RelationshipDelete = change.Delete<Relationship, undefined>;
19
+
12
20
  const resourceTypeZ = z.union([
13
21
  z.literal("label"),
14
22
  z.literal("builtin"),
@@ -20,24 +28,22 @@ const resourceTypeZ = z.union([
20
28
  z.literal("range-alias"),
21
29
  z.literal("user"),
22
30
  z.literal("workspace"),
23
- z.literal("pid"),
31
+ z.literal("schematic"),
24
32
  z.literal("lineplot"),
25
- z.literal("rack")
33
+ z.literal("rack"),
34
+ z.literal("device"),
35
+ z.literal("task"),
26
36
  ]);
27
37
 
28
38
  export type ResourceType = z.infer<typeof resourceTypeZ>;
29
39
 
30
- export const idZ = z.object({
31
- type: resourceTypeZ,
32
- key: z.string(),
33
- });
40
+ export const idZ = z.object({ type: resourceTypeZ, key: z.string() });
41
+
42
+ export type IDPayload = z.infer<typeof idZ>;
34
43
 
35
44
  export const stringIDZ = z.string().transform((v) => {
36
45
  const [type, key] = v.split(":");
37
- return {
38
- type: type as ResourceType,
39
- key,
40
- };
46
+ return { type: type as ResourceType, key };
41
47
  });
42
48
 
43
49
  export const crudeIDZ = z.union([stringIDZ, idZ]);
@@ -93,8 +99,8 @@ export const resourceSchemaZ = z
93
99
  .object({
94
100
  id: ID.z,
95
101
  name: z.string(),
96
- schema: schemaZ.optional(),
97
- data: z.record(z.unknown()).optional(),
102
+ schema: schemaZ.optional().nullable(),
103
+ data: z.record(z.unknown()).optional().nullable(),
98
104
  })
99
105
  .transform((resource) => {
100
106
  return {
@@ -103,7 +109,10 @@ export const resourceSchemaZ = z
103
109
  };
104
110
  });
105
111
 
106
- export type Resource = z.infer<typeof resourceSchemaZ>;
112
+ export type Resource<T extends {} = {}> = Omit<
113
+ z.output<typeof resourceSchemaZ>,
114
+ "data"
115
+ > & { data?: T | null };
107
116
 
108
117
  export const relationshipSchemaZ = z.object({
109
118
  from: ID.z,
@@ -43,9 +43,9 @@ export class Active {
43
43
  async setActive(range: Key): Promise<void> {
44
44
  await sendRequired<typeof setActiveReqZ, typeof setActiveResZ>(
45
45
  this.client,
46
- SET_ENDPOINT,
47
- { range },
48
- setActiveReqZ,
46
+ SET_ENDPOINT,
47
+ { range },
48
+ setActiveReqZ,
49
49
  setActiveResZ,
50
50
  );
51
51
  }
@@ -65,8 +65,8 @@ export class Active {
65
65
  async clearActive(range: Key): Promise<void> {
66
66
  await sendRequired<typeof clearActiveReqZ, typeof clearActiveResZ>(
67
67
  this.client,
68
- CLEAR_ENDPOINT,
69
- { range },
68
+ CLEAR_ENDPOINT,
69
+ { range },
70
70
  clearActiveReqZ,
71
71
  clearActiveResZ,
72
72
  );
@@ -8,7 +8,7 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
11
- import { type change } from "@synnaxlabs/x";
11
+ import { type change } from "@synnaxlabs/x/change";
12
12
  import { z } from "zod";
13
13
 
14
14
  import { type channel } from "@/channel";
@@ -133,7 +133,7 @@ export class Aliaser {
133
133
  }
134
134
 
135
135
  async openChangeTracker(): Promise<signals.Observable<string, Alias>> {
136
- return await signals.Observable.open<string, Alias>(
136
+ return await signals.openObservable<string, Alias>(
137
137
  this.frameClient,
138
138
  ALIAS_SET_NAME,
139
139
  ALIAS_DELETE_NAME,
@@ -7,11 +7,12 @@
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";
11
- import { type AsyncTermSearcher, toArray } from "@synnaxlabs/x";
10
+ import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
11
+ import { type AsyncTermSearcher } from "@synnaxlabs/x/search";
12
+ import { toArray } from "@synnaxlabs/x/toArray";
12
13
 
13
14
  import { type Retriever as ChannelRetriever } from "@/channel/retriever";
14
- import { QueryError } from "@/errors";
15
+ import { MultipleFoundError, NotFoundError, QueryError } from "@/errors";
15
16
  import { type framer } from "@/framer";
16
17
  import { type label } from "@/label";
17
18
  import { Active } from "@/ranger/active";
@@ -26,14 +27,34 @@ import {
26
27
  type Params,
27
28
  type Payload,
28
29
  analyzeParams,
30
+ payloadZ,
31
+ keyZ,
29
32
  } from "@/ranger/payload";
30
33
  import { Range } from "@/ranger/range";
31
- import { type Retriever } from "@/ranger/retriever";
32
34
  import { type Writer } from "@/ranger/writer";
35
+ import { signals } from "@/signals";
36
+ import { z } from "zod";
37
+ import { CrudeTimeRange, TimeRange } from "@synnaxlabs/x";
38
+ import { nullableArrayZ } from "@/util/zod";
39
+
40
+ const retrieveReqZ = z.object({
41
+ keys: keyZ.array().optional(),
42
+ names: z.array(z.string()).optional(),
43
+ term: z.string().optional(),
44
+ overlapsWith: TimeRange.z.optional(),
45
+ });
46
+
47
+ export type RetrieveRequest = z.infer<typeof retrieveReqZ>;
48
+
49
+ const RETRIEVE_ENDPOINT = "/range/retrieve";
50
+
51
+ const retrieveResZ = z.object({
52
+ ranges: nullableArrayZ(payloadZ),
53
+ });
33
54
 
34
55
  export class Client implements AsyncTermSearcher<string, Key, Range> {
56
+ readonly type: string = "range";
35
57
  private readonly frameClient: framer.Client;
36
- private readonly retriever: Retriever;
37
58
  private readonly writer: Writer;
38
59
  private readonly unaryClient: UnaryClient;
39
60
  private readonly channels: ChannelRetriever;
@@ -42,14 +63,12 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
42
63
 
43
64
  constructor(
44
65
  frameClient: framer.Client,
45
- retriever: Retriever,
46
66
  writer: Writer,
47
67
  unary: UnaryClient,
48
68
  channels: ChannelRetriever,
49
69
  labelClient: label.Client,
50
70
  ) {
51
71
  this.frameClient = frameClient;
52
- this.retriever = retriever;
53
72
  this.writer = writer;
54
73
  this.unaryClient = unary;
55
74
  this.channels = channels;
@@ -76,25 +95,42 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
76
95
  }
77
96
 
78
97
  async search(term: string): Promise<Range[]> {
79
- return this.sugar(await this.retriever.search(term));
98
+ return this.sugar(await this.execRetrieve({ term }));
80
99
  }
81
100
 
82
101
  async page(offset: number, limit: number): Promise<Range[]> {
83
102
  return [];
84
103
  }
85
104
 
105
+ async retrieve(range: CrudeTimeRange): Promise<Range[]>;
106
+
86
107
  async retrieve(range: Key | Name): Promise<Range>;
87
108
 
88
- async retrieve(params: Keys | Names): Promise<Range[]>;
109
+ async retrieve(range: Keys | Names): Promise<Range[]>;
110
+
111
+ async retrieve(params: Params | CrudeTimeRange): Promise<Range | Range[]> {
112
+ if (typeof params === "object" && "start" in params)
113
+ return await this.execRetrieve({ overlapsWith: new TimeRange(params) });
114
+ const { single, actual, variant, normalized } = analyzeParams(params);
115
+ const ranges = await this.execRetrieve({ [variant]: normalized });
116
+ if (!single) return ranges;
117
+ if (ranges.length === 0)
118
+ throw new NotFoundError(`range matching ${actual} not found`);
119
+ if (ranges.length > 1)
120
+ throw new MultipleFoundError(`multiple ranges matching ${actual} found`);
121
+ return ranges[0];
122
+ }
89
123
 
90
- async retrieve(params: Params): Promise<Range | Range[]> {
91
- const { single, actual } = analyzeParams(params);
92
- const res = this.sugar(await this.retriever.retrieve(params));
93
- if (!single) return res;
94
- if (res.length === 0) throw new QueryError(`range matching ${actual} not found`);
95
- if (res.length > 1)
96
- throw new QueryError(`multiple ranges matching ${actual} found`);
97
- return res[0];
124
+ private async execRetrieve(req: RetrieveRequest): Promise<Range[]> {
125
+ return this.sugar(
126
+ await sendRequired<typeof retrieveReqZ, typeof retrieveResZ>(
127
+ this.unaryClient,
128
+ RETRIEVE_ENDPOINT,
129
+ req,
130
+ retrieveReqZ,
131
+ retrieveResZ,
132
+ ).then((res) => res.ranges),
133
+ );
98
134
  }
99
135
 
100
136
  async setActive(range: Key): Promise<void> {
@@ -117,6 +153,7 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
117
153
  payload.name,
118
154
  payload.timeRange,
119
155
  payload.key,
156
+ payload.color,
120
157
  this.frameClient,
121
158
  new KV(payload.key, this.unaryClient),
122
159
  new Aliaser(payload.key, this.frameClient, this.unaryClient),
@@ -125,4 +162,18 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
125
162
  );
126
163
  });
127
164
  }
165
+
166
+ async openTracker(): Promise<signals.Observable<string, Range>> {
167
+ return await signals.openObservable<string, Range>(
168
+ this.frameClient,
169
+ "sy_range_set",
170
+ "sy_range_delete",
171
+ (variant, data) => {
172
+ if (variant === "delete")
173
+ return data.toStrings().map((k) => ({ variant, key: k, value: undefined }));
174
+ const sugared = this.sugar(data.parseJSON(payloadZ));
175
+ return sugared.map((r) => ({ variant, key: r.key, value: r }));
176
+ },
177
+ );
178
+ }
128
179
  }
@@ -11,5 +11,4 @@ export * from "@/ranger/client";
11
11
  export * from "@/ranger/writer";
12
12
  export * from "@/ranger/payload";
13
13
  export * from "@/ranger/range";
14
- export * from "@/ranger/retriever";
15
14
  export type { Alias, AliasChange } from "@/ranger/alias";
package/src/ranger/kv.ts CHANGED
@@ -8,7 +8,8 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  import { type UnaryClient, sendRequired } from "@synnaxlabs/freighter";
11
- import { isObject, toArray } from "@synnaxlabs/x";
11
+ import { isObject } from "@synnaxlabs/x/identity";
12
+ import { toArray } from "@synnaxlabs/x/toArray";
12
13
  import { z } from "zod";
13
14
 
14
15
  import { type Key, keyZ } from "@/ranger/payload";
@@ -65,6 +66,10 @@ export class KV {
65
66
  return Array.isArray(keys) ? res.pairs : res.pairs[keys];
66
67
  }
67
68
 
69
+ async list(): Promise<Record<string, string>> {
70
+ return this.get([]);
71
+ }
72
+
68
73
  async set(key: string, value: string): Promise<void>;
69
74
 
70
75
  async set(kv: Record<string, string>): Promise<void>;
@@ -7,7 +7,8 @@
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 { TimeRange, toArray } from "@synnaxlabs/x";
10
+ import { TimeRange } from "@synnaxlabs/x/telem";
11
+ import { toArray } from "@synnaxlabs/x/toArray";
11
12
  import { z } from "zod";
12
13
 
13
14
  export const keyZ = z.string().uuid();
@@ -21,6 +22,7 @@ export const payloadZ = z.object({
21
22
  key: keyZ,
22
23
  name: z.string().min(1),
23
24
  timeRange: TimeRange.z,
25
+ color: z.string().optional(),
24
26
  });
25
27
  export type Payload = z.infer<typeof payloadZ>;
26
28
 
@@ -29,7 +31,7 @@ export const newPayloadZ = payloadZ.extend({
29
31
  });
30
32
  export type NewPayload = z.infer<typeof newPayloadZ>;
31
33
 
32
- export type ParamAnalsysisResult =
34
+ export type ParamAnalysisResult =
33
35
  | {
34
36
  single: true;
35
37
  variant: "keys";
@@ -55,7 +57,7 @@ export type ParamAnalsysisResult =
55
57
  actual: Names;
56
58
  };
57
59
 
58
- export const analyzeParams = (params: Params): ParamAnalsysisResult => {
60
+ export const analyzeParams = (params: Params): ParamAnalysisResult => {
59
61
  const normal = toArray(params) as Keys | Names;
60
62
  if (normal.length === 0) {
61
63
  throw new Error("Range params must not be empty");
@@ -66,5 +68,5 @@ export const analyzeParams = (params: Params): ParamAnalsysisResult => {
66
68
  variant: isKey ? "keys" : "names",
67
69
  normalized: normal,
68
70
  actual: params,
69
- } as const as ParamAnalsysisResult;
71
+ } as const as ParamAnalysisResult;
70
72
  };