brakit 0.8.7 → 9.0.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/README.md +15 -3
- package/dist/api.d.ts +66 -46
- package/dist/api.js +727 -730
- package/dist/bin/brakit.js +294 -432
- package/dist/dashboard-client.global.js +345 -222
- package/dist/dashboard.html +390 -231
- package/dist/mcp/server.js +7 -15
- package/dist/runtime/index.js +1367 -1607
- package/package.json +1 -1
package/dist/runtime/index.js
CHANGED
|
@@ -14,48 +14,10 @@ var __export = (target, all) => {
|
|
|
14
14
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
// src/constants/
|
|
18
|
-
var
|
|
19
|
-
var
|
|
20
|
-
"src/constants/
|
|
21
|
-
"use strict";
|
|
22
|
-
DASHBOARD_PREFIX = "/__brakit";
|
|
23
|
-
DASHBOARD_API_REQUESTS = `${DASHBOARD_PREFIX}/api/requests`;
|
|
24
|
-
DASHBOARD_API_EVENTS = `${DASHBOARD_PREFIX}/api/events`;
|
|
25
|
-
DASHBOARD_API_FLOWS = `${DASHBOARD_PREFIX}/api/flows`;
|
|
26
|
-
DASHBOARD_API_CLEAR = `${DASHBOARD_PREFIX}/api/clear`;
|
|
27
|
-
DASHBOARD_API_LOGS = `${DASHBOARD_PREFIX}/api/logs`;
|
|
28
|
-
DASHBOARD_API_FETCHES = `${DASHBOARD_PREFIX}/api/fetches`;
|
|
29
|
-
DASHBOARD_API_ERRORS = `${DASHBOARD_PREFIX}/api/errors`;
|
|
30
|
-
DASHBOARD_API_QUERIES = `${DASHBOARD_PREFIX}/api/queries`;
|
|
31
|
-
DASHBOARD_API_INGEST = `${DASHBOARD_PREFIX}/api/ingest`;
|
|
32
|
-
DASHBOARD_API_METRICS = `${DASHBOARD_PREFIX}/api/metrics`;
|
|
33
|
-
DASHBOARD_API_ACTIVITY = `${DASHBOARD_PREFIX}/api/activity`;
|
|
34
|
-
DASHBOARD_API_METRICS_LIVE = `${DASHBOARD_PREFIX}/api/metrics/live`;
|
|
35
|
-
DASHBOARD_API_INSIGHTS = `${DASHBOARD_PREFIX}/api/insights`;
|
|
36
|
-
DASHBOARD_API_SECURITY = `${DASHBOARD_PREFIX}/api/security`;
|
|
37
|
-
DASHBOARD_API_TAB = `${DASHBOARD_PREFIX}/api/tab`;
|
|
38
|
-
DASHBOARD_API_FINDINGS = `${DASHBOARD_PREFIX}/api/findings`;
|
|
39
|
-
DASHBOARD_API_FINDINGS_REPORT = `${DASHBOARD_PREFIX}/api/findings/report`;
|
|
40
|
-
VALID_TABS_TUPLE = [
|
|
41
|
-
"overview",
|
|
42
|
-
"actions",
|
|
43
|
-
"requests",
|
|
44
|
-
"fetches",
|
|
45
|
-
"queries",
|
|
46
|
-
"errors",
|
|
47
|
-
"logs",
|
|
48
|
-
"performance",
|
|
49
|
-
"security"
|
|
50
|
-
];
|
|
51
|
-
VALID_TABS = new Set(VALID_TABS_TUPLE);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// src/constants/limits.ts
|
|
56
|
-
var MAX_REQUEST_ENTRIES, DEFAULT_MAX_BODY_CAPTURE, DEFAULT_API_LIMIT, MAX_TELEMETRY_ENTRIES, MAX_TAB_NAME_LENGTH, MAX_INGEST_BYTES, TERMINAL_TRUNCATE_LENGTH, SENSITIVE_MASK_MIN_LENGTH, SENSITIVE_MASK_VISIBLE_CHARS, MAX_JSON_BODY_BYTES, ANALYSIS_DEBOUNCE_MS, ISSUE_ID_HASH_LENGTH, ISSUES_DATA_VERSION, SENSITIVE_MASK_PLACEHOLDER, PROJECT_HASH_LENGTH, SECRET_SCAN_ARRAY_LIMIT, PII_SCAN_ARRAY_LIMIT, MIN_SECRET_VALUE_LENGTH, FULL_RECORD_MIN_FIELDS, LIST_PII_MIN_ITEMS, MAX_API_LIMIT, MAX_OBJECT_SCAN_DEPTH, MAX_UNIQUE_ENDPOINTS, MAX_ACCUMULATOR_ENTRIES, ISSUE_PRUNE_TTL_MS;
|
|
57
|
-
var init_limits = __esm({
|
|
58
|
-
"src/constants/limits.ts"() {
|
|
17
|
+
// src/constants/config.ts
|
|
18
|
+
var MAX_REQUEST_ENTRIES, DEFAULT_MAX_BODY_CAPTURE, DEFAULT_API_LIMIT, MAX_TELEMETRY_ENTRIES, MAX_TAB_NAME_LENGTH, MAX_INGEST_BYTES, TERMINAL_TRUNCATE_LENGTH, SENSITIVE_MASK_MIN_LENGTH, SENSITIVE_MASK_VISIBLE_CHARS, MAX_JSON_BODY_BYTES, ANALYSIS_DEBOUNCE_MS, ISSUE_ID_HASH_LENGTH, ISSUES_DATA_VERSION, SENSITIVE_MASK_PLACEHOLDER, PROJECT_HASH_LENGTH, SECRET_SCAN_ARRAY_LIMIT, PII_SCAN_ARRAY_LIMIT, MIN_SECRET_VALUE_LENGTH, FULL_RECORD_MIN_FIELDS, LIST_PII_MIN_ITEMS, MAX_API_LIMIT, MAX_OBJECT_SCAN_DEPTH, MAX_UNIQUE_ENDPOINTS, MAX_ACCUMULATOR_ENTRIES, ISSUE_PRUNE_TTL_MS, FLOW_GAP_MS, SLOW_REQUEST_THRESHOLD_MS, MIN_POLLING_SEQUENCE, ENDPOINT_TRUNCATE_LENGTH, N1_QUERY_THRESHOLD, ERROR_RATE_THRESHOLD_PCT, SLOW_ENDPOINT_THRESHOLD_MS, MIN_REQUESTS_FOR_INSIGHT, HIGH_QUERY_COUNT_PER_REQ, CROSS_ENDPOINT_MIN_ENDPOINTS, CROSS_ENDPOINT_PCT, CROSS_ENDPOINT_MIN_OCCURRENCES, REDUNDANT_QUERY_MIN_COUNT, LARGE_RESPONSE_BYTES, HIGH_ROW_COUNT, OVERFETCH_MIN_REQUESTS, OVERFETCH_MIN_FIELDS, OVERFETCH_MIN_INTERNAL_IDS, OVERFETCH_NULL_RATIO, REGRESSION_PCT_THRESHOLD, REGRESSION_MIN_INCREASE_MS, REGRESSION_MIN_REQUESTS, QUERY_COUNT_REGRESSION_RATIO, OVERFETCH_MANY_FIELDS, OVERFETCH_UNWRAP_MIN_SIZE, MAX_DUPLICATE_INSIGHTS, INSIGHT_WINDOW_PER_ENDPOINT, CLEAN_HITS_FOR_RESOLUTION, STALE_ISSUE_TTL_MS, STRICT_MODE_MAX_GAP_MS, METRICS_DIR, METRICS_FILE, PORT_FILE, ISSUES_FILE, METRICS_FLUSH_INTERVAL_MS, METRICS_MAX_SESSIONS, METRICS_MAX_DATA_POINTS, ISSUES_FLUSH_INTERVAL_MS, SSE_HEARTBEAT_INTERVAL_MS, NOISE_HOSTS, NOISE_PATH_PATTERNS, VALID_ISSUE_STATES, VALID_ISSUE_CATEGORIES, VALID_AI_FIX_STATUSES;
|
|
19
|
+
var init_config = __esm({
|
|
20
|
+
"src/constants/config.ts"() {
|
|
59
21
|
"use strict";
|
|
60
22
|
MAX_REQUEST_ENTRIES = 1e3;
|
|
61
23
|
DEFAULT_MAX_BODY_CAPTURE = 10240;
|
|
@@ -82,14 +44,6 @@ var init_limits = __esm({
|
|
|
82
44
|
MAX_UNIQUE_ENDPOINTS = 500;
|
|
83
45
|
MAX_ACCUMULATOR_ENTRIES = 1e3;
|
|
84
46
|
ISSUE_PRUNE_TTL_MS = 10 * 60 * 1e3;
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// src/constants/thresholds.ts
|
|
89
|
-
var FLOW_GAP_MS, SLOW_REQUEST_THRESHOLD_MS, MIN_POLLING_SEQUENCE, ENDPOINT_TRUNCATE_LENGTH, N1_QUERY_THRESHOLD, ERROR_RATE_THRESHOLD_PCT, SLOW_ENDPOINT_THRESHOLD_MS, MIN_REQUESTS_FOR_INSIGHT, HIGH_QUERY_COUNT_PER_REQ, CROSS_ENDPOINT_MIN_ENDPOINTS, CROSS_ENDPOINT_PCT, CROSS_ENDPOINT_MIN_OCCURRENCES, REDUNDANT_QUERY_MIN_COUNT, LARGE_RESPONSE_BYTES, HIGH_ROW_COUNT, OVERFETCH_MIN_REQUESTS, OVERFETCH_MIN_FIELDS, OVERFETCH_MIN_INTERNAL_IDS, OVERFETCH_NULL_RATIO, REGRESSION_PCT_THRESHOLD, REGRESSION_MIN_INCREASE_MS, REGRESSION_MIN_REQUESTS, QUERY_COUNT_REGRESSION_RATIO, OVERFETCH_MANY_FIELDS, OVERFETCH_UNWRAP_MIN_SIZE, MAX_DUPLICATE_INSIGHTS, INSIGHT_WINDOW_PER_ENDPOINT, CLEAN_HITS_FOR_RESOLUTION, STALE_ISSUE_TTL_MS;
|
|
90
|
-
var init_thresholds = __esm({
|
|
91
|
-
"src/constants/thresholds.ts"() {
|
|
92
|
-
"use strict";
|
|
93
47
|
FLOW_GAP_MS = 5e3;
|
|
94
48
|
SLOW_REQUEST_THRESHOLD_MS = 2e3;
|
|
95
49
|
MIN_POLLING_SEQUENCE = 3;
|
|
@@ -119,25 +73,7 @@ var init_thresholds = __esm({
|
|
|
119
73
|
INSIGHT_WINDOW_PER_ENDPOINT = 20;
|
|
120
74
|
CLEAN_HITS_FOR_RESOLUTION = 5;
|
|
121
75
|
STALE_ISSUE_TTL_MS = 30 * 60 * 1e3;
|
|
122
|
-
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// src/constants/transport.ts
|
|
126
|
-
var SSE_HEARTBEAT_INTERVAL_MS, NOISE_HOSTS, NOISE_PATH_PATTERNS;
|
|
127
|
-
var init_transport = __esm({
|
|
128
|
-
"src/constants/transport.ts"() {
|
|
129
|
-
"use strict";
|
|
130
|
-
SSE_HEARTBEAT_INTERVAL_MS = 3e4;
|
|
131
|
-
NOISE_HOSTS = ["registry.npmjs.org", "telemetry.nextjs.org", "vitejs.dev"];
|
|
132
|
-
NOISE_PATH_PATTERNS = [".hot-update.", "__webpack", "__vite"];
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
// src/constants/metrics.ts
|
|
137
|
-
var METRICS_DIR, METRICS_FILE, PORT_FILE, ISSUES_FILE, METRICS_FLUSH_INTERVAL_MS, METRICS_MAX_SESSIONS, METRICS_MAX_DATA_POINTS, ISSUES_FLUSH_INTERVAL_MS;
|
|
138
|
-
var init_metrics = __esm({
|
|
139
|
-
"src/constants/metrics.ts"() {
|
|
140
|
-
"use strict";
|
|
76
|
+
STRICT_MODE_MAX_GAP_MS = 2e3;
|
|
141
77
|
METRICS_DIR = ".brakit";
|
|
142
78
|
METRICS_FILE = "metrics.json";
|
|
143
79
|
PORT_FILE = ".brakit/port";
|
|
@@ -146,14 +82,50 @@ var init_metrics = __esm({
|
|
|
146
82
|
METRICS_MAX_SESSIONS = 50;
|
|
147
83
|
METRICS_MAX_DATA_POINTS = 200;
|
|
148
84
|
ISSUES_FLUSH_INTERVAL_MS = 1e4;
|
|
85
|
+
SSE_HEARTBEAT_INTERVAL_MS = 3e4;
|
|
86
|
+
NOISE_HOSTS = ["registry.npmjs.org", "telemetry.nextjs.org", "vitejs.dev"];
|
|
87
|
+
NOISE_PATH_PATTERNS = [".hot-update.", "__webpack", "__vite"];
|
|
88
|
+
VALID_ISSUE_STATES = /* @__PURE__ */ new Set(["open", "fixing", "resolved", "stale", "regressed"]);
|
|
89
|
+
VALID_ISSUE_CATEGORIES = /* @__PURE__ */ new Set(["security", "performance", "reliability"]);
|
|
90
|
+
VALID_AI_FIX_STATUSES = /* @__PURE__ */ new Set(["fixed", "wont_fix"]);
|
|
149
91
|
}
|
|
150
92
|
});
|
|
151
93
|
|
|
152
|
-
// src/constants/
|
|
153
|
-
var BRAKIT_REQUEST_ID_HEADER, BRAKIT_FETCH_ID_HEADER, SENSITIVE_HEADER_NAMES;
|
|
154
|
-
var
|
|
155
|
-
"src/constants/
|
|
94
|
+
// src/constants/labels.ts
|
|
95
|
+
var DASHBOARD_PREFIX, DASHBOARD_API_REQUESTS, DASHBOARD_API_EVENTS, DASHBOARD_API_FLOWS, DASHBOARD_API_CLEAR, DASHBOARD_API_LOGS, DASHBOARD_API_FETCHES, DASHBOARD_API_ERRORS, DASHBOARD_API_QUERIES, DASHBOARD_API_INGEST, DASHBOARD_API_METRICS, DASHBOARD_API_ACTIVITY, DASHBOARD_API_METRICS_LIVE, DASHBOARD_API_INSIGHTS, DASHBOARD_API_SECURITY, DASHBOARD_API_TAB, DASHBOARD_API_FINDINGS, DASHBOARD_API_FINDINGS_REPORT, VALID_TABS_TUPLE, VALID_TABS, BRAKIT_REQUEST_ID_HEADER, BRAKIT_FETCH_ID_HEADER, SENSITIVE_HEADER_NAMES, HTTP_OK, HTTP_NO_CONTENT, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_METHOD_NOT_ALLOWED, HTTP_PAYLOAD_TOO_LARGE, HTTP_INTERNAL_ERROR, SECURITY_HEADERS, CONTENT_ENCODING_GZIP, CONTENT_ENCODING_BR, CONTENT_ENCODING_DEFLATE, SEVERITY_ICON, SSE_EVENT_FETCH, SSE_EVENT_LOG, SSE_EVENT_ERROR, SSE_EVENT_QUERY, SSE_EVENT_ISSUES, SDK_EVENT_REQUEST, SDK_EVENT_DB_QUERY, SDK_EVENT_FETCH, SDK_EVENT_LOG, SDK_EVENT_ERROR, SDK_EVENT_AUTH_CHECK, POSTHOG_HOST, POSTHOG_CAPTURE_PATH, POSTHOG_REQUEST_TIMEOUT_MS, SPEED_BUCKET_THRESHOLDS, TIMELINE_FETCH, TIMELINE_LOG, TIMELINE_ERROR, TIMELINE_QUERY;
|
|
96
|
+
var init_labels = __esm({
|
|
97
|
+
"src/constants/labels.ts"() {
|
|
156
98
|
"use strict";
|
|
99
|
+
DASHBOARD_PREFIX = "/__brakit";
|
|
100
|
+
DASHBOARD_API_REQUESTS = `${DASHBOARD_PREFIX}/api/requests`;
|
|
101
|
+
DASHBOARD_API_EVENTS = `${DASHBOARD_PREFIX}/api/events`;
|
|
102
|
+
DASHBOARD_API_FLOWS = `${DASHBOARD_PREFIX}/api/flows`;
|
|
103
|
+
DASHBOARD_API_CLEAR = `${DASHBOARD_PREFIX}/api/clear`;
|
|
104
|
+
DASHBOARD_API_LOGS = `${DASHBOARD_PREFIX}/api/logs`;
|
|
105
|
+
DASHBOARD_API_FETCHES = `${DASHBOARD_PREFIX}/api/fetches`;
|
|
106
|
+
DASHBOARD_API_ERRORS = `${DASHBOARD_PREFIX}/api/errors`;
|
|
107
|
+
DASHBOARD_API_QUERIES = `${DASHBOARD_PREFIX}/api/queries`;
|
|
108
|
+
DASHBOARD_API_INGEST = `${DASHBOARD_PREFIX}/api/ingest`;
|
|
109
|
+
DASHBOARD_API_METRICS = `${DASHBOARD_PREFIX}/api/metrics`;
|
|
110
|
+
DASHBOARD_API_ACTIVITY = `${DASHBOARD_PREFIX}/api/activity`;
|
|
111
|
+
DASHBOARD_API_METRICS_LIVE = `${DASHBOARD_PREFIX}/api/metrics/live`;
|
|
112
|
+
DASHBOARD_API_INSIGHTS = `${DASHBOARD_PREFIX}/api/insights`;
|
|
113
|
+
DASHBOARD_API_SECURITY = `${DASHBOARD_PREFIX}/api/security`;
|
|
114
|
+
DASHBOARD_API_TAB = `${DASHBOARD_PREFIX}/api/tab`;
|
|
115
|
+
DASHBOARD_API_FINDINGS = `${DASHBOARD_PREFIX}/api/findings`;
|
|
116
|
+
DASHBOARD_API_FINDINGS_REPORT = `${DASHBOARD_PREFIX}/api/findings/report`;
|
|
117
|
+
VALID_TABS_TUPLE = [
|
|
118
|
+
"overview",
|
|
119
|
+
"actions",
|
|
120
|
+
"requests",
|
|
121
|
+
"fetches",
|
|
122
|
+
"queries",
|
|
123
|
+
"errors",
|
|
124
|
+
"logs",
|
|
125
|
+
"performance",
|
|
126
|
+
"security"
|
|
127
|
+
];
|
|
128
|
+
VALID_TABS = new Set(VALID_TABS_TUPLE);
|
|
157
129
|
BRAKIT_REQUEST_ID_HEADER = "x-brakit-request-id";
|
|
158
130
|
BRAKIT_FETCH_ID_HEADER = "x-brakit-fetch-id";
|
|
159
131
|
SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -164,13 +136,53 @@ var init_headers = __esm({
|
|
|
164
136
|
"x-api-key",
|
|
165
137
|
"x-auth-token"
|
|
166
138
|
]);
|
|
139
|
+
HTTP_OK = 200;
|
|
140
|
+
HTTP_NO_CONTENT = 204;
|
|
141
|
+
HTTP_BAD_REQUEST = 400;
|
|
142
|
+
HTTP_NOT_FOUND = 404;
|
|
143
|
+
HTTP_METHOD_NOT_ALLOWED = 405;
|
|
144
|
+
HTTP_PAYLOAD_TOO_LARGE = 413;
|
|
145
|
+
HTTP_INTERNAL_ERROR = 500;
|
|
146
|
+
SECURITY_HEADERS = {
|
|
147
|
+
"x-content-type-options": "nosniff",
|
|
148
|
+
"x-frame-options": "DENY",
|
|
149
|
+
"referrer-policy": "no-referrer",
|
|
150
|
+
"content-security-policy": "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'; img-src data:"
|
|
151
|
+
};
|
|
152
|
+
CONTENT_ENCODING_GZIP = "gzip";
|
|
153
|
+
CONTENT_ENCODING_BR = "br";
|
|
154
|
+
CONTENT_ENCODING_DEFLATE = "deflate";
|
|
155
|
+
SEVERITY_ICON = {
|
|
156
|
+
critical: "\u2717",
|
|
157
|
+
warning: "\u26A0",
|
|
158
|
+
info: "\u2139"
|
|
159
|
+
};
|
|
160
|
+
SSE_EVENT_FETCH = "fetch";
|
|
161
|
+
SSE_EVENT_LOG = "log";
|
|
162
|
+
SSE_EVENT_ERROR = "error_event";
|
|
163
|
+
SSE_EVENT_QUERY = "query";
|
|
164
|
+
SSE_EVENT_ISSUES = "issues";
|
|
165
|
+
SDK_EVENT_REQUEST = "request";
|
|
166
|
+
SDK_EVENT_DB_QUERY = "db.query";
|
|
167
|
+
SDK_EVENT_FETCH = "fetch";
|
|
168
|
+
SDK_EVENT_LOG = "log";
|
|
169
|
+
SDK_EVENT_ERROR = "error";
|
|
170
|
+
SDK_EVENT_AUTH_CHECK = "auth.check";
|
|
171
|
+
POSTHOG_HOST = "https://us.i.posthog.com";
|
|
172
|
+
POSTHOG_CAPTURE_PATH = "/i/v0/e/";
|
|
173
|
+
POSTHOG_REQUEST_TIMEOUT_MS = 3e3;
|
|
174
|
+
SPEED_BUCKET_THRESHOLDS = [200, 500, 1e3, 2e3, 5e3];
|
|
175
|
+
TIMELINE_FETCH = "fetch";
|
|
176
|
+
TIMELINE_LOG = "log";
|
|
177
|
+
TIMELINE_ERROR = "error";
|
|
178
|
+
TIMELINE_QUERY = "query";
|
|
167
179
|
}
|
|
168
180
|
});
|
|
169
181
|
|
|
170
|
-
// src/constants/
|
|
182
|
+
// src/constants/features.ts
|
|
171
183
|
var CLOUD_SIGNALS, MAX_HEALTH_ERRORS, RECOVERY_WINDOW_MS, LOCALHOST_IPS, LOCALHOST_HOSTNAMES, URL_PARSE_BASE, DIR_MODE_OWNER_ONLY, FILE_MODE_OWNER_ONLY;
|
|
172
|
-
var
|
|
173
|
-
"src/constants/
|
|
184
|
+
var init_features = __esm({
|
|
185
|
+
"src/constants/features.ts"() {
|
|
174
186
|
"use strict";
|
|
175
187
|
CLOUD_SIGNALS = [
|
|
176
188
|
"VERCEL",
|
|
@@ -203,112 +215,13 @@ var init_network = __esm({
|
|
|
203
215
|
}
|
|
204
216
|
});
|
|
205
217
|
|
|
206
|
-
// src/constants/mcp.ts
|
|
207
|
-
var init_mcp = __esm({
|
|
208
|
-
"src/constants/mcp.ts"() {
|
|
209
|
-
"use strict";
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// src/constants/encoding.ts
|
|
214
|
-
var CONTENT_ENCODING_GZIP, CONTENT_ENCODING_BR, CONTENT_ENCODING_DEFLATE;
|
|
215
|
-
var init_encoding = __esm({
|
|
216
|
-
"src/constants/encoding.ts"() {
|
|
217
|
-
"use strict";
|
|
218
|
-
CONTENT_ENCODING_GZIP = "gzip";
|
|
219
|
-
CONTENT_ENCODING_BR = "br";
|
|
220
|
-
CONTENT_ENCODING_DEFLATE = "deflate";
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
// src/constants/severity.ts
|
|
225
|
-
var SEVERITY_ICON;
|
|
226
|
-
var init_severity = __esm({
|
|
227
|
-
"src/constants/severity.ts"() {
|
|
228
|
-
"use strict";
|
|
229
|
-
SEVERITY_ICON = {
|
|
230
|
-
critical: "\u2717",
|
|
231
|
-
warning: "\u26A0",
|
|
232
|
-
info: "\u2139"
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
// src/constants/telemetry.ts
|
|
238
|
-
var POSTHOG_HOST, POSTHOG_CAPTURE_PATH, POSTHOG_REQUEST_TIMEOUT_MS, SPEED_BUCKET_THRESHOLDS;
|
|
239
|
-
var init_telemetry = __esm({
|
|
240
|
-
"src/constants/telemetry.ts"() {
|
|
241
|
-
"use strict";
|
|
242
|
-
POSTHOG_HOST = "https://us.i.posthog.com";
|
|
243
|
-
POSTHOG_CAPTURE_PATH = "/i/v0/e/";
|
|
244
|
-
POSTHOG_REQUEST_TIMEOUT_MS = 3e3;
|
|
245
|
-
SPEED_BUCKET_THRESHOLDS = [200, 500, 1e3, 2e3, 5e3];
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
// src/constants/lifecycle.ts
|
|
250
|
-
var VALID_ISSUE_STATES, VALID_ISSUE_CATEGORIES, VALID_AI_FIX_STATUSES;
|
|
251
|
-
var init_lifecycle = __esm({
|
|
252
|
-
"src/constants/lifecycle.ts"() {
|
|
253
|
-
"use strict";
|
|
254
|
-
VALID_ISSUE_STATES = /* @__PURE__ */ new Set(["open", "fixing", "resolved", "stale", "regressed"]);
|
|
255
|
-
VALID_ISSUE_CATEGORIES = /* @__PURE__ */ new Set(["security", "performance", "reliability"]);
|
|
256
|
-
VALID_AI_FIX_STATUSES = /* @__PURE__ */ new Set(["fixed", "wont_fix"]);
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
// src/constants/cli.ts
|
|
261
|
-
var init_cli = __esm({
|
|
262
|
-
"src/constants/cli.ts"() {
|
|
263
|
-
"use strict";
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
// src/constants/timeline.ts
|
|
268
|
-
var TIMELINE_FETCH, TIMELINE_LOG, TIMELINE_ERROR, TIMELINE_QUERY;
|
|
269
|
-
var init_timeline = __esm({
|
|
270
|
-
"src/constants/timeline.ts"() {
|
|
271
|
-
"use strict";
|
|
272
|
-
TIMELINE_FETCH = "fetch";
|
|
273
|
-
TIMELINE_LOG = "log";
|
|
274
|
-
TIMELINE_ERROR = "error";
|
|
275
|
-
TIMELINE_QUERY = "query";
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
// src/constants/sdk-events.ts
|
|
280
|
-
var SDK_EVENT_REQUEST, SDK_EVENT_DB_QUERY, SDK_EVENT_FETCH, SDK_EVENT_LOG, SDK_EVENT_ERROR, SDK_EVENT_AUTH_CHECK;
|
|
281
|
-
var init_sdk_events = __esm({
|
|
282
|
-
"src/constants/sdk-events.ts"() {
|
|
283
|
-
"use strict";
|
|
284
|
-
SDK_EVENT_REQUEST = "request";
|
|
285
|
-
SDK_EVENT_DB_QUERY = "db.query";
|
|
286
|
-
SDK_EVENT_FETCH = "fetch";
|
|
287
|
-
SDK_EVENT_LOG = "log";
|
|
288
|
-
SDK_EVENT_ERROR = "error";
|
|
289
|
-
SDK_EVENT_AUTH_CHECK = "auth.check";
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
|
|
293
218
|
// src/constants/index.ts
|
|
294
219
|
var init_constants = __esm({
|
|
295
220
|
"src/constants/index.ts"() {
|
|
296
221
|
"use strict";
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
init_transport();
|
|
301
|
-
init_metrics();
|
|
302
|
-
init_headers();
|
|
303
|
-
init_network();
|
|
304
|
-
init_mcp();
|
|
305
|
-
init_encoding();
|
|
306
|
-
init_severity();
|
|
307
|
-
init_telemetry();
|
|
308
|
-
init_lifecycle();
|
|
309
|
-
init_cli();
|
|
310
|
-
init_timeline();
|
|
311
|
-
init_sdk_events();
|
|
222
|
+
init_config();
|
|
223
|
+
init_labels();
|
|
224
|
+
init_features();
|
|
312
225
|
}
|
|
313
226
|
});
|
|
314
227
|
|
|
@@ -377,7 +290,7 @@ function setupFetchHook(emit) {
|
|
|
377
290
|
statusCode: msg.response.statusCode ?? 0,
|
|
378
291
|
durationMs: Math.round(performance.now() - info.startTime),
|
|
379
292
|
parentRequestId: info.parentRequestId,
|
|
380
|
-
timestamp:
|
|
293
|
+
timestamp: performance.now()
|
|
381
294
|
}
|
|
382
295
|
});
|
|
383
296
|
});
|
|
@@ -407,7 +320,7 @@ function setupConsoleHook(emit) {
|
|
|
407
320
|
const ctx = getRequestContext();
|
|
408
321
|
if (!ctx) return;
|
|
409
322
|
const message = format(...args);
|
|
410
|
-
const timestamp =
|
|
323
|
+
const timestamp = performance.now();
|
|
411
324
|
const parentRequestId = ctx.requestId;
|
|
412
325
|
if (level === "error") {
|
|
413
326
|
const errorArg = args.find((a) => a instanceof Error);
|
|
@@ -474,7 +387,7 @@ function createCaptureError(emit) {
|
|
|
474
387
|
message: error.message,
|
|
475
388
|
stack: error.stack ?? "",
|
|
476
389
|
parentRequestId: ctx?.requestId ?? null,
|
|
477
|
-
timestamp:
|
|
390
|
+
timestamp: performance.now()
|
|
478
391
|
}
|
|
479
392
|
});
|
|
480
393
|
};
|
|
@@ -538,65 +451,28 @@ var init_adapter_registry = __esm({
|
|
|
538
451
|
}
|
|
539
452
|
});
|
|
540
453
|
|
|
541
|
-
// src/instrument/adapters/shared.ts
|
|
542
|
-
import { createRequire } from "module";
|
|
543
|
-
function tryRequire(id) {
|
|
544
|
-
try {
|
|
545
|
-
return appRequire(id);
|
|
546
|
-
} catch {
|
|
547
|
-
return null;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
function captureRequestId() {
|
|
551
|
-
return getRequestContext()?.requestId ?? null;
|
|
552
|
-
}
|
|
553
|
-
var appRequire;
|
|
554
|
-
var init_shared = __esm({
|
|
555
|
-
"src/instrument/adapters/shared.ts"() {
|
|
556
|
-
"use strict";
|
|
557
|
-
init_context();
|
|
558
|
-
appRequire = createRequire(process.cwd() + "/index.js");
|
|
559
|
-
}
|
|
560
|
-
});
|
|
561
|
-
|
|
562
454
|
// src/instrument/adapters/normalize.ts
|
|
563
455
|
function normalizeSQL(sql) {
|
|
564
456
|
if (!sql) return { op: "OTHER", table: "" };
|
|
565
457
|
const trimmed = sql.trim();
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
}
|
|
571
|
-
const tableMatch = trimmed.match(/(?:FROM|INTO|UPDATE)\s+"?\w+"?\."?(\w+)"?/i);
|
|
572
|
-
const table = tableMatch?.[1] ?? "";
|
|
573
|
-
switch (op) {
|
|
574
|
-
case "SELECT":
|
|
575
|
-
return { op: "SELECT", table };
|
|
576
|
-
case "INSERT":
|
|
577
|
-
return { op: "INSERT", table };
|
|
578
|
-
case "UPDATE":
|
|
579
|
-
return { op: "UPDATE", table };
|
|
580
|
-
case "DELETE":
|
|
581
|
-
return { op: "DELETE", table };
|
|
582
|
-
default:
|
|
583
|
-
return { op: "OTHER", table };
|
|
584
|
-
}
|
|
458
|
+
const keyword = trimmed.split(/\s+/, 1)[0].toUpperCase();
|
|
459
|
+
const op = VALID_OPS.has(keyword) ? keyword : "OTHER";
|
|
460
|
+
const table = trimmed.match(TABLE_RE)?.[1] ?? "";
|
|
461
|
+
return { op, table };
|
|
585
462
|
}
|
|
586
463
|
function normalizePrismaOp(operation) {
|
|
587
464
|
return PRISMA_OP_MAP[operation] ?? "OTHER";
|
|
588
465
|
}
|
|
589
466
|
function normalizeQueryParams(sql) {
|
|
590
467
|
if (!sql) return null;
|
|
591
|
-
|
|
592
|
-
n = n.replace(/\b\d+(\.\d+)?\b/g, "?");
|
|
593
|
-
n = n.replace(/\$\d+/g, "?");
|
|
594
|
-
return n;
|
|
468
|
+
return sql.replace(SQL_PARAM_MARKER, "?").replace(SQL_STRING_LITERAL, "?").replace(SQL_NUMBER_LITERAL, "?");
|
|
595
469
|
}
|
|
596
|
-
var PRISMA_OP_MAP;
|
|
470
|
+
var VALID_OPS, TABLE_RE, PRISMA_OP_MAP, SQL_PARAM_MARKER, SQL_STRING_LITERAL, SQL_NUMBER_LITERAL;
|
|
597
471
|
var init_normalize = __esm({
|
|
598
472
|
"src/instrument/adapters/normalize.ts"() {
|
|
599
473
|
"use strict";
|
|
474
|
+
VALID_OPS = /* @__PURE__ */ new Set(["SELECT", "INSERT", "UPDATE", "DELETE"]);
|
|
475
|
+
TABLE_RE = /(?:FROM|INTO|UPDATE)\s+(?:"?\w+"?\.)?"?(\w+)"?/i;
|
|
600
476
|
PRISMA_OP_MAP = {
|
|
601
477
|
findUnique: "SELECT",
|
|
602
478
|
findUniqueOrThrow: "SELECT",
|
|
@@ -615,79 +491,199 @@ var init_normalize = __esm({
|
|
|
615
491
|
delete: "DELETE",
|
|
616
492
|
deleteMany: "DELETE"
|
|
617
493
|
};
|
|
494
|
+
SQL_PARAM_MARKER = /\$\d+/g;
|
|
495
|
+
SQL_STRING_LITERAL = /'[^']*'/g;
|
|
496
|
+
SQL_NUMBER_LITERAL = /\b\d+(\.\d+)?\b/g;
|
|
618
497
|
}
|
|
619
498
|
});
|
|
620
499
|
|
|
621
|
-
// src/
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
500
|
+
// src/utils/type-guards.ts
|
|
501
|
+
function isString(val) {
|
|
502
|
+
return typeof val === "string";
|
|
503
|
+
}
|
|
504
|
+
function isNumber(val) {
|
|
505
|
+
return typeof val === "number" && !isNaN(val);
|
|
506
|
+
}
|
|
507
|
+
function isBoolean(val) {
|
|
508
|
+
return typeof val === "boolean";
|
|
509
|
+
}
|
|
510
|
+
function isThenable(value) {
|
|
511
|
+
return value != null && typeof value.then === "function";
|
|
512
|
+
}
|
|
513
|
+
function getErrorMessage(err) {
|
|
514
|
+
if (err instanceof Error) return err.message;
|
|
515
|
+
if (typeof err === "string") return err;
|
|
516
|
+
return String(err);
|
|
517
|
+
}
|
|
518
|
+
function isValidIssueState(val) {
|
|
519
|
+
return typeof val === "string" && VALID_ISSUE_STATES.has(val);
|
|
520
|
+
}
|
|
521
|
+
function isValidIssueCategory(val) {
|
|
522
|
+
return typeof val === "string" && VALID_ISSUE_CATEGORIES.has(val);
|
|
523
|
+
}
|
|
524
|
+
function isValidAiFixStatus(val) {
|
|
525
|
+
return typeof val === "string" && VALID_AI_FIX_STATUSES.has(val);
|
|
526
|
+
}
|
|
527
|
+
function validateIssuesData(parsed) {
|
|
528
|
+
if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
529
|
+
const obj = parsed;
|
|
530
|
+
if (obj.version === ISSUES_DATA_VERSION && Array.isArray(obj.issues)) {
|
|
531
|
+
return parsed;
|
|
532
|
+
}
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
function validateMetricsData(parsed) {
|
|
536
|
+
if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
537
|
+
const obj = parsed;
|
|
538
|
+
if (obj.version === 1 && Array.isArray(obj.endpoints)) {
|
|
539
|
+
return parsed;
|
|
540
|
+
}
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
var init_type_guards = __esm({
|
|
544
|
+
"src/utils/type-guards.ts"() {
|
|
625
545
|
"use strict";
|
|
626
|
-
|
|
546
|
+
init_config();
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// src/utils/log.ts
|
|
551
|
+
function brakitWarn(message) {
|
|
552
|
+
process.stderr.write(`${PREFIX} ${message}
|
|
553
|
+
`);
|
|
554
|
+
}
|
|
555
|
+
function brakitDebug(message) {
|
|
556
|
+
if (process.env.DEBUG_BRAKIT) {
|
|
557
|
+
process.stderr.write(`${PREFIX}:debug ${message}
|
|
558
|
+
`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
var PREFIX;
|
|
562
|
+
var init_log = __esm({
|
|
563
|
+
"src/utils/log.ts"() {
|
|
564
|
+
"use strict";
|
|
565
|
+
PREFIX = "[brakit]";
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// src/instrument/adapters/shared.ts
|
|
570
|
+
import { createRequire } from "module";
|
|
571
|
+
function tryRequire(id) {
|
|
572
|
+
try {
|
|
573
|
+
return appRequire(id);
|
|
574
|
+
} catch {
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function getActiveRequestId() {
|
|
579
|
+
return getRequestContext()?.requestId ?? null;
|
|
580
|
+
}
|
|
581
|
+
function getPrototype(lib, className) {
|
|
582
|
+
const defaultExport = lib.default;
|
|
583
|
+
const cls = defaultExport?.[className] ?? lib[className];
|
|
584
|
+
if (!cls || typeof cls !== "function") return null;
|
|
585
|
+
return cls.prototype ?? null;
|
|
586
|
+
}
|
|
587
|
+
function buildQueryEvent(config, sql, op, table, start, requestId, rowCount) {
|
|
588
|
+
return {
|
|
589
|
+
type: "query",
|
|
590
|
+
data: {
|
|
591
|
+
driver: config.driver,
|
|
592
|
+
source: config.driver,
|
|
593
|
+
sql,
|
|
594
|
+
normalizedOp: op,
|
|
595
|
+
table,
|
|
596
|
+
durationMs: Math.round(performance.now() - start),
|
|
597
|
+
rowCount: rowCount ?? void 0,
|
|
598
|
+
parentRequestId: requestId,
|
|
599
|
+
timestamp: performance.now()
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
function wrapQueryMethod(original, emit, config) {
|
|
604
|
+
return function(...args) {
|
|
605
|
+
const sql = config.extractSql(args);
|
|
606
|
+
const start = performance.now();
|
|
607
|
+
const requestId = getActiveRequestId();
|
|
608
|
+
const { op, table } = normalizeSQL(sql ?? "");
|
|
609
|
+
const emitQuery = (result2) => {
|
|
610
|
+
const rowCount = config.extractRowCount?.(result2);
|
|
611
|
+
emit(buildQueryEvent(config, sql, op, table, start, requestId, rowCount));
|
|
612
|
+
};
|
|
613
|
+
const lastIdx = args.length - 1;
|
|
614
|
+
if (lastIdx >= 0 && typeof args[lastIdx] === "function") {
|
|
615
|
+
const originalCallback = args[lastIdx];
|
|
616
|
+
args[lastIdx] = function(...callbackArgs) {
|
|
617
|
+
emitQuery(callbackArgs[1]);
|
|
618
|
+
return originalCallback.apply(this, callbackArgs);
|
|
619
|
+
};
|
|
620
|
+
return original.apply(this, args);
|
|
621
|
+
}
|
|
622
|
+
const result = original.apply(this, args);
|
|
623
|
+
if (isThenable(result)) {
|
|
624
|
+
return result.then((res) => {
|
|
625
|
+
try {
|
|
626
|
+
emitQuery(res);
|
|
627
|
+
} catch (e) {
|
|
628
|
+
brakitDebug(`query telemetry: ${getErrorMessage(e)}`);
|
|
629
|
+
}
|
|
630
|
+
return res;
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
if (config.supportsEventEmitter && result && typeof result.on === "function") {
|
|
634
|
+
result.on(
|
|
635
|
+
"end",
|
|
636
|
+
(res) => emitQuery(res)
|
|
637
|
+
);
|
|
638
|
+
return result;
|
|
639
|
+
}
|
|
640
|
+
return result;
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
var appRequire;
|
|
644
|
+
var init_shared = __esm({
|
|
645
|
+
"src/instrument/adapters/shared.ts"() {
|
|
646
|
+
"use strict";
|
|
647
|
+
init_context();
|
|
627
648
|
init_normalize();
|
|
649
|
+
init_type_guards();
|
|
650
|
+
init_log();
|
|
651
|
+
appRequire = createRequire(process.cwd() + "/index.js");
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
// src/instrument/adapters/pg.ts
|
|
656
|
+
var origQuery, proto, pgConfig, pgAdapter;
|
|
657
|
+
var init_pg = __esm({
|
|
658
|
+
"src/instrument/adapters/pg.ts"() {
|
|
659
|
+
"use strict";
|
|
660
|
+
init_shared();
|
|
628
661
|
origQuery = null;
|
|
629
662
|
proto = null;
|
|
663
|
+
pgConfig = {
|
|
664
|
+
driver: "pg",
|
|
665
|
+
extractSql: (args) => {
|
|
666
|
+
const q = args[0];
|
|
667
|
+
if (typeof q === "string") return q;
|
|
668
|
+
if (typeof q === "object" && q !== null && "text" in q) return q.text;
|
|
669
|
+
return void 0;
|
|
670
|
+
},
|
|
671
|
+
extractRowCount: (result) => result?.rowCount,
|
|
672
|
+
supportsEventEmitter: true
|
|
673
|
+
};
|
|
630
674
|
pgAdapter = {
|
|
631
675
|
name: "pg",
|
|
632
676
|
detect() {
|
|
633
677
|
return tryRequire("pg") !== null;
|
|
634
678
|
},
|
|
679
|
+
/** Monkeypatches pg's Client prototype to intercept database queries and emit telemetry events. */
|
|
635
680
|
patch(emit) {
|
|
636
681
|
const pg = tryRequire("pg");
|
|
637
682
|
if (!pg) return;
|
|
638
|
-
|
|
639
|
-
if (!Client || typeof Client !== "function") return;
|
|
640
|
-
proto = Client.prototype ?? null;
|
|
683
|
+
proto = getPrototype(pg, "Client");
|
|
641
684
|
if (!proto?.query) return;
|
|
642
685
|
origQuery = proto.query;
|
|
643
|
-
|
|
644
|
-
proto.query = function(...args) {
|
|
645
|
-
const first = args[0];
|
|
646
|
-
const sql = typeof first === "string" ? first : typeof first === "object" && first !== null && "text" in first ? first.text : void 0;
|
|
647
|
-
const start = performance.now();
|
|
648
|
-
const requestId = captureRequestId();
|
|
649
|
-
const { op, table } = normalizeSQL(sql ?? "");
|
|
650
|
-
const emitQuery = (rowCount) => {
|
|
651
|
-
emit({
|
|
652
|
-
type: "query",
|
|
653
|
-
data: {
|
|
654
|
-
driver: "pg",
|
|
655
|
-
source: "pg",
|
|
656
|
-
sql,
|
|
657
|
-
normalizedOp: op,
|
|
658
|
-
table,
|
|
659
|
-
durationMs: Math.round(performance.now() - start),
|
|
660
|
-
rowCount: rowCount ?? void 0,
|
|
661
|
-
parentRequestId: requestId,
|
|
662
|
-
timestamp: Date.now()
|
|
663
|
-
}
|
|
664
|
-
});
|
|
665
|
-
};
|
|
666
|
-
const lastIdx = args.length - 1;
|
|
667
|
-
if (lastIdx >= 0 && typeof args[lastIdx] === "function") {
|
|
668
|
-
const origCb = args[lastIdx];
|
|
669
|
-
args[lastIdx] = function(err, res) {
|
|
670
|
-
emitQuery(res?.rowCount ?? void 0);
|
|
671
|
-
return origCb.call(this, err, res);
|
|
672
|
-
};
|
|
673
|
-
return saved.apply(this, args);
|
|
674
|
-
}
|
|
675
|
-
const result = saved.apply(this, args);
|
|
676
|
-
if (result && typeof result.then === "function") {
|
|
677
|
-
return result.then((res) => {
|
|
678
|
-
try {
|
|
679
|
-
emitQuery(res?.rowCount ?? void 0);
|
|
680
|
-
} catch {
|
|
681
|
-
}
|
|
682
|
-
return res;
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
if (result && typeof result.on === "function") {
|
|
686
|
-
result.on("end", (res) => emitQuery(res?.rowCount ?? void 0));
|
|
687
|
-
return result;
|
|
688
|
-
}
|
|
689
|
-
return result;
|
|
690
|
-
};
|
|
686
|
+
proto.query = wrapQueryMethod(origQuery, emit, pgConfig);
|
|
691
687
|
},
|
|
692
688
|
unpatch() {
|
|
693
689
|
if (proto && origQuery) {
|
|
@@ -701,70 +697,34 @@ var init_pg = __esm({
|
|
|
701
697
|
});
|
|
702
698
|
|
|
703
699
|
// src/instrument/adapters/mysql2.ts
|
|
704
|
-
var originals2, proto2, mysql2Adapter;
|
|
700
|
+
var PATCHED_METHODS, originals2, proto2, mysql2Config, mysql2Adapter;
|
|
705
701
|
var init_mysql2 = __esm({
|
|
706
702
|
"src/instrument/adapters/mysql2.ts"() {
|
|
707
703
|
"use strict";
|
|
708
704
|
init_shared();
|
|
709
|
-
|
|
705
|
+
PATCHED_METHODS = ["query", "execute"];
|
|
710
706
|
originals2 = /* @__PURE__ */ new Map();
|
|
711
707
|
proto2 = null;
|
|
708
|
+
mysql2Config = {
|
|
709
|
+
driver: "mysql2",
|
|
710
|
+
extractSql: (args) => typeof args[0] === "string" ? args[0] : void 0
|
|
711
|
+
};
|
|
712
712
|
mysql2Adapter = {
|
|
713
713
|
name: "mysql2",
|
|
714
714
|
detect() {
|
|
715
715
|
return tryRequire("mysql2") !== null;
|
|
716
716
|
},
|
|
717
|
+
/** Monkeypatches mysql2's Connection prototype to intercept database queries and emit telemetry events. */
|
|
717
718
|
patch(emit) {
|
|
718
719
|
const mysql2 = tryRequire("mysql2");
|
|
719
720
|
if (!mysql2) return;
|
|
720
|
-
proto2 = mysql2
|
|
721
|
+
proto2 = getPrototype(mysql2, "Connection");
|
|
721
722
|
if (!proto2) return;
|
|
722
|
-
for (const method of
|
|
723
|
+
for (const method of PATCHED_METHODS) {
|
|
723
724
|
const orig = proto2[method];
|
|
724
725
|
if (typeof orig !== "function") continue;
|
|
725
726
|
originals2.set(method, orig);
|
|
726
|
-
proto2[method] =
|
|
727
|
-
const first = args[0];
|
|
728
|
-
const sql = typeof first === "string" ? first : void 0;
|
|
729
|
-
const start = performance.now();
|
|
730
|
-
const requestId = captureRequestId();
|
|
731
|
-
const { op, table } = normalizeSQL(sql ?? "");
|
|
732
|
-
const emitQuery = () => {
|
|
733
|
-
emit({
|
|
734
|
-
type: "query",
|
|
735
|
-
data: {
|
|
736
|
-
driver: "mysql2",
|
|
737
|
-
source: "mysql2",
|
|
738
|
-
sql,
|
|
739
|
-
normalizedOp: op,
|
|
740
|
-
table,
|
|
741
|
-
durationMs: Math.round(performance.now() - start),
|
|
742
|
-
parentRequestId: requestId,
|
|
743
|
-
timestamp: Date.now()
|
|
744
|
-
}
|
|
745
|
-
});
|
|
746
|
-
};
|
|
747
|
-
const lastIdx = args.length - 1;
|
|
748
|
-
if (lastIdx >= 0 && typeof args[lastIdx] === "function") {
|
|
749
|
-
const origCb = args[lastIdx];
|
|
750
|
-
args[lastIdx] = function() {
|
|
751
|
-
emitQuery();
|
|
752
|
-
return origCb.apply(this, arguments);
|
|
753
|
-
};
|
|
754
|
-
return orig.apply(this, args);
|
|
755
|
-
}
|
|
756
|
-
const result = orig.apply(this, args);
|
|
757
|
-
if (result && typeof result.then === "function") {
|
|
758
|
-
return result.then((res) => {
|
|
759
|
-
try {
|
|
760
|
-
emitQuery();
|
|
761
|
-
} catch {
|
|
762
|
-
}
|
|
763
|
-
return res;
|
|
764
|
-
});
|
|
765
|
-
}
|
|
766
|
-
return result;
|
|
767
|
-
};
|
|
727
|
+
proto2[method] = wrapQueryMethod(orig, emit, mysql2Config);
|
|
768
728
|
}
|
|
769
729
|
},
|
|
770
730
|
unpatch() {
|
|
@@ -797,9 +757,8 @@ var init_prisma = __esm({
|
|
|
797
757
|
patch(emit) {
|
|
798
758
|
const prismaModule = tryRequire("@prisma/client");
|
|
799
759
|
if (!prismaModule) return;
|
|
800
|
-
|
|
801
|
-
if (!
|
|
802
|
-
prismaProto = PrismaClient.prototype;
|
|
760
|
+
prismaProto = getPrototype(prismaModule, "PrismaClient");
|
|
761
|
+
if (!prismaProto) return;
|
|
803
762
|
origConnect = prismaProto.$connect;
|
|
804
763
|
if (typeof origConnect !== "function") return;
|
|
805
764
|
const saved = origConnect;
|
|
@@ -815,7 +774,7 @@ var init_prisma = __esm({
|
|
|
815
774
|
args: opArgs,
|
|
816
775
|
query
|
|
817
776
|
}) {
|
|
818
|
-
const requestId =
|
|
777
|
+
const requestId = getActiveRequestId();
|
|
819
778
|
const start = performance.now();
|
|
820
779
|
const result = await query(opArgs);
|
|
821
780
|
emit({
|
|
@@ -829,7 +788,7 @@ var init_prisma = __esm({
|
|
|
829
788
|
table: model,
|
|
830
789
|
durationMs: Math.round(performance.now() - start),
|
|
831
790
|
parentRequestId: requestId,
|
|
832
|
-
timestamp:
|
|
791
|
+
timestamp: performance.now()
|
|
833
792
|
}
|
|
834
793
|
});
|
|
835
794
|
return result;
|
|
@@ -874,24 +833,35 @@ var init_adapters = __esm({
|
|
|
874
833
|
}
|
|
875
834
|
});
|
|
876
835
|
|
|
877
|
-
// src/
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
836
|
+
// src/utils/endpoint.ts
|
|
837
|
+
function isDynamicSegment(segment) {
|
|
838
|
+
return UUID_RE.test(segment) || NUMERIC_ID_RE.test(segment) || HEX_HASH_RE.test(segment) || ALPHA_TOKEN_RE.test(segment);
|
|
839
|
+
}
|
|
840
|
+
function normalizePath(path) {
|
|
841
|
+
const qIdx = path.indexOf("?");
|
|
842
|
+
const pathname = qIdx === -1 ? path : path.slice(0, qIdx);
|
|
843
|
+
return pathname.split("/").map((seg) => seg && isDynamicSegment(seg) ? DYNAMIC_SEGMENT_PLACEHOLDER : seg).join("/");
|
|
844
|
+
}
|
|
845
|
+
function getEndpointKey(method, path) {
|
|
846
|
+
return `${method} ${normalizePath(path)}`;
|
|
847
|
+
}
|
|
848
|
+
function extractEndpointFromDesc(desc) {
|
|
849
|
+
return desc.match(ENDPOINT_PREFIX_RE)?.[1] ?? null;
|
|
850
|
+
}
|
|
851
|
+
function stripQueryString(path) {
|
|
852
|
+
const i = path.indexOf("?");
|
|
853
|
+
return i === -1 ? path : path.slice(0, i);
|
|
854
|
+
}
|
|
855
|
+
var UUID_RE, NUMERIC_ID_RE, HEX_HASH_RE, ALPHA_TOKEN_RE, DYNAMIC_SEGMENT_PLACEHOLDER, ENDPOINT_PREFIX_RE;
|
|
856
|
+
var init_endpoint = __esm({
|
|
857
|
+
"src/utils/endpoint.ts"() {
|
|
881
858
|
"use strict";
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
HTTP_INTERNAL_ERROR = 500;
|
|
889
|
-
SECURITY_HEADERS = {
|
|
890
|
-
"x-content-type-options": "nosniff",
|
|
891
|
-
"x-frame-options": "DENY",
|
|
892
|
-
"referrer-policy": "no-referrer",
|
|
893
|
-
"content-security-policy": "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'; img-src data:"
|
|
894
|
-
};
|
|
859
|
+
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
860
|
+
NUMERIC_ID_RE = /^\d+$/;
|
|
861
|
+
HEX_HASH_RE = /^[0-9a-f]{12,}$/i;
|
|
862
|
+
ALPHA_TOKEN_RE = /^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z0-9_-]{8,}$/;
|
|
863
|
+
DYNAMIC_SEGMENT_PLACEHOLDER = ":id";
|
|
864
|
+
ENDPOINT_PREFIX_RE = /^(\S+\s+\S+)/;
|
|
895
865
|
}
|
|
896
866
|
});
|
|
897
867
|
|
|
@@ -915,6 +885,7 @@ var init_http_status = __esm({
|
|
|
915
885
|
function detectCategory(req) {
|
|
916
886
|
const { method, url, statusCode, responseHeaders } = req;
|
|
917
887
|
if (req.isStatic) return "static";
|
|
888
|
+
if (req.isHealthCheck) return "health-check";
|
|
918
889
|
if (statusCode === 307 && (url.includes("__clerk_handshake") || url.includes("__clerk_db_jwt"))) {
|
|
919
890
|
return "auth-handshake";
|
|
920
891
|
}
|
|
@@ -1079,20 +1050,41 @@ var init_label = __esm({
|
|
|
1079
1050
|
});
|
|
1080
1051
|
|
|
1081
1052
|
// src/analysis/transforms.ts
|
|
1082
|
-
function
|
|
1053
|
+
function isDuplicateCandidate(req) {
|
|
1054
|
+
return DUPLICATE_CATEGORIES.has(req.category);
|
|
1055
|
+
}
|
|
1056
|
+
function buildRequestKey(req) {
|
|
1057
|
+
return `${req.method} ${stripQueryString(getEffectivePath(req))}`;
|
|
1058
|
+
}
|
|
1059
|
+
function isStrictModePattern(requests, counts) {
|
|
1060
|
+
if (counts.size === 0 || ![...counts.values()].every((c) => c === 2)) {
|
|
1061
|
+
return false;
|
|
1062
|
+
}
|
|
1063
|
+
const firstByKey = /* @__PURE__ */ new Map();
|
|
1064
|
+
for (const req of requests) {
|
|
1065
|
+
if (!isDuplicateCandidate(req)) continue;
|
|
1066
|
+
const key = buildRequestKey(req);
|
|
1067
|
+
const first = firstByKey.get(key);
|
|
1068
|
+
if (!first) {
|
|
1069
|
+
firstByKey.set(key, req);
|
|
1070
|
+
} else if (Math.abs(req.startedAt - first.startedAt) > STRICT_MODE_MAX_GAP_MS) {
|
|
1071
|
+
return false;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
return true;
|
|
1075
|
+
}
|
|
1076
|
+
function flagDuplicateRequests(requests) {
|
|
1083
1077
|
const counts = /* @__PURE__ */ new Map();
|
|
1084
1078
|
for (const req of requests) {
|
|
1085
|
-
if (req
|
|
1086
|
-
|
|
1087
|
-
const key = `${req.method} ${getEffectivePath(req).split("?")[0]}`;
|
|
1079
|
+
if (!isDuplicateCandidate(req)) continue;
|
|
1080
|
+
const key = buildRequestKey(req);
|
|
1088
1081
|
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
1089
1082
|
}
|
|
1090
|
-
const isStrictMode =
|
|
1083
|
+
const isStrictMode = isStrictModePattern(requests, counts);
|
|
1091
1084
|
const seen = /* @__PURE__ */ new Set();
|
|
1092
1085
|
for (const req of requests) {
|
|
1093
|
-
if (req
|
|
1094
|
-
|
|
1095
|
-
const key = `${req.method} ${getEffectivePath(req).split("?")[0]}`;
|
|
1086
|
+
if (!isDuplicateCandidate(req)) continue;
|
|
1087
|
+
const key = buildRequestKey(req);
|
|
1096
1088
|
if (seen.has(key)) {
|
|
1097
1089
|
if (isStrictMode) {
|
|
1098
1090
|
req.isStrictModeDupe = true;
|
|
@@ -1104,20 +1096,20 @@ function markDuplicates(requests) {
|
|
|
1104
1096
|
}
|
|
1105
1097
|
}
|
|
1106
1098
|
}
|
|
1107
|
-
function
|
|
1099
|
+
function mergePollingSequences(requests) {
|
|
1108
1100
|
const result = [];
|
|
1109
1101
|
let i = 0;
|
|
1110
1102
|
while (i < requests.length) {
|
|
1111
1103
|
const current = requests[i];
|
|
1112
|
-
const currentEffective = getEffectivePath(current)
|
|
1104
|
+
const currentEffective = stripQueryString(getEffectivePath(current));
|
|
1113
1105
|
if (current.method === "GET" && current.category === "data-fetch") {
|
|
1114
|
-
let
|
|
1115
|
-
while (
|
|
1116
|
-
|
|
1106
|
+
let nextIndex = i + 1;
|
|
1107
|
+
while (nextIndex < requests.length && requests[nextIndex].method === "GET" && stripQueryString(getEffectivePath(requests[nextIndex])) === currentEffective) {
|
|
1108
|
+
nextIndex++;
|
|
1117
1109
|
}
|
|
1118
|
-
const count =
|
|
1110
|
+
const count = nextIndex - i;
|
|
1119
1111
|
if (count >= MIN_POLLING_SEQUENCE) {
|
|
1120
|
-
const last = requests[
|
|
1112
|
+
const last = requests[nextIndex - 1];
|
|
1121
1113
|
const pollingDuration = last.startedAt + last.durationMs - current.startedAt;
|
|
1122
1114
|
const endpointName = prettifyEndpoint(currentEffective);
|
|
1123
1115
|
result.push({
|
|
@@ -1128,7 +1120,7 @@ function collapsePolling(requests) {
|
|
|
1128
1120
|
pollingDurationMs: pollingDuration,
|
|
1129
1121
|
isDuplicate: false
|
|
1130
1122
|
});
|
|
1131
|
-
i =
|
|
1123
|
+
i = nextIndex;
|
|
1132
1124
|
continue;
|
|
1133
1125
|
}
|
|
1134
1126
|
}
|
|
@@ -1141,18 +1133,18 @@ function formatDurationLabel(ms) {
|
|
|
1141
1133
|
if (ms < 1e3) return `${ms}ms`;
|
|
1142
1134
|
return `${(ms / 1e3).toFixed(1)}s`;
|
|
1143
1135
|
}
|
|
1144
|
-
function
|
|
1136
|
+
function collectRequestWarnings(requests) {
|
|
1145
1137
|
const warnings = [];
|
|
1146
1138
|
const duplicateCount = requests.filter((r) => r.isDuplicate).length;
|
|
1147
1139
|
if (duplicateCount > 0) {
|
|
1148
1140
|
const unique = new Set(
|
|
1149
|
-
requests.filter((r) => r.isDuplicate).map((r) =>
|
|
1141
|
+
requests.filter((r) => r.isDuplicate).map((r) => buildRequestKey(r))
|
|
1150
1142
|
);
|
|
1151
1143
|
const endpoints = unique.size;
|
|
1152
1144
|
const sameData = requests.filter((r) => r.isDuplicate).every((r) => {
|
|
1153
|
-
const key =
|
|
1145
|
+
const key = buildRequestKey(r);
|
|
1154
1146
|
const first = requests.find(
|
|
1155
|
-
(o) => !o.isDuplicate &&
|
|
1147
|
+
(o) => !o.isDuplicate && buildRequestKey(o) === key
|
|
1156
1148
|
);
|
|
1157
1149
|
return first && first.responseBody === r.responseBody;
|
|
1158
1150
|
});
|
|
@@ -1173,18 +1165,30 @@ function detectWarnings(requests) {
|
|
|
1173
1165
|
}
|
|
1174
1166
|
return warnings;
|
|
1175
1167
|
}
|
|
1168
|
+
var DUPLICATE_CATEGORIES;
|
|
1176
1169
|
var init_transforms = __esm({
|
|
1177
1170
|
"src/analysis/transforms.ts"() {
|
|
1178
1171
|
"use strict";
|
|
1179
1172
|
init_constants();
|
|
1173
|
+
init_config();
|
|
1180
1174
|
init_categorize();
|
|
1181
1175
|
init_label();
|
|
1182
1176
|
init_http_status();
|
|
1177
|
+
init_endpoint();
|
|
1178
|
+
DUPLICATE_CATEGORIES = /* @__PURE__ */ new Set(["data-fetch", "auth-check"]);
|
|
1183
1179
|
}
|
|
1184
1180
|
});
|
|
1185
1181
|
|
|
1186
1182
|
// src/analysis/group.ts
|
|
1187
1183
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
1184
|
+
function shouldStartNewFlow(labeled, currentRequests, lastEndTime, currentSourcePage, startedAt) {
|
|
1185
|
+
if (currentRequests.length === 0) return false;
|
|
1186
|
+
const sourcePage = labeled.sourcePage;
|
|
1187
|
+
const isNewPage = sourcePage !== void 0 && currentSourcePage !== void 0 && sourcePage !== currentSourcePage;
|
|
1188
|
+
const isTimeGap = startedAt - lastEndTime > FLOW_GAP_MS;
|
|
1189
|
+
const isPageLoad = labeled.category === "page-load" || labeled.category === "navigation";
|
|
1190
|
+
return isNewPage || isTimeGap || isPageLoad;
|
|
1191
|
+
}
|
|
1188
1192
|
function groupRequestsIntoFlows(requests) {
|
|
1189
1193
|
if (requests.length === 0) return [];
|
|
1190
1194
|
const flows = [];
|
|
@@ -1195,17 +1199,12 @@ function groupRequestsIntoFlows(requests) {
|
|
|
1195
1199
|
if (req.path.startsWith(DASHBOARD_PREFIX)) continue;
|
|
1196
1200
|
const labeled = labelRequest(req);
|
|
1197
1201
|
if (labeled.category === "static") continue;
|
|
1198
|
-
|
|
1199
|
-
const gap = currentRequests.length > 0 ? req.startedAt - lastEndTime : 0;
|
|
1200
|
-
const isNewPage = currentRequests.length > 0 && sourcePage !== void 0 && currentSourcePage !== void 0 && sourcePage !== currentSourcePage;
|
|
1201
|
-
const isPageLoad = labeled.category === "page-load" || labeled.category === "navigation";
|
|
1202
|
-
const isTimeGap = currentRequests.length > 0 && gap > FLOW_GAP_MS;
|
|
1203
|
-
if (currentRequests.length > 0 && (isNewPage || isTimeGap || isPageLoad)) {
|
|
1202
|
+
if (shouldStartNewFlow(labeled, currentRequests, lastEndTime, currentSourcePage, req.startedAt)) {
|
|
1204
1203
|
flows.push(buildFlow(currentRequests));
|
|
1205
1204
|
currentRequests = [];
|
|
1206
1205
|
}
|
|
1207
1206
|
currentRequests.push(labeled);
|
|
1208
|
-
currentSourcePage = sourcePage ?? currentSourcePage;
|
|
1207
|
+
currentSourcePage = labeled.sourcePage ?? currentSourcePage;
|
|
1209
1208
|
lastEndTime = Math.max(lastEndTime, req.startedAt + req.durationMs);
|
|
1210
1209
|
}
|
|
1211
1210
|
if (currentRequests.length > 0) {
|
|
@@ -1214,8 +1213,8 @@ function groupRequestsIntoFlows(requests) {
|
|
|
1214
1213
|
return flows;
|
|
1215
1214
|
}
|
|
1216
1215
|
function buildFlow(rawRequests) {
|
|
1217
|
-
|
|
1218
|
-
const requests =
|
|
1216
|
+
flagDuplicateRequests(rawRequests);
|
|
1217
|
+
const requests = mergePollingSequences(rawRequests);
|
|
1219
1218
|
const first = requests[0];
|
|
1220
1219
|
const startTime = first.startedAt;
|
|
1221
1220
|
const endTime = Math.max(
|
|
@@ -1234,7 +1233,7 @@ function buildFlow(rawRequests) {
|
|
|
1234
1233
|
startTime,
|
|
1235
1234
|
totalDurationMs: Math.round(endTime - startTime),
|
|
1236
1235
|
hasErrors: requests.some((r) => isErrorStatus(r.statusCode)),
|
|
1237
|
-
warnings:
|
|
1236
|
+
warnings: collectRequestWarnings(rawRequests),
|
|
1238
1237
|
sourcePage,
|
|
1239
1238
|
redundancyPct
|
|
1240
1239
|
};
|
|
@@ -1246,20 +1245,20 @@ function getDominantSourcePage(requests) {
|
|
|
1246
1245
|
counts.set(req.sourcePage, (counts.get(req.sourcePage) ?? 0) + 1);
|
|
1247
1246
|
}
|
|
1248
1247
|
}
|
|
1249
|
-
let
|
|
1250
|
-
let
|
|
1248
|
+
let mostCommonPage = "";
|
|
1249
|
+
let highestCount = 0;
|
|
1251
1250
|
for (const [page, count] of counts) {
|
|
1252
|
-
if (count >
|
|
1253
|
-
|
|
1254
|
-
|
|
1251
|
+
if (count > highestCount) {
|
|
1252
|
+
mostCommonPage = page;
|
|
1253
|
+
highestCount = count;
|
|
1255
1254
|
}
|
|
1256
1255
|
}
|
|
1257
|
-
return
|
|
1256
|
+
return mostCommonPage || (requests[0]?.path ? stripQueryString(requests[0].path) : "") || "/";
|
|
1258
1257
|
}
|
|
1259
1258
|
function deriveFlowLabel(requests, sourcePage) {
|
|
1260
1259
|
const trigger = requests.find((r) => r.category === "api-call") ?? requests.find((r) => r.category === "server-action") ?? requests.find((r) => r.category === "page-load") ?? requests.find((r) => r.category === "navigation") ?? requests.find((r) => r.category === "data-fetch") ?? requests[0];
|
|
1261
1260
|
if (trigger.category === "page-load" || trigger.category === "navigation") {
|
|
1262
|
-
const pageName = prettifyPageName(trigger.path
|
|
1261
|
+
const pageName = prettifyPageName(stripQueryString(trigger.path));
|
|
1263
1262
|
return `${pageName} Page`;
|
|
1264
1263
|
}
|
|
1265
1264
|
if (trigger.category === "api-call") {
|
|
@@ -1288,6 +1287,7 @@ var init_group = __esm({
|
|
|
1288
1287
|
"use strict";
|
|
1289
1288
|
init_constants();
|
|
1290
1289
|
init_http_status();
|
|
1290
|
+
init_endpoint();
|
|
1291
1291
|
init_label();
|
|
1292
1292
|
init_categorize();
|
|
1293
1293
|
init_transforms();
|
|
@@ -1385,12 +1385,28 @@ var init_shared2 = __esm({
|
|
|
1385
1385
|
"src/dashboard/api/shared.ts"() {
|
|
1386
1386
|
"use strict";
|
|
1387
1387
|
init_constants();
|
|
1388
|
-
|
|
1389
|
-
|
|
1388
|
+
init_config();
|
|
1389
|
+
init_labels();
|
|
1390
1390
|
}
|
|
1391
1391
|
});
|
|
1392
1392
|
|
|
1393
1393
|
// src/dashboard/api/handlers.ts
|
|
1394
|
+
function filterByStatusRange(requests, statusStr) {
|
|
1395
|
+
if (statusStr.endsWith("xx")) {
|
|
1396
|
+
const prefix = parseInt(statusStr[0], 10);
|
|
1397
|
+
return requests.filter(
|
|
1398
|
+
(r) => Math.floor(r.statusCode / 100) === prefix
|
|
1399
|
+
);
|
|
1400
|
+
}
|
|
1401
|
+
const code = parseInt(statusStr, 10);
|
|
1402
|
+
return requests.filter((r) => r.statusCode === code);
|
|
1403
|
+
}
|
|
1404
|
+
function filterBySearch(requests, searchQuery) {
|
|
1405
|
+
const lower = searchQuery.toLowerCase();
|
|
1406
|
+
return requests.filter(
|
|
1407
|
+
(r) => r.url.toLowerCase().includes(lower) || r.requestBody?.toLowerCase().includes(lower) || r.responseBody?.toLowerCase().includes(lower)
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1394
1410
|
function sanitizeRequest(r) {
|
|
1395
1411
|
return {
|
|
1396
1412
|
...r,
|
|
@@ -1398,7 +1414,7 @@ function sanitizeRequest(r) {
|
|
|
1398
1414
|
responseHeaders: maskSensitiveHeaders(r.responseHeaders)
|
|
1399
1415
|
};
|
|
1400
1416
|
}
|
|
1401
|
-
function createRequestsHandler(
|
|
1417
|
+
function createRequestsHandler(services) {
|
|
1402
1418
|
return (req, res) => {
|
|
1403
1419
|
if (!requireGet(req, res)) return;
|
|
1404
1420
|
const url = parseRequestUrl(req);
|
|
@@ -1408,26 +1424,15 @@ function createRequestsHandler(registry) {
|
|
|
1408
1424
|
const rawLimit = parseInt(url.searchParams.get("limit") ?? String(DEFAULT_API_LIMIT), 10);
|
|
1409
1425
|
const limit = Math.min(Math.max(rawLimit || DEFAULT_API_LIMIT, 1), MAX_API_LIMIT);
|
|
1410
1426
|
const offset = Math.max(parseInt(url.searchParams.get("offset") ?? "0", 10) || 0, 0);
|
|
1411
|
-
let results = [...
|
|
1427
|
+
let results = [...services.requestStore.getAll()].reverse();
|
|
1412
1428
|
if (method) {
|
|
1413
1429
|
results = results.filter((r) => r.method === method.toUpperCase());
|
|
1414
1430
|
}
|
|
1415
1431
|
if (status) {
|
|
1416
|
-
|
|
1417
|
-
const prefix = parseInt(status[0], 10);
|
|
1418
|
-
results = results.filter(
|
|
1419
|
-
(r) => Math.floor(r.statusCode / 100) === prefix
|
|
1420
|
-
);
|
|
1421
|
-
} else {
|
|
1422
|
-
const code = parseInt(status, 10);
|
|
1423
|
-
results = results.filter((r) => r.statusCode === code);
|
|
1424
|
-
}
|
|
1432
|
+
results = filterByStatusRange(results, status);
|
|
1425
1433
|
}
|
|
1426
1434
|
if (search) {
|
|
1427
|
-
|
|
1428
|
-
results = results.filter(
|
|
1429
|
-
(r) => r.url.toLowerCase().includes(lower) || r.requestBody?.toLowerCase().includes(lower) || r.responseBody?.toLowerCase().includes(lower)
|
|
1430
|
-
);
|
|
1435
|
+
results = filterBySearch(results, search);
|
|
1431
1436
|
}
|
|
1432
1437
|
const total = results.length;
|
|
1433
1438
|
results = results.slice(offset, offset + limit);
|
|
@@ -1435,96 +1440,84 @@ function createRequestsHandler(registry) {
|
|
|
1435
1440
|
sendJson(req, res, HTTP_OK, { total, requests: sanitized });
|
|
1436
1441
|
};
|
|
1437
1442
|
}
|
|
1438
|
-
function createFlowsHandler(
|
|
1443
|
+
function createFlowsHandler(services) {
|
|
1439
1444
|
return (req, res) => {
|
|
1440
1445
|
if (!requireGet(req, res)) return;
|
|
1441
|
-
const flows = groupRequestsIntoFlows(
|
|
1446
|
+
const flows = groupRequestsIntoFlows(services.requestStore.getAll()).reverse().map((flow) => ({
|
|
1442
1447
|
...flow,
|
|
1443
1448
|
requests: flow.requests.map(sanitizeRequest)
|
|
1444
1449
|
}));
|
|
1445
1450
|
sendJson(req, res, HTTP_OK, { total: flows.length, flows });
|
|
1446
1451
|
};
|
|
1447
1452
|
}
|
|
1448
|
-
function createClearHandler(
|
|
1453
|
+
function createClearHandler(services) {
|
|
1449
1454
|
return (req, res) => {
|
|
1450
1455
|
if (req.method !== "POST") {
|
|
1451
1456
|
sendJson(req, res, HTTP_METHOD_NOT_ALLOWED, { error: "Method not allowed" });
|
|
1452
1457
|
return;
|
|
1453
1458
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1459
|
+
services.requestStore.clear();
|
|
1460
|
+
services.fetchStore.clear();
|
|
1461
|
+
services.logStore.clear();
|
|
1462
|
+
services.errorStore.clear();
|
|
1463
|
+
services.queryStore.clear();
|
|
1464
|
+
services.metricsStore.reset();
|
|
1465
|
+
services.issueStore.clear();
|
|
1466
|
+
services.bus.emit("store:cleared", void 0);
|
|
1462
1467
|
sendJson(req, res, HTTP_OK, { cleared: true });
|
|
1463
1468
|
};
|
|
1464
1469
|
}
|
|
1465
|
-
function createFetchesHandler(
|
|
1466
|
-
return (req, res) => handleTelemetryGet(req, res,
|
|
1470
|
+
function createFetchesHandler(services) {
|
|
1471
|
+
return (req, res) => handleTelemetryGet(req, res, services.fetchStore);
|
|
1467
1472
|
}
|
|
1468
|
-
function createLogsHandler(
|
|
1469
|
-
return (req, res) => handleTelemetryGet(req, res,
|
|
1473
|
+
function createLogsHandler(services) {
|
|
1474
|
+
return (req, res) => handleTelemetryGet(req, res, services.logStore);
|
|
1470
1475
|
}
|
|
1471
|
-
function createErrorsHandler(
|
|
1472
|
-
return (req, res) => handleTelemetryGet(req, res,
|
|
1476
|
+
function createErrorsHandler(services) {
|
|
1477
|
+
return (req, res) => handleTelemetryGet(req, res, services.errorStore);
|
|
1473
1478
|
}
|
|
1474
|
-
function createQueriesHandler(
|
|
1475
|
-
return (req, res) => handleTelemetryGet(req, res,
|
|
1479
|
+
function createQueriesHandler(services) {
|
|
1480
|
+
return (req, res) => handleTelemetryGet(req, res, services.queryStore);
|
|
1476
1481
|
}
|
|
1477
1482
|
var init_handlers = __esm({
|
|
1478
1483
|
"src/dashboard/api/handlers.ts"() {
|
|
1479
1484
|
"use strict";
|
|
1480
1485
|
init_group();
|
|
1481
1486
|
init_constants();
|
|
1482
|
-
|
|
1487
|
+
init_labels();
|
|
1483
1488
|
init_shared2();
|
|
1484
1489
|
}
|
|
1485
1490
|
});
|
|
1486
1491
|
|
|
1487
|
-
// src/utils/
|
|
1488
|
-
function
|
|
1489
|
-
return
|
|
1490
|
-
}
|
|
1491
|
-
function isNumber(val) {
|
|
1492
|
-
return typeof val === "number" && !isNaN(val);
|
|
1493
|
-
}
|
|
1494
|
-
function isBoolean(val) {
|
|
1495
|
-
return typeof val === "boolean";
|
|
1496
|
-
}
|
|
1497
|
-
function getErrorMessage(err) {
|
|
1498
|
-
if (err instanceof Error) return err.message;
|
|
1499
|
-
if (typeof err === "string") return err;
|
|
1500
|
-
return String(err);
|
|
1501
|
-
}
|
|
1502
|
-
function isValidIssueState(val) {
|
|
1503
|
-
return typeof val === "string" && VALID_ISSUE_STATES.has(val);
|
|
1504
|
-
}
|
|
1505
|
-
function isValidIssueCategory(val) {
|
|
1506
|
-
return typeof val === "string" && VALID_ISSUE_CATEGORIES.has(val);
|
|
1507
|
-
}
|
|
1508
|
-
function isValidAiFixStatus(val) {
|
|
1509
|
-
return typeof val === "string" && VALID_AI_FIX_STATUSES.has(val);
|
|
1510
|
-
}
|
|
1511
|
-
function validateIssuesData(parsed) {
|
|
1512
|
-
if (parsed != null && typeof parsed === "object" && !Array.isArray(parsed) && parsed.version === ISSUES_DATA_VERSION && Array.isArray(parsed.issues)) {
|
|
1513
|
-
return parsed;
|
|
1514
|
-
}
|
|
1515
|
-
return null;
|
|
1492
|
+
// src/utils/static-patterns.ts
|
|
1493
|
+
function isStaticPath(urlPath) {
|
|
1494
|
+
return STATIC_PATTERNS.some((p) => p.test(urlPath));
|
|
1516
1495
|
}
|
|
1517
|
-
function
|
|
1518
|
-
|
|
1519
|
-
return parsed;
|
|
1520
|
-
}
|
|
1521
|
-
return null;
|
|
1496
|
+
function isHealthCheckPath(urlPath) {
|
|
1497
|
+
return HEALTH_CHECK_PATTERNS.some((p) => p.test(urlPath));
|
|
1522
1498
|
}
|
|
1523
|
-
var
|
|
1524
|
-
|
|
1499
|
+
var STATIC_PATTERNS, HEALTH_CHECK_PATTERNS;
|
|
1500
|
+
var init_static_patterns = __esm({
|
|
1501
|
+
"src/utils/static-patterns.ts"() {
|
|
1525
1502
|
"use strict";
|
|
1526
|
-
|
|
1527
|
-
|
|
1503
|
+
STATIC_PATTERNS = [
|
|
1504
|
+
/\.(?:js|css|map|ico|png|jpg|jpeg|gif|svg|webp|woff2?|ttf|eot)$/,
|
|
1505
|
+
/^\/favicon/,
|
|
1506
|
+
/^\/node_modules\//,
|
|
1507
|
+
// Framework-specific static/internal paths
|
|
1508
|
+
/^\/_next\//,
|
|
1509
|
+
/^\/__nextjs/,
|
|
1510
|
+
/^\/@vite\//,
|
|
1511
|
+
/^\/__vite/
|
|
1512
|
+
];
|
|
1513
|
+
HEALTH_CHECK_PATTERNS = [
|
|
1514
|
+
/^\/health(z|check)?$/i,
|
|
1515
|
+
/^\/ping$/i,
|
|
1516
|
+
/^\/(ready|readiness|liveness)$/i,
|
|
1517
|
+
/^\/status$/i,
|
|
1518
|
+
/^\/__health$/i,
|
|
1519
|
+
/^\/api\/health(z|check)?$/i
|
|
1520
|
+
];
|
|
1528
1521
|
}
|
|
1529
1522
|
});
|
|
1530
1523
|
|
|
@@ -1545,8 +1538,8 @@ function numOrUndef(val) {
|
|
|
1545
1538
|
function headers(val) {
|
|
1546
1539
|
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
1547
1540
|
const result = {};
|
|
1548
|
-
for (const [
|
|
1549
|
-
if (typeof
|
|
1541
|
+
for (const [key, value] of Object.entries(val)) {
|
|
1542
|
+
if (typeof value === "string") result[key] = value;
|
|
1550
1543
|
}
|
|
1551
1544
|
return result;
|
|
1552
1545
|
}
|
|
@@ -1610,7 +1603,7 @@ function parseRequestEvent(data, ts) {
|
|
|
1610
1603
|
id: str(data.id, randomUUID4()),
|
|
1611
1604
|
method: str(data.method, "GET"),
|
|
1612
1605
|
url,
|
|
1613
|
-
path: url
|
|
1606
|
+
path: stripQueryString(url),
|
|
1614
1607
|
headers: headers(data.headers),
|
|
1615
1608
|
requestBody: isString(data.requestBody) ? data.requestBody : null,
|
|
1616
1609
|
statusCode: num(data.statusCode, 200),
|
|
@@ -1619,7 +1612,8 @@ function parseRequestEvent(data, ts) {
|
|
|
1619
1612
|
startedAt: ts,
|
|
1620
1613
|
durationMs: num(data.durationMs, 0),
|
|
1621
1614
|
responseSize: num(data.responseSize, 0),
|
|
1622
|
-
isStatic: isBoolean(data.isStatic) ? data.isStatic : false
|
|
1615
|
+
isStatic: isBoolean(data.isStatic) ? data.isStatic : false,
|
|
1616
|
+
isHealthCheck: isBoolean(data.isHealthCheck) ? data.isHealthCheck : isHealthCheckPath(stripQueryString(url))
|
|
1623
1617
|
};
|
|
1624
1618
|
}
|
|
1625
1619
|
function routeSDKEvent(event, stores) {
|
|
@@ -1653,7 +1647,9 @@ var init_sdk_event_parser = __esm({
|
|
|
1653
1647
|
"src/dashboard/api/sdk-event-parser.ts"() {
|
|
1654
1648
|
"use strict";
|
|
1655
1649
|
init_type_guards();
|
|
1656
|
-
|
|
1650
|
+
init_labels();
|
|
1651
|
+
init_static_patterns();
|
|
1652
|
+
init_endpoint();
|
|
1657
1653
|
LOG_LEVEL_MAP = {
|
|
1658
1654
|
debug: "debug",
|
|
1659
1655
|
info: "info",
|
|
@@ -1673,28 +1669,28 @@ function isBrakitBatch(msg) {
|
|
|
1673
1669
|
function isSDKPayload(msg) {
|
|
1674
1670
|
return typeof msg === "object" && msg !== null && "_brakit" in msg && "version" in msg && typeof msg.version === "number";
|
|
1675
1671
|
}
|
|
1676
|
-
function createIngestHandler(
|
|
1672
|
+
function createIngestHandler(services) {
|
|
1677
1673
|
const routeEvent = (event) => {
|
|
1678
1674
|
switch (event.type) {
|
|
1679
1675
|
case TIMELINE_FETCH:
|
|
1680
|
-
|
|
1676
|
+
services.fetchStore.add(event.data);
|
|
1681
1677
|
break;
|
|
1682
1678
|
case TIMELINE_LOG:
|
|
1683
|
-
|
|
1679
|
+
services.logStore.add(event.data);
|
|
1684
1680
|
break;
|
|
1685
1681
|
case TIMELINE_ERROR:
|
|
1686
|
-
|
|
1682
|
+
services.errorStore.add(event.data);
|
|
1687
1683
|
break;
|
|
1688
1684
|
case TIMELINE_QUERY:
|
|
1689
|
-
|
|
1685
|
+
services.queryStore.add(event.data);
|
|
1690
1686
|
break;
|
|
1691
1687
|
}
|
|
1692
1688
|
};
|
|
1693
|
-
const queryStore =
|
|
1694
|
-
const fetchStore =
|
|
1695
|
-
const logStore =
|
|
1696
|
-
const errorStore =
|
|
1697
|
-
const requestStore =
|
|
1689
|
+
const queryStore = services.queryStore;
|
|
1690
|
+
const fetchStore = services.fetchStore;
|
|
1691
|
+
const logStore = services.logStore;
|
|
1692
|
+
const errorStore = services.errorStore;
|
|
1693
|
+
const requestStore = services.requestStore;
|
|
1698
1694
|
const stores = {
|
|
1699
1695
|
addQuery: (data) => queryStore.add(data),
|
|
1700
1696
|
addFetch: (data) => fetchStore.add(data),
|
|
@@ -1754,9 +1750,8 @@ function createIngestHandler(registry) {
|
|
|
1754
1750
|
var init_ingest = __esm({
|
|
1755
1751
|
"src/dashboard/api/ingest.ts"() {
|
|
1756
1752
|
"use strict";
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
init_timeline();
|
|
1753
|
+
init_config();
|
|
1754
|
+
init_labels();
|
|
1760
1755
|
init_shared2();
|
|
1761
1756
|
init_sdk_event_parser();
|
|
1762
1757
|
}
|
|
@@ -1776,11 +1771,11 @@ function createMetricsHandler(metricsStore) {
|
|
|
1776
1771
|
sendJson(req, res, HTTP_OK, { endpoints: metricsStore.getAll() });
|
|
1777
1772
|
};
|
|
1778
1773
|
}
|
|
1779
|
-
var
|
|
1774
|
+
var init_metrics = __esm({
|
|
1780
1775
|
"src/dashboard/api/metrics.ts"() {
|
|
1781
1776
|
"use strict";
|
|
1782
1777
|
init_shared2();
|
|
1783
|
-
|
|
1778
|
+
init_labels();
|
|
1784
1779
|
}
|
|
1785
1780
|
});
|
|
1786
1781
|
|
|
@@ -1798,61 +1793,55 @@ var init_metrics_live = __esm({
|
|
|
1798
1793
|
}
|
|
1799
1794
|
});
|
|
1800
1795
|
|
|
1801
|
-
// src/utils/log.ts
|
|
1802
|
-
function brakitWarn(message) {
|
|
1803
|
-
process.stderr.write(`${PREFIX} ${message}
|
|
1804
|
-
`);
|
|
1805
|
-
}
|
|
1806
|
-
function brakitDebug(message) {
|
|
1807
|
-
if (process.env.DEBUG_BRAKIT) {
|
|
1808
|
-
process.stderr.write(`${PREFIX}:debug ${message}
|
|
1809
|
-
`);
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
var PREFIX;
|
|
1813
|
-
var init_log = __esm({
|
|
1814
|
-
"src/utils/log.ts"() {
|
|
1815
|
-
"use strict";
|
|
1816
|
-
PREFIX = "[brakit]";
|
|
1817
|
-
}
|
|
1818
|
-
});
|
|
1819
|
-
|
|
1820
1796
|
// src/dashboard/api/activity.ts
|
|
1821
|
-
function
|
|
1797
|
+
function buildTimeline(services, requestId) {
|
|
1798
|
+
const fetches = services.fetchStore.getByRequest(requestId);
|
|
1799
|
+
const logs = services.logStore.getByRequest(requestId);
|
|
1800
|
+
const errors = services.errorStore.getByRequest(requestId);
|
|
1801
|
+
const queries = services.queryStore.getByRequest(requestId);
|
|
1802
|
+
const timeline = [];
|
|
1803
|
+
for (const fetch of fetches)
|
|
1804
|
+
timeline.push({ type: TIMELINE_FETCH, timestamp: fetch.timestamp, data: fetch });
|
|
1805
|
+
for (const log of logs)
|
|
1806
|
+
timeline.push({ type: TIMELINE_LOG, timestamp: log.timestamp, data: log });
|
|
1807
|
+
for (const error of errors)
|
|
1808
|
+
timeline.push({ type: TIMELINE_ERROR, timestamp: error.timestamp, data: error });
|
|
1809
|
+
for (const query of queries)
|
|
1810
|
+
timeline.push({ type: TIMELINE_QUERY, timestamp: query.timestamp, data: query });
|
|
1811
|
+
timeline.sort((a, b) => a.timestamp - b.timestamp);
|
|
1812
|
+
return {
|
|
1813
|
+
total: timeline.length,
|
|
1814
|
+
timeline,
|
|
1815
|
+
counts: {
|
|
1816
|
+
fetches: fetches.length,
|
|
1817
|
+
logs: logs.length,
|
|
1818
|
+
errors: errors.length,
|
|
1819
|
+
queries: queries.length
|
|
1820
|
+
}
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
function createActivityHandler(services) {
|
|
1822
1824
|
return (req, res) => {
|
|
1823
1825
|
if (!requireGet(req, res)) return;
|
|
1824
1826
|
try {
|
|
1825
1827
|
const url = parseRequestUrl(req);
|
|
1826
1828
|
const requestId = url.searchParams.get("requestId");
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
+
const requestIds = url.searchParams.get("requestIds");
|
|
1830
|
+
if (!requestId && !requestIds) {
|
|
1831
|
+
sendJson(req, res, HTTP_BAD_REQUEST, { error: "requestId or requestIds parameter required" });
|
|
1829
1832
|
return;
|
|
1830
1833
|
}
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
for (const
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
for (const q of queries)
|
|
1843
|
-
timeline.push({ type: TIMELINE_QUERY, timestamp: q.timestamp, data: q });
|
|
1844
|
-
timeline.sort((a, b) => a.timestamp - b.timestamp);
|
|
1845
|
-
sendJson(req, res, HTTP_OK, {
|
|
1846
|
-
requestId,
|
|
1847
|
-
total: timeline.length,
|
|
1848
|
-
timeline,
|
|
1849
|
-
counts: {
|
|
1850
|
-
fetches: fetches.length,
|
|
1851
|
-
logs: logs.length,
|
|
1852
|
-
errors: errors.length,
|
|
1853
|
-
queries: queries.length
|
|
1854
|
-
}
|
|
1855
|
-
});
|
|
1834
|
+
if (requestId) {
|
|
1835
|
+
const result = buildTimeline(services, requestId);
|
|
1836
|
+
sendJson(req, res, HTTP_OK, { requestId, ...result });
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
const ids = (requestIds || "").split(",").filter(Boolean).slice(0, MAX_BATCH_IDS);
|
|
1840
|
+
const activities = {};
|
|
1841
|
+
for (const id of ids) {
|
|
1842
|
+
activities[id] = buildTimeline(services, id);
|
|
1843
|
+
}
|
|
1844
|
+
sendJson(req, res, HTTP_OK, { requestIds: ids, activities });
|
|
1856
1845
|
} catch (err) {
|
|
1857
1846
|
brakitDebug(`activity handler error: ${err}`);
|
|
1858
1847
|
if (!res.headersSent) {
|
|
@@ -1861,13 +1850,14 @@ function createActivityHandler(registry) {
|
|
|
1861
1850
|
}
|
|
1862
1851
|
};
|
|
1863
1852
|
}
|
|
1853
|
+
var MAX_BATCH_IDS;
|
|
1864
1854
|
var init_activity = __esm({
|
|
1865
1855
|
"src/dashboard/api/activity.ts"() {
|
|
1866
1856
|
"use strict";
|
|
1867
1857
|
init_shared2();
|
|
1868
|
-
|
|
1869
|
-
init_timeline();
|
|
1858
|
+
init_labels();
|
|
1870
1859
|
init_log();
|
|
1860
|
+
MAX_BATCH_IDS = 50;
|
|
1871
1861
|
}
|
|
1872
1862
|
});
|
|
1873
1863
|
|
|
@@ -1877,7 +1867,7 @@ var init_api = __esm({
|
|
|
1877
1867
|
"use strict";
|
|
1878
1868
|
init_handlers();
|
|
1879
1869
|
init_ingest();
|
|
1880
|
-
|
|
1870
|
+
init_metrics();
|
|
1881
1871
|
init_metrics_live();
|
|
1882
1872
|
init_activity();
|
|
1883
1873
|
}
|
|
@@ -1952,25 +1942,12 @@ var init_issues = __esm({
|
|
|
1952
1942
|
"use strict";
|
|
1953
1943
|
init_shared2();
|
|
1954
1944
|
init_type_guards();
|
|
1955
|
-
|
|
1956
|
-
}
|
|
1957
|
-
});
|
|
1958
|
-
|
|
1959
|
-
// src/constants/events.ts
|
|
1960
|
-
var SSE_EVENT_FETCH, SSE_EVENT_LOG, SSE_EVENT_ERROR, SSE_EVENT_QUERY, SSE_EVENT_ISSUES;
|
|
1961
|
-
var init_events = __esm({
|
|
1962
|
-
"src/constants/events.ts"() {
|
|
1963
|
-
"use strict";
|
|
1964
|
-
SSE_EVENT_FETCH = "fetch";
|
|
1965
|
-
SSE_EVENT_LOG = "log";
|
|
1966
|
-
SSE_EVENT_ERROR = "error_event";
|
|
1967
|
-
SSE_EVENT_QUERY = "query";
|
|
1968
|
-
SSE_EVENT_ISSUES = "issues";
|
|
1945
|
+
init_labels();
|
|
1969
1946
|
}
|
|
1970
1947
|
});
|
|
1971
1948
|
|
|
1972
1949
|
// src/dashboard/sse.ts
|
|
1973
|
-
function createSSEHandler(
|
|
1950
|
+
function createSSEHandler(services) {
|
|
1974
1951
|
const clients = /* @__PURE__ */ new Set();
|
|
1975
1952
|
function broadcast(eventType, data) {
|
|
1976
1953
|
if (clients.size === 0) return;
|
|
@@ -1992,7 +1969,7 @@ data: ${data}
|
|
|
1992
1969
|
}
|
|
1993
1970
|
}
|
|
1994
1971
|
}
|
|
1995
|
-
const bus =
|
|
1972
|
+
const bus = services.bus;
|
|
1996
1973
|
bus.on("request:completed", (r) => broadcast(null, JSON.stringify(r)));
|
|
1997
1974
|
bus.on("telemetry:fetch", (e) => broadcast(SSE_EVENT_FETCH, JSON.stringify(e)));
|
|
1998
1975
|
bus.on("telemetry:log", (e) => broadcast(SSE_EVENT_LOG, JSON.stringify(e)));
|
|
@@ -2042,8 +2019,7 @@ var init_sse = __esm({
|
|
|
2042
2019
|
"src/dashboard/sse.ts"() {
|
|
2043
2020
|
"use strict";
|
|
2044
2021
|
init_constants();
|
|
2045
|
-
|
|
2046
|
-
init_events();
|
|
2022
|
+
init_labels();
|
|
2047
2023
|
init_shared2();
|
|
2048
2024
|
}
|
|
2049
2025
|
});
|
|
@@ -2098,7 +2074,7 @@ async function ensureGitignoreAsync(dir, entry) {
|
|
|
2098
2074
|
var init_fs = __esm({
|
|
2099
2075
|
"src/utils/fs.ts"() {
|
|
2100
2076
|
"use strict";
|
|
2101
|
-
|
|
2077
|
+
init_config();
|
|
2102
2078
|
init_log();
|
|
2103
2079
|
init_type_guards();
|
|
2104
2080
|
}
|
|
@@ -2187,7 +2163,7 @@ function computeIssueId(issue) {
|
|
|
2187
2163
|
var init_issue_id = __esm({
|
|
2188
2164
|
"src/utils/issue-id.ts"() {
|
|
2189
2165
|
"use strict";
|
|
2190
|
-
|
|
2166
|
+
init_config();
|
|
2191
2167
|
}
|
|
2192
2168
|
});
|
|
2193
2169
|
|
|
@@ -2200,10 +2176,7 @@ var init_issue_store = __esm({
|
|
|
2200
2176
|
"src/store/issue-store.ts"() {
|
|
2201
2177
|
"use strict";
|
|
2202
2178
|
init_fs();
|
|
2203
|
-
|
|
2204
|
-
init_limits();
|
|
2205
|
-
init_thresholds();
|
|
2206
|
-
init_limits();
|
|
2179
|
+
init_config();
|
|
2207
2180
|
init_atomic_writer();
|
|
2208
2181
|
init_log();
|
|
2209
2182
|
init_type_guards();
|
|
@@ -2446,7 +2419,7 @@ function unwrapResponse(parsed) {
|
|
|
2446
2419
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
|
|
2447
2420
|
const obj = parsed;
|
|
2448
2421
|
const keys = Object.keys(obj);
|
|
2449
|
-
if (keys.length >
|
|
2422
|
+
if (keys.length > MAX_WRAPPER_KEYS) return parsed;
|
|
2450
2423
|
let best = null;
|
|
2451
2424
|
let bestSize = 0;
|
|
2452
2425
|
for (const key of keys) {
|
|
@@ -2464,10 +2437,12 @@ function unwrapResponse(parsed) {
|
|
|
2464
2437
|
}
|
|
2465
2438
|
return best && bestSize >= OVERFETCH_UNWRAP_MIN_SIZE ? best : parsed;
|
|
2466
2439
|
}
|
|
2440
|
+
var MAX_WRAPPER_KEYS;
|
|
2467
2441
|
var init_response = __esm({
|
|
2468
2442
|
"src/utils/response.ts"() {
|
|
2469
2443
|
"use strict";
|
|
2470
|
-
|
|
2444
|
+
init_config();
|
|
2445
|
+
MAX_WRAPPER_KEYS = 3;
|
|
2471
2446
|
}
|
|
2472
2447
|
});
|
|
2473
2448
|
|
|
@@ -2505,92 +2480,153 @@ var init_patterns = __esm({
|
|
|
2505
2480
|
}
|
|
2506
2481
|
});
|
|
2507
2482
|
|
|
2508
|
-
// src/
|
|
2509
|
-
function
|
|
2510
|
-
|
|
2511
|
-
if (
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
for (let i = 0; i < Math.min(obj.length, SECRET_SCAN_ARRAY_LIMIT); i++) {
|
|
2515
|
-
found.push(...findSecretKeys(obj[i], prefix, depth + 1));
|
|
2516
|
-
}
|
|
2517
|
-
return found;
|
|
2483
|
+
// src/utils/collections.ts
|
|
2484
|
+
function getOrCreate(map, key, create) {
|
|
2485
|
+
let value = map.get(key);
|
|
2486
|
+
if (value === void 0) {
|
|
2487
|
+
value = create();
|
|
2488
|
+
map.set(key, value);
|
|
2518
2489
|
}
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2490
|
+
return value;
|
|
2491
|
+
}
|
|
2492
|
+
function deduplicateFindings(items, extract) {
|
|
2493
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2494
|
+
const findings = [];
|
|
2495
|
+
for (const item of items) {
|
|
2496
|
+
const result = extract(item);
|
|
2497
|
+
if (!result) continue;
|
|
2498
|
+
const existing = seen.get(result.key);
|
|
2499
|
+
if (existing) {
|
|
2500
|
+
existing.count++;
|
|
2501
|
+
continue;
|
|
2526
2502
|
}
|
|
2503
|
+
seen.set(result.key, result.finding);
|
|
2504
|
+
findings.push(result.finding);
|
|
2527
2505
|
}
|
|
2528
|
-
return
|
|
2506
|
+
return findings;
|
|
2529
2507
|
}
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2508
|
+
function groupBy(items, keyFn) {
|
|
2509
|
+
const map = /* @__PURE__ */ new Map();
|
|
2510
|
+
for (const item of items) {
|
|
2511
|
+
const key = keyFn(item);
|
|
2512
|
+
if (key == null) continue;
|
|
2513
|
+
let arr = map.get(key);
|
|
2514
|
+
if (!arr) {
|
|
2515
|
+
arr = [];
|
|
2516
|
+
map.set(key, arr);
|
|
2517
|
+
}
|
|
2518
|
+
arr.push(item);
|
|
2519
|
+
}
|
|
2520
|
+
return map;
|
|
2521
|
+
}
|
|
2522
|
+
var init_collections = __esm({
|
|
2523
|
+
"src/utils/collections.ts"() {
|
|
2524
|
+
"use strict";
|
|
2525
|
+
}
|
|
2526
|
+
});
|
|
2527
|
+
|
|
2528
|
+
// src/utils/object-scan.ts
|
|
2529
|
+
function walkObject(obj, visitor, options) {
|
|
2530
|
+
const opts = { ...DEFAULTS, ...options };
|
|
2531
|
+
walk(obj, visitor, opts, 0);
|
|
2532
|
+
}
|
|
2533
|
+
function walk(obj, visitor, opts, depth) {
|
|
2534
|
+
if (depth >= opts.maxDepth) return;
|
|
2535
|
+
if (!obj || typeof obj !== "object") return;
|
|
2536
|
+
if (Array.isArray(obj)) {
|
|
2537
|
+
for (let i = 0; i < Math.min(obj.length, opts.arrayLimit); i++) {
|
|
2538
|
+
walk(obj[i], visitor, opts, depth + 1);
|
|
2539
|
+
}
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2542
|
+
for (const key of Object.keys(obj)) {
|
|
2543
|
+
const val = obj[key];
|
|
2544
|
+
visitor(key, val, depth);
|
|
2545
|
+
if (typeof val === "object" && val !== null) {
|
|
2546
|
+
walk(val, visitor, opts, depth + 1);
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
function collectFromObject(obj, match, options) {
|
|
2551
|
+
const results = [];
|
|
2552
|
+
walkObject(obj, (key, value) => {
|
|
2553
|
+
const result = match(key, value);
|
|
2554
|
+
if (result !== null) results.push(result);
|
|
2555
|
+
}, options);
|
|
2556
|
+
return results;
|
|
2557
|
+
}
|
|
2558
|
+
var DEFAULTS;
|
|
2559
|
+
var init_object_scan = __esm({
|
|
2560
|
+
"src/utils/object-scan.ts"() {
|
|
2561
|
+
"use strict";
|
|
2562
|
+
init_config();
|
|
2563
|
+
DEFAULTS = {
|
|
2564
|
+
maxDepth: MAX_OBJECT_SCAN_DEPTH,
|
|
2565
|
+
arrayLimit: SECRET_SCAN_ARRAY_LIMIT
|
|
2572
2566
|
};
|
|
2573
2567
|
}
|
|
2574
2568
|
});
|
|
2575
2569
|
|
|
2576
|
-
// src/analysis/rules/
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2570
|
+
// src/analysis/rules/auth-rules.ts
|
|
2571
|
+
function findSecretKeys(obj) {
|
|
2572
|
+
return collectFromObject(
|
|
2573
|
+
obj,
|
|
2574
|
+
(key, val) => SECRET_KEYS.test(key) && typeof val === "string" && val.length >= MIN_SECRET_VALUE_LENGTH && !MASKED_RE.test(val) ? key : null
|
|
2575
|
+
);
|
|
2576
|
+
}
|
|
2577
|
+
function isFrameworkResponse(request) {
|
|
2578
|
+
if (isRedirect(request.statusCode)) return true;
|
|
2579
|
+
if (request.path?.startsWith("/__")) return true;
|
|
2580
|
+
if (request.responseHeaders?.["x-middleware-rewrite"]) return true;
|
|
2581
|
+
return false;
|
|
2582
|
+
}
|
|
2583
|
+
var exposedSecretRule, tokenInUrlRule, insecureCookieRule, corsCredentialsRule;
|
|
2584
|
+
var init_auth_rules = __esm({
|
|
2585
|
+
"src/analysis/rules/auth-rules.ts"() {
|
|
2580
2586
|
"use strict";
|
|
2581
2587
|
init_patterns();
|
|
2588
|
+
init_config();
|
|
2589
|
+
init_http_status();
|
|
2590
|
+
init_collections();
|
|
2591
|
+
init_object_scan();
|
|
2592
|
+
exposedSecretRule = {
|
|
2593
|
+
id: "exposed-secret",
|
|
2594
|
+
severity: "critical",
|
|
2595
|
+
name: "Exposed Secret in Response",
|
|
2596
|
+
hint: RULE_HINTS["exposed-secret"],
|
|
2597
|
+
check(ctx) {
|
|
2598
|
+
return deduplicateFindings(ctx.requests, (request) => {
|
|
2599
|
+
if (isErrorStatus(request.statusCode)) return null;
|
|
2600
|
+
const parsed = ctx.parsedBodies.response.get(request.id);
|
|
2601
|
+
if (!parsed) return null;
|
|
2602
|
+
const keys = findSecretKeys(parsed);
|
|
2603
|
+
if (keys.length === 0) return null;
|
|
2604
|
+
const ep = `${request.method} ${request.path}`;
|
|
2605
|
+
return {
|
|
2606
|
+
key: `${ep}:${keys.sort().join(",")}`,
|
|
2607
|
+
finding: {
|
|
2608
|
+
severity: "critical",
|
|
2609
|
+
rule: "exposed-secret",
|
|
2610
|
+
title: "Exposed Secret in Response",
|
|
2611
|
+
desc: `${ep} \u2014 response contains ${keys.join(", ")} field${keys.length > 1 ? "s" : ""}`,
|
|
2612
|
+
hint: this.hint,
|
|
2613
|
+
endpoint: ep,
|
|
2614
|
+
count: 1
|
|
2615
|
+
}
|
|
2616
|
+
};
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
};
|
|
2582
2620
|
tokenInUrlRule = {
|
|
2583
2621
|
id: "token-in-url",
|
|
2584
2622
|
severity: "critical",
|
|
2585
2623
|
name: "Auth Token in URL",
|
|
2586
2624
|
hint: RULE_HINTS["token-in-url"],
|
|
2587
2625
|
check(ctx) {
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
const
|
|
2592
|
-
if (qIdx === -1) continue;
|
|
2593
|
-
const params = r.url.substring(qIdx + 1).split("&");
|
|
2626
|
+
return deduplicateFindings(ctx.requests, (request) => {
|
|
2627
|
+
const qIdx = request.url.indexOf("?");
|
|
2628
|
+
if (qIdx === -1) return null;
|
|
2629
|
+
const params = request.url.substring(qIdx + 1).split("&");
|
|
2594
2630
|
const flagged = [];
|
|
2595
2631
|
for (const param of params) {
|
|
2596
2632
|
const [name, ...rest] = param.split("=");
|
|
@@ -2600,165 +2636,50 @@ var init_token_in_url = __esm({
|
|
|
2600
2636
|
flagged.push(name);
|
|
2601
2637
|
}
|
|
2602
2638
|
}
|
|
2603
|
-
if (flagged.length === 0)
|
|
2604
|
-
const ep = `${
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
existing.count++;
|
|
2609
|
-
continue;
|
|
2610
|
-
}
|
|
2611
|
-
const finding = {
|
|
2612
|
-
severity: "critical",
|
|
2613
|
-
rule: "token-in-url",
|
|
2614
|
-
title: "Auth Token in URL",
|
|
2615
|
-
desc: `${ep} \u2014 ${flagged.join(", ")} exposed in query string`,
|
|
2616
|
-
hint: this.hint,
|
|
2617
|
-
endpoint: ep,
|
|
2618
|
-
count: 1
|
|
2619
|
-
};
|
|
2620
|
-
seen.set(dedupKey, finding);
|
|
2621
|
-
findings.push(finding);
|
|
2622
|
-
}
|
|
2623
|
-
return findings;
|
|
2624
|
-
}
|
|
2625
|
-
};
|
|
2626
|
-
}
|
|
2627
|
-
});
|
|
2628
|
-
|
|
2629
|
-
// src/analysis/rules/stack-trace-leak.ts
|
|
2630
|
-
var stackTraceLeakRule;
|
|
2631
|
-
var init_stack_trace_leak = __esm({
|
|
2632
|
-
"src/analysis/rules/stack-trace-leak.ts"() {
|
|
2633
|
-
"use strict";
|
|
2634
|
-
init_patterns();
|
|
2635
|
-
stackTraceLeakRule = {
|
|
2636
|
-
id: "stack-trace-leak",
|
|
2637
|
-
severity: "critical",
|
|
2638
|
-
name: "Stack Trace Leaked to Client",
|
|
2639
|
-
hint: RULE_HINTS["stack-trace-leak"],
|
|
2640
|
-
check(ctx) {
|
|
2641
|
-
const findings = [];
|
|
2642
|
-
const seen = /* @__PURE__ */ new Map();
|
|
2643
|
-
for (const r of ctx.requests) {
|
|
2644
|
-
if (!r.responseBody) continue;
|
|
2645
|
-
if (!STACK_TRACE_RE.test(r.responseBody)) continue;
|
|
2646
|
-
const ep = `${r.method} ${r.path}`;
|
|
2647
|
-
const existing = seen.get(ep);
|
|
2648
|
-
if (existing) {
|
|
2649
|
-
existing.count++;
|
|
2650
|
-
continue;
|
|
2651
|
-
}
|
|
2652
|
-
const finding = {
|
|
2653
|
-
severity: "critical",
|
|
2654
|
-
rule: "stack-trace-leak",
|
|
2655
|
-
title: "Stack Trace Leaked to Client",
|
|
2656
|
-
desc: `${ep} \u2014 response exposes internal stack trace`,
|
|
2657
|
-
hint: this.hint,
|
|
2658
|
-
endpoint: ep,
|
|
2659
|
-
count: 1
|
|
2660
|
-
};
|
|
2661
|
-
seen.set(ep, finding);
|
|
2662
|
-
findings.push(finding);
|
|
2663
|
-
}
|
|
2664
|
-
return findings;
|
|
2665
|
-
}
|
|
2666
|
-
};
|
|
2667
|
-
}
|
|
2668
|
-
});
|
|
2669
|
-
|
|
2670
|
-
// src/analysis/rules/error-info-leak.ts
|
|
2671
|
-
var CRITICAL_PATTERNS, errorInfoLeakRule;
|
|
2672
|
-
var init_error_info_leak = __esm({
|
|
2673
|
-
"src/analysis/rules/error-info-leak.ts"() {
|
|
2674
|
-
"use strict";
|
|
2675
|
-
init_patterns();
|
|
2676
|
-
CRITICAL_PATTERNS = [
|
|
2677
|
-
{ re: DB_CONN_RE, label: "database connection string" },
|
|
2678
|
-
{ re: SQL_FRAGMENT_RE, label: "SQL query fragment" },
|
|
2679
|
-
{ re: SECRET_VAL_RE, label: "secret value" }
|
|
2680
|
-
];
|
|
2681
|
-
errorInfoLeakRule = {
|
|
2682
|
-
id: "error-info-leak",
|
|
2683
|
-
severity: "critical",
|
|
2684
|
-
name: "Sensitive Data in Error Response",
|
|
2685
|
-
hint: RULE_HINTS["error-info-leak"],
|
|
2686
|
-
check(ctx) {
|
|
2687
|
-
const findings = [];
|
|
2688
|
-
const seen = /* @__PURE__ */ new Map();
|
|
2689
|
-
for (const r of ctx.requests) {
|
|
2690
|
-
if (r.statusCode < 400) continue;
|
|
2691
|
-
if (!r.responseBody) continue;
|
|
2692
|
-
if (r.responseHeaders["x-nextjs-error"] || r.responseHeaders["x-nextjs-matched-path"]) continue;
|
|
2693
|
-
const ep = `${r.method} ${r.path}`;
|
|
2694
|
-
for (const p of CRITICAL_PATTERNS) {
|
|
2695
|
-
if (!p.re.test(r.responseBody)) continue;
|
|
2696
|
-
const dedupKey = `${ep}:${p.label}`;
|
|
2697
|
-
const existing = seen.get(dedupKey);
|
|
2698
|
-
if (existing) {
|
|
2699
|
-
existing.count++;
|
|
2700
|
-
continue;
|
|
2701
|
-
}
|
|
2702
|
-
const finding = {
|
|
2639
|
+
if (flagged.length === 0) return null;
|
|
2640
|
+
const ep = `${request.method} ${request.path}`;
|
|
2641
|
+
return {
|
|
2642
|
+
key: `${ep}:${flagged.sort().join(",")}`,
|
|
2643
|
+
finding: {
|
|
2703
2644
|
severity: "critical",
|
|
2704
|
-
rule: "
|
|
2705
|
-
title: "
|
|
2706
|
-
desc: `${ep} \u2014
|
|
2645
|
+
rule: "token-in-url",
|
|
2646
|
+
title: "Auth Token in URL",
|
|
2647
|
+
desc: `${ep} \u2014 ${flagged.join(", ")} exposed in query string`,
|
|
2707
2648
|
hint: this.hint,
|
|
2708
2649
|
endpoint: ep,
|
|
2709
2650
|
count: 1
|
|
2710
|
-
}
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
}
|
|
2714
|
-
}
|
|
2715
|
-
return findings;
|
|
2651
|
+
}
|
|
2652
|
+
};
|
|
2653
|
+
});
|
|
2716
2654
|
}
|
|
2717
2655
|
};
|
|
2718
|
-
}
|
|
2719
|
-
});
|
|
2720
|
-
|
|
2721
|
-
// src/analysis/rules/insecure-cookie.ts
|
|
2722
|
-
function isFrameworkResponse(r) {
|
|
2723
|
-
if (isRedirect(r.statusCode)) return true;
|
|
2724
|
-
if (r.path?.startsWith("/__")) return true;
|
|
2725
|
-
if (r.responseHeaders?.["x-middleware-rewrite"]) return true;
|
|
2726
|
-
return false;
|
|
2727
|
-
}
|
|
2728
|
-
var insecureCookieRule;
|
|
2729
|
-
var init_insecure_cookie = __esm({
|
|
2730
|
-
"src/analysis/rules/insecure-cookie.ts"() {
|
|
2731
|
-
"use strict";
|
|
2732
|
-
init_patterns();
|
|
2733
|
-
init_http_status();
|
|
2734
2656
|
insecureCookieRule = {
|
|
2735
2657
|
id: "insecure-cookie",
|
|
2736
2658
|
severity: "warning",
|
|
2737
2659
|
name: "Insecure Cookie",
|
|
2738
2660
|
hint: RULE_HINTS["insecure-cookie"],
|
|
2739
2661
|
check(ctx) {
|
|
2740
|
-
const
|
|
2741
|
-
const
|
|
2742
|
-
|
|
2743
|
-
if (
|
|
2744
|
-
|
|
2745
|
-
const setCookie = r.responseHeaders["set-cookie"];
|
|
2662
|
+
const cookieEntries = [];
|
|
2663
|
+
for (const request of ctx.requests) {
|
|
2664
|
+
if (!request.responseHeaders) continue;
|
|
2665
|
+
if (isFrameworkResponse(request)) continue;
|
|
2666
|
+
const setCookie = request.responseHeaders["set-cookie"];
|
|
2746
2667
|
if (!setCookie) continue;
|
|
2747
2668
|
const cookies = setCookie.split(/,(?=\s*[A-Za-z0-9_\-]+=)/);
|
|
2748
2669
|
for (const cookie of cookies) {
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
}
|
|
2761
|
-
|
|
2670
|
+
cookieEntries.push({ cookie });
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
return deduplicateFindings(cookieEntries, ({ cookie }) => {
|
|
2674
|
+
const cookieName = cookie.trim().split("=")[0].trim();
|
|
2675
|
+
const lower = cookie.toLowerCase();
|
|
2676
|
+
const issues = [];
|
|
2677
|
+
if (!lower.includes("httponly")) issues.push("HttpOnly");
|
|
2678
|
+
if (!lower.includes("samesite")) issues.push("SameSite");
|
|
2679
|
+
if (issues.length === 0) return null;
|
|
2680
|
+
return {
|
|
2681
|
+
key: `${cookieName}:${issues.join(",")}`,
|
|
2682
|
+
finding: {
|
|
2762
2683
|
severity: "warning",
|
|
2763
2684
|
rule: "insecure-cookie",
|
|
2764
2685
|
title: "Insecure Cookie",
|
|
@@ -2766,56 +2687,11 @@ var init_insecure_cookie = __esm({
|
|
|
2766
2687
|
hint: this.hint,
|
|
2767
2688
|
endpoint: cookieName,
|
|
2768
2689
|
count: 1
|
|
2769
|
-
}
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
}
|
|
2773
|
-
}
|
|
2774
|
-
return findings;
|
|
2775
|
-
}
|
|
2776
|
-
};
|
|
2777
|
-
}
|
|
2778
|
-
});
|
|
2779
|
-
|
|
2780
|
-
// src/analysis/rules/sensitive-logs.ts
|
|
2781
|
-
var sensitiveLogsRule;
|
|
2782
|
-
var init_sensitive_logs = __esm({
|
|
2783
|
-
"src/analysis/rules/sensitive-logs.ts"() {
|
|
2784
|
-
"use strict";
|
|
2785
|
-
init_patterns();
|
|
2786
|
-
sensitiveLogsRule = {
|
|
2787
|
-
id: "sensitive-logs",
|
|
2788
|
-
severity: "warning",
|
|
2789
|
-
name: "Sensitive Data in Logs",
|
|
2790
|
-
hint: RULE_HINTS["sensitive-logs"],
|
|
2791
|
-
check(ctx) {
|
|
2792
|
-
let count = 0;
|
|
2793
|
-
for (const log of ctx.logs) {
|
|
2794
|
-
if (!log.message) continue;
|
|
2795
|
-
if (log.message.startsWith("[brakit]")) continue;
|
|
2796
|
-
if (LOG_SECRET_RE.test(log.message)) count++;
|
|
2797
|
-
}
|
|
2798
|
-
if (count === 0) return [];
|
|
2799
|
-
return [{
|
|
2800
|
-
severity: "warning",
|
|
2801
|
-
rule: "sensitive-logs",
|
|
2802
|
-
title: "Sensitive Data in Logs",
|
|
2803
|
-
desc: `Console output contains secret/token values \u2014 ${count} occurrence${count !== 1 ? "s" : ""}`,
|
|
2804
|
-
hint: this.hint,
|
|
2805
|
-
endpoint: "console",
|
|
2806
|
-
count
|
|
2807
|
-
}];
|
|
2690
|
+
}
|
|
2691
|
+
};
|
|
2692
|
+
});
|
|
2808
2693
|
}
|
|
2809
2694
|
};
|
|
2810
|
-
}
|
|
2811
|
-
});
|
|
2812
|
-
|
|
2813
|
-
// src/analysis/rules/cors-credentials.ts
|
|
2814
|
-
var corsCredentialsRule;
|
|
2815
|
-
var init_cors_credentials = __esm({
|
|
2816
|
-
"src/analysis/rules/cors-credentials.ts"() {
|
|
2817
|
-
"use strict";
|
|
2818
|
-
init_patterns();
|
|
2819
2695
|
corsCredentialsRule = {
|
|
2820
2696
|
id: "cors-credentials",
|
|
2821
2697
|
severity: "warning",
|
|
@@ -2824,12 +2700,12 @@ var init_cors_credentials = __esm({
|
|
|
2824
2700
|
check(ctx) {
|
|
2825
2701
|
const findings = [];
|
|
2826
2702
|
const seen = /* @__PURE__ */ new Set();
|
|
2827
|
-
for (const
|
|
2828
|
-
if (!
|
|
2829
|
-
const origin =
|
|
2830
|
-
const creds =
|
|
2703
|
+
for (const request of ctx.requests) {
|
|
2704
|
+
if (!request.responseHeaders) continue;
|
|
2705
|
+
const origin = request.responseHeaders["access-control-allow-origin"];
|
|
2706
|
+
const creds = request.responseHeaders["access-control-allow-credentials"];
|
|
2831
2707
|
if (origin !== "*" || creds !== "true") continue;
|
|
2832
|
-
const ep = `${
|
|
2708
|
+
const ep = `${request.method} ${request.path}`;
|
|
2833
2709
|
if (seen.has(ep)) continue;
|
|
2834
2710
|
seen.add(ep);
|
|
2835
2711
|
findings.push({
|
|
@@ -2848,25 +2724,13 @@ var init_cors_credentials = __esm({
|
|
|
2848
2724
|
}
|
|
2849
2725
|
});
|
|
2850
2726
|
|
|
2851
|
-
// src/analysis/rules/
|
|
2852
|
-
function findEmails(obj
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
emails.push(...findEmails(obj[i], depth + 1));
|
|
2859
|
-
}
|
|
2860
|
-
return emails;
|
|
2861
|
-
}
|
|
2862
|
-
for (const v of Object.values(obj)) {
|
|
2863
|
-
if (typeof v === "string" && EMAIL_RE.test(v)) {
|
|
2864
|
-
emails.push(v);
|
|
2865
|
-
} else if (typeof v === "object" && v !== null) {
|
|
2866
|
-
emails.push(...findEmails(v, depth + 1));
|
|
2867
|
-
}
|
|
2868
|
-
}
|
|
2869
|
-
return emails;
|
|
2727
|
+
// src/analysis/rules/data-rules.ts
|
|
2728
|
+
function findEmails(obj) {
|
|
2729
|
+
return collectFromObject(
|
|
2730
|
+
obj,
|
|
2731
|
+
(_key, val) => typeof val === "string" && EMAIL_RE.test(val) ? val : null,
|
|
2732
|
+
{ arrayLimit: PII_SCAN_ARRAY_LIMIT }
|
|
2733
|
+
);
|
|
2870
2734
|
}
|
|
2871
2735
|
function topLevelFieldCount(obj) {
|
|
2872
2736
|
if (Array.isArray(obj)) {
|
|
@@ -2939,14 +2803,104 @@ function detectPII(method, reqBody, resBody) {
|
|
|
2939
2803
|
const target = unwrapResponse(resBody);
|
|
2940
2804
|
return detectEchoPII(method, reqBody, target) ?? detectFullRecordPII(target) ?? detectListPII(target) ?? detectSensitiveFieldPII(target);
|
|
2941
2805
|
}
|
|
2942
|
-
var WRITE_METHODS, REASON_LABELS, responsePiiLeakRule;
|
|
2943
|
-
var
|
|
2944
|
-
"src/analysis/rules/
|
|
2806
|
+
var stackTraceLeakRule, CRITICAL_PATTERNS, errorInfoLeakRule, sensitiveLogsRule, WRITE_METHODS, REASON_LABELS, responsePiiLeakRule;
|
|
2807
|
+
var init_data_rules = __esm({
|
|
2808
|
+
"src/analysis/rules/data-rules.ts"() {
|
|
2945
2809
|
"use strict";
|
|
2810
|
+
init_collections();
|
|
2946
2811
|
init_patterns();
|
|
2947
2812
|
init_response();
|
|
2948
|
-
|
|
2813
|
+
init_config();
|
|
2949
2814
|
init_http_status();
|
|
2815
|
+
init_object_scan();
|
|
2816
|
+
stackTraceLeakRule = {
|
|
2817
|
+
id: "stack-trace-leak",
|
|
2818
|
+
severity: "critical",
|
|
2819
|
+
name: "Stack Trace Leaked to Client",
|
|
2820
|
+
hint: RULE_HINTS["stack-trace-leak"],
|
|
2821
|
+
check(ctx) {
|
|
2822
|
+
return deduplicateFindings(ctx.requests, (request) => {
|
|
2823
|
+
if (!request.responseBody) return null;
|
|
2824
|
+
if (!STACK_TRACE_RE.test(request.responseBody)) return null;
|
|
2825
|
+
const ep = `${request.method} ${request.path}`;
|
|
2826
|
+
return {
|
|
2827
|
+
key: ep,
|
|
2828
|
+
finding: {
|
|
2829
|
+
severity: "critical",
|
|
2830
|
+
rule: "stack-trace-leak",
|
|
2831
|
+
title: "Stack Trace Leaked to Client",
|
|
2832
|
+
desc: `${ep} \u2014 response exposes internal stack trace`,
|
|
2833
|
+
hint: this.hint,
|
|
2834
|
+
endpoint: ep,
|
|
2835
|
+
count: 1
|
|
2836
|
+
}
|
|
2837
|
+
};
|
|
2838
|
+
});
|
|
2839
|
+
}
|
|
2840
|
+
};
|
|
2841
|
+
CRITICAL_PATTERNS = [
|
|
2842
|
+
{ re: DB_CONN_RE, label: "database connection string" },
|
|
2843
|
+
{ re: SQL_FRAGMENT_RE, label: "SQL query fragment" },
|
|
2844
|
+
{ re: SECRET_VAL_RE, label: "secret value" }
|
|
2845
|
+
];
|
|
2846
|
+
errorInfoLeakRule = {
|
|
2847
|
+
id: "error-info-leak",
|
|
2848
|
+
severity: "critical",
|
|
2849
|
+
name: "Sensitive Data in Error Response",
|
|
2850
|
+
hint: RULE_HINTS["error-info-leak"],
|
|
2851
|
+
check(ctx) {
|
|
2852
|
+
const entries = [];
|
|
2853
|
+
for (const request of ctx.requests) {
|
|
2854
|
+
if (request.statusCode < 400) continue;
|
|
2855
|
+
if (!request.responseBody) continue;
|
|
2856
|
+
if (request.responseHeaders["x-nextjs-error"] || request.responseHeaders["x-nextjs-matched-path"]) continue;
|
|
2857
|
+
const ep = `${request.method} ${request.path}`;
|
|
2858
|
+
for (const pattern of CRITICAL_PATTERNS) {
|
|
2859
|
+
if (pattern.re.test(request.responseBody)) {
|
|
2860
|
+
entries.push({ ep, pattern, body: request.responseBody });
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
return deduplicateFindings(entries, ({ ep, pattern }) => {
|
|
2865
|
+
return {
|
|
2866
|
+
key: `${ep}:${pattern.label}`,
|
|
2867
|
+
finding: {
|
|
2868
|
+
severity: "critical",
|
|
2869
|
+
rule: "error-info-leak",
|
|
2870
|
+
title: "Sensitive Data in Error Response",
|
|
2871
|
+
desc: `${ep} \u2014 error response exposes ${pattern.label}`,
|
|
2872
|
+
hint: this.hint,
|
|
2873
|
+
endpoint: ep,
|
|
2874
|
+
count: 1
|
|
2875
|
+
}
|
|
2876
|
+
};
|
|
2877
|
+
});
|
|
2878
|
+
}
|
|
2879
|
+
};
|
|
2880
|
+
sensitiveLogsRule = {
|
|
2881
|
+
id: "sensitive-logs",
|
|
2882
|
+
severity: "warning",
|
|
2883
|
+
name: "Sensitive Data in Logs",
|
|
2884
|
+
hint: RULE_HINTS["sensitive-logs"],
|
|
2885
|
+
check(ctx) {
|
|
2886
|
+
let count = 0;
|
|
2887
|
+
for (const log of ctx.logs) {
|
|
2888
|
+
if (!log.message) continue;
|
|
2889
|
+
if (log.message.startsWith("[brakit]")) continue;
|
|
2890
|
+
if (LOG_SECRET_RE.test(log.message)) count++;
|
|
2891
|
+
}
|
|
2892
|
+
if (count === 0) return [];
|
|
2893
|
+
return [{
|
|
2894
|
+
severity: "warning",
|
|
2895
|
+
rule: "sensitive-logs",
|
|
2896
|
+
title: "Sensitive Data in Logs",
|
|
2897
|
+
desc: `Console output contains secret/token values \u2014 ${count} occurrence${count !== 1 ? "s" : ""}`,
|
|
2898
|
+
hint: this.hint,
|
|
2899
|
+
endpoint: "console",
|
|
2900
|
+
count
|
|
2901
|
+
}];
|
|
2902
|
+
}
|
|
2903
|
+
};
|
|
2950
2904
|
WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
|
|
2951
2905
|
REASON_LABELS = {
|
|
2952
2906
|
echo: "echoes back PII from the request body",
|
|
@@ -2960,35 +2914,28 @@ var init_response_pii_leak = __esm({
|
|
|
2960
2914
|
name: "PII Leak in Response",
|
|
2961
2915
|
hint: RULE_HINTS["response-pii-leak"],
|
|
2962
2916
|
check(ctx) {
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
if (
|
|
2968
|
-
const
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
const
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
desc: `${ep} \u2014 exposes PII in response`,
|
|
2984
|
-
hint: `Detection: ${REASON_LABELS[detection.reason]}. ${this.hint}`,
|
|
2985
|
-
endpoint: ep,
|
|
2986
|
-
count: 1
|
|
2917
|
+
return deduplicateFindings(ctx.requests, (request) => {
|
|
2918
|
+
if (isErrorStatus(request.statusCode)) return null;
|
|
2919
|
+
if (SELF_SERVICE_PATH.test(request.path)) return null;
|
|
2920
|
+
const resJson = ctx.parsedBodies.response.get(request.id);
|
|
2921
|
+
if (!resJson) return null;
|
|
2922
|
+
const reqJson = ctx.parsedBodies.request.get(request.id) ?? null;
|
|
2923
|
+
const detection = detectPII(request.method, reqJson, resJson);
|
|
2924
|
+
if (!detection) return null;
|
|
2925
|
+
const ep = `${request.method} ${request.path}`;
|
|
2926
|
+
return {
|
|
2927
|
+
key: ep,
|
|
2928
|
+
finding: {
|
|
2929
|
+
severity: "warning",
|
|
2930
|
+
rule: "response-pii-leak",
|
|
2931
|
+
title: "PII Leak in Response",
|
|
2932
|
+
desc: `${ep} \u2014 exposes PII in response`,
|
|
2933
|
+
hint: `Detection: ${REASON_LABELS[detection.reason]}. ${this.hint}`,
|
|
2934
|
+
endpoint: ep,
|
|
2935
|
+
count: 1
|
|
2936
|
+
}
|
|
2987
2937
|
};
|
|
2988
|
-
|
|
2989
|
-
findings.push(finding);
|
|
2990
|
-
}
|
|
2991
|
-
return findings;
|
|
2938
|
+
});
|
|
2992
2939
|
}
|
|
2993
2940
|
};
|
|
2994
2941
|
}
|
|
@@ -2998,14 +2945,14 @@ var init_response_pii_leak = __esm({
|
|
|
2998
2945
|
function buildBodyCache(requests) {
|
|
2999
2946
|
const response = /* @__PURE__ */ new Map();
|
|
3000
2947
|
const request = /* @__PURE__ */ new Map();
|
|
3001
|
-
for (const
|
|
3002
|
-
if (
|
|
3003
|
-
const parsed = tryParseJson(
|
|
3004
|
-
if (parsed != null) response.set(
|
|
2948
|
+
for (const req of requests) {
|
|
2949
|
+
if (req.responseBody) {
|
|
2950
|
+
const parsed = tryParseJson(req.responseBody);
|
|
2951
|
+
if (parsed != null) response.set(req.id, parsed);
|
|
3005
2952
|
}
|
|
3006
|
-
if (
|
|
3007
|
-
const parsed = tryParseJson(
|
|
3008
|
-
if (parsed != null) request.set(
|
|
2953
|
+
if (req.requestBody) {
|
|
2954
|
+
const parsed = tryParseJson(req.requestBody);
|
|
2955
|
+
if (parsed != null) request.set(req.id, parsed);
|
|
3009
2956
|
}
|
|
3010
2957
|
}
|
|
3011
2958
|
return { response, request };
|
|
@@ -3027,14 +2974,10 @@ var init_scanner = __esm({
|
|
|
3027
2974
|
"src/analysis/rules/scanner.ts"() {
|
|
3028
2975
|
"use strict";
|
|
3029
2976
|
init_response();
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
init_insecure_cookie();
|
|
3035
|
-
init_sensitive_logs();
|
|
3036
|
-
init_cors_credentials();
|
|
3037
|
-
init_response_pii_leak();
|
|
2977
|
+
init_log();
|
|
2978
|
+
init_type_guards();
|
|
2979
|
+
init_auth_rules();
|
|
2980
|
+
init_data_rules();
|
|
3038
2981
|
SecurityScanner = class {
|
|
3039
2982
|
constructor() {
|
|
3040
2983
|
this.rules = [];
|
|
@@ -3051,7 +2994,8 @@ var init_scanner = __esm({
|
|
|
3051
2994
|
for (const rule of this.rules) {
|
|
3052
2995
|
try {
|
|
3053
2996
|
findings.push(...rule.check(ctx));
|
|
3054
|
-
} catch {
|
|
2997
|
+
} catch (e) {
|
|
2998
|
+
brakitDebug(`rule ${rule.id} failed: ${getErrorMessage(e)}`);
|
|
3055
2999
|
}
|
|
3056
3000
|
}
|
|
3057
3001
|
return findings;
|
|
@@ -3068,14 +3012,8 @@ var init_rules = __esm({
|
|
|
3068
3012
|
"src/analysis/rules/index.ts"() {
|
|
3069
3013
|
"use strict";
|
|
3070
3014
|
init_scanner();
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
init_stack_trace_leak();
|
|
3074
|
-
init_error_info_leak();
|
|
3075
|
-
init_insecure_cookie();
|
|
3076
|
-
init_sensitive_logs();
|
|
3077
|
-
init_cors_credentials();
|
|
3078
|
-
init_response_pii_leak();
|
|
3015
|
+
init_auth_rules();
|
|
3016
|
+
init_data_rules();
|
|
3079
3017
|
}
|
|
3080
3018
|
});
|
|
3081
3019
|
|
|
@@ -3088,56 +3026,14 @@ var init_disposable = __esm({
|
|
|
3088
3026
|
constructor() {
|
|
3089
3027
|
this.items = [];
|
|
3090
3028
|
}
|
|
3091
|
-
add(teardown) {
|
|
3092
|
-
this.items.push(typeof teardown === "function" ? { dispose: teardown } : teardown);
|
|
3093
|
-
}
|
|
3094
|
-
dispose() {
|
|
3095
|
-
for (const d of this.items) d.dispose();
|
|
3096
|
-
this.items.length = 0;
|
|
3097
|
-
}
|
|
3098
|
-
};
|
|
3099
|
-
}
|
|
3100
|
-
});
|
|
3101
|
-
|
|
3102
|
-
// src/utils/collections.ts
|
|
3103
|
-
function groupBy(items, keyFn) {
|
|
3104
|
-
const map = /* @__PURE__ */ new Map();
|
|
3105
|
-
for (const item of items) {
|
|
3106
|
-
const key = keyFn(item);
|
|
3107
|
-
if (key == null) continue;
|
|
3108
|
-
let arr = map.get(key);
|
|
3109
|
-
if (!arr) {
|
|
3110
|
-
arr = [];
|
|
3111
|
-
map.set(key, arr);
|
|
3112
|
-
}
|
|
3113
|
-
arr.push(item);
|
|
3114
|
-
}
|
|
3115
|
-
return map;
|
|
3116
|
-
}
|
|
3117
|
-
var init_collections = __esm({
|
|
3118
|
-
"src/utils/collections.ts"() {
|
|
3119
|
-
"use strict";
|
|
3120
|
-
}
|
|
3121
|
-
});
|
|
3122
|
-
|
|
3123
|
-
// src/utils/endpoint.ts
|
|
3124
|
-
function normalizePath(path) {
|
|
3125
|
-
const qIdx = path.indexOf("?");
|
|
3126
|
-
const pathname = qIdx === -1 ? path : path.slice(0, qIdx);
|
|
3127
|
-
return pathname.split("/").map((seg) => seg && DYNAMIC_SEGMENT_RE.test(seg) ? ":id" : seg).join("/");
|
|
3128
|
-
}
|
|
3129
|
-
function getEndpointKey(method, path) {
|
|
3130
|
-
return `${method} ${normalizePath(path)}`;
|
|
3131
|
-
}
|
|
3132
|
-
function extractEndpointFromDesc(desc) {
|
|
3133
|
-
return desc.match(ENDPOINT_PREFIX_RE)?.[1] ?? null;
|
|
3134
|
-
}
|
|
3135
|
-
var DYNAMIC_SEGMENT_RE, ENDPOINT_PREFIX_RE;
|
|
3136
|
-
var init_endpoint = __esm({
|
|
3137
|
-
"src/utils/endpoint.ts"() {
|
|
3138
|
-
"use strict";
|
|
3139
|
-
DYNAMIC_SEGMENT_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$|^\d+$|^[0-9a-f]{12,}$|^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z0-9_-]{8,}$/i;
|
|
3140
|
-
ENDPOINT_PREFIX_RE = /^(\S+\s+\S+)/;
|
|
3029
|
+
add(teardown) {
|
|
3030
|
+
this.items.push(typeof teardown === "function" ? { dispose: teardown } : teardown);
|
|
3031
|
+
}
|
|
3032
|
+
dispose() {
|
|
3033
|
+
for (const d of this.items) d.dispose();
|
|
3034
|
+
this.items.length = 0;
|
|
3035
|
+
}
|
|
3036
|
+
};
|
|
3141
3037
|
}
|
|
3142
3038
|
});
|
|
3143
3039
|
|
|
@@ -3161,7 +3057,7 @@ var init_query_helpers = __esm({
|
|
|
3161
3057
|
});
|
|
3162
3058
|
|
|
3163
3059
|
// src/analysis/insights/prepare.ts
|
|
3164
|
-
function
|
|
3060
|
+
function emptyEndpointGroup() {
|
|
3165
3061
|
return {
|
|
3166
3062
|
total: 0,
|
|
3167
3063
|
errors: 0,
|
|
@@ -3173,16 +3069,12 @@ function createEndpointGroup() {
|
|
|
3173
3069
|
queryShapeDurations: /* @__PURE__ */ new Map()
|
|
3174
3070
|
};
|
|
3175
3071
|
}
|
|
3176
|
-
function
|
|
3072
|
+
function keepRecentPerEndpoint(requests) {
|
|
3177
3073
|
const byEndpoint = /* @__PURE__ */ new Map();
|
|
3178
|
-
for (const
|
|
3179
|
-
const
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
list = [];
|
|
3183
|
-
byEndpoint.set(ep, list);
|
|
3184
|
-
}
|
|
3185
|
-
list.push(r);
|
|
3074
|
+
for (const request of requests) {
|
|
3075
|
+
const endpointKey = getEndpointKey(request.method, request.path);
|
|
3076
|
+
const list = getOrCreate(byEndpoint, endpointKey, () => []);
|
|
3077
|
+
list.push(request);
|
|
3186
3078
|
}
|
|
3187
3079
|
const windowed = [];
|
|
3188
3080
|
for (const [, reqs] of byEndpoint) {
|
|
@@ -3190,54 +3082,67 @@ function windowByEndpoint(requests) {
|
|
|
3190
3082
|
}
|
|
3191
3083
|
return windowed;
|
|
3192
3084
|
}
|
|
3085
|
+
function filterUserRequests(requests) {
|
|
3086
|
+
return requests.filter(
|
|
3087
|
+
(request) => !request.isStatic && !request.isHealthCheck && (!request.path || !request.path.startsWith(DASHBOARD_PREFIX))
|
|
3088
|
+
);
|
|
3089
|
+
}
|
|
3193
3090
|
function extractActiveEndpoints(requests) {
|
|
3194
3091
|
const endpoints = /* @__PURE__ */ new Set();
|
|
3195
|
-
for (const
|
|
3196
|
-
|
|
3197
|
-
endpoints.add(getEndpointKey(r.method, r.path));
|
|
3198
|
-
}
|
|
3092
|
+
for (const request of filterUserRequests(requests)) {
|
|
3093
|
+
endpoints.add(getEndpointKey(request.method, request.path));
|
|
3199
3094
|
}
|
|
3200
3095
|
return endpoints;
|
|
3201
3096
|
}
|
|
3202
|
-
function
|
|
3203
|
-
const nonStatic = ctx.requests.filter(
|
|
3204
|
-
(r) => !r.isStatic && (!r.path || !r.path.startsWith(DASHBOARD_PREFIX))
|
|
3205
|
-
);
|
|
3206
|
-
const queriesByReq = groupBy(ctx.queries, (q) => q.parentRequestId);
|
|
3207
|
-
const fetchesByReq = groupBy(ctx.fetches, (f) => f.parentRequestId);
|
|
3208
|
-
const reqById = new Map(nonStatic.map((r) => [r.id, r]));
|
|
3209
|
-
const recent = windowByEndpoint(nonStatic);
|
|
3097
|
+
function aggregateEndpointMetrics(recent, queriesByReq, fetchesByReq) {
|
|
3210
3098
|
const endpointGroups = /* @__PURE__ */ new Map();
|
|
3211
|
-
for (const
|
|
3212
|
-
const
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3099
|
+
for (const request of recent) {
|
|
3100
|
+
const endpointKey = getEndpointKey(request.method, request.path);
|
|
3101
|
+
const group = getOrCreate(endpointGroups, endpointKey, emptyEndpointGroup);
|
|
3102
|
+
group.total++;
|
|
3103
|
+
if (isErrorStatus(request.statusCode)) group.errors++;
|
|
3104
|
+
group.totalDuration += request.durationMs;
|
|
3105
|
+
group.totalSize += request.responseSize ?? 0;
|
|
3106
|
+
const reqQueries = queriesByReq.get(request.id) ?? [];
|
|
3107
|
+
group.queryCount += reqQueries.length;
|
|
3108
|
+
for (const query of reqQueries) {
|
|
3109
|
+
group.totalQueryTimeMs += query.durationMs;
|
|
3110
|
+
const shape = getQueryShape(query);
|
|
3111
|
+
const info = getQueryInfo(query);
|
|
3112
|
+
const shapeDuration = getOrCreate(group.queryShapeDurations, shape, () => ({
|
|
3113
|
+
totalMs: 0,
|
|
3114
|
+
count: 0,
|
|
3115
|
+
label: info.op + (info.table ? ` ${info.table}` : "")
|
|
3116
|
+
}));
|
|
3117
|
+
shapeDuration.totalMs += query.durationMs;
|
|
3118
|
+
shapeDuration.count++;
|
|
3217
3119
|
}
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
g.totalSize += r.responseSize ?? 0;
|
|
3222
|
-
const reqQueries = queriesByReq.get(r.id) ?? [];
|
|
3223
|
-
g.queryCount += reqQueries.length;
|
|
3224
|
-
for (const q of reqQueries) {
|
|
3225
|
-
g.totalQueryTimeMs += q.durationMs;
|
|
3226
|
-
const shape = getQueryShape(q);
|
|
3227
|
-
const info = getQueryInfo(q);
|
|
3228
|
-
let sd = g.queryShapeDurations.get(shape);
|
|
3229
|
-
if (!sd) {
|
|
3230
|
-
sd = { totalMs: 0, count: 0, label: info.op + (info.table ? ` ${info.table}` : "") };
|
|
3231
|
-
g.queryShapeDurations.set(shape, sd);
|
|
3232
|
-
}
|
|
3233
|
-
sd.totalMs += q.durationMs;
|
|
3234
|
-
sd.count++;
|
|
3120
|
+
const reqFetches = fetchesByReq.get(request.id) ?? [];
|
|
3121
|
+
for (const fetch of reqFetches) {
|
|
3122
|
+
group.totalFetchTimeMs += fetch.durationMs;
|
|
3235
3123
|
}
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3124
|
+
}
|
|
3125
|
+
return endpointGroups;
|
|
3126
|
+
}
|
|
3127
|
+
function collectStrictModeDupeIds(ctx) {
|
|
3128
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3129
|
+
for (const flow of ctx.flows) {
|
|
3130
|
+
for (const req of flow.requests) {
|
|
3131
|
+
if (req.isStrictModeDupe) ids.add(req.id);
|
|
3239
3132
|
}
|
|
3240
3133
|
}
|
|
3134
|
+
return ids;
|
|
3135
|
+
}
|
|
3136
|
+
function buildInsightContext(ctx) {
|
|
3137
|
+
const strictModeDupeIds = collectStrictModeDupeIds(ctx);
|
|
3138
|
+
const nonStatic = filterUserRequests(ctx.requests).filter((req) => !strictModeDupeIds.has(req.id));
|
|
3139
|
+
const filteredQueries = strictModeDupeIds.size > 0 ? ctx.queries.filter((q) => !q.parentRequestId || !strictModeDupeIds.has(q.parentRequestId)) : ctx.queries;
|
|
3140
|
+
const filteredFetches = strictModeDupeIds.size > 0 ? ctx.fetches.filter((f) => !f.parentRequestId || !strictModeDupeIds.has(f.parentRequestId)) : ctx.fetches;
|
|
3141
|
+
const queriesByReq = groupBy(filteredQueries, (query) => query.parentRequestId);
|
|
3142
|
+
const fetchesByReq = groupBy(filteredFetches, (fetch) => fetch.parentRequestId);
|
|
3143
|
+
const reqById = new Map(nonStatic.map((request) => [request.id, request]));
|
|
3144
|
+
const recent = keepRecentPerEndpoint(nonStatic);
|
|
3145
|
+
const endpointGroups = aggregateEndpointMetrics(recent, queriesByReq, fetchesByReq);
|
|
3241
3146
|
return {
|
|
3242
3147
|
...ctx,
|
|
3243
3148
|
nonStatic,
|
|
@@ -3254,7 +3159,7 @@ var init_prepare = __esm({
|
|
|
3254
3159
|
init_endpoint();
|
|
3255
3160
|
init_constants();
|
|
3256
3161
|
init_http_status();
|
|
3257
|
-
|
|
3162
|
+
init_config();
|
|
3258
3163
|
init_query_helpers();
|
|
3259
3164
|
}
|
|
3260
3165
|
});
|
|
@@ -3265,6 +3170,8 @@ var init_runner = __esm({
|
|
|
3265
3170
|
"src/analysis/insights/runner.ts"() {
|
|
3266
3171
|
"use strict";
|
|
3267
3172
|
init_prepare();
|
|
3173
|
+
init_log();
|
|
3174
|
+
init_type_guards();
|
|
3268
3175
|
SEVERITY_ORDER = { critical: 0, warning: 1, info: 2 };
|
|
3269
3176
|
InsightRunner = class {
|
|
3270
3177
|
constructor() {
|
|
@@ -3274,12 +3181,13 @@ var init_runner = __esm({
|
|
|
3274
3181
|
this.rules.push(rule);
|
|
3275
3182
|
}
|
|
3276
3183
|
run(ctx) {
|
|
3277
|
-
const prepared =
|
|
3184
|
+
const prepared = buildInsightContext(ctx);
|
|
3278
3185
|
const insights = [];
|
|
3279
3186
|
for (const rule of this.rules) {
|
|
3280
3187
|
try {
|
|
3281
3188
|
insights.push(...rule.check(prepared));
|
|
3282
|
-
} catch {
|
|
3189
|
+
} catch (e) {
|
|
3190
|
+
brakitDebug(`insight rule ${rule.id} failed: ${getErrorMessage(e)}`);
|
|
3283
3191
|
}
|
|
3284
3192
|
}
|
|
3285
3193
|
insights.sort(
|
|
@@ -3291,14 +3199,15 @@ var init_runner = __esm({
|
|
|
3291
3199
|
}
|
|
3292
3200
|
});
|
|
3293
3201
|
|
|
3294
|
-
// src/analysis/insights/rules/
|
|
3295
|
-
var n1Rule;
|
|
3296
|
-
var
|
|
3297
|
-
"src/analysis/insights/rules/
|
|
3202
|
+
// src/analysis/insights/rules/query-rules.ts
|
|
3203
|
+
var n1Rule, redundantQueryRule, selectStarRule, highRowsRule, queryHeavyRule;
|
|
3204
|
+
var init_query_rules = __esm({
|
|
3205
|
+
"src/analysis/insights/rules/query-rules.ts"() {
|
|
3298
3206
|
"use strict";
|
|
3299
3207
|
init_query_helpers();
|
|
3300
3208
|
init_endpoint();
|
|
3301
3209
|
init_constants();
|
|
3210
|
+
init_patterns();
|
|
3302
3211
|
n1Rule = {
|
|
3303
3212
|
id: "n1",
|
|
3304
3213
|
check(ctx) {
|
|
@@ -3309,15 +3218,15 @@ var init_n1 = __esm({
|
|
|
3309
3218
|
if (!req) continue;
|
|
3310
3219
|
const endpoint = getEndpointKey(req.method, req.path);
|
|
3311
3220
|
const shapeGroups = /* @__PURE__ */ new Map();
|
|
3312
|
-
for (const
|
|
3313
|
-
const shape = getQueryShape(
|
|
3221
|
+
for (const query of reqQueries) {
|
|
3222
|
+
const shape = getQueryShape(query);
|
|
3314
3223
|
let group = shapeGroups.get(shape);
|
|
3315
3224
|
if (!group) {
|
|
3316
|
-
group = { count: 0, distinctSql: /* @__PURE__ */ new Set(), first:
|
|
3225
|
+
group = { count: 0, distinctSql: /* @__PURE__ */ new Set(), first: query };
|
|
3317
3226
|
shapeGroups.set(shape, group);
|
|
3318
3227
|
}
|
|
3319
3228
|
group.count++;
|
|
3320
|
-
group.distinctSql.add(
|
|
3229
|
+
group.distinctSql.add(query.sql ?? shape);
|
|
3321
3230
|
}
|
|
3322
3231
|
for (const [, sg] of shapeGroups) {
|
|
3323
3232
|
if (sg.count <= N1_QUERY_THRESHOLD || sg.distinctSql.size <= 1) continue;
|
|
@@ -3338,75 +3247,6 @@ var init_n1 = __esm({
|
|
|
3338
3247
|
return insights;
|
|
3339
3248
|
}
|
|
3340
3249
|
};
|
|
3341
|
-
}
|
|
3342
|
-
});
|
|
3343
|
-
|
|
3344
|
-
// src/analysis/insights/rules/cross-endpoint.ts
|
|
3345
|
-
var crossEndpointRule;
|
|
3346
|
-
var init_cross_endpoint = __esm({
|
|
3347
|
-
"src/analysis/insights/rules/cross-endpoint.ts"() {
|
|
3348
|
-
"use strict";
|
|
3349
|
-
init_query_helpers();
|
|
3350
|
-
init_endpoint();
|
|
3351
|
-
init_constants();
|
|
3352
|
-
crossEndpointRule = {
|
|
3353
|
-
id: "cross-endpoint",
|
|
3354
|
-
check(ctx) {
|
|
3355
|
-
const insights = [];
|
|
3356
|
-
const queryMap = /* @__PURE__ */ new Map();
|
|
3357
|
-
const allEndpoints = /* @__PURE__ */ new Set();
|
|
3358
|
-
for (const [reqId, reqQueries] of ctx.queriesByReq) {
|
|
3359
|
-
const req = ctx.reqById.get(reqId);
|
|
3360
|
-
if (!req) continue;
|
|
3361
|
-
const endpoint = getEndpointKey(req.method, req.path);
|
|
3362
|
-
allEndpoints.add(endpoint);
|
|
3363
|
-
const seenInReq = /* @__PURE__ */ new Set();
|
|
3364
|
-
for (const q of reqQueries) {
|
|
3365
|
-
const shape = getQueryShape(q);
|
|
3366
|
-
let entry = queryMap.get(shape);
|
|
3367
|
-
if (!entry) {
|
|
3368
|
-
entry = { endpoints: /* @__PURE__ */ new Set(), count: 0, first: q };
|
|
3369
|
-
queryMap.set(shape, entry);
|
|
3370
|
-
}
|
|
3371
|
-
entry.count++;
|
|
3372
|
-
if (!seenInReq.has(shape)) {
|
|
3373
|
-
seenInReq.add(shape);
|
|
3374
|
-
entry.endpoints.add(endpoint);
|
|
3375
|
-
}
|
|
3376
|
-
}
|
|
3377
|
-
}
|
|
3378
|
-
if (allEndpoints.size >= CROSS_ENDPOINT_MIN_ENDPOINTS) {
|
|
3379
|
-
for (const [, cem] of queryMap) {
|
|
3380
|
-
if (cem.count < CROSS_ENDPOINT_MIN_OCCURRENCES) continue;
|
|
3381
|
-
if (cem.endpoints.size < CROSS_ENDPOINT_MIN_ENDPOINTS) continue;
|
|
3382
|
-
const p = Math.round(cem.endpoints.size / allEndpoints.size * 100);
|
|
3383
|
-
if (p < CROSS_ENDPOINT_PCT) continue;
|
|
3384
|
-
const info = getQueryInfo(cem.first);
|
|
3385
|
-
const label = info.op + (info.table ? ` ${info.table}` : "");
|
|
3386
|
-
insights.push({
|
|
3387
|
-
severity: "warning",
|
|
3388
|
-
type: "cross-endpoint",
|
|
3389
|
-
title: "Repeated Query Across Endpoints",
|
|
3390
|
-
desc: `${label} runs on ${cem.endpoints.size} of ${allEndpoints.size} endpoints (${p}%).`,
|
|
3391
|
-
hint: "This query runs on most of your endpoints. Load it once in middleware or cache the result to avoid redundant database calls.",
|
|
3392
|
-
nav: "queries"
|
|
3393
|
-
});
|
|
3394
|
-
}
|
|
3395
|
-
}
|
|
3396
|
-
return insights;
|
|
3397
|
-
}
|
|
3398
|
-
};
|
|
3399
|
-
}
|
|
3400
|
-
});
|
|
3401
|
-
|
|
3402
|
-
// src/analysis/insights/rules/redundant-query.ts
|
|
3403
|
-
var redundantQueryRule;
|
|
3404
|
-
var init_redundant_query = __esm({
|
|
3405
|
-
"src/analysis/insights/rules/redundant-query.ts"() {
|
|
3406
|
-
"use strict";
|
|
3407
|
-
init_query_helpers();
|
|
3408
|
-
init_endpoint();
|
|
3409
|
-
init_constants();
|
|
3410
3250
|
redundantQueryRule = {
|
|
3411
3251
|
id: "redundant-query",
|
|
3412
3252
|
check(ctx) {
|
|
@@ -3417,27 +3257,27 @@ var init_redundant_query = __esm({
|
|
|
3417
3257
|
if (!req) continue;
|
|
3418
3258
|
const endpoint = getEndpointKey(req.method, req.path);
|
|
3419
3259
|
const exact = /* @__PURE__ */ new Map();
|
|
3420
|
-
for (const
|
|
3421
|
-
if (!
|
|
3422
|
-
let entry = exact.get(
|
|
3260
|
+
for (const query of reqQueries) {
|
|
3261
|
+
if (!query.sql) continue;
|
|
3262
|
+
let entry = exact.get(query.sql);
|
|
3423
3263
|
if (!entry) {
|
|
3424
|
-
entry = { count: 0, first:
|
|
3425
|
-
exact.set(
|
|
3264
|
+
entry = { count: 0, first: query };
|
|
3265
|
+
exact.set(query.sql, entry);
|
|
3426
3266
|
}
|
|
3427
3267
|
entry.count++;
|
|
3428
3268
|
}
|
|
3429
|
-
for (const [,
|
|
3430
|
-
if (
|
|
3431
|
-
const info = getQueryInfo(
|
|
3269
|
+
for (const [, entry] of exact) {
|
|
3270
|
+
if (entry.count < REDUNDANT_QUERY_MIN_COUNT) continue;
|
|
3271
|
+
const info = getQueryInfo(entry.first);
|
|
3432
3272
|
const label = info.op + (info.table ? ` ${info.table}` : "");
|
|
3433
|
-
const
|
|
3434
|
-
if (seen.has(
|
|
3435
|
-
seen.add(
|
|
3273
|
+
const deduplicationKey = `${endpoint}:${label}`;
|
|
3274
|
+
if (seen.has(deduplicationKey)) continue;
|
|
3275
|
+
seen.add(deduplicationKey);
|
|
3436
3276
|
insights.push({
|
|
3437
3277
|
severity: "warning",
|
|
3438
3278
|
type: "redundant-query",
|
|
3439
3279
|
title: "Redundant Query",
|
|
3440
|
-
desc: `${label} runs ${
|
|
3280
|
+
desc: `${label} runs ${entry.count}x with identical params in ${endpoint}.`,
|
|
3441
3281
|
hint: "The exact same query with identical parameters runs multiple times in one request. Cache the first result or lift the query to a shared function.",
|
|
3442
3282
|
nav: "queries"
|
|
3443
3283
|
});
|
|
@@ -3446,228 +3286,16 @@ var init_redundant_query = __esm({
|
|
|
3446
3286
|
return insights;
|
|
3447
3287
|
}
|
|
3448
3288
|
};
|
|
3449
|
-
}
|
|
3450
|
-
});
|
|
3451
|
-
|
|
3452
|
-
// src/analysis/insights/rules/error.ts
|
|
3453
|
-
var errorRule;
|
|
3454
|
-
var init_error = __esm({
|
|
3455
|
-
"src/analysis/insights/rules/error.ts"() {
|
|
3456
|
-
"use strict";
|
|
3457
|
-
errorRule = {
|
|
3458
|
-
id: "error",
|
|
3459
|
-
check(ctx) {
|
|
3460
|
-
if (ctx.errors.length === 0) return [];
|
|
3461
|
-
const insights = [];
|
|
3462
|
-
const groups = /* @__PURE__ */ new Map();
|
|
3463
|
-
for (const e of ctx.errors) {
|
|
3464
|
-
const name = e.name || "Error";
|
|
3465
|
-
groups.set(name, (groups.get(name) ?? 0) + 1);
|
|
3466
|
-
}
|
|
3467
|
-
for (const [name, cnt] of groups) {
|
|
3468
|
-
insights.push({
|
|
3469
|
-
severity: "critical",
|
|
3470
|
-
type: "error",
|
|
3471
|
-
title: "Unhandled Error",
|
|
3472
|
-
desc: `${name} \u2014 occurred ${cnt} time${cnt !== 1 ? "s" : ""}`,
|
|
3473
|
-
hint: "Unhandled errors crash request handlers. Wrap async code in try/catch or add error-handling middleware.",
|
|
3474
|
-
nav: "errors"
|
|
3475
|
-
});
|
|
3476
|
-
}
|
|
3477
|
-
return insights;
|
|
3478
|
-
}
|
|
3479
|
-
};
|
|
3480
|
-
}
|
|
3481
|
-
});
|
|
3482
|
-
|
|
3483
|
-
// src/analysis/insights/rules/error-hotspot.ts
|
|
3484
|
-
var errorHotspotRule;
|
|
3485
|
-
var init_error_hotspot = __esm({
|
|
3486
|
-
"src/analysis/insights/rules/error-hotspot.ts"() {
|
|
3487
|
-
"use strict";
|
|
3488
|
-
init_constants();
|
|
3489
|
-
errorHotspotRule = {
|
|
3490
|
-
id: "error-hotspot",
|
|
3491
|
-
check(ctx) {
|
|
3492
|
-
const insights = [];
|
|
3493
|
-
for (const [ep, g] of ctx.endpointGroups) {
|
|
3494
|
-
if (g.total < MIN_REQUESTS_FOR_INSIGHT) continue;
|
|
3495
|
-
const errorRate = Math.round(g.errors / g.total * 100);
|
|
3496
|
-
if (errorRate >= ERROR_RATE_THRESHOLD_PCT) {
|
|
3497
|
-
insights.push({
|
|
3498
|
-
severity: "critical",
|
|
3499
|
-
type: "error-hotspot",
|
|
3500
|
-
title: "Error Hotspot",
|
|
3501
|
-
desc: `${ep} \u2014 ${errorRate}% error rate (${g.errors}/${g.total} requests)`,
|
|
3502
|
-
hint: "This endpoint frequently returns errors. Check the response bodies for error details and stack traces.",
|
|
3503
|
-
nav: "requests"
|
|
3504
|
-
});
|
|
3505
|
-
}
|
|
3506
|
-
}
|
|
3507
|
-
return insights;
|
|
3508
|
-
}
|
|
3509
|
-
};
|
|
3510
|
-
}
|
|
3511
|
-
});
|
|
3512
|
-
|
|
3513
|
-
// src/analysis/insights/rules/duplicate.ts
|
|
3514
|
-
var duplicateRule;
|
|
3515
|
-
var init_duplicate = __esm({
|
|
3516
|
-
"src/analysis/insights/rules/duplicate.ts"() {
|
|
3517
|
-
"use strict";
|
|
3518
|
-
init_constants();
|
|
3519
|
-
duplicateRule = {
|
|
3520
|
-
id: "duplicate",
|
|
3521
|
-
check(ctx) {
|
|
3522
|
-
const dupCounts = /* @__PURE__ */ new Map();
|
|
3523
|
-
const flowCount = /* @__PURE__ */ new Map();
|
|
3524
|
-
for (const flow of ctx.flows) {
|
|
3525
|
-
if (!flow.requests) continue;
|
|
3526
|
-
const seenInFlow = /* @__PURE__ */ new Set();
|
|
3527
|
-
for (const fr of flow.requests) {
|
|
3528
|
-
if (!fr.isDuplicate) continue;
|
|
3529
|
-
const dupKey = `${fr.method} ${fr.label ?? fr.path ?? fr.url}`;
|
|
3530
|
-
dupCounts.set(dupKey, (dupCounts.get(dupKey) ?? 0) + 1);
|
|
3531
|
-
if (!seenInFlow.has(dupKey)) {
|
|
3532
|
-
seenInFlow.add(dupKey);
|
|
3533
|
-
flowCount.set(dupKey, (flowCount.get(dupKey) ?? 0) + 1);
|
|
3534
|
-
}
|
|
3535
|
-
}
|
|
3536
|
-
}
|
|
3537
|
-
const dupEntries = [...dupCounts.entries()].map(([key, count]) => ({ key, count, flows: flowCount.get(key) ?? 0 })).sort((a, b) => b.count - a.count);
|
|
3538
|
-
const insights = [];
|
|
3539
|
-
for (let i = 0; i < Math.min(dupEntries.length, MAX_DUPLICATE_INSIGHTS); i++) {
|
|
3540
|
-
const d = dupEntries[i];
|
|
3541
|
-
insights.push({
|
|
3542
|
-
severity: "warning",
|
|
3543
|
-
type: "duplicate",
|
|
3544
|
-
title: "Duplicate API Call",
|
|
3545
|
-
desc: `${d.key} loaded ${d.count}x as duplicate across ${d.flows} action${d.flows !== 1 ? "s" : ""}`,
|
|
3546
|
-
hint: "Multiple components independently fetch the same endpoint. Lift the fetch to a parent component, use a data cache, or deduplicate with React Query / SWR.",
|
|
3547
|
-
nav: "actions"
|
|
3548
|
-
});
|
|
3549
|
-
}
|
|
3550
|
-
return insights;
|
|
3551
|
-
}
|
|
3552
|
-
};
|
|
3553
|
-
}
|
|
3554
|
-
});
|
|
3555
|
-
|
|
3556
|
-
// src/utils/format.ts
|
|
3557
|
-
function formatDuration(ms) {
|
|
3558
|
-
if (ms < 1e3) return `${ms}ms`;
|
|
3559
|
-
return `${(ms / 1e3).toFixed(1)}s`;
|
|
3560
|
-
}
|
|
3561
|
-
function formatSize(bytes) {
|
|
3562
|
-
if (bytes < 1024) return `${bytes}B`;
|
|
3563
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
3564
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
3565
|
-
}
|
|
3566
|
-
function pct(part, total) {
|
|
3567
|
-
return total > 0 ? Math.round(part / total * 100) : 0;
|
|
3568
|
-
}
|
|
3569
|
-
var init_format = __esm({
|
|
3570
|
-
"src/utils/format.ts"() {
|
|
3571
|
-
"use strict";
|
|
3572
|
-
}
|
|
3573
|
-
});
|
|
3574
|
-
|
|
3575
|
-
// src/analysis/insights/rules/slow.ts
|
|
3576
|
-
var slowRule;
|
|
3577
|
-
var init_slow = __esm({
|
|
3578
|
-
"src/analysis/insights/rules/slow.ts"() {
|
|
3579
|
-
"use strict";
|
|
3580
|
-
init_format();
|
|
3581
|
-
init_constants();
|
|
3582
|
-
slowRule = {
|
|
3583
|
-
id: "slow",
|
|
3584
|
-
check(ctx) {
|
|
3585
|
-
const insights = [];
|
|
3586
|
-
for (const [ep, g] of ctx.endpointGroups) {
|
|
3587
|
-
if (g.total < MIN_REQUESTS_FOR_INSIGHT) continue;
|
|
3588
|
-
const avgMs = Math.round(g.totalDuration / g.total);
|
|
3589
|
-
if (avgMs < SLOW_ENDPOINT_THRESHOLD_MS) continue;
|
|
3590
|
-
const avgQueryMs = Math.round(g.totalQueryTimeMs / g.total);
|
|
3591
|
-
const avgFetchMs = Math.round(g.totalFetchTimeMs / g.total);
|
|
3592
|
-
const avgAppMs = Math.max(0, avgMs - avgQueryMs - avgFetchMs);
|
|
3593
|
-
const parts = [];
|
|
3594
|
-
if (avgQueryMs > 0) parts.push(`DB ${formatDuration(avgQueryMs)} ${pct(avgQueryMs, avgMs)}%`);
|
|
3595
|
-
if (avgFetchMs > 0) parts.push(`Fetch ${formatDuration(avgFetchMs)} ${pct(avgFetchMs, avgMs)}%`);
|
|
3596
|
-
if (avgAppMs > 0) parts.push(`App ${formatDuration(avgAppMs)} ${pct(avgAppMs, avgMs)}%`);
|
|
3597
|
-
const breakdown = parts.length > 0 ? ` [${parts.join(" \xB7 ")}]` : "";
|
|
3598
|
-
let detail;
|
|
3599
|
-
let slowestMs = 0;
|
|
3600
|
-
for (const [, sd] of g.queryShapeDurations) {
|
|
3601
|
-
const avgShapeMs = sd.totalMs / sd.count;
|
|
3602
|
-
if (avgShapeMs > slowestMs) {
|
|
3603
|
-
slowestMs = avgShapeMs;
|
|
3604
|
-
detail = `Slowest query: ${sd.label} \u2014 avg ${formatDuration(Math.round(avgShapeMs))} (${sd.count}x)`;
|
|
3605
|
-
}
|
|
3606
|
-
}
|
|
3607
|
-
insights.push({
|
|
3608
|
-
severity: "warning",
|
|
3609
|
-
type: "slow",
|
|
3610
|
-
title: "Slow Endpoint",
|
|
3611
|
-
desc: `${ep} \u2014 avg ${formatDuration(avgMs)}${breakdown}`,
|
|
3612
|
-
hint: avgQueryMs >= avgFetchMs && avgQueryMs >= avgAppMs ? "Most time is in database queries. Check the Queries tab for slow or redundant queries." : avgFetchMs >= avgQueryMs && avgFetchMs >= avgAppMs ? "Most time is in outbound HTTP calls. Check if upstream services are slow or if calls can be parallelized." : "Most time is in application code. Profile the handler for CPU-heavy operations or blocking calls.",
|
|
3613
|
-
detail,
|
|
3614
|
-
nav: "requests"
|
|
3615
|
-
});
|
|
3616
|
-
}
|
|
3617
|
-
return insights;
|
|
3618
|
-
}
|
|
3619
|
-
};
|
|
3620
|
-
}
|
|
3621
|
-
});
|
|
3622
|
-
|
|
3623
|
-
// src/analysis/insights/rules/query-heavy.ts
|
|
3624
|
-
var queryHeavyRule;
|
|
3625
|
-
var init_query_heavy = __esm({
|
|
3626
|
-
"src/analysis/insights/rules/query-heavy.ts"() {
|
|
3627
|
-
"use strict";
|
|
3628
|
-
init_constants();
|
|
3629
|
-
queryHeavyRule = {
|
|
3630
|
-
id: "query-heavy",
|
|
3631
|
-
check(ctx) {
|
|
3632
|
-
const insights = [];
|
|
3633
|
-
for (const [ep, g] of ctx.endpointGroups) {
|
|
3634
|
-
if (g.total < MIN_REQUESTS_FOR_INSIGHT) continue;
|
|
3635
|
-
const avgQueries = Math.round(g.queryCount / g.total);
|
|
3636
|
-
if (avgQueries > HIGH_QUERY_COUNT_PER_REQ) {
|
|
3637
|
-
insights.push({
|
|
3638
|
-
severity: "warning",
|
|
3639
|
-
type: "query-heavy",
|
|
3640
|
-
title: "Query-Heavy Endpoint",
|
|
3641
|
-
desc: `${ep} \u2014 avg ${avgQueries} queries/request`,
|
|
3642
|
-
hint: "Too many queries per request increases latency. Combine queries with JOINs, use batch operations, or reduce the number of data fetches.",
|
|
3643
|
-
nav: "queries"
|
|
3644
|
-
});
|
|
3645
|
-
}
|
|
3646
|
-
}
|
|
3647
|
-
return insights;
|
|
3648
|
-
}
|
|
3649
|
-
};
|
|
3650
|
-
}
|
|
3651
|
-
});
|
|
3652
|
-
|
|
3653
|
-
// src/analysis/insights/rules/select-star.ts
|
|
3654
|
-
var selectStarRule;
|
|
3655
|
-
var init_select_star = __esm({
|
|
3656
|
-
"src/analysis/insights/rules/select-star.ts"() {
|
|
3657
|
-
"use strict";
|
|
3658
|
-
init_query_helpers();
|
|
3659
|
-
init_constants();
|
|
3660
|
-
init_patterns();
|
|
3661
3289
|
selectStarRule = {
|
|
3662
3290
|
id: "select-star",
|
|
3663
3291
|
check(ctx) {
|
|
3664
3292
|
const seen = /* @__PURE__ */ new Map();
|
|
3665
3293
|
for (const [, reqQueries] of ctx.queriesByReq) {
|
|
3666
|
-
for (const
|
|
3667
|
-
if (!
|
|
3668
|
-
const isSelectStar = SELECT_STAR_RE.test(
|
|
3294
|
+
for (const query of reqQueries) {
|
|
3295
|
+
if (!query.sql) continue;
|
|
3296
|
+
const isSelectStar = SELECT_STAR_RE.test(query.sql.trim()) || SELECT_DOT_STAR_RE.test(query.sql);
|
|
3669
3297
|
if (!isSelectStar) continue;
|
|
3670
|
-
const info = getQueryInfo(
|
|
3298
|
+
const info = getQueryInfo(query);
|
|
3671
3299
|
const key = info.table || "unknown";
|
|
3672
3300
|
seen.set(key, (seen.get(key) ?? 0) + 1);
|
|
3673
3301
|
}
|
|
@@ -3687,24 +3315,14 @@ var init_select_star = __esm({
|
|
|
3687
3315
|
return insights;
|
|
3688
3316
|
}
|
|
3689
3317
|
};
|
|
3690
|
-
}
|
|
3691
|
-
});
|
|
3692
|
-
|
|
3693
|
-
// src/analysis/insights/rules/high-rows.ts
|
|
3694
|
-
var highRowsRule;
|
|
3695
|
-
var init_high_rows = __esm({
|
|
3696
|
-
"src/analysis/insights/rules/high-rows.ts"() {
|
|
3697
|
-
"use strict";
|
|
3698
|
-
init_query_helpers();
|
|
3699
|
-
init_constants();
|
|
3700
3318
|
highRowsRule = {
|
|
3701
3319
|
id: "high-rows",
|
|
3702
3320
|
check(ctx) {
|
|
3703
3321
|
const seen = /* @__PURE__ */ new Map();
|
|
3704
3322
|
for (const [, reqQueries] of ctx.queriesByReq) {
|
|
3705
|
-
for (const
|
|
3706
|
-
if (!
|
|
3707
|
-
const info = getQueryInfo(
|
|
3323
|
+
for (const query of reqQueries) {
|
|
3324
|
+
if (!query.rowCount || query.rowCount <= HIGH_ROW_COUNT) continue;
|
|
3325
|
+
const info = getQueryInfo(query);
|
|
3708
3326
|
const key = `${info.op} ${info.table || "unknown"}`;
|
|
3709
3327
|
let entry = seen.get(key);
|
|
3710
3328
|
if (!entry) {
|
|
@@ -3712,7 +3330,7 @@ var init_high_rows = __esm({
|
|
|
3712
3330
|
seen.set(key, entry);
|
|
3713
3331
|
}
|
|
3714
3332
|
entry.count++;
|
|
3715
|
-
if (
|
|
3333
|
+
if (query.rowCount > entry.max) entry.max = query.rowCount;
|
|
3716
3334
|
}
|
|
3717
3335
|
}
|
|
3718
3336
|
const insights = [];
|
|
@@ -3730,32 +3348,76 @@ var init_high_rows = __esm({
|
|
|
3730
3348
|
return insights;
|
|
3731
3349
|
}
|
|
3732
3350
|
};
|
|
3351
|
+
queryHeavyRule = {
|
|
3352
|
+
id: "query-heavy",
|
|
3353
|
+
check(ctx) {
|
|
3354
|
+
const insights = [];
|
|
3355
|
+
for (const [endpointKey, group] of ctx.endpointGroups) {
|
|
3356
|
+
if (group.total < MIN_REQUESTS_FOR_INSIGHT) continue;
|
|
3357
|
+
const avgQueries = Math.round(group.queryCount / group.total);
|
|
3358
|
+
if (avgQueries > HIGH_QUERY_COUNT_PER_REQ) {
|
|
3359
|
+
insights.push({
|
|
3360
|
+
severity: "warning",
|
|
3361
|
+
type: "query-heavy",
|
|
3362
|
+
title: "Query-Heavy Endpoint",
|
|
3363
|
+
desc: `${endpointKey} \u2014 avg ${avgQueries} queries/request`,
|
|
3364
|
+
hint: "Too many queries per request increases latency. Combine queries with JOINs, use batch operations, or reduce the number of data fetches.",
|
|
3365
|
+
nav: "queries"
|
|
3366
|
+
});
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
return insights;
|
|
3370
|
+
}
|
|
3371
|
+
};
|
|
3372
|
+
}
|
|
3373
|
+
});
|
|
3374
|
+
|
|
3375
|
+
// src/utils/format.ts
|
|
3376
|
+
function formatDuration(ms) {
|
|
3377
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
3378
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
3379
|
+
}
|
|
3380
|
+
function formatSize(bytes) {
|
|
3381
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
3382
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
3383
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
3384
|
+
}
|
|
3385
|
+
function pct(part, total) {
|
|
3386
|
+
return total > 0 ? Math.round(part / total * 100) : 0;
|
|
3387
|
+
}
|
|
3388
|
+
var init_format = __esm({
|
|
3389
|
+
"src/utils/format.ts"() {
|
|
3390
|
+
"use strict";
|
|
3733
3391
|
}
|
|
3734
3392
|
});
|
|
3735
3393
|
|
|
3736
|
-
// src/analysis/insights/rules/response-
|
|
3737
|
-
var responseOverfetchRule;
|
|
3738
|
-
var
|
|
3739
|
-
"src/analysis/insights/rules/response-
|
|
3394
|
+
// src/analysis/insights/rules/response-rules.ts
|
|
3395
|
+
var responseOverfetchRule, largeResponseRule;
|
|
3396
|
+
var init_response_rules = __esm({
|
|
3397
|
+
"src/analysis/insights/rules/response-rules.ts"() {
|
|
3740
3398
|
"use strict";
|
|
3741
3399
|
init_endpoint();
|
|
3742
3400
|
init_response();
|
|
3743
3401
|
init_http_status();
|
|
3402
|
+
init_format();
|
|
3744
3403
|
init_patterns();
|
|
3404
|
+
init_log();
|
|
3405
|
+
init_type_guards();
|
|
3745
3406
|
init_constants();
|
|
3746
3407
|
responseOverfetchRule = {
|
|
3747
3408
|
id: "response-overfetch",
|
|
3748
3409
|
check(ctx) {
|
|
3749
3410
|
const insights = [];
|
|
3750
3411
|
const seen = /* @__PURE__ */ new Set();
|
|
3751
|
-
for (const
|
|
3752
|
-
if (isErrorStatus(
|
|
3753
|
-
const
|
|
3754
|
-
if (seen.has(
|
|
3412
|
+
for (const request of ctx.nonStatic) {
|
|
3413
|
+
if (isErrorStatus(request.statusCode) || !request.responseBody) continue;
|
|
3414
|
+
const endpointKey = getEndpointKey(request.method, request.path);
|
|
3415
|
+
if (seen.has(endpointKey)) continue;
|
|
3755
3416
|
let parsed;
|
|
3756
3417
|
try {
|
|
3757
|
-
parsed = JSON.parse(
|
|
3758
|
-
} catch {
|
|
3418
|
+
parsed = JSON.parse(request.responseBody);
|
|
3419
|
+
} catch (e) {
|
|
3420
|
+
brakitDebug(`json parse: ${getErrorMessage(e)}`);
|
|
3759
3421
|
continue;
|
|
3760
3422
|
}
|
|
3761
3423
|
const target = unwrapResponse(parsed);
|
|
@@ -3778,12 +3440,12 @@ var init_response_overfetch = __esm({
|
|
|
3778
3440
|
reasons.push(`${fields.length} fields returned`);
|
|
3779
3441
|
}
|
|
3780
3442
|
if (reasons.length > 0) {
|
|
3781
|
-
seen.add(
|
|
3443
|
+
seen.add(endpointKey);
|
|
3782
3444
|
insights.push({
|
|
3783
3445
|
severity: "info",
|
|
3784
3446
|
type: "response-overfetch",
|
|
3785
3447
|
title: "Response Overfetch",
|
|
3786
|
-
desc: `${
|
|
3448
|
+
desc: `${endpointKey} \u2014 ${reasons.join(", ")}`,
|
|
3787
3449
|
hint: "This response returns more data than the client likely needs. Use a DTO or select only required fields to reduce payload size and avoid leaking internal structure.",
|
|
3788
3450
|
nav: "requests"
|
|
3789
3451
|
});
|
|
@@ -3792,29 +3454,19 @@ var init_response_overfetch = __esm({
|
|
|
3792
3454
|
return insights;
|
|
3793
3455
|
}
|
|
3794
3456
|
};
|
|
3795
|
-
}
|
|
3796
|
-
});
|
|
3797
|
-
|
|
3798
|
-
// src/analysis/insights/rules/large-response.ts
|
|
3799
|
-
var largeResponseRule;
|
|
3800
|
-
var init_large_response = __esm({
|
|
3801
|
-
"src/analysis/insights/rules/large-response.ts"() {
|
|
3802
|
-
"use strict";
|
|
3803
|
-
init_format();
|
|
3804
|
-
init_constants();
|
|
3805
3457
|
largeResponseRule = {
|
|
3806
3458
|
id: "large-response",
|
|
3807
3459
|
check(ctx) {
|
|
3808
3460
|
const insights = [];
|
|
3809
|
-
for (const [
|
|
3810
|
-
if (
|
|
3811
|
-
const avgSize = Math.round(
|
|
3461
|
+
for (const [endpointKey, group] of ctx.endpointGroups) {
|
|
3462
|
+
if (group.total < OVERFETCH_MIN_REQUESTS) continue;
|
|
3463
|
+
const avgSize = Math.round(group.totalSize / group.total);
|
|
3812
3464
|
if (avgSize > LARGE_RESPONSE_BYTES) {
|
|
3813
3465
|
insights.push({
|
|
3814
3466
|
severity: "info",
|
|
3815
3467
|
type: "large-response",
|
|
3816
3468
|
title: "Large Response",
|
|
3817
|
-
desc: `${
|
|
3469
|
+
desc: `${endpointKey} \u2014 avg ${formatSize(avgSize)} response`,
|
|
3818
3470
|
hint: "Large API responses increase network transfer time. Implement pagination, field filtering, or response compression.",
|
|
3819
3471
|
nav: "requests"
|
|
3820
3472
|
});
|
|
@@ -3826,13 +3478,57 @@ var init_large_response = __esm({
|
|
|
3826
3478
|
}
|
|
3827
3479
|
});
|
|
3828
3480
|
|
|
3829
|
-
// src/analysis/insights/rules/
|
|
3830
|
-
var regressionRule;
|
|
3831
|
-
var
|
|
3832
|
-
"src/analysis/insights/rules/
|
|
3481
|
+
// src/analysis/insights/rules/reliability-rules.ts
|
|
3482
|
+
var errorRule, errorHotspotRule, regressionRule, slowRule;
|
|
3483
|
+
var init_reliability_rules = __esm({
|
|
3484
|
+
"src/analysis/insights/rules/reliability-rules.ts"() {
|
|
3833
3485
|
"use strict";
|
|
3834
3486
|
init_format();
|
|
3835
3487
|
init_constants();
|
|
3488
|
+
errorRule = {
|
|
3489
|
+
id: "error",
|
|
3490
|
+
check(ctx) {
|
|
3491
|
+
if (ctx.errors.length === 0) return [];
|
|
3492
|
+
const insights = [];
|
|
3493
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3494
|
+
for (const error of ctx.errors) {
|
|
3495
|
+
const name = error.name || "Error";
|
|
3496
|
+
groups.set(name, (groups.get(name) ?? 0) + 1);
|
|
3497
|
+
}
|
|
3498
|
+
for (const [name, cnt] of groups) {
|
|
3499
|
+
insights.push({
|
|
3500
|
+
severity: "critical",
|
|
3501
|
+
type: "error",
|
|
3502
|
+
title: "Unhandled Error",
|
|
3503
|
+
desc: `${name} \u2014 occurred ${cnt} time${cnt !== 1 ? "s" : ""}`,
|
|
3504
|
+
hint: "Unhandled errors crash request handlers. Wrap async code in try/catch or add error-handling middleware.",
|
|
3505
|
+
nav: "errors"
|
|
3506
|
+
});
|
|
3507
|
+
}
|
|
3508
|
+
return insights;
|
|
3509
|
+
}
|
|
3510
|
+
};
|
|
3511
|
+
errorHotspotRule = {
|
|
3512
|
+
id: "error-hotspot",
|
|
3513
|
+
check(ctx) {
|
|
3514
|
+
const insights = [];
|
|
3515
|
+
for (const [endpointKey, group] of ctx.endpointGroups) {
|
|
3516
|
+
if (group.total < MIN_REQUESTS_FOR_INSIGHT) continue;
|
|
3517
|
+
const errorRate = Math.round(group.errors / group.total * 100);
|
|
3518
|
+
if (errorRate >= ERROR_RATE_THRESHOLD_PCT) {
|
|
3519
|
+
insights.push({
|
|
3520
|
+
severity: "critical",
|
|
3521
|
+
type: "error-hotspot",
|
|
3522
|
+
title: "Error Hotspot",
|
|
3523
|
+
desc: `${endpointKey} \u2014 ${errorRate}% error rate (${group.errors}/${group.total} requests)`,
|
|
3524
|
+
hint: "This endpoint frequently returns errors. Check the response bodies for error details and stack traces.",
|
|
3525
|
+
nav: "requests"
|
|
3526
|
+
});
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
return insights;
|
|
3530
|
+
}
|
|
3531
|
+
};
|
|
3836
3532
|
regressionRule = {
|
|
3837
3533
|
id: "regression",
|
|
3838
3534
|
check(ctx) {
|
|
@@ -3869,6 +3565,136 @@ var init_regression = __esm({
|
|
|
3869
3565
|
return insights;
|
|
3870
3566
|
}
|
|
3871
3567
|
};
|
|
3568
|
+
slowRule = {
|
|
3569
|
+
id: "slow",
|
|
3570
|
+
check(ctx) {
|
|
3571
|
+
const insights = [];
|
|
3572
|
+
for (const [endpointKey, group] of ctx.endpointGroups) {
|
|
3573
|
+
if (group.total < MIN_REQUESTS_FOR_INSIGHT) continue;
|
|
3574
|
+
const avgMs = Math.round(group.totalDuration / group.total);
|
|
3575
|
+
if (avgMs < SLOW_ENDPOINT_THRESHOLD_MS) continue;
|
|
3576
|
+
const avgQueryMs = Math.round(group.totalQueryTimeMs / group.total);
|
|
3577
|
+
const avgFetchMs = Math.round(group.totalFetchTimeMs / group.total);
|
|
3578
|
+
const avgAppMs = Math.max(0, avgMs - avgQueryMs - avgFetchMs);
|
|
3579
|
+
const parts = [];
|
|
3580
|
+
if (avgQueryMs > 0) parts.push(`DB ${formatDuration(avgQueryMs)} ${pct(avgQueryMs, avgMs)}%`);
|
|
3581
|
+
if (avgFetchMs > 0) parts.push(`Fetch ${formatDuration(avgFetchMs)} ${pct(avgFetchMs, avgMs)}%`);
|
|
3582
|
+
if (avgAppMs > 0) parts.push(`App ${formatDuration(avgAppMs)} ${pct(avgAppMs, avgMs)}%`);
|
|
3583
|
+
const breakdown = parts.length > 0 ? ` [${parts.join(" \xB7 ")}]` : "";
|
|
3584
|
+
let detail;
|
|
3585
|
+
let slowestMs = 0;
|
|
3586
|
+
for (const [, shapeDuration] of group.queryShapeDurations) {
|
|
3587
|
+
const avgShapeMs = shapeDuration.totalMs / shapeDuration.count;
|
|
3588
|
+
if (avgShapeMs > slowestMs) {
|
|
3589
|
+
slowestMs = avgShapeMs;
|
|
3590
|
+
detail = `Slowest query: ${shapeDuration.label} \u2014 avg ${formatDuration(Math.round(avgShapeMs))} (${shapeDuration.count}x)`;
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
insights.push({
|
|
3594
|
+
severity: "warning",
|
|
3595
|
+
type: "slow",
|
|
3596
|
+
title: "Slow Endpoint",
|
|
3597
|
+
desc: `${endpointKey} \u2014 avg ${formatDuration(avgMs)}${breakdown}`,
|
|
3598
|
+
hint: avgQueryMs >= avgFetchMs && avgQueryMs >= avgAppMs ? "Most time is in database queries. Check the Queries tab for slow or redundant queries." : avgFetchMs >= avgQueryMs && avgFetchMs >= avgAppMs ? "Most time is in outbound HTTP calls. Check if upstream services are slow or if calls can be parallelized." : "Most time is in application code. Profile the handler for CPU-heavy operations or blocking calls.",
|
|
3599
|
+
detail,
|
|
3600
|
+
nav: "requests"
|
|
3601
|
+
});
|
|
3602
|
+
}
|
|
3603
|
+
return insights;
|
|
3604
|
+
}
|
|
3605
|
+
};
|
|
3606
|
+
}
|
|
3607
|
+
});
|
|
3608
|
+
|
|
3609
|
+
// src/analysis/insights/rules/pattern-rules.ts
|
|
3610
|
+
var duplicateRule, crossEndpointRule;
|
|
3611
|
+
var init_pattern_rules = __esm({
|
|
3612
|
+
"src/analysis/insights/rules/pattern-rules.ts"() {
|
|
3613
|
+
"use strict";
|
|
3614
|
+
init_query_helpers();
|
|
3615
|
+
init_endpoint();
|
|
3616
|
+
init_constants();
|
|
3617
|
+
duplicateRule = {
|
|
3618
|
+
id: "duplicate",
|
|
3619
|
+
check(ctx) {
|
|
3620
|
+
const dupCounts = /* @__PURE__ */ new Map();
|
|
3621
|
+
const flowCount = /* @__PURE__ */ new Map();
|
|
3622
|
+
for (const flow of ctx.flows) {
|
|
3623
|
+
if (!flow.requests) continue;
|
|
3624
|
+
const seenInFlow = /* @__PURE__ */ new Set();
|
|
3625
|
+
for (const request of flow.requests) {
|
|
3626
|
+
if (!request.isDuplicate) continue;
|
|
3627
|
+
const deduplicationKey = `${request.method} ${request.label ?? request.path ?? request.url}`;
|
|
3628
|
+
dupCounts.set(deduplicationKey, (dupCounts.get(deduplicationKey) ?? 0) + 1);
|
|
3629
|
+
if (!seenInFlow.has(deduplicationKey)) {
|
|
3630
|
+
seenInFlow.add(deduplicationKey);
|
|
3631
|
+
flowCount.set(deduplicationKey, (flowCount.get(deduplicationKey) ?? 0) + 1);
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
const dupEntries = [...dupCounts.entries()].map(([key, count]) => ({ key, count, flows: flowCount.get(key) ?? 0 })).sort((a, b) => b.count - a.count);
|
|
3636
|
+
const insights = [];
|
|
3637
|
+
for (let i = 0; i < Math.min(dupEntries.length, MAX_DUPLICATE_INSIGHTS); i++) {
|
|
3638
|
+
const duplicate = dupEntries[i];
|
|
3639
|
+
insights.push({
|
|
3640
|
+
severity: "warning",
|
|
3641
|
+
type: "duplicate",
|
|
3642
|
+
title: "Duplicate API Call",
|
|
3643
|
+
desc: `${duplicate.key} loaded ${duplicate.count}x as duplicate across ${duplicate.flows} action${duplicate.flows !== 1 ? "s" : ""}`,
|
|
3644
|
+
hint: "Multiple components independently fetch the same endpoint. Lift the fetch to a parent component, use a data cache, or deduplicate with React Query / SWR.",
|
|
3645
|
+
nav: "actions"
|
|
3646
|
+
});
|
|
3647
|
+
}
|
|
3648
|
+
return insights;
|
|
3649
|
+
}
|
|
3650
|
+
};
|
|
3651
|
+
crossEndpointRule = {
|
|
3652
|
+
id: "cross-endpoint",
|
|
3653
|
+
check(ctx) {
|
|
3654
|
+
const insights = [];
|
|
3655
|
+
const queryMap = /* @__PURE__ */ new Map();
|
|
3656
|
+
const allEndpoints = /* @__PURE__ */ new Set();
|
|
3657
|
+
for (const [reqId, reqQueries] of ctx.queriesByReq) {
|
|
3658
|
+
const req = ctx.reqById.get(reqId);
|
|
3659
|
+
if (!req) continue;
|
|
3660
|
+
const endpoint = getEndpointKey(req.method, req.path);
|
|
3661
|
+
allEndpoints.add(endpoint);
|
|
3662
|
+
const seenInReq = /* @__PURE__ */ new Set();
|
|
3663
|
+
for (const query of reqQueries) {
|
|
3664
|
+
const shape = getQueryShape(query);
|
|
3665
|
+
let entry = queryMap.get(shape);
|
|
3666
|
+
if (!entry) {
|
|
3667
|
+
entry = { endpoints: /* @__PURE__ */ new Set(), count: 0, first: query };
|
|
3668
|
+
queryMap.set(shape, entry);
|
|
3669
|
+
}
|
|
3670
|
+
entry.count++;
|
|
3671
|
+
if (!seenInReq.has(shape)) {
|
|
3672
|
+
seenInReq.add(shape);
|
|
3673
|
+
entry.endpoints.add(endpoint);
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
}
|
|
3677
|
+
if (allEndpoints.size >= CROSS_ENDPOINT_MIN_ENDPOINTS) {
|
|
3678
|
+
for (const [, queryMetric] of queryMap) {
|
|
3679
|
+
if (queryMetric.count < CROSS_ENDPOINT_MIN_OCCURRENCES) continue;
|
|
3680
|
+
if (queryMetric.endpoints.size < CROSS_ENDPOINT_MIN_ENDPOINTS) continue;
|
|
3681
|
+
const coveragePct = Math.round(queryMetric.endpoints.size / allEndpoints.size * 100);
|
|
3682
|
+
if (coveragePct < CROSS_ENDPOINT_PCT) continue;
|
|
3683
|
+
const info = getQueryInfo(queryMetric.first);
|
|
3684
|
+
const label = info.op + (info.table ? ` ${info.table}` : "");
|
|
3685
|
+
insights.push({
|
|
3686
|
+
severity: "warning",
|
|
3687
|
+
type: "cross-endpoint",
|
|
3688
|
+
title: "Repeated Query Across Endpoints",
|
|
3689
|
+
desc: `${label} runs on ${queryMetric.endpoints.size} of ${allEndpoints.size} endpoints (${coveragePct}%).`,
|
|
3690
|
+
hint: "This query runs on most of your endpoints. Load it once in middleware or cache the result to avoid redundant database calls.",
|
|
3691
|
+
nav: "queries"
|
|
3692
|
+
});
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3695
|
+
return insights;
|
|
3696
|
+
}
|
|
3697
|
+
};
|
|
3872
3698
|
}
|
|
3873
3699
|
});
|
|
3874
3700
|
|
|
@@ -3898,19 +3724,10 @@ var init_security = __esm({
|
|
|
3898
3724
|
var init_rules2 = __esm({
|
|
3899
3725
|
"src/analysis/insights/rules/index.ts"() {
|
|
3900
3726
|
"use strict";
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
init_error_hotspot();
|
|
3906
|
-
init_duplicate();
|
|
3907
|
-
init_slow();
|
|
3908
|
-
init_query_heavy();
|
|
3909
|
-
init_select_star();
|
|
3910
|
-
init_high_rows();
|
|
3911
|
-
init_response_overfetch();
|
|
3912
|
-
init_large_response();
|
|
3913
|
-
init_regression();
|
|
3727
|
+
init_query_rules();
|
|
3728
|
+
init_response_rules();
|
|
3729
|
+
init_reliability_rules();
|
|
3730
|
+
init_pattern_rules();
|
|
3914
3731
|
init_security();
|
|
3915
3732
|
}
|
|
3916
3733
|
});
|
|
@@ -3997,7 +3814,7 @@ var AnalysisEngine;
|
|
|
3997
3814
|
var init_engine = __esm({
|
|
3998
3815
|
"src/analysis/engine.ts"() {
|
|
3999
3816
|
"use strict";
|
|
4000
|
-
|
|
3817
|
+
init_config();
|
|
4001
3818
|
init_disposable();
|
|
4002
3819
|
init_group();
|
|
4003
3820
|
init_rules();
|
|
@@ -4006,8 +3823,8 @@ var init_engine = __esm({
|
|
|
4006
3823
|
init_issue_id();
|
|
4007
3824
|
init_prepare();
|
|
4008
3825
|
AnalysisEngine = class {
|
|
4009
|
-
constructor(
|
|
4010
|
-
this.
|
|
3826
|
+
constructor(services, debounceMs = ANALYSIS_DEBOUNCE_MS) {
|
|
3827
|
+
this.services = services;
|
|
4011
3828
|
this.debounceMs = debounceMs;
|
|
4012
3829
|
this.cachedInsights = [];
|
|
4013
3830
|
this.cachedFindings = [];
|
|
@@ -4016,7 +3833,7 @@ var init_engine = __esm({
|
|
|
4016
3833
|
this.scanner = createDefaultScanner();
|
|
4017
3834
|
}
|
|
4018
3835
|
start() {
|
|
4019
|
-
const bus = this.
|
|
3836
|
+
const bus = this.services.bus;
|
|
4020
3837
|
this.subs.add(bus.on("request:completed", () => this.scheduleRecompute()));
|
|
4021
3838
|
this.subs.add(bus.on("telemetry:query", () => this.scheduleRecompute()));
|
|
4022
3839
|
this.subs.add(bus.on("telemetry:error", () => this.scheduleRecompute()));
|
|
@@ -4043,12 +3860,12 @@ var init_engine = __esm({
|
|
|
4043
3860
|
}, this.debounceMs);
|
|
4044
3861
|
}
|
|
4045
3862
|
recompute() {
|
|
4046
|
-
const allRequests = this.
|
|
4047
|
-
const queries = this.
|
|
4048
|
-
const errors = this.
|
|
4049
|
-
const logs = this.
|
|
4050
|
-
const fetches = this.
|
|
4051
|
-
const requests =
|
|
3863
|
+
const allRequests = this.services.requestStore.getAll();
|
|
3864
|
+
const queries = this.services.queryStore.getAll();
|
|
3865
|
+
const errors = this.services.errorStore.getAll();
|
|
3866
|
+
const logs = this.services.logStore.getAll();
|
|
3867
|
+
const fetches = this.services.fetchStore.getAll();
|
|
3868
|
+
const requests = keepRecentPerEndpoint(allRequests);
|
|
4052
3869
|
const flows = groupRequestsIntoFlows(requests);
|
|
4053
3870
|
this.cachedFindings = this.scanner.scan({ requests, logs });
|
|
4054
3871
|
this.cachedInsights = computeInsights({
|
|
@@ -4057,33 +3874,29 @@ var init_engine = __esm({
|
|
|
4057
3874
|
errors,
|
|
4058
3875
|
flows,
|
|
4059
3876
|
fetches,
|
|
4060
|
-
previousMetrics: this.
|
|
3877
|
+
previousMetrics: this.services.metricsStore.getAll(),
|
|
4061
3878
|
securityFindings: this.cachedFindings
|
|
4062
3879
|
});
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
issueStore.upsert(insightToIssue(insight), "passive");
|
|
4070
|
-
}
|
|
4071
|
-
const currentIssueIds = /* @__PURE__ */ new Set();
|
|
4072
|
-
for (const finding of this.cachedFindings) {
|
|
4073
|
-
currentIssueIds.add(computeIssueId(securityFindingToIssue(finding)));
|
|
4074
|
-
}
|
|
4075
|
-
for (const insight of this.cachedInsights) {
|
|
4076
|
-
currentIssueIds.add(computeIssueId(insightToIssue(insight)));
|
|
4077
|
-
}
|
|
4078
|
-
const activeEndpoints = extractActiveEndpoints(allRequests);
|
|
4079
|
-
issueStore.reconcile(currentIssueIds, activeEndpoints);
|
|
4080
|
-
const update = {
|
|
4081
|
-
insights: this.cachedInsights,
|
|
4082
|
-
findings: this.cachedFindings,
|
|
4083
|
-
issues: issueStore.getAll()
|
|
4084
|
-
};
|
|
4085
|
-
this.registry.get("event-bus").emit("analysis:updated", update);
|
|
3880
|
+
const issueStore = this.services.issueStore;
|
|
3881
|
+
const currentIssueIds = /* @__PURE__ */ new Set();
|
|
3882
|
+
for (const finding of this.cachedFindings) {
|
|
3883
|
+
const issue = securityFindingToIssue(finding);
|
|
3884
|
+
issueStore.upsert(issue, "passive");
|
|
3885
|
+
currentIssueIds.add(computeIssueId(issue));
|
|
4086
3886
|
}
|
|
3887
|
+
for (const insight of this.cachedInsights) {
|
|
3888
|
+
const issue = insightToIssue(insight);
|
|
3889
|
+
issueStore.upsert(issue, "passive");
|
|
3890
|
+
currentIssueIds.add(computeIssueId(issue));
|
|
3891
|
+
}
|
|
3892
|
+
const activeEndpoints = extractActiveEndpoints(allRequests);
|
|
3893
|
+
issueStore.reconcile(currentIssueIds, activeEndpoints);
|
|
3894
|
+
const update = {
|
|
3895
|
+
insights: this.cachedInsights,
|
|
3896
|
+
findings: this.cachedFindings,
|
|
3897
|
+
issues: issueStore.getAll()
|
|
3898
|
+
};
|
|
3899
|
+
this.services.bus.emit("analysis:updated", update);
|
|
4087
3900
|
}
|
|
4088
3901
|
};
|
|
4089
3902
|
}
|
|
@@ -4101,7 +3914,7 @@ var init_src = __esm({
|
|
|
4101
3914
|
init_engine();
|
|
4102
3915
|
init_insights2();
|
|
4103
3916
|
init_insights();
|
|
4104
|
-
VERSION = "0.
|
|
3917
|
+
VERSION = "9.0.0";
|
|
4105
3918
|
}
|
|
4106
3919
|
});
|
|
4107
3920
|
|
|
@@ -4121,6 +3934,7 @@ function getBaseStyles() {
|
|
|
4121
3934
|
--red:#dc2626;
|
|
4122
3935
|
--cyan:#0891b2;
|
|
4123
3936
|
--green-bg:rgba(22,163,74,0.08);--green-bg-subtle:rgba(22,163,74,0.05);--green-border:rgba(22,163,74,0.2);--green-border-subtle:rgba(22,163,74,0.15);
|
|
3937
|
+
--amber-bg:rgba(217,119,6,0.07);--red-bg:rgba(220,38,38,0.07);--blue-bg:rgba(37,99,235,0.08);--cyan-bg:rgba(8,145,178,0.07);
|
|
4124
3938
|
--sidebar-width:232px;--header-height:52px;
|
|
4125
3939
|
--radius:8px;--radius-sm:6px;
|
|
4126
3940
|
--shadow-sm:0 1px 2px rgba(0,0,0,0.05);
|
|
@@ -4243,8 +4057,8 @@ function getFlowStyles() {
|
|
|
4243
4057
|
.flow-req-count{font-family:var(--mono);font-size:12px;color:var(--text-muted);flex-shrink:0;text-align:right}
|
|
4244
4058
|
.flow-badge-pill{font-size:11px;flex-shrink:0;font-family:var(--mono);font-weight:600;padding:2px 10px;border-radius:10px;text-align:center}
|
|
4245
4059
|
.flow-badge-pill.badge-clean{background:var(--green-bg);color:var(--green)}
|
|
4246
|
-
.flow-badge-pill.badge-warn{background:
|
|
4247
|
-
.flow-badge-pill.badge-error{background:
|
|
4060
|
+
.flow-badge-pill.badge-warn{background:var(--amber-bg);color:var(--amber)}
|
|
4061
|
+
.flow-badge-pill.badge-error{background:var(--red-bg);color:var(--red)}
|
|
4248
4062
|
.flow-duration{font-family:var(--mono);font-size:12px;color:var(--text-muted);flex-shrink:0;width:60px;text-align:right}
|
|
4249
4063
|
|
|
4250
4064
|
/* Flow expand panel */
|
|
@@ -4263,23 +4077,23 @@ function getFlowStyles() {
|
|
|
4263
4077
|
/* Method badges */
|
|
4264
4078
|
.method-badge{display:inline-flex;align-items:center;justify-content:center;padding:3px 8px;border-radius:5px;font-size:10px;font-weight:700;font-family:var(--mono);letter-spacing:.3px;flex-shrink:0}
|
|
4265
4079
|
.method-badge-GET{background:var(--green-bg);color:var(--green)}
|
|
4266
|
-
.method-badge-POST{background:
|
|
4267
|
-
.method-badge-PUT,.method-badge-PATCH{background:
|
|
4268
|
-
.method-badge-DELETE{background:
|
|
4080
|
+
.method-badge-POST{background:var(--blue-bg);color:var(--blue)}
|
|
4081
|
+
.method-badge-PUT,.method-badge-PATCH{background:var(--amber-bg);color:var(--amber)}
|
|
4082
|
+
.method-badge-DELETE{background:var(--red-bg);color:var(--red)}
|
|
4269
4083
|
.method-badge-HEAD,.method-badge-OPTIONS{background:var(--bg-muted);color:var(--text-muted)}
|
|
4270
4084
|
|
|
4271
4085
|
/* Status pills */
|
|
4272
4086
|
.status-pill{display:inline-flex;align-items:center;padding:1px 7px;border-radius:4px;font-size:11px;font-weight:600;font-family:var(--mono);flex-shrink:0}
|
|
4273
4087
|
.status-pill-2xx{background:var(--green-bg);color:var(--green)}
|
|
4274
|
-
.status-pill-3xx{background:
|
|
4275
|
-
.status-pill-4xx{background:
|
|
4276
|
-
.status-pill-5xx{background:
|
|
4088
|
+
.status-pill-3xx{background:var(--cyan-bg);color:var(--cyan)}
|
|
4089
|
+
.status-pill-4xx{background:var(--amber-bg);color:var(--amber)}
|
|
4090
|
+
.status-pill-5xx{background:var(--red-bg);color:var(--red)}
|
|
4277
4091
|
|
|
4278
4092
|
.traffic-card-path{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text);font-weight:500;font-size:13px}
|
|
4279
4093
|
.traffic-card-path.is-dup{color:var(--text-muted);font-weight:400}
|
|
4280
4094
|
.traffic-card-dur{color:var(--text-muted);font-size:12px;flex-shrink:0}
|
|
4281
4095
|
.traffic-card-size{color:var(--text-muted);font-size:11px;flex-shrink:0}
|
|
4282
|
-
.traffic-card-dup{font-size:10px;color:var(--amber);flex-shrink:0;font-weight:600;background:
|
|
4096
|
+
.traffic-card-dup{font-size:10px;color:var(--amber);flex-shrink:0;font-weight:600;background:var(--amber-bg);padding:1px 7px;border-radius:4px}
|
|
4283
4097
|
|
|
4284
4098
|
/* Body toggles */
|
|
4285
4099
|
.traffic-body{padding:0;margin-top:8px}
|
|
@@ -4308,7 +4122,7 @@ function getFlowStyles() {
|
|
|
4308
4122
|
.flow-subreq .subreq-label.is-dup{color:var(--text-muted);font-weight:400}
|
|
4309
4123
|
.flow-subreq .subreq-status{flex-shrink:0}
|
|
4310
4124
|
.flow-subreq .subreq-dur{color:var(--text-muted);font-size:12px;text-align:right;flex-shrink:0}
|
|
4311
|
-
.flow-subreq .subreq-dup-tag{font-size:10px;color:var(--amber);flex-shrink:0;font-weight:600;background:
|
|
4125
|
+
.flow-subreq .subreq-dup-tag{font-size:10px;color:var(--amber);flex-shrink:0;font-weight:600;background:var(--amber-bg);padding:1px 7px;border-radius:4px}
|
|
4312
4126
|
.flow-subreq-detail{display:none;padding:12px 0;border-bottom:1px solid var(--border-subtle)}
|
|
4313
4127
|
.flow-subreq-detail.open{display:block}
|
|
4314
4128
|
|
|
@@ -4337,6 +4151,41 @@ function getFlowStyles() {
|
|
|
4337
4151
|
/* Strict Mode duplicate banner */
|
|
4338
4152
|
.strict-mode-dupe{opacity:0.55}
|
|
4339
4153
|
.strict-mode-banner{font-size:11px;color:var(--text-muted);padding:6px 0 0;font-family:var(--mono)}
|
|
4154
|
+
|
|
4155
|
+
/* Flow detail tabs */
|
|
4156
|
+
.flow-detail-tabs{display:flex;gap:0;margin-bottom:14px;border-bottom:1px solid var(--border)}
|
|
4157
|
+
.flow-tab{padding:8px 16px;font-size:12px;font-family:var(--mono);font-weight:600;color:var(--text-muted);background:none;border:none;border-bottom:2px solid transparent;cursor:pointer;transition:all .15s;letter-spacing:.3px}
|
|
4158
|
+
.flow-tab:hover{color:var(--text)}
|
|
4159
|
+
.flow-tab.active{color:var(--accent);border-bottom-color:var(--accent)}
|
|
4160
|
+
|
|
4161
|
+
/* Waterfall chart \u2014 request bars on time axis, sub-events as text rows */
|
|
4162
|
+
.flow-waterfall{padding:0;font-family:var(--mono);font-size:11px}
|
|
4163
|
+
.wf-time-axis{display:flex;justify-content:space-between;font-size:9px;color:var(--text-muted);padding:0 0 6px;margin-left:180px;margin-right:56px;border-bottom:1px solid var(--border);margin-bottom:2px}
|
|
4164
|
+
.wf-rows{display:flex;flex-direction:column;gap:0}
|
|
4165
|
+
|
|
4166
|
+
/* Request group \u2014 request bar + its sub-events */
|
|
4167
|
+
.wf-request-group{border-bottom:1px solid var(--border-subtle);padding:2px 0}
|
|
4168
|
+
.wf-request-group:last-child{border-bottom:none}
|
|
4169
|
+
|
|
4170
|
+
/* Request row \u2014 label | bar on time axis | duration */
|
|
4171
|
+
.wf-req-row{display:flex;align-items:center;gap:0;height:24px;transition:background .1s}
|
|
4172
|
+
.wf-req-row:hover{background:var(--bg-hover)}
|
|
4173
|
+
.wf-req-label{width:180px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text);font-weight:500;padding:0 10px 0 0;font-size:11px}
|
|
4174
|
+
.wf-bar-track{flex:1;position:relative;height:14px;min-width:0;overflow:hidden}
|
|
4175
|
+
.wf-bar{position:absolute;top:1px;height:12px;border-radius:3px;opacity:0.8;min-width:3px}
|
|
4176
|
+
.wf-req-row:hover .wf-bar{opacity:1}
|
|
4177
|
+
.wf-req-dur{width:56px;flex-shrink:0;text-align:right;color:var(--text-muted);font-size:10px;padding-left:8px}
|
|
4178
|
+
|
|
4179
|
+
/* Sub-event rows \u2014 same layout as request rows: label | bar track | duration */
|
|
4180
|
+
.wf-sub-row{display:flex;align-items:center;gap:0;height:20px;transition:background .1s}
|
|
4181
|
+
.wf-sub-row:hover{background:var(--bg-hover)}
|
|
4182
|
+
.wf-sub-label{width:180px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-muted);font-size:10px;padding-left:14px;display:flex;align-items:center;gap:6px}
|
|
4183
|
+
.wf-sub-dot{width:6px;height:6px;border-radius:2px;flex-shrink:0}
|
|
4184
|
+
.wf-sub-bar-sized{height:8px !important;top:3px !important;opacity:0.65}
|
|
4185
|
+
.wf-sub-row:hover .wf-sub-bar-sized{opacity:0.9}
|
|
4186
|
+
.wf-sub-dur{width:56px;flex-shrink:0;text-align:right;color:var(--text-dim);font-size:9px;padding-left:8px}
|
|
4187
|
+
|
|
4188
|
+
.wf-loading{color:var(--text-muted);padding:12px 0;font-size:11px;font-family:var(--mono)}
|
|
4340
4189
|
`;
|
|
4341
4190
|
}
|
|
4342
4191
|
var init_flows = __esm({
|
|
@@ -4842,7 +4691,7 @@ function getTimelineStyles() {
|
|
|
4842
4691
|
}
|
|
4843
4692
|
`;
|
|
4844
4693
|
}
|
|
4845
|
-
var
|
|
4694
|
+
var init_timeline = __esm({
|
|
4846
4695
|
"src/dashboard/styles/timeline.ts"() {
|
|
4847
4696
|
"use strict";
|
|
4848
4697
|
}
|
|
@@ -4862,7 +4711,7 @@ var init_styles = __esm({
|
|
|
4862
4711
|
init_graph();
|
|
4863
4712
|
init_overview();
|
|
4864
4713
|
init_security2();
|
|
4865
|
-
|
|
4714
|
+
init_timeline();
|
|
4866
4715
|
}
|
|
4867
4716
|
});
|
|
4868
4717
|
|
|
@@ -4957,10 +4806,10 @@ function isTelemetryEnabled() {
|
|
|
4957
4806
|
return cachedEnabled;
|
|
4958
4807
|
}
|
|
4959
4808
|
var CONFIG_DIR, CONFIG_PATH, cachedEnabled;
|
|
4960
|
-
var
|
|
4809
|
+
var init_config2 = __esm({
|
|
4961
4810
|
"src/telemetry/config.ts"() {
|
|
4962
4811
|
"use strict";
|
|
4963
|
-
|
|
4812
|
+
init_features();
|
|
4964
4813
|
CONFIG_DIR = join3(homedir2(), ".brakit");
|
|
4965
4814
|
CONFIG_PATH = join3(CONFIG_DIR, "config.json");
|
|
4966
4815
|
cachedEnabled = null;
|
|
@@ -5001,12 +4850,12 @@ function speedBucket(ms) {
|
|
|
5001
4850
|
}
|
|
5002
4851
|
return `>${t[t.length - 1]}ms`;
|
|
5003
4852
|
}
|
|
5004
|
-
function trackSession(
|
|
4853
|
+
function trackSession(services) {
|
|
5005
4854
|
if (!isTelemetryEnabled()) return;
|
|
5006
4855
|
const isFirstSession = readConfig() === null;
|
|
5007
4856
|
const config = getOrCreateConfig();
|
|
5008
|
-
const metricsStore =
|
|
5009
|
-
const analysisEngine =
|
|
4857
|
+
const metricsStore = services.metricsStore;
|
|
4858
|
+
const analysisEngine = services.analysisEngine;
|
|
5010
4859
|
const live = metricsStore.getLiveEndpoints();
|
|
5011
4860
|
const insights = analysisEngine.getInsights();
|
|
5012
4861
|
const findings = analysisEngine.getFindings();
|
|
@@ -5034,9 +4883,9 @@ function trackSession(registry) {
|
|
|
5034
4883
|
first_session: isFirstSession,
|
|
5035
4884
|
adapters_detected: session.adapters,
|
|
5036
4885
|
request_count: session.requestCount,
|
|
5037
|
-
error_count:
|
|
5038
|
-
query_count:
|
|
5039
|
-
fetch_count:
|
|
4886
|
+
error_count: services.errorStore.getAll().length,
|
|
4887
|
+
query_count: services.queryStore.getAll().length,
|
|
4888
|
+
fetch_count: services.fetchStore.getAll().length,
|
|
5040
4889
|
insight_count: insights.length,
|
|
5041
4890
|
finding_count: findings.length,
|
|
5042
4891
|
insight_types: [...session.insightTypes],
|
|
@@ -5069,13 +4918,13 @@ function trackSession(registry) {
|
|
|
5069
4918
|
}
|
|
5070
4919
|
}
|
|
5071
4920
|
var POSTHOG_KEY, session;
|
|
5072
|
-
var
|
|
4921
|
+
var init_telemetry = __esm({
|
|
5073
4922
|
"src/telemetry/index.ts"() {
|
|
5074
4923
|
"use strict";
|
|
5075
4924
|
init_src();
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
4925
|
+
init_config2();
|
|
4926
|
+
init_labels();
|
|
4927
|
+
init_config2();
|
|
5079
4928
|
POSTHOG_KEY = "phc_E9TwydCGnSfPLIUhNxChpeg32TSowjk31KiPhnLPP0x";
|
|
5080
4929
|
session = {
|
|
5081
4930
|
startTime: 0,
|
|
@@ -5097,32 +4946,30 @@ var init_telemetry2 = __esm({
|
|
|
5097
4946
|
function isDashboardRequest(url) {
|
|
5098
4947
|
return url === DASHBOARD_PREFIX || url.startsWith(DASHBOARD_PREFIX + "/");
|
|
5099
4948
|
}
|
|
5100
|
-
function createDashboardHandler(
|
|
5101
|
-
const metricsStore =
|
|
4949
|
+
function createDashboardHandler(services) {
|
|
4950
|
+
const metricsStore = services.metricsStore;
|
|
5102
4951
|
const routes = {
|
|
5103
|
-
[DASHBOARD_API_REQUESTS]: createRequestsHandler(
|
|
5104
|
-
[DASHBOARD_API_EVENTS]: createSSEHandler(
|
|
5105
|
-
[DASHBOARD_API_FLOWS]: createFlowsHandler(
|
|
5106
|
-
[DASHBOARD_API_CLEAR]: createClearHandler(
|
|
5107
|
-
[DASHBOARD_API_LOGS]: createLogsHandler(
|
|
5108
|
-
[DASHBOARD_API_FETCHES]: createFetchesHandler(
|
|
5109
|
-
[DASHBOARD_API_ERRORS]: createErrorsHandler(
|
|
5110
|
-
[DASHBOARD_API_QUERIES]: createQueriesHandler(
|
|
4952
|
+
[DASHBOARD_API_REQUESTS]: createRequestsHandler(services),
|
|
4953
|
+
[DASHBOARD_API_EVENTS]: createSSEHandler(services),
|
|
4954
|
+
[DASHBOARD_API_FLOWS]: createFlowsHandler(services),
|
|
4955
|
+
[DASHBOARD_API_CLEAR]: createClearHandler(services),
|
|
4956
|
+
[DASHBOARD_API_LOGS]: createLogsHandler(services),
|
|
4957
|
+
[DASHBOARD_API_FETCHES]: createFetchesHandler(services),
|
|
4958
|
+
[DASHBOARD_API_ERRORS]: createErrorsHandler(services),
|
|
4959
|
+
[DASHBOARD_API_QUERIES]: createQueriesHandler(services),
|
|
5111
4960
|
[DASHBOARD_API_METRICS]: createMetricsHandler(metricsStore),
|
|
5112
4961
|
[DASHBOARD_API_METRICS_LIVE]: createLiveMetricsHandler(metricsStore),
|
|
5113
|
-
[DASHBOARD_API_INGEST]: createIngestHandler(
|
|
5114
|
-
[DASHBOARD_API_ACTIVITY]: createActivityHandler(
|
|
4962
|
+
[DASHBOARD_API_INGEST]: createIngestHandler(services),
|
|
4963
|
+
[DASHBOARD_API_ACTIVITY]: createActivityHandler(services)
|
|
5115
4964
|
};
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
);
|
|
5125
|
-
}
|
|
4965
|
+
const issueStore = services.issueStore;
|
|
4966
|
+
routes[DASHBOARD_API_INSIGHTS] = createIssuesHandler(issueStore);
|
|
4967
|
+
routes[DASHBOARD_API_SECURITY] = createIssuesHandler(issueStore);
|
|
4968
|
+
routes[DASHBOARD_API_FINDINGS] = createFindingsHandler(issueStore);
|
|
4969
|
+
routes[DASHBOARD_API_FINDINGS_REPORT] = createIssuesReportHandler(
|
|
4970
|
+
issueStore,
|
|
4971
|
+
services.bus
|
|
4972
|
+
);
|
|
5126
4973
|
routes[DASHBOARD_API_TAB] = (req, res) => {
|
|
5127
4974
|
const raw = (req.url ?? "").split("tab=")[1];
|
|
5128
4975
|
if (raw) {
|
|
@@ -5133,7 +4980,7 @@ function createDashboardHandler(registry) {
|
|
|
5133
4980
|
res.end();
|
|
5134
4981
|
};
|
|
5135
4982
|
return (req, res, config) => {
|
|
5136
|
-
const path = (req.url ?? "/")
|
|
4983
|
+
const path = stripQueryString(req.url ?? "/");
|
|
5137
4984
|
const handler = routes[path];
|
|
5138
4985
|
if (handler) {
|
|
5139
4986
|
handler(req, res);
|
|
@@ -5151,13 +4998,14 @@ function createDashboardHandler(registry) {
|
|
|
5151
4998
|
var init_router = __esm({
|
|
5152
4999
|
"src/dashboard/router.ts"() {
|
|
5153
5000
|
"use strict";
|
|
5001
|
+
init_endpoint();
|
|
5154
5002
|
init_constants();
|
|
5155
|
-
|
|
5003
|
+
init_labels();
|
|
5156
5004
|
init_api();
|
|
5157
5005
|
init_issues();
|
|
5158
5006
|
init_sse();
|
|
5159
5007
|
init_page();
|
|
5160
|
-
|
|
5008
|
+
init_telemetry();
|
|
5161
5009
|
}
|
|
5162
5010
|
});
|
|
5163
5011
|
|
|
@@ -5198,51 +5046,6 @@ var init_event_bus = __esm({
|
|
|
5198
5046
|
}
|
|
5199
5047
|
});
|
|
5200
5048
|
|
|
5201
|
-
// src/core/service-registry.ts
|
|
5202
|
-
var ServiceRegistry;
|
|
5203
|
-
var init_service_registry = __esm({
|
|
5204
|
-
"src/core/service-registry.ts"() {
|
|
5205
|
-
"use strict";
|
|
5206
|
-
ServiceRegistry = class {
|
|
5207
|
-
constructor() {
|
|
5208
|
-
this.services = /* @__PURE__ */ new Map();
|
|
5209
|
-
}
|
|
5210
|
-
register(name, service) {
|
|
5211
|
-
this.services.set(name, service);
|
|
5212
|
-
}
|
|
5213
|
-
get(name) {
|
|
5214
|
-
const service = this.services.get(name);
|
|
5215
|
-
if (!service) throw new Error(`Service "${name}" not registered`);
|
|
5216
|
-
return service;
|
|
5217
|
-
}
|
|
5218
|
-
has(name) {
|
|
5219
|
-
return this.services.has(name);
|
|
5220
|
-
}
|
|
5221
|
-
};
|
|
5222
|
-
}
|
|
5223
|
-
});
|
|
5224
|
-
|
|
5225
|
-
// src/utils/static-patterns.ts
|
|
5226
|
-
function isStaticPath(urlPath) {
|
|
5227
|
-
return STATIC_PATTERNS.some((p) => p.test(urlPath));
|
|
5228
|
-
}
|
|
5229
|
-
var STATIC_PATTERNS;
|
|
5230
|
-
var init_static_patterns = __esm({
|
|
5231
|
-
"src/utils/static-patterns.ts"() {
|
|
5232
|
-
"use strict";
|
|
5233
|
-
STATIC_PATTERNS = [
|
|
5234
|
-
/\.(?:js|css|map|ico|png|jpg|jpeg|gif|svg|webp|woff2?|ttf|eot)$/,
|
|
5235
|
-
/^\/favicon/,
|
|
5236
|
-
/^\/node_modules\//,
|
|
5237
|
-
// Framework-specific static/internal paths
|
|
5238
|
-
/^\/_next\//,
|
|
5239
|
-
/^\/__nextjs/,
|
|
5240
|
-
/^\/@vite\//,
|
|
5241
|
-
/^\/__vite/
|
|
5242
|
-
];
|
|
5243
|
-
}
|
|
5244
|
-
});
|
|
5245
|
-
|
|
5246
5049
|
// src/store/request-store.ts
|
|
5247
5050
|
function flattenHeaders(headers2) {
|
|
5248
5051
|
const flat = {};
|
|
@@ -5258,6 +5061,7 @@ var init_request_store = __esm({
|
|
|
5258
5061
|
"use strict";
|
|
5259
5062
|
init_constants();
|
|
5260
5063
|
init_static_patterns();
|
|
5064
|
+
init_endpoint();
|
|
5261
5065
|
RequestStore = class {
|
|
5262
5066
|
constructor(maxEntries = MAX_REQUEST_ENTRIES) {
|
|
5263
5067
|
this.maxEntries = maxEntries;
|
|
@@ -5266,7 +5070,7 @@ var init_request_store = __esm({
|
|
|
5266
5070
|
}
|
|
5267
5071
|
capture(input) {
|
|
5268
5072
|
const url = input.url;
|
|
5269
|
-
const path = url
|
|
5073
|
+
const path = stripQueryString(url);
|
|
5270
5074
|
let requestBodyStr = null;
|
|
5271
5075
|
if (input.requestBody && input.requestBody.length > 0) {
|
|
5272
5076
|
requestBodyStr = input.requestBody.subarray(0, input.config.maxBodyCapture).toString("utf-8");
|
|
@@ -5291,7 +5095,8 @@ var init_request_store = __esm({
|
|
|
5291
5095
|
startedAt: input.startTime,
|
|
5292
5096
|
durationMs: Math.round((input.endTime ?? performance.now()) - input.startTime),
|
|
5293
5097
|
responseSize: input.responseBody?.length ?? 0,
|
|
5294
|
-
isStatic: isStaticPath(path)
|
|
5098
|
+
isStatic: isStaticPath(path),
|
|
5099
|
+
isHealthCheck: isHealthCheckPath(path)
|
|
5295
5100
|
};
|
|
5296
5101
|
this.requests.push(entry);
|
|
5297
5102
|
if (this.requests.length > this.maxEntries) {
|
|
@@ -5368,50 +5173,6 @@ var init_telemetry_store = __esm({
|
|
|
5368
5173
|
}
|
|
5369
5174
|
});
|
|
5370
5175
|
|
|
5371
|
-
// src/store/fetch-store.ts
|
|
5372
|
-
var FetchStore;
|
|
5373
|
-
var init_fetch_store = __esm({
|
|
5374
|
-
"src/store/fetch-store.ts"() {
|
|
5375
|
-
"use strict";
|
|
5376
|
-
init_telemetry_store();
|
|
5377
|
-
FetchStore = class extends TelemetryStore {
|
|
5378
|
-
};
|
|
5379
|
-
}
|
|
5380
|
-
});
|
|
5381
|
-
|
|
5382
|
-
// src/store/log-store.ts
|
|
5383
|
-
var LogStore;
|
|
5384
|
-
var init_log_store = __esm({
|
|
5385
|
-
"src/store/log-store.ts"() {
|
|
5386
|
-
"use strict";
|
|
5387
|
-
init_telemetry_store();
|
|
5388
|
-
LogStore = class extends TelemetryStore {
|
|
5389
|
-
};
|
|
5390
|
-
}
|
|
5391
|
-
});
|
|
5392
|
-
|
|
5393
|
-
// src/store/error-store.ts
|
|
5394
|
-
var ErrorStore;
|
|
5395
|
-
var init_error_store = __esm({
|
|
5396
|
-
"src/store/error-store.ts"() {
|
|
5397
|
-
"use strict";
|
|
5398
|
-
init_telemetry_store();
|
|
5399
|
-
ErrorStore = class extends TelemetryStore {
|
|
5400
|
-
};
|
|
5401
|
-
}
|
|
5402
|
-
});
|
|
5403
|
-
|
|
5404
|
-
// src/store/query-store.ts
|
|
5405
|
-
var QueryStore;
|
|
5406
|
-
var init_query_store = __esm({
|
|
5407
|
-
"src/store/query-store.ts"() {
|
|
5408
|
-
"use strict";
|
|
5409
|
-
init_telemetry_store();
|
|
5410
|
-
QueryStore = class extends TelemetryStore {
|
|
5411
|
-
};
|
|
5412
|
-
}
|
|
5413
|
-
});
|
|
5414
|
-
|
|
5415
5176
|
// src/utils/math.ts
|
|
5416
5177
|
function percentile(values, p) {
|
|
5417
5178
|
if (values.length === 0) return 0;
|
|
@@ -5482,7 +5243,7 @@ var init_metrics_store = __esm({
|
|
|
5482
5243
|
this.flush(true);
|
|
5483
5244
|
}
|
|
5484
5245
|
recordRequest(req, metrics) {
|
|
5485
|
-
if (req.isStatic) return;
|
|
5246
|
+
if (req.isStatic || req.isHealthCheck) return;
|
|
5486
5247
|
this.dirty = true;
|
|
5487
5248
|
const key = getEndpointKey(req.method, req.path);
|
|
5488
5249
|
let acc = this.accumulators.get(key);
|
|
@@ -5710,10 +5471,6 @@ var init_store = __esm({
|
|
|
5710
5471
|
"use strict";
|
|
5711
5472
|
init_request_store();
|
|
5712
5473
|
init_telemetry_store();
|
|
5713
|
-
init_fetch_store();
|
|
5714
|
-
init_log_store();
|
|
5715
|
-
init_error_store();
|
|
5716
|
-
init_query_store();
|
|
5717
5474
|
init_metrics_store();
|
|
5718
5475
|
init_persistence();
|
|
5719
5476
|
}
|
|
@@ -5745,9 +5502,9 @@ function formatConsoleLine(issue, suffix) {
|
|
|
5745
5502
|
}
|
|
5746
5503
|
return line;
|
|
5747
5504
|
}
|
|
5748
|
-
function startTerminalInsights(
|
|
5749
|
-
const bus =
|
|
5750
|
-
const metricsStore =
|
|
5505
|
+
function startTerminalInsights(services, proxyPort) {
|
|
5506
|
+
const bus = services.bus;
|
|
5507
|
+
const metricsStore = services.metricsStore;
|
|
5751
5508
|
const printedKeys = /* @__PURE__ */ new Set();
|
|
5752
5509
|
const resolvedKeys = /* @__PURE__ */ new Set();
|
|
5753
5510
|
const dashUrl = `localhost:${proxyPort}${DASHBOARD_PREFIX}`;
|
|
@@ -5818,8 +5575,8 @@ var init_terminal = __esm({
|
|
|
5818
5575
|
"use strict";
|
|
5819
5576
|
init_src();
|
|
5820
5577
|
init_constants();
|
|
5821
|
-
|
|
5822
|
-
|
|
5578
|
+
init_config();
|
|
5579
|
+
init_labels();
|
|
5823
5580
|
SEVERITY_COLOR = {
|
|
5824
5581
|
critical: pc.red,
|
|
5825
5582
|
warning: pc.yellow,
|
|
@@ -5914,8 +5671,14 @@ function outgoingToIncoming(headers2) {
|
|
|
5914
5671
|
}
|
|
5915
5672
|
return result;
|
|
5916
5673
|
}
|
|
5674
|
+
function getDecompressor(encoding) {
|
|
5675
|
+
if (encoding === CONTENT_ENCODING_GZIP) return gunzip;
|
|
5676
|
+
if (encoding === CONTENT_ENCODING_BR) return brotliDecompress;
|
|
5677
|
+
if (encoding === CONTENT_ENCODING_DEFLATE) return inflate;
|
|
5678
|
+
return null;
|
|
5679
|
+
}
|
|
5917
5680
|
function decompressAsync(body, encoding) {
|
|
5918
|
-
const decompressor = encoding
|
|
5681
|
+
const decompressor = getDecompressor(encoding);
|
|
5919
5682
|
if (!decompressor) return Promise.resolve(body);
|
|
5920
5683
|
return new Promise((resolve6) => {
|
|
5921
5684
|
decompressor(body, (err, result) => {
|
|
@@ -5929,7 +5692,7 @@ function toBuffer(chunk) {
|
|
|
5929
5692
|
if (typeof chunk === "string") return Buffer.from(chunk);
|
|
5930
5693
|
return null;
|
|
5931
5694
|
}
|
|
5932
|
-
function captureInProcess(req, res, requestId, requestStore) {
|
|
5695
|
+
function captureInProcess(req, res, requestId, requestStore, isChild = false) {
|
|
5933
5696
|
const startTime = performance.now();
|
|
5934
5697
|
const method = req.method ?? "GET";
|
|
5935
5698
|
const resChunks = [];
|
|
@@ -5975,30 +5738,32 @@ function captureInProcess(req, res, requestId, requestStore) {
|
|
|
5975
5738
|
const responseHeaders = outgoingToIncoming(res.getHeaders());
|
|
5976
5739
|
const responseContentType = String(res.getHeader("content-type") ?? "");
|
|
5977
5740
|
const capturedChunks = resChunks.slice();
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
body
|
|
5741
|
+
if (!isChild) {
|
|
5742
|
+
void (async () => {
|
|
5743
|
+
try {
|
|
5744
|
+
let body = capturedChunks.length > 0 ? Buffer.concat(capturedChunks) : null;
|
|
5745
|
+
if (body && encoding && !truncated) {
|
|
5746
|
+
body = await decompressAsync(body, encoding);
|
|
5747
|
+
}
|
|
5748
|
+
requestStore.capture({
|
|
5749
|
+
requestId,
|
|
5750
|
+
method,
|
|
5751
|
+
url: req.url ?? "/",
|
|
5752
|
+
requestHeaders: req.headers,
|
|
5753
|
+
requestBody: null,
|
|
5754
|
+
statusCode,
|
|
5755
|
+
responseHeaders,
|
|
5756
|
+
responseBody: body,
|
|
5757
|
+
responseContentType,
|
|
5758
|
+
startTime,
|
|
5759
|
+
endTime,
|
|
5760
|
+
config: { maxBodyCapture: DEFAULT_MAX_BODY_CAPTURE }
|
|
5761
|
+
});
|
|
5762
|
+
} catch (e) {
|
|
5763
|
+
brakitDebug(`capture store: ${getErrorMessage(e)}`);
|
|
5983
5764
|
}
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
method,
|
|
5987
|
-
url: req.url ?? "/",
|
|
5988
|
-
requestHeaders: req.headers,
|
|
5989
|
-
requestBody: null,
|
|
5990
|
-
statusCode,
|
|
5991
|
-
responseHeaders,
|
|
5992
|
-
responseBody: body,
|
|
5993
|
-
responseContentType,
|
|
5994
|
-
startTime,
|
|
5995
|
-
endTime,
|
|
5996
|
-
config: { maxBodyCapture: DEFAULT_MAX_BODY_CAPTURE }
|
|
5997
|
-
});
|
|
5998
|
-
} catch (e) {
|
|
5999
|
-
brakitDebug(`capture store: ${getErrorMessage(e)}`);
|
|
6000
|
-
}
|
|
6001
|
-
})();
|
|
5765
|
+
})();
|
|
5766
|
+
}
|
|
6002
5767
|
return result;
|
|
6003
5768
|
};
|
|
6004
5769
|
}
|
|
@@ -6046,13 +5811,15 @@ function installInterceptor(deps) {
|
|
|
6046
5811
|
deps.handleDashboard(req, res, deps.config);
|
|
6047
5812
|
return true;
|
|
6048
5813
|
}
|
|
6049
|
-
const
|
|
5814
|
+
const propagated = req.headers[BRAKIT_REQUEST_ID_HEADER];
|
|
5815
|
+
const requestId = propagated ?? randomUUID8();
|
|
5816
|
+
const isChild = propagated !== void 0;
|
|
6050
5817
|
const ctx = {
|
|
6051
5818
|
requestId,
|
|
6052
5819
|
url,
|
|
6053
5820
|
method: req.method ?? "GET"
|
|
6054
5821
|
};
|
|
6055
|
-
captureInProcess(req, res, requestId, deps.requestStore);
|
|
5822
|
+
captureInProcess(req, res, requestId, deps.requestStore, isChild);
|
|
6056
5823
|
return storage.run(
|
|
6057
5824
|
ctx,
|
|
6058
5825
|
() => original.apply(this, [event, ...args])
|
|
@@ -6075,7 +5842,8 @@ var init_interceptor = __esm({
|
|
|
6075
5842
|
init_safe_wrap();
|
|
6076
5843
|
init_guard();
|
|
6077
5844
|
init_capture();
|
|
6078
|
-
|
|
5845
|
+
init_labels();
|
|
5846
|
+
init_constants();
|
|
6079
5847
|
originalEmit = null;
|
|
6080
5848
|
}
|
|
6081
5849
|
});
|
|
@@ -6093,18 +5861,12 @@ function setup() {
|
|
|
6093
5861
|
initPromise = doSetup();
|
|
6094
5862
|
return initPromise;
|
|
6095
5863
|
}
|
|
6096
|
-
function createStores(bus
|
|
5864
|
+
function createStores(bus) {
|
|
6097
5865
|
const requestStore = new RequestStore();
|
|
6098
|
-
const fetchStore = new
|
|
6099
|
-
const logStore = new
|
|
6100
|
-
const errorStore = new
|
|
6101
|
-
const queryStore = new
|
|
6102
|
-
registry.register("event-bus", bus);
|
|
6103
|
-
registry.register("request-store", requestStore);
|
|
6104
|
-
registry.register("fetch-store", fetchStore);
|
|
6105
|
-
registry.register("log-store", logStore);
|
|
6106
|
-
registry.register("error-store", errorStore);
|
|
6107
|
-
registry.register("query-store", queryStore);
|
|
5866
|
+
const fetchStore = new TelemetryStore();
|
|
5867
|
+
const logStore = new TelemetryStore();
|
|
5868
|
+
const errorStore = new TelemetryStore();
|
|
5869
|
+
const queryStore = new TelemetryStore();
|
|
6108
5870
|
bus.on("telemetry:fetch", (data) => fetchStore.add(data));
|
|
6109
5871
|
bus.on("telemetry:query", (data) => queryStore.add(data));
|
|
6110
5872
|
bus.on("telemetry:log", (data) => logStore.add(data));
|
|
@@ -6138,17 +5900,16 @@ function installHooks(bus) {
|
|
|
6138
5900
|
adapterNames: adapterRegistry.getActive().map((a) => a.name)
|
|
6139
5901
|
};
|
|
6140
5902
|
}
|
|
6141
|
-
function startAnalysis(
|
|
6142
|
-
const bus = registry.get("event-bus");
|
|
5903
|
+
function startAnalysis(bus, stores, dataDir, services) {
|
|
6143
5904
|
const metricsStore = new MetricsStore(new FileMetricsPersistence(dataDir));
|
|
6144
5905
|
metricsStore.start();
|
|
6145
|
-
registry.register("metrics-store", metricsStore);
|
|
6146
5906
|
const issueStore = new IssueStore(dataDir);
|
|
6147
5907
|
issueStore.start();
|
|
6148
|
-
|
|
6149
|
-
|
|
5908
|
+
services.metricsStore = metricsStore;
|
|
5909
|
+
services.issueStore = issueStore;
|
|
5910
|
+
const analysisEngine = new AnalysisEngine(services);
|
|
6150
5911
|
analysisEngine.start();
|
|
6151
|
-
|
|
5912
|
+
services.analysisEngine = analysisEngine;
|
|
6152
5913
|
bus.on("request:completed", (req) => {
|
|
6153
5914
|
const queries = stores.queryStore.getByRequest(req.id);
|
|
6154
5915
|
const fetches = stores.fetchStore.getByRequest(req.id);
|
|
@@ -6160,7 +5921,7 @@ function startAnalysis(registry, stores, dataDir) {
|
|
|
6160
5921
|
});
|
|
6161
5922
|
return { analysisEngine, metricsStore, issueStore };
|
|
6162
5923
|
}
|
|
6163
|
-
function registerLifecycle(
|
|
5924
|
+
function registerLifecycle(allServices, stores, services, cwd) {
|
|
6164
5925
|
let telemetrySent = false;
|
|
6165
5926
|
const sendTelemetry = () => {
|
|
6166
5927
|
if (telemetrySent) return;
|
|
@@ -6172,7 +5933,7 @@ function registerLifecycle(registry, stores, services, cwd) {
|
|
|
6172
5933
|
recordRulesTriggered(
|
|
6173
5934
|
services.analysisEngine.getFindings().map((f) => f.rule)
|
|
6174
5935
|
);
|
|
6175
|
-
trackSession(
|
|
5936
|
+
trackSession(allServices);
|
|
6176
5937
|
};
|
|
6177
5938
|
let teardownCalled = false;
|
|
6178
5939
|
const runTeardown = () => {
|
|
@@ -6201,20 +5962,23 @@ function registerLifecycle(registry, stores, services, cwd) {
|
|
|
6201
5962
|
async function doSetup() {
|
|
6202
5963
|
brakitDebug(`[setup] doSetup called at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
6203
5964
|
const bus = new EventBus();
|
|
6204
|
-
const registry = new ServiceRegistry();
|
|
6205
5965
|
const cwd = process.cwd();
|
|
6206
|
-
const stores = createStores(bus
|
|
5966
|
+
const stores = createStores(bus);
|
|
5967
|
+
const services = {
|
|
5968
|
+
bus,
|
|
5969
|
+
...stores
|
|
5970
|
+
};
|
|
6207
5971
|
const { framework, adapterNames } = installHooks(bus);
|
|
6208
5972
|
initSession(framework, detectPackageManagerSync(cwd), false, adapterNames);
|
|
6209
5973
|
const dataDir = getProjectDataDir(cwd);
|
|
6210
|
-
const
|
|
5974
|
+
const analysisServices = startAnalysis(bus, stores, dataDir, services);
|
|
6211
5975
|
const config = {
|
|
6212
5976
|
proxyPort: 0,
|
|
6213
5977
|
targetPort: 0,
|
|
6214
5978
|
showStatic: false,
|
|
6215
5979
|
maxBodyCapture: DEFAULT_MAX_BODY_CAPTURE
|
|
6216
5980
|
};
|
|
6217
|
-
const handleDashboard = createDashboardHandler(
|
|
5981
|
+
const handleDashboard = createDashboardHandler(services);
|
|
6218
5982
|
installInterceptor({
|
|
6219
5983
|
handleDashboard,
|
|
6220
5984
|
config,
|
|
@@ -6247,14 +6011,14 @@ async function doSetup() {
|
|
|
6247
6011
|
brakitDebug(`port file write failed: ${getErrorMessage(err)}`);
|
|
6248
6012
|
}
|
|
6249
6013
|
})();
|
|
6250
|
-
startTerminalInsights(
|
|
6014
|
+
startTerminalInsights(services, port);
|
|
6251
6015
|
process.stdout.write(
|
|
6252
6016
|
` brakit v${VERSION} \u2014 http://localhost:${port}${DASHBOARD_PREFIX}
|
|
6253
6017
|
`
|
|
6254
6018
|
);
|
|
6255
6019
|
}
|
|
6256
6020
|
});
|
|
6257
|
-
registerLifecycle(
|
|
6021
|
+
registerLifecycle(services, stores, analysisServices, cwd);
|
|
6258
6022
|
}
|
|
6259
6023
|
var initPromise;
|
|
6260
6024
|
var init_setup = __esm({
|
|
@@ -6266,12 +6030,8 @@ var init_setup = __esm({
|
|
|
6266
6030
|
init_adapters();
|
|
6267
6031
|
init_router();
|
|
6268
6032
|
init_event_bus();
|
|
6269
|
-
init_service_registry();
|
|
6270
6033
|
init_request_store();
|
|
6271
|
-
|
|
6272
|
-
init_log_store();
|
|
6273
|
-
init_error_store();
|
|
6274
|
-
init_query_store();
|
|
6034
|
+
init_telemetry_store();
|
|
6275
6035
|
init_store();
|
|
6276
6036
|
init_issue_store();
|
|
6277
6037
|
init_engine();
|
|
@@ -6284,7 +6044,7 @@ var init_setup = __esm({
|
|
|
6284
6044
|
init_type_guards();
|
|
6285
6045
|
init_fs();
|
|
6286
6046
|
init_project();
|
|
6287
|
-
|
|
6047
|
+
init_telemetry();
|
|
6288
6048
|
initPromise = null;
|
|
6289
6049
|
}
|
|
6290
6050
|
});
|