@synnaxlabs/client 0.15.3 → 0.16.3

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.
@@ -7,6 +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 { type UnaryClient } from "@synnaxlabs/freighter";
10
11
  import {
11
12
  DataType,
12
13
  Rate,
@@ -16,7 +17,7 @@ import {
16
17
  type TimeRange,
17
18
  type AsyncTermSearcher,
18
19
  toArray,
19
- type CrudeTimeSpan,
20
+ type CrudeTimeStamp,
20
21
  } from "@synnaxlabs/x";
21
22
 
22
23
  import { type Creator } from "@/channel/creator";
@@ -28,25 +29,65 @@ import {
28
29
  payload,
29
30
  type NewPayload,
30
31
  } from "@/channel/payload";
31
- import { analyzeParams, CacheRetriever, ClusterRetriever, DebouncedBatchRetriever, type Retriever } from "@/channel/retriever";
32
- import { QueryError } from "@/errors";
32
+ import {
33
+ analyzeParams,
34
+ CacheRetriever,
35
+ ClusterRetriever,
36
+ DebouncedBatchRetriever,
37
+ type Retriever,
38
+ } from "@/channel/retriever";
39
+ import { MultipleResultsError, NoResultsError, ValidationError } from "@/errors";
33
40
  import { type framer } from "@/framer";
34
- import { UnaryClient } from "@synnaxlabs/freighter";
35
41
 
36
42
  /**
37
- * Represents a Channel in a Synnax database. It should not be instantiated
38
- * directly, but rather created or retrieved from a {@link Client}.
43
+ * Represents a Channel in a Synnax database. Typically, channels should not be
44
+ * instantiated directly, but instead created via the `.channels.create` or retrieved
45
+ * via the `.channels.retrieve` method on a Synnax client.
46
+ *
47
+ * Please refer to the [Synnax documentation](https://docs.synnaxlabs.com) for detailed
48
+ * information on what channels are and how to use them.
39
49
  */
40
50
  export class Channel {
41
51
  private readonly _frameClient: framer.Client | null;
42
- key: Key;
43
- name: string;
44
- rate: Rate;
45
- dataType: DataType;
46
- leaseholder: number;
47
- index: Key;
48
- isIndex: boolean;
49
- alias: string | undefined;
52
+ /**
53
+ * A unique key identifying the channel in the Synnax database. This key is
54
+ * automatically assigned by Synnax.
55
+ */
56
+ readonly key: Key;
57
+ /**
58
+ * A human-readable name for the channel. This name is not guaranteed to be
59
+ * unique.
60
+ */
61
+ readonly name: string;
62
+ /**
63
+ * The rate at which the channel samples telemetry. This only applies to fixed rate
64
+ * channels, and will be 0 if the channel is indexed.
65
+ */
66
+ readonly rate: Rate;
67
+ /**
68
+ * The data type of the channel.
69
+ */
70
+ readonly dataType: DataType;
71
+ /**
72
+ * The key of the node in the Synnax cluster that holds the 'lease' over the channel
73
+ * i.e. it's the only node in the cluster allowed to accept writes to the channel. This
74
+ * property is mostly for internal use.
75
+ */
76
+ readonly leaseholder: number;
77
+ /**
78
+ * The key of the index channel that this channel is associated with i.e. the channel
79
+ * that stores its timestamps.
80
+ */
81
+ readonly index: Key;
82
+ /**
83
+ * This is set to true if the channel is an index channel, and false otherwise.
84
+ */
85
+ readonly isIndex: boolean;
86
+ /**
87
+ * An alias for the channel under a specific range. This parameter is unstable and
88
+ * should not be relied upon in the current version of Synnax.
89
+ */
90
+ readonly alias: string | undefined;
50
91
 
51
92
  constructor({
52
93
  dataType,
@@ -75,10 +116,15 @@ export class Channel {
75
116
 
76
117
  private get framer(): framer.Client {
77
118
  if (this._frameClient == null)
78
- throw new Error("cannot read from a channel that has not been created");
119
+ throw new ValidationError("cannot read from a channel that has not been created");
79
120
  return this._frameClient;
80
121
  }
81
122
 
123
+ /**
124
+ * Returns the payload representation of this channel i.e. a pure JS object with
125
+ * all of the channel fields but without any methods. This is used internally for
126
+ * network transportation, but also provided to you as a convenience.
127
+ */
82
128
  get payload(): Payload {
83
129
  return payload.parse({
84
130
  key: this.key,
@@ -108,14 +154,15 @@ export class Channel {
108
154
  * @param start - The starting timestamp of the first sample in data.
109
155
  * @param data - THe telemetry to write to the channel.
110
156
  */
111
- async write(start: CrudeTimeSpan, data: NativeTypedArray): Promise<void> {
157
+ async write(start: CrudeTimeStamp, data: NativeTypedArray): Promise<void> {
112
158
  return await this.framer.write(this.key, start, data);
113
159
  }
114
160
  }
115
161
 
116
162
  /**
117
163
  * The core client class for executing channel operations against a Synnax
118
- * cluster.
164
+ * cluster. This class should not be instantiated directly, and instead should be used
165
+ * through the `channels` property of an {@link Synnax} client.
119
166
  */
120
167
  export class Client implements AsyncTermSearcher<string, Key, Channel> {
121
168
  private readonly frameClient: framer.Client;
@@ -124,45 +171,127 @@ export class Client implements AsyncTermSearcher<string, Key, Channel> {
124
171
  private readonly client: UnaryClient;
125
172
 
126
173
  constructor(
127
- segmentClient: framer.Client,
174
+ frameClient: framer.Client,
128
175
  retriever: Retriever,
129
176
  client: UnaryClient,
130
- creator: Creator
131
- ) {
132
- this.frameClient = segmentClient;
177
+ creator: Creator,
178
+ ) {
179
+ this.frameClient = frameClient;
133
180
  this.retriever = retriever;
134
181
  this.client = client;
135
182
  this.creator = creator;
136
183
  }
137
184
 
185
+ /**
186
+ * Creates a single channel with the given properties.
187
+ *
188
+ * @param name - A human-readable name for the channel.
189
+ * @param rate - The rate of the channel. This only applies to fixed rate channels.
190
+ * @param dataType - The data type for the samples stored in the channel.
191
+ * @param index - The key of the index channel that this channel should be associated
192
+ * with. An 'index' channel is a channel that stores timestamps for other channels. Refer
193
+ * to the Synnax documentation (https://docs.synnaxlabs.com) for more information. The
194
+ * index channel must have already been created. This field does not need to be specified
195
+ * if the channel is an index channel, or the channel is a fixed rate channel. If this
196
+ * value is specified, the 'rate' parameter will be ignored.
197
+ * @param isIndex - Set to true if the channel is an index channel, and false otherwise.
198
+ * Index channels must have a data type of `DataType.TIMESTAMP`.
199
+ * @returns the created channel. {@see Channel}
200
+ * @throws {ValidationError} if any of the parameters for creating the channel are
201
+ * invalid.
202
+ *
203
+ * @example
204
+ * ```typescript
205
+ * const indexChannel = await client.channels.create({
206
+ * name: "time",
207
+ * dataType: DataType.TIMESTAMP,
208
+ * isIndex: true,
209
+ * })
210
+ *
211
+ *
212
+ * const dataChannel = await client.channels.create({
213
+ * name: "temperature",
214
+ * dataType: DataType.FLOAT,
215
+ * index: indexChannel.key,
216
+ * });
217
+ * ```
218
+ */
138
219
  async create(channel: NewPayload): Promise<Channel>;
139
220
 
140
- async create(channels: NewPayload[]): Promise<Channel[]>;
141
-
142
221
  /**
143
- * Creates a new channel with the given properties.
222
+ * Creates multiple channels with the given properties. The order of the channels
223
+ * returned is guaranteed to match the order of the channels passed in.
224
+ *
225
+ * @param channels - An array of channel properties to create.
226
+ * For each channel, the following properties should be considered:
144
227
  *
145
- * @param props.rate - The rate of the channel.
146
- * @param props.dataType - The data type of the channel.
147
- * @param props.name - The name of the channel. Optional.
148
- * @param props.nodeKey - The ID of the node that holds the lease on the
149
- * channel. If you don't know what this is, don't worry about it.
150
- * @returns The created channel.
228
+ * @param name - A human-readable name for the channel.
229
+ * @param rate - The rate of the channel. This only applies to fixed rate channels. If
230
+ * the 'index' parameter is specified or 'isIndex' is set to true, this parameter will
231
+ * be ignored.
232
+ * @param dataType - The data type for the samples stored in the channel.
233
+ * @param index - The key of the index channel that this channel should be associated
234
+ * with. An 'index' channel is a channel that stores timestamps for other channels. Refer
235
+ * to the Synnax documentation (https://docs.synnaxlabs.com) for more information. The
236
+ * index channel must have already been created. This field does not need to be specified
237
+ * if the channel is an index channel, or the channel is a fixed rate channel. If this
238
+ * value is specified, the 'rate' parameter will be ignored.
239
+ * @param isIndex - Set to true if the channel is an index channel, and false otherwise.
240
+ * Index channels must have a data type of `DataType.TIMESTAMP`.
241
+ *
242
+ * @param channels
151
243
  */
244
+ async create(channels: NewPayload[]): Promise<Channel[]>;
245
+
152
246
  async create(channels: NewPayload | NewPayload[]): Promise<Channel | Channel[]> {
247
+ console.log("ABC");
153
248
  const single = !Array.isArray(channels);
154
- const res = this.sugar(await this.creator.create(toArray(channels)));
249
+ const payloads = await this.creator.create(toArray(channels));
250
+ const res = this.sugar(payloads);
155
251
  return single ? res[0] : res;
156
252
  }
157
253
 
254
+ /**
255
+ * Retrieves a channel from the database using the given key or name.
256
+ *
257
+ * @param channel - The key or name of the channel to retrieve.
258
+ * @param options - Optional parameters to control the retrieval process.
259
+ * @param options.dataTypes - Limits the query to only channels with the specified data
260
+ * type.
261
+ * @param options.notDataTypes - Limits the query to only channels without the specified
262
+ * data type.
263
+ *
264
+ * @returns The retrieved channel.
265
+ * @throws {NotFoundError} if the channel does not exist in the cluster.
266
+ * @throws {MultipleResultsError} is only thrown if the channel is retrieved by name,
267
+ * and multiple channels with the same name exist in the cluster.
268
+ *
269
+ * @example
270
+ *
271
+ * ```typescript
272
+ * const channel = await client.channels.retrieve("temperature");
273
+ * const channel = await client.channels.retrieve(1);
274
+ * ```
275
+ */
158
276
  async retrieve(channel: KeyOrName, rangeKey?: string): Promise<Channel>;
159
277
 
278
+ /**
279
+ * Retrieves multiple channels from the database using the provided keys or the
280
+ * provided names.
281
+ *
282
+ * @param channels - The keys or the names of the channels to retrieve. Note that
283
+ * this method does not support mixing keys and names in the same call.
284
+ * @param options - Optional parameters to control the retrieval process.
285
+ * @param options.dataTypes - Limits the query to only channels with the specified data
286
+ * type.
287
+ * @param options.notDataTypes - Limits the query to only channels without the specified
288
+ *
289
+ */
160
290
  async retrieve(channels: Params, rangeKey?: string): Promise<Channel[]>;
161
291
 
162
292
  /**
163
293
  * Retrieves a channel from the database using the given parameters.
164
- * @param props.key - The key of the channel to retrieve.
165
- * @param props.name - The name of the channel to retrieve. If props.key is set,
294
+ *
166
295
  * this will be ignored.
167
296
  * @returns The retrieved channel.
168
297
  * @raises {QueryError} If the channel does not exist or if multiple results are returned.
@@ -172,9 +301,10 @@ export class Client implements AsyncTermSearcher<string, Key, Channel> {
172
301
  if (normalized.length === 0) return [];
173
302
  const res = this.sugar(await this.retriever.retrieve(channels, rangeKey));
174
303
  if (!single) return res;
175
- if (res.length === 0) throw new QueryError(`channel matching ${actual} not found`);
304
+ if (res.length === 0)
305
+ throw new NoResultsError(`channel matching ${actual} not found`);
176
306
  if (res.length > 1)
177
- throw new QueryError(`multiple channels matching ${actual} found`);
307
+ throw new MultipleResultsError(`multiple channels matching ${actual} found`);
178
308
  return res[0];
179
309
  }
180
310
 
@@ -190,8 +320,10 @@ export class Client implements AsyncTermSearcher<string, Key, Channel> {
190
320
  return this.sugar(await this.retriever.page(offset, limit, rangeKey));
191
321
  }
192
322
 
193
- createDebouncedBatchRetriever(deb:number = 10): Retriever {
194
- return new CacheRetriever(new DebouncedBatchRetriever(new ClusterRetriever(this.client),deb))
323
+ createDebouncedBatchRetriever(deb: number = 10): Retriever {
324
+ return new CacheRetriever(
325
+ new DebouncedBatchRetriever(new ClusterRetriever(this.client), deb),
326
+ );
195
327
  }
196
328
 
197
329
  private sugar(payloads: Payload[]): Channel[] {
@@ -220,4 +352,4 @@ class SearcherUnderRange implements AsyncTermSearcher<string, Key, Channel> {
220
352
  async retrieve(channels: Key[]): Promise<Channel[]> {
221
353
  return await this.client.retrieve(channels, this.rangeKey);
222
354
  }
223
- }
355
+ }
@@ -45,8 +45,8 @@ export type NewPayload = z.input<typeof newPayload>;
45
45
  export const parseChannels = (channels: NewPayload[]): NewPayload[] =>
46
46
  channels.map((channel) => ({
47
47
  name: channel.name,
48
- dataType: new DataType(channel.dataType),
49
- rate: new Rate(channel.rate ?? 0),
48
+ dataType: channel.dataType,
49
+ rate: channel.rate ?? 0,
50
50
  leaseholder: channel.leaseholder,
51
51
  index: channel.index,
52
52
  isIndex: channel.isIndex,
package/src/client.ts CHANGED
@@ -15,15 +15,15 @@ import { channel } from "@/channel";
15
15
  import { connection } from "@/connection";
16
16
  import { errorsMiddleware } from "@/errors";
17
17
  import { framer } from "@/framer";
18
+ import { hardware } from "@/hardware";
19
+ import { device } from "@/hardware/device";
20
+ import { rack } from "@/hardware/rack";
21
+ import { task } from "@/hardware/task";
18
22
  import { label } from "@/label";
19
23
  import { ontology } from "@/ontology";
20
24
  import { ranger } from "@/ranger";
21
25
  import { Transport } from "@/transport";
22
26
  import { workspace } from "@/workspace";
23
- import { hardware } from "@/hardware";
24
- import { device } from "@/hardware/device";
25
- import { rack } from "@/hardware/rack";
26
- import { task } from "@/hardware/task";
27
27
 
28
28
  export const synnaxPropsZ = z.object({
29
29
  host: z.string().min(1),
@@ -96,7 +96,12 @@ export default class Synnax {
96
96
  );
97
97
  const chCreator = new channel.Creator(this.transport.unary);
98
98
  this.telem = new framer.Client(this.transport.stream, chRetriever);
99
- this.channels = new channel.Client(this.telem, chRetriever, this.transport.unary, chCreator);
99
+ this.channels = new channel.Client(
100
+ this.telem,
101
+ chRetriever,
102
+ this.transport.unary,
103
+ chCreator,
104
+ );
100
105
  this.connectivity = new connection.Checker(
101
106
  this.transport.unary,
102
107
  connectivityPollFrequency,
package/src/errors.ts CHANGED
@@ -95,6 +95,10 @@ export class UnexpectedError extends BaseError {
95
95
  */
96
96
  export class QueryError extends BaseError {}
97
97
 
98
+ export class NoResultsError extends QueryError {}
99
+
100
+ export class MultipleResultsError extends QueryError {}
101
+
98
102
  /**
99
103
  * RouteError is raised when a routing error occurs.
100
104
  */
@@ -27,6 +27,7 @@ describe("Streamer", () => {
27
27
  test("happy path", async () => {
28
28
  const ch = await newChannel();
29
29
  const streamer = await client.telem.newStreamer(ch.key);
30
+ await new Promise((resolve) => setTimeout(resolve, 100));
30
31
  const writer = await client.telem.newWriter({
31
32
  start: TimeStamp.now(),
32
33
  channels: ch.key,