@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
package/src/auth/auth.ts CHANGED
@@ -10,21 +10,9 @@
10
10
  import type { Middleware, UnaryClient } from "@synnaxlabs/freighter";
11
11
  import { z } from "zod";
12
12
 
13
- import { AuthError } from "@/errors";
13
+ import { InvalidTokenError } from "@/errors";
14
14
  import { user } from "@/user";
15
15
 
16
- export const tokenMiddleware =
17
- (token: () => Promise<string>): Middleware =>
18
- async (md, next) => {
19
- try {
20
- const tk = await token();
21
- md.params.Authorization = `Bearer ${tk}`;
22
- } catch (err) {
23
- return [md, err as Error];
24
- }
25
- return await next(md);
26
- };
27
-
28
16
  export const insecureCredentialsZ = z.object({
29
17
  username: z.string(),
30
18
  password: z.string(),
@@ -40,45 +28,56 @@ export type TokenResponse = z.infer<typeof tokenResponseZ>;
40
28
 
41
29
  const LOGIN_ENDPOINT = "/auth/login";
42
30
 
31
+ const MAX_RETRIES = 3;
32
+
43
33
  export class Client {
44
- private token: string | undefined;
34
+ token: string | undefined;
45
35
  private readonly client: UnaryClient;
46
- authenticating: Promise<void> | undefined;
36
+ private readonly credentials: InsecureCredentials;
37
+ private authenticating: Promise<Error | null> | undefined;
47
38
  authenticated: boolean;
48
39
  user: user.Payload | undefined;
40
+ private retryCount: number;
49
41
 
50
42
  constructor(client: UnaryClient, credentials: InsecureCredentials) {
51
43
  this.client = client;
52
44
  this.authenticated = false;
53
- this.authenticate(credentials);
54
- }
55
-
56
- authenticate(credentials: InsecureCredentials): void {
57
- this.authenticating = new Promise((resolve, reject) => {
58
- this.client
59
- .send<typeof insecureCredentialsZ, typeof tokenResponseZ>(
60
- LOGIN_ENDPOINT,
61
- credentials,
62
- insecureCredentialsZ,
63
- tokenResponseZ,
64
- )
65
- .then(([res, err]) => {
66
- if (err != null) return reject(err);
67
- this.token = res?.token;
68
- this.user = res?.user;
69
- this.authenticated = true;
70
- resolve();
71
- })
72
- .catch(reject);
73
- });
45
+ this.credentials = credentials;
46
+ this.retryCount = 0;
74
47
  }
75
48
 
76
49
  middleware(): Middleware {
77
- return tokenMiddleware(async () => {
78
- if (!this.authenticated) await this.authenticating;
79
- if (this.token == null)
80
- throw new AuthError("[auth] - attempting to authenticate without a token");
81
- return this.token;
82
- });
50
+ const mw: Middleware = async (reqCtx, next) => {
51
+ if (!this.authenticated && !reqCtx.target.endsWith(LOGIN_ENDPOINT)) {
52
+ if (this.authenticating == null)
53
+ this.authenticating = new Promise(async (resolve) => {
54
+ const [res, err] = await this.client.send(
55
+ LOGIN_ENDPOINT,
56
+
57
+ this.credentials,
58
+ insecureCredentialsZ,
59
+ tokenResponseZ,
60
+ );
61
+ if (err != null) return resolve(err);
62
+ this.token = res?.token;
63
+ this.user = res?.user;
64
+ this.authenticated = true;
65
+ resolve(null);
66
+ });
67
+ const err = await this.authenticating;
68
+ if (err != null) return [reqCtx, err];
69
+ }
70
+ reqCtx.params.Authorization = `Bearer ${this.token}`;
71
+ const [resCtx, err] = await next(reqCtx);
72
+ if (err instanceof InvalidTokenError && this.retryCount < MAX_RETRIES) {
73
+ this.authenticated = false;
74
+ this.authenticating = undefined;
75
+ this.retryCount += 1;
76
+ return mw(reqCtx, next);
77
+ }
78
+ this.retryCount = 0;
79
+ return [resCtx, err];
80
+ };
81
+ return mw;
83
82
  }
84
83
  }
@@ -1,51 +1,51 @@
1
+ import { DataType, Rate } from "@synnaxlabs/x/telem";
1
2
  import { describe, expect, it, vi } from "vitest";
2
- import { newClient } from "@/setupspecs";
3
- import { DebouncedBatchRetriever, Retriever, analyzeParams } from "@/channel/retriever";
4
- import { Params, Payload } from "@/channel/payload";
5
- import { DataType, Rate } from "@synnaxlabs/x";
6
-
7
3
 
4
+ import { type Params, type Payload } from "@/channel/payload";
5
+ import {
6
+ DebouncedBatchRetriever,
7
+ type Retriever,
8
+ type RetrieveOptions,
9
+ analyzeChannelParams,
10
+ } from "@/channel/retriever";
8
11
 
9
12
  class MockRetriever implements Retriever {
10
- func: (channels: Params, rangeKey?: string) => Promise<Payload[]>;
13
+ func: (channels: Params, options?: RetrieveOptions) => Promise<Payload[]>;
11
14
 
12
- constructor(func: (channels: Params, rangeKey?: string) => Promise<Payload[]>) {
15
+ constructor(
16
+ func: (channels: Params, options?: RetrieveOptions) => Promise<Payload[]>,
17
+ ) {
13
18
  this.func = func;
14
19
  }
15
20
 
16
- async search(term: string, rangeKey?: string): Promise<Payload[]> {
21
+ async search(term: string): Promise<Payload[]> {
17
22
  throw new Error("Method not implemented.");
18
23
  }
19
24
 
20
- async page(offset: number, limit: number, rangeKey?: string): Promise<Payload[]> {
25
+ async page(offset: number, limit: number): Promise<Payload[]> {
21
26
  throw new Error("Method not implemented.");
22
27
  }
23
28
 
24
- async retrieve(channels: Params, rangeKey?: string): Promise<Payload[]> {
25
- return this.func(channels, rangeKey);
29
+ async retrieve(channels: Params, options?: RetrieveOptions): Promise<Payload[]> {
30
+ return await this.func(channels, options);
26
31
  }
27
-
28
32
  }
29
33
 
30
-
31
34
  describe("channelRetriever", () => {
32
35
  it("should batch multiple retrieve requests", async () => {
33
36
  const called = vi.fn();
34
37
  const base = new MockRetriever(async (batch): Promise<Payload[]> => {
35
38
  called(batch);
36
- const {normalized} = analyzeParams(batch);
37
- return normalized.map(
38
- (key) =>
39
- ({
40
- key: key as number,
41
- name: `channel-${key}`,
42
- dataType: DataType.FLOAT32,
43
- isIndex: false,
44
- rate: Rate.hz(1),
45
- leaseholder: 1,
46
- index:0
47
- }),
48
- );
39
+ const { normalized } = analyzeChannelParams(batch);
40
+ return normalized.map((key) => ({
41
+ key: key as number,
42
+ name: `channel-${key}`,
43
+ dataType: DataType.FLOAT32,
44
+ isIndex: false,
45
+ rate: Rate.hz(1),
46
+ leaseholder: 1,
47
+ index: 0,
48
+ }));
49
49
  });
50
50
  const retriever = new DebouncedBatchRetriever(base, 10);
51
51
  const res = await Promise.all([
@@ -61,20 +61,17 @@ describe("channelRetriever", () => {
61
61
  const called = vi.fn();
62
62
  const base = new MockRetriever(async (batch): Promise<Payload[]> => {
63
63
  called(batch);
64
- const {normalized} = analyzeParams(batch);
65
- return normalized.map(
66
- (key) =>
67
- ({
68
- key: key as number,
69
- name: `channel-${key}`,
70
- dataType: DataType.FLOAT32,
71
- isIndex: false,
72
- rate: Rate.hz(1),
73
- leaseholder: 1,
74
- index:0
75
- }),
76
- );
77
- })
64
+ const { normalized } = analyzeChannelParams(batch);
65
+ return normalized.map((key) => ({
66
+ key: key as number,
67
+ name: `channel-${key}`,
68
+ dataType: DataType.FLOAT32,
69
+ isIndex: false,
70
+ rate: Rate.hz(1),
71
+ leaseholder: 1,
72
+ index: 0,
73
+ }));
74
+ });
78
75
  const retriever = new DebouncedBatchRetriever(base, 10);
79
76
  const res = await Promise.all([
80
77
  retriever.retrieve([1]),
@@ -7,11 +7,11 @@
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 { DataType, Rate, TimeStamp } from "@synnaxlabs/x";
11
- import { describe, test, expect, it } from "vitest";
10
+ import { DataType, Rate, TimeStamp } from "@synnaxlabs/x/telem";
11
+ import { describe, expect, test, it } from "vitest";
12
12
 
13
13
  import { Channel } from "@/channel/client";
14
- import { QueryError } from "@/errors";
14
+ import { NotFoundError, QueryError } from "@/errors";
15
15
  import { newClient } from "@/setupspecs";
16
16
 
17
17
  const client = newClient();
@@ -185,7 +185,7 @@ describe("Channel", () => {
185
185
  });
186
186
  test("retrieve by key - not found", async () => {
187
187
  await expect(async () => await client.channels.retrieve("1-1000")).rejects.toThrow(
188
- QueryError,
188
+ NotFoundError,
189
189
  );
190
190
  });
191
191
  test("retrieve by name", async () => {
@@ -8,17 +8,17 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  import { type UnaryClient } from "@synnaxlabs/freighter";
11
+ import { type AsyncTermSearcher } from "@synnaxlabs/x/search";
11
12
  import {
12
13
  DataType,
13
14
  Rate,
14
15
  type TypedArray,
15
16
  type CrudeDensity,
16
17
  type TimeRange,
17
- type AsyncTermSearcher,
18
- toArray,
19
18
  type CrudeTimeStamp,
20
19
  type MultiSeries,
21
- } from "@synnaxlabs/x";
20
+ } from "@synnaxlabs/x/telem";
21
+ import { toArray } from "@synnaxlabs/x/toArray";
22
22
 
23
23
  import {
24
24
  type Key,
@@ -29,15 +29,18 @@ import {
29
29
  type NewPayload,
30
30
  } from "@/channel/payload";
31
31
  import {
32
- analyzeParams,
32
+ analyzeChannelParams,
33
33
  CacheRetriever,
34
34
  ClusterRetriever,
35
35
  DebouncedBatchRetriever,
36
36
  type Retriever,
37
+ type PageOptions,
38
+ type RetrieveOptions,
37
39
  } from "@/channel/retriever";
38
40
  import { type Writer } from "@/channel/writer";
39
- import { MultipleFoundError, NotFoundError, ValidationError } from "@/errors";
41
+ import { MultipleFoundError, ValidationError } from "@/errors";
40
42
  import { type framer } from "@/framer";
43
+ import { checkForMultipleOrNoResults } from "@/util/retrieve";
41
44
 
42
45
  interface CreateOptions {
43
46
  retrieveIfNameExists?: boolean;
@@ -169,6 +172,7 @@ export class Channel {
169
172
  * through the `channels` property of an {@link Synnax} client.
170
173
  */
171
174
  export class Client implements AsyncTermSearcher<string, Key, Channel> {
175
+ readonly type = "channel";
172
176
  private readonly frameClient: framer.Client;
173
177
  private readonly client: UnaryClient;
174
178
  readonly retriever: Retriever;
@@ -287,7 +291,7 @@ export class Client implements AsyncTermSearcher<string, Key, Channel> {
287
291
  * const channel = await client.channels.retrieve(1);
288
292
  * ```
289
293
  */
290
- async retrieve(channel: KeyOrName, rangeKey?: string): Promise<Channel>;
294
+ async retrieve(channel: KeyOrName, options?: RetrieveOptions): Promise<Channel>;
291
295
 
292
296
  /**
293
297
  * Retrieves multiple channels from the database using the provided keys or the
@@ -301,7 +305,7 @@ export class Client implements AsyncTermSearcher<string, Key, Channel> {
301
305
  * @param options.notDataTypes - Limits the query to only channels without the specified
302
306
  *
303
307
  */
304
- async retrieve(channels: Params, rangeKey?: string): Promise<Channel[]>;
308
+ async retrieve(channels: Params, options?: RetrieveOptions): Promise<Channel[]>;
305
309
 
306
310
  /**
307
311
  * Retrieves a channel from the database using the given parameters.
@@ -310,34 +314,45 @@ export class Client implements AsyncTermSearcher<string, Key, Channel> {
310
314
  * @returns The retrieved channel.
311
315
  * @raises {QueryError} If the channel does not exist or if multiple results are returned.
312
316
  */
313
- async retrieve(channels: Params, rangeKey?: string): Promise<Channel | Channel[]> {
314
- const { single, actual, normalized } = analyzeParams(channels);
315
- if (normalized.length === 0) return [];
316
- const res = this.sugar(await this.retriever.retrieve(channels, rangeKey));
317
- if (!single) return res;
318
- if (res.length === 0)
319
- throw new NotFoundError(`channel matching ${actual} not found`);
320
- if (res.length > 1)
321
- throw new MultipleFoundError(`multiple channels matching ${actual} found`);
322
- return res[0];
317
+ async retrieve(
318
+ channels: Params,
319
+ options?: RetrieveOptions,
320
+ ): Promise<Channel | Channel[]> {
321
+ const isSingle = !Array.isArray(channels);
322
+ const res = this.sugar(await this.retriever.retrieve(channels, options));
323
+ checkForMultipleOrNoResults("channel", channels, res, isSingle);
324
+ return isSingle ? res[0] : res;
323
325
  }
324
326
 
325
- async delete(channels: Params): Promise<void> {
326
- const { normalized, variant } = analyzeParams(channels);
327
- if (variant === "keys") return await this.writer.delete({ keys: normalized });
328
- return await this.writer.delete({ names: normalized });
327
+ async search(term: string, options?: RetrieveOptions): Promise<Channel[]> {
328
+ return this.sugar(await this.retriever.search(term, options));
329
329
  }
330
330
 
331
- async search(term: string, rangeKey?: string): Promise<Channel[]> {
332
- return this.sugar(await this.retriever.search(term, rangeKey));
331
+ async delete(channels: Params): Promise<void> {
332
+ const { normalized, variant } = analyzeChannelParams(channels);
333
+ if (variant === "keys")
334
+ return await this.writer.delete({ keys: normalized as Key[] });
335
+ return await this.writer.delete({ names: normalized as string[] });
333
336
  }
334
337
 
335
- newSearcherUnderRange(rangeKey?: string): AsyncTermSearcher<string, Key, Channel> {
336
- return new SearcherUnderRange(this, rangeKey);
338
+ newSearcherWithOptions(
339
+ options: RetrieveOptions,
340
+ ): AsyncTermSearcher<string, Key, Channel> {
341
+ return {
342
+ type: this.type,
343
+ search: async (term: string) => await this.search(term, options),
344
+ retrieve: async (keys: Key[]) => await this.retrieve(keys, options),
345
+ page: async (offset: number, limit: number) =>
346
+ await this.page(offset, limit, options),
347
+ };
337
348
  }
338
349
 
339
- async page(offset: number, limit: number, rangeKey?: string): Promise<Channel[]> {
340
- return this.sugar(await this.retriever.page(offset, limit, rangeKey));
350
+ async page(
351
+ offset: number,
352
+ limit: number,
353
+ options?: Omit<RetrieveOptions, "limit" | "offset">,
354
+ ): Promise<Channel[]> {
355
+ return this.sugar(await this.retriever.page(offset, limit, options));
341
356
  }
342
357
 
343
358
  createDebouncedBatchRetriever(deb: number = 10): Retriever {
@@ -351,25 +366,3 @@ export class Client implements AsyncTermSearcher<string, Key, Channel> {
351
366
  return payloads.map((p) => new Channel({ ...p, frameClient }));
352
367
  }
353
368
  }
354
-
355
- class SearcherUnderRange implements AsyncTermSearcher<string, Key, Channel> {
356
- private readonly client: Client;
357
- private readonly rangeKey?: string;
358
-
359
- constructor(client: Client, rangeKey?: string) {
360
- this.client = client;
361
- this.rangeKey = rangeKey;
362
- }
363
-
364
- async search(term: string): Promise<Channel[]> {
365
- return await this.client.search(term, this.rangeKey);
366
- }
367
-
368
- async page(offset: number, limit: number): Promise<Channel[]> {
369
- return await this.client.page(offset, limit, this.rangeKey);
370
- }
371
-
372
- async retrieve(channels: Key[]): Promise<Channel[]> {
373
- return await this.client.retrieve(channels, this.rangeKey);
374
- }
375
- }
@@ -0,0 +1,37 @@
1
+ // Copyright 2023 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import type { UnaryClient } from "@synnaxlabs/freighter";
11
+ import { z } from "zod";
12
+
13
+ import { type Payload, payload, newPayload, type NewPayload } from "@/channel/payload";
14
+
15
+ const reqZ = z.object({ channels: newPayload.array() });
16
+
17
+ const resZ = z.object({ channels: payload.array() });
18
+
19
+ export class Creator {
20
+ private static readonly ENDPOINT = "/channel/create";
21
+ private readonly client: UnaryClient;
22
+
23
+ constructor(client: UnaryClient) {
24
+ this.client = client;
25
+ }
26
+
27
+ async create(channels: NewPayload[]): Promise<Payload[]> {
28
+ const [res, err] = await this.client.send<typeof reqZ, typeof resZ>(
29
+ Creator.ENDPOINT,
30
+ { channels },
31
+ reqZ,
32
+ resZ,
33
+ );
34
+ if (err != null) throw err;
35
+ return res.channels;
36
+ }
37
+ }
@@ -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 { DataType, Rate } from "@synnaxlabs/x";
10
+ import { DataType, Rate } from "@synnaxlabs/x/telem";
11
11
  import { z } from "zod";
12
12
 
13
13
  export const keyZ = z.number();
@@ -38,6 +38,7 @@ export const newPayload = payload.extend({
38
38
  index: z.number().optional(),
39
39
  rate: Rate.z.optional().default(0),
40
40
  isIndex: z.boolean().optional(),
41
+ virtual: z.boolean().optional().default(false),
41
42
  });
42
43
 
43
44
  export type NewPayload = z.input<typeof newPayload>;
@@ -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
  import type { UnaryClient } from "@synnaxlabs/freighter";
10
- import { debounce, toArray } from "@synnaxlabs/x";
10
+ import { debounce } from "@synnaxlabs/x/debounce";
11
+ import { DataType } from "@synnaxlabs/x/telem";
11
12
  import { Mutex } from "async-mutex";
12
13
  import { z } from "zod";
13
14
 
@@ -16,13 +17,13 @@ import {
16
17
  type KeyOrName,
17
18
  type Keys,
18
19
  type KeysOrNames,
19
- type Name,
20
- type Names,
21
20
  type Params,
22
21
  type Payload,
23
22
  payload,
24
23
  } from "@/channel/payload";
25
24
  import { QueryError } from "@/errors";
25
+ import { type ParamAnalysisResult, analyzeParams } from "@/util/retrieve";
26
+ import { nullableArrayZ } from "@/util/zod";
26
27
 
27
28
  const reqZ = z.object({
28
29
  leaseholder: z.number().optional(),
@@ -32,18 +33,34 @@ const reqZ = z.object({
32
33
  rangeKey: z.string().optional(),
33
34
  limit: z.number().optional(),
34
35
  offset: z.number().optional(),
36
+ dataTypes: DataType.z.array().optional(),
37
+ notDataTypes: DataType.z.array().optional(),
38
+ virtual: z.boolean().optional(),
39
+ isIndex: z.boolean().optional(),
40
+ internal: z.boolean().optional(),
35
41
  });
36
42
 
37
43
  type Request = z.input<typeof reqZ>;
38
44
 
45
+ export type RetrieveOptions = Omit<Request, "keys" | "names" | "search">;
46
+ export type PageOptions = Omit<RetrieveOptions, "offset" | "limit">;
47
+
39
48
  const resZ = z.object({
40
- channels: payload.array(),
49
+ channels: nullableArrayZ(payload),
41
50
  });
42
51
 
52
+ export const analyzeChannelParams = (
53
+ channels: Params,
54
+ ): ParamAnalysisResult<KeyOrName, { number: "keys"; string: "names" }> =>
55
+ analyzeParams(channels, {
56
+ number: "keys",
57
+ string: "names",
58
+ });
59
+
43
60
  export interface Retriever {
44
- retrieve: (channels: Params, rangeKey?: string) => Promise<Payload[]>;
45
- search: (term: string, rangeKey?: string) => Promise<Payload[]>;
46
- page: (offset: number, limit: number, rangeKey?: string) => Promise<Payload[]>;
61
+ retrieve: (channels: Params, opts?: RetrieveOptions) => Promise<Payload[]>;
62
+ search: (term: string, opts?: RetrieveOptions) => Promise<Payload[]>;
63
+ page: (offset: number, limit: number, opts?: PageOptions) => Promise<Payload[]>;
47
64
  }
48
65
 
49
66
  export class ClusterRetriever implements Retriever {
@@ -54,17 +71,20 @@ export class ClusterRetriever implements Retriever {
54
71
  this.client = client;
55
72
  }
56
73
 
57
- async search(term: string, rangeKey?: string): Promise<Payload[]> {
58
- return await this.execute({ search: term, rangeKey });
74
+ async search(term: string, options?: RetrieveOptions): Promise<Payload[]> {
75
+ return await this.execute({ search: term, ...options });
59
76
  }
60
77
 
61
- async retrieve(channels: Params, rangeKey?: string): Promise<Payload[]> {
62
- const { variant, normalized } = analyzeParams(channels);
63
- return await this.execute({ [variant]: normalized, rangeKey });
78
+ async retrieve(channels: Params, options?: RetrieveOptions): Promise<Payload[]> {
79
+ let { variant, normalized } = analyzeChannelParams(channels);
80
+ if (variant === "keys" && (normalized as Key[]).indexOf(0) !== -1)
81
+ normalized = (normalized as Key[]).filter((k) => k !== 0);
82
+ if (normalized.length === 0) return [];
83
+ return await this.execute({ [variant]: normalized, ...options });
64
84
  }
65
85
 
66
- async page(offset: number, limit: number, rangeKey?: string): Promise<Payload[]> {
67
- return await this.execute({ offset, limit, rangeKey });
86
+ async page(offset: number, limit: number, options?: PageOptions): Promise<Payload[]> {
87
+ return await this.execute({ offset, limit, ...options });
68
88
  }
69
89
 
70
90
  private async execute(request: Request): Promise<Payload[]> {
@@ -90,16 +110,19 @@ export class CacheRetriever implements Retriever {
90
110
  this.wrapped = wrapped;
91
111
  }
92
112
 
93
- async search(term: string, rangeKey?: string): Promise<Payload[]> {
94
- return await this.wrapped.search(term, rangeKey);
113
+ async search(term: string, options?: RetrieveOptions): Promise<Payload[]> {
114
+ return await this.wrapped.search(term, options);
95
115
  }
96
116
 
97
- async page(offset: number, limit: number, rangeKey?: string): Promise<Payload[]> {
98
- return await this.wrapped.page(offset, limit, rangeKey);
117
+ async page(offset: number, limit: number, options?: PageOptions): Promise<Payload[]> {
118
+ return await this.wrapped.page(offset, limit, options);
99
119
  }
100
120
 
101
- async retrieve(channels: Params): Promise<Payload[]> {
102
- const { normalized } = analyzeParams(channels);
121
+ async retrieve(channels: Params, options?: RetrieveOptions): Promise<Payload[]> {
122
+ const { normalized } = analyzeParams<string | number>(channels, {
123
+ string: "names",
124
+ number: "keys",
125
+ });
103
126
  const results: Payload[] = [];
104
127
  const toFetch: KeysOrNames = [];
105
128
  normalized.forEach((keyOrName) => {
@@ -108,7 +131,7 @@ export class CacheRetriever implements Retriever {
108
131
  else toFetch.push(keyOrName as never);
109
132
  });
110
133
  if (toFetch.length === 0) return results;
111
- const fetched = await this.wrapped.retrieve(toFetch);
134
+ const fetched = await this.wrapped.retrieve(toFetch, options);
112
135
  this.updateCache(fetched);
113
136
  return results.concat(fetched);
114
137
  }
@@ -127,49 +150,6 @@ export class CacheRetriever implements Retriever {
127
150
  }
128
151
  }
129
152
 
130
- export type ParamAnalysisResult =
131
- | {
132
- single: true;
133
- variant: "names";
134
- normalized: Names;
135
- actual: Name;
136
- }
137
- | {
138
- single: true;
139
- variant: "keys";
140
- normalized: Keys;
141
- actual: Key;
142
- }
143
- | {
144
- single: false;
145
- variant: "keys";
146
- normalized: Keys;
147
- actual: Keys;
148
- }
149
- | {
150
- single: false;
151
- variant: "names";
152
- normalized: Names;
153
- actual: Names;
154
- };
155
-
156
- export const analyzeParams = (channels: Params): ParamAnalysisResult => {
157
- let normal = (toArray(channels) as KeysOrNames).filter((c) => c !== 0);
158
- let variant: "names" | "keys" = "keys";
159
- if (typeof normal[0] === "string") {
160
- if (isNaN(parseInt(normal[0]))) variant = "names";
161
- else {
162
- normal = normal.map((v) => parseInt(v as string));
163
- }
164
- }
165
- return {
166
- single: !Array.isArray(channels),
167
- variant,
168
- normalized: normal,
169
- actual: channels,
170
- } as const as ParamAnalysisResult;
171
- };
172
-
173
153
  export interface PromiseFns<T> {
174
154
  resolve: (value: T) => void;
175
155
  reject: (reason?: any) => void;
@@ -189,22 +169,26 @@ export class DebouncedBatchRetriever implements Retriever {
189
169
  }, deb);
190
170
  }
191
171
 
192
- async search(term: string, rangeKey?: string): Promise<Payload[]> {
193
- return await this.wrapped.search(term, rangeKey);
172
+ async search(term: string, options?: RetrieveOptions): Promise<Payload[]> {
173
+ return await this.wrapped.search(term, options);
194
174
  }
195
175
 
196
- async page(offset: number, limit: number, rangeKey?: string): Promise<Payload[]> {
197
- return await this.wrapped.page(offset, limit, rangeKey);
176
+ async page(
177
+ offset: number,
178
+ limit: number,
179
+ options?: RetrieveOptions,
180
+ ): Promise<Payload[]> {
181
+ return await this.wrapped.page(offset, limit, options);
198
182
  }
199
183
 
200
184
  async retrieve(channels: Params): Promise<Payload[]> {
201
- const { normalized, variant } = analyzeParams(channels);
185
+ const { normalized, variant } = analyzeChannelParams(channels);
202
186
  // Bypass on name fetches for now.
203
187
  if (variant === "names") return await this.wrapped.retrieve(normalized);
204
188
  // eslint-disable-next-line @typescript-eslint/promise-function-async
205
189
  const a = new Promise<Payload[]>((resolve, reject) => {
206
190
  void this.mu.runExclusive(() => {
207
- this.requests.set(normalized, { resolve, reject });
191
+ this.requests.set(normalized as Key[], { resolve, reject });
208
192
  this.debouncedRun();
209
193
  });
210
194
  });
@@ -233,7 +217,7 @@ export const retrieveRequired = async (
233
217
  r: Retriever,
234
218
  params: Params,
235
219
  ): Promise<Payload[]> => {
236
- const { normalized } = analyzeParams(params);
220
+ const { normalized } = analyzeChannelParams(params);
237
221
  const results = await r.retrieve(normalized);
238
222
  const notFound: KeyOrName[] = [];
239
223
  normalized.forEach((v) => {