@descryy/runtime-adapter-typescript 0.0.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.
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/node-source-location-resolver.d.ts +29 -0
- package/dist/node-source-location-resolver.d.ts.map +1 -0
- package/dist/node-source-location-resolver.js +159 -0
- package/dist/node-source-location-resolver.js.map +1 -0
- package/dist/typescript-runtime-adapter.d.ts +14 -0
- package/dist/typescript-runtime-adapter.d.ts.map +1 -0
- package/dist/typescript-runtime-adapter.js +32 -0
- package/dist/typescript-runtime-adapter.js.map +1 -0
- package/dist/v8-frame-parsing.d.ts +78 -0
- package/dist/v8-frame-parsing.d.ts.map +1 -0
- package/dist/v8-frame-parsing.js +130 -0
- package/dist/v8-frame-parsing.js.map +1 -0
- package/dist/v8-stack-trace-parser.d.ts +22 -0
- package/dist/v8-stack-trace-parser.d.ts.map +1 -0
- package/dist/v8-stack-trace-parser.js +83 -0
- package/dist/v8-stack-trace-parser.js.map +1 -0
- package/package.json +34 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { RawStackFrame, ParsedLocation } from "./v8-frame-parsing.ts";
|
|
2
|
+
export { isV8HeaderLine, isV8FrameLine, isV8NamedFrameLine, parseV8Frame, parseLocation, createV8LineClassifier } from "./v8-frame-parsing.ts";
|
|
3
|
+
export { createNodeSourceLocationResolver } from "./node-source-location-resolver.ts";
|
|
4
|
+
export { createV8StackTraceParser } from "./v8-stack-trace-parser.ts";
|
|
5
|
+
export type { TypeScriptRuntimeAdapterOptions } from "./typescript-runtime-adapter.ts";
|
|
6
|
+
export { createTypeScriptRuntimeAdapter } from "./typescript-runtime-adapter.ts";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,kBAAkB,EAAE,YAAY,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/I,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAC;AACtF,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,YAAY,EAAE,+BAA+B,EAAE,MAAM,iCAAiC,CAAC;AACvF,OAAO,EAAE,8BAA8B,EAAE,MAAM,iCAAiC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { isV8HeaderLine, isV8FrameLine, isV8NamedFrameLine, parseV8Frame, parseLocation, createV8LineClassifier } from "./v8-frame-parsing.js";
|
|
2
|
+
export { createNodeSourceLocationResolver } from "./node-source-location-resolver.js";
|
|
3
|
+
export { createV8StackTraceParser } from "./v8-stack-trace-parser.js";
|
|
4
|
+
export { createTypeScriptRuntimeAdapter } from "./typescript-runtime-adapter.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,kBAAkB,EAAE,YAAY,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/I,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAC;AACtF,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,OAAO,EAAE,8BAA8B,EAAE,MAAM,iCAAiC,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node/TypeScript `SourceLocationResolver` — resolves a V8-captured
|
|
3
|
+
* compiled-code location back to authored source through a `//# sourceMappingURL`
|
|
4
|
+
* source map, when one exists, and classifies `SourceLocation.reliability`
|
|
5
|
+
* (RT-003) honestly rather than defaulting.
|
|
6
|
+
*
|
|
7
|
+
* - No `sourceMappingURL` reference at all → `self-contained`. Nothing was
|
|
8
|
+
* lost — the V8-captured compiled location IS the accurate answer, same
|
|
9
|
+
* category as a Python traceback's self-captured frame. This is NOT
|
|
10
|
+
* `side-artifact-missing`, which means "we expected a map and couldn't
|
|
11
|
+
* get it."
|
|
12
|
+
* - A reference exists but the map file can't be found or parsed, or has
|
|
13
|
+
* no entry for this exact line:column → `side-artifact-missing`.
|
|
14
|
+
* - A reference exists, the map resolves this position, and either
|
|
15
|
+
* `sourcesContent` is absent for that source or the on-disk file matches
|
|
16
|
+
* it → `side-artifact-resolved`.
|
|
17
|
+
* - A reference exists, the map resolves this position, `sourcesContent`
|
|
18
|
+
* is present for that source, AND the on-disk file's content differs
|
|
19
|
+
* → `side-artifact-stale`. This is the one value nothing else in this
|
|
20
|
+
* build exercises without this check actually running — comparing
|
|
21
|
+
* `sourcesContent` against disk rather than punting on staleness
|
|
22
|
+
* detection entirely.
|
|
23
|
+
* - A location with no file on disk at all (`node:events`, a stdlib
|
|
24
|
+
* internal) → `self-contained`. There was never an artifact to lose;
|
|
25
|
+
* the captured location is the accurate answer.
|
|
26
|
+
*/
|
|
27
|
+
import type { SourceLocationResolver } from "@descryy/runtime-backend-observation";
|
|
28
|
+
export declare function createNodeSourceLocationResolver(): SourceLocationResolver;
|
|
29
|
+
//# sourceMappingURL=node-source-location-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-source-location-resolver.d.ts","sourceRoot":"","sources":["../src/node-source-location-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAQH,OAAO,KAAK,EAAoB,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AA6IrG,wBAAgB,gCAAgC,IAAI,sBAAsB,CAMzE"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node/TypeScript `SourceLocationResolver` — resolves a V8-captured
|
|
3
|
+
* compiled-code location back to authored source through a `//# sourceMappingURL`
|
|
4
|
+
* source map, when one exists, and classifies `SourceLocation.reliability`
|
|
5
|
+
* (RT-003) honestly rather than defaulting.
|
|
6
|
+
*
|
|
7
|
+
* - No `sourceMappingURL` reference at all → `self-contained`. Nothing was
|
|
8
|
+
* lost — the V8-captured compiled location IS the accurate answer, same
|
|
9
|
+
* category as a Python traceback's self-captured frame. This is NOT
|
|
10
|
+
* `side-artifact-missing`, which means "we expected a map and couldn't
|
|
11
|
+
* get it."
|
|
12
|
+
* - A reference exists but the map file can't be found or parsed, or has
|
|
13
|
+
* no entry for this exact line:column → `side-artifact-missing`.
|
|
14
|
+
* - A reference exists, the map resolves this position, and either
|
|
15
|
+
* `sourcesContent` is absent for that source or the on-disk file matches
|
|
16
|
+
* it → `side-artifact-resolved`.
|
|
17
|
+
* - A reference exists, the map resolves this position, `sourcesContent`
|
|
18
|
+
* is present for that source, AND the on-disk file's content differs
|
|
19
|
+
* → `side-artifact-stale`. This is the one value nothing else in this
|
|
20
|
+
* build exercises without this check actually running — comparing
|
|
21
|
+
* `sourcesContent` against disk rather than punting on staleness
|
|
22
|
+
* detection entirely.
|
|
23
|
+
* - A location with no file on disk at all (`node:events`, a stdlib
|
|
24
|
+
* internal) → `self-contained`. There was never an artifact to lose;
|
|
25
|
+
* the captured location is the accurate answer.
|
|
26
|
+
*/
|
|
27
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
28
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
29
|
+
import { dirname, resolve as resolvePath, isAbsolute } from "node:path";
|
|
30
|
+
import { TraceMap, originalPositionFor, sourceContentFor } from "@jridgewell/trace-mapping";
|
|
31
|
+
const SOURCE_MAPPING_URL_PATTERN = /\/\/[#@]\s*sourceMappingURL=(\S+)/g;
|
|
32
|
+
function normalizeCompiledFile(rawFile) {
|
|
33
|
+
return rawFile.startsWith("file://") ? fileURLToPath(rawFile) : rawFile;
|
|
34
|
+
}
|
|
35
|
+
/** Last match wins, per convention — and a large bundle could contain earlier false-positive-shaped text. */
|
|
36
|
+
function findSourceMappingUrl(compiledText) {
|
|
37
|
+
let last;
|
|
38
|
+
for (const match of compiledText.matchAll(SOURCE_MAPPING_URL_PATTERN)) {
|
|
39
|
+
last = match[1];
|
|
40
|
+
}
|
|
41
|
+
return last;
|
|
42
|
+
}
|
|
43
|
+
function loadTraceMap(compiledFilePath, mappingUrl) {
|
|
44
|
+
if (mappingUrl.startsWith("data:")) {
|
|
45
|
+
const marker = "base64,";
|
|
46
|
+
const idx = mappingUrl.indexOf(marker);
|
|
47
|
+
if (idx === -1)
|
|
48
|
+
return undefined;
|
|
49
|
+
try {
|
|
50
|
+
const json = Buffer.from(mappingUrl.slice(idx + marker.length), "base64").toString("utf8");
|
|
51
|
+
return { map: new TraceMap(json, pathToFileURL(compiledFilePath).href) };
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const mapPath = isAbsolute(mappingUrl) ? mappingUrl : resolvePath(dirname(compiledFilePath), mappingUrl);
|
|
58
|
+
if (!existsSync(mapPath))
|
|
59
|
+
return undefined;
|
|
60
|
+
try {
|
|
61
|
+
const json = readFileSync(mapPath, "utf8");
|
|
62
|
+
return { map: new TraceMap(json, pathToFileURL(mapPath).href) };
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function normalizeLineEndings(text) {
|
|
69
|
+
return text.replace(/\r\n/g, "\n");
|
|
70
|
+
}
|
|
71
|
+
function selfContained(frame, file) {
|
|
72
|
+
return {
|
|
73
|
+
file,
|
|
74
|
+
line: frame.line,
|
|
75
|
+
column: frame.column,
|
|
76
|
+
functionName: frame.functionName,
|
|
77
|
+
reliability: "self-contained",
|
|
78
|
+
resolvedVia: null,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function sideArtifactMissing(frame, file, mappingUrl) {
|
|
82
|
+
return {
|
|
83
|
+
file,
|
|
84
|
+
line: frame.line,
|
|
85
|
+
column: frame.column,
|
|
86
|
+
functionName: frame.functionName,
|
|
87
|
+
reliability: "side-artifact-missing",
|
|
88
|
+
resolvedVia: mappingUrl,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function resolveSync(frame) {
|
|
92
|
+
// V8 always names a file, so this is unreachable from the V8 parser --
|
|
93
|
+
// the type admits null because a JVM frame can omit it. Handled rather
|
|
94
|
+
// than asserted away, since an assertion here would become a crash the
|
|
95
|
+
// moment some other caller reuses this resolver.
|
|
96
|
+
if (frame.line === null || frame.file === null) {
|
|
97
|
+
return selfContained(frame, frame.file);
|
|
98
|
+
}
|
|
99
|
+
const file = normalizeCompiledFile(frame.file);
|
|
100
|
+
if (!existsSync(file)) {
|
|
101
|
+
// e.g. "node:events" -- a stdlib internal with no file on disk at all.
|
|
102
|
+
// Nothing was ever there to lose; the captured location is accurate.
|
|
103
|
+
return selfContained(frame, file);
|
|
104
|
+
}
|
|
105
|
+
const compiledText = readFileSync(file, "utf8");
|
|
106
|
+
const mappingUrl = findSourceMappingUrl(compiledText);
|
|
107
|
+
if (mappingUrl === undefined) {
|
|
108
|
+
return selfContained(frame, file);
|
|
109
|
+
}
|
|
110
|
+
if (frame.column === null) {
|
|
111
|
+
// A source map can't be walked to a specific position without a
|
|
112
|
+
// column — trying anyway would silently substitute a guessed column
|
|
113
|
+
// and report it as resolved. Report what was actually captured
|
|
114
|
+
// instead of a fabricated mapping.
|
|
115
|
+
return selfContained(frame, file);
|
|
116
|
+
}
|
|
117
|
+
const loaded = loadTraceMap(file, mappingUrl);
|
|
118
|
+
if (loaded === undefined) {
|
|
119
|
+
return sideArtifactMissing(frame, file, mappingUrl);
|
|
120
|
+
}
|
|
121
|
+
// Stack frame line is 1-based, matching originalPositionFor's expectation.
|
|
122
|
+
// Column is 1-based in V8 stacks but 0-based in trace-mapping's needle.
|
|
123
|
+
const original = originalPositionFor(loaded.map, { line: frame.line, column: Math.max(0, frame.column - 1) });
|
|
124
|
+
if (original.source === null || original.line === null || original.column === null) {
|
|
125
|
+
return sideArtifactMissing(frame, file, mappingUrl);
|
|
126
|
+
}
|
|
127
|
+
const sourceIndex = loaded.map.sources.indexOf(original.source);
|
|
128
|
+
const embeddedContent = sourceContentFor(loaded.map, original.source);
|
|
129
|
+
const resolvedSourcePath = sourceIndex >= 0 ? (loaded.map.resolvedSources[sourceIndex] ?? original.source) : original.source;
|
|
130
|
+
const onDiskPath = resolvedSourcePath.startsWith("file://") ? fileURLToPath(resolvedSourcePath) : resolvedSourcePath;
|
|
131
|
+
let reliability = "side-artifact-resolved";
|
|
132
|
+
if (embeddedContent !== null && existsSync(onDiskPath)) {
|
|
133
|
+
const onDisk = readFileSync(onDiskPath, "utf8");
|
|
134
|
+
if (normalizeLineEndings(onDisk) !== normalizeLineEndings(embeddedContent)) {
|
|
135
|
+
reliability = "side-artifact-stale";
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// embeddedContent === null, or the on-disk file is unreadable: nothing to
|
|
139
|
+
// compare against, so freshness is simply unverifiable — resolved stays
|
|
140
|
+
// the honest answer rather than downgrading on the absence of a check.
|
|
141
|
+
return {
|
|
142
|
+
file: onDiskPath,
|
|
143
|
+
line: original.line,
|
|
144
|
+
// trace-mapping's column is 0-based; SourceLocation.column is reported
|
|
145
|
+
// 1-based to match how every other frame in this system is stated.
|
|
146
|
+
column: original.column + 1,
|
|
147
|
+
functionName: original.name ?? frame.functionName,
|
|
148
|
+
reliability,
|
|
149
|
+
resolvedVia: mappingUrl,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
export function createNodeSourceLocationResolver() {
|
|
153
|
+
return {
|
|
154
|
+
resolve(frame) {
|
|
155
|
+
return Promise.resolve(resolveSync(frame));
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=node-source-location-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-source-location-resolver.js","sourceRoot":"","sources":["../src/node-source-location-resolver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The concrete `RuntimeAdapter` for TypeScript/Node — V1's only language
|
|
3
|
+
* (per the explicit vertical-slice scope limit: no second language until
|
|
4
|
+
* this one closes end to end). Wires this package's V8 stack-trace parser,
|
|
5
|
+
* Node source-map resolver, and V8 line classifier into the language-
|
|
6
|
+
* neutral seam `@descryy/runtime-backend-observation` defines.
|
|
7
|
+
*/
|
|
8
|
+
import type { RuntimeAdapter } from "@descryy/runtime-backend-observation";
|
|
9
|
+
export interface TypeScriptRuntimeAdapterOptions {
|
|
10
|
+
/** Attributed on every emitted Evidence's `service` field (plan §16.7). Omit when not applicable. */
|
|
11
|
+
readonly service?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function createTypeScriptRuntimeAdapter(options?: TypeScriptRuntimeAdapterOptions): RuntimeAdapter;
|
|
14
|
+
//# sourceMappingURL=typescript-runtime-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typescript-runtime-adapter.d.ts","sourceRoot":"","sources":["../src/typescript-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,sCAAsC,CAAC;AAQhG,MAAM,WAAW,+BAA+B;IAC9C,qGAAqG;IACrG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,8BAA8B,CAAC,OAAO,GAAE,+BAAoC,GAAG,cAAc,CAuB5G"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The concrete `RuntimeAdapter` for TypeScript/Node — V1's only language
|
|
3
|
+
* (per the explicit vertical-slice scope limit: no second language until
|
|
4
|
+
* this one closes end to end). Wires this package's V8 stack-trace parser,
|
|
5
|
+
* Node source-map resolver, and V8 line classifier into the language-
|
|
6
|
+
* neutral seam `@descryy/runtime-backend-observation` defines.
|
|
7
|
+
*/
|
|
8
|
+
import { createLogCollector } from "@descryy/runtime-backend-observation";
|
|
9
|
+
import { createV8LineClassifier } from "./v8-frame-parsing.js";
|
|
10
|
+
import { createNodeSourceLocationResolver } from "./node-source-location-resolver.js";
|
|
11
|
+
import { createV8StackTraceParser } from "./v8-stack-trace-parser.js";
|
|
12
|
+
export function createTypeScriptRuntimeAdapter(options = {}) {
|
|
13
|
+
const sourceLocationResolver = createNodeSourceLocationResolver();
|
|
14
|
+
const stackTraceParser = createV8StackTraceParser(sourceLocationResolver);
|
|
15
|
+
function createBackendCollectors(sources) {
|
|
16
|
+
return sources.map((source) => createLogCollector({
|
|
17
|
+
source,
|
|
18
|
+
// One classifier per source, never one shared: it is stateful now,
|
|
19
|
+
// and a shared instance would let one process's trace arm another's.
|
|
20
|
+
classifier: createV8LineClassifier(),
|
|
21
|
+
stackTraceParser,
|
|
22
|
+
...(options.service !== undefined ? { service: options.service } : {}),
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
language: "typescript",
|
|
27
|
+
stackTraceParser,
|
|
28
|
+
sourceLocationResolver,
|
|
29
|
+
createBackendCollectors,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=typescript-runtime-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typescript-runtime-adapter.js","sourceRoot":"","sources":["../src/typescript-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAE1E,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAC;AACtF,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAOtE,MAAM,UAAU,8BAA8B,CAAC,UAA2C,EAAE;IAC1F,MAAM,sBAAsB,GAAG,gCAAgC,EAAE,CAAC;IAClE,MAAM,gBAAgB,GAAG,wBAAwB,CAAC,sBAAsB,CAAC,CAAC;IAE1E,SAAS,uBAAuB,CAAC,OAAuC;QACtE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAC5B,kBAAkB,CAAC;YACjB,MAAM;YACN,mEAAmE;YACnE,qEAAqE;YACrE,UAAU,EAAE,sBAAsB,EAAE;YACpC,gBAAgB;YAChB,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvE,CAAC,CACH,CAAC;IACJ,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,YAAY;QACtB,gBAAgB;QAChB,sBAAsB;QACtB,uBAAuB;KACxB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V8's `Error.stack` line shapes — the `LineClassifier` for
|
|
3
|
+
* `ExceptionBlockDetector` plus the raw-text-to-frame-parts extraction that
|
|
4
|
+
* feeds `SourceLocationResolver`.
|
|
5
|
+
*
|
|
6
|
+
* The header pattern is deliberately NOT anchored to the start of the
|
|
7
|
+
* line. Measured against `broken-invoices-backend`'s own real output
|
|
8
|
+
* (`console.error` with a custom prefix): `"[broken-invoices] DELETE
|
|
9
|
+
* /api/invoices/3 failed: TypeError: Cannot read properties of null
|
|
10
|
+
* (reading 'toUpperCase')"` — the error type sits mid-line behind
|
|
11
|
+
* application context, which a start-anchored pattern would have missed
|
|
12
|
+
* entirely, misclassifying a genuine exception as an unattributed
|
|
13
|
+
* stack-trace block. The block-detector's own "header immediately
|
|
14
|
+
* followed by a frame line" rule keeps this safe from false positives: a
|
|
15
|
+
* log line that merely *mentions* an Error-shaped word with nothing
|
|
16
|
+
* frame-shaped after it never becomes an EXCEPTION.
|
|
17
|
+
*/
|
|
18
|
+
export interface RawStackFrame {
|
|
19
|
+
readonly functionName: string | null;
|
|
20
|
+
/** Unparsed location text — `file:///abs/path.js:37:44`, `node:events:519:28`, or `<anonymous>` (no line:col, dropped by the caller rather than fabricated). */
|
|
21
|
+
readonly location: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ParsedLocation {
|
|
24
|
+
readonly file: string;
|
|
25
|
+
readonly line: number;
|
|
26
|
+
readonly column: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function isV8HeaderLine(line: string): boolean;
|
|
29
|
+
export declare function isV8FrameLine(line: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* The `at fn (loc)` form specifically — a shape no other runtime in this
|
|
32
|
+
* repo produces, and therefore the anchor `createV8StackTraceParser` uses
|
|
33
|
+
* to decide that text is V8's at all. See its header for why a bare frame
|
|
34
|
+
* alone is not enough to make that call.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isV8NamedFrameLine(line: string): boolean;
|
|
37
|
+
export declare function parseV8Frame(line: string): RawStackFrame | null;
|
|
38
|
+
/** `null` for a frame with no parseable line:column (e.g. `<anonymous>`) — never fabricated. */
|
|
39
|
+
export declare function parseLocation(location: string): ParsedLocation | null;
|
|
40
|
+
/**
|
|
41
|
+
* A **stateful** `LineClassifier`, for RT-035's reason and RT-047's
|
|
42
|
+
* evidence.
|
|
43
|
+
*
|
|
44
|
+
* V8 was the last adapter still handing `ExceptionBlockDetector` a bare
|
|
45
|
+
* pair of stateless predicates — `{ isHeader: isV8HeaderLine, isFrame:
|
|
46
|
+
* isV8FrameLine }` — and it was wrong in exactly the way every other
|
|
47
|
+
* language's had already been found to be.
|
|
48
|
+
*
|
|
49
|
+
* **`FRAME_BARE` matches a Rust backtrace's location lines.** ` at
|
|
50
|
+
* /rustc/<hash>/library/std/src/panicking.rs:698:5` is `at` followed by a
|
|
51
|
+
* single parenthesis-free token, which is also precisely the shape of a
|
|
52
|
+
* bare V8 frame. RT-039 found this at the *parser* layer and fixed it there
|
|
53
|
+
* by requiring the whole text to contain a V8 header or a named `at fn
|
|
54
|
+
* (loc)` frame — and then reported that the matrix confirmed the classifier
|
|
55
|
+
* layer too. It had not: the classifier half of that matrix was inspecting
|
|
56
|
+
* an empty array (see `false-accept-matrix.test.ts`). With the matrix
|
|
57
|
+
* actually running, the V8 classifier opens **nine** blocks on one Rust
|
|
58
|
+
* backtrace.
|
|
59
|
+
*
|
|
60
|
+
* The rule here is the parser's anchor applied line by line, which is what
|
|
61
|
+
* a classifier can do that a set of predicates cannot: a **named** frame is
|
|
62
|
+
* always block content and arms the classifier; a **bare** frame is block
|
|
63
|
+
* content only once armed. A header arms it too.
|
|
64
|
+
*
|
|
65
|
+
* **The cost is the same one RT-039 already took and is restated rather
|
|
66
|
+
* than rediscovered:** a V8 trace consisting solely of bare frames does not
|
|
67
|
+
* open a block. Measured against four real V8 traces in RT-039, a bare
|
|
68
|
+
* frame occurs only where V8 has no function name, and Node's own internal
|
|
69
|
+
* frames — which follow every such throw — are all named, so the shape is
|
|
70
|
+
* rare. A disclosed recall loss, taken in the direction this project always
|
|
71
|
+
* takes it, over nine fabricated frames pointing into another language's
|
|
72
|
+
* standard library.
|
|
73
|
+
*/
|
|
74
|
+
export declare function createV8LineClassifier(): {
|
|
75
|
+
isHeader(line: string): boolean;
|
|
76
|
+
isFrame(line: string): boolean;
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=v8-frame-parsing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v8-frame-parsing.d.ts","sourceRoot":"","sources":["../src/v8-frame-parsing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,gKAAgK;IAChK,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAyBD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAU/D;AAED,gGAAgG;AAChG,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAQrE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,sBAAsB,IAAI;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAChC,CAoBA"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V8's `Error.stack` line shapes — the `LineClassifier` for
|
|
3
|
+
* `ExceptionBlockDetector` plus the raw-text-to-frame-parts extraction that
|
|
4
|
+
* feeds `SourceLocationResolver`.
|
|
5
|
+
*
|
|
6
|
+
* The header pattern is deliberately NOT anchored to the start of the
|
|
7
|
+
* line. Measured against `broken-invoices-backend`'s own real output
|
|
8
|
+
* (`console.error` with a custom prefix): `"[broken-invoices] DELETE
|
|
9
|
+
* /api/invoices/3 failed: TypeError: Cannot read properties of null
|
|
10
|
+
* (reading 'toUpperCase')"` — the error type sits mid-line behind
|
|
11
|
+
* application context, which a start-anchored pattern would have missed
|
|
12
|
+
* entirely, misclassifying a genuine exception as an unattributed
|
|
13
|
+
* stack-trace block. The block-detector's own "header immediately
|
|
14
|
+
* followed by a frame line" rule keeps this safe from false positives: a
|
|
15
|
+
* log line that merely *mentions* an Error-shaped word with nothing
|
|
16
|
+
* frame-shaped after it never becomes an EXCEPTION.
|
|
17
|
+
*/
|
|
18
|
+
const HEADER_PATTERN = /\b(?<type>(?:[A-Z][\w$]*)?Error)(?::\s*(?<message>.*))?$/;
|
|
19
|
+
const FRAME_WITH_PARENS = /^\s*at\s+(?:new\s+)?(?<fn>.+?)\s+\((?<loc>[^()]+)\)\s*$/;
|
|
20
|
+
/**
|
|
21
|
+
* A location-only frame: `at file:///app/x.js:1:1`, `at <anonymous>`,
|
|
22
|
+
* `at node:internal/main/run_main_module:23:47`.
|
|
23
|
+
*
|
|
24
|
+
* **Parentheses are excluded deliberately, and the JVM adapter's
|
|
25
|
+
* cross-language control is what found this.** Without the exclusion this
|
|
26
|
+
* pattern matched `at com.example.Thrower.handler(Thrower.java:12)` — a JVM
|
|
27
|
+
* frame is `at` followed by a single non-whitespace run, exactly like a
|
|
28
|
+
* bare V8 frame. A real bare V8 frame never contains parentheses: once
|
|
29
|
+
* there is a function name, V8 emits the `fn (loc)` form instead. So the
|
|
30
|
+
* exclusion costs no genuine V8 frame and removes the whole false-accept.
|
|
31
|
+
*
|
|
32
|
+
* The parser was already safe here — `parseLocation` requires a trailing
|
|
33
|
+
* `:line:column` and refuses the JVM shape — but the *classifier* is what
|
|
34
|
+
* `ExceptionBlockDetector` uses to decide whether a line belongs to a
|
|
35
|
+
* block at all, so accepting a foreign runtime's frames there is enough for
|
|
36
|
+
* a mis-wired adapter to half-work rather than fail.
|
|
37
|
+
*/
|
|
38
|
+
const FRAME_BARE = /^\s*at\s+(?<loc>[^\s()]+)\s*$/;
|
|
39
|
+
const LOCATION_WITH_LINE_COL = /^(?<file>.+):(?<line>\d+):(?<col>\d+)$/;
|
|
40
|
+
export function isV8HeaderLine(line) {
|
|
41
|
+
return HEADER_PATTERN.test(line);
|
|
42
|
+
}
|
|
43
|
+
export function isV8FrameLine(line) {
|
|
44
|
+
return FRAME_WITH_PARENS.test(line) || FRAME_BARE.test(line);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The `at fn (loc)` form specifically — a shape no other runtime in this
|
|
48
|
+
* repo produces, and therefore the anchor `createV8StackTraceParser` uses
|
|
49
|
+
* to decide that text is V8's at all. See its header for why a bare frame
|
|
50
|
+
* alone is not enough to make that call.
|
|
51
|
+
*/
|
|
52
|
+
export function isV8NamedFrameLine(line) {
|
|
53
|
+
return FRAME_WITH_PARENS.test(line);
|
|
54
|
+
}
|
|
55
|
+
export function parseV8Frame(line) {
|
|
56
|
+
const withParens = FRAME_WITH_PARENS.exec(line);
|
|
57
|
+
if (withParens?.groups) {
|
|
58
|
+
return { functionName: withParens.groups["fn"] ?? null, location: withParens.groups["loc"] ?? "" };
|
|
59
|
+
}
|
|
60
|
+
const bare = FRAME_BARE.exec(line);
|
|
61
|
+
if (bare?.groups) {
|
|
62
|
+
return { functionName: null, location: bare.groups["loc"] ?? "" };
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/** `null` for a frame with no parseable line:column (e.g. `<anonymous>`) — never fabricated. */
|
|
67
|
+
export function parseLocation(location) {
|
|
68
|
+
const match = LOCATION_WITH_LINE_COL.exec(location);
|
|
69
|
+
if (!match?.groups)
|
|
70
|
+
return null;
|
|
71
|
+
return {
|
|
72
|
+
file: match.groups["file"] ?? "",
|
|
73
|
+
line: Number(match.groups["line"]),
|
|
74
|
+
column: Number(match.groups["col"]),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A **stateful** `LineClassifier`, for RT-035's reason and RT-047's
|
|
79
|
+
* evidence.
|
|
80
|
+
*
|
|
81
|
+
* V8 was the last adapter still handing `ExceptionBlockDetector` a bare
|
|
82
|
+
* pair of stateless predicates — `{ isHeader: isV8HeaderLine, isFrame:
|
|
83
|
+
* isV8FrameLine }` — and it was wrong in exactly the way every other
|
|
84
|
+
* language's had already been found to be.
|
|
85
|
+
*
|
|
86
|
+
* **`FRAME_BARE` matches a Rust backtrace's location lines.** ` at
|
|
87
|
+
* /rustc/<hash>/library/std/src/panicking.rs:698:5` is `at` followed by a
|
|
88
|
+
* single parenthesis-free token, which is also precisely the shape of a
|
|
89
|
+
* bare V8 frame. RT-039 found this at the *parser* layer and fixed it there
|
|
90
|
+
* by requiring the whole text to contain a V8 header or a named `at fn
|
|
91
|
+
* (loc)` frame — and then reported that the matrix confirmed the classifier
|
|
92
|
+
* layer too. It had not: the classifier half of that matrix was inspecting
|
|
93
|
+
* an empty array (see `false-accept-matrix.test.ts`). With the matrix
|
|
94
|
+
* actually running, the V8 classifier opens **nine** blocks on one Rust
|
|
95
|
+
* backtrace.
|
|
96
|
+
*
|
|
97
|
+
* The rule here is the parser's anchor applied line by line, which is what
|
|
98
|
+
* a classifier can do that a set of predicates cannot: a **named** frame is
|
|
99
|
+
* always block content and arms the classifier; a **bare** frame is block
|
|
100
|
+
* content only once armed. A header arms it too.
|
|
101
|
+
*
|
|
102
|
+
* **The cost is the same one RT-039 already took and is restated rather
|
|
103
|
+
* than rediscovered:** a V8 trace consisting solely of bare frames does not
|
|
104
|
+
* open a block. Measured against four real V8 traces in RT-039, a bare
|
|
105
|
+
* frame occurs only where V8 has no function name, and Node's own internal
|
|
106
|
+
* frames — which follow every such throw — are all named, so the shape is
|
|
107
|
+
* rare. A disclosed recall loss, taken in the direction this project always
|
|
108
|
+
* takes it, over nine fabricated frames pointing into another language's
|
|
109
|
+
* standard library.
|
|
110
|
+
*/
|
|
111
|
+
export function createV8LineClassifier() {
|
|
112
|
+
let armed = false;
|
|
113
|
+
return {
|
|
114
|
+
isHeader(line) {
|
|
115
|
+
if (isV8HeaderLine(line)) {
|
|
116
|
+
armed = true;
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
},
|
|
121
|
+
isFrame(line) {
|
|
122
|
+
if (isV8NamedFrameLine(line)) {
|
|
123
|
+
armed = true;
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
return armed && FRAME_BARE.test(line);
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=v8-frame-parsing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v8-frame-parsing.js","sourceRoot":"","sources":["../src/v8-frame-parsing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAcH,MAAM,cAAc,GAAG,0DAA0D,CAAC;AAClF,MAAM,iBAAiB,GAAG,yDAAyD,CAAC;AACpF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,GAAG,+BAA+B,CAAC;AACnD,MAAM,sBAAsB,GAAG,wCAAwC,CAAC;AAExE,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,IAAI,UAAU,EAAE,MAAM,EAAE,CAAC;QACvB,OAAO,EAAE,YAAY,EAAE,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;IACrG,CAAC;IACD,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,IAAI,EAAE,MAAM,EAAE,CAAC;QACjB,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;IACpE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,KAAK,GAAG,sBAAsB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACpD,IAAI,CAAC,KAAK,EAAE,MAAM;QAAE,OAAO,IAAI,CAAC;IAChC,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;QAChC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAClC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;KACpC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,UAAU,sBAAsB;IAIpC,IAAI,KAAK,GAAG,KAAK,CAAC;IAElB,OAAO;QACL,QAAQ,CAAC,IAAY;YACnB,IAAI,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzB,KAAK,GAAG,IAAI,CAAC;gBACb,OAAO,IAAI,CAAC;YACd,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,CAAC,IAAY;YAClB,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7B,KAAK,GAAG,IAAI,CAAC;gBACb,OAAO,IAAI,CAAC;YACd,CAAC;YACD,OAAO,KAAK,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composes the pure V8 line-recognition functions (`v8-frame-parsing.ts`)
|
|
3
|
+
* with a `SourceLocationResolver` to implement the full async
|
|
4
|
+
* `StackTraceParser` contract.
|
|
5
|
+
*
|
|
6
|
+
* fidelity is always reported "synchronous". V8's zero-cost async stack
|
|
7
|
+
* traces mean every frame this parser sees is a genuine, unbroken link in
|
|
8
|
+
* the chain — V8 does not fabricate frames. What a single stack capture
|
|
9
|
+
* can never show is who scheduled a callback across a real event-loop
|
|
10
|
+
* reentry point (a timer, an emitted event, a fresh microtask): the stack
|
|
11
|
+
* simply, correctly, ends at that boundary (`node:events`, `node:timers`,
|
|
12
|
+
* the module loader — see the real broken-invoices-backend and thrower.ts
|
|
13
|
+
* fixtures, both of which bottom out this way). That is a hard limit of
|
|
14
|
+
* what V8 stacks can ever contain, not evidence that what IS shown is
|
|
15
|
+
* fragmented. Detecting a genuinely lost logical caller would need
|
|
16
|
+
* async_hooks-based correlation across reentry points, not text parsing —
|
|
17
|
+
* out of scope here, and reporting "concurrency-fragmented" without that
|
|
18
|
+
* evidence would be a guess this parser has no basis for.
|
|
19
|
+
*/
|
|
20
|
+
import type { SourceLocationResolver, StackTraceParser } from "@descryy/runtime-backend-observation";
|
|
21
|
+
export declare function createV8StackTraceParser(resolver: SourceLocationResolver): StackTraceParser;
|
|
22
|
+
//# sourceMappingURL=v8-stack-trace-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v8-stack-trace-parser.d.ts","sourceRoot":"","sources":["../src/v8-stack-trace-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAIrG,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,gBAAgB,CAkE3F"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composes the pure V8 line-recognition functions (`v8-frame-parsing.ts`)
|
|
3
|
+
* with a `SourceLocationResolver` to implement the full async
|
|
4
|
+
* `StackTraceParser` contract.
|
|
5
|
+
*
|
|
6
|
+
* fidelity is always reported "synchronous". V8's zero-cost async stack
|
|
7
|
+
* traces mean every frame this parser sees is a genuine, unbroken link in
|
|
8
|
+
* the chain — V8 does not fabricate frames. What a single stack capture
|
|
9
|
+
* can never show is who scheduled a callback across a real event-loop
|
|
10
|
+
* reentry point (a timer, an emitted event, a fresh microtask): the stack
|
|
11
|
+
* simply, correctly, ends at that boundary (`node:events`, `node:timers`,
|
|
12
|
+
* the module loader — see the real broken-invoices-backend and thrower.ts
|
|
13
|
+
* fixtures, both of which bottom out this way). That is a hard limit of
|
|
14
|
+
* what V8 stacks can ever contain, not evidence that what IS shown is
|
|
15
|
+
* fragmented. Detecting a genuinely lost logical caller would need
|
|
16
|
+
* async_hooks-based correlation across reentry points, not text parsing —
|
|
17
|
+
* out of scope here, and reporting "concurrency-fragmented" without that
|
|
18
|
+
* evidence would be a guess this parser has no basis for.
|
|
19
|
+
*/
|
|
20
|
+
import { parseV8Frame, parseLocation, isV8HeaderLine, isV8NamedFrameLine } from "./v8-frame-parsing.js";
|
|
21
|
+
export function createV8StackTraceParser(resolver) {
|
|
22
|
+
return {
|
|
23
|
+
async parse(rawText) {
|
|
24
|
+
const lines = rawText.split(/\r\n|\r|\n/);
|
|
25
|
+
// **Anchor check — the all-pairs matrix is what forced this.** A bare
|
|
26
|
+
// V8 frame is `at <location>`, and a Rust backtrace's location lines
|
|
27
|
+
// are `at /rustc/.../panicking.rs:698:5`. Those are the same shape,
|
|
28
|
+
// and without an anchor this parser accepted a whole Rust backtrace
|
|
29
|
+
// and produced nine confident, well-formed, wrong frames.
|
|
30
|
+
//
|
|
31
|
+
// The `at fn (loc)` form is the anchor because no other runtime here
|
|
32
|
+
// emits it, and measurement says a real V8 trace effectively always
|
|
33
|
+
// contains one: bare frames appear only where V8 has no function name
|
|
34
|
+
// (an ESM top-level throw), and Node's own internal frames — which
|
|
35
|
+
// follow every such throw — are all named. Verified against real
|
|
36
|
+
// output for ESM top-level, CJS top-level, an arrow in `Array.map`,
|
|
37
|
+
// and an unhandled rejection.
|
|
38
|
+
//
|
|
39
|
+
// The cost, stated: a trace consisting solely of bare frames is
|
|
40
|
+
// refused rather than parsed. That is a disclosed recall loss in a
|
|
41
|
+
// rare shape, taken in the direction this project always takes it —
|
|
42
|
+
// refusing beats fabricating, and the alternative here was nine
|
|
43
|
+
// fabricated frames pointing into another language's standard library.
|
|
44
|
+
if (!lines.some(isV8HeaderLine) && !lines.some(isV8NamedFrameLine)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const frames = [];
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
const rawFrame = parseV8Frame(line);
|
|
50
|
+
if (rawFrame === null)
|
|
51
|
+
continue; // a header line, blank line, or anything not frame-shaped
|
|
52
|
+
const parsedLocation = parseLocation(rawFrame.location);
|
|
53
|
+
if (parsedLocation === null) {
|
|
54
|
+
// A real, if rare, V8 shape: a native built-in call with no JS
|
|
55
|
+
// source location at all (e.g. "at Array.map (<anonymous>)").
|
|
56
|
+
// There is no line to report and none to fabricate -- omitted
|
|
57
|
+
// rather than guessed, same precision-over-recall choice this
|
|
58
|
+
// package's block detector already makes for ambiguous headers.
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const location = await resolver.resolve({
|
|
62
|
+
file: parsedLocation.file,
|
|
63
|
+
line: parsedLocation.line,
|
|
64
|
+
column: parsedLocation.column,
|
|
65
|
+
functionName: rawFrame.functionName,
|
|
66
|
+
});
|
|
67
|
+
frames.push({ location, raw: line });
|
|
68
|
+
}
|
|
69
|
+
if (frames.length === 0) {
|
|
70
|
+
// Nothing frame-shaped in this text at all -- not recognizable as
|
|
71
|
+
// V8's format, per this interface's own contract: null, never an
|
|
72
|
+
// empty-but-structured StackTrace standing in for "couldn't parse."
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
// `primaryFrameIndex` is required so that every adapter states an
|
|
76
|
+
// answer rather than inheriting one (RT-083). 0 is today's behaviour
|
|
77
|
+
// and is stated, not defaulted; the real value here is where the
|
|
78
|
+
// runtime declares its own boundary — V8 puts no frames of its own above the application's. Lane C.
|
|
79
|
+
return { fidelity: "synchronous", frames, primaryFrameIndex: 0 };
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=v8-stack-trace-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v8-stack-trace-parser.js","sourceRoot":"","sources":["../src/v8-stack-trace-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAExG,MAAM,UAAU,wBAAwB,CAAC,QAAgC;IACvE,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,OAAe;YACzB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAE1C,sEAAsE;YACtE,qEAAqE;YACrE,oEAAoE;YACpE,oEAAoE;YACpE,0DAA0D;YAC1D,EAAE;YACF,qEAAqE;YACrE,oEAAoE;YACpE,sEAAsE;YACtE,mEAAmE;YACnE,iEAAiE;YACjE,oEAAoE;YACpE,8BAA8B;YAC9B,EAAE;YACF,gEAAgE;YAChE,mEAAmE;YACnE,oEAAoE;YACpE,gEAAgE;YAChE,uEAAuE;YACvE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,EAAE,CAAC;gBACnE,OAAO,IAAI,CAAC;YACd,CAAC;YAED,MAAM,MAAM,GAAiB,EAAE,CAAC;YAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;gBACpC,IAAI,QAAQ,KAAK,IAAI;oBAAE,SAAS,CAAC,0DAA0D;gBAE3F,MAAM,cAAc,GAAG,aAAa,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBACxD,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;oBAC5B,+DAA+D;oBAC/D,8DAA8D;oBAC9D,8DAA8D;oBAC9D,8DAA8D;oBAC9D,gEAAgE;oBAChE,SAAS;gBACX,CAAC;gBAED,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;oBACtC,IAAI,EAAE,cAAc,CAAC,IAAI;oBACzB,IAAI,EAAE,cAAc,CAAC,IAAI;oBACzB,MAAM,EAAE,cAAc,CAAC,MAAM;oBAC7B,YAAY,EAAE,QAAQ,CAAC,YAAY;iBACpC,CAAC,CAAC;gBACH,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YACvC,CAAC;YAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxB,kEAAkE;gBAClE,iEAAiE;gBACjE,oEAAoE;gBACpE,OAAO,IAAI,CAAC;YACd,CAAC;YAEK,kEAAkE;YACxE,qEAAqE;YACrE,iEAAiE;YACjE,oGAAoG;YACpG,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC;QACnE,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@descryy/runtime-adapter-typescript",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "TypeScript/Node runtime adapter: V8 stack-trace parsing, Node source-map resolution, the concrete RuntimeAdapter for TS/Node.",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.5"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"registry": "https://registry.npmjs.org",
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -b"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@descryy/runtime-contracts": "0.0.0",
|
|
28
|
+
"@descryy/runtime-backend-observation": "0.0.0",
|
|
29
|
+
"@jridgewell/trace-mapping": "^0.3.31"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@descryy/runtime-evidence-store": "*"
|
|
33
|
+
}
|
|
34
|
+
}
|