doover-js 0.1.2 → 0.2.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 (84) hide show
  1. package/README.md +104 -10
  2. package/dist/apis/connections-api.d.ts +6 -0
  3. package/dist/apis/connections-api.js +8 -0
  4. package/dist/auth/auth-profile.d.ts +45 -0
  5. package/dist/auth/auth-profile.js +72 -0
  6. package/dist/auth/auth-store.d.ts +14 -0
  7. package/dist/auth/auth-store.js +2 -0
  8. package/dist/auth/build-auth.d.ts +37 -0
  9. package/dist/auth/build-auth.js +75 -0
  10. package/dist/auth/cookie-auth.d.ts +17 -0
  11. package/dist/auth/cookie-auth.js +29 -0
  12. package/dist/auth/doover-auth.d.ts +41 -0
  13. package/dist/auth/doover-auth.js +24 -0
  14. package/dist/auth/doover-token-auth.d.ts +35 -0
  15. package/dist/auth/doover-token-auth.js +178 -0
  16. package/dist/auth/errors.d.ts +3 -0
  17. package/dist/auth/errors.js +10 -0
  18. package/dist/auth/jwt.d.ts +6 -0
  19. package/dist/auth/jwt.js +29 -0
  20. package/dist/client/doover-client.d.ts +15 -0
  21. package/dist/client/doover-client.js +45 -4
  22. package/dist/client/singleton.d.ts +26 -0
  23. package/dist/client/singleton.js +59 -0
  24. package/dist/client/stats.d.ts +55 -0
  25. package/dist/client/stats.js +97 -0
  26. package/dist/gateway/gateway-client.d.ts +34 -8
  27. package/dist/gateway/gateway-client.js +147 -52
  28. package/dist/gateway/types.d.ts +0 -1
  29. package/dist/http/rest-client.d.ts +21 -3
  30. package/dist/http/rest-client.js +33 -2
  31. package/dist/index.d.ts +12 -0
  32. package/dist/index.js +23 -5
  33. package/dist/node/config-manager.d.ts +34 -0
  34. package/dist/node/config-manager.js +177 -0
  35. package/dist/node/index.d.ts +1 -0
  36. package/dist/node/index.js +5 -0
  37. package/dist/react/context.d.ts +19 -0
  38. package/dist/react/context.js +28 -0
  39. package/dist/react/index.d.ts +24 -0
  40. package/dist/react/index.js +40 -0
  41. package/dist/react/sharedQueryClient.d.ts +21 -0
  42. package/dist/react/sharedQueryClient.js +39 -0
  43. package/dist/react/useAgentChannel.d.ts +7 -0
  44. package/dist/react/useAgentChannel.js +12 -0
  45. package/dist/react/useAgentConnections.d.ts +8 -0
  46. package/dist/react/useAgentConnections.js +24 -0
  47. package/dist/react/useChannelAggregate.d.ts +13 -0
  48. package/dist/react/useChannelAggregate.js +38 -0
  49. package/dist/react/useChannelMessages.d.ts +23 -0
  50. package/dist/react/useChannelMessages.js +56 -0
  51. package/dist/react/useChannelSubscription.d.ts +16 -0
  52. package/dist/react/useChannelSubscription.js +43 -0
  53. package/dist/react/useConnectionState.d.ts +21 -0
  54. package/dist/react/useConnectionState.js +43 -0
  55. package/dist/react/useMultiAgentAggregates.d.ts +22 -0
  56. package/dist/react/useMultiAgentAggregates.js +82 -0
  57. package/dist/react/useMultiAgentChannelMessages.d.ts +18 -0
  58. package/dist/react/useMultiAgentChannelMessages.js +78 -0
  59. package/dist/react/useSendMessage.d.ts +9 -0
  60. package/dist/react/useSendMessage.js +16 -0
  61. package/dist/react/useSendRpc.d.ts +61 -0
  62. package/dist/react/useSendRpc.js +158 -0
  63. package/dist/react/useTurnCredentials.d.ts +12 -0
  64. package/dist/react/useTurnCredentials.js +25 -0
  65. package/dist/react/useUpdateAggregate.d.ts +14 -0
  66. package/dist/react/useUpdateAggregate.js +25 -0
  67. package/dist/react/useUpdateMessage.d.ts +17 -0
  68. package/dist/react/useUpdateMessage.js +24 -0
  69. package/dist/test/apis.test.js +7 -1
  70. package/dist/test/auth.test.d.ts +1 -0
  71. package/dist/test/auth.test.js +502 -0
  72. package/dist/test/config-manager.test.d.ts +1 -0
  73. package/dist/test/config-manager.test.js +160 -0
  74. package/dist/test/doover-client.test.js +13 -0
  75. package/dist/test/doover-data-provider.test.js +155 -0
  76. package/dist/test/gateway-client.test.js +122 -8
  77. package/dist/test/react.test.d.ts +1 -0
  78. package/dist/test/react.test.js +353 -0
  79. package/dist/test/singleton.test.d.ts +1 -0
  80. package/dist/test/singleton.test.js +62 -0
  81. package/dist/types/common.d.ts +40 -0
  82. package/dist/viewer/doover-data-provider.d.ts +34 -5
  83. package/dist/viewer/doover-data-provider.js +96 -9
  84. package/package.json +33 -2
@@ -0,0 +1,177 @@
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
+ exports.ConfigManager = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const os = __importStar(require("os"));
40
+ const auth_profile_1 = require("../auth/auth-profile");
41
+ const errors_1 = require("../auth/errors");
42
+ /**
43
+ * File-backed profile store compatible with pydoover's config format.
44
+ *
45
+ * Reads `~/.doover/config` (or a custom path) synchronously on construction,
46
+ * matching Python's eager-read model.
47
+ *
48
+ * Behaviour:
49
+ * - Doover2 `[profile=...]` blocks are parsed into `AuthProfile` entries.
50
+ * - Legacy Doover1 `[...]` blocks and unrecognised raw blocks are preserved
51
+ * verbatim in their original order and written back unchanged.
52
+ * - `write()` creates the `~/.doover` directory if missing.
53
+ */
54
+ class ConfigManager {
55
+ constructor(configPath) {
56
+ this.currentProfile = null;
57
+ this.current = null;
58
+ /** Parsed Doover2 profiles keyed by profile name. */
59
+ this.profiles = new Map();
60
+ /**
61
+ * Ordered list of *blocks* in the file as they were originally read. Each
62
+ * entry is either a `{ type: "managed"; name: string }` reference into
63
+ * `this.profiles`, or a `{ type: "raw"; text: string }` blob that is
64
+ * preserved verbatim.
65
+ */
66
+ this.blocks = [];
67
+ this.configPath =
68
+ configPath ?? path.join(os.homedir(), ".doover", "config");
69
+ this.read();
70
+ }
71
+ // ------------------------------------------------------------------
72
+ // AuthProfileStore interface
73
+ // ------------------------------------------------------------------
74
+ get(profileName) {
75
+ return this.profiles.get(profileName);
76
+ }
77
+ create(entry) {
78
+ const existing = this.profiles.has(entry.profile);
79
+ this.profiles.set(entry.profile, entry);
80
+ if (!existing) {
81
+ this.blocks.push({ type: "managed", name: entry.profile });
82
+ }
83
+ }
84
+ delete(profileName) {
85
+ this.profiles.delete(profileName);
86
+ this.blocks = this.blocks.filter((b) => !(b.type === "managed" && b.name === profileName));
87
+ if (this.currentProfile === profileName) {
88
+ this.currentProfile = null;
89
+ this.current = null;
90
+ }
91
+ }
92
+ write() {
93
+ const dir = path.dirname(this.configPath);
94
+ try {
95
+ fs.mkdirSync(dir, { recursive: true });
96
+ }
97
+ catch (err) {
98
+ throw new errors_1.DooverAuthError(`Failed to create config directory '${dir}': ${err instanceof Error ? err.message : String(err)}`);
99
+ }
100
+ const sections = [];
101
+ for (const block of this.blocks) {
102
+ if (block.type === "raw") {
103
+ sections.push(block.text);
104
+ }
105
+ else {
106
+ const profile = this.profiles.get(block.name);
107
+ if (profile) {
108
+ sections.push(profile.dumpBlock());
109
+ }
110
+ }
111
+ }
112
+ try {
113
+ fs.writeFileSync(this.configPath, sections.join("\n\n") + "\n", "utf-8");
114
+ }
115
+ catch (err) {
116
+ throw new errors_1.DooverAuthError(`Failed to write config file '${this.configPath}': ${err instanceof Error ? err.message : String(err)}`);
117
+ }
118
+ }
119
+ // ------------------------------------------------------------------
120
+ // Internal parsing
121
+ // ------------------------------------------------------------------
122
+ read() {
123
+ let content;
124
+ try {
125
+ content = fs.readFileSync(this.configPath, "utf-8");
126
+ }
127
+ catch {
128
+ // File does not exist — start empty.
129
+ return;
130
+ }
131
+ this.profiles.clear();
132
+ this.blocks = [];
133
+ const lines = content.split(/\r?\n/);
134
+ let i = 0;
135
+ while (i < lines.length) {
136
+ const line = lines[i];
137
+ const profileMatch = line.match(/^\[profile=(.+)\]$/);
138
+ if (profileMatch) {
139
+ // Doover2 managed block
140
+ const profileName = profileMatch[1];
141
+ const bodyLines = [];
142
+ i++;
143
+ while (i < lines.length && !lines[i].startsWith("[")) {
144
+ bodyLines.push(lines[i]);
145
+ i++;
146
+ }
147
+ const profile = auth_profile_1.AuthProfile.parse(profileName, bodyLines);
148
+ this.profiles.set(profileName, profile);
149
+ this.blocks.push({ type: "managed", name: profileName });
150
+ }
151
+ else if (line.match(/^\[.+\]$/) && !line.match(/^\[profile=/)) {
152
+ // Legacy Doover1 block — preserve verbatim.
153
+ const rawLines = [line];
154
+ i++;
155
+ while (i < lines.length && !lines[i].startsWith("[")) {
156
+ rawLines.push(lines[i]);
157
+ i++;
158
+ }
159
+ this.blocks.push({ type: "raw", text: rawLines.join("\n").trimEnd() });
160
+ }
161
+ else {
162
+ // Stray line outside any block — preserve as raw.
163
+ if (line.trim()) {
164
+ this.blocks.push({ type: "raw", text: line });
165
+ }
166
+ i++;
167
+ }
168
+ }
169
+ // Set default current profile to the first managed profile.
170
+ if (this.profiles.size > 0) {
171
+ const firstName = this.profiles.keys().next().value;
172
+ this.currentProfile = firstName;
173
+ this.current = this.profiles.get(firstName);
174
+ }
175
+ }
176
+ }
177
+ exports.ConfigManager = ConfigManager;
@@ -0,0 +1 @@
1
+ export { ConfigManager } from "./config-manager";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConfigManager = void 0;
4
+ var config_manager_1 = require("./config-manager");
5
+ Object.defineProperty(exports, "ConfigManager", { enumerable: true, get: function () { return config_manager_1.ConfigManager; } });
@@ -0,0 +1,19 @@
1
+ import { type ReactNode } from "react";
2
+ import { DooverClient } from "../client/doover-client";
3
+ export interface DooverProviderProps {
4
+ client: DooverClient;
5
+ children: ReactNode;
6
+ }
7
+ /**
8
+ * Wraps a React subtree with a `DooverClient` instance so downstream hooks
9
+ * (`useDooverClient`, `useChannelAggregate`, etc.) can read it from context.
10
+ *
11
+ * Pair with a `@tanstack/react-query` `<QueryClientProvider>` in the same tree —
12
+ * the hooks call `useQueryClient()` internally and will throw without one.
13
+ */
14
+ export declare function DooverProvider({ client, children }: DooverProviderProps): import("react/jsx-runtime").JSX.Element;
15
+ /**
16
+ * Read the `DooverClient` from context. Throws if called outside a
17
+ * `<DooverProvider>`.
18
+ */
19
+ export declare function useDooverClient(): DooverClient;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DooverProvider = DooverProvider;
4
+ exports.useDooverClient = useDooverClient;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const react_1 = require("react");
7
+ const DooverClientContext = (0, react_1.createContext)(null);
8
+ /**
9
+ * Wraps a React subtree with a `DooverClient` instance so downstream hooks
10
+ * (`useDooverClient`, `useChannelAggregate`, etc.) can read it from context.
11
+ *
12
+ * Pair with a `@tanstack/react-query` `<QueryClientProvider>` in the same tree —
13
+ * the hooks call `useQueryClient()` internally and will throw without one.
14
+ */
15
+ function DooverProvider({ client, children }) {
16
+ return ((0, jsx_runtime_1.jsx)(DooverClientContext.Provider, { value: client, children: children }));
17
+ }
18
+ /**
19
+ * Read the `DooverClient` from context. Throws if called outside a
20
+ * `<DooverProvider>`.
21
+ */
22
+ function useDooverClient() {
23
+ const client = (0, react_1.useContext)(DooverClientContext);
24
+ if (!client) {
25
+ throw new Error("useDooverClient must be called inside a <DooverProvider>.");
26
+ }
27
+ return client;
28
+ }
@@ -0,0 +1,24 @@
1
+ export { DooverProvider, useDooverClient } from "./context";
2
+ export type { DooverProviderProps } from "./context";
3
+ export { useConnectionState } from "./useConnectionState";
4
+ export type { ConnectionState, ConnectionStatus } from "./useConnectionState";
5
+ export { useAgentConnections, agentConnectionsQueryKey, } from "./useAgentConnections";
6
+ export { useChannelSubscription, } from "./useChannelSubscription";
7
+ export type { ChannelSubscriptionHandlers } from "./useChannelSubscription";
8
+ export { useChannelAggregate, channelAggregateQueryKey, } from "./useChannelAggregate";
9
+ export { useAgentChannel } from "./useAgentChannel";
10
+ export { useSendMessage } from "./useSendMessage";
11
+ export { useUpdateAggregate } from "./useUpdateAggregate";
12
+ export type { UseUpdateAggregateOptions } from "./useUpdateAggregate";
13
+ export { useUpdateMessage } from "./useUpdateMessage";
14
+ export type { UpdateMessageVariables, UseUpdateMessageOptions, } from "./useUpdateMessage";
15
+ export { useChannelMessages, channelMessagesQueryKey, } from "./useChannelMessages";
16
+ export type { UseChannelMessagesOptions, UseChannelMessagesResult, } from "./useChannelMessages";
17
+ export { useSendRpc } from "./useSendRpc";
18
+ export type { RpcCommandId, RpcStatusEvent, SendRpcVariables, UseSendRpcOptions, UseSendRpcResult, } from "./useSendRpc";
19
+ export { useMultiAgentAggregates, multiAgentAggregatesQueryKey, } from "./useMultiAgentAggregates";
20
+ export type { UseMultiAgentAggregatesOptions, UseMultiAgentAggregatesResult, } from "./useMultiAgentAggregates";
21
+ export { useMultiAgentChannelMessages, multiAgentChannelMessagesQueryKey, } from "./useMultiAgentChannelMessages";
22
+ export type { UseMultiAgentChannelMessagesOptions, UseMultiAgentChannelMessagesResult, } from "./useMultiAgentChannelMessages";
23
+ export { useTurnCredentials } from "./useTurnCredentials";
24
+ export { getSharedQueryClient, resetSharedQueryClient, } from "./sharedQueryClient";
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resetSharedQueryClient = exports.getSharedQueryClient = exports.useTurnCredentials = exports.multiAgentChannelMessagesQueryKey = exports.useMultiAgentChannelMessages = exports.multiAgentAggregatesQueryKey = exports.useMultiAgentAggregates = exports.useSendRpc = exports.channelMessagesQueryKey = exports.useChannelMessages = exports.useUpdateMessage = exports.useUpdateAggregate = exports.useSendMessage = exports.useAgentChannel = exports.channelAggregateQueryKey = exports.useChannelAggregate = exports.useChannelSubscription = exports.agentConnectionsQueryKey = exports.useAgentConnections = exports.useConnectionState = exports.useDooverClient = exports.DooverProvider = void 0;
4
+ var context_1 = require("./context");
5
+ Object.defineProperty(exports, "DooverProvider", { enumerable: true, get: function () { return context_1.DooverProvider; } });
6
+ Object.defineProperty(exports, "useDooverClient", { enumerable: true, get: function () { return context_1.useDooverClient; } });
7
+ var useConnectionState_1 = require("./useConnectionState");
8
+ Object.defineProperty(exports, "useConnectionState", { enumerable: true, get: function () { return useConnectionState_1.useConnectionState; } });
9
+ var useAgentConnections_1 = require("./useAgentConnections");
10
+ Object.defineProperty(exports, "useAgentConnections", { enumerable: true, get: function () { return useAgentConnections_1.useAgentConnections; } });
11
+ Object.defineProperty(exports, "agentConnectionsQueryKey", { enumerable: true, get: function () { return useAgentConnections_1.agentConnectionsQueryKey; } });
12
+ var useChannelSubscription_1 = require("./useChannelSubscription");
13
+ Object.defineProperty(exports, "useChannelSubscription", { enumerable: true, get: function () { return useChannelSubscription_1.useChannelSubscription; } });
14
+ var useChannelAggregate_1 = require("./useChannelAggregate");
15
+ Object.defineProperty(exports, "useChannelAggregate", { enumerable: true, get: function () { return useChannelAggregate_1.useChannelAggregate; } });
16
+ Object.defineProperty(exports, "channelAggregateQueryKey", { enumerable: true, get: function () { return useChannelAggregate_1.channelAggregateQueryKey; } });
17
+ var useAgentChannel_1 = require("./useAgentChannel");
18
+ Object.defineProperty(exports, "useAgentChannel", { enumerable: true, get: function () { return useAgentChannel_1.useAgentChannel; } });
19
+ var useSendMessage_1 = require("./useSendMessage");
20
+ Object.defineProperty(exports, "useSendMessage", { enumerable: true, get: function () { return useSendMessage_1.useSendMessage; } });
21
+ var useUpdateAggregate_1 = require("./useUpdateAggregate");
22
+ Object.defineProperty(exports, "useUpdateAggregate", { enumerable: true, get: function () { return useUpdateAggregate_1.useUpdateAggregate; } });
23
+ var useUpdateMessage_1 = require("./useUpdateMessage");
24
+ Object.defineProperty(exports, "useUpdateMessage", { enumerable: true, get: function () { return useUpdateMessage_1.useUpdateMessage; } });
25
+ var useChannelMessages_1 = require("./useChannelMessages");
26
+ Object.defineProperty(exports, "useChannelMessages", { enumerable: true, get: function () { return useChannelMessages_1.useChannelMessages; } });
27
+ Object.defineProperty(exports, "channelMessagesQueryKey", { enumerable: true, get: function () { return useChannelMessages_1.channelMessagesQueryKey; } });
28
+ var useSendRpc_1 = require("./useSendRpc");
29
+ Object.defineProperty(exports, "useSendRpc", { enumerable: true, get: function () { return useSendRpc_1.useSendRpc; } });
30
+ var useMultiAgentAggregates_1 = require("./useMultiAgentAggregates");
31
+ Object.defineProperty(exports, "useMultiAgentAggregates", { enumerable: true, get: function () { return useMultiAgentAggregates_1.useMultiAgentAggregates; } });
32
+ Object.defineProperty(exports, "multiAgentAggregatesQueryKey", { enumerable: true, get: function () { return useMultiAgentAggregates_1.multiAgentAggregatesQueryKey; } });
33
+ var useMultiAgentChannelMessages_1 = require("./useMultiAgentChannelMessages");
34
+ Object.defineProperty(exports, "useMultiAgentChannelMessages", { enumerable: true, get: function () { return useMultiAgentChannelMessages_1.useMultiAgentChannelMessages; } });
35
+ Object.defineProperty(exports, "multiAgentChannelMessagesQueryKey", { enumerable: true, get: function () { return useMultiAgentChannelMessages_1.multiAgentChannelMessagesQueryKey; } });
36
+ var useTurnCredentials_1 = require("./useTurnCredentials");
37
+ Object.defineProperty(exports, "useTurnCredentials", { enumerable: true, get: function () { return useTurnCredentials_1.useTurnCredentials; } });
38
+ var sharedQueryClient_1 = require("./sharedQueryClient");
39
+ Object.defineProperty(exports, "getSharedQueryClient", { enumerable: true, get: function () { return sharedQueryClient_1.getSharedQueryClient; } });
40
+ Object.defineProperty(exports, "resetSharedQueryClient", { enumerable: true, get: function () { return sharedQueryClient_1.resetSharedQueryClient; } });
@@ -0,0 +1,21 @@
1
+ import { QueryClient, type QueryClientConfig } from "@tanstack/react-query";
2
+ /**
3
+ * Get a process-wide `QueryClient` instance, creating it on first call.
4
+ *
5
+ * Uses `globalThis.__doover_js_query_client__` so that every app + remote
6
+ * in a module-federation setup ends up with the same `QueryClient` — and
7
+ * therefore the same cache — even if they each load their own copy of the
8
+ * react-query library. The first caller's `config` wins.
9
+ *
10
+ * Wrap your tree once at the app root:
11
+ *
12
+ * ```tsx
13
+ * import { QueryClientProvider } from "@tanstack/react-query";
14
+ * import { getSharedQueryClient } from "doover-js/react";
15
+ *
16
+ * <QueryClientProvider client={getSharedQueryClient()}>…</QueryClientProvider>
17
+ * ```
18
+ */
19
+ export declare function getSharedQueryClient(config?: QueryClientConfig): QueryClient;
20
+ /** Test-only: clear the shared `QueryClient`. */
21
+ export declare function resetSharedQueryClient(): void;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSharedQueryClient = getSharedQueryClient;
4
+ exports.resetSharedQueryClient = resetSharedQueryClient;
5
+ const react_query_1 = require("@tanstack/react-query");
6
+ const GLOBAL_KEY = "__doover_js_query_client__";
7
+ function globalBag() {
8
+ return globalThis;
9
+ }
10
+ /**
11
+ * Get a process-wide `QueryClient` instance, creating it on first call.
12
+ *
13
+ * Uses `globalThis.__doover_js_query_client__` so that every app + remote
14
+ * in a module-federation setup ends up with the same `QueryClient` — and
15
+ * therefore the same cache — even if they each load their own copy of the
16
+ * react-query library. The first caller's `config` wins.
17
+ *
18
+ * Wrap your tree once at the app root:
19
+ *
20
+ * ```tsx
21
+ * import { QueryClientProvider } from "@tanstack/react-query";
22
+ * import { getSharedQueryClient } from "doover-js/react";
23
+ *
24
+ * <QueryClientProvider client={getSharedQueryClient()}>…</QueryClientProvider>
25
+ * ```
26
+ */
27
+ function getSharedQueryClient(config) {
28
+ const bag = globalBag();
29
+ const existing = bag[GLOBAL_KEY];
30
+ if (existing)
31
+ return existing;
32
+ const client = new react_query_1.QueryClient(config);
33
+ bag[GLOBAL_KEY] = client;
34
+ return client;
35
+ }
36
+ /** Test-only: clear the shared `QueryClient`. */
37
+ function resetSharedQueryClient() {
38
+ delete globalBag()[GLOBAL_KEY];
39
+ }
@@ -0,0 +1,7 @@
1
+ import type { Aggregate } from "../types/common";
2
+ /**
3
+ * Convenience wrapper around `useChannelAggregate` that accepts an agent id
4
+ * and channel name as separate arguments — the most common shape at call
5
+ * sites where the identifier isn't already a `ChannelIdentifier`.
6
+ */
7
+ export declare function useAgentChannel<TData extends Aggregate["data"] = Aggregate["data"]>(agentId: string | undefined, channelName: string): import("@tanstack/react-query").UseQueryResult<Aggregate<TData> | undefined>;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useAgentChannel = useAgentChannel;
4
+ const useChannelAggregate_1 = require("./useChannelAggregate");
5
+ /**
6
+ * Convenience wrapper around `useChannelAggregate` that accepts an agent id
7
+ * and channel name as separate arguments — the most common shape at call
8
+ * sites where the identifier isn't already a `ChannelIdentifier`.
9
+ */
10
+ function useAgentChannel(agentId, channelName) {
11
+ return (0, useChannelAggregate_1.useChannelAggregate)({ agentId, channelName });
12
+ }
@@ -0,0 +1,8 @@
1
+ import { type UseQueryResult } from "@tanstack/react-query";
2
+ import type { ConnectionDetails } from "../types/common";
3
+ export declare function agentConnectionsQueryKey(agentId: string | undefined): readonly ["doover", "agent", string | undefined, "connections"];
4
+ /**
5
+ * Fetch the list of active WSS connections for an agent. Query key:
6
+ * `["doover", "agent", agentId, "connections"]`.
7
+ */
8
+ export declare function useAgentConnections(agentId: string | undefined): UseQueryResult<ConnectionDetails[]>;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.agentConnectionsQueryKey = agentConnectionsQueryKey;
4
+ exports.useAgentConnections = useAgentConnections;
5
+ const react_query_1 = require("@tanstack/react-query");
6
+ const context_1 = require("./context");
7
+ function agentConnectionsQueryKey(agentId) {
8
+ return ["doover", "agent", agentId, "connections"];
9
+ }
10
+ /**
11
+ * Fetch the list of active WSS connections for an agent. Query key:
12
+ * `["doover", "agent", agentId, "connections"]`.
13
+ */
14
+ function useAgentConnections(agentId) {
15
+ const client = (0, context_1.useDooverClient)();
16
+ return (0, react_query_1.useQuery)({
17
+ queryKey: agentConnectionsQueryKey(agentId),
18
+ enabled: !!agentId,
19
+ staleTime: 30000,
20
+ queryFn: () => agentId
21
+ ? client.connections.getAgentConnections(agentId)
22
+ : Promise.resolve([]),
23
+ });
24
+ }
@@ -0,0 +1,13 @@
1
+ import { type UseQueryResult } from "@tanstack/react-query";
2
+ import type { Aggregate } from "../types/common";
3
+ import type { ChannelIdentifier } from "../types/viewer";
4
+ export declare function channelAggregateQueryKey(agentId: string | undefined, channelName: string | undefined): readonly ["doover", "agent", string | undefined, "channel", string | undefined];
5
+ /**
6
+ * Fetch a channel's aggregate and keep it live via the gateway. Incoming
7
+ * `channelSync` and `aggregateUpdate` events patch the react-query cache
8
+ * under `channelAggregateQueryKey(agentId, channelName)`.
9
+ *
10
+ * Typed on `TData` — the shape of `aggregate.data`. Defaults to the generic
11
+ * `Record<string, JSONValue>` used by `Aggregate`.
12
+ */
13
+ export declare function useChannelAggregate<TData extends Aggregate["data"] = Aggregate["data"]>(identifier: ChannelIdentifier): UseQueryResult<Aggregate<TData> | undefined>;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.channelAggregateQueryKey = channelAggregateQueryKey;
4
+ exports.useChannelAggregate = useChannelAggregate;
5
+ const react_1 = require("react");
6
+ const react_query_1 = require("@tanstack/react-query");
7
+ const context_1 = require("./context");
8
+ const useChannelSubscription_1 = require("./useChannelSubscription");
9
+ function channelAggregateQueryKey(agentId, channelName) {
10
+ return ["doover", "agent", agentId, "channel", channelName];
11
+ }
12
+ /**
13
+ * Fetch a channel's aggregate and keep it live via the gateway. Incoming
14
+ * `channelSync` and `aggregateUpdate` events patch the react-query cache
15
+ * under `channelAggregateQueryKey(agentId, channelName)`.
16
+ *
17
+ * Typed on `TData` — the shape of `aggregate.data`. Defaults to the generic
18
+ * `Record<string, JSONValue>` used by `Aggregate`.
19
+ */
20
+ function useChannelAggregate(identifier) {
21
+ const client = (0, context_1.useDooverClient)();
22
+ const queryClient = (0, react_query_1.useQueryClient)();
23
+ const { agentId, channelName } = identifier;
24
+ const key = channelAggregateQueryKey(agentId, channelName);
25
+ const onAggregate = (0, react_1.useCallback)((aggregate) => {
26
+ queryClient.setQueryData(key, aggregate);
27
+ },
28
+ // The key array is structurally stable per (agentId, channelName).
29
+ // eslint-disable-next-line react-hooks/exhaustive-deps
30
+ [queryClient, agentId, channelName]);
31
+ (0, useChannelSubscription_1.useChannelSubscription)(identifier, { onAggregate });
32
+ return (0, react_query_1.useQuery)({
33
+ queryKey: key,
34
+ enabled: !!agentId && !!channelName,
35
+ staleTime: Infinity,
36
+ queryFn: () => client.viewer.getAggregate(identifier),
37
+ });
38
+ }
@@ -0,0 +1,23 @@
1
+ import { type InfiniteData, type UseInfiniteQueryResult } from "@tanstack/react-query";
2
+ import type { MessageStructure } from "../types/common";
3
+ import type { ChannelIdentifier } from "../types/viewer";
4
+ export declare function channelMessagesQueryKey(agentId: string | undefined, channelName: string | undefined): readonly ["doover", "agent", string | undefined, "channel", string | undefined, "messages"];
5
+ export interface UseChannelMessagesOptions {
6
+ limit?: number;
7
+ /** If false, skip subscribing for live message-create updates. Defaults true. */
8
+ liveUpdates?: boolean;
9
+ }
10
+ type Page<TData> = MessageStructure<TData>[];
11
+ export interface UseChannelMessagesResult<TData> extends Omit<UseInfiniteQueryResult<InfiniteData<Page<TData>>>, "data"> {
12
+ /** Flat chronological list across all loaded pages. */
13
+ messages: MessageStructure<TData>[];
14
+ /** Raw react-query data (pages). */
15
+ data: InfiniteData<Page<TData>> | undefined;
16
+ }
17
+ /**
18
+ * Paginated infinite query over `DooverDataProvider.getMessages`, with live
19
+ * `messageCreate` pushes prepended/appended to the newest page. The "next"
20
+ * page fetches older messages (cursor = oldest-loaded message id).
21
+ */
22
+ export declare function useChannelMessages<TData = unknown>(identifier: ChannelIdentifier, options?: UseChannelMessagesOptions): UseChannelMessagesResult<TData>;
23
+ export {};
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.channelMessagesQueryKey = channelMessagesQueryKey;
4
+ exports.useChannelMessages = useChannelMessages;
5
+ const react_1 = require("react");
6
+ const react_query_1 = require("@tanstack/react-query");
7
+ const context_1 = require("./context");
8
+ const useChannelSubscription_1 = require("./useChannelSubscription");
9
+ function channelMessagesQueryKey(agentId, channelName) {
10
+ return ["doover", "agent", agentId, "channel", channelName, "messages"];
11
+ }
12
+ /**
13
+ * Paginated infinite query over `DooverDataProvider.getMessages`, with live
14
+ * `messageCreate` pushes prepended/appended to the newest page. The "next"
15
+ * page fetches older messages (cursor = oldest-loaded message id).
16
+ */
17
+ function useChannelMessages(identifier, options) {
18
+ const client = (0, context_1.useDooverClient)();
19
+ const queryClient = (0, react_query_1.useQueryClient)();
20
+ const { agentId, channelName } = identifier;
21
+ const limit = options?.limit;
22
+ const liveUpdates = options?.liveUpdates ?? true;
23
+ const key = channelMessagesQueryKey(agentId, channelName);
24
+ const onMessage = (0, react_1.useCallback)((message) => {
25
+ queryClient.setQueryData(key, (current) => {
26
+ if (!current)
27
+ return current;
28
+ const typed = message;
29
+ // Newest page is index 0 — prepend there.
30
+ const [firstPage, ...rest] = current.pages;
31
+ return {
32
+ ...current,
33
+ pages: [[...(firstPage ?? []), typed], ...rest],
34
+ };
35
+ });
36
+ },
37
+ // eslint-disable-next-line react-hooks/exhaustive-deps
38
+ [queryClient, agentId, channelName]);
39
+ (0, useChannelSubscription_1.useChannelSubscription)(liveUpdates ? identifier : undefined, { onMessage });
40
+ const query = (0, react_query_1.useInfiniteQuery)({
41
+ queryKey: key,
42
+ enabled: !!agentId && !!channelName,
43
+ staleTime: Infinity,
44
+ initialPageParam: undefined,
45
+ getNextPageParam: (lastPage) => lastPage && lastPage.length > 0 ? lastPage[0]?.id : undefined,
46
+ queryFn: async ({ pageParam }) => {
47
+ const page = await client.viewer.getMessages(identifier, {
48
+ ...(typeof pageParam === "string" ? { before: pageParam } : {}),
49
+ ...(limit !== undefined ? { limit } : {}),
50
+ });
51
+ return (page ?? []);
52
+ },
53
+ });
54
+ const messages = (0, react_1.useMemo)(() => (query.data?.pages ?? []).flat(), [query.data]);
55
+ return { ...query, messages };
56
+ }
@@ -0,0 +1,16 @@
1
+ import type { Aggregate, MessageStructure } from "../types/common";
2
+ import type { ChannelIdentifier } from "../types/viewer";
3
+ export interface ChannelSubscriptionHandlers {
4
+ onMessage?: (message: MessageStructure) => void;
5
+ onMessageUpdate?: (message: MessageStructure) => void;
6
+ onAggregate?: (aggregate: Aggregate) => void;
7
+ }
8
+ /**
9
+ * Attach live-update handlers to a channel for the lifetime of the host
10
+ * component. Wraps `DooverDataProvider.subscribeToChannel` with ref-counted
11
+ * subscribe/unsubscribe and handler refs so changes to the handler closures
12
+ * don't re-trigger the underlying subscribe.
13
+ *
14
+ * Passing `undefined` (or an identifier without both fields) is a no-op.
15
+ */
16
+ export declare function useChannelSubscription(identifier: ChannelIdentifier | undefined, handlers: ChannelSubscriptionHandlers): void;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useChannelSubscription = useChannelSubscription;
4
+ const react_1 = require("react");
5
+ const context_1 = require("./context");
6
+ /**
7
+ * Attach live-update handlers to a channel for the lifetime of the host
8
+ * component. Wraps `DooverDataProvider.subscribeToChannel` with ref-counted
9
+ * subscribe/unsubscribe and handler refs so changes to the handler closures
10
+ * don't re-trigger the underlying subscribe.
11
+ *
12
+ * Passing `undefined` (or an identifier without both fields) is a no-op.
13
+ */
14
+ function useChannelSubscription(identifier, handlers) {
15
+ const client = (0, context_1.useDooverClient)();
16
+ const handlersRef = (0, react_1.useRef)(handlers);
17
+ handlersRef.current = handlers;
18
+ (0, react_1.useEffect)(() => {
19
+ if (!identifier?.agentId || !identifier?.channelName)
20
+ return;
21
+ const messageCallback = (_id, message) => {
22
+ handlersRef.current.onMessage?.(message);
23
+ };
24
+ const aggregateCallback = (_id, aggregate) => {
25
+ handlersRef.current.onAggregate?.(aggregate);
26
+ };
27
+ const messageUpdateCallback = (_id, message) => {
28
+ handlersRef.current.onMessageUpdate?.(message);
29
+ };
30
+ void client.viewer.subscribeToChannel(identifier, messageCallback, aggregateCallback, messageUpdateCallback);
31
+ return () => {
32
+ client.viewer
33
+ .unsubscribeFromChannel(identifier, messageCallback)
34
+ .catch(() => {
35
+ // Already unsubscribed — race between strict-mode double-effect
36
+ // and async subscribe is harmless.
37
+ });
38
+ };
39
+ // Identifier fields are the real deps; we intentionally leave `handlers`
40
+ // out because it's read through handlersRef.
41
+ // eslint-disable-next-line react-hooks/exhaustive-deps
42
+ }, [client, identifier?.agentId, identifier?.channelName]);
43
+ }
@@ -0,0 +1,21 @@
1
+ export type ConnectionStatus = "connecting" | "open";
2
+ export interface ConnectionState {
3
+ /**
4
+ * `"open"` once the gateway has received a `Ready` frame;
5
+ * `"connecting"` any time the socket is reconnecting or awaiting Ready.
6
+ */
7
+ status: ConnectionStatus;
8
+ /** Timestamp of the most recent `Ready` frame, or `null` until the first one. */
9
+ lastOpenedAt: number | null;
10
+ }
11
+ /**
12
+ * Subscribes to the gateway's connection lifecycle and returns a snapshot
13
+ * suitable for a connection-indicator UI.
14
+ *
15
+ * The gateway doesn't measure application-level latency — liveness is
16
+ * handled at the WebSocket-protocol layer via ping/pong frames (opcodes
17
+ * 0x9 / 0xA) which browsers answer automatically but don't expose to JS.
18
+ * Dead connections surface as the `close` event, which we already handle
19
+ * via auto-reconnect.
20
+ */
21
+ export declare function useConnectionState(): ConnectionState;