@theotherwillembotha/node-red-cluster 0.0.55

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.
@@ -0,0 +1,70 @@
1
+
2
+ /*
3
+
4
+ This file was automatically generated using the NODERED Core utility.
5
+ Any modifications to his file will be overwritten the next time the code is regenerated.
6
+
7
+ You have been warned.
8
+
9
+ */
10
+ "use strict";
11
+ const runtime = require("node-red").runtime;
12
+ const ClusterService = require("./cluster/service/ClusterService").ClusterService;
13
+
14
+ module.exports = function (RED) {
15
+
16
+ // 1. make a list of all the plugins that need to be installed.
17
+ let pluginList = [ClusterService]
18
+ .map(service => service.getServiceDescriptor())
19
+ .filter(plugin => !RED.plugins.get(plugin.id()));
20
+
21
+ // 2. register a listener if there are any plugins in the pluginList.
22
+ if(pluginList.length > 0){
23
+ RED.events.on('registry:plugin-added', async pluginID => {
24
+
25
+ let addedPlugin = pluginList.find(plugin => plugin.id() === pluginID);
26
+ let plugin = RED.plugins.get(pluginID);
27
+
28
+ if(addedPlugin && plugin.instantiate && !plugin.instance){
29
+ console.log("Deploying instance of: " + plugin.id);
30
+ plugin.instance = new plugin.class();
31
+
32
+ // instantiate the plugin.
33
+ await plugin.instance.init(RED);
34
+
35
+ // publish an event that it has ben deployed.
36
+ RED.events.emit("plugin.instantiated", pluginID);
37
+
38
+ pluginList.splice(pluginList.findIndex(current => current.id() === addedPlugin.id()), 1);
39
+
40
+ // register a flow deployment listener.
41
+ let startupDeployment = true;
42
+ RED.events.on('runtime-event', async (event) => {
43
+ // startup deployment.
44
+ if ("runtime-deploy" === event?.id && startupDeployment) {
45
+ //console.log("STARTUP DEPLOYMENT");
46
+ startupDeployment = false;
47
+ const flows = await runtime.flows.getFlows({});
48
+ await plugin.instance.onDeploy(flows);
49
+ }
50
+
51
+ // stopping flows on redeployment.
52
+ if ("runtime-state" === event?.id && event?.payload?.state === "stop" && event?.payload?.deploy) {
53
+ //console.log("REDEPLOY!");
54
+ const flows = await runtime.flows.getFlows({});
55
+ await plugin.instance.onDeploy(flows);
56
+ }
57
+ });
58
+ }
59
+ });
60
+ }
61
+
62
+ // 3. register the plugins.
63
+ [...pluginList].forEach(plugin => {
64
+ RED.plugins.registerPlugin(plugin.id(), {
65
+ type: plugin.type(),
66
+ instantiate:true,
67
+ class: plugin.clazz()
68
+ });
69
+ })
70
+ };
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ClusterConfigNode = exports.ClusterRole = void 0;
13
+ const node_red_plugincore_1 = require("@theotherwillembotha/node-red-plugincore");
14
+ const ClusterService_1 = require("../service/ClusterService");
15
+ var ClusterRole;
16
+ (function (ClusterRole) {
17
+ ClusterRole["Member"] = "Member";
18
+ ClusterRole["Observer"] = "Observer";
19
+ })(ClusterRole || (exports.ClusterRole = ClusterRole = {}));
20
+ function toNodeStatus(status, error) {
21
+ switch (status) {
22
+ case "connecting": return { fill: "yellow", shape: "ring", text: "connecting..." };
23
+ case "connected": return { fill: "green", shape: "dot", text: "connected" };
24
+ case "disconnected": return { fill: "red", shape: "ring", text: "disconnected" };
25
+ case "error": return { fill: "red", shape: "ring", text: error?.message ?? "error" };
26
+ default: return { fill: "grey", shape: "ring", text: "idle" };
27
+ }
28
+ }
29
+ let ClusterConfigNode = class ClusterConfigNode extends node_red_plugincore_1.ConfigNode {
30
+ constructor(node, config) {
31
+ super(node, config);
32
+ this._unsubscribeStatus = null;
33
+ this._instanceId = config.instanceId;
34
+ this._zkRootPath = config.zkRootPath || "/nodered-cluster";
35
+ this._natsAddress = config.natsAddress;
36
+ this._natsUser = config.natsUser || "";
37
+ this._natsPass = config.natsPass || "";
38
+ this._role = config.role || ClusterRole.Member;
39
+ const client = ClusterService_1.ClusterService.ensureClient(this.id(), {
40
+ instanceId: this._instanceId,
41
+ zkRootPath: this._zkRootPath,
42
+ natsAddress: this._natsAddress,
43
+ natsUser: this._natsUser || undefined,
44
+ natsPass: this._natsPass || undefined,
45
+ role: this._role,
46
+ });
47
+ // Reflect the live client status on this node's status indicator.
48
+ this.node().status(toNodeStatus(client.status()));
49
+ this._unsubscribeStatus = client.onStatusChange((status, error) => {
50
+ this.node().status(toNodeStatus(status, error));
51
+ });
52
+ }
53
+ onInit() {
54
+ this.node().on("close", (removed, done) => {
55
+ this._unsubscribeStatus?.();
56
+ this._unsubscribeStatus = null;
57
+ if (removed) {
58
+ // Node permanently deleted tear down the cluster connection.
59
+ ClusterService_1.ClusterService.removeClient(this.id());
60
+ }
61
+ done();
62
+ });
63
+ }
64
+ // ── Accessors ────────────────────────────────────────────────────────────
65
+ instanceId() { return this._instanceId; }
66
+ zkRootPath() { return this._zkRootPath; }
67
+ natsAddress() { return this._natsAddress; }
68
+ natsUser() { return this._natsUser; }
69
+ natsPass() { return this._natsPass; }
70
+ role() { return this._role; }
71
+ isObserver() {
72
+ return this._role === ClusterRole.Observer;
73
+ }
74
+ getClient() {
75
+ return ClusterService_1.ClusterService.getClient(this.id());
76
+ }
77
+ };
78
+ exports.ClusterConfigNode = ClusterConfigNode;
79
+ exports.ClusterConfigNode = ClusterConfigNode = __decorate([
80
+ (0, node_red_plugincore_1.NodeDescription)({
81
+ id: "ClusterConfigNode",
82
+ name: "Cluster Config Node",
83
+ group: "config",
84
+ sourceFile: node_red_plugincore_1.SourceUtility.getSourcePath("/build/", "/src/") + "ClusterConfigNode.html",
85
+ package: "@theotherwillembotha/node-red-cluster",
86
+ tags: ["Cluster"]
87
+ }),
88
+ __metadata("design:paramtypes", [Object, Object])
89
+ ], ClusterConfigNode);
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ClusterPublishNode = exports.DeliveryMode = void 0;
13
+ const node_red_plugincore_1 = require("@theotherwillembotha/node-red-plugincore");
14
+ const ClusterConfigNode_1 = require("./ClusterConfigNode");
15
+ var DeliveryMode;
16
+ (function (DeliveryMode) {
17
+ DeliveryMode["Durable"] = "Durable";
18
+ DeliveryMode["Ephemeral"] = "Ephemeral";
19
+ })(DeliveryMode || (exports.DeliveryMode = DeliveryMode = {}));
20
+ const observerStatus = { fill: "grey", shape: "ring", text: "observer publish disabled" };
21
+ const readyStatus = { fill: "green", shape: "dot", text: "ready" };
22
+ const notConnectedStatus = { fill: "yellow", shape: "ring", text: "waiting for connection..." };
23
+ let ClusterPublishNode = class ClusterPublishNode extends node_red_plugincore_1.BaseNode {
24
+ constructor(node, config) {
25
+ super(node, config);
26
+ this._unsubscribeStatus = null;
27
+ this._configNode = node_red_plugincore_1.NodeManager.RED.nodes.getNode(config.clusterConfig).node();
28
+ this._subject = config.subject;
29
+ this._mode = config.mode || DeliveryMode.Durable;
30
+ this._ttl = config.ttl || 30;
31
+ if (this._configNode.isObserver()) {
32
+ this.node().status(observerStatus);
33
+ return;
34
+ }
35
+ const client = this._configNode.getClient();
36
+ if (client) {
37
+ this.node().status(client.status() === "connected" ? readyStatus : notConnectedStatus);
38
+ this._unsubscribeStatus = client.onStatusChange((status) => {
39
+ if (status === "connected") {
40
+ this.node().status(readyStatus);
41
+ this._announceEndpoint(client);
42
+ }
43
+ else {
44
+ this.node().status(notConnectedStatus);
45
+ }
46
+ });
47
+ if (client.status() === "connected") {
48
+ this._announceEndpoint(client);
49
+ }
50
+ }
51
+ else {
52
+ this.node().status(notConnectedStatus);
53
+ }
54
+ }
55
+ onInit() {
56
+ this.node().on("close", (removed, done) => {
57
+ this._unsubscribeStatus?.();
58
+ this._unsubscribeStatus = null;
59
+ if (removed) {
60
+ const client = this._configNode.getClient();
61
+ if (client)
62
+ this._removeEndpoint(client);
63
+ }
64
+ done();
65
+ });
66
+ }
67
+ async messageReceived(message) {
68
+ if (this._configNode.isObserver())
69
+ return;
70
+ const client = this._configNode.getClient();
71
+ if (!client || client.status() !== "connected") {
72
+ this.node().warn("ClusterPublish: not connected, message dropped");
73
+ return;
74
+ }
75
+ const subject = client.buildPublishSubject(this._subject);
76
+ const data = JSON.stringify(message.payload);
77
+ try {
78
+ if (this._mode === DeliveryMode.Durable) {
79
+ await client.jsPublish(subject, data);
80
+ }
81
+ else {
82
+ client.corePublish(subject, data);
83
+ }
84
+ }
85
+ catch (err) {
86
+ this.node().error(`ClusterPublish: failed to publish: ${err.message}`, message);
87
+ this.node().status({ fill: "red", shape: "ring", text: `publish error: ${err.message}` });
88
+ }
89
+ }
90
+ // ── KV endpoint announcement ─────────────────────────────────────────────
91
+ _announceEndpoint(client) {
92
+ client.announceEndpoint(this._subject, this._mode, this._ttl).catch(err => {
93
+ this.node().warn(`ClusterPublish: failed to announce endpoint: ${err.message}`);
94
+ });
95
+ }
96
+ _removeEndpoint(client) {
97
+ client.removeEndpoint(this._subject).catch(() => { });
98
+ }
99
+ };
100
+ exports.ClusterPublishNode = ClusterPublishNode;
101
+ __decorate([
102
+ (0, node_red_plugincore_1.onInput)(),
103
+ __metadata("design:type", Function),
104
+ __metadata("design:paramtypes", [Object]),
105
+ __metadata("design:returntype", Promise)
106
+ ], ClusterPublishNode.prototype, "messageReceived", null);
107
+ exports.ClusterPublishNode = ClusterPublishNode = __decorate([
108
+ (0, node_red_plugincore_1.NodeDescription)({
109
+ id: "ClusterPublishNode",
110
+ name: "Cluster Publish Node",
111
+ group: "cluster",
112
+ sourceFile: node_red_plugincore_1.SourceUtility.getSourcePath("/build/", "/src/") + "ClusterPublishNode.html",
113
+ package: "@theotherwillembotha/node-red-cluster",
114
+ dependencies: [ClusterConfigNode_1.ClusterConfigNode],
115
+ tags: ["Cluster"]
116
+ }),
117
+ __metadata("design:paramtypes", [Object, Object])
118
+ ], ClusterPublishNode);
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ClusterSubscribeNode = void 0;
13
+ const node_red_plugincore_1 = require("@theotherwillembotha/node-red-plugincore");
14
+ const ClusterConfigNode_1 = require("./ClusterConfigNode");
15
+ const subscribedStatus = { fill: "green", shape: "dot", text: "subscribed" };
16
+ const disconnectedStatus = { fill: "red", shape: "ring", text: "disconnected" };
17
+ const connectingStatus = { fill: "yellow", shape: "ring", text: "waiting for connection..." };
18
+ let ClusterSubscribeNode = class ClusterSubscribeNode extends node_red_plugincore_1.BaseNode {
19
+ constructor(node, config) {
20
+ super(node, config);
21
+ this._subscriptions = [];
22
+ this._unsubscribeStatus = null;
23
+ this._configNode = node_red_plugincore_1.NodeManager.RED.nodes.getNode(config.clusterConfig).node();
24
+ this._subjectPattern = config.subjectPattern;
25
+ const client = this._configNode.getClient();
26
+ if (client) {
27
+ this._unsubscribeStatus = client.onStatusChange((status) => {
28
+ if (status === "connected") {
29
+ this._subscribe(client);
30
+ }
31
+ else {
32
+ this._unsubscribe();
33
+ this.node().status(disconnectedStatus);
34
+ }
35
+ });
36
+ if (client.status() === "connected") {
37
+ this._subscribe(client);
38
+ }
39
+ else {
40
+ this.node().status(connectingStatus);
41
+ }
42
+ }
43
+ else {
44
+ this.node().status(connectingStatus);
45
+ }
46
+ }
47
+ onInit() {
48
+ this.node().on("close", (removed, done) => {
49
+ this._unsubscribeStatus?.();
50
+ this._unsubscribeStatus = null;
51
+ if (removed) { /* nothing to clean up */ }
52
+ this._unsubscribe();
53
+ done();
54
+ });
55
+ }
56
+ // ── Subscription management ──────────────────────────────────────────────
57
+ _subscribe(client) {
58
+ if (this._subscriptions.length > 0)
59
+ return; // already subscribed
60
+ const nc = client.natsClient();
61
+ if (!nc)
62
+ return;
63
+ const patterns = this._subjectPattern
64
+ .split(",")
65
+ .map(p => p.trim())
66
+ .filter(p => p.length > 0);
67
+ const node = this.node();
68
+ for (const pattern of patterns) {
69
+ const sub = nc.subscribe(client.buildSubscribePattern(pattern));
70
+ this._subscriptions.push(sub);
71
+ (async () => {
72
+ for await (const msg of sub) {
73
+ try {
74
+ node.send({ payload: JSON.parse(msg.string()), topic: msg.subject });
75
+ }
76
+ catch (_) {
77
+ node.send({ payload: msg.string(), topic: msg.subject });
78
+ }
79
+ }
80
+ })();
81
+ }
82
+ const count = this._subscriptions.length;
83
+ this.node().status(count > 1
84
+ ? { fill: "green", shape: "dot", text: `subscribed (${count})` }
85
+ : subscribedStatus);
86
+ }
87
+ _unsubscribe() {
88
+ this._subscriptions.forEach(sub => sub.unsubscribe());
89
+ this._subscriptions = [];
90
+ }
91
+ };
92
+ exports.ClusterSubscribeNode = ClusterSubscribeNode;
93
+ exports.ClusterSubscribeNode = ClusterSubscribeNode = __decorate([
94
+ (0, node_red_plugincore_1.NodeDescription)({
95
+ id: "ClusterSubscribeNode",
96
+ name: "Cluster Subscribe Node",
97
+ group: "cluster",
98
+ sourceFile: node_red_plugincore_1.SourceUtility.getSourcePath("/build/", "/src/") + "ClusterSubscribeNode.html",
99
+ package: "@theotherwillembotha/node-red-cluster",
100
+ dependencies: [ClusterConfigNode_1.ClusterConfigNode],
101
+ tags: ["Cluster"]
102
+ }),
103
+ __metadata("design:paramtypes", [Object, Object])
104
+ ], ClusterSubscribeNode);
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ClusterClient = void 0;
4
+ const transport_node_1 = require("@nats-io/transport-node");
5
+ const jetstream_1 = require("@nats-io/jetstream");
6
+ const kv_1 = require("@nats-io/kv");
7
+ class ClusterClient {
8
+ constructor(params) {
9
+ this._natsClient = null;
10
+ this._kvBucket = null;
11
+ this._status = "idle";
12
+ this._statusListeners = [];
13
+ this._streamName = null;
14
+ this._params = params;
15
+ }
16
+ // ── Lifecycle ────────────────────────────────────────────────────────────
17
+ updateParams(params) {
18
+ if (JSON.stringify(params) === JSON.stringify(this._params))
19
+ return;
20
+ this._streamName = null;
21
+ // stop() must run with the OLD params so it cleans up the correct KV keys,
22
+ // then swap params in before start() so the new identity is registered.
23
+ this.stop().then(() => {
24
+ this._params = params;
25
+ return this.start();
26
+ }).catch(err => {
27
+ console.error("ClusterClient: restart after param change failed:", err);
28
+ });
29
+ }
30
+ async start() {
31
+ this._setStatus("connecting");
32
+ const connectOpts = { servers: this._params.natsAddress };
33
+ if (this._params.natsUser)
34
+ connectOpts.user = this._params.natsUser;
35
+ if (this._params.natsPass)
36
+ connectOpts.pass = this._params.natsPass;
37
+ this._natsClient = await (0, transport_node_1.connect)(connectOpts);
38
+ await this._initKV();
39
+ this._setStatus("connected");
40
+ }
41
+ async stop() {
42
+ if (this._kvBucket) {
43
+ try {
44
+ await this._kvBucket.delete(`instances.${this._params.instanceId}`);
45
+ }
46
+ catch (_) { }
47
+ try {
48
+ const iter = await this._kvBucket.keys(`endpoints.${this._params.instanceId}.>`);
49
+ for await (const key of iter) {
50
+ try {
51
+ await this._kvBucket.delete(key);
52
+ }
53
+ catch (_) { }
54
+ }
55
+ }
56
+ catch (_) { /* no endpoint keys to clean up */ }
57
+ this._kvBucket = null;
58
+ }
59
+ if (this._natsClient) {
60
+ try {
61
+ await this._natsClient.drain();
62
+ }
63
+ catch (_) { /* ignore drain errors */ }
64
+ this._natsClient = null;
65
+ }
66
+ this._streamName = null;
67
+ this._setStatus("idle");
68
+ }
69
+ // ── Accessors ────────────────────────────────────────────────────────────
70
+ natsClient() { return this._natsClient; }
71
+ kvBucket() { return this._kvBucket; }
72
+ status() { return this._status; }
73
+ params() { return this._params; }
74
+ // ── Subject helpers ──────────────────────────────────────────────────────
75
+ /** `nodered-cluster.field-a.commands.opengate` */
76
+ buildPublishSubject(subject) {
77
+ return `${this.rootPrefix()}.${this._params.instanceId}.${subject}`;
78
+ }
79
+ /** `nodered-cluster.*.commands.opengate` */
80
+ buildSubscribePattern(subjectPattern) {
81
+ return `${this.rootPrefix()}.${subjectPattern}`;
82
+ }
83
+ /** Strip leading `/`, replace `/` with `.` e.g. `/nodered-cluster` → `nodered-cluster` */
84
+ rootPrefix() {
85
+ return this._params.zkRootPath.replace(/^\//, "").replace(/\//g, ".");
86
+ }
87
+ /** KV bucket name e.g. `CLUSTER_NODERED_CLUSTER` */
88
+ kvBucketName() {
89
+ return "CLUSTER_" + this.rootPrefix().toUpperCase().replace(/[.\-\/]/g, "_");
90
+ }
91
+ // ── KV endpoint/instance helpers ─────────────────────────────────────────
92
+ async announceEndpoint(subject, mode, ttl) {
93
+ const kv = this._kvBucket;
94
+ if (!kv)
95
+ return;
96
+ await kv.put(`endpoints.${this._params.instanceId}.${subject}`, JSON.stringify({ mode, ttl }));
97
+ }
98
+ async removeEndpoint(subject) {
99
+ const kv = this._kvBucket;
100
+ if (!kv)
101
+ return;
102
+ await kv.delete(`endpoints.${this._params.instanceId}.${subject}`);
103
+ }
104
+ // ── Stream management ────────────────────────────────────────────────────
105
+ async ensureStream() {
106
+ if (this._streamName)
107
+ return this._streamName;
108
+ const nc = this._natsClient;
109
+ const jsm = await (0, jetstream_1.jetstreamManager)(nc);
110
+ const prefix = this.rootPrefix();
111
+ const name = prefix.toUpperCase().replace(/[.\-\/]/g, "_");
112
+ try {
113
+ await jsm.streams.info(name);
114
+ }
115
+ catch (_) {
116
+ await jsm.streams.add({ name, subjects: [`${prefix}.>`] });
117
+ }
118
+ this._streamName = name;
119
+ return name;
120
+ }
121
+ /** Publish to JetStream (durable delivery). Ensures stream exists first. */
122
+ async jsPublish(subject, data) {
123
+ await this.ensureStream();
124
+ const js = (0, jetstream_1.jetstream)(this._natsClient);
125
+ await js.publish(subject, data);
126
+ }
127
+ /** Publish via core NATS (ephemeral fire and forget). */
128
+ corePublish(subject, data) {
129
+ this._natsClient.publish(subject, data);
130
+ }
131
+ // ── Status subscriptions ─────────────────────────────────────────────────
132
+ onStatusChange(listener) {
133
+ this._statusListeners.push(listener);
134
+ return () => {
135
+ this._statusListeners = this._statusListeners.filter(l => l !== listener);
136
+ };
137
+ }
138
+ // ── Internals ────────────────────────────────────────────────────────────
139
+ _setStatus(status, error) {
140
+ this._status = status;
141
+ this._statusListeners.forEach(l => l(status, error));
142
+ }
143
+ async _initKV() {
144
+ const kvm = new kv_1.Kvm(this._natsClient);
145
+ this._kvBucket = await kvm.create(this.kvBucketName(), { history: 1 });
146
+ await this._kvBucket.put(`instances.${this._params.instanceId}`, JSON.stringify({ natsAddress: this._params.natsAddress, role: this._params.role }));
147
+ }
148
+ }
149
+ exports.ClusterClient = ClusterClient;