@autter/otlp-ingester 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,441 @@
1
+ import { detectStackLanguage } from "./fingerprint.js";
2
+ /**
3
+ * Raw stack text → structured frames, INNERMOST (crash site) FIRST.
4
+ *
5
+ * The legacy `message-v1` fingerprint hashes stack lines as text, which
6
+ * keeps Python line numbers, takes Python's outermost frames, and counts
7
+ * dependency frames. `frames-v3` (fingerprint-v3.ts) hashes structured
8
+ * frames instead, so every supported language is parsed into the sink's
9
+ * StackFrame shape here, in one consistent orientation:
10
+ *
11
+ * - V8 (Node, Chrome, Edge): `at fn (file:line:col)` / `at file:line:col`
12
+ * - Firefox / Safari: `fn@url:line:col` / `@url:line:col`
13
+ * - Python: `File "…", line N, in fn` — tracebacks list the OUTERMOST frame
14
+ * first, so the frames are reversed; only the last (reported) traceback
15
+ * of a chained exception is used
16
+ * - Go: function line + `\t<file>.go:<line> +0x…`, first goroutine only
17
+ * - JVM: `at pkg.Class.method(File.java:N)`, first exception block only
18
+ * (before `Caused by:`)
19
+ * - .NET: `at Ns.Type.Method(args) in <file>:line N`; for wrapped
20
+ * exceptions only the frames after the last "End of inner exception"
21
+ * - Rust: `N: path::to::fn` + `at <file>:<line>:<col>`
22
+ * - Ruby / PHP best effort, so those stacks do not collapse to the message
23
+ *
24
+ * `inApp` follows the contract §3 rule (isInAppFrame). `resolved` is always
25
+ * false here; symbolicate.ts sets it for frames a source map resolved.
26
+ * Shared parity vectors: stack-parse-vectors.json.
27
+ */
28
+ /** Frames kept per stack (contract §1: at most 50). */
29
+ export const MAX_FRAMES = 50;
30
+ const MAX_FUNCTION_CHARS = 500;
31
+ const MAX_PATH_CHARS = 1000;
32
+ // ── In-app rule (contract §3) ─────────────────────────────────────────────
33
+ const NOT_IN_APP_PATH_PARTS = [
34
+ "node_modules/",
35
+ "/site-packages/",
36
+ "/dist-packages/",
37
+ "/vendor/",
38
+ "/.pnpm/",
39
+ ];
40
+ const NOT_IN_APP_PATH_PREFIXES = ["node:", "internal/"];
41
+ const EXTENSION_SCHEMES = [
42
+ "chrome-extension:",
43
+ "moz-extension:",
44
+ "safari-extension:",
45
+ "safari-web-extension:",
46
+ "webkit-masked-url:",
47
+ ];
48
+ /**
49
+ * Path half of §3: dependency directories, Node internals and browser
50
+ * extension scripts are not in-app. Windows separators are normalised so
51
+ * `C:\app\node_modules\x.js` counts too. A frame with no path is in-app.
52
+ */
53
+ export function isInAppPath(path) {
54
+ if (!path)
55
+ return true;
56
+ const normalized = path.replace(/\\/g, "/");
57
+ if (NOT_IN_APP_PATH_PARTS.some((part) => normalized.includes(part)))
58
+ return false;
59
+ if (NOT_IN_APP_PATH_PREFIXES.some((prefix) => normalized.startsWith(prefix)))
60
+ return false;
61
+ const lower = normalized.toLowerCase();
62
+ if (EXTENSION_SCHEMES.some((scheme) => lower.startsWith(scheme)))
63
+ return false;
64
+ return true;
65
+ }
66
+ /** Fully qualified name for the language rules (`module.function`). */
67
+ function qualifiedName(frame) {
68
+ if (frame.module && frame.function)
69
+ return `${frame.module}.${frame.function}`;
70
+ return frame.module ?? frame.function ?? "";
71
+ }
72
+ /**
73
+ * Contract §3. Source-map ignore lists are applied by symbolicate.ts; the
74
+ * language rules apply only to frames of that language, so a JS object
75
+ * that happens to be called `runtime` or `System` stays in-app.
76
+ */
77
+ export function isInAppFrame(frame, language) {
78
+ if (!isInAppPath(frame.filename))
79
+ return false;
80
+ const qualified = qualifiedName(frame);
81
+ if (language === "go") {
82
+ if (qualified.startsWith("runtime."))
83
+ return false;
84
+ if ((frame.filename ?? "").replace(/\\/g, "/").includes("/usr/local/go/"))
85
+ return false;
86
+ }
87
+ if (language === "jvm" && /^(?:java|javax|kotlin|jdk)\./.test(qualified))
88
+ return false;
89
+ if (language === "dotnet" && /^(?:System|Microsoft)\./.test(qualified))
90
+ return false;
91
+ return true;
92
+ }
93
+ // ── Helpers ───────────────────────────────────────────────────────────────
94
+ /** Strip a query string and/or hash from a path or URL. */
95
+ export function stripQueryAndHash(path) {
96
+ const at = path.search(/[?#]/);
97
+ return at === -1 ? path : path.slice(0, at);
98
+ }
99
+ function clean(value, max) {
100
+ if (value == null)
101
+ return null;
102
+ const trimmed = value.trim();
103
+ return trimmed ? trimmed.slice(0, max) : null;
104
+ }
105
+ function toInt(value) {
106
+ if (value === undefined)
107
+ return null;
108
+ const n = Number.parseInt(value, 10);
109
+ return Number.isFinite(n) ? n : null;
110
+ }
111
+ function frame(language, parts) {
112
+ const filename = clean(parts.filename ? stripQueryAndHash(parts.filename) : null, MAX_PATH_CHARS);
113
+ const out = {
114
+ function: clean(parts.function, MAX_FUNCTION_CHARS),
115
+ module: clean(parts.module, MAX_FUNCTION_CHARS),
116
+ filename,
117
+ lineno: parts.lineno ?? null,
118
+ colno: parts.colno ?? null,
119
+ inApp: true,
120
+ resolved: false,
121
+ };
122
+ out.inApp = isInAppFrame(out, language);
123
+ return out;
124
+ }
125
+ /**
126
+ * Drop the trailing (balanced) call-argument group: "f(0x1, 0x2)" → "f",
127
+ * "C->handle(Object(Request))" → "C->handle". Receiver parentheses earlier
128
+ * in the name ("main.(*T).M(…)") are kept.
129
+ */
130
+ function stripTrailingArgs(fn) {
131
+ const trimmed = fn.trim();
132
+ if (!trimmed.endsWith(")"))
133
+ return trimmed;
134
+ let depth = 0;
135
+ for (let i = trimmed.length - 1; i >= 0; i--) {
136
+ const ch = trimmed[i];
137
+ if (ch === ")")
138
+ depth++;
139
+ else if (ch === "(" && --depth === 0)
140
+ return i > 0 ? trimmed.slice(0, i).trim() : trimmed;
141
+ }
142
+ return trimmed;
143
+ }
144
+ /** Split at the last "." outside brackets/angles: "A.B.C[T]" → ["A.B", "C[T]"]. */
145
+ function splitLastDot(name) {
146
+ let depth = 0;
147
+ for (let i = name.length - 1; i > 0; i--) {
148
+ const ch = name[i];
149
+ if (ch === "]" || ch === ">" || ch === ")")
150
+ depth++;
151
+ else if (ch === "[" || ch === "<" || ch === "(")
152
+ depth = Math.max(0, depth - 1);
153
+ else if (ch === "." && depth === 0) {
154
+ // ".ctor" / ".cctor": the dot belongs to the method name.
155
+ if (name[i - 1] === ".")
156
+ return [name.slice(0, i - 1), name.slice(i)];
157
+ return [name.slice(0, i), name.slice(i + 1)];
158
+ }
159
+ }
160
+ return [null, name];
161
+ }
162
+ // ── JavaScript ────────────────────────────────────────────────────────────
163
+ const V8_LINE_RE = /^\s*at\s+(.*)$/;
164
+ const V8_WITH_FN_RE = /^(.*?)\s+\((.*)\)$/;
165
+ const LOCATION_RE = /^(.*?):(\d+)(?::(\d+))?$/;
166
+ const GECKO_LINE_RE = /^\s*(.*?)@(.*?)\s*$/;
167
+ /** "file:line:col" (or a bare non-path marker) → location parts. */
168
+ function jsLocation(raw) {
169
+ let location = raw.trim();
170
+ // eval frames: "eval at fn (file:1:2), <anonymous>:3:4" — use the
171
+ // evaluating script's position.
172
+ if (location.startsWith("eval at ")) {
173
+ const inner = /\(([^()]+?):(\d+):(\d+)\)/.exec(location);
174
+ if (inner)
175
+ return { filename: inner[1], lineno: toInt(inner[2]), colno: toInt(inner[3]) };
176
+ }
177
+ if (/^(?:native|<anonymous>|\[native code\]|index \d+|unknown location)$/i.test(location)) {
178
+ return { filename: null, lineno: null, colno: null };
179
+ }
180
+ const m = LOCATION_RE.exec(location);
181
+ if (m && m[1])
182
+ return { filename: m[1], lineno: toInt(m[2]), colno: toInt(m[3]) };
183
+ location = location.replace(/^address at /, "");
184
+ return { filename: location || null, lineno: null, colno: null };
185
+ }
186
+ function parseV8(lines) {
187
+ const frames = [];
188
+ for (const line of lines) {
189
+ const m = V8_LINE_RE.exec(line);
190
+ if (!m)
191
+ continue;
192
+ const body = m[1].trim();
193
+ const withFn = V8_WITH_FN_RE.exec(body);
194
+ if (withFn) {
195
+ frames.push(frame("javascript", { function: withFn[1], ...jsLocation(withFn[2]) }));
196
+ }
197
+ else {
198
+ // "at async file:1:2" is an anonymous async function: the location
199
+ // is what follows "async " (the platform parser mirrors this).
200
+ frames.push(frame("javascript", jsLocation(body.replace(/^async /, ""))));
201
+ }
202
+ if (frames.length >= MAX_FRAMES)
203
+ break;
204
+ }
205
+ return frames;
206
+ }
207
+ /** A Firefox/Safari location: something with :line(:col), or [native code]. */
208
+ function isGeckoLocation(location) {
209
+ return /:\d+(?::\d+)?$/.test(location) || location === "[native code]";
210
+ }
211
+ function parseGecko(lines) {
212
+ const frames = [];
213
+ for (const line of lines) {
214
+ const m = GECKO_LINE_RE.exec(line);
215
+ if (!m || !isGeckoLocation(m[2]))
216
+ continue;
217
+ // Firefox eval frames: "fn@url line 2 > eval:1:5" — keep the script.
218
+ const location = m[2].replace(/ line (\d+) > (?:eval|Function)(?::\d+:\d+)?$/, ":$1");
219
+ frames.push(frame("javascript", { function: m[1] || null, ...jsLocation(location) }));
220
+ if (frames.length >= MAX_FRAMES)
221
+ break;
222
+ }
223
+ return frames;
224
+ }
225
+ // ── Python ────────────────────────────────────────────────────────────────
226
+ const PY_FILE_RE = /^\s*File "(.+)", line (\d+)(?:, in (.+?))?\s*$/;
227
+ const PY_TRACEBACK_RE = /^\s*Traceback \(most recent call last\):\s*$/;
228
+ function parsePython(lines) {
229
+ // A chained exception prints one traceback per exception; the reported
230
+ // (final) exception's frames are the last block.
231
+ let start = 0;
232
+ for (let i = 0; i < lines.length; i++)
233
+ if (PY_TRACEBACK_RE.test(lines[i]))
234
+ start = i;
235
+ const outermostFirst = [];
236
+ for (const line of lines.slice(start)) {
237
+ const m = PY_FILE_RE.exec(line);
238
+ if (!m)
239
+ continue;
240
+ outermostFirst.push(frame("python", { function: m[3] ?? null, filename: m[1], lineno: toInt(m[2]) }));
241
+ }
242
+ return outermostFirst.reverse().slice(0, MAX_FRAMES);
243
+ }
244
+ // ── Go ────────────────────────────────────────────────────────────────────
245
+ const GO_LOCATION_RE = /^\s*(.+\.go):(\d+)(?:\s+\+0x[0-9a-f]+)?\s*$/;
246
+ const GO_GOROUTINE_RE = /\bgoroutine \d+ \[/;
247
+ /** "net/http.(*conn).serve" → module "net/http", function "(*conn).serve". */
248
+ function splitGoName(qualified) {
249
+ const slash = qualified.lastIndexOf("/", qualified.indexOf("(") === -1 ? qualified.length : qualified.indexOf("("));
250
+ const dot = qualified.indexOf(".", slash + 1);
251
+ if (dot <= 0)
252
+ return { module: null, function: qualified };
253
+ return { module: qualified.slice(0, dot), function: qualified.slice(dot + 1) };
254
+ }
255
+ function parseGo(lines) {
256
+ const frames = [];
257
+ let fn = "";
258
+ let goroutines = 0;
259
+ for (const line of lines) {
260
+ const trimmed = line.trim();
261
+ if (GO_GOROUTINE_RE.test(trimmed)) {
262
+ // Only the first goroutine is the one that failed.
263
+ if (++goroutines > 1 && frames.length > 0)
264
+ break;
265
+ fn = "";
266
+ continue;
267
+ }
268
+ const loc = GO_LOCATION_RE.exec(line);
269
+ if (loc && fn) {
270
+ const name = stripTrailingArgs(fn.replace(/^created by\s+/, "").replace(/\s+in goroutine \d+\s*$/, ""));
271
+ const { module, function: func } = splitGoName(name);
272
+ frames.push(frame("go", { function: func, module, filename: loc[1].trim(), lineno: toInt(loc[2]) }));
273
+ if (frames.length >= MAX_FRAMES)
274
+ break;
275
+ fn = "";
276
+ continue;
277
+ }
278
+ if (trimmed && !loc)
279
+ fn = trimmed;
280
+ }
281
+ return frames;
282
+ }
283
+ // ── JVM ───────────────────────────────────────────────────────────────────
284
+ function parseJvm(lines) {
285
+ const frames = [];
286
+ for (const line of lines) {
287
+ // The reported exception's frames come first; causes follow.
288
+ if (/^\s*(?:Caused by|Suppressed):/.test(line) && frames.length > 0)
289
+ break;
290
+ const m = /^\s*at\s+(.+?)\((.*)\)\s*$/.exec(line);
291
+ if (!m)
292
+ continue;
293
+ // "java.base/java.lang.Thread.run", "app//com.example.Foo.bar"
294
+ const qualified = m[1].trim().replace(/^.*\//, "");
295
+ const [module, method] = splitLastDot(qualified);
296
+ const location = m[2].trim();
297
+ let filename = null;
298
+ let lineno = null;
299
+ if (!/^(?:Native Method|Unknown Source)$/.test(location)) {
300
+ const loc = /^(.*?)(?::(\d+))?$/.exec(location);
301
+ filename = loc[1] || null;
302
+ lineno = toInt(loc[2]);
303
+ }
304
+ frames.push(frame("jvm", { function: method, module, filename, lineno }));
305
+ if (frames.length >= MAX_FRAMES)
306
+ break;
307
+ }
308
+ return frames;
309
+ }
310
+ // ── .NET ──────────────────────────────────────────────────────────────────
311
+ function parseDotnet(lines) {
312
+ let frames = [];
313
+ for (const line of lines) {
314
+ // "Outer ---> Inner\n at inner…\n --- End of inner exception stack
315
+ // trace ---\n at outer…": the reported (outer) frames come last.
316
+ if (/---\s*End of inner exception stack trace\s*---/i.test(line)) {
317
+ frames = [];
318
+ continue;
319
+ }
320
+ const m = /^\s*at\s+(.+?)(?:\s+in\s+(.+?):line\s+(\d+))?\s*$/.exec(line);
321
+ if (!m)
322
+ continue;
323
+ const [module, method] = splitLastDot(stripTrailingArgs(m[1]));
324
+ frames.push(frame("dotnet", { function: method, module, filename: m[2] ?? null, lineno: toInt(m[3]) }));
325
+ }
326
+ return frames.slice(0, MAX_FRAMES);
327
+ }
328
+ // ── Rust ──────────────────────────────────────────────────────────────────
329
+ function parseRust(lines) {
330
+ const frames = [];
331
+ for (let i = 0; i < lines.length && frames.length < MAX_FRAMES; i++) {
332
+ const m = /^\s*\d+:\s+(?:0x[0-9a-f]+\s+-\s+)?(.+?)\s*$/.exec(lines[i]);
333
+ if (!m)
334
+ continue;
335
+ const fn = m[1].replace(/::h[0-9a-f]{6,}$/, "").trim();
336
+ if (!fn)
337
+ continue;
338
+ const at = /^\s*at\s+(.+?):(\d+)(?::(\d+))?\s*$/.exec(lines[i + 1] ?? "");
339
+ if (at)
340
+ i++;
341
+ frames.push(frame("rust", {
342
+ function: fn,
343
+ filename: at?.[1] ?? null,
344
+ lineno: toInt(at?.[2]),
345
+ colno: toInt(at?.[3]),
346
+ }));
347
+ }
348
+ return frames;
349
+ }
350
+ // ── Ruby / PHP (best effort) ──────────────────────────────────────────────
351
+ // A .NET frame without source locations: "at Ns.Type.Method(Type arg)".
352
+ // V8 always puts a space before "(" or has no parentheses at all.
353
+ const DOTNET_BARE_RE = /^\s*at\s+[\w.`<>\[\],+|]+\([\w.`<>\[\],&*\s]*\)\s*$/;
354
+ const RUBY_RE = /^\s*(?:from\s+)?(.+?):(\d+)(?::in\s+[`'](?:[\w:#.]+[#.])?(.+?)')?\s*$/;
355
+ const PHP_RE = /^\s*#\d+\s+(.+?)\((\d+)\):\s+(.+?)\s*$/;
356
+ function parseRuby(lines) {
357
+ const frames = [];
358
+ for (const line of lines) {
359
+ const m = RUBY_RE.exec(line);
360
+ if (!m || !/\.(?:rb|erb|rake)$/.test(m[1]))
361
+ continue;
362
+ frames.push(frame("ruby", { function: m[3] ?? null, filename: m[1], lineno: toInt(m[2]) }));
363
+ if (frames.length >= MAX_FRAMES)
364
+ break;
365
+ }
366
+ return frames;
367
+ }
368
+ function parsePhp(lines) {
369
+ const frames = [];
370
+ for (const line of lines) {
371
+ const m = PHP_RE.exec(line);
372
+ if (!m)
373
+ continue;
374
+ frames.push(frame("php", { function: stripTrailingArgs(m[3]), filename: m[1], lineno: toInt(m[2]) }));
375
+ if (frames.length >= MAX_FRAMES)
376
+ break;
377
+ }
378
+ return frames;
379
+ }
380
+ // ── Entry point ───────────────────────────────────────────────────────────
381
+ /** The language of a raw stack, or null when it has no recognisable frames. */
382
+ export function detectFrameLanguage(lines) {
383
+ switch (detectStackLanguage(lines)) {
384
+ case "go":
385
+ return "go";
386
+ case "rust":
387
+ return "rust";
388
+ case "jvm":
389
+ return "jvm";
390
+ case "dotnet":
391
+ return "dotnet";
392
+ default:
393
+ }
394
+ if (lines.some((line) => PY_FILE_RE.test(line)))
395
+ return "python";
396
+ if (lines.some((line) => DOTNET_BARE_RE.test(line)) &&
397
+ !lines.some((line) => /:\d+(?::\d+)?\)?\s*$/.test(line))) {
398
+ return "dotnet";
399
+ }
400
+ if (lines.some((line) => V8_LINE_RE.test(line)))
401
+ return "javascript";
402
+ if (lines.some((line) => {
403
+ const m = GECKO_LINE_RE.exec(line);
404
+ return Boolean(m && isGeckoLocation(m[2]));
405
+ }))
406
+ return "javascript";
407
+ if (lines.some((line) => PHP_RE.test(line)))
408
+ return "php";
409
+ if (lines.some((line) => {
410
+ const m = RUBY_RE.exec(line);
411
+ return Boolean(m && /\.(?:rb|erb|rake)$/.test(m[1]));
412
+ }))
413
+ return "ruby";
414
+ return null;
415
+ }
416
+ /** Parse a raw stack into frames, innermost first (≤ MAX_FRAMES). */
417
+ export function parseStackFrames(stack) {
418
+ if (!stack)
419
+ return [];
420
+ const lines = stack.split(/\r?\n/);
421
+ switch (detectFrameLanguage(lines)) {
422
+ case "go":
423
+ return parseGo(lines);
424
+ case "rust":
425
+ return parseRust(lines);
426
+ case "jvm":
427
+ return parseJvm(lines);
428
+ case "dotnet":
429
+ return parseDotnet(lines);
430
+ case "python":
431
+ return parsePython(lines);
432
+ case "javascript":
433
+ return lines.some((line) => V8_LINE_RE.test(line)) ? parseV8(lines) : parseGecko(lines);
434
+ case "php":
435
+ return parsePhp(lines);
436
+ case "ruby":
437
+ return parseRuby(lines);
438
+ default:
439
+ return [];
440
+ }
441
+ }
@@ -0,0 +1,79 @@
1
+ import { TraceMap } from "@jridgewell/trace-mapping";
2
+ import type { SourceMapKey } from "./source-maps-lookup.js";
3
+ import type { StackFrame } from "./types.js";
4
+ /**
5
+ * Symbolication at ingest: minified browser frames are resolved through the
6
+ * source maps uploaded for the release (`/v1/sourcemaps`) BEFORE they are
7
+ * fingerprinted, so `frames-v3` hashes `checkout.tsx`, not
8
+ * `index-5f3a9c1e.js` — and an issue survives deploys and preview domains.
9
+ *
10
+ * - Function names come from the map's `names`: the name mapped at the
11
+ * CALLER's position is the callee's original name (the call site names
12
+ * what it calls). Without one, a resolved frame keeps its parsed name
13
+ * only when that is not an obviously mangled one (≤ 2 characters), since
14
+ * a mangled name changes on every build.
15
+ * - Sources the map lists in `ignoreList` / `x_google_ignoreList` are not
16
+ * in-app; a resolved path under node_modules/ etc. is not in-app either.
17
+ * - Context lines only when the map carries `sourcesContent` (the upload
18
+ * strips it today, so there are none for now).
19
+ * - Never fails ingest: any error leaves the frame as parsed.
20
+ *
21
+ * Parsed maps are cached per (org, repository, release, file) — or per debug
22
+ * id — in a byte-bounded LRU, so a busy issue never refetches its map.
23
+ */
24
+ export type MapFetcher = (key: SourceMapKey) => Promise<string | null>;
25
+ export interface SymbolicationScope {
26
+ orgId: string;
27
+ repositoryId: string;
28
+ release: string | null;
29
+ /** Script URL → debug id (EI-13), when the browser sent them. */
30
+ debugIds?: Record<string, string> | null;
31
+ }
32
+ /** Path-only form of a script URL — the key uploads are stored under. */
33
+ export declare function scriptPath(filename: string): string | null;
34
+ export interface SourceMapCacheOptions {
35
+ /** Budget for the estimated in-memory size of parsed maps (default 64 MiB). */
36
+ maxBytes?: number;
37
+ maxEntries?: number;
38
+ /** How long a found map is reused (default 1 h). */
39
+ hitTtlMs?: number;
40
+ /** How long "no map uploaded" is remembered (default 5 min). */
41
+ missTtlMs?: number;
42
+ /** How long a failed lookup is remembered (default 30 s). */
43
+ errorTtlMs?: number;
44
+ now?: () => number;
45
+ }
46
+ export declare class SourceMapCache {
47
+ private readonly entries;
48
+ private readonly inFlight;
49
+ private weight;
50
+ private readonly maxBytes;
51
+ private readonly maxEntries;
52
+ private readonly hitTtlMs;
53
+ private readonly missTtlMs;
54
+ private readonly errorTtlMs;
55
+ private readonly now;
56
+ /** Fetches made (cache misses) — for tests and /healthz-style debugging. */
57
+ fetches: number;
58
+ constructor(options?: SourceMapCacheOptions);
59
+ static keyOf(key: SourceMapKey): string;
60
+ get size(): number;
61
+ /** The parsed map for `key` (null when none), fetching at most once at a time. */
62
+ get(key: SourceMapKey, fetchMap: MapFetcher): Promise<TraceMap | null>;
63
+ private load;
64
+ private store;
65
+ private remove;
66
+ }
67
+ export declare class Symbolicator {
68
+ private readonly fetchMap;
69
+ readonly cache: SourceMapCache;
70
+ constructor(fetchMap: MapFetcher, cache?: SourceMapCache);
71
+ /**
72
+ * Resolve the JS frames of one stack. Returns a new array; frames that
73
+ * cannot be resolved (no map, no position, non-JS) are returned as given.
74
+ */
75
+ symbolicate(frames: StackFrame[], scope: SymbolicationScope): Promise<StackFrame[]>;
76
+ private debugIdFor;
77
+ private keyFor;
78
+ private resolveAll;
79
+ }