@synnaxlabs/client 0.2.1 → 0.13.6
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/.eslintrc.cjs +18 -0
- package/.pytest_cache/README.md +8 -0
- package/.turbo/turbo-build.log +16 -0
- package/LICENSE +4 -21
- package/{build/module/lib → dist/auth}/auth.d.ts +16 -19
- package/dist/auth/index.d.ts +1 -0
- package/dist/cdc/external.d.ts +1 -0
- package/dist/cdc/index.d.ts +1 -0
- package/dist/cdc/observable.d.ts +17 -0
- package/dist/channel/client.d.ts +58 -0
- package/dist/channel/creator.d.ts +8 -0
- package/dist/channel/external.d.ts +4 -0
- package/dist/channel/index.d.ts +1 -0
- package/dist/channel/payload.d.ts +63 -0
- package/dist/channel/retriever.d.ts +49 -0
- package/dist/client.cjs.js +23050 -0
- package/dist/client.cjs.js.map +1 -0
- package/dist/client.d.ts +73 -0
- package/dist/client.es.js +23050 -0
- package/dist/client.es.js.map +1 -0
- package/dist/connection/checker.d.ts +66 -0
- package/dist/connection/index.d.ts +1 -0
- package/dist/control/authority.d.ts +6 -0
- package/dist/control/external.d.ts +2 -0
- package/dist/control/index.d.ts +1 -0
- package/dist/control/state.d.ts +81 -0
- package/{build/main/lib → dist}/errors.d.ts +6 -3
- package/dist/framer/adapter.d.ts +21 -0
- package/dist/framer/client.d.ts +44 -0
- package/dist/framer/external.d.ts +5 -0
- package/dist/framer/frame.d.ts +251 -0
- package/dist/framer/index.d.ts +1 -0
- package/{build/module/lib/segment → dist/framer}/iterator.d.ts +32 -64
- package/dist/framer/streamProxy.d.ts +12 -0
- package/dist/framer/streamer.d.ts +17 -0
- package/dist/framer/writer.d.ts +257 -0
- package/dist/index.d.ts +16 -0
- package/dist/label/client.d.ts +25 -0
- package/dist/label/external.d.ts +4 -0
- package/dist/label/index.d.ts +1 -0
- package/dist/label/payload.d.ts +20 -0
- package/dist/label/retriever.d.ts +13 -0
- package/dist/label/writer.d.ts +26 -0
- package/dist/ontology/cdc.d.ts +25 -0
- package/dist/ontology/client.d.ts +25 -0
- package/dist/ontology/external.d.ts +3 -0
- package/dist/ontology/group/client.d.ts +11 -0
- package/dist/ontology/group/external.d.ts +2 -0
- package/dist/ontology/group/group.d.ts +7 -0
- package/dist/ontology/group/index.d.ts +1 -0
- package/dist/ontology/group/payload.d.ts +40 -0
- package/dist/ontology/group/writer.d.ts +13 -0
- package/dist/ontology/index.d.ts +1 -0
- package/dist/ontology/ontology.spec.d.ts +1 -0
- package/dist/ontology/payload.d.ts +235 -0
- package/dist/ontology/retriever.d.ts +12 -0
- package/dist/ontology/signals.d.ts +25 -0
- package/dist/ontology/writer.d.ts +9 -0
- package/dist/ranger/active.d.ts +9 -0
- package/dist/ranger/alias.d.ts +32 -0
- package/dist/ranger/client.d.ts +31 -0
- package/dist/ranger/external.d.ts +6 -0
- package/dist/ranger/index.d.ts +1 -0
- package/dist/ranger/kv.d.ts +50 -0
- package/dist/ranger/payload.d.ts +94 -0
- package/dist/ranger/range.d.ts +29 -0
- package/dist/ranger/ranger.spec.d.ts +1 -0
- package/dist/ranger/retriever.d.ts +10 -0
- package/dist/ranger/writer.d.ts +9 -0
- package/{build/main → dist}/setupspecs.d.ts +2 -2
- package/dist/signals/external.d.ts +1 -0
- package/dist/signals/index.d.ts +1 -0
- package/dist/signals/observable.d.ts +17 -0
- package/dist/transport.d.ts +10 -0
- package/dist/user/index.d.ts +1 -0
- package/{build/main/lib → dist}/user/payload.d.ts +3 -3
- package/dist/util/telem.d.ts +2 -0
- package/dist/workspace/client.d.ts +22 -0
- package/dist/workspace/external.d.ts +2 -0
- package/dist/workspace/index.d.ts +1 -0
- package/dist/workspace/lineplot/client.d.ts +15 -0
- package/dist/workspace/lineplot/external.d.ts +2 -0
- package/dist/workspace/lineplot/index.d.ts +1 -0
- package/dist/workspace/lineplot/linePlot.spec.d.ts +1 -0
- package/dist/workspace/lineplot/payload.d.ts +31 -0
- package/dist/workspace/lineplot/retriever.d.ts +9 -0
- package/dist/workspace/lineplot/writer.d.ts +39 -0
- package/dist/workspace/payload.d.ts +31 -0
- package/dist/workspace/pid/client.d.ts +16 -0
- package/dist/workspace/pid/external.d.ts +2 -0
- package/dist/workspace/pid/index.d.ts +1 -0
- package/dist/workspace/pid/payload.d.ts +37 -0
- package/dist/workspace/pid/pid.spec.d.ts +1 -0
- package/dist/workspace/pid/retriever.d.ts +9 -0
- package/dist/workspace/pid/writer.d.ts +46 -0
- package/dist/workspace/retriever.d.ts +12 -0
- package/dist/workspace/workspace.spec.d.ts +1 -0
- package/dist/workspace/writer.d.ts +55 -0
- package/package.json +27 -98
- package/src/auth/auth.spec.ts +46 -0
- package/src/auth/auth.ts +83 -0
- package/src/auth/index.ts +10 -0
- package/src/channel/channel.spec.ts +82 -0
- package/src/channel/client.ts +209 -0
- package/src/channel/creator.ts +43 -0
- package/src/channel/external.ts +13 -0
- package/src/channel/index.ts +10 -0
- package/src/channel/payload.ts +52 -0
- package/src/channel/retriever.ts +160 -0
- package/src/client.ts +116 -0
- package/src/connection/checker.ts +104 -0
- package/src/connection/connection.spec.ts +35 -0
- package/src/connection/index.ts +10 -0
- package/src/control/authority.ts +26 -0
- package/src/control/external.ts +11 -0
- package/src/control/index.ts +10 -0
- package/src/control/state.spec.ts +24 -0
- package/src/control/state.ts +133 -0
- package/src/errors.ts +163 -0
- package/src/framer/adapter.ts +116 -0
- package/src/framer/client.ts +116 -0
- package/src/framer/external.ts +14 -0
- package/src/framer/frame.spec.ts +317 -0
- package/src/framer/frame.ts +412 -0
- package/src/framer/index.ts +10 -0
- package/src/framer/iterator.spec.ts +62 -0
- package/src/framer/iterator.ts +240 -0
- package/src/framer/streamProxy.ts +59 -0
- package/src/framer/streamer.spec.ts +42 -0
- package/src/framer/streamer.ts +86 -0
- package/src/framer/writer.spec.ts +52 -0
- package/src/framer/writer.ts +236 -0
- package/src/index.ts +53 -0
- package/src/label/client.ts +103 -0
- package/src/label/external.ts +13 -0
- package/src/label/index.ts +10 -0
- package/src/label/label.spec.ts +51 -0
- package/src/label/payload.ts +29 -0
- package/src/label/retriever.ts +65 -0
- package/src/label/writer.ts +90 -0
- package/src/ontology/client.ts +104 -0
- package/src/ontology/external.ts +12 -0
- package/src/ontology/group/client.ts +40 -0
- package/src/ontology/group/external.ts +11 -0
- package/src/ontology/group/group.spec.ts +46 -0
- package/src/ontology/group/group.ts +27 -0
- package/src/ontology/group/index.ts +10 -0
- package/src/ontology/group/payload.ts +65 -0
- package/src/ontology/group/writer.ts +48 -0
- package/src/ontology/index.ts +10 -0
- package/src/ontology/ontology.spec.ts +114 -0
- package/src/ontology/payload.ts +118 -0
- package/src/ontology/retriever.ts +91 -0
- package/src/ontology/signals.ts +135 -0
- package/src/ontology/writer.ts +49 -0
- package/src/ranger/active.ts +56 -0
- package/src/ranger/alias.ts +183 -0
- package/src/ranger/client.ts +129 -0
- package/src/ranger/external.ts +15 -0
- package/src/ranger/index.ts +10 -0
- package/src/ranger/kv.ts +91 -0
- package/src/ranger/payload.ts +70 -0
- package/src/ranger/range.ts +95 -0
- package/src/ranger/ranger.spec.ts +201 -0
- package/src/ranger/retriever.ts +50 -0
- package/src/ranger/writer.ts +80 -0
- package/src/setupspecs.ts +25 -0
- package/src/signals/external.ts +10 -0
- package/src/signals/index.ts +10 -0
- package/src/signals/observable.ts +80 -0
- package/src/transport.ts +39 -0
- package/src/user/index.ts +10 -0
- package/src/user/payload.ts +17 -0
- package/src/util/telem.ts +19 -0
- package/src/vite-env.d.ts +11 -0
- package/src/workspace/client.ts +75 -0
- package/src/workspace/external.ts +11 -0
- package/src/workspace/index.ts +10 -0
- package/src/workspace/lineplot/client.ts +51 -0
- package/src/workspace/lineplot/external.ts +11 -0
- package/src/workspace/lineplot/index.ts +10 -0
- package/src/workspace/lineplot/linePlot.spec.ts +78 -0
- package/src/workspace/lineplot/payload.ts +29 -0
- package/src/workspace/lineplot/retriever.ts +49 -0
- package/src/workspace/lineplot/writer.ts +109 -0
- package/src/workspace/payload.ts +29 -0
- package/src/workspace/pid/client.ts +55 -0
- package/src/workspace/pid/external.ts +11 -0
- package/src/workspace/pid/index.ts +10 -0
- package/src/workspace/pid/payload.ts +31 -0
- package/src/workspace/pid/pid.spec.ts +111 -0
- package/src/workspace/pid/retriever.ts +45 -0
- package/src/workspace/pid/writer.ts +130 -0
- package/src/workspace/retriever.ts +66 -0
- package/src/workspace/workspace.spec.ts +62 -0
- package/src/workspace/writer.ts +103 -0
- package/tsconfig.json +7 -0
- package/tsconfig.vite.json +4 -0
- package/vite.config.ts +25 -0
- package/CHANGELOG.md +0 -5
- package/build/main/index.d.ts +0 -4
- package/build/main/index.js +0 -35
- package/build/main/lib/auth.d.ts +0 -54
- package/build/main/lib/auth.js +0 -62
- package/build/main/lib/auth.spec.js +0 -39
- package/build/main/lib/channel/channel.spec.js +0 -49
- package/build/main/lib/channel/client.d.ts +0 -94
- package/build/main/lib/channel/client.js +0 -134
- package/build/main/lib/channel/creator.d.ts +0 -19
- package/build/main/lib/channel/creator.js +0 -44
- package/build/main/lib/channel/payload.d.ts +0 -25
- package/build/main/lib/channel/payload.js +0 -18
- package/build/main/lib/channel/registry.d.ts +0 -9
- package/build/main/lib/channel/registry.js +0 -37
- package/build/main/lib/channel/retriever.d.ts +0 -11
- package/build/main/lib/channel/retriever.js +0 -39
- package/build/main/lib/client.d.ts +0 -30
- package/build/main/lib/client.js +0 -46
- package/build/main/lib/errors.js +0 -122
- package/build/main/lib/segment/client.d.ts +0 -62
- package/build/main/lib/segment/client.js +0 -95
- package/build/main/lib/segment/iterator.d.ts +0 -134
- package/build/main/lib/segment/iterator.js +0 -253
- package/build/main/lib/segment/iterator.spec.js +0 -73
- package/build/main/lib/segment/payload.d.ts +0 -16
- package/build/main/lib/segment/payload.js +0 -13
- package/build/main/lib/segment/splitter.d.ts +0 -7
- package/build/main/lib/segment/splitter.js +0 -25
- package/build/main/lib/segment/typed.d.ts +0 -15
- package/build/main/lib/segment/typed.js +0 -49
- package/build/main/lib/segment/validator.d.ts +0 -22
- package/build/main/lib/segment/validator.js +0 -64
- package/build/main/lib/segment/writer.d.ts +0 -98
- package/build/main/lib/segment/writer.js +0 -183
- package/build/main/lib/segment/writer.spec.js +0 -90
- package/build/main/lib/telem.d.ts +0 -395
- package/build/main/lib/telem.js +0 -553
- package/build/main/lib/telem.spec.js +0 -152
- package/build/main/lib/transport.d.ts +0 -10
- package/build/main/lib/transport.js +0 -22
- package/build/main/lib/user/payload.js +0 -9
- package/build/main/lib/util/telem.d.ts +0 -2
- package/build/main/lib/util/telem.js +0 -13
- package/build/main/setupspecs.js +0 -17
- package/build/module/index.d.ts +0 -4
- package/build/module/index.js +0 -5
- package/build/module/lib/auth.js +0 -63
- package/build/module/lib/auth.spec.js +0 -34
- package/build/module/lib/channel/channel.spec.js +0 -44
- package/build/module/lib/channel/client.d.ts +0 -94
- package/build/module/lib/channel/client.js +0 -134
- package/build/module/lib/channel/creator.d.ts +0 -19
- package/build/module/lib/channel/creator.js +0 -42
- package/build/module/lib/channel/payload.d.ts +0 -25
- package/build/module/lib/channel/payload.js +0 -15
- package/build/module/lib/channel/registry.d.ts +0 -9
- package/build/module/lib/channel/registry.js +0 -36
- package/build/module/lib/channel/retriever.d.ts +0 -11
- package/build/module/lib/channel/retriever.js +0 -37
- package/build/module/lib/client.d.ts +0 -30
- package/build/module/lib/client.js +0 -44
- package/build/module/lib/errors.d.ts +0 -53
- package/build/module/lib/errors.js +0 -113
- package/build/module/lib/segment/client.d.ts +0 -62
- package/build/module/lib/segment/client.js +0 -94
- package/build/module/lib/segment/iterator.js +0 -248
- package/build/module/lib/segment/iterator.spec.js +0 -68
- package/build/module/lib/segment/payload.d.ts +0 -16
- package/build/module/lib/segment/payload.js +0 -10
- package/build/module/lib/segment/splitter.d.ts +0 -7
- package/build/module/lib/segment/splitter.js +0 -26
- package/build/module/lib/segment/typed.d.ts +0 -15
- package/build/module/lib/segment/typed.js +0 -49
- package/build/module/lib/segment/validator.d.ts +0 -22
- package/build/module/lib/segment/validator.js +0 -60
- package/build/module/lib/segment/writer.d.ts +0 -98
- package/build/module/lib/segment/writer.js +0 -183
- package/build/module/lib/segment/writer.spec.js +0 -85
- package/build/module/lib/telem.d.ts +0 -395
- package/build/module/lib/telem.js +0 -545
- package/build/module/lib/telem.spec.js +0 -147
- package/build/module/lib/transport.d.ts +0 -10
- package/build/module/lib/transport.js +0 -22
- package/build/module/lib/user/payload.d.ts +0 -12
- package/build/module/lib/user/payload.js +0 -6
- package/build/module/lib/util/telem.d.ts +0 -2
- package/build/module/lib/util/telem.js +0 -9
- package/build/module/setupspecs.d.ts +0 -4
- package/build/module/setupspecs.js +0 -16
- /package/{build/main/lib → dist/auth}/auth.spec.d.ts +0 -0
- /package/{build/main/lib → dist}/channel/channel.spec.d.ts +0 -0
- /package/{build/main/lib/segment/iterator.spec.d.ts → dist/connection/connection.spec.d.ts} +0 -0
- /package/{build/main/lib/segment/writer.spec.d.ts → dist/control/state.spec.d.ts} +0 -0
- /package/{build/main/lib/telem.spec.d.ts → dist/framer/frame.spec.d.ts} +0 -0
- /package/{build/module/lib/segment → dist/framer}/iterator.spec.d.ts +0 -0
- /package/{build/module/lib/auth.spec.d.ts → dist/framer/streamer.spec.d.ts} +0 -0
- /package/{build/module/lib/segment → dist/framer}/writer.spec.d.ts +0 -0
- /package/{build/module/lib/channel/channel.spec.d.ts → dist/label/label.spec.d.ts} +0 -0
- /package/{build/module/lib/telem.spec.d.ts → dist/ontology/group/group.spec.d.ts} +0 -0
package/src/auth/auth.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
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 { Middleware, UnaryClient } from "@synnaxlabs/freighter";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
|
|
13
|
+
import { AuthError } from "@/errors";
|
|
14
|
+
import { user } from "@/user";
|
|
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
|
+
export const insecureCredentialsZ = z.object({
|
|
29
|
+
username: z.string(),
|
|
30
|
+
password: z.string(),
|
|
31
|
+
});
|
|
32
|
+
export type InsecureCredentials = z.infer<typeof insecureCredentialsZ>;
|
|
33
|
+
|
|
34
|
+
export const tokenResponseZ = z.object({
|
|
35
|
+
token: z.string(),
|
|
36
|
+
user: user.payloadZ,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export type TokenResponse = z.infer<typeof tokenResponseZ>;
|
|
40
|
+
|
|
41
|
+
const LOGIN_ENDPOINT = "/auth/login";
|
|
42
|
+
|
|
43
|
+
export class Client {
|
|
44
|
+
private token: string | undefined;
|
|
45
|
+
private readonly client: UnaryClient;
|
|
46
|
+
authenticating: Promise<void> | undefined;
|
|
47
|
+
authenticated: boolean;
|
|
48
|
+
user: user.Payload | undefined;
|
|
49
|
+
|
|
50
|
+
constructor(client: UnaryClient, creds: InsecureCredentials) {
|
|
51
|
+
this.client = client;
|
|
52
|
+
this.authenticated = false;
|
|
53
|
+
this.authenticate(creds);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
authenticate(creds: InsecureCredentials): void {
|
|
57
|
+
this.authenticating = new Promise((resolve, reject) => {
|
|
58
|
+
this.client
|
|
59
|
+
.send<typeof insecureCredentialsZ, typeof tokenResponseZ>(
|
|
60
|
+
LOGIN_ENDPOINT,
|
|
61
|
+
creds,
|
|
62
|
+
tokenResponseZ,
|
|
63
|
+
)
|
|
64
|
+
.then(([res, err]) => {
|
|
65
|
+
if (err != null) return reject(err);
|
|
66
|
+
this.token = res?.token;
|
|
67
|
+
this.user = res?.user;
|
|
68
|
+
this.authenticated = true;
|
|
69
|
+
resolve();
|
|
70
|
+
})
|
|
71
|
+
.catch(reject);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
middleware(): Middleware {
|
|
76
|
+
return tokenMiddleware(async () => {
|
|
77
|
+
if (!this.authenticated) await this.authenticating;
|
|
78
|
+
if (this.token == null)
|
|
79
|
+
throw new AuthError("[auth] - attempting to authenticate without a token");
|
|
80
|
+
return this.token;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
export * as auth from "@/auth/auth";
|
|
@@ -0,0 +1,82 @@
|
|
|
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 { DataType, Rate } from "@synnaxlabs/x";
|
|
11
|
+
import { describe, test, expect } from "vitest";
|
|
12
|
+
|
|
13
|
+
import { QueryError } from "@/errors";
|
|
14
|
+
import { newClient } from "@/setupspecs";
|
|
15
|
+
|
|
16
|
+
const client = newClient();
|
|
17
|
+
|
|
18
|
+
describe("Channel", () => {
|
|
19
|
+
describe("create", () => {
|
|
20
|
+
test("create one", async () => {
|
|
21
|
+
const channel = await client.channels.create({
|
|
22
|
+
name: "test",
|
|
23
|
+
leaseholder: 1,
|
|
24
|
+
rate: Rate.hz(1),
|
|
25
|
+
dataType: DataType.FLOAT32,
|
|
26
|
+
});
|
|
27
|
+
expect(channel.name, "test").toEqual("test");
|
|
28
|
+
expect(channel.leaseholder).toEqual(1);
|
|
29
|
+
expect(channel.rate).toEqual(Rate.hz(1));
|
|
30
|
+
expect(channel.dataType).toEqual(DataType.FLOAT32);
|
|
31
|
+
});
|
|
32
|
+
test("create index and indexed pair", async () => {
|
|
33
|
+
const one = await client.channels.create({
|
|
34
|
+
name: "Time",
|
|
35
|
+
isIndex: true,
|
|
36
|
+
dataType: DataType.TIMESTAMP,
|
|
37
|
+
});
|
|
38
|
+
expect(one.key).not.toEqual(0);
|
|
39
|
+
const two = await client.channels.create({
|
|
40
|
+
name: "test",
|
|
41
|
+
index: one.key,
|
|
42
|
+
dataType: DataType.FLOAT32,
|
|
43
|
+
});
|
|
44
|
+
expect(two.key).not.toEqual(0);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
test("retrieve by key", async () => {
|
|
48
|
+
const channel = await client.channels.create({
|
|
49
|
+
name: "test",
|
|
50
|
+
leaseholder: 1,
|
|
51
|
+
rate: Rate.hz(1),
|
|
52
|
+
dataType: DataType.FLOAT32,
|
|
53
|
+
});
|
|
54
|
+
const retrieved = await client.channels.retrieve(channel.key);
|
|
55
|
+
expect(retrieved.name).toEqual("test");
|
|
56
|
+
expect(retrieved.leaseholder).toEqual(1);
|
|
57
|
+
expect(retrieved.rate).toEqual(Rate.hz(1));
|
|
58
|
+
expect(retrieved.dataType).toEqual(DataType.FLOAT32);
|
|
59
|
+
});
|
|
60
|
+
test("retrieve by key - not found", async () => {
|
|
61
|
+
await expect(async () => await client.channels.retrieve("1-1000")).rejects.toThrow(
|
|
62
|
+
QueryError,
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("retrieve by name", async () => {
|
|
67
|
+
const retrieved = await client.channels.retrieve(["test"]);
|
|
68
|
+
expect(retrieved.length).toBeGreaterThan(0);
|
|
69
|
+
retrieved.forEach((ch) => expect(ch.name).toEqual("test"));
|
|
70
|
+
});
|
|
71
|
+
test("retrieve by key - not found", async () => {
|
|
72
|
+
await expect(async () => await client.channels.retrieve("1-1000")).rejects.toThrow(
|
|
73
|
+
QueryError,
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("retrieve by name", async () => {
|
|
78
|
+
const retrieved = await client.channels.retrieve(["test"]);
|
|
79
|
+
expect(retrieved.length).toBeGreaterThan(0);
|
|
80
|
+
retrieved.forEach((ch) => expect(ch.name).toEqual("test"));
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
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 {
|
|
11
|
+
DataType,
|
|
12
|
+
Rate,
|
|
13
|
+
type NativeTypedArray,
|
|
14
|
+
type CrudeDensity,
|
|
15
|
+
type Series,
|
|
16
|
+
type TimeRange,
|
|
17
|
+
type AsyncTermSearcher,
|
|
18
|
+
toArray,
|
|
19
|
+
type CrudeTimeSpan,
|
|
20
|
+
} from "@synnaxlabs/x";
|
|
21
|
+
|
|
22
|
+
import { type Creator } from "@/channel/creator";
|
|
23
|
+
import {
|
|
24
|
+
type Key,
|
|
25
|
+
type KeyOrName,
|
|
26
|
+
type Params,
|
|
27
|
+
type Payload,
|
|
28
|
+
payload,
|
|
29
|
+
type NewPayload,
|
|
30
|
+
} from "@/channel/payload";
|
|
31
|
+
import { analyzeParams, type Retriever } from "@/channel/retriever";
|
|
32
|
+
import { QueryError } from "@/errors";
|
|
33
|
+
import { type framer } from "@/framer";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Represents a Channel in a Synnax database. It should not be instantiated
|
|
37
|
+
* directly, but rather created or retrieved from a {@link Client}.
|
|
38
|
+
*/
|
|
39
|
+
export class Channel {
|
|
40
|
+
private readonly _frameClient: framer.Client | null;
|
|
41
|
+
key: Key;
|
|
42
|
+
name: string;
|
|
43
|
+
rate: Rate;
|
|
44
|
+
dataType: DataType;
|
|
45
|
+
leaseholder: number;
|
|
46
|
+
index: Key;
|
|
47
|
+
isIndex: boolean;
|
|
48
|
+
|
|
49
|
+
constructor({
|
|
50
|
+
dataType,
|
|
51
|
+
rate,
|
|
52
|
+
name,
|
|
53
|
+
leaseholder = 0,
|
|
54
|
+
key = 0,
|
|
55
|
+
density = 0,
|
|
56
|
+
isIndex = false,
|
|
57
|
+
index = 0,
|
|
58
|
+
frameClient,
|
|
59
|
+
}: NewPayload & {
|
|
60
|
+
frameClient?: framer.Client;
|
|
61
|
+
density?: CrudeDensity;
|
|
62
|
+
}) {
|
|
63
|
+
this.key = key;
|
|
64
|
+
this.name = name;
|
|
65
|
+
this.rate = new Rate(rate ?? 0);
|
|
66
|
+
this.dataType = new DataType(dataType);
|
|
67
|
+
this.leaseholder = leaseholder;
|
|
68
|
+
this.index = index;
|
|
69
|
+
this.isIndex = isIndex;
|
|
70
|
+
this._frameClient = frameClient ?? null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private get framer(): framer.Client {
|
|
74
|
+
if (this._frameClient == null)
|
|
75
|
+
throw new Error("cannot read from a channel that has not been created");
|
|
76
|
+
return this._frameClient;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get payload(): Payload {
|
|
80
|
+
return payload.parse({
|
|
81
|
+
key: this.key,
|
|
82
|
+
name: this.name,
|
|
83
|
+
rate: this.rate.valueOf(),
|
|
84
|
+
dataType: this.dataType.valueOf(),
|
|
85
|
+
leaseholder: this.leaseholder,
|
|
86
|
+
index: this.index,
|
|
87
|
+
isIndex: this.isIndex,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Reads telemetry from the channel between the two timestamps.
|
|
93
|
+
*
|
|
94
|
+
* @param start - The starting timestamp of the range to read from.
|
|
95
|
+
* @param end - The ending timestamp of the range to read from.
|
|
96
|
+
* @returns A typed array containing the retrieved
|
|
97
|
+
*/
|
|
98
|
+
async read(tr: TimeRange): Promise<Series | undefined> {
|
|
99
|
+
return await this.framer.read(tr, this.key);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Writes telemetry to the channel starting at the given timestamp.
|
|
104
|
+
*
|
|
105
|
+
* @param start - The starting timestamp of the first sample in data.
|
|
106
|
+
* @param data - THe telemetry to write to the channel.
|
|
107
|
+
*/
|
|
108
|
+
async write(start: CrudeTimeSpan, data: NativeTypedArray): Promise<void> {
|
|
109
|
+
return await this.framer.write(this.key, start, data);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The core client class for executing channel operations against a Synnax
|
|
115
|
+
* cluster.
|
|
116
|
+
*/
|
|
117
|
+
export class Client implements AsyncTermSearcher<string, Key, Channel> {
|
|
118
|
+
private readonly frameClient: framer.Client;
|
|
119
|
+
private readonly retriever: Retriever;
|
|
120
|
+
private readonly creator: Creator;
|
|
121
|
+
|
|
122
|
+
constructor(segmentClient: framer.Client, retriever: Retriever, creator: Creator) {
|
|
123
|
+
this.frameClient = segmentClient;
|
|
124
|
+
this.retriever = retriever;
|
|
125
|
+
this.creator = creator;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async create(channel: NewPayload): Promise<Channel>;
|
|
129
|
+
|
|
130
|
+
async create(channels: NewPayload[]): Promise<Channel[]>;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Creates a new channel with the given properties.
|
|
134
|
+
*
|
|
135
|
+
* @param props.rate - The rate of the channel.
|
|
136
|
+
* @param props.dataType - The data type of the channel.
|
|
137
|
+
* @param props.name - The name of the channel. Optional.
|
|
138
|
+
* @param props.nodeKey - The ID of the node that holds the lease on the
|
|
139
|
+
* channel. If you don't know what this is, don't worry about it.
|
|
140
|
+
* @returns The created channel.
|
|
141
|
+
*/
|
|
142
|
+
async create(channels: NewPayload | NewPayload[]): Promise<Channel | Channel[]> {
|
|
143
|
+
const single = !Array.isArray(channels);
|
|
144
|
+
const res = this.sugar(await this.creator.create(toArray(channels)));
|
|
145
|
+
return single ? res[0] : res;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async retrieve(channel: KeyOrName): Promise<Channel>;
|
|
149
|
+
|
|
150
|
+
async retrieve(channels: Params): Promise<Channel[]>;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Retrieves a channel from the database using the given parameters.
|
|
154
|
+
* @param props.key - The key of the channel to retrieve.
|
|
155
|
+
* @param props.name - The name of the channel to retrieve. If props.key is set,
|
|
156
|
+
* this will be ignored.
|
|
157
|
+
* @returns The retrieved channel.
|
|
158
|
+
* @raises {QueryError} If the channel does not exist or if multiple results are returned.
|
|
159
|
+
*/
|
|
160
|
+
async retrieve(channels: Params): Promise<Channel | Channel[]> {
|
|
161
|
+
const { single, actual, normalized } = analyzeParams(channels);
|
|
162
|
+
if (normalized.length === 0) return [];
|
|
163
|
+
const res = this.sugar(await this.retriever.retrieve(channels));
|
|
164
|
+
if (!single) return res;
|
|
165
|
+
if (res.length === 0) throw new QueryError(`channel matching ${actual} not found`);
|
|
166
|
+
if (res.length > 1)
|
|
167
|
+
throw new QueryError(`multiple channels matching ${actual} found`);
|
|
168
|
+
return res[0];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async search(term: string, rangeKey?: string): Promise<Channel[]> {
|
|
172
|
+
return this.sugar(await this.retriever.search(term, rangeKey));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
newSearcherUnderRange(rangeKey?: string): AsyncTermSearcher<string, Key, Channel> {
|
|
176
|
+
return new SearcherUnderRange(this, rangeKey);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async page(offset: number, limit: number): Promise<Channel[]> {
|
|
180
|
+
return this.sugar(await this.retriever.page(offset, limit));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private sugar(payloads: Payload[]): Channel[] {
|
|
184
|
+
const { frameClient } = this;
|
|
185
|
+
return payloads.map((p) => new Channel({ ...p, frameClient }));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
class SearcherUnderRange implements AsyncTermSearcher<string, Key, Channel> {
|
|
190
|
+
private readonly client: Client;
|
|
191
|
+
private readonly rangeKey?: string;
|
|
192
|
+
|
|
193
|
+
constructor(client: Client, rangeKey?: string) {
|
|
194
|
+
this.client = client;
|
|
195
|
+
this.rangeKey = rangeKey;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async search(term: string): Promise<Channel[]> {
|
|
199
|
+
return await this.client.search(term, this.rangeKey);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async page(offset: number, limit: number): Promise<Channel[]> {
|
|
203
|
+
return await this.client.page(offset, limit);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async retrieve(channels: Key[]): Promise<Channel[]> {
|
|
207
|
+
return await this.client.retrieve(channels);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
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 {
|
|
14
|
+
type Payload,
|
|
15
|
+
payload,
|
|
16
|
+
parseChannels,
|
|
17
|
+
newPayload,
|
|
18
|
+
type NewPayload,
|
|
19
|
+
} from "@/channel/payload";
|
|
20
|
+
|
|
21
|
+
const reqZ = z.object({ channels: newPayload.array() });
|
|
22
|
+
|
|
23
|
+
const resZ = z.object({ channels: payload.array() });
|
|
24
|
+
|
|
25
|
+
export class Creator {
|
|
26
|
+
private static readonly ENDPOINT = "/channel/create";
|
|
27
|
+
private readonly client: UnaryClient;
|
|
28
|
+
|
|
29
|
+
constructor(client: UnaryClient) {
|
|
30
|
+
this.client = client;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async create(channels: NewPayload[]): Promise<Payload[]> {
|
|
34
|
+
const req = { channels: parseChannels(channels) };
|
|
35
|
+
const [res, err] = await this.client.send<typeof reqZ, typeof resZ>(
|
|
36
|
+
Creator.ENDPOINT,
|
|
37
|
+
req,
|
|
38
|
+
resZ,
|
|
39
|
+
);
|
|
40
|
+
if (err != null) throw err;
|
|
41
|
+
return res.channels;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
export * from "@/channel/creator";
|
|
11
|
+
export * from "@/channel/client";
|
|
12
|
+
export * from "@/channel/payload";
|
|
13
|
+
export * from "@/channel/retriever";
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
export * as channel from "@/channel/external";
|
|
@@ -0,0 +1,52 @@
|
|
|
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 { DataType, Rate } from "@synnaxlabs/x";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
|
|
13
|
+
export const keyZ = z.number();
|
|
14
|
+
export type Key = number;
|
|
15
|
+
export type Keys = number[];
|
|
16
|
+
export type Name = string;
|
|
17
|
+
export type Names = string[];
|
|
18
|
+
export type KeyOrName = Key | Name;
|
|
19
|
+
export type KeysOrNames = Keys | Names;
|
|
20
|
+
export type Params = Key | Name | Keys | Names;
|
|
21
|
+
|
|
22
|
+
export const payload = z.object({
|
|
23
|
+
name: z.string(),
|
|
24
|
+
key: z.number(),
|
|
25
|
+
rate: Rate.z,
|
|
26
|
+
dataType: DataType.z,
|
|
27
|
+
leaseholder: z.number(),
|
|
28
|
+
index: z.number(),
|
|
29
|
+
isIndex: z.boolean(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export type Payload = z.infer<typeof payload>;
|
|
33
|
+
|
|
34
|
+
export const newPayload = payload.extend({
|
|
35
|
+
key: z.number().optional(),
|
|
36
|
+
leaseholder: z.number().optional(),
|
|
37
|
+
index: z.number().optional(),
|
|
38
|
+
rate: Rate.z.optional(),
|
|
39
|
+
isIndex: z.boolean().optional(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export type NewPayload = z.input<typeof newPayload>;
|
|
43
|
+
|
|
44
|
+
export const parseChannels = (channels: NewPayload[]): NewPayload[] =>
|
|
45
|
+
channels.map((channel) => ({
|
|
46
|
+
name: channel.name,
|
|
47
|
+
dataType: new DataType(channel.dataType),
|
|
48
|
+
rate: new Rate(channel.rate ?? 0),
|
|
49
|
+
leaseholder: channel.leaseholder,
|
|
50
|
+
index: channel.index,
|
|
51
|
+
isIndex: channel.isIndex,
|
|
52
|
+
}));
|
|
@@ -0,0 +1,160 @@
|
|
|
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 { toArray } from "@synnaxlabs/x";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
type Key,
|
|
16
|
+
type KeyOrName,
|
|
17
|
+
type Keys,
|
|
18
|
+
type KeysOrNames,
|
|
19
|
+
type Name,
|
|
20
|
+
type Names,
|
|
21
|
+
type Params,
|
|
22
|
+
type Payload,
|
|
23
|
+
payload,
|
|
24
|
+
} from "@/channel/payload";
|
|
25
|
+
import { QueryError } from "@/errors";
|
|
26
|
+
|
|
27
|
+
const reqZ = z.object({
|
|
28
|
+
leaseholder: z.number().optional(),
|
|
29
|
+
keys: z.number().array().optional(),
|
|
30
|
+
names: z.string().array().optional(),
|
|
31
|
+
search: z.string().optional(),
|
|
32
|
+
searchRangeKey: z.string().optional(),
|
|
33
|
+
limit: z.number().optional(),
|
|
34
|
+
offset: z.number().optional(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
type Request = z.infer<typeof reqZ>;
|
|
38
|
+
|
|
39
|
+
const resZ = z.object({
|
|
40
|
+
channels: payload.array(),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export interface Retriever {
|
|
44
|
+
retrieve: (channels: Params) => Promise<Payload[]>;
|
|
45
|
+
search: (term: string, rangeKey?: string) => Promise<Payload[]>;
|
|
46
|
+
page: (offset: number, limit: number) => Promise<Payload[]>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class ClusterRetriever implements Retriever {
|
|
50
|
+
private static readonly ENDPOINT = "/channel/retrieve";
|
|
51
|
+
private readonly client: UnaryClient;
|
|
52
|
+
|
|
53
|
+
constructor(client: UnaryClient) {
|
|
54
|
+
this.client = client;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async search(term: string, rangeKey?: string): Promise<Payload[]> {
|
|
58
|
+
return await this.execute({ search: term, searchRangeKey: rangeKey });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async retrieve(channels: Params): Promise<Payload[]> {
|
|
62
|
+
const { variant, normalized } = analyzeParams(channels);
|
|
63
|
+
return await this.execute({ [variant]: normalized });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async page(offset: number, limit: number): Promise<Payload[]> {
|
|
67
|
+
return await this.execute({ offset, limit });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private async execute(request: Request): Promise<Payload[]> {
|
|
71
|
+
const [res, err] = await this.client.send(ClusterRetriever.ENDPOINT, request, resZ);
|
|
72
|
+
if (err != null) throw err;
|
|
73
|
+
return res.channels;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export class CacheRetriever implements Retriever {
|
|
78
|
+
private readonly cache: Map<number, Payload>;
|
|
79
|
+
private readonly namesToKeys: Map<string, number>;
|
|
80
|
+
private readonly wrapped: Retriever;
|
|
81
|
+
|
|
82
|
+
constructor(wrapped: Retriever) {
|
|
83
|
+
this.cache = new Map();
|
|
84
|
+
this.namesToKeys = new Map();
|
|
85
|
+
this.wrapped = wrapped;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async search(term: string, searchRangeKey?: string): Promise<Payload[]> {
|
|
89
|
+
return await this.wrapped.search(term, searchRangeKey);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async page(offset: number, limit: number): Promise<Payload[]> {
|
|
93
|
+
return await this.wrapped.page(offset, limit);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async retrieve(channels: Params): Promise<Payload[]> {
|
|
97
|
+
const { normalized } = analyzeParams(channels);
|
|
98
|
+
const results: Payload[] = [];
|
|
99
|
+
const toFetch: KeysOrNames = [];
|
|
100
|
+
normalized.forEach((keyOrName) => {
|
|
101
|
+
const c = this.getFromCache(keyOrName);
|
|
102
|
+
if (c != null) results.push(c);
|
|
103
|
+
else toFetch.push(keyOrName as never);
|
|
104
|
+
});
|
|
105
|
+
if (toFetch.length === 0) return results;
|
|
106
|
+
const fetched = await this.wrapped.retrieve(toFetch);
|
|
107
|
+
this.updateCache(fetched);
|
|
108
|
+
return results.concat(fetched);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private updateCache(channels: Payload[]): void {
|
|
112
|
+
channels.forEach((channel) => {
|
|
113
|
+
this.cache.set(channel.key, channel);
|
|
114
|
+
this.namesToKeys.set(channel.name, channel.key);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private getFromCache(channel: KeyOrName): Payload | undefined {
|
|
119
|
+
const key = typeof channel === "number" ? channel : this.namesToKeys.get(channel);
|
|
120
|
+
if (key == null) return undefined;
|
|
121
|
+
return this.cache.get(key);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export type ParamAnalysisResult =
|
|
126
|
+
| {
|
|
127
|
+
single: true;
|
|
128
|
+
variant: "names";
|
|
129
|
+
normalized: Names;
|
|
130
|
+
actual: Name;
|
|
131
|
+
}
|
|
132
|
+
| {
|
|
133
|
+
single: true;
|
|
134
|
+
variant: "keys";
|
|
135
|
+
normalized: Keys;
|
|
136
|
+
actual: Key;
|
|
137
|
+
}
|
|
138
|
+
| {
|
|
139
|
+
single: false;
|
|
140
|
+
variant: "keys";
|
|
141
|
+
normalized: Keys;
|
|
142
|
+
actual: Keys;
|
|
143
|
+
}
|
|
144
|
+
| {
|
|
145
|
+
single: false;
|
|
146
|
+
variant: "names";
|
|
147
|
+
normalized: Names;
|
|
148
|
+
actual: Names;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const analyzeParams = (channels: Params): ParamAnalysisResult => {
|
|
152
|
+
const normal = toArray(channels) as KeysOrNames;
|
|
153
|
+
if (normal.length === 0) throw new QueryError("No channels provided");
|
|
154
|
+
return {
|
|
155
|
+
single: !Array.isArray(channels),
|
|
156
|
+
variant: typeof normal[0] === "number" ? "keys" : "names",
|
|
157
|
+
normalized: normal,
|
|
158
|
+
actual: channels,
|
|
159
|
+
} as const as ParamAnalysisResult;
|
|
160
|
+
};
|