@rasputin-ai/core 0.7.1 → 0.8.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.
Files changed (36) hide show
  1. package/dist/create-client/create-client-types.d.ts +8 -0
  2. package/dist/create-client/create-client-types.d.ts.map +1 -1
  3. package/dist/create-client/create-client.d.ts.map +1 -1
  4. package/dist/create-ingest-event/create-ingest-event-types.d.ts +2 -0
  5. package/dist/create-ingest-event/create-ingest-event-types.d.ts.map +1 -1
  6. package/dist/derivation/rasputin-redacted.d.ts +3 -0
  7. package/dist/derivation/rasputin-redacted.d.ts.map +1 -0
  8. package/dist/derivation/redact-env-secret-values.d.ts +10 -0
  9. package/dist/derivation/redact-env-secret-values.d.ts.map +1 -0
  10. package/dist/derivation/sensitive-property-name.d.ts.map +1 -1
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +604 -332
  14. package/dist/sdk-meta.d.ts +1 -1
  15. package/dist/suppression/compute-burst-key.d.ts +1 -1
  16. package/dist/suppression/compute-burst-key.d.ts.map +1 -1
  17. package/dist/suppression/create-suppression.d.ts +3 -4
  18. package/dist/suppression/create-suppression.d.ts.map +1 -1
  19. package/dist/suppression/grouping-ref-cache.d.ts +6 -0
  20. package/dist/suppression/grouping-ref-cache.d.ts.map +1 -0
  21. package/dist/suppression/parameterize-message.d.ts +2 -4
  22. package/dist/suppression/parameterize-message.d.ts.map +1 -1
  23. package/dist/suppression/suppression-types.d.ts +6 -0
  24. package/dist/suppression/suppression-types.d.ts.map +1 -1
  25. package/dist/transport/create-transport.d.ts.map +1 -1
  26. package/dist/transport/default-ingest-request-timeout.d.ts +2 -0
  27. package/dist/transport/default-ingest-request-timeout.d.ts.map +1 -0
  28. package/dist/transport/node-http-fetch.d.ts +6 -3
  29. package/dist/transport/node-http-fetch.d.ts.map +1 -1
  30. package/dist/transport/send-batch.d.ts +6 -4
  31. package/dist/transport/send-batch.d.ts.map +1 -1
  32. package/dist/transport/transport-types.d.ts +13 -3
  33. package/dist/transport/transport-types.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/dist/suppression/identity-needs-message-template.d.ts +0 -8
  36. package/dist/suppression/identity-needs-message-template.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  // src/transport/node-http-fetch.ts
2
- import * as http from "node:http";
3
- import * as https from "node:https";
2
+ import * as net from "node:net";
3
+ import * as tls from "node:tls";
4
+ var CONNECT_TIMEOUT_MS = 2e3;
5
+ var abortError = () => Object.assign(new Error("aborted"), { name: "AbortError" });
6
+ var connectTimeoutError = () => Object.assign(new Error("connect timeout"), { name: "ConnectTimeoutError" });
4
7
  var headerRecord = (headers) => {
5
8
  const view = new Headers(headers);
6
9
  const record = {};
@@ -9,26 +12,84 @@ var headerRecord = (headers) => {
9
12
  });
10
13
  return record;
11
14
  };
12
- var writeBody = (req, body) => {
13
- if (body == null) {
14
- req.end();
15
- return;
16
- }
17
- if (typeof body === "string") {
18
- req.end(body);
19
- return;
20
- }
15
+ var toBuffer = (body) => {
16
+ if (body == null) return Buffer.alloc(0);
17
+ if (typeof body === "string") return Buffer.from(body);
21
18
  if (ArrayBuffer.isView(body)) {
22
- req.end(Buffer.from(body.buffer, body.byteOffset, body.byteLength));
23
- return;
24
- }
25
- if (body instanceof ArrayBuffer) {
26
- req.end(Buffer.from(body));
27
- return;
19
+ return Buffer.from(body.buffer, body.byteOffset, body.byteLength);
20
+ }
21
+ if (body instanceof ArrayBuffer) return Buffer.from(body);
22
+ return Buffer.alloc(0);
23
+ };
24
+ var encodeHttpRequest = (url, init) => {
25
+ const method = (init?.method ?? "GET").toUpperCase();
26
+ const path = `${url.pathname}${url.search}` || "/";
27
+ const headers = headerRecord(init?.headers);
28
+ const body = toBuffer(init?.body ?? null);
29
+ if (body.length > 0 && headers["content-length"] === void 0) {
30
+ headers["content-length"] = String(body.length);
31
+ }
32
+ if (headers.host === void 0) headers.host = url.host;
33
+ if (headers.connection === void 0) headers.connection = "close";
34
+ const head = [`${method} ${path} HTTP/1.1`];
35
+ for (const [key, value] of Object.entries(headers)) {
36
+ head.push(`${key}: ${value}`);
37
+ }
38
+ return Buffer.concat([Buffer.from(`${head.join("\r\n")}\r
39
+ \r
40
+ `), body]);
41
+ };
42
+ var tryDecodeChunked = (raw) => {
43
+ const parts = [];
44
+ let offset = 0;
45
+ for (; ; ) {
46
+ const lineEnd = raw.indexOf("\r\n", offset);
47
+ if (lineEnd < 0) return null;
48
+ const size = Number.parseInt(raw.subarray(offset, lineEnd).toString("ascii"), 16);
49
+ if (!Number.isFinite(size) || size < 0) throw new Error("invalid chunk size");
50
+ const dataStart = lineEnd + 2;
51
+ if (size === 0) return raw.length < dataStart + 2 ? null : Buffer.concat(parts);
52
+ const dataEnd = dataStart + size;
53
+ if (raw.length < dataEnd + 2) return null;
54
+ parts.push(raw.subarray(dataStart, dataEnd));
55
+ offset = dataEnd + 2;
56
+ }
57
+ };
58
+ var toResponseBody = (buf) => {
59
+ const copy = new Uint8Array(buf.byteLength);
60
+ copy.set(buf);
61
+ return copy;
62
+ };
63
+ var tryParseHttpResponse = (raw, connectionClosed) => {
64
+ const split = raw.indexOf("\r\n\r\n");
65
+ if (split < 0) return null;
66
+ const lines = raw.subarray(0, split).toString("latin1").split("\r\n");
67
+ const status = /^HTTP\/\d\.\d (\d{3})/.exec(lines[0] ?? "");
68
+ if (!status) throw new Error("invalid HTTP response");
69
+ const headers = new Headers();
70
+ for (const line of lines.slice(1)) {
71
+ const colon = line.indexOf(":");
72
+ if (colon < 0) continue;
73
+ headers.append(line.slice(0, colon).trim(), line.slice(colon + 1).trim());
74
+ }
75
+ const body = raw.subarray(split + 4);
76
+ if (headers.get("transfer-encoding")?.toLowerCase().includes("chunked")) {
77
+ const decoded = tryDecodeChunked(body);
78
+ return decoded === null ? null : new Response(toResponseBody(decoded), { status: Number(status[1]), headers });
79
+ }
80
+ const lengthHeader = headers.get("content-length");
81
+ if (lengthHeader !== null) {
82
+ const length = Number(lengthHeader);
83
+ if (!Number.isInteger(length) || length < 0) throw new Error("invalid content-length");
84
+ if (body.length < length) return null;
85
+ return new Response(toResponseBody(body.subarray(0, length)), {
86
+ status: Number(status[1]),
87
+ headers
88
+ });
28
89
  }
29
- req.end();
90
+ return connectionClosed ? new Response(toResponseBody(body), { status: Number(status[1]), headers }) : null;
30
91
  };
31
- var createNodeHttpFetch = (unrefSockets = false) => (input, init) => new Promise((resolve4, reject) => {
92
+ var defaultIngestFetch = () => (input, init) => new Promise((resolve4, reject) => {
32
93
  let parsed;
33
94
  try {
34
95
  parsed = new URL(String(input));
@@ -36,62 +97,77 @@ var createNodeHttpFetch = (unrefSockets = false) => (input, init) => new Promise
36
97
  reject(error);
37
98
  return;
38
99
  }
39
- const lib = parsed.protocol === "https:" ? https : http;
40
- const req = lib.request(
41
- {
42
- protocol: parsed.protocol,
43
- hostname: parsed.hostname,
44
- port: parsed.port,
45
- path: `${parsed.pathname}${parsed.search}`,
46
- method: init?.method ?? "GET",
47
- headers: headerRecord(init?.headers),
48
- agent: false
49
- },
50
- (response) => {
51
- const chunks = [];
52
- response.on("data", (chunk) => {
53
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
54
- });
55
- response.on("end", () => {
56
- const headers = new Headers();
57
- for (const [key, value] of Object.entries(response.headers)) {
58
- if (value === void 0) continue;
59
- if (Array.isArray(value)) {
60
- for (const item of value) headers.append(key, item);
61
- } else {
62
- headers.set(key, value);
63
- }
64
- }
65
- resolve4(
66
- new Response(Buffer.concat(chunks), {
67
- status: response.statusCode ?? 0,
68
- headers
69
- })
70
- );
71
- });
100
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
101
+ reject(new Error(`unsupported protocol: ${parsed.protocol}`));
102
+ return;
103
+ }
104
+ if (init?.signal?.aborted) {
105
+ reject(abortError());
106
+ return;
107
+ }
108
+ const port = Number(parsed.port) || (parsed.protocol === "https:" ? 443 : 80);
109
+ const socket = parsed.protocol === "https:" ? tls.connect({ host: parsed.hostname, port, servername: parsed.hostname }) : net.connect({ host: parsed.hostname, port });
110
+ socket.unref();
111
+ let settled = false;
112
+ let sent = false;
113
+ let connectTimer;
114
+ const finish = (error, response) => {
115
+ if (settled) return;
116
+ settled = true;
117
+ if (connectTimer) {
118
+ clearTimeout(connectTimer);
119
+ connectTimer = void 0;
72
120
  }
73
- );
74
- const abort = () => {
75
- req.destroy();
76
- reject(Object.assign(new Error("aborted"), { name: "AbortError" }));
121
+ socket.destroy();
122
+ if (error) reject(error);
123
+ else if (response) resolve4(response);
77
124
  };
78
- if (init?.signal) {
79
- if (init.signal.aborted) {
80
- abort();
81
- return;
125
+ connectTimer = setTimeout(() => {
126
+ if (sent || settled) return;
127
+ finish(connectTimeoutError());
128
+ }, CONNECT_TIMEOUT_MS);
129
+ const onConnected = () => {
130
+ if (settled || sent) return;
131
+ sent = true;
132
+ if (connectTimer) {
133
+ clearTimeout(connectTimer);
134
+ connectTimer = void 0;
82
135
  }
83
- init.signal.addEventListener("abort", abort, { once: true });
84
- }
85
- if (unrefSockets) {
86
- req.on("socket", (socket) => {
87
- socket.unref();
88
- });
89
- }
90
- req.setTimeout(5e3, abort);
91
- req.on("error", reject);
92
- writeBody(req, init?.body ?? null);
136
+ try {
137
+ socket.write(encodeHttpRequest(parsed, init));
138
+ } catch (error) {
139
+ finish(error instanceof Error ? error : new Error(String(error)));
140
+ }
141
+ };
142
+ if (parsed.protocol === "https:") {
143
+ socket.once("secureConnect", onConnected);
144
+ } else {
145
+ socket.once("connect", onConnected);
146
+ if (socket.remoteAddress) onConnected();
147
+ }
148
+ init?.signal?.addEventListener("abort", () => finish(abortError()), { once: true });
149
+ if (init?.signal?.aborted) finish(abortError());
150
+ let buf = Buffer.alloc(0);
151
+ socket.on("data", (chunk) => {
152
+ buf = Buffer.concat([buf, chunk]);
153
+ try {
154
+ const response = tryParseHttpResponse(buf, false);
155
+ if (response) finish(null, response);
156
+ } catch (error) {
157
+ finish(error instanceof Error ? error : new Error(String(error)));
158
+ }
159
+ });
160
+ socket.on("end", () => {
161
+ try {
162
+ const response = tryParseHttpResponse(buf, true);
163
+ if (response) finish(null, response);
164
+ else finish(new Error("incomplete HTTP response"));
165
+ } catch (error) {
166
+ finish(error instanceof Error ? error : new Error(String(error)));
167
+ }
168
+ });
169
+ socket.on("error", (error) => finish(error));
93
170
  });
94
- var defaultIngestFetch = (options) => createNodeHttpFetch(options?.unrefSockets === true);
95
171
 
96
172
  // src/announce-deploy/announce-deploy.ts
97
173
  var trimmedString = (value) => typeof value === "string" ? value.trim() : "";
@@ -361,6 +437,257 @@ var finalizeResolution = (resolution, stackFrames) => {
361
437
  return "exact";
362
438
  };
363
439
 
440
+ // src/derivation/rasputin-redacted.ts
441
+ var RASPUTIN_REDACTED = "[RASPUTIN_REDACTED]";
442
+
443
+ // src/derivation/sensitive-property-name.ts
444
+ var DEFAULT_SENSITIVE_PROPERTY_NAMES = [
445
+ "password",
446
+ "passwd",
447
+ "pwd",
448
+ "passphrase",
449
+ "cookie",
450
+ "cookies",
451
+ "signedcookies",
452
+ "setcookie",
453
+ "secret",
454
+ "secrets",
455
+ "clientsecret",
456
+ "authorization",
457
+ "proxyauthorization",
458
+ "bearer"
459
+ ];
460
+ var DEFAULT_SENSITIVE_PROPERTY_NAME_SET = new Set(DEFAULT_SENSITIVE_PROPERTY_NAMES);
461
+ var SENSITIVE_SUFFIXES = [
462
+ "password",
463
+ "passwd",
464
+ "passphrase",
465
+ "pwd",
466
+ "cookie",
467
+ "cookies"
468
+ ];
469
+ var compactKey = (key) => {
470
+ let compacted = "";
471
+ for (let index = 0; index < key.length; index++) {
472
+ const code = key.charCodeAt(index);
473
+ if (code === 45 || code === 46 || code === 95) continue;
474
+ compacted += code >= 65 && code <= 90 ? String.fromCharCode(code + 32) : key[index];
475
+ }
476
+ return compacted;
477
+ };
478
+ var stripTrailingDigits = (value) => {
479
+ let end = value.length;
480
+ while (end > 0) {
481
+ const code = value.charCodeAt(end - 1);
482
+ if (code < 48 || code > 57) break;
483
+ end--;
484
+ }
485
+ return end === 0 ? value : value.slice(0, end);
486
+ };
487
+ var defaultSensitivePropertyNames = () => DEFAULT_SENSITIVE_PROPERTY_NAME_SET;
488
+ var propertyNameIsSensitive = (key) => {
489
+ const compacted = compactKey(key);
490
+ const compactForms = [compacted, stripTrailingDigits(compacted)].filter(
491
+ (value, index, values) => value.length > 0 && values.indexOf(value) === index
492
+ );
493
+ for (const form of compactForms) {
494
+ if (DEFAULT_SENSITIVE_PROPERTY_NAME_SET.has(form)) return true;
495
+ for (const suffix of SENSITIVE_SUFFIXES) {
496
+ if (form.endsWith(suffix)) return true;
497
+ }
498
+ }
499
+ return false;
500
+ };
501
+
502
+ // src/derivation/redact-env-secret-values.ts
503
+ var MIN_ENV_SECRET_LENGTH = 6;
504
+ var MAX_ENV_SECRET_NEEDLE_LENGTH = 512;
505
+ var COMMON_CONFIG_VALUES = /* @__PURE__ */ new Set([
506
+ "enabled",
507
+ "disabled",
508
+ "production",
509
+ "development",
510
+ "staging",
511
+ "testing",
512
+ "localhost",
513
+ "undefined"
514
+ ]);
515
+ var identityMatcher = {
516
+ redact: (text) => text,
517
+ needleCount: 0
518
+ };
519
+ var envKeyLooksSensitive = (key) => {
520
+ if (propertyNameIsSensitive(key)) return true;
521
+ const compact = key.replace(/[-._]/g, "").toLowerCase();
522
+ return compact.includes("accesskey") || compact.includes("apikey") || compact.endsWith("secret") || compact.endsWith("token") || compact.includes("privatekey");
523
+ };
524
+ var isCollectableSecretValue = (value) => value.length >= MIN_ENV_SECRET_LENGTH && !COMMON_CONFIG_VALUES.has(value.toLowerCase());
525
+ var looksLikeUrl = (value) => {
526
+ const schemeEnd = value.indexOf("://");
527
+ return schemeEnd > 0 && schemeEnd < 32;
528
+ };
529
+ var urlUserinfo = (value) => {
530
+ const schemeEnd = value.indexOf("://");
531
+ if (schemeEnd <= 0) return void 0;
532
+ const rest = value.slice(schemeEnd + 3);
533
+ let authorityEnd = rest.length;
534
+ for (const marker of ["/", "?", "#"]) {
535
+ const at = rest.indexOf(marker);
536
+ if (at >= 0 && at < authorityEnd) authorityEnd = at;
537
+ }
538
+ const authority = rest.slice(0, authorityEnd);
539
+ const userinfoEnd = authority.lastIndexOf("@");
540
+ if (userinfoEnd < 0) return void 0;
541
+ const userinfo = authority.slice(0, userinfoEnd);
542
+ if (!userinfo) return void 0;
543
+ const colon = userinfo.indexOf(":");
544
+ const encodedPassword = colon < 0 ? "" : userinfo.slice(colon + 1);
545
+ if (!encodedPassword) return void 0;
546
+ let password = encodedPassword;
547
+ try {
548
+ password = decodeURIComponent(encodedPassword.replace(/\+/g, " "));
549
+ } catch {
550
+ password = encodedPassword;
551
+ }
552
+ return { password, encodedPassword, userinfo };
553
+ };
554
+ var querySecretValues = (value) => {
555
+ const queryAt = value.indexOf("?");
556
+ if (queryAt < 0) return [];
557
+ const hashAt = value.indexOf("#", queryAt + 1);
558
+ const query = hashAt < 0 ? value.slice(queryAt + 1) : value.slice(queryAt + 1, hashAt);
559
+ if (!query.includes("=")) return [];
560
+ const found = [];
561
+ const parts = query.split("&");
562
+ for (let index = 0; index < parts.length; index++) {
563
+ const part = parts[index];
564
+ if (!part) continue;
565
+ const equals = part.indexOf("=");
566
+ if (equals <= 0) continue;
567
+ const rawKey = part.slice(0, equals);
568
+ let key = rawKey;
569
+ try {
570
+ key = decodeURIComponent(rawKey.replace(/\+/g, " "));
571
+ } catch {
572
+ key = rawKey;
573
+ }
574
+ if (!propertyNameIsSensitive(key) && !propertyNameIsSensitive(rawKey)) continue;
575
+ const rawValue = part.slice(equals + 1);
576
+ if (!rawValue) continue;
577
+ found.push(rawValue);
578
+ try {
579
+ const decoded = decodeURIComponent(rawValue.replace(/\+/g, " "));
580
+ if (decoded !== rawValue) found.push(decoded);
581
+ } catch {
582
+ }
583
+ }
584
+ return found;
585
+ };
586
+ var addNeedle = (needles, value) => {
587
+ if (value.length < MIN_ENV_SECRET_LENGTH) return;
588
+ needles.add(
589
+ value.length > MAX_ENV_SECRET_NEEDLE_LENGTH ? value.slice(0, MAX_ENV_SECRET_NEEDLE_LENGTH) : value
590
+ );
591
+ };
592
+ var addWithRepresentations = (needles, value) => {
593
+ addNeedle(needles, value);
594
+ if (value.length > MAX_ENV_SECRET_NEEDLE_LENGTH) return;
595
+ try {
596
+ const encoded = encodeURIComponent(value);
597
+ if (encoded !== value) addNeedle(needles, encoded);
598
+ const plus = encoded.replace(/%20/g, "+");
599
+ if (plus !== encoded) addNeedle(needles, plus);
600
+ } catch {
601
+ }
602
+ try {
603
+ const jsonEscaped = JSON.stringify(value).slice(1, -1);
604
+ if (jsonEscaped !== value) addNeedle(needles, jsonEscaped);
605
+ } catch {
606
+ }
607
+ };
608
+ var collectEnvSecretValues = (env = process.env) => {
609
+ const needles = /* @__PURE__ */ new Set();
610
+ let keys;
611
+ try {
612
+ keys = Object.keys(env);
613
+ } catch {
614
+ return [];
615
+ }
616
+ for (let index = 0; index < keys.length; index++) {
617
+ const key = keys[index];
618
+ if (!key || key === "PWD") continue;
619
+ let value;
620
+ try {
621
+ value = env[key];
622
+ } catch {
623
+ continue;
624
+ }
625
+ if (typeof value !== "string" || value.length === 0) continue;
626
+ try {
627
+ const userinfo = urlUserinfo(value);
628
+ if (userinfo) {
629
+ addWithRepresentations(needles, userinfo.password);
630
+ if (userinfo.encodedPassword !== userinfo.password) {
631
+ addWithRepresentations(needles, userinfo.encodedPassword);
632
+ }
633
+ if (userinfo.userinfo.length >= MIN_ENV_SECRET_LENGTH) {
634
+ addWithRepresentations(needles, userinfo.userinfo);
635
+ }
636
+ addWithRepresentations(needles, value);
637
+ }
638
+ const queryValues = querySecretValues(value);
639
+ for (let queryIndex = 0; queryIndex < queryValues.length; queryIndex++) {
640
+ const part = queryValues[queryIndex];
641
+ if (part) addWithRepresentations(needles, part);
642
+ }
643
+ if (!envKeyLooksSensitive(key) || !isCollectableSecretValue(value)) continue;
644
+ if (looksLikeUrl(value) && !userinfo && queryValues.length === 0) continue;
645
+ addWithRepresentations(needles, value);
646
+ } catch {
647
+ }
648
+ }
649
+ return [...needles];
650
+ };
651
+ var createExactSecretMatcher = (secrets) => {
652
+ const unique = [];
653
+ const exact = /* @__PURE__ */ new Set();
654
+ for (let index = 0; index < secrets.length; index++) {
655
+ const secret = secrets[index];
656
+ if (!secret || secret.length < MIN_ENV_SECRET_LENGTH || exact.has(secret)) continue;
657
+ exact.add(secret);
658
+ unique.push(secret);
659
+ }
660
+ if (unique.length === 0) return identityMatcher;
661
+ unique.sort((left, right) => right.length - left.length);
662
+ const minLen = unique[unique.length - 1]?.length ?? MIN_ENV_SECRET_LENGTH;
663
+ return {
664
+ needleCount: unique.length,
665
+ redact(text) {
666
+ try {
667
+ if (text.length < minLen) return text;
668
+ if (exact.has(text)) return RASPUTIN_REDACTED;
669
+ let result = text;
670
+ for (let index = 0; index < unique.length; index++) {
671
+ const needle = unique[index];
672
+ if (!needle || result.length < needle.length) continue;
673
+ if (result.indexOf(needle) === -1) continue;
674
+ result = result.replaceAll(needle, RASPUTIN_REDACTED);
675
+ }
676
+ return result;
677
+ } catch {
678
+ return text;
679
+ }
680
+ }
681
+ };
682
+ };
683
+ var createEnvSecretMatcher = (env = process.env) => {
684
+ try {
685
+ return createExactSecretMatcher(collectEnvSecretValues(env));
686
+ } catch {
687
+ return identityMatcher;
688
+ }
689
+ };
690
+
364
691
  // src/normalize-frames/normalize-frames.ts
365
692
  import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
366
693
  import { fileURLToPath as fileURLToPath2 } from "node:url";
@@ -808,13 +1135,7 @@ var resolveSourceMaps = async (frames) => {
808
1135
 
809
1136
  // src/sdk-meta.ts
810
1137
  var SDK_NAME = "@rasputin-ai/core";
811
- var SDK_VERSION = "0.7.1";
812
-
813
- // src/suppression/identity-needs-message-template.ts
814
- var identityNeedsMessageTemplate = (type, code) => {
815
- if (code != null && code !== "") return false;
816
- return type === "Error";
817
- };
1138
+ var SDK_VERSION = "0.8.0";
818
1139
 
819
1140
  // src/suppression/parameterize-message.ts
820
1141
  var MAX_INPUT_LENGTH = 8192;
@@ -872,10 +1193,6 @@ var RULES = [
872
1193
  {
873
1194
  token: "duration",
874
1195
  pattern: /\b\d+(?:\.\d+)?\s?(?:ns|µs|us|ms|secs?|seconds?|mins?|minutes?|hrs?|hours?|days?)\b/giu
875
- },
876
- {
877
- token: "id",
878
- pattern: /\b(?=[a-z0-9]*[a-z])(?=[a-z0-9]*\d)[a-z0-9]{8,}\b/giu
879
1196
  }
880
1197
  ];
881
1198
  var normalizeForIdentity = (normalized) => {
@@ -936,19 +1253,22 @@ var frameBurstParts = (frames) => {
936
1253
  const inApp = collapsed.filter((frame) => frame.in_app);
937
1254
  const selected = (inApp.length > 0 ? inApp : collapsed).slice(0, FRAME_COUNT);
938
1255
  return selected.map((frame) => {
939
- const file = preferredFile(frame);
1256
+ const loc = frame.original ?? frame.generated;
940
1257
  const fn = normalizeFunction(frame.function ?? frame.original?.name);
941
- return `${file}:${fn}`;
1258
+ return `${loc.file}:${fn}:${loc.line ?? ""}:${loc.column ?? ""}`;
942
1259
  });
943
1260
  };
944
- var computeBurstKey = (errorType, message, code, frames) => {
1261
+ var computeBurstKey = (errorType, message, code, frames, route, method) => {
945
1262
  const parts = frameBurstParts(frames);
946
1263
  const codePart = code ?? "";
947
- let key = `${errorType}\0${codePart}\0${parts.join("\0")}`;
948
- if (parts.length === 0 || identityNeedsMessageTemplate(errorType, code)) {
949
- key += `\0${parameterizeMessage(message)}`;
950
- }
951
- return key;
1264
+ return [
1265
+ errorType,
1266
+ codePart,
1267
+ method ?? "",
1268
+ route ?? "",
1269
+ parameterizeMessage(message),
1270
+ parts.join("\0")
1271
+ ].join("\0");
952
1272
  };
953
1273
 
954
1274
  // src/suppression/create-suppression.ts
@@ -999,9 +1319,12 @@ var createSuppression = (options = {}) => {
999
1319
  windowStart: bucket.windowStart,
1000
1320
  windowEndMs,
1001
1321
  lastRelease: bucket.lastRelease,
1322
+ lastRoute: bucket.lastRoute,
1323
+ lastMethod: bucket.lastMethod,
1002
1324
  lastEnvironment: bucket.lastEnvironment,
1003
1325
  identity: bucket.identity,
1004
- captureRawFrames: bucket.captureRawFrames
1326
+ captureRawFrames: bucket.captureRawFrames,
1327
+ burstKey: bucket.burstKey
1005
1328
  });
1006
1329
  const toSummary = (draft, identity) => ({
1007
1330
  count: draft.count,
@@ -1009,7 +1332,8 @@ var createSuppression = (options = {}) => {
1009
1332
  window_end: new Date(draft.windowEndMs).toISOString(),
1010
1333
  release: draft.lastRelease,
1011
1334
  environment: draft.lastEnvironment,
1012
- ...identity
1335
+ ...identity,
1336
+ ...draft.lastRoute || draft.lastMethod ? { request: { route: draft.lastRoute, method: draft.lastMethod } } : {}
1013
1337
  });
1014
1338
  const flushSuppressed = (bucket, windowEndMs) => {
1015
1339
  if (bucket.suppressed <= 0) return;
@@ -1020,9 +1344,17 @@ var createSuppression = (options = {}) => {
1020
1344
  const now = input.now ?? Date.now();
1021
1345
  const identity = captureIdentity(input);
1022
1346
  const captureRawFrames = (input.rawFrames ?? []).slice(0, MAX_STACK_FRAMES);
1023
- const burstKey = computeBurstKey(input.errorType, input.message, input.code, input.frames);
1347
+ const burstKey = computeBurstKey(
1348
+ input.errorType,
1349
+ input.message,
1350
+ input.code,
1351
+ input.frames,
1352
+ input.route,
1353
+ input.method
1354
+ );
1024
1355
  const release = input.release;
1025
1356
  const route = input.route ?? null;
1357
+ const method = input.method ?? null;
1026
1358
  let bucket = burstBuckets.get(burstKey);
1027
1359
  if (!bucket) {
1028
1360
  bucket = {
@@ -1031,9 +1363,11 @@ var createSuppression = (options = {}) => {
1031
1363
  windowStart: now,
1032
1364
  lastRelease: release,
1033
1365
  lastRoute: route,
1366
+ lastMethod: method,
1034
1367
  lastEnvironment: input.environment,
1035
1368
  identity,
1036
- captureRawFrames
1369
+ captureRawFrames,
1370
+ burstKey
1037
1371
  };
1038
1372
  burstBuckets.set(burstKey, bucket);
1039
1373
  evictIfNeeded();
@@ -1048,6 +1382,7 @@ var createSuppression = (options = {}) => {
1048
1382
  bucket.windowStart = now;
1049
1383
  bucket.lastRelease = release;
1050
1384
  bucket.lastRoute = route;
1385
+ bucket.lastMethod = method;
1051
1386
  bucket.lastEnvironment = input.environment;
1052
1387
  bucket.identity = identity;
1053
1388
  bucket.captureRawFrames = captureRawFrames;
@@ -1057,6 +1392,7 @@ var createSuppression = (options = {}) => {
1057
1392
  bucket.sentInWindow += 1;
1058
1393
  bucket.lastRelease = release;
1059
1394
  bucket.lastRoute = route;
1395
+ bucket.lastMethod = method;
1060
1396
  bucket.lastEnvironment = input.environment;
1061
1397
  return { action: "send", priority: "repeat" };
1062
1398
  }
@@ -1073,7 +1409,8 @@ var createSuppression = (options = {}) => {
1073
1409
  }
1074
1410
  return drafts.map((draft) => ({
1075
1411
  summary: toSummary(draft, draft.identity),
1076
- rawFrames: draft.captureRawFrames
1412
+ rawFrames: draft.captureRawFrames,
1413
+ burstKey: draft.burstKey
1077
1414
  }));
1078
1415
  };
1079
1416
  const takeSummaries = async (now = Date.now(), prepareSummary) => {
@@ -1103,6 +1440,42 @@ var createSuppression = (options = {}) => {
1103
1440
  };
1104
1441
  };
1105
1442
 
1443
+ // src/suppression/grouping-ref-cache.ts
1444
+ var GROUPING_REF_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
1445
+ var createGroupingRefCache = (maxSize = 2e3) => {
1446
+ const byBurstKey = /* @__PURE__ */ new Map();
1447
+ const get = (burstKey, now = Date.now()) => {
1448
+ const entry = byBurstKey.get(burstKey);
1449
+ if (!entry || entry.expiresAt <= now) {
1450
+ if (entry) byBurstKey.delete(burstKey);
1451
+ return null;
1452
+ }
1453
+ byBurstKey.delete(burstKey);
1454
+ byBurstKey.set(burstKey, entry);
1455
+ return entry.groupingRef;
1456
+ };
1457
+ const set = (burstKey, groupingRef, now = Date.now()) => {
1458
+ byBurstKey.delete(burstKey);
1459
+ byBurstKey.set(burstKey, { groupingRef, expiresAt: now + GROUPING_REF_TTL_MS });
1460
+ while (byBurstKey.size > maxSize) {
1461
+ const oldest = byBurstKey.keys().next().value;
1462
+ if (oldest === void 0) break;
1463
+ byBurstKey.delete(oldest);
1464
+ }
1465
+ };
1466
+ const getOrCreate = (burstKey, now = Date.now()) => {
1467
+ const existing = get(burstKey, now);
1468
+ if (existing) return existing;
1469
+ const groupingRef = crypto.randomUUID();
1470
+ set(burstKey, groupingRef, now);
1471
+ return groupingRef;
1472
+ };
1473
+ return { get, set, getOrCreate };
1474
+ };
1475
+
1476
+ // src/transport/default-ingest-request-timeout.ts
1477
+ var DEFAULT_INGEST_REQUEST_TIMEOUT_MS = 2e4;
1478
+
1106
1479
  // src/transport/process-shutdown-hooks.ts
1107
1480
  var SIGNAL_EXIT_CODES = { SIGTERM: 143, SIGINT: 130 };
1108
1481
  var RASPUTIN_PROCESS_LISTENER = Symbol.for("rasputin.process.listener.v1");
@@ -1179,33 +1552,32 @@ var encodeJsonRequestBody = (payload, gzipThresholdBytes = GZIP_JSON_THRESHOLD_B
1179
1552
 
1180
1553
  // src/transport/send-batch.ts
1181
1554
  var sleep = (ms, signal) => new Promise((resolve4) => {
1182
- if (ms <= 0 || signal?.aborted) {
1555
+ if (ms <= 0) {
1183
1556
  resolve4();
1184
1557
  return;
1185
1558
  }
1186
1559
  const timer = setTimeout(resolve4, ms);
1187
1560
  timer.unref?.();
1188
- signal?.addEventListener(
1189
- "abort",
1190
- () => {
1191
- clearTimeout(timer);
1192
- resolve4();
1193
- },
1194
- { once: true }
1195
- );
1561
+ const onAbort = () => {
1562
+ clearTimeout(timer);
1563
+ resolve4();
1564
+ };
1565
+ signal.addEventListener("abort", onAbort, { once: true });
1566
+ if (signal.aborted) onAbort();
1196
1567
  });
1197
1568
  var backoffMs = (attempt) => {
1198
1569
  const base = Math.min(3e4, 500 * 2 ** attempt);
1199
1570
  return Math.floor(base * (0.5 + Math.random()));
1200
1571
  };
1201
- var abortError = () => Object.assign(new Error("aborted"), { name: "AbortError" });
1572
+ var abortError2 = () => Object.assign(new Error("aborted"), { name: "AbortError" });
1202
1573
  var fetchUntilAborted = (fetchFn, url, init) => new Promise((resolve4, reject) => {
1574
+ const onAbort = () => reject(abortError2());
1575
+ init.signal.addEventListener("abort", onAbort, { once: true });
1203
1576
  if (init.signal.aborted) {
1204
- reject(abortError());
1577
+ init.signal.removeEventListener("abort", onAbort);
1578
+ reject(abortError2());
1205
1579
  return;
1206
1580
  }
1207
- const onAbort = () => reject(abortError());
1208
- init.signal.addEventListener("abort", onAbort, { once: true });
1209
1581
  void fetchFn(url, init).then(
1210
1582
  (value) => {
1211
1583
  init.signal.removeEventListener("abort", onAbort);
@@ -1225,8 +1597,29 @@ var retryAfterMs = (response) => {
1225
1597
  const date = Date.parse(header);
1226
1598
  return Number.isFinite(date) ? Math.max(0, date - Date.now()) : null;
1227
1599
  };
1600
+ var asRefList = (value) => {
1601
+ if (!Array.isArray(value)) return [];
1602
+ return value.map((entry) => typeof entry === "string" ? entry : null);
1603
+ };
1604
+ var readGroupingRefs = async (response) => {
1605
+ try {
1606
+ const body = await response.json();
1607
+ const payload = typeof body === "object" && body !== null && "payload" in body && typeof body.payload === "object" && body.payload !== null ? body.payload : body;
1608
+ if (typeof payload !== "object" || payload === null) {
1609
+ return { eventGroupingRefs: [], summaryGroupingRefs: [] };
1610
+ }
1611
+ const record = payload;
1612
+ return {
1613
+ eventGroupingRefs: asRefList(record.event_grouping_refs),
1614
+ summaryGroupingRefs: asRefList(record.summary_grouping_refs)
1615
+ };
1616
+ } catch {
1617
+ return { eventGroupingRefs: [], summaryGroupingRefs: [] };
1618
+ }
1619
+ };
1228
1620
  var sendBatch = async (options) => {
1229
1621
  const summaries = options.summaries ?? [];
1622
+ const { signal } = options;
1230
1623
  let payload;
1231
1624
  if (summaries.length > 0) {
1232
1625
  payload = { events: options.events, summaries };
@@ -1235,12 +1628,13 @@ var sendBatch = async (options) => {
1235
1628
  }
1236
1629
  if (options.sdkVersion) payload.sdk_version = options.sdkVersion;
1237
1630
  if (options.sdkName) payload.sdk_name = options.sdkName;
1631
+ payload.scrub_pii = options.scrubPii !== false;
1238
1632
  if (options.idempotencyKey) payload.idempotency_key = options.idempotencyKey;
1239
1633
  let attempts = 0;
1240
1634
  let networkErrors = 0;
1241
1635
  let rateLimitedResponses = 0;
1242
1636
  let serverErrorResponses = 0;
1243
- const result = (outcome, failureKind = null, statusCode = null) => ({
1637
+ const result = (outcome, failureKind = null, statusCode = null, eventGroupingRefs = [], summaryGroupingRefs = []) => ({
1244
1638
  outcome,
1245
1639
  attempts,
1246
1640
  retries: Math.max(0, attempts - 1),
@@ -1249,15 +1643,10 @@ var sendBatch = async (options) => {
1249
1643
  serverErrorResponses,
1250
1644
  aborted: failureKind === "aborted",
1251
1645
  failureKind,
1252
- statusCode
1646
+ statusCode,
1647
+ eventGroupingRefs,
1648
+ summaryGroupingRefs
1253
1649
  });
1254
- const abortAfter = (ms) => {
1255
- const controller = new AbortController();
1256
- const timer = setTimeout(() => controller.abort(), ms);
1257
- timer.unref?.();
1258
- return { signal: controller.signal, cancel: () => clearTimeout(timer) };
1259
- };
1260
- const requestTimeoutMs = options.requestTimeoutMs ?? 5e3;
1261
1650
  const { body, gzip } = encodeJsonRequestBody(payload, options.gzipThresholdBytes);
1262
1651
  const headers = {
1263
1652
  Authorization: `Bearer ${options.projectApiKey}`,
@@ -1266,10 +1655,8 @@ var sendBatch = async (options) => {
1266
1655
  };
1267
1656
  if (gzip) headers["Content-Encoding"] = "gzip";
1268
1657
  for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
1269
- if (options.signal?.aborted) return result("failed", "aborted");
1658
+ if (signal.aborted) return result("failed", "aborted");
1270
1659
  attempts++;
1271
- const timeout = abortAfter(requestTimeoutMs);
1272
- const signal = options.signal ? AbortSignal.any([options.signal, timeout.signal]) : timeout.signal;
1273
1660
  try {
1274
1661
  const response = await fetchUntilAborted(options.fetch, options.ingestUrl, {
1275
1662
  method: "POST",
@@ -1277,13 +1664,22 @@ var sendBatch = async (options) => {
1277
1664
  body,
1278
1665
  signal
1279
1666
  });
1280
- if (response.ok) return result("ok");
1667
+ if (response.ok) {
1668
+ const refs = await readGroupingRefs(response);
1669
+ return result(
1670
+ "ok",
1671
+ null,
1672
+ response.status,
1673
+ refs.eventGroupingRefs,
1674
+ refs.summaryGroupingRefs
1675
+ );
1676
+ }
1281
1677
  if (response.status === 429) {
1282
1678
  rateLimitedResponses++;
1283
1679
  if (attempt === options.maxRetries)
1284
1680
  return result("failed", "rate_limited", response.status);
1285
1681
  const wait = retryAfterMs(response) ?? backoffMs(attempt);
1286
- await sleep(wait, options.signal);
1682
+ await sleep(wait, signal);
1287
1683
  continue;
1288
1684
  }
1289
1685
  if (response.status >= 400 && response.status < 500) {
@@ -1291,19 +1687,17 @@ var sendBatch = async (options) => {
1291
1687
  }
1292
1688
  serverErrorResponses++;
1293
1689
  if (attempt === options.maxRetries) return result("failed", "server_error", response.status);
1294
- await sleep(backoffMs(attempt), options.signal);
1690
+ await sleep(backoffMs(attempt), signal);
1295
1691
  } catch (error) {
1296
1692
  if (signal.aborted || error instanceof Error && error.name === "AbortError") {
1297
1693
  return result("failed", "aborted");
1298
1694
  }
1299
1695
  networkErrors++;
1300
1696
  if (attempt === options.maxRetries) return result("failed", "network_error");
1301
- await sleep(backoffMs(attempt), options.signal);
1302
- } finally {
1303
- timeout.cancel();
1697
+ await sleep(backoffMs(attempt), signal);
1304
1698
  }
1305
1699
  }
1306
- return result("failed", options.signal?.aborted ? "aborted" : "network_error");
1700
+ return result("failed", signal.aborted ? "aborted" : "network_error");
1307
1701
  };
1308
1702
 
1309
1703
  // src/transport/transport-queue.ts
@@ -1430,9 +1824,8 @@ var createTransport = (options) => {
1430
1824
  const flushIntervalMs = options.flushIntervalMs ?? DEFAULTS2.flushIntervalMs;
1431
1825
  const gzipThresholdBytes = options.gzipThresholdBytes ?? DEFAULTS2.gzipThresholdBytes;
1432
1826
  const maxRetries = options.maxRetries ?? DEFAULTS2.maxRetries;
1433
- const requestTimeoutMs = options.requestTimeoutMs ?? 5e3;
1827
+ const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_INGEST_REQUEST_TIMEOUT_MS;
1434
1828
  const fetchFn = options.fetch ?? defaultIngestFetch();
1435
- const backgroundFetch = options.fetch ?? defaultIngestFetch({ unrefSockets: true });
1436
1829
  const queue = createQueue(
1437
1830
  options.maxQueueSize ?? DEFAULTS2.maxQueueSize,
1438
1831
  options.maxQueueBytes ?? DEFAULTS2.maxQueueBytes
@@ -1460,6 +1853,7 @@ var createTransport = (options) => {
1460
1853
  let flushAbort;
1461
1854
  let flushAbortTimer;
1462
1855
  let flushDeadlineMs = 0;
1856
+ let flushHoldsProcess = false;
1463
1857
  let inFlightBytes = 0;
1464
1858
  let retryBytes = 0;
1465
1859
  const estimateItemsBytes = (items) => {
@@ -1474,23 +1868,30 @@ var createTransport = (options) => {
1474
1868
  };
1475
1869
  const setFlushDeadline = (timeoutMs, holdProcess = false) => {
1476
1870
  const nextDeadline = timeoutMs > 0 ? Date.now() + timeoutMs : Number.POSITIVE_INFINITY;
1477
- if (flushAbort && nextDeadline >= flushDeadlineMs) return;
1478
- flushDeadlineMs = nextDeadline;
1479
- if (!flushAbort) flushAbort = new AbortController();
1871
+ if (holdProcess) flushHoldsProcess = true;
1872
+ const shouldReplace = !flushAbort || nextDeadline < flushDeadlineMs;
1873
+ if (!shouldReplace && !holdProcess) return;
1874
+ if (shouldReplace) {
1875
+ flushDeadlineMs = nextDeadline;
1876
+ if (!flushAbort) flushAbort = new AbortController();
1877
+ }
1480
1878
  if (flushAbortTimer) clearTimeout(flushAbortTimer);
1481
- if (Number.isFinite(nextDeadline)) {
1482
- flushAbortTimer = setTimeout(
1483
- () => flushAbort?.abort(),
1484
- Math.max(0, nextDeadline - Date.now())
1485
- );
1486
- if (!holdProcess) flushAbortTimer.unref?.();
1879
+ if (!Number.isFinite(flushDeadlineMs)) {
1880
+ flushAbortTimer = void 0;
1881
+ return;
1487
1882
  }
1883
+ flushAbortTimer = setTimeout(
1884
+ () => flushAbort?.abort(),
1885
+ Math.max(0, flushDeadlineMs - Date.now())
1886
+ );
1887
+ if (!flushHoldsProcess) flushAbortTimer.unref?.();
1488
1888
  };
1489
1889
  const clearFlushDeadline = () => {
1490
1890
  if (flushAbortTimer) clearTimeout(flushAbortTimer);
1491
1891
  flushAbortTimer = void 0;
1492
1892
  flushAbort = void 0;
1493
1893
  flushDeadlineMs = 0;
1894
+ flushHoldsProcess = false;
1494
1895
  };
1495
1896
  const schedule = () => {
1496
1897
  if (timer || startTimer || !enabled || closed || shuttingDown2) return;
@@ -1529,11 +1930,8 @@ var createTransport = (options) => {
1529
1930
  };
1530
1931
  const whenAborted = (signal) => new Promise((_, reject) => {
1531
1932
  const abort = () => reject(signal.reason ?? new Error("aborted"));
1532
- if (signal.aborted) {
1533
- abort();
1534
- return;
1535
- }
1536
1933
  signal.addEventListener("abort", abort, { once: true });
1934
+ if (signal.aborted) abort();
1537
1935
  });
1538
1936
  const performFlush = async () => {
1539
1937
  options.beforeFlush?.();
@@ -1587,15 +1985,15 @@ var createTransport = (options) => {
1587
1985
  }
1588
1986
  }
1589
1987
  const retainOrDrop = (nextReplays, createdAt) => {
1988
+ inFlightBytes = 0;
1590
1989
  if (nextReplays >= DEFAULTS2.maxBatchReplays || Date.now() - createdAt >= DEFAULTS2.maxRetryBatchAgeMs) {
1591
1990
  batchesDropped++;
1592
1991
  retryBatch = null;
1593
1992
  retryBytes = 0;
1594
1993
  } else {
1595
1994
  retryBatch = { items: batch, idempotencyKey, createdAt, replays: nextReplays };
1596
- retryBytes = inFlightBytes;
1995
+ retryBytes = estimateItemsBytes(batch);
1597
1996
  }
1598
- inFlightBytes = 0;
1599
1997
  syncReservedBytes();
1600
1998
  };
1601
1999
  if (controller.signal.aborted) {
@@ -1610,10 +2008,10 @@ var createTransport = (options) => {
1610
2008
  idempotencyKey,
1611
2009
  sdkVersion: options.sdkVersion,
1612
2010
  sdkName: options.sdkName,
2011
+ scrubPii: options.scrubPii,
1613
2012
  gzipThresholdBytes,
1614
2013
  maxRetries: shuttingDown2 ? 0 : maxRetries,
1615
- requestTimeoutMs,
1616
- fetch: shuttingDown2 ? fetchFn : backgroundFetch,
2014
+ fetch: fetchFn,
1617
2015
  signal: controller.signal
1618
2016
  });
1619
2017
  sendAttempts += result.attempts;
@@ -1622,8 +2020,21 @@ var createTransport = (options) => {
1622
2020
  rateLimitedResponses += result.rateLimitedResponses;
1623
2021
  serverErrorResponses += result.serverErrorResponses;
1624
2022
  if (result.aborted) flushTimeouts++;
1625
- if (result.outcome === "ok") batchesSucceeded++;
1626
- else {
2023
+ if (result.outcome === "ok") {
2024
+ batchesSucceeded++;
2025
+ const eventBurstKeys = [];
2026
+ const summaryBurstKeys = [];
2027
+ for (const item of batch) {
2028
+ if (item.kind === "event") eventBurstKeys.push(item.burstKey);
2029
+ else summaryBurstKeys.push(item.burstKey);
2030
+ }
2031
+ options.onGroupingRefs?.({
2032
+ eventBurstKeys,
2033
+ summaryBurstKeys,
2034
+ eventGroupingRefs: result.eventGroupingRefs,
2035
+ summaryGroupingRefs: result.summaryGroupingRefs
2036
+ });
2037
+ } else {
1627
2038
  lastFailureKind = result.failureKind;
1628
2039
  lastFailureStatusCode = result.statusCode;
1629
2040
  if (result.outcome === "dropped") batchesDropped++;
@@ -1679,15 +2090,15 @@ var createTransport = (options) => {
1679
2090
  }
1680
2091
  await startFlushWorker();
1681
2092
  };
1682
- const enqueue = (event, priority = "first", rawFrames = [], encodedBytes) => {
2093
+ const enqueue = (event, priority = "first", rawFrames = [], encodedBytes, burstKey) => {
1683
2094
  if (!enabled || closed) return;
1684
- queue.enqueue({ kind: "event", event, priority, rawFrames, encodedBytes });
2095
+ queue.enqueue({ kind: "event", event, priority, rawFrames, encodedBytes, burstKey });
1685
2096
  schedule();
1686
2097
  if (queue.size() >= batchSize) kickBackgroundFlush();
1687
2098
  };
1688
- const enqueueSummary = (summary, rawFrames = []) => {
2099
+ const enqueueSummary = (summary, rawFrames = [], burstKey) => {
1689
2100
  if (!enabled || closed) return;
1690
- queue.enqueue({ kind: "summary", summary, priority: "summary", rawFrames });
2101
+ queue.enqueue({ kind: "summary", summary, priority: "summary", rawFrames, burstKey });
1691
2102
  schedule();
1692
2103
  if (queue.size() >= batchSize) kickBackgroundFlush();
1693
2104
  };
@@ -1730,21 +2141,10 @@ var createTransport = (options) => {
1730
2141
  stopTimer();
1731
2142
  unregisterSignals();
1732
2143
  shuttingDown2 = true;
1733
- await new Promise((resolve4, reject) => {
1734
- const timer2 = setTimeout(resolve4, DEFAULTS2.shutdownFlushMs + 250);
1735
- void flush(DEFAULTS2.shutdownFlushMs).then(
1736
- () => {
1737
- clearTimeout(timer2);
1738
- resolve4();
1739
- },
1740
- (error) => {
1741
- clearTimeout(timer2);
1742
- reject(error);
1743
- }
1744
- );
2144
+ await flush(DEFAULTS2.shutdownFlushMs).catch(() => {
1745
2145
  });
1746
- dropRemainingOnExit();
1747
2146
  closed = true;
2147
+ dropRemainingOnExit();
1748
2148
  queue.clear();
1749
2149
  retryBatch = null;
1750
2150
  };
@@ -1937,6 +2337,7 @@ var createClient = (options, hooks = {}) => {
1937
2337
  const activation = resolveClientActivation(options);
1938
2338
  if (!activation.enabled) return noopClient();
1939
2339
  const { ingestToken, projectApiKey, apiUrl, release } = activation;
2340
+ const redactEnvSecrets = createEnvSecretMatcher().redact;
1940
2341
  const environment = options.environment;
1941
2342
  const { ingestUrl, deployUrl } = resolveEndpointUrls(apiUrl);
1942
2343
  const repoRoot = resolveRepoRoot(options);
@@ -1965,6 +2366,7 @@ var createClient = (options, hooks = {}) => {
1965
2366
  return pending;
1966
2367
  };
1967
2368
  const suppression = createSuppression();
2369
+ const groupingRefs = createGroupingRefCache();
1968
2370
  let enqueuePendingSummaries = () => {
1969
2371
  };
1970
2372
  const transport = createTransport({
@@ -1975,7 +2377,23 @@ var createClient = (options, hooks = {}) => {
1975
2377
  installSignalHandlers: hooks.installTransportSignalHandlers,
1976
2378
  sdkVersion: hooks.sdkVersion ?? SDK_VERSION,
1977
2379
  sdkName: hooks.sdkName ?? SDK_NAME,
2380
+ scrubPii: options.scrubPII !== false,
1978
2381
  beforeFlush: () => enqueuePendingSummaries(),
2382
+ onGroupingRefs: ({
2383
+ eventBurstKeys,
2384
+ summaryBurstKeys,
2385
+ eventGroupingRefs,
2386
+ summaryGroupingRefs
2387
+ }) => {
2388
+ for (const [index, burstKey] of eventBurstKeys.entries()) {
2389
+ const groupingRef = eventGroupingRefs[index];
2390
+ if (burstKey && groupingRef) groupingRefs.set(burstKey, groupingRef);
2391
+ }
2392
+ for (const [index, burstKey] of summaryBurstKeys.entries()) {
2393
+ const groupingRef = summaryGroupingRefs[index];
2394
+ if (burstKey && groupingRef) groupingRefs.set(burstKey, groupingRef);
2395
+ }
2396
+ },
1979
2397
  prepareEvent: async ({ event, rawFrames }) => {
1980
2398
  const prepared = await resolveStack(rawFrames);
1981
2399
  return withResolvedFrames(event, prepared.frames, prepared.resolution);
@@ -2008,6 +2426,17 @@ var createClient = (options, hooks = {}) => {
2008
2426
  resolution: "none",
2009
2427
  request: context?.request
2010
2428
  });
2429
+ event.message = redactEnvSecrets(event.message);
2430
+ const burstKey = computeBurstKey(
2431
+ event.type,
2432
+ event.message,
2433
+ event.code,
2434
+ frames,
2435
+ context?.request?.route,
2436
+ context?.request?.method
2437
+ );
2438
+ const groupingRef = groupingRefs.getOrCreate(burstKey);
2439
+ event.grouping_ref = groupingRef;
2011
2440
  const decision = suppression.decide({
2012
2441
  errorType: event.type,
2013
2442
  message: event.message,
@@ -2017,7 +2446,8 @@ var createClient = (options, hooks = {}) => {
2017
2446
  code: event.code,
2018
2447
  environment,
2019
2448
  release,
2020
- route: context?.request?.route
2449
+ route: context?.request?.route,
2450
+ method: context?.request?.method
2021
2451
  });
2022
2452
  if (decision.action !== "send") return;
2023
2453
  const runtimeState = resolveRuntimeEvidence(context, hooks);
@@ -2028,13 +2458,15 @@ var createClient = (options, hooks = {}) => {
2028
2458
  } catch {
2029
2459
  encodedBytes = void 0;
2030
2460
  }
2031
- transport.enqueue(event, decision.priority, rawFrames, encodedBytes);
2461
+ transport.enqueue(event, decision.priority, rawFrames, encodedBytes, burstKey);
2032
2462
  } catch {
2033
2463
  }
2034
2464
  };
2035
2465
  enqueuePendingSummaries = () => {
2036
- for (const { summary, rawFrames } of suppression.drainSummaries()) {
2037
- transport.enqueueSummary(summary, rawFrames);
2466
+ for (const { summary, rawFrames, burstKey } of suppression.drainSummaries()) {
2467
+ const groupingRef = groupingRefs.getOrCreate(burstKey);
2468
+ summary.grouping_ref = groupingRef;
2469
+ transport.enqueueSummary(summary, rawFrames, burstKey);
2038
2470
  }
2039
2471
  };
2040
2472
  const flush = async (timeoutMs) => {
@@ -2139,169 +2571,6 @@ var isRasputinTaggedValue = (value) => {
2139
2571
  };
2140
2572
  var rasputinTagKind = (value) => isRasputinTaggedValue(value) ? value[1] : void 0;
2141
2573
 
2142
- // src/derivation/sensitive-property-name.ts
2143
- var DEFAULT_SENSITIVE_PROPERTY_NAMES = [
2144
- "password",
2145
- "passwd",
2146
- "pwd",
2147
- "passphrase",
2148
- "token",
2149
- "accesstoken",
2150
- "refreshtoken",
2151
- "sessiontoken",
2152
- "authorization",
2153
- "auth",
2154
- "bearer",
2155
- "cookie",
2156
- "cookies",
2157
- "signedcookies",
2158
- "setcookie",
2159
- "secret",
2160
- "secrets",
2161
- "clientsecret",
2162
- "apikey",
2163
- "xapikey",
2164
- "credential",
2165
- "credentials",
2166
- "privatekey",
2167
- "privkey",
2168
- "pem",
2169
- "jwt",
2170
- "otp",
2171
- "totp",
2172
- "pin",
2173
- "mfacode",
2174
- "sessionid",
2175
- "sid",
2176
- "databaseurl",
2177
- "connectionstring",
2178
- "dsn",
2179
- "mongouri",
2180
- "redisurl",
2181
- "signingkey",
2182
- "encryptionkey",
2183
- "cvv",
2184
- "cvc",
2185
- "cardnumber",
2186
- "ssn",
2187
- "creditcard",
2188
- "securitycode",
2189
- "twofactorcode",
2190
- "masterkey",
2191
- "servicerolekey",
2192
- "stripekey",
2193
- "rawheaders"
2194
- ];
2195
- var DEFAULT_SENSITIVE_PROPERTY_NAME_SET = new Set(DEFAULT_SENSITIVE_PROPERTY_NAMES);
2196
- var SENSITIVE_SEGMENTS = /* @__PURE__ */ new Set([
2197
- "password",
2198
- "passwd",
2199
- "pwd",
2200
- "passphrase",
2201
- "token",
2202
- "secret",
2203
- "cookie",
2204
- "cookies",
2205
- "authorization",
2206
- "auth",
2207
- "bearer",
2208
- "credential",
2209
- "credentials",
2210
- "jwt",
2211
- "otp",
2212
- "totp",
2213
- "pin",
2214
- "sid",
2215
- "dsn",
2216
- "pem",
2217
- "privkey",
2218
- "cvv",
2219
- "cvc",
2220
- "ssn",
2221
- "secrets"
2222
- ]);
2223
- var SENSITIVE_SUFFIXES = [
2224
- "password",
2225
- "passwd",
2226
- "token",
2227
- "secret",
2228
- "apikey",
2229
- "privatekey",
2230
- "credential",
2231
- "credentials",
2232
- "cookie"
2233
- ];
2234
- var compactKey = (key) => {
2235
- let compacted = "";
2236
- for (let index = 0; index < key.length; index++) {
2237
- const code = key.charCodeAt(index);
2238
- if (code === 45 || code === 46 || code === 95) continue;
2239
- compacted += code >= 65 && code <= 90 ? String.fromCharCode(code + 32) : key[index];
2240
- }
2241
- return compacted;
2242
- };
2243
- var stripTrailingDigits = (value) => {
2244
- let end = value.length;
2245
- while (end > 0) {
2246
- const code = value.charCodeAt(end - 1);
2247
- if (code < 48 || code > 57) break;
2248
- end--;
2249
- }
2250
- return end === 0 ? value : value.slice(0, end);
2251
- };
2252
- var segmentsOf = (key) => {
2253
- const segments = [];
2254
- let current = "";
2255
- for (let index = 0; index < key.length; index++) {
2256
- const char = key[index] ?? "";
2257
- const code = key.charCodeAt(index);
2258
- if (code === 45 || code === 46 || code === 95) {
2259
- if (current) segments.push(current.toLowerCase());
2260
- current = "";
2261
- continue;
2262
- }
2263
- if (current && code >= 65 && code <= 90) {
2264
- const prev = current.charCodeAt(current.length - 1);
2265
- const prevIsLower = prev >= 97 && prev <= 122;
2266
- const next = key.charCodeAt(index + 1);
2267
- const nextIsLower = next >= 97 && next <= 122;
2268
- if (prevIsLower || nextIsLower) {
2269
- segments.push(current.toLowerCase());
2270
- current = char;
2271
- continue;
2272
- }
2273
- }
2274
- current += char;
2275
- }
2276
- if (current) segments.push(current.toLowerCase());
2277
- return segments;
2278
- };
2279
- var adjacentPairsSensitive = (left, right) => left === "api" && right === "key" || left === "private" && right === "key" || left === "client" && right === "secret" || left === "set" && right === "cookie" || left === "database" && right === "url" || left === "connection" && right === "string" || left === "mongo" && (right === "uri" || right === "url") || left === "mongodb" && (right === "uri" || right === "url") || left === "redis" && (right === "uri" || right === "url") || left === "postgres" && (right === "uri" || right === "url") || left === "database" && right === "uri" || left === "signing" && right === "key" || left === "master" && right === "key" || left === "stripe" && right === "key" || left === "service" && right === "role" || left === "security" && right === "code" || left === "two" && right === "factor" || left === "encryption" && right === "key" || left === "session" && (right === "id" || right === "key" || right === "token") || left === "card" && right === "number" || left === "mfa" && right === "code";
2280
- var defaultSensitivePropertyNames = () => DEFAULT_SENSITIVE_PROPERTY_NAME_SET;
2281
- var propertyNameIsSensitive = (key) => {
2282
- const compacted = compactKey(key);
2283
- const compactForms = [compacted, stripTrailingDigits(compacted)].filter(
2284
- (value, index, values) => value.length > 0 && values.indexOf(value) === index
2285
- );
2286
- for (const form of compactForms) {
2287
- if (DEFAULT_SENSITIVE_PROPERTY_NAME_SET.has(form)) return true;
2288
- for (const suffix of SENSITIVE_SUFFIXES) {
2289
- if (form.endsWith(suffix)) return true;
2290
- }
2291
- if (form.includes("apikey") || form.includes("xapikey")) return true;
2292
- }
2293
- const segments = segmentsOf(key).map((segment) => stripTrailingDigits(segment) || segment);
2294
- for (const segment of segments) {
2295
- if (SENSITIVE_SEGMENTS.has(segment)) return true;
2296
- }
2297
- for (let index = 0; index < segments.length - 1; index++) {
2298
- const left = segments[index];
2299
- const right = segments[index + 1];
2300
- if (left && right && adjacentPairsSensitive(left, right)) return true;
2301
- }
2302
- return false;
2303
- };
2304
-
2305
2574
  // src/instrumentation-manifest/instrumentation-manifest-types.ts
2306
2575
  var INSTRUMENTATION_MANIFEST_SCHEMA_VERSION = 3;
2307
2576
 
@@ -2333,9 +2602,12 @@ var uploadInstrumentationManifest = async (options) => {
2333
2602
  };
2334
2603
  export {
2335
2604
  INSTRUMENTATION_MANIFEST_SCHEMA_VERSION,
2605
+ MAX_ENV_SECRET_NEEDLE_LENGTH,
2606
+ RASPUTIN_REDACTED,
2336
2607
  RASPUTIN_WIRE_TAG,
2337
2608
  announceDeploy,
2338
2609
  createClient,
2610
+ createEnvSecretMatcher,
2339
2611
  createIngestEvent,
2340
2612
  createSuppression,
2341
2613
  createTransport,