@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.
Files changed (84) hide show
  1. package/LICENSE +58 -0
  2. package/README.md +55 -0
  3. package/lib/commonjs/components/EventsCopySettingsView.js +645 -0
  4. package/lib/commonjs/components/EventsModal.js +263 -0
  5. package/lib/commonjs/components/ReactQueryEventDetail.js +428 -0
  6. package/lib/commonjs/components/UnifiedEventDetail.js +370 -0
  7. package/lib/commonjs/components/UnifiedEventFilters.js +113 -0
  8. package/lib/commonjs/components/UnifiedEventItem.js +349 -0
  9. package/lib/commonjs/components/UnifiedEventList.js +154 -0
  10. package/lib/commonjs/components/UnifiedEventViewer.js +126 -0
  11. package/lib/commonjs/hooks/useUnifiedEvents.js +237 -0
  12. package/lib/commonjs/index.js +205 -0
  13. package/lib/commonjs/package.json +1 -0
  14. package/lib/commonjs/preset.js +66 -0
  15. package/lib/commonjs/stores/unifiedEventStore.js +413 -0
  16. package/lib/commonjs/types/copySettings.js +220 -0
  17. package/lib/commonjs/types/index.js +17 -0
  18. package/lib/commonjs/utils/autoDiscoverEventSources.js +640 -0
  19. package/lib/commonjs/utils/badgeSelectionStorage.js +58 -0
  20. package/lib/commonjs/utils/copySettingsStorage.js +66 -0
  21. package/lib/commonjs/utils/correlationUtils.js +130 -0
  22. package/lib/commonjs/utils/eventExportFormatter.js +1095 -0
  23. package/lib/commonjs/utils/eventTransformers.js +496 -0
  24. package/lib/module/components/EventsCopySettingsView.js +641 -0
  25. package/lib/module/components/EventsModal.js +259 -0
  26. package/lib/module/components/ReactQueryEventDetail.js +424 -0
  27. package/lib/module/components/UnifiedEventDetail.js +366 -0
  28. package/lib/module/components/UnifiedEventFilters.js +109 -0
  29. package/lib/module/components/UnifiedEventItem.js +345 -0
  30. package/lib/module/components/UnifiedEventList.js +150 -0
  31. package/lib/module/components/UnifiedEventViewer.js +122 -0
  32. package/lib/module/hooks/useUnifiedEvents.js +234 -0
  33. package/lib/module/index.js +77 -0
  34. package/lib/module/preset.js +62 -0
  35. package/lib/module/stores/unifiedEventStore.js +387 -0
  36. package/lib/module/types/copySettings.js +215 -0
  37. package/lib/module/types/index.js +37 -0
  38. package/lib/module/utils/autoDiscoverEventSources.js +633 -0
  39. package/lib/module/utils/badgeSelectionStorage.js +52 -0
  40. package/lib/module/utils/copySettingsStorage.js +61 -0
  41. package/lib/module/utils/correlationUtils.js +120 -0
  42. package/lib/module/utils/eventExportFormatter.js +1085 -0
  43. package/lib/module/utils/eventTransformers.js +487 -0
  44. package/lib/typescript/components/EventsCopySettingsView.d.ts +16 -0
  45. package/lib/typescript/components/EventsModal.d.ts +16 -0
  46. package/lib/typescript/components/ReactQueryEventDetail.d.ts +15 -0
  47. package/lib/typescript/components/UnifiedEventDetail.d.ts +15 -0
  48. package/lib/typescript/components/UnifiedEventFilters.d.ts +21 -0
  49. package/lib/typescript/components/UnifiedEventItem.d.ts +26 -0
  50. package/lib/typescript/components/UnifiedEventList.d.ts +27 -0
  51. package/lib/typescript/components/UnifiedEventViewer.d.ts +8 -0
  52. package/lib/typescript/hooks/useUnifiedEvents.d.ts +30 -0
  53. package/lib/typescript/index.d.ts +28 -0
  54. package/lib/typescript/preset.d.ts +62 -0
  55. package/lib/typescript/stores/unifiedEventStore.d.ts +146 -0
  56. package/lib/typescript/types/copySettings.d.ts +179 -0
  57. package/lib/typescript/types/index.d.ts +73 -0
  58. package/lib/typescript/utils/autoDiscoverEventSources.d.ts +74 -0
  59. package/lib/typescript/utils/badgeSelectionStorage.d.ts +21 -0
  60. package/lib/typescript/utils/copySettingsStorage.d.ts +21 -0
  61. package/lib/typescript/utils/correlationUtils.d.ts +36 -0
  62. package/lib/typescript/utils/eventExportFormatter.d.ts +49 -0
  63. package/lib/typescript/utils/eventTransformers.d.ts +119 -0
  64. package/package.json +91 -0
  65. package/src/components/EventsCopySettingsView.tsx +742 -0
  66. package/src/components/EventsModal.tsx +328 -0
  67. package/src/components/ReactQueryEventDetail.tsx +413 -0
  68. package/src/components/UnifiedEventDetail.tsx +371 -0
  69. package/src/components/UnifiedEventFilters.tsx +156 -0
  70. package/src/components/UnifiedEventItem.tsx +396 -0
  71. package/src/components/UnifiedEventList.tsx +197 -0
  72. package/src/components/UnifiedEventViewer.tsx +132 -0
  73. package/src/hooks/useUnifiedEvents.ts +288 -0
  74. package/src/index.tsx +112 -0
  75. package/src/preset.tsx +57 -0
  76. package/src/stores/unifiedEventStore.ts +405 -0
  77. package/src/types/copySettings.ts +269 -0
  78. package/src/types/index.ts +96 -0
  79. package/src/utils/autoDiscoverEventSources.ts +690 -0
  80. package/src/utils/badgeSelectionStorage.ts +51 -0
  81. package/src/utils/copySettingsStorage.ts +61 -0
  82. package/src/utils/correlationUtils.ts +146 -0
  83. package/src/utils/eventExportFormatter.ts +1233 -0
  84. 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
+ }