@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.
- package/.pytest_cache/README.md +8 -0
- package/.turbo/turbo-build.log +10 -10
- package/README.md +11 -2
- package/dist/channel/client.d.ts +147 -15
- package/dist/client.cjs +21 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/{client.es.js → client.js} +4775 -4653
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +4 -0
- package/dist/ontology/payload.d.ts +38 -38
- package/examples/index.js +1 -0
- package/examples/package.json +16 -0
- package/package.json +5 -5
- package/src/channel/channel.spec.ts +19 -2
- package/src/channel/client.ts +170 -38
- package/src/channel/payload.ts +2 -2
- package/src/client.ts +10 -5
- package/src/errors.ts +4 -0
- package/src/framer/streamer.spec.ts +1 -0
- package/dist/client.cjs.js +0 -21
- package/dist/client.cjs.js.map +0 -1
- package/dist/client.es.js.map +0 -1
package/src/channel/client.ts
CHANGED
|
@@ -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
|
|
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 {
|
|
32
|
-
|
|
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.
|
|
38
|
-
* directly, but
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
174
|
+
frameClient: framer.Client,
|
|
128
175
|
retriever: Retriever,
|
|
129
176
|
client: UnaryClient,
|
|
130
|
-
creator: Creator
|
|
131
|
-
|
|
132
|
-
this.frameClient =
|
|
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
|
|
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
|
|
146
|
-
* @param
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* @
|
|
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
|
|
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
|
-
*
|
|
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)
|
|
304
|
+
if (res.length === 0)
|
|
305
|
+
throw new NoResultsError(`channel matching ${actual} not found`);
|
|
176
306
|
if (res.length > 1)
|
|
177
|
-
throw new
|
|
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(
|
|
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
|
+
}
|
package/src/channel/payload.ts
CHANGED
|
@@ -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:
|
|
49
|
-
rate:
|
|
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(
|
|
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,
|