@hedwigjs/devtools 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +340 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +2 -0
- package/dist/index.js.LICENSE.txt +9 -0
- package/dist/inspector/attachInspector.d.ts +15 -0
- package/dist/inspector/createInspectorStore.d.ts +21 -0
- package/dist/inspector/matchPattern.d.ts +14 -0
- package/dist/inspector/ringLog.d.ts +29 -0
- package/dist/inspector/testFixtures.d.ts +4 -0
- package/dist/inspector/types.d.ts +165 -0
- package/dist/ui/MessageBrokerDevTools.d.ts +45 -0
- package/dist/ui/components/AccordionRow/AccordionRow.d.ts +13 -0
- package/dist/ui/shell/devtoolsShell/DevToolsShell.d.ts +16 -0
- package/dist/ui/shell/devtoolsShell/PanelResizeHandle.d.ts +8 -0
- package/dist/ui/shell/floatingToggleButton/FloatingToggleButton.d.ts +10 -0
- package/dist/ui/shell/layout/panelFrame.d.ts +5 -0
- package/dist/ui/shell/layout/panelTypes.d.ts +28 -0
- package/dist/ui/shell/layout/usePanelLayoutState.d.ts +16 -0
- package/dist/ui/tabs/bridges/BridgesTab.d.ts +6 -0
- package/dist/ui/tabs/bridges/components/BridgeRow/BridgeRow.d.ts +7 -0
- package/dist/ui/tabs/clients/ClientsLogTab.d.ts +9 -0
- package/dist/ui/tabs/clients/components/ClientCard/ClientCard.d.ts +10 -0
- package/dist/ui/tabs/clients/components/ClientDetail/ClientDetail.d.ts +10 -0
- package/dist/ui/tabs/clients/components/ClientSummary/ClientSummary.d.ts +11 -0
- package/dist/ui/tabs/debug/DebugTab.d.ts +20 -0
- package/dist/ui/tabs/debug/components/ResultPanel/ResultPanel.d.ts +12 -0
- package/dist/ui/tabs/debug/components/SourcePicker/SourcePicker.d.ts +20 -0
- package/dist/ui/tabs/debug/components/TopicPicker/TopicPicker.d.ts +16 -0
- package/dist/ui/tabs/definitions.d.ts +9 -0
- package/dist/ui/tabs/messages/MessagesLogTab.d.ts +12 -0
- package/dist/ui/tabs/messages/components/MessageDetail/MessageDetail.d.ts +7 -0
- package/dist/ui/tabs/messages/components/MessageRow/MessageRow.d.ts +7 -0
- package/dist/ui/tabs/messages/components/MessageSummary/MessageSummary.d.ts +9 -0
- package/dist/ui/tabs/messages/components/MessagesToolbar/MessagesToolbar.d.ts +13 -0
- package/dist/ui/tabs/messages/components/StreamRow/StreamRow.d.ts +15 -0
- package/dist/ui/tabs/messages/formatTimestamp.d.ts +1 -0
- package/dist/ui/tabs/messages/rollup.d.ts +44 -0
- package/dist/ui/tabs/renderActiveTab.d.ts +19 -0
- package/dist/ui/tabs/replay-buffer/ReplayBufferTab.d.ts +6 -0
- package/dist/ui/tabs/replay-buffer/components/HistoryEntryRow/HistoryEntryRow.d.ts +7 -0
- package/dist/ui/tabs/system-events/SystemEventsTab.d.ts +6 -0
- package/dist/ui/tabs/system-events/components/SystemEventRow/SystemEventRow.d.ts +7 -0
- package/dist/ui/topicsRegistry.d.ts +42 -0
- package/dist/ui/utils/formatRelativeTime.d.ts +5 -0
- package/package.json +80 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MessageBrokerForDevTools } from "./types";
|
|
2
|
+
import type { MessageInspectorStore } from "./createInspectorStore";
|
|
3
|
+
/**
|
|
4
|
+
* Attaches the inspector store to broker hooks + system events.
|
|
5
|
+
*
|
|
6
|
+
* Two channels:
|
|
7
|
+
* - Extension hooks (useBeforeSendHook / useAfterSendHook) drive the
|
|
8
|
+
* live user-message feed with pending → delivered/failed transitions.
|
|
9
|
+
* - `$systemEvents` (client/subscription/bridge lifecycle) drives two
|
|
10
|
+
* things: the aggregate Clients tab (via `refresh`) and the dedicated
|
|
11
|
+
* System Events log (via `pushSystemEvent`).
|
|
12
|
+
*
|
|
13
|
+
* @returns Detach function that unsubscribes everything.
|
|
14
|
+
*/
|
|
15
|
+
export declare function attachInspector(broker: MessageBrokerForDevTools, store: MessageInspectorStore): () => void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Message, RoutingResult } from "@hedwigjs/broker";
|
|
2
|
+
import type { InspectorSnapshot, MessagesFilter, MessageBrokerForDevTools, SystemEventName } from "./types";
|
|
3
|
+
export interface CreateInspectorStoreOptions {
|
|
4
|
+
maxEvents: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function createInspectorStore(options: CreateInspectorStoreOptions): {
|
|
7
|
+
subscribe: (listener: () => void) => () => void;
|
|
8
|
+
getSnapshot: () => InspectorSnapshot;
|
|
9
|
+
setAttached: (value: boolean) => void;
|
|
10
|
+
onBeforeSend: (message: Readonly<Message>) => void;
|
|
11
|
+
onAfterSend: (message: Readonly<Message>, result: RoutingResult) => void;
|
|
12
|
+
clearLog: () => void;
|
|
13
|
+
setMessagesFilter: (patch: Partial<MessagesFilter>) => void;
|
|
14
|
+
clearMessagesFilter: () => void;
|
|
15
|
+
refreshClients: (broker: MessageBrokerForDevTools) => void;
|
|
16
|
+
refreshHistory: (broker: MessageBrokerForDevTools) => void;
|
|
17
|
+
refreshBridges: (broker: MessageBrokerForDevTools) => void;
|
|
18
|
+
pushSystemEvent: (name: SystemEventName, payload: unknown) => void;
|
|
19
|
+
clearSystemEvents: () => void;
|
|
20
|
+
};
|
|
21
|
+
export type MessageInspectorStore = ReturnType<typeof createInspectorStore>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Glob-style topic matcher. Mirrors the broker-internal `matchPattern`
|
|
3
|
+
* so DevTools can attribute messages to bridge forward-patterns without
|
|
4
|
+
* a private import.
|
|
5
|
+
*
|
|
6
|
+
* Supported patterns:
|
|
7
|
+
* - `*` → matches everything
|
|
8
|
+
* - `user.*` → prefix
|
|
9
|
+
* - `*.created.v1` → suffix
|
|
10
|
+
* - `user.*.v1` → middle
|
|
11
|
+
* - `user.login` → exact
|
|
12
|
+
*/
|
|
13
|
+
export declare function matchPattern(topic: string, pattern: string): boolean;
|
|
14
|
+
export declare function matchesAnyPattern(topic: string, patterns: ReadonlyArray<string>): boolean;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { MessageLogEntry } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Кольцо на фиксированной ёмкости: O(1) на append при полном буфере, без копирования хвоста.
|
|
4
|
+
* Порядок `toArray()` — от старейшей записи к новейшей.
|
|
5
|
+
*
|
|
6
|
+
* Generic — используется и для сообщений, и для системных событий.
|
|
7
|
+
* Опциональный `keyOf` позволяет искать элемент по внутреннему id
|
|
8
|
+
* (нужно для двухфазной записи сообщения: pending → delivered).
|
|
9
|
+
*/
|
|
10
|
+
export declare function createRingBuffer<T>(capacity: number, keyOf?: (item: T) => string): {
|
|
11
|
+
push: (e: T) => void;
|
|
12
|
+
setAt: (physicalIndex: number, e: T) => void;
|
|
13
|
+
getAt: (physicalIndex: number) => T | undefined;
|
|
14
|
+
findIndexById: (id: string) => number;
|
|
15
|
+
toArray: () => T[];
|
|
16
|
+
clear: () => void;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Convenience factory для главного кольца сообщений — сохраняет старую
|
|
20
|
+
* сигнатуру, чтобы не переписывать call-sites одним махом.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createMessageRingBuffer(capacity: number): {
|
|
23
|
+
push: (e: MessageLogEntry) => void;
|
|
24
|
+
setAt: (physicalIndex: number, e: MessageLogEntry) => void;
|
|
25
|
+
getAt: (physicalIndex: number) => MessageLogEntry | undefined;
|
|
26
|
+
findIndexById: (id: string) => number;
|
|
27
|
+
toArray: () => MessageLogEntry[];
|
|
28
|
+
clear: () => void;
|
|
29
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type { Message, RoutingResult, SubscriptionOptions, HistoryEntry, SystemEventsEmitter, Inspector } from "@hedwigjs/broker";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal broker contract for devtools subscription.
|
|
4
|
+
* Matches BrokerCore surface accessible without exporting the class.
|
|
5
|
+
*
|
|
6
|
+
* - `useBeforeSendHook` / `useAfterSendHook`: extension hooks the inspector uses
|
|
7
|
+
* to record the live message feed (topic, result, latency).
|
|
8
|
+
* - `$systemEvents`: broker-internal push channel of lifecycle events
|
|
9
|
+
* (client.*, subscription.*, bridge.*) — keeps the client tree in sync.
|
|
10
|
+
* The `$` prefix signals this is a tooling-only API.
|
|
11
|
+
* - `inspect`: pull API for point-in-time state snapshots (clients, history,
|
|
12
|
+
* bridges). Used for initial hydration without races.
|
|
13
|
+
*/
|
|
14
|
+
export interface MessageBrokerForDevTools {
|
|
15
|
+
useBeforeSendHook(hook: (message: Readonly<Message>) => {
|
|
16
|
+
allowed: true;
|
|
17
|
+
} | {
|
|
18
|
+
allowed: false;
|
|
19
|
+
message: string;
|
|
20
|
+
}): () => void;
|
|
21
|
+
useAfterSendHook(hook: (message: Readonly<Message>, result: RoutingResult) => void): () => void;
|
|
22
|
+
$systemEvents: SystemEventsEmitter<string, Record<string, any>>;
|
|
23
|
+
inspect: Inspector<string, Record<string, any>>;
|
|
24
|
+
$debug: {
|
|
25
|
+
send(source: string, topic: string, target: string, data: unknown): Promise<RoutingResult>;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export type { HistoryEntry };
|
|
29
|
+
export type LogStatus = "pending" | "delivered" | "failed";
|
|
30
|
+
/** multicast = broadcast to '*' (emit); unicast = targeted to a specific client (request). */
|
|
31
|
+
export type MessageKind = "multicast" | "unicast";
|
|
32
|
+
export interface MessageLogEntry {
|
|
33
|
+
id: string;
|
|
34
|
+
topic: string;
|
|
35
|
+
source: string;
|
|
36
|
+
target: string;
|
|
37
|
+
/** ISO timestamp */
|
|
38
|
+
createdAt: string;
|
|
39
|
+
status: LogStatus;
|
|
40
|
+
kind: MessageKind;
|
|
41
|
+
/** Number of subscribers that received this emit (undefined for unicast). */
|
|
42
|
+
subscriberCount?: number;
|
|
43
|
+
replayed?: boolean;
|
|
44
|
+
fromExternal?: boolean;
|
|
45
|
+
/** Message was fired via `broker.$debug.send` (DevTools spoof / test). */
|
|
46
|
+
synthetic?: boolean;
|
|
47
|
+
dataPreview?: string;
|
|
48
|
+
latencyMs?: number;
|
|
49
|
+
result?: {
|
|
50
|
+
status: "ACK" | "NACK";
|
|
51
|
+
reason: string;
|
|
52
|
+
message: string;
|
|
53
|
+
/** Recipient client ID — unicast only. */
|
|
54
|
+
recipientId?: string;
|
|
55
|
+
/** All recipient client IDs — multicast only. */
|
|
56
|
+
recipientIds?: string[];
|
|
57
|
+
/** Serialized return value from the handler (request-reply only). */
|
|
58
|
+
responsePreview?: string;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export interface MessagesFilter {
|
|
62
|
+
/** Text search on topic name. */
|
|
63
|
+
topic: string;
|
|
64
|
+
/** Filter by client ID (matches source or any recipient). */
|
|
65
|
+
clientId?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Narrows clientId filter to sent or received messages only.
|
|
68
|
+
* Ignored when clientId is not set.
|
|
69
|
+
*/
|
|
70
|
+
direction?: "sent" | "received";
|
|
71
|
+
kind?: MessageKind;
|
|
72
|
+
/** Filter by delivery result. "pending" = still in flight. */
|
|
73
|
+
result?: "ACK" | "NACK" | "pending";
|
|
74
|
+
}
|
|
75
|
+
export declare const EMPTY_MESSAGES_FILTER: MessagesFilter;
|
|
76
|
+
/**
|
|
77
|
+
* Configuration for collapsing high-frequency same-topic bursts (e.g. SSE
|
|
78
|
+
* chunk streams) into a single expandable row so a flood of events from
|
|
79
|
+
* one source doesn't drown out everything else in the log.
|
|
80
|
+
*
|
|
81
|
+
* A group is formed when consecutive entries share the same `(topic, source)`
|
|
82
|
+
* and each pair is within `windowMs`. Groups with < `minCount` entries stay
|
|
83
|
+
* flattened as individual rows — the rollup only kicks in for real bursts.
|
|
84
|
+
*/
|
|
85
|
+
export interface MessagesRollupConfig {
|
|
86
|
+
/** Minimum consecutive matching entries to fold into a stream row. */
|
|
87
|
+
minCount: number;
|
|
88
|
+
/** Max gap (ms) between adjacent entries to still consider them one stream. */
|
|
89
|
+
windowMs: number;
|
|
90
|
+
}
|
|
91
|
+
export declare const DEFAULT_MESSAGES_ROLLUP: MessagesRollupConfig;
|
|
92
|
+
/**
|
|
93
|
+
* One system event surfaced by `broker.$systemEvents`. These are broker
|
|
94
|
+
* infrastructure signals (client / subscription / bridge lifecycle) —
|
|
95
|
+
* NOT user message topics. DevTools shows them in a dedicated tab so
|
|
96
|
+
* they don't drown out (or get drowned by) the user-message feed.
|
|
97
|
+
*/
|
|
98
|
+
export type SystemEventName = "client.registered" | "client.unregistered" | "subscription.added" | "subscription.removed" | "subscription.rejected" | "message.rejected" | "bridge.added" | "bridge.removed";
|
|
99
|
+
export interface SystemEventLogEntry {
|
|
100
|
+
/** Monotonic local id, assigned by the store on ingestion. */
|
|
101
|
+
id: string;
|
|
102
|
+
/** ISO timestamp when DevTools received the event. */
|
|
103
|
+
at: string;
|
|
104
|
+
name: SystemEventName;
|
|
105
|
+
/** Raw payload from broker — shape depends on `name`. */
|
|
106
|
+
payload: unknown;
|
|
107
|
+
}
|
|
108
|
+
export interface ClientSubscriptionEntry {
|
|
109
|
+
topic: string;
|
|
110
|
+
options?: SubscriptionOptions;
|
|
111
|
+
/** Unix ms of the last message received on this subscription. Null if never. */
|
|
112
|
+
lastReceivedAt: number | null;
|
|
113
|
+
}
|
|
114
|
+
export interface ClientEntry {
|
|
115
|
+
id: string;
|
|
116
|
+
connectedAt: number;
|
|
117
|
+
/** Unix ms of the last message sent or received by this client. Null if none. */
|
|
118
|
+
lastActiveAt: number | null;
|
|
119
|
+
/** Messages sent by this client visible in the current ring buffer. */
|
|
120
|
+
sentCount: number;
|
|
121
|
+
/** Messages received by this client visible in the current ring buffer. */
|
|
122
|
+
receivedCount: number;
|
|
123
|
+
subscriptions: ClientSubscriptionEntry[];
|
|
124
|
+
}
|
|
125
|
+
export interface BridgeEntry {
|
|
126
|
+
id: string;
|
|
127
|
+
forwardPatterns: ReadonlyArray<string>;
|
|
128
|
+
/**
|
|
129
|
+
* Transport class name (`WebSocket`, `SSE`, `PostMessage`,
|
|
130
|
+
* `BroadcastChannel`, or a custom class name). Populated by the broker
|
|
131
|
+
* from the transport's constructor; may be `undefined` for anonymous
|
|
132
|
+
* transports.
|
|
133
|
+
*/
|
|
134
|
+
transportKind?: string;
|
|
135
|
+
/**
|
|
136
|
+
* Approx. count of local emits whose topic matches this bridge's
|
|
137
|
+
* forward patterns — i.e. messages that WOULD have been sent out
|
|
138
|
+
* through this transport. Broker doesn't expose per-bridge attribution
|
|
139
|
+
* directly, so this is a heuristic over the message log.
|
|
140
|
+
*/
|
|
141
|
+
sentThroughCount: number;
|
|
142
|
+
/**
|
|
143
|
+
* Approx. count of `fromExternal=true` messages matching this bridge's
|
|
144
|
+
* forward patterns — i.e. messages injected FROM this transport. Same
|
|
145
|
+
* heuristic caveat as above.
|
|
146
|
+
*/
|
|
147
|
+
receivedFromCount: number;
|
|
148
|
+
}
|
|
149
|
+
export interface InspectorSnapshot {
|
|
150
|
+
/** Oldest → newest order. Reverse before rendering in the log. */
|
|
151
|
+
entries: ReadonlyArray<MessageLogEntry>;
|
|
152
|
+
totalSeen: number;
|
|
153
|
+
attached: boolean;
|
|
154
|
+
clients: ReadonlyArray<ClientEntry>;
|
|
155
|
+
messagesFilter: MessagesFilter;
|
|
156
|
+
/** Current contents of the broker's replay buffer (oldest → newest). */
|
|
157
|
+
historyEntries: ReadonlyArray<HistoryEntry>;
|
|
158
|
+
/** Recent system events (oldest → newest). Ring-buffered by `maxEvents`. */
|
|
159
|
+
systemEvents: ReadonlyArray<SystemEventLogEntry>;
|
|
160
|
+
/** Registered bridges with cached forward patterns and derived counters. */
|
|
161
|
+
bridges: ReadonlyArray<BridgeEntry>;
|
|
162
|
+
}
|
|
163
|
+
declare function serializeDataPreview(data: unknown, maxLen?: number): string;
|
|
164
|
+
declare function snapshotFrom(list: MessageLogEntry[], totalSeen: number, attached: boolean, clients: ClientEntry[], messagesFilter: MessagesFilter, historyEntries: ReadonlyArray<HistoryEntry>, systemEvents: ReadonlyArray<SystemEventLogEntry>, bridges: ReadonlyArray<BridgeEntry>): InspectorSnapshot;
|
|
165
|
+
export { serializeDataPreview, snapshotFrom };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { MessageBrokerForDevTools, MessagesRollupConfig } from "../inspector/types";
|
|
3
|
+
import type { DevToolsPanelPosition } from "./shell/layout/panelTypes";
|
|
4
|
+
import type { TopicsRegistry } from "./topicsRegistry";
|
|
5
|
+
export type { DevToolsPanelPosition } from "./shell/layout/panelTypes";
|
|
6
|
+
export interface MessageBrokerDevToolsProps {
|
|
7
|
+
broker: MessageBrokerForDevTools;
|
|
8
|
+
/**
|
|
9
|
+
* Каталог топиков из registry-пакета (например, `@your-org/topics-registry`).
|
|
10
|
+
* Если передан — будущие табы (Topics, Send-tester) показывают полный
|
|
11
|
+
* перечень контрактов и примеры payload'ов. Loose coupling: DevTools
|
|
12
|
+
* не зависит от конкретного registry-пакета.
|
|
13
|
+
*/
|
|
14
|
+
registry?: TopicsRegistry;
|
|
15
|
+
/** Defaults to true only in development (requires DefinePlugin in the build). */
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
/** Maximum number of recent events to keep in the log. */
|
|
18
|
+
maxEvents?: number;
|
|
19
|
+
/** Default dock side for the panel (also the first value written to localStorage). */
|
|
20
|
+
defaultPosition?: DevToolsPanelPosition;
|
|
21
|
+
/**
|
|
22
|
+
* Edge on which the floating toggle button lives. Independent from the
|
|
23
|
+
* panel's position: the FAB stays a stable anchor while the panel can dock
|
|
24
|
+
* anywhere. Defaults to `"right"` — a discreet edge-attached rail that
|
|
25
|
+
* doesn't collide with typical bottom-right chat widgets.
|
|
26
|
+
*/
|
|
27
|
+
fabPosition?: DevToolsPanelPosition;
|
|
28
|
+
/** localStorage key for position, active tab, and open state. */
|
|
29
|
+
storageKey?: string;
|
|
30
|
+
/** Whether the panel is open by default (when no saved state exists). */
|
|
31
|
+
defaultOpen?: boolean;
|
|
32
|
+
/** Icon for the floating toggle button. Falls back to the built-in mascot PNG. */
|
|
33
|
+
toggleIcon?: ReactNode;
|
|
34
|
+
/**
|
|
35
|
+
* Auto-collapse bursts of same-source same-topic events into stream rows
|
|
36
|
+
* in the Messages tab. Prevents streaming producers (SSE chunks,
|
|
37
|
+
* chatty polling loops) from flooding the log.
|
|
38
|
+
*
|
|
39
|
+
* - `undefined` (default) — enabled with `{ minCount: 5, windowMs: 1000 }`.
|
|
40
|
+
* - `false` — disabled, flat log for every event.
|
|
41
|
+
* - `{ minCount, windowMs }` — custom thresholds.
|
|
42
|
+
*/
|
|
43
|
+
rollup?: MessagesRollupConfig | false;
|
|
44
|
+
}
|
|
45
|
+
export declare const MessageBrokerDevTools: ({ broker, registry, enabled, maxEvents, defaultPosition, fabPosition, storageKey, defaultOpen, toggleIcon, rollup, }: MessageBrokerDevToolsProps) => ReactNode;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
2
|
+
interface AccordionRowProps {
|
|
3
|
+
/** Collapsed content — receives `open` so it can render the chevron. */
|
|
4
|
+
summary: (open: boolean) => ReactNode;
|
|
5
|
+
/** Detail content shown when the row is expanded. */
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
/** Props forwarded to the root `<div>` (e.g. className, data-*). */
|
|
8
|
+
rootProps?: ComponentPropsWithoutRef<"div"> & {
|
|
9
|
+
[key: `data-${string}`]: string | undefined;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export declare function AccordionRow({ summary, children, rootProps, }: AccordionRowProps): ReactNode;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { MessageInspectorStore } from "../../../inspector/createInspectorStore";
|
|
3
|
+
import type { MessageBrokerForDevTools, MessagesRollupConfig } from "../../../inspector/types";
|
|
4
|
+
import type { DevToolsLayoutState, DevToolsPanelPosition, DevToolsTabId } from "../layout/panelTypes";
|
|
5
|
+
export interface DevToolsShellProps {
|
|
6
|
+
store: MessageInspectorStore;
|
|
7
|
+
layout: DevToolsLayoutState;
|
|
8
|
+
messagesRollup: MessagesRollupConfig | null;
|
|
9
|
+
broker: MessageBrokerForDevTools;
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
onPositionChange: (p: DevToolsPanelPosition) => void;
|
|
12
|
+
onTabChange: (t: DevToolsTabId) => void;
|
|
13
|
+
onSetSizeMain: (px: number) => void;
|
|
14
|
+
onToggleFullscreen: () => void;
|
|
15
|
+
}
|
|
16
|
+
export declare const DevToolsShell: ({ store, layout, messagesRollup, broker, onClose, onPositionChange, onTabChange, onSetSizeMain, onToggleFullscreen, }: DevToolsShellProps) => ReactNode;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { DevToolsPanelPosition } from "../layout/panelTypes";
|
|
3
|
+
export interface PanelResizeHandleProps {
|
|
4
|
+
position: DevToolsPanelPosition;
|
|
5
|
+
onResize: (mainPx: number) => void;
|
|
6
|
+
disabled: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare const PanelResizeHandle: ({ position, onResize, disabled, }: PanelResizeHandleProps) => ReactNode;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { DevToolsPanelPosition } from "../layout/panelTypes";
|
|
3
|
+
export interface FloatingToggleButtonProps {
|
|
4
|
+
position: DevToolsPanelPosition;
|
|
5
|
+
attached: boolean;
|
|
6
|
+
onOpen: () => void;
|
|
7
|
+
/** Override the built-in icon (e.g. a custom img element). */
|
|
8
|
+
icon?: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
export declare const FloatingToggleButton: ({ position, attached, onOpen, icon, }: FloatingToggleButtonProps) => ReactNode;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DevToolsPanelPosition } from "./panelTypes";
|
|
2
|
+
export declare const DEFAULT_SIZE_MAIN: Record<DevToolsPanelPosition, number>;
|
|
3
|
+
export declare const MIN_SIZE_MAIN = 200;
|
|
4
|
+
export declare function getMaxSizeMain(position: DevToolsPanelPosition, viewportW: number, viewportH: number): number;
|
|
5
|
+
export declare function clampSizeMain(position: DevToolsPanelPosition, px: number, viewportW: number, viewportH: number): number;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dock side of the panel (similar to the toolbar in React Query Devtools).
|
|
3
|
+
* top/bottom — full-width panel; left/right — side column.
|
|
4
|
+
*/
|
|
5
|
+
export type DevToolsPanelPosition = "top" | "bottom" | "left" | "right";
|
|
6
|
+
/**
|
|
7
|
+
* Tab identifiers. Add new sections here and in `renderActiveTab.tsx`.
|
|
8
|
+
*/
|
|
9
|
+
export type DevToolsTabId = "messages" | "clients" | "bridges" | "replay-buffer" | "system-events" | "debug";
|
|
10
|
+
export interface DevToolsPreFullscreen {
|
|
11
|
+
sizeMain: number;
|
|
12
|
+
position: DevToolsPanelPosition;
|
|
13
|
+
}
|
|
14
|
+
export interface DevToolsLayoutState {
|
|
15
|
+
isOpen: boolean;
|
|
16
|
+
position: DevToolsPanelPosition;
|
|
17
|
+
activeTab: DevToolsTabId;
|
|
18
|
+
/** Panel occupies the full viewport; exit restores preFullscreen. */
|
|
19
|
+
isFullscreen: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Main-axis size in px: top/bottom panels use height, left/right use width.
|
|
22
|
+
*/
|
|
23
|
+
sizeMain: number;
|
|
24
|
+
/**
|
|
25
|
+
* Captured on fullscreen enter; not persisted to localStorage (in-memory only while the panel is open).
|
|
26
|
+
*/
|
|
27
|
+
preFullscreen: DevToolsPreFullscreen | null;
|
|
28
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DevToolsLayoutState, DevToolsPanelPosition, DevToolsTabId } from "./panelTypes";
|
|
2
|
+
export declare function usePanelLayoutState(options: {
|
|
3
|
+
storageKey?: string;
|
|
4
|
+
defaultPosition: DevToolsPanelPosition;
|
|
5
|
+
defaultOpen?: boolean;
|
|
6
|
+
}): [
|
|
7
|
+
state: DevToolsLayoutState,
|
|
8
|
+
actions: {
|
|
9
|
+
setOpen: (v: boolean) => void;
|
|
10
|
+
toggle: () => void;
|
|
11
|
+
setPosition: (p: DevToolsPanelPosition) => void;
|
|
12
|
+
setActiveTab: (t: DevToolsTabId) => void;
|
|
13
|
+
setSizeMain: (px: number) => void;
|
|
14
|
+
toggleFullscreen: () => void;
|
|
15
|
+
}
|
|
16
|
+
];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { MessageInspectorStore } from "../../../inspector/createInspectorStore";
|
|
3
|
+
export interface BridgesTabProps {
|
|
4
|
+
store: MessageInspectorStore;
|
|
5
|
+
}
|
|
6
|
+
export declare function BridgesTab({ store }: BridgesTabProps): ReactNode;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { MessageInspectorStore } from "../../../inspector/createInspectorStore";
|
|
3
|
+
import type { DevToolsTabId } from "../../shell/layout/panelTypes";
|
|
4
|
+
import type { TabNavigateOptions } from "../renderActiveTab";
|
|
5
|
+
export interface ClientsLogTabProps {
|
|
6
|
+
store: MessageInspectorStore;
|
|
7
|
+
onNavigate: (tab: DevToolsTabId, options?: TabNavigateOptions) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare function ClientsLogTab({ store, onNavigate }: ClientsLogTabProps): ReactNode;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { ClientEntry } from "../../../../../inspector/types";
|
|
3
|
+
import type { DevToolsTabId } from "../../../../shell/layout/panelTypes";
|
|
4
|
+
import type { TabNavigateOptions } from "../../../renderActiveTab";
|
|
5
|
+
interface ClientCardProps {
|
|
6
|
+
client: ClientEntry;
|
|
7
|
+
onNavigate: (tab: DevToolsTabId, options?: TabNavigateOptions) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare function ClientCard({ client, onNavigate }: ClientCardProps): ReactNode;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { ClientEntry } from "../../../../../inspector/types";
|
|
3
|
+
import type { DevToolsTabId } from "../../../../shell/layout/panelTypes";
|
|
4
|
+
import type { TabNavigateOptions } from "../../../renderActiveTab";
|
|
5
|
+
interface ClientDetailProps {
|
|
6
|
+
client: ClientEntry;
|
|
7
|
+
onNavigate: (tab: DevToolsTabId, options?: TabNavigateOptions) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare function ClientDetail({ client, onNavigate }: ClientDetailProps): ReactNode;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { ClientEntry } from "../../../../../inspector/types";
|
|
3
|
+
import type { DevToolsTabId } from "../../../../shell/layout/panelTypes";
|
|
4
|
+
import type { TabNavigateOptions } from "../../../renderActiveTab";
|
|
5
|
+
interface ClientSummaryProps {
|
|
6
|
+
client: ClientEntry;
|
|
7
|
+
isOpen: boolean;
|
|
8
|
+
onNavigate: (tab: DevToolsTabId, options?: TabNavigateOptions) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function ClientSummary({ client, isOpen, onNavigate }: ClientSummaryProps): ReactNode;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { MessageInspectorStore } from "../../../inspector/createInspectorStore";
|
|
3
|
+
import type { MessageBrokerForDevTools } from "../../../inspector/types";
|
|
4
|
+
export interface DebugTabProps {
|
|
5
|
+
store: MessageInspectorStore;
|
|
6
|
+
broker: MessageBrokerForDevTools;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Debug tab — hand-craft and send test events into the broker.
|
|
10
|
+
*
|
|
11
|
+
* Delegates to `broker.$debug.send()` under the hood (single broker
|
|
12
|
+
* primitive for both multicast and unicast — target picks the branch).
|
|
13
|
+
* The `source` field is a free string; broker doesn't touch the client
|
|
14
|
+
* registry, so impersonating a real client id is safe.
|
|
15
|
+
*
|
|
16
|
+
* Registry (if passed to <MessageBrokerDevTools />) drives topic
|
|
17
|
+
* autocomplete + example payload prefill. Without a registry the topic
|
|
18
|
+
* field is free-form and payload starts at `{}`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function DebugTab({ store, broker }: DebugTabProps): ReactNode;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { RoutingResult } from "@hedwigjs/broker";
|
|
3
|
+
interface ResultPanelProps {
|
|
4
|
+
result: RoutingResult;
|
|
5
|
+
mode: "multicast" | "unicast";
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Shows the RoutingResult from broker.$debug.send — status, reason,
|
|
9
|
+
* subscribers count (multicast) or response data (unicast).
|
|
10
|
+
*/
|
|
11
|
+
export declare function ResultPanel({ result, mode }: ResultPanelProps): ReactNode;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
interface SourcePickerProps {
|
|
3
|
+
value: string;
|
|
4
|
+
/** Registered client ids for dropdown suggestions. */
|
|
5
|
+
suggestions: ReadonlyArray<string>;
|
|
6
|
+
onChange: (value: string) => void;
|
|
7
|
+
/** Placeholder shown when the input is empty. */
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Text input with a dropdown that opens on focus.
|
|
12
|
+
*
|
|
13
|
+
* Same interaction model as TopicPicker: pick from the list OR type a
|
|
14
|
+
* free string. Free strings are allowed on purpose — the whole point of
|
|
15
|
+
* `broker.$debug.send` is that source is an arbitrary label, not
|
|
16
|
+
* restricted to registered clients (bridges use non-client source labels
|
|
17
|
+
* like `ai-backend`, `notifications-backend`).
|
|
18
|
+
*/
|
|
19
|
+
export declare function SourcePicker({ value, suggestions, onChange, placeholder, }: SourcePickerProps): ReactNode;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { TopicContractInfo, TopicsRegistry } from "../../../../topicsRegistry";
|
|
3
|
+
interface TopicPickerProps {
|
|
4
|
+
value: string;
|
|
5
|
+
registry: TopicsRegistry | null;
|
|
6
|
+
onTextChange: (value: string) => void;
|
|
7
|
+
onPick: (contract: TopicContractInfo) => void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Text input + suggestion dropdown over the topics registry. Free-form
|
|
11
|
+
* typing works even without a registry (dropdown just stays empty).
|
|
12
|
+
* Selecting an entry fires `onPick` so the parent can prefill the
|
|
13
|
+
* payload editor from `examples.happy`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function TopicPicker({ value, registry, onTextChange, onPick, }: TopicPickerProps): ReactNode;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { InspectorSnapshot } from "../../inspector/types";
|
|
2
|
+
import type { DevToolsTabId } from "../shell/layout/panelTypes";
|
|
3
|
+
export interface TabDefinition {
|
|
4
|
+
id: DevToolsTabId;
|
|
5
|
+
label: string;
|
|
6
|
+
/** Returns the badge count to show next to the tab label. Always shown when defined. */
|
|
7
|
+
getBadge?: (snapshot: InspectorSnapshot) => number;
|
|
8
|
+
}
|
|
9
|
+
export declare const MBDT_TAB_DEFINITIONS: ReadonlyArray<TabDefinition>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { MessageInspectorStore } from "../../../inspector/createInspectorStore";
|
|
3
|
+
import type { MessagesRollupConfig } from "../../../inspector/types";
|
|
4
|
+
export interface MessagesLogTabProps {
|
|
5
|
+
store: MessageInspectorStore;
|
|
6
|
+
/**
|
|
7
|
+
* Auto-rollup high-frequency same-source bursts into collapsible stream
|
|
8
|
+
* rows. Pass `null` to disable and always show a flat log.
|
|
9
|
+
*/
|
|
10
|
+
rollup: MessagesRollupConfig | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function MessagesLogTab({ store, rollup }: MessagesLogTabProps): ReactNode;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { MessageLogEntry } from "../../../../../inspector/types";
|
|
3
|
+
interface MessageDetailProps {
|
|
4
|
+
entry: MessageLogEntry;
|
|
5
|
+
}
|
|
6
|
+
export declare function MessageDetail({ entry }: MessageDetailProps): ReactNode;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { MessageLogEntry } from "../../../../../inspector/types";
|
|
3
|
+
interface MessageSummaryProps {
|
|
4
|
+
entry: MessageLogEntry;
|
|
5
|
+
/** Passed by AccordionRow so the toggle reflects the current state. */
|
|
6
|
+
open: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function MessageSummary({ entry, open }: MessageSummaryProps): ReactNode;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { MessagesFilter } from "../../../../../inspector/types";
|
|
3
|
+
interface MessagesToolbarProps {
|
|
4
|
+
filter: MessagesFilter;
|
|
5
|
+
onFilterChange: (patch: Partial<MessagesFilter>) => void;
|
|
6
|
+
onClear: () => void;
|
|
7
|
+
onClearLog: () => void;
|
|
8
|
+
visibleCount: number;
|
|
9
|
+
totalCount: number;
|
|
10
|
+
totalSeen: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function MessagesToolbar({ filter, onFilterChange, onClear, onClearLog, visibleCount, totalCount, totalSeen, }: MessagesToolbarProps): ReactNode;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { StreamGroup } from "../../rollup";
|
|
3
|
+
interface StreamRowProps {
|
|
4
|
+
group: StreamGroup;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Collapsible row that stands in for a burst of same-topic messages from
|
|
8
|
+
* one source (typical for streaming producers like AI chat SSE chunks).
|
|
9
|
+
*
|
|
10
|
+
* Summary shows count + duration + latest payload preview; expanding it
|
|
11
|
+
* renders every original {@link MessageRow} inside, so no information is
|
|
12
|
+
* hidden — just deferred.
|
|
13
|
+
*/
|
|
14
|
+
export declare function StreamRow({ group }: StreamRowProps): ReactNode;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function formatTimestamp(iso: string): string;
|