doover-js 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) 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 +16 -0
  150. package/dist/types/provenance.d.ts +45 -0
  151. package/dist/types/provenance.js +2 -0
  152. package/dist/types/viewer.d.ts +3 -0
  153. package/dist/viewer/doover-data-provider.d.ts +30 -34
  154. package/dist/viewer/doover-data-provider.js +82 -316
  155. package/package.json +1 -1
@@ -0,0 +1,49 @@
1
+ import type { GatewayClientLike } from "./data-client";
2
+ import type { ChannelRef } from "../types/common";
3
+ import type { WebSocketSession } from "../gateway/types";
4
+ /** Minimal view of what MultiplexGateway needs from the owning multiplex. */
5
+ export interface MultiplexGatewayHost {
6
+ /** Enabled members with `gateway.subscribe`. */
7
+ gatewayMembers(): Array<{
8
+ id: string;
9
+ gateway: GatewayClientLike;
10
+ }>;
11
+ /** Enabled members owning `agentId` that have `gateway.subscribe`. */
12
+ gatewayMembersForAgent(agentId: string): Array<{
13
+ id: string;
14
+ gateway: GatewayClientLike;
15
+ }>;
16
+ }
17
+ /**
18
+ * `GatewayClientLike` facade over the members' gateways:
19
+ * - connect/disconnect/reconnect → fan out to members with `gateway.subscribe`.
20
+ * - subscribe/unsubscribe/subscribeToChannel → route to owning members.
21
+ * - on/off → register against every member; events forwarded.
22
+ * - isConnected → all members with `gateway.subscribe` are connected.
23
+ * - getSession → first connected member's session (locked decision #5).
24
+ * - getSubscriptions/getSubscriptionCount → union across members.
25
+ */
26
+ export declare class MultiplexGateway implements GatewayClientLike {
27
+ private readonly host;
28
+ /** consumer event handler → set of member gateways it was registered on. */
29
+ private readonly eventBindings;
30
+ constructor(host: MultiplexGatewayHost);
31
+ setStats(): void;
32
+ setProvenanceHook(): void;
33
+ connect(): Promise<void>;
34
+ disconnect(code?: number, reason?: string): void;
35
+ reconnect(): Promise<void>;
36
+ on(event: string, handler: (...a: unknown[]) => void): void;
37
+ off(event: string, handler: (...a: unknown[]) => void): void;
38
+ subscribe(channel: ChannelRef, options?: {
39
+ diff_only?: boolean;
40
+ }): void;
41
+ unsubscribe(channel: ChannelRef): void;
42
+ subscribeToChannel(channel: ChannelRef, handlers: Record<string, (...a: unknown[]) => void>): () => void;
43
+ syncChannel(channel: ChannelRef): void;
44
+ sendOneShotMessage(channel: ChannelRef, data: unknown): void;
45
+ getSession(): WebSocketSession | null;
46
+ isConnected(): boolean;
47
+ getSubscriptionCount(): number;
48
+ getSubscriptions(): ChannelRef[];
49
+ }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MultiplexGateway = void 0;
4
+ /**
5
+ * `GatewayClientLike` facade over the members' gateways:
6
+ * - connect/disconnect/reconnect → fan out to members with `gateway.subscribe`.
7
+ * - subscribe/unsubscribe/subscribeToChannel → route to owning members.
8
+ * - on/off → register against every member; events forwarded.
9
+ * - isConnected → all members with `gateway.subscribe` are connected.
10
+ * - getSession → first connected member's session (locked decision #5).
11
+ * - getSubscriptions/getSubscriptionCount → union across members.
12
+ */
13
+ class MultiplexGateway {
14
+ constructor(host) {
15
+ this.host = host;
16
+ /** consumer event handler → set of member gateways it was registered on. */
17
+ this.eventBindings = new Map();
18
+ }
19
+ setStats() { }
20
+ setProvenanceHook() {
21
+ /* No-op: provenance hooks are set on each member's concrete gateway impl by their owning
22
+ * DataClient (DooverClient/LocalAgentClient). The composite MultiplexGateway is never the
23
+ * target of a hook call. */
24
+ }
25
+ async connect() {
26
+ await Promise.all(this.host.gatewayMembers().map((m) => m.gateway.connect()));
27
+ }
28
+ disconnect(code, reason) {
29
+ for (const m of this.host.gatewayMembers())
30
+ m.gateway.disconnect(code, reason);
31
+ }
32
+ async reconnect() {
33
+ await Promise.all(this.host.gatewayMembers().map((m) => m.gateway.reconnect()));
34
+ }
35
+ on(event, handler) {
36
+ const perEvent = this.eventBindings.get(event) ??
37
+ this.eventBindings.set(event, new Map()).get(event);
38
+ const gws = new Set();
39
+ for (const m of this.host.gatewayMembers()) {
40
+ const gw = m.gateway;
41
+ gw.on(event, handler);
42
+ gws.add(gw);
43
+ }
44
+ perEvent.set(handler, gws);
45
+ }
46
+ off(event, handler) {
47
+ const gws = this.eventBindings.get(event)?.get(handler);
48
+ if (!gws)
49
+ return;
50
+ for (const gw of gws)
51
+ gw.off(event, handler);
52
+ this.eventBindings.get(event).delete(handler);
53
+ }
54
+ subscribe(channel, options) {
55
+ for (const m of this.host.gatewayMembersForAgent(channel.agent_id))
56
+ m.gateway.subscribe(channel, options);
57
+ }
58
+ unsubscribe(channel) {
59
+ for (const m of this.host.gatewayMembersForAgent(channel.agent_id))
60
+ m.gateway.unsubscribe(channel);
61
+ }
62
+ subscribeToChannel(channel, handlers) {
63
+ const offs = this.host
64
+ .gatewayMembersForAgent(channel.agent_id)
65
+ .map((m) => m.gateway.subscribeToChannel(channel, handlers));
66
+ let done = false;
67
+ return () => {
68
+ if (done)
69
+ return;
70
+ done = true;
71
+ offs.forEach((o) => o());
72
+ };
73
+ }
74
+ syncChannel(channel) {
75
+ for (const m of this.host.gatewayMembersForAgent(channel.agent_id))
76
+ m.gateway.syncChannel(channel);
77
+ }
78
+ sendOneShotMessage(channel, data) {
79
+ for (const m of this.host.gatewayMembersForAgent(channel.agent_id))
80
+ m.gateway.sendOneShotMessage(channel, data);
81
+ }
82
+ getSession() {
83
+ for (const m of this.host.gatewayMembers()) {
84
+ const s = m.gateway.getSession();
85
+ if (s)
86
+ return s;
87
+ }
88
+ return null;
89
+ }
90
+ isConnected() {
91
+ const members = this.host.gatewayMembers();
92
+ if (members.length === 0)
93
+ return false;
94
+ return members.every((m) => m.gateway.isConnected());
95
+ }
96
+ getSubscriptionCount() {
97
+ return this.getSubscriptions().length;
98
+ }
99
+ getSubscriptions() {
100
+ const seen = new Set();
101
+ const out = [];
102
+ for (const m of this.host.gatewayMembers()) {
103
+ for (const c of m.gateway.getSubscriptions()) {
104
+ const k = `${c.agent_id}/${c.name}`;
105
+ if (seen.has(k))
106
+ continue;
107
+ seen.add(k);
108
+ out.push(c);
109
+ }
110
+ }
111
+ return out;
112
+ }
113
+ }
114
+ exports.MultiplexGateway = MultiplexGateway;
@@ -0,0 +1,12 @@
1
+ import type { MessageStructure } from "../types/common";
2
+ /** Keep the first item for each key; preserve input order otherwise. */
3
+ export declare function dedupeBy<T>(items: T[], keyOf: (item: T) => string): T[];
4
+ /**
5
+ * Merge per-member message arrays: concatenate, de-dup by `id` (first member
6
+ * wins), sort by id to match `order` ("desc" = newest first, the cloud's
7
+ * native order; "asc" = oldest first), then truncate to `limit` if given.
8
+ */
9
+ export declare function mergeMessages(perMember: MessageStructure[][], opts: {
10
+ order: "asc" | "desc";
11
+ limit?: number;
12
+ }): MessageStructure[];
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dedupeBy = dedupeBy;
4
+ exports.mergeMessages = mergeMessages;
5
+ /** Keep the first item for each key; preserve input order otherwise. */
6
+ function dedupeBy(items, keyOf) {
7
+ const seen = new Set();
8
+ const out = [];
9
+ for (const item of items) {
10
+ const k = keyOf(item);
11
+ if (seen.has(k))
12
+ continue;
13
+ seen.add(k);
14
+ out.push(item);
15
+ }
16
+ return out;
17
+ }
18
+ /** Compare two snowflake-ish id strings numerically when possible, else lexically. */
19
+ function compareIds(a, b) {
20
+ try {
21
+ const ba = BigInt(a);
22
+ const bb = BigInt(b);
23
+ return ba < bb ? -1 : ba > bb ? 1 : 0;
24
+ }
25
+ catch {
26
+ return a < b ? -1 : a > b ? 1 : 0;
27
+ }
28
+ }
29
+ /**
30
+ * Merge per-member message arrays: concatenate, de-dup by `id` (first member
31
+ * wins), sort by id to match `order` ("desc" = newest first, the cloud's
32
+ * native order; "asc" = oldest first), then truncate to `limit` if given.
33
+ */
34
+ function mergeMessages(perMember, opts) {
35
+ const all = [].concat(...perMember);
36
+ const unique = dedupeBy(all, (m) => m.id);
37
+ unique.sort((x, y) => {
38
+ const c = compareIds(x.id, y.id);
39
+ return opts.order === "asc" ? c : -c;
40
+ });
41
+ return typeof opts.limit === "number" ? unique.slice(0, opts.limit) : unique;
42
+ }
@@ -0,0 +1,60 @@
1
+ import type { SourceProvenance } from "../types/provenance";
2
+ /** A stamped value: adds `__source` to plain objects and their array elements.
3
+ * For object values, also adds `__source` to any known nested-object props
4
+ * (aggregate, message) and stamps elements of array-valued props. */
5
+ type StampedElement<T> = T extends Record<string, unknown> ? T & {
6
+ __source?: SourceProvenance;
7
+ } : T;
8
+ type StampedProp<V> = V extends (infer E)[] ? StampedElement<E>[] : V extends Record<string, unknown> ? V & {
9
+ __source?: SourceProvenance;
10
+ } : V;
11
+ type Stamped<T> = T extends (infer E)[] ? StampedElement<E>[] : T extends Record<string, unknown> ? {
12
+ [K in keyof T]: StampedProp<T[K]>;
13
+ } & {
14
+ __source?: SourceProvenance;
15
+ } : T;
16
+ /** Map a subclient type so that async methods return Stamped results. */
17
+ export type WrappedSubclient<T> = {
18
+ [K in keyof T]: T[K] extends (...args: infer A) => Promise<infer R> ? (...args: A) => Promise<Stamped<R>> : T[K];
19
+ };
20
+ export interface ClientIdentity {
21
+ id: string;
22
+ kind: string;
23
+ label?: string;
24
+ meta?: Record<string, unknown>;
25
+ }
26
+ interface RestContext {
27
+ /** e.g. "messages.listMessages" */
28
+ method: string;
29
+ /** caller inputs (agentId, channelName, params, body summary) */
30
+ request: Record<string, unknown>;
31
+ startedAt: number;
32
+ durationMs: number;
33
+ status?: number;
34
+ }
35
+ interface GatewayContext {
36
+ event: string;
37
+ sessionId?: string;
38
+ }
39
+ export declare class ProvenanceStamper {
40
+ private readonly identity;
41
+ constructor(identity: ClientIdentity);
42
+ stampRest<T>(value: T, ctx: RestContext): Stamped<T>;
43
+ stampGatewayEvent<T>(value: T, ctx: GatewayContext): Stamped<T>;
44
+ /** Build the full provenance envelope from a `via`. */
45
+ private prov;
46
+ /**
47
+ * Arrays → stamp each plain-object element. Plain objects → set `__source`,
48
+ * then shallow-stamp array-valued props' elements and known nested-object
49
+ * props (`aggregate`, `message`). Everything else (Blob, primitives,
50
+ * undefined, FormData, class instances) → returned unchanged.
51
+ */
52
+ private stampGraph;
53
+ }
54
+ /**
55
+ * Returns a Proxy over `api` whose methods, when they return a Promise, stamp
56
+ * the resolved value with `via.method = "<subclientName>.<methodName>"`.
57
+ * Synchronous returns (e.g. `createMultipartPayload`) pass through untouched.
58
+ */
59
+ export declare function wrapSubclient<T extends object>(api: T, subclientName: string, stamper: ProvenanceStamper): WrappedSubclient<T>;
60
+ export {};
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProvenanceStamper = void 0;
4
+ exports.wrapSubclient = wrapSubclient;
5
+ /** Property names whose (object) value should also be stamped, for the known
6
+ * gateway envelopes ({ ..., aggregate }, { ..., message }). */
7
+ const NESTED_OBJECT_PROPS = ["aggregate", "message"];
8
+ function isPlainObject(v) {
9
+ if (typeof v !== "object" || v === null)
10
+ return false;
11
+ if (Array.isArray(v))
12
+ return false;
13
+ if (typeof Blob !== "undefined" && v instanceof Blob)
14
+ return false;
15
+ const proto = Object.getPrototypeOf(v);
16
+ return proto === Object.prototype || proto === null;
17
+ }
18
+ class ProvenanceStamper {
19
+ constructor(identity) {
20
+ this.identity = identity;
21
+ }
22
+ stampRest(value, ctx) {
23
+ const via = {
24
+ transport: "rest",
25
+ method: ctx.method,
26
+ request: ctx.request,
27
+ startedAt: ctx.startedAt,
28
+ durationMs: ctx.durationMs,
29
+ ...(ctx.status !== undefined ? { status: ctx.status } : {}),
30
+ };
31
+ return this.stampGraph(value, via);
32
+ }
33
+ stampGatewayEvent(value, ctx) {
34
+ const via = {
35
+ transport: "gateway",
36
+ event: ctx.event,
37
+ ...(ctx.sessionId !== undefined ? { sessionId: ctx.sessionId } : {}),
38
+ receivedAt: Date.now(),
39
+ };
40
+ return this.stampGraph(value, via);
41
+ }
42
+ /** Build the full provenance envelope from a `via`. */
43
+ prov(via) {
44
+ return { client: this.identity, retrievedAt: Date.now(), via };
45
+ }
46
+ /**
47
+ * Arrays → stamp each plain-object element. Plain objects → set `__source`,
48
+ * then shallow-stamp array-valued props' elements and known nested-object
49
+ * props (`aggregate`, `message`). Everything else (Blob, primitives,
50
+ * undefined, FormData, class instances) → returned unchanged.
51
+ */
52
+ stampGraph(value, via) {
53
+ const prov = this.prov(via);
54
+ if (Array.isArray(value)) {
55
+ return value.map((el) => (isPlainObject(el) ? { ...el, __source: prov } : el));
56
+ }
57
+ if (!isPlainObject(value))
58
+ return value;
59
+ const out = { ...value, __source: prov };
60
+ for (const [k, v] of Object.entries(out)) {
61
+ if (k === "__source")
62
+ continue;
63
+ if (Array.isArray(v)) {
64
+ out[k] = v.map((el) => (isPlainObject(el) ? { ...el, __source: prov } : el));
65
+ }
66
+ else if (NESTED_OBJECT_PROPS.includes(k) && isPlainObject(v)) {
67
+ out[k] = { ...v, __source: prov };
68
+ }
69
+ }
70
+ return out;
71
+ }
72
+ }
73
+ exports.ProvenanceStamper = ProvenanceStamper;
74
+ /**
75
+ * Returns a Proxy over `api` whose methods, when they return a Promise, stamp
76
+ * the resolved value with `via.method = "<subclientName>.<methodName>"`.
77
+ * Synchronous returns (e.g. `createMultipartPayload`) pass through untouched.
78
+ */
79
+ function wrapSubclient(api, subclientName, stamper) {
80
+ return new Proxy(api, {
81
+ get(target, prop, receiver) {
82
+ const value = Reflect.get(target, prop, receiver);
83
+ if (typeof value !== "function" || typeof prop !== "string")
84
+ return value;
85
+ const methodName = `${subclientName}.${prop}`;
86
+ return function (...args) {
87
+ const startedAt = Date.now();
88
+ const out = value.apply(target, args);
89
+ if (out instanceof Promise) {
90
+ return out.then((result) => stamper.stampRest(result, {
91
+ method: methodName,
92
+ request: { args: summariseArgs(args) },
93
+ startedAt,
94
+ durationMs: Date.now() - startedAt,
95
+ }));
96
+ }
97
+ return out;
98
+ };
99
+ },
100
+ });
101
+ }
102
+ /** Keep `via.request` small: drop FormData/Blob bodies, cap string length. */
103
+ function summariseArgs(args) {
104
+ return args.map((a) => {
105
+ if (typeof FormData !== "undefined" && a instanceof FormData)
106
+ return "[FormData]";
107
+ if (typeof Blob !== "undefined" && a instanceof Blob)
108
+ return "[Blob]";
109
+ if (typeof a === "string" && a.length > 200)
110
+ return `${a.slice(0, 200)}…`;
111
+ return a;
112
+ });
113
+ }
@@ -26,9 +26,22 @@ export interface GatewayStatsSnapshot {
26
26
  /** Frames we've received on the gateway socket. */
27
27
  messagesReceived: number;
28
28
  }
29
+ export interface RpcStatsSnapshot {
30
+ enabled: boolean;
31
+ totalRpcs: number;
32
+ pendingRpcs: number;
33
+ completedRpcs: number;
34
+ failedRpcs: number;
35
+ timedOutRpcs: number;
36
+ abortedRpcs: number;
37
+ averageLatencyMs: number | null;
38
+ lastLatencyMs: number | null;
39
+ peakPendingRpcs: number;
40
+ }
29
41
  export interface DooverStatsSnapshot {
30
42
  rest: RestStatsSnapshot;
31
43
  gateway: GatewayStatsSnapshot;
44
+ rpc: RpcStatsSnapshot;
32
45
  }
33
46
  export declare class DooverStatsCollector {
34
47
  private enabled;
@@ -40,6 +53,15 @@ export declare class DooverStatsCollector {
40
53
  private rLastLatency;
41
54
  private gSent;
42
55
  private gReceived;
56
+ private rpTotal;
57
+ private rpPending;
58
+ private rpCompleted;
59
+ private rpFailed;
60
+ private rpTimedOut;
61
+ private rpAborted;
62
+ private rpLatencySum;
63
+ private rpLastLatency;
64
+ private rpPeakPending;
43
65
  setEnabled(enabled: boolean): void;
44
66
  isEnabled(): boolean;
45
67
  reset(): void;
@@ -51,5 +73,7 @@ export declare class DooverStatsCollector {
51
73
  recordRestEnd(startedAt: number | null, succeeded: boolean): void;
52
74
  recordGatewaySent(): void;
53
75
  recordGatewayReceived(): void;
76
+ recordRpcStart(): number | null;
77
+ recordRpcEnd(startedAt: number | null, outcome: "success" | "error" | "timeout" | "abort"): void;
54
78
  snapshot(): DooverStatsSnapshot;
55
79
  }
@@ -18,6 +18,15 @@ class DooverStatsCollector {
18
18
  this.rLastLatency = null;
19
19
  this.gSent = 0;
20
20
  this.gReceived = 0;
21
+ this.rpTotal = 0;
22
+ this.rpPending = 0;
23
+ this.rpCompleted = 0;
24
+ this.rpFailed = 0;
25
+ this.rpTimedOut = 0;
26
+ this.rpAborted = 0;
27
+ this.rpLatencySum = 0;
28
+ this.rpLastLatency = null;
29
+ this.rpPeakPending = 0;
21
30
  }
22
31
  setEnabled(enabled) {
23
32
  this.enabled = enabled;
@@ -34,6 +43,15 @@ class DooverStatsCollector {
34
43
  this.rLastLatency = null;
35
44
  this.gSent = 0;
36
45
  this.gReceived = 0;
46
+ this.rpTotal = 0;
47
+ this.rpPending = 0;
48
+ this.rpCompleted = 0;
49
+ this.rpFailed = 0;
50
+ this.rpTimedOut = 0;
51
+ this.rpAborted = 0;
52
+ this.rpLatencySum = 0;
53
+ this.rpLastLatency = null;
54
+ this.rpPeakPending = 0;
37
55
  }
38
56
  /**
39
57
  * Record the start of a REST request. Returns the start timestamp the
@@ -68,8 +86,40 @@ class DooverStatsCollector {
68
86
  return;
69
87
  this.gReceived += 1;
70
88
  }
89
+ recordRpcStart() {
90
+ if (!this.enabled)
91
+ return null;
92
+ this.rpTotal += 1;
93
+ this.rpPending += 1;
94
+ if (this.rpPending > this.rpPeakPending)
95
+ this.rpPeakPending = this.rpPending;
96
+ return now();
97
+ }
98
+ recordRpcEnd(startedAt, outcome) {
99
+ if (!this.enabled || startedAt === null)
100
+ return;
101
+ this.rpPending = Math.max(0, this.rpPending - 1);
102
+ switch (outcome) {
103
+ case "success":
104
+ this.rpCompleted += 1;
105
+ break;
106
+ case "error":
107
+ this.rpFailed += 1;
108
+ break;
109
+ case "timeout":
110
+ this.rpTimedOut += 1;
111
+ break;
112
+ case "abort":
113
+ this.rpAborted += 1;
114
+ break;
115
+ }
116
+ const latency = now() - startedAt;
117
+ this.rpLastLatency = latency;
118
+ this.rpLatencySum += latency;
119
+ }
71
120
  snapshot() {
72
121
  const settled = this.rCompleted + this.rFailed;
122
+ const rpcSettled = this.rpCompleted + this.rpFailed + this.rpTimedOut + this.rpAborted;
73
123
  return {
74
124
  rest: {
75
125
  enabled: this.enabled,
@@ -85,6 +135,18 @@ class DooverStatsCollector {
85
135
  messagesSent: this.gSent,
86
136
  messagesReceived: this.gReceived,
87
137
  },
138
+ rpc: {
139
+ enabled: this.enabled,
140
+ totalRpcs: this.rpTotal,
141
+ pendingRpcs: this.rpPending,
142
+ completedRpcs: this.rpCompleted,
143
+ failedRpcs: this.rpFailed,
144
+ timedOutRpcs: this.rpTimedOut,
145
+ abortedRpcs: this.rpAborted,
146
+ averageLatencyMs: rpcSettled > 0 ? this.rpLatencySum / rpcSettled : null,
147
+ lastLatencyMs: this.rpLastLatency,
148
+ peakPendingRpcs: this.rpPeakPending,
149
+ },
88
150
  };
89
151
  }
90
152
  }
@@ -0,0 +1,24 @@
1
+ import type { AgentScope, DataClientStatus, GatewayClientLike } from "./data-client";
2
+ /**
3
+ * Tracks a `DataClient`'s realtime status by subscribing to its gateway's
4
+ * `open` / `ready` / `close` / `wssError` events. Emits a fresh
5
+ * `DataClientStatus` to listeners whenever the derived snapshot changes.
6
+ */
7
+ export declare class ClientStatusTracker {
8
+ private readonly clientId;
9
+ private readonly gateway;
10
+ private readonly knownScope;
11
+ private lastEvent;
12
+ private lastError;
13
+ private session;
14
+ private listeners;
15
+ private detach;
16
+ constructor(clientId: string, gateway: GatewayClientLike, knownScope: () => AgentScope | "unknown" | string);
17
+ /** Call when the agent scope resolves (so listeners re-render). */
18
+ notifyScopeChanged(): void;
19
+ getStatus(): DataClientStatus;
20
+ onChange(listener: (status: DataClientStatus) => void): () => void;
21
+ dispose(): void;
22
+ private transition;
23
+ private emit;
24
+ }
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ClientStatusTracker = void 0;
4
+ /**
5
+ * Tracks a `DataClient`'s realtime status by subscribing to its gateway's
6
+ * `open` / `ready` / `close` / `wssError` events. Emits a fresh
7
+ * `DataClientStatus` to listeners whenever the derived snapshot changes.
8
+ */
9
+ class ClientStatusTracker {
10
+ constructor(clientId, gateway, knownScope) {
11
+ this.clientId = clientId;
12
+ this.gateway = gateway;
13
+ this.knownScope = knownScope;
14
+ this.lastEvent = "init";
15
+ this.session = null;
16
+ this.listeners = new Set();
17
+ this.detach = [];
18
+ const onOpen = () => this.transition("open");
19
+ const onReady = (s) => {
20
+ if (s?.session_id)
21
+ this.session = { id: s.session_id };
22
+ this.transition("ready");
23
+ };
24
+ const onClose = () => this.transition("close");
25
+ const onErr = (e) => {
26
+ this.lastError = e?.message ?? "gateway error";
27
+ this.transition("error");
28
+ };
29
+ gateway.on("open", onOpen);
30
+ gateway.on("ready", onReady);
31
+ gateway.on("close", onClose);
32
+ gateway.on("wssError", onErr);
33
+ this.detach.push(() => gateway.off("open", onOpen), () => gateway.off("ready", onReady), () => gateway.off("close", onClose), () => gateway.off("wssError", onErr));
34
+ }
35
+ /** Call when the agent scope resolves (so listeners re-render). */
36
+ notifyScopeChanged() {
37
+ this.emit();
38
+ }
39
+ getStatus() {
40
+ const connected = this.gateway.isConnected();
41
+ let state;
42
+ if (this.lastEvent === "error")
43
+ state = "error";
44
+ else if (connected)
45
+ state = "connected";
46
+ else if (this.lastEvent === "init")
47
+ state = "disconnected";
48
+ else
49
+ state = "connecting"; // saw open/close but not currently connected → reconnecting
50
+ return {
51
+ clientId: this.clientId,
52
+ connected,
53
+ state,
54
+ session: this.session,
55
+ lastEvent: this.lastEvent,
56
+ latencyMs: null,
57
+ ...(this.lastError ? { lastError: this.lastError } : {}),
58
+ agentScope: this.knownScope(),
59
+ at: Date.now(),
60
+ };
61
+ }
62
+ onChange(listener) {
63
+ this.listeners.add(listener);
64
+ let active = true;
65
+ return () => {
66
+ if (!active)
67
+ return;
68
+ active = false;
69
+ this.listeners.delete(listener);
70
+ };
71
+ }
72
+ dispose() {
73
+ this.detach.forEach((fn) => fn());
74
+ this.detach = [];
75
+ this.listeners.clear();
76
+ }
77
+ transition(event) {
78
+ this.lastEvent = event;
79
+ if (event !== "error") {
80
+ // a successful lifecycle event clears a stale error so state can recover
81
+ this.lastError = undefined;
82
+ }
83
+ this.emit();
84
+ }
85
+ emit() {
86
+ const snapshot = this.getStatus();
87
+ this.listeners.forEach((l) => l(snapshot));
88
+ }
89
+ }
90
+ exports.ClientStatusTracker = ClientStatusTracker;