@synnaxlabs/client 0.43.1 → 0.44.2

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 (245) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/dist/access/payload.d.ts +1 -1
  3. package/dist/access/payload.d.ts.map +1 -1
  4. package/dist/access/policy/client.d.ts +263 -6
  5. package/dist/access/policy/client.d.ts.map +1 -1
  6. package/dist/access/policy/external.d.ts +0 -1
  7. package/dist/access/policy/external.d.ts.map +1 -1
  8. package/dist/access/policy/payload.d.ts +105 -93
  9. package/dist/access/policy/payload.d.ts.map +1 -1
  10. package/dist/auth/auth.d.ts +1 -1
  11. package/dist/auth/auth.d.ts.map +1 -1
  12. package/dist/channel/client.d.ts +23 -17
  13. package/dist/channel/client.d.ts.map +1 -1
  14. package/dist/channel/payload.d.ts +151 -21
  15. package/dist/channel/payload.d.ts.map +1 -1
  16. package/dist/channel/retriever.d.ts +9 -16
  17. package/dist/channel/retriever.d.ts.map +1 -1
  18. package/dist/channel/writer.d.ts +1 -1
  19. package/dist/channel/writer.d.ts.map +1 -1
  20. package/dist/client.cjs +27 -135
  21. package/dist/client.d.ts +3 -3
  22. package/dist/client.d.ts.map +1 -1
  23. package/dist/client.js +8657 -28963
  24. package/dist/connection/checker.d.ts +1 -1
  25. package/dist/connection/checker.d.ts.map +1 -1
  26. package/dist/control/client.d.ts +1 -0
  27. package/dist/control/client.d.ts.map +1 -1
  28. package/dist/control/state.d.ts +6 -6
  29. package/dist/control/state.d.ts.map +1 -1
  30. package/dist/errors.d.ts +18 -5
  31. package/dist/errors.d.ts.map +1 -1
  32. package/dist/framer/adapter.d.ts +3 -3
  33. package/dist/framer/adapter.d.ts.map +1 -1
  34. package/dist/framer/client.d.ts +4 -13
  35. package/dist/framer/client.d.ts.map +1 -1
  36. package/dist/framer/codec.d.ts +1 -1
  37. package/dist/framer/codec.d.ts.map +1 -1
  38. package/dist/framer/deleter.d.ts +5 -5
  39. package/dist/framer/deleter.d.ts.map +1 -1
  40. package/dist/framer/frame.d.ts +5 -7
  41. package/dist/framer/frame.d.ts.map +1 -1
  42. package/dist/framer/streamProxy.d.ts +1 -1
  43. package/dist/framer/streamProxy.d.ts.map +1 -1
  44. package/dist/framer/streamer.d.ts +235 -20
  45. package/dist/framer/streamer.d.ts.map +1 -1
  46. package/dist/framer/writer.d.ts +302 -33
  47. package/dist/framer/writer.d.ts.map +1 -1
  48. package/dist/hardware/device/client.d.ts +49 -28
  49. package/dist/hardware/device/client.d.ts.map +1 -1
  50. package/dist/hardware/device/payload.d.ts +126 -46
  51. package/dist/hardware/device/payload.d.ts.map +1 -1
  52. package/dist/hardware/rack/client.d.ts +78 -22
  53. package/dist/hardware/rack/client.d.ts.map +1 -1
  54. package/dist/hardware/rack/payload.d.ts +99 -56
  55. package/dist/hardware/rack/payload.d.ts.map +1 -1
  56. package/dist/hardware/task/client.d.ts +100 -41
  57. package/dist/hardware/task/client.d.ts.map +1 -1
  58. package/dist/hardware/task/payload.d.ts +83 -61
  59. package/dist/hardware/task/payload.d.ts.map +1 -1
  60. package/dist/index.d.ts +2 -2
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/label/client.d.ts +138 -20
  63. package/dist/label/client.d.ts.map +1 -1
  64. package/dist/label/external.d.ts +0 -2
  65. package/dist/label/external.d.ts.map +1 -1
  66. package/dist/label/payload.d.ts +4 -5
  67. package/dist/label/payload.d.ts.map +1 -1
  68. package/dist/ontology/client.d.ts +45 -135
  69. package/dist/ontology/client.d.ts.map +1 -1
  70. package/dist/ontology/group/group.d.ts +3 -3
  71. package/dist/ontology/group/group.d.ts.map +1 -1
  72. package/dist/ontology/group/payload.d.ts +3 -27
  73. package/dist/ontology/group/payload.d.ts.map +1 -1
  74. package/dist/ontology/payload.d.ts +114 -243
  75. package/dist/ontology/payload.d.ts.map +1 -1
  76. package/dist/ontology/writer.d.ts +4 -4
  77. package/dist/ontology/writer.d.ts.map +1 -1
  78. package/dist/ranger/alias.d.ts +15 -5
  79. package/dist/ranger/alias.d.ts.map +1 -1
  80. package/dist/ranger/client.d.ts +91 -30
  81. package/dist/ranger/client.d.ts.map +1 -1
  82. package/dist/ranger/external.d.ts +1 -1
  83. package/dist/ranger/external.d.ts.map +1 -1
  84. package/dist/ranger/kv.d.ts +11 -12
  85. package/dist/ranger/kv.d.ts.map +1 -1
  86. package/dist/ranger/payload.d.ts +19 -44
  87. package/dist/ranger/payload.d.ts.map +1 -1
  88. package/dist/ranger/writer.d.ts +22 -19
  89. package/dist/ranger/writer.d.ts.map +1 -1
  90. package/dist/testutil/client.d.ts +4 -0
  91. package/dist/testutil/client.d.ts.map +1 -0
  92. package/dist/user/client.d.ts +59 -6
  93. package/dist/user/client.d.ts.map +1 -1
  94. package/dist/user/payload.d.ts +4 -6
  95. package/dist/user/payload.d.ts.map +1 -1
  96. package/dist/user/retriever.d.ts +2 -2
  97. package/dist/user/retriever.d.ts.map +1 -1
  98. package/dist/util/decodeJSONString.d.ts +2 -2
  99. package/dist/util/decodeJSONString.d.ts.map +1 -1
  100. package/dist/util/parseWithoutKeyConversion.d.ts +2 -2
  101. package/dist/util/parseWithoutKeyConversion.d.ts.map +1 -1
  102. package/dist/util/retrieve.d.ts +1 -1
  103. package/dist/util/retrieve.d.ts.map +1 -1
  104. package/dist/util/zod.d.ts +1 -1
  105. package/dist/util/zod.d.ts.map +1 -1
  106. package/dist/workspace/client.d.ts +17 -6
  107. package/dist/workspace/client.d.ts.map +1 -1
  108. package/dist/workspace/lineplot/client.d.ts +2 -2
  109. package/dist/workspace/lineplot/client.d.ts.map +1 -1
  110. package/dist/workspace/lineplot/payload.d.ts +8 -9
  111. package/dist/workspace/lineplot/payload.d.ts.map +1 -1
  112. package/dist/workspace/log/client.d.ts +2 -2
  113. package/dist/workspace/log/client.d.ts.map +1 -1
  114. package/dist/workspace/log/payload.d.ts +8 -9
  115. package/dist/workspace/log/payload.d.ts.map +1 -1
  116. package/dist/workspace/payload.d.ts +10 -11
  117. package/dist/workspace/payload.d.ts.map +1 -1
  118. package/dist/workspace/schematic/client.d.ts +2 -2
  119. package/dist/workspace/schematic/client.d.ts.map +1 -1
  120. package/dist/workspace/schematic/payload.d.ts +10 -11
  121. package/dist/workspace/schematic/payload.d.ts.map +1 -1
  122. package/dist/workspace/table/client.d.ts +2 -2
  123. package/dist/workspace/table/client.d.ts.map +1 -1
  124. package/dist/workspace/table/payload.d.ts +10 -11
  125. package/dist/workspace/table/payload.d.ts.map +1 -1
  126. package/examples/node/package-lock.json +47 -39
  127. package/examples/node/package.json +2 -1
  128. package/examples/node/streamWrite.js +5 -11
  129. package/package.json +14 -13
  130. package/src/access/payload.ts +1 -1
  131. package/src/access/policy/client.ts +87 -32
  132. package/src/access/policy/external.ts +0 -1
  133. package/src/access/policy/payload.ts +4 -4
  134. package/src/access/policy/policy.spec.ts +86 -83
  135. package/src/auth/auth.spec.ts +29 -18
  136. package/src/auth/auth.ts +1 -1
  137. package/src/channel/batchRetriever.spec.ts +4 -9
  138. package/src/channel/channel.spec.ts +24 -6
  139. package/src/channel/client.ts +52 -51
  140. package/src/channel/payload.ts +15 -16
  141. package/src/channel/retriever.ts +26 -41
  142. package/src/channel/writer.ts +7 -4
  143. package/src/client.ts +4 -4
  144. package/src/connection/checker.ts +1 -1
  145. package/src/connection/connection.spec.ts +31 -23
  146. package/src/control/client.ts +2 -2
  147. package/src/control/state.spec.ts +3 -3
  148. package/src/control/state.ts +1 -1
  149. package/src/errors.spec.ts +9 -5
  150. package/src/errors.ts +28 -15
  151. package/src/framer/adapter.spec.ts +118 -9
  152. package/src/framer/adapter.ts +24 -11
  153. package/src/framer/client.spec.ts +125 -2
  154. package/src/framer/client.ts +41 -47
  155. package/src/framer/codec.ts +1 -1
  156. package/src/framer/deleter.spec.ts +2 -2
  157. package/src/framer/deleter.ts +1 -1
  158. package/src/framer/frame.ts +1 -4
  159. package/src/framer/iterator.spec.ts +8 -8
  160. package/src/framer/iterator.ts +1 -1
  161. package/src/framer/streamProxy.ts +1 -1
  162. package/src/framer/streamer.spec.ts +185 -36
  163. package/src/framer/streamer.ts +28 -36
  164. package/src/framer/writer.spec.ts +7 -6
  165. package/src/framer/writer.ts +97 -111
  166. package/src/hardware/device/client.ts +45 -131
  167. package/src/hardware/device/device.spec.ts +163 -52
  168. package/src/hardware/device/payload.ts +10 -21
  169. package/src/hardware/rack/client.ts +87 -105
  170. package/src/hardware/rack/payload.ts +4 -13
  171. package/src/hardware/rack/rack.spec.ts +28 -35
  172. package/src/hardware/task/client.ts +335 -291
  173. package/src/hardware/task/payload.ts +86 -62
  174. package/src/hardware/task/task.spec.ts +208 -32
  175. package/src/index.ts +2 -1
  176. package/src/label/client.ts +100 -95
  177. package/src/label/external.ts +0 -2
  178. package/src/label/label.spec.ts +8 -6
  179. package/src/label/payload.ts +3 -4
  180. package/src/ontology/client.ts +41 -324
  181. package/src/ontology/group/group.spec.ts +2 -2
  182. package/src/ontology/group/group.ts +4 -5
  183. package/src/ontology/group/payload.ts +2 -25
  184. package/src/ontology/group/writer.ts +1 -1
  185. package/src/ontology/ontology.spec.ts +355 -41
  186. package/src/ontology/payload.ts +77 -112
  187. package/src/ontology/writer.ts +8 -17
  188. package/src/ranger/alias.ts +45 -37
  189. package/src/ranger/client.ts +144 -149
  190. package/src/ranger/external.ts +1 -1
  191. package/src/ranger/kv.ts +9 -27
  192. package/src/ranger/payload.ts +23 -37
  193. package/src/ranger/ranger.spec.ts +37 -56
  194. package/src/ranger/writer.ts +1 -1
  195. package/src/{signals/index.ts → testutil/client.ts} +11 -1
  196. package/src/user/client.ts +122 -47
  197. package/src/user/payload.ts +2 -5
  198. package/src/user/retriever.ts +1 -1
  199. package/src/user/user.spec.ts +31 -31
  200. package/src/user/writer.ts +1 -1
  201. package/src/util/decodeJSONString.ts +3 -3
  202. package/src/util/parseWithoutKeyConversion.ts +2 -2
  203. package/src/util/retrieve.ts +1 -1
  204. package/src/util/zod.ts +1 -1
  205. package/src/workspace/client.ts +20 -36
  206. package/src/workspace/lineplot/client.ts +5 -7
  207. package/src/workspace/lineplot/lineplot.spec.ts +2 -2
  208. package/src/workspace/lineplot/payload.ts +4 -7
  209. package/src/workspace/log/client.ts +5 -7
  210. package/src/workspace/log/log.spec.ts +2 -2
  211. package/src/workspace/log/payload.ts +4 -7
  212. package/src/workspace/payload.ts +4 -7
  213. package/src/workspace/schematic/client.ts +5 -7
  214. package/src/workspace/schematic/payload.ts +4 -7
  215. package/src/workspace/schematic/schematic.spec.ts +2 -2
  216. package/src/workspace/table/client.ts +5 -7
  217. package/src/workspace/table/payload.ts +4 -7
  218. package/src/workspace/table/table.spec.ts +2 -2
  219. package/src/workspace/workspace.spec.ts +2 -2
  220. package/dist/access/policy/ontology.d.ts +0 -5
  221. package/dist/access/policy/ontology.d.ts.map +0 -1
  222. package/dist/access/policy/retriever.d.ts +0 -40
  223. package/dist/access/policy/retriever.d.ts.map +0 -1
  224. package/dist/access/policy/writer.d.ts +0 -9
  225. package/dist/access/policy/writer.d.ts.map +0 -1
  226. package/dist/label/retriever.d.ts +0 -14
  227. package/dist/label/retriever.d.ts.map +0 -1
  228. package/dist/label/writer.d.ts +0 -54
  229. package/dist/label/writer.d.ts.map +0 -1
  230. package/dist/setupspecs.d.ts +0 -5
  231. package/dist/setupspecs.d.ts.map +0 -1
  232. package/dist/signals/external.d.ts +0 -2
  233. package/dist/signals/external.d.ts.map +0 -1
  234. package/dist/signals/index.d.ts +0 -2
  235. package/dist/signals/index.d.ts.map +0 -1
  236. package/dist/signals/observable.d.ts +0 -12
  237. package/dist/signals/observable.d.ts.map +0 -1
  238. package/src/access/policy/ontology.ts +0 -17
  239. package/src/access/policy/retriever.ts +0 -44
  240. package/src/access/policy/writer.ts +0 -65
  241. package/src/label/retriever.ts +0 -63
  242. package/src/label/writer.ts +0 -95
  243. package/src/setupspecs.ts +0 -27
  244. package/src/signals/external.ts +0 -10
  245. package/src/signals/observable.ts +0 -42
@@ -10,13 +10,118 @@
10
10
  import { describe, expect, it, test } from "vitest";
11
11
 
12
12
  import { ontology } from "@/ontology";
13
- import { newClient } from "@/setupspecs";
13
+ import { createTestClient } from "@/testutil/client";
14
14
 
15
- const client = newClient();
15
+ const client = createTestClient();
16
16
 
17
17
  const randomName = (): string => `group-${Math.random()}`;
18
18
 
19
19
  describe("Ontology", () => {
20
+ describe("parseIDs", () => {
21
+ it("should parse a single ID object", () => {
22
+ const id: ontology.ID = { type: "group", key: "test-key" };
23
+ const result = ontology.parseIDs(id);
24
+ expect(result).toEqual([id]);
25
+ });
26
+
27
+ it("should parse an array of ID objects", () => {
28
+ const ids: ontology.ID[] = [
29
+ { type: "group", key: "test-key-1" },
30
+ { type: "channel", key: "test-key-2" },
31
+ ];
32
+ const result = ontology.parseIDs(ids);
33
+ expect(result).toEqual(ids);
34
+ });
35
+
36
+ it("should parse a single string ID", () => {
37
+ const stringId = "group:test-key";
38
+ const result = ontology.parseIDs(stringId);
39
+ expect(result).toEqual([{ type: "group", key: "test-key" }]);
40
+ });
41
+
42
+ it("should parse an array of string IDs", () => {
43
+ const stringIds = ["group:test-key-1", "channel:test-key-2"];
44
+ const result = ontology.parseIDs(stringIds);
45
+ expect(result).toEqual([
46
+ { type: "group", key: "test-key-1" },
47
+ { type: "channel", key: "test-key-2" },
48
+ ]);
49
+ });
50
+
51
+ it("should extract ID from a single Resource object", () => {
52
+ const resource: ontology.Resource = {
53
+ id: { type: "group", key: "test-key" },
54
+ name: "Test Resource",
55
+ key: "group:test-key",
56
+ };
57
+ const result = ontology.parseIDs(resource);
58
+ expect(result).toEqual([{ type: "group", key: "test-key" }]);
59
+ });
60
+
61
+ it("should extract IDs from an array of Resource objects", () => {
62
+ const resources: ontology.Resource[] = [
63
+ {
64
+ id: { type: "group", key: "test-key-1" },
65
+ name: "Test Resource 1",
66
+ key: "group:test-key-1",
67
+ },
68
+ {
69
+ id: { type: "channel", key: "test-key-2" },
70
+ name: "Test Resource 2",
71
+ key: "channel:test-key-2",
72
+ },
73
+ ];
74
+ const result = ontology.parseIDs(resources);
75
+ expect(result).toEqual([
76
+ { type: "group", key: "test-key-1" },
77
+ { type: "channel", key: "test-key-2" },
78
+ ]);
79
+ });
80
+
81
+ it("should return empty array for empty input", () => {
82
+ const result = ontology.parseIDs([]);
83
+ expect(result).toEqual([]);
84
+ });
85
+
86
+ it("should handle string IDs with colons in the key", () => {
87
+ const stringId = "group:test:key:with:colons";
88
+ const result = ontology.parseIDs(stringId);
89
+ expect(result).toEqual([{ type: "group", key: "test" }]);
90
+ });
91
+
92
+ it("should handle mixed Resource objects with different data types", () => {
93
+ const resources: ontology.Resource[] = [
94
+ {
95
+ id: { type: "group", key: "test-key-1" },
96
+ name: "Test Resource 1",
97
+ key: "group:test-key-1",
98
+ data: { customField: "value" },
99
+ },
100
+ {
101
+ id: { type: "channel", key: "test-key-2" },
102
+ name: "Test Resource 2",
103
+ key: "channel:test-key-2",
104
+ data: null,
105
+ },
106
+ ];
107
+ const result = ontology.parseIDs(resources);
108
+ expect(result).toEqual([
109
+ { type: "group", key: "test-key-1" },
110
+ { type: "channel", key: "test-key-2" },
111
+ ]);
112
+ });
113
+
114
+ it("should throw an error for invalid string ID format", () => {
115
+ const invalidStringId = "invalid-format";
116
+ expect(() => ontology.parseIDs(invalidStringId)).toThrow();
117
+ });
118
+
119
+ it("should throw an error for invalid resource type in string ID", () => {
120
+ const invalidStringId = "invalid-type:test-key";
121
+ expect(() => ontology.parseIDs(invalidStringId)).toThrow();
122
+ });
123
+ });
124
+
20
125
  describe("retrieve", () => {
21
126
  test("retrieve", async () => {
22
127
  const name = randomName();
@@ -43,20 +148,6 @@ describe("Ontology", () => {
43
148
  expect(parents[0].name).toEqual(name);
44
149
  });
45
150
  });
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_ID, 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
151
  describe("write", () => {
61
152
  test("add children", async () => {
62
153
  const name = randomName();
@@ -93,36 +184,259 @@ describe("Ontology", () => {
93
184
  expect(newRootLength).toEqual(oldRootLength - 1);
94
185
  });
95
186
  });
187
+
96
188
  describe("signals", async () => {
97
189
  it("should correctly decode a set of relationships from a string", () => {
98
- const rel = ontology.parseRelationship("typeA:keyA->parent->typeB:keyB");
99
- expect(rel.type).toEqual("parent");
100
- expect(rel.from.type).toEqual("typeA");
190
+ const rel = ontology.relationshipZ.parse("table:keyA->parent->schematic:keyB");
191
+ expect(rel.type).toEqual(ontology.PARENT_OF_RELATIONSHIP_TYPE);
192
+ expect(rel.from.type).toEqual("table");
101
193
  expect(rel.from.key).toEqual("keyA");
102
- expect(rel.to.type).toEqual("typeB");
194
+ expect(rel.to.type).toEqual("schematic");
103
195
  expect(rel.to.key).toEqual("keyB");
104
196
  });
105
- it("should correctly propagate resource changes to the ontology", async () => {
106
- const change = await client.ontology.openChangeTracker();
107
- const p = new Promise<ontology.ResourceChange[]>((resolve) =>
108
- change.resources.onChange((changes) => resolve(changes)),
109
- );
110
- await client.ontology.groups.create(ontology.ROOT_ID, randomName());
111
- const c = await p;
112
- expect(c.length).toBeGreaterThan(0);
113
- await change.close();
114
- });
115
- it("should correctly propagate relationship changes to the ontology", async () => {
116
- const change = await client.ontology.openChangeTracker();
117
- const p = new Promise<ontology.RelationshipChange[]>((resolve) => {
118
- change.relationships.onChange((changes) => {
119
- resolve(changes);
120
- });
121
- });
122
- await client.ontology.groups.create(ontology.ROOT_ID, randomName());
123
- const c = await p;
124
- expect(c.length).toBeGreaterThan(0);
125
- await change.close();
197
+ });
198
+
199
+ describe("matchRelationship", () => {
200
+ const sampleRelationship: ontology.Relationship = {
201
+ from: { type: "group", key: "test-group" },
202
+ type: "parent",
203
+ to: { type: "channel", key: "test-channel" },
204
+ };
205
+
206
+ describe("type matching", () => {
207
+ it("should return true when types match", () => {
208
+ const match: ontology.MatchRelationshipArgs = { type: "parent" };
209
+ const result = ontology.matchRelationship(sampleRelationship, match);
210
+ expect(result).toBe(true);
211
+ });
212
+
213
+ it("should return false when types don't match", () => {
214
+ const match: ontology.MatchRelationshipArgs = { type: "child" };
215
+ const result = ontology.matchRelationship(sampleRelationship, match);
216
+ expect(result).toBe(false);
217
+ });
218
+ });
219
+
220
+ describe("from ID matching", () => {
221
+ it("should return true when from type matches", () => {
222
+ const match: ontology.MatchRelationshipArgs = {
223
+ type: "parent",
224
+ from: { type: "group" },
225
+ };
226
+ const result = ontology.matchRelationship(sampleRelationship, match);
227
+ expect(result).toBe(true);
228
+ });
229
+
230
+ it("should return true when from key matches", () => {
231
+ const match: ontology.MatchRelationshipArgs = {
232
+ type: "parent",
233
+ from: { key: "test-group" },
234
+ };
235
+ const result = ontology.matchRelationship(sampleRelationship, match);
236
+ expect(result).toBe(true);
237
+ });
238
+
239
+ it("should return true when both from type and key match", () => {
240
+ const match: ontology.MatchRelationshipArgs = {
241
+ type: "parent",
242
+ from: { type: "group", key: "test-group" },
243
+ };
244
+ const result = ontology.matchRelationship(sampleRelationship, match);
245
+ expect(result).toBe(true);
246
+ });
247
+
248
+ it("should return false when from type doesn't match", () => {
249
+ const match: ontology.MatchRelationshipArgs = {
250
+ type: "parent",
251
+ from: { type: "channel" },
252
+ };
253
+ const result = ontology.matchRelationship(sampleRelationship, match);
254
+ expect(result).toBe(false);
255
+ });
256
+
257
+ it("should return false when from key doesn't match", () => {
258
+ const match: ontology.MatchRelationshipArgs = {
259
+ type: "parent",
260
+ from: { key: "wrong-key" },
261
+ };
262
+ const result = ontology.matchRelationship(sampleRelationship, match);
263
+ expect(result).toBe(false);
264
+ });
265
+
266
+ it("should return false when from type matches but key doesn't", () => {
267
+ const match: ontology.MatchRelationshipArgs = {
268
+ type: "parent",
269
+ from: { type: "group", key: "wrong-key" },
270
+ };
271
+ const result = ontology.matchRelationship(sampleRelationship, match);
272
+ expect(result).toBe(false);
273
+ });
274
+ });
275
+
276
+ describe("to ID matching", () => {
277
+ it("should return true when to type matches", () => {
278
+ const match: ontology.MatchRelationshipArgs = {
279
+ type: "parent",
280
+ to: { type: "channel" },
281
+ };
282
+ const result = ontology.matchRelationship(sampleRelationship, match);
283
+ expect(result).toBe(true);
284
+ });
285
+
286
+ it("should return true when to key matches", () => {
287
+ const match: ontology.MatchRelationshipArgs = {
288
+ type: "parent",
289
+ to: { key: "test-channel" },
290
+ };
291
+ const result = ontology.matchRelationship(sampleRelationship, match);
292
+ expect(result).toBe(true);
293
+ });
294
+
295
+ it("should return true when both to type and key match", () => {
296
+ const match: ontology.MatchRelationshipArgs = {
297
+ type: "parent",
298
+ to: { type: "channel", key: "test-channel" },
299
+ };
300
+ const result = ontology.matchRelationship(sampleRelationship, match);
301
+ expect(result).toBe(true);
302
+ });
303
+
304
+ it("should return false when to type doesn't match", () => {
305
+ const match: ontology.MatchRelationshipArgs = {
306
+ type: "parent",
307
+ to: { type: "group" },
308
+ };
309
+ const result = ontology.matchRelationship(sampleRelationship, match);
310
+ expect(result).toBe(false);
311
+ });
312
+
313
+ it("should return false when to key doesn't match", () => {
314
+ const match = { type: "parent", to: { key: "wrong-key" } };
315
+ const result = ontology.matchRelationship(sampleRelationship, match);
316
+ expect(result).toBe(false);
317
+ });
318
+
319
+ it("should return false when to type matches but key doesn't", () => {
320
+ const match: ontology.MatchRelationshipArgs = {
321
+ type: "parent",
322
+ to: { type: "channel", key: "wrong-key" },
323
+ };
324
+ const result = ontology.matchRelationship(sampleRelationship, match);
325
+ expect(result).toBe(false);
326
+ });
327
+ });
328
+
329
+ describe("combined matching", () => {
330
+ it("should return true when all specified criteria match", () => {
331
+ const match: ontology.MatchRelationshipArgs = {
332
+ type: "parent",
333
+ from: { type: "group", key: "test-group" },
334
+ to: { type: "channel", key: "test-channel" },
335
+ };
336
+ const result = ontology.matchRelationship(sampleRelationship, match);
337
+ expect(result).toBe(true);
338
+ });
339
+
340
+ it("should return false when type matches but from doesn't", () => {
341
+ const match: ontology.MatchRelationshipArgs = {
342
+ type: "parent",
343
+ from: { type: "channel" },
344
+ to: { type: "channel", key: "test-channel" },
345
+ };
346
+ const result = ontology.matchRelationship(sampleRelationship, match);
347
+ expect(result).toBe(false);
348
+ });
349
+
350
+ it("should return false when type matches but to doesn't", () => {
351
+ const match: ontology.MatchRelationshipArgs = {
352
+ type: "parent",
353
+ from: { type: "group", key: "test-group" },
354
+ to: { type: "group" },
355
+ };
356
+ const result = ontology.matchRelationship(sampleRelationship, match);
357
+ expect(result).toBe(false);
358
+ });
359
+
360
+ it("should return false when from and to match but type doesn't", () => {
361
+ const match: ontology.MatchRelationshipArgs = {
362
+ type: "child",
363
+ from: { type: "group", key: "test-group" },
364
+ to: { type: "channel", key: "test-channel" },
365
+ };
366
+ const result = ontology.matchRelationship(sampleRelationship, match);
367
+ expect(result).toBe(false);
368
+ });
369
+ });
370
+
371
+ describe("partial matching", () => {
372
+ it("should return true when only type is specified and matches", () => {
373
+ const match: ontology.MatchRelationshipArgs = { type: "parent" };
374
+ const result = ontology.matchRelationship(sampleRelationship, match);
375
+ expect(result).toBe(true);
376
+ });
377
+
378
+ it("should return true when only from type is specified and matches", () => {
379
+ const match: ontology.MatchRelationshipArgs = {
380
+ type: "parent",
381
+ from: { type: "group" },
382
+ };
383
+ const result = ontology.matchRelationship(sampleRelationship, match);
384
+ expect(result).toBe(true);
385
+ });
386
+
387
+ it("should return true when only to type is specified and matches", () => {
388
+ const match: ontology.MatchRelationshipArgs = {
389
+ type: "parent",
390
+ to: { type: "channel" },
391
+ };
392
+ const result = ontology.matchRelationship(sampleRelationship, match);
393
+ expect(result).toBe(true);
394
+ });
395
+
396
+ it("should return true when only from key is specified and matches", () => {
397
+ const match: ontology.MatchRelationshipArgs = {
398
+ type: "parent",
399
+ from: { key: "test-group" },
400
+ };
401
+ const result = ontology.matchRelationship(sampleRelationship, match);
402
+ expect(result).toBe(true);
403
+ });
404
+
405
+ it("should return true when only to key is specified and matches", () => {
406
+ const match: ontology.MatchRelationshipArgs = {
407
+ type: "parent",
408
+ to: { key: "test-channel" },
409
+ };
410
+ const result = ontology.matchRelationship(sampleRelationship, match);
411
+ expect(result).toBe(true);
412
+ });
413
+ });
414
+
415
+ describe("edge cases", () => {
416
+ it("should handle empty from and to objects", () => {
417
+ const match: ontology.MatchRelationshipArgs = {
418
+ type: "parent",
419
+ from: {},
420
+ to: {},
421
+ };
422
+ const result = ontology.matchRelationship(sampleRelationship, match);
423
+ expect(result).toBe(true);
424
+ });
425
+
426
+ it("should handle relationships with empty keys", () => {
427
+ const relationship: ontology.Relationship = {
428
+ from: { type: "group", key: "" },
429
+ type: "parent",
430
+ to: { type: "channel", key: "" },
431
+ };
432
+ const match: ontology.MatchRelationshipArgs = {
433
+ type: "parent",
434
+ from: { key: "" },
435
+ to: { key: "" },
436
+ };
437
+ const result = ontology.matchRelationship(relationship, match);
438
+ expect(result).toBe(true);
439
+ });
126
440
  });
127
441
  });
128
442
  });
@@ -7,30 +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 { type change, type UnknownRecord, unknownRecordZ } from "@synnaxlabs/x";
11
- import { z } from "zod/v4";
12
-
13
- import {
14
- ALLOW_ALL_ONTOLOGY_TYPE as ALLOW_ALL_TYPE,
15
- ONTOLOGY_TYPE as POLICY_TYPE,
16
- } from "@/access/policy/ontology";
17
- import { ONTOLOGY_TYPE as CHANNEL_TYPE } from "@/channel/payload";
18
- import { ONTOLOGY_TYPE as FRAMER_TYPE } from "@/framer/frame";
19
- import { ONTOLOGY_TYPE as DEVICE_TYPE } from "@/hardware/device/payload";
20
- import { ONTOLOGY_TYPE as RACK_TYPE } from "@/hardware/rack/payload";
21
- import { ONTOLOGY_TYPE as TASK_TYPE } from "@/hardware/task/payload";
22
- import { ONTOLOGY_TYPE as LABEL_TYPE } from "@/label/payload";
23
- import { ONTOLOGY_TYPE as GROUP_TYPE } from "@/ontology/group/payload";
24
- import {
25
- ALIAS_ONTOLOGY_TYPE as RANGE_ALIAS_TYPE,
26
- ONTOLOGY_TYPE as RANGE_TYPE,
27
- } from "@/ranger/payload";
28
- import { ONTOLOGY_TYPE as USER_TYPE } from "@/user/payload";
29
- import { ONTOLOGY_TYPE as LINE_PLOT_TYPE } from "@/workspace/lineplot/payload";
30
- import { ONTOLOGY_TYPE as LOG_TYPE } from "@/workspace/log/payload";
31
- import { ONTOLOGY_TYPE as WORKSPACE_TYPE } from "@/workspace/payload";
32
- import { ONTOLOGY_TYPE as SCHEMATIC_TYPE } from "@/workspace/schematic/payload";
33
- import { ONTOLOGY_TYPE as TABLE_TYPE } from "@/workspace/table/payload";
10
+ import { array, type change, record } from "@synnaxlabs/x";
11
+ import { z } from "zod";
34
12
 
35
13
  export type ResourceChange = change.Change<ID, Resource>;
36
14
  export interface ResourceSet extends change.Set<ID, Resource> {}
@@ -44,113 +22,100 @@ export const CLUSTER_TYPE = "cluster";
44
22
  export const NODE_TYPE = "node";
45
23
 
46
24
  export const resourceTypeZ = z.enum([
47
- LABEL_TYPE,
48
- LOG_TYPE,
49
- ALLOW_ALL_TYPE,
50
- BUILTIN_TYPE,
51
- CLUSTER_TYPE,
52
- CHANNEL_TYPE,
53
- NODE_TYPE,
54
- GROUP_TYPE,
55
- RANGE_TYPE,
56
- FRAMER_TYPE,
57
- RANGE_ALIAS_TYPE,
58
- USER_TYPE,
59
- WORKSPACE_TYPE,
60
- SCHEMATIC_TYPE,
61
- LINE_PLOT_TYPE,
62
- RACK_TYPE,
63
- DEVICE_TYPE,
64
- TASK_TYPE,
65
- POLICY_TYPE,
66
- TABLE_TYPE,
25
+ "label",
26
+ "log",
27
+ "allow_all",
28
+ "builtin",
29
+ "cluster",
30
+ "channel",
31
+ "node",
32
+ "group",
33
+ "range",
34
+ "framer",
35
+ "range-alias",
36
+ "user",
37
+ "workspace",
38
+ "schematic",
39
+ "lineplot",
40
+ "rack",
41
+ "device",
42
+ "task",
43
+ "policy",
44
+ "table",
67
45
  ]);
68
46
  export type ResourceType = z.infer<typeof resourceTypeZ>;
69
47
 
70
- export const idZ = z.object({ type: resourceTypeZ, key: z.string() });
71
- export interface IDPayload extends z.infer<typeof idZ> {}
72
-
73
- export const stringIDZ = z.string().transform((v) => {
48
+ const stringIDZ = z.string().transform((v) => {
74
49
  const [type, key] = v.split(":");
75
50
  return { type: resourceTypeZ.parse(type), key: key ?? "" };
76
51
  });
77
52
 
78
- export const crudeIDZ = z.union([stringIDZ, idZ]);
79
- export type CrudeID = z.input<typeof crudeIDZ>;
80
-
81
- export class ID {
82
- type: ResourceType;
83
- key: string;
84
-
85
- constructor(args: z.input<typeof crudeIDZ> | ID) {
86
- if (args instanceof ID) {
87
- this.type = args.type;
88
- this.key = args.key;
89
- return;
90
- }
91
- if (typeof args === "string") {
92
- const [type, key] = args.split(":");
93
- this.type = type as ResourceType;
94
- this.key = key ?? "";
95
- return;
96
- }
97
- this.type = args.type;
98
- this.key = args.key;
99
- }
100
-
101
- toString(): string {
102
- return `${this.type}:${this.key}`;
103
- }
104
-
105
- isType(): boolean {
106
- return this.key === "";
107
- }
108
-
109
- matchesType(type: ResourceType): boolean {
110
- return this.type === type && this.isType();
111
- }
112
-
113
- get payload(): IDPayload {
114
- return { type: this.type, key: this.key };
115
- }
116
-
117
- static readonly z = z.union([z.instanceof(ID), crudeIDZ.transform((v) => new ID(v))]);
118
- }
53
+ export const idZ = z.object({ type: resourceTypeZ, key: z.string() }).or(stringIDZ);
119
54
 
120
- export const ROOT_ID = new ID({ type: BUILTIN_TYPE, key: "root" });
55
+ export type ID = z.infer<typeof idZ>;
121
56
 
122
- export const schemaFieldZ = z.object({ type: z.number() });
123
- export interface SchemaField extends z.infer<typeof schemaFieldZ> {}
57
+ export const ROOT_ID: ID = { type: BUILTIN_TYPE, key: "root" };
124
58
 
125
- export const schemaZ = z.object({
126
- type: resourceTypeZ,
127
- fields: z.record(z.string(), schemaFieldZ),
128
- });
129
- export interface Schema extends z.infer<typeof schemaZ> {}
59
+ export const idToString = (id: ID) => `${id.type}:${id.key}`;
60
+
61
+ export const idsEqual = (a: ID, b: ID) => a.type === b.type && a.key === b.key;
62
+
63
+ export const parseIDs = (
64
+ ids: ID | ID[] | string | string[] | Resource | Resource[],
65
+ ): ID[] => {
66
+ const arr = array.toArray(ids);
67
+ if (arr.length === 0) return [];
68
+ if (typeof arr[0] === "object" && "id" in arr[0])
69
+ return (arr as Resource[]).map(({ id }) => id);
70
+ return arr.map((id) => idZ.parse(id));
71
+ };
130
72
 
131
73
  export const resourceZ = z
132
74
  .object({
133
- id: ID.z,
75
+ id: idZ,
134
76
  name: z.string(),
135
- schema: schemaZ.optional().nullable(),
136
- data: unknownRecordZ.optional().nullable(),
77
+ data: record.unknownZ.optional().nullable(),
137
78
  })
138
- .transform((resource) => ({ key: resource.id.toString(), ...resource }));
139
- export interface Resource<T extends UnknownRecord = UnknownRecord>
79
+ .transform((resource) => ({ key: idToString(resource.id), ...resource }));
80
+ export interface Resource<T extends record.Unknown = record.Unknown>
140
81
  extends Omit<z.infer<typeof resourceZ>, "data"> {
141
82
  data?: T | null;
142
83
  }
143
84
 
144
- export const TO_RELATIONSHIP_DIRECTION = "to";
145
- export const FROM_RELATIONSHIP_DIRECTION = "from";
146
- export type RelationshipDirection =
147
- | typeof TO_RELATIONSHIP_DIRECTION
148
- | typeof FROM_RELATIONSHIP_DIRECTION;
85
+ export type RelationshipDirection = "to" | "from";
149
86
 
150
- export const relationshipSchemaZ = z.object({ from: ID.z, type: z.string(), to: ID.z });
151
- export interface Relationship extends z.infer<typeof relationshipSchemaZ> {}
87
+ export const oppositeRelationshipDirection = (
88
+ direction: RelationshipDirection,
89
+ ): RelationshipDirection => (direction === "to" ? "from" : "to");
90
+
91
+ export const relationshipZ = z.object({ from: idZ, type: z.string(), to: idZ }).or(
92
+ z.string().transform((v) => {
93
+ const [from, type, to] = v.split("->");
94
+ return { from: idZ.parse(from), type, to: idZ.parse(to) };
95
+ }),
96
+ );
97
+ export type Relationship = z.infer<typeof relationshipZ>;
98
+
99
+ export const relationshipToString = (relationship: Relationship) =>
100
+ `${idToString(relationship.from)}->${relationship.type}->${idToString(relationship.to)}`;
101
+
102
+ export const PARENT_OF_RELATIONSHIP_TYPE = "parent";
103
+
104
+ export interface MatchRelationshipArgs {
105
+ from?: Partial<ID>;
106
+ type: string;
107
+ to?: Partial<ID>;
108
+ }
152
109
 
153
- export const parseRelationship = (str: string): Relationship => {
154
- const [from, type, to] = str.split("->");
155
- return { from: new ID(from), type, to: new ID(to) };
110
+ export const matchRelationship = (
111
+ relationship: Relationship,
112
+ match: MatchRelationshipArgs,
113
+ ) => {
114
+ if (match.type != null && match.type !== relationship.type) return false;
115
+ if (match.from?.type != null && match.from.type !== relationship.from.type)
116
+ return false;
117
+ if (match.to?.type != null && match.to.type !== relationship.to.type) return false;
118
+ if (match.from?.key != null && match.from.key !== relationship.from.key) return false;
119
+ if (match.to?.key != null && match.to.key !== relationship.to.key) return false;
120
+ return true;
156
121
  };