@zlink-systems/framework 0.14.0 → 0.16.0
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/dist/.tsbuildinfo +1 -1
- package/dist/contracts/Configuration/Builders.d.ts +2 -0
- package/dist/contracts/Configuration/EndpointNotation.d.ts +3 -3
- package/dist/contracts/Configuration/EndpointNotation.js +24 -7
- package/dist/contracts/Configuration/FanoutTopic.d.ts +2 -0
- package/dist/contracts/Configuration/FanoutTopic.js +14 -0
- package/dist/contracts/Configuration/RegistrationBuilders.js +13 -0
- package/dist/contracts/Configuration/RegistrationTypes.d.ts +2 -0
- package/dist/contracts/Configuration/RegistrationValidators.js +19 -14
- package/dist/runtime/backend/contracts/index.d.ts +1 -0
- package/dist/runtime/backend/node/node-backend-adapter-support.js +9 -1
- package/dist/runtime/backend/node/node-socket-backend-adapter.js +6 -0
- package/dist/runtime/channels/channel-clients.js +2 -0
- package/dist/runtime/channels/channel-socket-registry.js +16 -2
- package/dist/runtime/channels/channel-transports.d.ts +2 -0
- package/dist/runtime/channels/channel-transports.js +23 -13
- package/dist/runtime/channels/fanout-service-wire.d.ts +2 -2
- package/dist/runtime/channels/fanout-service-wire.js +5 -10
- package/dist/runtime/locations/in-memory-authority-store.js +9 -1
- package/dist/runtime/locations/location-store-repository.js +61 -3
- package/dist/runtime/streams/index.d.ts +1 -1
- package/dist/runtime/streams/index.js +7 -0
- package/package.json +3 -3
- package/src/contracts/Configuration/Builders.ts +2 -0
- package/src/contracts/Configuration/EndpointNotation.ts +23 -6
- package/src/contracts/Configuration/FanoutTopic.ts +12 -0
- package/src/contracts/Configuration/RegistrationBuilders.ts +17 -0
- package/src/contracts/Configuration/RegistrationTypes.ts +2 -0
- package/src/contracts/Configuration/RegistrationValidators.ts +19 -17
- package/src/runtime/backend/contracts/index.ts +1 -0
- package/src/runtime/backend/node/node-backend-adapter-support.ts +13 -2
- package/src/runtime/backend/node/node-socket-backend-adapter.ts +7 -0
- package/src/runtime/channels/channel-clients.ts +2 -0
- package/src/runtime/channels/channel-socket-registry.ts +22 -2
- package/src/runtime/channels/channel-transports.ts +11 -2
- package/src/runtime/channels/fanout-service-wire.ts +5 -8
- package/src/runtime/locations/in-memory-authority-store.ts +9 -1
- package/src/runtime/locations/location-store-repository.ts +73 -3
- package/src/runtime/streams/index.ts +18 -1
|
@@ -138,7 +138,9 @@ export interface ZLinkFanoutChannelBuilder {
|
|
|
138
138
|
setAdvertiseHost(advertiseHost: string): this;
|
|
139
139
|
routingId(routingId: string): this;
|
|
140
140
|
setRoutingIdPrefix(prefix: string): this;
|
|
141
|
+
setNoDrop(noDrop?: boolean): this;
|
|
141
142
|
enableSubscriber(): this;
|
|
143
|
+
subscribe(topic: string): this;
|
|
142
144
|
connect(endpoint: string): this;
|
|
143
145
|
subscriberConnections(): ZLinkEndpointConnections;
|
|
144
146
|
}
|
|
@@ -71,8 +71,8 @@ export declare function parseEndpointHostPort(endpoint: string, expectedScheme:
|
|
|
71
71
|
* endpoint, or (when `restrictToScheme` is given) when its scheme doesn't
|
|
72
72
|
* match after lowercasing -- callers translate that into their own
|
|
73
73
|
* capability-specific `ZLinkConfigurationException` message. Returns
|
|
74
|
-
* `boundEndpoint` normalized
|
|
75
|
-
*
|
|
76
|
-
*
|
|
74
|
+
* `boundEndpoint` normalized when `advertiseHost` is undefined, except that
|
|
75
|
+
* wildcard TCP hosts use the same-family loopback address. This keeps the
|
|
76
|
+
* bind-only default connectable without advertising a wildcard address.
|
|
77
77
|
*/
|
|
78
78
|
export declare function buildAdvertisedEndpoint(boundEndpoint: string, advertiseHost: string | undefined, restrictToScheme?: string): string | undefined;
|
|
@@ -173,7 +173,7 @@ function parseEndpointHostPort(endpoint, expectedScheme) {
|
|
|
173
173
|
port: /^\d+$/.test(port) ? Number(port) : 0
|
|
174
174
|
};
|
|
175
175
|
}
|
|
176
|
-
const ADVERTISE_SOURCE_PATTERN = /^([A-Za-z][A-Za-z0-9+.-]*):\/\/(
|
|
176
|
+
const ADVERTISE_SOURCE_PATTERN = /^([A-Za-z][A-Za-z0-9+.-]*):\/\/(\[[^\]]+\]|[^:]+):(\d+)$/;
|
|
177
177
|
/**
|
|
178
178
|
* Builds a normalized `scheme://advertiseHost:port` endpoint from a bound
|
|
179
179
|
* endpoint, replacing only the host. Consolidates what used to be three
|
|
@@ -186,13 +186,27 @@ const ADVERTISE_SOURCE_PATTERN = /^([A-Za-z][A-Za-z0-9+.-]*):\/\/(?:\[[^\]]+\]|[
|
|
|
186
186
|
* endpoint, or (when `restrictToScheme` is given) when its scheme doesn't
|
|
187
187
|
* match after lowercasing -- callers translate that into their own
|
|
188
188
|
* capability-specific `ZLinkConfigurationException` message. Returns
|
|
189
|
-
* `boundEndpoint` normalized
|
|
190
|
-
*
|
|
191
|
-
*
|
|
189
|
+
* `boundEndpoint` normalized when `advertiseHost` is undefined, except that
|
|
190
|
+
* wildcard TCP hosts use the same-family loopback address. This keeps the
|
|
191
|
+
* bind-only default connectable without advertising a wildcard address.
|
|
192
192
|
*/
|
|
193
193
|
function buildAdvertisedEndpoint(boundEndpoint, advertiseHost, restrictToScheme) {
|
|
194
|
-
if (advertiseHost === undefined)
|
|
195
|
-
|
|
194
|
+
if (advertiseHost === undefined) {
|
|
195
|
+
const normalized = normalizeEndpoint(boundEndpoint);
|
|
196
|
+
const match = ADVERTISE_SOURCE_PATTERN.exec(normalized);
|
|
197
|
+
if (match === null)
|
|
198
|
+
return normalized;
|
|
199
|
+
const boundHost = unbracketHost(match[2]);
|
|
200
|
+
const defaultHost = boundHost === '0.0.0.0'
|
|
201
|
+
? '127.0.0.1'
|
|
202
|
+
: boundHost === '::'
|
|
203
|
+
? '::1'
|
|
204
|
+
: undefined;
|
|
205
|
+
if (defaultHost === undefined)
|
|
206
|
+
return normalized;
|
|
207
|
+
const host = defaultHost.includes(':') ? `[${defaultHost}]` : defaultHost;
|
|
208
|
+
return normalizeEndpoint(`${match[1].toLowerCase()}://${host}:${match[3]}`);
|
|
209
|
+
}
|
|
196
210
|
const match = ADVERTISE_SOURCE_PATTERN.exec(boundEndpoint);
|
|
197
211
|
if (match === null)
|
|
198
212
|
return undefined;
|
|
@@ -202,7 +216,10 @@ function buildAdvertisedEndpoint(boundEndpoint, advertiseHost, restrictToScheme)
|
|
|
202
216
|
const host = advertiseHost.includes(':') && !advertiseHost.startsWith('[')
|
|
203
217
|
? `[${advertiseHost}]`
|
|
204
218
|
: advertiseHost;
|
|
205
|
-
return normalizeEndpoint(`${scheme}://${host}:${match[
|
|
219
|
+
return normalizeEndpoint(`${scheme}://${host}:${match[3]}`);
|
|
220
|
+
}
|
|
221
|
+
function unbracketHost(host) {
|
|
222
|
+
return host.startsWith('[') && host.endsWith(']') ? host.slice(1, -1) : host;
|
|
206
223
|
}
|
|
207
224
|
function normalizeSuffix(suffix) {
|
|
208
225
|
if (suffix.length === 0) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FANOUT_LIVENESS_TOPIC = void 0;
|
|
4
|
+
exports.requirePublicFanoutTopic = requirePublicFanoutTopic;
|
|
5
|
+
const ConfigurationException_1 = require("./ConfigurationException");
|
|
6
|
+
exports.FANOUT_LIVENESS_TOPIC = '\x01ZLF1';
|
|
7
|
+
function requirePublicFanoutTopic(topic) {
|
|
8
|
+
if (typeof topic !== 'string') {
|
|
9
|
+
throw new ConfigurationException_1.ZLinkConfigurationException('Fanout topic must be a string.');
|
|
10
|
+
}
|
|
11
|
+
if (topic.startsWith(exports.FANOUT_LIVENESS_TOPIC)) {
|
|
12
|
+
throw new ConfigurationException_1.ZLinkConfigurationException('Fanout topic is reserved for framework liveness.');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -14,6 +14,7 @@ const EndpointNotation_1 = require("./EndpointNotation");
|
|
|
14
14
|
const InternalDefaults_1 = require("./InternalDefaults");
|
|
15
15
|
const RegistrationBuilderPolicy_1 = require("./RegistrationBuilderPolicy");
|
|
16
16
|
const DiagnosticsValidation_1 = require("./DiagnosticsValidation");
|
|
17
|
+
const FanoutTopic_1 = require("./FanoutTopic");
|
|
17
18
|
function createFrameworkOptions(configure) {
|
|
18
19
|
const builder = new ZLinkFrameworkOptionsBuilder();
|
|
19
20
|
configure(builder);
|
|
@@ -362,6 +363,10 @@ class DefaultFanoutChannelBuilder {
|
|
|
362
363
|
this.channel.routingIdPrefix = (0, RegistrationBuilderPolicy_1.validateRoutingIdPrefix)(prefix);
|
|
363
364
|
return this;
|
|
364
365
|
}
|
|
366
|
+
setNoDrop(noDrop = true) {
|
|
367
|
+
this.channel.noDrop = noDrop;
|
|
368
|
+
return this;
|
|
369
|
+
}
|
|
365
370
|
enableSubscriber(endpoint) {
|
|
366
371
|
this.selectSubscriberMode(endpoint === undefined ? 'automatic' : 'manual');
|
|
367
372
|
this.channel.subscriber ??= { manualConnections: [] };
|
|
@@ -373,6 +378,14 @@ class DefaultFanoutChannelBuilder {
|
|
|
373
378
|
}
|
|
374
379
|
return this;
|
|
375
380
|
}
|
|
381
|
+
subscribe(topic) {
|
|
382
|
+
(0, FanoutTopic_1.requirePublicFanoutTopic)(topic);
|
|
383
|
+
this.channel.subscriptions ??= [];
|
|
384
|
+
if (!this.channel.subscriptions.includes(topic)) {
|
|
385
|
+
this.channel.subscriptions.push(topic);
|
|
386
|
+
}
|
|
387
|
+
return this;
|
|
388
|
+
}
|
|
376
389
|
connect(endpoint) {
|
|
377
390
|
requireRegistrationName(endpoint, `Fanout channel '${this.name}' subscriber endpoint`);
|
|
378
391
|
this.selectSubscriberMode('manual');
|
|
@@ -138,11 +138,13 @@ export interface ZLinkFrameworkRegistrationOptions {
|
|
|
138
138
|
export interface ZLinkChannelOptions {
|
|
139
139
|
readonly routingId?: string;
|
|
140
140
|
readonly routingIdPrefix?: string;
|
|
141
|
+
readonly noDrop?: boolean;
|
|
141
142
|
readonly requestTimeoutMs?: number;
|
|
142
143
|
readonly client?: ZLinkClientCapabilityOptions;
|
|
143
144
|
readonly publisher?: ZLinkPublisherCapabilityOptions;
|
|
144
145
|
readonly routeMesh?: ZLinkRouteMeshChannelOptions;
|
|
145
146
|
readonly publishHandlers?: readonly ZLinkChannelPublishHandlerRegistration[];
|
|
147
|
+
readonly subscriptions?: readonly string[];
|
|
146
148
|
readonly requestHandlers?: readonly ZLinkChannelRequestHandlerRegistration[];
|
|
147
149
|
readonly sendHandlers?: readonly ZLinkChannelSendHandlerRegistration[];
|
|
148
150
|
readonly server?: {
|
|
@@ -7,8 +7,9 @@ const RouteChannelInternalState_1 = require("./RouteChannelInternalState");
|
|
|
7
7
|
const TimerRegistrationValidator_1 = require("./TimerRegistrationValidator");
|
|
8
8
|
const Locations_1 = require("../Locations");
|
|
9
9
|
const SendTimeoutValidation_1 = require("./SendTimeoutValidation");
|
|
10
|
+
const FanoutTopic_1 = require("./FanoutTopic");
|
|
10
11
|
function validateFrameworkRegistration(registration, _options = {}) {
|
|
11
|
-
validateListenerNetworkIdentity('process network',
|
|
12
|
+
validateListenerNetworkIdentity('process network', registration.network.bindHost, registration.network.advertiseHost);
|
|
12
13
|
const actorCapableSpotNodes = [...registration.spotNodes.values()]
|
|
13
14
|
.filter((spotNode) => toActorFactoryCount(spotNode.actorFactories) > 0);
|
|
14
15
|
if (actorCapableSpotNodes.length > 1) {
|
|
@@ -143,9 +144,21 @@ function requireNonNegativeSafeInteger(label, value) {
|
|
|
143
144
|
}
|
|
144
145
|
function validateChannelCapabilities(channels, peerLocationConfigured) {
|
|
145
146
|
for (const [channelName, channel] of Object.entries(channels ?? {})) {
|
|
147
|
+
for (const topic of channel.subscriptions ?? []) {
|
|
148
|
+
(0, FanoutTopic_1.requirePublicFanoutTopic)(topic);
|
|
149
|
+
}
|
|
150
|
+
if ((channel.subscriptions ?? []).length > 0 && channel.subscriber === undefined) {
|
|
151
|
+
throw new ConfigurationException_1.ZLinkConfigurationException(`Channel '${channelName}' fanout subscriptions require a subscriber capability.`);
|
|
152
|
+
}
|
|
153
|
+
if (channel.noDrop !== undefined && typeof channel.noDrop !== 'boolean') {
|
|
154
|
+
throw new ConfigurationException_1.ZLinkConfigurationException(`Channel '${channelName}' NoDrop must be a boolean.`);
|
|
155
|
+
}
|
|
156
|
+
if (channel.noDrop !== undefined && channel.publisher === undefined) {
|
|
157
|
+
throw new ConfigurationException_1.ZLinkConfigurationException(`Channel '${channelName}' NoDrop requires a publisher role.`);
|
|
158
|
+
}
|
|
146
159
|
if (channel.server !== undefined) {
|
|
147
160
|
requireEndpoint(`channel '${channelName}' server`, channel.server.bind);
|
|
148
|
-
validateListenerNetworkIdentity(`channel '${channelName}' server`, channel.server.
|
|
161
|
+
validateListenerNetworkIdentity(`channel '${channelName}' server`, channel.server.bindHost, channel.server.advertiseHost);
|
|
149
162
|
if (channel.server.routingId !== undefined) {
|
|
150
163
|
requireName(`channel '${channelName}' server routingId`, channel.server.routingId);
|
|
151
164
|
}
|
|
@@ -154,7 +167,7 @@ function validateChannelCapabilities(channels, peerLocationConfigured) {
|
|
|
154
167
|
}
|
|
155
168
|
if (channel.publisher !== undefined) {
|
|
156
169
|
requireEndpoint(`channel '${channelName}' publisher`, channel.publisher.bind);
|
|
157
|
-
validateListenerNetworkIdentity(`channel '${channelName}' publisher`, channel.publisher.
|
|
170
|
+
validateListenerNetworkIdentity(`channel '${channelName}' publisher`, channel.publisher.bindHost, channel.publisher.advertiseHost);
|
|
158
171
|
validateFanoutPublisherIdentity(channelName, channel.routingId, channel.routingIdPrefix, peerLocationConfigured);
|
|
159
172
|
}
|
|
160
173
|
if (channel.client !== undefined) {
|
|
@@ -199,7 +212,7 @@ function validateSpotNodes(registration) {
|
|
|
199
212
|
validateSpotNodeCapability(`SpotNode '${spotNodeName}' router`, spotNode.router);
|
|
200
213
|
validateMeshChannels(spotNodeName, spotNode.meshChannels);
|
|
201
214
|
if (spotNode.router !== undefined) {
|
|
202
|
-
validateListenerNetworkIdentity(`SpotNode '${spotNodeName}' router`, spotNode.router.
|
|
215
|
+
validateListenerNetworkIdentity(`SpotNode '${spotNodeName}' router`, spotNode.router.bindHost, spotNode.router.advertiseHost);
|
|
203
216
|
}
|
|
204
217
|
validateSpotNodeCapability(`SpotNode '${spotNodeName}' pubSub`, spotNode.pubSub);
|
|
205
218
|
requireNonNegativeSafeInteger(`SpotNode '${spotNodeName}' instanceSpotIdleTimeoutMs`, spotNode.instanceSpotIdleTimeoutMs);
|
|
@@ -264,8 +277,7 @@ function validateSpotNodeCapability(capabilityName, capability) {
|
|
|
264
277
|
throw new ConfigurationException_1.ZLinkConfigurationException(`${capabilityName} routingId must not be empty or padded.`);
|
|
265
278
|
}
|
|
266
279
|
}
|
|
267
|
-
function validateListenerNetworkIdentity(capabilityName,
|
|
268
|
-
const bindHost = configuredBindHost ?? tcpEndpointHost(bindEndpoint);
|
|
280
|
+
function validateListenerNetworkIdentity(capabilityName, configuredBindHost, advertiseHost) {
|
|
269
281
|
if (configuredBindHost !== undefined) {
|
|
270
282
|
requireName(`${capabilityName} bind host`, configuredBindHost);
|
|
271
283
|
}
|
|
@@ -275,13 +287,6 @@ function validateListenerNetworkIdentity(capabilityName, bindEndpoint, configure
|
|
|
275
287
|
throw new ConfigurationException_1.ZLinkConfigurationException(`${capabilityName} advertise host must identify a connectable host, not a wildcard address.`);
|
|
276
288
|
}
|
|
277
289
|
}
|
|
278
|
-
if (bindHost !== undefined && isWildcardHost(bindHost) && advertiseHost === undefined) {
|
|
279
|
-
throw new ConfigurationException_1.ZLinkConfigurationException(`${capabilityName} must define an advertise host when its bind host is a wildcard address.`);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
function tcpEndpointHost(endpoint) {
|
|
283
|
-
const match = /^tcp:\/\/(\[[^\]]+\]|[^:]+):\d+$/.exec(endpoint ?? '');
|
|
284
|
-
return match?.[1];
|
|
285
290
|
}
|
|
286
291
|
function isWildcardHost(host) {
|
|
287
292
|
const normalized = host.startsWith('[') && host.endsWith(']')
|
|
@@ -359,7 +364,7 @@ function validateStreamNodes(registration) {
|
|
|
359
364
|
for (const [streamNodeName, streamNode] of registration.streamNodes.entries()) {
|
|
360
365
|
requireNonNegativeInteger(`STREAM node '${streamNodeName}' maxMessageSize`, streamNode.maxMessageSize);
|
|
361
366
|
requireEndpoint(`STREAM node '${streamNodeName}'`, streamNode.bind);
|
|
362
|
-
validateListenerNetworkIdentity(`STREAM node '${streamNodeName}'`, streamNode.
|
|
367
|
+
validateListenerNetworkIdentity(`STREAM node '${streamNodeName}'`, streamNode.bindHost, streamNode.advertiseHost);
|
|
363
368
|
if (streamNode.tlsServer !== undefined) {
|
|
364
369
|
requireFilePath(`STREAM node '${streamNodeName}' TLS certificate`, streamNode.tlsServer.certificatePath);
|
|
365
370
|
requireFilePath(`STREAM node '${streamNodeName}' TLS key`, streamNode.tlsServer.keyPath);
|
|
@@ -351,6 +351,7 @@ export interface ZLinkBackendRouterSocket extends ZLinkBackendConnectableSocket
|
|
|
351
351
|
}
|
|
352
352
|
export interface ZLinkBackendPublisherSocket extends ZLinkBackendSocket {
|
|
353
353
|
sendHighWaterMark: number;
|
|
354
|
+
noDrop: boolean;
|
|
354
355
|
publish(topic: string, message: Message | readonly Message[]): void;
|
|
355
356
|
}
|
|
356
357
|
export interface ZLinkBackendSubscriberSocket extends ZLinkBackendConnectableSocket {
|
|
@@ -17,6 +17,7 @@ exports.translateBindingResultError = translateBindingResultError;
|
|
|
17
17
|
exports.toNativeActorRef = toNativeActorRef;
|
|
18
18
|
const node_backend_adapter_1 = require("../node-backend-adapter");
|
|
19
19
|
const runtime_values_1 = require("../runtime-values");
|
|
20
|
+
const submission_result_1 = require("../../messaging/submission-result");
|
|
20
21
|
exports.zlink = (0, node_backend_adapter_1.loadBinding)();
|
|
21
22
|
function isBindingNotFound(error) {
|
|
22
23
|
return error instanceof exports.zlink.ConfigError && error.result === exports.zlink.ConfigResult.NotFound;
|
|
@@ -47,7 +48,14 @@ function submitBindingPublish(operation, payload) {
|
|
|
47
48
|
current.submit();
|
|
48
49
|
}
|
|
49
50
|
catch (error) {
|
|
50
|
-
|
|
51
|
+
const translated = translateBindingResultError(error);
|
|
52
|
+
if (translated instanceof runtime_values_1.ZLinkBackendResultError
|
|
53
|
+
&& translated.operation === 'submit'
|
|
54
|
+
&& (translated.result === runtime_values_1.SubmitResult.Backpressured
|
|
55
|
+
|| translated.result === runtime_values_1.SubmitResult.NotAdmitted)) {
|
|
56
|
+
(0, submission_result_1.requireOneWayCompletion)({ status: submission_result_1.ZLinkSubmitStatus.Backpressured }, 'Classic fanout publish');
|
|
57
|
+
}
|
|
58
|
+
throw translated;
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
61
|
function submitBindingReply(operation, payload) {
|
|
@@ -80,6 +80,12 @@ function wrapSocket(nativeInstance, pollCompletion) {
|
|
|
80
80
|
set sendHighWaterMark(value) {
|
|
81
81
|
requireSocketOptions(socket).sendHwm = value;
|
|
82
82
|
},
|
|
83
|
+
get noDrop() {
|
|
84
|
+
return socket.options?.noDrop ?? false;
|
|
85
|
+
},
|
|
86
|
+
set noDrop(value) {
|
|
87
|
+
requireSocketOptions(socket).noDrop = value;
|
|
88
|
+
},
|
|
83
89
|
get receiveHighWaterMark() {
|
|
84
90
|
return Number(socket.options?.recvHwm ?? 0n);
|
|
85
91
|
},
|
|
@@ -6,6 +6,7 @@ const framework_errors_internal_1 = require("../framework-errors-internal");
|
|
|
6
6
|
const submission_result_1 = require("../messaging/submission-result");
|
|
7
7
|
const abort_1 = require("../abort");
|
|
8
8
|
const execution_1 = require("../execution");
|
|
9
|
+
const fanout_service_wire_1 = require("./fanout-service-wire");
|
|
9
10
|
const spot_outbound_1 = require("../spots/spot-outbound");
|
|
10
11
|
const packet_name_1 = require("../messaging/packet-name");
|
|
11
12
|
class DefaultZLinkChannelClient {
|
|
@@ -59,6 +60,7 @@ class DefaultZLinkFanoutClient {
|
|
|
59
60
|
const topic = hasExplicitTopic
|
|
60
61
|
? topicOrEvent
|
|
61
62
|
: (0, packet_name_1.resolveFrameworkPacketName)(event, undefined, 'Fanout');
|
|
63
|
+
(0, fanout_service_wire_1.requirePublicFanoutTopic)(topic);
|
|
62
64
|
const packetName = (0, packet_name_1.resolveFrameworkPacketName)(event, undefined, 'Fanout');
|
|
63
65
|
return new DefaultZLinkFanoutPublishCall(() => this.requirePublisherChannel(channelName), async (signal) => ({
|
|
64
66
|
status: (await this.requireTransport().publish(channelName, topic, packetName, event, signal)).status
|
|
@@ -730,8 +730,7 @@ class ZLinkChannelSocketRegistry {
|
|
|
730
730
|
}
|
|
731
731
|
const subscriber = this.adapter.createSubscriberSocket(this.context);
|
|
732
732
|
subscriber.setChannelName(channelName);
|
|
733
|
-
subscriber.
|
|
734
|
-
subscriber.setSubscription(fanout_service_wire_1.FANOUT_LIVENESS_TOPIC);
|
|
733
|
+
setFanoutSubscriptions(subscriber, channel.subscriptions);
|
|
735
734
|
const monitor = this.monitoringAdapter.openSocketMonitor(subscriber);
|
|
736
735
|
const connection = {
|
|
737
736
|
channelName,
|
|
@@ -913,6 +912,9 @@ class ZLinkChannelSocketRegistry {
|
|
|
913
912
|
try {
|
|
914
913
|
publisher.publish(fanout_service_wire_1.FANOUT_LIVENESS_TOPIC, payload);
|
|
915
914
|
}
|
|
915
|
+
catch (error) {
|
|
916
|
+
this.oneWayFailureSink?.(error);
|
|
917
|
+
}
|
|
916
918
|
finally {
|
|
917
919
|
payload.close();
|
|
918
920
|
}
|
|
@@ -1216,6 +1218,7 @@ class ZLinkChannelSocketRegistry {
|
|
|
1216
1218
|
}
|
|
1217
1219
|
const publisher = this.adapter.createPublisherSocket(this.context);
|
|
1218
1220
|
publisher.setChannelName(channelName);
|
|
1221
|
+
applyFanoutPublisherSocketOptions(publisher, channel);
|
|
1219
1222
|
publisher.bind(channel.publisher.bind);
|
|
1220
1223
|
this.publishers.set(channelName, publisher);
|
|
1221
1224
|
this.fanoutPublisherNextBeacon.set(channelName, performance.now() + CLIENT_SERVER_PROBE_INTERVAL_MS);
|
|
@@ -1386,6 +1389,14 @@ function closeMessages(parts) {
|
|
|
1386
1389
|
for (const part of parts)
|
|
1387
1390
|
part.close();
|
|
1388
1391
|
}
|
|
1392
|
+
function setFanoutSubscriptions(subscriber, applicationTopics) {
|
|
1393
|
+
const topics = new Set(applicationTopics);
|
|
1394
|
+
if (topics.size === 0)
|
|
1395
|
+
topics.add('');
|
|
1396
|
+
topics.add(fanout_service_wire_1.FANOUT_LIVENESS_TOPIC);
|
|
1397
|
+
for (const topic of topics)
|
|
1398
|
+
subscriber.setSubscription(topic);
|
|
1399
|
+
}
|
|
1389
1400
|
function deriveRoutingId(baseRoutingId, suffix) {
|
|
1390
1401
|
const derived = `${baseRoutingId}\0${suffix}`;
|
|
1391
1402
|
if (Buffer.byteLength(derived, 'utf8') > 255) {
|
|
@@ -1393,6 +1404,9 @@ function deriveRoutingId(baseRoutingId, suffix) {
|
|
|
1393
1404
|
}
|
|
1394
1405
|
return derived;
|
|
1395
1406
|
}
|
|
1407
|
+
function applyFanoutPublisherSocketOptions(publisher, channel) {
|
|
1408
|
+
publisher.noDrop = channel.noDrop ?? false;
|
|
1409
|
+
}
|
|
1396
1410
|
function fanoutDiscoveryConnectionId(connectionId) {
|
|
1397
1411
|
return `fanout:${connectionId.replaceAll('\0', '/')}`;
|
|
1398
1412
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ZLinkBackendMessageLike as MessageLike } from '../backend/runtime-values';
|
|
2
2
|
import type { ZLinkBackendMeshNode, ZLinkBackendSpot } from '../backend/contracts';
|
|
3
|
+
import { ZLinkFrameworkException } from '../../contracts';
|
|
3
4
|
import type { ZLinkFanoutListenerStatus } from '../../contracts';
|
|
4
5
|
import type { Message } from '../../contracts/Common/Message';
|
|
5
6
|
import { type ZLinkSubmitResult } from '../messaging/submission-result';
|
|
@@ -124,4 +125,5 @@ export declare class ZLinkRuntimeRouteTransport implements ZLinkRouteClientTrans
|
|
|
124
125
|
private waitForMeshReply;
|
|
125
126
|
private decodeMeshReply;
|
|
126
127
|
}
|
|
128
|
+
export declare function meshRequestFailure(meshName: string, result: number, nativeErrno: number): ZLinkFrameworkException;
|
|
127
129
|
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ZLinkRuntimeRouteTransport = void 0;
|
|
4
|
+
exports.meshRequestFailure = meshRequestFailure;
|
|
4
5
|
const framework_errors_internal_1 = require("../framework-errors-internal");
|
|
5
6
|
const runtime_message_1 = require("../backend/runtime-message");
|
|
6
7
|
const runtime_values_1 = require("../backend/runtime-values");
|
|
@@ -413,19 +414,28 @@ function meshRequestFailure(meshName, result, nativeErrno) {
|
|
|
413
414
|
const wireKind = canonical
|
|
414
415
|
? (0, framework_errors_internal_1.internalFrameworkErrorKindFromWireReply)(result, nativeErrno)
|
|
415
416
|
: undefined;
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
417
|
+
// NotFound without an errno is the selection itself coming back empty: the
|
|
418
|
+
// send path and its connection are there, and applying eligibility and drain
|
|
419
|
+
// left no member to pick. The spec names that Unavailable, not NotFound, and
|
|
420
|
+
// not the protocol error a non-canonical pair would otherwise produce
|
|
421
|
+
// (06-framework-api "no eligible select-one member"). A named target that
|
|
422
|
+
// does not exist arrives with an errno and still ends as NotFound.
|
|
423
|
+
const noEligibleMember = result === runtime_values_1.RequestResult.NotFound && nativeErrno === 0;
|
|
424
|
+
const kind = noEligibleMember
|
|
425
|
+
? framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RouteNotConnected
|
|
426
|
+
: !canonical
|
|
427
|
+
? framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RequestProtocolError
|
|
428
|
+
: result === runtime_values_1.RequestResult.NotFound
|
|
429
|
+
? framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RequestTargetNotFound
|
|
430
|
+
: result === runtime_values_1.RequestResult.TimedOut
|
|
431
|
+
? framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.DeadlineExceeded
|
|
432
|
+
: result === runtime_values_1.RequestResult.Terminated
|
|
433
|
+
? framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RuntimeShutdown
|
|
434
|
+
: result === runtime_values_1.RequestResult.Conflict || result === runtime_values_1.RequestResult.InternalError
|
|
435
|
+
? wireKind ?? framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RequestProtocolError
|
|
436
|
+
: result === runtime_values_1.RequestResult.NotConnected || result === runtime_values_1.RequestResult.Backpressured
|
|
437
|
+
? framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RouteNotConnected
|
|
438
|
+
: wireKind ?? framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RequestFailed;
|
|
429
439
|
const error = (0, framework_errors_internal_1.createInternalFrameworkException)(kind, `MeshNode '${meshName}' request failed with result ${result} and errno ${nativeErrno}.`, result === runtime_values_1.RequestResult.NotConnected || result === runtime_values_1.RequestResult.Backpressured);
|
|
430
440
|
// An operation id was returned before this completion was observed. The
|
|
431
441
|
// application envelope therefore crossed the native submission boundary,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { FANOUT_LIVENESS_TOPIC, requirePublicFanoutTopic } from '../../contracts/Configuration/FanoutTopic';
|
|
1
2
|
import type { ZLinkChannelEnvelopeHeader } from './channel-envelope';
|
|
2
|
-
export
|
|
3
|
+
export { FANOUT_LIVENESS_TOPIC, requirePublicFanoutTopic };
|
|
3
4
|
export declare const FANOUT_LIVENESS_PAYLOAD: Uint8Array<ArrayBuffer>;
|
|
4
5
|
export type ZLinkFanoutInboundKind = 'application' | 'beacon' | 'protocolError';
|
|
5
6
|
export interface ZLinkFanoutInboundClassification {
|
|
@@ -9,4 +10,3 @@ export interface ZLinkFanoutInboundClassification {
|
|
|
9
10
|
export declare function inspectFanoutInbound(topic: string, parts: readonly {
|
|
10
11
|
data(): Uint8Array;
|
|
11
12
|
}[]): ZLinkFanoutInboundClassification;
|
|
12
|
-
export declare function requirePublicFanoutTopic(topic: string): void;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FANOUT_LIVENESS_PAYLOAD = exports.FANOUT_LIVENESS_TOPIC = void 0;
|
|
3
|
+
exports.FANOUT_LIVENESS_PAYLOAD = exports.requirePublicFanoutTopic = exports.FANOUT_LIVENESS_TOPIC = void 0;
|
|
4
4
|
exports.inspectFanoutInbound = inspectFanoutInbound;
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const FanoutTopic_1 = require("../../contracts/Configuration/FanoutTopic");
|
|
6
|
+
Object.defineProperty(exports, "FANOUT_LIVENESS_TOPIC", { enumerable: true, get: function () { return FanoutTopic_1.FANOUT_LIVENESS_TOPIC; } });
|
|
7
|
+
Object.defineProperty(exports, "requirePublicFanoutTopic", { enumerable: true, get: function () { return FanoutTopic_1.requirePublicFanoutTopic; } });
|
|
7
8
|
const channel_envelope_inspection_1 = require("./channel-envelope-inspection");
|
|
8
|
-
exports.FANOUT_LIVENESS_TOPIC = '\x01ZLF1';
|
|
9
9
|
exports.FANOUT_LIVENESS_PAYLOAD = Uint8Array.from([0x5a, 0x46, 0x01, 0x01]);
|
|
10
10
|
function inspectFanoutInbound(topic, parts) {
|
|
11
|
-
if (topic !==
|
|
11
|
+
if (topic !== FanoutTopic_1.FANOUT_LIVENESS_TOPIC) {
|
|
12
12
|
const header = (0, channel_envelope_inspection_1.tryDecodeChannelHeader)(parts);
|
|
13
13
|
return header === undefined
|
|
14
14
|
? { kind: 'protocolError' }
|
|
@@ -22,8 +22,3 @@ function inspectFanoutInbound(topic, parts) {
|
|
|
22
22
|
? { kind: 'beacon' }
|
|
23
23
|
: { kind: 'protocolError' };
|
|
24
24
|
}
|
|
25
|
-
function requirePublicFanoutTopic(topic) {
|
|
26
|
-
if (topic === exports.FANOUT_LIVENESS_TOPIC) {
|
|
27
|
-
throw new configuration_1.ZLinkConfigurationException('Fanout topic is reserved for framework liveness.');
|
|
28
|
-
}
|
|
29
|
-
}
|
|
@@ -174,7 +174,15 @@ class ZLinkInMemoryAuthorityStore {
|
|
|
174
174
|
if (current.snapshot.allocation.state === 'active') {
|
|
175
175
|
return { kind: 'alreadyExists', current: this.snapshot(current.snapshot) };
|
|
176
176
|
}
|
|
177
|
-
|
|
177
|
+
// An unfinished creation whose owner lease ended is cancellable.
|
|
178
|
+
// Reclaim it so the key does not stay blocked forever.
|
|
179
|
+
if (this.isOwnerLive(current.snapshot) || current.creation === undefined) {
|
|
180
|
+
return { kind: 'conflict', current: this.read(key) };
|
|
181
|
+
}
|
|
182
|
+
this.adjustCapacity(this.pendingCapacity, current.snapshot.allocation, -1);
|
|
183
|
+
this.rows.delete(key);
|
|
184
|
+
this.creationTerminals.set(current.creation.reservationId, 'aborted');
|
|
185
|
+
this.scanRevision++;
|
|
178
186
|
}
|
|
179
187
|
const target = creationTarget(request);
|
|
180
188
|
if (!this.validation.isTargetLive(target.descriptor, target.lifecycleGeneration, target.owner)) {
|
|
@@ -758,9 +758,56 @@ class ZLinkLocationStoreRepository extends in_memory_location_store_1.ZLinkInMem
|
|
|
758
758
|
|| snapshot.allocation.stableType !== request.intent.stableType) {
|
|
759
759
|
return { kind: 'typeMismatch', current: snapshot };
|
|
760
760
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
761
|
+
if (snapshot.allocation.state === 'active') {
|
|
762
|
+
return { kind: 'alreadyExists', current: snapshot };
|
|
763
|
+
}
|
|
764
|
+
if (record.aggregate !== undefined
|
|
765
|
+
|| isCanonicalAuthorityPayload(record.snapshot.payload)) {
|
|
766
|
+
// A relocation record carries its own recovery protocol. Reserve
|
|
767
|
+
// may reclaim only an unfinished plain creation.
|
|
768
|
+
return { kind: 'conflict', current: snapshot };
|
|
769
|
+
}
|
|
770
|
+
// The row is Reserved by another owner. An unfinished creation
|
|
771
|
+
// whose owner lease ended is cancellable, so reclaim it and retry
|
|
772
|
+
// instead of blocking the key forever.
|
|
773
|
+
const staleOwnerKey = ownerKey(snapshot.ownerId);
|
|
774
|
+
const staleCapacityKey = capacityKey(snapshot.allocation.descriptor.meshName, String(snapshot.allocation.descriptor.rid));
|
|
775
|
+
const [staleOwnerRead, staleCapacityRead] = await Promise.all([
|
|
776
|
+
this.provider.read(staleOwnerKey, signal),
|
|
777
|
+
this.provider.read(staleCapacityKey, signal)
|
|
778
|
+
]);
|
|
779
|
+
if (staleOwnerRead.kind === 'found'
|
|
780
|
+
&& staleOwnerRead.value.expiresAt === undefined) {
|
|
781
|
+
// A lease is always written with a positive TTL, so a missing
|
|
782
|
+
// expiry is a corrupt record. Reclaiming deletes authority state,
|
|
783
|
+
// so refuse rather than read the absence as expiry.
|
|
784
|
+
throw new Error('Location Store owner lease record is invalid.');
|
|
785
|
+
}
|
|
786
|
+
if (sameLiveOwner(staleOwnerRead, record.snapshot)) {
|
|
787
|
+
return { kind: 'conflict', current: snapshot };
|
|
788
|
+
}
|
|
789
|
+
const staleCapacity = staleCapacityRead.kind === 'missing'
|
|
790
|
+
? emptyCapacityRecord()
|
|
791
|
+
: decodeJson(staleCapacityRead.value.bytes);
|
|
792
|
+
await this.provider.write({
|
|
793
|
+
conditions: [
|
|
794
|
+
{ kind: 'version', key: rowKey, expected: current.value.version },
|
|
795
|
+
versionCondition(staleOwnerKey, staleOwnerRead),
|
|
796
|
+
conditionFor(staleCapacityKey, staleCapacityRead)
|
|
797
|
+
],
|
|
798
|
+
mutations: [
|
|
799
|
+
{ kind: 'delete', key: rowKey },
|
|
800
|
+
{
|
|
801
|
+
kind: 'put',
|
|
802
|
+
key: staleCapacityKey,
|
|
803
|
+
bytes: encodeJson({
|
|
804
|
+
active: staleCapacity.active,
|
|
805
|
+
pending: subtractCapacity(staleCapacity.pending, record.snapshot.allocation.capacity)
|
|
806
|
+
})
|
|
807
|
+
}
|
|
808
|
+
]
|
|
809
|
+
}, signal);
|
|
810
|
+
continue;
|
|
764
811
|
}
|
|
765
812
|
const descriptor = liveTargetDescriptor(descriptorRead, leaseRead, request.target);
|
|
766
813
|
if (descriptor === undefined) {
|
|
@@ -950,6 +997,12 @@ class ZLinkLocationStoreRepository extends in_memory_location_store_1.ZLinkInMem
|
|
|
950
997
|
this.provider.read(leaseKey, signal),
|
|
951
998
|
this.provider.read(capacityRowKey, signal)
|
|
952
999
|
]);
|
|
1000
|
+
if (leaseRead.kind === 'found' && leaseRead.value.expiresAt === undefined) {
|
|
1001
|
+
// A lease is always written with a positive TTL, so a missing expiry
|
|
1002
|
+
// is a corrupt record. Abort deletes authority state, so refuse
|
|
1003
|
+
// rather than read the absence as expiry.
|
|
1004
|
+
throw new Error('Location Store owner lease record is invalid.');
|
|
1005
|
+
}
|
|
953
1006
|
if (!sameLiveOwner(leaseRead, record.snapshot))
|
|
954
1007
|
return { kind: 'stale' };
|
|
955
1008
|
const capacity = capacityRead.kind === 'missing'
|
|
@@ -2896,6 +2949,11 @@ function sameCreationTarget(snapshot, target) {
|
|
|
2896
2949
|
&& snapshot.ownerId === target.owner.ownerId
|
|
2897
2950
|
&& snapshot.ownerLeaseGeneration === target.owner.leaseGeneration;
|
|
2898
2951
|
}
|
|
2952
|
+
const CANONICAL_AUTHORITY_MAGIC = Buffer.from('ZLAU');
|
|
2953
|
+
function isCanonicalAuthorityPayload(payload) {
|
|
2954
|
+
return payload.byteLength >= CANONICAL_AUTHORITY_MAGIC.byteLength
|
|
2955
|
+
&& Buffer.from(payload.buffer, payload.byteOffset, CANONICAL_AUTHORITY_MAGIC.byteLength).equals(CANONICAL_AUTHORITY_MAGIC);
|
|
2956
|
+
}
|
|
2899
2957
|
function sameLiveOwner(lease, snapshot) {
|
|
2900
2958
|
if (lease.kind === 'missing')
|
|
2901
2959
|
return false;
|
|
@@ -3,7 +3,7 @@ import type { ZLinkProviderResolver } from '../../contracts/Common/ZLinkProvider
|
|
|
3
3
|
import type { ZLinkSubmitResult } from '../messaging/submission-result';
|
|
4
4
|
import { ZLinkMessage } from '../../contracts';
|
|
5
5
|
import type { Message } from '../../contracts/Common/Message';
|
|
6
|
-
import type
|
|
6
|
+
import { type ZLinkFrameworkRegistration } from '../configuration';
|
|
7
7
|
import { ZLinkDispatchErrorReporter } from '../channels';
|
|
8
8
|
import type { ZLinkBackendAdapterFactory, ZLinkBackendActorSessionNode, ZLinkBackendContext, ZLinkBackendMeshNode } from '../backend/contracts';
|
|
9
9
|
import type { ZLinkMeshCompletionTable } from '../backend/mesh-completion-table';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ZLinkStreamBindingRuntime = exports.ZLinkStreamSessionNodeRuntime = exports.ZLinkStreamSessionRuntime = exports.ZLinkStreamRuntimeManager = exports.DefaultZLinkSessionContext = exports.DefaultZLinkSessionActor = exports.DefaultZLinkBoundSessionFactory = exports.DefaultZLinkBoundSession = exports.ZLinkManagedStream = exports.zlinkStreamLz4CompressionCodec = exports.ZLinkActorSessionBindingRegistry = exports.ZLinkActorSessionLifecycleCoordinator = exports.ZLinkPendingSessionRequest = void 0;
|
|
4
|
+
const configuration_1 = require("../configuration");
|
|
4
5
|
const service_session_binding_ingress_port_1 = require("../foundation/service-session-binding-ingress-port");
|
|
5
6
|
const protocol_1 = require("./protocol");
|
|
6
7
|
const actor_session_binding_registry_1 = require("./actor-session-binding-registry");
|
|
@@ -64,6 +65,11 @@ class ZLinkStreamRuntimeManager {
|
|
|
64
65
|
socket.setTlsServer(tlsServer.certificatePath, tlsServer.keyPath, tlsServer.requireClientCertificate ?? false);
|
|
65
66
|
}
|
|
66
67
|
socket.bind(streamNode.bind);
|
|
68
|
+
const boundEndpoint = socket.lastEndpoint ?? streamNode.bind;
|
|
69
|
+
const advertisedEndpoint = (0, configuration_1.buildAdvertisedEndpoint)(boundEndpoint, streamNode.advertiseHost, 'tcp');
|
|
70
|
+
if (advertisedEndpoint === undefined) {
|
|
71
|
+
throw new configuration_1.ZLinkConfigurationException(`STREAM node '${nodeName}' advertised host requires a TCP endpoint, received '${boundEndpoint}'.`);
|
|
72
|
+
}
|
|
67
73
|
const readablePoller = streamAdapter.createReadablePoller(socket);
|
|
68
74
|
const nativeSessionRoutes = new Map();
|
|
69
75
|
if (actorDispatchEnabled) {
|
|
@@ -120,6 +126,7 @@ class ZLinkStreamRuntimeManager {
|
|
|
120
126
|
runtime.start();
|
|
121
127
|
this.nodes.set(nodeName, {
|
|
122
128
|
meshName: applicationMeshName,
|
|
129
|
+
advertisedEndpoint,
|
|
123
130
|
runtime,
|
|
124
131
|
socket,
|
|
125
132
|
monitor,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zlink-systems/framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@opentelemetry/api": "^1.9.1",
|
|
21
21
|
"@opentelemetry/api-logs": "^0.221.0",
|
|
22
|
-
"@zlink-systems/stream-wire": "0.
|
|
23
|
-
"@zlink-systems/zlink": "1.
|
|
22
|
+
"@zlink-systems/stream-wire": "0.16.0",
|
|
23
|
+
"@zlink-systems/zlink": "1.2.0"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -184,7 +184,9 @@ export interface ZLinkFanoutChannelBuilder {
|
|
|
184
184
|
setAdvertiseHost(advertiseHost: string): this;
|
|
185
185
|
routingId(routingId: string): this;
|
|
186
186
|
setRoutingIdPrefix(prefix: string): this;
|
|
187
|
+
setNoDrop(noDrop?: boolean): this;
|
|
187
188
|
enableSubscriber(): this;
|
|
189
|
+
subscribe(topic: string): this;
|
|
188
190
|
connect(endpoint: string): this;
|
|
189
191
|
subscriberConnections(): ZLinkEndpointConnections;
|
|
190
192
|
}
|