@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/client.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
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 { TimeSpan, TimeStamp, URL } from "@synnaxlabs/x";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
|
|
13
|
+
import { auth } from "@/auth";
|
|
14
|
+
import { channel } from "@/channel";
|
|
15
|
+
import { connection } from "@/connection";
|
|
16
|
+
import { errorsMiddleware } from "@/errors";
|
|
17
|
+
import { framer } from "@/framer";
|
|
18
|
+
import { label } from "@/label";
|
|
19
|
+
import { ontology } from "@/ontology";
|
|
20
|
+
import { ranger } from "@/ranger";
|
|
21
|
+
import { Transport } from "@/transport";
|
|
22
|
+
import { workspace } from "@/workspace";
|
|
23
|
+
|
|
24
|
+
export const synnaxPropsZ = z.object({
|
|
25
|
+
host: z.string().min(1),
|
|
26
|
+
port: z.number().or(z.string()),
|
|
27
|
+
username: z.string().optional(),
|
|
28
|
+
password: z.string().optional(),
|
|
29
|
+
connectivityPollFrequency: TimeSpan.z.default(TimeSpan.seconds(30)),
|
|
30
|
+
secure: z.boolean().optional().default(false),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export type SynnaxProps = z.input<typeof synnaxPropsZ>;
|
|
34
|
+
export type ParsedSynnaxProps = z.output<typeof synnaxPropsZ>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Client to perform operations against a Synnax cluster.
|
|
38
|
+
*
|
|
39
|
+
* @property channel - Channel client for creating and retrieving channels.
|
|
40
|
+
* @property data - Data client for reading and writing telemetry.
|
|
41
|
+
* @property connectivity - Client for retrieving connectivity information.
|
|
42
|
+
* @property ontology - Client for querying the cluster's ontology.
|
|
43
|
+
*/
|
|
44
|
+
// eslint-disable-next-line import/no-default-export
|
|
45
|
+
export default class Synnax {
|
|
46
|
+
private readonly transport: Transport;
|
|
47
|
+
readonly createdAt: TimeStamp;
|
|
48
|
+
readonly telem: framer.Client;
|
|
49
|
+
readonly ranges: ranger.Client;
|
|
50
|
+
readonly channels: channel.Client;
|
|
51
|
+
readonly auth: auth.Client | undefined;
|
|
52
|
+
readonly connectivity: connection.Checker;
|
|
53
|
+
readonly ontology: ontology.Client;
|
|
54
|
+
readonly props: ParsedSynnaxProps;
|
|
55
|
+
readonly workspaces: workspace.Client;
|
|
56
|
+
readonly labels: label.Client;
|
|
57
|
+
static readonly connectivity = connection.Checker;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param props.host - Hostname of a node in the cluster.
|
|
61
|
+
* @param props.port - Port of the node in the cluster.
|
|
62
|
+
* @param props.username - Username for authentication. Not required if the
|
|
63
|
+
* cluster is insecure.
|
|
64
|
+
* @param props.password - Password for authentication. Not required if the
|
|
65
|
+
* cluster is insecure.
|
|
66
|
+
* @param props.connectivityPollFrequency - Frequency at which to poll the
|
|
67
|
+
* cluster for connectivity information. Defaults to 5 seconds.
|
|
68
|
+
* @param props.secure - Whether to connect to the cluster using TLS. The cluster
|
|
69
|
+
* must be configured to support TLS. Defaults to false.
|
|
70
|
+
*
|
|
71
|
+
* A Synnax client must be closed when it is no longer needed. This will stop
|
|
72
|
+
* the client from polling the cluster for connectivity information.
|
|
73
|
+
*/
|
|
74
|
+
constructor(props: SynnaxProps) {
|
|
75
|
+
this.createdAt = TimeStamp.now();
|
|
76
|
+
this.props = synnaxPropsZ.parse(props);
|
|
77
|
+
const { host, port, username, password, connectivityPollFrequency, secure } =
|
|
78
|
+
this.props;
|
|
79
|
+
this.transport = new Transport(new URL({ host, port: Number(port) }), secure);
|
|
80
|
+
this.transport.use(errorsMiddleware);
|
|
81
|
+
if (username != null && password != null) {
|
|
82
|
+
this.auth = new auth.Client(this.transport.unary, {
|
|
83
|
+
username,
|
|
84
|
+
password,
|
|
85
|
+
});
|
|
86
|
+
this.transport.use(this.auth.middleware());
|
|
87
|
+
}
|
|
88
|
+
const chRetriever = new channel.CacheRetriever(
|
|
89
|
+
new channel.ClusterRetriever(this.transport.unary),
|
|
90
|
+
);
|
|
91
|
+
const chCreator = new channel.Creator(this.transport.unary);
|
|
92
|
+
this.telem = new framer.Client(this.transport.stream, chRetriever);
|
|
93
|
+
this.channels = new channel.Client(this.telem, chRetriever, chCreator);
|
|
94
|
+
this.connectivity = new connection.Checker(
|
|
95
|
+
this.transport.unary,
|
|
96
|
+
connectivityPollFrequency,
|
|
97
|
+
);
|
|
98
|
+
this.ontology = new ontology.Client(this.transport.unary, this.telem);
|
|
99
|
+
const rangeRetriever = new ranger.Retriever(this.transport.unary);
|
|
100
|
+
const rangeWriter = new ranger.Writer(this.transport.unary);
|
|
101
|
+
this.labels = new label.Client(this.transport.unary, this.telem);
|
|
102
|
+
this.ranges = new ranger.Client(
|
|
103
|
+
this.telem,
|
|
104
|
+
rangeRetriever,
|
|
105
|
+
rangeWriter,
|
|
106
|
+
this.transport.unary,
|
|
107
|
+
chRetriever,
|
|
108
|
+
this.labels,
|
|
109
|
+
);
|
|
110
|
+
this.workspaces = new workspace.Client(this.transport.unary);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
close(): void {
|
|
114
|
+
this.connectivity.stopChecking();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
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 { TimeSpan } from "@synnaxlabs/x";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
|
|
14
|
+
const STATUSES = ["disconnected", "connecting", "connected", "failed"] as const;
|
|
15
|
+
export const status = z.enum(STATUSES);
|
|
16
|
+
export type Status = z.infer<typeof status>;
|
|
17
|
+
|
|
18
|
+
export const state = z.object({
|
|
19
|
+
status,
|
|
20
|
+
error: z.instanceof(Error).optional(),
|
|
21
|
+
message: z.string().optional(),
|
|
22
|
+
clusterKey: z.string(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export type State = z.infer<typeof state>;
|
|
26
|
+
|
|
27
|
+
const responseZ = z.object({
|
|
28
|
+
clusterKey: z.string(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const DEFAULT: State = {
|
|
32
|
+
clusterKey: "",
|
|
33
|
+
status: "disconnected",
|
|
34
|
+
error: undefined,
|
|
35
|
+
message: "Disconnected",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** Polls a synnax cluster for connectivity information. */
|
|
39
|
+
export class Checker {
|
|
40
|
+
private static readonly ENDPOINT = "/connectivity/check";
|
|
41
|
+
static readonly DEFAULT: State = DEFAULT;
|
|
42
|
+
private readonly _state: State;
|
|
43
|
+
private readonly pollFrequency = TimeSpan.seconds(30);
|
|
44
|
+
private readonly client: UnaryClient;
|
|
45
|
+
private interval?: NodeJS.Timeout;
|
|
46
|
+
private readonly onChangeHandlers: Array<(state: State) => void> = [];
|
|
47
|
+
static readonly connectionStateZ = state;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param client - The transport client to use for connectivity checks.
|
|
51
|
+
* @param pollFreq - The frequency at which to poll the cluster for
|
|
52
|
+
* connectivity information.
|
|
53
|
+
*/
|
|
54
|
+
constructor(client: UnaryClient, pollFreq: TimeSpan = TimeSpan.seconds(30)) {
|
|
55
|
+
this._state = { ...DEFAULT };
|
|
56
|
+
this.client = client;
|
|
57
|
+
this.pollFrequency = pollFreq;
|
|
58
|
+
void this.check();
|
|
59
|
+
this.startChecking();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Stops the connectivity client from polling the cluster for connectivity */
|
|
63
|
+
stopChecking(): void {
|
|
64
|
+
if (this.interval != null) clearInterval(this.interval);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Executes a connectivity check and updates the client status and error, as
|
|
69
|
+
* well as calling any registered change handlers.
|
|
70
|
+
*/
|
|
71
|
+
async check(): Promise<State> {
|
|
72
|
+
const prevStatus = this._state.status;
|
|
73
|
+
try {
|
|
74
|
+
const [res, err] = await this.client.send(Checker.ENDPOINT, null, responseZ);
|
|
75
|
+
if (err != null) throw err;
|
|
76
|
+
this._state.status = "connected";
|
|
77
|
+
this._state.message = `Connected to cluster ${res.clusterKey}`;
|
|
78
|
+
this._state.clusterKey = res.clusterKey;
|
|
79
|
+
} catch (err) {
|
|
80
|
+
this._state.status = "failed";
|
|
81
|
+
this._state.error = err as Error;
|
|
82
|
+
this._state.message = this.state.error?.message;
|
|
83
|
+
}
|
|
84
|
+
if (this.onChangeHandlers.length > 0 && prevStatus !== this._state.status)
|
|
85
|
+
this.onChangeHandlers.forEach((handler) => handler(this.state));
|
|
86
|
+
return this.state;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** @returns a copy of the current client state. */
|
|
90
|
+
get state(): State {
|
|
91
|
+
return { ...this._state };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** @param callback - The function to call when the client status changes. */
|
|
95
|
+
onChange(callback: (state: State) => void): void {
|
|
96
|
+
this.onChangeHandlers.push(callback);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private startChecking(): void {
|
|
100
|
+
this.interval = setInterval(() => {
|
|
101
|
+
void this.check();
|
|
102
|
+
}, this.pollFrequency.milliseconds);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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 { URL } from "@synnaxlabs/x";
|
|
11
|
+
import { describe, expect, it } from "vitest";
|
|
12
|
+
|
|
13
|
+
import { auth } from "@/auth";
|
|
14
|
+
import { Checker } from "@/connection/checker";
|
|
15
|
+
import { HOST, PORT } from "@/setupspecs";
|
|
16
|
+
import { Transport } from "@/transport";
|
|
17
|
+
|
|
18
|
+
describe("connectivity", () => {
|
|
19
|
+
it("should connect to the server", async () => {
|
|
20
|
+
const transport = new Transport(new URL({ host: HOST, port: PORT }));
|
|
21
|
+
const client = new auth.Client(transport.unary, {
|
|
22
|
+
username: "synnax",
|
|
23
|
+
password: "seldon",
|
|
24
|
+
});
|
|
25
|
+
await client.authenticating;
|
|
26
|
+
expect(client.authenticated).toBeTruthy();
|
|
27
|
+
|
|
28
|
+
transport.use(client.middleware());
|
|
29
|
+
|
|
30
|
+
const connectivity = new Checker(transport.unary);
|
|
31
|
+
|
|
32
|
+
await connectivity.check();
|
|
33
|
+
expect(connectivity.state.status).toEqual("connected");
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -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 connection from "@/connection/checker";
|
|
@@ -0,0 +1,26 @@
|
|
|
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 { z } from "zod";
|
|
11
|
+
|
|
12
|
+
export class Authority extends Number {
|
|
13
|
+
static readonly ABSOLUTE = 255;
|
|
14
|
+
static readonly DEFAULT = 1;
|
|
15
|
+
|
|
16
|
+
static readonly z = z.union([
|
|
17
|
+
z.instanceof(Authority),
|
|
18
|
+
z
|
|
19
|
+
.number()
|
|
20
|
+
.int()
|
|
21
|
+
.min(0)
|
|
22
|
+
.max(255)
|
|
23
|
+
.transform((n) => new Authority(n)),
|
|
24
|
+
z.instanceof(Number).transform((n) => new Authority(n)),
|
|
25
|
+
]);
|
|
26
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
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 "@/control/authority";
|
|
11
|
+
export * from "@/control/state";
|
|
@@ -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 control from "@/control/external";
|
|
@@ -0,0 +1,24 @@
|
|
|
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 { describe, expect, it } from "vitest";
|
|
11
|
+
|
|
12
|
+
import { control } from "@/control";
|
|
13
|
+
import { newClient } from "@/setupspecs";
|
|
14
|
+
|
|
15
|
+
const client = newClient();
|
|
16
|
+
|
|
17
|
+
describe("state", () => {
|
|
18
|
+
it("should receive the initial control state from the cluster", async () => {
|
|
19
|
+
const s = await control.StateTracker.open(client.telem);
|
|
20
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
21
|
+
expect(s.states.size).toBeGreaterThan(0);
|
|
22
|
+
s.close();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
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 Destructor, binary, observe } from "@synnaxlabs/x";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
|
|
13
|
+
import { type Key as ChannelKey } from "@/channel/payload";
|
|
14
|
+
import { Authority } from "@/control/authority";
|
|
15
|
+
import { type Client as FrameClient } from "@/framer/client";
|
|
16
|
+
import { type Streamer as FrameStreamer } from "@/framer/streamer";
|
|
17
|
+
|
|
18
|
+
export const subjectZ = z.object({
|
|
19
|
+
name: z.string(),
|
|
20
|
+
key: z.string(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export interface Subject {
|
|
24
|
+
name: string;
|
|
25
|
+
key: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const stateZ = z.object({
|
|
29
|
+
subject: subjectZ,
|
|
30
|
+
resource: z.number(),
|
|
31
|
+
authority: Authority.z,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export interface State {
|
|
35
|
+
subject: Subject;
|
|
36
|
+
resource: ChannelKey;
|
|
37
|
+
authority: Authority;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const filterTransfersByChannelKey =
|
|
41
|
+
(...resources: ChannelKey[]) =>
|
|
42
|
+
(transfers: Transfer[]): Transfer[] =>
|
|
43
|
+
transfers.filter((t) => {
|
|
44
|
+
let ok = false;
|
|
45
|
+
if (t.to != null) ok = resources.includes(t.to.resource);
|
|
46
|
+
if (t.from != null && !ok) ok = resources.includes(t.from.resource);
|
|
47
|
+
return ok;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
interface Release {
|
|
51
|
+
from: State;
|
|
52
|
+
to?: null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface Acquire {
|
|
56
|
+
from?: null;
|
|
57
|
+
to: State;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type Transfer =
|
|
61
|
+
| {
|
|
62
|
+
from: State;
|
|
63
|
+
to: State;
|
|
64
|
+
}
|
|
65
|
+
| Release
|
|
66
|
+
| Acquire;
|
|
67
|
+
|
|
68
|
+
export const transferString = (t: Transfer): string => {
|
|
69
|
+
if (t.to == null) return `${t.from?.resource} - ${t.from?.subject.name} -> released`;
|
|
70
|
+
if (t.from == null)
|
|
71
|
+
return `${t.to.resource} - released -> ${
|
|
72
|
+
t.to.subject.name
|
|
73
|
+
} (${t.to.authority.toString()})`;
|
|
74
|
+
return `${t.to.resource} - ${t.from.subject.name} -> ${
|
|
75
|
+
t.to.subject.name
|
|
76
|
+
} (${t.to.authority.toString()})`;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
interface Update {
|
|
80
|
+
transfers: Transfer[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export class StateTracker implements observe.Observable<Transfer[]> {
|
|
84
|
+
readonly states: Map<ChannelKey, State>;
|
|
85
|
+
private readonly streamer: FrameStreamer;
|
|
86
|
+
private readonly ecd: binary.EncoderDecoder;
|
|
87
|
+
private readonly observer: observe.Observer<Transfer[]>;
|
|
88
|
+
private readonly closePromise: Promise<void>;
|
|
89
|
+
|
|
90
|
+
private constructor(streamer: FrameStreamer) {
|
|
91
|
+
this.states = new Map();
|
|
92
|
+
this.ecd = new binary.JSONEncoderDecoder();
|
|
93
|
+
this.observer = new observe.Observer<Transfer[]>();
|
|
94
|
+
this.streamer = streamer;
|
|
95
|
+
this.closePromise = this.stream();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
subjects(): Subject[] {
|
|
99
|
+
const subjects = new Map<string, Subject>();
|
|
100
|
+
this.states.forEach((s) => subjects.set(s.subject.key, s.subject));
|
|
101
|
+
return Array.from(subjects.values());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
onChange(handler: observe.Handler<Transfer[]>): Destructor {
|
|
105
|
+
return this.observer.onChange(handler);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async close(): Promise<void> {
|
|
109
|
+
this.streamer.close();
|
|
110
|
+
await this.closePromise;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
static async open(client: FrameClient): Promise<StateTracker> {
|
|
114
|
+
const streamer = await client.newStreamer("sy_node_1_control");
|
|
115
|
+
return new StateTracker(streamer);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private async stream(): Promise<void> {
|
|
119
|
+
for await (const frame of this.streamer) {
|
|
120
|
+
const update: Update = this.ecd.decode(frame.series[0].buffer);
|
|
121
|
+
this.merge(update);
|
|
122
|
+
this.observer.notify(update.transfers);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private merge(update: Update): void {
|
|
127
|
+
update.transfers.forEach((t) => {
|
|
128
|
+
if (t.from == null && t.to == null) console.warn("Invalid transfer: ", t);
|
|
129
|
+
if (t.to == null) this.states.delete(t.from.resource);
|
|
130
|
+
else this.states.set(t.to.resource, t.to);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
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
|
+
BaseTypedError,
|
|
12
|
+
type Middleware,
|
|
13
|
+
registerError,
|
|
14
|
+
Unreachable,
|
|
15
|
+
type ErrorPayload,
|
|
16
|
+
} from "@synnaxlabs/freighter";
|
|
17
|
+
|
|
18
|
+
const _FREIGHTER_EXCEPTION_PREFIX = "sy.api.";
|
|
19
|
+
|
|
20
|
+
enum APIErrorType {
|
|
21
|
+
General = _FREIGHTER_EXCEPTION_PREFIX + "general",
|
|
22
|
+
Parse = _FREIGHTER_EXCEPTION_PREFIX + "parse",
|
|
23
|
+
Auth = _FREIGHTER_EXCEPTION_PREFIX + "auth",
|
|
24
|
+
Unexpected = _FREIGHTER_EXCEPTION_PREFIX + "unexpected",
|
|
25
|
+
Validation = _FREIGHTER_EXCEPTION_PREFIX + "validation",
|
|
26
|
+
Query = _FREIGHTER_EXCEPTION_PREFIX + "query",
|
|
27
|
+
Route = _FREIGHTER_EXCEPTION_PREFIX + "route",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface Field {
|
|
31
|
+
field: string;
|
|
32
|
+
message: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class BaseError extends BaseTypedError {
|
|
36
|
+
constructor(message: string) {
|
|
37
|
+
super(message, _FREIGHTER_EXCEPTION_PREFIX);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Raised when a validation error occurs.
|
|
43
|
+
*/
|
|
44
|
+
export class ValidationError extends BaseError {
|
|
45
|
+
fields: Field[];
|
|
46
|
+
|
|
47
|
+
constructor(fieldsOrMessage: string | Field[] | Field) {
|
|
48
|
+
if (typeof fieldsOrMessage === "string") {
|
|
49
|
+
super(fieldsOrMessage);
|
|
50
|
+
this.fields = [];
|
|
51
|
+
} else if (Array.isArray(fieldsOrMessage)) {
|
|
52
|
+
super(
|
|
53
|
+
fieldsOrMessage.map((field) => `${field.field}: ${field.message}`).join("\n"),
|
|
54
|
+
);
|
|
55
|
+
this.fields = fieldsOrMessage;
|
|
56
|
+
} else {
|
|
57
|
+
super(`${fieldsOrMessage.field}: ${fieldsOrMessage.message}`);
|
|
58
|
+
this.fields = [fieldsOrMessage];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* GeneralError is raised when a general error occurs.
|
|
65
|
+
*/
|
|
66
|
+
export class GeneralError extends BaseError {}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* ParseError is raised when a parse error occurs.
|
|
70
|
+
*/
|
|
71
|
+
export class ParseError extends BaseError {}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* AuthError is raised when an authentication error occurs.
|
|
75
|
+
*/
|
|
76
|
+
export class AuthError extends BaseError {}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* UnexpectedError is raised when an unexpected error occurs.
|
|
80
|
+
*/
|
|
81
|
+
export class UnexpectedError extends BaseError {
|
|
82
|
+
constructor(message: string) {
|
|
83
|
+
super(`
|
|
84
|
+
Unexpected error encountered:
|
|
85
|
+
|
|
86
|
+
${message}
|
|
87
|
+
|
|
88
|
+
Please report this to the Synnax team.
|
|
89
|
+
`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* QueryError is raised when a query error occurs.
|
|
95
|
+
*/
|
|
96
|
+
export class QueryError extends BaseError {}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* RouteError is raised when a routing error occurs.
|
|
100
|
+
*/
|
|
101
|
+
export class RouteError extends BaseError {
|
|
102
|
+
path: string;
|
|
103
|
+
|
|
104
|
+
constructor(message: string, path: string) {
|
|
105
|
+
super(message);
|
|
106
|
+
this.path = path;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Raised when time-series data is not contiguous.
|
|
112
|
+
*/
|
|
113
|
+
export class ContiguityError extends BaseError {}
|
|
114
|
+
|
|
115
|
+
const decode = (payload: ErrorPayload): Error | null => {
|
|
116
|
+
if (!payload.type.startsWith(_FREIGHTER_EXCEPTION_PREFIX)) return null;
|
|
117
|
+
switch (payload.type) {
|
|
118
|
+
case APIErrorType.General:
|
|
119
|
+
return new GeneralError(payload.data);
|
|
120
|
+
case APIErrorType.Parse:
|
|
121
|
+
return new ParseError(payload.data);
|
|
122
|
+
case APIErrorType.Auth:
|
|
123
|
+
return new AuthError(payload.data);
|
|
124
|
+
case APIErrorType.Unexpected:
|
|
125
|
+
return new UnexpectedError(payload.data);
|
|
126
|
+
case APIErrorType.Validation:
|
|
127
|
+
return new ValidationError(payload.data);
|
|
128
|
+
case APIErrorType.Query:
|
|
129
|
+
return new QueryError(payload.data);
|
|
130
|
+
case APIErrorType.Route:
|
|
131
|
+
return new RouteError(payload.data, payload.data);
|
|
132
|
+
default:
|
|
133
|
+
return new UnexpectedError(payload.data);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const encode = (): ErrorPayload => {
|
|
138
|
+
throw new Error("Not implemented");
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
registerError({ encode, decode });
|
|
142
|
+
|
|
143
|
+
export const validateFieldNotNull = (
|
|
144
|
+
key: string,
|
|
145
|
+
value: unknown,
|
|
146
|
+
message: string = "must be provided",
|
|
147
|
+
): void => {
|
|
148
|
+
if (value == null) throw new ValidationError({ field: key, message });
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const errorsMiddleware: Middleware = async (ctx, next) => {
|
|
152
|
+
const [res, err] = await next(ctx);
|
|
153
|
+
if (err == null) return [res, err];
|
|
154
|
+
if (err instanceof Unreachable)
|
|
155
|
+
return [
|
|
156
|
+
res,
|
|
157
|
+
new Unreachable({
|
|
158
|
+
message: `Cannot reach cluster at ${err.url.host}:${err.url.port}`,
|
|
159
|
+
url: err.url,
|
|
160
|
+
}),
|
|
161
|
+
];
|
|
162
|
+
return [res, err];
|
|
163
|
+
};
|