@hediet/linkrpc-hub 0.0.1
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 +101 -0
- package/dist/chunks/config-BCvkg7jv.d.ts +566 -0
- package/dist/chunks/configFile-y6Phntqu.d.ts +9 -0
- package/dist/chunks/connectionTokenBinder.interfaces-B-beCg06.js +40 -0
- package/dist/chunks/connectionTokenBinder.interfaces-B-beCg06.js.map +1 -0
- package/dist/chunks/connectionTokenBinder.interfaces-BhzQ1DTS.d.ts +36 -0
- package/dist/chunks/hubConnectionAcceptor-B5-8JsFY.js +2768 -0
- package/dist/chunks/hubConnectionAcceptor-B5-8JsFY.js.map +1 -0
- package/dist/chunks/hubConnectionAcceptor-BwydvFa4.d.ts +1560 -0
- package/dist/chunks/index-CLIUrV88.d.ts +481 -0
- package/dist/chunks/node-CTXsQ6oa.js +460 -0
- package/dist/chunks/node-CTXsQ6oa.js.map +1 -0
- package/dist/chunks/nodeTransit-CWeFnbwt.js +444 -0
- package/dist/chunks/nodeTransit-CWeFnbwt.js.map +1 -0
- package/dist/chunks/nodeTransit-cmtZgdpW.d.ts +226 -0
- package/dist/chunks/runHub-P3YUwwdv.js +1797 -0
- package/dist/chunks/runHub-P3YUwwdv.js.map +1 -0
- package/dist/chunks/server-BAxchQhy.js +1368 -0
- package/dist/chunks/server-BAxchQhy.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +38 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +305 -0
- package/dist/config.js.map +1 -0
- package/dist/configFile.d.ts +2 -0
- package/dist/configFile.js +27 -0
- package/dist/configFile.js.map +1 -0
- package/dist/engine/runHub.d.ts +42 -0
- package/dist/engine/runHub.js +2 -0
- package/dist/hub/server/client.d.ts +2 -0
- package/dist/hub/server/client.js +2 -0
- package/dist/hub/server/connectionTokenBinder.d.ts +2 -0
- package/dist/hub/server/connectionTokenBinder.js +2 -0
- package/dist/hub/server/index.d.ts +5 -0
- package/dist/hub/server/index.js +5 -0
- package/dist/hub/server/node/index.d.ts +218 -0
- package/dist/hub/server/node/index.js +2 -0
- package/dist/hub/server/transit.d.ts +2 -0
- package/dist/hub/server/transit.js +2 -0
- package/dist/index.d.ts +512 -0
- package/dist/index.js +309 -0
- package/dist/index.js.map +1 -0
- package/dist/serve.d.ts +14 -0
- package/dist/serve.js +31 -0
- package/dist/serve.js.map +1 -0
- package/dist/spawn.d.ts +12 -0
- package/dist/spawn.js +25 -0
- package/dist/spawn.js.map +1 -0
- package/package.json +83 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { STREAM_METHOD } from "@hediet/linkrpc";
|
|
2
|
+
//#region src/hub/server/nodeTransit.ts
|
|
3
|
+
/**
|
|
4
|
+
* Coalesces {@link NodeTransit}s into per-operation {@link FlowSummary}s and
|
|
5
|
+
* reports each after a short quiet period. A fast request is reported **once**
|
|
6
|
+
* (path + params + response); a slow/stuck one is reported **partial** while
|
|
7
|
+
* in flight and again on completion.
|
|
8
|
+
*
|
|
9
|
+
* Correlation:
|
|
10
|
+
* - request ↔ response and adjacent-node hops join by shared
|
|
11
|
+
* `(edgeId, requestId)` endpoints;
|
|
12
|
+
* - **notifications** (no request id) join heuristically by
|
|
13
|
+
* `(edgeId, method, paramsHash)` — same edge + method + params ⇒ same message.
|
|
14
|
+
*/
|
|
15
|
+
var TransitAggregator = class {
|
|
16
|
+
_options;
|
|
17
|
+
_flushDelayMs;
|
|
18
|
+
_giveUpMs;
|
|
19
|
+
_streamRetainMs;
|
|
20
|
+
_now;
|
|
21
|
+
_setTimer;
|
|
22
|
+
_clearTimer;
|
|
23
|
+
_hashParams;
|
|
24
|
+
_labelOf;
|
|
25
|
+
_flows = /* @__PURE__ */ new Set();
|
|
26
|
+
_endpointToFlow = /* @__PURE__ */ new Map();
|
|
27
|
+
_nextId = 1;
|
|
28
|
+
constructor(_options) {
|
|
29
|
+
this._options = _options;
|
|
30
|
+
this._flushDelayMs = _options.flushDelayMs ?? 50;
|
|
31
|
+
this._giveUpMs = _options.giveUpMs ?? 3e5;
|
|
32
|
+
this._streamRetainMs = _options.streamRetainMs ?? 1e3;
|
|
33
|
+
this._now = _options.now ?? (() => Date.now());
|
|
34
|
+
this._setTimer = _options.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
|
|
35
|
+
this._clearTimer = _options.clearTimer ?? ((h) => clearTimeout(h));
|
|
36
|
+
this._hashParams = _options.hashParams ?? ((p) => JSON.stringify(p ?? null));
|
|
37
|
+
this._labelOf = _options.labelOf;
|
|
38
|
+
}
|
|
39
|
+
add(transit) {
|
|
40
|
+
if (transit.kind === "stream") {
|
|
41
|
+
const parent = this._findFlow(this._endpointKeys(transit));
|
|
42
|
+
if (!(parent !== void 0 && transit.timeMs - parent.firstTs <= this._streamRetainMs)) {
|
|
43
|
+
this._emitStandaloneStream(transit, parent);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const keys = this._endpointKeys(transit);
|
|
48
|
+
let flow;
|
|
49
|
+
for (const k of keys) {
|
|
50
|
+
const f = this._endpointToFlow.get(k);
|
|
51
|
+
if (!f) continue;
|
|
52
|
+
if (!flow) flow = f;
|
|
53
|
+
else if (f !== flow) flow = this._merge(flow, f);
|
|
54
|
+
}
|
|
55
|
+
if (!flow) {
|
|
56
|
+
flow = {
|
|
57
|
+
id: `flow${this._nextId++}`,
|
|
58
|
+
endpoints: /* @__PURE__ */ new Set(),
|
|
59
|
+
transits: [],
|
|
60
|
+
settled: false,
|
|
61
|
+
reportedPartial: false,
|
|
62
|
+
timer: void 0,
|
|
63
|
+
timerIsGiveUp: false,
|
|
64
|
+
firstTs: transit.timeMs
|
|
65
|
+
};
|
|
66
|
+
this._flows.add(flow);
|
|
67
|
+
}
|
|
68
|
+
flow.transits.push(transit);
|
|
69
|
+
if (transit.timeMs < flow.firstTs) flow.firstTs = transit.timeMs;
|
|
70
|
+
for (const k of keys) {
|
|
71
|
+
flow.endpoints.add(k);
|
|
72
|
+
this._endpointToFlow.set(k, flow);
|
|
73
|
+
}
|
|
74
|
+
if (transit.kind === "response") flow.settled = true;
|
|
75
|
+
this._armFlush(flow);
|
|
76
|
+
}
|
|
77
|
+
/** Drop all pending flows and timers. Does not emit. */
|
|
78
|
+
dispose() {
|
|
79
|
+
for (const flow of this._flows) if (flow.timer !== void 0) this._clearTimer(flow.timer);
|
|
80
|
+
this._flows.clear();
|
|
81
|
+
this._endpointToFlow.clear();
|
|
82
|
+
}
|
|
83
|
+
/** Emit pending flows as partial summaries before a diagnostic capture ends. */
|
|
84
|
+
flush() {
|
|
85
|
+
for (const flow of [...this._flows]) {
|
|
86
|
+
if (flow.timer !== void 0) this._clearTimer(flow.timer);
|
|
87
|
+
flow.timer = void 0;
|
|
88
|
+
this._options.onFlow(this._summarize(flow));
|
|
89
|
+
this._delete(flow);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** First flow registered under any of `keys`, if any (no merge/create). */
|
|
93
|
+
_findFlow(keys) {
|
|
94
|
+
for (const k of keys) {
|
|
95
|
+
const f = this._endpointToFlow.get(k);
|
|
96
|
+
if (f) return f;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Report a single stream frame as its own one-line flow, correlated to its
|
|
101
|
+
* owning request by method + path (the request itself is reported separately
|
|
102
|
+
* when it settles). Used for frames that arrive after {@link _streamRetainMs}
|
|
103
|
+
* so they surface live instead of waiting on a slow request to finish.
|
|
104
|
+
*/
|
|
105
|
+
_emitStandaloneStream(transit, parent) {
|
|
106
|
+
const msg = _streamMessagesOf([transit])[0];
|
|
107
|
+
if (!msg) return;
|
|
108
|
+
const method = parent?.transits.find((t) => t.kind === "request" || t.kind === "notification")?.method;
|
|
109
|
+
const startTs = parent?.firstTs ?? transit.timeMs;
|
|
110
|
+
this._options.onFlow({
|
|
111
|
+
id: `flow${this._nextId++}`,
|
|
112
|
+
kind: "stream",
|
|
113
|
+
method,
|
|
114
|
+
params: void 0,
|
|
115
|
+
status: "completed",
|
|
116
|
+
startTs,
|
|
117
|
+
endTs: transit.timeMs,
|
|
118
|
+
durationMs: transit.timeMs - startTs,
|
|
119
|
+
path: this._buildStreamPath(transit),
|
|
120
|
+
partial: false,
|
|
121
|
+
stream: [msg]
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/** Path for a lone stream transit (in → node → out), with labels applied. */
|
|
125
|
+
_buildStreamPath(t) {
|
|
126
|
+
const path = [];
|
|
127
|
+
const push = (s) => {
|
|
128
|
+
if (path[path.length - 1] !== s) path.push(s);
|
|
129
|
+
};
|
|
130
|
+
if (t.in) push(this._label(t.in.edgeId));
|
|
131
|
+
push(t.nodeId);
|
|
132
|
+
if (t.out) push(this._label(t.out.edgeId));
|
|
133
|
+
return path;
|
|
134
|
+
}
|
|
135
|
+
_endpointKeys(t) {
|
|
136
|
+
const keys = [];
|
|
137
|
+
for (const ep of [t.in, t.out]) {
|
|
138
|
+
if (!ep) continue;
|
|
139
|
+
if (t.kind === "notification") keys.push(`${ep.edgeId}#N#${t.method ?? ""}#${this._hashParams(t.params)}`);
|
|
140
|
+
else if (ep.requestId !== void 0) keys.push(`${ep.edgeId}#${String(ep.requestId)}`);
|
|
141
|
+
}
|
|
142
|
+
return keys;
|
|
143
|
+
}
|
|
144
|
+
_merge(into, other) {
|
|
145
|
+
if (into === other) return into;
|
|
146
|
+
for (const t of other.transits) into.transits.push(t);
|
|
147
|
+
for (const k of other.endpoints) {
|
|
148
|
+
into.endpoints.add(k);
|
|
149
|
+
this._endpointToFlow.set(k, into);
|
|
150
|
+
}
|
|
151
|
+
into.settled ||= other.settled;
|
|
152
|
+
into.reportedPartial ||= other.reportedPartial;
|
|
153
|
+
if (other.firstTs < into.firstTs) into.firstTs = other.firstTs;
|
|
154
|
+
if (other.timer !== void 0) this._clearTimer(other.timer);
|
|
155
|
+
this._flows.delete(other);
|
|
156
|
+
return into;
|
|
157
|
+
}
|
|
158
|
+
_armFlush(flow) {
|
|
159
|
+
if (flow.timer !== void 0) this._clearTimer(flow.timer);
|
|
160
|
+
flow.timerIsGiveUp = false;
|
|
161
|
+
flow.timer = this._setTimer(() => this._flush(flow), this._flushDelayMs);
|
|
162
|
+
}
|
|
163
|
+
_flush(flow) {
|
|
164
|
+
flow.timer = void 0;
|
|
165
|
+
if (!this._flows.has(flow)) return;
|
|
166
|
+
const summary = this._summarize(flow);
|
|
167
|
+
if (summary.status === "pending") {
|
|
168
|
+
if (!flow.reportedPartial) {
|
|
169
|
+
flow.reportedPartial = true;
|
|
170
|
+
this._options.onFlow(summary);
|
|
171
|
+
}
|
|
172
|
+
flow.timerIsGiveUp = true;
|
|
173
|
+
flow.timer = this._setTimer(() => this._giveUp(flow), this._giveUpMs);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
this._options.onFlow(summary);
|
|
177
|
+
this._delete(flow);
|
|
178
|
+
}
|
|
179
|
+
_giveUp(flow) {
|
|
180
|
+
flow.timer = void 0;
|
|
181
|
+
this._delete(flow);
|
|
182
|
+
}
|
|
183
|
+
_delete(flow) {
|
|
184
|
+
if (flow.timer !== void 0) this._clearTimer(flow.timer);
|
|
185
|
+
flow.timer = void 0;
|
|
186
|
+
this._flows.delete(flow);
|
|
187
|
+
for (const k of flow.endpoints) if (this._endpointToFlow.get(k) === flow) this._endpointToFlow.delete(k);
|
|
188
|
+
}
|
|
189
|
+
_summarize(flow) {
|
|
190
|
+
const transits = [...flow.transits].sort((a, b) => a.timeMs - b.timeMs);
|
|
191
|
+
const head = transits.find((t) => t.kind === "request") ?? transits.find((t) => t.kind === "notification") ?? transits[0];
|
|
192
|
+
const response = [...transits].reverse().find((t) => t.kind === "response");
|
|
193
|
+
let status;
|
|
194
|
+
if (response) status = response.error ? "failed" : "completed";
|
|
195
|
+
else if (head?.kind === "notification") status = head.disposition === "dropped" ? "dropped" : "completed";
|
|
196
|
+
else if (transits.some((t) => t.disposition === "unroutable")) status = "unroutable";
|
|
197
|
+
else if (transits.some((t) => t.disposition === "dropped")) status = "dropped";
|
|
198
|
+
else status = "pending";
|
|
199
|
+
const endTs = response?.timeMs ?? (status === "pending" ? void 0 : transits[transits.length - 1]?.timeMs);
|
|
200
|
+
const startTs = flow.firstTs;
|
|
201
|
+
const durationMs = (endTs ?? this._now()) - startTs;
|
|
202
|
+
const stream = _streamMessagesOf(transits);
|
|
203
|
+
return {
|
|
204
|
+
id: flow.id,
|
|
205
|
+
kind: head?.kind === "notification" ? "notification" : "request",
|
|
206
|
+
method: head?.method,
|
|
207
|
+
params: head?.params,
|
|
208
|
+
status,
|
|
209
|
+
result: response?.result,
|
|
210
|
+
error: response?.error,
|
|
211
|
+
startTs,
|
|
212
|
+
endTs,
|
|
213
|
+
durationMs,
|
|
214
|
+
path: this._buildPath(transits),
|
|
215
|
+
partial: status === "pending",
|
|
216
|
+
...stream.length > 0 ? { stream } : {}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
_buildPath(transits) {
|
|
220
|
+
const forward = transits.filter((t) => t.kind === "request" || t.kind === "notification");
|
|
221
|
+
const path = [];
|
|
222
|
+
const push = (s) => {
|
|
223
|
+
if (path[path.length - 1] !== s) path.push(s);
|
|
224
|
+
};
|
|
225
|
+
for (const t of forward) {
|
|
226
|
+
if (t.in) push(this._label(t.in.edgeId));
|
|
227
|
+
push(t.nodeId);
|
|
228
|
+
if (t.out) push(this._label(t.out.edgeId));
|
|
229
|
+
}
|
|
230
|
+
return path;
|
|
231
|
+
}
|
|
232
|
+
_label(edgeId) {
|
|
233
|
+
return this._labelOf?.(edgeId) ?? edgeId;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* Presentation adapter that coalesces the public traffic stream into legacy
|
|
238
|
+
* {@link FlowSummary} values. Routing and endpoint observation stay entirely
|
|
239
|
+
* behind the inspection interfaces; consumers only provide formatting/UI.
|
|
240
|
+
*/
|
|
241
|
+
var TrafficFlowAggregator = class {
|
|
242
|
+
_portLabels = /* @__PURE__ */ new Map();
|
|
243
|
+
_aggregator;
|
|
244
|
+
constructor(options) {
|
|
245
|
+
this._aggregator = new TransitAggregator({
|
|
246
|
+
...options,
|
|
247
|
+
labelOf: (portId) => this._portLabels.get(portId) ?? options.labelOf?.(portId)
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
add(transit) {
|
|
251
|
+
this._aggregator.add({
|
|
252
|
+
timeMs: transit.timeMs,
|
|
253
|
+
nodeId: transit.nodeId,
|
|
254
|
+
...transit.in !== void 0 ? { in: toTransitEndpoint(transit.in) } : {},
|
|
255
|
+
...transit.out !== void 0 ? { out: toTransitEndpoint(transit.out) } : {},
|
|
256
|
+
disposition: transit.disposition,
|
|
257
|
+
kind: transit.kind,
|
|
258
|
+
method: transit.method,
|
|
259
|
+
params: _isJsonValue(transit.params) ? transit.params : void 0,
|
|
260
|
+
result: _isJsonValue(transit.result) ? transit.result : void 0,
|
|
261
|
+
error: transit.error === void 0 ? void 0 : {
|
|
262
|
+
code: transit.error.code,
|
|
263
|
+
message: transit.error.message,
|
|
264
|
+
..._isJsonValue(transit.error.data) ? { data: transit.error.data } : {}
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
setPortLabel(portId, label) {
|
|
269
|
+
this._portLabels.set(portId, label);
|
|
270
|
+
}
|
|
271
|
+
flush() {
|
|
272
|
+
this._aggregator.flush();
|
|
273
|
+
}
|
|
274
|
+
dispose() {
|
|
275
|
+
this._aggregator.dispose();
|
|
276
|
+
this._portLabels.clear();
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
function toTransitEndpoint(endpoint) {
|
|
280
|
+
return {
|
|
281
|
+
edgeId: endpoint.portId,
|
|
282
|
+
portId: endpoint.portId,
|
|
283
|
+
...endpoint.requestId !== void 0 ? { requestId: endpoint.requestId } : {}
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function _isJsonValue(value) {
|
|
287
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return true;
|
|
288
|
+
if (Array.isArray(value)) return value.every(_isJsonValue);
|
|
289
|
+
if (typeof value !== "object" || value === void 0) return false;
|
|
290
|
+
return Object.values(value).every(_isJsonValue);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Render a {@link FlowSummary} as a single, human-readable log line. Payloads
|
|
294
|
+
* are JSON-stringified and truncated. Intended for a VS Code output channel.
|
|
295
|
+
*/
|
|
296
|
+
function formatFlowSummary(s, maxPayload = 200) {
|
|
297
|
+
if (s.kind === "stream") {
|
|
298
|
+
const m = s.stream?.[0];
|
|
299
|
+
const route = s.path.join(" → ");
|
|
300
|
+
const arrow = m?.dir === "toCaller" ? "←" : "→";
|
|
301
|
+
const body = m?.control !== void 0 ? `ctrl:${m.control}` : _trunc(m?.payload, maxPayload);
|
|
302
|
+
return `┄ ${route} ${s.method ?? STREAM_METHOD} ${arrow} stream ${body} (+${s.durationMs}ms)`;
|
|
303
|
+
}
|
|
304
|
+
const head = `${s.status === "completed" ? "✓" : s.status === "failed" ? "✗" : s.status === "pending" ? "⧖" : "·"} ${s.path.join(" → ")} ${`${s.method ?? "(unknown)"}(${_trunc(s.params, maxPayload)})`} ${s.status === "pending" ? `…in flight (${s.durationMs}ms)` : s.status === "completed" ? `→ ${_trunc(s.result, maxPayload)} (${s.durationMs}ms)` : s.status === "failed" ? `→ error ${_trunc(s.error, maxPayload)} (${s.durationMs}ms)` : `${s.status} (${s.durationMs}ms)`}`;
|
|
305
|
+
if (!s.stream || s.stream.length === 0) return head;
|
|
306
|
+
return [head, ...s.stream.map((m) => {
|
|
307
|
+
return ` ${m.dir === "toCaller" ? "←" : "→"} stream ${m.control !== void 0 ? `ctrl:${m.control}` : _trunc(m.payload, maxPayload)}`;
|
|
308
|
+
})].join("\n");
|
|
309
|
+
}
|
|
310
|
+
function _trunc(value, max) {
|
|
311
|
+
if (value === void 0) return "";
|
|
312
|
+
let str;
|
|
313
|
+
try {
|
|
314
|
+
str = JSON.stringify(value) ?? String(value);
|
|
315
|
+
} catch {
|
|
316
|
+
str = String(value);
|
|
317
|
+
}
|
|
318
|
+
return str.length > max ? `${str.slice(0, max)}…` : str;
|
|
319
|
+
}
|
|
320
|
+
/** Extract the `$stream::send` frames from a flow's transits, in arrival order. */
|
|
321
|
+
function _streamMessagesOf(transits) {
|
|
322
|
+
const out = [];
|
|
323
|
+
for (const t of transits) {
|
|
324
|
+
if (t.kind !== "stream") continue;
|
|
325
|
+
const p = t.params ?? void 0;
|
|
326
|
+
const dir = p?.dir === "toCallee" ? "toCallee" : "toCaller";
|
|
327
|
+
const control = typeof p?.control?.type === "string" ? p.control.type : void 0;
|
|
328
|
+
out.push({
|
|
329
|
+
timeMs: t.timeMs,
|
|
330
|
+
dir,
|
|
331
|
+
...control !== void 0 ? { control } : {},
|
|
332
|
+
...p?.payload !== void 0 ? { payload: p.payload } : {}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
return out;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Wire a {@link TransitAggregator} to a line sink: each settled (or partial)
|
|
339
|
+
* flow is rendered with {@link formatFlowSummary} and handed to `log`. This is
|
|
340
|
+
* the shared engine behind both the VS Code "linkrpc Flows" output channel and
|
|
341
|
+
* the CLI's `--log-messages` flag.
|
|
342
|
+
*/
|
|
343
|
+
function createFlowLogger(opts) {
|
|
344
|
+
const agg = new TransitAggregator({
|
|
345
|
+
onFlow: (s) => opts.log(formatFlowSummary(s, opts.maxPayload)),
|
|
346
|
+
...opts.flushDelayMs !== void 0 ? { flushDelayMs: opts.flushDelayMs } : {},
|
|
347
|
+
...opts.giveUpMs !== void 0 ? { giveUpMs: opts.giveUpMs } : {},
|
|
348
|
+
...opts.labelOf !== void 0 ? { labelOf: opts.labelOf } : {}
|
|
349
|
+
});
|
|
350
|
+
return {
|
|
351
|
+
onTransit: (t) => agg.add(t),
|
|
352
|
+
dispose: () => agg.dispose()
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Wrap an {@link IMessageTransport} so every JSON-RPC message crossing it — in
|
|
357
|
+
* both directions — is rendered to a line sink, exactly like {@link Hub} traffic.
|
|
358
|
+
*
|
|
359
|
+
* No hub is involved: the tap point is modelled as a single node with two edges
|
|
360
|
+
* (`local`, `peer`). Each outbound message is a transit `local → peer`, each
|
|
361
|
+
* inbound one `peer → local`; requests and their responses still pair by their
|
|
362
|
+
* shared `(peer-edge, requestId)`, so the rendered flows match the hub view.
|
|
363
|
+
* Use this to inspect a *direct* connection that never touches a local hub.
|
|
364
|
+
*/
|
|
365
|
+
function tapTransport(inner, opts) {
|
|
366
|
+
const logger = createFlowLogger({
|
|
367
|
+
log: opts.log,
|
|
368
|
+
...opts.maxPayload !== void 0 ? { maxPayload: opts.maxPayload } : {}
|
|
369
|
+
});
|
|
370
|
+
const local = opts.localLabel ?? "local";
|
|
371
|
+
const remote = opts.remoteLabel ?? "peer";
|
|
372
|
+
const nodeId = opts.nodeId ?? local;
|
|
373
|
+
const emit = (m, outbound) => {
|
|
374
|
+
try {
|
|
375
|
+
logger.onTransit(_wireTransit(m, outbound, nodeId, local, remote));
|
|
376
|
+
} catch {}
|
|
377
|
+
};
|
|
378
|
+
return {
|
|
379
|
+
send: (m) => {
|
|
380
|
+
emit(m, true);
|
|
381
|
+
return inner.send(m);
|
|
382
|
+
},
|
|
383
|
+
setListener: (listener) => inner.setListener(listener === void 0 ? void 0 : (m) => {
|
|
384
|
+
emit(m, false);
|
|
385
|
+
listener(m);
|
|
386
|
+
}),
|
|
387
|
+
dispose: () => {
|
|
388
|
+
logger.dispose();
|
|
389
|
+
inner.dispose();
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
/** Classify one wire message into a {@link NodeTransit} at the tap node. */
|
|
394
|
+
function _wireTransit(m, outbound, nodeId, local, remote) {
|
|
395
|
+
const fromEdge = outbound ? local : remote;
|
|
396
|
+
const toEdge = outbound ? remote : local;
|
|
397
|
+
let kind;
|
|
398
|
+
let method;
|
|
399
|
+
let requestId;
|
|
400
|
+
let params;
|
|
401
|
+
let result;
|
|
402
|
+
let error;
|
|
403
|
+
const req = m;
|
|
404
|
+
if (typeof req.method === "string") {
|
|
405
|
+
method = req.method;
|
|
406
|
+
params = req.params;
|
|
407
|
+
if (req.id !== void 0 && req.id !== null) {
|
|
408
|
+
kind = "request";
|
|
409
|
+
requestId = req.id;
|
|
410
|
+
} else kind = "notification";
|
|
411
|
+
} else {
|
|
412
|
+
kind = "response";
|
|
413
|
+
const resp = m;
|
|
414
|
+
if (resp.id !== void 0 && resp.id !== null) requestId = resp.id;
|
|
415
|
+
if (m.error !== void 0) {
|
|
416
|
+
const e = m.error;
|
|
417
|
+
error = {
|
|
418
|
+
code: e.code,
|
|
419
|
+
message: e.message,
|
|
420
|
+
...e.data !== void 0 ? { data: e.data } : {}
|
|
421
|
+
};
|
|
422
|
+
} else result = m.result;
|
|
423
|
+
}
|
|
424
|
+
const endpoint = (edgeId) => requestId !== void 0 ? {
|
|
425
|
+
edgeId,
|
|
426
|
+
requestId
|
|
427
|
+
} : { edgeId };
|
|
428
|
+
return {
|
|
429
|
+
timeMs: Date.now(),
|
|
430
|
+
nodeId,
|
|
431
|
+
disposition: "forwarded",
|
|
432
|
+
kind,
|
|
433
|
+
in: endpoint(fromEdge),
|
|
434
|
+
out: endpoint(toEdge),
|
|
435
|
+
...method !== void 0 ? { method } : {},
|
|
436
|
+
...params !== void 0 ? { params } : {},
|
|
437
|
+
...result !== void 0 ? { result } : {},
|
|
438
|
+
...error !== void 0 ? { error } : {}
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
//#endregion
|
|
442
|
+
export { tapTransport as a, formatFlowSummary as i, TransitAggregator as n, createFlowLogger as r, TrafficFlowAggregator as t };
|
|
443
|
+
|
|
444
|
+
//# sourceMappingURL=nodeTransit-CWeFnbwt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodeTransit-CWeFnbwt.js","names":[],"sources":["../../src/hub/server/nodeTransit.ts"],"sourcesContent":["/**\n * Node-transit observation — the inspection primitive for a {@link Hub}.\n *\n * A **node transit** records a single message passing *through* a routing\n * node: the edge it arrived on (`in`) and the edge it left on (`out`),\n * together with the per-edge request id on each side. Because a node is\n * exactly where a router rewrites the JSON-RPC `id` (the hub swaps\n * `originalId → hubId`), capturing the crossing at the node makes the\n * rewrite explicit — so a request and its response pair locally by their\n * `(in.requestId, out.requestId)`, and adjacent nodes chain by the shared\n * `(edgeId, requestId)` on the edge between them. No global span id needed\n * for in-process inspection.\n *\n * Emission is **off by default**: a hub with no transit observer pays no\n * cost (the emit sites short-circuit on the missing observer), mirroring the\n * `IHubLogger` contract.\n */\nimport type {\n JsonRpcError,\n JsonRpcMessage,\n JsonRpcRequest,\n JsonRpcSuccess,\n JsonValue,\n RequestId,\n} from '@hediet/linkrpc';\nimport type { IMessageTransport } from '@hediet/linkrpc';\nimport type { TrafficTransitEvent } from '@hediet/linkrpc/inspection';\nimport { STREAM_METHOD } from '@hediet/linkrpc';\n\n/** What kind of message crossed the node. Cancel/ping/pong are stream controls. */\nexport type TransitKind = 'request' | 'notification' | 'response' | 'stream';\n\n/** What the node did with the message. */\nexport type TransitDisposition = 'forwarded' | 'consumed' | 'dropped' | 'unroutable';\n\n/** One side of a transit: the edge, plus the request id as it appears there. */\nexport interface TransitEndpoint {\n readonly edgeId: string;\n /** Stable topology port id. Older/custom emitters may omit it. */\n readonly portId?: string;\n /** JSON-RPC request id on this edge. Absent for notifications. */\n readonly requestId?: RequestId;\n}\n\n/** Wire-shaped error mirrored onto a `response` transit. */\nexport interface TransitError {\n readonly code: number;\n readonly message: string;\n readonly data?: JsonValue;\n}\n\n/**\n * A single message passing through a node. `in` absent ⇒ the message\n * originated here; `out` absent ⇒ it was consumed or dropped here.\n */\nexport interface NodeTransit {\n readonly timeMs: number;\n readonly nodeId: string;\n readonly in?: TransitEndpoint;\n readonly out?: TransitEndpoint;\n readonly disposition: TransitDisposition;\n readonly kind: TransitKind;\n readonly method?: string;\n readonly params?: JsonValue;\n readonly result?: JsonValue;\n readonly error?: TransitError;\n}\n\n/** Sink for node transits. Synchronous; keep it cheap. */\nexport type NodeTransitObserver = (transit: NodeTransit) => void;\n\n// ---------------------------------------------------------------------------\n// Aggregation\n// ---------------------------------------------------------------------------\n\n/** Status of a coalesced flow at the moment it is reported. */\nexport type FlowStatus = 'completed' | 'failed' | 'pending' | 'dropped' | 'unroutable';\n\n/** One stream frame (`$stream::send`) attached to a flow, in arrival order. */\nexport interface FlowStreamMessage {\n readonly timeMs: number;\n /** `toCaller` = callee→caller (progress); `toCallee` = caller→callee (input/cancel). */\n readonly dir: 'toCaller' | 'toCallee';\n /** Reserved control verb (`cancel`/`ping`/`pong`), if this is a control frame. */\n readonly control?: string;\n /** App stream payload (typed per call by the originating method's stream schema). */\n readonly payload?: JsonValue;\n}\n\n/** A coalesced logical operation, reported once (or twice: partial → final). */\nexport interface FlowSummary {\n readonly id: string;\n readonly kind: TransitKind;\n readonly method: string | undefined;\n readonly params: JsonValue | undefined;\n readonly status: FlowStatus;\n readonly result?: JsonValue;\n readonly error?: TransitError;\n readonly startTs: number;\n readonly endTs: number | undefined;\n readonly durationMs: number;\n /** Ordered, interleaved edge/node labels the message traversed. */\n readonly path: readonly string[];\n /** True when reported while still in flight (slow / stuck). */\n readonly partial: boolean;\n /**\n * Stream frames (`$stream::send`) seen on this flow, in arrival order. Only\n * populated when the hub is configured to emit stream transits (debug+); an\n * absent/empty array means \"not captured\", not \"no streaming happened\".\n */\n readonly stream?: readonly FlowStreamMessage[];\n}\n\nexport interface TransitAggregatorOptions {\n /** Called once a flow settles, or once when it is first reported partial. */\n readonly onFlow: (summary: FlowSummary) => void;\n /** Quiet-period before a flow is flushed. Default 50ms. */\n readonly flushDelayMs?: number;\n /** Drop an unsettled flow after this long (leak guard). Default 5min. */\n readonly giveUpMs?: number;\n /**\n * How long after a request starts its stream frames stay attached to that\n * request's flow. Frames within this window fold into the request summary;\n * later frames are logged independently (live), so a long-running request's\n * stream output is visible promptly and doesn't accumulate unbounded in the\n * pending flow. Default 1000ms.\n */\n readonly streamRetainMs?: number;\n /** Map an edgeId to a friendly label for {@link FlowSummary.path}. */\n readonly labelOf?: (edgeId: string) => string | undefined;\n /** Injected clock (tests). Default {@link Date.now}. */\n readonly now?: () => number;\n /** Injected timer (tests). Default {@link setTimeout}. */\n readonly setTimer?: (cb: () => void, ms: number) => unknown;\n /** Injected timer clear (tests). Default {@link clearTimeout}. */\n readonly clearTimer?: (handle: unknown) => void;\n /** Hash params for the notification-correlation heuristic. Default JSON. */\n readonly hashParams?: (params: JsonValue | undefined) => string;\n}\n\ninterface Flow {\n readonly id: string;\n readonly endpoints: Set<string>;\n readonly transits: NodeTransit[];\n settled: boolean;\n reportedPartial: boolean;\n timer: unknown | undefined;\n timerIsGiveUp: boolean;\n firstTs: number;\n}\n\n/**\n * Coalesces {@link NodeTransit}s into per-operation {@link FlowSummary}s and\n * reports each after a short quiet period. A fast request is reported **once**\n * (path + params + response); a slow/stuck one is reported **partial** while\n * in flight and again on completion.\n *\n * Correlation:\n * - request ↔ response and adjacent-node hops join by shared\n * `(edgeId, requestId)` endpoints;\n * - **notifications** (no request id) join heuristically by\n * `(edgeId, method, paramsHash)` — same edge + method + params ⇒ same message.\n */\nexport class TransitAggregator {\n private readonly _flushDelayMs: number;\n private readonly _giveUpMs: number;\n private readonly _streamRetainMs: number;\n private readonly _now: () => number;\n private readonly _setTimer: (cb: () => void, ms: number) => unknown;\n private readonly _clearTimer: (handle: unknown) => void;\n private readonly _hashParams: (params: JsonValue | undefined) => string;\n private readonly _labelOf: ((edgeId: string) => string | undefined) | undefined;\n\n private readonly _flows = new Set<Flow>();\n private readonly _endpointToFlow = new Map<string, Flow>();\n private _nextId = 1;\n\n constructor(private readonly _options: TransitAggregatorOptions) {\n this._flushDelayMs = _options.flushDelayMs ?? 50;\n this._giveUpMs = _options.giveUpMs ?? 5 * 60_000;\n this._streamRetainMs = _options.streamRetainMs ?? 1000;\n this._now = _options.now ?? (() => Date.now());\n this._setTimer = _options.setTimer ?? ((cb, ms) => setTimeout(cb, ms));\n this._clearTimer = _options.clearTimer ?? ((h) => clearTimeout(h as ReturnType<typeof setTimeout>));\n this._hashParams = _options.hashParams ?? ((p) => JSON.stringify(p ?? null));\n this._labelOf = _options.labelOf;\n }\n\n public add(transit: NodeTransit): void {\n // Stream frames attach to their owning request's flow only briefly: past\n // `streamRetainMs` after the request started (or with no owning flow at\n // all) they're logged independently and never buffered — so long-running\n // requests stream live and don't grow the pending flow unbounded.\n if (transit.kind === 'stream') {\n const parent = this._findFlow(this._endpointKeys(transit));\n const fresh = parent !== undefined\n && transit.timeMs - parent.firstTs <= this._streamRetainMs;\n if (!fresh) {\n this._emitStandaloneStream(transit, parent);\n return;\n }\n }\n\n const keys = this._endpointKeys(transit);\n\n // Find every existing flow this transit touches; merge them.\n let flow: Flow | undefined;\n for (const k of keys) {\n const f = this._endpointToFlow.get(k);\n if (!f) continue;\n if (!flow) flow = f;\n else if (f !== flow) flow = this._merge(flow, f);\n }\n if (!flow) {\n flow = {\n id: `flow${this._nextId++}`,\n endpoints: new Set(),\n transits: [],\n settled: false,\n reportedPartial: false,\n timer: undefined,\n timerIsGiveUp: false,\n firstTs: transit.timeMs,\n };\n this._flows.add(flow);\n }\n\n flow.transits.push(transit);\n if (transit.timeMs < flow.firstTs) flow.firstTs = transit.timeMs;\n for (const k of keys) {\n flow.endpoints.add(k);\n this._endpointToFlow.set(k, flow);\n }\n if (transit.kind === 'response') flow.settled = true;\n\n this._armFlush(flow);\n }\n\n /** Drop all pending flows and timers. Does not emit. */\n public dispose(): void {\n for (const flow of this._flows) {\n if (flow.timer !== undefined) this._clearTimer(flow.timer);\n }\n this._flows.clear();\n this._endpointToFlow.clear();\n }\n\n /** Emit pending flows as partial summaries before a diagnostic capture ends. */\n public flush(): void {\n for (const flow of [...this._flows]) {\n if (flow.timer !== undefined) this._clearTimer(flow.timer);\n flow.timer = undefined;\n this._options.onFlow(this._summarize(flow));\n this._delete(flow);\n }\n }\n\n /** First flow registered under any of `keys`, if any (no merge/create). */\n private _findFlow(keys: string[]): Flow | undefined {\n for (const k of keys) {\n const f = this._endpointToFlow.get(k);\n if (f) return f;\n }\n return undefined;\n }\n\n /**\n * Report a single stream frame as its own one-line flow, correlated to its\n * owning request by method + path (the request itself is reported separately\n * when it settles). Used for frames that arrive after {@link _streamRetainMs}\n * so they surface live instead of waiting on a slow request to finish.\n */\n private _emitStandaloneStream(transit: NodeTransit, parent: Flow | undefined): void {\n const msg = _streamMessagesOf([transit])[0];\n if (!msg) return;\n const method = parent?.transits.find(\n (t) => t.kind === 'request' || t.kind === 'notification',\n )?.method;\n const startTs = parent?.firstTs ?? transit.timeMs;\n this._options.onFlow({\n id: `flow${this._nextId++}`,\n kind: 'stream',\n method,\n params: undefined,\n status: 'completed',\n startTs,\n endTs: transit.timeMs,\n durationMs: transit.timeMs - startTs,\n path: this._buildStreamPath(transit),\n partial: false,\n stream: [msg],\n });\n }\n\n /** Path for a lone stream transit (in → node → out), with labels applied. */\n private _buildStreamPath(t: NodeTransit): string[] {\n const path: string[] = [];\n const push = (s: string): void => {\n if (path[path.length - 1] !== s) path.push(s);\n };\n if (t.in) push(this._label(t.in.edgeId));\n push(t.nodeId);\n if (t.out) push(this._label(t.out.edgeId));\n return path;\n }\n\n private _endpointKeys(t: NodeTransit): string[] {\n const keys: string[] = [];\n for (const ep of [t.in, t.out]) {\n if (!ep) continue;\n if (t.kind === 'notification') {\n // No request id — correlate by edge + method + params.\n keys.push(`${ep.edgeId}#N#${t.method ?? ''}#${this._hashParams(t.params)}`);\n } else if (ep.requestId !== undefined) {\n keys.push(`${ep.edgeId}#${String(ep.requestId)}`);\n }\n }\n return keys;\n }\n\n private _merge(into: Flow, other: Flow): Flow {\n if (into === other) return into;\n for (const t of other.transits) into.transits.push(t);\n for (const k of other.endpoints) {\n into.endpoints.add(k);\n this._endpointToFlow.set(k, into);\n }\n into.settled ||= other.settled;\n into.reportedPartial ||= other.reportedPartial;\n if (other.firstTs < into.firstTs) into.firstTs = other.firstTs;\n if (other.timer !== undefined) this._clearTimer(other.timer);\n this._flows.delete(other);\n return into;\n }\n\n private _armFlush(flow: Flow): void {\n if (flow.timer !== undefined) this._clearTimer(flow.timer);\n flow.timerIsGiveUp = false;\n flow.timer = this._setTimer(() => this._flush(flow), this._flushDelayMs);\n }\n\n private _flush(flow: Flow): void {\n flow.timer = undefined;\n if (!this._flows.has(flow)) return;\n\n const summary = this._summarize(flow);\n if (summary.status === 'pending') {\n if (!flow.reportedPartial) {\n flow.reportedPartial = true;\n this._options.onFlow(summary);\n }\n // Keep the flow alive awaiting completion, but guard against leaks.\n flow.timerIsGiveUp = true;\n flow.timer = this._setTimer(() => this._giveUp(flow), this._giveUpMs);\n return;\n }\n this._options.onFlow(summary);\n this._delete(flow);\n }\n\n private _giveUp(flow: Flow): void {\n flow.timer = undefined;\n this._delete(flow);\n }\n\n private _delete(flow: Flow): void {\n if (flow.timer !== undefined) this._clearTimer(flow.timer);\n flow.timer = undefined;\n this._flows.delete(flow);\n for (const k of flow.endpoints) {\n if (this._endpointToFlow.get(k) === flow) this._endpointToFlow.delete(k);\n }\n }\n\n private _summarize(flow: Flow): FlowSummary {\n const transits = [...flow.transits].sort((a, b) => a.timeMs - b.timeMs);\n const head =\n transits.find((t) => t.kind === 'request') ??\n transits.find((t) => t.kind === 'notification') ??\n transits[0];\n const response = [...transits].reverse().find((t) => t.kind === 'response');\n\n let status: FlowStatus;\n if (response) {\n status = response.error ? 'failed' : 'completed';\n } else if (head?.kind === 'notification') {\n status = head.disposition === 'dropped' ? 'dropped' : 'completed';\n } else if (transits.some((t) => t.disposition === 'unroutable')) {\n status = 'unroutable';\n } else if (transits.some((t) => t.disposition === 'dropped')) {\n status = 'dropped';\n } else {\n status = 'pending';\n }\n\n const endTs = response?.timeMs\n ?? (status === 'pending' ? undefined : transits[transits.length - 1]?.timeMs);\n const startTs = flow.firstTs;\n const durationMs = (endTs ?? this._now()) - startTs;\n\n const stream = _streamMessagesOf(transits);\n\n return {\n id: flow.id,\n kind: head?.kind === 'notification' ? 'notification' : 'request',\n method: head?.method,\n params: head?.params,\n status,\n result: response?.result,\n error: response?.error,\n startTs,\n endTs,\n durationMs,\n path: this._buildPath(transits),\n partial: status === 'pending',\n ...(stream.length > 0 ? { stream } : {}),\n };\n }\n\n private _buildPath(transits: NodeTransit[]): string[] {\n const forward = transits.filter((t) => t.kind === 'request' || t.kind === 'notification');\n const path: string[] = [];\n const push = (s: string): void => {\n if (path[path.length - 1] !== s) path.push(s);\n };\n for (const t of forward) {\n if (t.in) push(this._label(t.in.edgeId));\n push(t.nodeId);\n if (t.out) push(this._label(t.out.edgeId));\n }\n return path;\n }\n\n private _label(edgeId: string): string {\n return this._labelOf?.(edgeId) ?? edgeId;\n }\n}\n\n/**\n * Presentation adapter that coalesces the public traffic stream into legacy\n * {@link FlowSummary} values. Routing and endpoint observation stay entirely\n * behind the inspection interfaces; consumers only provide formatting/UI.\n */\nexport class TrafficFlowAggregator {\n private readonly _portLabels = new Map<string, string>();\n private readonly _aggregator: TransitAggregator;\n\n constructor(options: TransitAggregatorOptions) {\n this._aggregator = new TransitAggregator({\n ...options,\n labelOf: (portId) => this._portLabels.get(portId) ?? options.labelOf?.(portId),\n });\n }\n\n public add(transit: TrafficTransitEvent): void {\n this._aggregator.add({\n timeMs: transit.timeMs,\n nodeId: transit.nodeId,\n ...(transit.in !== undefined ? { in: toTransitEndpoint(transit.in) } : {}),\n ...(transit.out !== undefined ? { out: toTransitEndpoint(transit.out) } : {}),\n disposition: transit.disposition,\n kind: transit.kind,\n method: transit.method,\n params: _isJsonValue(transit.params) ? transit.params : undefined,\n result: _isJsonValue(transit.result) ? transit.result : undefined,\n error: transit.error === undefined\n ? undefined\n : {\n code: transit.error.code,\n message: transit.error.message,\n ...(_isJsonValue(transit.error.data) ? { data: transit.error.data } : {}),\n },\n });\n }\n\n public setPortLabel(portId: string, label: string): void {\n this._portLabels.set(portId, label);\n }\n\n public flush(): void {\n this._aggregator.flush();\n }\n\n public dispose(): void {\n this._aggregator.dispose();\n this._portLabels.clear();\n }\n}\n\nfunction toTransitEndpoint(\n endpoint: NonNullable<TrafficTransitEvent['in']>,\n): TransitEndpoint {\n return {\n edgeId: endpoint.portId,\n portId: endpoint.portId,\n ...(endpoint.requestId !== undefined ? { requestId: endpoint.requestId } : {}),\n };\n}\n\nfunction _isJsonValue(value: unknown): value is JsonValue {\n if (\n value === null\n || typeof value === 'string'\n || typeof value === 'number'\n || typeof value === 'boolean'\n ) {\n return true;\n }\n if (Array.isArray(value)) return value.every(_isJsonValue);\n if (typeof value !== 'object' || value === undefined) return false;\n return Object.values(value).every(_isJsonValue);\n}\n\n/**\n * Render a {@link FlowSummary} as a single, human-readable log line. Payloads\n * are JSON-stringified and truncated. Intended for a VS Code output channel.\n */\nexport function formatFlowSummary(s: FlowSummary, maxPayload = 200): string {\n // A standalone stream frame (one logged independently of its slow request)\n // renders compactly on one line, tagged with the offset since the request\n // started so it can be lined up against the request's own summary.\n if (s.kind === 'stream') {\n const m = s.stream?.[0];\n const route = s.path.join(' → ');\n const arrow = m?.dir === 'toCaller' ? '←' : '→';\n const body = m?.control !== undefined ? `ctrl:${m.control}` : _trunc(m?.payload, maxPayload);\n const call = s.method ?? STREAM_METHOD;\n return `┄ ${route} ${call} ${arrow} stream ${body} (+${s.durationMs}ms)`;\n }\n const icon =\n s.status === 'completed' ? '✓' :\n s.status === 'failed' ? '✗' :\n s.status === 'pending' ? '⧖' :\n '·';\n const route = s.path.join(' → ');\n const call = `${s.method ?? '(unknown)'}(${_trunc(s.params, maxPayload)})`;\n const tail =\n s.status === 'pending' ? `…in flight (${s.durationMs}ms)` :\n s.status === 'completed' ? `→ ${_trunc(s.result, maxPayload)} (${s.durationMs}ms)` :\n s.status === 'failed' ? `→ error ${_trunc(s.error, maxPayload)} (${s.durationMs}ms)` :\n `${s.status} (${s.durationMs}ms)`;\n const head = `${icon} ${route} ${call} ${tail}`;\n if (!s.stream || s.stream.length === 0) return head;\n // Attach each stream frame on its own indented line, in arrival order.\n const frames = s.stream.map((m) => {\n const arrow = m.dir === 'toCaller' ? '←' : '→';\n const body = m.control !== undefined\n ? `ctrl:${m.control}`\n : _trunc(m.payload, maxPayload);\n return ` ${arrow} stream ${body}`;\n });\n return [head, ...frames].join('\\n');\n}\n\nfunction _trunc(value: unknown, max: number): string {\n if (value === undefined) return '';\n let str: string;\n try {\n str = JSON.stringify(value) ?? String(value);\n } catch {\n str = String(value);\n }\n return str.length > max ? `${str.slice(0, max)}…` : str;\n}\n\n/** Extract the `$stream::send` frames from a flow's transits, in arrival order. */\nfunction _streamMessagesOf(transits: readonly NodeTransit[]): FlowStreamMessage[] {\n const out: FlowStreamMessage[] = [];\n for (const t of transits) {\n if (t.kind !== 'stream') continue;\n const p = (t.params ?? undefined) as\n | { dir?: unknown; control?: { type?: unknown }; payload?: JsonValue }\n | undefined;\n const dir = p?.dir === 'toCallee' ? 'toCallee' : 'toCaller';\n const control = typeof p?.control?.type === 'string' ? p.control.type : undefined;\n out.push({\n timeMs: t.timeMs,\n dir,\n ...(control !== undefined ? { control } : {}),\n ...(p?.payload !== undefined ? { payload: p.payload } : {}),\n });\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Flow logger\n// ---------------------------------------------------------------------------\n\nexport interface FlowLoggerOptions {\n /** Sink for the formatted, one-line-per-flow output. */\n readonly log: (line: string) => void;\n /**\n * Max JSON length per payload before truncation. Default 200; pass\n * {@link Number.POSITIVE_INFINITY} for full, untruncated payloads.\n */\n readonly maxPayload?: number;\n /** Quiet-period before a flow is flushed. Default 50ms. */\n readonly flushDelayMs?: number;\n /** Drop an unsettled flow after this long (leak guard). Default 5min. */\n readonly giveUpMs?: number;\n /** Map an edgeId to a friendly label for the rendered path. */\n readonly labelOf?: (edgeId: string) => string | undefined;\n}\n\n/** A ready-to-attach flow logger: an {@link NodeTransitObserver} + teardown. */\nexport interface FlowLogger {\n /** Wire this as a hub / overlay `onTransit` observer. */\n readonly onTransit: NodeTransitObserver;\n /** Flush nothing; just clear pending timers. */\n dispose(): void;\n}\n\n/**\n * Wire a {@link TransitAggregator} to a line sink: each settled (or partial)\n * flow is rendered with {@link formatFlowSummary} and handed to `log`. This is\n * the shared engine behind both the VS Code \"linkrpc Flows\" output channel and\n * the CLI's `--log-messages` flag.\n */\nexport function createFlowLogger(opts: FlowLoggerOptions): FlowLogger {\n const agg = new TransitAggregator({\n onFlow: (s) => opts.log(formatFlowSummary(s, opts.maxPayload)),\n ...(opts.flushDelayMs !== undefined ? { flushDelayMs: opts.flushDelayMs } : {}),\n ...(opts.giveUpMs !== undefined ? { giveUpMs: opts.giveUpMs } : {}),\n ...(opts.labelOf !== undefined ? { labelOf: opts.labelOf } : {}),\n });\n return {\n onTransit: (t) => agg.add(t),\n dispose: () => agg.dispose(),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Wire tap\n// ---------------------------------------------------------------------------\n\nexport interface WireTapOptions {\n /** Sink for the formatted, one-line-per-flow output. */\n readonly log: (line: string) => void;\n /** Max JSON length per payload before truncation. Default 200. */\n readonly maxPayload?: number;\n /** Edge label for the near end (the tapping side). Default `\"local\"`. */\n readonly localLabel?: string;\n /** Edge label for the far end (the peer). Default `\"peer\"`. */\n readonly remoteLabel?: string;\n /** Synthetic node id for the tap point. Default = {@link localLabel}. */\n readonly nodeId?: string;\n}\n\n/**\n * Wrap an {@link IMessageTransport} so every JSON-RPC message crossing it — in\n * both directions — is rendered to a line sink, exactly like {@link Hub} traffic.\n *\n * No hub is involved: the tap point is modelled as a single node with two edges\n * (`local`, `peer`). Each outbound message is a transit `local → peer`, each\n * inbound one `peer → local`; requests and their responses still pair by their\n * shared `(peer-edge, requestId)`, so the rendered flows match the hub view.\n * Use this to inspect a *direct* connection that never touches a local hub.\n */\nexport function tapTransport(\n inner: IMessageTransport,\n opts: WireTapOptions,\n): IMessageTransport {\n const logger = createFlowLogger({\n log: opts.log,\n ...(opts.maxPayload !== undefined ? { maxPayload: opts.maxPayload } : {}),\n });\n const local = opts.localLabel ?? 'local';\n const remote = opts.remoteLabel ?? 'peer';\n const nodeId = opts.nodeId ?? local;\n const emit = (m: JsonRpcMessage, outbound: boolean): void => {\n try {\n logger.onTransit(_wireTransit(m, outbound, nodeId, local, remote));\n } catch { /* never let logging break the transport */ }\n };\n return {\n send: (m) => {\n emit(m, true);\n return inner.send(m);\n },\n setListener: (listener) =>\n inner.setListener(\n listener === undefined\n ? undefined\n : (m) => {\n emit(m, false);\n listener(m);\n },\n ),\n dispose: () => {\n logger.dispose();\n inner.dispose();\n },\n };\n}\n\n/** Classify one wire message into a {@link NodeTransit} at the tap node. */\nfunction _wireTransit(\n m: JsonRpcMessage,\n outbound: boolean,\n nodeId: string,\n local: string,\n remote: string,\n): NodeTransit {\n const fromEdge = outbound ? local : remote;\n const toEdge = outbound ? remote : local;\n\n let kind: TransitKind;\n let method: string | undefined;\n let requestId: RequestId | undefined;\n let params: JsonValue | undefined;\n let result: JsonValue | undefined;\n let error: TransitError | undefined;\n\n const req = m as JsonRpcRequest;\n if (typeof req.method === 'string') {\n method = req.method;\n params = req.params;\n if (req.id !== undefined && req.id !== null) {\n kind = 'request';\n requestId = req.id;\n } else {\n kind = 'notification';\n }\n } else {\n kind = 'response';\n const resp = m as JsonRpcSuccess & JsonRpcError;\n if (resp.id !== undefined && resp.id !== null) requestId = resp.id;\n if ((m as JsonRpcError).error !== undefined) {\n const e = (m as JsonRpcError).error;\n error = {\n code: e.code,\n message: e.message,\n ...(e.data !== undefined ? { data: e.data } : {}),\n };\n } else {\n result = (m as JsonRpcSuccess).result;\n }\n }\n\n const endpoint = (edgeId: string): TransitEndpoint =>\n requestId !== undefined ? { edgeId, requestId } : { edgeId };\n\n return {\n timeMs: Date.now(),\n nodeId,\n disposition: 'forwarded',\n kind,\n in: endpoint(fromEdge),\n out: endpoint(toEdge),\n ...(method !== undefined ? { method } : {}),\n ...(params !== undefined ? { params } : {}),\n ...(result !== undefined ? { result } : {}),\n ...(error !== undefined ? { error } : {}),\n };\n}\n"],"mappings":"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"}
|