doover-js 0.5.3 → 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 +18 -1
- package/dist/types/openapi.d.ts +1 -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,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
4
|
+
const chai_1 = require("chai");
|
|
5
|
+
const mocha_1 = require("mocha");
|
|
6
|
+
const react_1 = require("@testing-library/react");
|
|
7
|
+
const react_2 = require("../react");
|
|
8
|
+
function controllableClient() {
|
|
9
|
+
let status = { clientId: "x", connected: false, state: "disconnected", agentScope: "unknown", at: Date.now() };
|
|
10
|
+
const listeners = new Set();
|
|
11
|
+
const noop = new Proxy({}, { get: () => () => Promise.resolve(undefined) });
|
|
12
|
+
return {
|
|
13
|
+
client: {
|
|
14
|
+
agents: noop, channels: noop, messages: noop, aggregates: noop, alarms: noop, connections: noop, notifications: noop, permissions: noop, processors: noop, turn: noop, users: noop, rpc: noop,
|
|
15
|
+
gateway: { connect: async () => { }, disconnect: () => { }, reconnect: async () => { }, on: () => { }, off: () => { }, subscribe: () => { }, unsubscribe: () => { }, subscribeToChannel: () => () => { }, syncChannel: () => { }, sendOneShotMessage: () => { }, getSession: () => null, isConnected: () => false, getSubscriptionCount: () => 0, getSubscriptions: () => [], setStats: () => { } },
|
|
16
|
+
getCapabilities: () => new Set(), supports: () => false,
|
|
17
|
+
isConnected: () => status.connected, getStatus: () => status,
|
|
18
|
+
onStatusChange: (l) => { listeners.add(l); return () => listeners.delete(l); },
|
|
19
|
+
getAgentScope: async () => ({ mode: "all" }), getKnownAgentScope: () => "unknown",
|
|
20
|
+
},
|
|
21
|
+
push(s) { status = { ...status, ...s, at: Date.now() }; listeners.forEach((l) => l(status)); },
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
(0, mocha_1.describe)("useClientStatus", () => {
|
|
25
|
+
(0, mocha_1.it)("seeds with getStatus() and re-renders on onStatusChange", () => {
|
|
26
|
+
const { client, push } = controllableClient();
|
|
27
|
+
const { result } = (0, react_1.renderHook)(() => (0, react_2.useClientStatus)(), {
|
|
28
|
+
wrapper: ({ children }) => (0, jsx_runtime_1.jsx)(react_2.DooverProvider, { client: client, children: children }),
|
|
29
|
+
});
|
|
30
|
+
(0, chai_1.expect)(result.current.state).to.equal("disconnected");
|
|
31
|
+
(0, react_1.act)(() => push({ connected: true, state: "connected" }));
|
|
32
|
+
(0, chai_1.expect)(result.current.state).to.equal("connected");
|
|
33
|
+
(0, chai_1.expect)(result.current.connected).to.equal(true);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
4
|
+
const chai_1 = require("chai");
|
|
5
|
+
const mocha_1 = require("mocha");
|
|
6
|
+
const react_1 = require("@testing-library/react");
|
|
7
|
+
const react_2 = require("../react");
|
|
8
|
+
const multiplex_client_1 = require("../client/multiplex-client");
|
|
9
|
+
function stubMember(id) {
|
|
10
|
+
const noop = new Proxy({}, { get: () => () => Promise.resolve(undefined) });
|
|
11
|
+
return {
|
|
12
|
+
agents: noop, channels: noop, messages: noop, aggregates: noop, alarms: noop, connections: noop, notifications: noop, permissions: noop, processors: noop, turn: noop, users: noop, rpc: noop,
|
|
13
|
+
gateway: { connect: async () => { }, disconnect: () => { }, reconnect: async () => { }, on: () => { }, off: () => { }, subscribe: () => { }, unsubscribe: () => { }, subscribeToChannel: () => () => { }, syncChannel: () => { }, sendOneShotMessage: () => { }, getSession: () => null, isConnected: () => false, getSubscriptionCount: () => 0, getSubscriptions: () => [], setStats: () => { } },
|
|
14
|
+
getCapabilities: () => new Set(["channels.list"]), supports: () => true,
|
|
15
|
+
isConnected: () => false, getStatus: () => ({ clientId: id, connected: false, state: "disconnected", agentScope: "unknown", at: Date.now() }),
|
|
16
|
+
onStatusChange: () => () => { }, getAgentScope: async () => ({ mode: "list", agentIds: [] }), getKnownAgentScope: () => "unknown",
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
(0, mocha_1.describe)("DooverProvider with a DataClient", () => {
|
|
20
|
+
(0, mocha_1.it)("accepts a MultiplexClient and useDooverClient returns it", () => {
|
|
21
|
+
const factory = (d) => stubMember(d.id);
|
|
22
|
+
const mux = new multiplex_client_1.MultiplexClient({ factory, register: [{ id: "cloud", kind: "cloud" }], enable: ["cloud"] });
|
|
23
|
+
const { result } = (0, react_1.renderHook)(() => (0, react_2.useDooverClient)(), {
|
|
24
|
+
wrapper: ({ children }) => (0, jsx_runtime_1.jsx)(react_2.DooverProvider, { client: mux, children: children }),
|
|
25
|
+
});
|
|
26
|
+
(0, chai_1.expect)(result.current).to.equal(mux);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const chai_1 = require("chai");
|
|
37
|
+
const mocha_1 = require("mocha");
|
|
38
|
+
const r = __importStar(require("../react"));
|
|
39
|
+
(0, mocha_1.describe)("react exports", () => {
|
|
40
|
+
(0, mocha_1.it)("exports useClientStatus and the key helpers", () => {
|
|
41
|
+
(0, chai_1.expect)(r.useClientStatus).to.be.a("function");
|
|
42
|
+
(0, chai_1.expect)(r.channelAggregateQueryKey).to.be.a("function");
|
|
43
|
+
(0, chai_1.expect)(r.useConnectionState).to.be.a("function"); // still exported (soft-deprecated)
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
4
|
+
const chai_1 = require("chai");
|
|
5
|
+
const mocha_1 = require("mocha");
|
|
6
|
+
const react_1 = require("@testing-library/react");
|
|
7
|
+
const react_query_1 = require("@tanstack/react-query");
|
|
8
|
+
const react_2 = require("../react");
|
|
9
|
+
const multiplex_client_1 = require("../client/multiplex-client");
|
|
10
|
+
function aggMember(id, value) {
|
|
11
|
+
const src = { client: { id, kind: id.startsWith("local") ? "local" : "cloud" }, retrievedAt: 0, via: { transport: "rest", method: "aggregates.getAggregate", request: {}, startedAt: 0, durationMs: 0 } };
|
|
12
|
+
const noop = new Proxy({}, { get: () => () => Promise.resolve(undefined) });
|
|
13
|
+
return {
|
|
14
|
+
agents: noop,
|
|
15
|
+
channels: { getChannel: async () => ({ name: "c", owner_id: id, is_private: false }) },
|
|
16
|
+
messages: noop,
|
|
17
|
+
aggregates: { getAggregate: async () => ({ ...value, __source: src }) },
|
|
18
|
+
alarms: noop, connections: noop, notifications: noop, permissions: noop, processors: noop, turn: noop, users: noop, rpc: noop,
|
|
19
|
+
gateway: { connect: async () => { }, disconnect: () => { }, reconnect: async () => { }, on: () => { }, off: () => { }, subscribe: () => { }, unsubscribe: () => { }, subscribeToChannel: () => () => { }, syncChannel: () => { }, sendOneShotMessage: () => { }, getSession: () => null, isConnected: () => false, getSubscriptionCount: () => 0, getSubscriptions: () => [], setStats: () => { } },
|
|
20
|
+
getCapabilities: () => new Set(["aggregates.get", "channels.get", "gateway.subscribe"]),
|
|
21
|
+
supports: (c) => ["aggregates.get", "channels.get", "gateway.subscribe"].includes(c),
|
|
22
|
+
isConnected: () => false, getStatus: () => ({ clientId: id, connected: false, state: "disconnected", agentScope: { mode: "all" }, at: 0 }),
|
|
23
|
+
onStatusChange: () => () => { }, getAgentScope: async () => ({ mode: "all" }), getKnownAgentScope: () => ({ mode: "all" }),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
(0, mocha_1.describe)("hooks: sources option + source-dimensioned keys", () => {
|
|
27
|
+
(0, mocha_1.it)("useChannelAggregate forwards sources and uses a source-dimensioned key; unscoped uses '*'", async () => {
|
|
28
|
+
const factory = (d) => d.id === "cloud"
|
|
29
|
+
? aggMember("cloud", { data: { v: 1 }, attachments: [] })
|
|
30
|
+
: aggMember("local:7", { data: { v: 2 }, attachments: [] });
|
|
31
|
+
const mux = new multiplex_client_1.MultiplexClient({ factory, register: [{ id: "cloud", kind: "cloud" }, { id: "local:7", kind: "local" }], enableAll: true });
|
|
32
|
+
const queryClient = new react_query_1.QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
33
|
+
const wrapper = ({ children }) => ((0, jsx_runtime_1.jsx)(react_query_1.QueryClientProvider, { client: queryClient, children: (0, jsx_runtime_1.jsx)(react_2.DooverProvider, { client: mux, children: children }) }));
|
|
34
|
+
const scoped = (0, react_1.renderHook)(() => (0, react_2.useChannelAggregate)({ agentId: "dev7", channelName: "c" }, { sources: ["local:7"] }), { wrapper });
|
|
35
|
+
await (0, react_1.waitFor)(() => (0, chai_1.expect)(scoped.result.current.data).to.deep.equal({ v: 2 }));
|
|
36
|
+
// its cache entry lives under the source-dimensioned key:
|
|
37
|
+
(0, chai_1.expect)(queryClient.getQueryData((0, react_2.channelAggregateQueryKey)("dev7", "c", ["local:7"]))).to.exist;
|
|
38
|
+
// an unscoped hook for the same channel uses the "*" key — distinct entry:
|
|
39
|
+
(0, chai_1.expect)((0, react_2.channelAggregateQueryKey)("dev7", "c")).to.deep.equal(["doover", "agent", "dev7", "channel", "c", "src", "*"]);
|
|
40
|
+
});
|
|
41
|
+
});
|
package/dist/test/react.test.js
CHANGED
|
@@ -95,7 +95,7 @@ function wrapper(client) {
|
|
|
95
95
|
await (0, react_1.waitFor)(() => {
|
|
96
96
|
(0, chai_1.expect)(result.current.isSuccess).to.equal(true);
|
|
97
97
|
});
|
|
98
|
-
(0, chai_1.expect)(result.current.data).to.deep.
|
|
98
|
+
(0, chai_1.expect)(result.current.data?.[0]).to.deep.include({ address: "x", agent_id: "a1" });
|
|
99
99
|
(0, chai_1.expect)(fetchMock.getCall(0).args[0]).to.equal("https://api.example.com/agents/a1/wss_connections");
|
|
100
100
|
});
|
|
101
101
|
(0, mocha_1.it)("useAgentConnections stays disabled when agentId is undefined", () => {
|
|
@@ -206,7 +206,7 @@ function wrapper(client) {
|
|
|
206
206
|
await result.current.mutateAsync({ y: 2 });
|
|
207
207
|
});
|
|
208
208
|
const cached = queryClient.getQueryData((0, react_2.channelAggregateQueryKey)("a1", "ui_cmds"));
|
|
209
|
-
(0, chai_1.expect)(cached).to.deep.
|
|
209
|
+
(0, chai_1.expect)(cached).to.deep.include({ data: { y: 2 }, attachments: [] });
|
|
210
210
|
});
|
|
211
211
|
(0, mocha_1.it)("useChannelMessages paginates and prepends on live messageCreate", async () => {
|
|
212
212
|
const oldId = (0, snowflake_1.generateSnowflakeIdAtTime)(new Date("2025-12-31T23:59:00.000Z"));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const chai_1 = require("chai");
|
|
4
|
+
const node_events_1 = require("node:events");
|
|
5
|
+
const rpc_dispatcher_1 = require("../rpc/rpc-dispatcher");
|
|
6
|
+
const errors_1 = require("../rpc/errors");
|
|
7
|
+
const stats_1 = require("../client/stats");
|
|
8
|
+
function makeFakeGateway() {
|
|
9
|
+
const ee = new node_events_1.EventEmitter();
|
|
10
|
+
let subscribeCalls = 0;
|
|
11
|
+
let unsubscribeCalls = 0;
|
|
12
|
+
const channels = new Map();
|
|
13
|
+
const key = (c) => `${c.agent_id}/${c.name}`;
|
|
14
|
+
const subscribeToChannel = (channel, handlers) => {
|
|
15
|
+
const k = key(channel);
|
|
16
|
+
let set = channels.get(k);
|
|
17
|
+
if (!set) {
|
|
18
|
+
set = new Set();
|
|
19
|
+
channels.set(k, set);
|
|
20
|
+
subscribeCalls += 1;
|
|
21
|
+
}
|
|
22
|
+
set.add(handlers);
|
|
23
|
+
ee.on("messageUpdate", (msg, requestData) => {
|
|
24
|
+
if (msg.channel.agent_id !== channel.agent_id || msg.channel.name !== channel.name)
|
|
25
|
+
return;
|
|
26
|
+
handlers.onMessageUpdate?.(msg, requestData);
|
|
27
|
+
});
|
|
28
|
+
let off = false;
|
|
29
|
+
return () => {
|
|
30
|
+
if (off)
|
|
31
|
+
return;
|
|
32
|
+
off = true;
|
|
33
|
+
set.delete(handlers);
|
|
34
|
+
if (set.size === 0) {
|
|
35
|
+
channels.delete(k);
|
|
36
|
+
unsubscribeCalls += 1;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
subscribeToChannel: subscribeToChannel,
|
|
42
|
+
emitMessageUpdate: (msg, rd) => ee.emit("messageUpdate", msg, rd),
|
|
43
|
+
get subscribeCalls() { return subscribeCalls; },
|
|
44
|
+
get unsubscribeCalls() { return unsubscribeCalls; },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function makeFakeMessagesApi() {
|
|
48
|
+
let nextId = "msg-1";
|
|
49
|
+
const posts = [];
|
|
50
|
+
const postMessage = (async (a, b, c) => {
|
|
51
|
+
const isPositional = typeof a === "string";
|
|
52
|
+
const agentId = isPositional ? a : a.agentId;
|
|
53
|
+
const channelName = isPositional ? b : a.channelName;
|
|
54
|
+
const body = isPositional ? c : b;
|
|
55
|
+
posts.push({ agentId, channelName, body });
|
|
56
|
+
const message = {
|
|
57
|
+
id: nextId,
|
|
58
|
+
data: body,
|
|
59
|
+
attachments: [],
|
|
60
|
+
author_id: "auth",
|
|
61
|
+
channel: { agent_id: agentId, name: channelName },
|
|
62
|
+
timestamp: 1,
|
|
63
|
+
};
|
|
64
|
+
return message;
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
postMessage,
|
|
68
|
+
posts,
|
|
69
|
+
setNextId: (id) => { nextId = id; },
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function rpcMessage(id, channel, status, request, response) {
|
|
73
|
+
return {
|
|
74
|
+
id,
|
|
75
|
+
data: { method: "do", request, status, response: response },
|
|
76
|
+
attachments: [],
|
|
77
|
+
author_id: "auth",
|
|
78
|
+
channel,
|
|
79
|
+
timestamp: 1,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
describe("RpcDispatcher", () => {
|
|
83
|
+
it("send resolves on terminal success status", async () => {
|
|
84
|
+
const gw = makeFakeGateway();
|
|
85
|
+
const messages = makeFakeMessagesApi();
|
|
86
|
+
messages.setNextId("rpc-1");
|
|
87
|
+
const dispatcher = new rpc_dispatcher_1.RpcDispatcher(gw, messages);
|
|
88
|
+
const channel = { agent_id: "a1", name: "c1" };
|
|
89
|
+
const promise = dispatcher.send({ agentId: "a1", channelName: "c1" }, { method: "do", request: { x: 1 } });
|
|
90
|
+
// wait one microtask so postMessage settles and pending is registered
|
|
91
|
+
await new Promise((r) => setImmediate(r));
|
|
92
|
+
gw.emitMessageUpdate(rpcMessage("rpc-1", channel, { code: "success" }, { x: 1 }, { ok: true }));
|
|
93
|
+
const result = await promise;
|
|
94
|
+
(0, chai_1.expect)(result).to.deep.equal({ ok: true });
|
|
95
|
+
(0, chai_1.expect)(gw.subscribeCalls).to.equal(1);
|
|
96
|
+
(0, chai_1.expect)(gw.unsubscribeCalls).to.equal(1);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe("RpcDispatcher additional outcomes", () => {
|
|
100
|
+
it("rejects with DooverRpcError on terminal error status", async () => {
|
|
101
|
+
const gw = makeFakeGateway();
|
|
102
|
+
const messages = makeFakeMessagesApi();
|
|
103
|
+
messages.setNextId("rpc-2");
|
|
104
|
+
const dispatcher = new rpc_dispatcher_1.RpcDispatcher(gw, messages);
|
|
105
|
+
const channel = { agent_id: "a1", name: "c1" };
|
|
106
|
+
const promise = dispatcher.send({ agentId: "a1", channelName: "c1" }, { method: "do", request: {} });
|
|
107
|
+
await new Promise((r) => setImmediate(r));
|
|
108
|
+
gw.emitMessageUpdate(rpcMessage("rpc-2", channel, { code: "error", message: "nope" }, {}));
|
|
109
|
+
let caught;
|
|
110
|
+
try {
|
|
111
|
+
await promise;
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
caught = e;
|
|
115
|
+
}
|
|
116
|
+
(0, chai_1.expect)(caught).to.be.instanceOf(errors_1.DooverRpcError);
|
|
117
|
+
(0, chai_1.expect)(caught.message).to.equal("nope");
|
|
118
|
+
});
|
|
119
|
+
it("rejects on timeoutMs with no terminal status", async () => {
|
|
120
|
+
const gw = makeFakeGateway();
|
|
121
|
+
const messages = makeFakeMessagesApi();
|
|
122
|
+
const dispatcher = new rpc_dispatcher_1.RpcDispatcher(gw, messages);
|
|
123
|
+
const promise = dispatcher.send({ agentId: "a1", channelName: "c1" }, { method: "do", request: {} }, { timeoutMs: 5 });
|
|
124
|
+
let caught;
|
|
125
|
+
try {
|
|
126
|
+
await promise;
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
caught = e;
|
|
130
|
+
}
|
|
131
|
+
(0, chai_1.expect)(caught?.message).to.equal("RPC timed out");
|
|
132
|
+
});
|
|
133
|
+
it("rejects on AbortSignal.abort()", async () => {
|
|
134
|
+
const gw = makeFakeGateway();
|
|
135
|
+
const messages = makeFakeMessagesApi();
|
|
136
|
+
const dispatcher = new rpc_dispatcher_1.RpcDispatcher(gw, messages);
|
|
137
|
+
const ac = new AbortController();
|
|
138
|
+
const promise = dispatcher.send({ agentId: "a1", channelName: "c1" }, { method: "do", request: {} }, { signal: ac.signal });
|
|
139
|
+
await new Promise((r) => setImmediate(r));
|
|
140
|
+
ac.abort(new Error("user cancelled"));
|
|
141
|
+
let caught;
|
|
142
|
+
try {
|
|
143
|
+
await promise;
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
caught = e;
|
|
147
|
+
}
|
|
148
|
+
(0, chai_1.expect)(caught?.message).to.equal("user cancelled");
|
|
149
|
+
});
|
|
150
|
+
it("two concurrent RPCs on the same channel share one gateway subscription", async () => {
|
|
151
|
+
const gw = makeFakeGateway();
|
|
152
|
+
const messages = makeFakeMessagesApi();
|
|
153
|
+
const dispatcher = new rpc_dispatcher_1.RpcDispatcher(gw, messages);
|
|
154
|
+
messages.setNextId("rpc-A");
|
|
155
|
+
const pA = dispatcher.send({ agentId: "a1", channelName: "c1" }, { method: "do", request: { n: 1 } });
|
|
156
|
+
await new Promise((r) => setImmediate(r));
|
|
157
|
+
messages.setNextId("rpc-B");
|
|
158
|
+
const pB = dispatcher.send({ agentId: "a1", channelName: "c1" }, { method: "do", request: { n: 2 } });
|
|
159
|
+
await new Promise((r) => setImmediate(r));
|
|
160
|
+
(0, chai_1.expect)(gw.subscribeCalls).to.equal(1);
|
|
161
|
+
const channel = { agent_id: "a1", name: "c1" };
|
|
162
|
+
gw.emitMessageUpdate(rpcMessage("rpc-A", channel, { code: "success" }, { n: 1 }, { a: true }));
|
|
163
|
+
gw.emitMessageUpdate(rpcMessage("rpc-B", channel, { code: "success" }, { n: 2 }, { b: true }));
|
|
164
|
+
(0, chai_1.expect)(await pA).to.deep.equal({ a: true });
|
|
165
|
+
(0, chai_1.expect)(await pB).to.deep.equal({ b: true });
|
|
166
|
+
(0, chai_1.expect)(gw.unsubscribeCalls).to.equal(1);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
describe("RpcDispatcher stats integration", () => {
|
|
170
|
+
it("records start and end via collector when enabled", async () => {
|
|
171
|
+
const gw = makeFakeGateway();
|
|
172
|
+
const messages = makeFakeMessagesApi();
|
|
173
|
+
const dispatcher = new rpc_dispatcher_1.RpcDispatcher(gw, messages);
|
|
174
|
+
const stats = new stats_1.DooverStatsCollector();
|
|
175
|
+
stats.setEnabled(true);
|
|
176
|
+
dispatcher.setStats(stats);
|
|
177
|
+
messages.setNextId("rpc-stats");
|
|
178
|
+
const channel = { agent_id: "a1", name: "c1" };
|
|
179
|
+
const p = dispatcher.send({ agentId: "a1", channelName: "c1" }, { method: "do", request: {} });
|
|
180
|
+
await new Promise((r) => setImmediate(r));
|
|
181
|
+
(0, chai_1.expect)(stats.snapshot().rpc.pendingRpcs).to.equal(1);
|
|
182
|
+
gw.emitMessageUpdate(rpcMessage("rpc-stats", channel, { code: "success" }, {}, { ok: true }));
|
|
183
|
+
await p;
|
|
184
|
+
const snap = stats.snapshot();
|
|
185
|
+
(0, chai_1.expect)(snap.rpc.totalRpcs).to.equal(1);
|
|
186
|
+
(0, chai_1.expect)(snap.rpc.completedRpcs).to.equal(1);
|
|
187
|
+
(0, chai_1.expect)(snap.rpc.pendingRpcs).to.equal(0);
|
|
188
|
+
(0, chai_1.expect)(snap.rpc.peakPendingRpcs).to.equal(1);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const chai_1 = require("chai");
|
|
4
|
+
const stats_1 = require("../client/stats");
|
|
5
|
+
describe("DooverStatsCollector RPC counters", () => {
|
|
6
|
+
it("returns zeroed rpc snapshot when disabled", () => {
|
|
7
|
+
const c = new stats_1.DooverStatsCollector();
|
|
8
|
+
const snap = c.snapshot();
|
|
9
|
+
(0, chai_1.expect)(snap.rpc).to.deep.equal({
|
|
10
|
+
enabled: false,
|
|
11
|
+
totalRpcs: 0,
|
|
12
|
+
pendingRpcs: 0,
|
|
13
|
+
completedRpcs: 0,
|
|
14
|
+
failedRpcs: 0,
|
|
15
|
+
timedOutRpcs: 0,
|
|
16
|
+
abortedRpcs: 0,
|
|
17
|
+
averageLatencyMs: null,
|
|
18
|
+
lastLatencyMs: null,
|
|
19
|
+
peakPendingRpcs: 0,
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
it("tracks success/error/timeout/abort separately", () => {
|
|
23
|
+
const c = new stats_1.DooverStatsCollector();
|
|
24
|
+
c.setEnabled(true);
|
|
25
|
+
const a = c.recordRpcStart();
|
|
26
|
+
const b = c.recordRpcStart();
|
|
27
|
+
const cc = c.recordRpcStart();
|
|
28
|
+
const d = c.recordRpcStart();
|
|
29
|
+
c.recordRpcEnd(a, "success");
|
|
30
|
+
c.recordRpcEnd(b, "error");
|
|
31
|
+
c.recordRpcEnd(cc, "timeout");
|
|
32
|
+
c.recordRpcEnd(d, "abort");
|
|
33
|
+
const snap = c.snapshot();
|
|
34
|
+
(0, chai_1.expect)(snap.rpc.totalRpcs).to.equal(4);
|
|
35
|
+
(0, chai_1.expect)(snap.rpc.pendingRpcs).to.equal(0);
|
|
36
|
+
(0, chai_1.expect)(snap.rpc.completedRpcs).to.equal(1);
|
|
37
|
+
(0, chai_1.expect)(snap.rpc.failedRpcs).to.equal(1);
|
|
38
|
+
(0, chai_1.expect)(snap.rpc.timedOutRpcs).to.equal(1);
|
|
39
|
+
(0, chai_1.expect)(snap.rpc.abortedRpcs).to.equal(1);
|
|
40
|
+
(0, chai_1.expect)(snap.rpc.averageLatencyMs).to.be.a("number");
|
|
41
|
+
(0, chai_1.expect)(snap.rpc.lastLatencyMs).to.be.a("number");
|
|
42
|
+
});
|
|
43
|
+
it("tracks peakPendingRpcs as a high-water mark", () => {
|
|
44
|
+
const c = new stats_1.DooverStatsCollector();
|
|
45
|
+
c.setEnabled(true);
|
|
46
|
+
const a = c.recordRpcStart();
|
|
47
|
+
const b = c.recordRpcStart();
|
|
48
|
+
const cc = c.recordRpcStart();
|
|
49
|
+
c.recordRpcEnd(a, "success");
|
|
50
|
+
c.recordRpcEnd(b, "success");
|
|
51
|
+
c.recordRpcEnd(cc, "success");
|
|
52
|
+
const d = c.recordRpcStart();
|
|
53
|
+
c.recordRpcEnd(d, "success");
|
|
54
|
+
(0, chai_1.expect)(c.snapshot().rpc.peakPendingRpcs).to.equal(3);
|
|
55
|
+
});
|
|
56
|
+
it("disabled collector short-circuits start (returns null)", () => {
|
|
57
|
+
const c = new stats_1.DooverStatsCollector();
|
|
58
|
+
(0, chai_1.expect)(c.recordRpcStart()).to.equal(null);
|
|
59
|
+
});
|
|
60
|
+
it("reset() clears RPC counters", () => {
|
|
61
|
+
const c = new stats_1.DooverStatsCollector();
|
|
62
|
+
c.setEnabled(true);
|
|
63
|
+
const a = c.recordRpcStart();
|
|
64
|
+
c.recordRpcEnd(a, "success");
|
|
65
|
+
c.reset();
|
|
66
|
+
(0, chai_1.expect)(c.snapshot().rpc.totalRpcs).to.equal(0);
|
|
67
|
+
(0, chai_1.expect)(c.snapshot().rpc.peakPendingRpcs).to.equal(0);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const chai_1 = require("chai");
|
|
4
|
+
const mocha_1 = require("mocha");
|
|
5
|
+
const status_tracker_1 = require("../client/status-tracker");
|
|
6
|
+
// Minimal GatewayClientLike stub: just on/off + isConnected + getSession.
|
|
7
|
+
function makeGatewayStub() {
|
|
8
|
+
const listeners = new Map();
|
|
9
|
+
let connected = false;
|
|
10
|
+
let session = null;
|
|
11
|
+
return {
|
|
12
|
+
gw: {
|
|
13
|
+
on(e, h) {
|
|
14
|
+
(listeners.get(e) ?? listeners.set(e, new Set()).get(e)).add(h);
|
|
15
|
+
},
|
|
16
|
+
off(e, h) {
|
|
17
|
+
listeners.get(e)?.delete(h);
|
|
18
|
+
},
|
|
19
|
+
isConnected: () => connected,
|
|
20
|
+
getSession: () => session,
|
|
21
|
+
},
|
|
22
|
+
emit(e, ...args) {
|
|
23
|
+
listeners.get(e)?.forEach((h) => h(...args));
|
|
24
|
+
},
|
|
25
|
+
setConnected(v) { connected = v; },
|
|
26
|
+
setSession(s) { session = s; },
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
(0, mocha_1.describe)("ClientStatusTracker", () => {
|
|
30
|
+
(0, mocha_1.it)("derives state from gateway lifecycle and notifies listeners", () => {
|
|
31
|
+
const { gw, emit, setConnected, setSession } = makeGatewayStub();
|
|
32
|
+
const tracker = new status_tracker_1.ClientStatusTracker("cloud", gw, () => "all");
|
|
33
|
+
const seen = [];
|
|
34
|
+
const off = tracker.onChange((s) => seen.push(s.state));
|
|
35
|
+
(0, chai_1.expect)(tracker.getStatus().state).to.equal("disconnected");
|
|
36
|
+
(0, chai_1.expect)(tracker.getStatus().agentScope).to.equal("all");
|
|
37
|
+
emit("open");
|
|
38
|
+
(0, chai_1.expect)(tracker.getStatus().lastEvent).to.equal("open");
|
|
39
|
+
setConnected(true);
|
|
40
|
+
setSession({ session_id: "s1" });
|
|
41
|
+
emit("ready", { session_id: "s1" });
|
|
42
|
+
const ready = tracker.getStatus();
|
|
43
|
+
(0, chai_1.expect)(ready.connected).to.equal(true);
|
|
44
|
+
(0, chai_1.expect)(ready.state).to.equal("connected");
|
|
45
|
+
(0, chai_1.expect)(ready.session).to.deep.equal({ id: "s1" });
|
|
46
|
+
setConnected(false);
|
|
47
|
+
emit("close", { code: 1006 });
|
|
48
|
+
(0, chai_1.expect)(tracker.getStatus().connected).to.equal(false);
|
|
49
|
+
(0, chai_1.expect)(tracker.getStatus().state).to.equal("connecting");
|
|
50
|
+
emit("wssError", { message: "boom" });
|
|
51
|
+
(0, chai_1.expect)(tracker.getStatus().state).to.equal("error");
|
|
52
|
+
(0, chai_1.expect)(tracker.getStatus().lastError).to.equal("boom");
|
|
53
|
+
(0, chai_1.expect)(seen.length).to.be.greaterThan(3);
|
|
54
|
+
off();
|
|
55
|
+
emit("open");
|
|
56
|
+
const len = seen.length;
|
|
57
|
+
emit("ready", { session_id: "s2" });
|
|
58
|
+
(0, chai_1.expect)(seen.length).to.equal(len); // unsubscribed
|
|
59
|
+
});
|
|
60
|
+
(0, mocha_1.it)("recovers from error state after reconnect", () => {
|
|
61
|
+
const { gw, emit, setConnected, setSession } = makeGatewayStub();
|
|
62
|
+
const tracker = new status_tracker_1.ClientStatusTracker("cloud", gw, () => "all");
|
|
63
|
+
// Drive to error state first
|
|
64
|
+
emit("open");
|
|
65
|
+
setConnected(true);
|
|
66
|
+
setSession({ session_id: "s1" });
|
|
67
|
+
emit("ready", { session_id: "s1" });
|
|
68
|
+
setConnected(false);
|
|
69
|
+
emit("close", { code: 1006 });
|
|
70
|
+
emit("wssError", { message: "connection lost" });
|
|
71
|
+
(0, chai_1.expect)(tracker.getStatus().state).to.equal("error");
|
|
72
|
+
(0, chai_1.expect)(tracker.getStatus().lastError).to.equal("connection lost");
|
|
73
|
+
// Reconnect: open clears error, transitions to connecting
|
|
74
|
+
emit("open");
|
|
75
|
+
(0, chai_1.expect)(tracker.getStatus().state).to.equal("connecting");
|
|
76
|
+
(0, chai_1.expect)(tracker.getStatus().lastError).to.be.undefined;
|
|
77
|
+
// Session established: transitions to connected with new session
|
|
78
|
+
setConnected(true);
|
|
79
|
+
setSession({ session_id: "s2" });
|
|
80
|
+
emit("ready", { session_id: "s2" });
|
|
81
|
+
const status = tracker.getStatus();
|
|
82
|
+
(0, chai_1.expect)(status.state).to.equal("connected");
|
|
83
|
+
(0, chai_1.expect)(status.session).to.deep.equal({ id: "s2" });
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const chai_1 = require("chai");
|
|
4
|
+
const users_api_1 = require("../apis/users-api");
|
|
5
|
+
const api_overloads_test_1 = require("./api-overloads.test");
|
|
6
|
+
describe("UsersApi", () => {
|
|
7
|
+
it("getMe calls /users/me on controlApiUrl", async () => {
|
|
8
|
+
const rest = (0, api_overloads_test_1.makeRestStub)();
|
|
9
|
+
const api = new users_api_1.UsersApi(rest, "https://control.example.com");
|
|
10
|
+
await api.getMe();
|
|
11
|
+
(0, chai_1.expect)(rest.calls).to.have.lengthOf(1);
|
|
12
|
+
(0, chai_1.expect)(rest.calls[0].method).to.equal("get");
|
|
13
|
+
(0, chai_1.expect)(rest.calls[0].args).to.deep.equal([
|
|
14
|
+
"/users/me",
|
|
15
|
+
undefined,
|
|
16
|
+
"https://control.example.com",
|
|
17
|
+
]);
|
|
18
|
+
});
|
|
19
|
+
});
|