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,82 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
// src/test/multiplex-noncore.test.ts
|
|
40
|
+
const chai_1 = require("chai");
|
|
41
|
+
const chai_as_promised_1 = __importDefault(require("chai-as-promised"));
|
|
42
|
+
const chai = __importStar(require("chai"));
|
|
43
|
+
const mocha_1 = require("mocha");
|
|
44
|
+
const multiplex_client_1 = require("../client/multiplex-client");
|
|
45
|
+
const errors_1 = require("../client/errors");
|
|
46
|
+
chai.use(chai_as_promised_1.default);
|
|
47
|
+
const { expect: xp } = chai;
|
|
48
|
+
function member(id, caps, impl) {
|
|
49
|
+
const noop = new Proxy({}, { get: () => () => Promise.resolve(undefined) });
|
|
50
|
+
const make = (name) => (impl[name] ?? noop);
|
|
51
|
+
return {
|
|
52
|
+
agents: noop, channels: noop, messages: noop, aggregates: noop,
|
|
53
|
+
alarms: make("alarms"), connections: make("connections"), notifications: make("notifications"),
|
|
54
|
+
permissions: make("permissions"), processors: make("processors"), turn: make("turn"), users: make("users"), rpc: noop,
|
|
55
|
+
gateway: { connect: async () => { }, disconnect: () => { }, reconnect: async () => { }, on: () => { }, off: () => { }, subscribe: () => { }, unsubscribe: () => { }, subscribeToChannel: () => () => { }, syncChannel: () => { }, sendOneShotMessage: () => { }, getSession: () => null, isConnected: () => false, getSubscriptionCount: () => 0, getSubscriptions: () => [], setStats: () => { } },
|
|
56
|
+
getCapabilities: () => new Set(caps), supports: (c) => caps.includes(c),
|
|
57
|
+
isConnected: () => false, getStatus: () => ({ clientId: id, connected: false, state: "disconnected", agentScope: { mode: "all" }, at: Date.now() }),
|
|
58
|
+
onStatusChange: () => () => { },
|
|
59
|
+
getAgentScope: async () => ({ mode: "all" }), getKnownAgentScope: () => ({ mode: "all" }),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
(0, mocha_1.describe)("MultiplexClient non-core methods", () => {
|
|
63
|
+
function mk(members) {
|
|
64
|
+
const factory = (d) => members[d.id];
|
|
65
|
+
return new multiplex_client_1.MultiplexClient({ factory, register: Object.keys(members).map((id) => ({ id, kind: "cloud" })), enableAll: true });
|
|
66
|
+
}
|
|
67
|
+
(0, mocha_1.it)("users.getMe returns the first member's result; throws if no member supports it", async () => {
|
|
68
|
+
const mux = mk({ cloud: member("cloud", ["users.me"], { users: { getMe: async () => ({ id: "u1" }) } }) });
|
|
69
|
+
(0, chai_1.expect)((await mux.users.getMe()).id).to.equal("u1");
|
|
70
|
+
const mux2 = mk({ cloud: member("cloud", [], {}) });
|
|
71
|
+
await xp(mux2.users.getMe()).to.be.rejectedWith(errors_1.UnsupportedCapabilityError);
|
|
72
|
+
});
|
|
73
|
+
(0, mocha_1.it)("alarms.listAlarms fans out; alarms.deleteAlarm routes to one", async () => {
|
|
74
|
+
const sink = [];
|
|
75
|
+
const mux = mk({ cloud: member("cloud", ["alarms.read", "alarms.write"], {
|
|
76
|
+
alarms: { listAlarms: async () => [{ id: "al1", name: "x" }], deleteAlarm: async (a) => { sink.push(`del:${a}`); } },
|
|
77
|
+
}) });
|
|
78
|
+
(0, chai_1.expect)((await mux.alarms.listAlarms("dev7", "c"))[0].id).to.equal("al1");
|
|
79
|
+
await mux.alarms.deleteAlarm("dev7", "c", "al1");
|
|
80
|
+
(0, chai_1.expect)(sink).to.deep.equal(["del:dev7"]);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
// src/test/multiplex-passthrough.test.ts — reuses the dataMember helper pattern from multiplex-reads.test.ts
|
|
4
|
+
const chai_1 = require("chai");
|
|
5
|
+
const mocha_1 = require("mocha");
|
|
6
|
+
const multiplex_client_1 = require("../client/multiplex-client");
|
|
7
|
+
function dataMember(id, channels) {
|
|
8
|
+
const src = { client: { id, kind: id.startsWith("local") ? "local" : "cloud" }, retrievedAt: Date.now(), via: { transport: "rest", method: "channels.listChannels", request: {}, startedAt: 0, durationMs: 1 } };
|
|
9
|
+
const noop = new Proxy({}, { get: () => () => Promise.resolve(undefined) });
|
|
10
|
+
return {
|
|
11
|
+
agents: noop, channels: { listChannels: async () => channels.map((c) => ({ ...c, is_private: false, owner_id: "o", __source: src })) },
|
|
12
|
+
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: (c) => c === "channels.list",
|
|
15
|
+
isConnected: () => false, getStatus: () => ({ clientId: id, connected: false, state: "disconnected", agentScope: { mode: "all" }, at: Date.now() }),
|
|
16
|
+
onStatusChange: () => () => { }, getAgentScope: async () => ({ mode: "all" }), getKnownAgentScope: () => ({ mode: "all" }),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
(0, mocha_1.describe)("MultiplexClient pass-through & re-enable", () => {
|
|
20
|
+
(0, mocha_1.it)("merged items keep their member __source (no re-stamp); disabled member drops out; re-enable rejoins (no rebuild)", async () => {
|
|
21
|
+
let builds = 0;
|
|
22
|
+
const factory = (d) => { builds += 1; return dataMember(d.id, d.id === "cloud" ? [{ name: "cloudch" }] : [{ name: "localch" }]); };
|
|
23
|
+
const mux = new multiplex_client_1.MultiplexClient({ factory, register: [{ id: "cloud", kind: "cloud" }, { id: "local:7", kind: "local" }], enableAll: true });
|
|
24
|
+
let list = await mux.channels.listChannels("dev7");
|
|
25
|
+
(0, chai_1.expect)(list.map((c) => c.name).sort()).to.deep.equal(["cloudch", "localch"]);
|
|
26
|
+
(0, chai_1.expect)(list.find((c) => c.name === "localch").__source?.client.id).to.equal("local:7");
|
|
27
|
+
mux.disableSource("local:7");
|
|
28
|
+
list = await mux.channels.listChannels("dev7");
|
|
29
|
+
(0, chai_1.expect)(list.map((c) => c.name)).to.deep.equal(["cloudch"]); // local dropped out
|
|
30
|
+
mux.enableSource("local:7");
|
|
31
|
+
list = await mux.channels.listChannels("dev7");
|
|
32
|
+
(0, chai_1.expect)(list.map((c) => c.name).sort()).to.deep.equal(["cloudch", "localch"]); // rejoined
|
|
33
|
+
(0, chai_1.expect)(builds).to.equal(2); // never rebuilt
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
// src/test/multiplex-reads.test.ts
|
|
4
|
+
const chai_1 = require("chai");
|
|
5
|
+
const mocha_1 = require("mocha");
|
|
6
|
+
const multiplex_client_1 = require("../client/multiplex-client");
|
|
7
|
+
// Helper that builds a member whose subclients return canned values + record __source.
|
|
8
|
+
function dataMember(id, opts) {
|
|
9
|
+
const scope = opts.scope ?? { mode: "all" };
|
|
10
|
+
const caps = new Set(opts.caps ?? ["agents.list", "channels.list", "channels.get", "aggregates.get", "messages.list"]);
|
|
11
|
+
const src = (method) => ({ client: { id, kind: id.startsWith("local") ? "local" : "cloud" }, retrievedAt: Date.now(), via: { transport: "rest", method, request: {}, startedAt: 0, durationMs: 1 } });
|
|
12
|
+
const noop = new Proxy({}, { get: () => () => Promise.resolve(undefined) });
|
|
13
|
+
return {
|
|
14
|
+
agents: { listAgents: async () => ({ agents: (opts.agents ?? []).map((a) => ({ ...a, __source: src("agents.listAgents") })) }) },
|
|
15
|
+
channels: {
|
|
16
|
+
listChannels: async (agentId) => (opts.channels?.[agentId] ?? []).map((c) => ({ ...c, __source: src("channels.listChannels") })),
|
|
17
|
+
getChannel: async (agentId, name) => { const c = opts.channel?.[`${agentId}/${name}`]; if (!c) {
|
|
18
|
+
const e = new Error("404");
|
|
19
|
+
e.status = 404;
|
|
20
|
+
throw e;
|
|
21
|
+
} return { ...c, __source: src("channels.getChannel") }; },
|
|
22
|
+
},
|
|
23
|
+
aggregates: {
|
|
24
|
+
getAggregate: async (agentId, name) => { const a = opts.aggregate?.[`${agentId}/${name}`]; if (!a) {
|
|
25
|
+
const e = new Error("404");
|
|
26
|
+
e.status = 404;
|
|
27
|
+
throw e;
|
|
28
|
+
} return { ...a, __source: src("aggregates.getAggregate") }; },
|
|
29
|
+
},
|
|
30
|
+
messages: {
|
|
31
|
+
listMessages: async (agentId, name) => (opts.messages?.[`${agentId}/${name}`] ?? []).map((m) => ({ ...m, data: {}, attachments: [], author_id: "a", channel: { agent_id: agentId, name }, __source: src("messages.listMessages") })),
|
|
32
|
+
},
|
|
33
|
+
alarms: noop, connections: noop, notifications: noop, permissions: noop, processors: noop, turn: noop, users: noop, rpc: noop,
|
|
34
|
+
gateway: { connect: async () => { }, disconnect: () => { }, reconnect: async () => { }, on: () => { }, off: () => { }, subscribe: () => { }, unsubscribe: () => { }, subscribeToChannel: () => () => { }, syncChannel: () => { }, sendOneShotMessage: () => { }, getSession: () => null, isConnected: () => false, getSubscriptionCount: () => 0, getSubscriptions: () => [], setStats: () => { } },
|
|
35
|
+
getCapabilities: () => caps, supports: (c) => caps.has(c),
|
|
36
|
+
isConnected: () => false, getStatus: () => ({ clientId: id, connected: false, state: "disconnected", agentScope: scope, at: Date.now() }),
|
|
37
|
+
onStatusChange: () => () => { },
|
|
38
|
+
getAgentScope: async () => scope, getKnownAgentScope: () => scope,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
(0, mocha_1.describe)("MultiplexClient reads", () => {
|
|
42
|
+
function mk(members) {
|
|
43
|
+
const factory = (d) => members[d.id];
|
|
44
|
+
return new multiplex_client_1.MultiplexClient({ factory, register: Object.keys(members).map((id) => ({ id, kind: id.startsWith("local") ? "local" : "cloud" })), enableAll: true });
|
|
45
|
+
}
|
|
46
|
+
(0, mocha_1.it)("listChannels merges + dedupes by name across owning members; items keep their member __source", async () => {
|
|
47
|
+
const mux = mk({
|
|
48
|
+
cloud: dataMember("cloud", { scope: { mode: "all" }, channels: { dev7: [{ name: "a", is_private: false, owner_id: "o" }, { name: "shared", is_private: false, owner_id: "o" }] } }),
|
|
49
|
+
"local:1": dataMember("local:1", { scope: { mode: "list", agentIds: ["dev7"] }, channels: { dev7: [{ name: "b", is_private: false, owner_id: "o" }, { name: "shared", is_private: false, owner_id: "o" }] } }),
|
|
50
|
+
});
|
|
51
|
+
const list = await mux.channels.listChannels("dev7");
|
|
52
|
+
(0, chai_1.expect)(list.map((c) => c.name).sort()).to.deep.equal(["a", "b", "shared"]);
|
|
53
|
+
const shared = list.find((c) => c.name === "shared");
|
|
54
|
+
(0, chai_1.expect)(shared.__source?.client.id).to.equal("cloud"); // first member wins
|
|
55
|
+
// sources-scoped → only local:1
|
|
56
|
+
const onlyLocal = await mux.channels.listChannels("dev7", { sources: ["local:1"] });
|
|
57
|
+
(0, chai_1.expect)(onlyLocal.map((c) => c.name).sort()).to.deep.equal(["b", "shared"]);
|
|
58
|
+
});
|
|
59
|
+
(0, mocha_1.it)("getChannel: only the local member owns dev9 → returns its value", async () => {
|
|
60
|
+
const mux = mk({
|
|
61
|
+
cloud: dataMember("cloud", { scope: { mode: "all" }, channel: { "dev7/c": { name: "c", v: 1 } } }),
|
|
62
|
+
"local:9": dataMember("local:9", { scope: { mode: "list", agentIds: ["dev9"] }, channel: { "dev9/c": { name: "c", v: 99 } } }),
|
|
63
|
+
});
|
|
64
|
+
// cloud is 'all' so it's also asked for dev9, but its getChannel 404s → ignored.
|
|
65
|
+
const ch = await mux.channels.getChannel("dev9", "c");
|
|
66
|
+
(0, chai_1.expect)(ch.v).to.equal(99);
|
|
67
|
+
(0, chai_1.expect)(ch.__source?.client.id).to.equal("local:9");
|
|
68
|
+
});
|
|
69
|
+
(0, mocha_1.it)("listMessages merges by id, dedupes, re-applies limit", async () => {
|
|
70
|
+
const mux = mk({
|
|
71
|
+
cloud: dataMember("cloud", { scope: { mode: "all" }, messages: { "dev7/c": [{ id: "5" }, { id: "3" }, { id: "1" }] } }),
|
|
72
|
+
"local:1": dataMember("local:1", { scope: { mode: "list", agentIds: ["dev7"] }, caps: ["messages.list", "channels.get"], messages: { "dev7/c": [{ id: "4" }, { id: "3" }, { id: "2" }] } }),
|
|
73
|
+
});
|
|
74
|
+
const msgs = await mux.messages.listMessages("dev7", "c", { limit: 4, order: "desc" });
|
|
75
|
+
(0, chai_1.expect)(msgs.map((m) => m.id)).to.deep.equal(["5", "4", "3", "2"]);
|
|
76
|
+
const m3 = msgs.find((m) => m.id === "3");
|
|
77
|
+
(0, chai_1.expect)(m3.__source?.client.id).to.equal("cloud");
|
|
78
|
+
});
|
|
79
|
+
(0, mocha_1.it)("listAgents concatenates members' agents and dedupes by id", async () => {
|
|
80
|
+
const mux = mk({
|
|
81
|
+
cloud: dataMember("cloud", { agents: [{ id: "dev7" }, { id: "dev8" }] }),
|
|
82
|
+
"local:7": dataMember("local:7", { agents: [{ id: "dev7" }] }),
|
|
83
|
+
});
|
|
84
|
+
const res = await mux.agents.listAgents();
|
|
85
|
+
(0, chai_1.expect)((res.agents ?? []).map((a) => a.id).sort()).to.deep.equal(["dev7", "dev8"]);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const chai_1 = require("chai");
|
|
7
|
+
const mocha_1 = require("mocha");
|
|
8
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
9
|
+
const multiplex_client_1 = require("../client/multiplex-client");
|
|
10
|
+
// A no-op DataClient stub with a controllable connected flag + gateway events.
|
|
11
|
+
function stubMember(id) {
|
|
12
|
+
let connected = false;
|
|
13
|
+
const gwListeners = new Set();
|
|
14
|
+
const noop = new Proxy({}, { get: () => () => Promise.resolve(undefined) });
|
|
15
|
+
const statusListeners = new Set();
|
|
16
|
+
return {
|
|
17
|
+
agents: noop, channels: noop, messages: noop, aggregates: noop, alarms: noop,
|
|
18
|
+
connections: noop, notifications: noop, permissions: noop, processors: noop,
|
|
19
|
+
turn: noop, users: noop, rpc: noop,
|
|
20
|
+
gateway: { connect: async () => { }, disconnect: () => { connected = false; }, reconnect: async () => { },
|
|
21
|
+
on: (_e, h) => gwListeners.add(h), off: (_e, h) => gwListeners.delete(h),
|
|
22
|
+
subscribe: () => { }, unsubscribe: () => { }, subscribeToChannel: () => () => { }, syncChannel: () => { },
|
|
23
|
+
sendOneShotMessage: () => { }, getSession: () => null, isConnected: () => connected,
|
|
24
|
+
getSubscriptionCount: () => 0, getSubscriptions: () => [], setStats: () => { } },
|
|
25
|
+
getCapabilities: () => new Set(["channels.list", "channels.get"]),
|
|
26
|
+
supports: (c) => c === "channels.list" || c === "channels.get",
|
|
27
|
+
isConnected: () => connected,
|
|
28
|
+
getStatus: () => ({ clientId: id, connected, state: connected ? "connected" : "disconnected", agentScope: "all", at: Date.now() }),
|
|
29
|
+
onStatusChange: (l) => { statusListeners.add(l); return () => statusListeners.delete(l); },
|
|
30
|
+
getAgentScope: async () => ({ mode: "all" }),
|
|
31
|
+
getKnownAgentScope: () => ({ mode: "all" }),
|
|
32
|
+
_setConnected(v) { connected = v; statusListeners.forEach((l) => l(undefined)); },
|
|
33
|
+
_emit() { gwListeners.forEach((h) => h()); },
|
|
34
|
+
gatewayListeners: gwListeners,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
(0, mocha_1.describe)("MultiplexClient registry & activation", () => {
|
|
38
|
+
(0, mocha_1.it)("builds a member via factory exactly once and reuses it on re-enable", () => {
|
|
39
|
+
const built = {};
|
|
40
|
+
const factory = sinon_1.default.spy((d) => { built[d.id] = (built[d.id] ?? 0) + 1; return stubMember(d.id); });
|
|
41
|
+
const mux = new multiplex_client_1.MultiplexClient({ factory, register: [{ id: "cloud", kind: "cloud" }], enable: ["cloud"] });
|
|
42
|
+
(0, chai_1.expect)(built.cloud).to.equal(1);
|
|
43
|
+
mux.disableSource("cloud");
|
|
44
|
+
mux.enableSource("cloud");
|
|
45
|
+
(0, chai_1.expect)(built.cloud).to.equal(1); // not rebuilt
|
|
46
|
+
(0, chai_1.expect)(mux.getActiveSources().map((s) => s.descriptor.id)).to.deep.equal(["cloud"]);
|
|
47
|
+
});
|
|
48
|
+
(0, mocha_1.it)("registerSource is idempotent on id (updates metadata only)", () => {
|
|
49
|
+
const factory = sinon_1.default.spy((d) => stubMember(d.id));
|
|
50
|
+
const mux = new multiplex_client_1.MultiplexClient({ factory });
|
|
51
|
+
mux.registerSource({ id: "local:1", kind: "local", label: "A" });
|
|
52
|
+
mux.registerSource({ id: "local:1", kind: "local", label: "B" });
|
|
53
|
+
(0, chai_1.expect)(mux.getRegisteredSources()).to.have.length(1);
|
|
54
|
+
(0, chai_1.expect)(mux.getRegisteredSources()[0].descriptor.label).to.equal("B");
|
|
55
|
+
(0, chai_1.expect)(factory.called).to.equal(false); // registering doesn't build
|
|
56
|
+
});
|
|
57
|
+
(0, mocha_1.it)("setActiveSources enables/disables to match exactly and is a no-op when unchanged", () => {
|
|
58
|
+
const factory = sinon_1.default.spy((d) => stubMember(d.id));
|
|
59
|
+
const mux = new multiplex_client_1.MultiplexClient({ factory });
|
|
60
|
+
mux.setActiveSources([{ id: "cloud", kind: "cloud" }, { id: "local:1", kind: "local" }]);
|
|
61
|
+
(0, chai_1.expect)(mux.getActiveSources().map((s) => s.descriptor.id).sort()).to.deep.equal(["cloud", "local:1"]);
|
|
62
|
+
const before = factory.callCount;
|
|
63
|
+
mux.setActiveSources(["local:1", "cloud"]);
|
|
64
|
+
(0, chai_1.expect)(factory.callCount).to.equal(before);
|
|
65
|
+
mux.setActiveSources(["cloud"]);
|
|
66
|
+
(0, chai_1.expect)(mux.getActiveSources().map((s) => s.descriptor.id)).to.deep.equal(["cloud"]);
|
|
67
|
+
(0, chai_1.expect)(mux.getRegisteredSources()).to.have.length(2);
|
|
68
|
+
});
|
|
69
|
+
(0, mocha_1.it)("disableSource keeps the client and (default) disconnects its gateway; removeSource discards", () => {
|
|
70
|
+
const member = stubMember("local:1");
|
|
71
|
+
const factory = () => member;
|
|
72
|
+
const mux = new multiplex_client_1.MultiplexClient({ factory, register: [{ id: "local:1", kind: "local" }], enable: ["local:1"] });
|
|
73
|
+
member._setConnected(true);
|
|
74
|
+
const disc = sinon_1.default.spy(member.gateway, "disconnect");
|
|
75
|
+
mux.disableSource("local:1");
|
|
76
|
+
(0, chai_1.expect)(disc.called).to.equal(true);
|
|
77
|
+
(0, chai_1.expect)(mux.getRegisteredSources()[0].client).to.equal(member);
|
|
78
|
+
mux.removeSource("local:1");
|
|
79
|
+
(0, chai_1.expect)(mux.getRegisteredSources()).to.have.length(0);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const chai_1 = require("chai");
|
|
4
|
+
const mocha_1 = require("mocha");
|
|
5
|
+
const multiplex_client_1 = require("../client/multiplex-client");
|
|
6
|
+
function member(id, scope, caps = ["channels.get"]) {
|
|
7
|
+
const noop = new Proxy({}, { get: () => () => Promise.resolve(undefined) });
|
|
8
|
+
return {
|
|
9
|
+
agents: noop, channels: noop, messages: noop, aggregates: noop, alarms: noop, connections: noop,
|
|
10
|
+
notifications: noop, permissions: noop, processors: noop, turn: noop, users: noop, rpc: noop,
|
|
11
|
+
gateway: { connect: async () => { }, disconnect: () => { }, reconnect: async () => { }, on: () => { }, off: () => { },
|
|
12
|
+
subscribe: () => { }, unsubscribe: () => { }, subscribeToChannel: () => () => { }, syncChannel: () => { },
|
|
13
|
+
sendOneShotMessage: () => { }, getSession: () => null, isConnected: () => false, getSubscriptionCount: () => 0,
|
|
14
|
+
getSubscriptions: () => [], setStats: () => { } },
|
|
15
|
+
getCapabilities: () => new Set(caps), supports: (c) => caps.includes(c),
|
|
16
|
+
isConnected: () => false, getStatus: () => ({ clientId: id, connected: false, state: "disconnected", agentScope: scope(), at: Date.now() }),
|
|
17
|
+
onStatusChange: () => () => { },
|
|
18
|
+
getAgentScope: async () => { const s = scope(); return s === "unknown" ? { mode: "list", agentIds: [] } : s; },
|
|
19
|
+
getKnownAgentScope: scope,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
(0, mocha_1.describe)("MultiplexClient agent-scope routing", () => {
|
|
23
|
+
(0, mocha_1.it)("membersForAgent: cloud (scope 'all') always included; local iff its list has the agent; 'unknown' included optimistically", () => {
|
|
24
|
+
let localScope = "unknown";
|
|
25
|
+
const factory = (d) => d.id === "cloud" ? member("cloud", () => ({ mode: "all" }))
|
|
26
|
+
: member("local", () => localScope);
|
|
27
|
+
const mux = new multiplex_client_1.MultiplexClient({ factory, register: [{ id: "cloud", kind: "cloud" }, { id: "local", kind: "local" }], enableAll: true });
|
|
28
|
+
const m = mux;
|
|
29
|
+
(0, chai_1.expect)(m.membersForAgent("dev7").map((x) => x.id).sort()).to.deep.equal(["cloud", "local"]);
|
|
30
|
+
localScope = { mode: "list", agentIds: ["dev9"] };
|
|
31
|
+
(0, chai_1.expect)(m.membersForAgent("dev7").map((x) => x.id)).to.deep.equal(["cloud"]);
|
|
32
|
+
localScope = { mode: "list", agentIds: ["dev7"] };
|
|
33
|
+
(0, chai_1.expect)(m.membersForAgent("dev7").map((x) => x.id).sort()).to.deep.equal(["cloud", "local"]);
|
|
34
|
+
});
|
|
35
|
+
(0, mocha_1.it)("getAgentScope rollup: 'all' if any enabled member is 'all'; else union list", async () => {
|
|
36
|
+
const factory = (d) => d.id === "cloud" ? member("cloud", () => ({ mode: "all" }))
|
|
37
|
+
: member(d.id, () => ({ mode: "list", agentIds: [d.id.replace("local:", "")] }));
|
|
38
|
+
const mux = new multiplex_client_1.MultiplexClient({ factory, register: [{ id: "cloud", kind: "cloud" }, { id: "local:a", kind: "local" }, { id: "local:b", kind: "local" }], enableAll: true });
|
|
39
|
+
(0, chai_1.expect)(await mux.getAgentScope()).to.deep.equal({ mode: "all" });
|
|
40
|
+
mux.disableSource("cloud");
|
|
41
|
+
const rolled = await mux.getAgentScope();
|
|
42
|
+
(0, chai_1.expect)(rolled).to.deep.equal({ mode: "list", agentIds: ["a", "b"] });
|
|
43
|
+
(0, chai_1.expect)(mux.getKnownAgentScope()).to.deep.equal({ mode: "list", agentIds: ["a", "b"] });
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const chai_1 = require("chai");
|
|
4
|
+
const mocha_1 = require("mocha");
|
|
5
|
+
const multiplex_client_1 = require("../client/multiplex-client");
|
|
6
|
+
function statefulMember(id) {
|
|
7
|
+
let connected = false;
|
|
8
|
+
let state = "disconnected";
|
|
9
|
+
const listeners = new Set();
|
|
10
|
+
const fire = () => listeners.forEach((l) => l({ clientId: id, connected, state, agentScope: { mode: "all" }, at: Date.now() }));
|
|
11
|
+
const noop = new Proxy({}, { get: () => () => Promise.resolve(undefined) });
|
|
12
|
+
return {
|
|
13
|
+
member: {
|
|
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: () => connected, getSubscriptionCount: () => 0, getSubscriptions: () => [], setStats: () => { } },
|
|
16
|
+
getCapabilities: () => new Set(["gateway.subscribe"]), supports: (c) => c === "gateway.subscribe",
|
|
17
|
+
isConnected: () => connected,
|
|
18
|
+
getStatus: () => ({ clientId: id, connected, state, agentScope: { mode: "all" }, at: Date.now() }),
|
|
19
|
+
onStatusChange: (l) => { listeners.add(l); return () => listeners.delete(l); },
|
|
20
|
+
getAgentScope: async () => ({ mode: "all" }), getKnownAgentScope: () => ({ mode: "all" }),
|
|
21
|
+
},
|
|
22
|
+
setConnected(v) { connected = v; state = v ? "connected" : "disconnected"; fire(); },
|
|
23
|
+
setState(s) { state = s; fire(); },
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
(0, mocha_1.describe)("MultiplexClient status rollup", () => {
|
|
27
|
+
(0, mocha_1.it)("isConnected = all gateway-subscribe members connected; state degrades; members[] present; onStatusChange fires", () => {
|
|
28
|
+
const a = statefulMember("cloud");
|
|
29
|
+
const b = statefulMember("local:7");
|
|
30
|
+
const factory = (d) => d.id === "cloud" ? a.member : b.member;
|
|
31
|
+
const mux = new multiplex_client_1.MultiplexClient({ factory, register: [{ id: "cloud", kind: "cloud", label: "Cloud" }, { id: "local:7", kind: "local" }], enableAll: true });
|
|
32
|
+
const seen = [];
|
|
33
|
+
const off = mux.onStatusChange((s) => seen.push(s));
|
|
34
|
+
(0, chai_1.expect)(mux.isConnected()).to.equal(false);
|
|
35
|
+
let st = mux.getStatus();
|
|
36
|
+
(0, chai_1.expect)(st.clientId).to.equal("multiplex");
|
|
37
|
+
(0, chai_1.expect)(st.members).to.have.length(2);
|
|
38
|
+
(0, chai_1.expect)(st.state).to.equal("disconnected");
|
|
39
|
+
a.setConnected(true);
|
|
40
|
+
st = mux.getStatus();
|
|
41
|
+
(0, chai_1.expect)(st.connected).to.equal(false); // not ALL connected
|
|
42
|
+
(0, chai_1.expect)(st.state).to.equal("degraded");
|
|
43
|
+
b.setConnected(true);
|
|
44
|
+
st = mux.getStatus();
|
|
45
|
+
(0, chai_1.expect)(st.connected).to.equal(true);
|
|
46
|
+
(0, chai_1.expect)(st.state).to.equal("connected");
|
|
47
|
+
a.setState("error");
|
|
48
|
+
(0, chai_1.expect)(mux.getStatus().state).to.equal("error");
|
|
49
|
+
(0, chai_1.expect)(seen.length).to.be.greaterThan(2);
|
|
50
|
+
off();
|
|
51
|
+
const n = seen.length;
|
|
52
|
+
b.setState("error");
|
|
53
|
+
(0, chai_1.expect)(seen.length).to.equal(n); // unsubscribed
|
|
54
|
+
});
|
|
55
|
+
(0, mocha_1.it)("disabled members appear in members[] as 'disconnected'", () => {
|
|
56
|
+
const a = statefulMember("cloud");
|
|
57
|
+
const factory = (d) => d.id === "cloud" ? a.member : statefulMember(d.id).member;
|
|
58
|
+
const mux = new multiplex_client_1.MultiplexClient({ factory, register: [{ id: "cloud", kind: "cloud" }, { id: "local:7", kind: "local" }], enable: ["cloud"] });
|
|
59
|
+
const st = mux.getStatus();
|
|
60
|
+
const local = st.members.find((m) => m.sourceId === "local:7");
|
|
61
|
+
(0, chai_1.expect)(local.status.state).to.equal("disconnected");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
// src/test/multiplex-writes.test.ts
|
|
40
|
+
const chai_1 = require("chai");
|
|
41
|
+
const chai_as_promised_1 = __importDefault(require("chai-as-promised"));
|
|
42
|
+
const chai = __importStar(require("chai"));
|
|
43
|
+
const mocha_1 = require("mocha");
|
|
44
|
+
const multiplex_client_1 = require("../client/multiplex-client");
|
|
45
|
+
const errors_1 = require("../client/errors");
|
|
46
|
+
chai.use(chai_as_promised_1.default);
|
|
47
|
+
const { expect: xp } = chai;
|
|
48
|
+
function writeMember(id, scope, caps, sink) {
|
|
49
|
+
const noop = new Proxy({}, { get: () => () => Promise.resolve(undefined) });
|
|
50
|
+
const post = async (agentId) => { sink.push(`${id}:${agentId}`); return { id: "m", data: {}, attachments: [], author_id: "a", channel: { agent_id: agentId, name: "c" } }; };
|
|
51
|
+
return {
|
|
52
|
+
agents: noop, channels: noop,
|
|
53
|
+
messages: { postMessage: (a) => post(a) },
|
|
54
|
+
aggregates: { putAggregate: async (a) => { sink.push(`${id}:put:${a}`); return { data: {}, attachments: [] }; } },
|
|
55
|
+
alarms: noop, connections: noop, notifications: noop, permissions: noop, processors: noop, turn: noop, users: noop, rpc: noop,
|
|
56
|
+
gateway: { connect: async () => { }, disconnect: () => { }, reconnect: async () => { }, on: () => { }, off: () => { }, subscribe: () => { }, unsubscribe: () => { }, subscribeToChannel: () => () => { }, syncChannel: () => { }, sendOneShotMessage: () => { }, getSession: () => null, isConnected: () => false, getSubscriptionCount: () => 0, getSubscriptions: () => [], setStats: () => { } },
|
|
57
|
+
getCapabilities: () => new Set(caps), supports: (c) => caps.includes(c),
|
|
58
|
+
isConnected: () => false, getStatus: () => ({ clientId: id, connected: false, state: "disconnected", agentScope: scope, at: Date.now() }),
|
|
59
|
+
onStatusChange: () => () => { },
|
|
60
|
+
getAgentScope: async () => scope, getKnownAgentScope: () => scope,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
(0, mocha_1.describe)("MultiplexClient writes", () => {
|
|
64
|
+
function mk(members) {
|
|
65
|
+
const factory = (d) => members[d.id];
|
|
66
|
+
return new multiplex_client_1.MultiplexClient({ factory, register: Object.keys(members).map((id) => ({ id, kind: id.startsWith("local") ? "local" : "cloud" })), enableAll: true });
|
|
67
|
+
}
|
|
68
|
+
(0, mocha_1.it)("routes to the single member that owns the agent and has the write cap", async () => {
|
|
69
|
+
const sink = [];
|
|
70
|
+
const mux = mk({
|
|
71
|
+
cloud: writeMember("cloud", { mode: "all" }, ["messages.post"], sink),
|
|
72
|
+
"local:7": writeMember("local:7", { mode: "list", agentIds: ["dev7"] }, ["messages.post"], sink),
|
|
73
|
+
});
|
|
74
|
+
// dev99 only owned by cloud (local:7 owns dev7) → routes to cloud
|
|
75
|
+
await mux.messages.postMessage("dev99", "c", { x: 1 });
|
|
76
|
+
(0, chai_1.expect)(sink).to.deep.equal(["cloud:dev99"]);
|
|
77
|
+
});
|
|
78
|
+
(0, mocha_1.it)("ambiguous when 2+ members own the agent and have the cap → AmbiguousWriteError", async () => {
|
|
79
|
+
const sink = [];
|
|
80
|
+
const mux = mk({
|
|
81
|
+
cloud: writeMember("cloud", { mode: "all" }, ["messages.post"], sink),
|
|
82
|
+
"local:7": writeMember("local:7", { mode: "list", agentIds: ["dev7"] }, ["messages.post"], sink),
|
|
83
|
+
});
|
|
84
|
+
await xp(mux.messages.postMessage("dev7", "c", { x: 1 })).to.be.rejectedWith(errors_1.AmbiguousWriteError);
|
|
85
|
+
// …unless scoped to one:
|
|
86
|
+
await mux.messages.postMessage("dev7", "c", { x: 1 }, { sources: ["local:7"] });
|
|
87
|
+
(0, chai_1.expect)(sink).to.deep.equal(["local:7:dev7"]);
|
|
88
|
+
});
|
|
89
|
+
(0, mocha_1.it)("no member with the cap → UnsupportedCapabilityError", async () => {
|
|
90
|
+
const mux = mk({ cloud: writeMember("cloud", { mode: "all" }, ["messages.list"], []) });
|
|
91
|
+
await xp(mux.messages.postMessage("dev7", "c", {})).to.be.rejectedWith(errors_1.UnsupportedCapabilityError);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const chai_1 = require("chai");
|
|
4
|
+
const mocha_1 = require("mocha");
|
|
5
|
+
const doover_client_1 = require("../client/doover-client");
|
|
6
|
+
const helpers_1 = require("./helpers");
|
|
7
|
+
function stripSource(v) {
|
|
8
|
+
return JSON.parse(JSON.stringify(v, (k, val) => (k === "__source" ? undefined : val)));
|
|
9
|
+
}
|
|
10
|
+
(0, mocha_1.describe)("provenance is additive only", () => {
|
|
11
|
+
(0, mocha_1.it)("REST payloads equal the wire body once __source is stripped", async () => {
|
|
12
|
+
const wire = { name: "c1", is_private: false, owner_id: "o", id: "ch1" };
|
|
13
|
+
const client = new doover_client_1.DooverClient({
|
|
14
|
+
dataRestUrl: "https://api.example.com",
|
|
15
|
+
controlApiUrl: "https://control.example.com",
|
|
16
|
+
dataWssUrl: "wss://ws.example.com",
|
|
17
|
+
fetchImpl: (0, helpers_1.createFetchMock)(() => (0, helpers_1.createJsonResponse)(wire)),
|
|
18
|
+
webSocketImpl: helpers_1.MockWebSocket,
|
|
19
|
+
disableBrowserLifecycleHooks: true,
|
|
20
|
+
});
|
|
21
|
+
const channel = await client.channels.getChannel("a1", "c1");
|
|
22
|
+
(0, chai_1.expect)(channel.__source).to.exist; // it IS stamped
|
|
23
|
+
(0, chai_1.expect)(stripSource(channel)).to.deep.equal(wire); // …but otherwise byte-identical
|
|
24
|
+
});
|
|
25
|
+
(0, mocha_1.it)("listMessages stamps each element, leaving the array shape intact", async () => {
|
|
26
|
+
const wire = [
|
|
27
|
+
{ id: "2", data: { v: 2 }, attachments: [], author_id: "a", channel: { agent_id: "a1", name: "c1" } },
|
|
28
|
+
{ id: "1", data: { v: 1 }, attachments: [], author_id: "a", channel: { agent_id: "a1", name: "c1" } },
|
|
29
|
+
];
|
|
30
|
+
const client = new doover_client_1.DooverClient({
|
|
31
|
+
dataRestUrl: "https://api.example.com",
|
|
32
|
+
controlApiUrl: "https://control.example.com",
|
|
33
|
+
dataWssUrl: "wss://ws.example.com",
|
|
34
|
+
fetchImpl: (0, helpers_1.createFetchMock)(() => (0, helpers_1.createJsonResponse)(wire)),
|
|
35
|
+
webSocketImpl: helpers_1.MockWebSocket,
|
|
36
|
+
disableBrowserLifecycleHooks: true,
|
|
37
|
+
});
|
|
38
|
+
const msgs = await client.messages.listMessages("a1", "c1", { limit: 2 });
|
|
39
|
+
(0, chai_1.expect)(msgs).to.have.length(2);
|
|
40
|
+
(0, chai_1.expect)(msgs[0].__source?.via).to.include({ method: "messages.listMessages" });
|
|
41
|
+
// shape minus __source and the client-added `timestamp` matches the wire item
|
|
42
|
+
const { __source: _s, timestamp: _t, ...rest } = msgs[0];
|
|
43
|
+
(0, chai_1.expect)(rest).to.deep.equal(wire[0]);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const chai_1 = require("chai");
|
|
4
|
+
const mocha_1 = require("mocha");
|
|
5
|
+
const provenance_1 = require("../client/provenance");
|
|
6
|
+
(0, mocha_1.describe)("ProvenanceStamper", () => {
|
|
7
|
+
const stamper = new provenance_1.ProvenanceStamper({ id: "cloud", kind: "cloud", meta: { x: 1 } });
|
|
8
|
+
(0, mocha_1.it)("stamps a plain object top-level", () => {
|
|
9
|
+
const out = stamper.stampRest({ a: 1 }, { method: "x.y", request: { z: 2 }, startedAt: 0, durationMs: 5, status: 200 });
|
|
10
|
+
(0, chai_1.expect)(out).to.include({ a: 1 });
|
|
11
|
+
(0, chai_1.expect)(out.__source?.client.id).to.equal("cloud");
|
|
12
|
+
(0, chai_1.expect)(out.__source?.via).to.deep.include({ transport: "rest", method: "x.y", durationMs: 5, status: 200 });
|
|
13
|
+
});
|
|
14
|
+
(0, mocha_1.it)("stamps each element of an array", () => {
|
|
15
|
+
const out = stamper.stampRest([{ a: 1 }, { a: 2 }], { method: "x.list", request: {}, startedAt: 0, durationMs: 1 });
|
|
16
|
+
(0, chai_1.expect)(out[0].__source?.client.kind).to.equal("cloud");
|
|
17
|
+
(0, chai_1.expect)(out[1].__source?.client.kind).to.equal("cloud");
|
|
18
|
+
});
|
|
19
|
+
(0, mocha_1.it)("stamps array-valued props one level deep (e.g. { results: [...] })", () => {
|
|
20
|
+
const out = stamper.stampRest({ results: [{ a: 1 }], count: 1 }, { method: "x.batch", request: {}, startedAt: 0, durationMs: 1 });
|
|
21
|
+
(0, chai_1.expect)(out.__source?.client.id).to.equal("cloud");
|
|
22
|
+
(0, chai_1.expect)(out.results[0].__source?.client.id).to.equal("cloud");
|
|
23
|
+
});
|
|
24
|
+
(0, mocha_1.it)("leaves Blobs and primitives untouched", () => {
|
|
25
|
+
const blob = new Blob(["x"]);
|
|
26
|
+
(0, chai_1.expect)(stamper.stampRest(blob, { method: "m", request: {}, startedAt: 0, durationMs: 1 })).to.equal(blob);
|
|
27
|
+
(0, chai_1.expect)(stamper.stampRest(undefined, { method: "m", request: {}, startedAt: 0, durationMs: 1 })).to.equal(undefined);
|
|
28
|
+
});
|
|
29
|
+
(0, mocha_1.it)("wrapSubclient stamps awaited results with subclient.method", async () => {
|
|
30
|
+
const api = { thing: async () => ({ a: 1 }), sync: () => 42 };
|
|
31
|
+
const wrapped = (0, provenance_1.wrapSubclient)(api, "channels", stamper);
|
|
32
|
+
const r = await wrapped.thing();
|
|
33
|
+
(0, chai_1.expect)(r.__source?.via).to.include({ method: "channels.thing", transport: "rest" });
|
|
34
|
+
(0, chai_1.expect)(wrapped.sync()).to.equal(42); // sync passthrough
|
|
35
|
+
});
|
|
36
|
+
(0, mocha_1.it)("stampGatewayEvent stamps payload + nested aggregate/message", () => {
|
|
37
|
+
const out = stamper.stampGatewayEvent({ author_id: "a", channel: { agent_id: "x", name: "c" }, aggregate: { data: {}, attachments: [] } }, { event: "aggregateUpdate", sessionId: "s1" });
|
|
38
|
+
(0, chai_1.expect)(out.__source?.via).to.deep.include({ transport: "gateway", event: "aggregateUpdate", sessionId: "s1" });
|
|
39
|
+
(0, chai_1.expect)(out.aggregate.__source?.via).to.deep.include({ transport: "gateway", event: "aggregateUpdate" });
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const chai_1 = require("chai");
|
|
4
|
+
const mocha_1 = require("mocha");
|
|
5
|
+
(0, mocha_1.describe)("SourceProvenance", () => {
|
|
6
|
+
(0, mocha_1.it)("is structurally usable on existing data shapes (compile-time)", () => {
|
|
7
|
+
const prov = {
|
|
8
|
+
client: { id: "cloud", kind: "cloud" },
|
|
9
|
+
retrievedAt: Date.now(),
|
|
10
|
+
via: { transport: "rest", method: "aggregates.getAggregate", request: {}, startedAt: 0, durationMs: 1 },
|
|
11
|
+
};
|
|
12
|
+
const agg = { data: {}, attachments: [], __source: prov };
|
|
13
|
+
(0, chai_1.expect)(agg.__source?.client.kind).to.equal("cloud");
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|