@rasputin-ai/core 0.5.0-alpha.9 → 0.5.1

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 (60) hide show
  1. package/README.md +3 -3
  2. package/dist/announce-deploy/announce-deploy.d.ts +2 -2
  3. package/dist/announce-deploy/announce-deploy.d.ts.map +1 -1
  4. package/dist/create-client/create-client-types.d.ts +23 -8
  5. package/dist/create-client/create-client-types.d.ts.map +1 -1
  6. package/dist/create-client/create-client.d.ts.map +1 -1
  7. package/dist/create-client/is-client-enabled.d.ts +17 -3
  8. package/dist/create-client/is-client-enabled.d.ts.map +1 -1
  9. package/dist/create-ingest-event/create-ingest-event-types.d.ts +4 -1
  10. package/dist/create-ingest-event/create-ingest-event-types.d.ts.map +1 -1
  11. package/dist/create-ingest-event/runtime-execution-state-types.d.ts +104 -43
  12. package/dist/create-ingest-event/runtime-execution-state-types.d.ts.map +1 -1
  13. package/dist/derivation/canonical-json.d.ts +0 -1
  14. package/dist/derivation/canonical-json.d.ts.map +1 -1
  15. package/dist/derivation/hash-instrumentation-manifest.d.ts +2 -2
  16. package/dist/derivation/hash-instrumentation-manifest.d.ts.map +1 -1
  17. package/dist/derivation/rasputin-wire-tag.d.ts +14 -2
  18. package/dist/derivation/rasputin-wire-tag.d.ts.map +1 -1
  19. package/dist/derivation/sensitive-property-name.d.ts +3 -0
  20. package/dist/derivation/sensitive-property-name.d.ts.map +1 -0
  21. package/dist/derivation/sha256.d.ts +0 -1
  22. package/dist/derivation/sha256.d.ts.map +1 -1
  23. package/dist/index.d.ts +4 -5
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +860 -1238
  26. package/dist/instrumentation-manifest/instrumentation-manifest-types.d.ts +26 -2
  27. package/dist/instrumentation-manifest/instrumentation-manifest-types.d.ts.map +1 -1
  28. package/dist/instrumentation-manifest/upload-instrumentation-manifest.d.ts +1 -0
  29. package/dist/instrumentation-manifest/upload-instrumentation-manifest.d.ts.map +1 -1
  30. package/dist/normalize-frames/normalize-frames.d.ts.map +1 -1
  31. package/dist/parse-stack/fold-synthetic-frames.d.ts +15 -0
  32. package/dist/parse-stack/fold-synthetic-frames.d.ts.map +1 -0
  33. package/dist/resolve-source-maps/resolve-source-maps.d.ts.map +1 -1
  34. package/dist/sdk-meta.d.ts +1 -1
  35. package/dist/sdk-meta.d.ts.map +1 -1
  36. package/dist/source-identity/source-identity.d.ts +14 -1
  37. package/dist/source-identity/source-identity.d.ts.map +1 -1
  38. package/dist/suppression/create-suppression.d.ts.map +1 -1
  39. package/dist/suppression/suppression-types.d.ts +2 -0
  40. package/dist/suppression/suppression-types.d.ts.map +1 -1
  41. package/dist/transport/create-transport.d.ts.map +1 -1
  42. package/dist/transport/node-http-fetch.d.ts +3 -0
  43. package/dist/transport/node-http-fetch.d.ts.map +1 -0
  44. package/dist/transport/process-shutdown-hooks.d.ts +8 -0
  45. package/dist/transport/process-shutdown-hooks.d.ts.map +1 -0
  46. package/dist/transport/send-batch.d.ts +1 -0
  47. package/dist/transport/send-batch.d.ts.map +1 -1
  48. package/dist/transport/transport-queue.d.ts +2 -1
  49. package/dist/transport/transport-queue.d.ts.map +1 -1
  50. package/dist/transport/transport-types.d.ts +36 -11
  51. package/dist/transport/transport-types.d.ts.map +1 -1
  52. package/package.json +2 -2
  53. package/dist/derivation/captured-value-markers.d.ts +0 -5
  54. package/dist/derivation/captured-value-markers.d.ts.map +0 -1
  55. package/dist/derivation/evaluate-derivation-plan.d.ts +0 -53
  56. package/dist/derivation/evaluate-derivation-plan.d.ts.map +0 -1
  57. package/dist/derivation/wire-values-equal.d.ts +0 -3
  58. package/dist/derivation/wire-values-equal.d.ts.map +0 -1
  59. package/dist/instrumentation-manifest/derivation-plan-types.d.ts +0 -142
  60. package/dist/instrumentation-manifest/derivation-plan-types.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,27 +1,122 @@
1
+ // src/transport/node-http-fetch.ts
2
+ import * as http from "node:http";
3
+ import * as https from "node:https";
4
+ var headerRecord = (headers) => {
5
+ const view = new Headers(headers);
6
+ const record = {};
7
+ view.forEach((value, key) => {
8
+ record[key] = value;
9
+ });
10
+ return record;
11
+ };
12
+ var writeBody = (req, body) => {
13
+ if (body == null) {
14
+ req.end();
15
+ return;
16
+ }
17
+ if (typeof body === "string" || body instanceof Uint8Array) {
18
+ req.end(body);
19
+ return;
20
+ }
21
+ req.end();
22
+ };
23
+ var nodeHttpFetch = (input, init) => new Promise((resolve4, reject) => {
24
+ let parsed;
25
+ try {
26
+ parsed = new URL(String(input));
27
+ } catch (error) {
28
+ reject(error);
29
+ return;
30
+ }
31
+ const lib = parsed.protocol === "https:" ? https : http;
32
+ const req = lib.request(
33
+ {
34
+ protocol: parsed.protocol,
35
+ hostname: parsed.hostname,
36
+ port: parsed.port,
37
+ path: `${parsed.pathname}${parsed.search}`,
38
+ method: init?.method ?? "GET",
39
+ headers: headerRecord(init?.headers)
40
+ },
41
+ (response) => {
42
+ const chunks = [];
43
+ response.on("data", (chunk) => {
44
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
45
+ });
46
+ response.on("end", () => {
47
+ const headers = new Headers();
48
+ for (const [key, value] of Object.entries(response.headers)) {
49
+ if (value === void 0) continue;
50
+ if (Array.isArray(value)) {
51
+ for (const item of value) headers.append(key, item);
52
+ } else {
53
+ headers.set(key, value);
54
+ }
55
+ }
56
+ resolve4(
57
+ new Response(Buffer.concat(chunks), {
58
+ status: response.statusCode ?? 0,
59
+ headers
60
+ })
61
+ );
62
+ });
63
+ }
64
+ );
65
+ const abort = () => {
66
+ req.destroy();
67
+ reject(Object.assign(new Error("aborted"), { name: "AbortError" }));
68
+ };
69
+ if (init?.signal) {
70
+ if (init.signal.aborted) {
71
+ abort();
72
+ return;
73
+ }
74
+ init.signal.addEventListener("abort", abort, { once: true });
75
+ }
76
+ req.setTimeout(1e4, abort);
77
+ req.on("error", reject);
78
+ writeBody(req, init?.body ?? null);
79
+ });
80
+ var defaultIngestFetch = () => process.versions.bun ? globalThis.fetch : nodeHttpFetch;
81
+
1
82
  // src/announce-deploy/announce-deploy.ts
83
+ var trimmedString = (value) => typeof value === "string" ? value.trim() : "";
2
84
  var announceDeploy = async (options) => {
3
- if (options.enabled === false) return "skipped";
4
- const release = options.release?.trim() ?? "";
5
- const environment = options.environment.trim();
6
- if (!environment) return "skipped";
7
- const fetchFn = options.fetch ?? globalThis.fetch;
8
85
  try {
9
- const response = await fetchFn(options.deployUrl, {
10
- method: "POST",
11
- headers: {
12
- Authorization: `Bearer ${options.projectApiKey}`,
13
- "Content-Type": "application/json",
14
- Accept: "application/json"
15
- },
16
- body: JSON.stringify({ release, environment })
17
- });
18
- if (response.ok) return "ok";
19
- return "failed";
86
+ if (options.enabled === false) return "skipped";
87
+ const release = trimmedString(options.release);
88
+ const environment = trimmedString(options.environment);
89
+ if (!environment) return "skipped";
90
+ const fetchFn = options.fetch ?? defaultIngestFetch();
91
+ const timeout = new AbortController();
92
+ const timer = setTimeout(() => timeout.abort(), 2e3);
93
+ timer.unref?.();
94
+ try {
95
+ const response = await fetchFn(options.deployUrl, {
96
+ method: "POST",
97
+ headers: {
98
+ Authorization: `Bearer ${options.projectApiKey}`,
99
+ "Content-Type": "application/json",
100
+ Accept: "application/json"
101
+ },
102
+ body: JSON.stringify({ release, environment }),
103
+ signal: timeout.signal
104
+ });
105
+ if (response.ok) return "ok";
106
+ return "failed";
107
+ } catch {
108
+ return "failed";
109
+ } finally {
110
+ clearTimeout(timer);
111
+ }
20
112
  } catch {
21
113
  return "failed";
22
114
  }
23
115
  };
24
116
 
117
+ // src/create-client/create-client.ts
118
+ import { Buffer as Buffer2 } from "node:buffer";
119
+
25
120
  // src/create-ingest-event/truncate-ingest-message.ts
26
121
  var MAX_MESSAGE_BYTES = 8 * 1024;
27
122
  var utf8Bytes = (value) => Buffer.byteLength(value, "utf8");
@@ -213,74 +308,41 @@ var finalizeResolution = (resolution, stackFrames) => {
213
308
  return "exact";
214
309
  };
215
310
 
216
- // src/detect-release/detect-release.ts
217
- import { readFileSync } from "node:fs";
218
- import { join } from "node:path";
219
- var ENV_KEYS = [
220
- "RASPUTIN_RELEASE",
221
- "VERCEL_GIT_COMMIT_SHA",
222
- "RAILWAY_GIT_COMMIT_SHA",
223
- "RENDER_GIT_COMMIT",
224
- "COMMIT_REF",
225
- "CF_PAGES_COMMIT_SHA",
226
- "HEROKU_SLUG_COMMIT",
227
- "GIT_COMMIT",
228
- "COMMIT_SHA",
229
- "GIT_SHA"
230
- ];
231
- var SHA_RE = /^[0-9a-f]{7,40}$/i;
232
- var isSha = (value) => SHA_RE.test(value);
233
- var warnInvalid = (source, value) => {
234
- console.warn(
235
- `[rasputin] Ignoring invalid release from ${source}: "${value}". Expected a git commit SHA (7\u201340 hex characters).`
236
- );
237
- };
238
- var readGitHead = (cwd) => {
239
- try {
240
- const gitDir = join(cwd, ".git");
241
- const head = readFileSync(join(gitDir, "HEAD"), "utf8").trim();
242
- if (isSha(head)) return head;
243
- const ref = head.replace(/^ref:\s*/, "");
244
- try {
245
- const sha = readFileSync(join(gitDir, ref), "utf8").trim();
246
- return isSha(sha) ? sha : void 0;
247
- } catch {
248
- const packed = readFileSync(join(gitDir, "packed-refs"), "utf8");
249
- const match = packed.match(new RegExp(`^([0-9a-f]{40}) ${ref}$`, "m"));
250
- return match?.[1];
251
- }
252
- } catch {
253
- return void 0;
254
- }
255
- };
256
- var detectRelease = (options = {}) => {
257
- const explicit = options.release?.trim();
258
- if (explicit) {
259
- if (isSha(explicit)) return explicit;
260
- warnInvalid("release option", explicit);
261
- }
262
- const env = options.env ?? process.env;
263
- for (const key of ENV_KEYS) {
264
- const value = env[key]?.trim();
265
- if (!value) continue;
266
- if (isSha(value)) return value;
267
- warnInvalid(key, value);
268
- }
269
- const fromGit = readGitHead(options.cwd ?? process.cwd());
270
- if (fromGit) return fromGit;
271
- console.warn(
272
- "[rasputin] No release detected. Set `RASPUTIN_RELEASE` to your git commit SHA, pass `release` in RasputinInit, or deploy on a platform that exposes a commit SHA env var (e.g. VERCEL_GIT_COMMIT_SHA). Events are not sent without a release."
273
- );
274
- return void 0;
275
- };
276
-
277
311
  // src/normalize-frames/normalize-frames.ts
278
312
  import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
279
313
  import { fileURLToPath as fileURLToPath2 } from "node:url";
280
314
 
315
+ // src/parse-stack/fold-synthetic-frames.ts
316
+ var RASPUTIN_SHELL_PREFIX = "__rasputinShell__";
317
+ var RASPUTIN_HANDLE_RUN_PREFIX = "__rasputinHandleRun__";
318
+ var frameFile = (frame) => (frame.file ?? frame.generated?.file ?? "").replaceAll("\\", "/");
319
+ var functionName = (frame) => frame.function ?? "";
320
+ var coreFunctionName = (name) => name.trim().replace(/^(async|new|get|set|bound)\s+/, "").replace(/^Object\./, "");
321
+ var isRasputinShellFrame = (name) => typeof name === "string" && coreFunctionName(name).startsWith(RASPUTIN_SHELL_PREFIX);
322
+ var isRasputinHandleRunFrame = (name) => typeof name === "string" && coreFunctionName(name).startsWith(RASPUTIN_HANDLE_RUN_PREFIX);
323
+ var isEngineAsyncStoreFile = (file) => /^(node:|bun:)/i.test(file) && /async[_-]?hooks|async_local_storage|async_context_frame/i.test(file);
324
+ var isInternalAsyncStoreFrame = (frame) => {
325
+ const name = functionName(frame);
326
+ const file = frameFile(frame);
327
+ if (name === "AsyncLocalStorage.run") return true;
328
+ if ((name === "run" || name === "Object.run") && isEngineAsyncStoreFile(file)) return true;
329
+ return false;
330
+ };
331
+ var isSyntheticRasputinFrame = (frame) => {
332
+ const name = functionName(frame);
333
+ if (isRasputinShellFrame(name) || isRasputinHandleRunFrame(name)) return true;
334
+ return isInternalAsyncStoreFrame(frame);
335
+ };
336
+ var foldSyntheticFrames = (frames) => {
337
+ const filtered = frames.filter((frame) => !isSyntheticRasputinFrame(frame));
338
+ if (filtered.length > 0) return filtered;
339
+ const withoutInternals = frames.filter((frame) => !isInternalAsyncStoreFrame(frame));
340
+ return withoutInternals.length > 0 ? withoutInternals : [...frames];
341
+ };
342
+
281
343
  // src/source-identity/source-identity.ts
282
344
  import { createHash } from "node:crypto";
283
- import { existsSync, readFileSync as readFileSync2 } from "node:fs";
345
+ import { existsSync, readFileSync } from "node:fs";
284
346
  import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
285
347
  import { fileURLToPath } from "node:url";
286
348
  var packageBoundaryByDirectory = /* @__PURE__ */ new Map();
@@ -307,7 +369,7 @@ var readPackageBoundary = (directory) => {
307
369
  if (existsSync(packageJson)) {
308
370
  let name;
309
371
  try {
310
- const parsed = JSON.parse(readFileSync2(packageJson, "utf8"));
372
+ const parsed = JSON.parse(readFileSync(packageJson, "utf8"));
311
373
  if (typeof parsed.name === "string" && parsed.name.trim()) name = parsed.name.trim();
312
374
  } catch {
313
375
  }
@@ -390,7 +452,7 @@ var isInApp = (file, absolute, underRoot, source) => {
390
452
  };
391
453
  var normalizeFrames = (frames, options) => {
392
454
  const appRoot = options.appRoot;
393
- return frames.map((frame) => {
455
+ return foldSyntheticFrames(frames).map((frame) => {
394
456
  const generated = normalizeFile(frame.generated.file, appRoot);
395
457
  const original = frame.original ? normalizeFile(frame.original.file, appRoot) : void 0;
396
458
  const generatedSource = generated.absolute ? sourceLocatorFromFile(generated.absolute) : void 0;
@@ -481,10 +543,10 @@ var parseStack = (stack) => {
481
543
 
482
544
  // src/repo-root/repo-root-from.ts
483
545
  import { existsSync as existsSync2 } from "node:fs";
484
- import { dirname as dirname2, join as join2, resolve as resolve3 } from "node:path";
546
+ import { dirname as dirname2, join, resolve as resolve3 } from "node:path";
485
547
  import { fileURLToPath as fileURLToPath3 } from "node:url";
486
- var isGitRoot = (dir) => existsSync2(join2(dir, ".git"));
487
- var hasPackageJson = (dir) => existsSync2(join2(dir, "package.json"));
548
+ var isGitRoot = (dir) => existsSync2(join(dir, ".git"));
549
+ var hasPackageJson = (dir) => existsSync2(join(dir, "package.json"));
488
550
  var repoRootFrom = (moduleUrl) => {
489
551
  let current = resolve3(dirname2(fileURLToPath3(moduleUrl)));
490
552
  let packageRoot;
@@ -542,12 +604,39 @@ Or set repoRoot yourself.
542
604
 
543
605
  // src/resolve-source-maps/resolve-source-maps.ts
544
606
  import { readFile } from "node:fs/promises";
545
- import { dirname as dirname3, isAbsolute as isAbsolute3, join as join3 } from "node:path";
607
+ import { dirname as dirname3, isAbsolute as isAbsolute3, join as join2 } from "node:path";
546
608
  import { fileURLToPath as fileURLToPath4 } from "node:url";
547
609
  import { LEAST_UPPER_BOUND, originalPositionFor, TraceMap } from "@jridgewell/trace-mapping";
548
610
  var SOURCE_EXT2 = /\.(ts|tsx|mts|cts)$/i;
549
611
  var SOURCEMAP_COMMENT = /(?:\/\/[#@][ \t]*sourceMappingURL=([^\s'"]+)|\/\*[#@][ \t]*sourceMappingURL=([^\s*'"]+)[ \t]*\*\/)\s*$/;
612
+ var MAX_MAP_CACHE = 64;
613
+ var MAX_MAP_CACHE_BYTES = 8 * 1024 * 1024;
550
614
  var mapCache = /* @__PURE__ */ new Map();
615
+ var mapCacheBytesByPath = /* @__PURE__ */ new Map();
616
+ var mapCacheBytes = 0;
617
+ var evictOldestMap = () => {
618
+ const oldest = mapCache.keys().next().value;
619
+ if (oldest === void 0) return false;
620
+ mapCache.delete(oldest);
621
+ mapCacheBytes = Math.max(0, mapCacheBytes - (mapCacheBytesByPath.get(oldest) ?? 0));
622
+ mapCacheBytesByPath.delete(oldest);
623
+ return true;
624
+ };
625
+ var setMapCache = (generatedPath, map, bytes) => {
626
+ const previous = mapCacheBytesByPath.get(generatedPath) ?? 0;
627
+ if (mapCache.has(generatedPath)) {
628
+ mapCache.delete(generatedPath);
629
+ mapCacheBytes = Math.max(0, mapCacheBytes - previous);
630
+ mapCacheBytesByPath.delete(generatedPath);
631
+ }
632
+ if (bytes > MAX_MAP_CACHE_BYTES) return;
633
+ while ((mapCache.size >= MAX_MAP_CACHE || mapCacheBytes + bytes > MAX_MAP_CACHE_BYTES) && evictOldestMap()) {
634
+ }
635
+ if (mapCacheBytes + bytes > MAX_MAP_CACHE_BYTES) return;
636
+ mapCache.set(generatedPath, map);
637
+ mapCacheBytesByPath.set(generatedPath, bytes);
638
+ mapCacheBytes += bytes;
639
+ };
551
640
  var toPath = (file) => {
552
641
  if (file.startsWith("file://")) {
553
642
  try {
@@ -592,7 +681,7 @@ var readMapPayload = async (generatedPath) => {
592
681
  if (comment?.startsWith("data:")) return decodeDataUrl(comment);
593
682
  if (comment) {
594
683
  try {
595
- return await readFile(join3(dirname3(generatedPath), comment), "utf8");
684
+ return await readFile(join2(dirname3(generatedPath), comment), "utf8");
596
685
  } catch {
597
686
  }
598
687
  }
@@ -604,18 +693,25 @@ var readMapPayload = async (generatedPath) => {
604
693
  };
605
694
  var loadTraceMap = async (generatedPath) => {
606
695
  const cached = mapCache.get(generatedPath);
607
- if (cached !== void 0) return cached;
696
+ if (cached !== void 0) {
697
+ const bytes = mapCacheBytesByPath.get(generatedPath) ?? 0;
698
+ mapCache.delete(generatedPath);
699
+ mapCacheBytesByPath.delete(generatedPath);
700
+ mapCache.set(generatedPath, cached);
701
+ mapCacheBytesByPath.set(generatedPath, bytes);
702
+ return cached;
703
+ }
608
704
  const payload = await readMapPayload(generatedPath);
609
705
  if (!payload) {
610
- mapCache.set(generatedPath, null);
706
+ setMapCache(generatedPath, null, 0);
611
707
  return null;
612
708
  }
613
709
  try {
614
710
  const map = new TraceMap(payload);
615
- mapCache.set(generatedPath, map);
711
+ setMapCache(generatedPath, map, payload.length);
616
712
  return map;
617
713
  } catch {
618
- mapCache.set(generatedPath, null);
714
+ setMapCache(generatedPath, null, payload.length);
619
715
  return null;
620
716
  }
621
717
  };
@@ -639,7 +735,7 @@ var resolveFrame = async (raw) => {
639
735
  return {
640
736
  ...base,
641
737
  original: {
642
- file: isAbsolute3(pos.source) ? pos.source : join3(dirname3(generatedPath), pos.source),
738
+ file: isAbsolute3(pos.source) ? pos.source : join2(dirname3(generatedPath), pos.source),
643
739
  line: pos.line,
644
740
  column: pos.column ?? void 0,
645
741
  name: pos.name ?? void 0
@@ -659,7 +755,7 @@ var resolveSourceMaps = async (frames) => {
659
755
 
660
756
  // src/sdk-meta.ts
661
757
  var SDK_NAME = "@rasputin-ai/core";
662
- var SDK_VERSION = "0.5.0-alpha.9";
758
+ var SDK_VERSION = "0.5.1";
663
759
 
664
760
  // src/suppression/identity-needs-message-template.ts
665
761
  var identityNeedsMessageTemplate = (type, code) => {
@@ -798,7 +894,8 @@ var computeBurstKey = (errorType, message, code, frames) => {
798
894
  var DEFAULTS = {
799
895
  windowMs: 6e4,
800
896
  capPerWindow: 5,
801
- maxBurstBuckets: 1e3
897
+ maxBurstBuckets: 1e3,
898
+ maxPendingSummaries: 1e3
802
899
  };
803
900
  var sameDim = (a, b) => (a ?? null) === (b ?? null);
804
901
  var captureIdentity = (input) => ({
@@ -813,8 +910,14 @@ var createSuppression = (options = {}) => {
813
910
  const windowMs = options.windowMs ?? DEFAULTS.windowMs;
814
911
  const capPerWindow = options.capPerWindow ?? DEFAULTS.capPerWindow;
815
912
  const maxBurstBuckets = options.maxBurstBuckets ?? DEFAULTS.maxBurstBuckets;
913
+ const maxPendingSummaries = options.maxPendingSummaries ?? DEFAULTS.maxPendingSummaries;
816
914
  const burstBuckets = /* @__PURE__ */ new Map();
817
915
  const pending = [];
916
+ const pushPending = (draft) => {
917
+ pending.push(draft);
918
+ if (pending.length > maxPendingSummaries)
919
+ pending.splice(0, pending.length - maxPendingSummaries);
920
+ };
818
921
  const touch = (burstKey, bucket) => {
819
922
  burstBuckets.delete(burstKey);
820
923
  burstBuckets.set(burstKey, bucket);
@@ -825,7 +928,7 @@ var createSuppression = (options = {}) => {
825
928
  if (oldest === void 0) break;
826
929
  const bucket = burstBuckets.get(oldest);
827
930
  if (bucket && bucket.suppressed > 0) {
828
- pending.push(toDraft(bucket, Date.now()));
931
+ pushPending(toDraft(bucket, Date.now()));
829
932
  }
830
933
  burstBuckets.delete(oldest);
831
934
  }
@@ -849,7 +952,7 @@ var createSuppression = (options = {}) => {
849
952
  });
850
953
  const flushSuppressed = (bucket, windowEndMs) => {
851
954
  if (bucket.suppressed <= 0) return;
852
- pending.push(toDraft(bucket, windowEndMs));
955
+ pushPending(toDraft(bucket, windowEndMs));
853
956
  bucket.suppressed = 0;
854
957
  };
855
958
  const decide = (input) => {
@@ -939,14 +1042,75 @@ var createSuppression = (options = {}) => {
939
1042
  };
940
1043
  };
941
1044
 
1045
+ // src/transport/process-shutdown-hooks.ts
1046
+ var SIGNAL_EXIT_CODES = { SIGTERM: 143, SIGINT: 130 };
1047
+ var RASPUTIN_PROCESS_LISTENER = Symbol.for("rasputin.process.listener.v1");
1048
+ var removeProcessListener = process.off.bind(process);
1049
+ var tagListener = (listener) => {
1050
+ Object.defineProperty(listener, RASPUTIN_PROCESS_LISTENER, { value: true });
1051
+ return listener;
1052
+ };
1053
+ var hostProcessListenerCount = (event) => {
1054
+ const listeners = event === "beforeExit" ? process.listeners("beforeExit") : process.listeners(event);
1055
+ return listeners.filter((listener) => !(RASPUTIN_PROCESS_LISTENER in listener)).length;
1056
+ };
1057
+ var exitAfterFlush = (done, code) => {
1058
+ void done.finally(() => {
1059
+ process.exit(code);
1060
+ });
1061
+ };
1062
+ var participants = /* @__PURE__ */ new Set();
1063
+ var installed = false;
1064
+ var shuttingDown = false;
1065
+ var onBeforeExit = tagListener(() => {
1066
+ for (const participant of participants) participant.onBeforeExit();
1067
+ });
1068
+ var handleSignal = (signal) => {
1069
+ if (shuttingDown) return;
1070
+ shuttingDown = true;
1071
+ const hostOwns = hostProcessListenerCount(signal) > 0;
1072
+ const snapshot = [...participants];
1073
+ const done = Promise.all(snapshot.map((participant) => participant.onSignal().catch(() => {
1074
+ }))).then(() => {
1075
+ for (const participant of snapshot) participant.dropRemaining();
1076
+ }).then(() => {
1077
+ });
1078
+ if (!hostOwns) exitAfterFlush(done, SIGNAL_EXIT_CODES[signal] ?? 143);
1079
+ };
1080
+ var onTerm = tagListener(() => {
1081
+ handleSignal("SIGTERM");
1082
+ });
1083
+ var onInt = tagListener(() => {
1084
+ handleSignal("SIGINT");
1085
+ });
1086
+ var registerProcessShutdownHooks = (participant) => {
1087
+ participants.add(participant);
1088
+ if (!installed) {
1089
+ process.on("beforeExit", onBeforeExit);
1090
+ process.on("SIGTERM", onTerm);
1091
+ process.on("SIGINT", onInt);
1092
+ installed = true;
1093
+ }
1094
+ return () => {
1095
+ participants.delete(participant);
1096
+ if (participants.size > 0 || !installed) return;
1097
+ removeProcessListener("beforeExit", onBeforeExit);
1098
+ removeProcessListener("SIGTERM", onTerm);
1099
+ removeProcessListener("SIGINT", onInt);
1100
+ installed = false;
1101
+ shuttingDown = false;
1102
+ };
1103
+ };
1104
+
942
1105
  // src/transport/send-batch.ts
943
1106
  import { gzipSync } from "node:zlib";
944
1107
  var sleep = (ms, signal) => new Promise((resolve4) => {
945
- if (signal?.aborted) {
1108
+ if (ms <= 0 || signal?.aborted) {
946
1109
  resolve4();
947
1110
  return;
948
1111
  }
949
1112
  const timer = setTimeout(resolve4, ms);
1113
+ timer.unref?.();
950
1114
  signal?.addEventListener(
951
1115
  "abort",
952
1116
  () => {
@@ -960,6 +1124,25 @@ var backoffMs = (attempt) => {
960
1124
  const base = Math.min(3e4, 500 * 2 ** attempt);
961
1125
  return Math.floor(base * (0.5 + Math.random()));
962
1126
  };
1127
+ var abortError = () => Object.assign(new Error("aborted"), { name: "AbortError" });
1128
+ var fetchUntilAborted = (fetchFn, url, init) => new Promise((resolve4, reject) => {
1129
+ if (init.signal.aborted) {
1130
+ reject(abortError());
1131
+ return;
1132
+ }
1133
+ const onAbort = () => reject(abortError());
1134
+ init.signal.addEventListener("abort", onAbort, { once: true });
1135
+ void fetchFn(url, init).then(
1136
+ (value) => {
1137
+ init.signal.removeEventListener("abort", onAbort);
1138
+ resolve4(value);
1139
+ },
1140
+ (error) => {
1141
+ init.signal.removeEventListener("abort", onAbort);
1142
+ reject(error);
1143
+ }
1144
+ );
1145
+ });
963
1146
  var retryAfterMs = (response) => {
964
1147
  const header = response.headers.get("retry-after");
965
1148
  if (!header) return null;
@@ -994,14 +1177,22 @@ var sendBatch = async (options) => {
994
1177
  failureKind,
995
1178
  statusCode
996
1179
  });
1180
+ const abortAfter = (ms) => {
1181
+ const controller = new AbortController();
1182
+ const timer = setTimeout(() => controller.abort(), ms);
1183
+ timer.unref?.();
1184
+ return { signal: controller.signal, cancel: () => clearTimeout(timer) };
1185
+ };
1186
+ const requestTimeoutMs = options.requestTimeoutMs ?? 2e3;
997
1187
  const json = JSON.stringify(payload);
1188
+ const jsonBytes = Buffer.byteLength(json, "utf8");
998
1189
  const headers = {
999
1190
  Authorization: `Bearer ${options.projectApiKey}`,
1000
1191
  "Content-Type": "application/json",
1001
1192
  Accept: "application/json"
1002
1193
  };
1003
1194
  let body = json;
1004
- if (Buffer.byteLength(json, "utf8") >= options.gzipThresholdBytes) {
1195
+ if (jsonBytes >= options.gzipThresholdBytes) {
1005
1196
  const compressed = gzipSync(json);
1006
1197
  const bytes = new Uint8Array(compressed.byteLength);
1007
1198
  bytes.set(compressed);
@@ -1010,13 +1201,15 @@ var sendBatch = async (options) => {
1010
1201
  }
1011
1202
  for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
1012
1203
  if (options.signal?.aborted) return result("failed", "aborted");
1204
+ attempts++;
1205
+ const timeout = abortAfter(requestTimeoutMs);
1206
+ const signal = options.signal ? AbortSignal.any([options.signal, timeout.signal]) : timeout.signal;
1013
1207
  try {
1014
- attempts++;
1015
- const response = await options.fetch(options.ingestUrl, {
1208
+ const response = await fetchUntilAborted(options.fetch, options.ingestUrl, {
1016
1209
  method: "POST",
1017
1210
  headers,
1018
1211
  body,
1019
- signal: options.signal
1212
+ signal
1020
1213
  });
1021
1214
  if (response.ok) return result("ok");
1022
1215
  if (response.status === 429) {
@@ -1038,6 +1231,8 @@ var sendBatch = async (options) => {
1038
1231
  networkErrors++;
1039
1232
  if (attempt === options.maxRetries) return result("failed", "network_error");
1040
1233
  await sleep(backoffMs(attempt), options.signal);
1234
+ } finally {
1235
+ timeout.cancel();
1041
1236
  }
1042
1237
  }
1043
1238
  return result("failed", options.signal?.aborted ? "aborted" : "network_error");
@@ -1049,11 +1244,40 @@ var RANK = {
1049
1244
  summary: 1,
1050
1245
  first: 2
1051
1246
  };
1052
- var createQueue = (maxSize) => {
1247
+ var createQueue = (maxSize, maxBytes = 8 * 1024 * 1024) => {
1053
1248
  const items = [];
1249
+ const itemBytes = [];
1054
1250
  let enqueued = 0;
1055
1251
  let droppedIncoming = 0;
1056
1252
  let droppedQueued = 0;
1253
+ let queuedBytes = 0;
1254
+ let reservedBytes = 0;
1255
+ const estimateBytes = (item) => {
1256
+ let bytes = 256;
1257
+ try {
1258
+ bytes = Buffer.byteLength(
1259
+ JSON.stringify(item.kind === "event" ? item.event : item.summary),
1260
+ "utf8"
1261
+ );
1262
+ } catch {
1263
+ bytes = 256;
1264
+ }
1265
+ for (const frame of item.rawFrames) {
1266
+ try {
1267
+ bytes += Buffer.byteLength(JSON.stringify(frame), "utf8");
1268
+ } catch {
1269
+ bytes += 64;
1270
+ }
1271
+ }
1272
+ return bytes;
1273
+ };
1274
+ const dropAt = (index) => {
1275
+ const bytes = itemBytes[index] ?? 0;
1276
+ items.splice(index, 1);
1277
+ itemBytes.splice(index, 1);
1278
+ queuedBytes = Math.max(0, queuedBytes - bytes);
1279
+ droppedQueued++;
1280
+ };
1057
1281
  const oldestLowestIndex = () => {
1058
1282
  let best = 0;
1059
1283
  for (let i = 1; i < items.length; i++) {
@@ -1065,7 +1289,17 @@ var createQueue = (maxSize) => {
1065
1289
  return best;
1066
1290
  };
1067
1291
  const enqueue = (item) => {
1068
- while (items.length >= maxSize) {
1292
+ const bytes = item.encodedBytes ?? estimateBytes(item);
1293
+ item.encodedBytes = bytes;
1294
+ if (bytes > maxBytes) {
1295
+ droppedIncoming++;
1296
+ return;
1297
+ }
1298
+ while (items.length >= maxSize || queuedBytes + reservedBytes + bytes > maxBytes) {
1299
+ if (items.length === 0) {
1300
+ droppedIncoming++;
1301
+ return;
1302
+ }
1069
1303
  const victimIndex = oldestLowestIndex();
1070
1304
  const victim = items[victimIndex];
1071
1305
  if (!victim) break;
@@ -1073,16 +1307,25 @@ var createQueue = (maxSize) => {
1073
1307
  droppedIncoming++;
1074
1308
  return;
1075
1309
  }
1076
- items.splice(victimIndex, 1);
1077
- droppedQueued++;
1310
+ dropAt(victimIndex);
1078
1311
  }
1079
1312
  items.push(item);
1313
+ itemBytes.push(bytes);
1314
+ queuedBytes += bytes;
1080
1315
  enqueued++;
1081
1316
  };
1082
- const drain = (count) => items.splice(0, count);
1317
+ const drain = (count) => {
1318
+ const drained = items.splice(0, count);
1319
+ const drainedBytes = itemBytes.splice(0, count);
1320
+ for (const size of drainedBytes) queuedBytes = Math.max(0, queuedBytes - size);
1321
+ return drained;
1322
+ };
1083
1323
  return {
1084
1324
  enqueue,
1085
1325
  drain,
1326
+ setReservedBytes: (bytes) => {
1327
+ reservedBytes = Math.max(0, bytes);
1328
+ },
1086
1329
  size: () => items.length,
1087
1330
  getStats: () => ({
1088
1331
  queued: items.length,
@@ -1093,6 +1336,8 @@ var createQueue = (maxSize) => {
1093
1336
  }),
1094
1337
  clear: () => {
1095
1338
  items.length = 0;
1339
+ itemBytes.length = 0;
1340
+ queuedBytes = 0;
1096
1341
  }
1097
1342
  };
1098
1343
  };
@@ -1101,25 +1346,15 @@ var createQueue = (maxSize) => {
1101
1346
  var DEFAULTS2 = {
1102
1347
  enabled: true,
1103
1348
  maxQueueSize: 500,
1349
+ maxQueueBytes: 8 * 1024 * 1024,
1104
1350
  batchSize: 30,
1105
1351
  flushIntervalMs: 5e3,
1106
1352
  gzipThresholdBytes: 10 * 1024,
1107
1353
  maxRetries: 5,
1108
- installSignalHandlers: true
1109
- };
1110
- var removeProcessListener = process.off.bind(process);
1111
- var holdProcessExitUntil = (done) => {
1112
- const nativeExit = process.exit.bind(process);
1113
- let heldCode;
1114
- const restore = () => {
1115
- process.exit = nativeExit;
1116
- if (heldCode !== void 0) nativeExit(heldCode);
1117
- };
1118
- const holdExit = ((code) => {
1119
- heldCode = code ?? 0;
1120
- });
1121
- process.exit = holdExit;
1122
- void done.finally(restore);
1354
+ installSignalHandlers: true,
1355
+ maxBatchReplays: 8,
1356
+ maxRetryBatchAgeMs: 6e4,
1357
+ shutdownFlushMs: 2e3
1123
1358
  };
1124
1359
  var createTransport = (options) => {
1125
1360
  const enabled = options.enabled ?? DEFAULTS2.enabled;
@@ -1127,12 +1362,16 @@ var createTransport = (options) => {
1127
1362
  const flushIntervalMs = options.flushIntervalMs ?? DEFAULTS2.flushIntervalMs;
1128
1363
  const gzipThresholdBytes = options.gzipThresholdBytes ?? DEFAULTS2.gzipThresholdBytes;
1129
1364
  const maxRetries = options.maxRetries ?? DEFAULTS2.maxRetries;
1130
- const fetchFn = options.fetch ?? globalThis.fetch;
1131
- const queue = createQueue(options.maxQueueSize ?? DEFAULTS2.maxQueueSize);
1365
+ const requestTimeoutMs = options.requestTimeoutMs ?? 2e3;
1366
+ const fetchFn = options.fetch ?? defaultIngestFetch();
1367
+ const queue = createQueue(
1368
+ options.maxQueueSize ?? DEFAULTS2.maxQueueSize,
1369
+ options.maxQueueBytes ?? DEFAULTS2.maxQueueBytes
1370
+ );
1132
1371
  let timer;
1133
1372
  let flushing;
1134
1373
  let closed = false;
1135
- let shuttingDown = false;
1374
+ let shuttingDown2 = false;
1136
1375
  let retryBatch = null;
1137
1376
  let flushesStarted = 0;
1138
1377
  let flushesJoined = 0;
@@ -1148,10 +1387,45 @@ var createTransport = (options) => {
1148
1387
  let serverErrorResponses = 0;
1149
1388
  let lastFailureKind = null;
1150
1389
  let lastFailureStatusCode = null;
1390
+ let flushAbort;
1391
+ let flushAbortTimer;
1392
+ let flushDeadlineMs = 0;
1393
+ let inFlightBytes = 0;
1394
+ let retryBytes = 0;
1395
+ const estimateItemsBytes = (items) => {
1396
+ let bytes = 0;
1397
+ for (const item of items) {
1398
+ bytes += item.encodedBytes ?? 256;
1399
+ }
1400
+ return bytes;
1401
+ };
1402
+ const syncReservedBytes = () => {
1403
+ queue.setReservedBytes(inFlightBytes + retryBytes);
1404
+ };
1405
+ const setFlushDeadline = (timeoutMs, holdProcess = false) => {
1406
+ const nextDeadline = timeoutMs > 0 ? Date.now() + timeoutMs : Number.POSITIVE_INFINITY;
1407
+ if (flushAbort && nextDeadline >= flushDeadlineMs) return;
1408
+ flushDeadlineMs = nextDeadline;
1409
+ if (!flushAbort) flushAbort = new AbortController();
1410
+ if (flushAbortTimer) clearTimeout(flushAbortTimer);
1411
+ if (Number.isFinite(nextDeadline)) {
1412
+ flushAbortTimer = setTimeout(
1413
+ () => flushAbort?.abort(),
1414
+ Math.max(0, nextDeadline - Date.now())
1415
+ );
1416
+ if (!holdProcess) flushAbortTimer.unref?.();
1417
+ }
1418
+ };
1419
+ const clearFlushDeadline = () => {
1420
+ if (flushAbortTimer) clearTimeout(flushAbortTimer);
1421
+ flushAbortTimer = void 0;
1422
+ flushAbort = void 0;
1423
+ flushDeadlineMs = 0;
1424
+ };
1151
1425
  const schedule = () => {
1152
- if (timer || !enabled || closed) return;
1426
+ if (timer || !enabled || closed || shuttingDown2) return;
1153
1427
  timer = setInterval(() => {
1154
- void flush();
1428
+ void kickBackgroundFlush();
1155
1429
  }, flushIntervalMs);
1156
1430
  timer.unref?.();
1157
1431
  };
@@ -1169,40 +1443,81 @@ var createTransport = (options) => {
1169
1443
  return item.summary;
1170
1444
  }
1171
1445
  };
1172
- const performFlush = async (timeoutMs) => {
1173
- const controller = new AbortController();
1174
- const timeout = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
1446
+ const whenAborted = (signal) => new Promise((_, reject) => {
1447
+ const abort = () => reject(signal.reason ?? new Error("aborted"));
1448
+ if (signal.aborted) {
1449
+ abort();
1450
+ return;
1451
+ }
1452
+ signal.addEventListener("abort", abort, { once: true });
1453
+ });
1454
+ const performFlush = async () => {
1455
+ options.beforeFlush?.();
1456
+ const controller = flushAbort ?? new AbortController();
1175
1457
  try {
1176
1458
  while (queue.size() > 0 || retryBatch) {
1177
1459
  if (controller.signal.aborted) break;
1178
1460
  const replaying = retryBatch !== null;
1179
1461
  const batch = retryBatch?.items ?? queue.drain(batchSize);
1180
1462
  const idempotencyKey = retryBatch?.idempotencyKey ?? crypto.randomUUID();
1463
+ const retryCreatedAt = retryBatch?.createdAt ?? Date.now();
1464
+ const retryReplays = retryBatch?.replays ?? 0;
1181
1465
  retryBatch = null;
1466
+ retryBytes = 0;
1467
+ inFlightBytes = estimateItemsBytes(batch);
1468
+ syncReservedBytes();
1182
1469
  if (batch.length === 0) break;
1183
1470
  if (replaying) batchReplays++;
1184
1471
  const events = [];
1185
1472
  const summaries = [];
1186
1473
  for (const item of batch) {
1474
+ if (controller.signal.aborted) break;
1187
1475
  if (item.kind === "summary") {
1188
- summaries.push(await prepareSummary(item));
1476
+ try {
1477
+ summaries.push(
1478
+ await Promise.race([prepareSummary(item), whenAborted(controller.signal)])
1479
+ );
1480
+ } catch {
1481
+ if (controller.signal.aborted) break;
1482
+ summaries.push(item.summary);
1483
+ }
1189
1484
  continue;
1190
1485
  }
1191
1486
  if (options.prepareEvent && item.rawFrames.length > 0) {
1192
1487
  try {
1193
1488
  events.push(
1194
- await options.prepareEvent({
1195
- event: item.event,
1196
- rawFrames: item.rawFrames
1197
- })
1489
+ await Promise.race([
1490
+ options.prepareEvent({
1491
+ event: item.event,
1492
+ rawFrames: item.rawFrames
1493
+ }),
1494
+ whenAborted(controller.signal)
1495
+ ])
1198
1496
  );
1199
1497
  } catch {
1498
+ if (controller.signal.aborted) break;
1200
1499
  events.push(item.event);
1201
1500
  }
1202
1501
  } else {
1203
1502
  events.push(item.event);
1204
1503
  }
1205
1504
  }
1505
+ const retainOrDrop = (nextReplays, createdAt) => {
1506
+ if (nextReplays >= DEFAULTS2.maxBatchReplays || Date.now() - createdAt >= DEFAULTS2.maxRetryBatchAgeMs) {
1507
+ batchesDropped++;
1508
+ retryBatch = null;
1509
+ retryBytes = 0;
1510
+ } else {
1511
+ retryBatch = { items: batch, idempotencyKey, createdAt, replays: nextReplays };
1512
+ retryBytes = inFlightBytes;
1513
+ }
1514
+ inFlightBytes = 0;
1515
+ syncReservedBytes();
1516
+ };
1517
+ if (controller.signal.aborted) {
1518
+ retainOrDrop((replaying ? retryReplays : 0) + 1, replaying ? retryCreatedAt : Date.now());
1519
+ break;
1520
+ }
1206
1521
  const result = await sendBatch({
1207
1522
  ingestUrl: options.ingestUrl,
1208
1523
  projectApiKey: options.projectApiKey,
@@ -1212,7 +1527,8 @@ var createTransport = (options) => {
1212
1527
  sdkVersion: options.sdkVersion,
1213
1528
  sdkName: options.sdkName,
1214
1529
  gzipThresholdBytes,
1215
- maxRetries,
1530
+ maxRetries: shuttingDown2 ? 0 : maxRetries,
1531
+ requestTimeoutMs,
1216
1532
  fetch: fetchFn,
1217
1533
  signal: controller.signal
1218
1534
  });
@@ -1230,70 +1546,120 @@ var createTransport = (options) => {
1230
1546
  else batchesFailed++;
1231
1547
  }
1232
1548
  if (result.outcome === "failed") {
1233
- retryBatch = { items: batch, idempotencyKey };
1549
+ retainOrDrop(
1550
+ result.aborted ? (replaying ? retryReplays : 0) + 1 : replaying ? retryReplays + 1 : 0,
1551
+ replaying ? retryCreatedAt : Date.now()
1552
+ );
1234
1553
  break;
1235
1554
  }
1555
+ inFlightBytes = 0;
1556
+ syncReservedBytes();
1236
1557
  }
1237
1558
  } finally {
1238
- if (timeout) clearTimeout(timeout);
1559
+ clearFlushDeadline();
1239
1560
  if (queue.size() === 0 && !retryBatch) stopTimer();
1240
1561
  }
1241
1562
  };
1563
+ const startFlushWorker = () => {
1564
+ if (flushing) return flushing;
1565
+ flushesStarted++;
1566
+ let settle;
1567
+ const activeFlush = new Promise((resolve4, reject) => {
1568
+ settle = (work) => {
1569
+ void work.then(resolve4, reject);
1570
+ };
1571
+ });
1572
+ flushing = activeFlush;
1573
+ settle(performFlush());
1574
+ void activeFlush.finally(() => {
1575
+ if (flushing === activeFlush) flushing = void 0;
1576
+ });
1577
+ return activeFlush;
1578
+ };
1579
+ const kickBackgroundFlush = () => {
1580
+ if (!enabled || closed || shuttingDown2) return;
1581
+ setFlushDeadline(1e4, false);
1582
+ if (flushing) return;
1583
+ startFlushWorker();
1584
+ };
1242
1585
  const flush = async (timeoutMs = 1e4) => {
1243
1586
  if (!enabled || closed) {
1244
1587
  queue.clear();
1245
1588
  retryBatch = null;
1246
1589
  return;
1247
1590
  }
1591
+ setFlushDeadline(timeoutMs, true);
1248
1592
  if (flushing) {
1249
1593
  flushesJoined++;
1250
1594
  return flushing;
1251
1595
  }
1252
- flushesStarted++;
1253
- const activeFlush = performFlush(timeoutMs);
1254
- flushing = activeFlush;
1255
- try {
1256
- await activeFlush;
1257
- } finally {
1258
- if (flushing === activeFlush) flushing = void 0;
1259
- }
1596
+ await startFlushWorker();
1260
1597
  };
1261
- const enqueue = (event, priority = "first", rawFrames = []) => {
1598
+ const enqueue = (event, priority = "first", rawFrames = [], encodedBytes) => {
1262
1599
  if (!enabled || closed) return;
1263
- queue.enqueue({ kind: "event", event, priority, rawFrames });
1600
+ queue.enqueue({ kind: "event", event, priority, rawFrames, encodedBytes });
1264
1601
  schedule();
1265
- if (queue.size() >= batchSize) void flush();
1602
+ if (queue.size() >= batchSize) kickBackgroundFlush();
1266
1603
  };
1267
1604
  const enqueueSummary = (summary, rawFrames = []) => {
1268
1605
  if (!enabled || closed) return;
1269
1606
  queue.enqueue({ kind: "summary", summary, priority: "summary", rawFrames });
1270
1607
  schedule();
1271
- if (queue.size() >= batchSize) void flush();
1608
+ if (queue.size() >= batchSize) kickBackgroundFlush();
1609
+ };
1610
+ const dropRemainingOnExit = () => {
1611
+ const remaining = queue.size() + (retryBatch?.items.length ?? 0);
1612
+ if (remaining === 0) return;
1613
+ queue.clear();
1614
+ retryBatch = null;
1615
+ retryBytes = 0;
1616
+ inFlightBytes = 0;
1617
+ syncReservedBytes();
1618
+ console.error(
1619
+ `[rasputin] Dropped ${remaining} telemetry event${remaining === 1 ? "" : "s"} on exit because ingest did not accept them in time.`
1620
+ );
1272
1621
  };
1273
- const onBeforeExit = () => {
1274
- if (closed || shuttingDown) return;
1622
+ const onBeforeExit2 = () => {
1623
+ if (closed || shuttingDown2) return;
1275
1624
  if (queue.size() === 0 && !retryBatch && !flushing) return;
1276
- void flush(2e3).catch(() => {
1277
- });
1625
+ shuttingDown2 = true;
1626
+ stopTimer();
1627
+ options.beforeFlush?.();
1628
+ void flush(DEFAULTS2.shutdownFlushMs).catch(() => {
1629
+ }).finally(dropRemainingOnExit);
1278
1630
  };
1279
- const onSignal = () => {
1280
- if (shuttingDown) return;
1281
- shuttingDown = true;
1631
+ const onSignal = async () => {
1632
+ if (shuttingDown2) return;
1633
+ shuttingDown2 = true;
1282
1634
  stopTimer();
1283
- holdProcessExitUntil(flush(2e3).catch(() => {
1284
- }));
1635
+ options.beforeFlush?.();
1636
+ await flush(DEFAULTS2.shutdownFlushMs).catch(() => {
1637
+ });
1638
+ };
1639
+ const unregisterSignals = enabled && (options.installSignalHandlers ?? DEFAULTS2.installSignalHandlers) ? registerProcessShutdownHooks({
1640
+ onBeforeExit: onBeforeExit2,
1641
+ onSignal,
1642
+ dropRemaining: dropRemainingOnExit
1643
+ }) : () => {
1285
1644
  };
1286
- if (enabled && (options.installSignalHandlers ?? DEFAULTS2.installSignalHandlers)) {
1287
- process.on("beforeExit", onBeforeExit);
1288
- process.on("SIGTERM", onSignal);
1289
- process.on("SIGINT", onSignal);
1290
- }
1291
1645
  const close = async () => {
1292
1646
  stopTimer();
1293
- removeProcessListener("beforeExit", onBeforeExit);
1294
- removeProcessListener("SIGTERM", onSignal);
1295
- removeProcessListener("SIGINT", onSignal);
1296
- await flush(2e3);
1647
+ unregisterSignals();
1648
+ shuttingDown2 = true;
1649
+ await new Promise((resolve4, reject) => {
1650
+ const timer2 = setTimeout(resolve4, DEFAULTS2.shutdownFlushMs + 250);
1651
+ void flush(DEFAULTS2.shutdownFlushMs).then(
1652
+ () => {
1653
+ clearTimeout(timer2);
1654
+ resolve4();
1655
+ },
1656
+ (error) => {
1657
+ clearTimeout(timer2);
1658
+ reject(error);
1659
+ }
1660
+ );
1661
+ });
1662
+ dropRemainingOnExit();
1297
1663
  closed = true;
1298
1664
  queue.clear();
1299
1665
  retryBatch = null;
@@ -1328,55 +1694,126 @@ var createTransport = (options) => {
1328
1694
  };
1329
1695
  };
1330
1696
 
1331
- // src/create-client/is-client-enabled.ts
1332
- var isClientEnabled = (options) => options.enabled ?? true;
1333
- var noopClient = () => ({
1334
- captureException: () => {
1335
- },
1336
- flush: async () => {
1337
- },
1338
- close: async () => {
1339
- },
1340
- getStats: () => ({
1341
- transport: {
1342
- queued: 0,
1343
- enqueued: 0,
1344
- droppedIncoming: 0,
1345
- droppedQueued: 0,
1346
- droppedTotal: 0,
1347
- flushesStarted: 0,
1348
- flushesJoined: 0,
1349
- sendAttempts: 0,
1350
- sendRetries: 0,
1351
- batchReplays: 0,
1352
- batchesSucceeded: 0,
1353
- batchesDropped: 0,
1354
- batchesFailed: 0,
1355
- flushTimeouts: 0,
1356
- networkErrors: 0,
1357
- rateLimitedResponses: 0,
1358
- serverErrorResponses: 0,
1359
- lastFailureKind: null,
1360
- lastFailureStatusCode: null
1361
- }
1362
- })
1363
- });
1364
-
1365
- // src/create-client/resolve-endpoint-urls.ts
1366
- var DEFAULT_API_URL = "https://api.rasputinai.dev";
1367
- var resolveEndpointUrls = (apiUrl = DEFAULT_API_URL) => {
1368
- const base = apiUrl.replace(/\/+$/, "");
1369
- return {
1370
- ingestUrl: `${base}/ingest`,
1371
- deployUrl: `${base}/deploy`,
1372
- instrumentationManifestUrl: `${base}/instrumentationManifest`
1373
- };
1374
- };
1375
-
1376
- // src/create-client/create-client.ts
1377
- var warnedUnresolvedMaps = false;
1378
- var warnedPartialResolution = false;
1379
- var warnUnresolvedMaps = (unresolved) => {
1697
+ // src/detect-release/detect-release.ts
1698
+ import { readFileSync as readFileSync2 } from "node:fs";
1699
+ import { join as join3 } from "node:path";
1700
+ var ENV_KEYS = [
1701
+ "RASPUTIN_RELEASE",
1702
+ "VERCEL_GIT_COMMIT_SHA",
1703
+ "RAILWAY_GIT_COMMIT_SHA",
1704
+ "RENDER_GIT_COMMIT",
1705
+ "COMMIT_REF",
1706
+ "CF_PAGES_COMMIT_SHA",
1707
+ "HEROKU_SLUG_COMMIT",
1708
+ "GIT_COMMIT",
1709
+ "COMMIT_SHA",
1710
+ "GIT_SHA"
1711
+ ];
1712
+ var SHA_RE = /^[0-9a-f]{7,40}$/i;
1713
+ var isSha = (value) => SHA_RE.test(value);
1714
+ var warnInvalid = (source, value) => {
1715
+ console.warn(
1716
+ `[rasputin] Ignoring invalid release from ${source}: "${value}". Expected a git commit SHA (7\u201340 hex characters).`
1717
+ );
1718
+ };
1719
+ var readGitHead = (cwd) => {
1720
+ try {
1721
+ const gitDir = join3(cwd, ".git");
1722
+ const head = readFileSync2(join3(gitDir, "HEAD"), "utf8").trim();
1723
+ if (isSha(head)) return head;
1724
+ const ref = head.replace(/^ref:\s*/, "");
1725
+ try {
1726
+ const sha = readFileSync2(join3(gitDir, ref), "utf8").trim();
1727
+ return isSha(sha) ? sha : void 0;
1728
+ } catch {
1729
+ const packed = readFileSync2(join3(gitDir, "packed-refs"), "utf8");
1730
+ const match = packed.match(new RegExp(`^([0-9a-f]{40}) ${ref}$`, "m"));
1731
+ return match?.[1];
1732
+ }
1733
+ } catch {
1734
+ return void 0;
1735
+ }
1736
+ };
1737
+ var detectRelease = (options = {}) => {
1738
+ const explicit = options.release?.trim();
1739
+ if (explicit) {
1740
+ if (isSha(explicit)) return explicit;
1741
+ warnInvalid("release option", explicit);
1742
+ }
1743
+ const env = options.env ?? process.env;
1744
+ for (const key of ENV_KEYS) {
1745
+ const value = env[key]?.trim();
1746
+ if (!value) continue;
1747
+ if (isSha(value)) return value;
1748
+ warnInvalid(key, value);
1749
+ }
1750
+ const fromGit = readGitHead(options.cwd ?? process.cwd());
1751
+ if (fromGit) return fromGit;
1752
+ console.warn(
1753
+ "[rasputin] No release detected. Set `RASPUTIN_RELEASE` to your git commit SHA, pass `release` in RasputinInit, or deploy on a platform that exposes a commit SHA env var (e.g. VERCEL_GIT_COMMIT_SHA). Events are not sent without a release."
1754
+ );
1755
+ return void 0;
1756
+ };
1757
+
1758
+ // src/create-client/is-client-enabled.ts
1759
+ var resolveClientActivation = (options) => {
1760
+ if (options.enabled === false) return { enabled: false };
1761
+ const projectApiKey = typeof options.projectApiKey === "string" ? options.projectApiKey.trim() : "";
1762
+ if (!projectApiKey) return { enabled: false };
1763
+ const explicit = typeof options.release === "string" ? options.release.trim() : "";
1764
+ if (!explicit) return { enabled: false };
1765
+ const release = detectRelease({ release: explicit });
1766
+ if (!release) return { enabled: false };
1767
+ return { enabled: true, projectApiKey, release };
1768
+ };
1769
+ var isClientEnabled = (options) => resolveClientActivation(options).enabled;
1770
+ var noopClient = () => ({
1771
+ captureException: () => {
1772
+ },
1773
+ flush: async () => {
1774
+ },
1775
+ close: async () => {
1776
+ },
1777
+ getStats: () => ({
1778
+ transport: {
1779
+ queued: 0,
1780
+ enqueued: 0,
1781
+ droppedIncoming: 0,
1782
+ droppedQueued: 0,
1783
+ droppedTotal: 0,
1784
+ flushesStarted: 0,
1785
+ flushesJoined: 0,
1786
+ sendAttempts: 0,
1787
+ sendRetries: 0,
1788
+ batchReplays: 0,
1789
+ batchesSucceeded: 0,
1790
+ batchesDropped: 0,
1791
+ batchesFailed: 0,
1792
+ flushTimeouts: 0,
1793
+ networkErrors: 0,
1794
+ rateLimitedResponses: 0,
1795
+ serverErrorResponses: 0,
1796
+ lastFailureKind: null,
1797
+ lastFailureStatusCode: null
1798
+ }
1799
+ })
1800
+ });
1801
+
1802
+ // src/create-client/resolve-endpoint-urls.ts
1803
+ var DEFAULT_API_URL = "https://api.rasputinai.dev";
1804
+ var resolveEndpointUrls = (apiUrl = DEFAULT_API_URL) => {
1805
+ const base = apiUrl.replace(/\/+$/, "");
1806
+ return {
1807
+ ingestUrl: `${base}/ingest`,
1808
+ deployUrl: `${base}/deploy`,
1809
+ instrumentationManifestUrl: `${base}/instrumentationManifest`
1810
+ };
1811
+ };
1812
+
1813
+ // src/create-client/create-client.ts
1814
+ var warnedUnresolvedMaps = false;
1815
+ var warnedPartialResolution = false;
1816
+ var warnUnresolvedMaps = (unresolved) => {
1380
1817
  if (!unresolved || warnedUnresolvedMaps) return;
1381
1818
  warnedUnresolvedMaps = true;
1382
1819
  console.warn(
@@ -1400,22 +1837,11 @@ var stackOf = (error) => {
1400
1837
  };
1401
1838
  var createClient = (options, hooks = {}) => {
1402
1839
  try {
1403
- if (!isClientEnabled(options)) return noopClient();
1404
- const projectApiKey = options.projectApiKey.trim();
1405
- const release = detectRelease({ release: options.release });
1840
+ const activation = resolveClientActivation(options);
1841
+ if (!activation.enabled) return noopClient();
1842
+ const { projectApiKey, release } = activation;
1406
1843
  const environment = options.environment;
1407
1844
  const { ingestUrl, deployUrl } = resolveEndpointUrls(options.apiUrl);
1408
- if (!release) {
1409
- if (hooks.announceDeploy ?? true) {
1410
- void announceDeploy({
1411
- deployUrl,
1412
- projectApiKey,
1413
- environment,
1414
- fetch: hooks.fetch
1415
- });
1416
- }
1417
- return noopClient();
1418
- }
1419
1845
  const repoRoot = resolveRepoRoot(options);
1420
1846
  if (repoRoot) warnIfSuspiciousRepoRoot(repoRoot);
1421
1847
  const MAX_STACK_RESOLVE_CACHE = 32;
@@ -1441,6 +1867,9 @@ var createClient = (options, hooks = {}) => {
1441
1867
  }
1442
1868
  return pending;
1443
1869
  };
1870
+ const suppression = createSuppression();
1871
+ let enqueuePendingSummaries = () => {
1872
+ };
1444
1873
  const transport = createTransport({
1445
1874
  ingestUrl,
1446
1875
  projectApiKey,
@@ -1449,6 +1878,7 @@ var createClient = (options, hooks = {}) => {
1449
1878
  installSignalHandlers: hooks.installTransportSignalHandlers,
1450
1879
  sdkVersion: hooks.sdkVersion ?? SDK_VERSION,
1451
1880
  sdkName: hooks.sdkName ?? SDK_NAME,
1881
+ beforeFlush: () => enqueuePendingSummaries(),
1452
1882
  prepareEvent: async ({ event, rawFrames }) => {
1453
1883
  const prepared = await resolveStack(rawFrames);
1454
1884
  return withResolvedFrames(event, prepared.frames, prepared.resolution);
@@ -1458,7 +1888,6 @@ var createClient = (options, hooks = {}) => {
1458
1888
  return { stack_frames: prepared.stack_frames, resolution: prepared.resolution };
1459
1889
  }
1460
1890
  });
1461
- const suppression = createSuppression();
1462
1891
  if (hooks.announceDeploy ?? true) {
1463
1892
  void announceDeploy({
1464
1893
  deployUrl,
@@ -1467,11 +1896,12 @@ var createClient = (options, hooks = {}) => {
1467
1896
  release,
1468
1897
  enabled: true,
1469
1898
  fetch: hooks.fetch
1899
+ }).catch(() => {
1470
1900
  });
1471
1901
  }
1472
1902
  const captureException = (error, context) => {
1473
1903
  try {
1474
- const rawFrames = parseStack(stackOf(error));
1904
+ const rawFrames = foldSyntheticFrames(parseStack(stackOf(error)));
1475
1905
  const frames = normalizeFrames(toResolvedFrames(rawFrames), { appRoot: repoRoot });
1476
1906
  const event = createIngestEvent({
1477
1907
  error,
@@ -1480,8 +1910,7 @@ var createClient = (options, hooks = {}) => {
1480
1910
  release,
1481
1911
  origin: "server",
1482
1912
  resolution: "none",
1483
- request: context?.request,
1484
- runtimeState: context?.runtimeState
1913
+ request: context?.request
1485
1914
  });
1486
1915
  const decision = suppression.decide({
1487
1916
  errorType: event.type,
@@ -1494,13 +1923,20 @@ var createClient = (options, hooks = {}) => {
1494
1923
  release,
1495
1924
  route: context?.request?.route
1496
1925
  });
1497
- if (decision.action === "send") {
1498
- transport.enqueue(event, decision.priority, rawFrames);
1926
+ if (decision.action !== "send") return;
1927
+ const runtimeState = context?.runtimeState ?? hooks.resolveRuntimeState?.(error, context);
1928
+ if (runtimeState) event.runtime_state = runtimeState;
1929
+ let encodedBytes;
1930
+ try {
1931
+ encodedBytes = Buffer2.byteLength(JSON.stringify(event), "utf8");
1932
+ } catch {
1933
+ encodedBytes = void 0;
1499
1934
  }
1935
+ transport.enqueue(event, decision.priority, rawFrames, encodedBytes);
1500
1936
  } catch {
1501
1937
  }
1502
1938
  };
1503
- const enqueuePendingSummaries = () => {
1939
+ enqueuePendingSummaries = () => {
1504
1940
  for (const { summary, rawFrames } of suppression.drainSummaries()) {
1505
1941
  transport.enqueueSummary(summary, rawFrames);
1506
1942
  }
@@ -1530,68 +1966,6 @@ var createClient = (options, hooks = {}) => {
1530
1966
  }
1531
1967
  };
1532
1968
 
1533
- // src/derivation/rasputin-wire-tag.ts
1534
- var RASPUTIN_WIRE_TAG = "\0rasputin";
1535
- var rasputinUnknown = () => [RASPUTIN_WIRE_TAG, "unknown"];
1536
- var rasputinUnavailable = (reason) => [
1537
- RASPUTIN_WIRE_TAG,
1538
- "unavailable",
1539
- reason
1540
- ];
1541
- var rasputinOmitted = () => [RASPUTIN_WIRE_TAG, "omitted"];
1542
- var rasputinRedacted = () => [RASPUTIN_WIRE_TAG, "redacted"];
1543
- var rasputinTruncated = (payload) => [
1544
- RASPUTIN_WIRE_TAG,
1545
- "truncated",
1546
- payload
1547
- ];
1548
- var isRasputinTaggedValue = (value) => {
1549
- if (!Array.isArray(value) || value.length < 2 || value[0] !== RASPUTIN_WIRE_TAG) return false;
1550
- const kind = value[1];
1551
- return kind === "unknown" || kind === "unavailable" || kind === "omitted" || kind === "redacted" || kind === "truncated";
1552
- };
1553
- var rasputinTagKind = (value) => isRasputinTaggedValue(value) ? value[1] : void 0;
1554
-
1555
- // src/derivation/captured-value-markers.ts
1556
- var REDACTED = "[RASPUTIN_REDACTED]";
1557
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1558
- var capturedValueCompleteness = (value) => {
1559
- if (value === REDACTED) return "partial";
1560
- const tagged = rasputinTagKind(value);
1561
- if (tagged === "unknown" || tagged === "truncated" || tagged === "unavailable" || tagged === "omitted") {
1562
- return "partial";
1563
- }
1564
- if (tagged === "redacted") return "partial";
1565
- if (isRasputinTaggedValue(value)) return "partial";
1566
- if (Array.isArray(value)) {
1567
- for (const item of value) {
1568
- if (capturedValueCompleteness(item) === "partial") return "partial";
1569
- }
1570
- return "exact";
1571
- }
1572
- if (!isRecord(value)) return "exact";
1573
- if (value.__rasputin_truncated === true) return "partial";
1574
- if (value.__rasputin_type === "omitted" || value.__rasputin_type === "accessor") return "partial";
1575
- if (value.__rasputin_type === "unserializable") return "partial";
1576
- for (const key of Object.keys(value)) {
1577
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
1578
- if (!descriptor || descriptor.get || descriptor.set) return "partial";
1579
- if (capturedValueCompleteness(descriptor.value) === "partial") return "partial";
1580
- }
1581
- return "exact";
1582
- };
1583
- var isRedactedCapturedValue = (value) => {
1584
- if (value === REDACTED) return true;
1585
- if (rasputinTagKind(value) === "redacted") return true;
1586
- if (isRecord(value) && value.__rasputin_type === "omitted") return true;
1587
- return false;
1588
- };
1589
- var isSerializerMarker = (value) => {
1590
- if (isRasputinTaggedValue(value)) return true;
1591
- if (!isRecord(value)) return false;
1592
- return value.__rasputin_truncated === true || typeof value.__rasputin_type === "string";
1593
- };
1594
-
1595
1969
  // src/derivation/canonical-json.ts
1596
1970
  var sortedKeys = (value) => Object.keys(value).sort();
1597
1971
  var canonicalJson = (value) => {
@@ -1622,967 +1996,214 @@ var canonicalJson = (value) => {
1622
1996
  };
1623
1997
 
1624
1998
  // src/derivation/sha256.ts
1625
- var K = [
1626
- 1116352408,
1627
- 1899447441,
1628
- 3049323471,
1629
- 3921009573,
1630
- 961987163,
1631
- 1508970993,
1632
- 2453635748,
1633
- 2870763221,
1634
- 3624381080,
1635
- 310598401,
1636
- 607225278,
1637
- 1426881987,
1638
- 1925078388,
1639
- 2162078206,
1640
- 2614888103,
1641
- 3248222580,
1642
- 3835390401,
1643
- 4022224774,
1644
- 264347078,
1645
- 604807628,
1646
- 770255983,
1647
- 1249150122,
1648
- 1555081692,
1649
- 1996064986,
1650
- 2554220882,
1651
- 2821834349,
1652
- 2952996808,
1653
- 3210313671,
1654
- 3336571891,
1655
- 3584528711,
1656
- 113926993,
1657
- 338241895,
1658
- 666307205,
1659
- 773529912,
1660
- 1294757372,
1661
- 1396182291,
1662
- 1695183700,
1663
- 1986661051,
1664
- 2177026350,
1665
- 2456956037,
1666
- 2730485921,
1667
- 2820302411,
1668
- 3259730800,
1669
- 3345764771,
1670
- 3516065817,
1671
- 3600352804,
1672
- 4094571909,
1673
- 275423344,
1674
- 430227734,
1675
- 506948616,
1676
- 659060556,
1677
- 883997877,
1678
- 958139571,
1679
- 1322822218,
1680
- 1537002063,
1681
- 1747873779,
1682
- 1955562222,
1683
- 2024104815,
1684
- 2227730452,
1685
- 2361852424,
1686
- 2428436474,
1687
- 2756734187,
1688
- 3204031479,
1689
- 3329325298
1690
- ];
1691
- var rotr = (value, bits) => value >>> bits | value << 32 - bits;
1692
- var sha256Hex = (input) => {
1693
- const encoded = new TextEncoder().encode(input);
1694
- const bitLen = encoded.length * 8;
1695
- const paddedLen = encoded.length + 9 + 63 >> 6 << 6 >>> 0;
1696
- const bytes = new Uint8Array(paddedLen);
1697
- bytes.set(encoded);
1698
- bytes[encoded.length] = 128;
1699
- const view = new DataView(bytes.buffer);
1700
- view.setUint32(paddedLen - 4, bitLen, false);
1701
- let h0 = 1779033703;
1702
- let h1 = 3144134277;
1703
- let h2 = 1013904242;
1704
- let h3 = 2773480762;
1705
- let h4 = 1359893119;
1706
- let h5 = 2600822924;
1707
- let h6 = 528734635;
1708
- let h7 = 1541459225;
1709
- const w = new Uint32Array(64);
1710
- for (let offset = 0; offset < paddedLen; offset += 64) {
1711
- for (let i = 0; i < 16; i++) w[i] = view.getUint32(offset + i * 4, false);
1712
- for (let i = 16; i < 64; i++) {
1713
- const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ w[i - 15] >>> 3;
1714
- const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ w[i - 2] >>> 10;
1715
- w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
1716
- }
1717
- let a = h0;
1718
- let b = h1;
1719
- let c = h2;
1720
- let d = h3;
1721
- let e = h4;
1722
- let f = h5;
1723
- let g = h6;
1724
- let h = h7;
1725
- for (let i = 0; i < 64; i++) {
1726
- const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
1727
- const ch = e & f ^ ~e & g;
1728
- const temp1 = h + S1 + ch + K[i] + w[i] >>> 0;
1729
- const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
1730
- const maj = a & b ^ a & c ^ b & c;
1731
- const temp2 = S0 + maj >>> 0;
1732
- h = g;
1733
- g = f;
1734
- f = e;
1735
- e = d + temp1 >>> 0;
1736
- d = c;
1737
- c = b;
1738
- b = a;
1739
- a = temp1 + temp2 >>> 0;
1740
- }
1741
- h0 = h0 + a >>> 0;
1742
- h1 = h1 + b >>> 0;
1743
- h2 = h2 + c >>> 0;
1744
- h3 = h3 + d >>> 0;
1745
- h4 = h4 + e >>> 0;
1746
- h5 = h5 + f >>> 0;
1747
- h6 = h6 + g >>> 0;
1748
- h7 = h7 + h >>> 0;
1749
- }
1750
- return [h0, h1, h2, h3, h4, h5, h6, h7].map((word) => word.toString(16).padStart(8, "0")).join("");
1751
- };
1999
+ import { createHash as createHash2 } from "node:crypto";
2000
+ var sha256Hex = (input) => createHash2("sha256").update(input).digest("hex");
1752
2001
 
1753
2002
  // src/derivation/hash-instrumentation-manifest.ts
2003
+ var hashCache = /* @__PURE__ */ new WeakMap();
1754
2004
  var hashableSource = (source) => {
1755
2005
  const { repositoryPath: _repositoryPath, ...rest } = source;
1756
2006
  return rest;
1757
2007
  };
1758
2008
  var hashableLocated = (items) => items.map((item) => ({ ...item, source: hashableSource(item.source) }));
1759
2009
  var hashInstrumentationManifest = (manifest) => {
1760
- return sha256Hex(
2010
+ const cached = hashCache.get(manifest);
2011
+ if (cached) return cached;
2012
+ const hash = sha256Hex(
1761
2013
  canonicalJson({
1762
2014
  schema_version: manifest.schema_version,
1763
2015
  functions: hashableLocated(manifest.functions),
1764
2016
  callSites: hashableLocated(manifest.callSites),
1765
- destructures: hashableLocated(manifest.destructures),
1766
- derivationPlans: manifest.derivationPlans ?? []
2017
+ destructures: hashableLocated(manifest.destructures)
1767
2018
  })
1768
2019
  );
2020
+ hashCache.set(manifest, hash);
2021
+ return hash;
1769
2022
  };
1770
2023
 
1771
- // src/derivation/wire-values-equal.ts
1772
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1773
- var arraysEqual = (left, right) => {
1774
- if (left.length !== right.length) return false;
1775
- for (let index = 0; index < left.length; index++) {
1776
- if (Object.hasOwn(left, index) !== Object.hasOwn(right, index)) return false;
1777
- if (!wireValuesEqual(left[index], right[index])) return false;
1778
- }
1779
- return true;
1780
- };
1781
- var wireValuesEqual = (left, right) => {
1782
- if (Object.is(left, right)) return true;
1783
- if (typeof left === "bigint" && typeof right === "bigint") return left === right;
1784
- if (isRasputinTaggedValue(left) || isRasputinTaggedValue(right)) {
1785
- if (!Array.isArray(left) || !Array.isArray(right)) return false;
1786
- return arraysEqual(left, right);
1787
- }
1788
- if (Array.isArray(left) && Array.isArray(right)) return arraysEqual(left, right);
1789
- if (!isRecord2(left) || !isRecord2(right)) return false;
1790
- const leftKeys = Object.keys(left).sort();
1791
- const rightKeys = Object.keys(right).sort();
1792
- if (leftKeys.length !== rightKeys.length) return false;
1793
- return leftKeys.every((key, index) => {
1794
- if (key !== rightKeys[index]) return false;
1795
- const leftDesc = Object.getOwnPropertyDescriptor(left, key);
1796
- const rightDesc = Object.getOwnPropertyDescriptor(right, key);
1797
- if (!leftDesc || !rightDesc) return false;
1798
- if (leftDesc.get || leftDesc.set || rightDesc.get || rightDesc.set) return false;
1799
- return wireValuesEqual(leftDesc.value, rightDesc.value);
1800
- });
2024
+ // src/derivation/rasputin-wire-tag.ts
2025
+ var RASPUTIN_WIRE_TAG = "\uE000rasputin";
2026
+ var rasputinUnknown = () => [RASPUTIN_WIRE_TAG, "unknown"];
2027
+ var rasputinUnavailable = (reason) => [
2028
+ RASPUTIN_WIRE_TAG,
2029
+ "unavailable",
2030
+ reason
2031
+ ];
2032
+ var rasputinOmitted = () => [RASPUTIN_WIRE_TAG, "omitted"];
2033
+ var rasputinRedacted = () => [RASPUTIN_WIRE_TAG, "redacted"];
2034
+ var rasputinTruncated = (payload) => [
2035
+ RASPUTIN_WIRE_TAG,
2036
+ "truncated",
2037
+ payload
2038
+ ];
2039
+ var isRasputinTaggedValue = (value) => {
2040
+ if (!Array.isArray(value) || value.length < 2 || value[0] !== RASPUTIN_WIRE_TAG) return false;
2041
+ const kind = value[1];
2042
+ return kind === "unknown" || kind === "unavailable" || kind === "omitted" || kind === "redacted" || kind === "truncated";
1801
2043
  };
2044
+ var rasputinTagKind = (value) => isRasputinTaggedValue(value) ? value[1] : void 0;
1802
2045
 
1803
- // src/derivation/evaluate-derivation-plan.ts
1804
- var MAX_INSTRUCTIONS = 1e4;
1805
- var MAX_DEPTH = 64;
1806
- var MAX_OUTPUT_BYTES = 256 * 1024;
1807
- var MAX_WALL_MS = 50;
1808
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1809
- var primitiveFromConst = (value) => {
1810
- switch (value.type) {
1811
- case "null":
1812
- return null;
1813
- case "undefined":
1814
- return void 0;
1815
- case "boolean":
1816
- return value.value;
1817
- case "number":
1818
- return value.negativeZero ? -0 : value.value;
1819
- case "string":
1820
- return value.value;
1821
- case "bigint":
1822
- try {
1823
- return BigInt(value.value);
1824
- } catch {
1825
- return rasputinUnavailable("invalid_bigint_const");
1826
- }
1827
- }
1828
- };
1829
- var estimateBytes = (value, depth = 0) => {
1830
- if (value === null || value === void 0) return 4;
1831
- switch (typeof value) {
1832
- case "boolean":
1833
- return 4;
1834
- case "number":
1835
- return 8;
1836
- case "string":
1837
- return value.length;
1838
- case "bigint":
1839
- return value.toString().length;
1840
- case "object": {
1841
- if (depth > 8) return 8;
1842
- if (Array.isArray(value)) {
1843
- let total2 = 2;
1844
- for (const item of value) total2 += estimateBytes(item, depth + 1);
1845
- return total2;
1846
- }
1847
- let total = 2;
1848
- for (const key of Object.keys(value)) {
1849
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
1850
- if (!descriptor || descriptor.get || descriptor.set) {
1851
- total += key.length + 8;
1852
- continue;
1853
- }
1854
- total += key.length + estimateBytes(descriptor.value, depth + 1);
1855
- }
1856
- return total;
1857
- }
1858
- default:
1859
- return 8;
1860
- }
1861
- };
1862
- var capturedFromWire = (value) => {
1863
- if (rasputinTagKind(value) === "omitted") {
1864
- return { origin: "unavailable", reason: "omitted_value" };
1865
- }
1866
- if (isRedactedCapturedValue(value)) {
1867
- return { origin: "captured", completeness: "partial", value: rasputinRedacted() };
1868
- }
1869
- return { origin: "captured", completeness: capturedValueCompleteness(value), value };
1870
- };
1871
- var checkpointRank = (value) => {
1872
- if (!value || value.origin === "unavailable") return 5;
1873
- if (value.origin === "captured" && value.completeness === "exact") return 1;
1874
- if (value.origin === "derived" && value.completeness === "exact") return 2;
1875
- if (value.origin === "captured") return 3;
1876
- return 4;
1877
- };
1878
- var resolveCheckpoint = (captured, derived, expressionId) => {
1879
- const capturedRank = checkpointRank(captured);
1880
- const derivedRank = checkpointRank(derived);
1881
- if (capturedRank === 1 && derivedRank === 2 && captured?.origin === "captured" && derived?.origin === "derived") {
1882
- if (!wireValuesEqual(captured.value, derived.value)) {
1883
- return {
1884
- origin: "captured",
1885
- completeness: "exact",
1886
- value: captured.value,
1887
- derivationValidation: {
1888
- status: "mismatch",
1889
- derivedValue: derived.value,
1890
- expressionId,
1891
- dependencies: derived.dependencies
1892
- }
1893
- };
1894
- }
1895
- return {
1896
- origin: "captured",
1897
- completeness: "exact",
1898
- value: captured.value,
1899
- derivationValidation: { status: "verified" }
1900
- };
1901
- }
1902
- if (capturedRank <= derivedRank) {
1903
- return captured ?? derived ?? { origin: "unavailable", reason: "no_checkpoint" };
1904
- }
1905
- return derived ?? { origin: "unavailable", reason: "no_checkpoint" };
1906
- };
1907
- var exactOperandsOrUnavailable = (parts) => {
1908
- const completeness = mergeCompleteness(parts);
1909
- if (completeness === "unavailable")
1910
- return { origin: "unavailable", reason: "partial_dependency" };
1911
- if (completeness === "partial") return { origin: "unavailable", reason: "partial_operand" };
1912
- return void 0;
1913
- };
1914
- var capturedResult = (root) => {
1915
- if (isRedactedCapturedValue(root.value)) {
1916
- return { origin: "captured", completeness: "partial", value: rasputinRedacted() };
1917
- }
1918
- const completeness = root.completeness === "partial" ? "partial" : capturedValueCompleteness(root.value);
1919
- return { origin: "captured", completeness, value: root.value };
1920
- };
1921
- var mergeCompleteness = (parts) => {
1922
- let partial = false;
1923
- for (const part of parts) {
1924
- if (part.origin === "unavailable") return "unavailable";
1925
- if (part.completeness === "partial") partial = true;
1926
- }
1927
- return partial ? "partial" : "exact";
1928
- };
1929
- var unwrapExactPrimitive = (result) => {
1930
- if (result.origin === "unavailable") return rasputinUnavailable(result.reason);
1931
- return result.value;
1932
- };
1933
- var isNumericPrimitive = (value) => typeof value === "number" && !isRasputinTaggedValue(value);
1934
- var isPrimitiveOperand = (value) => {
1935
- if (isRasputinTaggedValue(value)) return false;
1936
- return value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string" || typeof value === "bigint" || typeof value === "undefined";
1937
- };
1938
- var omittedCallKey = (parentCallId, functionId) => `${parentCallId}\0${functionId}`;
1939
- var argsIncludeOmitted = (args) => args.some((arg) => rasputinTagKind(arg) === "omitted");
1940
- var recordReconstructedCallArgs = (queue, parentCallId, functionId, args) => {
1941
- const key = omittedCallKey(parentCallId, functionId);
1942
- const pending = queue.get(key);
1943
- if (pending) pending.push(args);
1944
- else queue.set(key, [args]);
1945
- };
1946
- var takeReconstructedCallArgs = (queue, parentCallId, functionId) => {
1947
- if (parentCallId == null) return void 0;
1948
- const pending = queue.get(omittedCallKey(parentCallId, functionId));
1949
- if (!pending || pending.length === 0) return void 0;
1950
- return pending.shift();
1951
- };
1952
- var resolveEnterArgs = (enter, queue) => {
1953
- if (!argsIncludeOmitted(enter.args)) return enter.args;
1954
- return takeReconstructedCallArgs(queue, enter.parentCallId, enter.functionId) ?? enter.args;
1955
- };
1956
- var takeChildCapturedReturn = (state, calleeFunctionId) => {
1957
- if (state.recordingCallId == null) return void 0;
1958
- const children = (state.childrenByParent.get(state.recordingCallId) ?? []).filter(
1959
- (child2) => child2.functionId === calleeFunctionId
1960
- );
1961
- const key = omittedCallKey(state.recordingCallId, calleeFunctionId);
1962
- const index = state.childCursor.get(key) ?? 0;
1963
- state.childCursor.set(key, index + 1);
1964
- const child = children[index];
1965
- if (!child) return void 0;
1966
- if (state.throwCallIds.has(child.callId)) return { threw: true };
1967
- return state.exitByCallId.get(child.callId);
1968
- };
1969
- var readOwnDataProperty = (object, key) => {
1970
- const descriptor = Object.getOwnPropertyDescriptor(object, key);
1971
- if (!descriptor) return { ok: false, reason: "missing_property" };
1972
- if (descriptor.get !== void 0 || descriptor.set !== void 0) {
1973
- return { ok: false, reason: "accessor_property" };
1974
- }
1975
- return { ok: true, value: descriptor.value };
1976
- };
1977
- var budgetExceeded = (state) => {
1978
- if (state.instructions > MAX_INSTRUCTIONS) return "instruction_budget";
1979
- if (state.depth > MAX_DEPTH) return "depth_budget";
1980
- if (state.outputBytes > MAX_OUTPUT_BYTES) return "output_size_budget";
1981
- if (Date.now() - state.startedAt > MAX_WALL_MS) return "wall_time_budget";
1982
- return void 0;
1983
- };
1984
- var evaluateExpression = (expressionId, plan, byId, cells, args, rootsById, factsById, calleeReturns, state, evaluateCallee) => {
1985
- const over = budgetExceeded(state);
1986
- if (over) return { origin: "unavailable", reason: over };
1987
- state.instructions++;
1988
- state.depth++;
1989
- try {
1990
- const existing = cells.get(expressionId);
1991
- if (existing?.status === "done") return existing.result;
1992
- if (existing?.status === "pending") {
1993
- return { origin: "unavailable", reason: "cyclic_expression" };
1994
- }
1995
- const expression = byId.get(expressionId);
1996
- if (!expression) return { origin: "unavailable", reason: "missing_expression" };
1997
- cells.set(expressionId, { status: "pending" });
1998
- const evalChild = (id) => evaluateExpression(
1999
- id,
2000
- plan,
2001
- byId,
2002
- cells,
2003
- args,
2004
- rootsById,
2005
- factsById,
2006
- calleeReturns,
2007
- state,
2008
- evaluateCallee
2009
- );
2010
- let result;
2011
- switch (expression.kind) {
2012
- case "const": {
2013
- const value = primitiveFromConst(expression.value);
2014
- result = { origin: "derived", completeness: "exact", value, dependencies: [] };
2015
- break;
2016
- }
2017
- case "param": {
2018
- if (expression.index < 0 || expression.index >= args.length) {
2019
- result = { origin: "unavailable", reason: "missing_argument" };
2020
- break;
2021
- }
2022
- const value = args[expression.index];
2023
- if (rasputinTagKind(value) === "omitted") {
2024
- result = { origin: "unavailable", reason: "omitted_argument" };
2025
- break;
2026
- }
2027
- result = capturedFromWire(value);
2028
- break;
2029
- }
2030
- case "root": {
2031
- const root = rootsById.get(expression.rootId);
2032
- if (!root) {
2033
- result = { origin: "unavailable", reason: "missing_root" };
2034
- break;
2035
- }
2036
- result = capturedResult(root);
2037
- break;
2038
- }
2039
- case "alias":
2040
- result = evalChild(expression.from);
2041
- break;
2042
- case "binary": {
2043
- const left = evalChild(expression.left);
2044
- const right = evalChild(expression.right);
2045
- const blocked = exactOperandsOrUnavailable([left, right]);
2046
- if (blocked) {
2047
- result = blocked;
2048
- break;
2049
- }
2050
- const leftValue = unwrapExactPrimitive(left);
2051
- const rightValue = unwrapExactPrimitive(right);
2052
- if (rasputinTagKind(leftValue) || rasputinTagKind(rightValue)) {
2053
- result = { origin: "unavailable", reason: "tagged_operand" };
2054
- break;
2055
- }
2056
- if (isRedactedCapturedValue(leftValue) || isRedactedCapturedValue(rightValue)) {
2057
- result = { origin: "unavailable", reason: "redacted_operand" };
2058
- break;
2059
- }
2060
- if (expression.guard === "numeric") {
2061
- if (!isNumericPrimitive(leftValue) || !isNumericPrimitive(rightValue)) {
2062
- result = { origin: "unavailable", reason: "numeric_guard_failed" };
2063
- break;
2064
- }
2065
- } else if (expression.guard === "primitive") {
2066
- if (!isPrimitiveOperand(leftValue) || !isPrimitiveOperand(rightValue)) {
2067
- result = { origin: "unavailable", reason: "primitive_guard_failed" };
2068
- break;
2069
- }
2070
- }
2071
- const value = applyBinary(expression.op, leftValue, rightValue);
2072
- if (value === void 0) {
2073
- result = { origin: "unavailable", reason: "unsupported_binary" };
2074
- break;
2075
- }
2076
- result = {
2077
- origin: "derived",
2078
- completeness: "exact",
2079
- value,
2080
- dependencies: [expression.left, expression.right]
2081
- };
2082
- break;
2083
- }
2084
- case "unary": {
2085
- const operand = evalChild(expression.operand);
2086
- const blocked = exactOperandsOrUnavailable([operand]);
2087
- if (blocked || operand.origin === "unavailable") {
2088
- result = blocked ?? operand;
2089
- break;
2090
- }
2091
- const raw = operand.value;
2092
- if (isRedactedCapturedValue(raw) || rasputinTagKind(raw)) {
2093
- result = { origin: "unavailable", reason: "tagged_operand" };
2094
- break;
2095
- }
2096
- if (expression.op === "typeof") {
2097
- result = {
2098
- origin: "derived",
2099
- completeness: "exact",
2100
- value: typeof raw,
2101
- dependencies: [expression.operand]
2102
- };
2103
- break;
2104
- }
2105
- if (expression.op === "not") {
2106
- result = {
2107
- origin: "derived",
2108
- completeness: "exact",
2109
- value: !raw,
2110
- dependencies: [expression.operand]
2111
- };
2112
- break;
2113
- }
2114
- if (!isNumericPrimitive(raw)) {
2115
- result = { origin: "unavailable", reason: "numeric_guard_failed" };
2116
- break;
2117
- }
2118
- result = {
2119
- origin: "derived",
2120
- completeness: "exact",
2121
- value: expression.op === "minus" ? -raw : +raw,
2122
- dependencies: [expression.operand]
2123
- };
2124
- break;
2125
- }
2126
- case "logical": {
2127
- const left = evalChild(expression.left);
2128
- const leftBlocked = exactOperandsOrUnavailable([left]);
2129
- if (leftBlocked || left.origin === "unavailable") {
2130
- result = leftBlocked ?? left;
2131
- break;
2132
- }
2133
- const takeRight = expression.op === "and" ? Boolean(left.value) : expression.op === "or" ? !left.value : left.value == null;
2134
- if (!takeRight) {
2135
- result = left.origin === "derived" ? left : {
2136
- origin: "derived",
2137
- completeness: "exact",
2138
- value: left.value,
2139
- dependencies: [expression.left]
2140
- };
2141
- break;
2142
- }
2143
- const right = evalChild(expression.right);
2144
- const blocked = exactOperandsOrUnavailable([left, right]);
2145
- if (blocked || right.origin === "unavailable") {
2146
- result = blocked ?? right;
2147
- break;
2148
- }
2149
- result = {
2150
- origin: "derived",
2151
- completeness: "exact",
2152
- value: right.value,
2153
- dependencies: [expression.left, expression.right]
2154
- };
2155
- break;
2156
- }
2157
- case "cond": {
2158
- const test = evalChild(expression.test);
2159
- const testBlocked = exactOperandsOrUnavailable([test]);
2160
- if (testBlocked || test.origin === "unavailable") {
2161
- result = testBlocked ?? test;
2162
- break;
2163
- }
2164
- const fact = [...factsById.values()].find(
2165
- (entry) => plan.controlFacts.some((definition) => definition.id === entry.factId)
2166
- );
2167
- const takeConsequent = fact ? fact.value : Boolean(test.value);
2168
- const branch = evalChild(takeConsequent ? expression.consequent : expression.alternate);
2169
- const blocked = exactOperandsOrUnavailable([test, branch]);
2170
- if (blocked || branch.origin === "unavailable") {
2171
- result = blocked ?? branch;
2172
- break;
2173
- }
2174
- result = {
2175
- origin: "derived",
2176
- completeness: "exact",
2177
- value: branch.value,
2178
- dependencies: [
2179
- expression.test,
2180
- takeConsequent ? expression.consequent : expression.alternate
2181
- ]
2182
- };
2183
- break;
2184
- }
2185
- case "template": {
2186
- const parts = [];
2187
- let text = "";
2188
- let failed;
2189
- for (const part of expression.parts) {
2190
- if (part.kind === "lit") {
2191
- text += part.value;
2192
- continue;
2193
- }
2194
- const nested = evalChild(part.expressionId);
2195
- parts.push(nested);
2196
- if (nested.origin === "unavailable") {
2197
- failed = nested;
2198
- break;
2199
- }
2200
- if (!isPrimitiveOperand(nested.value) || isRasputinTaggedValue(nested.value)) {
2201
- failed = { origin: "unavailable", reason: "template_non_primitive" };
2202
- break;
2203
- }
2204
- text += String(nested.value);
2205
- }
2206
- if (failed) {
2207
- result = failed;
2208
- break;
2209
- }
2210
- const completeness = parts.length === 0 ? "exact" : mergeCompleteness(parts);
2211
- if (completeness === "unavailable") {
2212
- result = { origin: "unavailable", reason: "partial_dependency" };
2213
- break;
2214
- }
2215
- result = {
2216
- origin: "derived",
2217
- completeness,
2218
- value: text,
2219
- dependencies: expression.parts.flatMap(
2220
- (part) => part.kind === "expr" ? [part.expressionId] : []
2221
- )
2222
- };
2223
- break;
2224
- }
2225
- case "object": {
2226
- const record = {};
2227
- const parts = [];
2228
- let failed;
2229
- for (const property of expression.properties) {
2230
- const nested = evalChild(property.value);
2231
- parts.push(nested);
2232
- if (nested.origin === "unavailable") {
2233
- failed = nested;
2234
- break;
2235
- }
2236
- record[property.key] = nested.value;
2237
- }
2238
- if (failed) {
2239
- result = failed;
2240
- break;
2241
- }
2242
- const completeness = mergeCompleteness(parts);
2243
- if (completeness === "unavailable") {
2244
- result = { origin: "unavailable", reason: "partial_dependency" };
2245
- break;
2246
- }
2247
- result = {
2248
- origin: "derived",
2249
- completeness,
2250
- value: record,
2251
- dependencies: expression.properties.map((property) => property.value)
2252
- };
2253
- break;
2254
- }
2255
- case "array": {
2256
- const items = [];
2257
- const parts = [];
2258
- let failed;
2259
- let unknownCount = 0;
2260
- for (const element of expression.elements) {
2261
- if (typeof element !== "number") {
2262
- items.push(rasputinUnknown());
2263
- unknownCount++;
2264
- continue;
2265
- }
2266
- const nested = evalChild(element);
2267
- parts.push(nested);
2268
- if (nested.origin === "unavailable") {
2269
- failed = nested;
2270
- break;
2271
- }
2272
- items.push(nested.value);
2273
- }
2274
- if (failed) {
2275
- result = failed;
2276
- break;
2277
- }
2278
- const completeness = unknownCount > 0 ? "partial" : parts.length === 0 ? "exact" : mergeCompleteness(parts);
2279
- if (completeness === "unavailable") {
2280
- result = { origin: "unavailable", reason: "partial_dependency" };
2281
- break;
2282
- }
2283
- result = {
2284
- origin: "derived",
2285
- completeness,
2286
- value: items,
2287
- dependencies: expression.elements.flatMap(
2288
- (element) => typeof element === "number" ? [element] : []
2289
- )
2290
- };
2291
- break;
2292
- }
2293
- case "prop": {
2294
- const object = evalChild(expression.object);
2295
- if (object.origin === "unavailable") {
2296
- result = object;
2297
- break;
2298
- }
2299
- if (isRedactedCapturedValue(object.value)) {
2300
- result = { origin: "unavailable", reason: "redacted_operand" };
2301
- break;
2302
- }
2303
- if (!isRecord3(object.value) || rasputinTagKind(object.value)) {
2304
- result = { origin: "unavailable", reason: "prop_on_non_object" };
2305
- break;
2306
- }
2307
- const owned = readOwnDataProperty(object.value, expression.key);
2308
- if (!owned.ok) {
2309
- result = { origin: "unavailable", reason: owned.reason };
2310
- break;
2311
- }
2312
- const completeness = object.completeness === "partial" || capturedValueCompleteness(owned.value) === "partial" ? "partial" : "exact";
2313
- result = {
2314
- origin: "derived",
2315
- completeness,
2316
- value: owned.value,
2317
- dependencies: [expression.object]
2318
- };
2319
- break;
2320
- }
2321
- case "index": {
2322
- const array = evalChild(expression.array);
2323
- if (array.origin === "unavailable") {
2324
- result = array;
2325
- break;
2326
- }
2327
- if (!Array.isArray(array.value)) {
2328
- result = { origin: "unavailable", reason: "index_on_non_array" };
2329
- break;
2330
- }
2331
- if (expression.index < 0 || expression.index >= array.value.length) {
2332
- result = { origin: "unavailable", reason: "index_out_of_range" };
2333
- break;
2334
- }
2335
- const value = array.value[expression.index];
2336
- if (rasputinTagKind(value) === "unknown") {
2337
- result = { origin: "unavailable", reason: "array_hole" };
2338
- break;
2339
- }
2340
- const completeness = array.completeness === "partial" || capturedValueCompleteness(value) === "partial" ? "partial" : "exact";
2341
- result = {
2342
- origin: "derived",
2343
- completeness,
2344
- value,
2345
- dependencies: [expression.array]
2346
- };
2347
- break;
2348
- }
2349
- case "pure_call": {
2350
- const childReturn = takeChildCapturedReturn(state, expression.calleeFunctionId);
2351
- if (childReturn && "threw" in childReturn) {
2352
- result = { origin: "unavailable", reason: "callee_threw" };
2353
- break;
2354
- }
2355
- const capturedReturn = childReturn && "present" in childReturn ? capturedFromWire(childReturn.value) : void 0;
2356
- const callee = calleeReturns.get(expression.calleeFunctionId);
2357
- const callArgs = [];
2358
- let argsExact = true;
2359
- for (const argId of expression.args) {
2360
- const nested = evalChild(argId);
2361
- if (nested.origin === "unavailable" || nested.completeness === "partial") {
2362
- argsExact = false;
2363
- continue;
2364
- }
2365
- callArgs.push(nested.value);
2366
- }
2367
- if (argsExact && state.recordingCallId != null) {
2368
- recordReconstructedCallArgs(
2369
- state.omittedCallArgs,
2370
- state.recordingCallId,
2371
- expression.calleeFunctionId,
2372
- callArgs
2373
- );
2374
- }
2375
- const canDeriveCallee = Boolean(
2376
- argsExact && callee && !callee.fallbackReason && callee.returnExpressionId != null
2377
- );
2378
- const derivedReturn = canDeriveCallee && callee ? evaluateCallee(callee, callArgs) : void 0;
2379
- result = resolveCheckpoint(capturedReturn, derivedReturn, expression.id);
2380
- break;
2381
- }
2382
- }
2383
- if (result.origin !== "unavailable") {
2384
- state.outputBytes += estimateBytes(result.value);
2385
- const sizeOver = budgetExceeded(state);
2386
- if (sizeOver) result = { origin: "unavailable", reason: sizeOver };
2387
- }
2388
- if (result.origin === "derived" && result.completeness === "exact") {
2389
- for (const dep of result.dependencies) {
2390
- const child = cells.get(dep);
2391
- if (child?.status === "done" && child.result.origin !== "unavailable") {
2392
- if (child.result.completeness === "partial") {
2393
- result = { ...result, completeness: "partial" };
2394
- break;
2395
- }
2396
- }
2397
- }
2398
- }
2399
- cells.set(expressionId, { status: "done", result });
2400
- return result;
2401
- } finally {
2402
- state.depth--;
2403
- }
2404
- };
2405
- var isJsPrimitive = (value) => {
2406
- const type = typeof value;
2407
- return value === null || type !== "object" && type !== "function";
2408
- };
2409
- var abstractEquals = (left, right) => {
2410
- if (!isJsPrimitive(left) || !isJsPrimitive(right)) return void 0;
2411
- return left == right;
2412
- };
2413
- var applyBinary = (op, left, right) => {
2414
- if (op === "add" && typeof left === "number" && typeof right === "number") return left + right;
2415
- if (op === "add" && typeof left === "string" && typeof right === "string") return left + right;
2416
- if (typeof left !== "number" || typeof right !== "number") {
2417
- if (op === "eq") return abstractEquals(left, right);
2418
- if (op === "neq") {
2419
- const equal = abstractEquals(left, right);
2420
- return equal === void 0 ? void 0 : !equal;
2046
+ // src/derivation/sensitive-property-name.ts
2047
+ var DEFAULT_SENSITIVE_PROPERTY_NAMES = [
2048
+ "password",
2049
+ "passwd",
2050
+ "pwd",
2051
+ "passphrase",
2052
+ "token",
2053
+ "accesstoken",
2054
+ "refreshtoken",
2055
+ "sessiontoken",
2056
+ "authorization",
2057
+ "auth",
2058
+ "bearer",
2059
+ "cookie",
2060
+ "cookies",
2061
+ "signedcookies",
2062
+ "setcookie",
2063
+ "secret",
2064
+ "secrets",
2065
+ "clientsecret",
2066
+ "apikey",
2067
+ "xapikey",
2068
+ "credential",
2069
+ "credentials",
2070
+ "privatekey",
2071
+ "privkey",
2072
+ "pem",
2073
+ "jwt",
2074
+ "otp",
2075
+ "totp",
2076
+ "pin",
2077
+ "mfacode",
2078
+ "sessionid",
2079
+ "sid",
2080
+ "databaseurl",
2081
+ "connectionstring",
2082
+ "dsn",
2083
+ "mongouri",
2084
+ "redisurl",
2085
+ "signingkey",
2086
+ "encryptionkey",
2087
+ "cvv",
2088
+ "cvc",
2089
+ "cardnumber",
2090
+ "ssn",
2091
+ "creditcard",
2092
+ "securitycode",
2093
+ "twofactorcode",
2094
+ "masterkey",
2095
+ "servicerolekey",
2096
+ "stripekey",
2097
+ "rawheaders"
2098
+ ];
2099
+ var DEFAULT_SENSITIVE_PROPERTY_NAME_SET = new Set(DEFAULT_SENSITIVE_PROPERTY_NAMES);
2100
+ var SENSITIVE_SEGMENTS = /* @__PURE__ */ new Set([
2101
+ "password",
2102
+ "passwd",
2103
+ "pwd",
2104
+ "passphrase",
2105
+ "token",
2106
+ "secret",
2107
+ "cookie",
2108
+ "cookies",
2109
+ "authorization",
2110
+ "auth",
2111
+ "bearer",
2112
+ "credential",
2113
+ "credentials",
2114
+ "jwt",
2115
+ "otp",
2116
+ "totp",
2117
+ "pin",
2118
+ "sid",
2119
+ "dsn",
2120
+ "pem",
2121
+ "privkey",
2122
+ "cvv",
2123
+ "cvc",
2124
+ "ssn",
2125
+ "secrets"
2126
+ ]);
2127
+ var SENSITIVE_SUFFIXES = [
2128
+ "password",
2129
+ "passwd",
2130
+ "token",
2131
+ "secret",
2132
+ "apikey",
2133
+ "privatekey",
2134
+ "credential",
2135
+ "credentials",
2136
+ "cookie"
2137
+ ];
2138
+ var compactKey = (key) => {
2139
+ let compacted = "";
2140
+ for (let index = 0; index < key.length; index++) {
2141
+ const code = key.charCodeAt(index);
2142
+ if (code === 45 || code === 46 || code === 95) continue;
2143
+ compacted += code >= 65 && code <= 90 ? String.fromCharCode(code + 32) : key[index];
2144
+ }
2145
+ return compacted;
2146
+ };
2147
+ var stripTrailingDigits = (value) => {
2148
+ let end = value.length;
2149
+ while (end > 0) {
2150
+ const code = value.charCodeAt(end - 1);
2151
+ if (code < 48 || code > 57) break;
2152
+ end--;
2153
+ }
2154
+ return end === 0 ? value : value.slice(0, end);
2155
+ };
2156
+ var segmentsOf = (key) => {
2157
+ const segments = [];
2158
+ let current = "";
2159
+ for (let index = 0; index < key.length; index++) {
2160
+ const char = key[index] ?? "";
2161
+ const code = key.charCodeAt(index);
2162
+ if (code === 45 || code === 46 || code === 95) {
2163
+ if (current) segments.push(current.toLowerCase());
2164
+ current = "";
2165
+ continue;
2421
2166
  }
2422
- if (op === "seq") return left === right;
2423
- if (op === "sneq") return left !== right;
2424
- return void 0;
2425
- }
2426
- switch (op) {
2427
- case "add":
2428
- return left + right;
2429
- case "sub":
2430
- return left - right;
2431
- case "mul":
2432
- return left * right;
2433
- case "div":
2434
- return left / right;
2435
- case "mod":
2436
- return left % right;
2437
- case "eq":
2438
- return left === right;
2439
- case "neq":
2440
- return left !== right;
2441
- case "seq":
2442
- return left === right;
2443
- case "sneq":
2444
- return left !== right;
2445
- case "lt":
2446
- return left < right;
2447
- case "lte":
2448
- return left <= right;
2449
- case "gt":
2450
- return left > right;
2451
- case "gte":
2452
- return left >= right;
2453
- }
2454
- };
2455
- var evaluatePlan = (plan, args, roots, facts, plansByFunctionId, state, recordingCallId, capturedExit, threw = false) => {
2456
- const previousRecordingCallId = state.recordingCallId;
2457
- state.recordingCallId = recordingCallId;
2458
- try {
2459
- const byId = new Map(plan.expressions.map((expression) => [expression.id, expression]));
2460
- const cells = /* @__PURE__ */ new Map();
2461
- const rootsById = new Map(roots.map((root) => [root.rootId, root]));
2462
- const factsById = new Map(facts.map((fact) => [fact.factId, fact]));
2463
- const evaluateCallee = (callee, callArgs) => {
2464
- if (callee.returnExpressionId == null) {
2465
- return { origin: "unavailable", reason: "callee_without_return" };
2167
+ if (current && code >= 65 && code <= 90) {
2168
+ const prev = current.charCodeAt(current.length - 1);
2169
+ const prevIsLower = prev >= 97 && prev <= 122;
2170
+ const next = key.charCodeAt(index + 1);
2171
+ const nextIsLower = next >= 97 && next <= 122;
2172
+ if (prevIsLower || nextIsLower) {
2173
+ segments.push(current.toLowerCase());
2174
+ current = char;
2175
+ continue;
2466
2176
  }
2467
- const nested = evaluatePlan(callee, callArgs, [], [], plansByFunctionId, state, null);
2468
- return nested.returnValue ?? { origin: "unavailable", reason: "callee_without_return" };
2469
- };
2470
- const values = {};
2471
- for (const expression of plan.expressions) {
2472
- values[expression.id] = evaluateExpression(
2473
- expression.id,
2474
- plan,
2475
- byId,
2476
- cells,
2477
- args,
2478
- rootsById,
2479
- factsById,
2480
- plansByFunctionId,
2481
- state,
2482
- evaluateCallee
2483
- );
2484
2177
  }
2485
- const variables = plan.variables.map((variable) => ({
2486
- variableId: variable.id,
2487
- name: variable.name,
2488
- availableAtLine: variable.availableAtLine,
2489
- availableAtColumn: variable.availableAtColumn,
2490
- result: values[variable.expressionId] ?? {
2491
- origin: "unavailable",
2492
- reason: "missing_expression"
2493
- }
2494
- }));
2495
- const derivedReturn = plan.returnExpressionId == null ? void 0 : values[plan.returnExpressionId] ?? {
2496
- origin: "unavailable",
2497
- reason: "missing_return"
2498
- };
2499
- return {
2500
- functionId: plan.functionId,
2501
- callId: roots[0]?.callId ?? facts[0]?.callId ?? 0,
2502
- values,
2503
- variables,
2504
- returnValue: threw ? void 0 : resolveCheckpoint(
2505
- capturedExit ? capturedFromWire(capturedExit.value) : void 0,
2506
- derivedReturn,
2507
- plan.returnExpressionId
2508
- )
2509
- };
2510
- } finally {
2511
- state.recordingCallId = previousRecordingCallId;
2178
+ current += char;
2512
2179
  }
2180
+ if (current) segments.push(current.toLowerCase());
2181
+ return segments;
2513
2182
  };
2514
- var evaluateRuntimeDerivation = (input) => {
2515
- if (!input.manifest) {
2516
- return { unavailableReason: "missing_manifest", functions: [] };
2517
- }
2518
- if (input.manifest.schema_version !== 3) {
2519
- return { unavailableReason: "manifest_version_mismatch", functions: [] };
2520
- }
2521
- const expectedHash = input.state.derivation?.manifestHash;
2522
- if (!expectedHash) {
2523
- return { unavailableReason: "missing_manifest_hash", functions: [] };
2524
- }
2525
- if (hashInstrumentationManifest(input.manifest) !== expectedHash) {
2526
- return { unavailableReason: "manifest_hash_mismatch", functions: [] };
2527
- }
2528
- const plansByFunctionId = new Map(
2529
- (input.manifest.derivationPlans ?? []).map((plan) => [plan.functionId, plan])
2183
+ 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";
2184
+ var defaultSensitivePropertyNames = () => DEFAULT_SENSITIVE_PROPERTY_NAME_SET;
2185
+ var propertyNameIsSensitive = (key) => {
2186
+ const compacted = compactKey(key);
2187
+ const compactForms = [compacted, stripTrailingDigits(compacted)].filter(
2188
+ (value, index, values) => value.length > 0 && values.indexOf(value) === index
2530
2189
  );
2531
- const roots = input.state.derivation?.roots ?? [];
2532
- const facts = input.state.derivation?.controlFacts ?? [];
2533
- const exitByCallId = /* @__PURE__ */ new Map();
2534
- const throwCallIds = /* @__PURE__ */ new Set();
2535
- const childrenByParent = /* @__PURE__ */ new Map();
2536
- for (const event of input.state.events) {
2537
- if (event.type === "function_enter" && event.parentCallId != null) {
2538
- const child = { callId: event.callId, functionId: event.functionId };
2539
- const siblings = childrenByParent.get(event.parentCallId);
2540
- if (siblings) siblings.push(child);
2541
- else childrenByParent.set(event.parentCallId, [child]);
2542
- }
2543
- if (event.type === "function_exit") {
2544
- exitByCallId.set(event.callId, { present: true, value: event.returnValue });
2190
+ for (const form of compactForms) {
2191
+ if (DEFAULT_SENSITIVE_PROPERTY_NAME_SET.has(form)) return true;
2192
+ for (const suffix of SENSITIVE_SUFFIXES) {
2193
+ if (form.endsWith(suffix)) return true;
2545
2194
  }
2546
- if (event.type === "function_throw") throwCallIds.add(event.callId);
2195
+ if (form.includes("apikey") || form.includes("xapikey")) return true;
2547
2196
  }
2548
- const state = {
2549
- instructions: 0,
2550
- depth: 0,
2551
- outputBytes: 0,
2552
- startedAt: Date.now(),
2553
- recordingCallId: null,
2554
- omittedCallArgs: /* @__PURE__ */ new Map(),
2555
- exitByCallId,
2556
- throwCallIds,
2557
- childrenByParent,
2558
- childCursor: /* @__PURE__ */ new Map()
2559
- };
2560
- const enterEvents = input.state.events.filter((event) => event.type === "function_enter");
2561
- const functions = [];
2562
- for (const enter of enterEvents) {
2563
- const plan = plansByFunctionId.get(enter.functionId);
2564
- if (!plan) continue;
2565
- const callRoots = roots.filter((root) => root.callId === enter.callId);
2566
- const callFacts = facts.filter((fact) => fact.callId === enter.callId);
2567
- const evaluated = evaluatePlan(
2568
- plan,
2569
- resolveEnterArgs(enter, state.omittedCallArgs),
2570
- callRoots,
2571
- callFacts,
2572
- plansByFunctionId,
2573
- state,
2574
- enter.callId,
2575
- exitByCallId.get(enter.callId),
2576
- throwCallIds.has(enter.callId)
2577
- );
2578
- evaluated.callId = enter.callId;
2579
- functions.push(evaluated);
2197
+ const segments = segmentsOf(key).map((segment) => stripTrailingDigits(segment) || segment);
2198
+ for (const segment of segments) {
2199
+ if (SENSITIVE_SEGMENTS.has(segment)) return true;
2580
2200
  }
2581
- return { functions };
2582
- };
2583
- var evaluatedValueAtLine = (evaluated, line) => {
2584
- if (!evaluated) return [];
2585
- return evaluated.variables.filter((variable) => line == null || variable.availableAtLine <= line).map((variable) => ({ name: variable.name, result: variable.result }));
2201
+ for (let index = 0; index < segments.length - 1; index++) {
2202
+ const left = segments[index];
2203
+ const right = segments[index + 1];
2204
+ if (left && right && adjacentPairsSensitive(left, right)) return true;
2205
+ }
2206
+ return false;
2586
2207
  };
2587
2208
 
2588
2209
  // src/instrumentation-manifest/instrumentation-manifest-types.ts
@@ -2592,7 +2213,7 @@ var INSTRUMENTATION_MANIFEST_SCHEMA_VERSION = 3;
2592
2213
  var uploadInstrumentationManifest = async (options) => {
2593
2214
  const release = options.release.trim();
2594
2215
  if (!release) return { ok: false };
2595
- const fetchFn = options.fetch ?? globalThis.fetch;
2216
+ const fetchFn = options.fetch ?? defaultIngestFetch();
2596
2217
  try {
2597
2218
  const response = await fetchFn(options.url, {
2598
2219
  method: "POST",
@@ -2601,7 +2222,8 @@ var uploadInstrumentationManifest = async (options) => {
2601
2222
  "Content-Type": "application/json",
2602
2223
  Accept: "application/json"
2603
2224
  },
2604
- body: JSON.stringify({ release, manifest: options.manifest })
2225
+ body: JSON.stringify({ release, manifest: options.manifest }),
2226
+ ...options.signal ? { signal: options.signal } : {}
2605
2227
  });
2606
2228
  if (!response.ok) return { ok: false };
2607
2229
  const body = await response.json();
@@ -2615,21 +2237,21 @@ export {
2615
2237
  INSTRUMENTATION_MANIFEST_SCHEMA_VERSION,
2616
2238
  RASPUTIN_WIRE_TAG,
2617
2239
  announceDeploy,
2618
- capturedValueCompleteness,
2619
2240
  createClient,
2620
2241
  createIngestEvent,
2621
2242
  createSuppression,
2622
2243
  createTransport,
2244
+ defaultSensitivePropertyNames,
2623
2245
  detectRelease,
2624
- evaluateRuntimeDerivation,
2625
- evaluatedValueAtLine,
2246
+ foldSyntheticFrames,
2626
2247
  hashInstrumentationManifest,
2627
2248
  isClientEnabled,
2249
+ isRasputinHandleRunFrame,
2250
+ isRasputinShellFrame,
2628
2251
  isRasputinTaggedValue,
2629
- isRedactedCapturedValue,
2630
- isSerializerMarker,
2631
2252
  normalizeFrames,
2632
2253
  parseStack,
2254
+ propertyNameIsSensitive,
2633
2255
  rasputinOmitted,
2634
2256
  rasputinRedacted,
2635
2257
  rasputinTagKind,