@termwright/probe-runtime 0.2.0 → 0.3.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/README.md +2 -2
- package/dist/index.d.ts +6 -3
- package/dist/index.js +56 -34
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Framework-neutral, fail-closed socket transport shared by zero-config
|
|
4
4
|
JavaScript probes. It owns handshake validation, full-snapshot publication,
|
|
5
|
-
revision commits, marker authentication,
|
|
5
|
+
revision commits, marker authentication, limits and channel
|
|
6
6
|
shutdown.
|
|
7
7
|
|
|
8
8
|
It intentionally does not observe framework state or write markers. Only the
|
|
@@ -16,7 +16,7 @@ Collection turns on automatically with `TERMWRIGHT_DEBUG=1`/`all` or
|
|
|
16
16
|
`TERMWRIGHT_DEBUG_FILE`, and can be forced with `connectProbe({
|
|
17
17
|
performanceMetrics: true, ... })`. Disabled sessions do not call the timer.
|
|
18
18
|
|
|
19
|
-
The snapshot reports full snapshots,
|
|
19
|
+
The snapshot reports full snapshots, encoded semantic bytes, node and
|
|
20
20
|
generic-node counts, failed publications, requested markers and serialization
|
|
21
21
|
time, together with per-frame averages. Framework sessions also supply their
|
|
22
22
|
Probe IR fact count and report publications superseded in their marker queue.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Socket } from 'node:net';
|
|
2
|
+
import { EvidenceProviderRegistry, FrozenEvidenceProviderRegistry } from '@termwright/evidence-provider';
|
|
2
3
|
import { ProtocolId, ProtocolLimits, ProbeInfo, AdapterCapability, FrameDecoder, SemanticSnapshot } from '@termwright/protocol';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -24,7 +25,8 @@ interface ConnectOptions {
|
|
|
24
25
|
readonly capabilities: readonly AdapterCapability[];
|
|
25
26
|
readonly adapterName: string;
|
|
26
27
|
readonly adapterVersion: string;
|
|
27
|
-
|
|
28
|
+
/** Isolated application provider registry. Defaults to the process facade. */
|
|
29
|
+
readonly evidenceProviderRegistry?: EvidenceProviderRegistry;
|
|
28
30
|
/** Milliseconds for connect plus handshake. Default 1000. */
|
|
29
31
|
readonly handshakeTimeoutMs?: number;
|
|
30
32
|
/**
|
|
@@ -44,7 +46,6 @@ interface ConnectOptions {
|
|
|
44
46
|
interface ProbePerformanceMetrics {
|
|
45
47
|
readonly enabled: boolean;
|
|
46
48
|
readonly fullSnapshots: number;
|
|
47
|
-
readonly deltas: number;
|
|
48
49
|
readonly semanticBytes: number;
|
|
49
50
|
readonly semanticNodes: number;
|
|
50
51
|
readonly unknownFrameworkNodes: number;
|
|
@@ -87,7 +88,7 @@ declare class ProbeChannel {
|
|
|
87
88
|
#private;
|
|
88
89
|
readonly session: ChannelSession;
|
|
89
90
|
/** @internal Built by {@link connectProbe} once the handshake succeeded. */
|
|
90
|
-
constructor(socket: Socket, session: ChannelSession, token: string, decoder: FrameDecoder, pending?: readonly unknown[], performanceMetrics?: boolean, performanceSink?: (metrics: ProbePerformanceMetrics) => void);
|
|
91
|
+
constructor(socket: Socket, session: ChannelSession, token: string, decoder: FrameDecoder, pending?: readonly unknown[], evidenceProviders?: FrozenEvidenceProviderRegistry, performanceMetrics?: boolean, performanceSink?: (metrics: ProbePerformanceMetrics) => void);
|
|
91
92
|
get isOpen(): boolean;
|
|
92
93
|
/** Revisions published so far. */
|
|
93
94
|
get revision(): number;
|
|
@@ -106,6 +107,8 @@ declare class ProbeChannel {
|
|
|
106
107
|
publish(snapshot: SemanticSnapshot, publication?: ProbePublicationMetrics): string | undefined;
|
|
107
108
|
/** Report a framework publication superseded before its marker could drain. */
|
|
108
109
|
recordCoalescedEvent(): void;
|
|
110
|
+
/** Report a typed fatal producer-contract violation and close the channel. */
|
|
111
|
+
fail(code: 'duplicate-semantic-key' | 'adapter-guarantee-violation' | 'internal', message: string): void;
|
|
109
112
|
/** Disable the channel and release the socket. Idempotent. */
|
|
110
113
|
close(): void;
|
|
111
114
|
}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import { createConnection } from "net";
|
|
3
3
|
import { appendFileSync } from "fs";
|
|
4
4
|
import { performance } from "perf_hooks";
|
|
5
|
+
import {
|
|
6
|
+
freezeEvidenceProviders
|
|
7
|
+
} from "@termwright/evidence-provider";
|
|
5
8
|
import {
|
|
6
9
|
createFrameDecoder,
|
|
7
10
|
encodeFrame,
|
|
@@ -15,16 +18,17 @@ var ProbeChannel = class {
|
|
|
15
18
|
#socket;
|
|
16
19
|
session;
|
|
17
20
|
#token;
|
|
21
|
+
#evidenceProviders;
|
|
18
22
|
#open = true;
|
|
19
23
|
#revision = 0;
|
|
20
|
-
#latest;
|
|
21
24
|
#performance;
|
|
22
25
|
#performanceSink;
|
|
23
26
|
/** @internal Built by {@link connectProbe} once the handshake succeeded. */
|
|
24
|
-
constructor(socket, session, token, decoder, pending = [], performanceMetrics = false, performanceSink) {
|
|
27
|
+
constructor(socket, session, token, decoder, pending = [], evidenceProviders = freezeEvidenceProviders(), performanceMetrics = false, performanceSink) {
|
|
25
28
|
this.#socket = socket;
|
|
26
29
|
this.session = session;
|
|
27
30
|
this.#token = token;
|
|
31
|
+
this.#evidenceProviders = evidenceProviders;
|
|
28
32
|
this.#performance = performanceMetrics ? {
|
|
29
33
|
fullSnapshots: 0,
|
|
30
34
|
semanticBytes: 0,
|
|
@@ -67,7 +71,6 @@ var ProbeChannel = class {
|
|
|
67
71
|
return Object.freeze({
|
|
68
72
|
enabled: false,
|
|
69
73
|
fullSnapshots: 0,
|
|
70
|
-
deltas: 0,
|
|
71
74
|
semanticBytes: 0,
|
|
72
75
|
semanticNodes: 0,
|
|
73
76
|
unknownFrameworkNodes: 0,
|
|
@@ -88,8 +91,6 @@ var ProbeChannel = class {
|
|
|
88
91
|
return Object.freeze({
|
|
89
92
|
enabled: true,
|
|
90
93
|
fullSnapshots: frames,
|
|
91
|
-
// ProbeChannel deliberately does not advertise tree-diffs.
|
|
92
|
-
deltas: 0,
|
|
93
94
|
semanticBytes: metrics.semanticBytes,
|
|
94
95
|
semanticNodes: metrics.semanticNodes,
|
|
95
96
|
unknownFrameworkNodes: metrics.unknownFrameworkNodes,
|
|
@@ -99,10 +100,7 @@ var ProbeChannel = class {
|
|
|
99
100
|
averageBytesPerFrame: average(metrics.semanticBytes, frames),
|
|
100
101
|
averageSemanticNodesPerFrame: average(metrics.semanticNodes, frames),
|
|
101
102
|
averageUnknownFrameworkNodesPerFrame: average(metrics.unknownFrameworkNodes, frames),
|
|
102
|
-
averageSerializationMicrosecondsPerFrame: average(
|
|
103
|
-
metrics.serializationMicroseconds,
|
|
104
|
-
frames
|
|
105
|
-
),
|
|
103
|
+
averageSerializationMicrosecondsPerFrame: average(metrics.serializationMicroseconds, frames),
|
|
106
104
|
probeEventsPerFrame: average(metrics.probeEvents, metrics.framesWithProbeEvents),
|
|
107
105
|
coalescedEvents: metrics.coalescedEvents,
|
|
108
106
|
renderCorrelationRate: null,
|
|
@@ -121,8 +119,26 @@ var ProbeChannel = class {
|
|
|
121
119
|
if (this.#performance !== null) this.#performance.droppedEvents += 1;
|
|
122
120
|
return void 0;
|
|
123
121
|
}
|
|
122
|
+
const providerEvidence = this.#evidenceProviders.collect({
|
|
123
|
+
sessionId: snapshot.sessionId,
|
|
124
|
+
revision: snapshot.revision,
|
|
125
|
+
columns: snapshot.columns,
|
|
126
|
+
rows: snapshot.rows,
|
|
127
|
+
resolveRecipient: (recipient) => {
|
|
128
|
+
const matches = "semanticId" in recipient ? snapshot.nodes.filter((node) => node.id === recipient.semanticId) : "testId" in recipient ? snapshot.nodes.filter((node) => node.testId === recipient.testId) : snapshot.nodes.filter(
|
|
129
|
+
(node) => node.role === recipient.role && node.name === recipient.name
|
|
130
|
+
);
|
|
131
|
+
if (matches.length !== 1) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`application evidence recipient resolved to ${matches.length} semantic nodes`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return matches[0].id;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
const qualifiedSnapshot = providerEvidence.length === 0 ? snapshot : Object.freeze({ ...snapshot, providerEvidence });
|
|
124
140
|
const sent = this.#send(
|
|
125
|
-
{ type: "snapshot", snapshot },
|
|
141
|
+
{ type: "snapshot", snapshot: qualifiedSnapshot },
|
|
126
142
|
this.#performance !== null
|
|
127
143
|
);
|
|
128
144
|
if (sent === void 0) {
|
|
@@ -131,7 +147,6 @@ var ProbeChannel = class {
|
|
|
131
147
|
return void 0;
|
|
132
148
|
}
|
|
133
149
|
this.#revision = snapshot.revision;
|
|
134
|
-
this.#latest = snapshot;
|
|
135
150
|
if (this.#performance !== null) {
|
|
136
151
|
this.#performance.fullSnapshots += 1;
|
|
137
152
|
this.#performance.semanticBytes += sent.bytes;
|
|
@@ -171,11 +186,17 @@ var ProbeChannel = class {
|
|
|
171
186
|
this.#emitPerformance();
|
|
172
187
|
}
|
|
173
188
|
}
|
|
189
|
+
/** Report a typed fatal producer-contract violation and close the channel. */
|
|
190
|
+
fail(code, message) {
|
|
191
|
+
if (!this.#open) return;
|
|
192
|
+
this.#send({ type: "error", code, message: message.slice(0, 1024) });
|
|
193
|
+
this.close();
|
|
194
|
+
}
|
|
174
195
|
/** Disable the channel and release the socket. Idempotent. */
|
|
175
196
|
close() {
|
|
176
197
|
if (!this.#open) return;
|
|
177
198
|
this.#open = false;
|
|
178
|
-
this.#
|
|
199
|
+
this.#evidenceProviders.close();
|
|
179
200
|
this.#socket.removeAllListeners("data");
|
|
180
201
|
this.#socket.destroy();
|
|
181
202
|
}
|
|
@@ -190,12 +211,6 @@ var ProbeChannel = class {
|
|
|
190
211
|
this.close();
|
|
191
212
|
return;
|
|
192
213
|
}
|
|
193
|
-
if (message.type !== "get-tree") return;
|
|
194
|
-
const held = this.#latest;
|
|
195
|
-
const usable = held !== void 0 && (message.revision === void 0 || message.revision === held.revision);
|
|
196
|
-
this.#send(
|
|
197
|
-
usable ? { type: "get-tree-result", requestId: message.requestId, snapshot: held } : { type: "get-tree-result", requestId: message.requestId, error: "revision not retained" }
|
|
198
|
-
);
|
|
199
214
|
}
|
|
200
215
|
#send(message, measureSerialization = false) {
|
|
201
216
|
if (!this.#open) return void 0;
|
|
@@ -251,6 +266,7 @@ function filePerformanceSink(path, context) {
|
|
|
251
266
|
}
|
|
252
267
|
async function connectProbe(options) {
|
|
253
268
|
const timeoutMs = options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;
|
|
269
|
+
const evidenceProviders = freezeEvidenceProviders(options.evidenceProviderRegistry);
|
|
254
270
|
return new Promise((resolve) => {
|
|
255
271
|
let settled = false;
|
|
256
272
|
let socket;
|
|
@@ -266,6 +282,7 @@ async function connectProbe(options) {
|
|
|
266
282
|
settled = true;
|
|
267
283
|
detach();
|
|
268
284
|
socket.destroy();
|
|
285
|
+
evidenceProviders.close();
|
|
269
286
|
resolve(null);
|
|
270
287
|
};
|
|
271
288
|
const accept = (session, pending) => {
|
|
@@ -273,25 +290,29 @@ async function connectProbe(options) {
|
|
|
273
290
|
settled = true;
|
|
274
291
|
detach();
|
|
275
292
|
const metricsPath = options.performanceMetricsFile ?? performanceFile(process.env);
|
|
276
|
-
resolve(
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
293
|
+
resolve(
|
|
294
|
+
new ProbeChannel(
|
|
295
|
+
socket,
|
|
296
|
+
session,
|
|
297
|
+
options.token,
|
|
298
|
+
decoder,
|
|
299
|
+
pending,
|
|
300
|
+
evidenceProviders,
|
|
301
|
+
options.performanceMetrics ?? (metricsPath !== void 0 || debugMetricsEnabled(process.env)),
|
|
302
|
+
filePerformanceSink(metricsPath, {
|
|
303
|
+
adapter: options.adapterName,
|
|
304
|
+
framework: options.probe.framework,
|
|
305
|
+
sessionId: session.sessionId
|
|
306
|
+
})
|
|
307
|
+
)
|
|
308
|
+
);
|
|
289
309
|
};
|
|
290
310
|
try {
|
|
291
311
|
socket = createConnection(options.endpoint);
|
|
292
312
|
socket.unref();
|
|
293
313
|
} catch {
|
|
294
314
|
clearTimeout(timer);
|
|
315
|
+
evidenceProviders.close();
|
|
295
316
|
resolve(null);
|
|
296
317
|
return;
|
|
297
318
|
}
|
|
@@ -303,11 +324,12 @@ async function connectProbe(options) {
|
|
|
303
324
|
encodeFrame(
|
|
304
325
|
{
|
|
305
326
|
type: "hello",
|
|
306
|
-
protocol:
|
|
327
|
+
protocol: PROTOCOL_ID,
|
|
307
328
|
token: options.token,
|
|
308
329
|
adapter: { name: options.adapterName, version: options.adapterVersion },
|
|
309
330
|
capabilities: options.capabilities,
|
|
310
|
-
probe: options.probe
|
|
331
|
+
probe: options.probe,
|
|
332
|
+
...evidenceProviders.registrations.length === 0 ? {} : { providers: evidenceProviders.registrations }
|
|
311
333
|
},
|
|
312
334
|
DEFAULT_LIMITS.maxFrameBytes
|
|
313
335
|
)
|
|
@@ -332,7 +354,7 @@ async function connectProbe(options) {
|
|
|
332
354
|
return;
|
|
333
355
|
}
|
|
334
356
|
const ack = parsed.message;
|
|
335
|
-
if (ack.protocol !==
|
|
357
|
+
if (ack.protocol !== PROTOCOL_ID) {
|
|
336
358
|
abandon();
|
|
337
359
|
return;
|
|
338
360
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Shared process-to-driver transport for zero-config JavaScript probes.\n *\n * Framework probes deliberately keep observation and marker placement in\n * their own packages. This module owns only the framework-neutral handshake,\n * full-snapshot publication and fail-closed socket lifecycle.\n */\n\nimport { createConnection, type Socket } from 'node:net';\nimport { appendFileSync } from 'node:fs';\nimport { performance } from 'node:perf_hooks';\nimport {\n createFrameDecoder,\n encodeFrame,\n encodeMarker,\n parseDriverMessage,\n DEFAULT_LIMITS,\n PROTOCOL_ID,\n type ProtocolId,\n type AdapterCapability,\n type AdapterToDriverMessage,\n type FrameDecoder,\n type ProbeInfo,\n type ProtocolLimits,\n type SemanticSnapshot,\n} from '@termwright/protocol';\n\n/** What the driver decided for this session. */\nexport interface ChannelSession {\n readonly protocol: ProtocolId;\n readonly sessionId: string;\n readonly limits: ProtocolLimits;\n readonly markerEnabled: boolean;\n}\n\n/** Connection parameters. */\nexport interface ConnectOptions {\n readonly endpoint: string;\n readonly token: string;\n readonly probe: ProbeInfo;\n readonly capabilities: readonly AdapterCapability[];\n readonly adapterName: string;\n readonly adapterVersion: string;\n readonly protocol?: ProtocolId;\n /** Milliseconds for connect plus handshake. Default 1000. */\n readonly handshakeTimeoutMs?: number;\n /**\n * Collect per-publication counters and timings.\n *\n * Defaults on when `TERMWRIGHT_DEBUG` enables driver diagnostics or an\n * adapter debug file is configured. Explicit false keeps even the timer off.\n */\n readonly performanceMetrics?: boolean;\n /**\n * Append machine-readable performance snapshots here. Defaults to\n * `TERMWRIGHT_DEBUG_FILE`; write failures never affect the application.\n */\n readonly performanceMetricsFile?: string;\n}\n\n/** Debug-only facts the shared transport can observe without guessing. */\nexport interface ProbePerformanceMetrics {\n readonly enabled: boolean;\n readonly fullSnapshots: number;\n readonly deltas: number;\n readonly semanticBytes: number;\n readonly semanticNodes: number;\n readonly unknownFrameworkNodes: number;\n readonly droppedEvents: number;\n readonly markerRequests: number;\n readonly serializationMicroseconds: number;\n readonly averageBytesPerFrame: number | null;\n readonly averageSemanticNodesPerFrame: number | null;\n readonly averageUnknownFrameworkNodesPerFrame: number | null;\n readonly averageSerializationMicrosecondsPerFrame: number | null;\n /** Present when the framework session supplies its pre-normalized fact count. */\n readonly probeEventsPerFrame: number | null;\n /** Superseded publications reported by a framework-owned marker queue. */\n readonly coalescedEvents: number;\n /** Only the framework-specific sink knows whether a returned marker drained. */\n readonly renderCorrelationRate: null;\n /** Normalization currently happens before the shared transport is called. */\n readonly parentNormalizationMicrosecondsPerFrame: null;\n}\n\ninterface MutableProbePerformanceMetrics {\n fullSnapshots: number;\n semanticBytes: number;\n semanticNodes: number;\n unknownFrameworkNodes: number;\n droppedEvents: number;\n markerRequests: number;\n serializationMicroseconds: number;\n probeEvents: number;\n framesWithProbeEvents: number;\n coalescedEvents: number;\n}\n\nexport interface ProbePublicationMetrics {\n /** Probe IR objects plus render/layout operations observed for this frame. */\n readonly probeEvents?: number;\n}\n\n/** One JSONL record written by an injected probe in debug-file mode. */\nexport interface ProbePerformanceRecord {\n readonly kind: 'termwright-probe-performance';\n readonly adapter: string;\n readonly framework: string;\n readonly sessionId: string;\n readonly metrics: ProbePerformanceMetrics;\n}\n\ninterface SendResult {\n readonly bytes: number;\n readonly serializationMicroseconds: number;\n}\n\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 1_000;\n\n/**\n * A connected channel.\n *\n * `publish` returns the marker rather than writing it. Only a framework probe\n * knows when its frame bytes have drained, and emitting earlier silently pairs\n * the tree with the wrong screen.\n */\nexport class ProbeChannel {\n readonly #socket: Socket;\n readonly session: ChannelSession;\n readonly #token: string;\n #open = true;\n #revision = 0;\n #latest: SemanticSnapshot | undefined;\n readonly #performance: MutableProbePerformanceMetrics | null;\n readonly #performanceSink: (() => void) | null;\n\n /** @internal Built by {@link connectProbe} once the handshake succeeded. */\n constructor(\n socket: Socket,\n session: ChannelSession,\n token: string,\n decoder: FrameDecoder,\n pending: readonly unknown[] = [],\n performanceMetrics = false,\n performanceSink?: (metrics: ProbePerformanceMetrics) => void,\n ) {\n this.#socket = socket;\n this.session = session;\n this.#token = token;\n this.#performance = performanceMetrics\n ? {\n fullSnapshots: 0,\n semanticBytes: 0,\n semanticNodes: 0,\n unknownFrameworkNodes: 0,\n droppedEvents: 0,\n markerRequests: 0,\n serializationMicroseconds: 0,\n probeEvents: 0,\n framesWithProbeEvents: 0,\n coalescedEvents: 0,\n }\n : null;\n this.#performanceSink = performanceSink === undefined\n ? null\n : () => performanceSink(this.performanceMetrics());\n\n socket.on('data', (chunk: Buffer) => {\n if (!this.#open) return;\n try {\n for (const value of decoder.push(chunk)) this.#dispatch(value);\n } catch {\n this.close();\n }\n });\n socket.on('error', () => this.close());\n socket.on('close', () => this.close());\n\n // The ACK and the driver's first request may share one pipe/TCP chunk. The\n // handshake decoder already removed every complete frame from its buffer,\n // so values after the ACK must be handed over explicitly or they vanish.\n for (const value of pending) this.#dispatch(value);\n }\n\n get isOpen(): boolean {\n return this.#open;\n }\n\n /** Revisions published so far. */\n get revision(): number {\n return this.#revision;\n }\n\n /**\n * A stable, immutable debug snapshot. Disabled collection reports no\n * fabricated zeros for averages that were never observed.\n */\n performanceMetrics(): ProbePerformanceMetrics {\n const metrics = this.#performance;\n if (metrics === null) {\n return Object.freeze({\n enabled: false,\n fullSnapshots: 0,\n deltas: 0,\n semanticBytes: 0,\n semanticNodes: 0,\n unknownFrameworkNodes: 0,\n droppedEvents: 0,\n markerRequests: 0,\n serializationMicroseconds: 0,\n averageBytesPerFrame: null,\n averageSemanticNodesPerFrame: null,\n averageUnknownFrameworkNodesPerFrame: null,\n averageSerializationMicrosecondsPerFrame: null,\n probeEventsPerFrame: null,\n coalescedEvents: 0,\n renderCorrelationRate: null,\n parentNormalizationMicrosecondsPerFrame: null,\n });\n }\n const frames = metrics.fullSnapshots;\n return Object.freeze({\n enabled: true,\n fullSnapshots: frames,\n // ProbeChannel deliberately does not advertise tree-diffs.\n deltas: 0,\n semanticBytes: metrics.semanticBytes,\n semanticNodes: metrics.semanticNodes,\n unknownFrameworkNodes: metrics.unknownFrameworkNodes,\n droppedEvents: metrics.droppedEvents,\n markerRequests: metrics.markerRequests,\n serializationMicroseconds: metrics.serializationMicroseconds,\n averageBytesPerFrame: average(metrics.semanticBytes, frames),\n averageSemanticNodesPerFrame: average(metrics.semanticNodes, frames),\n averageUnknownFrameworkNodesPerFrame: average(metrics.unknownFrameworkNodes, frames),\n averageSerializationMicrosecondsPerFrame: average(\n metrics.serializationMicroseconds,\n frames,\n ),\n probeEventsPerFrame: average(metrics.probeEvents, metrics.framesWithProbeEvents),\n coalescedEvents: metrics.coalescedEvents,\n renderCorrelationRate: null,\n parentNormalizationMicrosecondsPerFrame: null,\n });\n }\n\n /**\n * Send a full tree and commit its revision.\n *\n * Full snapshots satisfy the producer side of dropped-frame recovery by\n * construction. Diff subscription can be added later without weakening this\n * initial correctness floor.\n */\n publish(\n snapshot: SemanticSnapshot,\n publication: ProbePublicationMetrics = {},\n ): string | undefined {\n if (!this.#open) {\n if (this.#performance !== null) this.#performance.droppedEvents += 1;\n return undefined;\n }\n const sent = this.#send(\n { type: 'snapshot', snapshot },\n this.#performance !== null,\n );\n if (sent === undefined) {\n if (this.#performance !== null) this.#performance.droppedEvents += 1;\n this.#emitPerformance();\n return undefined;\n }\n this.#revision = snapshot.revision;\n this.#latest = snapshot;\n if (this.#performance !== null) {\n this.#performance.fullSnapshots += 1;\n this.#performance.semanticBytes += sent.bytes;\n this.#performance.semanticNodes += snapshot.nodes.length;\n this.#performance.unknownFrameworkNodes += snapshot.nodes.filter(\n (node) => node.role === 'generic',\n ).length;\n this.#performance.serializationMicroseconds += sent.serializationMicroseconds;\n if (\n publication.probeEvents !== undefined\n && Number.isSafeInteger(publication.probeEvents)\n && publication.probeEvents >= 0\n ) {\n this.#performance.probeEvents += publication.probeEvents;\n this.#performance.framesWithProbeEvents += 1;\n }\n }\n if (this.#send({ type: 'revision-commit', revision: snapshot.revision }) === undefined) {\n if (this.#performance !== null) this.#performance.droppedEvents += 1;\n this.#emitPerformance();\n return undefined;\n }\n\n if (!this.session.markerEnabled) {\n this.#emitPerformance();\n return undefined;\n }\n try {\n const marker = encodeMarker(this.#token, this.session.sessionId, snapshot.revision);\n if (this.#performance !== null) this.#performance.markerRequests += 1;\n this.#emitPerformance();\n return marker;\n } catch {\n this.#emitPerformance();\n return undefined;\n }\n }\n\n /** Report a framework publication superseded before its marker could drain. */\n recordCoalescedEvent(): void {\n if (this.#performance !== null) {\n this.#performance.coalescedEvents += 1;\n this.#emitPerformance();\n }\n }\n\n /** Disable the channel and release the socket. Idempotent. */\n close(): void {\n if (!this.#open) return;\n this.#open = false;\n this.#latest = undefined;\n this.#socket.removeAllListeners('data');\n this.#socket.destroy();\n }\n\n #dispatch(value: unknown): void {\n const parsed = parseDriverMessage(value, this.session.limits);\n if (!parsed.ok) {\n this.close();\n return;\n }\n const message = parsed.message;\n if (message.type === 'error') {\n this.close();\n return;\n }\n if (message.type !== 'get-tree') return;\n\n const held = this.#latest;\n const usable = held !== undefined\n && (message.revision === undefined || message.revision === held.revision);\n this.#send(\n usable\n ? { type: 'get-tree-result', requestId: message.requestId, snapshot: held }\n : { type: 'get-tree-result', requestId: message.requestId, error: 'revision not retained' },\n );\n }\n\n #send(message: AdapterToDriverMessage, measureSerialization = false): SendResult | undefined {\n if (!this.#open) return undefined;\n let frame: Uint8Array;\n let serializationMicroseconds: number;\n try {\n const started = measureSerialization ? performance.now() : 0;\n frame = encodeFrame(message, this.session.limits.maxFrameBytes);\n serializationMicroseconds = measureSerialization\n ? (performance.now() - started) * 1_000\n : 0;\n } catch {\n // This is locally produced data and encodeFrame writes nothing before it\n // rejects. Drop only this publication: a later, smaller framework frame\n // can still be valid and must not lose the already negotiated channel.\n return undefined;\n }\n try {\n this.#socket.write(frame);\n return { bytes: frame.byteLength, serializationMicroseconds };\n } catch {\n this.close();\n return undefined;\n }\n }\n\n #emitPerformance(): void {\n try {\n this.#performanceSink?.();\n } catch {\n // Debug output is strictly fail-open: a bad path cannot break rendering.\n }\n }\n}\n\nfunction average(total: number, count: number): number | null {\n return count === 0 ? null : total / count;\n}\n\nfunction debugMetricsEnabled(env: NodeJS.ProcessEnv): boolean {\n if ((env['TERMWRIGHT_DEBUG_FILE'] ?? '').trim().length > 0) return true;\n const value = (env['TERMWRIGHT_DEBUG'] ?? '').trim().toLowerCase();\n return value === '1' || value === 'true' || value === 'api' || value === 'all' || value === 'on';\n}\n\nfunction performanceFile(env: NodeJS.ProcessEnv): string | undefined {\n const path = (env['TERMWRIGHT_DEBUG_FILE'] ?? '').trim();\n return path.length === 0 ? undefined : path;\n}\n\nfunction filePerformanceSink(\n path: string | undefined,\n context: { readonly adapter: string; readonly framework: string; readonly sessionId: string },\n): ((metrics: ProbePerformanceMetrics) => void) | undefined {\n if (path === undefined) return undefined;\n return (metrics) => {\n const record: ProbePerformanceRecord = {\n kind: 'termwright-probe-performance',\n adapter: context.adapter,\n framework: context.framework,\n sessionId: context.sessionId,\n metrics,\n };\n appendFileSync(path, `${JSON.stringify(record)}\\n`, 'utf8');\n };\n}\n\n/**\n * Connect and complete the handshake.\n *\n * Any failure resolves to `null`. A refused socket, malformed acknowledgement\n * or disappearing driver must be indistinguishable from disabled semantics to\n * the application.\n */\nexport async function connectProbe(options: ConnectOptions): Promise<ProbeChannel | null> {\n const timeoutMs = options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;\n\n return new Promise<ProbeChannel | null>((resolve) => {\n let settled = false;\n let socket: Socket;\n const decoder = createFrameDecoder(DEFAULT_LIMITS.maxFrameBytes);\n const timer = setTimeout(() => abandon(), timeoutMs);\n timer.unref();\n\n const detach = (): void => {\n clearTimeout(timer);\n for (const event of ['connect', 'data', 'error', 'close']) socket.removeAllListeners(event);\n };\n const abandon = (): void => {\n if (settled) return;\n settled = true;\n detach();\n socket.destroy();\n resolve(null);\n };\n const accept = (session: ChannelSession, pending: readonly unknown[]): void => {\n if (settled) return;\n settled = true;\n detach();\n const metricsPath = options.performanceMetricsFile ?? performanceFile(process.env);\n resolve(new ProbeChannel(\n socket,\n session,\n options.token,\n decoder,\n pending,\n options.performanceMetrics ?? (metricsPath !== undefined || debugMetricsEnabled(process.env)),\n filePerformanceSink(metricsPath, {\n adapter: options.adapterName,\n framework: options.probe.framework,\n sessionId: session.sessionId,\n }),\n ));\n };\n\n try {\n socket = createConnection(options.endpoint);\n socket.unref();\n } catch {\n clearTimeout(timer);\n resolve(null);\n return;\n }\n\n socket.on('error', abandon);\n socket.on('close', abandon);\n socket.on('connect', () => {\n try {\n socket.write(\n encodeFrame(\n {\n type: 'hello',\n protocol: options.protocol ?? PROTOCOL_ID,\n token: options.token,\n adapter: { name: options.adapterName, version: options.adapterVersion },\n capabilities: options.capabilities,\n probe: options.probe,\n },\n DEFAULT_LIMITS.maxFrameBytes,\n ),\n );\n } catch {\n abandon();\n }\n });\n\n socket.on('data', (chunk: Buffer) => {\n if (settled) return;\n let messages: readonly unknown[];\n try {\n messages = decoder.push(chunk);\n } catch {\n abandon();\n return;\n }\n if (messages.length === 0) return;\n\n const parsed = parseDriverMessage(messages[0], DEFAULT_LIMITS);\n if (!parsed.ok || parsed.message.type !== 'hello-ack') {\n abandon();\n return;\n }\n const ack = parsed.message;\n if (ack.protocol !== (options.protocol ?? PROTOCOL_ID)) {\n abandon();\n return;\n }\n accept(\n {\n protocol: ack.protocol,\n sessionId: ack.sessionId,\n limits: narrowLimits(ack.limits),\n markerEnabled: ack.marker.enabled,\n },\n messages.slice(1),\n );\n });\n });\n}\n\n/** Adopt the driver's limits, never above our own: it may tighten, not widen. */\nfunction narrowLimits(offered: ProtocolLimits): ProtocolLimits {\n const entries = Object.entries(DEFAULT_LIMITS).map(([key, fallback]) => {\n const value = (offered as unknown as Record<string, unknown>)[key];\n const usable = typeof value === 'number' && Number.isSafeInteger(value) && value > 0;\n return [key, usable ? Math.min(value, fallback as number) : fallback];\n });\n return Object.freeze(Object.fromEntries(entries)) as ProtocolLimits;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Shared process-to-driver transport for zero-config JavaScript probes.\n *\n * Framework probes deliberately keep observation and marker placement in\n * their own packages. This module owns only the framework-neutral handshake,\n * full-snapshot publication and fail-closed socket lifecycle.\n */\n\nimport { createConnection, type Socket } from 'node:net';\nimport { appendFileSync } from 'node:fs';\nimport { performance } from 'node:perf_hooks';\nimport {\n freezeEvidenceProviders,\n type EvidenceProviderRegistry,\n type FrozenEvidenceProviderRegistry,\n} from '@termwright/evidence-provider';\nimport {\n createFrameDecoder,\n encodeFrame,\n encodeMarker,\n parseDriverMessage,\n DEFAULT_LIMITS,\n PROTOCOL_ID,\n type ProtocolId,\n type AdapterCapability,\n type AdapterToDriverMessage,\n type FrameDecoder,\n type ProbeInfo,\n type ProtocolLimits,\n type SemanticSnapshot,\n} from '@termwright/protocol';\n\n/** What the driver decided for this session. */\nexport interface ChannelSession {\n readonly protocol: ProtocolId;\n readonly sessionId: string;\n readonly limits: ProtocolLimits;\n readonly markerEnabled: boolean;\n}\n\n/** Connection parameters. */\nexport interface ConnectOptions {\n readonly endpoint: string;\n readonly token: string;\n readonly probe: ProbeInfo;\n readonly capabilities: readonly AdapterCapability[];\n readonly adapterName: string;\n readonly adapterVersion: string;\n /** Isolated application provider registry. Defaults to the process facade. */\n readonly evidenceProviderRegistry?: EvidenceProviderRegistry;\n /** Milliseconds for connect plus handshake. Default 1000. */\n readonly handshakeTimeoutMs?: number;\n /**\n * Collect per-publication counters and timings.\n *\n * Defaults on when `TERMWRIGHT_DEBUG` enables driver diagnostics or an\n * adapter debug file is configured. Explicit false keeps even the timer off.\n */\n readonly performanceMetrics?: boolean;\n /**\n * Append machine-readable performance snapshots here. Defaults to\n * `TERMWRIGHT_DEBUG_FILE`; write failures never affect the application.\n */\n readonly performanceMetricsFile?: string;\n}\n\n/** Debug-only facts the shared transport can observe without guessing. */\nexport interface ProbePerformanceMetrics {\n readonly enabled: boolean;\n readonly fullSnapshots: number;\n readonly semanticBytes: number;\n readonly semanticNodes: number;\n readonly unknownFrameworkNodes: number;\n readonly droppedEvents: number;\n readonly markerRequests: number;\n readonly serializationMicroseconds: number;\n readonly averageBytesPerFrame: number | null;\n readonly averageSemanticNodesPerFrame: number | null;\n readonly averageUnknownFrameworkNodesPerFrame: number | null;\n readonly averageSerializationMicrosecondsPerFrame: number | null;\n /** Present when the framework session supplies its pre-normalized fact count. */\n readonly probeEventsPerFrame: number | null;\n /** Superseded publications reported by a framework-owned marker queue. */\n readonly coalescedEvents: number;\n /** Only the framework-specific sink knows whether a returned marker drained. */\n readonly renderCorrelationRate: null;\n /** Normalization currently happens before the shared transport is called. */\n readonly parentNormalizationMicrosecondsPerFrame: null;\n}\n\ninterface MutableProbePerformanceMetrics {\n fullSnapshots: number;\n semanticBytes: number;\n semanticNodes: number;\n unknownFrameworkNodes: number;\n droppedEvents: number;\n markerRequests: number;\n serializationMicroseconds: number;\n probeEvents: number;\n framesWithProbeEvents: number;\n coalescedEvents: number;\n}\n\nexport interface ProbePublicationMetrics {\n /** Probe IR objects plus render/layout operations observed for this frame. */\n readonly probeEvents?: number;\n}\n\n/** One JSONL record written by an injected probe in debug-file mode. */\nexport interface ProbePerformanceRecord {\n readonly kind: 'termwright-probe-performance';\n readonly adapter: string;\n readonly framework: string;\n readonly sessionId: string;\n readonly metrics: ProbePerformanceMetrics;\n}\n\ninterface SendResult {\n readonly bytes: number;\n readonly serializationMicroseconds: number;\n}\n\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 1_000;\n\n/**\n * A connected channel.\n *\n * `publish` returns the marker rather than writing it. Only a framework probe\n * knows when its frame bytes have drained, and emitting earlier silently pairs\n * the tree with the wrong screen.\n */\nexport class ProbeChannel {\n readonly #socket: Socket;\n readonly session: ChannelSession;\n readonly #token: string;\n readonly #evidenceProviders: FrozenEvidenceProviderRegistry;\n #open = true;\n #revision = 0;\n readonly #performance: MutableProbePerformanceMetrics | null;\n readonly #performanceSink: (() => void) | null;\n\n /** @internal Built by {@link connectProbe} once the handshake succeeded. */\n constructor(\n socket: Socket,\n session: ChannelSession,\n token: string,\n decoder: FrameDecoder,\n pending: readonly unknown[] = [],\n evidenceProviders: FrozenEvidenceProviderRegistry = freezeEvidenceProviders(),\n performanceMetrics = false,\n performanceSink?: (metrics: ProbePerformanceMetrics) => void,\n ) {\n this.#socket = socket;\n this.session = session;\n this.#token = token;\n this.#evidenceProviders = evidenceProviders;\n this.#performance = performanceMetrics\n ? {\n fullSnapshots: 0,\n semanticBytes: 0,\n semanticNodes: 0,\n unknownFrameworkNodes: 0,\n droppedEvents: 0,\n markerRequests: 0,\n serializationMicroseconds: 0,\n probeEvents: 0,\n framesWithProbeEvents: 0,\n coalescedEvents: 0,\n }\n : null;\n this.#performanceSink =\n performanceSink === undefined ? null : () => performanceSink(this.performanceMetrics());\n\n socket.on('data', (chunk: Buffer) => {\n if (!this.#open) return;\n try {\n for (const value of decoder.push(chunk)) this.#dispatch(value);\n } catch {\n this.close();\n }\n });\n socket.on('error', () => this.close());\n socket.on('close', () => this.close());\n\n // The ACK and the driver's first request may share one pipe/TCP chunk. The\n // handshake decoder already removed every complete frame from its buffer,\n // so values after the ACK must be handed over explicitly or they vanish.\n for (const value of pending) this.#dispatch(value);\n }\n\n get isOpen(): boolean {\n return this.#open;\n }\n\n /** Revisions published so far. */\n get revision(): number {\n return this.#revision;\n }\n\n /**\n * A stable, immutable debug snapshot. Disabled collection reports no\n * fabricated zeros for averages that were never observed.\n */\n performanceMetrics(): ProbePerformanceMetrics {\n const metrics = this.#performance;\n if (metrics === null) {\n return Object.freeze({\n enabled: false,\n fullSnapshots: 0,\n semanticBytes: 0,\n semanticNodes: 0,\n unknownFrameworkNodes: 0,\n droppedEvents: 0,\n markerRequests: 0,\n serializationMicroseconds: 0,\n averageBytesPerFrame: null,\n averageSemanticNodesPerFrame: null,\n averageUnknownFrameworkNodesPerFrame: null,\n averageSerializationMicrosecondsPerFrame: null,\n probeEventsPerFrame: null,\n coalescedEvents: 0,\n renderCorrelationRate: null,\n parentNormalizationMicrosecondsPerFrame: null,\n });\n }\n const frames = metrics.fullSnapshots;\n return Object.freeze({\n enabled: true,\n fullSnapshots: frames,\n semanticBytes: metrics.semanticBytes,\n semanticNodes: metrics.semanticNodes,\n unknownFrameworkNodes: metrics.unknownFrameworkNodes,\n droppedEvents: metrics.droppedEvents,\n markerRequests: metrics.markerRequests,\n serializationMicroseconds: metrics.serializationMicroseconds,\n averageBytesPerFrame: average(metrics.semanticBytes, frames),\n averageSemanticNodesPerFrame: average(metrics.semanticNodes, frames),\n averageUnknownFrameworkNodesPerFrame: average(metrics.unknownFrameworkNodes, frames),\n averageSerializationMicrosecondsPerFrame: average(metrics.serializationMicroseconds, frames),\n probeEventsPerFrame: average(metrics.probeEvents, metrics.framesWithProbeEvents),\n coalescedEvents: metrics.coalescedEvents,\n renderCorrelationRate: null,\n parentNormalizationMicrosecondsPerFrame: null,\n });\n }\n\n /**\n * Send a full tree and commit its revision.\n *\n * Full snapshots satisfy the producer side of dropped-frame recovery by\n * construction. Diff subscription can be added later without weakening this\n * initial correctness floor.\n */\n publish(\n snapshot: SemanticSnapshot,\n publication: ProbePublicationMetrics = {},\n ): string | undefined {\n if (!this.#open) {\n if (this.#performance !== null) this.#performance.droppedEvents += 1;\n return undefined;\n }\n const providerEvidence = this.#evidenceProviders.collect({\n sessionId: snapshot.sessionId,\n revision: snapshot.revision,\n columns: snapshot.columns,\n rows: snapshot.rows,\n resolveRecipient: (recipient) => {\n const matches =\n 'semanticId' in recipient\n ? snapshot.nodes.filter((node) => node.id === recipient.semanticId)\n : 'testId' in recipient\n ? snapshot.nodes.filter((node) => node.testId === recipient.testId)\n : snapshot.nodes.filter(\n (node) => node.role === recipient.role && node.name === recipient.name,\n );\n if (matches.length !== 1) {\n throw new Error(\n `application evidence recipient resolved to ${matches.length} semantic nodes`,\n );\n }\n return matches[0]!.id;\n },\n });\n const qualifiedSnapshot: SemanticSnapshot =\n providerEvidence.length === 0 ? snapshot : Object.freeze({ ...snapshot, providerEvidence });\n const sent = this.#send(\n { type: 'snapshot', snapshot: qualifiedSnapshot },\n this.#performance !== null,\n );\n if (sent === undefined) {\n if (this.#performance !== null) this.#performance.droppedEvents += 1;\n this.#emitPerformance();\n return undefined;\n }\n this.#revision = snapshot.revision;\n if (this.#performance !== null) {\n this.#performance.fullSnapshots += 1;\n this.#performance.semanticBytes += sent.bytes;\n this.#performance.semanticNodes += snapshot.nodes.length;\n this.#performance.unknownFrameworkNodes += snapshot.nodes.filter(\n (node) => node.role === 'generic',\n ).length;\n this.#performance.serializationMicroseconds += sent.serializationMicroseconds;\n if (\n publication.probeEvents !== undefined &&\n Number.isSafeInteger(publication.probeEvents) &&\n publication.probeEvents >= 0\n ) {\n this.#performance.probeEvents += publication.probeEvents;\n this.#performance.framesWithProbeEvents += 1;\n }\n }\n if (this.#send({ type: 'revision-commit', revision: snapshot.revision }) === undefined) {\n if (this.#performance !== null) this.#performance.droppedEvents += 1;\n this.#emitPerformance();\n return undefined;\n }\n\n if (!this.session.markerEnabled) {\n this.#emitPerformance();\n return undefined;\n }\n try {\n const marker = encodeMarker(this.#token, this.session.sessionId, snapshot.revision);\n if (this.#performance !== null) this.#performance.markerRequests += 1;\n this.#emitPerformance();\n return marker;\n } catch {\n this.#emitPerformance();\n return undefined;\n }\n }\n\n /** Report a framework publication superseded before its marker could drain. */\n recordCoalescedEvent(): void {\n if (this.#performance !== null) {\n this.#performance.coalescedEvents += 1;\n this.#emitPerformance();\n }\n }\n\n /** Report a typed fatal producer-contract violation and close the channel. */\n fail(\n code: 'duplicate-semantic-key' | 'adapter-guarantee-violation' | 'internal',\n message: string,\n ): void {\n if (!this.#open) return;\n this.#send({ type: 'error', code, message: message.slice(0, 1_024) });\n this.close();\n }\n\n /** Disable the channel and release the socket. Idempotent. */\n close(): void {\n if (!this.#open) return;\n this.#open = false;\n this.#evidenceProviders.close();\n this.#socket.removeAllListeners('data');\n this.#socket.destroy();\n }\n\n #dispatch(value: unknown): void {\n const parsed = parseDriverMessage(value, this.session.limits);\n if (!parsed.ok) {\n this.close();\n return;\n }\n const message = parsed.message;\n if (message.type === 'error') {\n this.close();\n return;\n }\n }\n\n #send(message: AdapterToDriverMessage, measureSerialization = false): SendResult | undefined {\n if (!this.#open) return undefined;\n let frame: Uint8Array;\n let serializationMicroseconds: number;\n try {\n const started = measureSerialization ? performance.now() : 0;\n frame = encodeFrame(message, this.session.limits.maxFrameBytes);\n serializationMicroseconds = measureSerialization ? (performance.now() - started) * 1_000 : 0;\n } catch {\n // This is locally produced data and encodeFrame writes nothing before it\n // rejects. Drop only this publication: a later, smaller framework frame\n // can still be valid and must not lose the already negotiated channel.\n return undefined;\n }\n try {\n this.#socket.write(frame);\n return { bytes: frame.byteLength, serializationMicroseconds };\n } catch {\n this.close();\n return undefined;\n }\n }\n\n #emitPerformance(): void {\n try {\n this.#performanceSink?.();\n } catch {\n // Debug output is strictly fail-open: a bad path cannot break rendering.\n }\n }\n}\n\nfunction average(total: number, count: number): number | null {\n return count === 0 ? null : total / count;\n}\n\nfunction debugMetricsEnabled(env: NodeJS.ProcessEnv): boolean {\n if ((env['TERMWRIGHT_DEBUG_FILE'] ?? '').trim().length > 0) return true;\n const value = (env['TERMWRIGHT_DEBUG'] ?? '').trim().toLowerCase();\n return value === '1' || value === 'true' || value === 'api' || value === 'all' || value === 'on';\n}\n\nfunction performanceFile(env: NodeJS.ProcessEnv): string | undefined {\n const path = (env['TERMWRIGHT_DEBUG_FILE'] ?? '').trim();\n return path.length === 0 ? undefined : path;\n}\n\nfunction filePerformanceSink(\n path: string | undefined,\n context: { readonly adapter: string; readonly framework: string; readonly sessionId: string },\n): ((metrics: ProbePerformanceMetrics) => void) | undefined {\n if (path === undefined) return undefined;\n return (metrics) => {\n const record: ProbePerformanceRecord = {\n kind: 'termwright-probe-performance',\n adapter: context.adapter,\n framework: context.framework,\n sessionId: context.sessionId,\n metrics,\n };\n appendFileSync(path, `${JSON.stringify(record)}\\n`, 'utf8');\n };\n}\n\n/**\n * Connect and complete the handshake.\n *\n * Any failure resolves to `null`. A refused socket, malformed acknowledgement\n * or disappearing driver must be indistinguishable from disabled semantics to\n * the application.\n */\nexport async function connectProbe(options: ConnectOptions): Promise<ProbeChannel | null> {\n const timeoutMs = options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;\n // Freeze synchronously, before a socket can connect and before hello is\n // constructed. Any later application registration is a lifecycle error.\n const evidenceProviders = freezeEvidenceProviders(options.evidenceProviderRegistry);\n\n return new Promise<ProbeChannel | null>((resolve) => {\n let settled = false;\n let socket: Socket;\n const decoder = createFrameDecoder(DEFAULT_LIMITS.maxFrameBytes);\n const timer = setTimeout(() => abandon(), timeoutMs);\n timer.unref();\n\n const detach = (): void => {\n clearTimeout(timer);\n for (const event of ['connect', 'data', 'error', 'close']) socket.removeAllListeners(event);\n };\n const abandon = (): void => {\n if (settled) return;\n settled = true;\n detach();\n socket.destroy();\n evidenceProviders.close();\n resolve(null);\n };\n const accept = (session: ChannelSession, pending: readonly unknown[]): void => {\n if (settled) return;\n settled = true;\n detach();\n const metricsPath = options.performanceMetricsFile ?? performanceFile(process.env);\n resolve(\n new ProbeChannel(\n socket,\n session,\n options.token,\n decoder,\n pending,\n evidenceProviders,\n options.performanceMetrics ??\n (metricsPath !== undefined || debugMetricsEnabled(process.env)),\n filePerformanceSink(metricsPath, {\n adapter: options.adapterName,\n framework: options.probe.framework,\n sessionId: session.sessionId,\n }),\n ),\n );\n };\n\n try {\n socket = createConnection(options.endpoint);\n socket.unref();\n } catch {\n clearTimeout(timer);\n evidenceProviders.close();\n resolve(null);\n return;\n }\n\n socket.on('error', abandon);\n socket.on('close', abandon);\n socket.on('connect', () => {\n try {\n socket.write(\n encodeFrame(\n {\n type: 'hello',\n protocol: PROTOCOL_ID,\n token: options.token,\n adapter: { name: options.adapterName, version: options.adapterVersion },\n capabilities: options.capabilities,\n probe: options.probe,\n ...(evidenceProviders.registrations.length === 0\n ? {}\n : { providers: evidenceProviders.registrations }),\n },\n DEFAULT_LIMITS.maxFrameBytes,\n ),\n );\n } catch {\n abandon();\n }\n });\n\n socket.on('data', (chunk: Buffer) => {\n if (settled) return;\n let messages: readonly unknown[];\n try {\n messages = decoder.push(chunk);\n } catch {\n abandon();\n return;\n }\n if (messages.length === 0) return;\n\n const parsed = parseDriverMessage(messages[0], DEFAULT_LIMITS);\n if (!parsed.ok || parsed.message.type !== 'hello-ack') {\n abandon();\n return;\n }\n const ack = parsed.message;\n if (ack.protocol !== PROTOCOL_ID) {\n abandon();\n return;\n }\n accept(\n {\n protocol: ack.protocol,\n sessionId: ack.sessionId,\n limits: narrowLimits(ack.limits),\n markerEnabled: ack.marker.enabled,\n },\n messages.slice(1),\n );\n });\n });\n}\n\n/** Adopt the driver's limits, never above our own: it may tighten, not widen. */\nfunction narrowLimits(offered: ProtocolLimits): ProtocolLimits {\n const entries = Object.entries(DEFAULT_LIMITS).map(([key, fallback]) => {\n const value = (offered as unknown as Record<string, unknown>)[key];\n const usable = typeof value === 'number' && Number.isSafeInteger(value) && value > 0;\n return [key, usable ? Math.min(value, fallback as number) : fallback];\n });\n return Object.freeze(Object.fromEntries(entries)) as ProtocolLimits;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@termwright/probe-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "shared fail-closed transport for zero-config JavaScript probes",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
12
12
|
"engines": {
|
|
13
|
-
"node": "
|
|
13
|
+
"node": "^22.0.0 || ^24.0.0"
|
|
14
14
|
},
|
|
15
15
|
"exports": {
|
|
16
16
|
".": {
|
|
@@ -22,11 +22,12 @@
|
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@termwright/
|
|
25
|
+
"@termwright/evidence-provider": "0.3.0",
|
|
26
|
+
"@termwright/protocol": "0.3.0"
|
|
26
27
|
},
|
|
27
28
|
"scripts": {
|
|
28
29
|
"build": "tsup src/index.ts --format esm --dts --sourcemap --clean",
|
|
29
30
|
"typecheck": "tsc --noEmit",
|
|
30
|
-
"test": "
|
|
31
|
+
"test": "pnpm --dir ../.. test -- -- --run packages/probe-runtime"
|
|
31
32
|
}
|
|
32
33
|
}
|