doover-js 0.5.4 → 0.6.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/README.md +146 -0
- package/dist/apis/_args.d.ts +16 -0
- package/dist/apis/_args.js +26 -0
- package/dist/apis/agents-api.d.ts +4 -1
- package/dist/apis/agents-api.js +139 -1
- package/dist/apis/aggregates-api.d.ts +23 -3
- package/dist/apis/aggregates-api.js +33 -4
- package/dist/apis/alarms-api.d.ts +30 -0
- package/dist/apis/alarms-api.js +51 -6
- package/dist/apis/channels-api.d.ts +40 -3
- package/dist/apis/channels-api.js +50 -5
- package/dist/apis/connections-api.d.ts +21 -5
- package/dist/apis/connections-api.js +26 -10
- package/dist/apis/messages-api.d.ts +54 -9
- package/dist/apis/messages-api.js +94 -12
- package/dist/apis/notifications-api.d.ts +52 -0
- package/dist/apis/notifications-api.js +99 -13
- package/dist/apis/permissions-api.d.ts +8 -0
- package/dist/apis/permissions-api.js +11 -2
- package/dist/apis/processors-api.d.ts +35 -0
- package/dist/apis/processors-api.js +64 -8
- package/dist/apis/users-api.d.ts +8 -0
- package/dist/apis/users-api.js +13 -0
- package/dist/client/capabilities.d.ts +10 -0
- package/dist/client/capabilities.js +43 -0
- package/dist/client/data-client.d.ts +104 -0
- package/dist/client/data-client.js +2 -0
- package/dist/client/doover-client.d.ts +31 -31
- package/dist/client/doover-client.js +58 -37
- package/dist/client/errors.d.ts +23 -0
- package/dist/client/errors.js +50 -0
- package/dist/client/local-agent-client.d.ts +65 -0
- package/dist/client/local-agent-client.js +222 -0
- package/dist/client/multiplex-client.d.ts +151 -0
- package/dist/client/multiplex-client.js +744 -0
- package/dist/client/multiplex-gateway.d.ts +49 -0
- package/dist/client/multiplex-gateway.js +114 -0
- package/dist/client/multiplex-merge.d.ts +12 -0
- package/dist/client/multiplex-merge.js +42 -0
- package/dist/client/provenance.d.ts +60 -0
- package/dist/client/provenance.js +113 -0
- package/dist/client/stats.d.ts +24 -0
- package/dist/client/stats.js +62 -0
- package/dist/client/status-tracker.d.ts +24 -0
- package/dist/client/status-tracker.js +90 -0
- package/dist/gateway/gateway-client.d.ts +23 -1
- package/dist/gateway/gateway-client.js +75 -6
- package/dist/http/rest-client.d.ts +5 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +22 -7
- package/dist/react/context.d.ts +9 -5
- package/dist/react/context.js +6 -2
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.js +3 -1
- package/dist/react/useChannelAggregate.d.ts +8 -1
- package/dist/react/useChannelAggregate.js +24 -6
- package/dist/react/useChannelMessage.d.ts +8 -1
- package/dist/react/useChannelMessage.js +17 -6
- package/dist/react/useChannelMessages.d.ts +8 -1
- package/dist/react/useChannelMessages.js +21 -7
- package/dist/react/useChannelSubscription.d.ts +6 -0
- package/dist/react/useChannelSubscription.js +8 -18
- package/dist/react/useClientStatus.d.ts +10 -0
- package/dist/react/useClientStatus.js +22 -0
- package/dist/react/useConnectionState.d.ts +5 -0
- package/dist/react/useConnectionState.js +5 -0
- package/dist/react/useMultiAgentAggregates.d.ts +8 -1
- package/dist/react/useMultiAgentAggregates.js +25 -21
- package/dist/react/useMultiAgentChannelMessages.d.ts +8 -1
- package/dist/react/useMultiAgentChannelMessages.js +23 -18
- package/dist/react/useSendMessage.d.ts +8 -1
- package/dist/react/useSendMessage.js +15 -2
- package/dist/react/useSendRpc.js +4 -1
- package/dist/react/useUpdateAggregate.d.ts +6 -0
- package/dist/react/useUpdateAggregate.js +20 -4
- package/dist/react/useUpdateMessage.d.ts +5 -0
- package/dist/react/useUpdateMessage.js +11 -2
- package/dist/rpc/errors.d.ts +10 -0
- package/dist/rpc/errors.js +23 -0
- package/dist/rpc/rpc-dispatcher.d.ts +28 -0
- package/dist/rpc/rpc-dispatcher.js +129 -0
- package/dist/test/api-args.test.d.ts +1 -0
- package/dist/test/api-args.test.js +48 -0
- package/dist/test/api-overloads.test.d.ts +9 -0
- package/dist/test/api-overloads.test.js +276 -0
- package/dist/test/capabilities.test.d.ts +1 -0
- package/dist/test/capabilities.test.js +27 -0
- package/dist/test/data-client-errors.test.d.ts +1 -0
- package/dist/test/data-client-errors.test.js +24 -0
- package/dist/test/data-client-shape.test.d.ts +1 -0
- package/dist/test/data-client-shape.test.js +28 -0
- package/dist/test/doover-client-dataclient.test.d.ts +1 -0
- package/dist/test/doover-client-dataclient.test.js +58 -0
- package/dist/test/doover-client-provenance.test.d.ts +1 -0
- package/dist/test/doover-client-provenance.test.js +32 -0
- package/dist/test/exports.test.d.ts +1 -0
- package/dist/test/exports.test.js +76 -0
- package/dist/test/gateway-provenance.test.d.ts +1 -0
- package/dist/test/gateway-provenance.test.js +29 -0
- package/dist/test/gateway-subscriptions.test.d.ts +1 -0
- package/dist/test/gateway-subscriptions.test.js +95 -0
- package/dist/test/local-agent-client.test.d.ts +1 -0
- package/dist/test/local-agent-client.test.js +202 -0
- package/dist/test/multiplex-capabilities.test.d.ts +1 -0
- package/dist/test/multiplex-capabilities.test.js +37 -0
- package/dist/test/multiplex-conflicts.test.d.ts +1 -0
- package/dist/test/multiplex-conflicts.test.js +35 -0
- package/dist/test/multiplex-gateway.test.d.ts +1 -0
- package/dist/test/multiplex-gateway.test.js +103 -0
- package/dist/test/multiplex-merge.test.d.ts +1 -0
- package/dist/test/multiplex-merge.test.js +19 -0
- package/dist/test/multiplex-noncore.test.d.ts +1 -0
- package/dist/test/multiplex-noncore.test.js +82 -0
- package/dist/test/multiplex-passthrough.test.d.ts +1 -0
- package/dist/test/multiplex-passthrough.test.js +35 -0
- package/dist/test/multiplex-reads.test.d.ts +1 -0
- package/dist/test/multiplex-reads.test.js +87 -0
- package/dist/test/multiplex-registry.test.d.ts +1 -0
- package/dist/test/multiplex-registry.test.js +81 -0
- package/dist/test/multiplex-routing.test.d.ts +1 -0
- package/dist/test/multiplex-routing.test.js +45 -0
- package/dist/test/multiplex-status.test.d.ts +1 -0
- package/dist/test/multiplex-status.test.js +63 -0
- package/dist/test/multiplex-writes.test.d.ts +1 -0
- package/dist/test/multiplex-writes.test.js +93 -0
- package/dist/test/provenance-regression.test.d.ts +1 -0
- package/dist/test/provenance-regression.test.js +45 -0
- package/dist/test/provenance-stamper.test.d.ts +1 -0
- package/dist/test/provenance-stamper.test.js +41 -0
- package/dist/test/provenance-type.test.d.ts +1 -0
- package/dist/test/provenance-type.test.js +15 -0
- package/dist/test/react-client-status.test.d.ts +1 -0
- package/dist/test/react-client-status.test.js +35 -0
- package/dist/test/react-dataclient.test.d.ts +1 -0
- package/dist/test/react-dataclient.test.js +28 -0
- package/dist/test/react-exports.test.d.ts +1 -0
- package/dist/test/react-exports.test.js +45 -0
- package/dist/test/react-sources-option.test.d.ts +1 -0
- package/dist/test/react-sources-option.test.js +41 -0
- package/dist/test/react.test.js +2 -2
- package/dist/test/rpc-dispatcher.test.d.ts +1 -0
- package/dist/test/rpc-dispatcher.test.js +190 -0
- package/dist/test/stats-rpc.test.d.ts +1 -0
- package/dist/test/stats-rpc.test.js +69 -0
- package/dist/test/status-tracker.test.d.ts +1 -0
- package/dist/test/status-tracker.test.js +85 -0
- package/dist/test/users-api.test.d.ts +1 -0
- package/dist/test/users-api.test.js +19 -0
- package/dist/types/common.d.ts +16 -0
- package/dist/types/provenance.d.ts +45 -0
- package/dist/types/provenance.js +2 -0
- package/dist/types/viewer.d.ts +3 -0
- package/dist/viewer/doover-data-provider.d.ts +30 -34
- package/dist/viewer/doover-data-provider.js +82 -316
- package/package.json +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type DooverClientConfig } from "../http/rest-client";
|
|
2
|
+
import type { Capability } from "./capabilities";
|
|
3
|
+
import type { AgentScope, AgentsApiLike, AggregatesApiLike, AlarmsApiLike, ChannelsApiLike, ConnectionsApiLike, DataClient, DataClientStatus, GatewayClientLike, MessagesApiLike, NotificationsApiLike, PermissionsApiLike, ProcessorsApiLike, RpcDispatcherLike, TurnApiLike, UsersApiLike } from "./data-client";
|
|
4
|
+
export interface LocalAgentClientConfig {
|
|
5
|
+
/** Base URL of the local agent's REST API, e.g. "http://192.168.0.7:49100". */
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
/** Base URL of the local agent's WebSocket gateway. Defaults to `baseUrl`
|
|
8
|
+
* with http(s)→ws(s). */
|
|
9
|
+
wssUrl?: string;
|
|
10
|
+
fetchImpl?: typeof fetch;
|
|
11
|
+
webSocketImpl?: typeof WebSocket;
|
|
12
|
+
webSocketFactory?: DooverClientConfig["webSocketFactory"];
|
|
13
|
+
disableBrowserLifecycleHooks?: boolean;
|
|
14
|
+
/** Stable source id. Defaults to `local:<host>:<port>` derived from baseUrl. */
|
|
15
|
+
sourceId?: string;
|
|
16
|
+
sourceLabel?: string;
|
|
17
|
+
/** Reserved for a future LAN auth blob — ignored in v1. */
|
|
18
|
+
auth?: unknown;
|
|
19
|
+
}
|
|
20
|
+
export declare class LocalAgentClient implements DataClient {
|
|
21
|
+
readonly agents: AgentsApiLike;
|
|
22
|
+
readonly channels: ChannelsApiLike;
|
|
23
|
+
readonly messages: MessagesApiLike;
|
|
24
|
+
readonly aggregates: AggregatesApiLike;
|
|
25
|
+
readonly alarms: AlarmsApiLike;
|
|
26
|
+
readonly connections: ConnectionsApiLike;
|
|
27
|
+
readonly notifications: NotificationsApiLike;
|
|
28
|
+
readonly permissions: PermissionsApiLike;
|
|
29
|
+
readonly processors: ProcessorsApiLike;
|
|
30
|
+
readonly turn: TurnApiLike;
|
|
31
|
+
readonly users: UsersApiLike;
|
|
32
|
+
readonly gateway: GatewayClientLike;
|
|
33
|
+
readonly rpc: RpcDispatcherLike;
|
|
34
|
+
private readonly identity;
|
|
35
|
+
private readonly capSet;
|
|
36
|
+
private readonly rest;
|
|
37
|
+
private readonly gatewayImpl;
|
|
38
|
+
private readonly statusTracker;
|
|
39
|
+
/** Resolved device-agent id list; null until first resolution. */
|
|
40
|
+
private resolvedScope;
|
|
41
|
+
private scopeResolving;
|
|
42
|
+
constructor(config: LocalAgentClientConfig);
|
|
43
|
+
getCapabilities(): ReadonlySet<Capability>;
|
|
44
|
+
supports(cap: Capability): boolean;
|
|
45
|
+
getAgentScope(): Promise<AgentScope>;
|
|
46
|
+
getKnownAgentScope(): AgentScope | "unknown";
|
|
47
|
+
isConnected(): boolean;
|
|
48
|
+
getStatus(): DataClientStatus;
|
|
49
|
+
onStatusChange(listener: (status: DataClientStatus) => void): () => void;
|
|
50
|
+
/**
|
|
51
|
+
* Wraps a real (provenance-wrapped) subclient so only `allowed` method names
|
|
52
|
+
* pass through; any other method call throws `UnsupportedCapabilityError`.
|
|
53
|
+
*
|
|
54
|
+
* Typing `real` as `WrappedSubclient<T>` and `allowed` as
|
|
55
|
+
* `ReadonlyArray<keyof WrappedSubclient<T> & string>` ensures the compiler
|
|
56
|
+
* catches typos in the allowed-method list at the call site.
|
|
57
|
+
*/
|
|
58
|
+
private gatedSubclient;
|
|
59
|
+
/**
|
|
60
|
+
* Builds a Proxy that throws `UnsupportedCapabilityError` (mapped via
|
|
61
|
+
* METHOD_TO_CAPABILITY) for every method call. Used for subclients with no
|
|
62
|
+
* advertised methods.
|
|
63
|
+
*/
|
|
64
|
+
private unsupportedSubclient;
|
|
65
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LocalAgentClient = void 0;
|
|
4
|
+
const agents_api_1 = require("../apis/agents-api");
|
|
5
|
+
const aggregates_api_1 = require("../apis/aggregates-api");
|
|
6
|
+
const channels_api_1 = require("../apis/channels-api");
|
|
7
|
+
const messages_api_1 = require("../apis/messages-api");
|
|
8
|
+
const gateway_client_1 = require("../gateway/gateway-client");
|
|
9
|
+
const rest_client_1 = require("../http/rest-client");
|
|
10
|
+
const errors_1 = require("./errors");
|
|
11
|
+
const provenance_1 = require("./provenance");
|
|
12
|
+
const status_tracker_1 = require("./status-tracker");
|
|
13
|
+
const LOCAL_CAPABILITIES = [
|
|
14
|
+
"agents.list",
|
|
15
|
+
"channels.list",
|
|
16
|
+
"channels.get",
|
|
17
|
+
"aggregates.get",
|
|
18
|
+
"aggregates.put",
|
|
19
|
+
"aggregates.patch",
|
|
20
|
+
"messages.list",
|
|
21
|
+
"messages.post",
|
|
22
|
+
"messages.put",
|
|
23
|
+
"gateway.subscribe",
|
|
24
|
+
"gateway.realtime",
|
|
25
|
+
"gateway.oneShot",
|
|
26
|
+
];
|
|
27
|
+
function deriveSourceId(baseUrl) {
|
|
28
|
+
try {
|
|
29
|
+
const u = new URL(baseUrl);
|
|
30
|
+
return `local:${u.hostname}${u.port ? `:${u.port}` : ""}`;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return `local:${baseUrl}`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function httpToWs(url) {
|
|
37
|
+
return url.replace(/^http/i, "ws");
|
|
38
|
+
}
|
|
39
|
+
class LocalAgentClient {
|
|
40
|
+
constructor(config) {
|
|
41
|
+
/** Resolved device-agent id list; null until first resolution. */
|
|
42
|
+
this.resolvedScope = null;
|
|
43
|
+
this.scopeResolving = null;
|
|
44
|
+
const sourceId = config.sourceId ?? deriveSourceId(config.baseUrl);
|
|
45
|
+
this.identity = {
|
|
46
|
+
id: sourceId,
|
|
47
|
+
kind: "local",
|
|
48
|
+
...(config.sourceLabel ? { label: config.sourceLabel } : {}),
|
|
49
|
+
meta: { baseUrl: config.baseUrl },
|
|
50
|
+
};
|
|
51
|
+
this.capSet = new Set(LOCAL_CAPABILITIES);
|
|
52
|
+
const stamper = new provenance_1.ProvenanceStamper(this.identity);
|
|
53
|
+
// RestClient / GatewayClient expect a DooverClientConfig — give them one
|
|
54
|
+
// with the local URLs, no auth, no org/sharing headers.
|
|
55
|
+
const restConfig = {
|
|
56
|
+
dataRestUrl: config.baseUrl,
|
|
57
|
+
controlApiUrl: config.baseUrl,
|
|
58
|
+
dataWssUrl: config.wssUrl ?? httpToWs(config.baseUrl),
|
|
59
|
+
organisationId: null,
|
|
60
|
+
sharing: "none",
|
|
61
|
+
fetchImpl: config.fetchImpl,
|
|
62
|
+
webSocketImpl: config.webSocketImpl,
|
|
63
|
+
webSocketFactory: config.webSocketFactory,
|
|
64
|
+
disableBrowserLifecycleHooks: config.disableBrowserLifecycleHooks,
|
|
65
|
+
};
|
|
66
|
+
this.rest = new rest_client_1.RestClient(restConfig); // no auth arg
|
|
67
|
+
this.gatewayImpl = new gateway_client_1.GatewayClient(restConfig); // no auth arg
|
|
68
|
+
this.gatewayImpl.setProvenanceHook((value, ctx) => stamper.stampGatewayEvent(value, ctx));
|
|
69
|
+
this.gateway = this.gatewayImpl;
|
|
70
|
+
// Real subclients for advertised methods, gated so only allowed methods pass;
|
|
71
|
+
// all others throw UnsupportedCapabilityError.
|
|
72
|
+
const realAgents = (0, provenance_1.wrapSubclient)(new agents_api_1.AgentsApi(this.rest, config.baseUrl), "agents", stamper);
|
|
73
|
+
const realChannels = (0, provenance_1.wrapSubclient)(new channels_api_1.ChannelsApi(this.rest), "channels", stamper);
|
|
74
|
+
const realMessages = (0, provenance_1.wrapSubclient)(new messages_api_1.MessagesApi(this.rest), "messages", stamper);
|
|
75
|
+
const realAggregates = (0, provenance_1.wrapSubclient)(new aggregates_api_1.AggregatesApi(this.rest), "aggregates", stamper);
|
|
76
|
+
this.agents = this.gatedSubclient("agents", realAgents, ["listAgents"]);
|
|
77
|
+
this.channels = this.gatedSubclient("channels", realChannels, ["listChannels", "getChannel"]);
|
|
78
|
+
this.aggregates = this.gatedSubclient("aggregates", realAggregates, ["getAggregate", "putAggregate", "patchAggregate"]);
|
|
79
|
+
this.messages = this.gatedSubclient("messages", realMessages, ["listMessages", "postMessage", "putMessage", "patchMessage", "createMultipartPayload"]);
|
|
80
|
+
// Never-advertised subclients: always throw.
|
|
81
|
+
this.alarms = this.unsupportedSubclient("alarms");
|
|
82
|
+
this.connections = this.unsupportedSubclient("connections");
|
|
83
|
+
this.notifications = this.unsupportedSubclient("notifications");
|
|
84
|
+
this.permissions = this.unsupportedSubclient("permissions");
|
|
85
|
+
this.processors = this.unsupportedSubclient("processors");
|
|
86
|
+
this.turn = this.unsupportedSubclient("turn");
|
|
87
|
+
this.users = this.unsupportedSubclient("users");
|
|
88
|
+
this.rpc = this.unsupportedSubclient("rpc");
|
|
89
|
+
this.statusTracker = new status_tracker_1.ClientStatusTracker(this.identity.id, this.gateway, () => this.getKnownAgentScope());
|
|
90
|
+
// Resolve the device id on (re)connect so routing is precise before the
|
|
91
|
+
// first agent-scoped request; invalidate the cache on each new session.
|
|
92
|
+
this.gatewayImpl.on("ready", () => {
|
|
93
|
+
this.resolvedScope = null;
|
|
94
|
+
void this.getAgentScope();
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// --- capabilities ---
|
|
98
|
+
getCapabilities() { return this.capSet; }
|
|
99
|
+
supports(cap) { return this.capSet.has(cap); }
|
|
100
|
+
// --- agent scope ---
|
|
101
|
+
getAgentScope() {
|
|
102
|
+
if (this.resolvedScope)
|
|
103
|
+
return Promise.resolve({ mode: "list", agentIds: this.resolvedScope });
|
|
104
|
+
if (this.scopeResolving)
|
|
105
|
+
return this.scopeResolving;
|
|
106
|
+
this.scopeResolving = (async () => {
|
|
107
|
+
let scope = { mode: "list", agentIds: [] };
|
|
108
|
+
try {
|
|
109
|
+
const res = await this.agents.listAgents();
|
|
110
|
+
const rawAgents = res?.agents ?? [];
|
|
111
|
+
const first = rawAgents[0];
|
|
112
|
+
const id = first?.id;
|
|
113
|
+
this.resolvedScope = id ? [String(id)] : [];
|
|
114
|
+
scope = { mode: "list", agentIds: this.resolvedScope };
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Don't cache on error — leave resolvedScope null so the next caller
|
|
118
|
+
// retries. Storing [] would permanently pin scope to no agents after a
|
|
119
|
+
// transient first-call failure (network unreachable, 503, etc.).
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
this.scopeResolving = null;
|
|
123
|
+
this.statusTracker.notifyScopeChanged();
|
|
124
|
+
}
|
|
125
|
+
return scope;
|
|
126
|
+
})();
|
|
127
|
+
return this.scopeResolving;
|
|
128
|
+
}
|
|
129
|
+
getKnownAgentScope() {
|
|
130
|
+
return this.resolvedScope ? { mode: "list", agentIds: this.resolvedScope } : "unknown";
|
|
131
|
+
}
|
|
132
|
+
// --- status ---
|
|
133
|
+
isConnected() { return this.gateway.isConnected(); }
|
|
134
|
+
getStatus() { return this.statusTracker.getStatus(); }
|
|
135
|
+
onStatusChange(listener) {
|
|
136
|
+
return this.statusTracker.onChange(listener);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Wraps a real (provenance-wrapped) subclient so only `allowed` method names
|
|
140
|
+
* pass through; any other method call throws `UnsupportedCapabilityError`.
|
|
141
|
+
*
|
|
142
|
+
* Typing `real` as `WrappedSubclient<T>` and `allowed` as
|
|
143
|
+
* `ReadonlyArray<keyof WrappedSubclient<T> & string>` ensures the compiler
|
|
144
|
+
* catches typos in the allowed-method list at the call site.
|
|
145
|
+
*/
|
|
146
|
+
gatedSubclient(name, real, allowed) {
|
|
147
|
+
const allowSet = new Set(allowed);
|
|
148
|
+
const clientId = this.identity.id;
|
|
149
|
+
return new Proxy(real, {
|
|
150
|
+
get(target, prop, receiver) {
|
|
151
|
+
if (typeof prop !== "string")
|
|
152
|
+
return Reflect.get(target, prop, receiver);
|
|
153
|
+
if (allowSet.has(prop))
|
|
154
|
+
return Reflect.get(target, prop, receiver);
|
|
155
|
+
return () => {
|
|
156
|
+
const cap = METHOD_TO_CAPABILITY[`${name}.${prop}`] ?? guessCapability(name);
|
|
157
|
+
return Promise.reject(new errors_1.UnsupportedCapabilityError(cap, clientId));
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Builds a Proxy that throws `UnsupportedCapabilityError` (mapped via
|
|
164
|
+
* METHOD_TO_CAPABILITY) for every method call. Used for subclients with no
|
|
165
|
+
* advertised methods.
|
|
166
|
+
*/
|
|
167
|
+
unsupportedSubclient(name) {
|
|
168
|
+
const clientId = this.identity.id;
|
|
169
|
+
return new Proxy({}, {
|
|
170
|
+
get(_t, prop) {
|
|
171
|
+
if (typeof prop !== "string")
|
|
172
|
+
return undefined;
|
|
173
|
+
return () => {
|
|
174
|
+
const cap = METHOD_TO_CAPABILITY[`${name}.${prop}`] ?? guessCapability(name);
|
|
175
|
+
return Promise.reject(new errors_1.UnsupportedCapabilityError(cap, clientId));
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
exports.LocalAgentClient = LocalAgentClient;
|
|
182
|
+
const METHOD_TO_CAPABILITY = {
|
|
183
|
+
"agents.listAgents": "agents.list",
|
|
184
|
+
"agents.getMultiAgentMessages": "agents.multiAgentMessages",
|
|
185
|
+
"agents.getMultiAgentAggregates": "agents.multiAgentAggregates",
|
|
186
|
+
"channels.listChannels": "channels.list",
|
|
187
|
+
"channels.getChannel": "channels.get",
|
|
188
|
+
"channels.createChannel": "channels.create",
|
|
189
|
+
"channels.putChannel": "channels.create",
|
|
190
|
+
"channels.archiveChannel": "channels.archive",
|
|
191
|
+
"channels.unarchiveChannel": "channels.archive",
|
|
192
|
+
"channels.listDataSeries": "channels.dataSeries",
|
|
193
|
+
"aggregates.getAggregate": "aggregates.get",
|
|
194
|
+
"aggregates.putAggregate": "aggregates.put",
|
|
195
|
+
"aggregates.patchAggregate": "aggregates.patch",
|
|
196
|
+
"aggregates.getAggregateAttachment": "aggregates.attachment",
|
|
197
|
+
"messages.listMessages": "messages.list",
|
|
198
|
+
"messages.postMessage": "messages.post",
|
|
199
|
+
"messages.getMessage": "messages.get",
|
|
200
|
+
"messages.putMessage": "messages.put",
|
|
201
|
+
"messages.patchMessage": "messages.put",
|
|
202
|
+
"messages.deleteMessage": "messages.delete",
|
|
203
|
+
"messages.getMessageAttachment": "messages.attachment",
|
|
204
|
+
"messages.getTimeseries": "messages.timeseries",
|
|
205
|
+
"messages.getInvocationLogs": "messages.invocationLogs",
|
|
206
|
+
"rpc.send": "rpc.send",
|
|
207
|
+
"users.getMe": "users.me",
|
|
208
|
+
"turn.createTurnToken": "turn.credentials",
|
|
209
|
+
};
|
|
210
|
+
function guessCapability(subclient) {
|
|
211
|
+
switch (subclient) {
|
|
212
|
+
case "alarms": return "alarms.read";
|
|
213
|
+
case "connections": return "connections.read";
|
|
214
|
+
case "notifications": return "notifications.read";
|
|
215
|
+
case "permissions": return "permissions.read";
|
|
216
|
+
case "processors": return "processors.read";
|
|
217
|
+
case "turn": return "turn.credentials";
|
|
218
|
+
case "users": return "users.me";
|
|
219
|
+
case "rpc": return "rpc.send";
|
|
220
|
+
default: return "channels.get";
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { Capability } from "./capabilities";
|
|
2
|
+
import type { AgentScope, AgentsApiLike, AggregatesApiLike, AlarmsApiLike, ChannelsApiLike, ConnectionsApiLike, DataClient, DataClientStatus, GatewayClientLike, MessagesApiLike, NotificationsApiLike, PermissionsApiLike, ProcessorsApiLike, RpcDispatcherLike, TurnApiLike, UsersApiLike } from "./data-client";
|
|
3
|
+
export type { DataClient } from "./data-client";
|
|
4
|
+
export type { AgentScope } from "./data-client";
|
|
5
|
+
export type { DataClientStatus } from "./data-client";
|
|
6
|
+
export interface MultiplexConflict {
|
|
7
|
+
method: string;
|
|
8
|
+
agentId: string;
|
|
9
|
+
channelName: string;
|
|
10
|
+
/** All owning members that returned a value (in member order). */
|
|
11
|
+
sourceIds: string[];
|
|
12
|
+
/** The values, parallel to `sourceIds`. The chosen one is index 0. */
|
|
13
|
+
values: unknown[];
|
|
14
|
+
at: number;
|
|
15
|
+
}
|
|
16
|
+
/** A serialisable description of a source. */
|
|
17
|
+
export interface SourceDescriptor {
|
|
18
|
+
id: string;
|
|
19
|
+
kind: string;
|
|
20
|
+
params?: Record<string, unknown>;
|
|
21
|
+
label?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface RegisteredSource {
|
|
24
|
+
descriptor: SourceDescriptor;
|
|
25
|
+
client?: DataClient;
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface MultiplexClientOptions {
|
|
29
|
+
factory: (descriptor: SourceDescriptor) => DataClient | Promise<DataClient>;
|
|
30
|
+
register?: SourceDescriptor[];
|
|
31
|
+
enable?: string[];
|
|
32
|
+
enableAll?: boolean;
|
|
33
|
+
disconnectOnDisable?: boolean;
|
|
34
|
+
clientId?: string;
|
|
35
|
+
}
|
|
36
|
+
type MuxEvent = "change" | "conflict";
|
|
37
|
+
export declare class MultiplexClient implements DataClient {
|
|
38
|
+
readonly agents: AgentsApiLike;
|
|
39
|
+
readonly channels: ChannelsApiLike;
|
|
40
|
+
readonly messages: MessagesApiLike;
|
|
41
|
+
readonly aggregates: AggregatesApiLike;
|
|
42
|
+
readonly alarms: AlarmsApiLike;
|
|
43
|
+
readonly connections: ConnectionsApiLike;
|
|
44
|
+
readonly notifications: NotificationsApiLike;
|
|
45
|
+
readonly permissions: PermissionsApiLike;
|
|
46
|
+
readonly processors: ProcessorsApiLike;
|
|
47
|
+
readonly turn: TurnApiLike;
|
|
48
|
+
readonly users: UsersApiLike;
|
|
49
|
+
readonly gateway: GatewayClientLike;
|
|
50
|
+
readonly rpc: RpcDispatcherLike;
|
|
51
|
+
private readonly registry;
|
|
52
|
+
private readonly factory;
|
|
53
|
+
private readonly disconnectOnDisable;
|
|
54
|
+
private readonly clientId;
|
|
55
|
+
private readonly eventListeners;
|
|
56
|
+
private capabilitiesCache;
|
|
57
|
+
private lastConflicts;
|
|
58
|
+
private readonly maxConflicts;
|
|
59
|
+
private readonly statusListeners;
|
|
60
|
+
/** sourceId → member unsubscribe fn for its onStatusChange. */
|
|
61
|
+
private readonly memberStatusUnsubs;
|
|
62
|
+
constructor(options: MultiplexClientOptions);
|
|
63
|
+
registerSource(descriptor: SourceDescriptor): void;
|
|
64
|
+
setActiveSources(idsOrDescriptors: Array<string | SourceDescriptor>): void;
|
|
65
|
+
enableSource(id: string, opts?: {
|
|
66
|
+
silent?: boolean;
|
|
67
|
+
}): void;
|
|
68
|
+
disableSource(id: string, opts?: {
|
|
69
|
+
silent?: boolean;
|
|
70
|
+
}): void;
|
|
71
|
+
removeSource(id: string): void;
|
|
72
|
+
getActiveSources(): readonly RegisteredSource[];
|
|
73
|
+
getRegisteredSources(): readonly RegisteredSource[];
|
|
74
|
+
/** Enabled members that have a built `client`. */
|
|
75
|
+
protected enabledClients(): Array<{
|
|
76
|
+
id: string;
|
|
77
|
+
src: RegisteredSource;
|
|
78
|
+
client: DataClient;
|
|
79
|
+
}>;
|
|
80
|
+
on(event: MuxEvent, handler: (...args: unknown[]) => void): void;
|
|
81
|
+
off(event: MuxEvent, handler: (...args: unknown[]) => void): void;
|
|
82
|
+
protected emit(event: MuxEvent, ...args: unknown[]): void;
|
|
83
|
+
private invalidateCapabilities;
|
|
84
|
+
private attachMemberListeners;
|
|
85
|
+
private detachMemberListeners;
|
|
86
|
+
private makeCompositeGateway;
|
|
87
|
+
/** Resolve the optional `{ sources?: string[] }` bag from a method's trailing
|
|
88
|
+
* arg: returns the candidate members, narrowed to `sources` if given (each
|
|
89
|
+
* must be enabled), still capability-filtered by the caller. The bag is the
|
|
90
|
+
* LAST argument if it is a plain object with a `sources` array. */
|
|
91
|
+
private splitSourcesOption;
|
|
92
|
+
private candidates;
|
|
93
|
+
/** Extract the agent id from the first positional arg (string or identifier object). */
|
|
94
|
+
private extractAgentId;
|
|
95
|
+
/** Throw if no enabled member supports `cap` at all (regardless of agent). */
|
|
96
|
+
private assertSomeMemberSupports;
|
|
97
|
+
private isNotFound;
|
|
98
|
+
private requiredMessagesCapability;
|
|
99
|
+
private unsupportedMethod;
|
|
100
|
+
private guessNonCoreCap;
|
|
101
|
+
protected recordConflict(method: string, agentId: string, channelName: string, values: Array<{
|
|
102
|
+
value: unknown;
|
|
103
|
+
id: string;
|
|
104
|
+
}>): void;
|
|
105
|
+
getLastConflicts(): readonly MultiplexConflict[];
|
|
106
|
+
/**
|
|
107
|
+
* Returns a method that routes a write to exactly one member:
|
|
108
|
+
* (a) `{ sources: [oneId] }` scoping → that member;
|
|
109
|
+
* (b) else the single enabled member that owns the targeted agent and has `cap`;
|
|
110
|
+
* (c) else `UnsupportedCapabilityError` (none) or `AmbiguousWriteError` (>1).
|
|
111
|
+
* The targeted agent id is `args[0]` (string form) or `args[0].agentId` (identifier form).
|
|
112
|
+
* Writes are never fanned out.
|
|
113
|
+
*/
|
|
114
|
+
private routedWrite;
|
|
115
|
+
/** Agent-scoped "first success" — used by messages.getTimeseries / getInvocationLogs. */
|
|
116
|
+
private makeFanoutFirst;
|
|
117
|
+
/** Used by messages.getMessageAttachment — returns the first member that yields a Blob. */
|
|
118
|
+
private makeBlobFanout;
|
|
119
|
+
private makeChannelsFacade;
|
|
120
|
+
private makeAggregatesFacade;
|
|
121
|
+
private makeMessagesFacade;
|
|
122
|
+
private makeAgentsFacade;
|
|
123
|
+
private makeReadFacade;
|
|
124
|
+
private makeRpcFacade;
|
|
125
|
+
getCapabilities(): ReadonlySet<Capability>;
|
|
126
|
+
supports(cap: Capability): boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Enabled members eligible for a request targeting `agentId`: those whose
|
|
129
|
+
* scope is `{ mode: "all" }` (the cloud — no enumeration), those whose list
|
|
130
|
+
* contains `agentId`, and those whose scope is still `"unknown"` (included
|
|
131
|
+
* optimistically; their scope settles for the next call). If `agentId` is
|
|
132
|
+
* undefined, all enabled members are eligible.
|
|
133
|
+
*/
|
|
134
|
+
protected membersForAgent(agentId?: string): Array<{
|
|
135
|
+
id: string;
|
|
136
|
+
src: RegisteredSource;
|
|
137
|
+
client: DataClient;
|
|
138
|
+
}>;
|
|
139
|
+
/** Members (enabled) that both own `agentId` and advertise `cap`. */
|
|
140
|
+
protected membersForAgentWithCapability(agentId: string | undefined, cap: Capability): {
|
|
141
|
+
id: string;
|
|
142
|
+
src: RegisteredSource;
|
|
143
|
+
client: DataClient;
|
|
144
|
+
}[];
|
|
145
|
+
getAgentScope(): Promise<AgentScope>;
|
|
146
|
+
getKnownAgentScope(): AgentScope | "unknown";
|
|
147
|
+
isConnected(): boolean;
|
|
148
|
+
getStatus(): DataClientStatus;
|
|
149
|
+
onStatusChange(listener: (status: DataClientStatus) => void): () => void;
|
|
150
|
+
private emitStatus;
|
|
151
|
+
}
|