@descryy/runtime-external-service-observation 0.1.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/collector-version.d.ts +10 -0
- package/dist/collector-version.d.ts.map +1 -0
- package/dist/collector-version.js +12 -0
- package/dist/collector-version.js.map +1 -0
- package/dist/fetch-instrumentation.d.ts +61 -0
- package/dist/fetch-instrumentation.d.ts.map +1 -0
- package/dist/fetch-instrumentation.js +137 -0
- package/dist/fetch-instrumentation.js.map +1 -0
- package/dist/header-correlation.d.ts +22 -0
- package/dist/header-correlation.d.ts.map +1 -0
- package/dist/header-correlation.js +35 -0
- package/dist/header-correlation.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/request-collector.d.ts +59 -0
- package/dist/request-collector.d.ts.map +1 -0
- package/dist/request-collector.js +183 -0
- package/dist/request-collector.js.map +1 -0
- package/dist/request-line-parser.d.ts +22 -0
- package/dist/request-line-parser.d.ts.map +1 -0
- package/dist/request-line-parser.js +62 -0
- package/dist/request-line-parser.js.map +1 -0
- package/dist/request-marker.d.ts +51 -0
- package/dist/request-marker.d.ts.map +1 -0
- package/dist/request-marker.js +32 -0
- package/dist/request-marker.js.map +1 -0
- package/dist/service-identity.d.ts +21 -0
- package/dist/service-identity.d.ts.map +1 -0
- package/dist/service-identity.js +28 -0
- package/dist/service-identity.js.map +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Evidence.collectorVersion`'s source for every collector in this package:
|
|
3
|
+
* this package's own `package.json` "version", read from the artifact
|
|
4
|
+
* itself rather than hand-duplicated into a second string that can drift
|
|
5
|
+
* from what actually shipped. Same idea as `nodeVersion` elsewhere in this
|
|
6
|
+
* repo (`process.version`, always known, never probed) applied to a
|
|
7
|
+
* package instead of the Node runtime.
|
|
8
|
+
*/
|
|
9
|
+
export declare const COLLECTOR_VERSION: string;
|
|
10
|
+
//# sourceMappingURL=collector-version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collector-version.d.ts","sourceRoot":"","sources":["../src/collector-version.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,eAAO,MAAM,iBAAiB,EAAE,MAA6E,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Evidence.collectorVersion`'s source for every collector in this package:
|
|
3
|
+
* this package's own `package.json` "version", read from the artifact
|
|
4
|
+
* itself rather than hand-duplicated into a second string that can drift
|
|
5
|
+
* from what actually shipped. Same idea as `nodeVersion` elsewhere in this
|
|
6
|
+
* repo (`process.version`, always known, never probed) applied to a
|
|
7
|
+
* package instead of the Node runtime.
|
|
8
|
+
*/
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
export const COLLECTOR_VERSION = require("../package.json").version;
|
|
12
|
+
//# sourceMappingURL=collector-version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collector-version.js","sourceRoot":"","sources":["../src/collector-version.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,MAAM,CAAC,MAAM,iBAAiB,GAAY,OAAO,CAAC,iBAAiB,CAAkC,CAAC,OAAO,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--import`-loaded preload: patches `globalThis.fetch` before the target
|
|
3
|
+
* application's own code runs, so every real outbound call the application
|
|
4
|
+
* issues through the bare global `fetch` is observed — proven directly (a
|
|
5
|
+
* probe script, not assumed): Node's global `fetch` is a single, mutable
|
|
6
|
+
* binding on `globalThis`, so a `--import` preload reassigning it here and
|
|
7
|
+
* an application's completely independent, later call to bare `fetch(...)`
|
|
8
|
+
* resolve to the same function. Same singleton-timing argument
|
|
9
|
+
* `sqlite-instrumentation.ts` (`@descryy/runtime-database-observation`,
|
|
10
|
+
* RT-141) already established for `node:sqlite`'s prototype, applied to a
|
|
11
|
+
* different kind of global.
|
|
12
|
+
*
|
|
13
|
+
* **Read, not write.** The wrapper calls the original `fetch` with the
|
|
14
|
+
* exact, untouched arguments it received — nothing about what leaves the
|
|
15
|
+
* process changes. Request/response bodies are never read here: `status`
|
|
16
|
+
* comes off the resolved `Response` before any body access, and describing
|
|
17
|
+
* the outbound request (`method`/`url`/headers) is done via the same
|
|
18
|
+
* `Request`/`Headers` normalisation `fetch` itself would do, on a
|
|
19
|
+
* side channel, not by touching the body stream passed to the real call.
|
|
20
|
+
*
|
|
21
|
+
* Deliberately minimal and import-free, like `sqlite-instrumentation.ts` and
|
|
22
|
+
* `test-runner`'s own `reporter.ts` — this runs inside the OBSERVED process,
|
|
23
|
+
* loaded by absolute path via `node --import`, never through this package's
|
|
24
|
+
* normal dependency graph. Interpretation (service identity, correlation
|
|
25
|
+
* ids) happens on the collector side, in the observing process, not here —
|
|
26
|
+
* this only captures.
|
|
27
|
+
*
|
|
28
|
+
* **The call-site stack is captured here and nowhere else.** It is the one
|
|
29
|
+
* fact only this process can supply: by the time the marker line reaches a
|
|
30
|
+
* collector it is text on a pipe, which is exactly why
|
|
31
|
+
* `ExternalRequestCollector` reported `stackTrace: null` for as long as this
|
|
32
|
+
* preload captured none. `observe_runtime`'s own header records the
|
|
33
|
+
* consequence — a backend-only run "resolves both kinds of node and writes no
|
|
34
|
+
* edge", because an edge needs a caller and an endpoint named by ONE
|
|
35
|
+
* observation and this one named only the endpoint. Capturing the stack at
|
|
36
|
+
* the call site is what makes an outbound call able to name its own caller.
|
|
37
|
+
*
|
|
38
|
+
* Raw text, deliberately unparsed. Parsing V8's format is
|
|
39
|
+
* `@descryy/runtime-adapter-typescript`'s job and belongs in the observing
|
|
40
|
+
* process, the same read-here/interpret-there split every marker in this
|
|
41
|
+
* repository already uses.
|
|
42
|
+
*
|
|
43
|
+
* The cost, stated rather than discovered: `new Error()` on every outbound
|
|
44
|
+
* call. That is a stack capture per request in a process that has opted into
|
|
45
|
+
* being observed by loading this preload; `Error.stackTraceLimit` is left
|
|
46
|
+
* exactly as the application set it, because raising it would be this file
|
|
47
|
+
* writing to the observed process's own configuration, which the paragraph
|
|
48
|
+
* above forbids.
|
|
49
|
+
*
|
|
50
|
+
* The marker prefix and the captured shape live in `request-marker.ts`, not
|
|
51
|
+
* here: this module has a module-scope effect and nothing that merely needs
|
|
52
|
+
* the vocabulary may be made to import it. See that file for the measurement
|
|
53
|
+
* behind that rule.
|
|
54
|
+
*
|
|
55
|
+
* One line of structured JSON per request, prefixed exactly like
|
|
56
|
+
* `sqlite-instrumentation.ts`'s own `DESCRY_DB_QUERY {json}` convention,
|
|
57
|
+
* itself following the benchmark harness's `BENCH_RESULT {json}` precedent
|
|
58
|
+
* (RT-072): scraping prose measures the prose, so nothing here is prose.
|
|
59
|
+
*/
|
|
60
|
+
export {};
|
|
61
|
+
//# sourceMappingURL=fetch-instrumentation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-instrumentation.d.ts","sourceRoot":"","sources":["../src/fetch-instrumentation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--import`-loaded preload: patches `globalThis.fetch` before the target
|
|
3
|
+
* application's own code runs, so every real outbound call the application
|
|
4
|
+
* issues through the bare global `fetch` is observed — proven directly (a
|
|
5
|
+
* probe script, not assumed): Node's global `fetch` is a single, mutable
|
|
6
|
+
* binding on `globalThis`, so a `--import` preload reassigning it here and
|
|
7
|
+
* an application's completely independent, later call to bare `fetch(...)`
|
|
8
|
+
* resolve to the same function. Same singleton-timing argument
|
|
9
|
+
* `sqlite-instrumentation.ts` (`@descryy/runtime-database-observation`,
|
|
10
|
+
* RT-141) already established for `node:sqlite`'s prototype, applied to a
|
|
11
|
+
* different kind of global.
|
|
12
|
+
*
|
|
13
|
+
* **Read, not write.** The wrapper calls the original `fetch` with the
|
|
14
|
+
* exact, untouched arguments it received — nothing about what leaves the
|
|
15
|
+
* process changes. Request/response bodies are never read here: `status`
|
|
16
|
+
* comes off the resolved `Response` before any body access, and describing
|
|
17
|
+
* the outbound request (`method`/`url`/headers) is done via the same
|
|
18
|
+
* `Request`/`Headers` normalisation `fetch` itself would do, on a
|
|
19
|
+
* side channel, not by touching the body stream passed to the real call.
|
|
20
|
+
*
|
|
21
|
+
* Deliberately minimal and import-free, like `sqlite-instrumentation.ts` and
|
|
22
|
+
* `test-runner`'s own `reporter.ts` — this runs inside the OBSERVED process,
|
|
23
|
+
* loaded by absolute path via `node --import`, never through this package's
|
|
24
|
+
* normal dependency graph. Interpretation (service identity, correlation
|
|
25
|
+
* ids) happens on the collector side, in the observing process, not here —
|
|
26
|
+
* this only captures.
|
|
27
|
+
*
|
|
28
|
+
* **The call-site stack is captured here and nowhere else.** It is the one
|
|
29
|
+
* fact only this process can supply: by the time the marker line reaches a
|
|
30
|
+
* collector it is text on a pipe, which is exactly why
|
|
31
|
+
* `ExternalRequestCollector` reported `stackTrace: null` for as long as this
|
|
32
|
+
* preload captured none. `observe_runtime`'s own header records the
|
|
33
|
+
* consequence — a backend-only run "resolves both kinds of node and writes no
|
|
34
|
+
* edge", because an edge needs a caller and an endpoint named by ONE
|
|
35
|
+
* observation and this one named only the endpoint. Capturing the stack at
|
|
36
|
+
* the call site is what makes an outbound call able to name its own caller.
|
|
37
|
+
*
|
|
38
|
+
* Raw text, deliberately unparsed. Parsing V8's format is
|
|
39
|
+
* `@descryy/runtime-adapter-typescript`'s job and belongs in the observing
|
|
40
|
+
* process, the same read-here/interpret-there split every marker in this
|
|
41
|
+
* repository already uses.
|
|
42
|
+
*
|
|
43
|
+
* The cost, stated rather than discovered: `new Error()` on every outbound
|
|
44
|
+
* call. That is a stack capture per request in a process that has opted into
|
|
45
|
+
* being observed by loading this preload; `Error.stackTraceLimit` is left
|
|
46
|
+
* exactly as the application set it, because raising it would be this file
|
|
47
|
+
* writing to the observed process's own configuration, which the paragraph
|
|
48
|
+
* above forbids.
|
|
49
|
+
*
|
|
50
|
+
* The marker prefix and the captured shape live in `request-marker.ts`, not
|
|
51
|
+
* here: this module has a module-scope effect and nothing that merely needs
|
|
52
|
+
* the vocabulary may be made to import it. See that file for the measurement
|
|
53
|
+
* behind that rule.
|
|
54
|
+
*
|
|
55
|
+
* One line of structured JSON per request, prefixed exactly like
|
|
56
|
+
* `sqlite-instrumentation.ts`'s own `DESCRY_DB_QUERY {json}` convention,
|
|
57
|
+
* itself following the benchmark harness's `BENCH_RESULT {json}` precedent
|
|
58
|
+
* (RT-072): scraping prose measures the prose, so nothing here is prose.
|
|
59
|
+
*/
|
|
60
|
+
import { REQUEST_MARKER } from "./request-marker.js";
|
|
61
|
+
/** This module's own location, in both shapes a V8 frame can print it: an ESM frame stringifies a `file://` URL, a CJS one a plain path. */
|
|
62
|
+
const SELF_URL = import.meta.url;
|
|
63
|
+
const SELF_PATH = SELF_URL.startsWith("file://") ? SELF_URL.slice("file://".length) : SELF_URL;
|
|
64
|
+
/**
|
|
65
|
+
* Frames belonging to this file are dropped **by file, not by count**.
|
|
66
|
+
* `primaryFrameIndex` is 0 for every V8 stack this project parses, so a
|
|
67
|
+
* single surviving wrapper frame would make every downstream resolution
|
|
68
|
+
* point at the instrumentation instead of at the code under observation --
|
|
69
|
+
* a failure that produces confident, well-formed, entirely wrong answers
|
|
70
|
+
* rather than an error. Matching on `import.meta.url` also survives this
|
|
71
|
+
* module being loaded from `.js` (the compiled preload, how it actually
|
|
72
|
+
* runs) or `.ts` (type stripping, how a test may run it).
|
|
73
|
+
*/
|
|
74
|
+
function callSiteStack() {
|
|
75
|
+
const raw = new Error().stack;
|
|
76
|
+
if (raw === undefined)
|
|
77
|
+
return null;
|
|
78
|
+
const lines = raw.split("\n");
|
|
79
|
+
const header = lines[0] ?? "Error";
|
|
80
|
+
const frames = lines.slice(1).filter((line) => !line.includes(SELF_URL) && !line.includes(SELF_PATH));
|
|
81
|
+
// Nothing but our own frames: no application call site was captured, which
|
|
82
|
+
// is `null` rather than a header line pretending to be a stack.
|
|
83
|
+
if (frames.length === 0)
|
|
84
|
+
return null;
|
|
85
|
+
return [header, ...frames].join("\n");
|
|
86
|
+
}
|
|
87
|
+
function emit(request) {
|
|
88
|
+
process.stdout.write(`${REQUEST_MARKER} ${JSON.stringify(request)}\n`);
|
|
89
|
+
}
|
|
90
|
+
function headersToObject(headers) {
|
|
91
|
+
const out = {};
|
|
92
|
+
for (const [name, value] of headers.entries())
|
|
93
|
+
out[name] = value;
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* `url`/`method`/`requestHeaders` off the call's own arguments, without
|
|
98
|
+
* touching the body -- a `Request` object already carries these structured;
|
|
99
|
+
* a `(input, init)` pair is normalised the same way `fetch` itself would,
|
|
100
|
+
* via the real `Headers` constructor rather than hand-parsed.
|
|
101
|
+
*/
|
|
102
|
+
function describeRequest(input, init) {
|
|
103
|
+
if (input instanceof Request) {
|
|
104
|
+
return { url: input.url, method: input.method, requestHeaders: headersToObject(input.headers) };
|
|
105
|
+
}
|
|
106
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
107
|
+
const method = (init?.method ?? "GET").toUpperCase();
|
|
108
|
+
return { url, method, requestHeaders: headersToObject(new Headers(init?.headers)) };
|
|
109
|
+
}
|
|
110
|
+
// Guards against double-patching if this module is somehow loaded twice
|
|
111
|
+
// (e.g. both `--import` and a transitive re-import) -- the second pass
|
|
112
|
+
// would otherwise wrap an already-wrapped fetch and double-emit. Same
|
|
113
|
+
// guard shape as sqlite-instrumentation.ts's own PATCHED symbol.
|
|
114
|
+
const PATCHED = Symbol.for("descry.external-service-observation.fetch-patched");
|
|
115
|
+
const target = globalThis;
|
|
116
|
+
if (target[PATCHED] !== true) {
|
|
117
|
+
target[PATCHED] = true;
|
|
118
|
+
const original = globalThis.fetch;
|
|
119
|
+
globalThis.fetch = async function patchedFetch(input, init) {
|
|
120
|
+
const described = describeRequest(input, init);
|
|
121
|
+
// Captured BEFORE the call, not in the handlers below: after `await` the
|
|
122
|
+
// application's own frames are gone from the stack.
|
|
123
|
+
const stack = callSiteStack();
|
|
124
|
+
const start = performance.now();
|
|
125
|
+
try {
|
|
126
|
+
const response = await original(input, init);
|
|
127
|
+
emit({ ...described, status: response.status, durationMs: performance.now() - start, success: true, error: null, stack });
|
|
128
|
+
return response;
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
132
|
+
emit({ ...described, status: null, durationMs: performance.now() - start, success: false, error: detail, stack });
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=fetch-instrumentation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-instrumentation.js","sourceRoot":"","sources":["../src/fetch-instrumentation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Correlation-id extraction from real HTTP headers — the same mechanism and
|
|
3
|
+
* the same closed set of header names as `extractHeaderCorrelationIds`
|
|
4
|
+
* (`@descryhq-wq/runtime-browser`'s `network-correlation.ts`).
|
|
5
|
+
*
|
|
6
|
+
* **Duplicated on purpose, not imported.** `browser` depends on Playwright
|
|
7
|
+
* to launch and drive a real browser — a real, heavy dependency this
|
|
8
|
+
* package has no other reason to carry, for a page-automation concern
|
|
9
|
+
* entirely unrelated to instrumenting a backend process's own outbound
|
|
10
|
+
* `fetch`. The function itself is small, pure, and reads a closed,
|
|
11
|
+
* unlikely-to-change set of conventional header names — the case this
|
|
12
|
+
* project's own `symbol.ts` distinguishes from (`toRepoRelative` is
|
|
13
|
+
* *shared* because its two callers must translate identically); these two
|
|
14
|
+
* copies have no such constraint, and importing across a Playwright
|
|
15
|
+
* dependency to avoid ten lines would be the worse coupling.
|
|
16
|
+
*/
|
|
17
|
+
export interface HeaderCorrelationIds {
|
|
18
|
+
readonly traceId: string | null;
|
|
19
|
+
readonly requestId: string | null;
|
|
20
|
+
}
|
|
21
|
+
export declare function extractHeaderCorrelationIds(headers: Readonly<Record<string, string>>): HeaderCorrelationIds;
|
|
22
|
+
//# sourceMappingURL=header-correlation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"header-correlation.d.ts","sourceRoot":"","sources":["../src/header-correlation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAUD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,oBAAoB,CAS3G"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Correlation-id extraction from real HTTP headers — the same mechanism and
|
|
3
|
+
* the same closed set of header names as `extractHeaderCorrelationIds`
|
|
4
|
+
* (`@descryhq-wq/runtime-browser`'s `network-correlation.ts`).
|
|
5
|
+
*
|
|
6
|
+
* **Duplicated on purpose, not imported.** `browser` depends on Playwright
|
|
7
|
+
* to launch and drive a real browser — a real, heavy dependency this
|
|
8
|
+
* package has no other reason to carry, for a page-automation concern
|
|
9
|
+
* entirely unrelated to instrumenting a backend process's own outbound
|
|
10
|
+
* `fetch`. The function itself is small, pure, and reads a closed,
|
|
11
|
+
* unlikely-to-change set of conventional header names — the case this
|
|
12
|
+
* project's own `symbol.ts` distinguishes from (`toRepoRelative` is
|
|
13
|
+
* *shared* because its two callers must translate identically); these two
|
|
14
|
+
* copies have no such constraint, and importing across a Playwright
|
|
15
|
+
* dependency to avoid ten lines would be the worse coupling.
|
|
16
|
+
*/
|
|
17
|
+
const TRACEPARENT_PATTERN = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/i;
|
|
18
|
+
function firstHeader(headers, names) {
|
|
19
|
+
for (const name of names) {
|
|
20
|
+
const value = headers[name];
|
|
21
|
+
if (value !== undefined && value.length > 0)
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
export function extractHeaderCorrelationIds(headers) {
|
|
27
|
+
const explicitTraceId = firstHeader(headers, ["x-trace-id", "trace-id"]);
|
|
28
|
+
const traceparent = headers["traceparent"];
|
|
29
|
+
const w3cTraceId = traceparent !== undefined ? (TRACEPARENT_PATTERN.exec(traceparent)?.[2] ?? null) : null;
|
|
30
|
+
return {
|
|
31
|
+
traceId: explicitTraceId ?? w3cTraceId,
|
|
32
|
+
requestId: firstHeader(headers, ["x-request-id", "request-id"]),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=header-correlation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"header-correlation.js","sourceRoot":"","sources":["../src/header-correlation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,mBAAmB,GAAG,8DAA8D,CAAC;AAO3F,SAAS,WAAW,CAAC,OAAyC,EAAE,KAAwB;IACtF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;IAC5D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,OAAyC;IACnF,MAAM,eAAe,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC,CAAC;IACzE,MAAM,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAE3G,OAAO;QACL,OAAO,EAAE,eAAe,IAAI,UAAU;QACtC,SAAS,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;KAChE,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { REQUEST_MARKER, type CapturedRequest } from "./request-marker.ts";
|
|
2
|
+
export { parseRequestLine } from "./request-line-parser.ts";
|
|
3
|
+
export { extractServiceIdentity } from "./service-identity.ts";
|
|
4
|
+
export { extractHeaderCorrelationIds, type HeaderCorrelationIds } from "./header-correlation.ts";
|
|
5
|
+
export type { ExternalRequestCollectorOptions } from "./request-collector.ts";
|
|
6
|
+
export { createExternalRequestCollector, FETCH_PRELOAD_MODULE_PATH } from "./request-collector.ts";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAE,KAAK,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAEjG,YAAY,EAAE,+BAA+B,EAAE,MAAM,wBAAwB,CAAC;AAC9E,OAAO,EAAE,8BAA8B,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Deliberately NOT from `fetch-instrumentation.ts`: that module replaces
|
|
2
|
+
// `globalThis.fetch` at module scope, and re-exporting a constant out of it
|
|
3
|
+
// put that patch into every consumer of this package -- `query-marker.ts`'s
|
|
4
|
+
// measured defect, which this package still had. The preload is loaded by
|
|
5
|
+
// absolute path via `node --import`, inside the observed process, never
|
|
6
|
+
// through this dependency graph.
|
|
7
|
+
export { REQUEST_MARKER } from "./request-marker.js";
|
|
8
|
+
export { parseRequestLine } from "./request-line-parser.js";
|
|
9
|
+
export { extractServiceIdentity } from "./service-identity.js";
|
|
10
|
+
export { extractHeaderCorrelationIds } from "./header-correlation.js";
|
|
11
|
+
export { createExternalRequestCollector, FETCH_PRELOAD_MODULE_PATH } from "./request-collector.js";
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,4EAA4E;AAC5E,4EAA4E;AAC5E,0EAA0E;AAC1E,wEAAwE;AACxE,iCAAiC;AACjC,OAAO,EAAE,cAAc,EAAwB,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAA6B,MAAM,yBAAyB,CAAC;AAGjG,OAAO,EAAE,8BAA8B,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ExternalRequestCollector` (plan §18 / checklist §18): translates
|
|
3
|
+
* `fetch-instrumentation.ts`'s captured requests into `EXTERNAL_REQUEST`
|
|
4
|
+
* Evidence. Same shape as `DatabaseQueryCollector`
|
|
5
|
+
* (`@descryy/runtime-database-observation`, RT-141) deliberately — a
|
|
6
|
+
* `ProcessOutputSource` in, `Evidence` out, streaming-first, a collector
|
|
7
|
+
* failure reported as evidence rather than swallowed — but for a different
|
|
8
|
+
* marker on the same stream.
|
|
9
|
+
*
|
|
10
|
+
* **What this collector does not do, named rather than silently absent:**
|
|
11
|
+
* no request/response body capture (the preload never reads either stream —
|
|
12
|
+
* see `fetch-instrumentation.ts`'s own "read, not write" note), no
|
|
13
|
+
* provider classification beyond a request's own hostname
|
|
14
|
+
* (`service-identity.ts`), and `graphNodeId` is always null — no resolver
|
|
15
|
+
* from a captured hostname to any graph node exists (nothing in the 15
|
|
16
|
+
* canonical node types names an external service). All real §18 rows, left
|
|
17
|
+
* open rather than guessed at, matching `DatabaseQueryCollector`'s own
|
|
18
|
+
* disclosure of the same shape of gap.
|
|
19
|
+
*
|
|
20
|
+
* ## The call-site stack, and why the parser is injected
|
|
21
|
+
*
|
|
22
|
+
* `stackTrace` used to be hard-null here, and `observe_runtime`'s own header
|
|
23
|
+
* gave the reason: *"it parses a log line the application printed and a
|
|
24
|
+
* printed line carries no stack."* The line is this package's own marker and
|
|
25
|
+
* the preload that writes it runs inside the calling process, so the stack
|
|
26
|
+
* was always available — nobody had captured it. It is captured now
|
|
27
|
+
* (`fetch-instrumentation.ts`), and this collector attaches it.
|
|
28
|
+
*
|
|
29
|
+
* Parsing it is **not** this package's job. V8's `Error.stack` format is one
|
|
30
|
+
* language's, and this collector observes the marker protocol, which is not.
|
|
31
|
+
* So a `StackTraceParser` is an option, supplied by whoever knows which
|
|
32
|
+
* runtime is being observed — `RuntimeAdapter.stackTraceParser` is exactly
|
|
33
|
+
* that value, already on the seam. Omitted, this collector behaves exactly as
|
|
34
|
+
* it did before the capture existed and **says so** in `capabilities()`:
|
|
35
|
+
* `stackCapture: unavailable`, with the reason. A half-wired capability that
|
|
36
|
+
* quietly reports nothing is the failure mode this project's honest-
|
|
37
|
+
* degradation rule exists to prevent.
|
|
38
|
+
*/
|
|
39
|
+
import type { Collector } from "@descryy/runtime-contracts";
|
|
40
|
+
import type { ProcessOutputSource, StackTraceParser } from "@descryy/runtime-backend-observation";
|
|
41
|
+
/** Absolute path to the compiled preload, for a caller building `node --import <this> <app>`. Never imported directly -- loaded by Node itself, in the observed process, before that process's own code runs. */
|
|
42
|
+
export declare const FETCH_PRELOAD_MODULE_PATH: string;
|
|
43
|
+
export interface ExternalRequestCollectorOptions {
|
|
44
|
+
readonly source: ProcessOutputSource;
|
|
45
|
+
/** Which owned system this collector's evidence should be attributed to (plan §16.7). Null when not applicable. */
|
|
46
|
+
readonly service?: string;
|
|
47
|
+
/**
|
|
48
|
+
* The observed runtime's own stack parser — `RuntimeAdapter.stackTraceParser`.
|
|
49
|
+
* Supplied by a caller that knows which language it launched; this package
|
|
50
|
+
* knows no language and must not (the IR boundary rule, one layer down).
|
|
51
|
+
*
|
|
52
|
+
* Omitted means captured stacks are not parsed and `stackTrace` stays
|
|
53
|
+
* `null`, which `capabilities()` reports as `stackCapture: unavailable`
|
|
54
|
+
* rather than leaving a reader to infer it from an absence.
|
|
55
|
+
*/
|
|
56
|
+
readonly stackTraceParser?: StackTraceParser;
|
|
57
|
+
}
|
|
58
|
+
export declare function createExternalRequestCollector(options: ExternalRequestCollectorOptions): Collector;
|
|
59
|
+
//# sourceMappingURL=request-collector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-collector.d.ts","sourceRoot":"","sources":["../src/request-collector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAGH,OAAO,KAAK,EAAE,SAAS,EAA+F,MAAM,4BAA4B,CAAC;AACzJ,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAOlG,iNAAiN;AACjN,eAAO,MAAM,yBAAyB,QAAwE,CAAC;AAE/G,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,mHAAmH;IACnH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;OAQG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CAC9C;AAmCD,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,+BAA+B,GAAG,SAAS,CA+GlG"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ExternalRequestCollector` (plan §18 / checklist §18): translates
|
|
3
|
+
* `fetch-instrumentation.ts`'s captured requests into `EXTERNAL_REQUEST`
|
|
4
|
+
* Evidence. Same shape as `DatabaseQueryCollector`
|
|
5
|
+
* (`@descryy/runtime-database-observation`, RT-141) deliberately — a
|
|
6
|
+
* `ProcessOutputSource` in, `Evidence` out, streaming-first, a collector
|
|
7
|
+
* failure reported as evidence rather than swallowed — but for a different
|
|
8
|
+
* marker on the same stream.
|
|
9
|
+
*
|
|
10
|
+
* **What this collector does not do, named rather than silently absent:**
|
|
11
|
+
* no request/response body capture (the preload never reads either stream —
|
|
12
|
+
* see `fetch-instrumentation.ts`'s own "read, not write" note), no
|
|
13
|
+
* provider classification beyond a request's own hostname
|
|
14
|
+
* (`service-identity.ts`), and `graphNodeId` is always null — no resolver
|
|
15
|
+
* from a captured hostname to any graph node exists (nothing in the 15
|
|
16
|
+
* canonical node types names an external service). All real §18 rows, left
|
|
17
|
+
* open rather than guessed at, matching `DatabaseQueryCollector`'s own
|
|
18
|
+
* disclosure of the same shape of gap.
|
|
19
|
+
*
|
|
20
|
+
* ## The call-site stack, and why the parser is injected
|
|
21
|
+
*
|
|
22
|
+
* `stackTrace` used to be hard-null here, and `observe_runtime`'s own header
|
|
23
|
+
* gave the reason: *"it parses a log line the application printed and a
|
|
24
|
+
* printed line carries no stack."* The line is this package's own marker and
|
|
25
|
+
* the preload that writes it runs inside the calling process, so the stack
|
|
26
|
+
* was always available — nobody had captured it. It is captured now
|
|
27
|
+
* (`fetch-instrumentation.ts`), and this collector attaches it.
|
|
28
|
+
*
|
|
29
|
+
* Parsing it is **not** this package's job. V8's `Error.stack` format is one
|
|
30
|
+
* language's, and this collector observes the marker protocol, which is not.
|
|
31
|
+
* So a `StackTraceParser` is an option, supplied by whoever knows which
|
|
32
|
+
* runtime is being observed — `RuntimeAdapter.stackTraceParser` is exactly
|
|
33
|
+
* that value, already on the seam. Omitted, this collector behaves exactly as
|
|
34
|
+
* it did before the capture existed and **says so** in `capabilities()`:
|
|
35
|
+
* `stackCapture: unavailable`, with the reason. A half-wired capability that
|
|
36
|
+
* quietly reports nothing is the failure mode this project's honest-
|
|
37
|
+
* degradation rule exists to prevent.
|
|
38
|
+
*/
|
|
39
|
+
import { fileURLToPath } from "node:url";
|
|
40
|
+
import { parseRequestLine } from "./request-line-parser.js";
|
|
41
|
+
import { extractServiceIdentity } from "./service-identity.js";
|
|
42
|
+
import { extractHeaderCorrelationIds } from "./header-correlation.js";
|
|
43
|
+
import { COLLECTOR_VERSION } from "./collector-version.js";
|
|
44
|
+
/** Absolute path to the compiled preload, for a caller building `node --import <this> <app>`. Never imported directly -- loaded by Node itself, in the observed process, before that process's own code runs. */
|
|
45
|
+
export const FETCH_PRELOAD_MODULE_PATH = fileURLToPath(new URL("./fetch-instrumentation.js", import.meta.url));
|
|
46
|
+
const EVENT_TYPE = "EXTERNAL_REQUEST";
|
|
47
|
+
function computeCapabilities(hasStackParser) {
|
|
48
|
+
const reason = "ExternalRequestCollector observes EXTERNAL_REQUEST events only.";
|
|
49
|
+
const status = { availability: "unavailable", reason };
|
|
50
|
+
return {
|
|
51
|
+
domObservation: status,
|
|
52
|
+
consoleObservation: status,
|
|
53
|
+
networkObservation: status,
|
|
54
|
+
backendLogAccess: status,
|
|
55
|
+
// Real HTTP headers, structurally read (Headers/Request, not text
|
|
56
|
+
// parsed) -- the same mechanism and the same claim strength
|
|
57
|
+
// BrowserNetworkCollector already makes for its own captured requests.
|
|
58
|
+
distributedTrace: { availability: "available", reason: null },
|
|
59
|
+
sourceMapping: status,
|
|
60
|
+
// Real: the preload captures the application's own stack at the fetch
|
|
61
|
+
// call site, in the observed process, before the call leaves it. Claimed
|
|
62
|
+
// only when a parser was actually supplied to turn it into frames --
|
|
63
|
+
// capturing text nothing can read is not a capability.
|
|
64
|
+
stackCapture: hasStackParser
|
|
65
|
+
? { availability: "available", reason: null }
|
|
66
|
+
: {
|
|
67
|
+
availability: "unavailable",
|
|
68
|
+
reason: "the fetch preload captures a call-site stack, but no StackTraceParser was supplied to this collector, " +
|
|
69
|
+
"so it is not parsed and `stackTrace` stays null. Pass `RuntimeAdapter.stackTraceParser` for the observed runtime.",
|
|
70
|
+
},
|
|
71
|
+
processLifecycle: status,
|
|
72
|
+
databaseObservation: { availability: "unavailable", reason: "ExternalRequestCollector instruments the global fetch only; queries are DatabaseQueryCollector's to observe (RT-141)." },
|
|
73
|
+
externalServiceObservation: { availability: "available", reason: null },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function createExternalRequestCollector(options) {
|
|
77
|
+
const capabilities = computeCapabilities(options.stackTraceParser !== undefined);
|
|
78
|
+
let consumeLoopDone;
|
|
79
|
+
let consumptionFailure = null;
|
|
80
|
+
/**
|
|
81
|
+
* `null` for every honest reason it can be: nothing captured, no parser
|
|
82
|
+
* supplied, or a parser that refused the text outright. A parser's `null`
|
|
83
|
+
* is its own contract ("not recognizable as this parser's format, never a
|
|
84
|
+
* best-effort guess") and is passed through rather than softened.
|
|
85
|
+
*/
|
|
86
|
+
async function parseStack(request) {
|
|
87
|
+
if (request.stack === null || options.stackTraceParser === undefined)
|
|
88
|
+
return null;
|
|
89
|
+
return await options.stackTraceParser.parse(request.stack);
|
|
90
|
+
}
|
|
91
|
+
async function emitCapturedRequest(request, context) {
|
|
92
|
+
const { traceId, requestId } = extractHeaderCorrelationIds(request.requestHeaders);
|
|
93
|
+
const stackTrace = await parseStack(request);
|
|
94
|
+
context.emit({
|
|
95
|
+
timestamp: new Date().toISOString(),
|
|
96
|
+
source: "backend-process",
|
|
97
|
+
service: options.service ?? null,
|
|
98
|
+
process: options.source.processId,
|
|
99
|
+
eventType: EVENT_TYPE,
|
|
100
|
+
payload: {
|
|
101
|
+
raw: `${request.method} ${request.url}`,
|
|
102
|
+
url: request.url,
|
|
103
|
+
method: request.method,
|
|
104
|
+
service: extractServiceIdentity(request.url),
|
|
105
|
+
status: request.status,
|
|
106
|
+
durationMs: request.durationMs,
|
|
107
|
+
success: request.success,
|
|
108
|
+
error: request.error,
|
|
109
|
+
},
|
|
110
|
+
traceId,
|
|
111
|
+
requestId,
|
|
112
|
+
correlationId: null,
|
|
113
|
+
// No resolver from a captured hostname to a graph node exists --
|
|
114
|
+
// none of the 15 canonical node types names an external service.
|
|
115
|
+
graphNodeId: null,
|
|
116
|
+
sourceLocation: null,
|
|
117
|
+
stackTrace,
|
|
118
|
+
// The observation itself (a real captured call, real timing, real
|
|
119
|
+
// status/failure) is not probabilistic.
|
|
120
|
+
confidence: 1,
|
|
121
|
+
redactionStatus: "pending-redaction",
|
|
122
|
+
collectorVersion: COLLECTOR_VERSION,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
async function consume(context) {
|
|
126
|
+
for await (const line of options.source.lines) {
|
|
127
|
+
const request = parseRequestLine(line.text);
|
|
128
|
+
if (request !== null)
|
|
129
|
+
await emitCapturedRequest(request, context);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
collectorId: `external-request-collector:${options.source.processId}`,
|
|
134
|
+
start(context) {
|
|
135
|
+
consumeLoopDone = consume(context).catch((error) => {
|
|
136
|
+
const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
137
|
+
consumptionFailure = detail;
|
|
138
|
+
try {
|
|
139
|
+
context.emit({
|
|
140
|
+
timestamp: new Date().toISOString(),
|
|
141
|
+
source: "backend-process",
|
|
142
|
+
service: options.service ?? null,
|
|
143
|
+
process: options.source.processId,
|
|
144
|
+
eventType: "COLLECTOR_ERROR",
|
|
145
|
+
payload: {
|
|
146
|
+
raw: `external request collector for process "${options.source.processId}" stopped consuming after an error: ${detail}`,
|
|
147
|
+
collectorId: `external-request-collector:${options.source.processId}`,
|
|
148
|
+
error: detail,
|
|
149
|
+
},
|
|
150
|
+
traceId: null,
|
|
151
|
+
requestId: null,
|
|
152
|
+
correlationId: null,
|
|
153
|
+
graphNodeId: null,
|
|
154
|
+
sourceLocation: null,
|
|
155
|
+
stackTrace: null,
|
|
156
|
+
confidence: 1,
|
|
157
|
+
redactionStatus: "pending-redaction",
|
|
158
|
+
collectorVersion: COLLECTOR_VERSION,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
catch (emitError) {
|
|
162
|
+
console.error(`[external-request-collector:${options.source.processId}] consumption failed (${detail}) AND the failure could not be emitted: ${String(emitError)}`);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
return Promise.resolve({ available: true });
|
|
166
|
+
},
|
|
167
|
+
async stop() {
|
|
168
|
+
await consumeLoopDone;
|
|
169
|
+
},
|
|
170
|
+
capabilities() {
|
|
171
|
+
if (consumptionFailure === null)
|
|
172
|
+
return capabilities;
|
|
173
|
+
return {
|
|
174
|
+
...capabilities,
|
|
175
|
+
distributedTrace: {
|
|
176
|
+
availability: "unavailable",
|
|
177
|
+
reason: `consumption stopped early after an error (${consumptionFailure}); output after that point was never observed.`,
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=request-collector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-collector.js","sourceRoot":"","sources":["../src/request-collector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAIzC,OAAO,EAAE,gBAAgB,EAAwB,MAAM,0BAA0B,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,iNAAiN;AACjN,MAAM,CAAC,MAAM,yBAAyB,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,4BAA4B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAkB/G,MAAM,UAAU,GAAqB,kBAAkB,CAAC;AAExD,SAAS,mBAAmB,CAAC,cAAuB;IAClD,MAAM,MAAM,GAAG,iEAAiE,CAAC;IACjF,MAAM,MAAM,GAAG,EAAE,YAAY,EAAE,aAAsB,EAAE,MAAM,EAAE,CAAC;IAChE,OAAO;QACL,cAAc,EAAE,MAAM;QACtB,kBAAkB,EAAE,MAAM;QAC1B,kBAAkB,EAAE,MAAM;QAC1B,gBAAgB,EAAE,MAAM;QACxB,kEAAkE;QAClE,4DAA4D;QAC5D,uEAAuE;QACvE,gBAAgB,EAAE,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE;QAC7D,aAAa,EAAE,MAAM;QACrB,sEAAsE;QACtE,yEAAyE;QACzE,qEAAqE;QACrE,uDAAuD;QACvD,YAAY,EAAE,cAAc;YAC1B,CAAC,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE;YAC7C,CAAC,CAAC;gBACE,YAAY,EAAE,aAAa;gBAC3B,MAAM,EACJ,wGAAwG;oBACxG,mHAAmH;aACtH;QACL,gBAAgB,EAAE,MAAM;QACxB,mBAAmB,EAAE,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,uHAAuH,EAAE;QACrL,0BAA0B,EAAE,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE;KACxE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,OAAwC;IACrF,MAAM,YAAY,GAAG,mBAAmB,CAAC,OAAO,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC;IACjF,IAAI,eAA0C,CAAC;IAC/C,IAAI,kBAAkB,GAAkB,IAAI,CAAC;IAE7C;;;;;OAKG;IACH,KAAK,UAAU,UAAU,CAAC,OAAwB;QAChD,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,IAAI,OAAO,CAAC,gBAAgB,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QAClF,OAAO,MAAM,OAAO,CAAC,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK,UAAU,mBAAmB,CAAC,OAAwB,EAAE,OAAyB;QACpF,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,2BAA2B,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QACnF,MAAM,UAAU,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC;QAC7C,OAAO,CAAC,IAAI,CAAC;YACX,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,MAAM,EAAE,iBAAiB;YACzB,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,IAAI;YAChC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS;YACjC,SAAS,EAAE,UAAU;YACrB,OAAO,EAAE;gBACP,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,EAAE;gBACvC,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,OAAO,EAAE,sBAAsB,CAAC,OAAO,CAAC,GAAG,CAAC;gBAC5C,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,KAAK,EAAE,OAAO,CAAC,KAAK;aACrB;YACD,OAAO;YACP,SAAS;YACT,aAAa,EAAE,IAAI;YACnB,iEAAiE;YACjE,iEAAiE;YACjE,WAAW,EAAE,IAAI;YACjB,cAAc,EAAE,IAAI;YACpB,UAAU;YACV,kEAAkE;YAClE,wCAAwC;YACxC,UAAU,EAAE,CAAC;YACb,eAAe,EAAE,mBAAmB;YACpC,gBAAgB,EAAE,iBAAiB;SACpC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,UAAU,OAAO,CAAC,OAAyB;QAC9C,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAC9C,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC5C,IAAI,OAAO,KAAK,IAAI;gBAAE,MAAM,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,OAAO;QACL,WAAW,EAAE,8BAA8B,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE;QAErE,KAAK,CAAC,OAAyB;YAC7B,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBAC1D,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC1F,kBAAkB,GAAG,MAAM,CAAC;gBAC5B,IAAI,CAAC;oBACH,OAAO,CAAC,IAAI,CAAC;wBACX,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;wBACnC,MAAM,EAAE,iBAAiB;wBACzB,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,IAAI;wBAChC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS;wBACjC,SAAS,EAAE,iBAAiB;wBAC5B,OAAO,EAAE;4BACP,GAAG,EAAE,2CAA2C,OAAO,CAAC,MAAM,CAAC,SAAS,uCAAuC,MAAM,EAAE;4BACvH,WAAW,EAAE,8BAA8B,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE;4BACrE,KAAK,EAAE,MAAM;yBACd;wBACD,OAAO,EAAE,IAAI;wBACb,SAAS,EAAE,IAAI;wBACf,aAAa,EAAE,IAAI;wBACnB,WAAW,EAAE,IAAI;wBACjB,cAAc,EAAE,IAAI;wBACpB,UAAU,EAAE,IAAI;wBAChB,UAAU,EAAE,CAAC;wBACb,eAAe,EAAE,mBAAmB;wBACpC,gBAAgB,EAAE,iBAAiB;qBACpC,CAAC,CAAC;gBACL,CAAC;gBAAC,OAAO,SAAkB,EAAE,CAAC;oBAC5B,OAAO,CAAC,KAAK,CACX,+BAA+B,OAAO,CAAC,MAAM,CAAC,SAAS,yBAAyB,MAAM,2CAA2C,MAAM,CAAC,SAAS,CAAC,EAAE,CACrJ,CAAC;gBACJ,CAAC;YACH,CAAC,CAAC,CAAC;YACH,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,CAAC;QAED,KAAK,CAAC,IAAI;YACR,MAAM,eAAe,CAAC;QACxB,CAAC;QAED,YAAY;YACV,IAAI,kBAAkB,KAAK,IAAI;gBAAE,OAAO,YAAY,CAAC;YACrD,OAAO;gBACL,GAAG,YAAY;gBACf,gBAAgB,EAAE;oBAChB,YAAY,EAAE,aAAa;oBAC3B,MAAM,EAAE,6CAA6C,kBAAkB,gDAAgD;iBACxH;aACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads one process-output line for the `REQUEST_MARKER` prefix.
|
|
3
|
+
*
|
|
4
|
+
* The marker comes from `request-marker.ts`, never from
|
|
5
|
+
* `fetch-instrumentation.ts` — importing the emitter to get a string would
|
|
6
|
+
* patch the parsing process's own `globalThis.fetch`. See `request-marker.ts`
|
|
7
|
+
* for the measurement. Everything else — the application's own real
|
|
8
|
+
* stdout/stderr — is not this parser's concern and is left alone; an
|
|
9
|
+
* external-request collector does not also try to be a log collector.
|
|
10
|
+
*/
|
|
11
|
+
import { REQUEST_MARKER, type CapturedRequest } from "./request-marker.ts";
|
|
12
|
+
/**
|
|
13
|
+
* `null` for a non-matching line (ordinary application output) AND for a
|
|
14
|
+
* line that starts with the marker but fails to parse as one of this
|
|
15
|
+
* package's own captured requests — a malformed marker line is refused,
|
|
16
|
+
* never guessed at, the same distinction `parseQueryLine`
|
|
17
|
+
* (`@descryy/runtime-database-observation`) makes.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseRequestLine(line: string): CapturedRequest | null;
|
|
20
|
+
export type { CapturedRequest };
|
|
21
|
+
export { REQUEST_MARKER };
|
|
22
|
+
//# sourceMappingURL=request-line-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-line-parser.d.ts","sourceRoot":"","sources":["../src/request-line-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAsB3E;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAcrE;AAED,YAAY,EAAE,eAAe,EAAE,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads one process-output line for the `REQUEST_MARKER` prefix.
|
|
3
|
+
*
|
|
4
|
+
* The marker comes from `request-marker.ts`, never from
|
|
5
|
+
* `fetch-instrumentation.ts` — importing the emitter to get a string would
|
|
6
|
+
* patch the parsing process's own `globalThis.fetch`. See `request-marker.ts`
|
|
7
|
+
* for the measurement. Everything else — the application's own real
|
|
8
|
+
* stdout/stderr — is not this parser's concern and is left alone; an
|
|
9
|
+
* external-request collector does not also try to be a log collector.
|
|
10
|
+
*/
|
|
11
|
+
import { REQUEST_MARKER } from "./request-marker.js";
|
|
12
|
+
function isCapturedRequest(value) {
|
|
13
|
+
if (typeof value !== "object" || value === null)
|
|
14
|
+
return false;
|
|
15
|
+
const v = value;
|
|
16
|
+
if (typeof v.url !== "string" || typeof v.method !== "string")
|
|
17
|
+
return false;
|
|
18
|
+
if (typeof v.durationMs !== "number" || typeof v.success !== "boolean")
|
|
19
|
+
return false;
|
|
20
|
+
if (v.status !== null && typeof v.status !== "number")
|
|
21
|
+
return false;
|
|
22
|
+
if (v.error !== null && typeof v.error !== "string")
|
|
23
|
+
return false;
|
|
24
|
+
if (typeof v.requestHeaders !== "object" || v.requestHeaders === null)
|
|
25
|
+
return false;
|
|
26
|
+
// `stack` is the one field allowed to be absent. A marker line written by a
|
|
27
|
+
// preload from before call-site capture existed is a well-formed line that
|
|
28
|
+
// genuinely carries no stack -- normalised to `null` below, which is what
|
|
29
|
+
// "no stack was captured" already means everywhere downstream. Anything
|
|
30
|
+
// present but not a string is malformed and refused, same as every other
|
|
31
|
+
// field here.
|
|
32
|
+
if (v.stack !== undefined && v.stack !== null && typeof v.stack !== "string")
|
|
33
|
+
return false;
|
|
34
|
+
return Object.values(v.requestHeaders).every((value) => typeof value === "string");
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* `null` for a non-matching line (ordinary application output) AND for a
|
|
38
|
+
* line that starts with the marker but fails to parse as one of this
|
|
39
|
+
* package's own captured requests — a malformed marker line is refused,
|
|
40
|
+
* never guessed at, the same distinction `parseQueryLine`
|
|
41
|
+
* (`@descryy/runtime-database-observation`) makes.
|
|
42
|
+
*/
|
|
43
|
+
export function parseRequestLine(line) {
|
|
44
|
+
if (!line.startsWith(`${REQUEST_MARKER} `))
|
|
45
|
+
return null;
|
|
46
|
+
const json = line.slice(REQUEST_MARKER.length + 1);
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = JSON.parse(json);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
if (!isCapturedRequest(parsed))
|
|
55
|
+
return null;
|
|
56
|
+
// Absent `stack` becomes an explicit `null` so no consumer has to
|
|
57
|
+
// distinguish "the field was missing" from "nothing was captured" -- they
|
|
58
|
+
// are the same fact and only one of them is representable downstream.
|
|
59
|
+
return { ...parsed, stack: parsed.stack ?? null };
|
|
60
|
+
}
|
|
61
|
+
export { REQUEST_MARKER };
|
|
62
|
+
//# sourceMappingURL=request-line-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-line-parser.js","sourceRoot":"","sources":["../src/request-line-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,cAAc,EAAwB,MAAM,qBAAqB,CAAC;AAI3E,SAAS,iBAAiB,CAAC,KAAc;IACvC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,CAAC,GAAG,KAAgC,CAAC;IAC3C,IAAI,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5E,IAAI,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACrF,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACpE,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAClE,IAAI,OAAO,CAAC,CAAC,cAAc,KAAK,QAAQ,IAAI,CAAC,CAAC,cAAc,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACpF,4EAA4E;IAC5E,2EAA2E;IAC3E,0EAA0E;IAC1E,wEAAwE;IACxE,yEAAyE;IACzE,cAAc;IACd,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC3F,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,cAAyC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC;AAChH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,cAAc,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACxD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACnD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,kEAAkE;IAClE,0EAA0E;IAC1E,sEAAsE;IACtE,OAAO,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;AACpD,CAAC;AAGD,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The wire vocabulary shared by the two halves of external-service
|
|
3
|
+
* observation: the marker prefix and the shape that follows it.
|
|
4
|
+
*
|
|
5
|
+
* This module exists to be **free of side effects**, and that is its entire
|
|
6
|
+
* job. `fetch-instrumentation.ts` — the other holder of these names until now
|
|
7
|
+
* — replaces `globalThis.fetch` at module scope and writes to stdout, which
|
|
8
|
+
* is right for a `--import` preload inside an observed process and wrong
|
|
9
|
+
* everywhere else. `request-line-parser.ts` needed nothing from it but the
|
|
10
|
+
* string `"DESCRY_EXTERNAL_REQUEST"`, and importing a string is still
|
|
11
|
+
* importing a module: the patch and the stdout writer came along, through the
|
|
12
|
+
* package index, into every consumer.
|
|
13
|
+
*
|
|
14
|
+
* **This is `query-marker.ts`'s defect, one package over, and it was still
|
|
15
|
+
* open.** `@descryy/runtime-database-observation` found the identical shape,
|
|
16
|
+
* measured it into `@descryy/mcp`'s JSON-RPC transport — 14,906 marker lines
|
|
17
|
+
* in one `analyze` call — and carved the vocabulary out. Nothing did the same
|
|
18
|
+
* here, and nothing noticed, because nothing outside this repository imports
|
|
19
|
+
* this package yet. `observe_runtime` consuming this collector is precisely
|
|
20
|
+
* the change that would have noticed.
|
|
21
|
+
*
|
|
22
|
+
* So: the emitter imports the vocabulary, the parser imports the vocabulary,
|
|
23
|
+
* and neither imports the other. Anything added here must stay a constant or
|
|
24
|
+
* a type. A function with a module-scope effect would rebuild the same trap
|
|
25
|
+
* one file over.
|
|
26
|
+
*/
|
|
27
|
+
export declare const REQUEST_MARKER = "DESCRY_EXTERNAL_REQUEST";
|
|
28
|
+
export interface CapturedRequest {
|
|
29
|
+
readonly url: string;
|
|
30
|
+
readonly method: string;
|
|
31
|
+
readonly requestHeaders: Readonly<Record<string, string>>;
|
|
32
|
+
/** `null` only when `success` is `false` -- the call never resolved to a response at all. */
|
|
33
|
+
readonly status: number | null;
|
|
34
|
+
readonly durationMs: number;
|
|
35
|
+
/** `true` iff `fetch` resolved rather than threw. An HTTP error status (4xx/5xx) is still `success: true` -- fetch itself does not reject on one. */
|
|
36
|
+
readonly success: boolean;
|
|
37
|
+
readonly error: string | null;
|
|
38
|
+
/**
|
|
39
|
+
* The **application's** own stack at the moment it called `fetch`, raw and
|
|
40
|
+
* unparsed, with the preload's own wrapper frames removed. `null` when the
|
|
41
|
+
* runtime produced no stack at all, or when nothing but the preload's own
|
|
42
|
+
* frames was in it.
|
|
43
|
+
*
|
|
44
|
+
* Raw text on purpose: parsing V8's format is
|
|
45
|
+
* `@descryy/runtime-adapter-typescript`'s job and belongs in the observing
|
|
46
|
+
* process, the same read-here/interpret-there split every marker in this
|
|
47
|
+
* repository already uses.
|
|
48
|
+
*/
|
|
49
|
+
readonly stack: string | null;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=request-marker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-marker.d.ts","sourceRoot":"","sources":["../src/request-marker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAOH,eAAO,MAAM,cAAc,4BAA8C,CAAC;AAE1E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1D,6FAA6F;IAC7F,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,qJAAqJ;IACrJ,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The wire vocabulary shared by the two halves of external-service
|
|
3
|
+
* observation: the marker prefix and the shape that follows it.
|
|
4
|
+
*
|
|
5
|
+
* This module exists to be **free of side effects**, and that is its entire
|
|
6
|
+
* job. `fetch-instrumentation.ts` — the other holder of these names until now
|
|
7
|
+
* — replaces `globalThis.fetch` at module scope and writes to stdout, which
|
|
8
|
+
* is right for a `--import` preload inside an observed process and wrong
|
|
9
|
+
* everywhere else. `request-line-parser.ts` needed nothing from it but the
|
|
10
|
+
* string `"DESCRY_EXTERNAL_REQUEST"`, and importing a string is still
|
|
11
|
+
* importing a module: the patch and the stdout writer came along, through the
|
|
12
|
+
* package index, into every consumer.
|
|
13
|
+
*
|
|
14
|
+
* **This is `query-marker.ts`'s defect, one package over, and it was still
|
|
15
|
+
* open.** `@descryy/runtime-database-observation` found the identical shape,
|
|
16
|
+
* measured it into `@descryy/mcp`'s JSON-RPC transport — 14,906 marker lines
|
|
17
|
+
* in one `analyze` call — and carved the vocabulary out. Nothing did the same
|
|
18
|
+
* here, and nothing noticed, because nothing outside this repository imports
|
|
19
|
+
* this package yet. `observe_runtime` consuming this collector is precisely
|
|
20
|
+
* the change that would have noticed.
|
|
21
|
+
*
|
|
22
|
+
* So: the emitter imports the vocabulary, the parser imports the vocabulary,
|
|
23
|
+
* and neither imports the other. Anything added here must stay a constant or
|
|
24
|
+
* a type. A function with a module-scope effect would rebuild the same trap
|
|
25
|
+
* one file over.
|
|
26
|
+
*/
|
|
27
|
+
import { RESERVED_MARKER_PREFIX } from "@descryy/runtime-contracts";
|
|
28
|
+
// Derived, not spelled again: `RESERVED_MARKER_PREFIX` is what the
|
|
29
|
+
// backend log collector skips on, so a marker that did not use it would
|
|
30
|
+
// come back as the application's own log output.
|
|
31
|
+
export const REQUEST_MARKER = `${RESERVED_MARKER_PREFIX}EXTERNAL_REQUEST`;
|
|
32
|
+
//# sourceMappingURL=request-marker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-marker.js","sourceRoot":"","sources":["../src/request-marker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEpE,mEAAmE;AACnE,wEAAwE;AACxE,iDAAiD;AACjD,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,sBAAsB,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A captured request's "service identity" — the checklist's §18 own
|
|
3
|
+
* `service identity` row — is its URL's hostname, and nothing more.
|
|
4
|
+
*
|
|
5
|
+
* **Declared, never inferred.** This does not attempt to recognise "this
|
|
6
|
+
* hostname is Stripe" or classify a call into `authentication provider` /
|
|
7
|
+
* `payment provider` / etc. (§18's own "Potential integrations" list) —
|
|
8
|
+
* that would be a lookup table of well-known hosts this project has no
|
|
9
|
+
* source of truth for, guessed rather than read off anything the
|
|
10
|
+
* application declared. The hostname is a real, observed fact; a provider
|
|
11
|
+
* label would not be. If a consumer wants a friendlier name for a known
|
|
12
|
+
* host, that is a later, disclosed classification step layered on top of
|
|
13
|
+
* this, not something this function silently does.
|
|
14
|
+
*
|
|
15
|
+
* `null` for a URL that does not parse — refused rather than guessed, the
|
|
16
|
+
* same shape `extractTargetTable`
|
|
17
|
+
* (`@descryy/runtime-database-observation`) refuses a SQL statement it does
|
|
18
|
+
* not recognise.
|
|
19
|
+
*/
|
|
20
|
+
export declare function extractServiceIdentity(url: string): string | null;
|
|
21
|
+
//# sourceMappingURL=service-identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-identity.d.ts","sourceRoot":"","sources":["../src/service-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMjE"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A captured request's "service identity" — the checklist's §18 own
|
|
3
|
+
* `service identity` row — is its URL's hostname, and nothing more.
|
|
4
|
+
*
|
|
5
|
+
* **Declared, never inferred.** This does not attempt to recognise "this
|
|
6
|
+
* hostname is Stripe" or classify a call into `authentication provider` /
|
|
7
|
+
* `payment provider` / etc. (§18's own "Potential integrations" list) —
|
|
8
|
+
* that would be a lookup table of well-known hosts this project has no
|
|
9
|
+
* source of truth for, guessed rather than read off anything the
|
|
10
|
+
* application declared. The hostname is a real, observed fact; a provider
|
|
11
|
+
* label would not be. If a consumer wants a friendlier name for a known
|
|
12
|
+
* host, that is a later, disclosed classification step layered on top of
|
|
13
|
+
* this, not something this function silently does.
|
|
14
|
+
*
|
|
15
|
+
* `null` for a URL that does not parse — refused rather than guessed, the
|
|
16
|
+
* same shape `extractTargetTable`
|
|
17
|
+
* (`@descryy/runtime-database-observation`) refuses a SQL statement it does
|
|
18
|
+
* not recognise.
|
|
19
|
+
*/
|
|
20
|
+
export function extractServiceIdentity(url) {
|
|
21
|
+
try {
|
|
22
|
+
return new URL(url).hostname;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=service-identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-identity.js","sourceRoot":"","sources":["../src/service-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAAW;IAChD,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@descryy/runtime-external-service-observation",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "External service observation (plan §18, checklist §18): real EXTERNAL_REQUEST capture via client instrumentation, never parsed application logs. Client #1 is the global fetch.",
|
|
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.1.0",
|
|
28
|
+
"@descryy/runtime-backend-observation": "0.1.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@descryy/runtime-controller": "*",
|
|
32
|
+
"@descryy/runtime-orchestrator": "*",
|
|
33
|
+
"@descryy/runtime-adapter-typescript": "*"
|
|
34
|
+
}
|
|
35
|
+
}
|