@oconde/radar 0.0.0-stage → 0.2.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.
@@ -0,0 +1,1495 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var __decorateClass = (decorators, target, key, kind) => {
20
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
21
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
22
+ if (decorator = decorators[i])
23
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
24
+ if (kind && result) __defProp(target, key, result);
25
+ return result;
26
+ };
27
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
28
+
29
+ // src/nest/index.ts
30
+ var nest_exports = {};
31
+ __export(nest_exports, {
32
+ RADAR_OPTIONS: () => RADAR_OPTIONS,
33
+ RadarExceptionFilter: () => RadarExceptionFilter,
34
+ RadarInterceptor: () => RadarInterceptor,
35
+ RadarModule: () => RadarModule,
36
+ RadarService: () => RadarService,
37
+ shouldCapture: () => shouldCapture
38
+ });
39
+ module.exports = __toCommonJS(nest_exports);
40
+
41
+ // node_modules/tsup/assets/cjs_shims.js
42
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
43
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
44
+
45
+ // src/nest/index.ts
46
+ var import_node_module2 = require("module");
47
+ var import_common = require("@nestjs/common");
48
+ var import_core = require("@nestjs/core");
49
+ var import_rxjs = require("rxjs");
50
+
51
+ // src/context.ts
52
+ var import_node_async_hooks = require("async_hooks");
53
+ var import_node_crypto = require("crypto");
54
+
55
+ // src/protocol/index.ts
56
+ var INGEST_PATH = "/api/v1/events";
57
+ var CHECKIN_PATH = "/api/v1/checkins/";
58
+ var CHECKIN_SLUG = /^[a-z0-9][a-z0-9-]{0,63}$/;
59
+ var CHECKIN_STATUSES = ["in_progress", "ok", "error"];
60
+ var LIMITS = {
61
+ batchBytes: 1e6,
62
+ batchEvents: 500,
63
+ messageLength: 2e3,
64
+ stringLength: 2e3,
65
+ attrsBytes: 16384,
66
+ bodyBytes: 16384,
67
+ headers: 50,
68
+ frames: 50,
69
+ contextFrames: 10,
70
+ contextLines: 5,
71
+ contextLineLength: 300,
72
+ causeDepth: 5,
73
+ depth: 6,
74
+ arrayItems: 50,
75
+ requestIdLength: 128,
76
+ checkInIdLength: 64
77
+ };
78
+
79
+ // src/context.ts
80
+ var STATE_KEY = /* @__PURE__ */ Symbol.for("@oconde/radar");
81
+ function globalState() {
82
+ const holder = globalThis;
83
+ let state2 = holder[STATE_KEY];
84
+ if (!state2) {
85
+ state2 = { storage: new import_node_async_hooks.AsyncLocalStorage() };
86
+ holder[STATE_KEY] = state2;
87
+ }
88
+ return state2;
89
+ }
90
+ function currentContext() {
91
+ return globalState().storage.getStore();
92
+ }
93
+ function runWithContext(context, fn) {
94
+ return globalState().storage.run(context, fn);
95
+ }
96
+ var SAFE_REQUEST_ID = /^[\x21-\x7e]+$/;
97
+ function requestIdFrom(incoming) {
98
+ const value = incoming?.trim();
99
+ return value && value.length <= LIMITS.requestIdLength && SAFE_REQUEST_ID.test(value) ? value : (0, import_node_crypto.randomUUID)();
100
+ }
101
+
102
+ // src/client.ts
103
+ var import_node_crypto3 = require("crypto");
104
+ var import_node_os = require("os");
105
+ var import_node_perf_hooks3 = require("perf_hooks");
106
+
107
+ // src/express.ts
108
+ var import_node_perf_hooks = require("perf_hooks");
109
+
110
+ // src/redact.ts
111
+ var import_node_crypto2 = require("crypto");
112
+ var SECRET_WORDS = /* @__PURE__ */ new Set([
113
+ "pass",
114
+ "passwd",
115
+ "password",
116
+ "passwords",
117
+ "senha",
118
+ "secret",
119
+ "secrets",
120
+ "token",
121
+ "tokens",
122
+ "authorization",
123
+ "auth",
124
+ "cookie",
125
+ "cookies",
126
+ "apikey",
127
+ "privatekey",
128
+ "cpf",
129
+ "card",
130
+ "cvv",
131
+ "cvc",
132
+ "pin",
133
+ "otp",
134
+ "signature",
135
+ "jwt",
136
+ "session",
137
+ "sessionid",
138
+ "credential",
139
+ "credentials"
140
+ ]);
141
+ var SECRET_PAIRS = [
142
+ ["api", "key"],
143
+ ["private", "key"],
144
+ ["access", "key"],
145
+ ["secret", "key"]
146
+ ];
147
+ var LOW_ENTROPY_WORDS = /* @__PURE__ */ new Set(["pass", "passwd", "password", "passwords", "senha", "cpf", "card", "cvv", "cvc", "pin", "otp"]);
148
+ var CUSTOM_SECRET_HEADER = /^x-.+-(key|secret|token)$/i;
149
+ var AUTH_SCHEME = /^(Bearer|Basic|Token|Digest|ApiKey)\s+/i;
150
+ var JWT_PREFIX = /^eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\./;
151
+ var MAX_DEPTH = 8;
152
+ var KEY_SLOT = /* @__PURE__ */ Symbol.for("@oconde/radar/fingerprint-key");
153
+ function fingerprintSlot() {
154
+ const holder = globalThis;
155
+ let slot = holder[KEY_SLOT];
156
+ if (!slot) {
157
+ slot = { key: (0, import_node_crypto2.randomBytes)(32) };
158
+ holder[KEY_SLOT] = slot;
159
+ }
160
+ return slot;
161
+ }
162
+ function setFingerprintSecret(secret) {
163
+ fingerprintSlot().key = secret ? (0, import_node_crypto2.createHash)("sha256").update(`@oconde/radar:${secret}`).digest() : (0, import_node_crypto2.randomBytes)(32);
164
+ }
165
+ function fingerprint(value) {
166
+ return (0, import_node_crypto2.createHmac)("sha256", fingerprintSlot().key).update(value).digest("hex").slice(0, 8);
167
+ }
168
+ function words(name) {
169
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
170
+ }
171
+ function isSensitiveKey(key) {
172
+ const parts = words(key);
173
+ if (parts.length === 1 && parts[0] === "key") return true;
174
+ if (parts.some((part) => SECRET_WORDS.has(part))) return true;
175
+ return SECRET_PAIRS.some(([first, second]) => parts.some((part, index) => part === first && parts[index + 1] === second));
176
+ }
177
+ function isLowEntropyKey(key) {
178
+ return words(key).some((part) => LOW_ENTROPY_WORDS.has(part));
179
+ }
180
+ function isSensitiveHeader(name) {
181
+ return isSensitiveKey(name) || CUSTOM_SECRET_HEADER.test(name);
182
+ }
183
+ function looksLikeCredential(value) {
184
+ const head = value.slice(0, 64);
185
+ return AUTH_SCHEME.test(head.slice(0, 16)) || JWT_PREFIX.test(head);
186
+ }
187
+ function maskValue(value, revealEdges = true) {
188
+ const text = typeof value === "string" ? value : JSON.stringify(value) ?? String(value);
189
+ const scheme = AUTH_SCHEME.exec(text.slice(0, 16));
190
+ const prefix = scheme ? `${scheme[1]} ` : "";
191
+ const secret = scheme ? text.slice(scheme[0].length) : text;
192
+ const visible = revealEdges && secret.length > 12 ? `${secret.slice(0, 4)}\u2026${secret.slice(-4)}` : "\u2022\u2022\u2022";
193
+ return `${prefix}${visible} #${fingerprint(text)}`;
194
+ }
195
+ function redactDeep(value, mode, depth = 0) {
196
+ if (mode === "none" || depth > MAX_DEPTH || value === null) return value;
197
+ if (typeof value === "string") return looksLikeCredential(value) ? maskValue(value) : value;
198
+ if (typeof value !== "object") return value;
199
+ if (Array.isArray(value)) return value.map((item) => redactDeep(item, mode, depth + 1));
200
+ const result = {};
201
+ for (const [key, item] of Object.entries(value)) {
202
+ const hasValue = item !== null && item !== void 0 && item !== "";
203
+ result[key] = isSensitiveKey(key) && hasValue ? maskValue(item, !isLowEntropyKey(key)) : redactDeep(item, mode, depth + 1);
204
+ }
205
+ return result;
206
+ }
207
+
208
+ // src/serialize.ts
209
+ var TRUNCATED = "\u2026[cortado]";
210
+ var LIMIT_REACHED = "\u2026[limite]";
211
+ var MAX_NODES = 2e3;
212
+ function truncate(value, max = LIMITS.stringLength) {
213
+ return value.length > max ? value.slice(0, max) + TRUNCATED : value;
214
+ }
215
+ function sanitize(value, depth = 0, seen = /* @__PURE__ */ new WeakSet(), budget = { remaining: MAX_NODES }) {
216
+ if (value === null || value === void 0) return value;
217
+ budget.remaining -= 1;
218
+ switch (typeof value) {
219
+ case "string":
220
+ return truncate(value);
221
+ case "number":
222
+ return Number.isFinite(value) ? value : String(value);
223
+ case "boolean":
224
+ return value;
225
+ case "bigint":
226
+ return value.toString();
227
+ case "function":
228
+ case "symbol":
229
+ return void 0;
230
+ }
231
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.toISOString();
232
+ if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) return `[bin\xE1rio ${value.byteLength} bytes]`;
233
+ if (value instanceof Error) return { name: value.name, message: truncate(value.message) };
234
+ const object = value;
235
+ if (seen.has(object)) return "[circular]";
236
+ if (depth >= LIMITS.depth) return "[profundidade]";
237
+ if (budget.remaining <= 0) return LIMIT_REACHED;
238
+ seen.add(object);
239
+ try {
240
+ if (Array.isArray(object)) {
241
+ const items = [];
242
+ for (const item of object.slice(0, LIMITS.arrayItems)) {
243
+ if (budget.remaining <= 0) {
244
+ items.push(LIMIT_REACHED);
245
+ break;
246
+ }
247
+ items.push(sanitize(item, depth + 1, seen, budget) ?? null);
248
+ }
249
+ if (object.length > LIMITS.arrayItems) items.push(`\u2026[mais ${object.length - LIMITS.arrayItems}]`);
250
+ return items;
251
+ }
252
+ if (object instanceof Map) return sanitize(Object.fromEntries(object.entries()), depth + 1, seen, budget);
253
+ if (object instanceof Set) return sanitize([...object], depth + 1, seen, budget);
254
+ const result = {};
255
+ for (const key in object) {
256
+ if (!Object.prototype.hasOwnProperty.call(object, key)) continue;
257
+ if (budget.remaining <= 0) {
258
+ result["\u2026"] = LIMIT_REACHED;
259
+ break;
260
+ }
261
+ const clean = sanitize(object[key], depth + 1, seen, budget);
262
+ if (clean !== void 0) result[key] = clean;
263
+ }
264
+ return result;
265
+ } finally {
266
+ seen.delete(object);
267
+ }
268
+ }
269
+ function limitBytes(value, maxBytes) {
270
+ const json = JSON.stringify(value);
271
+ if (json === void 0 || Buffer.byteLength(json) <= maxBytes) return value;
272
+ return { _truncated: truncate(json, maxBytes) };
273
+ }
274
+ function prepareAttrs(data, mode) {
275
+ const clean = redactDeep(sanitize(data), mode);
276
+ const record = isRecord(clean) ? clean : { value: clean };
277
+ return limitBytes(record, LIMITS.attrsBytes);
278
+ }
279
+ function isRecord(value) {
280
+ return typeof value === "object" && value !== null && !Array.isArray(value);
281
+ }
282
+
283
+ // src/request.ts
284
+ function headerValue(value) {
285
+ if (value === void 0) return void 0;
286
+ return Array.isArray(value) ? value.join(", ") : String(value);
287
+ }
288
+ function requestHeaders(headers, mode) {
289
+ const result = {};
290
+ let count = 0;
291
+ for (const [name, raw] of Object.entries(headers)) {
292
+ const value = headerValue(raw);
293
+ if (value === void 0) continue;
294
+ if (count >= LIMITS.headers) break;
295
+ const key = name.toLowerCase();
296
+ result[key] = mode === "mask" && (isSensitiveHeader(key) || looksLikeCredential(value)) ? maskValue(value) : truncate(value);
297
+ count++;
298
+ }
299
+ return result;
300
+ }
301
+ function requestPath(req) {
302
+ const url = req.originalUrl ?? req.url ?? "";
303
+ const end = url.indexOf("?");
304
+ return end >= 0 ? url.slice(0, end) : url;
305
+ }
306
+ function requestRoute(req, route) {
307
+ if (route) return route;
308
+ return typeof req.route?.path === "string" ? req.route.path : void 0;
309
+ }
310
+ var ROUTE_PARAM = /^:([A-Za-z_$][\w$]*)/;
311
+ function pathSegments(value) {
312
+ const segments = value.split("/");
313
+ while (segments.length > 1 && segments[segments.length - 1] === "") segments.pop();
314
+ return segments;
315
+ }
316
+ function decodeSegment(value) {
317
+ try {
318
+ return decodeURIComponent(value);
319
+ } catch {
320
+ return value;
321
+ }
322
+ }
323
+ function routeParams(req, path) {
324
+ if (typeof req.route?.path !== "string") return {};
325
+ const pattern = pathSegments(`${typeof req.baseUrl === "string" ? req.baseUrl : ""}${req.route.path}`).filter((segment, index) => index > 0 || segment !== "");
326
+ const values = pathSegments(path);
327
+ if (pattern.length > values.length) return {};
328
+ const offset = values.length - pattern.length;
329
+ const result = {};
330
+ for (let index = 0; index < pattern.length; index++) {
331
+ const value = values[offset + index];
332
+ const param = ROUTE_PARAM.exec(pattern[index]);
333
+ if (!param) {
334
+ if (pattern[index] !== value) return {};
335
+ continue;
336
+ }
337
+ if (value) result[param[1]] = decodeSegment(value);
338
+ }
339
+ return result;
340
+ }
341
+ function pathParams(req, path) {
342
+ return { ...routeParams(req, path), ...req.params ?? {} };
343
+ }
344
+ function redactPath(path, params, mode) {
345
+ if (mode === "none" || !params) return path;
346
+ let result = path;
347
+ for (const [key, value] of Object.entries(params)) {
348
+ if (typeof value !== "string" || value.length < 4 || !isSensitiveKey(key)) continue;
349
+ const masked = maskValue(value, !isLowEntropyKey(key));
350
+ for (const form of /* @__PURE__ */ new Set([value, encodeURIComponent(value)])) result = result.split(form).join(masked);
351
+ }
352
+ return result;
353
+ }
354
+ function safeUrl(req, mode) {
355
+ const url = req.originalUrl ?? req.url ?? "";
356
+ const queryStart = url.indexOf("?");
357
+ const path = queryStart >= 0 ? url.slice(0, queryStart) : url;
358
+ const query = queryStart >= 0 ? url.slice(queryStart) : "";
359
+ return redactUrl(redactPath(path, pathParams(req, path), mode) + query, mode);
360
+ }
361
+ function redactUrl(url, mode) {
362
+ const queryStart = url.indexOf("?");
363
+ if (mode === "none" || queryStart < 0) return url;
364
+ const params = new URLSearchParams(url.slice(queryStart + 1));
365
+ let changed = false;
366
+ for (const [key, value] of [...params.entries()]) {
367
+ if (isSensitiveKey(key) && value !== "") {
368
+ params.set(key, maskValue(value, !isLowEntropyKey(key)));
369
+ changed = true;
370
+ }
371
+ }
372
+ return changed ? `${url.slice(0, queryStart)}?${params.toString()}` : url;
373
+ }
374
+ function requestInfo(req, mode, extra = {}, detail = "full") {
375
+ const route = requestRoute(req, extra.route);
376
+ if (detail === "route") {
377
+ return {
378
+ method: (req.method ?? "GET").toUpperCase(),
379
+ url: route ?? "",
380
+ ...route ? { route } : {},
381
+ ...extra.status !== void 0 ? { status: extra.status } : {},
382
+ ...extra.durationMs !== void 0 ? { durationMs: extra.durationMs } : {}
383
+ };
384
+ }
385
+ const params = prepareRecord(req.params, mode);
386
+ const query = prepareRecord(req.query, mode);
387
+ const body = prepareBody(req.body, mode);
388
+ const userAgent = headerValue(req.headers["user-agent"]);
389
+ const ip = req.ip ?? req.socket?.remoteAddress;
390
+ return {
391
+ method: (req.method ?? "GET").toUpperCase(),
392
+ url: truncate(safeUrl(req, mode)),
393
+ ...route ? { route } : {},
394
+ ...params ? { params } : {},
395
+ ...query ? { query } : {},
396
+ headers: requestHeaders(req.headers, mode),
397
+ ...body !== void 0 ? { body } : {},
398
+ ...ip ? { ip } : {},
399
+ ...userAgent ? { userAgent: truncate(userAgent, 500) } : {},
400
+ ...extra.status !== void 0 ? { status: extra.status } : {},
401
+ ...extra.durationMs !== void 0 ? { durationMs: extra.durationMs } : {}
402
+ };
403
+ }
404
+ function isEmptyRecord(value) {
405
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0;
406
+ }
407
+ function prepareRecord(value, mode) {
408
+ if (typeof value !== "object" || value === null) return void 0;
409
+ const clean = prepare(value, mode);
410
+ return isEmptyRecord(clean) || typeof clean !== "object" || clean === null ? void 0 : clean;
411
+ }
412
+ function prepare(value, mode) {
413
+ return redactDeep(sanitize(value), mode);
414
+ }
415
+ function prepareBody(body, mode) {
416
+ if (body === void 0 || body === null || body === "") return void 0;
417
+ const clean = prepare(body, mode);
418
+ if (isEmptyRecord(clean)) return void 0;
419
+ return limitBytes(clean, LIMITS.bodyBytes);
420
+ }
421
+
422
+ // src/express.ts
423
+ function createMiddleware(logger) {
424
+ return (req, res, next) => {
425
+ let context;
426
+ try {
427
+ const requestId = requestIdFrom(headerValue(req.headers["x-request-id"]));
428
+ context = { requestId, startedAt: import_node_perf_hooks.performance.now(), req };
429
+ res.setHeader("x-request-id", requestId);
430
+ if (logger.shouldLogRequest(req)) res.once("finish", () => logger.logHttpRequest(context, res.statusCode));
431
+ } catch {
432
+ next();
433
+ return;
434
+ }
435
+ runWithContext(context, () => next());
436
+ };
437
+ }
438
+
439
+ // src/options.ts
440
+ var DEFAULT_ENDPOINT = "https://radar-ingest.oconde.dev";
441
+ var LEVELS = ["debug", "info", "warn", "error"];
442
+ function resolveOptions(options = {}, env = process.env) {
443
+ const key = options.key?.trim() || void 0;
444
+ return {
445
+ key,
446
+ endpoint: (options.endpoint ?? DEFAULT_ENDPOINT).replace(/\/+$/, ""),
447
+ environment: options.environment ?? env.NODE_ENV ?? "production",
448
+ release: options.release || void 0,
449
+ enabled: options.enabled ?? Boolean(key),
450
+ console: options.console ?? false,
451
+ minLevel: options.minLevel && LEVELS.includes(options.minLevel) ? options.minLevel : "info",
452
+ redact: options.redact === "none" ? "none" : "mask",
453
+ requestDetail: options.requestDetail === "route" ? "route" : "full",
454
+ logRequests: options.logRequests ?? true,
455
+ ignorePaths: options.ignorePaths ?? ["/healthz", "/health"],
456
+ captureUnhandled: options.captureUnhandled ?? true,
457
+ debug: options.debug ?? false
458
+ };
459
+ }
460
+ function levelEnabled(level, minLevel) {
461
+ return LEVELS.indexOf(level) >= LEVELS.indexOf(minLevel);
462
+ }
463
+
464
+ // src/process-handlers.ts
465
+ var import_node_util = require("util");
466
+ var INSTALLED = /* @__PURE__ */ Symbol.for("@oconde/radar/process-handlers");
467
+ var MODE_FLAG = /^--unhandled-rejections=(\S+)$/;
468
+ function unhandledRejectionsMode(execArgv = process.execArgv, nodeOptions = process.env.NODE_OPTIONS ?? "") {
469
+ for (const arg of [...nodeOptions.split(/\s+/), ...execArgv]) {
470
+ const match = MODE_FLAG.exec(arg);
471
+ if (match?.[1]) return match[1];
472
+ }
473
+ return "throw";
474
+ }
475
+ function asUncaught(reason) {
476
+ if (reason instanceof Error) return reason;
477
+ const error = new Error(
478
+ `This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "${(0, import_node_util.inspect)(reason)}".`
479
+ );
480
+ error.code = "ERR_UNHANDLED_REJECTION";
481
+ return error;
482
+ }
483
+ function installProcessHandlers(capturer) {
484
+ const holder = process;
485
+ if (holder[INSTALLED]) return;
486
+ holder[INSTALLED] = true;
487
+ process.on("unhandledRejection", (reason) => {
488
+ const mode = unhandledRejectionsMode();
489
+ const othersListen = process.listenerCount("unhandledRejection") > 1;
490
+ if (othersListen || mode !== "throw") {
491
+ capturer.captureUnhandled(reason, "error");
492
+ if (!othersListen && mode === "warn-with-error-code") process.exitCode = 1;
493
+ return;
494
+ }
495
+ const uncaught = asUncaught(reason);
496
+ setImmediate(() => {
497
+ throw uncaught;
498
+ });
499
+ });
500
+ process.on("uncaughtException", (error) => {
501
+ capturer.captureUnhandled(error, "fatal");
502
+ if (process.listenerCount("uncaughtException") > 1) return;
503
+ try {
504
+ process.stderr.write(`${(0, import_node_util.inspect)(error)}
505
+ `);
506
+ } catch {
507
+ process.exitCode = 1;
508
+ }
509
+ void capturer.flush(2e3).finally(() => process.exit(1));
510
+ });
511
+ }
512
+
513
+ // src/source.ts
514
+ var import_promises = require("fs/promises");
515
+ var import_node_module = require("module");
516
+ var import_node_path = require("path");
517
+ var import_node_url = require("url");
518
+
519
+ // src/function-name.ts
520
+ var IDENTIFIER = "[A-Za-z_$][\\w$]*";
521
+ var MAX_LINES_UP = 400;
522
+ var MAX_HEADER_LINES = 6;
523
+ var MAX_NAME_LENGTH = 100;
524
+ var CONTROL_BLOCK = /^\s*(?:\}\s*)?(?:if|else|for|while|do|switch|try|catch|finally|with)\b/;
525
+ var CLASS_BODY = new RegExp(`\\bclass\\s+${IDENTIFIER}`);
526
+ var MARKUP_BOUNDARY = /<\/?(?:script|style|template)\b/i;
527
+ var KEYWORDS = /* @__PURE__ */ new Set(["if", "for", "while", "switch", "catch", "function", "return", "new", "typeof", "await", "else", "do", "try"]);
528
+ var NAME_PATTERNS = [
529
+ new RegExp(`\\bfunction\\s*\\*?\\s*(${IDENTIFIER})\\s*\\(`),
530
+ new RegExp(`(${IDENTIFIER})\\s*[:=]\\s*(?:async\\s+)?function\\b`),
531
+ new RegExp(`(${IDENTIFIER})\\s*(?::\\s*[^=]+)?=\\s*(?:async\\s+)?(?:\\([^]*\\)|${IDENTIFIER})\\s*(?::\\s*[^=]+)?=>\\s*$`),
532
+ new RegExp(`(?:^|[{,(]\\s*)(${IDENTIFIER})\\s*:\\s*(?:async\\s+)?(?:\\([^]*\\)|${IDENTIFIER})\\s*(?::\\s*[^=]+)?=>\\s*$`)
533
+ ];
534
+ var METHOD_PATTERN = new RegExp(`^\\s*(?:(?:export|default|public|private|protected|static|async|override|readonly|get|set)\\s+)*\\*?\\s*(${IDENTIFIER})\\s*(?:<[^>]*>)?\\s*\\([^]*\\)\\s*(?::\\s*[^{]+)?$`);
535
+ var ANONYMOUS_FUNCTION = /\bfunction\b|=>/;
536
+ function codeOnly(text) {
537
+ return text.replace(/(["'`])(?:\\.|(?!\1)[^\\])*\1/g, '""').replace(/\/\*.*?\*\//g, "").replace(/(^|[^:])\/\/.*$/, "$1");
538
+ }
539
+ function parenBalance(text) {
540
+ let balance = 0;
541
+ for (const char of text) {
542
+ if (char === "(") balance += 1;
543
+ else if (char === ")") balance -= 1;
544
+ }
545
+ return balance;
546
+ }
547
+ function headerOf(lines, index, prefix) {
548
+ let header = prefix;
549
+ for (let previous = index - 1; previous >= 0 && previous >= index - MAX_HEADER_LINES; previous--) {
550
+ if (header.trim() !== "" && parenBalance(header) >= 0) break;
551
+ header = `${codeOnly(lines[previous] ?? "")} ${header}`;
552
+ }
553
+ return header;
554
+ }
555
+ function nameIn(header) {
556
+ const patterns = ANONYMOUS_FUNCTION.test(header) ? NAME_PATTERNS : [...NAME_PATTERNS, METHOD_PATTERN];
557
+ for (const pattern of patterns) {
558
+ const name = pattern.exec(header)?.[1];
559
+ if (name && !KEYWORDS.has(name)) return name.slice(0, MAX_NAME_LENGTH);
560
+ }
561
+ return void 0;
562
+ }
563
+ function enclosingFunctionName(lines, line, column) {
564
+ const start = line - 1;
565
+ if (start < 0 || start >= lines.length) return void 0;
566
+ let depth = 0;
567
+ for (let index = start; index >= 0 && index >= start - MAX_LINES_UP; index--) {
568
+ const raw = lines[index] ?? "";
569
+ if (index < start && MARKUP_BOUNDARY.test(raw)) return void 0;
570
+ const code = codeOnly(index === start && column ? raw.slice(0, Math.max(0, column - 1)) : raw);
571
+ for (let position = code.length - 1; position >= 0; position--) {
572
+ const char = code[position];
573
+ if (char === "}") {
574
+ depth += 1;
575
+ } else if (char === "{") {
576
+ if (depth > 0) {
577
+ depth -= 1;
578
+ continue;
579
+ }
580
+ const header = headerOf(lines, index, code.slice(0, position));
581
+ if (CLASS_BODY.test(header)) return void 0;
582
+ if (CONTROL_BLOCK.test(header)) continue;
583
+ const name = nameIn(header);
584
+ if (name) return name;
585
+ }
586
+ }
587
+ }
588
+ return void 0;
589
+ }
590
+
591
+ // src/source.ts
592
+ var MAX_FILE_BYTES = 2 * 1024 * 1024;
593
+ var MAX_CACHED_FILES = 50;
594
+ var MAPPING_URL = /\/\/[#@]\s*sourceMappingURL=(\S+)\s*$/;
595
+ var cache = /* @__PURE__ */ new Map();
596
+ async function enrichFrames(frames, cwd = process.cwd(), options = {}) {
597
+ const result = [];
598
+ let withContext = 0;
599
+ for (const frame of frames) {
600
+ try {
601
+ const { frame: located, lines, original } = await mapFrame(frame, options.mapLocator);
602
+ const named = options.inferFunctionNames && original && !located.fn && lines && located.line ? withInferredName(located, lines) : located;
603
+ const mapped = named.inApp && isLibraryPath(named.file) ? { ...named, inApp: false } : named;
604
+ let next = mapped;
605
+ if (mapped.inApp && lines && mapped.line && withContext < LIMITS.contextFrames) {
606
+ const context = contextFor(lines, mapped.line);
607
+ if (context) {
608
+ next = { ...mapped, context };
609
+ withContext++;
610
+ }
611
+ }
612
+ result.push({ ...next, file: relativize(next.file, cwd) });
613
+ } catch {
614
+ result.push({ ...frame, file: relativize(frame.file, cwd) });
615
+ }
616
+ }
617
+ return result;
618
+ }
619
+ async function enrichException(exception, cwd = process.cwd(), options = {}) {
620
+ const frames = await enrichFrames(exception.frames, cwd, options);
621
+ if (!exception.cause) return { ...exception, frames };
622
+ return { ...exception, frames, cause: await enrichException(exception.cause, cwd, options) };
623
+ }
624
+ function withInferredName(frame, lines) {
625
+ const fn = enclosingFunctionName(lines, frame.line ?? 0, frame.col);
626
+ return fn ? { ...frame, fn } : frame;
627
+ }
628
+ function isLibraryPath(file) {
629
+ return file.includes("/node_modules/") || file.includes("\\node_modules\\");
630
+ }
631
+ function relativize(file, cwd) {
632
+ if (!(0, import_node_path.isAbsolute)(file)) return file;
633
+ const path = (0, import_node_path.relative)(cwd, file);
634
+ return path.startsWith("..") || (0, import_node_path.isAbsolute)(path) ? file : path.split(import_node_path.sep).join("/");
635
+ }
636
+ async function mapFrame(frame, locator) {
637
+ if (!frame.line || !(0, import_node_path.isAbsolute)(frame.file)) return { frame, lines: null, original: false };
638
+ const file = await loadFile(frame.file, locator);
639
+ if (!file) return { frame, lines: null, original: false };
640
+ if (!file.map) return { frame, lines: file.lines, original: false };
641
+ const entry = file.map.map.findEntry(frame.line - 1, (frame.col ?? 1) - 1);
642
+ if (typeof entry.originalSource !== "string" || typeof entry.originalLine !== "number") return { frame, lines: file.lines, original: false };
643
+ const originalPath = resolveSource(file.map, entry.originalSource);
644
+ const mapped = { ...frame, file: originalPath, line: entry.originalLine + 1, col: (entry.originalColumn ?? 0) + 1 };
645
+ const lines = embeddedLines(file.map, file.map.sources.indexOf(entry.originalSource));
646
+ if (lines) return { frame: mapped, lines, original: true };
647
+ const original = await loadFile(originalPath);
648
+ return { frame: mapped, lines: original?.lines ?? null, original: true };
649
+ }
650
+ function embeddedLines(map, index) {
651
+ if (index < 0) return null;
652
+ const cached = map.lines.get(index);
653
+ if (cached) return cached;
654
+ const content = map.contents[index];
655
+ if (typeof content !== "string") return null;
656
+ const lines = content.split(/\r?\n/);
657
+ map.lines.set(index, lines);
658
+ return lines;
659
+ }
660
+ function contextFor(lines, line) {
661
+ const index = line - 1;
662
+ if (index < 0 || index >= lines.length) return void 0;
663
+ const clip = (text) => text.length > LIMITS.contextLineLength ? `${text.slice(0, LIMITS.contextLineLength)}\u2026` : text;
664
+ return {
665
+ pre: lines.slice(Math.max(0, index - LIMITS.contextLines), index).map(clip),
666
+ line: clip(lines[index] ?? ""),
667
+ post: lines.slice(index + 1, index + 1 + LIMITS.contextLines).map(clip)
668
+ };
669
+ }
670
+ function resolveSource(map, source) {
671
+ if (source.startsWith("file://")) {
672
+ try {
673
+ return (0, import_node_url.fileURLToPath)(source);
674
+ } catch {
675
+ return source;
676
+ }
677
+ }
678
+ return (0, import_node_path.isAbsolute)(source) ? source : (0, import_node_path.resolve)(map.baseDir, source);
679
+ }
680
+ function loadFile(path, locator) {
681
+ const key = locator ? `${path}\0located` : path;
682
+ const cached = cache.get(key);
683
+ if (cached) {
684
+ cache.delete(key);
685
+ cache.set(key, cached);
686
+ return cached;
687
+ }
688
+ const pending = readSource(path, locator).catch(() => null);
689
+ cache.set(key, pending);
690
+ if (cache.size > MAX_CACHED_FILES) {
691
+ const oldest = cache.keys().next().value;
692
+ if (oldest !== void 0) cache.delete(oldest);
693
+ }
694
+ return pending;
695
+ }
696
+ async function readSource(path, locator) {
697
+ const text = await readText(path);
698
+ if (text === null) return null;
699
+ const lines = text.split(/\r?\n/);
700
+ return { lines, map: await loadMap(path, lines, locator) };
701
+ }
702
+ async function readText(path) {
703
+ try {
704
+ const info = await (0, import_promises.stat)(path);
705
+ if (!info.isFile() || info.size > MAX_FILE_BYTES) return null;
706
+ return await (0, import_promises.readFile)(path, "utf8");
707
+ } catch {
708
+ return null;
709
+ }
710
+ }
711
+ async function loadMap(path, lines, locator) {
712
+ let url;
713
+ for (let index = lines.length - 1; index >= 0 && index >= lines.length - 5; index--) {
714
+ const match = MAPPING_URL.exec(lines[index] ?? "");
715
+ if (match) {
716
+ url = match[1];
717
+ break;
718
+ }
719
+ }
720
+ let raw;
721
+ let mapDir;
722
+ const located = url ? null : locator ? locator(path) : `${path}.map`;
723
+ if (located) {
724
+ raw = await readText(located);
725
+ mapDir = (0, import_node_path.dirname)(located);
726
+ } else if (!url) {
727
+ return null;
728
+ } else if (url.startsWith("data:")) {
729
+ const comma = url.indexOf(",");
730
+ const encoded = url.slice(comma + 1);
731
+ raw = url.slice(0, comma).endsWith(";base64") ? Buffer.from(encoded, "base64").toString("utf8") : decodeURIComponent(encoded);
732
+ mapDir = (0, import_node_path.dirname)(path);
733
+ } else {
734
+ const mapPath = url.startsWith("file://") ? (0, import_node_url.fileURLToPath)(url) : (0, import_node_path.resolve)((0, import_node_path.dirname)(path), decodeURIComponent(url));
735
+ raw = await readText(mapPath);
736
+ mapDir = (0, import_node_path.dirname)(mapPath);
737
+ }
738
+ if (!raw) return null;
739
+ try {
740
+ const payload = JSON.parse(raw);
741
+ return {
742
+ map: new import_node_module.SourceMap(payload),
743
+ baseDir: (0, import_node_path.resolve)(mapDir, payload.sourceRoot ?? ""),
744
+ sources: payload.sources ?? [],
745
+ contents: payload.sourcesContent ?? [],
746
+ lines: /* @__PURE__ */ new Map()
747
+ };
748
+ } catch {
749
+ return null;
750
+ }
751
+ }
752
+
753
+ // src/stack.ts
754
+ var import_node_url2 = require("url");
755
+ var WITH_FUNCTION = /^at (?:async )?(.+?) \((.+)\)$/;
756
+ var LOCATION = /^(.+):(\d+):(\d+)$/;
757
+ var MAX_FRAME_LINE = 1e3;
758
+ function parseStack(stack) {
759
+ if (!stack) return [];
760
+ const frames = [];
761
+ for (const raw of stack.split("\n")) {
762
+ const line = raw.trim();
763
+ if (line.length > MAX_FRAME_LINE || !line.startsWith("at ")) continue;
764
+ const withFunction = WITH_FUNCTION.exec(line);
765
+ const fn = withFunction?.[1];
766
+ const location = withFunction ? withFunction[2] ?? "" : line.replace(/^at (?:async )?/, "");
767
+ const parsed = LOCATION.exec(location);
768
+ if (parsed) {
769
+ const file = toPath(parsed[1] ?? "");
770
+ frames.push({ ...fn ? { fn } : {}, file, line: Number(parsed[2]), col: Number(parsed[3]), inApp: isInApp(file) });
771
+ } else {
772
+ frames.push({ ...fn ? { fn } : {}, file: location, inApp: false });
773
+ }
774
+ if (frames.length >= LIMITS.frames) break;
775
+ }
776
+ return frames;
777
+ }
778
+ function isInApp(file) {
779
+ if (file.includes("/node_modules/") || file.includes("\\node_modules\\")) return false;
780
+ if (file.startsWith("node:") || file.startsWith("internal/")) return false;
781
+ return file.startsWith("/") || /^[A-Za-z]:[\\/]/.test(file);
782
+ }
783
+ function exceptionInfo(error, depth = 0) {
784
+ if (error instanceof Error) {
785
+ const info = {
786
+ type: errorType(error),
787
+ message: truncate(error.message ?? "", LIMITS.messageLength),
788
+ frames: parseStack(error.stack)
789
+ };
790
+ const cause = error.cause;
791
+ if (cause !== void 0 && cause !== null && depth < LIMITS.causeDepth) info.cause = exceptionInfo(cause, depth + 1);
792
+ return info;
793
+ }
794
+ if (typeof error === "object" && error !== null) {
795
+ const record = error;
796
+ const message = typeof record.message === "string" ? record.message : safeStringify(error);
797
+ return { type: typeof record.name === "string" ? record.name : "Object", message: truncate(message, LIMITS.messageLength), frames: [] };
798
+ }
799
+ return { type: "Error", message: truncate(String(error), LIMITS.messageLength), frames: [] };
800
+ }
801
+ function toPath(location) {
802
+ if (!location.startsWith("file://")) return location;
803
+ try {
804
+ return (0, import_node_url2.fileURLToPath)(location);
805
+ } catch {
806
+ return location;
807
+ }
808
+ }
809
+ function errorType(error) {
810
+ if (error.name && error.name !== "Error") return error.name;
811
+ const constructorName = error.constructor?.name;
812
+ return constructorName && constructorName !== "Object" ? constructorName : "Error";
813
+ }
814
+ function safeStringify(value) {
815
+ try {
816
+ return JSON.stringify(value) ?? String(value);
817
+ } catch {
818
+ return String(value);
819
+ }
820
+ }
821
+
822
+ // src/transport.ts
823
+ var import_node_util2 = require("util");
824
+ var import_node_zlib = require("zlib");
825
+
826
+ // src/util.ts
827
+ var import_node_perf_hooks2 = require("perf_hooks");
828
+ function describeError(error) {
829
+ return error instanceof Error ? error.message : String(error);
830
+ }
831
+ function elapsedSince(startedAt) {
832
+ return Math.round(import_node_perf_hooks2.performance.now() - startedAt);
833
+ }
834
+
835
+ // src/transport.ts
836
+ var gzipAsync = (0, import_node_util2.promisify)(import_node_zlib.gzip);
837
+ var MAX_BATCH_BYTES = 9e5;
838
+ var GZIP_THRESHOLD_BYTES = 8192;
839
+ var MAX_BACKOFF_MS = 3e4;
840
+ var MAX_RETRY_AFTER_MS = 864e5;
841
+ var UNAUTHORIZED_PAUSE_MS = 10 * 6e4;
842
+ var Transport = class {
843
+ constructor(config) {
844
+ this.config = config;
845
+ this.maxQueue = config.maxQueue ?? 1e3;
846
+ this.batchSize = Math.min(config.batchSize ?? 100, LIMITS.batchEvents);
847
+ this.timer = setInterval(() => {
848
+ void this.send();
849
+ }, config.flushIntervalMs ?? 2e3);
850
+ this.timer.unref();
851
+ }
852
+ config;
853
+ queue = [];
854
+ droppedCount = 0;
855
+ inFlight = null;
856
+ nextAttemptAt = 0;
857
+ backoffMs = 0;
858
+ unauthorizedUntil = 0;
859
+ stopped = false;
860
+ warned = /* @__PURE__ */ new Set();
861
+ timer;
862
+ maxQueue;
863
+ batchSize;
864
+ get pending() {
865
+ return this.queue.length;
866
+ }
867
+ get dropped() {
868
+ return this.droppedCount;
869
+ }
870
+ enqueue(event) {
871
+ if (this.stopped) return;
872
+ if (this.queue.length >= this.maxQueue) {
873
+ this.droppedCount += 1;
874
+ this.warn("queue_full", "fila do Radar cheia; eventos est\xE3o sendo descartados", false);
875
+ return;
876
+ }
877
+ this.queue.push(event);
878
+ if (this.queue.length >= this.batchSize) void this.send();
879
+ }
880
+ send() {
881
+ if (this.inFlight) return this.inFlight;
882
+ if (this.stopped || this.queue.length === 0 || Date.now() < this.nextAttemptAt) return Promise.resolve(false);
883
+ const attempt = this.sendBatch().catch((error) => {
884
+ this.warn("internal", `envio ao Radar falhou: ${describeError(error)}`, false);
885
+ return false;
886
+ });
887
+ this.inFlight = attempt.finally(() => {
888
+ this.inFlight = null;
889
+ });
890
+ return this.inFlight;
891
+ }
892
+ async flush(timeoutMs) {
893
+ const deadline = Date.now() + timeoutMs;
894
+ while (this.queue.length > 0 || this.inFlight) {
895
+ const remaining = deadline - Date.now();
896
+ if (remaining <= 0) return;
897
+ const progressed = await withTimeout(this.send(), remaining);
898
+ if (progressed !== true && !this.inFlight) return;
899
+ }
900
+ }
901
+ stop() {
902
+ this.stopped = true;
903
+ clearInterval(this.timer);
904
+ }
905
+ async sendBatch() {
906
+ const items = this.queue.splice(0, this.batchSize);
907
+ const resolved = await Promise.all(items.map((item) => Promise.resolve().then(() => typeof item === "function" ? item() : item).then((event) => event, () => null)));
908
+ const events = [];
909
+ const leftovers = [];
910
+ let bytes = 0;
911
+ for (const event of resolved) {
912
+ if (!event) {
913
+ this.droppedCount += 1;
914
+ continue;
915
+ }
916
+ const size = Buffer.byteLength(JSON.stringify(event));
917
+ if (events.length > 0 && bytes + size > MAX_BATCH_BYTES) {
918
+ leftovers.push(event);
919
+ continue;
920
+ }
921
+ events.push(event);
922
+ bytes += size;
923
+ }
924
+ if (leftovers.length > 0) this.queue.unshift(...leftovers);
925
+ if (events.length === 0) return true;
926
+ const dropped = this.droppedCount;
927
+ const batch = {
928
+ v: 1,
929
+ sdk: this.config.sdk,
930
+ environment: this.config.environment,
931
+ ...this.config.release ? { release: this.config.release } : {},
932
+ ...dropped > 0 ? { dropped } : {},
933
+ events
934
+ };
935
+ const outcome = await this.post(batch);
936
+ switch (outcome.kind) {
937
+ case "ok":
938
+ this.droppedCount -= dropped;
939
+ this.backoffMs = 0;
940
+ this.nextAttemptAt = 0;
941
+ return true;
942
+ case "retry":
943
+ this.requeue(events);
944
+ this.backoffMs = Math.min(this.backoffMs > 0 ? this.backoffMs * 2 : 1e3, MAX_BACKOFF_MS);
945
+ this.nextAttemptAt = Date.now() + Math.max(this.backoffMs, outcome.retryAfterMs ?? 0);
946
+ return false;
947
+ case "unauthorized":
948
+ this.droppedCount += events.length;
949
+ this.pauseUnauthorized(outcome.status);
950
+ return false;
951
+ case "rejected":
952
+ this.droppedCount += events.length;
953
+ this.warn(`rejected_${outcome.status}`, `lote recusado pelo Radar (${outcome.status}); eventos descartados`, true);
954
+ return true;
955
+ }
956
+ }
957
+ async checkIn(slug, payload) {
958
+ if (this.stopped || !this.config.checkInBaseUrl || Date.now() < this.unauthorizedUntil) return;
959
+ let response;
960
+ try {
961
+ response = await fetch(this.config.checkInBaseUrl + encodeURIComponent(slug), {
962
+ method: "POST",
963
+ headers: this.headers(),
964
+ body: JSON.stringify(payload),
965
+ redirect: "error",
966
+ signal: AbortSignal.timeout(this.config.requestTimeoutMs ?? 5e3)
967
+ });
968
+ } catch (error) {
969
+ this.warn("checkin_network", `check-in no Radar falhou: ${describeError(error)}`, false);
970
+ return;
971
+ }
972
+ await response.arrayBuffer().catch(() => void 0);
973
+ if (response.ok) return;
974
+ if (response.status === 401 || response.status === 403) {
975
+ this.pauseUnauthorized(response.status);
976
+ return;
977
+ }
978
+ if (response.status === 404) {
979
+ this.warn(`checkin_unknown_${slug}`, `tarefa agendada "${slug}" n\xE3o existe no projeto da chave; crie o monitor com esse slug no Radar`, true);
980
+ return;
981
+ }
982
+ this.warn(`checkin_rejected_${response.status}`, `check-in recusado pelo Radar (${response.status})`, false);
983
+ }
984
+ pauseUnauthorized(status) {
985
+ this.droppedCount += this.queue.length;
986
+ this.queue = [];
987
+ this.unauthorizedUntil = Date.now() + UNAUTHORIZED_PAUSE_MS;
988
+ this.nextAttemptAt = Math.max(this.nextAttemptAt, this.unauthorizedUntil);
989
+ this.warn("unauthorized", `chave do Radar recusada (${status}); envios pausados por 10 minutos`, true);
990
+ }
991
+ headers() {
992
+ return {
993
+ "content-type": "application/json",
994
+ authorization: `Bearer ${this.config.key}`,
995
+ "user-agent": `${this.config.sdk.name}/${this.config.sdk.version}`
996
+ };
997
+ }
998
+ requeue(events) {
999
+ this.queue.unshift(...events);
1000
+ const overflow = this.queue.length - this.maxQueue;
1001
+ if (overflow > 0) {
1002
+ this.queue.splice(this.maxQueue, overflow);
1003
+ this.droppedCount += overflow;
1004
+ }
1005
+ }
1006
+ async post(batch) {
1007
+ const json = JSON.stringify(batch);
1008
+ const compress = Buffer.byteLength(json) > GZIP_THRESHOLD_BYTES;
1009
+ const headers = this.headers();
1010
+ if (compress) headers["content-encoding"] = "gzip";
1011
+ let response;
1012
+ try {
1013
+ response = await fetch(this.config.url, {
1014
+ method: "POST",
1015
+ headers,
1016
+ body: compress ? await gzipAsync(json) : json,
1017
+ redirect: "error",
1018
+ signal: AbortSignal.timeout(this.config.requestTimeoutMs ?? 5e3)
1019
+ });
1020
+ } catch (error) {
1021
+ this.warn("network", `Radar indispon\xEDvel: ${describeError(error)}`, false);
1022
+ return { kind: "retry" };
1023
+ }
1024
+ await response.arrayBuffer().catch(() => void 0);
1025
+ if (response.ok) return { kind: "ok" };
1026
+ if (response.status === 401 || response.status === 403) return { kind: "unauthorized", status: response.status };
1027
+ if (response.status === 429) return { kind: "retry", retryAfterMs: retryAfterMs(response.headers.get("retry-after")) };
1028
+ if (response.status >= 500) return { kind: "retry" };
1029
+ return { kind: "rejected", status: response.status };
1030
+ }
1031
+ warn(key, message, always) {
1032
+ if (this.warned.has(key)) return;
1033
+ this.warned.add(key);
1034
+ this.config.onWarning?.(message, always);
1035
+ }
1036
+ };
1037
+ function retryAfterMs(header) {
1038
+ if (!header) return void 0;
1039
+ const seconds = Number(header);
1040
+ if (Number.isFinite(seconds)) return Math.min(Math.max(0, seconds * 1e3), MAX_RETRY_AFTER_MS);
1041
+ const date = Date.parse(header);
1042
+ return Number.isNaN(date) ? void 0 : Math.min(Math.max(0, date - Date.now()), MAX_RETRY_AFTER_MS);
1043
+ }
1044
+ function withTimeout(promise, ms) {
1045
+ return new Promise((resolve2) => {
1046
+ const timer = setTimeout(() => resolve2(void 0), ms);
1047
+ timer.unref();
1048
+ promise.then(
1049
+ (value) => {
1050
+ clearTimeout(timer);
1051
+ resolve2(value);
1052
+ },
1053
+ () => {
1054
+ clearTimeout(timer);
1055
+ resolve2(void 0);
1056
+ }
1057
+ );
1058
+ });
1059
+ }
1060
+
1061
+ // src/version.ts
1062
+ var SDK_NAME = "@oconde/radar";
1063
+ var SDK_VERSION = true ? "0.2.0" : "0.0.0-dev";
1064
+
1065
+ // src/client.ts
1066
+ var MAX_CHECK_IN_DURATION_MS = 7 * 864e5;
1067
+ var MAX_CHECK_IN_ENVIRONMENT = 64;
1068
+ var MAX_CHECK_IN_RELEASE = 128;
1069
+ var RadarClient = class {
1070
+ options = resolveOptions({}, {});
1071
+ transport = null;
1072
+ initialized = false;
1073
+ closed = false;
1074
+ captured = /* @__PURE__ */ new WeakSet();
1075
+ get isEnabled() {
1076
+ return this.transport !== null && !this.closed;
1077
+ }
1078
+ get settings() {
1079
+ return this.options;
1080
+ }
1081
+ init(options = {}) {
1082
+ try {
1083
+ if (this.initialized) {
1084
+ this.debugWarn("radar.init chamado mais de uma vez; mantida a primeira configura\xE7\xE3o");
1085
+ return;
1086
+ }
1087
+ this.initialized = true;
1088
+ this.options = resolveOptions(options);
1089
+ setFingerprintSecret(this.options.key);
1090
+ if (this.options.enabled && this.options.key) {
1091
+ this.transport = new Transport({
1092
+ url: this.options.endpoint + INGEST_PATH,
1093
+ key: this.options.key,
1094
+ sdk: { name: SDK_NAME, version: SDK_VERSION },
1095
+ environment: this.options.environment,
1096
+ ...this.options.release ? { release: this.options.release } : {},
1097
+ checkInBaseUrl: this.options.endpoint + CHECKIN_PATH,
1098
+ onWarning: (message, always) => always ? this.writeWarning(message) : this.debugWarn(message)
1099
+ });
1100
+ }
1101
+ if (this.options.captureUnhandled && (this.transport || this.options.console)) installProcessHandlers(this);
1102
+ } catch (error) {
1103
+ this.debugWarn(`radar.init falhou: ${describeError(error)}`);
1104
+ }
1105
+ }
1106
+ debug(message, data) {
1107
+ this.safely(() => this.emitLog("debug", message, data));
1108
+ }
1109
+ info(message, data) {
1110
+ this.safely(() => this.emitLog("info", message, data));
1111
+ }
1112
+ warn(message, data) {
1113
+ this.safely(() => this.emitLog("warn", message, data));
1114
+ }
1115
+ error(message, data) {
1116
+ this.safely(() => this.emitLog("error", message, data));
1117
+ }
1118
+ logRequest(title, data, options = {}) {
1119
+ this.safely(() => {
1120
+ const context = currentContext();
1121
+ const mode = options.redact ?? this.options.redact;
1122
+ const request = context?.req ? requestInfo(context.req, mode, { route: context.route }, this.options.requestDetail) : void 0;
1123
+ this.emitLog(options.level ?? "info", title, data, { request, mode });
1124
+ });
1125
+ }
1126
+ captureError(error, data) {
1127
+ this.safely(() => this.emitError(error, data, "error", true));
1128
+ }
1129
+ captureUnhandled(error, level) {
1130
+ this.safely(() => this.emitError(error, void 0, level, false));
1131
+ }
1132
+ setUser(user) {
1133
+ this.safely(() => {
1134
+ const context = currentContext();
1135
+ if (!context) return;
1136
+ context.user = {
1137
+ ...user.id !== void 0 && user.id !== null ? { id: truncate(String(user.id), 200) } : {},
1138
+ ...user.email ? { email: truncate(String(user.email), 200) } : {},
1139
+ ...user.name ? { name: truncate(String(user.name), 200) } : {}
1140
+ };
1141
+ });
1142
+ }
1143
+ async track(name, fn, data) {
1144
+ const execute = async () => {
1145
+ const started = import_node_perf_hooks3.performance.now();
1146
+ try {
1147
+ const result = await fn();
1148
+ this.info(name, { ...data, durationMs: elapsedSince(started), ok: true });
1149
+ return result;
1150
+ } catch (error) {
1151
+ this.error(name, { ...data, durationMs: elapsedSince(started), ok: false });
1152
+ this.captureError(error, data);
1153
+ throw error;
1154
+ }
1155
+ };
1156
+ if (currentContext()) return execute();
1157
+ return runWithContext({ requestId: (0, import_node_crypto3.randomUUID)(), startedAt: import_node_perf_hooks3.performance.now() }, execute);
1158
+ }
1159
+ withContext(fn, options = {}) {
1160
+ return runWithContext({ requestId: requestIdFrom(options.requestId), startedAt: import_node_perf_hooks3.performance.now() }, fn);
1161
+ }
1162
+ async checkIn(slug, status, options = {}) {
1163
+ try {
1164
+ if (!this.transport || this.closed) return;
1165
+ if (typeof slug !== "string" || !CHECKIN_SLUG.test(slug)) {
1166
+ this.debugWarn(`slug de tarefa agendada inv\xE1lido: ${String(slug).slice(0, 80)}`);
1167
+ return;
1168
+ }
1169
+ if (!CHECKIN_STATUSES.includes(status)) return;
1170
+ const checkInId = typeof options.checkInId === "string" && options.checkInId ? options.checkInId.slice(0, LIMITS.checkInIdLength) : void 0;
1171
+ const durationMs = typeof options.durationMs === "number" && Number.isFinite(options.durationMs) && options.durationMs >= 0 ? Math.min(Math.round(options.durationMs), MAX_CHECK_IN_DURATION_MS) : void 0;
1172
+ const environment = this.options.environment.slice(0, MAX_CHECK_IN_ENVIRONMENT) || "production";
1173
+ const release = this.options.release?.slice(0, MAX_CHECK_IN_RELEASE);
1174
+ const payload = {
1175
+ status,
1176
+ ...checkInId ? { checkInId } : {},
1177
+ ...durationMs !== void 0 ? { durationMs } : {},
1178
+ environment,
1179
+ ...release ? { release } : {}
1180
+ };
1181
+ await this.transport.checkIn(slug, payload);
1182
+ } catch (error) {
1183
+ this.debugWarn(`check-in falhou: ${describeError(error)}`);
1184
+ }
1185
+ }
1186
+ async cron(slug, fn) {
1187
+ const checkInId = (0, import_node_crypto3.randomUUID)();
1188
+ const started = import_node_perf_hooks3.performance.now();
1189
+ const execute = async () => {
1190
+ await this.checkIn(slug, "in_progress", { checkInId });
1191
+ try {
1192
+ const result = await fn();
1193
+ await this.checkIn(slug, "ok", { checkInId, durationMs: elapsedSince(started) });
1194
+ return result;
1195
+ } catch (error) {
1196
+ this.captureError(error, { cron: slug });
1197
+ await this.checkIn(slug, "error", { checkInId, durationMs: elapsedSince(started) });
1198
+ throw error;
1199
+ }
1200
+ };
1201
+ if (currentContext()) return execute();
1202
+ return this.withContext(execute);
1203
+ }
1204
+ middleware() {
1205
+ return createMiddleware(this);
1206
+ }
1207
+ shouldLogRequest(req) {
1208
+ return this.options.logRequests && !this.options.ignorePaths.includes(requestPath(req));
1209
+ }
1210
+ logHttpRequest(context, status) {
1211
+ this.safely(() => {
1212
+ const req = context.req;
1213
+ if (!req) return;
1214
+ const level = status >= 500 ? "error" : status >= 400 ? "warn" : "info";
1215
+ const route = requestRoute(req, context.route);
1216
+ runWithContext(
1217
+ context,
1218
+ () => this.emitLog(level, "http.request", {
1219
+ method: (req.method ?? "GET").toUpperCase(),
1220
+ ...route ? { route } : {},
1221
+ ...this.options.requestDetail === "full" ? { path: redactPath(requestPath(req), pathParams(req, requestPath(req)), this.options.redact) } : {},
1222
+ status,
1223
+ durationMs: elapsedSince(context.startedAt)
1224
+ })
1225
+ );
1226
+ });
1227
+ }
1228
+ async flush(timeoutMs = 2e3) {
1229
+ try {
1230
+ await this.transport?.flush(timeoutMs);
1231
+ } catch (error) {
1232
+ this.debugWarn(`flush falhou: ${describeError(error)}`);
1233
+ }
1234
+ }
1235
+ async close(timeoutMs = 2e3) {
1236
+ if (this.closed) return;
1237
+ await this.flush(timeoutMs);
1238
+ this.closed = true;
1239
+ this.transport?.stop();
1240
+ }
1241
+ emitLog(level, message, data, extras = {}) {
1242
+ if (this.closed || !levelEnabled(level, this.options.minLevel)) return;
1243
+ const context = currentContext();
1244
+ const mode = extras.mode ?? this.options.redact;
1245
+ const event = {
1246
+ type: "log",
1247
+ ts: Date.now(),
1248
+ level,
1249
+ message: truncate(String(message), LIMITS.messageLength),
1250
+ ...context ? { requestId: context.requestId } : {},
1251
+ ...data !== void 0 ? { attrs: prepareAttrs(data, mode) } : {},
1252
+ ...extras.request ? { request: extras.request } : {},
1253
+ ...context?.user ? { user: context.user } : {}
1254
+ };
1255
+ this.print(event);
1256
+ this.transport?.enqueue(event);
1257
+ }
1258
+ captureException(exception, options = {}) {
1259
+ this.safely(() => this.emitException(exception, options));
1260
+ }
1261
+ emitError(error, data, level, handled) {
1262
+ if (this.closed) return;
1263
+ if (typeof error === "object" && error !== null) {
1264
+ if (this.captured.has(error)) return;
1265
+ this.captured.add(error);
1266
+ }
1267
+ this.emitException(exceptionInfo(error), { level, handled, ...data !== void 0 ? { attrs: data } : {} }, error instanceof Error ? error.stack : void 0);
1268
+ }
1269
+ emitException(exception, options, stack) {
1270
+ if (this.closed) return;
1271
+ const context = currentContext();
1272
+ const mode = this.options.redact;
1273
+ const request = options.request === null ? void 0 : options.request ?? (context?.req ? requestInfo(context.req, mode, { route: context.route }, this.options.requestDetail) : void 0);
1274
+ const runtime = options.runtime === void 0 ? runtimeInfo() : options.runtime;
1275
+ const event = {
1276
+ type: "error",
1277
+ ts: Date.now(),
1278
+ level: options.level ?? "error",
1279
+ handled: options.handled ?? true,
1280
+ exception,
1281
+ ...context ? { requestId: context.requestId } : {},
1282
+ ...request ? { request } : {},
1283
+ ...context?.user ? { user: context.user } : {},
1284
+ ...runtime ? { runtime } : {},
1285
+ ...options.attrs !== void 0 ? { attrs: prepareAttrs(options.attrs, mode) } : {}
1286
+ };
1287
+ this.print(event, stack);
1288
+ if (!this.transport) return;
1289
+ this.transport.enqueue(
1290
+ () => enrichException(event.exception, process.cwd(), options.enrich).then(
1291
+ (enriched) => ({ ...event, exception: enriched }),
1292
+ () => event
1293
+ )
1294
+ );
1295
+ }
1296
+ print(event, stack) {
1297
+ if (!this.options.console) return;
1298
+ try {
1299
+ const requestId = event.requestId ? { requestId: event.requestId } : {};
1300
+ if (event.type === "log") {
1301
+ const line2 = { ...event.attrs, ts: new Date(event.ts).toISOString(), level: event.level, message: event.message, ...requestId };
1302
+ (event.level === "error" ? process.stderr : process.stdout).write(`${JSON.stringify(line2)}
1303
+ `);
1304
+ return;
1305
+ }
1306
+ const line = {
1307
+ ...event.attrs,
1308
+ ts: new Date(event.ts).toISOString(),
1309
+ level: event.level,
1310
+ message: `${event.exception.type}: ${event.exception.message}`,
1311
+ ...requestId,
1312
+ ...stack ? { stack } : {}
1313
+ };
1314
+ process.stderr.write(`${JSON.stringify(line)}
1315
+ `);
1316
+ } catch {
1317
+ return;
1318
+ }
1319
+ }
1320
+ safely(action) {
1321
+ try {
1322
+ action();
1323
+ } catch (error) {
1324
+ this.debugWarn(`falha interna do Radar: ${describeError(error)}`);
1325
+ }
1326
+ }
1327
+ debugWarn(message) {
1328
+ if (this.options.debug) this.writeWarning(message);
1329
+ }
1330
+ writeWarning(message) {
1331
+ try {
1332
+ process.stderr.write(`[radar] ${message}
1333
+ `);
1334
+ } catch {
1335
+ return;
1336
+ }
1337
+ }
1338
+ };
1339
+ function runtimeInfo() {
1340
+ return {
1341
+ name: "node",
1342
+ version: process.versions.node,
1343
+ host: (0, import_node_os.hostname)(),
1344
+ pid: process.pid,
1345
+ memoryMb: Math.round(process.memoryUsage.rss() / 1048576),
1346
+ uptimeS: Math.round(process.uptime())
1347
+ };
1348
+ }
1349
+
1350
+ // src/radar.ts
1351
+ var state = globalState();
1352
+ var radar = state.client ?? (state.client = new RadarClient());
1353
+
1354
+ // src/nest/index.ts
1355
+ var RADAR_OPTIONS = /* @__PURE__ */ Symbol.for("@oconde/radar/options");
1356
+ function shouldCapture(error) {
1357
+ return !(error instanceof import_common.HttpException) || error.getStatus() >= 500;
1358
+ }
1359
+ var RadarInterceptor = class {
1360
+ intercept(context, next) {
1361
+ if (context.getType() === "http") {
1362
+ const request = context.switchToHttp().getRequest();
1363
+ const store = currentContext();
1364
+ const route = request?.route?.path;
1365
+ if (store && typeof route === "string") store.route = route;
1366
+ }
1367
+ return next.handle().pipe(
1368
+ (0, import_rxjs.catchError)((error) => {
1369
+ if (shouldCapture(error)) radar.captureError(error);
1370
+ return (0, import_rxjs.throwError)(() => error);
1371
+ })
1372
+ );
1373
+ }
1374
+ };
1375
+ RadarInterceptor = __decorateClass([
1376
+ (0, import_common.Injectable)()
1377
+ ], RadarInterceptor);
1378
+ var RadarService = class {
1379
+ debug(message, data) {
1380
+ radar.debug(message, data);
1381
+ }
1382
+ info(message, data) {
1383
+ radar.info(message, data);
1384
+ }
1385
+ warn(message, data) {
1386
+ radar.warn(message, data);
1387
+ }
1388
+ error(message, data) {
1389
+ radar.error(message, data);
1390
+ }
1391
+ logRequest(title, data, options) {
1392
+ radar.logRequest(title, data, options);
1393
+ }
1394
+ captureError(error, data) {
1395
+ radar.captureError(error, data);
1396
+ }
1397
+ setUser(user) {
1398
+ radar.setUser(user);
1399
+ }
1400
+ track(name, fn, data) {
1401
+ return radar.track(name, fn, data);
1402
+ }
1403
+ flush(timeoutMs) {
1404
+ return radar.flush(timeoutMs);
1405
+ }
1406
+ };
1407
+ RadarService = __decorateClass([
1408
+ (0, import_common.Injectable)()
1409
+ ], RadarService);
1410
+ var RadarExceptionFilter = class extends import_core.BaseExceptionFilter {
1411
+ catch(exception, host) {
1412
+ if (shouldCapture(exception)) radar.captureError(exception);
1413
+ super.catch(exception, host);
1414
+ }
1415
+ };
1416
+ RadarExceptionFilter = __decorateClass([
1417
+ (0, import_common.Catch)()
1418
+ ], RadarExceptionFilter);
1419
+ function nestMajorVersion() {
1420
+ try {
1421
+ const load = (0, import_node_module2.createRequire)(importMetaUrl);
1422
+ const { version } = load("@nestjs/core/package.json");
1423
+ return Number.parseInt(version, 10);
1424
+ } catch {
1425
+ return 11;
1426
+ }
1427
+ }
1428
+ var ALL_ROUTES = { path: nestMajorVersion() >= 11 ? "{*path}" : "*", method: import_common.RequestMethod.ALL };
1429
+ var RadarModule = class {
1430
+ constructor(options) {
1431
+ this.options = options;
1432
+ }
1433
+ options;
1434
+ static forRoot(options = {}) {
1435
+ return RadarModule.create(
1436
+ [
1437
+ {
1438
+ provide: RADAR_OPTIONS,
1439
+ useFactory: () => {
1440
+ radar.init(options);
1441
+ return options;
1442
+ }
1443
+ }
1444
+ ],
1445
+ []
1446
+ );
1447
+ }
1448
+ static forRootAsync(options) {
1449
+ const factory = options.useFactory;
1450
+ return RadarModule.create(
1451
+ [
1452
+ {
1453
+ provide: RADAR_OPTIONS,
1454
+ inject: options.inject ?? [],
1455
+ useFactory: async (...args) => {
1456
+ const resolved = await factory(...args);
1457
+ radar.init(resolved);
1458
+ return resolved;
1459
+ }
1460
+ }
1461
+ ],
1462
+ options.imports ?? []
1463
+ );
1464
+ }
1465
+ static create(providers, imports) {
1466
+ return {
1467
+ module: RadarModule,
1468
+ global: true,
1469
+ imports,
1470
+ providers: [...providers, RadarService, { provide: import_core.APP_INTERCEPTOR, useClass: RadarInterceptor }],
1471
+ exports: [RadarService, RADAR_OPTIONS]
1472
+ };
1473
+ }
1474
+ configure(consumer) {
1475
+ consumer.apply(radar.middleware()).forRoutes(ALL_ROUTES);
1476
+ }
1477
+ async onApplicationShutdown() {
1478
+ await radar.close();
1479
+ }
1480
+ };
1481
+ RadarModule = __decorateClass([
1482
+ (0, import_common.Global)(),
1483
+ (0, import_common.Module)({}),
1484
+ __decorateParam(0, (0, import_common.Inject)(RADAR_OPTIONS))
1485
+ ], RadarModule);
1486
+ // Annotate the CommonJS export names for ESM import in node:
1487
+ 0 && (module.exports = {
1488
+ RADAR_OPTIONS,
1489
+ RadarExceptionFilter,
1490
+ RadarInterceptor,
1491
+ RadarModule,
1492
+ RadarService,
1493
+ shouldCapture
1494
+ });
1495
+ //# sourceMappingURL=index.cjs.map