@rasputin-ai/core 0.7.2-beta.1 → 0.9.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 (35) hide show
  1. package/README.md +3 -3
  2. package/dist/create-client/create-client-types.d.ts +8 -0
  3. package/dist/create-client/create-client-types.d.ts.map +1 -1
  4. package/dist/create-client/create-client.d.ts.map +1 -1
  5. package/dist/create-ingest-event/create-ingest-event-types.d.ts +2 -0
  6. package/dist/create-ingest-event/create-ingest-event-types.d.ts.map +1 -1
  7. package/dist/create-ingest-event/runtime-execution-state-types.d.ts +2 -4
  8. package/dist/create-ingest-event/runtime-execution-state-types.d.ts.map +1 -1
  9. package/dist/derivation/redact-env-secret-values.d.ts.map +1 -1
  10. package/dist/derivation/sensitive-property-name.d.ts.map +1 -1
  11. package/dist/index.js +360 -280
  12. package/dist/sdk-meta.d.ts +1 -1
  13. package/dist/sdk-meta.d.ts.map +1 -1
  14. package/dist/suppression/compute-burst-key.d.ts +1 -1
  15. package/dist/suppression/compute-burst-key.d.ts.map +1 -1
  16. package/dist/suppression/create-suppression.d.ts +3 -4
  17. package/dist/suppression/create-suppression.d.ts.map +1 -1
  18. package/dist/suppression/grouping-ref-cache.d.ts +6 -0
  19. package/dist/suppression/grouping-ref-cache.d.ts.map +1 -0
  20. package/dist/suppression/parameterize-message.d.ts +2 -4
  21. package/dist/suppression/parameterize-message.d.ts.map +1 -1
  22. package/dist/suppression/suppression-types.d.ts +6 -0
  23. package/dist/suppression/suppression-types.d.ts.map +1 -1
  24. package/dist/transport/create-transport.d.ts.map +1 -1
  25. package/dist/transport/default-ingest-request-timeout.d.ts +2 -0
  26. package/dist/transport/default-ingest-request-timeout.d.ts.map +1 -0
  27. package/dist/transport/node-http-fetch.d.ts +6 -3
  28. package/dist/transport/node-http-fetch.d.ts.map +1 -1
  29. package/dist/transport/send-batch.d.ts +6 -4
  30. package/dist/transport/send-batch.d.ts.map +1 -1
  31. package/dist/transport/transport-types.d.ts +13 -3
  32. package/dist/transport/transport-types.d.ts.map +1 -1
  33. package/package.json +1 -1
  34. package/dist/suppression/identity-needs-message-template.d.ts +0 -8
  35. 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() : "";
@@ -370,13 +446,6 @@ var DEFAULT_SENSITIVE_PROPERTY_NAMES = [
370
446
  "passwd",
371
447
  "pwd",
372
448
  "passphrase",
373
- "token",
374
- "accesstoken",
375
- "refreshtoken",
376
- "sessiontoken",
377
- "authorization",
378
- "auth",
379
- "bearer",
380
449
  "cookie",
381
450
  "cookies",
382
451
  "signedcookies",
@@ -384,77 +453,18 @@ var DEFAULT_SENSITIVE_PROPERTY_NAMES = [
384
453
  "secret",
385
454
  "secrets",
386
455
  "clientsecret",
387
- "apikey",
388
- "xapikey",
389
- "credential",
390
- "credentials",
391
- "privatekey",
392
- "privkey",
393
- "pem",
394
- "jwt",
395
- "otp",
396
- "totp",
397
- "pin",
398
- "mfacode",
399
- "sessionid",
400
- "sid",
401
- "databaseurl",
402
- "connectionstring",
403
- "dsn",
404
- "mongouri",
405
- "redisurl",
406
- "signingkey",
407
- "encryptionkey",
408
- "cvv",
409
- "cvc",
410
- "cardnumber",
411
- "ssn",
412
- "creditcard",
413
- "securitycode",
414
- "twofactorcode",
415
- "masterkey",
416
- "servicerolekey",
417
- "stripekey",
418
- "rawheaders"
456
+ "authorization",
457
+ "proxyauthorization",
458
+ "bearer"
419
459
  ];
420
460
  var DEFAULT_SENSITIVE_PROPERTY_NAME_SET = new Set(DEFAULT_SENSITIVE_PROPERTY_NAMES);
421
- var SENSITIVE_SEGMENTS = /* @__PURE__ */ new Set([
461
+ var SENSITIVE_SUFFIXES = [
422
462
  "password",
423
463
  "passwd",
424
- "pwd",
425
464
  "passphrase",
426
- "token",
427
- "secret",
465
+ "pwd",
428
466
  "cookie",
429
- "cookies",
430
- "authorization",
431
- "auth",
432
- "bearer",
433
- "credential",
434
- "credentials",
435
- "jwt",
436
- "otp",
437
- "totp",
438
- "pin",
439
- "sid",
440
- "dsn",
441
- "pem",
442
- "privkey",
443
- "cvv",
444
- "cvc",
445
- "ssn",
446
- "secrets"
447
- ]);
448
- var SENSITIVE_SUFFIXES = [
449
- "password",
450
- "passwd",
451
- "token",
452
- "secret",
453
- "apikey",
454
- "privatekey",
455
- "credential",
456
- "credentials",
457
- "cookie"
467
+ "cookies"
458
468
  ];
459
469
  var compactKey = (key) => {
460
470
  let compacted = "";
@@ -474,34 +484,6 @@ var stripTrailingDigits = (value) => {
474
484
  }
475
485
  return end === 0 ? value : value.slice(0, end);
476
486
  };
477
- var segmentsOf = (key) => {
478
- const segments = [];
479
- let current = "";
480
- for (let index = 0; index < key.length; index++) {
481
- const char = key[index] ?? "";
482
- const code = key.charCodeAt(index);
483
- if (code === 45 || code === 46 || code === 95) {
484
- if (current) segments.push(current.toLowerCase());
485
- current = "";
486
- continue;
487
- }
488
- if (current && code >= 65 && code <= 90) {
489
- const prev = current.charCodeAt(current.length - 1);
490
- const prevIsLower = prev >= 97 && prev <= 122;
491
- const next = key.charCodeAt(index + 1);
492
- const nextIsLower = next >= 97 && next <= 122;
493
- if (prevIsLower || nextIsLower) {
494
- segments.push(current.toLowerCase());
495
- current = char;
496
- continue;
497
- }
498
- }
499
- current += char;
500
- }
501
- if (current) segments.push(current.toLowerCase());
502
- return segments;
503
- };
504
- 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";
505
487
  var defaultSensitivePropertyNames = () => DEFAULT_SENSITIVE_PROPERTY_NAME_SET;
506
488
  var propertyNameIsSensitive = (key) => {
507
489
  const compacted = compactKey(key);
@@ -513,16 +495,6 @@ var propertyNameIsSensitive = (key) => {
513
495
  for (const suffix of SENSITIVE_SUFFIXES) {
514
496
  if (form.endsWith(suffix)) return true;
515
497
  }
516
- if (form.includes("apikey") || form.includes("xapikey")) return true;
517
- }
518
- const segments = segmentsOf(key).map((segment) => stripTrailingDigits(segment) || segment);
519
- for (const segment of segments) {
520
- if (SENSITIVE_SEGMENTS.has(segment)) return true;
521
- }
522
- for (let index = 0; index < segments.length - 1; index++) {
523
- const left = segments[index];
524
- const right = segments[index + 1];
525
- if (left && right && adjacentPairsSensitive(left, right)) return true;
526
498
  }
527
499
  return false;
528
500
  };
@@ -544,7 +516,11 @@ var identityMatcher = {
544
516
  redact: (text) => text,
545
517
  needleCount: 0
546
518
  };
547
- var envKeyLooksSensitive = (key) => propertyNameIsSensitive(key) || key.replace(/[-._]/g, "").toLowerCase().includes("accesskey");
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
+ };
548
524
  var isCollectableSecretValue = (value) => value.length >= MIN_ENV_SECRET_LENGTH && !COMMON_CONFIG_VALUES.has(value.toLowerCase());
549
525
  var looksLikeUrl = (value) => {
550
526
  const schemeEnd = value.indexOf("://");
@@ -657,6 +633,7 @@ var collectEnvSecretValues = (env = process.env) => {
657
633
  if (userinfo.userinfo.length >= MIN_ENV_SECRET_LENGTH) {
658
634
  addWithRepresentations(needles, userinfo.userinfo);
659
635
  }
636
+ addWithRepresentations(needles, value);
660
637
  }
661
638
  const queryValues = querySecretValues(value);
662
639
  for (let queryIndex = 0; queryIndex < queryValues.length; queryIndex++) {
@@ -1158,13 +1135,7 @@ var resolveSourceMaps = async (frames) => {
1158
1135
 
1159
1136
  // src/sdk-meta.ts
1160
1137
  var SDK_NAME = "@rasputin-ai/core";
1161
- var SDK_VERSION = "0.7.2-beta.1";
1162
-
1163
- // src/suppression/identity-needs-message-template.ts
1164
- var identityNeedsMessageTemplate = (type, code) => {
1165
- if (code != null && code !== "") return false;
1166
- return type === "Error";
1167
- };
1138
+ var SDK_VERSION = "0.9.0";
1168
1139
 
1169
1140
  // src/suppression/parameterize-message.ts
1170
1141
  var MAX_INPUT_LENGTH = 8192;
@@ -1222,10 +1193,6 @@ var RULES = [
1222
1193
  {
1223
1194
  token: "duration",
1224
1195
  pattern: /\b\d+(?:\.\d+)?\s?(?:ns|µs|us|ms|secs?|seconds?|mins?|minutes?|hrs?|hours?|days?)\b/giu
1225
- },
1226
- {
1227
- token: "id",
1228
- pattern: /\b(?=[a-z0-9]*[a-z])(?=[a-z0-9]*\d)[a-z0-9]{8,}\b/giu
1229
1196
  }
1230
1197
  ];
1231
1198
  var normalizeForIdentity = (normalized) => {
@@ -1286,19 +1253,22 @@ var frameBurstParts = (frames) => {
1286
1253
  const inApp = collapsed.filter((frame) => frame.in_app);
1287
1254
  const selected = (inApp.length > 0 ? inApp : collapsed).slice(0, FRAME_COUNT);
1288
1255
  return selected.map((frame) => {
1289
- const file = preferredFile(frame);
1256
+ const loc = frame.original ?? frame.generated;
1290
1257
  const fn = normalizeFunction(frame.function ?? frame.original?.name);
1291
- return `${file}:${fn}`;
1258
+ return `${loc.file}:${fn}:${loc.line ?? ""}:${loc.column ?? ""}`;
1292
1259
  });
1293
1260
  };
1294
- var computeBurstKey = (errorType, message, code, frames) => {
1261
+ var computeBurstKey = (errorType, message, code, frames, route, method) => {
1295
1262
  const parts = frameBurstParts(frames);
1296
1263
  const codePart = code ?? "";
1297
- let key = `${errorType}\0${codePart}\0${parts.join("\0")}`;
1298
- if (parts.length === 0 || identityNeedsMessageTemplate(errorType, code)) {
1299
- key += `\0${parameterizeMessage(message)}`;
1300
- }
1301
- return key;
1264
+ return [
1265
+ errorType,
1266
+ codePart,
1267
+ method ?? "",
1268
+ route ?? "",
1269
+ parameterizeMessage(message),
1270
+ parts.join("\0")
1271
+ ].join("\0");
1302
1272
  };
1303
1273
 
1304
1274
  // src/suppression/create-suppression.ts
@@ -1349,9 +1319,12 @@ var createSuppression = (options = {}) => {
1349
1319
  windowStart: bucket.windowStart,
1350
1320
  windowEndMs,
1351
1321
  lastRelease: bucket.lastRelease,
1322
+ lastRoute: bucket.lastRoute,
1323
+ lastMethod: bucket.lastMethod,
1352
1324
  lastEnvironment: bucket.lastEnvironment,
1353
1325
  identity: bucket.identity,
1354
- captureRawFrames: bucket.captureRawFrames
1326
+ captureRawFrames: bucket.captureRawFrames,
1327
+ burstKey: bucket.burstKey
1355
1328
  });
1356
1329
  const toSummary = (draft, identity) => ({
1357
1330
  count: draft.count,
@@ -1359,7 +1332,8 @@ var createSuppression = (options = {}) => {
1359
1332
  window_end: new Date(draft.windowEndMs).toISOString(),
1360
1333
  release: draft.lastRelease,
1361
1334
  environment: draft.lastEnvironment,
1362
- ...identity
1335
+ ...identity,
1336
+ ...draft.lastRoute || draft.lastMethod ? { request: { route: draft.lastRoute, method: draft.lastMethod } } : {}
1363
1337
  });
1364
1338
  const flushSuppressed = (bucket, windowEndMs) => {
1365
1339
  if (bucket.suppressed <= 0) return;
@@ -1370,9 +1344,17 @@ var createSuppression = (options = {}) => {
1370
1344
  const now = input.now ?? Date.now();
1371
1345
  const identity = captureIdentity(input);
1372
1346
  const captureRawFrames = (input.rawFrames ?? []).slice(0, MAX_STACK_FRAMES);
1373
- 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
+ );
1374
1355
  const release = input.release;
1375
1356
  const route = input.route ?? null;
1357
+ const method = input.method ?? null;
1376
1358
  let bucket = burstBuckets.get(burstKey);
1377
1359
  if (!bucket) {
1378
1360
  bucket = {
@@ -1381,9 +1363,11 @@ var createSuppression = (options = {}) => {
1381
1363
  windowStart: now,
1382
1364
  lastRelease: release,
1383
1365
  lastRoute: route,
1366
+ lastMethod: method,
1384
1367
  lastEnvironment: input.environment,
1385
1368
  identity,
1386
- captureRawFrames
1369
+ captureRawFrames,
1370
+ burstKey
1387
1371
  };
1388
1372
  burstBuckets.set(burstKey, bucket);
1389
1373
  evictIfNeeded();
@@ -1398,6 +1382,7 @@ var createSuppression = (options = {}) => {
1398
1382
  bucket.windowStart = now;
1399
1383
  bucket.lastRelease = release;
1400
1384
  bucket.lastRoute = route;
1385
+ bucket.lastMethod = method;
1401
1386
  bucket.lastEnvironment = input.environment;
1402
1387
  bucket.identity = identity;
1403
1388
  bucket.captureRawFrames = captureRawFrames;
@@ -1407,6 +1392,7 @@ var createSuppression = (options = {}) => {
1407
1392
  bucket.sentInWindow += 1;
1408
1393
  bucket.lastRelease = release;
1409
1394
  bucket.lastRoute = route;
1395
+ bucket.lastMethod = method;
1410
1396
  bucket.lastEnvironment = input.environment;
1411
1397
  return { action: "send", priority: "repeat" };
1412
1398
  }
@@ -1423,7 +1409,8 @@ var createSuppression = (options = {}) => {
1423
1409
  }
1424
1410
  return drafts.map((draft) => ({
1425
1411
  summary: toSummary(draft, draft.identity),
1426
- rawFrames: draft.captureRawFrames
1412
+ rawFrames: draft.captureRawFrames,
1413
+ burstKey: draft.burstKey
1427
1414
  }));
1428
1415
  };
1429
1416
  const takeSummaries = async (now = Date.now(), prepareSummary) => {
@@ -1453,6 +1440,42 @@ var createSuppression = (options = {}) => {
1453
1440
  };
1454
1441
  };
1455
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
+
1456
1479
  // src/transport/process-shutdown-hooks.ts
1457
1480
  var SIGNAL_EXIT_CODES = { SIGTERM: 143, SIGINT: 130 };
1458
1481
  var RASPUTIN_PROCESS_LISTENER = Symbol.for("rasputin.process.listener.v1");
@@ -1529,33 +1552,32 @@ var encodeJsonRequestBody = (payload, gzipThresholdBytes = GZIP_JSON_THRESHOLD_B
1529
1552
 
1530
1553
  // src/transport/send-batch.ts
1531
1554
  var sleep = (ms, signal) => new Promise((resolve4) => {
1532
- if (ms <= 0 || signal?.aborted) {
1555
+ if (ms <= 0) {
1533
1556
  resolve4();
1534
1557
  return;
1535
1558
  }
1536
1559
  const timer = setTimeout(resolve4, ms);
1537
1560
  timer.unref?.();
1538
- signal?.addEventListener(
1539
- "abort",
1540
- () => {
1541
- clearTimeout(timer);
1542
- resolve4();
1543
- },
1544
- { once: true }
1545
- );
1561
+ const onAbort = () => {
1562
+ clearTimeout(timer);
1563
+ resolve4();
1564
+ };
1565
+ signal.addEventListener("abort", onAbort, { once: true });
1566
+ if (signal.aborted) onAbort();
1546
1567
  });
1547
1568
  var backoffMs = (attempt) => {
1548
1569
  const base = Math.min(3e4, 500 * 2 ** attempt);
1549
1570
  return Math.floor(base * (0.5 + Math.random()));
1550
1571
  };
1551
- var abortError = () => Object.assign(new Error("aborted"), { name: "AbortError" });
1572
+ var abortError2 = () => Object.assign(new Error("aborted"), { name: "AbortError" });
1552
1573
  var fetchUntilAborted = (fetchFn, url, init) => new Promise((resolve4, reject) => {
1574
+ const onAbort = () => reject(abortError2());
1575
+ init.signal.addEventListener("abort", onAbort, { once: true });
1553
1576
  if (init.signal.aborted) {
1554
- reject(abortError());
1577
+ init.signal.removeEventListener("abort", onAbort);
1578
+ reject(abortError2());
1555
1579
  return;
1556
1580
  }
1557
- const onAbort = () => reject(abortError());
1558
- init.signal.addEventListener("abort", onAbort, { once: true });
1559
1581
  void fetchFn(url, init).then(
1560
1582
  (value) => {
1561
1583
  init.signal.removeEventListener("abort", onAbort);
@@ -1575,8 +1597,29 @@ var retryAfterMs = (response) => {
1575
1597
  const date = Date.parse(header);
1576
1598
  return Number.isFinite(date) ? Math.max(0, date - Date.now()) : null;
1577
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
+ };
1578
1620
  var sendBatch = async (options) => {
1579
1621
  const summaries = options.summaries ?? [];
1622
+ const { signal } = options;
1580
1623
  let payload;
1581
1624
  if (summaries.length > 0) {
1582
1625
  payload = { events: options.events, summaries };
@@ -1585,12 +1628,13 @@ var sendBatch = async (options) => {
1585
1628
  }
1586
1629
  if (options.sdkVersion) payload.sdk_version = options.sdkVersion;
1587
1630
  if (options.sdkName) payload.sdk_name = options.sdkName;
1631
+ payload.scrub_pii = options.scrubPii !== false;
1588
1632
  if (options.idempotencyKey) payload.idempotency_key = options.idempotencyKey;
1589
1633
  let attempts = 0;
1590
1634
  let networkErrors = 0;
1591
1635
  let rateLimitedResponses = 0;
1592
1636
  let serverErrorResponses = 0;
1593
- const result = (outcome, failureKind = null, statusCode = null) => ({
1637
+ const result = (outcome, failureKind = null, statusCode = null, eventGroupingRefs = [], summaryGroupingRefs = []) => ({
1594
1638
  outcome,
1595
1639
  attempts,
1596
1640
  retries: Math.max(0, attempts - 1),
@@ -1599,15 +1643,10 @@ var sendBatch = async (options) => {
1599
1643
  serverErrorResponses,
1600
1644
  aborted: failureKind === "aborted",
1601
1645
  failureKind,
1602
- statusCode
1646
+ statusCode,
1647
+ eventGroupingRefs,
1648
+ summaryGroupingRefs
1603
1649
  });
1604
- const abortAfter = (ms) => {
1605
- const controller = new AbortController();
1606
- const timer = setTimeout(() => controller.abort(), ms);
1607
- timer.unref?.();
1608
- return { signal: controller.signal, cancel: () => clearTimeout(timer) };
1609
- };
1610
- const requestTimeoutMs = options.requestTimeoutMs ?? 5e3;
1611
1650
  const { body, gzip } = encodeJsonRequestBody(payload, options.gzipThresholdBytes);
1612
1651
  const headers = {
1613
1652
  Authorization: `Bearer ${options.projectApiKey}`,
@@ -1616,10 +1655,8 @@ var sendBatch = async (options) => {
1616
1655
  };
1617
1656
  if (gzip) headers["Content-Encoding"] = "gzip";
1618
1657
  for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
1619
- if (options.signal?.aborted) return result("failed", "aborted");
1658
+ if (signal.aborted) return result("failed", "aborted");
1620
1659
  attempts++;
1621
- const timeout = abortAfter(requestTimeoutMs);
1622
- const signal = options.signal ? AbortSignal.any([options.signal, timeout.signal]) : timeout.signal;
1623
1660
  try {
1624
1661
  const response = await fetchUntilAborted(options.fetch, options.ingestUrl, {
1625
1662
  method: "POST",
@@ -1627,13 +1664,22 @@ var sendBatch = async (options) => {
1627
1664
  body,
1628
1665
  signal
1629
1666
  });
1630
- 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
+ }
1631
1677
  if (response.status === 429) {
1632
1678
  rateLimitedResponses++;
1633
1679
  if (attempt === options.maxRetries)
1634
1680
  return result("failed", "rate_limited", response.status);
1635
1681
  const wait = retryAfterMs(response) ?? backoffMs(attempt);
1636
- await sleep(wait, options.signal);
1682
+ await sleep(wait, signal);
1637
1683
  continue;
1638
1684
  }
1639
1685
  if (response.status >= 400 && response.status < 500) {
@@ -1641,19 +1687,17 @@ var sendBatch = async (options) => {
1641
1687
  }
1642
1688
  serverErrorResponses++;
1643
1689
  if (attempt === options.maxRetries) return result("failed", "server_error", response.status);
1644
- await sleep(backoffMs(attempt), options.signal);
1690
+ await sleep(backoffMs(attempt), signal);
1645
1691
  } catch (error) {
1646
1692
  if (signal.aborted || error instanceof Error && error.name === "AbortError") {
1647
1693
  return result("failed", "aborted");
1648
1694
  }
1649
1695
  networkErrors++;
1650
1696
  if (attempt === options.maxRetries) return result("failed", "network_error");
1651
- await sleep(backoffMs(attempt), options.signal);
1652
- } finally {
1653
- timeout.cancel();
1697
+ await sleep(backoffMs(attempt), signal);
1654
1698
  }
1655
1699
  }
1656
- return result("failed", options.signal?.aborted ? "aborted" : "network_error");
1700
+ return result("failed", signal.aborted ? "aborted" : "network_error");
1657
1701
  };
1658
1702
 
1659
1703
  // src/transport/transport-queue.ts
@@ -1780,9 +1824,8 @@ var createTransport = (options) => {
1780
1824
  const flushIntervalMs = options.flushIntervalMs ?? DEFAULTS2.flushIntervalMs;
1781
1825
  const gzipThresholdBytes = options.gzipThresholdBytes ?? DEFAULTS2.gzipThresholdBytes;
1782
1826
  const maxRetries = options.maxRetries ?? DEFAULTS2.maxRetries;
1783
- const requestTimeoutMs = options.requestTimeoutMs ?? 5e3;
1827
+ const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_INGEST_REQUEST_TIMEOUT_MS;
1784
1828
  const fetchFn = options.fetch ?? defaultIngestFetch();
1785
- const backgroundFetch = options.fetch ?? defaultIngestFetch({ unrefSockets: true });
1786
1829
  const queue = createQueue(
1787
1830
  options.maxQueueSize ?? DEFAULTS2.maxQueueSize,
1788
1831
  options.maxQueueBytes ?? DEFAULTS2.maxQueueBytes
@@ -1810,6 +1853,7 @@ var createTransport = (options) => {
1810
1853
  let flushAbort;
1811
1854
  let flushAbortTimer;
1812
1855
  let flushDeadlineMs = 0;
1856
+ let flushHoldsProcess = false;
1813
1857
  let inFlightBytes = 0;
1814
1858
  let retryBytes = 0;
1815
1859
  const estimateItemsBytes = (items) => {
@@ -1824,23 +1868,30 @@ var createTransport = (options) => {
1824
1868
  };
1825
1869
  const setFlushDeadline = (timeoutMs, holdProcess = false) => {
1826
1870
  const nextDeadline = timeoutMs > 0 ? Date.now() + timeoutMs : Number.POSITIVE_INFINITY;
1827
- if (flushAbort && nextDeadline >= flushDeadlineMs) return;
1828
- flushDeadlineMs = nextDeadline;
1829
- 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
+ }
1830
1878
  if (flushAbortTimer) clearTimeout(flushAbortTimer);
1831
- if (Number.isFinite(nextDeadline)) {
1832
- flushAbortTimer = setTimeout(
1833
- () => flushAbort?.abort(),
1834
- Math.max(0, nextDeadline - Date.now())
1835
- );
1836
- if (!holdProcess) flushAbortTimer.unref?.();
1879
+ if (!Number.isFinite(flushDeadlineMs)) {
1880
+ flushAbortTimer = void 0;
1881
+ return;
1837
1882
  }
1883
+ flushAbortTimer = setTimeout(
1884
+ () => flushAbort?.abort(),
1885
+ Math.max(0, flushDeadlineMs - Date.now())
1886
+ );
1887
+ if (!flushHoldsProcess) flushAbortTimer.unref?.();
1838
1888
  };
1839
1889
  const clearFlushDeadline = () => {
1840
1890
  if (flushAbortTimer) clearTimeout(flushAbortTimer);
1841
1891
  flushAbortTimer = void 0;
1842
1892
  flushAbort = void 0;
1843
1893
  flushDeadlineMs = 0;
1894
+ flushHoldsProcess = false;
1844
1895
  };
1845
1896
  const schedule = () => {
1846
1897
  if (timer || startTimer || !enabled || closed || shuttingDown2) return;
@@ -1879,11 +1930,8 @@ var createTransport = (options) => {
1879
1930
  };
1880
1931
  const whenAborted = (signal) => new Promise((_, reject) => {
1881
1932
  const abort = () => reject(signal.reason ?? new Error("aborted"));
1882
- if (signal.aborted) {
1883
- abort();
1884
- return;
1885
- }
1886
1933
  signal.addEventListener("abort", abort, { once: true });
1934
+ if (signal.aborted) abort();
1887
1935
  });
1888
1936
  const performFlush = async () => {
1889
1937
  options.beforeFlush?.();
@@ -1937,15 +1985,15 @@ var createTransport = (options) => {
1937
1985
  }
1938
1986
  }
1939
1987
  const retainOrDrop = (nextReplays, createdAt) => {
1988
+ inFlightBytes = 0;
1940
1989
  if (nextReplays >= DEFAULTS2.maxBatchReplays || Date.now() - createdAt >= DEFAULTS2.maxRetryBatchAgeMs) {
1941
1990
  batchesDropped++;
1942
1991
  retryBatch = null;
1943
1992
  retryBytes = 0;
1944
1993
  } else {
1945
1994
  retryBatch = { items: batch, idempotencyKey, createdAt, replays: nextReplays };
1946
- retryBytes = inFlightBytes;
1995
+ retryBytes = estimateItemsBytes(batch);
1947
1996
  }
1948
- inFlightBytes = 0;
1949
1997
  syncReservedBytes();
1950
1998
  };
1951
1999
  if (controller.signal.aborted) {
@@ -1960,10 +2008,10 @@ var createTransport = (options) => {
1960
2008
  idempotencyKey,
1961
2009
  sdkVersion: options.sdkVersion,
1962
2010
  sdkName: options.sdkName,
2011
+ scrubPii: options.scrubPii,
1963
2012
  gzipThresholdBytes,
1964
2013
  maxRetries: shuttingDown2 ? 0 : maxRetries,
1965
- requestTimeoutMs,
1966
- fetch: shuttingDown2 ? fetchFn : backgroundFetch,
2014
+ fetch: fetchFn,
1967
2015
  signal: controller.signal
1968
2016
  });
1969
2017
  sendAttempts += result.attempts;
@@ -1972,8 +2020,21 @@ var createTransport = (options) => {
1972
2020
  rateLimitedResponses += result.rateLimitedResponses;
1973
2021
  serverErrorResponses += result.serverErrorResponses;
1974
2022
  if (result.aborted) flushTimeouts++;
1975
- if (result.outcome === "ok") batchesSucceeded++;
1976
- 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 {
1977
2038
  lastFailureKind = result.failureKind;
1978
2039
  lastFailureStatusCode = result.statusCode;
1979
2040
  if (result.outcome === "dropped") batchesDropped++;
@@ -2029,15 +2090,15 @@ var createTransport = (options) => {
2029
2090
  }
2030
2091
  await startFlushWorker();
2031
2092
  };
2032
- const enqueue = (event, priority = "first", rawFrames = [], encodedBytes) => {
2093
+ const enqueue = (event, priority = "first", rawFrames = [], encodedBytes, burstKey) => {
2033
2094
  if (!enabled || closed) return;
2034
- queue.enqueue({ kind: "event", event, priority, rawFrames, encodedBytes });
2095
+ queue.enqueue({ kind: "event", event, priority, rawFrames, encodedBytes, burstKey });
2035
2096
  schedule();
2036
2097
  if (queue.size() >= batchSize) kickBackgroundFlush();
2037
2098
  };
2038
- const enqueueSummary = (summary, rawFrames = []) => {
2099
+ const enqueueSummary = (summary, rawFrames = [], burstKey) => {
2039
2100
  if (!enabled || closed) return;
2040
- queue.enqueue({ kind: "summary", summary, priority: "summary", rawFrames });
2101
+ queue.enqueue({ kind: "summary", summary, priority: "summary", rawFrames, burstKey });
2041
2102
  schedule();
2042
2103
  if (queue.size() >= batchSize) kickBackgroundFlush();
2043
2104
  };
@@ -2080,21 +2141,10 @@ var createTransport = (options) => {
2080
2141
  stopTimer();
2081
2142
  unregisterSignals();
2082
2143
  shuttingDown2 = true;
2083
- await new Promise((resolve4, reject) => {
2084
- const timer2 = setTimeout(resolve4, DEFAULTS2.shutdownFlushMs + 250);
2085
- void flush(DEFAULTS2.shutdownFlushMs).then(
2086
- () => {
2087
- clearTimeout(timer2);
2088
- resolve4();
2089
- },
2090
- (error) => {
2091
- clearTimeout(timer2);
2092
- reject(error);
2093
- }
2094
- );
2144
+ await flush(DEFAULTS2.shutdownFlushMs).catch(() => {
2095
2145
  });
2096
- dropRemainingOnExit();
2097
2146
  closed = true;
2147
+ dropRemainingOnExit();
2098
2148
  queue.clear();
2099
2149
  retryBatch = null;
2100
2150
  };
@@ -2316,6 +2366,7 @@ var createClient = (options, hooks = {}) => {
2316
2366
  return pending;
2317
2367
  };
2318
2368
  const suppression = createSuppression();
2369
+ const groupingRefs = createGroupingRefCache();
2319
2370
  let enqueuePendingSummaries = () => {
2320
2371
  };
2321
2372
  const transport = createTransport({
@@ -2326,7 +2377,23 @@ var createClient = (options, hooks = {}) => {
2326
2377
  installSignalHandlers: hooks.installTransportSignalHandlers,
2327
2378
  sdkVersion: hooks.sdkVersion ?? SDK_VERSION,
2328
2379
  sdkName: hooks.sdkName ?? SDK_NAME,
2380
+ scrubPii: options.scrubPII !== false,
2329
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
+ },
2330
2397
  prepareEvent: async ({ event, rawFrames }) => {
2331
2398
  const prepared = await resolveStack(rawFrames);
2332
2399
  return withResolvedFrames(event, prepared.frames, prepared.resolution);
@@ -2360,6 +2427,16 @@ var createClient = (options, hooks = {}) => {
2360
2427
  request: context?.request
2361
2428
  });
2362
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;
2363
2440
  const decision = suppression.decide({
2364
2441
  errorType: event.type,
2365
2442
  message: event.message,
@@ -2369,7 +2446,8 @@ var createClient = (options, hooks = {}) => {
2369
2446
  code: event.code,
2370
2447
  environment,
2371
2448
  release,
2372
- route: context?.request?.route
2449
+ route: context?.request?.route,
2450
+ method: context?.request?.method
2373
2451
  });
2374
2452
  if (decision.action !== "send") return;
2375
2453
  const runtimeState = resolveRuntimeEvidence(context, hooks);
@@ -2380,13 +2458,15 @@ var createClient = (options, hooks = {}) => {
2380
2458
  } catch {
2381
2459
  encodedBytes = void 0;
2382
2460
  }
2383
- transport.enqueue(event, decision.priority, rawFrames, encodedBytes);
2461
+ transport.enqueue(event, decision.priority, rawFrames, encodedBytes, burstKey);
2384
2462
  } catch {
2385
2463
  }
2386
2464
  };
2387
2465
  enqueuePendingSummaries = () => {
2388
- for (const { summary, rawFrames } of suppression.drainSummaries()) {
2389
- 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);
2390
2470
  }
2391
2471
  };
2392
2472
  const flush = async (timeoutMs) => {