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.
Files changed (156) hide show
  1. package/README.md +146 -0
  2. package/dist/apis/_args.d.ts +16 -0
  3. package/dist/apis/_args.js +26 -0
  4. package/dist/apis/agents-api.d.ts +4 -1
  5. package/dist/apis/agents-api.js +139 -1
  6. package/dist/apis/aggregates-api.d.ts +23 -3
  7. package/dist/apis/aggregates-api.js +33 -4
  8. package/dist/apis/alarms-api.d.ts +30 -0
  9. package/dist/apis/alarms-api.js +51 -6
  10. package/dist/apis/channels-api.d.ts +40 -3
  11. package/dist/apis/channels-api.js +50 -5
  12. package/dist/apis/connections-api.d.ts +21 -5
  13. package/dist/apis/connections-api.js +26 -10
  14. package/dist/apis/messages-api.d.ts +54 -9
  15. package/dist/apis/messages-api.js +94 -12
  16. package/dist/apis/notifications-api.d.ts +52 -0
  17. package/dist/apis/notifications-api.js +99 -13
  18. package/dist/apis/permissions-api.d.ts +8 -0
  19. package/dist/apis/permissions-api.js +11 -2
  20. package/dist/apis/processors-api.d.ts +35 -0
  21. package/dist/apis/processors-api.js +64 -8
  22. package/dist/apis/users-api.d.ts +8 -0
  23. package/dist/apis/users-api.js +13 -0
  24. package/dist/client/capabilities.d.ts +10 -0
  25. package/dist/client/capabilities.js +43 -0
  26. package/dist/client/data-client.d.ts +104 -0
  27. package/dist/client/data-client.js +2 -0
  28. package/dist/client/doover-client.d.ts +31 -31
  29. package/dist/client/doover-client.js +58 -37
  30. package/dist/client/errors.d.ts +23 -0
  31. package/dist/client/errors.js +50 -0
  32. package/dist/client/local-agent-client.d.ts +65 -0
  33. package/dist/client/local-agent-client.js +222 -0
  34. package/dist/client/multiplex-client.d.ts +151 -0
  35. package/dist/client/multiplex-client.js +744 -0
  36. package/dist/client/multiplex-gateway.d.ts +49 -0
  37. package/dist/client/multiplex-gateway.js +114 -0
  38. package/dist/client/multiplex-merge.d.ts +12 -0
  39. package/dist/client/multiplex-merge.js +42 -0
  40. package/dist/client/provenance.d.ts +60 -0
  41. package/dist/client/provenance.js +113 -0
  42. package/dist/client/stats.d.ts +24 -0
  43. package/dist/client/stats.js +62 -0
  44. package/dist/client/status-tracker.d.ts +24 -0
  45. package/dist/client/status-tracker.js +90 -0
  46. package/dist/gateway/gateway-client.d.ts +23 -1
  47. package/dist/gateway/gateway-client.js +75 -6
  48. package/dist/http/rest-client.d.ts +5 -0
  49. package/dist/index.d.ts +15 -0
  50. package/dist/index.js +22 -7
  51. package/dist/react/context.d.ts +9 -5
  52. package/dist/react/context.js +6 -2
  53. package/dist/react/index.d.ts +3 -0
  54. package/dist/react/index.js +3 -1
  55. package/dist/react/useChannelAggregate.d.ts +8 -1
  56. package/dist/react/useChannelAggregate.js +24 -6
  57. package/dist/react/useChannelMessage.d.ts +8 -1
  58. package/dist/react/useChannelMessage.js +17 -6
  59. package/dist/react/useChannelMessages.d.ts +8 -1
  60. package/dist/react/useChannelMessages.js +21 -7
  61. package/dist/react/useChannelSubscription.d.ts +6 -0
  62. package/dist/react/useChannelSubscription.js +8 -18
  63. package/dist/react/useClientStatus.d.ts +10 -0
  64. package/dist/react/useClientStatus.js +22 -0
  65. package/dist/react/useConnectionState.d.ts +5 -0
  66. package/dist/react/useConnectionState.js +5 -0
  67. package/dist/react/useMultiAgentAggregates.d.ts +8 -1
  68. package/dist/react/useMultiAgentAggregates.js +25 -21
  69. package/dist/react/useMultiAgentChannelMessages.d.ts +8 -1
  70. package/dist/react/useMultiAgentChannelMessages.js +23 -18
  71. package/dist/react/useSendMessage.d.ts +8 -1
  72. package/dist/react/useSendMessage.js +15 -2
  73. package/dist/react/useSendRpc.js +4 -1
  74. package/dist/react/useUpdateAggregate.d.ts +6 -0
  75. package/dist/react/useUpdateAggregate.js +20 -4
  76. package/dist/react/useUpdateMessage.d.ts +5 -0
  77. package/dist/react/useUpdateMessage.js +11 -2
  78. package/dist/rpc/errors.d.ts +10 -0
  79. package/dist/rpc/errors.js +23 -0
  80. package/dist/rpc/rpc-dispatcher.d.ts +28 -0
  81. package/dist/rpc/rpc-dispatcher.js +129 -0
  82. package/dist/test/api-args.test.d.ts +1 -0
  83. package/dist/test/api-args.test.js +48 -0
  84. package/dist/test/api-overloads.test.d.ts +9 -0
  85. package/dist/test/api-overloads.test.js +276 -0
  86. package/dist/test/capabilities.test.d.ts +1 -0
  87. package/dist/test/capabilities.test.js +27 -0
  88. package/dist/test/data-client-errors.test.d.ts +1 -0
  89. package/dist/test/data-client-errors.test.js +24 -0
  90. package/dist/test/data-client-shape.test.d.ts +1 -0
  91. package/dist/test/data-client-shape.test.js +28 -0
  92. package/dist/test/doover-client-dataclient.test.d.ts +1 -0
  93. package/dist/test/doover-client-dataclient.test.js +58 -0
  94. package/dist/test/doover-client-provenance.test.d.ts +1 -0
  95. package/dist/test/doover-client-provenance.test.js +32 -0
  96. package/dist/test/exports.test.d.ts +1 -0
  97. package/dist/test/exports.test.js +76 -0
  98. package/dist/test/gateway-provenance.test.d.ts +1 -0
  99. package/dist/test/gateway-provenance.test.js +29 -0
  100. package/dist/test/gateway-subscriptions.test.d.ts +1 -0
  101. package/dist/test/gateway-subscriptions.test.js +95 -0
  102. package/dist/test/local-agent-client.test.d.ts +1 -0
  103. package/dist/test/local-agent-client.test.js +202 -0
  104. package/dist/test/multiplex-capabilities.test.d.ts +1 -0
  105. package/dist/test/multiplex-capabilities.test.js +37 -0
  106. package/dist/test/multiplex-conflicts.test.d.ts +1 -0
  107. package/dist/test/multiplex-conflicts.test.js +35 -0
  108. package/dist/test/multiplex-gateway.test.d.ts +1 -0
  109. package/dist/test/multiplex-gateway.test.js +103 -0
  110. package/dist/test/multiplex-merge.test.d.ts +1 -0
  111. package/dist/test/multiplex-merge.test.js +19 -0
  112. package/dist/test/multiplex-noncore.test.d.ts +1 -0
  113. package/dist/test/multiplex-noncore.test.js +82 -0
  114. package/dist/test/multiplex-passthrough.test.d.ts +1 -0
  115. package/dist/test/multiplex-passthrough.test.js +35 -0
  116. package/dist/test/multiplex-reads.test.d.ts +1 -0
  117. package/dist/test/multiplex-reads.test.js +87 -0
  118. package/dist/test/multiplex-registry.test.d.ts +1 -0
  119. package/dist/test/multiplex-registry.test.js +81 -0
  120. package/dist/test/multiplex-routing.test.d.ts +1 -0
  121. package/dist/test/multiplex-routing.test.js +45 -0
  122. package/dist/test/multiplex-status.test.d.ts +1 -0
  123. package/dist/test/multiplex-status.test.js +63 -0
  124. package/dist/test/multiplex-writes.test.d.ts +1 -0
  125. package/dist/test/multiplex-writes.test.js +93 -0
  126. package/dist/test/provenance-regression.test.d.ts +1 -0
  127. package/dist/test/provenance-regression.test.js +45 -0
  128. package/dist/test/provenance-stamper.test.d.ts +1 -0
  129. package/dist/test/provenance-stamper.test.js +41 -0
  130. package/dist/test/provenance-type.test.d.ts +1 -0
  131. package/dist/test/provenance-type.test.js +15 -0
  132. package/dist/test/react-client-status.test.d.ts +1 -0
  133. package/dist/test/react-client-status.test.js +35 -0
  134. package/dist/test/react-dataclient.test.d.ts +1 -0
  135. package/dist/test/react-dataclient.test.js +28 -0
  136. package/dist/test/react-exports.test.d.ts +1 -0
  137. package/dist/test/react-exports.test.js +45 -0
  138. package/dist/test/react-sources-option.test.d.ts +1 -0
  139. package/dist/test/react-sources-option.test.js +41 -0
  140. package/dist/test/react.test.js +2 -2
  141. package/dist/test/rpc-dispatcher.test.d.ts +1 -0
  142. package/dist/test/rpc-dispatcher.test.js +190 -0
  143. package/dist/test/stats-rpc.test.d.ts +1 -0
  144. package/dist/test/stats-rpc.test.js +69 -0
  145. package/dist/test/status-tracker.test.d.ts +1 -0
  146. package/dist/test/status-tracker.test.js +85 -0
  147. package/dist/test/users-api.test.d.ts +1 -0
  148. package/dist/test/users-api.test.js +19 -0
  149. package/dist/types/common.d.ts +18 -1
  150. package/dist/types/openapi.d.ts +1 -0
  151. package/dist/types/provenance.d.ts +45 -0
  152. package/dist/types/provenance.js +2 -0
  153. package/dist/types/viewer.d.ts +3 -0
  154. package/dist/viewer/doover-data-provider.d.ts +30 -34
  155. package/dist/viewer/doover-data-provider.js +82 -316
  156. package/package.json +1 -1
@@ -0,0 +1,76 @@
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 doover = __importStar(require("../index"));
39
+ (0, mocha_1.describe)("public exports (Phase 1–3)", () => {
40
+ (0, mocha_1.it)("exports LocalAgentClient", () => {
41
+ (0, chai_1.expect)(doover.LocalAgentClient).to.be.a("function");
42
+ });
43
+ (0, mocha_1.it)("exports MultiplexClient", () => {
44
+ (0, chai_1.expect)(doover.MultiplexClient).to.be.a("function");
45
+ });
46
+ (0, mocha_1.it)("exports the new capability/contract symbols", () => {
47
+ (0, chai_1.expect)(doover.ALL_CAPABILITIES).to.be.an("array");
48
+ (0, chai_1.expect)(doover.UnsupportedCapabilityError).to.be.a("function");
49
+ (0, chai_1.expect)(doover.AmbiguousWriteError).to.be.a("function");
50
+ });
51
+ (0, mocha_1.it)("type-only exports resolve (compile-time)", () => {
52
+ // If any of these types are removed from the public index the import above
53
+ // will fail at typecheck time (tsc --noEmit), catching broken type exports.
54
+ const _prov = {
55
+ client: { id: "cloud", kind: "cloud" },
56
+ retrievedAt: 0,
57
+ via: { transport: "rest", method: "test", request: {}, startedAt: 0, durationMs: 0 },
58
+ };
59
+ const _scope = { mode: "all" };
60
+ const _status = {
61
+ clientId: "cloud",
62
+ connected: true,
63
+ };
64
+ // DataClient is an interface — reference it via a typed variable declaration.
65
+ // We don't construct one; the assignment below is never reached at runtime.
66
+ void _prov;
67
+ void _scope;
68
+ void _status;
69
+ (0, chai_1.expect)(true).to.equal(true); // keep mocha happy
70
+ });
71
+ (0, mocha_1.it)("exports the full Phase-1..4 surface", () => {
72
+ for (const name of ["ALL_CAPABILITIES", "UnsupportedCapabilityError", "AmbiguousWriteError", "LocalAgentClient", "MultiplexClient", "DooverClient"]) {
73
+ (0, chai_1.expect)(doover[name], name).to.exist;
74
+ }
75
+ });
76
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const chai_1 = require("chai");
4
+ const mocha_1 = require("mocha");
5
+ const gateway_client_1 = require("../gateway/gateway-client");
6
+ function makeConfig() {
7
+ return {
8
+ dataRestUrl: "https://example.com/api",
9
+ controlApiUrl: "https://example.com/control",
10
+ dataWssUrl: "wss://example.com/gateway",
11
+ disableBrowserLifecycleHooks: true,
12
+ };
13
+ }
14
+ (0, mocha_1.describe)("GatewayClient provenance hook", () => {
15
+ (0, mocha_1.it)("stamps emitted payloads when a hook is set; raw when not", () => {
16
+ const gw = new gateway_client_1.GatewayClient(makeConfig());
17
+ let stamped;
18
+ gw.on("messageCreate", (m) => { stamped = m; });
19
+ // Use a valid snowflake-format id (epoch-based numeric string)
20
+ const snowflake = String(BigInt(Date.now()) << 22n);
21
+ // no hook → raw
22
+ gw.handleMessage(JSON.stringify({ op: 0, t: "MessageCreate", d: { id: snowflake, data: {}, attachments: [], author_id: "a", channel: { agent_id: "x", name: "c" } } }));
23
+ (0, chai_1.expect)(stamped.__source).to.equal(undefined);
24
+ // with hook → stamped
25
+ gw.setProvenanceHook((value, ctx) => ({ ...value, __source: { client: { id: "cloud", kind: "cloud" }, retrievedAt: Date.now(), via: { transport: "gateway", event: ctx.event, receivedAt: Date.now() } } }));
26
+ gw.handleMessage(JSON.stringify({ op: 0, t: "MessageCreate", d: { id: snowflake, data: {}, attachments: [], author_id: "a", channel: { agent_id: "x", name: "c" } } }));
27
+ (0, chai_1.expect)(stamped.__source?.via.event).to.equal("messageCreate");
28
+ });
29
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const chai_1 = require("chai");
4
+ const gateway_client_1 = require("../gateway/gateway-client");
5
+ function makeConfig() {
6
+ return {
7
+ dataRestUrl: "https://example.com/api",
8
+ controlApiUrl: "https://example.com/control",
9
+ dataWssUrl: "wss://example.com/gateway",
10
+ disableBrowserLifecycleHooks: true,
11
+ };
12
+ }
13
+ function fakeMessage(channel = { agent_id: "a1", name: "c1" }, id = "m1") {
14
+ return {
15
+ id,
16
+ data: {},
17
+ attachments: [],
18
+ author_id: "auth",
19
+ channel,
20
+ timestamp: 1,
21
+ };
22
+ }
23
+ describe("GatewayClient.subscribeToChannel", () => {
24
+ it("first handler triggers wire-level subscribe", () => {
25
+ const gw = new gateway_client_1.GatewayClient(makeConfig());
26
+ let subscribeCalls = 0;
27
+ gw.subscribe = () => {
28
+ subscribeCalls += 1;
29
+ };
30
+ gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onMessage: () => { } });
31
+ (0, chai_1.expect)(subscribeCalls).to.equal(1);
32
+ });
33
+ it("second handler does not trigger another wire-level subscribe", () => {
34
+ const gw = new gateway_client_1.GatewayClient(makeConfig());
35
+ let subscribeCalls = 0;
36
+ gw.subscribe = () => {
37
+ subscribeCalls += 1;
38
+ };
39
+ gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onMessage: () => { } });
40
+ gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onMessage: () => { } });
41
+ (0, chai_1.expect)(subscribeCalls).to.equal(1);
42
+ });
43
+ it("returned unsubscribe drops the handler; last drop unsubscribes", () => {
44
+ const gw = new gateway_client_1.GatewayClient(makeConfig());
45
+ let unsubCalls = 0;
46
+ gw.subscribe = () => { };
47
+ gw.unsubscribe = () => {
48
+ unsubCalls += 1;
49
+ };
50
+ const off1 = gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onMessage: () => { } });
51
+ const off2 = gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onMessage: () => { } });
52
+ off1();
53
+ (0, chai_1.expect)(unsubCalls).to.equal(0);
54
+ off2();
55
+ (0, chai_1.expect)(unsubCalls).to.equal(1);
56
+ });
57
+ it("returned unsubscribe is idempotent", () => {
58
+ const gw = new gateway_client_1.GatewayClient(makeConfig());
59
+ let unsubCalls = 0;
60
+ gw.subscribe = () => { };
61
+ gw.unsubscribe = () => {
62
+ unsubCalls += 1;
63
+ };
64
+ const off = gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onMessage: () => { } });
65
+ off();
66
+ off();
67
+ off();
68
+ (0, chai_1.expect)(unsubCalls).to.equal(1);
69
+ });
70
+ it("messageCreate event fans out to onMessage handlers for the matching channel", () => {
71
+ const gw = new gateway_client_1.GatewayClient(makeConfig());
72
+ gw.subscribe = () => { };
73
+ let received;
74
+ gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onMessage: (m) => { received = m; } });
75
+ gw.emit("messageCreate", fakeMessage());
76
+ (0, chai_1.expect)(received?.id).to.equal("m1");
77
+ });
78
+ it("messageCreate for a different channel does not fire the handler", () => {
79
+ const gw = new gateway_client_1.GatewayClient(makeConfig());
80
+ gw.subscribe = () => { };
81
+ let received = false;
82
+ gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onMessage: () => { received = true; } });
83
+ gw.emit("messageCreate", fakeMessage({ agent_id: "a1", name: "c2" }, "m2"));
84
+ (0, chai_1.expect)(received).to.equal(false);
85
+ });
86
+ it("aggregateUpdate fans out to onAggregate handlers", () => {
87
+ const gw = new gateway_client_1.GatewayClient(makeConfig());
88
+ gw.subscribe = () => { };
89
+ let received;
90
+ gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onAggregate: (a) => { received = a; } });
91
+ const aggregate = { data: { x: 1 }, attachments: [] };
92
+ gw.emit("aggregateUpdate", { channel: { agent_id: "a1", name: "c1" }, aggregate });
93
+ (0, chai_1.expect)(received?.data).to.deep.equal({ x: 1 });
94
+ });
95
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,202 @@
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
+ const chai_1 = require("chai");
40
+ const chai_as_promised_1 = __importDefault(require("chai-as-promised"));
41
+ const chai = __importStar(require("chai"));
42
+ const mocha_1 = require("mocha");
43
+ const local_agent_client_1 = require("../client/local-agent-client");
44
+ const errors_1 = require("../client/errors");
45
+ const helpers_1 = require("./helpers");
46
+ // Valid snowflake IDs for use in mock payloads (id must be a numeric string for BigInt conversion).
47
+ const SNOWFLAKE_1 = "179418309739937792";
48
+ const SNOWFLAKE_2 = "179418309744132096";
49
+ chai.use(chai_as_promised_1.default);
50
+ const { expect: xpect } = chai;
51
+ function makeLocal(extra = {}) {
52
+ return new local_agent_client_1.LocalAgentClient({
53
+ baseUrl: "http://192.168.0.7:49100",
54
+ fetchImpl: (0, helpers_1.createFetchMock)(),
55
+ webSocketImpl: helpers_1.MockWebSocket,
56
+ disableBrowserLifecycleHooks: true,
57
+ sourceId: "local:192.168.0.7:49100",
58
+ ...extra,
59
+ });
60
+ }
61
+ (0, mocha_1.describe)("LocalAgentClient", () => {
62
+ (0, mocha_1.it)("advertises the v1 capability set and not the others", () => {
63
+ const caps = makeLocal().getCapabilities();
64
+ for (const c of ["agents.list", "channels.list", "channels.get", "aggregates.get",
65
+ "aggregates.put", "aggregates.patch", "messages.list", "messages.post", "messages.put",
66
+ "gateway.subscribe", "gateway.realtime", "gateway.oneShot"]) {
67
+ (0, chai_1.expect)(caps.has(c)).to.equal(true);
68
+ }
69
+ for (const c of ["messages.listHistorical", "messages.get", "channels.create",
70
+ "channels.archive", "rpc.send", "users.me", "alarms.read"]) {
71
+ (0, chai_1.expect)(caps.has(c)).to.equal(false);
72
+ }
73
+ });
74
+ (0, mocha_1.it)("throw-stubs every unadvertised method with UnsupportedCapabilityError", async () => {
75
+ const c = makeLocal();
76
+ await xpect(c.messages.getMessage("a", "ch", "m")).to.be.rejectedWith(errors_1.UnsupportedCapabilityError);
77
+ await xpect(c.channels.createChannel("a", "ch", {})).to.be.rejectedWith(errors_1.UnsupportedCapabilityError);
78
+ await xpect(c.users.getMe()).to.be.rejectedWith(errors_1.UnsupportedCapabilityError);
79
+ await xpect(c.rpc.send({ agentId: "a", channelName: "ch" }, { method: "x", request: {} })).to.be.rejectedWith(errors_1.UnsupportedCapabilityError);
80
+ try {
81
+ await c.alarms.listAlarms("a", "ch");
82
+ chai_1.expect.fail("should throw");
83
+ }
84
+ catch (e) {
85
+ (0, chai_1.expect)(e).to.be.instanceOf(errors_1.UnsupportedCapabilityError);
86
+ (0, chai_1.expect)(e.capability).to.equal("alarms.read");
87
+ (0, chai_1.expect)(e.clientId).to.equal("local:192.168.0.7:49100");
88
+ }
89
+ });
90
+ (0, mocha_1.it)("listChannels / getChannel / getAggregate / listMessages hit the local REST base and carry __source kind 'local'", async () => {
91
+ const fetchMock = (0, helpers_1.createFetchMock)((url) => {
92
+ if (url.endsWith("/channels"))
93
+ return (0, helpers_1.createJsonResponse)([{ name: "c1", is_private: false, owner_id: "o" }]);
94
+ if (url.includes("/channels/c1/aggregate"))
95
+ return (0, helpers_1.createJsonResponse)({ data: { v: 1 }, attachments: [] });
96
+ if (url.includes("/channels/c1/messages"))
97
+ return (0, helpers_1.createJsonResponse)([{ id: SNOWFLAKE_1, data: {}, attachments: [], author_id: "a", channel: { agent_id: "dev7", name: "c1" } }]);
98
+ if (url.includes("/channels/c1"))
99
+ return (0, helpers_1.createJsonResponse)({ name: "c1", is_private: false, owner_id: "o" });
100
+ return (0, helpers_1.createJsonResponse)({});
101
+ });
102
+ const c = makeLocal({ fetchImpl: fetchMock });
103
+ const list = await c.channels.listChannels("dev7");
104
+ (0, chai_1.expect)(list[0].__source?.client.kind).to.equal("local");
105
+ (0, chai_1.expect)(list[0].__source?.via).to.include({ transport: "rest", method: "channels.listChannels" });
106
+ const agg = await c.aggregates.getAggregate("dev7", "c1");
107
+ (0, chai_1.expect)(agg.__source?.client.id).to.equal("local:192.168.0.7:49100");
108
+ const msgs = await c.messages.listMessages("dev7", "c1");
109
+ (0, chai_1.expect)(msgs[0].__source?.via).to.include({ transport: "rest" });
110
+ });
111
+ (0, mocha_1.it)("postMessage / putMessage / putAggregate / patchAggregate go through to the local REST base", async () => {
112
+ const seen = [];
113
+ const fetchMock = (0, helpers_1.createFetchMock)((url, init) => {
114
+ seen.push({ method: init?.method, url });
115
+ if (url.includes("/messages"))
116
+ return (0, helpers_1.createJsonResponse)({ id: SNOWFLAKE_2, data: {}, attachments: [], author_id: "a", channel: { agent_id: "dev7", name: "c1" } });
117
+ return (0, helpers_1.createJsonResponse)({ data: {}, attachments: [] });
118
+ });
119
+ const c = makeLocal({ fetchImpl: fetchMock });
120
+ const m = await c.messages.postMessage("dev7", "c1", { hello: 1 });
121
+ (0, chai_1.expect)(m.__source?.client.kind).to.equal("local");
122
+ await c.aggregates.putAggregate("dev7", "c1", { v: 2 });
123
+ await c.aggregates.patchAggregate("dev7", "c1", { v: 3 });
124
+ await c.messages.putMessage("dev7", "c1", SNOWFLAKE_2, { v: 4 });
125
+ (0, chai_1.expect)(seen.some((s) => s.method === "POST" && s.url.includes("/messages"))).to.equal(true);
126
+ (0, chai_1.expect)(seen.some((s) => s.method === "PUT" && s.url.includes("/aggregate"))).to.equal(true);
127
+ (0, chai_1.expect)(seen.some((s) => s.method === "PATCH" && s.url.includes("/aggregate"))).to.equal(true);
128
+ });
129
+ (0, mocha_1.it)("status reflects the local gateway: disconnected before connect, connected after Ready", async () => {
130
+ const c = makeLocal();
131
+ (0, chai_1.expect)(c.isConnected()).to.equal(false);
132
+ (0, chai_1.expect)(c.getStatus().state).to.equal("disconnected");
133
+ await c.gateway.connect();
134
+ const ws = helpers_1.MockWebSocket.instances[helpers_1.MockWebSocket.instances.length - 1];
135
+ ws.open();
136
+ ws.receive({ op: 0, t: "Hello", d: {} });
137
+ ws.receive({ op: 0, t: "Ready", d: { session_id: "s1", session_token: "t", subscriptions: [] } });
138
+ (0, chai_1.expect)(c.isConnected()).to.equal(true);
139
+ (0, chai_1.expect)(c.getStatus().state).to.equal("connected");
140
+ (0, chai_1.expect)(c.getStatus().clientId).to.equal("local:192.168.0.7:49100");
141
+ });
142
+ (0, mocha_1.it)("realtime: subscribeToChannel handlers receive stamped MessageCreate", async () => {
143
+ const c = makeLocal();
144
+ await c.gateway.connect();
145
+ const ws = helpers_1.MockWebSocket.instances[helpers_1.MockWebSocket.instances.length - 1];
146
+ ws.open();
147
+ ws.receive({ op: 0, t: "Ready", d: { session_id: "s1", session_token: "t", subscriptions: [] } });
148
+ let got;
149
+ c.gateway.subscribeToChannel({ agent_id: "dev7", name: "c1" }, { onMessage: (m) => { got = m; } });
150
+ ws.receive({ op: 0, t: "MessageCreate", d: { id: SNOWFLAKE_1, data: {}, attachments: [], author_id: "a", channel: { agent_id: "dev7", name: "c1" } } });
151
+ (0, chai_1.expect)(got.__source?.client.kind).to.equal("local");
152
+ });
153
+ (0, mocha_1.it)("getAgentScope resolves to { mode: 'list', agentIds: [deviceId] } via listAgents, caches it, and getKnownAgentScope is 'unknown' first", async () => {
154
+ let agentCalls = 0;
155
+ const fetchMock = (0, helpers_1.createFetchMock)((url) => {
156
+ if (url.includes("/agents")) {
157
+ agentCalls += 1;
158
+ return (0, helpers_1.createJsonResponse)({ agents: [{ id: "dev7", name: "Device 7" }] });
159
+ }
160
+ return (0, helpers_1.createJsonResponse)({});
161
+ });
162
+ const c = makeLocal({ fetchImpl: fetchMock });
163
+ (0, chai_1.expect)(c.getKnownAgentScope()).to.equal("unknown");
164
+ const scope = await c.getAgentScope();
165
+ (0, chai_1.expect)(scope).to.deep.equal({ mode: "list", agentIds: ["dev7"] });
166
+ (0, chai_1.expect)(c.getKnownAgentScope()).to.deep.equal({ mode: "list", agentIds: ["dev7"] });
167
+ await c.getAgentScope(); // cached
168
+ (0, chai_1.expect)(agentCalls).to.equal(1);
169
+ });
170
+ (0, mocha_1.it)("reconnect (Ready frame) invalidates the scope cache and re-fetches the agent id", async () => {
171
+ let agentCalls = 0;
172
+ const fetchMock = (0, helpers_1.createFetchMock)((url) => {
173
+ if (url.includes("/agents")) {
174
+ agentCalls += 1;
175
+ return (0, helpers_1.createJsonResponse)({ agents: [{ id: "dev7", name: "Device 7" }] });
176
+ }
177
+ return (0, helpers_1.createJsonResponse)({});
178
+ });
179
+ const c = makeLocal({ fetchImpl: fetchMock });
180
+ // 1. Initial resolve — agentCalls becomes 1, scope is cached.
181
+ const scope1 = await c.getAgentScope();
182
+ (0, chai_1.expect)(scope1).to.deep.equal({ mode: "list", agentIds: ["dev7"] });
183
+ (0, chai_1.expect)(agentCalls).to.equal(1);
184
+ // 2. Connect the gateway, open the socket, and send a Ready frame — this
185
+ // should null the cache and kick off a background re-fetch.
186
+ await c.gateway.connect();
187
+ const ws = helpers_1.MockWebSocket.instances[helpers_1.MockWebSocket.instances.length - 1];
188
+ ws.open();
189
+ ws.receive({ op: 0, t: "Hello", d: {} });
190
+ ws.receive({ op: 0, t: "Ready", d: { session_id: "s2", session_token: "t2", subscriptions: [] } });
191
+ // 3. The "ready" handler fires synchronously, nulling resolvedScope and
192
+ // firing off an async re-fetch. Flush the microtask queue so the async
193
+ // listAgents() call has time to complete before we assert.
194
+ await new Promise((r) => setImmediate(r));
195
+ await new Promise((r) => setImmediate(r));
196
+ // 4. Re-fetch should have run: agentCalls === 2 and scope is still [dev7].
197
+ (0, chai_1.expect)(agentCalls).to.equal(2);
198
+ const scope2 = await c.getAgentScope(); // now served from refreshed cache
199
+ (0, chai_1.expect)(scope2).to.deep.equal({ mode: "list", agentIds: ["dev7"] });
200
+ (0, chai_1.expect)(agentCalls).to.equal(2); // no third fetch — cache hit
201
+ });
202
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
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 memberWithCaps(id, caps) {
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),
16
+ supports: (c) => caps.includes(c),
17
+ isConnected: () => false,
18
+ getStatus: () => ({ clientId: id, connected: false, state: "disconnected", agentScope: "all", at: Date.now() }),
19
+ onStatusChange: () => () => { },
20
+ getAgentScope: async () => ({ mode: "all" }),
21
+ getKnownAgentScope: () => ({ mode: "all" }),
22
+ };
23
+ }
24
+ (0, mocha_1.describe)("MultiplexClient capabilities", () => {
25
+ (0, mocha_1.it)("is the union over enabled members; recomputed on enable/disable", () => {
26
+ const factory = (d) => d.id === "cloud" ? memberWithCaps("cloud", ["channels.list", "messages.listHistorical"])
27
+ : memberWithCaps("local", ["channels.list", "messages.list"]);
28
+ const mux = new multiplex_client_1.MultiplexClient({ factory, register: [{ id: "cloud", kind: "cloud" }, { id: "local", kind: "local" }], enable: ["cloud"] });
29
+ (0, chai_1.expect)([...mux.getCapabilities()].sort()).to.deep.equal(["channels.list", "messages.listHistorical"]);
30
+ mux.enableSource("local");
31
+ (0, chai_1.expect)([...mux.getCapabilities()].sort()).to.deep.equal(["channels.list", "messages.list", "messages.listHistorical"]);
32
+ mux.disableSource("cloud");
33
+ (0, chai_1.expect)([...mux.getCapabilities()].sort()).to.deep.equal(["channels.list", "messages.list"]);
34
+ (0, chai_1.expect)(mux.supports("messages.list")).to.equal(true);
35
+ (0, chai_1.expect)(mux.supports("messages.listHistorical")).to.equal(false);
36
+ });
37
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // src/test/multiplex-conflicts.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 aggMember(id, scope, value) {
8
+ const noop = new Proxy({}, { get: () => () => Promise.resolve(undefined) });
9
+ return {
10
+ agents: noop, channels: noop, messages: noop,
11
+ aggregates: { getAggregate: async () => ({ ...value, __source: { client: { id, kind: "x" }, retrievedAt: 0, via: { transport: "rest", method: "aggregates.getAggregate", request: {}, startedAt: 0, durationMs: 0 } } }) },
12
+ 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(["aggregates.get"]), supports: (c) => c === "aggregates.get",
15
+ isConnected: () => false, getStatus: () => ({ clientId: id, connected: false, state: "disconnected", agentScope: scope, at: Date.now() }),
16
+ onStatusChange: () => () => { },
17
+ getAgentScope: async () => scope, getKnownAgentScope: () => scope,
18
+ };
19
+ }
20
+ (0, mocha_1.describe)("MultiplexClient conflicts", () => {
21
+ (0, mocha_1.it)("getAggregate across two owning members → first wins, conflict emitted + snapshot recorded", async () => {
22
+ const factory = (d) => d.id === "cloud" ? aggMember("cloud", { mode: "all" }, { data: { v: 1 }, attachments: [] }) : aggMember("local:7", { mode: "list", agentIds: ["dev7"] }, { data: { v: 2 }, attachments: [] });
23
+ const mux = new multiplex_client_1.MultiplexClient({ factory, register: [{ id: "cloud", kind: "cloud" }, { id: "local:7", kind: "local" }], enableAll: true });
24
+ const events = [];
25
+ mux.on("conflict", (c) => events.push(c));
26
+ const agg = await mux.aggregates.getAggregate("dev7", "c");
27
+ (0, chai_1.expect)(agg.data.v).to.equal(1); // first member
28
+ (0, chai_1.expect)(agg.__source?.client.id).to.equal("cloud");
29
+ (0, chai_1.expect)(events).to.have.length(1);
30
+ const conflicts = mux.getLastConflicts();
31
+ (0, chai_1.expect)(conflicts).to.have.length(1);
32
+ (0, chai_1.expect)(conflicts[0].method).to.equal("aggregates.getAggregate");
33
+ (0, chai_1.expect)(conflicts[0].sourceIds.sort()).to.deep.equal(["cloud", "local:7"]);
34
+ });
35
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,103 @@
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
+ // A member with a controllable fake gateway.
7
+ function gwMember(id, scope) {
8
+ const subs = [];
9
+ let connected = false;
10
+ const channelHandlers = new Map();
11
+ const eventListeners = new Map();
12
+ const member = {
13
+ agents: {}, channels: {}, messages: {}, aggregates: {},
14
+ alarms: {}, connections: {}, notifications: {}, permissions: {},
15
+ processors: {}, turn: {}, users: {}, rpc: {},
16
+ gateway: {
17
+ connect: async () => { connected = true; }, disconnect: () => { connected = false; }, reconnect: async () => { },
18
+ on: (event, handler) => {
19
+ if (!eventListeners.has(event))
20
+ eventListeners.set(event, new Set());
21
+ eventListeners.get(event).add(handler);
22
+ },
23
+ off: (event, handler) => {
24
+ eventListeners.get(event)?.delete(handler);
25
+ },
26
+ subscribe: (c) => subs.push(`${c.agent_id}/${c.name}`),
27
+ unsubscribe: (c) => { const i = subs.indexOf(`${c.agent_id}/${c.name}`); if (i >= 0)
28
+ subs.splice(i, 1); },
29
+ subscribeToChannel: (c, h) => {
30
+ const k = `${c.agent_id}/${c.name}`;
31
+ if (!channelHandlers.has(k)) {
32
+ channelHandlers.set(k, new Set());
33
+ subs.push(k); // mirrors real GatewayClient: subscribeToChannel calls subscribe internally
34
+ }
35
+ channelHandlers.get(k).add(h);
36
+ return () => {
37
+ channelHandlers.get(k)?.delete(h);
38
+ if ((channelHandlers.get(k)?.size ?? 0) === 0) {
39
+ channelHandlers.delete(k);
40
+ const i = subs.indexOf(k);
41
+ if (i >= 0)
42
+ subs.splice(i, 1); // mirrors real GatewayClient: last unsub triggers unsubscribe
43
+ }
44
+ };
45
+ },
46
+ syncChannel: () => { }, sendOneShotMessage: () => { }, getSession: () => connected ? { session_id: `${id}-s` } : null,
47
+ isConnected: () => connected, getSubscriptionCount: () => subs.length, getSubscriptions: () => subs.map((s) => ({ agent_id: s.split("/")[0], name: s.split("/")[1] })), setStats: () => { },
48
+ },
49
+ getCapabilities: () => new Set(["gateway.subscribe", "gateway.realtime"]),
50
+ supports: (c) => c === "gateway.subscribe" || c === "gateway.realtime",
51
+ isConnected: () => connected, getStatus: () => ({ clientId: id, connected, state: connected ? "connected" : "disconnected", agentScope: scope, at: Date.now() }),
52
+ onStatusChange: () => () => { },
53
+ getAgentScope: async () => scope, getKnownAgentScope: () => scope,
54
+ };
55
+ return {
56
+ member, subs,
57
+ deliver(k, m) { channelHandlers.get(k)?.forEach((h) => h.onMessage?.(m)); },
58
+ emit(event, ...args) { eventListeners.get(event)?.forEach((h) => h(...args)); },
59
+ isConnected: () => connected,
60
+ };
61
+ }
62
+ (0, mocha_1.describe)("MultiplexGateway", () => {
63
+ (0, mocha_1.it)("subscribeToChannel routes to owning members; unsubscribe tears down; connect/isConnected aggregate", async () => {
64
+ const c = gwMember("cloud", { mode: "all" });
65
+ const l = gwMember("local:7", { mode: "list", agentIds: ["dev7"] });
66
+ const factory = (d) => d.id === "cloud" ? c.member : l.member;
67
+ const mux = new multiplex_client_1.MultiplexClient({ factory, register: [{ id: "cloud", kind: "cloud" }, { id: "local:7", kind: "local" }], enableAll: true });
68
+ await mux.gateway.connect();
69
+ (0, chai_1.expect)(mux.gateway.isConnected()).to.equal(true);
70
+ (0, chai_1.expect)(mux.isConnected()).to.equal(true);
71
+ let got;
72
+ const off = mux.gateway.subscribeToChannel({ agent_id: "dev7", name: "c1" }, { onMessage: (m) => { got = m; } });
73
+ // both cloud (all) and local:7 (owns dev7) subscribed
74
+ (0, chai_1.expect)(c.subs).to.deep.equal(["dev7/c1"]);
75
+ (0, chai_1.expect)(l.subs).to.deep.equal(["dev7/c1"]);
76
+ l.deliver("dev7/c1", { id: "m1" });
77
+ (0, chai_1.expect)(got).to.deep.equal({ id: "m1" });
78
+ off();
79
+ (0, chai_1.expect)(c.subs).to.deep.equal([]);
80
+ (0, chai_1.expect)(l.subs).to.deep.equal([]);
81
+ });
82
+ (0, mocha_1.it)("on/off forwards gateway events from all members and off deregisters", async () => {
83
+ const c = gwMember("cloud", { mode: "all" });
84
+ const l = gwMember("local:7", { mode: "list", agentIds: ["dev7"] });
85
+ const factory = (d) => d.id === "cloud" ? c.member : l.member;
86
+ const mux = new multiplex_client_1.MultiplexClient({ factory, register: [{ id: "cloud", kind: "cloud" }, { id: "local:7", kind: "local" }], enableAll: true });
87
+ let callCount = 0;
88
+ // open fires with no arguments; use a () => void signature to match the typed overload.
89
+ const handler = () => { callCount++; };
90
+ mux.gateway.on("open", handler);
91
+ // Emit from member A — handler should fire once.
92
+ c.emit("open");
93
+ (0, chai_1.expect)(callCount).to.equal(1);
94
+ // Emit from member B — handler should fire again.
95
+ l.emit("open");
96
+ (0, chai_1.expect)(callCount).to.equal(2);
97
+ // After off(), neither member triggers the handler.
98
+ mux.gateway.off("open", handler);
99
+ c.emit("open");
100
+ l.emit("open");
101
+ (0, chai_1.expect)(callCount).to.equal(2);
102
+ });
103
+ });
@@ -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 mocha_1 = require("mocha");
5
+ const multiplex_merge_1 = require("../client/multiplex-merge");
6
+ (0, mocha_1.describe)("multiplex merge helpers", () => {
7
+ (0, mocha_1.it)("dedupeBy keeps the first occurrence", () => {
8
+ const out = (0, multiplex_merge_1.dedupeBy)([{ id: "a", n: 1 }, { id: "b", n: 2 }, { id: "a", n: 3 }], (x) => x.id);
9
+ (0, chai_1.expect)(out).to.deep.equal([{ id: "a", n: 1 }, { id: "b", n: 2 }]);
10
+ });
11
+ (0, mocha_1.it)("mergeMessages: dedup by id, sort to requested order, re-apply limit", () => {
12
+ const a = [{ id: "5" }, { id: "3" }, { id: "1" }];
13
+ const b = [{ id: "4" }, { id: "3" }, { id: "2" }];
14
+ const desc = (0, multiplex_merge_1.mergeMessages)([a, b], { order: "desc", limit: 4 });
15
+ (0, chai_1.expect)(desc.map((m) => m.id)).to.deep.equal(["5", "4", "3", "2"]);
16
+ const asc = (0, multiplex_merge_1.mergeMessages)([a, b], { order: "asc", limit: 3 });
17
+ (0, chai_1.expect)(asc.map((m) => m.id)).to.deep.equal(["1", "2", "3"]);
18
+ });
19
+ });
@@ -0,0 +1 @@
1
+ export {};