causal-inspector 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/dist/CausalInspector.css +124 -0
- package/dist/CausalInspector.d.ts +13 -0
- package/dist/CausalInspector.js +257 -0
- package/dist/components/CopyablePayload.d.ts +4 -0
- package/dist/components/CopyablePayload.js +44 -0
- package/dist/components/FilterBar.d.ts +1 -0
- package/dist/components/FilterBar.js +23 -0
- package/dist/components/GlobalScrubber.d.ts +1 -0
- package/dist/components/GlobalScrubber.js +148 -0
- package/dist/components/JsonSyntax.d.ts +3 -0
- package/dist/components/JsonSyntax.js +40 -0
- package/dist/context.d.ts +26 -0
- package/dist/context.js +28 -0
- package/dist/engines/index.d.ts +22 -0
- package/dist/engines/index.js +29 -0
- package/dist/engines/query.d.ts +14 -0
- package/dist/engines/query.js +241 -0
- package/dist/engines/scrubber.d.ts +12 -0
- package/dist/engines/scrubber.js +69 -0
- package/dist/engines/storage.d.ts +15 -0
- package/dist/engines/storage.js +16 -0
- package/dist/engines/subscription.d.ts +17 -0
- package/dist/engines/subscription.js +44 -0
- package/dist/engines/url.d.ts +13 -0
- package/dist/engines/url.js +64 -0
- package/dist/events.d.ts +77 -0
- package/dist/events.js +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +29 -0
- package/dist/machine/core.d.ts +22 -0
- package/dist/machine/core.js +34 -0
- package/dist/machine/engine.d.ts +17 -0
- package/dist/machine/engine.js +21 -0
- package/dist/machine/events.d.ts +18 -0
- package/dist/machine/events.js +1 -0
- package/dist/machine/hooks.d.ts +23 -0
- package/dist/machine/hooks.js +52 -0
- package/dist/machine/index.d.ts +5 -0
- package/dist/machine/index.js +4 -0
- package/dist/machine/store.d.ts +27 -0
- package/dist/machine/store.js +42 -0
- package/dist/panes/AggregateTimelinePane.d.ts +2 -0
- package/dist/panes/AggregateTimelinePane.js +224 -0
- package/dist/panes/CausalFlowPane.d.ts +7 -0
- package/dist/panes/CausalFlowPane.js +596 -0
- package/dist/panes/CausalTreePane.d.ts +5 -0
- package/dist/panes/CausalTreePane.js +158 -0
- package/dist/panes/CorrelationExplorerPane.d.ts +2 -0
- package/dist/panes/CorrelationExplorerPane.js +46 -0
- package/dist/panes/LogsPane.d.ts +6 -0
- package/dist/panes/LogsPane.js +65 -0
- package/dist/panes/TimelinePane.d.ts +6 -0
- package/dist/panes/TimelinePane.js +121 -0
- package/dist/panes/WaterfallPane.d.ts +2 -0
- package/dist/panes/WaterfallPane.js +202 -0
- package/dist/queries.d.ts +15 -0
- package/dist/queries.js +175 -0
- package/dist/reducer.d.ts +4 -0
- package/dist/reducer.js +177 -0
- package/dist/state.d.ts +34 -0
- package/dist/state.js +39 -0
- package/dist/theme.d.ts +7 -0
- package/dist/theme.js +34 -0
- package/dist/types.d.ts +140 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +14 -0
- package/dist/utils.js +91 -0
- package/package.json +43 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useCallback } from "react";
|
|
3
|
+
import { useSelector, useDispatch } from "../machine";
|
|
4
|
+
import { inScrubberRange } from "../utils";
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Timing helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
function parseTime(ts) {
|
|
9
|
+
if (!ts)
|
|
10
|
+
return null;
|
|
11
|
+
return new Date(ts).getTime();
|
|
12
|
+
}
|
|
13
|
+
function formatDuration(ms) {
|
|
14
|
+
if (ms < 1)
|
|
15
|
+
return "<1ms";
|
|
16
|
+
if (ms < 1000)
|
|
17
|
+
return `${Math.round(ms)}ms`;
|
|
18
|
+
if (ms < 60_000)
|
|
19
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
20
|
+
return `${(ms / 60_000).toFixed(1)}m`;
|
|
21
|
+
}
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Colors
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
const STATUS_COLORS = {
|
|
26
|
+
completed: { bar: "#22c55e", barEnd: "#16a34a", text: "#bbf7d0" },
|
|
27
|
+
running: { bar: "#eab308", barEnd: "#ca8a04", text: "#fef08a" },
|
|
28
|
+
error: { bar: "#ef4444", barEnd: "#dc2626", text: "#fecaca" },
|
|
29
|
+
};
|
|
30
|
+
function statusColor(status) {
|
|
31
|
+
return STATUS_COLORS[status] ?? STATUS_COLORS.running;
|
|
32
|
+
}
|
|
33
|
+
function buildBars(outcomes) {
|
|
34
|
+
const bars = [];
|
|
35
|
+
let minMs = Infinity;
|
|
36
|
+
let maxMs = -Infinity;
|
|
37
|
+
for (const o of outcomes) {
|
|
38
|
+
const start = parseTime(o.startedAt);
|
|
39
|
+
if (start == null)
|
|
40
|
+
continue; // can't render without start
|
|
41
|
+
const end = parseTime(o.completedAt) ?? Date.now();
|
|
42
|
+
minMs = Math.min(minMs, start);
|
|
43
|
+
maxMs = Math.max(maxMs, end);
|
|
44
|
+
bars.push({
|
|
45
|
+
reactorId: o.reactorId,
|
|
46
|
+
status: o.status,
|
|
47
|
+
error: o.error,
|
|
48
|
+
attempts: o.attempts,
|
|
49
|
+
startMs: start,
|
|
50
|
+
endMs: end,
|
|
51
|
+
triggeringEventIds: o.triggeringEventIds,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
// Sort by start time
|
|
55
|
+
bars.sort((a, b) => a.startMs - b.startMs);
|
|
56
|
+
return { bars, minMs, maxMs };
|
|
57
|
+
}
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Constants
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
const ROW_HEIGHT = 36;
|
|
62
|
+
const LABEL_WIDTH = 160;
|
|
63
|
+
const BAR_MIN_WIDTH = 4;
|
|
64
|
+
const PADDING_X = 12;
|
|
65
|
+
export function WaterfallPane() {
|
|
66
|
+
const correlationId = useSelector((s) => s.flowCorrelationId);
|
|
67
|
+
const outcomes = useSelector((s) => correlationId ? s.outcomes[correlationId] ?? [] : []);
|
|
68
|
+
const flowData = useSelector((s) => s.flowData);
|
|
69
|
+
const scrubberStart = useSelector((s) => s.scrubberStart);
|
|
70
|
+
const scrubberEnd = useSelector((s) => s.scrubberEnd);
|
|
71
|
+
const logsFilter = useSelector((s) => s.logsFilter);
|
|
72
|
+
const dispatch = useDispatch();
|
|
73
|
+
const handleBarClick = useCallback((bar) => {
|
|
74
|
+
dispatch({ type: "ui/handler_selected", payload: { reactorId: bar.reactorId } });
|
|
75
|
+
}, [dispatch]);
|
|
76
|
+
const { bars, minMs, maxMs } = useMemo(() => buildBars(outcomes), [outcomes]);
|
|
77
|
+
const rangeMs = maxMs - minMs || 1;
|
|
78
|
+
// Map event id → seq for scrubber sync
|
|
79
|
+
const eventIdToSeq = useMemo(() => {
|
|
80
|
+
const map = new Map();
|
|
81
|
+
for (const e of flowData) {
|
|
82
|
+
if (e.id)
|
|
83
|
+
map.set(e.id, e.seq);
|
|
84
|
+
}
|
|
85
|
+
return map;
|
|
86
|
+
}, [flowData]);
|
|
87
|
+
// Map scrubber end seq → timestamp for cursor line
|
|
88
|
+
const scrubberMs = useMemo(() => {
|
|
89
|
+
if (scrubberEnd == null)
|
|
90
|
+
return null;
|
|
91
|
+
const event = flowData.find((e) => e.seq === scrubberEnd);
|
|
92
|
+
if (!event)
|
|
93
|
+
return null;
|
|
94
|
+
return new Date(event.ts).getTime();
|
|
95
|
+
}, [scrubberEnd, flowData]);
|
|
96
|
+
if (!correlationId) {
|
|
97
|
+
return (_jsx("div", { style: { height: "100%", display: "flex", alignItems: "center", justifyContent: "center", color: "#50506a", fontSize: 12, letterSpacing: "0.03em" }, children: "Open a flow to see the reactor waterfall" }));
|
|
98
|
+
}
|
|
99
|
+
if (bars.length === 0) {
|
|
100
|
+
return (_jsx("div", { style: { height: "100%", display: "flex", alignItems: "center", justifyContent: "center", color: "#50506a", fontSize: 12, letterSpacing: "0.03em" }, children: "No reactor execution data for this correlation" }));
|
|
101
|
+
}
|
|
102
|
+
return (_jsxs("div", { style: { height: "100%", overflow: "auto", padding: `10px ${PADDING_X}px` }, children: [_jsxs("div", { style: { display: "flex", marginBottom: 6, marginLeft: LABEL_WIDTH, position: "relative", height: 16 }, children: [_jsx("span", { style: { fontSize: 9, color: "#40405a", letterSpacing: "0.04em" }, children: "0ms" }), _jsx("span", { style: { fontSize: 9, color: "#40405a", position: "absolute", right: 0, letterSpacing: "0.04em" }, children: formatDuration(rangeMs) }), scrubberMs != null && scrubberMs >= minMs && scrubberMs <= maxMs && (_jsx("div", { style: {
|
|
103
|
+
position: "absolute",
|
|
104
|
+
left: `${((scrubberMs - minMs) / rangeMs) * 100}%`,
|
|
105
|
+
top: 0,
|
|
106
|
+
bottom: -4,
|
|
107
|
+
width: 1,
|
|
108
|
+
background: "#6366f1",
|
|
109
|
+
pointerEvents: "none",
|
|
110
|
+
boxShadow: "0 0 4px rgba(99, 102, 241, 0.4)",
|
|
111
|
+
} }))] }), bars.map((bar) => {
|
|
112
|
+
const offsetPct = ((bar.startMs - minMs) / rangeMs) * 100;
|
|
113
|
+
const widthPct = Math.max(((bar.endMs - bar.startMs) / rangeMs) * 100, 0.5);
|
|
114
|
+
const colors = statusColor(bar.status);
|
|
115
|
+
const duration = bar.endMs - bar.startMs;
|
|
116
|
+
// Scrubber sync: dim bars whose triggering events are all outside range
|
|
117
|
+
const isFuture = (scrubberStart != null || scrubberEnd != null) &&
|
|
118
|
+
bar.triggeringEventIds.length > 0 &&
|
|
119
|
+
bar.triggeringEventIds.every((eid) => {
|
|
120
|
+
const seq = eventIdToSeq.get(eid);
|
|
121
|
+
return seq != null && !inScrubberRange(seq, scrubberStart, scrubberEnd);
|
|
122
|
+
});
|
|
123
|
+
// Scrubber cursor position within this bar's track
|
|
124
|
+
const cursorPct = scrubberMs != null && scrubberMs >= minMs && scrubberMs <= maxMs
|
|
125
|
+
? ((scrubberMs - minMs) / rangeMs) * 100
|
|
126
|
+
: null;
|
|
127
|
+
const isSelected = logsFilter.reactorId === bar.reactorId;
|
|
128
|
+
return (_jsxs("div", { onClick: () => handleBarClick(bar), style: {
|
|
129
|
+
display: "flex",
|
|
130
|
+
alignItems: "center",
|
|
131
|
+
height: ROW_HEIGHT,
|
|
132
|
+
gap: 0,
|
|
133
|
+
opacity: isFuture ? 0.25 : 1,
|
|
134
|
+
transition: "opacity 200ms, background 150ms",
|
|
135
|
+
cursor: "pointer",
|
|
136
|
+
borderRadius: 6,
|
|
137
|
+
background: isSelected ? "rgba(99, 102, 241, 0.15)" : "transparent",
|
|
138
|
+
paddingLeft: 4,
|
|
139
|
+
paddingRight: 4,
|
|
140
|
+
}, children: [_jsx("div", { style: {
|
|
141
|
+
width: LABEL_WIDTH,
|
|
142
|
+
flexShrink: 0,
|
|
143
|
+
fontSize: 11,
|
|
144
|
+
color: "#c0c0d0",
|
|
145
|
+
fontWeight: 500,
|
|
146
|
+
overflow: "hidden",
|
|
147
|
+
textOverflow: "ellipsis",
|
|
148
|
+
whiteSpace: "nowrap",
|
|
149
|
+
paddingRight: 10,
|
|
150
|
+
letterSpacing: "0.01em",
|
|
151
|
+
}, title: bar.reactorId, children: bar.reactorId }), _jsxs("div", { style: {
|
|
152
|
+
flex: 1,
|
|
153
|
+
position: "relative",
|
|
154
|
+
height: 22,
|
|
155
|
+
background: "rgba(255, 255, 255, 0.02)",
|
|
156
|
+
borderRadius: 5,
|
|
157
|
+
overflow: "hidden",
|
|
158
|
+
}, children: [_jsx("div", { style: {
|
|
159
|
+
position: "absolute",
|
|
160
|
+
left: `${offsetPct}%`,
|
|
161
|
+
width: `${widthPct}%`,
|
|
162
|
+
minWidth: BAR_MIN_WIDTH,
|
|
163
|
+
height: "100%",
|
|
164
|
+
background: `linear-gradient(90deg, ${colors.bar}, ${colors.barEnd})`,
|
|
165
|
+
borderRadius: 4,
|
|
166
|
+
opacity: 0.8,
|
|
167
|
+
display: "flex",
|
|
168
|
+
alignItems: "center",
|
|
169
|
+
paddingLeft: 5,
|
|
170
|
+
paddingRight: 5,
|
|
171
|
+
boxShadow: `0 1px 4px ${colors.bar}30`,
|
|
172
|
+
}, title: `${bar.reactorId}: ${bar.status} (${formatDuration(duration)})${bar.attempts > 1 ? ` — ${bar.attempts} attempts` : ""}${bar.error ? `\nError: ${bar.error}` : ""}`, children: _jsx("span", { style: {
|
|
173
|
+
fontSize: 9,
|
|
174
|
+
fontWeight: 600,
|
|
175
|
+
color: "#0a0a0f",
|
|
176
|
+
whiteSpace: "nowrap",
|
|
177
|
+
overflow: "hidden",
|
|
178
|
+
textOverflow: "ellipsis",
|
|
179
|
+
}, children: formatDuration(duration) }) }), cursorPct != null && (_jsx("div", { style: {
|
|
180
|
+
position: "absolute",
|
|
181
|
+
left: `${cursorPct}%`,
|
|
182
|
+
top: 0,
|
|
183
|
+
bottom: 0,
|
|
184
|
+
width: 1,
|
|
185
|
+
background: "#6366f1",
|
|
186
|
+
pointerEvents: "none",
|
|
187
|
+
boxShadow: "0 0 4px rgba(99, 102, 241, 0.3)",
|
|
188
|
+
} }))] }), _jsxs("div", { style: {
|
|
189
|
+
width: 80,
|
|
190
|
+
flexShrink: 0,
|
|
191
|
+
display: "flex",
|
|
192
|
+
alignItems: "center",
|
|
193
|
+
gap: 4,
|
|
194
|
+
paddingLeft: 10,
|
|
195
|
+
}, children: [_jsx("span", { style: {
|
|
196
|
+
fontSize: 10,
|
|
197
|
+
fontWeight: 500,
|
|
198
|
+
color: colors.text,
|
|
199
|
+
opacity: 0.8,
|
|
200
|
+
}, children: bar.status }), bar.attempts > 1 && (_jsxs("span", { style: { fontSize: 9, color: "#50506a" }, children: ["x", bar.attempts] }))] })] }, bar.reactorId));
|
|
201
|
+
}), _jsxs("div", { style: { marginTop: 10, fontSize: 10, color: "#40405a", marginLeft: LABEL_WIDTH, letterSpacing: "0.03em" }, children: ["Total wall time: ", formatDuration(rangeMs)] })] }));
|
|
202
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** GraphQL query and subscription documents for the causal inspector API. */
|
|
2
|
+
export declare const EVENTS_SUBSCRIPTION = "\n subscription Events($lastSeq: Int) {\n inspectorEventAdded(lastSeq: $lastSeq) {\n \n seq\n ts\n type\n name\n id\n parentId\n correlationId\n reactorId\n aggregateType\n aggregateId\n streamVersion\n summary\n payload\n\n }\n }\n";
|
|
3
|
+
export declare const INSPECTOR_EVENTS = "\n query InspectorEvents(\n $limit: Int!\n $cursor: Int\n $search: String\n $from: DateTime\n $to: DateTime\n $correlationId: String\n $aggregateKey: String\n ) {\n inspectorEvents(\n limit: $limit\n cursor: $cursor\n search: $search\n from: $from\n to: $to\n correlationId: $correlationId\n aggregateKey: $aggregateKey\n ) {\n events {\n \n seq\n ts\n type\n name\n id\n parentId\n correlationId\n reactorId\n aggregateType\n aggregateId\n streamVersion\n summary\n payload\n\n }\n nextCursor\n }\n }\n";
|
|
4
|
+
export declare const INSPECTOR_CAUSAL_TREE = "\n query InspectorCausalTree($seq: Int!) {\n inspectorCausalTree(seq: $seq) {\n events {\n \n seq\n ts\n type\n name\n id\n parentId\n correlationId\n reactorId\n aggregateType\n aggregateId\n streamVersion\n summary\n payload\n\n }\n rootSeq\n }\n }\n";
|
|
5
|
+
export declare const INSPECTOR_CAUSAL_FLOW = "\n query InspectorCausalFlow($correlationId: String!) {\n inspectorCausalFlow(correlationId: $correlationId) {\n events {\n \n seq\n ts\n type\n name\n id\n parentId\n correlationId\n reactorId\n aggregateType\n aggregateId\n streamVersion\n summary\n payload\n\n }\n }\n }\n";
|
|
6
|
+
export declare const INSPECTOR_REACTOR_LOGS = "\n query InspectorReactorLogs($eventId: String!, $reactorId: String!) {\n inspectorReactorLogs(eventId: $eventId, reactorId: $reactorId) {\n eventId\n reactorId\n level\n message\n data\n loggedAt\n }\n }\n";
|
|
7
|
+
export declare const INSPECTOR_REACTOR_LOGS_BY_CORRELATION = "\n query InspectorReactorLogsByCorrelation($correlationId: String!) {\n inspectorReactorLogsByCorrelation(correlationId: $correlationId) {\n eventId\n reactorId\n level\n message\n data\n loggedAt\n }\n }\n";
|
|
8
|
+
export declare const INSPECTOR_REACTOR_DESCRIPTIONS = "\n query InspectorReactorDescriptions($correlationId: String!) {\n inspectorReactorDescriptions(correlationId: $correlationId) {\n reactorId\n blocks\n }\n }\n";
|
|
9
|
+
export declare const INSPECTOR_REACTOR_DESCRIPTION_SNAPSHOTS = "\n query InspectorReactorDescriptionSnapshots($correlationId: String!) {\n inspectorReactorDescriptionSnapshots(correlationId: $correlationId) {\n seq\n eventId\n reactorId\n blocks\n }\n }\n";
|
|
10
|
+
export declare const INSPECTOR_AGGREGATE_TIMELINE = "\n query InspectorAggregateTimeline($correlationId: String!) {\n inspectorAggregateTimeline(correlationId: $correlationId) {\n seq\n eventId\n eventType\n aggregates {\n key\n state\n }\n }\n }\n";
|
|
11
|
+
export declare const INSPECTOR_REACTOR_DEPENDENCIES = "\n query InspectorReactorDependencies {\n inspectorReactorDependencies {\n reactorId\n inputEventTypes\n outputEventTypes\n }\n }\n";
|
|
12
|
+
export declare const INSPECTOR_AGGREGATE_KEYS = "\n query InspectorAggregateKeys {\n inspectorAggregateKeys\n }\n";
|
|
13
|
+
export declare const INSPECTOR_AGGREGATE_LIFECYCLE = "\n query InspectorAggregateLifecycle($aggregateKey: String!, $limit: Int) {\n inspectorAggregateLifecycle(aggregateKey: $aggregateKey, limit: $limit) {\n seq\n eventId\n eventType\n ts\n correlationId\n aggregateKey\n state\n }\n }\n";
|
|
14
|
+
export declare const INSPECTOR_CORRELATIONS = "\n query InspectorCorrelations($search: String, $limit: Int) {\n inspectorCorrelations(search: $search, limit: $limit) {\n correlationId\n eventCount\n firstTs\n lastTs\n rootEventType\n hasErrors\n }\n }\n";
|
|
15
|
+
export declare const INSPECTOR_REACTOR_OUTCOMES = "\n query InspectorReactorOutcomes($correlationId: String!) {\n inspectorReactorOutcomes(correlationId: $correlationId) {\n reactorId\n status\n error\n attempts\n startedAt\n completedAt\n triggeringEventIds\n }\n }\n";
|
package/dist/queries.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/** GraphQL query and subscription documents for the causal inspector API. */
|
|
2
|
+
const EVENT_FIELDS = `
|
|
3
|
+
seq
|
|
4
|
+
ts
|
|
5
|
+
type
|
|
6
|
+
name
|
|
7
|
+
id
|
|
8
|
+
parentId
|
|
9
|
+
correlationId
|
|
10
|
+
reactorId
|
|
11
|
+
aggregateType
|
|
12
|
+
aggregateId
|
|
13
|
+
streamVersion
|
|
14
|
+
summary
|
|
15
|
+
payload
|
|
16
|
+
`;
|
|
17
|
+
export const EVENTS_SUBSCRIPTION = `
|
|
18
|
+
subscription Events($lastSeq: Int) {
|
|
19
|
+
inspectorEventAdded(lastSeq: $lastSeq) {
|
|
20
|
+
${EVENT_FIELDS}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
24
|
+
export const INSPECTOR_EVENTS = `
|
|
25
|
+
query InspectorEvents(
|
|
26
|
+
$limit: Int!
|
|
27
|
+
$cursor: Int
|
|
28
|
+
$search: String
|
|
29
|
+
$from: DateTime
|
|
30
|
+
$to: DateTime
|
|
31
|
+
$correlationId: String
|
|
32
|
+
$aggregateKey: String
|
|
33
|
+
) {
|
|
34
|
+
inspectorEvents(
|
|
35
|
+
limit: $limit
|
|
36
|
+
cursor: $cursor
|
|
37
|
+
search: $search
|
|
38
|
+
from: $from
|
|
39
|
+
to: $to
|
|
40
|
+
correlationId: $correlationId
|
|
41
|
+
aggregateKey: $aggregateKey
|
|
42
|
+
) {
|
|
43
|
+
events {
|
|
44
|
+
${EVENT_FIELDS}
|
|
45
|
+
}
|
|
46
|
+
nextCursor
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
export const INSPECTOR_CAUSAL_TREE = `
|
|
51
|
+
query InspectorCausalTree($seq: Int!) {
|
|
52
|
+
inspectorCausalTree(seq: $seq) {
|
|
53
|
+
events {
|
|
54
|
+
${EVENT_FIELDS}
|
|
55
|
+
}
|
|
56
|
+
rootSeq
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
60
|
+
export const INSPECTOR_CAUSAL_FLOW = `
|
|
61
|
+
query InspectorCausalFlow($correlationId: String!) {
|
|
62
|
+
inspectorCausalFlow(correlationId: $correlationId) {
|
|
63
|
+
events {
|
|
64
|
+
${EVENT_FIELDS}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
export const INSPECTOR_REACTOR_LOGS = `
|
|
70
|
+
query InspectorReactorLogs($eventId: String!, $reactorId: String!) {
|
|
71
|
+
inspectorReactorLogs(eventId: $eventId, reactorId: $reactorId) {
|
|
72
|
+
eventId
|
|
73
|
+
reactorId
|
|
74
|
+
level
|
|
75
|
+
message
|
|
76
|
+
data
|
|
77
|
+
loggedAt
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
`;
|
|
81
|
+
export const INSPECTOR_REACTOR_LOGS_BY_CORRELATION = `
|
|
82
|
+
query InspectorReactorLogsByCorrelation($correlationId: String!) {
|
|
83
|
+
inspectorReactorLogsByCorrelation(correlationId: $correlationId) {
|
|
84
|
+
eventId
|
|
85
|
+
reactorId
|
|
86
|
+
level
|
|
87
|
+
message
|
|
88
|
+
data
|
|
89
|
+
loggedAt
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
`;
|
|
93
|
+
export const INSPECTOR_REACTOR_DESCRIPTIONS = `
|
|
94
|
+
query InspectorReactorDescriptions($correlationId: String!) {
|
|
95
|
+
inspectorReactorDescriptions(correlationId: $correlationId) {
|
|
96
|
+
reactorId
|
|
97
|
+
blocks
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
`;
|
|
101
|
+
export const INSPECTOR_REACTOR_DESCRIPTION_SNAPSHOTS = `
|
|
102
|
+
query InspectorReactorDescriptionSnapshots($correlationId: String!) {
|
|
103
|
+
inspectorReactorDescriptionSnapshots(correlationId: $correlationId) {
|
|
104
|
+
seq
|
|
105
|
+
eventId
|
|
106
|
+
reactorId
|
|
107
|
+
blocks
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
export const INSPECTOR_AGGREGATE_TIMELINE = `
|
|
112
|
+
query InspectorAggregateTimeline($correlationId: String!) {
|
|
113
|
+
inspectorAggregateTimeline(correlationId: $correlationId) {
|
|
114
|
+
seq
|
|
115
|
+
eventId
|
|
116
|
+
eventType
|
|
117
|
+
aggregates {
|
|
118
|
+
key
|
|
119
|
+
state
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
`;
|
|
124
|
+
export const INSPECTOR_REACTOR_DEPENDENCIES = `
|
|
125
|
+
query InspectorReactorDependencies {
|
|
126
|
+
inspectorReactorDependencies {
|
|
127
|
+
reactorId
|
|
128
|
+
inputEventTypes
|
|
129
|
+
outputEventTypes
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
`;
|
|
133
|
+
export const INSPECTOR_AGGREGATE_KEYS = `
|
|
134
|
+
query InspectorAggregateKeys {
|
|
135
|
+
inspectorAggregateKeys
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
138
|
+
export const INSPECTOR_AGGREGATE_LIFECYCLE = `
|
|
139
|
+
query InspectorAggregateLifecycle($aggregateKey: String!, $limit: Int) {
|
|
140
|
+
inspectorAggregateLifecycle(aggregateKey: $aggregateKey, limit: $limit) {
|
|
141
|
+
seq
|
|
142
|
+
eventId
|
|
143
|
+
eventType
|
|
144
|
+
ts
|
|
145
|
+
correlationId
|
|
146
|
+
aggregateKey
|
|
147
|
+
state
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
`;
|
|
151
|
+
export const INSPECTOR_CORRELATIONS = `
|
|
152
|
+
query InspectorCorrelations($search: String, $limit: Int) {
|
|
153
|
+
inspectorCorrelations(search: $search, limit: $limit) {
|
|
154
|
+
correlationId
|
|
155
|
+
eventCount
|
|
156
|
+
firstTs
|
|
157
|
+
lastTs
|
|
158
|
+
rootEventType
|
|
159
|
+
hasErrors
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
`;
|
|
163
|
+
export const INSPECTOR_REACTOR_OUTCOMES = `
|
|
164
|
+
query InspectorReactorOutcomes($correlationId: String!) {
|
|
165
|
+
inspectorReactorOutcomes(correlationId: $correlationId) {
|
|
166
|
+
reactorId
|
|
167
|
+
status
|
|
168
|
+
error
|
|
169
|
+
attempts
|
|
170
|
+
startedAt
|
|
171
|
+
completedAt
|
|
172
|
+
triggeringEventIds
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
`;
|
package/dist/reducer.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared navigation logic used by both user-initiated facts
|
|
3
|
+
* (ui/flow_opened, ui/handler_selected) and browser-initiated
|
|
4
|
+
* navigation (location/changed from popstate).
|
|
5
|
+
*/
|
|
6
|
+
function applyNavigation(draft, correlationId, handler) {
|
|
7
|
+
// Correlation changed → reset flow state
|
|
8
|
+
if (correlationId !== draft.flowCorrelationId) {
|
|
9
|
+
if (correlationId) {
|
|
10
|
+
draft.flowCorrelationId = correlationId;
|
|
11
|
+
draft.flowData = [];
|
|
12
|
+
draft.flowSelection = null;
|
|
13
|
+
draft.scrubberStart = null;
|
|
14
|
+
draft.scrubberEnd = null;
|
|
15
|
+
draft.scrubberPlaying = false;
|
|
16
|
+
draft.logsFilter = {
|
|
17
|
+
scope: "correlation",
|
|
18
|
+
reactorId: null,
|
|
19
|
+
correlationId,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
draft.flowCorrelationId = null;
|
|
24
|
+
draft.flowData = [];
|
|
25
|
+
draft.flowSelection = null;
|
|
26
|
+
draft.scrubberStart = null;
|
|
27
|
+
draft.scrubberEnd = null;
|
|
28
|
+
draft.scrubberPlaying = false;
|
|
29
|
+
draft.logsFilter = {
|
|
30
|
+
scope: "reactor",
|
|
31
|
+
reactorId: null,
|
|
32
|
+
correlationId: null,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Handler changed → update logs filter
|
|
37
|
+
if (handler && handler !== draft.logsFilter.reactorId) {
|
|
38
|
+
draft.logsFilter = {
|
|
39
|
+
scope: "reactor",
|
|
40
|
+
reactorId: handler,
|
|
41
|
+
correlationId: draft.flowCorrelationId,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export const reducer = (draft, event) => {
|
|
46
|
+
switch (event.type) {
|
|
47
|
+
// ── Subscription ──
|
|
48
|
+
case "events/received": {
|
|
49
|
+
const newEvents = event.payload;
|
|
50
|
+
// Filter subscription events against active filters so they don't
|
|
51
|
+
// pollute the view when the user has a search or correlation filter.
|
|
52
|
+
const filtered = newEvents.filter((e) => {
|
|
53
|
+
if (draft.filters.correlationId && e.correlationId !== draft.filters.correlationId) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
if (draft.filters.search) {
|
|
57
|
+
const s = draft.filters.search.toLowerCase();
|
|
58
|
+
const matches = e.name.toLowerCase().includes(s) ||
|
|
59
|
+
e.payload.toLowerCase().includes(s) ||
|
|
60
|
+
(e.correlationId ?? "").toLowerCase().includes(s);
|
|
61
|
+
if (!matches)
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
});
|
|
66
|
+
if (filtered.length > 0) {
|
|
67
|
+
draft.events.unshift(...filtered);
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case "events/subscription_connected":
|
|
72
|
+
draft.subscription = "connected";
|
|
73
|
+
break;
|
|
74
|
+
case "events/subscription_error":
|
|
75
|
+
draft.subscription = "error";
|
|
76
|
+
break;
|
|
77
|
+
// ── Query results ──
|
|
78
|
+
case "events/page_loaded": {
|
|
79
|
+
const { events, hasMore } = event.payload;
|
|
80
|
+
draft.events.push(...events);
|
|
81
|
+
draft.hasMore = hasMore;
|
|
82
|
+
draft.loading = false;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case "events/causal_tree_loaded":
|
|
86
|
+
draft.causalTree = event.payload;
|
|
87
|
+
break;
|
|
88
|
+
case "events/flow_loaded":
|
|
89
|
+
draft.flowData = event.payload;
|
|
90
|
+
break;
|
|
91
|
+
case "events/logs_loaded":
|
|
92
|
+
draft.logs = event.payload;
|
|
93
|
+
break;
|
|
94
|
+
case "events/descriptions_loaded": {
|
|
95
|
+
const { correlationId, descriptions } = event.payload;
|
|
96
|
+
draft.descriptions[correlationId] = descriptions;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case "events/description_snapshots_loaded": {
|
|
100
|
+
const { correlationId, snapshots } = event.payload;
|
|
101
|
+
draft.descriptionSnapshots[correlationId] = snapshots;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case "events/aggregate_timeline_loaded": {
|
|
105
|
+
const { correlationId, entries } = event.payload;
|
|
106
|
+
draft.aggregateTimeline[correlationId] = entries;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case "events/outcomes_loaded": {
|
|
110
|
+
const { correlationId, outcomes } = event.payload;
|
|
111
|
+
draft.outcomes[correlationId] = outcomes;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case "events/correlations_loaded":
|
|
115
|
+
draft.correlations = event.payload;
|
|
116
|
+
draft.correlationsLoading = false;
|
|
117
|
+
break;
|
|
118
|
+
case "events/reactor_dependencies_loaded":
|
|
119
|
+
draft.reactorDependencies = event.payload;
|
|
120
|
+
break;
|
|
121
|
+
case "events/aggregate_keys_loaded":
|
|
122
|
+
draft.aggregateKeys = event.payload;
|
|
123
|
+
break;
|
|
124
|
+
case "events/aggregate_lifecycle_loaded":
|
|
125
|
+
draft.aggregateLifecycleKey = event.payload.key;
|
|
126
|
+
draft.aggregateLifecycle = event.payload.entries;
|
|
127
|
+
break;
|
|
128
|
+
// ── Navigation (user facts + browser popstate) ──
|
|
129
|
+
case "ui/flow_opened":
|
|
130
|
+
applyNavigation(draft, event.payload.correlationId, null);
|
|
131
|
+
break;
|
|
132
|
+
case "ui/flow_closed":
|
|
133
|
+
applyNavigation(draft, null, null);
|
|
134
|
+
break;
|
|
135
|
+
case "ui/handler_selected":
|
|
136
|
+
applyNavigation(draft, draft.flowCorrelationId, event.payload.reactorId);
|
|
137
|
+
break;
|
|
138
|
+
case "location/changed":
|
|
139
|
+
applyNavigation(draft, event.payload.correlationId, event.payload.handler);
|
|
140
|
+
break;
|
|
141
|
+
// ── UI ──
|
|
142
|
+
case "ui/event_selected":
|
|
143
|
+
draft.selectedSeq = event.payload.seq;
|
|
144
|
+
break;
|
|
145
|
+
case "ui/event_deselected":
|
|
146
|
+
draft.selectedSeq = null;
|
|
147
|
+
draft.causalTree = null;
|
|
148
|
+
break;
|
|
149
|
+
case "ui/flow_node_selected":
|
|
150
|
+
draft.flowSelection = event.payload;
|
|
151
|
+
break;
|
|
152
|
+
case "ui/filter_changed":
|
|
153
|
+
Object.assign(draft.filters, event.payload);
|
|
154
|
+
break;
|
|
155
|
+
case "ui/load_more_requested":
|
|
156
|
+
draft.loading = true;
|
|
157
|
+
break;
|
|
158
|
+
case "ui/layout_changed":
|
|
159
|
+
draft.paneLayout = event.payload;
|
|
160
|
+
break;
|
|
161
|
+
case "ui/scrubber_start_changed":
|
|
162
|
+
draft.scrubberStart = event.payload.start;
|
|
163
|
+
break;
|
|
164
|
+
case "ui/scrubber_end_changed":
|
|
165
|
+
draft.scrubberEnd = event.payload.end;
|
|
166
|
+
break;
|
|
167
|
+
case "ui/scrubber_play_toggled":
|
|
168
|
+
draft.scrubberPlaying = !draft.scrubberPlaying;
|
|
169
|
+
break;
|
|
170
|
+
case "ui/scrubber_speed_changed":
|
|
171
|
+
draft.scrubberSpeed = event.payload.speed;
|
|
172
|
+
break;
|
|
173
|
+
case "ui/correlations_requested":
|
|
174
|
+
draft.correlationsLoading = true;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
};
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { InspectorEvent, CorrelationSummary, ReactorDependency, AggregateLifecycleEntry, FilterState, FlowSelection, ReactorDescription, ReactorDescriptionSnapshot, AggregateTimelineEntry, ReactorLog, ReactorOutcome, LogsFilter, PaneLayout } from "./types";
|
|
2
|
+
export type InspectorState = {
|
|
3
|
+
events: InspectorEvent[];
|
|
4
|
+
hasMore: boolean;
|
|
5
|
+
loading: boolean;
|
|
6
|
+
selectedSeq: number | null;
|
|
7
|
+
flowCorrelationId: string | null;
|
|
8
|
+
flowData: InspectorEvent[];
|
|
9
|
+
flowSelection: FlowSelection;
|
|
10
|
+
scrubberStart: number | null;
|
|
11
|
+
scrubberEnd: number | null;
|
|
12
|
+
scrubberPlaying: boolean;
|
|
13
|
+
scrubberSpeed: number;
|
|
14
|
+
causalTree: {
|
|
15
|
+
events: InspectorEvent[];
|
|
16
|
+
rootSeq: number;
|
|
17
|
+
} | null;
|
|
18
|
+
filters: FilterState;
|
|
19
|
+
logs: ReactorLog[];
|
|
20
|
+
logsFilter: LogsFilter;
|
|
21
|
+
descriptions: Record<string, ReactorDescription[]>;
|
|
22
|
+
descriptionSnapshots: Record<string, ReactorDescriptionSnapshot[]>;
|
|
23
|
+
aggregateTimeline: Record<string, AggregateTimelineEntry[]>;
|
|
24
|
+
outcomes: Record<string, ReactorOutcome[]>;
|
|
25
|
+
correlations: CorrelationSummary[];
|
|
26
|
+
correlationsLoading: boolean;
|
|
27
|
+
reactorDependencies: ReactorDependency[];
|
|
28
|
+
aggregateKeys: string[];
|
|
29
|
+
aggregateLifecycle: AggregateLifecycleEntry[];
|
|
30
|
+
aggregateLifecycleKey: string | null;
|
|
31
|
+
subscription: "connected" | "disconnected" | "error";
|
|
32
|
+
paneLayout: PaneLayout | null;
|
|
33
|
+
};
|
|
34
|
+
export declare const initialState: InspectorState;
|
package/dist/state.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const initialState = {
|
|
2
|
+
events: [],
|
|
3
|
+
hasMore: true,
|
|
4
|
+
loading: false,
|
|
5
|
+
selectedSeq: null,
|
|
6
|
+
flowCorrelationId: null,
|
|
7
|
+
flowData: [],
|
|
8
|
+
flowSelection: null,
|
|
9
|
+
scrubberStart: null,
|
|
10
|
+
scrubberEnd: null,
|
|
11
|
+
scrubberPlaying: false,
|
|
12
|
+
scrubberSpeed: 300,
|
|
13
|
+
causalTree: null,
|
|
14
|
+
filters: {
|
|
15
|
+
search: "",
|
|
16
|
+
from: null,
|
|
17
|
+
to: null,
|
|
18
|
+
correlationId: null,
|
|
19
|
+
aggregateKey: null,
|
|
20
|
+
},
|
|
21
|
+
logs: [],
|
|
22
|
+
logsFilter: {
|
|
23
|
+
scope: "reactor",
|
|
24
|
+
reactorId: null,
|
|
25
|
+
correlationId: null,
|
|
26
|
+
},
|
|
27
|
+
descriptions: {},
|
|
28
|
+
descriptionSnapshots: {},
|
|
29
|
+
aggregateTimeline: {},
|
|
30
|
+
outcomes: {},
|
|
31
|
+
correlations: [],
|
|
32
|
+
correlationsLoading: false,
|
|
33
|
+
reactorDependencies: [],
|
|
34
|
+
aggregateKeys: [],
|
|
35
|
+
aggregateLifecycle: [],
|
|
36
|
+
aggregateLifecycleKey: null,
|
|
37
|
+
subscription: "disconnected",
|
|
38
|
+
paneLayout: null,
|
|
39
|
+
};
|
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Deterministic hue from event name — consistent colors across renders. */
|
|
2
|
+
export declare function eventHue(name: string): number;
|
|
3
|
+
/** Background color blended onto dark background — more saturated. */
|
|
4
|
+
export declare function eventBg(name: string): string;
|
|
5
|
+
export declare function eventBorder(name: string): string;
|
|
6
|
+
export declare function eventTextColor(name: string): string;
|
|
7
|
+
export declare const LOG_LEVEL_COLORS: Record<string, string>;
|