@descryy/runtime-database-observation 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.
@@ -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,32 @@
1
+ /**
2
+ * §10/§18's "graph correlation" row (RT-141's own disclosed gap, closed
3
+ * here): resolving an observed `DATABASE_QUERY`'s captured table name to
4
+ * the real `DATABASE_TABLE` graph node `resolveDatabaseTableNode`
5
+ * (`@descryy/runtime-graph-correlator`) already knows how to find.
6
+ *
7
+ * Same shape as `test-runner`'s own `resolveObservedTestCase`
8
+ * (`graph-mapping.ts`), deliberately: a thin per-package translation, not a
9
+ * new abstraction, and it does not call `EvidenceStore.attribute()` itself
10
+ * — that needs the evidence's real `evidenceId`, which only exists after
11
+ * `store.write()` has returned it, outside this collector's own reach, same
12
+ * as every other collector's `graphNodeId: null`. The caller that already
13
+ * holds both the store and the graph `driver` attributes; this only
14
+ * resolves.
15
+ */
16
+ import type { SqlDriver } from "@descryy/core";
17
+ import { type DatabaseTableResolution } from "@descryy/runtime-graph-correlator";
18
+ import type { Evidence } from "@descryy/runtime-contracts";
19
+ export type ObservedDatabaseQueryResolution = DatabaseTableResolution | {
20
+ readonly outcome: "no-table";
21
+ readonly reason: string;
22
+ };
23
+ /**
24
+ * Refuses (`"no-table"`, never throws) for anything that isn't
25
+ * `DATABASE_QUERY` evidence with a real `payload.table` — the same
26
+ * "wrong input, refuse rather than guess" shape every resolver in
27
+ * `graph-correlator` already uses.
28
+ */
29
+ export declare function resolveObservedDatabaseQuery(driver: SqlDriver, evidence: Pick<Evidence, "eventType" | "payload">, options?: {
30
+ readonly limit?: number;
31
+ }): ObservedDatabaseQueryResolution;
32
+ //# sourceMappingURL=graph-mapping.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-mapping.d.ts","sourceRoot":"","sources":["../src/graph-mapping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAA4B,KAAK,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AAC3G,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAE3D,MAAM,MAAM,+BAA+B,GACvC,uBAAuB,GACvB;IAAE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9D;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,WAAW,GAAG,SAAS,CAAC,EACjD,OAAO,GAAE;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACxC,+BAA+B,CAkBjC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * §10/§18's "graph correlation" row (RT-141's own disclosed gap, closed
3
+ * here): resolving an observed `DATABASE_QUERY`'s captured table name to
4
+ * the real `DATABASE_TABLE` graph node `resolveDatabaseTableNode`
5
+ * (`@descryy/runtime-graph-correlator`) already knows how to find.
6
+ *
7
+ * Same shape as `test-runner`'s own `resolveObservedTestCase`
8
+ * (`graph-mapping.ts`), deliberately: a thin per-package translation, not a
9
+ * new abstraction, and it does not call `EvidenceStore.attribute()` itself
10
+ * — that needs the evidence's real `evidenceId`, which only exists after
11
+ * `store.write()` has returned it, outside this collector's own reach, same
12
+ * as every other collector's `graphNodeId: null`. The caller that already
13
+ * holds both the store and the graph `driver` attributes; this only
14
+ * resolves.
15
+ */
16
+ import { resolveDatabaseTableNode } from "@descryy/runtime-graph-correlator";
17
+ /**
18
+ * Refuses (`"no-table"`, never throws) for anything that isn't
19
+ * `DATABASE_QUERY` evidence with a real `payload.table` — the same
20
+ * "wrong input, refuse rather than guess" shape every resolver in
21
+ * `graph-correlator` already uses.
22
+ */
23
+ export function resolveObservedDatabaseQuery(driver, evidence, options = {}) {
24
+ if (evidence.eventType !== "DATABASE_QUERY") {
25
+ return {
26
+ outcome: "no-table",
27
+ reason: `evidence.eventType is "${evidence.eventType}", not "DATABASE_QUERY" -- resolveObservedDatabaseQuery only knows how to map DatabaseQueryCollector evidence.`,
28
+ };
29
+ }
30
+ const payload = evidence.payload;
31
+ const table = typeof payload.table === "string" ? payload.table : null;
32
+ if (table === null) {
33
+ return {
34
+ outcome: "no-table",
35
+ reason: "evidence.payload.table is null or missing -- extractTargetTable could not name a table for this query " +
36
+ "(a JOIN, a comma-join, a CTE, or a statement it doesn't recognise).",
37
+ };
38
+ }
39
+ return resolveDatabaseTableNode(driver, table, options);
40
+ }
41
+ //# sourceMappingURL=graph-mapping.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-mapping.js","sourceRoot":"","sources":["../src/graph-mapping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAE,wBAAwB,EAAgC,MAAM,mCAAmC,CAAC;AAO3G;;;;;GAKG;AACH,MAAM,UAAU,4BAA4B,CAC1C,MAAiB,EACjB,QAAiD,EACjD,UAAuC,EAAE;IAEzC,IAAI,QAAQ,CAAC,SAAS,KAAK,gBAAgB,EAAE,CAAC;QAC5C,OAAO;YACL,OAAO,EAAE,UAAU;YACnB,MAAM,EAAE,0BAA0B,QAAQ,CAAC,SAAS,gHAAgH;SACrK,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAuC,CAAC;IACjE,MAAM,KAAK,GAAG,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IACvE,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,OAAO;YACL,OAAO,EAAE,UAAU;YACnB,MAAM,EACJ,wGAAwG;gBACxG,qEAAqE;SACxE,CAAC;IACJ,CAAC;IACD,OAAO,wBAAwB,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,8 @@
1
+ export { extractTargetTable } from "./table-extraction.ts";
2
+ export { QUERY_MARKER, type CapturedMethod, type CapturedQuery } from "./query-line-parser.ts";
3
+ export { parseQueryLine } from "./query-line-parser.ts";
4
+ export type { DatabaseQueryCollectorOptions } from "./query-collector.ts";
5
+ export { createDatabaseQueryCollector, SQLITE_PRELOAD_MODULE_PATH } from "./query-collector.ts";
6
+ export type { ObservedDatabaseQueryResolution } from "./graph-mapping.ts";
7
+ export { resolveObservedDatabaseQuery } from "./graph-mapping.ts";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC/F,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,YAAY,EAAE,6BAA6B,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,4BAA4B,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAEhG,YAAY,EAAE,+BAA+B,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { extractTargetTable } from "./table-extraction.js";
2
+ export { QUERY_MARKER } from "./query-line-parser.js";
3
+ export { parseQueryLine } from "./query-line-parser.js";
4
+ export { createDatabaseQueryCollector, SQLITE_PRELOAD_MODULE_PATH } from "./query-collector.js";
5
+ export { resolveObservedDatabaseQuery } from "./graph-mapping.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAE,YAAY,EAA2C,MAAM,wBAAwB,CAAC;AAC/F,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAGxD,OAAO,EAAE,4BAA4B,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAGhG,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `DatabaseQueryCollector` (plan §18 / checklist §14): translates
3
+ * `sqlite-instrumentation.ts`'s captured queries into `DATABASE_QUERY`
4
+ * Evidence. Same shape as `LogCollector` (`@descryy/runtime-backend-observation`)
5
+ * deliberately — a `ProcessOutputSource` in, `Evidence` out, streaming-first,
6
+ * a collector failure reported as evidence rather than swallowed — but for
7
+ * a different marker on the same stream.
8
+ *
9
+ * **What this collector does not do, named rather than silently absent:**
10
+ * no transaction-boundary tracking (BEGIN/COMMIT/ROLLBACK are captured as
11
+ * ordinary queries, not grouped) — a real, still-open §14/§18 row.
12
+ *
13
+ * **`graphNodeId` on emitted evidence is still always null here, same as
14
+ * every other collector** (RT-076's own precedent, `test-runner-collector.ts`)
15
+ * — `graph-mapping.ts`'s `resolveObservedDatabaseQuery` is what a caller
16
+ * holding both the store and the graph driver uses to resolve a captured
17
+ * table name to the real `DATABASE_TABLE` node and attribute it, after
18
+ * `write()`, the same way `vertical-slice` already does for every other
19
+ * evidence type. The resolver itself (RT-146) is real; this collector
20
+ * simply isn't the one that calls it.
21
+ */
22
+ import type { Collector } from "@descryy/runtime-contracts";
23
+ import { type ProcessOutputSource } from "@descryy/runtime-backend-observation";
24
+ /** 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. */
25
+ export declare const SQLITE_PRELOAD_MODULE_PATH: string;
26
+ export interface DatabaseQueryCollectorOptions {
27
+ readonly source: ProcessOutputSource;
28
+ /** Which owned system this collector's evidence should be attributed to (plan §16.7). Null when not applicable. */
29
+ readonly service?: string;
30
+ }
31
+ export declare function createDatabaseQueryCollector(options: DatabaseQueryCollectorOptions): Collector;
32
+ //# sourceMappingURL=query-collector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-collector.d.ts","sourceRoot":"","sources":["../src/query-collector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAmF,MAAM,4BAA4B,CAAC;AAC7I,OAAO,EAAyB,KAAK,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAMvG,iNAAiN;AACjN,eAAO,MAAM,0BAA0B,QAAyE,CAAC;AAEjH,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,mHAAmH;IACnH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AA2BD,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,6BAA6B,GAAG,SAAS,CAkG9F"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * `DatabaseQueryCollector` (plan §18 / checklist §14): translates
3
+ * `sqlite-instrumentation.ts`'s captured queries into `DATABASE_QUERY`
4
+ * Evidence. Same shape as `LogCollector` (`@descryy/runtime-backend-observation`)
5
+ * deliberately — a `ProcessOutputSource` in, `Evidence` out, streaming-first,
6
+ * a collector failure reported as evidence rather than swallowed — but for
7
+ * a different marker on the same stream.
8
+ *
9
+ * **What this collector does not do, named rather than silently absent:**
10
+ * no transaction-boundary tracking (BEGIN/COMMIT/ROLLBACK are captured as
11
+ * ordinary queries, not grouped) — a real, still-open §14/§18 row.
12
+ *
13
+ * **`graphNodeId` on emitted evidence is still always null here, same as
14
+ * every other collector** (RT-076's own precedent, `test-runner-collector.ts`)
15
+ * — `graph-mapping.ts`'s `resolveObservedDatabaseQuery` is what a caller
16
+ * holding both the store and the graph driver uses to resolve a captured
17
+ * table name to the real `DATABASE_TABLE` node and attribute it, after
18
+ * `write()`, the same way `vertical-slice` already does for every other
19
+ * evidence type. The resolver itself (RT-146) is real; this collector
20
+ * simply isn't the one that calls it.
21
+ */
22
+ import { fileURLToPath } from "node:url";
23
+ import { extractCorrelationIds } from "@descryy/runtime-backend-observation";
24
+ import { parseQueryLine } from "./query-line-parser.js";
25
+ import { extractTargetTable } from "./table-extraction.js";
26
+ import { COLLECTOR_VERSION } from "./collector-version.js";
27
+ /** 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. */
28
+ export const SQLITE_PRELOAD_MODULE_PATH = fileURLToPath(new URL("./sqlite-instrumentation.js", import.meta.url));
29
+ const EVENT_TYPE = "DATABASE_QUERY";
30
+ function computeCapabilities() {
31
+ const reason = "DatabaseQueryCollector observes DATABASE_QUERY events only.";
32
+ const status = { availability: "unavailable", reason };
33
+ return {
34
+ domObservation: status,
35
+ consoleObservation: status,
36
+ networkObservation: status,
37
+ backendLogAccess: status,
38
+ distributedTrace: {
39
+ availability: "degraded",
40
+ reason: "extracts trace-id/request-id from the query text itself (a declared SQL comment convention, same mechanism LogCollector uses on process output); does not observe application-level propagation.",
41
+ },
42
+ sourceMapping: status,
43
+ stackCapture: status,
44
+ processLifecycle: status,
45
+ // The field this collector's own RT-141 entry named as missing and
46
+ // deferred (RT-142) -- closed here (RT-146), alongside the field's
47
+ // own addition to the contract.
48
+ databaseObservation: { availability: "available", reason: null },
49
+ externalServiceObservation: { availability: "unavailable", reason: "DatabaseQueryCollector instruments node:sqlite only; outbound requests are ExternalRequestCollector's to observe (RT-146)." },
50
+ };
51
+ }
52
+ export function createDatabaseQueryCollector(options) {
53
+ const capabilities = computeCapabilities();
54
+ let consumeLoopDone;
55
+ let consumptionFailure = null;
56
+ function emitCapturedQuery(query, context) {
57
+ const { traceId, requestId } = extractCorrelationIds(query.sql);
58
+ context.emit({
59
+ timestamp: new Date().toISOString(),
60
+ source: "backend-process",
61
+ service: options.service ?? null,
62
+ process: options.source.processId,
63
+ eventType: EVENT_TYPE,
64
+ payload: {
65
+ raw: query.sql,
66
+ method: query.method,
67
+ durationMs: query.durationMs,
68
+ success: query.success,
69
+ error: query.error,
70
+ table: extractTargetTable(query.sql),
71
+ },
72
+ traceId,
73
+ requestId,
74
+ correlationId: null,
75
+ // Resolved later, by a caller holding the store and driver --
76
+ // resolveObservedDatabaseQuery (graph-mapping.ts), not here. Same
77
+ // split every other collector's graphNodeId already takes.
78
+ graphNodeId: null,
79
+ sourceLocation: null,
80
+ stackTrace: null,
81
+ // The observation itself (a real captured call, real timing, real
82
+ // success/failure) is not probabilistic.
83
+ confidence: 1,
84
+ redactionStatus: "pending-redaction",
85
+ collectorVersion: COLLECTOR_VERSION,
86
+ });
87
+ }
88
+ async function consume(context) {
89
+ for await (const line of options.source.lines) {
90
+ const query = parseQueryLine(line.text);
91
+ if (query !== null)
92
+ emitCapturedQuery(query, context);
93
+ }
94
+ }
95
+ return {
96
+ collectorId: `database-query-collector:${options.source.processId}`,
97
+ start(context) {
98
+ consumeLoopDone = consume(context).catch((error) => {
99
+ const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
100
+ consumptionFailure = detail;
101
+ try {
102
+ context.emit({
103
+ timestamp: new Date().toISOString(),
104
+ source: "backend-process",
105
+ service: options.service ?? null,
106
+ process: options.source.processId,
107
+ eventType: "COLLECTOR_ERROR",
108
+ payload: {
109
+ raw: `database query collector for process "${options.source.processId}" stopped consuming after an error: ${detail}`,
110
+ collectorId: `database-query-collector:${options.source.processId}`,
111
+ error: detail,
112
+ },
113
+ traceId: null,
114
+ requestId: null,
115
+ correlationId: null,
116
+ graphNodeId: null,
117
+ sourceLocation: null,
118
+ stackTrace: null,
119
+ confidence: 1,
120
+ redactionStatus: "pending-redaction",
121
+ collectorVersion: COLLECTOR_VERSION,
122
+ });
123
+ }
124
+ catch (emitError) {
125
+ console.error(`[database-query-collector:${options.source.processId}] consumption failed (${detail}) AND the failure could not be emitted: ${String(emitError)}`);
126
+ }
127
+ });
128
+ return Promise.resolve({ available: true });
129
+ },
130
+ async stop() {
131
+ await consumeLoopDone;
132
+ },
133
+ capabilities() {
134
+ if (consumptionFailure === null)
135
+ return capabilities;
136
+ return {
137
+ ...capabilities,
138
+ distributedTrace: {
139
+ availability: "unavailable",
140
+ reason: `consumption stopped early after an error (${consumptionFailure}); output after that point was never observed.`,
141
+ },
142
+ };
143
+ },
144
+ };
145
+ }
146
+ //# sourceMappingURL=query-collector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-collector.js","sourceRoot":"","sources":["../src/query-collector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Reads one process-output line for `sqlite-instrumentation.ts`'s own
3
+ * `QUERY_MARKER` prefix. Everything else — the application's own real
4
+ * stdout/stderr — is not this parser's concern and is left alone; a
5
+ * database collector does not also try to be a log collector.
6
+ */
7
+ import { QUERY_MARKER, type CapturedMethod, type CapturedQuery } from "./sqlite-instrumentation.ts";
8
+ /**
9
+ * `null` for a non-matching line (ordinary application output) AND for a
10
+ * line that starts with the marker but fails to parse as one of this
11
+ * package's own captured queries — a malformed marker line is refused,
12
+ * never guessed at, the same "wrong shape, not a wrong value" distinction
13
+ * `test-runner`'s own `isNodeTestEvent` makes.
14
+ */
15
+ export declare function parseQueryLine(line: string): CapturedQuery | null;
16
+ export type { CapturedMethod, CapturedQuery };
17
+ export { QUERY_MARKER };
18
+ //# sourceMappingURL=query-line-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-line-parser.d.ts","sourceRoot":"","sources":["../src/query-line-parser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAcpG;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAUjE;AAED,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Reads one process-output line for `sqlite-instrumentation.ts`'s own
3
+ * `QUERY_MARKER` prefix. Everything else — the application's own real
4
+ * stdout/stderr — is not this parser's concern and is left alone; a
5
+ * database collector does not also try to be a log collector.
6
+ */
7
+ import { QUERY_MARKER } from "./sqlite-instrumentation.js";
8
+ function isCapturedQuery(value) {
9
+ if (typeof value !== "object" || value === null)
10
+ return false;
11
+ const v = value;
12
+ return (typeof v.sql === "string" &&
13
+ typeof v.method === "string" &&
14
+ typeof v.durationMs === "number" &&
15
+ typeof v.success === "boolean" &&
16
+ (v.error === null || typeof v.error === "string"));
17
+ }
18
+ /**
19
+ * `null` for a non-matching line (ordinary application output) AND for a
20
+ * line that starts with the marker but fails to parse as one of this
21
+ * package's own captured queries — a malformed marker line is refused,
22
+ * never guessed at, the same "wrong shape, not a wrong value" distinction
23
+ * `test-runner`'s own `isNodeTestEvent` makes.
24
+ */
25
+ export function parseQueryLine(line) {
26
+ if (!line.startsWith(`${QUERY_MARKER} `))
27
+ return null;
28
+ const json = line.slice(QUERY_MARKER.length + 1);
29
+ let parsed;
30
+ try {
31
+ parsed = JSON.parse(json);
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ return isCapturedQuery(parsed) ? parsed : null;
37
+ }
38
+ export { QUERY_MARKER };
39
+ //# sourceMappingURL=query-line-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-line-parser.js","sourceRoot":"","sources":["../src/query-line-parser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAA2C,MAAM,6BAA6B,CAAC;AAEpG,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,CAAC,GAAG,KAAgC,CAAC;IAC3C,OAAO,CACL,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ;QACzB,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ;QAC5B,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ;QAChC,OAAO,CAAC,CAAC,OAAO,KAAK,SAAS;QAC9B,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAClD,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,YAAY,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACjD,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,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC;AAGD,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * `--import`-loaded preload: patches `node:sqlite`'s `DatabaseSync`
3
+ * prototype before the target application's own code runs, so every real
4
+ * query the application issues through its own `import { DatabaseSync }
5
+ * from "node:sqlite"` is observed — proven directly (a probe script, not
6
+ * assumed): Node's built-in modules are a process-wide singleton, so a
7
+ * `--import` preload patching `DatabaseSync.prototype` here and an
8
+ * application's completely separate `import` of the same built-in later
9
+ * see the same prototype object.
10
+ *
11
+ * Deliberately minimal and import-free, like `test-runner`'s own
12
+ * `reporter.ts` — this runs inside the OBSERVED process, not this
13
+ * package's own, and is loaded by absolute path via `node --import`, never
14
+ * through this package's normal dependency graph. Interpretation (target
15
+ * table, correlation ids) happens on the collector side, in the observing
16
+ * process, not here — this only captures.
17
+ *
18
+ * One line of structured JSON per query, prefixed exactly like the
19
+ * benchmark harness's own `BENCH_RESULT {json}` convention (RT-072):
20
+ * scraping prose measures the prose, so nothing here is prose.
21
+ */
22
+ export declare const QUERY_MARKER = "DESCRY_DB_QUERY";
23
+ export type CapturedMethod = "exec" | "prepare" | "run" | "get" | "all";
24
+ export interface CapturedQuery {
25
+ readonly sql: string;
26
+ readonly method: CapturedMethod;
27
+ readonly durationMs: number;
28
+ readonly success: boolean;
29
+ readonly error: string | null;
30
+ }
31
+ //# sourceMappingURL=sqlite-instrumentation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-instrumentation.d.ts","sourceRoot":"","sources":["../src/sqlite-instrumentation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,eAAO,MAAM,YAAY,oBAAoB,CAAC;AAE9C,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAExE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * `--import`-loaded preload: patches `node:sqlite`'s `DatabaseSync`
3
+ * prototype before the target application's own code runs, so every real
4
+ * query the application issues through its own `import { DatabaseSync }
5
+ * from "node:sqlite"` is observed — proven directly (a probe script, not
6
+ * assumed): Node's built-in modules are a process-wide singleton, so a
7
+ * `--import` preload patching `DatabaseSync.prototype` here and an
8
+ * application's completely separate `import` of the same built-in later
9
+ * see the same prototype object.
10
+ *
11
+ * Deliberately minimal and import-free, like `test-runner`'s own
12
+ * `reporter.ts` — this runs inside the OBSERVED process, not this
13
+ * package's own, and is loaded by absolute path via `node --import`, never
14
+ * through this package's normal dependency graph. Interpretation (target
15
+ * table, correlation ids) happens on the collector side, in the observing
16
+ * process, not here — this only captures.
17
+ *
18
+ * One line of structured JSON per query, prefixed exactly like the
19
+ * benchmark harness's own `BENCH_RESULT {json}` convention (RT-072):
20
+ * scraping prose measures the prose, so nothing here is prose.
21
+ */
22
+ import { DatabaseSync } from "node:sqlite";
23
+ export const QUERY_MARKER = "DESCRY_DB_QUERY";
24
+ function emit(query) {
25
+ process.stdout.write(`${QUERY_MARKER} ${JSON.stringify(query)}\n`);
26
+ }
27
+ function timed(sql, method, run) {
28
+ const start = performance.now();
29
+ try {
30
+ const result = run();
31
+ emit({ sql, method, durationMs: performance.now() - start, success: true, error: null });
32
+ return result;
33
+ }
34
+ catch (error) {
35
+ const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
36
+ emit({ sql, method, durationMs: performance.now() - start, success: false, error: detail });
37
+ throw error;
38
+ }
39
+ }
40
+ // Guards against double-patching if this module is somehow loaded twice
41
+ // (e.g. both `--import` and a transitive re-import) -- the second pass
42
+ // would otherwise wrap an already-wrapped method and double-emit.
43
+ const PATCHED = Symbol.for("descry.database-observation.sqlite-patched");
44
+ const target = DatabaseSync.prototype;
45
+ if (target[PATCHED] !== true) {
46
+ target[PATCHED] = true;
47
+ const originalExec = DatabaseSync.prototype.exec;
48
+ DatabaseSync.prototype.exec = function patchedExec(sql, ...rest) {
49
+ return timed(sql, "exec", () => originalExec.call(this, sql, ...rest));
50
+ };
51
+ const originalPrepare = DatabaseSync.prototype.prepare;
52
+ DatabaseSync.prototype.prepare = function patchedPrepare(sql) {
53
+ const stmt = timed(sql, "prepare", () => originalPrepare.call(this, sql));
54
+ const stmtTarget = stmt;
55
+ for (const method of ["run", "get", "all"]) {
56
+ const original = stmtTarget[method];
57
+ stmtTarget[method] = function patchedStatementMethod(...args) {
58
+ return timed(sql, method, () => original.apply(stmt, args));
59
+ };
60
+ }
61
+ // `iterate()` deliberately left unwrapped: it returns a lazy generator,
62
+ // so "duration" would mean the time to build the iterator, not the
63
+ // time to consume it -- a different, misleading number under the same
64
+ // name. Disclosed here rather than measured wrong.
65
+ return stmt;
66
+ };
67
+ }
68
+ //# sourceMappingURL=sqlite-instrumentation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-instrumentation.js","sourceRoot":"","sources":["../src/sqlite-instrumentation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Extracts the single target table a DML statement names — deliberately
3
+ * not a SQL parser, same reasoning `adapter-sql`'s `parse.ts` gives for
4
+ * its own DDL-only scope: a bounded reader that recognises a few shapes
5
+ * and refuses (`null`) on everything else is honest; a reader that guesses
6
+ * at a JOIN or a subquery is not.
7
+ *
8
+ * `adapter-sql` (descry-adapters) reads CREATE/ALTER — schema declaration.
9
+ * This reads INSERT/UPDATE/DELETE/SELECT — a different problem (which
10
+ * table did a query at RUNTIME actually touch), so its own small reader
11
+ * rather than a false shared abstraction over two different questions.
12
+ *
13
+ * **Refused, not guessed, for:** a JOIN anywhere in the statement (which
14
+ * table is "the" table is genuinely ambiguous), a comma-separated FROM
15
+ * list (old-style implicit join, same ambiguity), a subquery in FROM, a
16
+ * CTE (`WITH ... AS`), and anything this reader does not recognise the
17
+ * shape of at all. The wrong-table negative control this package's tests
18
+ * assert on is exactly this: a two-table JOIN must resolve to `null`, not
19
+ * to either table picked arbitrarily.
20
+ */
21
+ /**
22
+ * `null` for anything not recognised as exactly one of INSERT/UPDATE/
23
+ * DELETE/SELECT with a single, unambiguous target table. Never a partial
24
+ * or best-effort name.
25
+ */
26
+ export declare function extractTargetTable(sql: string): string | null;
27
+ //# sourceMappingURL=table-extraction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table-extraction.d.ts","sourceRoot":"","sources":["../src/table-extraction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AA8BH;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAmB7D"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Extracts the single target table a DML statement names — deliberately
3
+ * not a SQL parser, same reasoning `adapter-sql`'s `parse.ts` gives for
4
+ * its own DDL-only scope: a bounded reader that recognises a few shapes
5
+ * and refuses (`null`) on everything else is honest; a reader that guesses
6
+ * at a JOIN or a subquery is not.
7
+ *
8
+ * `adapter-sql` (descry-adapters) reads CREATE/ALTER — schema declaration.
9
+ * This reads INSERT/UPDATE/DELETE/SELECT — a different problem (which
10
+ * table did a query at RUNTIME actually touch), so its own small reader
11
+ * rather than a false shared abstraction over two different questions.
12
+ *
13
+ * **Refused, not guessed, for:** a JOIN anywhere in the statement (which
14
+ * table is "the" table is genuinely ambiguous), a comma-separated FROM
15
+ * list (old-style implicit join, same ambiguity), a subquery in FROM, a
16
+ * CTE (`WITH ... AS`), and anything this reader does not recognise the
17
+ * shape of at all. The wrong-table negative control this package's tests
18
+ * assert on is exactly this: a two-table JOIN must resolve to `null`, not
19
+ * to either table picked arbitrarily.
20
+ */
21
+ const IDENTIFIER = String.raw `(?:\[[^\]]+\]|"[^"]+"|` + "`[^`]+`" + String.raw `|[A-Za-z_][A-Za-z0-9_]*)`;
22
+ const QUALIFIED = `${IDENTIFIER}(?:\\.${IDENTIFIER})?`;
23
+ const INSERT_INTO = new RegExp(`^\\s*INSERT\\s+INTO\\s+(${QUALIFIED})`, "i");
24
+ const UPDATE = new RegExp(`^\\s*UPDATE\\s+(${QUALIFIED})`, "i");
25
+ const DELETE_FROM = new RegExp(`^\\s*DELETE\\s+FROM\\s+(${QUALIFIED})`, "i");
26
+ const SELECT_FROM = new RegExp(`\\bFROM\\s+(${QUALIFIED})`, "i");
27
+ /** Anywhere in the statement — a JOIN or a CTE makes "the" target table an unresolvable question, not a hard one. */
28
+ const AMBIGUOUS = /\bJOIN\b|\bWITH\b\s+[\w"`[]/i;
29
+ function unquote(identifier) {
30
+ return identifier.replace(/^["`[]|["`\]]$/g, "");
31
+ }
32
+ /** Last segment of a possibly schema-qualified name (`main.widgets` -> `widgets`), matching `adapter-sql`'s own convention for the same question. */
33
+ function unqualify(raw) {
34
+ const parts = raw.split(".").map(unquote);
35
+ return parts.at(-1) ?? raw;
36
+ }
37
+ /** A comma directly after the FROM target, before any keyword, is the old-style implicit-join list — the same ambiguity a JOIN is, spelled differently. */
38
+ function hasCommaJoinAfter(sql, matchEnd) {
39
+ const rest = sql.slice(matchEnd);
40
+ const nextKeyword = /^\s*(,|WHERE|GROUP|ORDER|LIMIT|JOIN|;|$)/i.exec(rest);
41
+ return nextKeyword !== null && nextKeyword[1] === ",";
42
+ }
43
+ /**
44
+ * `null` for anything not recognised as exactly one of INSERT/UPDATE/
45
+ * DELETE/SELECT with a single, unambiguous target table. Never a partial
46
+ * or best-effort name.
47
+ */
48
+ export function extractTargetTable(sql) {
49
+ const trimmed = sql.trim();
50
+ if (AMBIGUOUS.test(trimmed))
51
+ return null;
52
+ const insert = INSERT_INTO.exec(trimmed);
53
+ if (insert !== null)
54
+ return unqualify(insert[1]);
55
+ const update = UPDATE.exec(trimmed);
56
+ if (update !== null)
57
+ return unqualify(update[1]);
58
+ const del = DELETE_FROM.exec(trimmed);
59
+ if (del !== null)
60
+ return unqualify(del[1]);
61
+ const select = SELECT_FROM.exec(trimmed);
62
+ if (select !== null && !hasCommaJoinAfter(trimmed, select.index + select[0].length)) {
63
+ return unqualify(select[1]);
64
+ }
65
+ return null;
66
+ }
67
+ //# sourceMappingURL=table-extraction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table-extraction.js","sourceRoot":"","sources":["../src/table-extraction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAA,wBAAwB,GAAG,SAAS,GAAG,MAAM,CAAC,GAAG,CAAA,0BAA0B,CAAC;AACzG,MAAM,SAAS,GAAG,GAAG,UAAU,SAAS,UAAU,IAAI,CAAC;AAEvD,MAAM,WAAW,GAAG,IAAI,MAAM,CAAC,2BAA2B,SAAS,GAAG,EAAE,GAAG,CAAC,CAAC;AAC7E,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,mBAAmB,SAAS,GAAG,EAAE,GAAG,CAAC,CAAC;AAChE,MAAM,WAAW,GAAG,IAAI,MAAM,CAAC,2BAA2B,SAAS,GAAG,EAAE,GAAG,CAAC,CAAC;AAC7E,MAAM,WAAW,GAAG,IAAI,MAAM,CAAC,eAAe,SAAS,GAAG,EAAE,GAAG,CAAC,CAAC;AAEjE,qHAAqH;AACrH,MAAM,SAAS,GAAG,8BAA8B,CAAC;AAEjD,SAAS,OAAO,CAAC,UAAkB;IACjC,OAAO,UAAU,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,qJAAqJ;AACrJ,SAAS,SAAS,CAAC,GAAW;IAC5B,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC1C,OAAO,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;AAC7B,CAAC;AAED,2JAA2J;AAC3J,SAAS,iBAAiB,CAAC,GAAW,EAAE,QAAgB;IACtD,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACjC,MAAM,WAAW,GAAG,2CAA2C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3E,OAAO,WAAW,KAAK,IAAI,IAAI,WAAW,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;AACxD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzC,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC;IAElD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC;IAElD,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtC,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,CAAC;IAE5C,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QACpF,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@descryy/runtime-database-observation",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "description": "Database query observation (plan §18, checklist §14): real DATABASE_QUERY capture via driver instrumentation, never parsed application logs. Driver #1 is node:sqlite.",
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": "*",
28
+ "@descryy/runtime-backend-observation": "*",
29
+ "@descryy/runtime-graph-correlator": "*"
30
+ },
31
+ "devDependencies": {
32
+ "@descryy/runtime-controller": "*",
33
+ "@descryy/runtime-orchestrator": "*"
34
+ }
35
+ }