@buoy-gg/network 1.7.2

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 (67) hide show
  1. package/README.md +381 -0
  2. package/lib/commonjs/index.js +34 -0
  3. package/lib/commonjs/network/components/NetworkCopySettingsView.js +867 -0
  4. package/lib/commonjs/network/components/NetworkEventDetailView.js +837 -0
  5. package/lib/commonjs/network/components/NetworkEventItemCompact.js +323 -0
  6. package/lib/commonjs/network/components/NetworkFilterViewV3.js +297 -0
  7. package/lib/commonjs/network/components/NetworkModal.js +937 -0
  8. package/lib/commonjs/network/hooks/useNetworkEvents.js +320 -0
  9. package/lib/commonjs/network/hooks/useTickEveryMinute.js +34 -0
  10. package/lib/commonjs/network/index.js +102 -0
  11. package/lib/commonjs/network/types/index.js +1 -0
  12. package/lib/commonjs/network/utils/extractOperationName.js +80 -0
  13. package/lib/commonjs/network/utils/formatGraphQLVariables.js +219 -0
  14. package/lib/commonjs/network/utils/formatting.js +30 -0
  15. package/lib/commonjs/network/utils/networkEventStore.js +269 -0
  16. package/lib/commonjs/network/utils/networkListener.js +801 -0
  17. package/lib/commonjs/package.json +1 -0
  18. package/lib/commonjs/preset.js +83 -0
  19. package/lib/module/index.js +7 -0
  20. package/lib/module/network/components/NetworkCopySettingsView.js +862 -0
  21. package/lib/module/network/components/NetworkEventDetailView.js +834 -0
  22. package/lib/module/network/components/NetworkEventItemCompact.js +320 -0
  23. package/lib/module/network/components/NetworkFilterViewV3.js +293 -0
  24. package/lib/module/network/components/NetworkModal.js +933 -0
  25. package/lib/module/network/hooks/useNetworkEvents.js +316 -0
  26. package/lib/module/network/hooks/useTickEveryMinute.js +29 -0
  27. package/lib/module/network/index.js +20 -0
  28. package/lib/module/network/types/index.js +1 -0
  29. package/lib/module/network/utils/extractOperationName.js +76 -0
  30. package/lib/module/network/utils/formatGraphQLVariables.js +213 -0
  31. package/lib/module/network/utils/formatting.js +9 -0
  32. package/lib/module/network/utils/networkEventStore.js +265 -0
  33. package/lib/module/network/utils/networkListener.js +791 -0
  34. package/lib/module/preset.js +79 -0
  35. package/lib/typescript/index.d.ts +3 -0
  36. package/lib/typescript/index.d.ts.map +1 -0
  37. package/lib/typescript/network/components/NetworkCopySettingsView.d.ts +26 -0
  38. package/lib/typescript/network/components/NetworkCopySettingsView.d.ts.map +1 -0
  39. package/lib/typescript/network/components/NetworkEventDetailView.d.ts +13 -0
  40. package/lib/typescript/network/components/NetworkEventDetailView.d.ts.map +1 -0
  41. package/lib/typescript/network/components/NetworkEventItemCompact.d.ts +12 -0
  42. package/lib/typescript/network/components/NetworkEventItemCompact.d.ts.map +1 -0
  43. package/lib/typescript/network/components/NetworkFilterViewV3.d.ts +22 -0
  44. package/lib/typescript/network/components/NetworkFilterViewV3.d.ts.map +1 -0
  45. package/lib/typescript/network/components/NetworkModal.d.ts +14 -0
  46. package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -0
  47. package/lib/typescript/network/hooks/useNetworkEvents.d.ts +72 -0
  48. package/lib/typescript/network/hooks/useNetworkEvents.d.ts.map +1 -0
  49. package/lib/typescript/network/hooks/useTickEveryMinute.d.ts +9 -0
  50. package/lib/typescript/network/hooks/useTickEveryMinute.d.ts.map +1 -0
  51. package/lib/typescript/network/index.d.ts +12 -0
  52. package/lib/typescript/network/index.d.ts.map +1 -0
  53. package/lib/typescript/network/types/index.d.ts +88 -0
  54. package/lib/typescript/network/types/index.d.ts.map +1 -0
  55. package/lib/typescript/network/utils/extractOperationName.d.ts +41 -0
  56. package/lib/typescript/network/utils/extractOperationName.d.ts.map +1 -0
  57. package/lib/typescript/network/utils/formatGraphQLVariables.d.ts +79 -0
  58. package/lib/typescript/network/utils/formatGraphQLVariables.d.ts.map +1 -0
  59. package/lib/typescript/network/utils/formatting.d.ts +6 -0
  60. package/lib/typescript/network/utils/formatting.d.ts.map +1 -0
  61. package/lib/typescript/network/utils/networkEventStore.d.ts +81 -0
  62. package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -0
  63. package/lib/typescript/network/utils/networkListener.d.ts +191 -0
  64. package/lib/typescript/network/utils/networkListener.d.ts.map +1 -0
  65. package/lib/typescript/preset.d.ts +76 -0
  66. package/lib/typescript/preset.d.ts.map +1 -0
  67. package/package.json +69 -0
@@ -0,0 +1,320 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.FREE_TIER_REQUEST_LIMIT = void 0;
7
+ exports.useNetworkEvents = useNetworkEvents;
8
+ var _react = require("react");
9
+ var _networkEventStore = require("../utils/networkEventStore");
10
+ var _networkListener = require("../utils/networkListener");
11
+ var _formatGraphQLVariables = require("../utils/formatGraphQLVariables");
12
+ /**
13
+ * Hook for accessing network events and controls
14
+ * Uses Reactotron-style listener pattern
15
+ */
16
+
17
+ /** Free tier limit for network requests */
18
+ const FREE_TIER_REQUEST_LIMIT = exports.FREE_TIER_REQUEST_LIMIT = 25;
19
+
20
+ /**
21
+ * Custom hook for accessing network events and controls
22
+ *
23
+ * This hook provides a complete interface for network monitoring, including
24
+ * event filtering, statistics calculation, and interception control. It uses
25
+ * the Reactotron-style listener pattern for network event handling.
26
+ *
27
+ * @returns Object containing filtered events, statistics, controls, and utilities
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * function NetworkMonitor() {
32
+ * const {
33
+ * events,
34
+ * stats,
35
+ * filter,
36
+ * setFilter,
37
+ * clearEvents,
38
+ * toggleInterception,
39
+ * isEnabled
40
+ * } = useNetworkEvents();
41
+ *
42
+ * return (
43
+ * <div>
44
+ * <p>Total requests: {stats.totalRequests}</p>
45
+ * <p>Success rate: {stats.successfulRequests}/{stats.totalRequests}</p>
46
+ * <button onClick={toggleInterception}>
47
+ * {isEnabled ? 'Stop' : 'Start'} Monitoring
48
+ * </button>
49
+ * </div>
50
+ * );
51
+ * }
52
+ * ```
53
+ *
54
+ * @performance Uses memoization for expensive filtering and statistics calculations
55
+ * @performance Optimizes string operations and array processing for large datasets
56
+ * @performance Includes Set-based lookups for O(1) filter matching
57
+ *
58
+ * @param options.isPro - Whether user has Pro access (unlimited events). Defaults to false (limited to FREE_TIER_REQUEST_LIMIT)
59
+ */
60
+ function useNetworkEvents(options = {}) {
61
+ const {
62
+ isPro = false
63
+ } = options;
64
+ const [events, setEvents] = (0, _react.useState)([]);
65
+ const [filter, setFilter] = (0, _react.useState)({});
66
+ const [isEnabled, setIsEnabled] = (0, _react.useState)(false);
67
+
68
+ // Subscribe to event store changes
69
+ (0, _react.useEffect)(() => {
70
+ // Subscribe to store changes
71
+ const unsubscribeStore = _networkEventStore.networkEventStore.subscribe(setEvents);
72
+
73
+ // Add listener to network events
74
+ const unsubscribeListener = (0, _networkListener.addNetworkListener)(event => {
75
+ // Only log in development and for non-ignored URLs
76
+ if (__DEV__ && !event.request.url.includes("symbolicate") && !event.request.url.includes(":8081")) {
77
+ // Network event processed: [event.type] [method] [url] - available for debugging if needed
78
+ }
79
+ _networkEventStore.networkEventStore.processNetworkEvent(event);
80
+ });
81
+
82
+ // Check if already listening
83
+ setIsEnabled((0, _networkListener.networkListener)().isActive);
84
+
85
+ // Start listening if not already
86
+ if (!(0, _networkListener.networkListener)().isActive) {
87
+ (0, _networkListener.startNetworkListener)();
88
+ setIsEnabled(true);
89
+ }
90
+
91
+ // Load initial events
92
+ setEvents(_networkEventStore.networkEventStore.getEvents());
93
+ return () => {
94
+ unsubscribeStore();
95
+ unsubscribeListener();
96
+ };
97
+ }, []);
98
+
99
+ // Clear all events
100
+ const clearEvents = (0, _react.useCallback)(() => {
101
+ _networkEventStore.networkEventStore.clearEvents();
102
+ }, []);
103
+
104
+ // Toggle interception
105
+ const toggleInterception = (0, _react.useCallback)(() => {
106
+ if (isEnabled) {
107
+ (0, _networkListener.stopNetworkListener)();
108
+ setIsEnabled(false);
109
+ } else {
110
+ (0, _networkListener.startNetworkListener)();
111
+ setIsEnabled(true);
112
+ }
113
+ }, [isEnabled]);
114
+
115
+ // Memoize search text processing to avoid repeated toLowerCase calls
116
+ // Performance: Expensive string operations repeated for every event on every filter
117
+ const searchLower = (0, _react.useMemo)(() => {
118
+ return filter.searchText ? filter.searchText.toLowerCase() : null;
119
+ }, [filter.searchText]);
120
+
121
+ // Memoize method filter Set for O(1) lookup instead of Array.includes
122
+ // Performance: Converting array.includes to Set.has for faster lookups with large method lists
123
+ const methodSet = (0, _react.useMemo)(() => {
124
+ return filter.method && filter.method.length > 0 ? new Set(filter.method) : null;
125
+ }, [filter.method]);
126
+
127
+ // Memoize content type Set for O(1) lookup
128
+ // Performance: Converting array.some to Set.has for faster content type matching
129
+ const contentTypeSet = (0, _react.useMemo)(() => {
130
+ return filter.contentType && filter.contentType.length > 0 ? new Set(filter.contentType) : null;
131
+ }, [filter.contentType]);
132
+
133
+ // Filter events with optimized string operations and Set lookups
134
+ // Performance: Complex multi-stage filtering with string operations and content type matching
135
+ const filteredEvents = (0, _react.useMemo)(() => {
136
+ let filtered = [...events];
137
+ if (methodSet) {
138
+ filtered = filtered.filter(e => methodSet.has(e.method));
139
+ }
140
+ if (filter.status && filter.status !== "all") {
141
+ switch (filter.status) {
142
+ case "success":
143
+ filtered = filtered.filter(e => e.status && e.status >= 200 && e.status < 300);
144
+ break;
145
+ case "error":
146
+ filtered = filtered.filter(e => e.error || e.status && e.status >= 400);
147
+ break;
148
+ case "pending":
149
+ filtered = filtered.filter(e => !e.status && !e.error);
150
+ break;
151
+ }
152
+ }
153
+ if (searchLower) {
154
+ filtered = filtered.filter(e => e.url.toLowerCase().includes(searchLower) || e.method.toLowerCase().includes(searchLower) || e.path?.toLowerCase().includes(searchLower) || e.host?.toLowerCase().includes(searchLower) || e.error && e.error.toLowerCase().includes(searchLower) ||
155
+ // Search by GraphQL operation name (e.g., "GetUser", "CreatePost")
156
+ e.operationName && e.operationName.toLowerCase().includes(searchLower) ||
157
+ // Search by GraphQL variable values (e.g., "Sandshrew", "123", "true")
158
+ // This enables finding specific requests like "GetPokemon › Sandshrew" by typing "Sandshrew"
159
+ (0, _formatGraphQLVariables.searchGraphQLVariables)(e.graphqlVariables, searchLower));
160
+ }
161
+ if (filter.host) {
162
+ filtered = filtered.filter(e => e.host === filter.host);
163
+ }
164
+ if (contentTypeSet) {
165
+ filtered = filtered.filter(e => {
166
+ const headers = e.responseHeaders || e.requestHeaders;
167
+ const contentType = headers?.["content-type"] || headers?.["Content-Type"] || "";
168
+ for (const type of contentTypeSet) {
169
+ switch (type) {
170
+ case "JSON":
171
+ if (contentType.includes("json")) return true;
172
+ break;
173
+ case "XML":
174
+ if (contentType.includes("xml")) return true;
175
+ break;
176
+ case "HTML":
177
+ if (contentType.includes("html")) return true;
178
+ break;
179
+ case "TEXT":
180
+ if (contentType.includes("text")) return true;
181
+ break;
182
+ case "IMAGE":
183
+ if (contentType.includes("image")) return true;
184
+ break;
185
+ case "VIDEO":
186
+ if (contentType.includes("video")) return true;
187
+ break;
188
+ case "AUDIO":
189
+ if (contentType.includes("audio")) return true;
190
+ break;
191
+ case "FORM":
192
+ if (contentType.includes("form")) return true;
193
+ break;
194
+ case "OTHER":
195
+ if (!contentType || !contentType.includes("json") && !contentType.includes("xml") && !contentType.includes("html") && !contentType.includes("text") && !contentType.includes("image") && !contentType.includes("video") && !contentType.includes("audio") && !contentType.includes("form")) {
196
+ return true;
197
+ }
198
+ break;
199
+ }
200
+ }
201
+ return false;
202
+ });
203
+ }
204
+ return filtered;
205
+ }, [events, filter, searchLower, methodSet, contentTypeSet]);
206
+
207
+ // For free users, determine which events are "locked" (beyond the limit)
208
+ // Based on original event order, not filtered results - so locked events stay locked even when searching
209
+ const lockedEventIds = (0, _react.useMemo)(() => {
210
+ if (isPro) {
211
+ return new Set();
212
+ }
213
+ // Events beyond the limit in the ORIGINAL events array are locked
214
+ const lockedIds = new Set();
215
+ events.slice(FREE_TIER_REQUEST_LIMIT).forEach(event => {
216
+ lockedIds.add(event.id);
217
+ });
218
+ return lockedIds;
219
+ }, [events, isPro]);
220
+
221
+ // Check if there are locked events in the current filtered results
222
+ const lockedEventsInFilter = (0, _react.useMemo)(() => {
223
+ return filteredEvents.filter(event => lockedEventIds.has(event.id));
224
+ }, [filteredEvents, lockedEventIds]);
225
+ const hasLockedEvents = lockedEventsInFilter.length > 0;
226
+ const lockedEventCount = lockedEventsInFilter.length;
227
+
228
+ // Helper to check if a specific event is locked
229
+ const isEventLocked = (0, _react.useCallback)(eventId => {
230
+ return lockedEventIds.has(eventId);
231
+ }, [lockedEventIds]);
232
+
233
+ // Memoize expensive statistics calculation by categorizing events in single pass
234
+ // Performance: Multiple array.filter operations replaced with single loop for better performance
235
+ const stats = (0, _react.useMemo)(() => {
236
+ let successful = 0;
237
+ let failed = 0;
238
+ let pending = 0;
239
+ let totalSent = 0;
240
+ let totalReceived = 0;
241
+ let durationSum = 0;
242
+ let durationCount = 0;
243
+
244
+ // Single pass through events for all statistics
245
+ for (const event of events) {
246
+ // Categorize status
247
+ if (event.status && event.status >= 200 && event.status < 300) {
248
+ successful++;
249
+ } else if (event.error || event.status && event.status >= 400) {
250
+ failed++;
251
+ } else if (!event.status && !event.error) {
252
+ pending++;
253
+ }
254
+
255
+ // Accumulate data sizes
256
+ totalSent += event.requestSize || 0;
257
+ totalReceived += event.responseSize || 0;
258
+
259
+ // Accumulate durations
260
+ if (event.duration) {
261
+ durationSum += event.duration;
262
+ durationCount++;
263
+ }
264
+ }
265
+ const avgDuration = durationCount > 0 ? durationSum / durationCount : 0;
266
+ return {
267
+ totalRequests: events.length,
268
+ successfulRequests: successful,
269
+ failedRequests: failed,
270
+ pendingRequests: pending,
271
+ totalDataSent: totalSent,
272
+ totalDataReceived: totalReceived,
273
+ averageDuration: Math.round(avgDuration)
274
+ };
275
+ }, [events]);
276
+
277
+ // Memoize unique hosts extraction with single pass instead of map + filter + Set
278
+ // Performance: Avoiding array.map().filter() chain, using single loop with Set for deduplication
279
+ const hosts = (0, _react.useMemo)(() => {
280
+ const hostSet = new Set();
281
+ for (const event of events) {
282
+ if (event.host) {
283
+ hostSet.add(event.host);
284
+ }
285
+ }
286
+ return Array.from(hostSet);
287
+ }, [events]);
288
+
289
+ // Memoize unique methods extraction with single pass
290
+ // Performance: Avoiding array.map() + Set constructor, using single loop for better performance
291
+ const methods = (0, _react.useMemo)(() => {
292
+ const methodSet = new Set();
293
+ for (const event of events) {
294
+ methodSet.add(event.method);
295
+ }
296
+ return Array.from(methodSet);
297
+ }, [events]);
298
+ return {
299
+ /** Filtered events (all shown, but some may be locked for free users) */
300
+ events: filteredEvents,
301
+ /** All events before filtering (for stats) */
302
+ allEvents: events,
303
+ stats,
304
+ filter,
305
+ setFilter,
306
+ clearEvents,
307
+ isEnabled,
308
+ toggleInterception,
309
+ hosts,
310
+ methods,
311
+ /** Whether there are locked events due to free tier limit */
312
+ hasLockedEvents,
313
+ /** Number of events locked due to free tier limit */
314
+ lockedEventCount,
315
+ /** Check if a specific event is locked (by ID) */
316
+ isEventLocked,
317
+ /** Free tier request limit */
318
+ requestLimit: FREE_TIER_REQUEST_LIMIT
319
+ };
320
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.TickProvider = TickProvider;
7
+ exports.useTickEveryMinute = useTickEveryMinute;
8
+ var _react = require("react");
9
+ var _jsxRuntime = require("react/jsx-runtime");
10
+ const TickContext = /*#__PURE__*/(0, _react.createContext)(Date.now());
11
+ function TickProvider({
12
+ children,
13
+ intervalMs = 60_000
14
+ }) {
15
+ const [tick, setTick] = (0, _react.useState)(() => Date.now());
16
+ (0, _react.useEffect)(() => {
17
+ const id = setInterval(() => {
18
+ setTick(Date.now());
19
+ }, intervalMs);
20
+ return () => {
21
+ clearInterval(id);
22
+ };
23
+ }, [intervalMs]);
24
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(TickContext.Provider, {
25
+ value: tick,
26
+ children: children
27
+ });
28
+ }
29
+ function useTickEveryMinute() {
30
+ const tick = (0, _react.useContext)(TickContext);
31
+
32
+ // Expose stable object so consumers can memoize on value changes
33
+ return (0, _react.useMemo)(() => tick, [tick]);
34
+ }
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "NetworkEventDetailView", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _NetworkEventDetailView.NetworkEventDetailView;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "NetworkEventItemCompact", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _NetworkEventItemCompact.NetworkEventItemCompact;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "NetworkModal", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _NetworkModal.NetworkModal;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "addNetworkListener", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _networkListener.addNetworkListener;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "formatBytes", {
31
+ enumerable: true,
32
+ get: function () {
33
+ return _formatting.formatBytes;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "formatDuration", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _formatting.formatDuration;
40
+ }
41
+ });
42
+ Object.defineProperty(exports, "formatHttpStatus", {
43
+ enumerable: true,
44
+ get: function () {
45
+ return _formatting.formatHttpStatus;
46
+ }
47
+ });
48
+ Object.defineProperty(exports, "getNetworkListenerCount", {
49
+ enumerable: true,
50
+ get: function () {
51
+ return _networkListener.getNetworkListenerCount;
52
+ }
53
+ });
54
+ Object.defineProperty(exports, "isNetworkListening", {
55
+ enumerable: true,
56
+ get: function () {
57
+ return _networkListener.isNetworkListening;
58
+ }
59
+ });
60
+ Object.defineProperty(exports, "networkEventStore", {
61
+ enumerable: true,
62
+ get: function () {
63
+ return _networkEventStore.networkEventStore;
64
+ }
65
+ });
66
+ Object.defineProperty(exports, "networkListener", {
67
+ enumerable: true,
68
+ get: function () {
69
+ return _networkListener.networkListener;
70
+ }
71
+ });
72
+ Object.defineProperty(exports, "removeAllNetworkListeners", {
73
+ enumerable: true,
74
+ get: function () {
75
+ return _networkListener.removeAllNetworkListeners;
76
+ }
77
+ });
78
+ Object.defineProperty(exports, "startNetworkListener", {
79
+ enumerable: true,
80
+ get: function () {
81
+ return _networkListener.startNetworkListener;
82
+ }
83
+ });
84
+ Object.defineProperty(exports, "stopNetworkListener", {
85
+ enumerable: true,
86
+ get: function () {
87
+ return _networkListener.stopNetworkListener;
88
+ }
89
+ });
90
+ Object.defineProperty(exports, "useNetworkEvents", {
91
+ enumerable: true,
92
+ get: function () {
93
+ return _useNetworkEvents.useNetworkEvents;
94
+ }
95
+ });
96
+ var _NetworkModal = require("./components/NetworkModal");
97
+ var _NetworkEventDetailView = require("./components/NetworkEventDetailView");
98
+ var _NetworkEventItemCompact = require("./components/NetworkEventItemCompact");
99
+ var _useNetworkEvents = require("./hooks/useNetworkEvents");
100
+ var _networkListener = require("./utils/networkListener");
101
+ var _networkEventStore = require("./utils/networkEventStore");
102
+ var _formatting = require("./utils/formatting");
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.extractOperationName = extractOperationName;
7
+ /**
8
+ * Extract GraphQL operation name from request data
9
+ *
10
+ * This utility extracts the operation name from GraphQL requests to enable
11
+ * searching and filtering by operation name rather than just URL paths.
12
+ *
13
+ * For GraphQL, all requests typically go to the same endpoint (e.g., /graphql),
14
+ * making URL-based search ineffective. This function extracts the actual
15
+ * operation name (e.g., "GetUser", "CreatePost") from the request payload.
16
+ *
17
+ * Extraction Methods (tried in order):
18
+ * 1. Direct operationName field in request data
19
+ * 2. Parse from query string using regex pattern
20
+ *
21
+ * @param requestData - The request data object (typically GraphQL query payload)
22
+ * @returns Operation name string or null if not found
23
+ *
24
+ * @example
25
+ * // Method 1: Explicit operationName field
26
+ * const data1 = {
27
+ * operationName: "GetUser",
28
+ * query: "query GetUser { user { id name } }"
29
+ * };
30
+ * extractOperationName(data1); // Returns "GetUser"
31
+ *
32
+ * @example
33
+ * // Method 2: Parse from query string
34
+ * const data2 = {
35
+ * query: "mutation CreatePost { createPost(title: \"Hello\") { id } }"
36
+ * };
37
+ * extractOperationName(data2); // Returns "CreatePost"
38
+ *
39
+ * @example
40
+ * // No operation name (anonymous query)
41
+ * const data3 = {
42
+ * query: "{ user { id name } }"
43
+ * };
44
+ * extractOperationName(data3); // Returns null
45
+ */
46
+ function extractOperationName(requestData) {
47
+ // Validate input is an object
48
+ if (!requestData || typeof requestData !== 'object') {
49
+ return null;
50
+ }
51
+
52
+ // Method 1: Check for explicit operationName field
53
+ // This is the standard GraphQL request format
54
+ if ('operationName' in requestData && requestData.operationName) {
55
+ const opName = requestData.operationName;
56
+ // Ensure it's a string and not empty
57
+ if (typeof opName === 'string' && opName.trim().length > 0) {
58
+ return opName.trim();
59
+ }
60
+ }
61
+
62
+ // Method 2: Parse from query string
63
+ // Handles cases where operationName field is missing or null
64
+ if ('query' in requestData && typeof requestData.query === 'string') {
65
+ const query = requestData.query;
66
+
67
+ // Match: query OperationName or mutation OperationName or subscription OperationName
68
+ // Pattern explanation:
69
+ // - (?:query|mutation|subscription) - Match operation type (non-capturing group)
70
+ // - \s+ - One or more whitespace characters
71
+ // - (\w+) - Capture operation name (letters, numbers, underscore)
72
+ const match = query.match(/(?:query|mutation|subscription)\s+(\w+)/);
73
+ if (match && match[1]) {
74
+ return match[1];
75
+ }
76
+ }
77
+
78
+ // No operation name found
79
+ return null;
80
+ }