@hediet/linkrpc 0.0.2 → 0.0.3

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.
Files changed (33) hide show
  1. package/README.md +141 -3
  2. package/dist/chunks/{hub.interfaces-Bf4VfWNX.js → hub.interfaces-BWqqhgtT.js} +2 -2
  3. package/dist/chunks/{hub.interfaces-Bf4VfWNX.js.map → hub.interfaces-BWqqhgtT.js.map} +1 -1
  4. package/dist/chunks/{hubAccess-C06qAKao.d.ts → hubAccess-DKl7hDrh.d.ts} +2 -2
  5. package/dist/chunks/{hubAccess-C06qAKao.d.ts.map → hubAccess-DKl7hDrh.d.ts.map} +1 -1
  6. package/dist/chunks/{hubFacade-DtQpGpUy.d.ts → hubFacade-CLgLIvBa.d.ts} +2 -2
  7. package/dist/chunks/{hubFacade-DtQpGpUy.d.ts.map → hubFacade-CLgLIvBa.d.ts.map} +1 -1
  8. package/dist/chunks/{hubFacade-BIB9CoAv.js → hubFacade-rlsBq0ZU.js} +2 -2
  9. package/dist/chunks/{hubFacade-BIB9CoAv.js.map → hubFacade-rlsBq0ZU.js.map} +1 -1
  10. package/dist/chunks/{boundedTrafficSubscription-XZ1KwXZC.js → inspectionHost-BA08eF6h.js} +766 -4
  11. package/dist/chunks/inspectionHost-BA08eF6h.js.map +1 -0
  12. package/dist/chunks/{linkRpcConnection-BadD37ok.d.ts → linkRpcConnection-C_Gs4nZ9.d.ts} +2061 -1682
  13. package/dist/chunks/linkRpcConnection-C_Gs4nZ9.d.ts.map +1 -0
  14. package/dist/chunks/{src-CAhXne71.js → src-BaCs8mWd.js} +2512 -2657
  15. package/dist/chunks/src-BaCs8mWd.js.map +1 -0
  16. package/dist/hub/client/index.d.ts +3 -3
  17. package/dist/hub/client/index.js +3 -3
  18. package/dist/hub/common/index.d.ts +2 -325
  19. package/dist/hub/common/index.d.ts.map +1 -1
  20. package/dist/hub/common/index.js +2 -2
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +3 -3
  23. package/dist/inspection/index.d.ts +87 -2
  24. package/dist/inspection/index.d.ts.map +1 -1
  25. package/dist/inspection/index.js +2 -2
  26. package/dist/node.d.ts +3 -3
  27. package/dist/node.js +4 -4
  28. package/dist/web.d.ts +2 -2
  29. package/dist/web.js +3 -3
  30. package/package.json +7 -2
  31. package/dist/chunks/boundedTrafficSubscription-XZ1KwXZC.js.map +0 -1
  32. package/dist/chunks/linkRpcConnection-BadD37ok.d.ts.map +0 -1
  33. package/dist/chunks/src-CAhXne71.js.map +0 -1
@@ -972,6 +972,135 @@ function interfaceFromSchema(schema) {
972
972
  return new InterfaceDefinition(info, members, { frozenSchema: schema });
973
973
  }
974
974
  //#endregion
975
+ //#region src/protocol/jsonRpc.ts
976
+ const ErrorCode = {
977
+ parseError: -32700,
978
+ invalidRequest: -32600,
979
+ methodNotFound: -32601,
980
+ invalidParams: -32602,
981
+ internalError: -32603,
982
+ /** Caller is authenticated but lacks a capability covering the call. */
983
+ permissionRequired: -32401,
984
+ /** The peer a request was routed to detached before it could respond. */
985
+ peerDisconnected: -32402,
986
+ /** The request exceeded the hub's idle timeout with no stream activity. */
987
+ requestTimeout: -32403,
988
+ /** The request was cancelled (by the caller, or by the hub on disconnect). */
989
+ cancelled: -32800
990
+ };
991
+ function isRequest(m) {
992
+ return typeof m.method === "string" && m.id !== void 0;
993
+ }
994
+ function isNotification(m) {
995
+ return typeof m.method === "string" && m.id === void 0;
996
+ }
997
+ function isResponse(m) {
998
+ return typeof m.method !== "string";
999
+ }
1000
+ //#endregion
1001
+ //#region src/crypto/cryptoProvider.ts
1002
+ const _b64UrlAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
1003
+ const _b64UrlReverse = (() => {
1004
+ const m = {};
1005
+ for (let i = 0; i < 64; i++) m[_b64UrlAlphabet[i]] = i;
1006
+ return m;
1007
+ })();
1008
+ function bytesToBase64Url(bytes) {
1009
+ let out = "";
1010
+ let i = 0;
1011
+ for (; i + 3 <= bytes.length; i += 3) {
1012
+ const n = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
1013
+ out += _b64UrlAlphabet[n >> 18 & 63] + _b64UrlAlphabet[n >> 12 & 63] + _b64UrlAlphabet[n >> 6 & 63] + _b64UrlAlphabet[n & 63];
1014
+ }
1015
+ const rem = bytes.length - i;
1016
+ if (rem === 1) {
1017
+ const n = bytes[i] << 16;
1018
+ out += _b64UrlAlphabet[n >> 18 & 63] + _b64UrlAlphabet[n >> 12 & 63];
1019
+ } else if (rem === 2) {
1020
+ const n = bytes[i] << 16 | bytes[i + 1] << 8;
1021
+ out += _b64UrlAlphabet[n >> 18 & 63] + _b64UrlAlphabet[n >> 12 & 63] + _b64UrlAlphabet[n >> 6 & 63];
1022
+ }
1023
+ return out;
1024
+ }
1025
+ function base64UrlToBytes(s) {
1026
+ const len = s.length;
1027
+ const fullGroups = Math.floor(len / 4);
1028
+ const rem = len % 4;
1029
+ if (rem === 1) throw new Error("base64UrlToBytes: invalid length");
1030
+ const outLen = fullGroups * 3 + (rem === 0 ? 0 : rem - 1);
1031
+ const out = new Uint8Array(outLen);
1032
+ let oi = 0;
1033
+ let i = 0;
1034
+ for (let g = 0; g < fullGroups; g++, i += 4) {
1035
+ const n = _lookup(s, i) << 18 | _lookup(s, i + 1) << 12 | _lookup(s, i + 2) << 6 | _lookup(s, i + 3);
1036
+ out[oi++] = n >> 16 & 255;
1037
+ out[oi++] = n >> 8 & 255;
1038
+ out[oi++] = n & 255;
1039
+ }
1040
+ if (rem === 2) {
1041
+ const n = _lookup(s, i) << 18 | _lookup(s, i + 1) << 12;
1042
+ out[oi++] = n >> 16 & 255;
1043
+ } else if (rem === 3) {
1044
+ const n = _lookup(s, i) << 18 | _lookup(s, i + 1) << 12 | _lookup(s, i + 2) << 6;
1045
+ out[oi++] = n >> 16 & 255;
1046
+ out[oi++] = n >> 8 & 255;
1047
+ }
1048
+ return out;
1049
+ }
1050
+ function _lookup(s, i) {
1051
+ const v = _b64UrlReverse[s[i]];
1052
+ if (v === void 0) throw new Error(`base64UrlToBytes: invalid char at ${i}`);
1053
+ return v;
1054
+ }
1055
+ /** Role-tag prefix on a {@link KeyId}'s inline-key form. */
1056
+ const KEY_ID_PREFIX = "key:";
1057
+ /** Role-tag prefix that wraps a genesis {@link KeyId} into a {@link PrincipalId}. */
1058
+ const PRINCIPAL_PREFIX = "id:";
1059
+ /** The inline {@link KeyId} for a raw public key: `"key:" + b64url(pk)`. */
1060
+ function keyIdForPublicKey(pk) {
1061
+ return KEY_ID_PREFIX + bytesToBase64Url(pk);
1062
+ }
1063
+ /** The perpetual {@link PrincipalId} for a raw public key: `"id:key:" + b64url(pk)`. */
1064
+ function principalForPublicKey(pk) {
1065
+ return "id:" + keyIdForPublicKey(pk);
1066
+ }
1067
+ /**
1068
+ * The genesis {@link KeyId} embedded in a perpetual {@link PrincipalId}
1069
+ * (`"id:" + keyId`). Throws if `principal` is not a well-formed `id:` name.
1070
+ */
1071
+ function keyIdForPrincipal(principal) {
1072
+ if (!principal.startsWith("id:")) throw new Error(`malformed principal (missing "id:" prefix): ${principal}`);
1073
+ return principal.slice(3);
1074
+ }
1075
+ /**
1076
+ * The raw public key carried inline by a `"key:"` {@link KeyId}. Throws on a
1077
+ * missing prefix or malformed base64url.
1078
+ */
1079
+ function publicKeyForKeyId(keyId) {
1080
+ if (!keyId.startsWith("key:")) throw new Error(`unsupported keyId (expected "${KEY_ID_PREFIX}" prefix): ${keyId}`);
1081
+ return base64UrlToBytes(keyId.slice(4));
1082
+ }
1083
+ /**
1084
+ * Resolve the verifying public key for `(principal, keyId)`, or `undefined`
1085
+ * to **reject** (fail closed). This is the single seam every verifier goes
1086
+ * through, replacing the old "the id *is* the key" decode.
1087
+ *
1088
+ * Phase 1 supports only inline genesis keys: the `keyId` must be a `"key:"`
1089
+ * form and the principal must be exactly `"id:" + keyId` (genesis
1090
+ * self-certification by string equality). `keydoc:`/rotation forms resolve
1091
+ * through `bindings`/`timeMs` later and are intentionally not handled here.
1092
+ */
1093
+ function resolveSigningKey(args) {
1094
+ const { principal, keyId } = args;
1095
+ if (!keyId.startsWith("key:")) return;
1096
+ if ("id:" + keyId !== principal) return;
1097
+ try {
1098
+ return { publicKey: base64UrlToBytes(keyId.slice(4)) };
1099
+ } catch {
1100
+ return;
1101
+ }
1102
+ }
1103
+ //#endregion
975
1104
  //#region src/inspection/inspection.interfaces.ts
976
1105
  const zTopologyPort = object({
977
1106
  portId: string(),
@@ -1146,6 +1275,142 @@ const nodeInterface = defineInterface({
1146
1275
  description: "Topology bootstrap: identify the node and port at this end of the direct connection."
1147
1276
  }, { getNodeId: requestType(object({}), zNodeInfo) });
1148
1277
  //#endregion
1278
+ //#region src/connection/streaming.ts
1279
+ /**
1280
+ * Direction a {@link STREAM_METHOD} message travels, relative to the
1281
+ * request it is correlated with. Made explicit on the wire so that a
1282
+ * middlebox (the hub) can *author* a stream message — e.g. inject a
1283
+ * cancel when a caller disconnects — without an inbound message whose
1284
+ * arrival link it could infer the direction from.
1285
+ */
1286
+ const StreamDir = {
1287
+ /** callee → caller: progress, partial results. Routes like a response. */
1288
+ toCaller: "toCaller",
1289
+ /** caller → callee: input, cancellation, keepalive ping. */
1290
+ toCallee: "toCallee"
1291
+ };
1292
+ /**
1293
+ * Reserved control verbs. A control message carries no app `payload`; it
1294
+ * is interpreted by the runtime / hub itself, independently of the
1295
+ * originating method's stream schemas, so *any* request is cancellable
1296
+ * and keep-alive-able even when it declares no app stream.
1297
+ */
1298
+ const StreamControlType = {
1299
+ /**
1300
+ * Ask the callee to abort the in-flight request. Always {@link
1301
+ * StreamDir.toCallee}. The callee surfaces this as an `AbortSignal`
1302
+ * and is expected to settle the request (typically with a
1303
+ * `cancelled` error response).
1304
+ */
1305
+ cancel: "cancel",
1306
+ /**
1307
+ * Keepalive / liveness probe. Resets the hub's per-request idle timer
1308
+ * so a long-running call is not reaped, and lets either side actively
1309
+ * probe the peer. Carries a `nonce` the peer echoes in its {@link
1310
+ * StreamControlType.pong}. May travel in either direction. Emitted
1311
+ * automatically (without awaiting a pong) by the channel for streaming
1312
+ * calls; see {@link STREAM_METHOD}.
1313
+ */
1314
+ ping: "ping",
1315
+ /**
1316
+ * Reply to a {@link StreamControlType.ping}, echoing the ping's
1317
+ * `nonce` so the prober can correlate it. Travels opposite the ping.
1318
+ * Carries no app effect — purely a liveness acknowledgement.
1319
+ */
1320
+ pong: "pong"
1321
+ };
1322
+ /**
1323
+ * Well-known `control.reason` strings. The set is **open** — any string
1324
+ * is valid on the wire; these are the reasons linkrpc itself emits.
1325
+ */
1326
+ const StreamControlReason = {
1327
+ /**
1328
+ * The caller's transport dropped while the request was in flight; the
1329
+ * hub cancels the callee's now-orphaned work.
1330
+ */
1331
+ clientDisconnected: "clientDisconnected",
1332
+ /**
1333
+ * The request exceeded the hub's idle timeout with no stream activity
1334
+ * (no ping, no stream message). See {@link STREAM_METHOD} docs.
1335
+ */
1336
+ idleTimeout: "idleTimeout"
1337
+ };
1338
+ /**
1339
+ * Streaming sub-protocol. Long-running requests can emit additional
1340
+ * notifications correlated with the original call in either direction
1341
+ * (callee → caller progress, caller → callee input / cancellation /
1342
+ * keepalive).
1343
+ *
1344
+ * Wire form: a JSON-RPC notification whose method is
1345
+ * `${streamInterface.info.id}::send` and whose params match
1346
+ * `streamInterface.members.send.paramsSchema`. The hub routes purely on
1347
+ * `requestId` (following the originating request's established path, like
1348
+ * a response); the originating request's capability authorization covers
1349
+ * its full stream lifetime, so stream notifications carry no method /
1350
+ * interface namespace of their own. App `payload` contents are typed per
1351
+ * request via the originating method's
1352
+ * `RequestType.clientStreamSchema` / `serverStreamSchema`; reserved
1353
+ * `control` messages are schema-independent.
1354
+ *
1355
+ * Idle timeout: a request that never streams (no `control` ping, no
1356
+ * stream message) is subject to the hub's per-request idle timeout
1357
+ * (default 30 minutes) — at which point it is cancelled and its caller
1358
+ * gets a `requestTimeout` error. This bounds the hub's pending-request
1359
+ * table, which is also what protects it from a slow-loris / DDoS that
1360
+ * opens requests and never completes them. Streaming-enabled calls keep
1361
+ * themselves alive by emitting a periodic {@link StreamControlType.ping}.
1362
+ *
1363
+ * The interface is declarative: nobody calls
1364
+ * `LinkRpcConnection.register(streamInterface, ...)`. The channel and the
1365
+ * hub intercept `$stream::send` directly. The definition exists so
1366
+ * reflection / consent UIs can describe the wire shape and so all wire
1367
+ * constants derive from one source.
1368
+ */
1369
+ const streamInterface = defineInterface({
1370
+ id: "$stream",
1371
+ description: "Reserved sub-protocol for in-flight stream messages correlated to a request by `requestId`."
1372
+ }, { send: notificationType(object({
1373
+ /**
1374
+ * Id of the in-flight request the message belongs to.
1375
+ * The hub rewrites this across the forwarding boundary
1376
+ * in the same way it rewrites response ids.
1377
+ */
1378
+ requestId: union([number(), string()]),
1379
+ /**
1380
+ * Which way this message travels relative to the request.
1381
+ * Explicit so the hub can author messages (e.g. cancel on
1382
+ * disconnect) without inferring direction from arrival.
1383
+ */
1384
+ dir: enum$1(["toCaller", "toCallee"]),
1385
+ /**
1386
+ * Reserved control verb (cancel / ping / pong). Mutually
1387
+ * exclusive with `payload` in practice: a control message
1388
+ * is interpreted by the runtime, not the application.
1389
+ */
1390
+ control: optional(object({
1391
+ type: enum$1([
1392
+ "cancel",
1393
+ "ping",
1394
+ "pong"
1395
+ ]),
1396
+ /** Open-set human/diagnostic reason; see {@link StreamControlReason}. */
1397
+ reason: optional(string()),
1398
+ /**
1399
+ * Correlation token. A `ping` carries a fresh nonce;
1400
+ * the matching `pong` echoes it back so the prober
1401
+ * can resolve the right outstanding probe.
1402
+ */
1403
+ nonce: optional(string())
1404
+ })),
1405
+ /** Opaque app payload — typed per call by the request's stream schema. */
1406
+ payload: optional(unknown())
1407
+ }), { description: "Emit a stream message tied to the request `requestId`." }) });
1408
+ /**
1409
+ * Full wire method name for stream notifications. Pulled from the
1410
+ * interface object so we have a single source of truth.
1411
+ */
1412
+ const STREAM_METHOD = `${streamInterface.info.id}::send`;
1413
+ //#endregion
1149
1414
  //#region src/inspection/trafficFlowFilter.ts
1150
1415
  var TrafficFlowFilter = class {
1151
1416
  _focused;
@@ -1196,24 +1461,37 @@ var TrafficWatchFlowTracker = class {
1196
1461
  this._active.clear();
1197
1462
  this._unclaimed.clear();
1198
1463
  }
1464
+ forgetPort(portId) {
1465
+ for (const flow of this._active) if (flow.hasPort(portId)) this._removeFlow(flow);
1466
+ }
1199
1467
  };
1200
1468
  var CorrelatedFlow = class {
1201
1469
  _endpoints = /* @__PURE__ */ new Set();
1470
+ _ports = /* @__PURE__ */ new Set();
1202
1471
  _completionEndpoint;
1203
1472
  constructor(endpoints, completionEndpoint = endpoints[0]) {
1204
- for (const endpoint of endpoints) this._endpoints.add(endpointKey(endpoint));
1473
+ for (const endpoint of endpoints) {
1474
+ this._endpoints.add(endpointKey(endpoint));
1475
+ this._ports.add(endpoint.portId);
1476
+ }
1205
1477
  this._completionEndpoint = completionEndpoint === void 0 ? void 0 : endpointKey(completionEndpoint);
1206
1478
  }
1207
1479
  matches(transit) {
1208
1480
  return requestRefsOf(transit).some((endpoint) => this._endpoints.has(endpointKey(endpoint)));
1209
1481
  }
1482
+ hasPort(portId) {
1483
+ return this._ports.has(portId);
1484
+ }
1210
1485
  accept(transit) {
1211
1486
  const endpoints = requestRefsOf(transit);
1212
1487
  if (!this.matches(transit)) return {
1213
1488
  matches: false,
1214
1489
  completed: false
1215
1490
  };
1216
- for (const endpoint of endpoints) this._endpoints.add(endpointKey(endpoint));
1491
+ for (const endpoint of endpoints) {
1492
+ this._endpoints.add(endpointKey(endpoint));
1493
+ this._ports.add(endpoint.portId);
1494
+ }
1217
1495
  const completed = transit.kind === "response" && (isTerminal(transit) || this._completionEndpoint !== void 0 && endpoints.some((endpoint) => endpointKey(endpoint) === this._completionEndpoint));
1218
1496
  if (completed) this._endpoints.clear();
1219
1497
  return {
@@ -1382,6 +1660,490 @@ function capPayload(value, maxBytes) {
1382
1660
  return preview;
1383
1661
  }
1384
1662
  //#endregion
1385
- export { EXTENSION_PREFIX as A, requestType as B, defineInterface as C, assertSchemaReferences as D, schemaToZod as E, NotificationType as F, normalizeJsonSchema as H, RequestType as I, applicationError as L, sha256 as M, jcsCanonicalize as N, componentSchemaName as O, jcsCanonicalizeBytes as P, isApplicationErrorValue as R, InterfaceDefinition as S, createSchemaToZod as T, zodToSvcJsonSchema as V, zTrafficEvent as _, topologyInterface as a, zTrafficTransitEvent as b, zParticipantDescriptorSource as c, zTopologyLink as d, zTopologyLinkEndpoint as f, zTopologyTransportInfo as g, zTopologyTransportEndpoint as h, nodeInterface as i, computeInterfaceHash as j, componentSchemaRef as k, zRouteClaim as l, zTopologyPort as m, TrafficFlowFilter as n, trafficInterface as o, zTopologyNode as p, TrafficWatchFlowTracker as r, zParticipantDescriptor as s, BoundedTrafficSubscription as t, zTopologyGraph as u, zTrafficOverflowEvent as v, interfaceFromSchema as w, zTrafficWatchResult as x, zTrafficTransitEndpoint as y, notificationType as z };
1663
+ //#region src/inspection/endpointTrafficInspector.ts
1664
+ const REQUEST_CORRELATION_RETENTION_MS = 18e5;
1665
+ const MAX_REQUEST_CORRELATIONS = 4096;
1666
+ var EndpointTrafficInspector = class {
1667
+ _nodeId;
1668
+ _portId;
1669
+ _emit;
1670
+ _isObserved;
1671
+ _outboundRequests = /* @__PURE__ */ new Map();
1672
+ _inboundRequests = /* @__PURE__ */ new Map();
1673
+ observe = (direction, message) => {
1674
+ this._onMessage(direction, message);
1675
+ };
1676
+ constructor(_nodeId, _portId, _emit, _isObserved) {
1677
+ this._nodeId = _nodeId;
1678
+ this._portId = _portId;
1679
+ this._emit = _emit;
1680
+ this._isObserved = _isObserved;
1681
+ }
1682
+ dispose() {
1683
+ this._outboundRequests.clear();
1684
+ this._inboundRequests.clear();
1685
+ }
1686
+ _onMessage(direction, message) {
1687
+ const now = Date.now();
1688
+ this._pruneRequestCorrelations(now);
1689
+ const publiclyObserved = this._isObserved();
1690
+ if (!publiclyObserved) this.dispose();
1691
+ let transit;
1692
+ const endpoint = this._endpoint("id" in message ? message.id ?? void 0 : void 0);
1693
+ const base = {
1694
+ type: "transit",
1695
+ timeMs: now,
1696
+ nodeId: this._nodeId,
1697
+ ...direction === "inbound" ? { in: endpoint } : { out: endpoint },
1698
+ disposition: direction === "inbound" ? "consumed" : "forwarded"
1699
+ };
1700
+ if (isRequest(message)) {
1701
+ const correlation = {
1702
+ method: message.method,
1703
+ timeMs: now
1704
+ };
1705
+ if (publiclyObserved) {
1706
+ const map = this._requestMap(direction);
1707
+ const key = requestKey(message.id);
1708
+ map.delete(key);
1709
+ map.set(key, correlation);
1710
+ }
1711
+ transit = {
1712
+ ...base,
1713
+ kind: "request",
1714
+ method: message.method,
1715
+ params: message.params
1716
+ };
1717
+ } else if (isResponse(message)) {
1718
+ const responseMap = direction === "inbound" ? this._outboundRequests : this._inboundRequests;
1719
+ const key = requestKey(message.id);
1720
+ const correlation = responseMap.get(key);
1721
+ responseMap.delete(key);
1722
+ transit = {
1723
+ ...base,
1724
+ kind: "response",
1725
+ method: correlation?.method,
1726
+ result: "result" in message ? message.result : void 0,
1727
+ error: "error" in message ? message.error : void 0
1728
+ };
1729
+ } else if (isNotification(message) && message.method === STREAM_METHOD) {
1730
+ const params = message.params;
1731
+ const streamEndpoint = this._endpoint(params?.requestId);
1732
+ transit = {
1733
+ ...base,
1734
+ ...direction === "inbound" ? { in: streamEndpoint } : { out: streamEndpoint },
1735
+ kind: "stream",
1736
+ method: STREAM_METHOD,
1737
+ params: message.params
1738
+ };
1739
+ } else transit = {
1740
+ ...base,
1741
+ kind: "notification",
1742
+ method: message.method,
1743
+ params: message.params
1744
+ };
1745
+ function requestKey(requestId) {
1746
+ return JSON.stringify([typeof requestId, requestId]);
1747
+ }
1748
+ this._emit(transit);
1749
+ }
1750
+ _requestMap(direction) {
1751
+ return direction === "outbound" ? this._outboundRequests : this._inboundRequests;
1752
+ }
1753
+ _endpoint(requestId) {
1754
+ return {
1755
+ edgeId: this._portId,
1756
+ portId: this._portId,
1757
+ ...requestId !== void 0 ? { requestId } : {}
1758
+ };
1759
+ }
1760
+ _pruneRequestCorrelations(timeMs) {
1761
+ this._pruneRequestMap(this._outboundRequests, timeMs);
1762
+ this._pruneRequestMap(this._inboundRequests, timeMs);
1763
+ }
1764
+ _pruneRequestMap(map, timeMs) {
1765
+ while (map.size !== 0) {
1766
+ const first = map.entries().next().value;
1767
+ if (first === void 0 || map.size <= MAX_REQUEST_CORRELATIONS && timeMs - first[1].timeMs <= REQUEST_CORRELATION_RETENTION_MS) return;
1768
+ map.delete(first[0]);
1769
+ }
1770
+ }
1771
+ };
1772
+ //#endregion
1773
+ //#region src/inspection/connectionInspectionSource.ts
1774
+ var ConnectionInspectionSource = class {
1775
+ _connection;
1776
+ _nodeId;
1777
+ _options;
1778
+ _isObserved;
1779
+ constructor(_connection, _nodeId, _options, _isObserved) {
1780
+ this._connection = _connection;
1781
+ this._nodeId = _nodeId;
1782
+ this._options = _options;
1783
+ this._isObserved = _isObserved;
1784
+ }
1785
+ snapshotTopology() {
1786
+ const { portId, label, peer, transport } = this._options;
1787
+ const nodes = [{
1788
+ nodeId: this._nodeId,
1789
+ ports: [{
1790
+ portId,
1791
+ ...label === void 0 ? {} : { label }
1792
+ }]
1793
+ }];
1794
+ const links = [];
1795
+ if (peer !== void 0) {
1796
+ nodes.push({
1797
+ nodeId: peer.nodeId,
1798
+ kind: "endpoint",
1799
+ ...peer.label === void 0 ? {} : { label: peer.label },
1800
+ ports: [{ portId: peer.portId }]
1801
+ });
1802
+ links.push({
1803
+ from: {
1804
+ nodeId: this._nodeId,
1805
+ portId
1806
+ },
1807
+ to: {
1808
+ nodeId: peer.nodeId,
1809
+ portId: peer.portId
1810
+ },
1811
+ peerState: "identified",
1812
+ ...transport === void 0 ? {} : { transport }
1813
+ });
1814
+ }
1815
+ return {
1816
+ nodes,
1817
+ links,
1818
+ routes: []
1819
+ };
1820
+ }
1821
+ onTopologyChanged(_listener) {
1822
+ return { dispose: () => {} };
1823
+ }
1824
+ observeTraffic(listener) {
1825
+ const inspector = new EndpointTrafficInspector(this._nodeId, this._options.portId, listener, this._isObserved);
1826
+ const observation = this._connection.observeWireMessages(inspector.observe);
1827
+ return { dispose: () => {
1828
+ observation.dispose();
1829
+ inspector.dispose();
1830
+ } };
1831
+ }
1832
+ };
1833
+ //#endregion
1834
+ //#region src/inspection/inspectionHost.ts
1835
+ /** A shared inspection scope, independent of both routing and its RPC connections. */
1836
+ var InspectionHost = class {
1837
+ _options;
1838
+ _sources = /* @__PURE__ */ new Map();
1839
+ _trackedConnections = /* @__PURE__ */ new Set();
1840
+ _bindings = /* @__PURE__ */ new Set();
1841
+ _subscribers = /* @__PURE__ */ new Set();
1842
+ _observers = /* @__PURE__ */ new Set();
1843
+ _topologyListeners = /* @__PURE__ */ new Set();
1844
+ _trafficSources = /* @__PURE__ */ new Set();
1845
+ _trafficWatches = new TrafficWatchFlowTracker();
1846
+ _disposed = false;
1847
+ nodeId;
1848
+ constructor(_options) {
1849
+ this._options = _options;
1850
+ this.nodeId = _options.nodeId;
1851
+ }
1852
+ get observerCount() {
1853
+ return this._subscribers.size + this._observers.size;
1854
+ }
1855
+ addSource(source) {
1856
+ this._assertActive();
1857
+ if (this._sources.has(source)) throw new Error("Inspection source is already registered");
1858
+ const readPorts = () => new Set(source.snapshotTopology().nodes.flatMap((node) => node.ports.map((port) => port.portId)));
1859
+ let ports = readPorts();
1860
+ const topology = source.onTopologyChanged(() => {
1861
+ const nextPorts = readPorts();
1862
+ const removed = [...ports].filter((port) => !nextPorts.has(port));
1863
+ ports = nextPorts;
1864
+ this._forgetUnusedPorts(removed);
1865
+ this._invalidateTopology();
1866
+ });
1867
+ let traffic;
1868
+ try {
1869
+ traffic = source.observeTraffic((event) => this.publishTransit(event));
1870
+ } catch (error) {
1871
+ topology.dispose();
1872
+ throw error;
1873
+ }
1874
+ const registration = { dispose: () => {
1875
+ if (this._sources.get(source) !== registration) return;
1876
+ this._sources.delete(source);
1877
+ topology.dispose();
1878
+ traffic.dispose();
1879
+ this._forgetUnusedPorts([...ports]);
1880
+ this._invalidateTopology();
1881
+ } };
1882
+ this._sources.set(source, registration);
1883
+ this._invalidateTopology();
1884
+ return registration;
1885
+ }
1886
+ trackConnection(connection, options) {
1887
+ this._assertActive();
1888
+ if (this.getGraph("").nodes.some((node) => node.ports.some((port) => port.portId === options.portId))) throw new Error(`Inspection port "${options.portId}" is already registered`);
1889
+ const source = new ConnectionInspectionSource(connection, this.nodeId, options, () => this.observerCount !== 0);
1890
+ const registration = this.addSource(source);
1891
+ let close;
1892
+ try {
1893
+ close = connection.onDidClose(() => result.dispose());
1894
+ } catch (error) {
1895
+ registration.dispose();
1896
+ throw error;
1897
+ }
1898
+ const result = { dispose: () => {
1899
+ this._trackedConnections.delete(result);
1900
+ close.dispose();
1901
+ registration.dispose();
1902
+ } };
1903
+ this._trackedConnections.add(result);
1904
+ return result;
1905
+ }
1906
+ expose(connection, options) {
1907
+ this._assertActive();
1908
+ const portId = options.portId ?? createPortId();
1909
+ const binding = new InspectionBinding(portId);
1910
+ const registrations = [];
1911
+ this._bindings.add(binding);
1912
+ const registerOptions = { serviceId: options.serviceId };
1913
+ try {
1914
+ const controls = new EndpointTrafficInspector(this.nodeId, portId, (event) => this._trafficWatches.accept(event), () => false);
1915
+ registrations.push(connection.observeWireMessages(controls.observe));
1916
+ registrations.push({ dispose: () => controls.dispose() });
1917
+ registrations.push(connection.registerInspection(nodeInterface, { getNodeId: () => ({
1918
+ nodeId: this.nodeId,
1919
+ portId,
1920
+ ...(options.descriptors ?? this._options.descriptors) === void 0 ? {} : { descriptors: [...options.descriptors ?? this._options.descriptors ?? []] }
1921
+ }) }, registerOptions));
1922
+ registrations.push(connection.registerInspection(topologyInterface, {
1923
+ getGraph: () => this.getGraph(options.serviceId, options.includeRoute === false ? void 0 : portId),
1924
+ watchGraph: (_params, _ctx, stream) => this._watchGraph(binding, stream)
1925
+ }, registerOptions));
1926
+ registrations.push(connection.registerInspection(trafficInterface, {
1927
+ watch: (params, _ctx, stream) => this._watchTraffic(binding, params, stream),
1928
+ watchWithPayloads: (params, _ctx, stream) => this._watchTraffic(binding, params, stream)
1929
+ }, registerOptions));
1930
+ } catch (error) {
1931
+ for (const registration of registrations.reverse()) registration.dispose();
1932
+ this._bindings.delete(binding);
1933
+ throw error;
1934
+ }
1935
+ binding.onDispose = () => {
1936
+ this._bindings.delete(binding);
1937
+ for (const registration of registrations.reverse()) registration.dispose();
1938
+ this._forgetUnusedPorts([portId]);
1939
+ this._invalidateTopology();
1940
+ };
1941
+ registrations.push(connection.onDidClose(() => binding.dispose()));
1942
+ this._invalidateTopology();
1943
+ return binding;
1944
+ }
1945
+ getGraph(observerServiceId, routePortId) {
1946
+ this._assertActive();
1947
+ const nodes = /* @__PURE__ */ new Map();
1948
+ const mergeNode = (node) => {
1949
+ const previous = nodes.get(node.nodeId);
1950
+ const ports = new Map(previous?.ports.map((port) => [port.portId, port]));
1951
+ for (const port of node.ports) ports.set(port.portId, {
1952
+ ...ports.get(port.portId),
1953
+ ...port
1954
+ });
1955
+ nodes.set(node.nodeId, {
1956
+ ...previous,
1957
+ ...node,
1958
+ ports: [...ports.values()]
1959
+ });
1960
+ };
1961
+ mergeNode({
1962
+ nodeId: this.nodeId,
1963
+ kind: this._options.kind ?? "endpoint",
1964
+ ...this._options.label === void 0 ? {} : { label: this._options.label },
1965
+ ...this._options.descriptors === void 0 ? {} : { descriptors: [...this._options.descriptors] },
1966
+ ports: []
1967
+ });
1968
+ const links = [];
1969
+ const routes = [];
1970
+ for (const source of this._sources.keys()) {
1971
+ const fragment = source.snapshotTopology();
1972
+ for (const node of fragment.nodes) mergeNode(node);
1973
+ links.push(...fragment.links);
1974
+ routes.push(...fragment.routes);
1975
+ }
1976
+ for (const binding of this._bindings) mergeNode({
1977
+ nodeId: this.nodeId,
1978
+ ports: [{ portId: binding.portId }]
1979
+ });
1980
+ if (routePortId !== void 0) routes.push({
1981
+ serviceId: observerServiceId,
1982
+ nodeId: this.nodeId,
1983
+ portId: routePortId,
1984
+ match: "exact"
1985
+ });
1986
+ return {
1987
+ observerServiceId,
1988
+ entryNodeId: this.nodeId,
1989
+ nodes: [...nodes.values()],
1990
+ links,
1991
+ routes
1992
+ };
1993
+ }
1994
+ publishTransit(event) {
1995
+ if (this._disposed || this._trafficWatches.accept(event)) return;
1996
+ for (const observer of [...this._observers]) try {
1997
+ observer(event);
1998
+ } catch {}
1999
+ for (const subscriber of this._subscribers) subscriber.enqueue(event);
2000
+ }
2001
+ subscribe(options, send) {
2002
+ this._assertActive();
2003
+ if (options.trafficIgnoreKey !== void 0 && !this._trafficWatches.claim(options.trafficIgnoreKey)) throw new Error("Traffic watch request was not observed before subscription");
2004
+ const subscriber = new BoundedTrafficSubscription(options, send, () => {
2005
+ this._subscribers.delete(subscriber);
2006
+ this._stopUnusedTrafficSources();
2007
+ });
2008
+ this._subscribers.add(subscriber);
2009
+ try {
2010
+ this._startTrafficSources();
2011
+ } catch (error) {
2012
+ subscriber.dispose();
2013
+ throw error;
2014
+ }
2015
+ return subscriber;
2016
+ }
2017
+ observeTraffic(observer) {
2018
+ this._assertActive();
2019
+ this._observers.add(observer);
2020
+ const registration = { dispose: () => {
2021
+ this._observers.delete(observer);
2022
+ this._stopUnusedTrafficSources();
2023
+ } };
2024
+ try {
2025
+ this._startTrafficSources();
2026
+ } catch (error) {
2027
+ registration.dispose();
2028
+ throw error;
2029
+ }
2030
+ return registration;
2031
+ }
2032
+ addTrafficSource(source) {
2033
+ this._assertActive();
2034
+ const entry = { source };
2035
+ this._trafficSources.add(entry);
2036
+ try {
2037
+ this._startTrafficSources();
2038
+ } catch (error) {
2039
+ this._trafficSources.delete(entry);
2040
+ throw error;
2041
+ }
2042
+ return { dispose: () => {
2043
+ this._trafficSources.delete(entry);
2044
+ entry.observation?.dispose();
2045
+ entry.observation = void 0;
2046
+ } };
2047
+ }
2048
+ dispose() {
2049
+ if (this._disposed) return;
2050
+ this._disposed = true;
2051
+ for (const binding of [...this._bindings]) binding.dispose();
2052
+ for (const subscriber of [...this._subscribers]) subscriber.dispose();
2053
+ this._observers.clear();
2054
+ this._stopUnusedTrafficSources();
2055
+ this._trafficSources.clear();
2056
+ for (const connection of [...this._trackedConnections]) connection.dispose();
2057
+ for (const source of [...this._sources.values()]) source.dispose();
2058
+ this._topologyListeners.clear();
2059
+ this._trafficWatches.clear();
2060
+ }
2061
+ _startTrafficSources() {
2062
+ if (this.observerCount === 0) return;
2063
+ for (const entry of this._trafficSources) entry.observation ??= entry.source.observe((event) => this.publishTransit(event));
2064
+ }
2065
+ _stopUnusedTrafficSources() {
2066
+ if (this.observerCount !== 0) return;
2067
+ for (const entry of this._trafficSources) {
2068
+ entry.observation?.dispose();
2069
+ entry.observation = void 0;
2070
+ }
2071
+ }
2072
+ _invalidateTopology() {
2073
+ if (this._disposed) return;
2074
+ for (const listener of this._topologyListeners) listener();
2075
+ }
2076
+ _forgetUnusedPorts(portIds) {
2077
+ if (this._disposed) return;
2078
+ const remaining = new Set(this.getGraph("").nodes.flatMap((node) => node.ports.map((port) => port.portId)));
2079
+ for (const portId of portIds) if (!remaining.has(portId)) this._trafficWatches.forgetPort(portId);
2080
+ }
2081
+ _watchGraph(binding, stream) {
2082
+ return new Promise((resolve, reject) => {
2083
+ let pending = false;
2084
+ let active = true;
2085
+ const finish = () => {
2086
+ active = false;
2087
+ this._topologyListeners.delete(invalidate);
2088
+ stream.signal.removeEventListener("abort", finish);
2089
+ binding.abort.signal.removeEventListener("abort", finish);
2090
+ resolve({});
2091
+ };
2092
+ const invalidate = () => {
2093
+ if (pending || !active) return;
2094
+ pending = true;
2095
+ queueMicrotask(() => {
2096
+ pending = false;
2097
+ if (!active) return;
2098
+ stream.send({}).catch((error) => {
2099
+ reject(error);
2100
+ finish();
2101
+ });
2102
+ });
2103
+ };
2104
+ this._topologyListeners.add(invalidate);
2105
+ stream.signal.addEventListener("abort", finish, { once: true });
2106
+ binding.abort.signal.addEventListener("abort", finish, { once: true });
2107
+ if (stream.signal.aborted || binding.abort.signal.aborted) finish();
2108
+ });
2109
+ }
2110
+ async _watchTraffic(binding, options, stream) {
2111
+ const subscription = this.subscribe(options, (event) => stream.send(event));
2112
+ const dispose = () => subscription.dispose();
2113
+ stream.signal.addEventListener("abort", dispose, { once: true });
2114
+ binding.abort.signal.addEventListener("abort", dispose, { once: true });
2115
+ if (stream.signal.aborted || binding.abort.signal.aborted) dispose();
2116
+ try {
2117
+ return await subscription.closed;
2118
+ } finally {
2119
+ stream.signal.removeEventListener("abort", dispose);
2120
+ binding.abort.signal.removeEventListener("abort", dispose);
2121
+ subscription.dispose();
2122
+ }
2123
+ }
2124
+ _assertActive() {
2125
+ if (this._disposed) throw new Error("Inspection host is disposed");
2126
+ }
2127
+ };
2128
+ var InspectionBinding = class {
2129
+ portId;
2130
+ abort = new AbortController();
2131
+ onDispose = () => {};
2132
+ constructor(portId) {
2133
+ this.portId = portId;
2134
+ }
2135
+ dispose() {
2136
+ if (this.abort.signal.aborted) return;
2137
+ this.abort.abort();
2138
+ this.onDispose();
2139
+ }
2140
+ };
2141
+ function createPortId() {
2142
+ const bytes = /* @__PURE__ */ new Uint8Array(16);
2143
+ globalThis.crypto.getRandomValues(bytes);
2144
+ return `port:${bytesToBase64Url(bytes)}`;
2145
+ }
2146
+ //#endregion
2147
+ export { jcsCanonicalize as $, PRINCIPAL_PREFIX as A, isRequest as B, zTopologyTransportInfo as C, zTrafficTransitEvent as D, zTrafficTransitEndpoint as E, principalForPublicKey as F, createSchemaToZod as G, InterfaceDefinition as H, publicKeyForKeyId as I, componentSchemaName as J, schemaToZod as K, resolveSigningKey as L, bytesToBase64Url as M, keyIdForPrincipal as N, zTrafficWatchResult as O, keyIdForPublicKey as P, sha256 as Q, ErrorCode as R, zTopologyTransportEndpoint as S, zTrafficOverflowEvent as T, defineInterface as U, isResponse as V, interfaceFromSchema as W, EXTENSION_PREFIX as X, componentSchemaRef as Y, computeInterfaceHash as Z, zTopologyGraph as _, TrafficWatchFlowTracker as a, notificationType as at, zTopologyNode as b, StreamControlType as c, normalizeJsonSchema as ct, nodeInterface as d, jcsCanonicalizeBytes as et, topologyInterface as f, zRouteClaim as g, zParticipantDescriptorSource as h, TrafficFlowFilter as i, isApplicationErrorValue as it, base64UrlToBytes as j, KEY_ID_PREFIX as k, StreamDir as l, zParticipantDescriptor as m, ConnectionInspectionSource as n, RequestType as nt, STREAM_METHOD as o, requestType as ot, trafficInterface as p, assertSchemaReferences as q, BoundedTrafficSubscription as r, applicationError as rt, StreamControlReason as s, zodToSvcJsonSchema as st, InspectionHost as t, NotificationType as tt, streamInterface as u, zTopologyLink as v, zTrafficEvent as w, zTopologyPort as x, zTopologyLinkEndpoint as y, isNotification as z };
1386
2148
 
1387
- //# sourceMappingURL=boundedTrafficSubscription-XZ1KwXZC.js.map
2149
+ //# sourceMappingURL=inspectionHost-BA08eF6h.js.map