@synnaxlabs/client 0.54.2 → 0.56.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 (222) hide show
  1. package/.turbo/turbo-build.log +10 -13
  2. package/dist/client.cjs +60 -42
  3. package/dist/client.js +8037 -6265
  4. package/dist/src/access/policy/client.d.ts +70 -80
  5. package/dist/src/access/policy/client.d.ts.map +1 -1
  6. package/dist/src/access/policy/types.gen.d.ts +18 -20
  7. package/dist/src/access/policy/types.gen.d.ts.map +1 -1
  8. package/dist/src/access/role/client.d.ts.map +1 -1
  9. package/dist/src/access/role/types.gen.d.ts +2 -2
  10. package/dist/src/actions/actions.d.ts +68 -0
  11. package/dist/src/actions/actions.d.ts.map +1 -0
  12. package/dist/src/actions/actions.spec.d.ts +2 -0
  13. package/dist/src/actions/actions.spec.d.ts.map +1 -0
  14. package/dist/src/actions/external.d.ts +2 -0
  15. package/dist/src/actions/external.d.ts.map +1 -0
  16. package/dist/src/actions/index.d.ts +2 -0
  17. package/dist/src/actions/index.d.ts.map +1 -0
  18. package/dist/src/arc/arc.spec.d.ts +2 -0
  19. package/dist/src/arc/arc.spec.d.ts.map +1 -0
  20. package/dist/src/arc/client.d.ts.map +1 -1
  21. package/dist/src/arc/compiler/types.gen.d.ts +1 -1
  22. package/dist/src/arc/compiler/types.gen.d.ts.map +1 -1
  23. package/dist/src/arc/graph/types.gen.d.ts +40 -40
  24. package/dist/src/arc/graph/types.gen.d.ts.map +1 -1
  25. package/dist/src/arc/ir/types.gen.d.ts +202 -233
  26. package/dist/src/arc/ir/types.gen.d.ts.map +1 -1
  27. package/dist/src/arc/module/types.gen.d.ts +63 -82
  28. package/dist/src/arc/module/types.gen.d.ts.map +1 -1
  29. package/dist/src/arc/program/types.gen.d.ts +63 -82
  30. package/dist/src/arc/program/types.gen.d.ts.map +1 -1
  31. package/dist/src/arc/types/types.gen.d.ts +11 -11
  32. package/dist/src/arc/types/types.gen.d.ts.map +1 -1
  33. package/dist/src/arc/types.gen.d.ts +139 -158
  34. package/dist/src/arc/types.gen.d.ts.map +1 -1
  35. package/dist/src/auth/auth.d.ts +3 -3
  36. package/dist/src/auth/auth.d.ts.map +1 -1
  37. package/dist/src/channel/client.d.ts +2 -2
  38. package/dist/src/channel/client.d.ts.map +1 -1
  39. package/dist/src/channel/retriever.d.ts +5 -8
  40. package/dist/src/channel/retriever.d.ts.map +1 -1
  41. package/dist/src/channel/types.gen.d.ts +3 -3
  42. package/dist/src/channel/types.gen.d.ts.map +1 -1
  43. package/dist/src/channel/writer.d.ts.map +1 -1
  44. package/dist/src/client.d.ts +5 -0
  45. package/dist/src/client.d.ts.map +1 -1
  46. package/dist/src/connection/checker.d.ts +17 -2
  47. package/dist/src/connection/checker.d.ts.map +1 -1
  48. package/dist/src/control/state.d.ts.map +1 -1
  49. package/dist/src/device/client.d.ts.map +1 -1
  50. package/dist/src/device/types.gen.d.ts +6 -8
  51. package/dist/src/device/types.gen.d.ts.map +1 -1
  52. package/dist/src/errors.d.ts +2 -0
  53. package/dist/src/errors.d.ts.map +1 -1
  54. package/dist/src/framer/adapter.d.ts.map +1 -1
  55. package/dist/src/framer/client.d.ts +2 -2
  56. package/dist/src/framer/client.d.ts.map +1 -1
  57. package/dist/src/framer/codec.d.ts +9 -1
  58. package/dist/src/framer/codec.d.ts.map +1 -1
  59. package/dist/src/framer/deleter.d.ts.map +1 -1
  60. package/dist/src/framer/frame.d.ts +1 -1
  61. package/dist/src/framer/iterator.d.ts +84 -3
  62. package/dist/src/framer/iterator.d.ts.map +1 -1
  63. package/dist/src/framer/streamProxy.d.ts.map +1 -1
  64. package/dist/src/framer/streamer.d.ts +1 -3
  65. package/dist/src/framer/streamer.d.ts.map +1 -1
  66. package/dist/src/framer/types.gen.d.ts +18 -0
  67. package/dist/src/framer/types.gen.d.ts.map +1 -1
  68. package/dist/src/framer/writer.d.ts +8 -8
  69. package/dist/src/framer/writer.d.ts.map +1 -1
  70. package/dist/src/group/client.d.ts +1 -2
  71. package/dist/src/group/client.d.ts.map +1 -1
  72. package/dist/src/group/types.gen.d.ts +2 -2
  73. package/dist/src/index.d.ts +2 -1
  74. package/dist/src/index.d.ts.map +1 -1
  75. package/dist/src/label/client.d.ts +5 -8
  76. package/dist/src/label/client.d.ts.map +1 -1
  77. package/dist/src/lineplot/client.d.ts.map +1 -1
  78. package/dist/src/lineplot/types.gen.d.ts +2 -2
  79. package/dist/src/log/client.d.ts.map +1 -1
  80. package/dist/src/log/types.gen.d.ts +2 -2
  81. package/dist/src/ontology/client.d.ts +1 -3
  82. package/dist/src/ontology/client.d.ts.map +1 -1
  83. package/dist/src/ontology/payload.d.ts +12 -16
  84. package/dist/src/ontology/payload.d.ts.map +1 -1
  85. package/dist/src/ontology/types.gen.d.ts +1 -2
  86. package/dist/src/ontology/types.gen.d.ts.map +1 -1
  87. package/dist/src/ontology/writer.d.ts +5 -10
  88. package/dist/src/ontology/writer.d.ts.map +1 -1
  89. package/dist/src/rack/client.d.ts.map +1 -1
  90. package/dist/src/rack/types.gen.d.ts +3 -3
  91. package/dist/src/ranger/alias/client.d.ts.map +1 -1
  92. package/dist/src/ranger/client.d.ts.map +1 -1
  93. package/dist/src/ranger/kv/client.d.ts.map +1 -1
  94. package/dist/src/ranger/types.gen.d.ts +6 -6
  95. package/dist/src/ranger/types.gen.d.ts.map +1 -1
  96. package/dist/src/ranger/writer.d.ts +2 -3
  97. package/dist/src/ranger/writer.d.ts.map +1 -1
  98. package/dist/src/schematic/actions.d.ts +147 -0
  99. package/dist/src/schematic/actions.d.ts.map +1 -0
  100. package/dist/src/schematic/actions.gen.d.ts +484 -0
  101. package/dist/src/schematic/actions.gen.d.ts.map +1 -0
  102. package/dist/src/schematic/actions.spec.d.ts +2 -0
  103. package/dist/src/schematic/actions.spec.d.ts.map +1 -0
  104. package/dist/src/schematic/client.d.ts +53 -2
  105. package/dist/src/schematic/client.d.ts.map +1 -1
  106. package/dist/src/schematic/external.d.ts +2 -0
  107. package/dist/src/schematic/external.d.ts.map +1 -1
  108. package/dist/src/schematic/symbol/client.d.ts.map +1 -1
  109. package/dist/src/schematic/symbol/types.gen.d.ts +48 -58
  110. package/dist/src/schematic/symbol/types.gen.d.ts.map +1 -1
  111. package/dist/src/schematic/types.gen.d.ts +131 -5
  112. package/dist/src/schematic/types.gen.d.ts.map +1 -1
  113. package/dist/src/status/client.d.ts.map +1 -1
  114. package/dist/src/status/payload.d.ts +3 -3
  115. package/dist/src/table/actions.d.ts +156 -0
  116. package/dist/src/table/actions.d.ts.map +1 -0
  117. package/dist/src/table/actions.gen.d.ts +587 -0
  118. package/dist/src/table/actions.gen.d.ts.map +1 -0
  119. package/dist/src/table/client.d.ts +28 -2
  120. package/dist/src/table/client.d.ts.map +1 -1
  121. package/dist/src/table/external.d.ts +2 -0
  122. package/dist/src/table/external.d.ts.map +1 -1
  123. package/dist/src/table/types.gen.d.ts +71 -4
  124. package/dist/src/table/types.gen.d.ts.map +1 -1
  125. package/dist/src/task/client.d.ts.map +1 -1
  126. package/dist/src/task/types.gen.d.ts +7 -7
  127. package/dist/src/task/types.gen.d.ts.map +1 -1
  128. package/dist/src/user/client.d.ts +2 -2
  129. package/dist/src/user/client.d.ts.map +1 -1
  130. package/dist/src/user/types.gen.d.ts +2 -2
  131. package/dist/src/view/client.d.ts.map +1 -1
  132. package/dist/src/view/types.gen.d.ts +2 -2
  133. package/dist/src/workspace/client.d.ts.map +1 -1
  134. package/dist/src/workspace/types.gen.d.ts +3 -3
  135. package/dist/src/workspace/types.gen.d.ts.map +1 -1
  136. package/package.json +12 -11
  137. package/src/access/policy/client.ts +4 -7
  138. package/src/access/role/client.ts +6 -26
  139. package/src/actions/actions.spec.ts +229 -0
  140. package/src/actions/actions.ts +104 -0
  141. package/src/actions/external.ts +10 -0
  142. package/src/actions/index.ts +10 -0
  143. package/src/arc/arc.spec.ts +44 -0
  144. package/src/arc/client.ts +3 -7
  145. package/src/arc/compiler/types.gen.ts +2 -1
  146. package/src/arc/ir/types.gen.ts +102 -48
  147. package/src/arc/lsp.spec.ts +3 -7
  148. package/src/arc/types/types.gen.ts +3 -3
  149. package/src/auth/auth.spec.ts +12 -13
  150. package/src/auth/auth.ts +48 -34
  151. package/src/channel/batchRetriever.spec.ts +13 -4
  152. package/src/channel/channel.spec.ts +13 -0
  153. package/src/channel/client.ts +8 -6
  154. package/src/channel/retriever.ts +7 -16
  155. package/src/channel/types.gen.ts +1 -2
  156. package/src/channel/writer.ts +4 -20
  157. package/src/client.ts +3 -0
  158. package/src/connection/checker.ts +48 -10
  159. package/src/connection/connection.spec.ts +64 -2
  160. package/src/control/state.ts +5 -4
  161. package/src/device/client.ts +5 -8
  162. package/src/device/device.spec.ts +7 -5
  163. package/src/device/types.gen.ts +4 -4
  164. package/src/errors.ts +8 -9
  165. package/src/framer/adapter.ts +2 -4
  166. package/src/framer/client.ts +12 -0
  167. package/src/framer/codec.spec.ts +53 -3
  168. package/src/framer/codec.ts +58 -25
  169. package/src/framer/deleter.ts +2 -8
  170. package/src/framer/iterator.ts +42 -39
  171. package/src/framer/streamProxy.ts +11 -12
  172. package/src/framer/streamer.spec.ts +1 -1
  173. package/src/framer/streamer.ts +2 -7
  174. package/src/framer/types.gen.ts +20 -0
  175. package/src/framer/writer.spec.ts +221 -1
  176. package/src/framer/writer.ts +53 -28
  177. package/src/group/client.ts +4 -7
  178. package/src/index.ts +3 -2
  179. package/src/label/client.ts +6 -16
  180. package/src/label/label.spec.ts +12 -0
  181. package/src/lineplot/client.ts +6 -21
  182. package/src/log/client.ts +6 -21
  183. package/src/ontology/client.ts +2 -3
  184. package/src/ontology/ontology.spec.ts +10 -0
  185. package/src/ontology/types.gen.ts +0 -1
  186. package/src/ontology/writer.ts +4 -7
  187. package/src/rack/client.ts +4 -7
  188. package/src/rack/rack.spec.ts +12 -1
  189. package/src/ranger/alias/client.ts +6 -11
  190. package/src/ranger/client.ts +2 -3
  191. package/src/ranger/kv/client.ts +4 -7
  192. package/src/ranger/ranger.spec.ts +12 -0
  193. package/src/ranger/writer.ts +4 -17
  194. package/src/schematic/access.spec.ts +6 -6
  195. package/src/schematic/actions.gen.ts +200 -0
  196. package/src/schematic/actions.spec.ts +699 -0
  197. package/src/schematic/actions.ts +168 -0
  198. package/src/schematic/client.ts +34 -30
  199. package/src/schematic/external.ts +2 -0
  200. package/src/schematic/schematic.spec.ts +233 -69
  201. package/src/schematic/symbol/client.spec.ts +33 -9
  202. package/src/schematic/symbol/client.ts +6 -11
  203. package/src/schematic/symbol/types.gen.ts +1 -10
  204. package/src/schematic/types.gen.ts +55 -6
  205. package/src/status/client.ts +4 -10
  206. package/src/status/status.spec.ts +7 -6
  207. package/src/table/access.spec.ts +0 -6
  208. package/src/table/actions.gen.ts +243 -0
  209. package/src/table/actions.ts +255 -0
  210. package/src/table/client.ts +21 -25
  211. package/src/table/external.ts +2 -0
  212. package/src/table/table.spec.ts +588 -43
  213. package/src/table/types.gen.ts +58 -5
  214. package/src/task/client.ts +14 -20
  215. package/src/task/task.spec.ts +15 -1
  216. package/src/task/types.gen.ts +8 -6
  217. package/src/user/client.ts +6 -11
  218. package/src/view/client.ts +4 -7
  219. package/src/view/view.spec.ts +9 -5
  220. package/src/workspace/client.ts +6 -16
  221. package/src/workspace/types.gen.ts +2 -1
  222. package/src/workspace/workspace.spec.ts +14 -1
@@ -27,7 +27,7 @@ type JSONRPCResponse =
27
27
  };
28
28
 
29
29
  type LSPReceiver = {
30
- receive: () => Promise<[{ content: string }, null] | [null, Error]>;
30
+ receive: () => Promise<{ content: string }>;
31
31
  };
32
32
 
33
33
  const MAX_DRAIN = 50;
@@ -38,9 +38,7 @@ const receiveResponse = async (
38
38
  expectedId: number,
39
39
  ): Promise<JSONRPCResponse> => {
40
40
  for (let i = 0; i < MAX_DRAIN; i++) {
41
- const [res, err] = await stream.receive();
42
- if (err != null) throw err;
43
- if (res == null) throw new Error("Expected response");
41
+ const res = await stream.receive();
44
42
  const msg = JSON.parse(res.content);
45
43
  if (!("method" in msg) && "id" in msg && msg.id === expectedId)
46
44
  return msg as JSONRPCResponse;
@@ -56,9 +54,7 @@ const receiveNotification = async (
56
54
  expectedMethod: string,
57
55
  ): Promise<JSONRPCRequest> => {
58
56
  for (let i = 0; i < MAX_DRAIN; i++) {
59
- const [res, err] = await stream.receive();
60
- if (err != null) throw err;
61
- if (res == null) throw new Error("Expected message");
57
+ const res = await stream.receive();
62
58
  const msg = JSON.parse(res.content);
63
59
  if ("method" in msg && msg.method === expectedMethod) return msg as JSONRPCRequest;
64
60
  }
@@ -9,7 +9,7 @@
9
9
 
10
10
  // Code generated by Oracle. DO NOT EDIT.
11
11
 
12
- import { array, zod } from "@synnaxlabs/x";
12
+ import { array, record, zod } from "@synnaxlabs/x";
13
13
  import { z } from "zod";
14
14
 
15
15
  export enum Kind {
@@ -48,9 +48,9 @@ export const chanDirectionZ = z.enum(ChanDirection);
48
48
  /** Channels contains channel declarations for reading from and writing to Synnax channels. */
49
49
  export const channelsZ = z.object({
50
50
  /** read contains readable channel indices mapped to parameter names. */
51
- read: z.record(z.uint32(), z.string()),
51
+ read: record.nullishToEmpty(z.uint32(), z.string()),
52
52
  /** write contains writable channel indices mapped to parameter names. */
53
- write: z.record(z.uint32(), z.string()),
53
+ write: record.nullishToEmpty(z.uint32(), z.string()),
54
54
  });
55
55
  export interface Channels extends z.infer<typeof channelsZ> {}
56
56
 
@@ -33,8 +33,8 @@ describe("auth", () => {
33
33
  );
34
34
  const client = new auth.Client(transport.unary, TEST_CLIENT_PARAMS);
35
35
  const mw = client.middleware();
36
- const res = await mw(DUMMY_CTX, async () => [DUMMY_CTX, null]);
37
- expect(res).toEqual([DUMMY_CTX, null]);
36
+ const res = await mw(DUMMY_CTX, async () => DUMMY_CTX);
37
+ expect(res).toEqual(DUMMY_CTX);
38
38
  });
39
39
 
40
40
  test("invalid credentials", async () => {
@@ -49,8 +49,7 @@ describe("auth", () => {
49
49
  password: "wrong",
50
50
  });
51
51
  const mw = client.middleware();
52
- const [, err] = await mw(DUMMY_CTX, async () => [DUMMY_CTX, null]);
53
- expect(err).toBeInstanceOf(AuthError);
52
+ await expect(mw(DUMMY_CTX, async () => DUMMY_CTX)).rejects.toThrow(AuthError);
54
53
  });
55
54
 
56
55
  describe("token retry", () => {
@@ -68,16 +67,16 @@ describe("auth", () => {
68
67
  let isFirst = true;
69
68
  let tkOne: string | undefined;
70
69
  let tkTwo: string | undefined;
71
- const [, err] = await mw(DUMMY_CTX, async () => {
70
+ const res = await mw(DUMMY_CTX, async () => {
72
71
  if (isFirst) {
73
72
  isFirst = false;
74
73
  tkOne = client.token;
75
- return [DUMMY_CTX, new ErrorType()];
74
+ throw new ErrorType();
76
75
  }
77
76
  tkTwo = client.token;
78
- return [DUMMY_CTX, null];
77
+ return DUMMY_CTX;
79
78
  });
80
- expect(err).toBeNull();
79
+ expect(res).toEqual(DUMMY_CTX);
81
80
  expect(tkOne).toBeDefined();
82
81
  expect(tkTwo).toBeDefined();
83
82
  });
@@ -92,11 +91,11 @@ describe("auth", () => {
92
91
  );
93
92
  const client = new auth.Client(transport.unary, TEST_CLIENT_PARAMS);
94
93
  const mw = client.middleware();
95
- const [, err] = await mw(DUMMY_CTX, async () => [
96
- DUMMY_CTX,
97
- new InvalidTokenError(),
98
- ]);
99
- expect(err).toBeInstanceOf(InvalidTokenError);
94
+ await expect(
95
+ mw(DUMMY_CTX, async () => {
96
+ throw new InvalidTokenError();
97
+ }),
98
+ ).rejects.toThrow(InvalidTokenError);
100
99
  });
101
100
  });
102
101
  });
package/src/auth/auth.ts CHANGED
@@ -7,16 +7,28 @@
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 Middleware, sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
10
+ import { type Middleware, type UnaryClient } from "@synnaxlabs/freighter";
11
+ import { TimeStamp } from "@synnaxlabs/x";
11
12
  import { z } from "zod";
12
13
 
13
14
  import { ExpiredTokenError, InvalidTokenError } from "@/errors";
14
15
  import { user } from "@/user";
15
16
 
16
- const insecureCredentialsZ = z.object({ username: z.string(), password: z.string() });
17
- interface InsecureCredentials extends z.infer<typeof insecureCredentialsZ> {}
17
+ const credentialsZ = z.object({ username: z.string(), password: z.string() });
18
+ interface Credentials extends z.infer<typeof credentialsZ> {}
18
19
 
19
- const tokenResponseZ = z.object({ token: z.string(), user: user.userZ });
20
+ const clusterInfoZ = z.object({
21
+ clusterKey: z.string(),
22
+ nodeVersion: z.string().optional(),
23
+ nodeKey: z.number().optional(),
24
+ nodeTime: TimeStamp.z,
25
+ });
26
+
27
+ const tokenResponseZ = z.object({
28
+ token: z.string(),
29
+ user: user.userZ,
30
+ clusterInfo: clusterInfoZ.optional(),
31
+ });
20
32
 
21
33
  const LOGIN_ENDPOINT = "/auth/login";
22
34
 
@@ -37,12 +49,12 @@ type AuthState =
37
49
 
38
50
  export class Client {
39
51
  private readonly client: UnaryClient;
40
- private readonly credentials: InsecureCredentials;
52
+ private readonly credentials: Credentials;
41
53
  private authState: AuthState = { authenticated: false };
42
54
  authenticating: Promise<Error | null> | undefined;
43
55
  private retryCount: number;
44
56
 
45
- constructor(client: UnaryClient, credentials: InsecureCredentials) {
57
+ constructor(client: UnaryClient, credentials: Credentials) {
46
58
  this.client = client;
47
59
  this.credentials = credentials;
48
60
  this.retryCount = 0;
@@ -70,8 +82,7 @@ export class Client {
70
82
 
71
83
  async changePassword(newPassword: string): Promise<void> {
72
84
  if (!this.authenticated) throw new Error("Not authenticated");
73
- await sendRequired<typeof changePasswordReqZ, typeof changePasswordResZ>(
74
- this.client,
85
+ await this.client.send(
75
86
  "/auth/change-password",
76
87
  {
77
88
  username: this.credentials.username,
@@ -87,39 +98,42 @@ export class Client {
87
98
  middleware(): Middleware {
88
99
  const mw: Middleware = async (reqCtx, next) => {
89
100
  if (!this.authenticated && !reqCtx.target.endsWith(LOGIN_ENDPOINT)) {
90
- this.authenticating ??= new Promise((resolve, reject) => {
91
- this.client
92
- .send(
101
+ this.authenticating ??= (async (): Promise<Error | null> => {
102
+ try {
103
+ const res = await this.client.send(
93
104
  LOGIN_ENDPOINT,
94
105
  this.credentials,
95
- insecureCredentialsZ,
106
+ credentialsZ,
96
107
  tokenResponseZ,
97
- )
98
- .then(([res, err]) => {
99
- if (err != null) return resolve(err);
100
- if (res == null) return resolve(new Error("No response from login"));
101
- this.authState = {
102
- authenticated: true,
103
- user: res.user,
104
- token: res.token,
105
- };
106
- resolve(null);
107
- })
108
- .catch(reject);
109
- });
108
+ );
109
+ this.authState = {
110
+ authenticated: true,
111
+ user: res.user,
112
+ token: res.token,
113
+ };
114
+ return null;
115
+ } catch (err) {
116
+ return err instanceof Error ? err : new Error(String(err));
117
+ }
118
+ })();
110
119
  const err = await this.authenticating;
111
- if (err != null) return [reqCtx, err];
120
+ if (err != null) throw err;
112
121
  }
113
122
  reqCtx.params.Authorization = `Bearer ${this.token}`;
114
- const [resCtx, err] = await next(reqCtx);
115
- if (RETRY_ON.some((e) => e.matches(err)) && this.retryCount < MAX_RETRIES) {
116
- this.authState = { authenticated: false };
117
- this.authenticating = undefined;
118
- this.retryCount += 1;
119
- return mw(reqCtx, next);
123
+ try {
124
+ const resCtx = await next(reqCtx);
125
+ this.retryCount = 0;
126
+ return resCtx;
127
+ } catch (err) {
128
+ if (RETRY_ON.some((e) => e.matches(err)) && this.retryCount < MAX_RETRIES) {
129
+ this.authState = { authenticated: false };
130
+ this.authenticating = undefined;
131
+ this.retryCount += 1;
132
+ return mw(reqCtx, next);
133
+ }
134
+ this.retryCount = 0;
135
+ throw err;
120
136
  }
121
- this.retryCount = 0;
122
- return [resCtx, err];
123
137
  };
124
138
  return mw;
125
139
  }
@@ -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, errors, Rate } from "@synnaxlabs/x";
10
+ import { DataType, errors, Rate, TimeSpan } from "@synnaxlabs/x";
11
11
  import { describe, expect, it, vi } from "vitest";
12
12
 
13
13
  import { channel } from "@/channel";
@@ -57,7 +57,10 @@ describe("channelchannel.Retriever", () => {
57
57
  operations: [],
58
58
  }));
59
59
  });
60
- const retriever = new channel.DebouncedBatchRetriever(base, 10);
60
+ const retriever = new channel.DebouncedBatchRetriever(
61
+ base,
62
+ TimeSpan.milliseconds(10),
63
+ );
61
64
  const res = await Promise.all([
62
65
  retriever.retrieve([1]),
63
66
  retriever.retrieve([2]),
@@ -86,7 +89,10 @@ describe("channelchannel.Retriever", () => {
86
89
  operations: [],
87
90
  }));
88
91
  });
89
- const retriever = new channel.DebouncedBatchRetriever(base, 10);
92
+ const retriever = new channel.DebouncedBatchRetriever(
93
+ base,
94
+ TimeSpan.milliseconds(10),
95
+ );
90
96
  const res = await Promise.all([
91
97
  retriever.retrieve([1]),
92
98
  retriever.retrieve([2]),
@@ -100,7 +106,10 @@ describe("channelchannel.Retriever", () => {
100
106
  const base = new MockRetriever(async (): Promise<channel.Payload[]> => {
101
107
  throw new Error("failed to fetch");
102
108
  });
103
- const retriever = new channel.DebouncedBatchRetriever(base, 10);
109
+ const retriever = new channel.DebouncedBatchRetriever(
110
+ base,
111
+ TimeSpan.milliseconds(10),
112
+ );
104
113
  await expect(retriever.retrieve([1])).rejects.toThrow("failed to fetch");
105
114
  });
106
115
  });
@@ -226,6 +226,19 @@ describe("Channel", () => {
226
226
  async () => await client.channels.retrieve("1-1000"),
227
227
  ).rejects.toThrow(NotFoundError);
228
228
  });
229
+ test("retrieve by search term", async () => {
230
+ const prefix = id.create();
231
+ const names = [`${prefix}_1`, `${prefix}_2`];
232
+ await client.channels.create(
233
+ names.map((name) => ({ name, virtual: true, dataType: DataType.FLOAT32 })),
234
+ );
235
+ await expect
236
+ .poll(async () => {
237
+ const results = await client.channels.retrieve({ searchTerm: prefix });
238
+ return results.map((c) => c.name).sort();
239
+ })
240
+ .toEqual(names);
241
+ });
229
242
  });
230
243
 
231
244
  describe("delete", async () => {
@@ -7,15 +7,17 @@
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 { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
10
+ import { type UnaryClient } from "@synnaxlabs/freighter";
11
11
  import {
12
12
  array,
13
13
  type CrudeDensity,
14
14
  type CrudeTimeRange,
15
+ type CrudeTimeSpan,
15
16
  type CrudeTimeStamp,
16
17
  DataType,
17
18
  type MultiSeries,
18
19
  status,
20
+ TimeSpan,
19
21
  type TypedArray,
20
22
  } from "@synnaxlabs/x";
21
23
  import { z } from "zod";
@@ -387,8 +389,7 @@ export class Client {
387
389
  */
388
390
  async delete(params: Params): Promise<void> {
389
391
  const { normalized, variant } = analyzeParams(params);
390
- if (variant === "keys")
391
- return await this.writer.delete({ keys: normalized as Key[] });
392
+ if (variant === "keys") return await this.writer.delete({ keys: normalized });
392
393
  return await this.writer.delete({ names: normalized as string[] });
393
394
  }
394
395
 
@@ -398,7 +399,9 @@ export class Client {
398
399
  return await this.writer.rename(array.toArray(keys), array.toArray(names));
399
400
  }
400
401
 
401
- createDebouncedBatchRetriever(deb: number = 10): Retriever {
402
+ createDebouncedBatchRetriever(
403
+ deb: CrudeTimeSpan = TimeSpan.milliseconds(10),
404
+ ): Retriever {
402
405
  return new CacheRetriever(
403
406
  new DebouncedBatchRetriever(new ClusterRetriever(this.client), deb),
404
407
  );
@@ -414,8 +417,7 @@ export class Client {
414
417
  }
415
418
 
416
419
  async retrieveGroup(): Promise<group.Group> {
417
- const res = await sendRequired(
418
- this.client,
420
+ const res = await this.client.send(
419
421
  "/channel/retrieve-group",
420
422
  {},
421
423
  retrieveGroupReqZ,
@@ -7,8 +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 { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
11
- import { array, DataType, debounce, zod } from "@synnaxlabs/x";
10
+ import { type UnaryClient } from "@synnaxlabs/freighter";
11
+ import { array, type CrudeTimeSpan, DataType, debounce, zod } from "@synnaxlabs/x";
12
12
  import { Mutex } from "async-mutex";
13
13
  import { z } from "zod";
14
14
 
@@ -36,13 +36,10 @@ const reqZ = z.object({
36
36
  internal: z.boolean().optional(),
37
37
  legacyCalculated: z.boolean().optional(),
38
38
  });
39
- export interface RetrieveRequest extends z.input<typeof reqZ> {}
39
+ export type RetrieveRequest = z.input<typeof reqZ>;
40
40
 
41
- export interface RetrieveOptions extends Omit<
42
- RetrieveRequest,
43
- "keys" | "names" | "search"
44
- > {}
45
- export interface PageOptions extends Omit<RetrieveOptions, "offset" | "limit"> {}
41
+ export type RetrieveOptions = Omit<RetrieveRequest, "keys" | "names" | "search">;
42
+ export type PageOptions = Omit<RetrieveOptions, "offset" | "limit">;
46
43
 
47
44
  const resZ = z.object({ channels: array.nullishToEmpty(payloadZ) });
48
45
 
@@ -86,13 +83,7 @@ export class ClusterRetriever implements Retriever {
86
83
  }
87
84
 
88
85
  private async execute(request: RetrieveRequest): Promise<Payload[]> {
89
- const res = await sendRequired(
90
- this.client,
91
- "/channel/retrieve",
92
- request,
93
- reqZ,
94
- resZ,
95
- );
86
+ const res = await this.client.send("/channel/retrieve", request, reqZ, resZ);
96
87
  return res.channels;
97
88
  }
98
89
  }
@@ -204,7 +195,7 @@ export class DebouncedBatchRetriever implements Retriever {
204
195
  private readonly wrapped: Retriever;
205
196
  private readonly debouncedRun: () => void;
206
197
 
207
- constructor(wrapped: Retriever, deb: number) {
198
+ constructor(wrapped: Retriever, deb: CrudeTimeSpan) {
208
199
  this.wrapped = wrapped;
209
200
  this.debouncedRun = debounce(() => {
210
201
  void this.run();
@@ -85,8 +85,7 @@ export const payloadZ = z.object({
85
85
  isIndex: z.boolean(),
86
86
  /**
87
87
  * index is the channel used to index this channel's values, associating
88
- * each value with a timestamp. If zero, the channel's data will be
89
- * indexed using its rate.
88
+ * each value with a timestamp.
90
89
  */
91
90
  index: keyZ,
92
91
  /** alias is an optional alternate name for the channel within a specific context. */
@@ -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 { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
10
+ import { type UnaryClient } from "@synnaxlabs/freighter";
11
11
  import { type DataType } from "@synnaxlabs/x";
12
12
  import { z } from "zod";
13
13
 
@@ -47,11 +47,7 @@ export class Writer {
47
47
  }
48
48
 
49
49
  async create(channels: New[]): Promise<Payload[]> {
50
- const { channels: created } = await sendRequired<
51
- typeof createReqZ,
52
- typeof createResZ
53
- >(
54
- this.client,
50
+ const { channels: created } = await this.client.send(
55
51
  "/channel/create",
56
52
  {
57
53
  channels: channels.map((c) => ({
@@ -68,25 +64,13 @@ export class Writer {
68
64
 
69
65
  async delete(props: DeleteProps): Promise<void> {
70
66
  const keys = keyZ.array().parse(props.keys ?? []);
71
- await sendRequired<typeof deleteReqZ, typeof deleteResZ>(
72
- this.client,
73
- "/channel/delete",
74
- props,
75
- deleteReqZ,
76
- deleteResZ,
77
- );
67
+ await this.client.send("/channel/delete", props, deleteReqZ, deleteResZ);
78
68
  if (keys.length > 0) this.cache.delete(keys);
79
69
  if (props.names != null) this.cache.delete(props.names);
80
70
  }
81
71
 
82
72
  async rename(keys: Key[], names: string[]): Promise<void> {
83
- await sendRequired<typeof renameReqZ, typeof renameResZ>(
84
- this.client,
85
- "/channel/rename",
86
- { keys, names },
87
- renameReqZ,
88
- renameResZ,
89
- );
73
+ await this.client.send("/channel/rename", { keys, names }, renameReqZ, renameResZ);
90
74
  this.cache.rename(keys, names);
91
75
  }
92
76
  }
package/src/client.ts CHANGED
@@ -45,6 +45,7 @@ export const synnaxParamsZ = z.object({
45
45
  username: z.string().min(1, "Username is required"),
46
46
  password: z.string().min(1, "Password is required"),
47
47
  connectivityPollFrequency: TimeSpan.z.default(TimeSpan.seconds(30)),
48
+ clockSkewThreshold: TimeSpan.z.default(TimeSpan.seconds(1)),
48
49
  secure: z.boolean().default(false),
49
50
  name: z.string().optional(),
50
51
  retry: breaker.breakerConfigZ.optional(),
@@ -116,6 +117,7 @@ export default class Synnax extends framer.Client {
116
117
  username,
117
118
  password,
118
119
  connectivityPollFrequency,
120
+ clockSkewThreshold,
119
121
  secure,
120
122
  retry: breaker,
121
123
  } = parsedParams;
@@ -141,6 +143,7 @@ export default class Synnax extends framer.Client {
141
143
  connectivityPollFrequency,
142
144
  this.clientVersion,
143
145
  parsedParams.name,
146
+ clockSkewThreshold,
144
147
  );
145
148
  this.control = new control.Client(this);
146
149
  this.ontology = new ontology.Client(this.transport.unary);
@@ -7,8 +7,14 @@
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 { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
11
- import { migrate, TimeSpan } from "@synnaxlabs/x";
10
+ import { type UnaryClient } from "@synnaxlabs/freighter";
11
+ import {
12
+ ClockSkewCalculator,
13
+ type CrudeTimeSpan,
14
+ migrate,
15
+ TimeSpan,
16
+ TimeStamp,
17
+ } from "@synnaxlabs/x";
12
18
  import { z } from "zod";
13
19
 
14
20
  export const statusZ = z.enum(["disconnected", "connecting", "connected", "failed"]);
@@ -22,12 +28,15 @@ export const stateZ = z.object({
22
28
  clientVersion: z.string(),
23
29
  clientServerCompatible: z.boolean(),
24
30
  nodeVersion: z.string().optional(),
31
+ clockSkew: TimeSpan.z.default(TimeSpan.ZERO),
32
+ clockSkewExceeded: z.boolean().default(false),
25
33
  });
26
34
  export interface State extends z.infer<typeof stateZ> {}
27
35
 
28
36
  const responseZ = z.object({
29
37
  clusterKey: z.string(),
30
38
  nodeVersion: z.string().optional(),
39
+ nodeTime: TimeStamp.z,
31
40
  });
32
41
  const requestZ = z.void();
33
42
 
@@ -38,6 +47,8 @@ const DEFAULT: State = {
38
47
  message: "Disconnected",
39
48
  clientServerCompatible: false,
40
49
  clientVersion: __VERSION__,
50
+ clockSkew: TimeSpan.ZERO,
51
+ clockSkewExceeded: false,
41
52
  };
42
53
 
43
54
  const createWarning = (
@@ -46,16 +57,16 @@ const createWarning = (
46
57
  clientIsNewer: boolean,
47
58
  ): string => {
48
59
  const toUpgrade = clientIsNewer ? "Core" : "client";
49
- return `Synnax Core version ${nodeVersion != null ? `${nodeVersion} ` : ""}is too ${clientIsNewer ? "old" : "new"} for client version ${clientVersion}.
60
+ return `The Synnax Core version ${nodeVersion != null ? `${nodeVersion} ` : ""}is too ${clientIsNewer ? "old" : "new"} for client version ${clientVersion}.
50
61
  This may cause compatibility issues. We recommend updating the ${toUpgrade}. For more information, see
51
- https://docs.synnaxlabs.com/reference/client/resources/troubleshooting#old-${toUpgrade}-version`;
62
+ https://docs.synnaxlabs.com/reference/client/resources/troubleshooting#old-${toUpgrade.toLowerCase()}-version`;
52
63
  };
53
64
 
54
65
  /** Polls a synnax cluster for connectivity information. */
55
66
  export class Checker {
56
67
  static readonly DEFAULT: State = DEFAULT;
57
68
  private readonly _state: State;
58
- private readonly pollFrequency = TimeSpan.seconds(30);
69
+ private readonly pollFrequency: TimeSpan;
59
70
  private readonly client: UnaryClient;
60
71
  private readonly name?: string;
61
72
  private interval?: NodeJS.Timeout;
@@ -63,6 +74,9 @@ export class Checker {
63
74
  private readonly onChangeHandlers: Array<(state: State) => void> = [];
64
75
  static readonly connectionStateZ = stateZ;
65
76
  private versionWarned = false;
77
+ private readonly skewCalc: ClockSkewCalculator;
78
+ private readonly clockSkewThreshold: TimeSpan;
79
+ private checking = false;
66
80
 
67
81
  /**
68
82
  * @param client - The transport client to use for connectivity checks.
@@ -71,15 +85,18 @@ export class Checker {
71
85
  */
72
86
  constructor(
73
87
  client: UnaryClient,
74
- pollFreq: TimeSpan = TimeSpan.seconds(30),
88
+ pollFreq: CrudeTimeSpan = TimeSpan.seconds(30),
75
89
  clientVersion: string,
76
90
  name?: string,
91
+ clockSkewThreshold: CrudeTimeSpan = TimeSpan.seconds(1),
77
92
  ) {
78
93
  this._state = { ...DEFAULT };
79
94
  this.client = client;
80
- this.pollFrequency = pollFreq;
95
+ this.pollFrequency = new TimeSpan(pollFreq);
81
96
  this.clientVersion = clientVersion;
82
97
  this.name = name;
98
+ this.skewCalc = new ClockSkewCalculator();
99
+ this.clockSkewThreshold = new TimeSpan(clockSkewThreshold).abs();
83
100
  void this.check();
84
101
  this.start();
85
102
  }
@@ -95,14 +112,30 @@ export class Checker {
95
112
  */
96
113
  async check(): Promise<State> {
97
114
  const prevStatus = this._state.status;
115
+ const prevSkewExceeded = this._state.clockSkewExceeded;
116
+ const measureSkew = !this.checking;
117
+ this.checking = true;
98
118
  try {
99
- const res = await sendRequired(
100
- this.client,
119
+ if (measureSkew) this.skewCalc.start();
120
+ const res = await this.client.send(
101
121
  "/connectivity/check",
102
122
  undefined,
103
123
  requestZ,
104
124
  responseZ,
105
125
  );
126
+ if (measureSkew) {
127
+ this.skewCalc.end(res.nodeTime);
128
+ this._state.clockSkew = this.skewCalc.skew;
129
+ this._state.clockSkewExceeded = this.skewCalc.exceeds(this.clockSkewThreshold);
130
+ if (this._state.clockSkewExceeded) {
131
+ const direction = this.skewCalc.skew.valueOf() > 0n ? "ahead of" : "behind";
132
+ console.warn(
133
+ `Measured excessive clock skew between this host and ` +
134
+ `the Synnax Core. This host is ${direction} the Synnax Core ` +
135
+ `by approximately ${this.skewCalc.skew.abs().toString()}.`,
136
+ );
137
+ }
138
+ }
106
139
  const nodeVersion = res.nodeVersion;
107
140
  const clientVersion = this.clientVersion;
108
141
  const warned = this.versionWarned;
@@ -140,8 +173,13 @@ export class Checker {
140
173
  this._state.status = "failed";
141
174
  this._state.error = err as Error;
142
175
  this._state.message = this.state.error?.message;
176
+ } finally {
177
+ this.checking = false;
143
178
  }
144
- if (this.onChangeHandlers.length > 0 && prevStatus !== this._state.status)
179
+ const changed =
180
+ prevStatus !== this._state.status ||
181
+ prevSkewExceeded !== this._state.clockSkewExceeded;
182
+ if (this.onChangeHandlers.length > 0 && changed)
145
183
  this.onChangeHandlers.forEach((handler) => handler(this.state));
146
184
  return this.state;
147
185
  }