@buoy-gg/events 2.1.1
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 +58 -0
- package/README.md +55 -0
- package/lib/commonjs/components/EventsCopySettingsView.js +645 -0
- package/lib/commonjs/components/EventsModal.js +263 -0
- package/lib/commonjs/components/ReactQueryEventDetail.js +428 -0
- package/lib/commonjs/components/UnifiedEventDetail.js +370 -0
- package/lib/commonjs/components/UnifiedEventFilters.js +113 -0
- package/lib/commonjs/components/UnifiedEventItem.js +349 -0
- package/lib/commonjs/components/UnifiedEventList.js +154 -0
- package/lib/commonjs/components/UnifiedEventViewer.js +126 -0
- package/lib/commonjs/hooks/useUnifiedEvents.js +237 -0
- package/lib/commonjs/index.js +205 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/preset.js +66 -0
- package/lib/commonjs/stores/unifiedEventStore.js +413 -0
- package/lib/commonjs/types/copySettings.js +220 -0
- package/lib/commonjs/types/index.js +17 -0
- package/lib/commonjs/utils/autoDiscoverEventSources.js +640 -0
- package/lib/commonjs/utils/badgeSelectionStorage.js +58 -0
- package/lib/commonjs/utils/copySettingsStorage.js +66 -0
- package/lib/commonjs/utils/correlationUtils.js +130 -0
- package/lib/commonjs/utils/eventExportFormatter.js +1095 -0
- package/lib/commonjs/utils/eventTransformers.js +496 -0
- package/lib/module/components/EventsCopySettingsView.js +641 -0
- package/lib/module/components/EventsModal.js +259 -0
- package/lib/module/components/ReactQueryEventDetail.js +424 -0
- package/lib/module/components/UnifiedEventDetail.js +366 -0
- package/lib/module/components/UnifiedEventFilters.js +109 -0
- package/lib/module/components/UnifiedEventItem.js +345 -0
- package/lib/module/components/UnifiedEventList.js +150 -0
- package/lib/module/components/UnifiedEventViewer.js +122 -0
- package/lib/module/hooks/useUnifiedEvents.js +234 -0
- package/lib/module/index.js +77 -0
- package/lib/module/preset.js +62 -0
- package/lib/module/stores/unifiedEventStore.js +387 -0
- package/lib/module/types/copySettings.js +215 -0
- package/lib/module/types/index.js +37 -0
- package/lib/module/utils/autoDiscoverEventSources.js +633 -0
- package/lib/module/utils/badgeSelectionStorage.js +52 -0
- package/lib/module/utils/copySettingsStorage.js +61 -0
- package/lib/module/utils/correlationUtils.js +120 -0
- package/lib/module/utils/eventExportFormatter.js +1085 -0
- package/lib/module/utils/eventTransformers.js +487 -0
- package/lib/typescript/components/EventsCopySettingsView.d.ts +16 -0
- package/lib/typescript/components/EventsModal.d.ts +16 -0
- package/lib/typescript/components/ReactQueryEventDetail.d.ts +15 -0
- package/lib/typescript/components/UnifiedEventDetail.d.ts +15 -0
- package/lib/typescript/components/UnifiedEventFilters.d.ts +21 -0
- package/lib/typescript/components/UnifiedEventItem.d.ts +26 -0
- package/lib/typescript/components/UnifiedEventList.d.ts +27 -0
- package/lib/typescript/components/UnifiedEventViewer.d.ts +8 -0
- package/lib/typescript/hooks/useUnifiedEvents.d.ts +30 -0
- package/lib/typescript/index.d.ts +28 -0
- package/lib/typescript/preset.d.ts +62 -0
- package/lib/typescript/stores/unifiedEventStore.d.ts +146 -0
- package/lib/typescript/types/copySettings.d.ts +179 -0
- package/lib/typescript/types/index.d.ts +73 -0
- package/lib/typescript/utils/autoDiscoverEventSources.d.ts +74 -0
- package/lib/typescript/utils/badgeSelectionStorage.d.ts +21 -0
- package/lib/typescript/utils/copySettingsStorage.d.ts +21 -0
- package/lib/typescript/utils/correlationUtils.d.ts +36 -0
- package/lib/typescript/utils/eventExportFormatter.d.ts +49 -0
- package/lib/typescript/utils/eventTransformers.d.ts +119 -0
- package/package.json +91 -0
- package/src/components/EventsCopySettingsView.tsx +742 -0
- package/src/components/EventsModal.tsx +328 -0
- package/src/components/ReactQueryEventDetail.tsx +413 -0
- package/src/components/UnifiedEventDetail.tsx +371 -0
- package/src/components/UnifiedEventFilters.tsx +156 -0
- package/src/components/UnifiedEventItem.tsx +396 -0
- package/src/components/UnifiedEventList.tsx +197 -0
- package/src/components/UnifiedEventViewer.tsx +132 -0
- package/src/hooks/useUnifiedEvents.ts +288 -0
- package/src/index.tsx +112 -0
- package/src/preset.tsx +57 -0
- package/src/stores/unifiedEventStore.ts +405 -0
- package/src/types/copySettings.ts +269 -0
- package/src/types/index.ts +96 -0
- package/src/utils/autoDiscoverEventSources.ts +690 -0
- package/src/utils/badgeSelectionStorage.ts +51 -0
- package/src/utils/copySettingsStorage.ts +61 -0
- package/src/utils/correlationUtils.ts +146 -0
- package/src/utils/eventExportFormatter.ts +1233 -0
- package/src/utils/eventTransformers.ts +567 -0
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Transformers
|
|
3
|
+
*
|
|
4
|
+
* Transform events from each source into the unified UnifiedEvent format.
|
|
5
|
+
*
|
|
6
|
+
* Note: These transformers use generic types to avoid hard dependencies
|
|
7
|
+
* on tool packages. The actual transformation logic is in autoDiscoverEventSources.ts.
|
|
8
|
+
* This file is kept for backwards compatibility and type exports.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { UnifiedEvent, EventStatus } from "../types";
|
|
12
|
+
|
|
13
|
+
let eventIdCounter = 0;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate a unique event ID
|
|
17
|
+
*/
|
|
18
|
+
function generateEventId(source: string): string {
|
|
19
|
+
return `${source}-${Date.now()}-${++eventIdCounter}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Storage Event Types (for backwards compatibility)
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* AsyncStorage event action types
|
|
28
|
+
*/
|
|
29
|
+
export type AsyncStorageAction =
|
|
30
|
+
| "setItem"
|
|
31
|
+
| "removeItem"
|
|
32
|
+
| "mergeItem"
|
|
33
|
+
| "clear"
|
|
34
|
+
| "multiSet"
|
|
35
|
+
| "multiRemove"
|
|
36
|
+
| "multiMerge";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Generic AsyncStorage event structure
|
|
40
|
+
*/
|
|
41
|
+
export interface AsyncStorageEvent {
|
|
42
|
+
action: AsyncStorageAction;
|
|
43
|
+
timestamp: Date;
|
|
44
|
+
data?: {
|
|
45
|
+
key?: string;
|
|
46
|
+
value?: unknown;
|
|
47
|
+
pairs?: Array<[string, unknown]>;
|
|
48
|
+
keys?: string[];
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Generic Storage event (AsyncStorage or MMKV)
|
|
54
|
+
*/
|
|
55
|
+
export interface StorageEvent {
|
|
56
|
+
storageType: "async" | "mmkv";
|
|
57
|
+
action: string;
|
|
58
|
+
timestamp: Date;
|
|
59
|
+
data?: {
|
|
60
|
+
key?: string;
|
|
61
|
+
value?: unknown;
|
|
62
|
+
instanceId?: string;
|
|
63
|
+
pairs?: Array<[string, unknown]>;
|
|
64
|
+
keys?: string[];
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get human-readable action name for AsyncStorage events
|
|
70
|
+
*/
|
|
71
|
+
function getAsyncStorageActionLabel(action: AsyncStorageAction): string {
|
|
72
|
+
switch (action) {
|
|
73
|
+
case "setItem":
|
|
74
|
+
return "Set Item";
|
|
75
|
+
case "removeItem":
|
|
76
|
+
return "Remove Item";
|
|
77
|
+
case "mergeItem":
|
|
78
|
+
return "Merge Item";
|
|
79
|
+
case "clear":
|
|
80
|
+
return "Clear All";
|
|
81
|
+
case "multiSet":
|
|
82
|
+
return "Multi Set";
|
|
83
|
+
case "multiRemove":
|
|
84
|
+
return "Multi Remove";
|
|
85
|
+
case "multiMerge":
|
|
86
|
+
return "Multi Merge";
|
|
87
|
+
default:
|
|
88
|
+
return action;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get subtitle for AsyncStorage event
|
|
94
|
+
*/
|
|
95
|
+
function getAsyncStorageSubtitle(event: AsyncStorageEvent): string {
|
|
96
|
+
const { action, data } = event;
|
|
97
|
+
|
|
98
|
+
switch (action) {
|
|
99
|
+
case "setItem":
|
|
100
|
+
case "removeItem":
|
|
101
|
+
case "mergeItem":
|
|
102
|
+
return data?.key || "unknown key";
|
|
103
|
+
|
|
104
|
+
case "multiSet":
|
|
105
|
+
case "multiMerge": {
|
|
106
|
+
const pairCount = data?.pairs?.length || 0;
|
|
107
|
+
return `${pairCount} key${pairCount !== 1 ? "s" : ""}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
case "multiRemove": {
|
|
111
|
+
const keyCount = data?.keys?.length || 0;
|
|
112
|
+
return `${keyCount} key${keyCount !== 1 ? "s" : ""}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
case "clear":
|
|
116
|
+
return "all keys";
|
|
117
|
+
|
|
118
|
+
default:
|
|
119
|
+
return "";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get status for storage event based on action type
|
|
125
|
+
*/
|
|
126
|
+
function getStorageStatus(action: string): EventStatus {
|
|
127
|
+
if (action.includes("set") || action.includes("Set") || action.includes("merge") || action.includes("Merge")) {
|
|
128
|
+
return "success";
|
|
129
|
+
}
|
|
130
|
+
if (action.includes("remove") || action.includes("Remove") || action === "clear" || action === "delete") {
|
|
131
|
+
return "neutral";
|
|
132
|
+
}
|
|
133
|
+
return "neutral";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Transform StorageEvent (AsyncStorage or MMKV) to UnifiedEvent
|
|
138
|
+
*/
|
|
139
|
+
export function transformStorageEvent(event: StorageEvent): UnifiedEvent {
|
|
140
|
+
if (event.storageType === "async") {
|
|
141
|
+
const asyncEvent = event as AsyncStorageEvent & { storageType: "async" };
|
|
142
|
+
return {
|
|
143
|
+
id: generateEventId("async"),
|
|
144
|
+
source: "storage-async",
|
|
145
|
+
timestamp: asyncEvent.timestamp.getTime(),
|
|
146
|
+
title: getAsyncStorageActionLabel(asyncEvent.action),
|
|
147
|
+
subtitle: getAsyncStorageSubtitle(asyncEvent),
|
|
148
|
+
status: getStorageStatus(asyncEvent.action),
|
|
149
|
+
originalEvent: asyncEvent,
|
|
150
|
+
};
|
|
151
|
+
} else {
|
|
152
|
+
const mmkvEvent = event as StorageEvent & { storageType: "mmkv" };
|
|
153
|
+
return {
|
|
154
|
+
id: generateEventId("mmkv"),
|
|
155
|
+
source: "storage-mmkv",
|
|
156
|
+
timestamp: mmkvEvent.timestamp.getTime(),
|
|
157
|
+
title: mmkvEvent.action,
|
|
158
|
+
subtitle: mmkvEvent.data?.key || mmkvEvent.data?.instanceId || "unknown",
|
|
159
|
+
status: getStorageStatus(mmkvEvent.action),
|
|
160
|
+
originalEvent: mmkvEvent,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Legacy export for backwards compatibility
|
|
167
|
+
*/
|
|
168
|
+
export function transformAsyncStorageEvent(event: AsyncStorageEvent): UnifiedEvent {
|
|
169
|
+
return transformStorageEvent({ ...event, storageType: "async" });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// Redux Event Types
|
|
174
|
+
// ============================================================================
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Generic Redux action structure
|
|
178
|
+
*/
|
|
179
|
+
export interface ReduxAction {
|
|
180
|
+
id: string;
|
|
181
|
+
type: string;
|
|
182
|
+
timestamp: number;
|
|
183
|
+
category?: "fulfilled" | "rejected" | "pending";
|
|
184
|
+
hasStateChange?: boolean;
|
|
185
|
+
payloadPreview?: string;
|
|
186
|
+
diffSummary?: string;
|
|
187
|
+
duration?: number;
|
|
188
|
+
sliceName?: string;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get status for Redux action based on category
|
|
193
|
+
*/
|
|
194
|
+
function getReduxStatus(action: ReduxAction): EventStatus {
|
|
195
|
+
switch (action.category) {
|
|
196
|
+
case "fulfilled":
|
|
197
|
+
return "success";
|
|
198
|
+
case "rejected":
|
|
199
|
+
return "error";
|
|
200
|
+
case "pending":
|
|
201
|
+
return "pending";
|
|
202
|
+
default:
|
|
203
|
+
return action.hasStateChange ? "success" : "neutral";
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get subtitle for Redux action
|
|
209
|
+
*/
|
|
210
|
+
function getReduxSubtitle(action: ReduxAction): string {
|
|
211
|
+
if (action.payloadPreview) {
|
|
212
|
+
return action.payloadPreview;
|
|
213
|
+
}
|
|
214
|
+
if (action.hasStateChange && action.diffSummary) {
|
|
215
|
+
return action.diffSummary;
|
|
216
|
+
}
|
|
217
|
+
if (action.duration !== undefined) {
|
|
218
|
+
return `${action.duration.toFixed(1)}ms`;
|
|
219
|
+
}
|
|
220
|
+
return action.sliceName || "";
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Transform ReduxAction to UnifiedEvent
|
|
225
|
+
*/
|
|
226
|
+
export function transformReduxAction(action: ReduxAction): UnifiedEvent {
|
|
227
|
+
return {
|
|
228
|
+
id: generateEventId("redux"),
|
|
229
|
+
source: "redux",
|
|
230
|
+
timestamp: action.timestamp,
|
|
231
|
+
title: action.type,
|
|
232
|
+
subtitle: getReduxSubtitle(action),
|
|
233
|
+
status: getReduxStatus(action),
|
|
234
|
+
originalEvent: action,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ============================================================================
|
|
239
|
+
// Network Event Types
|
|
240
|
+
// ============================================================================
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Generic Network event structure
|
|
244
|
+
*/
|
|
245
|
+
export interface NetworkEvent {
|
|
246
|
+
id: string;
|
|
247
|
+
method: string;
|
|
248
|
+
url: string;
|
|
249
|
+
path?: string;
|
|
250
|
+
host?: string;
|
|
251
|
+
status?: number;
|
|
252
|
+
error?: unknown;
|
|
253
|
+
duration?: number;
|
|
254
|
+
timestamp: number;
|
|
255
|
+
operationName?: string;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Get status for network event based on HTTP status code
|
|
260
|
+
*/
|
|
261
|
+
function getNetworkStatus(event: NetworkEvent): EventStatus {
|
|
262
|
+
if (event.status === undefined) {
|
|
263
|
+
return "pending";
|
|
264
|
+
}
|
|
265
|
+
if (event.error || event.status >= 400) {
|
|
266
|
+
return "error";
|
|
267
|
+
}
|
|
268
|
+
if (event.status >= 200 && event.status < 400) {
|
|
269
|
+
return "success";
|
|
270
|
+
}
|
|
271
|
+
return "neutral";
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Get title for network event
|
|
276
|
+
*/
|
|
277
|
+
function getNetworkTitle(event: NetworkEvent): string {
|
|
278
|
+
if (event.operationName) {
|
|
279
|
+
return event.operationName;
|
|
280
|
+
}
|
|
281
|
+
return `${event.method} ${event.path || event.url}`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get subtitle for network event
|
|
286
|
+
*/
|
|
287
|
+
function getNetworkSubtitle(event: NetworkEvent): string {
|
|
288
|
+
const parts: string[] = [];
|
|
289
|
+
|
|
290
|
+
if (event.status !== undefined) {
|
|
291
|
+
parts.push(`${event.status}`);
|
|
292
|
+
} else if (event.error) {
|
|
293
|
+
parts.push("Error");
|
|
294
|
+
} else {
|
|
295
|
+
parts.push("Pending");
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (event.duration !== undefined) {
|
|
299
|
+
parts.push(`${event.duration.toFixed(0)}ms`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (event.host) {
|
|
303
|
+
parts.push(event.host);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return parts.join(" · ");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Transform NetworkEvent to UnifiedEvent
|
|
311
|
+
*/
|
|
312
|
+
export function transformNetworkEvent(event: NetworkEvent): UnifiedEvent {
|
|
313
|
+
return {
|
|
314
|
+
id: generateEventId("network"),
|
|
315
|
+
source: "network",
|
|
316
|
+
timestamp: event.timestamp,
|
|
317
|
+
title: getNetworkTitle(event),
|
|
318
|
+
subtitle: getNetworkSubtitle(event),
|
|
319
|
+
status: getNetworkStatus(event),
|
|
320
|
+
originalEvent: event,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ============================================================================
|
|
325
|
+
// React Query Event Types
|
|
326
|
+
// ============================================================================
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Generic React Query event structure
|
|
330
|
+
*/
|
|
331
|
+
export interface ReactQueryEvent {
|
|
332
|
+
id: string;
|
|
333
|
+
type: string;
|
|
334
|
+
timestamp: number;
|
|
335
|
+
queryKey?: unknown[];
|
|
336
|
+
mutationKey?: unknown[];
|
|
337
|
+
mutationId?: number;
|
|
338
|
+
queryHash?: string;
|
|
339
|
+
duration?: number;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Format query key for display
|
|
344
|
+
*/
|
|
345
|
+
function formatQueryKey(queryKey: unknown[] | undefined): string {
|
|
346
|
+
if (!queryKey || queryKey.length === 0) return "unknown";
|
|
347
|
+
|
|
348
|
+
return queryKey
|
|
349
|
+
.map((part) => {
|
|
350
|
+
if (typeof part === "string") return part;
|
|
351
|
+
if (typeof part === "number") return String(part);
|
|
352
|
+
if (typeof part === "object" && part !== null) {
|
|
353
|
+
const obj = part as Record<string, unknown>;
|
|
354
|
+
const firstValue = Object.values(obj)[0];
|
|
355
|
+
if (typeof firstValue === "string" || typeof firstValue === "number") {
|
|
356
|
+
return String(firstValue);
|
|
357
|
+
}
|
|
358
|
+
return JSON.stringify(part).slice(0, 20);
|
|
359
|
+
}
|
|
360
|
+
return String(part);
|
|
361
|
+
})
|
|
362
|
+
.join(" › ");
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Get status for React Query event
|
|
367
|
+
*/
|
|
368
|
+
function getReactQueryStatus(event: ReactQueryEvent): EventStatus {
|
|
369
|
+
switch (event.type) {
|
|
370
|
+
case "query-fetch-start":
|
|
371
|
+
case "mutation-start":
|
|
372
|
+
return "pending";
|
|
373
|
+
case "query-fetch-success":
|
|
374
|
+
case "mutation-success":
|
|
375
|
+
return "success";
|
|
376
|
+
case "query-fetch-error":
|
|
377
|
+
case "mutation-error":
|
|
378
|
+
return "error";
|
|
379
|
+
case "query-invalidated":
|
|
380
|
+
return "neutral";
|
|
381
|
+
default:
|
|
382
|
+
return "neutral";
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Get title for React Query event
|
|
388
|
+
*/
|
|
389
|
+
function getReactQueryTitle(event: ReactQueryEvent): string {
|
|
390
|
+
if (event.queryKey) {
|
|
391
|
+
return formatQueryKey(event.queryKey);
|
|
392
|
+
}
|
|
393
|
+
if (event.mutationKey) {
|
|
394
|
+
return formatQueryKey(event.mutationKey);
|
|
395
|
+
}
|
|
396
|
+
if (event.mutationId !== undefined) {
|
|
397
|
+
return `Mutation #${event.mutationId}`;
|
|
398
|
+
}
|
|
399
|
+
return event.type.replace(/-/g, " ");
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Get subtitle for React Query event
|
|
404
|
+
*/
|
|
405
|
+
function getReactQuerySubtitle(event: ReactQueryEvent): string {
|
|
406
|
+
const parts: string[] = [];
|
|
407
|
+
|
|
408
|
+
switch (event.type) {
|
|
409
|
+
case "query-fetch-start":
|
|
410
|
+
parts.push("Fetching");
|
|
411
|
+
break;
|
|
412
|
+
case "query-fetch-success":
|
|
413
|
+
parts.push("Success");
|
|
414
|
+
break;
|
|
415
|
+
case "query-fetch-error":
|
|
416
|
+
parts.push("Error");
|
|
417
|
+
break;
|
|
418
|
+
case "query-invalidated":
|
|
419
|
+
parts.push("Invalidated");
|
|
420
|
+
break;
|
|
421
|
+
case "mutation-start":
|
|
422
|
+
parts.push("Mutating");
|
|
423
|
+
break;
|
|
424
|
+
case "mutation-success":
|
|
425
|
+
parts.push("Success");
|
|
426
|
+
break;
|
|
427
|
+
case "mutation-error":
|
|
428
|
+
parts.push("Error");
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (event.duration !== undefined) {
|
|
433
|
+
parts.push(`${event.duration.toFixed(0)}ms`);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return parts.join(" · ");
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Get correlation ID for React Query event
|
|
441
|
+
*/
|
|
442
|
+
function getReactQueryCorrelationId(event: ReactQueryEvent): string | undefined {
|
|
443
|
+
if (event.queryHash) {
|
|
444
|
+
return `rq-query-${event.queryHash}`;
|
|
445
|
+
}
|
|
446
|
+
if (event.mutationId !== undefined) {
|
|
447
|
+
return `rq-mutation-${event.mutationId}`;
|
|
448
|
+
}
|
|
449
|
+
return undefined;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Get sequence number within correlation group
|
|
454
|
+
*/
|
|
455
|
+
function getReactQuerySequence(event: ReactQueryEvent): number {
|
|
456
|
+
switch (event.type) {
|
|
457
|
+
case "query-fetch-start":
|
|
458
|
+
case "mutation-start":
|
|
459
|
+
return 1;
|
|
460
|
+
case "query-fetch-success":
|
|
461
|
+
case "query-fetch-error":
|
|
462
|
+
case "mutation-success":
|
|
463
|
+
case "mutation-error":
|
|
464
|
+
return 2;
|
|
465
|
+
case "query-invalidated":
|
|
466
|
+
return 3;
|
|
467
|
+
default:
|
|
468
|
+
return 1;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Determine if a React Query event is a mutation
|
|
474
|
+
*/
|
|
475
|
+
function isReactQueryMutation(event: ReactQueryEvent): boolean {
|
|
476
|
+
return event.type.startsWith("mutation-");
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Transform ReactQueryEvent to UnifiedEvent
|
|
481
|
+
*/
|
|
482
|
+
export function transformReactQueryEvent(event: ReactQueryEvent): UnifiedEvent {
|
|
483
|
+
const isMutation = isReactQueryMutation(event);
|
|
484
|
+
return {
|
|
485
|
+
id: generateEventId(isMutation ? "react-query-mutation" : "react-query-query"),
|
|
486
|
+
source: isMutation ? "react-query-mutation" : "react-query-query",
|
|
487
|
+
timestamp: event.timestamp,
|
|
488
|
+
title: getReactQueryTitle(event),
|
|
489
|
+
subtitle: getReactQuerySubtitle(event),
|
|
490
|
+
status: getReactQueryStatus(event),
|
|
491
|
+
originalEvent: event,
|
|
492
|
+
correlationId: getReactQueryCorrelationId(event),
|
|
493
|
+
sequenceInGroup: getReactQuerySequence(event),
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// ============================================================================
|
|
498
|
+
// Route Event Types
|
|
499
|
+
// ============================================================================
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Generic Route change event structure
|
|
503
|
+
*/
|
|
504
|
+
export interface RouteChangeEvent {
|
|
505
|
+
pathname: string;
|
|
506
|
+
params: Record<string, unknown>;
|
|
507
|
+
timestamp: number;
|
|
508
|
+
previousPathname?: string;
|
|
509
|
+
timeSincePrevious?: number;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Get status for route event
|
|
514
|
+
*/
|
|
515
|
+
function getRouteStatus(event: RouteChangeEvent): EventStatus {
|
|
516
|
+
if (event.pathname === "/") return "success";
|
|
517
|
+
if (Object.keys(event.params).length > 0) return "success";
|
|
518
|
+
return "neutral";
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Get title for route event
|
|
523
|
+
*/
|
|
524
|
+
function getRouteTitle(event: RouteChangeEvent): string {
|
|
525
|
+
return event.pathname || "/";
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Get subtitle for route event
|
|
530
|
+
*/
|
|
531
|
+
function getRouteSubtitle(event: RouteChangeEvent): string {
|
|
532
|
+
const parts: string[] = [];
|
|
533
|
+
|
|
534
|
+
const paramCount = Object.keys(event.params).length;
|
|
535
|
+
if (paramCount > 0) {
|
|
536
|
+
parts.push(`${paramCount} param${paramCount !== 1 ? "s" : ""}`);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (event.timeSincePrevious !== undefined && event.timeSincePrevious > 0) {
|
|
540
|
+
if (event.timeSincePrevious < 1000) {
|
|
541
|
+
parts.push(`${event.timeSincePrevious}ms`);
|
|
542
|
+
} else {
|
|
543
|
+
parts.push(`${(event.timeSincePrevious / 1000).toFixed(1)}s`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (event.previousPathname && event.previousPathname !== event.pathname) {
|
|
548
|
+
parts.push(`from ${event.previousPathname}`);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return parts.join(" · ") || "navigation";
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Transform RouteChangeEvent to UnifiedEvent
|
|
556
|
+
*/
|
|
557
|
+
export function transformRouteEvent(event: RouteChangeEvent): UnifiedEvent {
|
|
558
|
+
return {
|
|
559
|
+
id: generateEventId("route"),
|
|
560
|
+
source: "route",
|
|
561
|
+
timestamp: event.timestamp,
|
|
562
|
+
title: getRouteTitle(event),
|
|
563
|
+
subtitle: getRouteSubtitle(event),
|
|
564
|
+
status: getRouteStatus(event),
|
|
565
|
+
originalEvent: event,
|
|
566
|
+
};
|
|
567
|
+
}
|