@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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +340 -0
  3. package/dist/index.d.ts +9 -0
  4. package/dist/index.js +2 -0
  5. package/dist/index.js.LICENSE.txt +9 -0
  6. package/dist/inspector/attachInspector.d.ts +15 -0
  7. package/dist/inspector/createInspectorStore.d.ts +21 -0
  8. package/dist/inspector/matchPattern.d.ts +14 -0
  9. package/dist/inspector/ringLog.d.ts +29 -0
  10. package/dist/inspector/testFixtures.d.ts +4 -0
  11. package/dist/inspector/types.d.ts +165 -0
  12. package/dist/ui/MessageBrokerDevTools.d.ts +45 -0
  13. package/dist/ui/components/AccordionRow/AccordionRow.d.ts +13 -0
  14. package/dist/ui/shell/devtoolsShell/DevToolsShell.d.ts +16 -0
  15. package/dist/ui/shell/devtoolsShell/PanelResizeHandle.d.ts +8 -0
  16. package/dist/ui/shell/floatingToggleButton/FloatingToggleButton.d.ts +10 -0
  17. package/dist/ui/shell/layout/panelFrame.d.ts +5 -0
  18. package/dist/ui/shell/layout/panelTypes.d.ts +28 -0
  19. package/dist/ui/shell/layout/usePanelLayoutState.d.ts +16 -0
  20. package/dist/ui/tabs/bridges/BridgesTab.d.ts +6 -0
  21. package/dist/ui/tabs/bridges/components/BridgeRow/BridgeRow.d.ts +7 -0
  22. package/dist/ui/tabs/clients/ClientsLogTab.d.ts +9 -0
  23. package/dist/ui/tabs/clients/components/ClientCard/ClientCard.d.ts +10 -0
  24. package/dist/ui/tabs/clients/components/ClientDetail/ClientDetail.d.ts +10 -0
  25. package/dist/ui/tabs/clients/components/ClientSummary/ClientSummary.d.ts +11 -0
  26. package/dist/ui/tabs/debug/DebugTab.d.ts +20 -0
  27. package/dist/ui/tabs/debug/components/ResultPanel/ResultPanel.d.ts +12 -0
  28. package/dist/ui/tabs/debug/components/SourcePicker/SourcePicker.d.ts +20 -0
  29. package/dist/ui/tabs/debug/components/TopicPicker/TopicPicker.d.ts +16 -0
  30. package/dist/ui/tabs/definitions.d.ts +9 -0
  31. package/dist/ui/tabs/messages/MessagesLogTab.d.ts +12 -0
  32. package/dist/ui/tabs/messages/components/MessageDetail/MessageDetail.d.ts +7 -0
  33. package/dist/ui/tabs/messages/components/MessageRow/MessageRow.d.ts +7 -0
  34. package/dist/ui/tabs/messages/components/MessageSummary/MessageSummary.d.ts +9 -0
  35. package/dist/ui/tabs/messages/components/MessagesToolbar/MessagesToolbar.d.ts +13 -0
  36. package/dist/ui/tabs/messages/components/StreamRow/StreamRow.d.ts +15 -0
  37. package/dist/ui/tabs/messages/formatTimestamp.d.ts +1 -0
  38. package/dist/ui/tabs/messages/rollup.d.ts +44 -0
  39. package/dist/ui/tabs/renderActiveTab.d.ts +19 -0
  40. package/dist/ui/tabs/replay-buffer/ReplayBufferTab.d.ts +6 -0
  41. package/dist/ui/tabs/replay-buffer/components/HistoryEntryRow/HistoryEntryRow.d.ts +7 -0
  42. package/dist/ui/tabs/system-events/SystemEventsTab.d.ts +6 -0
  43. package/dist/ui/tabs/system-events/components/SystemEventRow/SystemEventRow.d.ts +7 -0
  44. package/dist/ui/topicsRegistry.d.ts +42 -0
  45. package/dist/ui/utils/formatRelativeTime.d.ts +5 -0
  46. package/package.json +80 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hedwig contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,340 @@
1
+ # @hedwigjs/devtools
2
+
3
+ React DevTools panel for `@hedwigjs/broker`. Watch every message,
4
+ inspect clients and bridges, replay the history buffer, catch
5
+ hook-driven rejections, and hand-craft synthetic messages against a
6
+ running broker — all from a docked panel you mount in your own app.
7
+
8
+ ```bash
9
+ # not yet published to npm — inside the Hedwig monorepo:
10
+ npm install @hedwigjs/devtools
11
+ ```
12
+
13
+ Peer deps: `@hedwigjs/broker`, `react`, `react-dom` (React 19).
14
+
15
+ > Pre-release (`0.1.0`, private). The panel props documented here are
16
+ > the stable surface; anything marked *internal* may change.
17
+
18
+ **Full project docs & reference stand →** [`../..#readme`](../..#readme)
19
+
20
+ ---
21
+
22
+ ## Table of contents
23
+
24
+ - [Quickstart](#quickstart)
25
+ - [What the panel shows](#what-the-panel-shows)
26
+ - [Props reference](#props-reference)
27
+ - [Topics registry (bring your own)](#topics-registry-bring-your-own)
28
+ - [Messages rollup](#messages-rollup)
29
+ - [Debug tab — synthesize messages](#debug-tab--synthesize-messages)
30
+ - [Enabling in production](#enabling-in-production)
31
+ - [Recipes](#recipes)
32
+ - [How it works](#how-it-works)
33
+ - [License](#license)
34
+
35
+ ---
36
+
37
+ ## Quickstart
38
+
39
+ Boot the broker as usual, mount the panel anywhere in your dev UI,
40
+ pass the broker instance in.
41
+
42
+ ```tsx
43
+ import { initBroker, getBroker } from '@hedwigjs/broker';
44
+ import { MessageBrokerDevTools } from '@hedwigjs/devtools';
45
+
46
+ initBroker({ history: { enabled: true, maxSize: 200 } });
47
+
48
+ function App() {
49
+ return (
50
+ <>
51
+ <YourAppRoot />
52
+ <MessageBrokerDevTools broker={getBroker()} />
53
+ </>
54
+ );
55
+ }
56
+ ```
57
+
58
+ That's the whole integration. The panel attaches to `broker.$systemEvents`
59
+ and the extension hooks (`useBeforeSendHook` / `useAfterSendHook`) on mount,
60
+ detaches on unmount, and renders itself as a floating rail with a toggle
61
+ button. Enabled by default only when `process.env.NODE_ENV === 'development'`.
62
+
63
+ ---
64
+
65
+ ## What the panel shows
66
+
67
+ Six tabs, each backed by one channel of broker observability.
68
+
69
+ | Tab | Source | Purpose |
70
+ | ----------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
71
+ | **Messages** | `useBeforeSendHook` (pending) + `useAfterSendHook` (delivered / failed) | Live feed of every message. Topic, source, target, status, latency, delivery result, JSON payload preview. |
72
+ | **Clients** | `inspect.getClients()` + `subscription.*` and `client.*` system events | Tree of every registered client and its subscriptions, with per-subscription last-received timestamp. |
73
+ | **Bridges** | `inspect.getBridges()` + `bridge.*` system events | Every registered bridge — forward patterns, transport kind, approximate send / receive counters. |
74
+ | **Replay Buffer** | `inspect.getHistory()` | Contents of the broker's history ring. Only populated when `initBroker({ history: { enabled: true } })`. |
75
+ | **System Events** | `$systemEvents.onAny` | Unified log of lifecycle signals: `client.*`, `subscription.*`, `bridge.*`, plus `*.rejected` security signals. |
76
+ | **Debug** | `broker.$debug.send` | Compose and send a synthetic message through the full pipeline. Impersonate any source; multicast or unicast. |
77
+
78
+ Rejections from hooks surface in three places at once:
79
+
80
+ - `subscription.rejected` → System Events tab (the security channel).
81
+ - `message.rejected` → System Events tab, **and** the corresponding
82
+ emit shows as `NACK HOOK_REJECTED` in Messages.
83
+ - Replayed and synthetic (DevTools-injected) messages are visually
84
+ marked so you can distinguish them from live user traffic.
85
+
86
+ ---
87
+
88
+ ## Props reference
89
+
90
+ ```ts
91
+ import type {
92
+ MessageBrokerDevToolsProps,
93
+ DevToolsPanelPosition,
94
+ } from '@hedwigjs/devtools';
95
+ ```
96
+
97
+ | Prop | Type | Default | Purpose |
98
+ | ----------------- | ---------------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------ |
99
+ | `broker` | `MessageBroker` (from `@hedwigjs/broker`) | — | Required. The broker to attach to. |
100
+ | `enabled` | `boolean` | `process.env.NODE_ENV === 'development'` | Master switch. When `false`, the component renders `null` and never attaches. |
101
+ | `registry` | `TopicsRegistry` | `undefined` | Optional topic catalog for autocomplete and payload prefill in the Debug tab. See below. |
102
+ | `maxEvents` | `number` | `100` | Ring-buffer capacity for the Messages log and System Events log. |
103
+ | `defaultPosition` | `DevToolsPanelPosition` — `"top" \| "bottom" \| "left" \| "right"` | `"bottom"` | Initial dock side. Persisted per user in `localStorage`. |
104
+ | `fabPosition` | `DevToolsPanelPosition` | `"right"` | Edge for the floating toggle button (independent of the panel's dock side). |
105
+ | `storageKey` | `string` | one built-in default | `localStorage` key for position, active tab, size, and open state. |
106
+ | `defaultOpen` | `boolean` | `false` | Open the panel on first mount (no saved state). |
107
+ | `toggleIcon` | `ReactNode` | built-in mascot PNG | Replace the FAB icon. |
108
+ | `rollup` | `MessagesRollupConfig \| false` | `{ minCount: 5, windowMs: 1000 }` | Collapse same-topic bursts. `false` disables rollup. See "Messages rollup". |
109
+
110
+ `MessageBrokerDevToolsProps` and `DevToolsPanelPosition` are exported
111
+ for callers that wrap the panel.
112
+
113
+ ---
114
+
115
+ ## Topics registry (bring your own)
116
+
117
+ DevTools does not depend on a specific registry package. It accepts
118
+ any `Record<string, TopicContractInfo>` — one entry per topic — via the
119
+ `registry` prop.
120
+
121
+ ```ts
122
+ export interface TopicContractInfo {
123
+ name: string; // 'cart.item-added.v1'
124
+ description: string; // shown in Debug tab + hover cards
125
+ examples?: Readonly<Record<string, unknown>>; // 'happy' key is the default fixture
126
+ deprecatedBy?: string; // topic that replaces this one
127
+ observability?: boolean; // NACK NO_SUBSCRIBERS is expected for this topic
128
+ }
129
+ ```
130
+
131
+ Common producers:
132
+
133
+ - `@hedwigjs/create-registry` — the opinionated starter kit; emits an
134
+ `EventContract`-shaped registry directly.
135
+ - **Hand-written manifests** — a `.ts` file exporting a plain object.
136
+ - **Codegen** — from Zod schemas, Protobuf, GraphQL, OpenAPI, or any
137
+ other source. Adapt the output shape into `TopicContractInfo`.
138
+
139
+ ```tsx
140
+ import { menuContracts } from '@your-app/menu-registry';
141
+ import { cartContracts } from '@your-app/cart-registry';
142
+
143
+ <MessageBrokerDevTools
144
+ broker={getBroker()}
145
+ registry={{ ...menuContracts, ...cartContracts }}
146
+ />
147
+ ```
148
+
149
+ When a registry is passed:
150
+
151
+ - Debug tab autocompletes topic names and prefills the payload editor
152
+ with `examples.happy` (or the first example when `happy` is absent).
153
+ - Hover cards on the Messages tab surface the topic description and
154
+ `deprecatedBy` warnings.
155
+ - Topics marked `observability: true` render `NACK NO_SUBSCRIBERS`
156
+ neutrally instead of as errors.
157
+
158
+ Without a registry, all of the above degrades gracefully — the Debug
159
+ tab is still fully functional with a free-form topic input and an
160
+ empty payload.
161
+
162
+ ---
163
+
164
+ ## Messages rollup
165
+
166
+ High-frequency streams (SSE chunks, chatty polling, tick-based
167
+ telemetry) can drown out everything else in the log. The rollup
168
+ collapses consecutive `(topic, source)` bursts into a single
169
+ expandable row.
170
+
171
+ ```tsx
172
+ <MessageBrokerDevTools
173
+ broker={getBroker()}
174
+ rollup={{ minCount: 5, windowMs: 1000 }} // default
175
+ />
176
+ ```
177
+
178
+ - `minCount` — minimum consecutive matching messages before folding
179
+ kicks in. Bursts shorter than this stay flat as individual rows.
180
+ - `windowMs` — the maximum gap between adjacent messages that still
181
+ count as one stream.
182
+
183
+ Pass `rollup={false}` for a fully flat feed (useful when you're
184
+ specifically debugging burst behaviour and want each row visible).
185
+
186
+ ---
187
+
188
+ ## Debug tab — synthesize messages
189
+
190
+ The Debug tab drives `broker.$debug.send`, the broker's internal
191
+ inject-with-arbitrary-source primitive. Every message it sends flows
192
+ through the **full pipeline**: hooks run, subscribers receive it, the
193
+ history buffer records it, bridges forward it. The only difference
194
+ from a normal `emit` is `synthetic: true` in the message envelope, so
195
+ DevTools can visually flag spoofed traffic.
196
+
197
+ Controls:
198
+
199
+ - **Multicast / Unicast** — picks `target: '*'` vs `target: <clientId>`.
200
+ - **Source** — dropdown of registered client ids plus a plain
201
+ `devtools` label. Impersonation is safe: the broker does not touch
202
+ the client registry when handling `$debug.send`.
203
+ - **Topic** — autocompleted from the registry when one is provided;
204
+ otherwise free-form.
205
+ - **Payload** — JSON editor, prefilled from `examples.happy` when the
206
+ registry has one.
207
+ - **Result panel** — full `RoutingResult` (status, reason, recipients,
208
+ handler response) is shown after send.
209
+
210
+ Typical uses: reproduce a bug against a running app without leaving
211
+ DevTools, exercise the receiver side of a topic before its producer
212
+ is written, prime the history buffer for a replay test.
213
+
214
+ ---
215
+
216
+ ## Enabling in production
217
+
218
+ By default the panel only renders when `process.env.NODE_ENV === 'development'`.
219
+ This relies on the standard `DefinePlugin` / bundler substitution.
220
+
221
+ - **Development builds** — the panel mounts as-is.
222
+ - **Preview / staging** — pass `enabled={someEnvFlag}` to gate it on
223
+ a feature flag or query parameter (`?debug=1`), so you can toggle
224
+ DevTools on for QA without shipping it to end users.
225
+ - **Production** — leave `enabled` unset (or explicitly `false`). The
226
+ component renders `null` and never attaches — the broker is not
227
+ touched, no hooks are registered, no listeners are added.
228
+
229
+ The panel bundle itself is `sideEffects: true` because it registers
230
+ global CSS on import — treat it as a dev-only import if you want it
231
+ tree-shaken out of the production bundle:
232
+
233
+ ```ts
234
+ const DevTools = process.env.NODE_ENV === 'development'
235
+ ? (await import('@hedwigjs/devtools')).MessageBrokerDevTools
236
+ : null;
237
+ ```
238
+
239
+ ---
240
+
241
+ ## Recipes
242
+
243
+ ### Multi-team monorepo — union the registries
244
+
245
+ ```tsx
246
+ import { menuContracts } from '@your-app/menu-registry';
247
+ import { cartContracts } from '@your-app/cart-registry';
248
+ import { userContracts } from '@your-app/user-registry';
249
+
250
+ const registry = { ...menuContracts, ...cartContracts, ...userContracts };
251
+
252
+ <MessageBrokerDevTools broker={getBroker()} registry={registry} />
253
+ ```
254
+
255
+ Each team keeps its own registry package. The shell composes them.
256
+
257
+ ### Dedicated `/dev` route
258
+
259
+ Mount the panel with `defaultOpen` on a route only reachable in
260
+ dev/staging:
261
+
262
+ ```tsx
263
+ <Route path="/dev" element={
264
+ <MessageBrokerDevTools
265
+ broker={getBroker()}
266
+ enabled
267
+ defaultOpen
268
+ defaultPosition="right"
269
+ storageKey="mfe-dev-panel"
270
+ />
271
+ } />
272
+ ```
273
+
274
+ ### Gate by query param in preview builds
275
+
276
+ ```tsx
277
+ const enabled = new URLSearchParams(location.search).has('debug');
278
+
279
+ <MessageBrokerDevTools broker={getBroker()} enabled={enabled} />
280
+ ```
281
+
282
+ Ship the bundle everywhere; only `?debug=1` visitors see it.
283
+
284
+ ### Custom FAB icon
285
+
286
+ ```tsx
287
+ <MessageBrokerDevTools
288
+ broker={getBroker()}
289
+ toggleIcon={<YourLogo size={32} />}
290
+ />
291
+ ```
292
+
293
+ ### Distinct panel per broker (multi-broker apps)
294
+
295
+ Nothing forces one broker per app. Pass different broker instances to
296
+ different panels with distinct `storageKey`s so each panel remembers
297
+ its own layout:
298
+
299
+ ```tsx
300
+ <MessageBrokerDevTools broker={brokerA} storageKey="devtools-a" />
301
+ <MessageBrokerDevTools broker={brokerB} storageKey="devtools-b" />
302
+ ```
303
+
304
+ ---
305
+
306
+ ## How it works
307
+
308
+ The panel is a thin React shell over the broker's stable observability
309
+ surface. On mount it calls an internal `attachInspector(broker, store)`
310
+ that wires up two channels:
311
+
312
+ - **Extension hooks (write path)** — `useBeforeSendHook` records a
313
+ `pending` entry when a message enters the pipeline; `useAfterSendHook`
314
+ flips it to `delivered` or `failed` and stamps the `RoutingResult`.
315
+ This is how the Messages tab shows a message before it's dispatched
316
+ and updates it in place with the final outcome and latency.
317
+ - **System events (lifecycle path)** — `$systemEvents.on('client.*')`,
318
+ `$systemEvents.on('subscription.*')`, and `$systemEvents.on('bridge.*')`
319
+ drive the Clients and Bridges tabs and populate the System Events log.
320
+ `subscription.rejected` and `message.rejected` are surfaced separately
321
+ as security signals.
322
+ - **Snapshots (initial hydration)** — `inspect.getClients()`,
323
+ `inspect.getHistory()`, and `inspect.getBridges()` prime state on
324
+ attach and refresh on each system event, so the tabs are correct
325
+ even for bridges that were registered *before* the panel mounted.
326
+
327
+ On unmount everything unsubscribes — the broker is left exactly as it
328
+ was before the panel attached. Nothing on the broker knows or cares
329
+ that DevTools was ever mounted; the panel is purely a consumer of the
330
+ public observability surface. Anyone can build an alternative panel
331
+ (a browser-extension host, a CLI tail, a metrics collector) against
332
+ the same surface — see
333
+ [`@hedwigjs/broker` observability surface](../broker/README.md#broker-extension-surface)
334
+ for the contract.
335
+
336
+ ---
337
+
338
+ ## License
339
+
340
+ MIT.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Публичный API: корневой UI-компонент и типы пропсов.
3
+ * Остальное (инспектор, attach) — внутренние детали пакета.
4
+ */
5
+ export { MessageBrokerDevTools } from "./ui/MessageBrokerDevTools";
6
+ export type { MessageBrokerDevToolsProps, DevToolsPanelPosition } from "./ui/MessageBrokerDevTools";
7
+ export { useTopicsRegistry } from "./ui/topicsRegistry";
8
+ export type { TopicsRegistry, TopicContractInfo } from "./ui/topicsRegistry";
9
+ export type { MessagesRollupConfig } from "./inspector/types";