@vscode/component-explorer-cli 0.2.1-163 → 0.2.1-164

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 (89) hide show
  1. package/README.md +90 -0
  2. package/dist/_virtual/_build-info.js +1 -1
  3. package/dist/commands/mcpCommand.js +6 -1
  4. package/dist/commands/mcpCommand.js.map +1 -1
  5. package/dist/commands/serveCommand.d.ts +4 -1
  6. package/dist/commands/serveCommand.d.ts.map +1 -1
  7. package/dist/commands/serveCommand.js +91 -25
  8. package/dist/commands/serveCommand.js.map +1 -1
  9. package/dist/daemon/DaemonService.d.ts +5 -208
  10. package/dist/daemon/DaemonService.d.ts.map +1 -1
  11. package/dist/daemon/DaemonService.js +228 -304
  12. package/dist/daemon/DaemonService.js.map +1 -1
  13. package/dist/daemon/client.d.ts +76 -5
  14. package/dist/daemon/client.d.ts.map +1 -1
  15. package/dist/daemon/daemonRpc.test.d.ts +2 -0
  16. package/dist/daemon/daemonRpc.test.d.ts.map +1 -0
  17. package/dist/daemon/endpointServer.d.ts +4 -0
  18. package/dist/daemon/endpointServer.d.ts.map +1 -0
  19. package/dist/daemon/endpointServer.js +24 -0
  20. package/dist/daemon/endpointServer.js.map +1 -0
  21. package/dist/daemon/inProcessClient.d.ts +3 -9
  22. package/dist/daemon/inProcessClient.d.ts.map +1 -1
  23. package/dist/daemon/inProcessClient.js +29 -30
  24. package/dist/daemon/inProcessClient.js.map +1 -1
  25. package/dist/daemon/lifecycle.d.ts +4 -5
  26. package/dist/daemon/lifecycle.d.ts.map +1 -1
  27. package/dist/daemon/lifecycle.js +3 -2
  28. package/dist/daemon/lifecycle.js.map +1 -1
  29. package/dist/daemon/pipeClient.d.ts +2 -3
  30. package/dist/daemon/pipeClient.d.ts.map +1 -1
  31. package/dist/daemon/pipeClient.js +93 -195
  32. package/dist/daemon/pipeClient.js.map +1 -1
  33. package/dist/daemon/pipeName.d.ts +2 -2
  34. package/dist/daemon/pipeName.d.ts.map +1 -1
  35. package/dist/daemon/pipeName.js +6 -4
  36. package/dist/daemon/pipeName.js.map +1 -1
  37. package/dist/daemon/pipeServer.d.ts +2 -2
  38. package/dist/daemon/pipeServer.d.ts.map +1 -1
  39. package/dist/daemon/pipeServer.js +79 -70
  40. package/dist/daemon/pipeServer.js.map +1 -1
  41. package/dist/daemon/registerDaemonConnection.d.ts +7 -0
  42. package/dist/daemon/registerDaemonConnection.d.ts.map +1 -0
  43. package/dist/daemon/registerDaemonConnection.js +37 -0
  44. package/dist/daemon/registerDaemonConnection.js.map +1 -0
  45. package/dist/daemon/transports.test.d.ts +2 -0
  46. package/dist/daemon/transports.test.d.ts.map +1 -0
  47. package/dist/daemon/types.d.ts +1655 -8
  48. package/dist/daemon/types.d.ts.map +1 -1
  49. package/dist/daemon/types.js +4 -0
  50. package/dist/daemon/types.js.map +1 -0
  51. package/dist/daemon/version.d.ts +1 -5
  52. package/dist/daemon/version.d.ts.map +1 -1
  53. package/dist/daemon/version.js +2 -6
  54. package/dist/daemon/version.js.map +1 -1
  55. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/inspectionHost-BA6ZmC_P.js +2523 -0
  56. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/inspectionHost-BA6ZmC_P.js.map +1 -0
  57. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/src-DYVSK_tz.js +1871 -0
  58. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/src-DYVSK_tz.js.map +1 -0
  59. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/webSocketTransport-Ct4OIdG9.js +220 -0
  60. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/webSocketTransport-Ct4OIdG9.js.map +1 -0
  61. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/node.js +721 -0
  62. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/node.js.map +1 -0
  63. package/dist/index.d.ts +0 -2
  64. package/dist/js-component-explorer/packages/common/dist/daemonRpc.js +165 -0
  65. package/dist/js-component-explorer/packages/common/dist/daemonRpc.js.map +1 -0
  66. package/dist/mcp/DaemonAccessor.d.ts +2 -3
  67. package/dist/mcp/DaemonAccessor.d.ts.map +1 -1
  68. package/dist/mcp/McpServer.d.ts +1 -1
  69. package/dist/mcp/McpServer.d.ts.map +1 -1
  70. package/dist/mcp/McpServer.js +69 -58
  71. package/dist/mcp/McpServer.js.map +1 -1
  72. package/dist/renderManifest.d.ts +70 -5
  73. package/dist/server/serverConfig.d.ts +0 -2
  74. package/dist/server/serverConfig.d.ts.map +1 -1
  75. package/dist/server/serverConfig.js.map +1 -1
  76. package/dist/server/viteServer.d.ts +0 -2
  77. package/dist/server/viteServer.d.ts.map +1 -1
  78. package/dist/server/viteServer.js.map +1 -1
  79. package/package.json +9 -8
  80. package/dist/formatValue.js +0 -96
  81. package/dist/formatValue.js.map +0 -1
  82. package/dist/js-component-explorer/packages/simple-api/dist/chunk-FJ7AVNQE.js +0 -138
  83. package/dist/js-component-explorer/packages/simple-api/dist/chunk-FJ7AVNQE.js.map +0 -1
  84. package/dist/js-component-explorer/packages/simple-api/dist/chunk-TTRCY65Z.js +0 -27
  85. package/dist/js-component-explorer/packages/simple-api/dist/chunk-TTRCY65Z.js.map +0 -1
  86. package/dist/js-component-explorer/packages/simple-api/dist/chunk-WNXMRXWV.js +0 -28
  87. package/dist/js-component-explorer/packages/simple-api/dist/chunk-WNXMRXWV.js.map +0 -1
  88. package/dist/js-component-explorer/packages/simple-api/dist/express.js +0 -104
  89. package/dist/js-component-explorer/packages/simple-api/dist/express.js.map +0 -1
@@ -0,0 +1,1871 @@
1
+ import { Q as preferRicherInterfaceSchema, xt as ErrorCode, d as nodeInterface, t as InspectionHost, l as StreamDir, c as StreamControlType, o as STREAM_METHOD, wt as isResponse, St as isNotification, Ct as isRequest, M as bytesToBase64Url, z as defineInterface, mt as requestType, Tt as RpcFailure, ft as isApplicationErrorValue, vt as parseDeclaredErrorBody, _t as jsonIssues, bt as brandApplicationError } from './inspectionHost-BA6ZmC_P.js';
2
+ import { object, array, string, optional, boolean, number, int, nonnegative, positive, unknown, union } from 'zod/mini';
3
+ import { safeParse } from 'zod/v4/core';
4
+
5
+ /*---------------------------------------------------------------------------------------------
6
+ * Copyright (c) Microsoft Corporation. All rights reserved.
7
+ * Licensed under the MIT License. See License.txt in the project root for license information.
8
+ *--------------------------------------------------------------------------------------------*/
9
+ //#endregion
10
+ //#region src/connection/nonCompliantServerError.ts
11
+ var NonCompliantServerError = class extends Error {
12
+ original;
13
+ issues;
14
+ kind = "nonCompliantServer";
15
+ constructor(original, issues) {
16
+ super(`Server error ${original.code} does not match its declared schema: ${issues.map((issue) => `${issue.path || "/"}: ${issue.message}`).join("; ")}`);
17
+ this.original = original;
18
+ this.issues = issues;
19
+ this.name = "NonCompliantServerError";
20
+ }
21
+ };
22
+ function validateBarePrefix(prefix) {
23
+ if (prefix.includes("::") || !/^[\x20-\x7e]*$/.test(prefix)) throw new Error("Bare interface prefix must contain only printable ASCII and must not contain \"::\".");
24
+ }
25
+ new Set("break case catch class const continue debugger default delete do else enum export extends false finally for function if import in instanceof new null return super switch this throw true try typeof var void while with implements interface let package private protected public static yield await".split(" "));
26
+ //#endregion
27
+ //#region src/protocol/methodName.ts
28
+ /**
29
+ * Parse a JSON-RPC method string. Returns `undefined` if any segment is
30
+ * empty or the segment count is out of range.
31
+ */
32
+ function parseMethodName(method) {
33
+ const parts = method.split("::");
34
+ if (parts.length === 3 && parts[0].length === 0 && parts[1].length > 0 && parts[2].length > 0) return {
35
+ kind: "interface",
36
+ interfaceId: parts[1],
37
+ member: parts[2]
38
+ };
39
+ if (parts.some((p) => p.length === 0)) return void 0;
40
+ if (parts.length === 1) return {
41
+ kind: "bare",
42
+ member: parts[0]
43
+ };
44
+ if (parts.length === 2) return {
45
+ kind: "interface",
46
+ interfaceId: parts[0],
47
+ member: parts[1]
48
+ };
49
+ if (parts.length === 3) return {
50
+ kind: "full",
51
+ serviceId: parts[0],
52
+ interfaceId: parts[1],
53
+ member: parts[2]
54
+ };
55
+ }
56
+ //#endregion
57
+ //#region src/protocol/signedObject.ts
58
+ /**
59
+ * The HubRPC **signed-object** standard: one rule for committing a
60
+ * signature (and a content hash) over any JSON object.
61
+ *
62
+ * > The signature `obj.$hubrpcSignature[domain]` covers
63
+ * > `jcs({ [domainValue(domain)]: obj without $hubrpcSignature and $hubrpcUnsigned })`.
64
+ *
65
+ * Both reserved keys are stripped before canonicalization — on **every**
66
+ * object, call or capability alike:
67
+ *
68
+ * - {@link HUBRPC_SIGNATURE_KEY} (`$hubrpcSignature`) holds the signature
69
+ * itself (a domain-keyed map of `{ keyId, sig }` entries). It can't sign
70
+ * itself, so it's excluded.
71
+ * - {@link HUBRPC_UNSIGNED_KEY} (`$hubrpcUnsigned`) holds extrinsic
72
+ * *attachments* (e.g. the presented capability bag) authored by someone
73
+ * other than this signer, so it's excluded too.
74
+ *
75
+ * Domain separation is intrinsic: the per-domain {@link signingDomainValue}
76
+ * is the sole key of the canonicalized wrapper object. A `"call"` signature
77
+ * can therefore never verify as a `"capability"` (or vice-versa). The domain
78
+ * is *supplied by context* by signer and verifier;
79
+ * the on-wire `$hubrpcSignature` key is a convenience index, never a trust
80
+ * claim.
81
+ *
82
+ * This module is pure (no private keys, no `crypto.verify`): it only defines
83
+ * the canonical bytes. Signing/verifying live in `../identity/signedObject`.
84
+ */
85
+ /** Reserved wire-key: the signature map `{ [domain]: SignatureEnvelope }`. */
86
+ const HUBRPC_SIGNATURE_KEY = "$hubrpcSignature";
87
+ /** Reserved wire-key: extrinsic unsigned attachments (e.g. the capability bag). */
88
+ const HUBRPC_UNSIGNED_KEY = "$hubrpcUnsigned";
89
+ /** Reserved wire-key: signed call meta, present only on calls (dodges the JSON-RPC param namespace). */
90
+ const HUBRPC_META_KEY = "$hubrpc";
91
+ /**
92
+ * Return the application-authored portion of a LinkRPC params value.
93
+ *
94
+ * Routing and gate layers must continue forwarding the original wire params so
95
+ * every chained hub can independently verify the envelope. Terminal dispatch
96
+ * may use this derived value for application-schema validation without mutating
97
+ * or replacing the wire message.
98
+ */
99
+ function stripLinkRpcWireMeta(wireParams) {
100
+ if (wireParams === void 0) return void 0;
101
+ if (wireParams === null || typeof wireParams !== "object" || Array.isArray(wireParams)) return wireParams;
102
+ const object = wireParams;
103
+ const reserved = [
104
+ HUBRPC_META_KEY,
105
+ HUBRPC_SIGNATURE_KEY,
106
+ HUBRPC_UNSIGNED_KEY
107
+ ];
108
+ if (!reserved.some((key) => key in object)) return object;
109
+ const userParams = {};
110
+ for (const key of Object.keys(object)) {
111
+ if (reserved.includes(key)) continue;
112
+ userParams[key] = object[key];
113
+ }
114
+ return userParams;
115
+ }
116
+ //#endregion
117
+ //#region src/hub/common/reflection.interfaces.ts
118
+ const defaultsInterface = defineInterface({
119
+ id: "hubrpc.defaults",
120
+ description: "Reflection: preset default service / interface on this connection."
121
+ }, {
122
+ get: requestType(object({}), object({
123
+ serviceId: optional(string()),
124
+ interfaceId: optional(string()),
125
+ /** Hash of the preset interface, if known. */
126
+ interfaceHash: optional(string())
127
+ })),
128
+ listBindings: requestType(object({}), object({ bindings: array(object({
129
+ prefix: string(),
130
+ serviceId: optional(string()),
131
+ interfaceId: string(),
132
+ interfaceHash: string()
133
+ })) }))
134
+ });
135
+ /**
136
+ * A single root-principal requirement. `transitive: true` means the requirement
137
+ * also applies to every service reachable *through* this one — i.e. when this
138
+ * listing is a `hubrpc.directory` reference, everything it lists inherits the
139
+ * requirement (also as transitive).
140
+ */
141
+ const zRootPrincipalReq = object({
142
+ principal: string(),
143
+ transitive: optional(boolean())
144
+ });
145
+ /** One OR-set. The set is satisfied by holding *any one* of its principals. */
146
+ const zRootPrincipalSet = array(zRootPrincipalReq);
147
+ const zServiceIdPattern = union([object({ exact: string() }), object({ prefix: string() })]);
148
+ const zServiceListing = object({
149
+ serviceId: string(),
150
+ interfaceId: string(),
151
+ /** Hash of the interface as implemented by this service. */
152
+ interfaceHash: string(),
153
+ /** Optional, non-authoritative discovery labels of this implemented interface. */
154
+ tags: optional(array(string())),
155
+ /** Optional non-normative description of the owning service. */
156
+ serviceDescription: optional(string()),
157
+ /**
158
+ * Root principals required to access this service, in CNF: the caller must
159
+ * satisfy **every** set (AND), and a set is satisfied by **any one** of its
160
+ * principals (OR). Omitted/empty => no root-principal requirement.
161
+ */
162
+ rootPrincipalSets: optional(array(zRootPrincipalSet)),
163
+ /**
164
+ * Service-id regions reachable through this referral. Meaningful only for
165
+ * `hubrpc.directory` entries; omission defaults to the referral service-id
166
+ * subtree.
167
+ */
168
+ reachableServiceIds: optional(array(zServiceIdPattern))
169
+ });
170
+ const directoryInterface = defineInterface({
171
+ id: "hubrpc.directory",
172
+ description: "Reflection: list services exposed by this endpoint. Can also list other directory services that can be explored."
173
+ }, {
174
+ list: requestType(object({
175
+ /** Filter: only return services implementing this interface id. */
176
+ interfaceId: optional(string()),
177
+ /** Filter: only return services whose interface id starts with this prefix. */
178
+ interfaceIdPrefix: optional(string()),
179
+ /** Filter: only return entries for this service id. */
180
+ serviceId: optional(string()),
181
+ /** Union filter used to constrain a transitive directory walk. */
182
+ serviceIdScopes: optional(array(zServiceIdPattern)),
183
+ /** Paging: opaque continuation token from a previous response. */
184
+ cursor: optional(string()),
185
+ /** Paging: max items in this page. Server MAY return fewer. */
186
+ limit: optional(number().check(int(), positive())),
187
+ /** Soft cap on time the server spends gathering this page, in ms. */
188
+ timeoutMs: optional(number().check(int(), nonnegative()))
189
+ }), object({
190
+ items: array(zServiceListing),
191
+ /** Omitted => no more pages. */
192
+ nextCursor: optional(string()),
193
+ /** True if `timeoutMs` cut the page short before exhausting results. */
194
+ truncated: optional(boolean())
195
+ })),
196
+ /**
197
+ * Coarse change tap on the directory.
198
+ *
199
+ * `watch` is a long-lived streaming request that emits an **empty tick**
200
+ * whenever the (optionally filtered) directory *might* have changed. The
201
+ * tick carries no delta and no payload — its only meaning is "re-`list`
202
+ * now". The consumer reconciles against its own last snapshot.
203
+ *
204
+ * This keeps the server stateless: it never computes or replays
205
+ * per-item deltas, never does an initial-sync replay. Over-emission is
206
+ * allowed (the consumer re-lists and finds nothing new); under-emission
207
+ * is not. Ticks are coalesced. The `interfaceId` / `serviceId` filters
208
+ * mirror `list` and are a relevance hint, not a guarantee.
209
+ *
210
+ * The request resolves when the caller cancels (or the connection
211
+ * drops); the runtime auto-detaches the stream when it settles.
212
+ */
213
+ watch: requestType(object({
214
+ /** Relevance hint: changes touching this interface id. */
215
+ interfaceId: optional(string()),
216
+ /** Relevance hint: changes touching an interface id with this prefix. */
217
+ interfaceIdPrefix: optional(string()),
218
+ /** Relevance hint: changes touching this service id. */
219
+ serviceId: optional(string()),
220
+ /** Relevance hint: changes within any of these service-id regions. */
221
+ serviceIdScopes: optional(array(zServiceIdPattern))
222
+ }), object({})).withStream({
223
+ /** Empty tick: "the directory may have changed; re-list." */
224
+ server: object({}) })
225
+ });
226
+ const schemasInterface = defineInterface({
227
+ id: "hubrpc.schemas",
228
+ description: "Reflection: fetch interface schemas by id (+ optional hash). Must serve the interfaces advertised in this endpoint's directory."
229
+ }, { get: requestType(object({
230
+ interfaceId: string(),
231
+ /** Omit => server picks the version it exposes. */
232
+ hash: optional(string())
233
+ }), object({
234
+ /** A full `SvcInterfaceSchema`. */
235
+ schema: unknown() })) });
236
+ /** Segment-aware service-id pattern matching (`foo` includes `foo/bar`, not `foobar`). */
237
+ function serviceIdMatchesPattern(serviceId, pattern) {
238
+ if ("exact" in pattern) return serviceId === pattern.exact;
239
+ return pattern.prefix === "" || serviceId === pattern.prefix || serviceId.startsWith(`${pattern.prefix}/`);
240
+ }
241
+ /** Whether a service id is included by a union of patterns. `undefined` means unfiltered. */
242
+ function serviceIdMatchesScopes(serviceId, scopes) {
243
+ return scopes === void 0 || scopes.some((pattern) => serviceIdMatchesPattern(serviceId, pattern));
244
+ }
245
+ //#endregion
246
+ //#region src/connection/channel.ts
247
+ var Channel = class Channel {
248
+ sender;
249
+ _setHandler;
250
+ _setWireObserver;
251
+ constructor(sender, _setHandler, _setWireObserver) {
252
+ this.sender = sender;
253
+ this._setHandler = _setHandler;
254
+ this._setWireObserver = _setWireObserver;
255
+ }
256
+ /** Bind the inbound request/notification handler. May be called before or after construction of {@link LinkRpcConnection}. */
257
+ setRequestHandler(handler) {
258
+ this._setHandler(handler);
259
+ }
260
+ /**
261
+ * Install the endpoint inspection observer at the JSON-RPC wire boundary.
262
+ * Intended for {@link LinkRpcConnection}; ordinary consumers should use the
263
+ * public `hubrpc.traffic` service instead.
264
+ */
265
+ setWireMessageObserver(observer) {
266
+ this._setWireObserver?.(observer);
267
+ }
268
+ /**
269
+ * Compose this channel with a sender-side decorator (typically the
270
+ * signing layer). The decorator wraps {@link sender} only; the
271
+ * {@link setRequestHandler} binding is shared with the original channel
272
+ * so the receive side is bound exactly once regardless of decoration depth.
273
+ */
274
+ withSender(decorate) {
275
+ return new Channel(decorate(this.sender), this._setHandler, this._setWireObserver);
276
+ }
277
+ };
278
+ var RpcError = class extends Error {
279
+ code;
280
+ origin;
281
+ data;
282
+ hasData;
283
+ constructor(message, code, data, origin = "local", hasData = data !== void 0) {
284
+ super(message);
285
+ this.code = code;
286
+ this.origin = origin;
287
+ this.message = message;
288
+ this.name = "RpcError";
289
+ this.hasData = hasData;
290
+ if (hasData) this.data = data;
291
+ }
292
+ };
293
+ //#endregion
294
+ //#region src/connection/jsonRpcChannel.ts
295
+ /**
296
+ * How often a streaming-enabled call emits a `toCallee` keepalive ping so a
297
+ * routing hub's per-request idle timer never reaps the in-flight request.
298
+ * Must stay comfortably below the hub's idle timeout (default 30 min).
299
+ */
300
+ const KEEPALIVE_PING_INTERVAL_MS = 6e5;
301
+ /**
302
+ * Minimal JSON-RPC 2.0 channel: correlates requests with responses,
303
+ * dispatches incoming requests/notifications to a handler.
304
+ *
305
+ * Construct via {@link JsonRpcChannel.create}: it returns a
306
+ * {@link Channel} factory which materialises the live channel once a
307
+ * handler is supplied via `.connect(handler)`. The handler is fixed for
308
+ * the channel's lifetime — no setter, no mid-flight swap.
309
+ *
310
+ * The outbound `TOutCtx` of the produced sender is `unknown` — this
311
+ * channel ignores per-call ctx entirely. Decorators (e.g.
312
+ * `SigningSender`) lift it to a concrete shape.
313
+ */
314
+ var JsonRpcChannel = class JsonRpcChannel {
315
+ _transport;
316
+ _requestsDeferred;
317
+ /**
318
+ * Wrap a transport in a {@link Channel}. The channel's sender is live
319
+ * immediately; call {@link Channel.setRequestHandler} (or pass the channel
320
+ * to {@link LinkRpcConnection}) to bind the inbound handler.
321
+ */
322
+ static create(transport) {
323
+ return this.createWithClose(transport).channel;
324
+ }
325
+ /**
326
+ * Wrap a transport with lifecycle hooks. deferRequests queues requests,
327
+ * notifications, and their input streams until resumeRequests is called.
328
+ * Responses and output streams remain live for setup-time outbound calls.
329
+ */
330
+ static createWithClose(transport, options = {}) {
331
+ const jrc = new JsonRpcChannel(transport, options.deferRequests === true);
332
+ return {
333
+ channel: new Channel(jrc, (h) => jrc.setRequestHandler(h), (observer) => jrc.setWireMessageObserver(observer)),
334
+ close: () => jrc.close(),
335
+ resumeRequests: () => jrc.resumeRequests()
336
+ };
337
+ }
338
+ _nextId = 1;
339
+ _pending = /* @__PURE__ */ new Map();
340
+ /**
341
+ * Per-request callbacks invoked when a {@link STREAM_METHOD}
342
+ * notification arrives with a matching `requestId`. Used in both
343
+ * directions: outgoing-request callers register here keyed by the
344
+ * id they sent; per-call incoming-request stream handles register
345
+ * keyed by the id they observed. Entries are removed when the
346
+ * corresponding request completes (response received for outgoing,
347
+ * response sent for incoming).
348
+ */
349
+ _streamListeners = /* @__PURE__ */ new Map();
350
+ /**
351
+ * Per-incoming-request handlers for reserved `control` messages
352
+ * (cancel / ping / pong) arriving as stream notifications. Keyed
353
+ * by the id observed; removed when the request settles.
354
+ */
355
+ _streamControl = /* @__PURE__ */ new Map();
356
+ _incomingAborts = /* @__PURE__ */ new Map();
357
+ _handler;
358
+ _wireObserver;
359
+ _closeError;
360
+ _deferredMessages = [];
361
+ setRequestHandler(handler) {
362
+ this._handler = handler;
363
+ }
364
+ setWireMessageObserver(observer) {
365
+ this._wireObserver = observer;
366
+ }
367
+ constructor(_transport, _requestsDeferred) {
368
+ this._transport = _transport;
369
+ this._requestsDeferred = _requestsDeferred;
370
+ this._transport.setListener((m) => this._onMessage(m));
371
+ }
372
+ resumeRequests() {
373
+ while (this._deferredMessages.length > 0) this._dispatchMessage(this._deferredMessages.shift());
374
+ this._requestsDeferred = false;
375
+ }
376
+ async sendRequest(method, params, opts) {
377
+ this._throwIfClosed();
378
+ const id = this._nextId++;
379
+ const req = {
380
+ jsonrpc: "2.0",
381
+ id,
382
+ method
383
+ };
384
+ if (params !== void 0) req.params = params;
385
+ const promise = new Promise((resolve, reject) => {
386
+ this._pending.set(String(id), {
387
+ resolve,
388
+ reject
389
+ });
390
+ });
391
+ this._observeOutbound(req, opts);
392
+ try {
393
+ await this._transport.send(req);
394
+ } catch (error) {
395
+ this._pending.delete(String(id));
396
+ this._observeSendFailure(id, error);
397
+ throw error;
398
+ }
399
+ return promise;
400
+ }
401
+ async sendNotification(method, params, opts) {
402
+ this._throwIfClosed();
403
+ const n = {
404
+ jsonrpc: "2.0",
405
+ method
406
+ };
407
+ if (params !== void 0) n.params = params;
408
+ this._observeOutbound(n, opts);
409
+ await this._transport.send(n);
410
+ }
411
+ sendRequestWithStream(method, params, opts) {
412
+ this._throwIfClosed();
413
+ const id = this._nextId++;
414
+ const key = String(id);
415
+ let resolveResult;
416
+ let rejectResult;
417
+ const result = new Promise((resolve, reject) => {
418
+ resolveResult = resolve;
419
+ rejectResult = reject;
420
+ });
421
+ this._pending.set(key, {
422
+ resolve: resolveResult,
423
+ reject: rejectResult
424
+ });
425
+ if (opts?.onStreamMessage) this._streamListeners.set(key, opts.onStreamMessage);
426
+ const pinger = this._makePinger(id, StreamDir.toCallee);
427
+ this._streamControl.set(key, pinger.onControl);
428
+ const ping = setInterval(() => {
429
+ this._sendStream(id, StreamDir.toCallee, { control: { type: StreamControlType.ping } });
430
+ }, KEEPALIVE_PING_INTERVAL_MS);
431
+ ping.unref?.();
432
+ const stopPing = () => {
433
+ clearInterval(ping);
434
+ pinger.dispose();
435
+ this._streamControl.delete(key);
436
+ };
437
+ const dispose = (reason = "streaming request disposed") => {
438
+ if (!this._pending.delete(key)) return;
439
+ this._streamListeners.delete(key);
440
+ stopPing();
441
+ rejectResult(new RpcError(reason, ErrorCode.cancelled));
442
+ };
443
+ result.then(stopPing, stopPing);
444
+ (async () => {
445
+ const req = {
446
+ jsonrpc: "2.0",
447
+ id,
448
+ method
449
+ };
450
+ if (params !== void 0) req.params = params;
451
+ this._observeOutbound(req, opts);
452
+ await this._transport.send(req);
453
+ })().catch((err) => {
454
+ this._pending.delete(key);
455
+ this._streamListeners.delete(key);
456
+ this._observeSendFailure(id, err);
457
+ stopPing();
458
+ rejectResult(err instanceof Error ? err : new Error(String(err)));
459
+ });
460
+ return {
461
+ result,
462
+ send: (payload) => this._sendStream(id, StreamDir.toCallee, { payload }),
463
+ cancel: (reason) => this._sendStream(id, StreamDir.toCallee, { control: reason !== void 0 ? {
464
+ type: StreamControlType.cancel,
465
+ reason
466
+ } : { type: StreamControlType.cancel } }),
467
+ dispose,
468
+ ping: pinger.ping
469
+ };
470
+ }
471
+ /**
472
+ * Build the symmetric ping/pong machinery for one in-flight request.
473
+ *
474
+ * `outboundDir` is the direction *this* side emits controls in
475
+ * (`toCallee` for the caller, `toCaller` for the callee). A received
476
+ * ping is answered with a pong in that same direction, echoing the
477
+ * ping's nonce; a received pong resolves the matching outstanding
478
+ * {@link ping} probe.
479
+ */
480
+ _makePinger(requestId, outboundDir) {
481
+ const pending = /* @__PURE__ */ new Map();
482
+ let nextNonce = 1;
483
+ const ping = () => {
484
+ const nonce = `${requestId}:${nextNonce++}`;
485
+ const p = new Promise((resolve, reject) => pending.set(nonce, {
486
+ resolve,
487
+ reject
488
+ }));
489
+ this._sendStream(requestId, outboundDir, { control: {
490
+ type: StreamControlType.ping,
491
+ nonce
492
+ } });
493
+ return p;
494
+ };
495
+ const onControl = (control) => {
496
+ if (control.type === StreamControlType.ping) this._sendStream(requestId, outboundDir, { control: {
497
+ type: StreamControlType.pong,
498
+ nonce: control.nonce
499
+ } });
500
+ else if (control.type === StreamControlType.pong && control.nonce !== void 0) {
501
+ const waiter = pending.get(control.nonce);
502
+ if (waiter) {
503
+ pending.delete(control.nonce);
504
+ waiter.resolve();
505
+ }
506
+ }
507
+ };
508
+ const dispose = () => {
509
+ const err = new RpcError("Request settled before pong", ErrorCode.cancelled);
510
+ for (const w of pending.values()) w.reject(err);
511
+ pending.clear();
512
+ };
513
+ return {
514
+ ping,
515
+ onControl,
516
+ dispose
517
+ };
518
+ }
519
+ /**
520
+ * Emit a stream notification ({@link STREAM_METHOD}) associated with
521
+ * an in-flight request. Internal: outgoing-side callers reach this
522
+ * via {@link RawStreamingCall.send} / `cancel`; incoming-side handlers
523
+ * reach it via {@link IncomingStream.send}.
524
+ */
525
+ async _sendStream(requestId, dir, fields) {
526
+ this._throwIfClosed();
527
+ const params = {
528
+ requestId,
529
+ dir,
530
+ ...fields
531
+ };
532
+ const n = {
533
+ jsonrpc: "2.0",
534
+ method: STREAM_METHOD,
535
+ params
536
+ };
537
+ this._observeOutbound(n);
538
+ await this._transport.send(n);
539
+ }
540
+ close() {
541
+ if (this._closeError) return;
542
+ const error = new RpcError("Connection closed", ErrorCode.peerDisconnected);
543
+ this._closeError = error;
544
+ this._deferredMessages.length = 0;
545
+ for (const abort of this._incomingAborts.values()) abort.abort(error);
546
+ this._incomingAborts.clear();
547
+ this._transport.dispose();
548
+ for (const p of this._pending.values()) p.reject(error);
549
+ this._pending.clear();
550
+ this._streamListeners.clear();
551
+ this._streamControl.clear();
552
+ }
553
+ _throwIfClosed() {
554
+ if (this._closeError) throw this._closeError;
555
+ }
556
+ _onMessage(m) {
557
+ this._observeInbound(m);
558
+ if (this._requestsDeferred && !isResponse(m) && !(isNotification(m) && m.method === STREAM_METHOD && m.params?.dir === StreamDir.toCaller)) {
559
+ this._deferredMessages.push(m);
560
+ return;
561
+ }
562
+ this._dispatchMessage(m);
563
+ }
564
+ _dispatchMessage(m) {
565
+ const ctx = m.context;
566
+ if (isResponse(m)) {
567
+ if (m.id === null) return;
568
+ const key = String(m.id);
569
+ const pending = this._pending.get(key);
570
+ if (!pending) return;
571
+ this._pending.delete(key);
572
+ this._streamListeners.delete(key);
573
+ if ("error" in m) {
574
+ const e = m.error;
575
+ const err = new RpcError(e.message, e.code, e.data, "remote", Object.hasOwn(e, "data"));
576
+ pending.reject(err);
577
+ } else pending.resolve(m.result);
578
+ return;
579
+ }
580
+ if (isRequest(m)) {
581
+ this._handleRequest(m, ctx).catch(() => {});
582
+ return;
583
+ }
584
+ if (isNotification(m)) {
585
+ if (m.method === STREAM_METHOD) {
586
+ this._handleStreamNotification(m);
587
+ return;
588
+ }
589
+ this._handleNotification(m, ctx);
590
+ return;
591
+ }
592
+ }
593
+ _handleStreamNotification(m) {
594
+ const params = m.params;
595
+ if (!params) return;
596
+ const requestId = params.requestId;
597
+ if (requestId === void 0) return;
598
+ const key = String(requestId);
599
+ if (params.control) {
600
+ this._streamControl.get(key)?.(params.control);
601
+ return;
602
+ }
603
+ const listener = this._streamListeners.get(key);
604
+ if (!listener) return;
605
+ listener(params.payload);
606
+ }
607
+ async _handleRequest(m, context) {
608
+ const key = String(m.id);
609
+ const pinger = this._makePinger(m.id, StreamDir.toCaller);
610
+ const stream = {
611
+ send: (payload) => this._sendStream(m.id, StreamDir.toCaller, { payload }),
612
+ onMessage: (listener) => {
613
+ if (listener) this._streamListeners.set(key, listener);
614
+ else this._streamListeners.delete(key);
615
+ },
616
+ ping: pinger.ping
617
+ };
618
+ const abort = new AbortController();
619
+ this._incomingAborts.set(key, abort);
620
+ this._streamControl.set(key, (control) => {
621
+ if (control.type === StreamControlType.cancel) abort.abort(new RpcError(control.reason ?? "cancelled", ErrorCode.cancelled));
622
+ else pinger.onControl(control);
623
+ });
624
+ let result;
625
+ try {
626
+ if (!this._handler) result = { error: {
627
+ code: ErrorCode.methodNotFound,
628
+ message: `No handler registered on this endpoint (method: ${m.method}).`
629
+ } };
630
+ else try {
631
+ result = await this._handler.handleRequest({
632
+ method: m.method,
633
+ params: m.params,
634
+ context,
635
+ stream,
636
+ signal: abort.signal
637
+ });
638
+ } catch (e) {
639
+ result = { error: {
640
+ code: ErrorCode.internalError,
641
+ message: e instanceof Error ? e.message : String(e)
642
+ } };
643
+ }
644
+ } finally {
645
+ this._streamListeners.delete(key);
646
+ this._streamControl.delete(key);
647
+ this._incomingAborts.delete(key);
648
+ pinger.dispose();
649
+ }
650
+ if ("result" in result) {
651
+ const response = {
652
+ jsonrpc: "2.0",
653
+ id: m.id,
654
+ result: result.result
655
+ };
656
+ this._observeOutbound(response);
657
+ await this._transport.send(response);
658
+ } else {
659
+ const resp = {
660
+ jsonrpc: "2.0",
661
+ id: m.id,
662
+ error: result.error
663
+ };
664
+ this._observeOutbound(resp);
665
+ await this._transport.send(resp);
666
+ }
667
+ }
668
+ _handleNotification(m, context) {
669
+ if (!this._handler) return;
670
+ try {
671
+ this._handler.handleNotification({
672
+ method: m.method,
673
+ params: m.params,
674
+ context,
675
+ stream: _NOOP_STREAM,
676
+ signal: _NEVER_ABORT
677
+ });
678
+ } catch {}
679
+ }
680
+ _observeInbound(message) {
681
+ const observer = this._wireObserver;
682
+ if (observer === void 0) return;
683
+ observer("inbound", message);
684
+ }
685
+ _observeOutbound(message, opts) {
686
+ const observer = this._wireObserver;
687
+ if (observer === void 0) return;
688
+ observer("outbound", message);
689
+ }
690
+ _observeSendFailure(requestId, error) {
691
+ if (this._wireObserver === void 0) return;
692
+ this._observeInbound({
693
+ jsonrpc: "2.0",
694
+ id: requestId,
695
+ error: {
696
+ code: ErrorCode.peerDisconnected,
697
+ message: error instanceof Error ? error.message : String(error)
698
+ }
699
+ });
700
+ }
701
+ };
702
+ /** Stub stream handle for notifications, which carry no request id. */
703
+ const _NOOP_STREAM = {
704
+ send: () => Promise.resolve(),
705
+ onMessage: () => {},
706
+ ping: () => Promise.resolve()
707
+ };
708
+ /** A signal that never aborts — handed to notification handlers. */
709
+ const _NEVER_ABORT = new AbortController().signal;
710
+ function defaultInterfaceTarget(iface) {
711
+ return Object.freeze({
712
+ mode: "default",
713
+ interface: iface
714
+ });
715
+ }
716
+ function isInterfaceTarget(value) {
717
+ return typeof value === "object" && value !== null && "interface" in value && "mode" in value && (value.mode === "qualified" || value.mode === "default" || value.mode === "bare");
718
+ }
719
+ //#endregion
720
+ //#region src/connection/linkRpcConnection.ts
721
+ /**
722
+ * High-level linkrpc connection. Layers method-name routing and zod-driven
723
+ * validation on top of a plain JSON-RPC channel.
724
+ *
725
+ * Typed proxies built by this class merely forward each call's
726
+ * `interfaceHash` hint to the channel so the signer stamps it into the
727
+ * `$hubrpc` envelope.
728
+ *
729
+ * `TInCtx` is the per-call out-of-band context the connection's transport
730
+ * carries. Default `undefined` covers cross-process and ordinary in-process
731
+ * transports. The hub's self/overlay connections instantiate with a
732
+ * concrete `TInCtx` (e.g. `Participant`) so handlers can see who originated
733
+ * the call.
734
+ *
735
+ * `TOutCtx` is the per-call override / extension bag the outbound sender
736
+ * understands (see {@link SendOpts.ctx}). It only ever flows into the
737
+ * sender as input, so it is a contravariant type parameter. Default `any`
738
+ * keeps the bare `LinkRpcConnection` a valid supertype for holders that do
739
+ * not care about the outbound ctx; pass a concrete shape (e.g.
740
+ * `SigningCallCtx`) to get precise {@link get} typing. The connection
741
+ * itself is agnostic to its contents — it merely forwards the `ctx`
742
+ * defaults supplied to {@link get}.
743
+ */
744
+ var LinkRpcConnection = class LinkRpcConnection {
745
+ /**
746
+ * Convenience: build a {@link JsonRpcChannel} `Channel` from the
747
+ * given transport and wrap it in an `LinkRpcConnection`. Use this
748
+ * when you have a transport at hand and don't need a decorator
749
+ * stack (e.g. signing).
750
+ */
751
+ static fromTransport(transport, options = {}) {
752
+ return new LinkRpcConnection(JsonRpcChannel.create(transport), options);
753
+ }
754
+ /** Underlying JSON-RPC sender — useful for callers that need raw access (e.g. to call hub-served methods that bypass the interface registry). */
755
+ channel;
756
+ /** key = `${serviceId ?? ""}::${interfaceId}` */
757
+ _registry = /* @__PURE__ */ new Map();
758
+ /** Descriptions for services that have been registered with a `serviceDescription`. */
759
+ _serviceDescriptions = /* @__PURE__ */ new Map();
760
+ /** Root-node-id requirement sets recorded per serviceId. */
761
+ _serviceRootPrincipalSets = /* @__PURE__ */ new Map();
762
+ _directoryWatchers = /* @__PURE__ */ new Set();
763
+ _directoryListeners = /* @__PURE__ */ new Set();
764
+ _inspection;
765
+ _inspectionHost;
766
+ _serviceInspectionRegistrations = /* @__PURE__ */ new Map();
767
+ _wireObservers = /* @__PURE__ */ new Set();
768
+ _closeListeners = /* @__PURE__ */ new Set();
769
+ _closed = false;
770
+ _wireChannel;
771
+ _validateOutboundParams;
772
+ /** Bare-method bindings, keyed by their exact wire prefix. */
773
+ _bareBindings = /* @__PURE__ */ new Map();
774
+ _generateTopologyId;
775
+ /**
776
+ * Construct from a {@link Channel} (binds the inbound handler and uses
777
+ * `channel.sender` for outbound calls) or from a bare
778
+ * {@link IRequestSender} (send-only — no inbound handler is registered,
779
+ * useful for bootstrap flows like `createManagedIdentity`).
780
+ */
781
+ constructor(channel, options = {}) {
782
+ this._validateOutboundParams = options.validateOutboundParams !== false;
783
+ this._generateTopologyId = options.generateTopologyId ?? createInspectionId;
784
+ if ("sender" in channel) {
785
+ this._wireChannel = channel;
786
+ this.channel = channel.sender;
787
+ channel.setRequestHandler({
788
+ handleRequest: (c) => this._handleRequest(c),
789
+ handleNotification: (c) => this._handleNotification(c)
790
+ });
791
+ } else {
792
+ this._wireChannel = void 0;
793
+ this.channel = channel;
794
+ }
795
+ }
796
+ get(ifaceOrTarget, opts) {
797
+ return this._getClient(ifaceOrTarget, opts, false);
798
+ }
799
+ _getClient(ifaceOrTarget, opts, allFailuresAsValues) {
800
+ if (isInterfaceTarget(ifaceOrTarget)) {
801
+ if (opts !== void 0) throw new Error(`${allFailuresAsValues ? "getResultClient" : "get"}: ${ifaceOrTarget.mode === "bare" ? "bare " : ""}interface targets do not accept service or call options.`);
802
+ if (ifaceOrTarget.mode === "bare") return this._buildBareClient(ifaceOrTarget.interface, ifaceOrTarget.prefix, allFailuresAsValues);
803
+ return this._buildClient(ifaceOrTarget.interface, ifaceOrTarget.mode === "qualified" ? { serviceId: ifaceOrTarget.serviceId } : {}, void 0, ifaceOrTarget.mode === "default", allFailuresAsValues);
804
+ }
805
+ return this._buildClient(ifaceOrTarget, opts ?? {}, void 0, false, allFailuresAsValues);
806
+ }
807
+ /**
808
+ * Get a typed client that emits foreign-protocol bare method names.
809
+ * Unlike {@link get}, these calls carry no LinkRPC interface metadata.
810
+ */
811
+ getBare(iface, opts = {}) {
812
+ return this._buildBareClient(iface, opts.prefix ?? "", false);
813
+ }
814
+ _buildBareClient(iface, prefix, allFailuresAsValues) {
815
+ validateBarePrefix(prefix);
816
+ for (const [name, member] of Object.entries(iface.members)) if (member.kind === "request" && (member.clientStreamSchema !== void 0 || member.serverStreamSchema !== void 0)) throw new Error(`getBare: streaming method "${name}" is not supported on foreign wires.`);
817
+ return this._buildClient(iface, {}, prefix, false, allFailuresAsValues);
818
+ }
819
+ getResultClient(ifaceOrTarget, opts) {
820
+ return this._getClient(ifaceOrTarget, opts, true);
821
+ }
822
+ /** Get a service-scoped handle; all interfaces obtained from it route via `serviceId` (form 3). */
823
+ service(serviceId) {
824
+ return new ServiceHandle(this, serviceId);
825
+ }
826
+ /** Register handlers, adding preset routing when passed a default or bare target. */
827
+ register(ifaceOrTarget, handlers, opts = {}) {
828
+ if (isInterfaceTarget(ifaceOrTarget)) {
829
+ if (ifaceOrTarget.mode === "qualified") {
830
+ if (opts.serviceId !== void 0 && opts.serviceId !== ifaceOrTarget.serviceId) throw new Error("register: serviceId conflicts with interface target.");
831
+ return this._register(ifaceOrTarget.interface, handlers, {
832
+ ...opts,
833
+ serviceId: ifaceOrTarget.serviceId || void 0
834
+ }, false);
835
+ }
836
+ const prefix = ifaceOrTarget.mode === "bare" ? ifaceOrTarget.prefix : "";
837
+ validateBarePrefix(prefix);
838
+ return this._register(ifaceOrTarget.interface, handlers, opts, false, prefix);
839
+ }
840
+ return this._register(ifaceOrTarget, handlers, opts, false);
841
+ }
842
+ /** Register inspection contracts without recursively enabling service inspection. */
843
+ registerInspection(iface, handlers, opts = {}) {
844
+ return this._register(iface, handlers, opts, true);
845
+ }
846
+ /** Observe this connection without replacing another inspection scope's observer. */
847
+ observeWireMessages(observer) {
848
+ if (this._closed) throw new Error("Connection is closed");
849
+ if (this._wireChannel === void 0) throw new Error("Connection has no observable wire channel");
850
+ this._wireObservers.add(observer);
851
+ this._wireChannel.setWireMessageObserver((direction, message) => {
852
+ for (const listener of [...this._wireObservers]) try {
853
+ listener(direction, message);
854
+ } catch {}
855
+ });
856
+ return { dispose: () => {
857
+ this._wireObservers.delete(observer);
858
+ if (this._wireObservers.size === 0) this._wireChannel?.setWireMessageObserver(void 0);
859
+ } };
860
+ }
861
+ onDidClose(listener) {
862
+ if (this._closed) throw new Error("Connection is closed");
863
+ this._closeListeners.add(listener);
864
+ return { dispose: () => {
865
+ this._closeListeners.delete(listener);
866
+ } };
867
+ }
868
+ _register(iface, handlers, opts, internalInspection, barePrefix) {
869
+ const serviceId = opts.serviceId;
870
+ const key = `${serviceId ?? ""}::${iface.info.id}`;
871
+ if (this._registry.has(key)) throw new Error(`Interface "${iface.info.id}" already registered${serviceId ? ` under service "${serviceId}"` : ""}.`);
872
+ if (barePrefix !== void 0 && this._bareBindings.has(barePrefix)) throw new Error(`Bare interface prefix "${barePrefix}" is already registered.`);
873
+ let descriptionWasAdded = false;
874
+ if (opts.serviceDescription !== void 0) {
875
+ if (serviceId === void 0) throw new Error("register: `serviceDescription` requires `serviceId`.");
876
+ const existing = this._serviceDescriptions.get(serviceId);
877
+ if (existing !== void 0 && existing !== opts.serviceDescription) throw new Error(`register: conflicting descriptions for service "${serviceId}".`);
878
+ descriptionWasAdded = existing === void 0;
879
+ }
880
+ let rootPrincipalSetsWereAdded = false;
881
+ if (opts.rootPrincipalSets !== void 0) {
882
+ if (serviceId === void 0) throw new Error("register: `rootPrincipalSets` requires `serviceId`.");
883
+ const existing = this._serviceRootPrincipalSets.get(serviceId);
884
+ if (existing !== void 0 && !rootPrincipalSetsEqual(existing, opts.rootPrincipalSets)) throw new Error(`register: conflicting rootPrincipalSets for service "${serviceId}".`);
885
+ rootPrincipalSetsWereAdded = existing === void 0;
886
+ }
887
+ const entry = {
888
+ iface,
889
+ handlers: iface.flattenHandlers(handlers),
890
+ serviceId,
891
+ internalInspection
892
+ };
893
+ if (descriptionWasAdded) this._serviceDescriptions.set(serviceId, opts.serviceDescription);
894
+ if (rootPrincipalSetsWereAdded) this._serviceRootPrincipalSets.set(serviceId, opts.rootPrincipalSets);
895
+ this._registry.set(key, entry);
896
+ if (barePrefix !== void 0) this._bareBindings.set(barePrefix, {
897
+ prefix: barePrefix,
898
+ entry
899
+ });
900
+ this._notifyDirectoryWatchers(entry, descriptionWasAdded || rootPrincipalSetsWereAdded);
901
+ if (!internalInspection && serviceId !== void 0 && this._inspection !== void 0 && !this._serviceInspectionRegistrations.has(serviceId)) try {
902
+ this._installServiceInspection(serviceId);
903
+ } catch (error) {
904
+ this._registry.delete(key);
905
+ if (barePrefix !== void 0) this._bareBindings.delete(barePrefix);
906
+ if (descriptionWasAdded) this._serviceDescriptions.delete(serviceId);
907
+ if (rootPrincipalSetsWereAdded) this._serviceRootPrincipalSets.delete(serviceId);
908
+ this._notifyDirectoryWatchers(entry);
909
+ throw error;
910
+ }
911
+ let disposed = false;
912
+ return { dispose: () => {
913
+ if (disposed) return;
914
+ disposed = true;
915
+ if (this._registry.get(key) !== entry) return;
916
+ this._registry.delete(key);
917
+ for (const [prefix, binding] of this._bareBindings) if (binding.entry === entry) this._bareBindings.delete(prefix);
918
+ this._notifyDirectoryWatchers(entry);
919
+ if (serviceId !== void 0 && !this._hasBusinessServiceRegistration(serviceId)) {
920
+ this._removeServiceInspection(serviceId);
921
+ this._serviceDescriptions.delete(serviceId);
922
+ this._serviceRootPrincipalSets.delete(serviceId);
923
+ }
924
+ } };
925
+ }
926
+ /** Snapshot of every interface currently registered on this connection. */
927
+ listRegisteredInterfaces() {
928
+ return Array.from(this._registry.values()).map((r) => {
929
+ const sid = r.serviceId ?? "";
930
+ const item = {
931
+ serviceId: sid,
932
+ interfaceId: r.iface.info.id,
933
+ interfaceHash: r.iface.schemaHash
934
+ };
935
+ if (r.iface.info.tags !== void 0) item.tags = r.iface.info.tags;
936
+ const desc = sid === "" ? void 0 : this._serviceDescriptions.get(sid);
937
+ if (desc !== void 0) item.serviceDescription = desc;
938
+ const sets = sid === "" ? void 0 : this._serviceRootPrincipalSets.get(sid);
939
+ if (sets !== void 0) item.rootPrincipalSets = sets;
940
+ return item;
941
+ });
942
+ }
943
+ /** Subscribe to coarse local directory changes; listeners must re-list. */
944
+ onDidChangeDirectory(listener) {
945
+ this._directoryListeners.add(listener);
946
+ return () => this._directoryListeners.delete(listener);
947
+ }
948
+ /**
949
+ * Look up a registered interface definition by id (and optional content
950
+ * hash). Returns `undefined` if no registered interface matches.
951
+ */
952
+ findRegisteredInterface(interfaceId, hash) {
953
+ let selected;
954
+ for (const r of this._registry.values()) {
955
+ if (r.iface.info.id !== interfaceId) continue;
956
+ if (hash !== void 0 && r.iface.schemaHash !== hash) continue;
957
+ if (selected === void 0 || preferRicherInterfaceSchema(selected.toSchema(), r.iface.toSchema()) === r.iface.toSchema()) selected = r.iface;
958
+ }
959
+ return selected;
960
+ }
961
+ /**
962
+ * Register the three linkrpc reflection interfaces (`defaults`,
963
+ * `directory`, `schemas`), backed by this connection's live registry.
964
+ *
965
+ * By default they live under the root service (form-2 reachable as
966
+ * `hubrpc.directory::list`). Pass `serviceId` to additionally mount
967
+ * them under a specific service — useful for participants that live
968
+ * behind a hub, so callers can reach reflection via form-3
969
+ * `<serviceId>::hubrpc.directory::list`.
970
+ *
971
+ * Idempotent: re-registering the same `(serviceId, interfaceId)` pair
972
+ * is a no-op.
973
+ */
974
+ enableReflection(opts = {}) {
975
+ const key = `${opts.serviceId ?? ""}::${defaultsInterface.info.id}`;
976
+ if (this._registry.has(key)) return { dispose() {} };
977
+ const prefix = `${opts.serviceId ?? ""}::`;
978
+ for (const iface of [directoryInterface, schemasInterface]) if (this._registry.has(`${prefix}${iface.info.id}`)) throw new Error(`enableReflection: partial reflection registration for service "${opts.serviceId ?? ""}".`);
979
+ const regOpts = opts.serviceId !== void 0 ? { serviceId: opts.serviceId } : {};
980
+ const registrations = [];
981
+ registrations.push(this.register(defaultsInterface, {
982
+ get: () => {
983
+ const binding = this._bareBindings.get("");
984
+ if (!binding) return {};
985
+ return {
986
+ serviceId: binding.entry.serviceId,
987
+ interfaceId: binding.entry.iface.info.id,
988
+ interfaceHash: binding.entry.iface.schemaHash
989
+ };
990
+ },
991
+ listBindings: () => ({ bindings: Array.from(this._bareBindings.values()).sort((a, b) => a.prefix < b.prefix ? -1 : a.prefix > b.prefix ? 1 : 0).map((binding) => ({
992
+ prefix: binding.prefix,
993
+ serviceId: binding.entry.serviceId,
994
+ interfaceId: binding.entry.iface.info.id,
995
+ interfaceHash: binding.entry.iface.schemaHash
996
+ })) })
997
+ }, regOpts));
998
+ registrations.push(this.register(directoryInterface, {
999
+ list: ({ interfaceId, interfaceIdPrefix, serviceId, serviceIdScopes, cursor, limit }) => {
1000
+ const all = this.listRegisteredInterfaces().filter((r) => opts.serviceId === void 0 || r.serviceId === opts.serviceId).filter((r) => interfaceId === void 0 || r.interfaceId === interfaceId).filter((r) => interfaceIdPrefix === void 0 || r.interfaceId.startsWith(interfaceIdPrefix)).filter((r) => serviceId === void 0 || r.serviceId === serviceId).filter((r) => serviceIdMatchesScopes(r.serviceId, serviceIdScopes));
1001
+ const start = cursor === void 0 ? 0 : Number.parseInt(cursor, 10) || 0;
1002
+ const end = limit === void 0 ? all.length : Math.min(all.length, start + limit);
1003
+ const page = all.slice(start, end).map((r) => ({
1004
+ serviceId: r.serviceId,
1005
+ interfaceId: r.interfaceId,
1006
+ interfaceHash: r.interfaceHash,
1007
+ tags: r.tags === void 0 ? void 0 : [...r.tags],
1008
+ serviceDescription: r.serviceDescription,
1009
+ rootPrincipalSets: r.rootPrincipalSets?.map((set) => set.map((p) => ({ ...p })))
1010
+ }));
1011
+ return end < all.length ? {
1012
+ items: page,
1013
+ nextCursor: String(end)
1014
+ } : { items: page };
1015
+ },
1016
+ watch: (params, _ctx, stream) => new Promise((resolve) => {
1017
+ const watcher = {
1018
+ interfaceId: params.interfaceId,
1019
+ interfaceIdPrefix: params.interfaceIdPrefix,
1020
+ serviceId: params.serviceId,
1021
+ serviceIdScopes: params.serviceIdScopes,
1022
+ send: () => stream.send({})
1023
+ };
1024
+ this._directoryWatchers.add(watcher);
1025
+ const finish = () => {
1026
+ this._directoryWatchers.delete(watcher);
1027
+ resolve({});
1028
+ };
1029
+ if (stream.signal.aborted) {
1030
+ finish();
1031
+ return;
1032
+ }
1033
+ stream.signal.addEventListener("abort", finish, { once: true });
1034
+ })
1035
+ }, regOpts));
1036
+ registrations.push(this.register(schemasInterface, { get: ({ interfaceId, hash }) => {
1037
+ const iface = this.findRegisteredInterface(interfaceId, hash);
1038
+ if (!iface) throw new RpcError("Interface not found", ErrorCode.methodNotFound, {
1039
+ reason: "unknown-interface",
1040
+ interfaceId,
1041
+ hash
1042
+ });
1043
+ return { schema: iface.toSchema() };
1044
+ } }, regOpts));
1045
+ if (opts.serviceId !== void 0) try {
1046
+ this.enableReflection();
1047
+ } catch (error) {
1048
+ for (const registration of registrations.reverse()) registration.dispose();
1049
+ throw error;
1050
+ }
1051
+ let disposed = false;
1052
+ return { dispose: () => {
1053
+ if (disposed) return;
1054
+ disposed = true;
1055
+ for (const registration of registrations.reverse()) registration.dispose();
1056
+ } };
1057
+ }
1058
+ _notifyDirectoryWatchers(entry, serviceMetadataChanged = false) {
1059
+ for (const listener of this._directoryListeners) try {
1060
+ listener();
1061
+ } catch {}
1062
+ for (const watcher of this._directoryWatchers) {
1063
+ if (watcher.pending) continue;
1064
+ const directlyMatches = directoryWatcherMatchesEntry(watcher, entry);
1065
+ const metadataMatches = serviceMetadataChanged && entry.serviceId !== void 0 && [...this._registry.values()].some((candidate) => candidate.serviceId === entry.serviceId && directoryWatcherMatchesEntry(watcher, candidate));
1066
+ if (!directlyMatches && !metadataMatches) continue;
1067
+ watcher.pending = true;
1068
+ queueMicrotask(() => {
1069
+ watcher.pending = false;
1070
+ if (this._directoryWatchers.has(watcher)) watcher.send();
1071
+ });
1072
+ }
1073
+ }
1074
+ /**
1075
+ * Enable the root `hubrpc.node::getNodeId` topology-bootstrap service.
1076
+ *
1077
+ * The generated ids are stable while this registration is active. They are
1078
+ * unauthenticated correlation labels only; callers must not use them for
1079
+ * identity, capability, or authorization decisions.
1080
+ */
1081
+ enableInspection(descriptors) {
1082
+ if (this._inspection !== void 0) return this._inspection;
1083
+ const info = {
1084
+ nodeId: this._generateTopologyId("node"),
1085
+ portId: this._generateTopologyId("port"),
1086
+ ...descriptors !== void 0 ? { descriptors: [...descriptors] } : {}
1087
+ };
1088
+ const registration = this._register(nodeInterface, { getNodeId: () => info }, {}, true);
1089
+ const host = new InspectionHost({
1090
+ nodeId: info.nodeId,
1091
+ descriptors
1092
+ });
1093
+ try {
1094
+ if (this._wireChannel !== void 0) host.trackConnection(this, { portId: info.portId });
1095
+ } catch (error) {
1096
+ host.dispose();
1097
+ registration.dispose();
1098
+ throw error;
1099
+ }
1100
+ this._inspectionHost = host;
1101
+ let disposed = false;
1102
+ const result = {
1103
+ ...info,
1104
+ observeTraffic: (observer) => host.observeTraffic(observer),
1105
+ dispose: () => {
1106
+ if (disposed) return;
1107
+ disposed = true;
1108
+ for (const serviceId of [...this._serviceInspectionRegistrations.keys()]) this._removeServiceInspection(serviceId);
1109
+ host.dispose();
1110
+ if (this._inspectionHost === host) this._inspectionHost = void 0;
1111
+ registration.dispose();
1112
+ if (this._inspection === result) this._inspection = void 0;
1113
+ }
1114
+ };
1115
+ this._inspection = result;
1116
+ try {
1117
+ const services = new Set(Array.from(this._registry.values()).filter((entry) => entry.serviceId !== void 0 && !entry.internalInspection).map((entry) => entry.serviceId));
1118
+ for (const serviceId of services) this._installServiceInspection(serviceId);
1119
+ } catch (error) {
1120
+ result.dispose();
1121
+ throw error;
1122
+ }
1123
+ return result;
1124
+ }
1125
+ /** Number of active endpoint traffic stream subscribers. */
1126
+ get trafficObserverCount() {
1127
+ return this._inspectionHost?.observerCount ?? 0;
1128
+ }
1129
+ close() {
1130
+ if (this._closed) return;
1131
+ this._closed = true;
1132
+ for (const listener of [...this._closeListeners]) listener();
1133
+ this._closeListeners.clear();
1134
+ this._inspection?.dispose();
1135
+ this._wireObservers.clear();
1136
+ this._wireChannel?.setWireMessageObserver(void 0);
1137
+ this.channel.close();
1138
+ }
1139
+ _buildClient(iface, opts, barePrefix, defaultRoute = false, allFailuresAsValues = false) {
1140
+ const { serviceId, ...ctxRest } = opts;
1141
+ const sendOpts = barePrefix === void 0 ? {
1142
+ ctx: ctxRest,
1143
+ interfaceHash: iface.schemaHash
1144
+ } : void 0;
1145
+ const prefix = barePrefix ?? (defaultRoute ? "" : serviceId ? `${serviceId}::${iface.info.id}::` : `${iface.info.id}::`);
1146
+ const proxy = Object.create(null);
1147
+ for (const [name, member] of Object.entries(iface.members)) {
1148
+ const wireMethod = `${prefix}${name}`;
1149
+ if (member.kind === "request") {
1150
+ if (member.clientStreamSchema !== void 0 || member.serverStreamSchema !== void 0) {
1151
+ const serverStreamSchema = member.serverStreamSchema;
1152
+ proxy[name] = (params, callOpts) => {
1153
+ const userListener = callOpts?.onMessage;
1154
+ const onStreamMessage = userListener !== void 0 ? (raw) => {
1155
+ if (serverStreamSchema !== void 0) {
1156
+ const r = safeParse(serverStreamSchema, raw);
1157
+ if (!r.success) return;
1158
+ userListener(r.data);
1159
+ return;
1160
+ }
1161
+ userListener(raw);
1162
+ } : void 0;
1163
+ const channelOpts = onStreamMessage !== void 0 ? {
1164
+ ...sendOpts,
1165
+ onStreamMessage
1166
+ } : sendOpts;
1167
+ let call;
1168
+ try {
1169
+ this._validateOutboundParamsFor(member, wireMethod, params);
1170
+ call = this.channel.sendRequestWithStream(wireMethod, params, channelOpts);
1171
+ } catch (cause) {
1172
+ if (!allFailuresAsValues) throw cause;
1173
+ call = {
1174
+ result: Promise.reject(cause),
1175
+ send: () => {
1176
+ throw cause;
1177
+ },
1178
+ cancel: () => {
1179
+ throw cause;
1180
+ },
1181
+ ping: async () => {
1182
+ throw cause;
1183
+ }
1184
+ };
1185
+ }
1186
+ const checked = addCheckedResult(call.result.then((raw) => validateWireResult(member.resultSchema, raw, wireMethod)), member, allFailuresAsValues);
1187
+ return Object.assign(checked, {
1188
+ send: async (payload) => call.send(payload),
1189
+ cancel: async (reason) => call.cancel(reason),
1190
+ dispose: (reason) => call.dispose?.(reason),
1191
+ ping: () => call.ping()
1192
+ });
1193
+ };
1194
+ } else proxy[name] = (params) => {
1195
+ return addCheckedResult((async () => {
1196
+ this._validateOutboundParamsFor(member, wireMethod, params);
1197
+ const raw = await this.channel.sendRequest(wireMethod, params, sendOpts);
1198
+ return validateWireResult(member.resultSchema, raw, wireMethod);
1199
+ })(), member, allFailuresAsValues);
1200
+ };
1201
+ } else {
1202
+ const notify = (params) => {
1203
+ this._validateOutboundParamsFor(member, wireMethod, params);
1204
+ this.channel.sendNotification(wireMethod, params, sendOpts);
1205
+ };
1206
+ proxy[name] = barePrefix === void 0 ? async (params) => notify(params) : notify;
1207
+ }
1208
+ }
1209
+ return iface.nestClient(proxy);
1210
+ }
1211
+ _validateOutboundParamsFor(member, wireMethod, params) {
1212
+ if (!this._validateOutboundParams) return;
1213
+ validateValue(member.paramsSchema, params, `Invalid params for ${wireMethod}`, ErrorCode.invalidParams);
1214
+ }
1215
+ async _handleRequest(call) {
1216
+ const parsed = this._parseRouted(call.method);
1217
+ if (!parsed.ok) return notFound(parsed.reason, call.method);
1218
+ const { entry, memberName } = parsed;
1219
+ const member = entry.iface.members[memberName];
1220
+ if (!member) return notFound("unknown-method", call.method);
1221
+ const handler = entry.handlers[memberName];
1222
+ if (!handler) return notFound("unknown-method", call.method);
1223
+ const userParams = stripLinkRpcWireMeta(call.params);
1224
+ const parsedParams = safeParse(member.paramsSchema, userParams);
1225
+ if (!parsedParams.success) return { error: {
1226
+ code: ErrorCode.invalidParams,
1227
+ message: "Invalid params",
1228
+ data: { issues: parsedParams.error.issues }
1229
+ } };
1230
+ if (member.kind !== "request") return notFound("unknown-method", call.method);
1231
+ const stream = this._buildStreamApi(call.stream, call.signal, member);
1232
+ try {
1233
+ const result = await handler(parsedParams.data, call.context, stream);
1234
+ const encodedError = encodeApplicationError(member, result);
1235
+ if (encodedError !== void 0) return { error: encodedError };
1236
+ validateValue(member.resultSchema, result, `Invalid result for ${call.method}`, ErrorCode.internalError);
1237
+ return { result: result === void 0 ? null : result };
1238
+ } catch (e) {
1239
+ if (e instanceof RpcError) return { error: {
1240
+ code: e.code,
1241
+ message: e.message,
1242
+ data: e.data
1243
+ } };
1244
+ return { error: {
1245
+ code: ErrorCode.internalError,
1246
+ message: e instanceof Error ? e.message : String(e)
1247
+ } };
1248
+ }
1249
+ }
1250
+ _buildStreamApi(callStream, signal, member) {
1251
+ return {
1252
+ send: (payload) => {
1253
+ if (member.serverStreamSchema) {
1254
+ const r = safeParse(member.serverStreamSchema, payload);
1255
+ if (!r.success) throw new Error(`Server stream payload failed schema validation: ${r.error.message}`);
1256
+ }
1257
+ return callStream.send(payload);
1258
+ },
1259
+ onMessage: (listener) => {
1260
+ callStream.onMessage((raw) => {
1261
+ if (member.clientStreamSchema) {
1262
+ const r = safeParse(member.clientStreamSchema, raw);
1263
+ if (!r.success) return;
1264
+ listener(r.data);
1265
+ return;
1266
+ }
1267
+ });
1268
+ },
1269
+ ping: () => callStream.ping(),
1270
+ signal
1271
+ };
1272
+ }
1273
+ _handleNotification(call) {
1274
+ const parsed = this._parseRouted(call.method);
1275
+ if (!parsed.ok) return;
1276
+ const { entry, memberName } = parsed;
1277
+ const member = entry.iface.members[memberName];
1278
+ if (member?.kind !== "notification") return;
1279
+ const handler = entry.handlers[memberName];
1280
+ if (!handler) return;
1281
+ const userParams = stripLinkRpcWireMeta(call.params);
1282
+ const parsedParams = safeParse(member.paramsSchema, userParams);
1283
+ if (!parsedParams.success) return;
1284
+ handler(parsedParams.data, call.context);
1285
+ }
1286
+ _parseRouted(method) {
1287
+ const parsed = parseMethodName(method);
1288
+ if (!parsed) return {
1289
+ ok: false,
1290
+ reason: "bad-method-grammar"
1291
+ };
1292
+ if (parsed.kind === "bare") {
1293
+ let selected;
1294
+ for (const binding of this._bareBindings.values()) if (parsed.member.startsWith(binding.prefix) && (selected === void 0 || binding.prefix.length > selected.prefix.length)) selected = binding;
1295
+ if (!selected) return {
1296
+ ok: false,
1297
+ reason: "no-preset"
1298
+ };
1299
+ return {
1300
+ ok: true,
1301
+ entry: selected.entry,
1302
+ memberName: parsed.member.slice(selected.prefix.length)
1303
+ };
1304
+ }
1305
+ const serviceId = parsed.kind === "full" ? parsed.serviceId : void 0;
1306
+ const key = `${serviceId ?? ""}::${parsed.interfaceId}`;
1307
+ const entry = this._registry.get(key);
1308
+ if (!entry) return {
1309
+ ok: false,
1310
+ reason: serviceId ? "unknown-service" : "unknown-interface"
1311
+ };
1312
+ return {
1313
+ ok: true,
1314
+ entry,
1315
+ memberName: parsed.member
1316
+ };
1317
+ }
1318
+ _installServiceInspection(serviceId) {
1319
+ if (this._serviceInspectionRegistrations.has(serviceId)) return;
1320
+ const info = this._inspection;
1321
+ const host = this._inspectionHost;
1322
+ if (info === void 0 || host === void 0) return;
1323
+ this._serviceInspectionRegistrations.set(serviceId, [host.expose(this, {
1324
+ serviceId,
1325
+ portId: info.portId
1326
+ })]);
1327
+ }
1328
+ _removeServiceInspection(serviceId) {
1329
+ const registrations = this._serviceInspectionRegistrations.get(serviceId);
1330
+ if (registrations === void 0) return;
1331
+ this._serviceInspectionRegistrations.delete(serviceId);
1332
+ for (const registration of registrations.reverse()) registration.dispose();
1333
+ }
1334
+ _hasBusinessServiceRegistration(serviceId) {
1335
+ return Array.from(this._registry.values()).some((entry) => entry.serviceId === serviceId && !entry.internalInspection);
1336
+ }
1337
+ };
1338
+ function createInspectionId(kind) {
1339
+ const bytes = /* @__PURE__ */ new Uint8Array(16);
1340
+ globalThis.crypto.getRandomValues(bytes);
1341
+ return `${kind}:${bytesToBase64Url(bytes)}`;
1342
+ }
1343
+ /** Structural equality for two CNF root-node-id requirement collections. */
1344
+ function rootPrincipalSetsEqual(a, b) {
1345
+ if (a.length !== b.length) return false;
1346
+ for (let i = 0; i < a.length; i++) {
1347
+ const sa = a[i];
1348
+ const sb = b[i];
1349
+ if (sa.length !== sb.length) return false;
1350
+ for (let j = 0; j < sa.length; j++) {
1351
+ if (sa[j].principal !== sb[j].principal) return false;
1352
+ if (sa[j].transitive === true !== (sb[j].transitive === true)) return false;
1353
+ }
1354
+ }
1355
+ return true;
1356
+ }
1357
+ function directoryWatcherMatchesEntry(watcher, entry) {
1358
+ const serviceId = entry.serviceId ?? "";
1359
+ return (watcher.interfaceId === void 0 || watcher.interfaceId === entry.iface.info.id) && (watcher.interfaceIdPrefix === void 0 || entry.iface.info.id.startsWith(watcher.interfaceIdPrefix)) && (watcher.serviceId === void 0 || watcher.serviceId === serviceId) && serviceIdMatchesScopes(serviceId, watcher.serviceIdScopes);
1360
+ }
1361
+ function addCheckedResult(promise, member, allFailuresAsValues = false) {
1362
+ const settled = promise.then((value) => ({
1363
+ ok: true,
1364
+ value
1365
+ }), (cause) => ({
1366
+ ok: false,
1367
+ error: decodeCheckedError(cause, member.applicationErrors),
1368
+ cause
1369
+ }));
1370
+ const result = settled.then((outcome) => {
1371
+ if (outcome.ok) return outcome.value;
1372
+ if (allFailuresAsValues) return new RpcFailure(outcome.error.kind === "application" ? {
1373
+ kind: "application",
1374
+ error: outcome.error
1375
+ } : {
1376
+ kind: "generic",
1377
+ error: outcome.error
1378
+ });
1379
+ if (outcome.error.kind === "application") return new RpcFailure(outcome.error);
1380
+ throw outcome.error.kind === "nonCompliantServer" ? outcome.error : outcome.cause;
1381
+ });
1382
+ return Object.assign(result, { result: async () => {
1383
+ result.catch(() => {});
1384
+ const outcome = await settled;
1385
+ return outcome.ok ? outcome : {
1386
+ ok: false,
1387
+ error: outcome.error
1388
+ };
1389
+ } });
1390
+ }
1391
+ function decodeCheckedError(cause, descriptors) {
1392
+ if (cause instanceof NonCompliantServerError) return cause;
1393
+ if (!(cause instanceof RpcError) || cause.origin !== "remote") return {
1394
+ kind: cause instanceof RpcError && cause.origin === "transport" ? "transport" : "local",
1395
+ cause
1396
+ };
1397
+ const original = {
1398
+ code: cause.code,
1399
+ message: cause.message,
1400
+ ...cause.hasData ? { data: cause.data } : {}
1401
+ };
1402
+ const candidates = descriptors.filter((candidate) => candidate.code === cause.code).sort((a, b) => Number(b.type !== void 0) - Number(a.type !== void 0));
1403
+ const body = {
1404
+ message: cause.message,
1405
+ ...cause.hasData ? { data: cause.data } : {}
1406
+ };
1407
+ const issues = [];
1408
+ for (const descriptor of candidates) {
1409
+ const parsed = parseDeclaredErrorBody(descriptor, body);
1410
+ if (!parsed.success) {
1411
+ issues.push(...parsed.issues);
1412
+ continue;
1413
+ }
1414
+ if (descriptor.type === void 0) return brandApplicationError({
1415
+ ...parsed.data,
1416
+ kind: "application",
1417
+ code: descriptor.code
1418
+ });
1419
+ const envelope = parsed.data.data;
1420
+ return brandApplicationError({
1421
+ kind: "application",
1422
+ code: descriptor.code,
1423
+ message: parsed.data.message,
1424
+ type: descriptor.type,
1425
+ ...typeof envelope === "object" && envelope !== null && "data" in envelope ? { data: envelope.data } : {}
1426
+ });
1427
+ }
1428
+ if (candidates.length > 0) return new NonCompliantServerError(original, issues);
1429
+ return {
1430
+ kind: "remote",
1431
+ ...original
1432
+ };
1433
+ }
1434
+ function encodeApplicationError(member, value) {
1435
+ if (!isApplicationErrorValue(value)) return void 0;
1436
+ const candidate = value;
1437
+ const descriptor = member.applicationErrors.find((error) => error.code === candidate.code && error.type === candidate.type && (error.bodySchema !== void 0 || error.type !== void 0 || error.message === candidate.message));
1438
+ if (descriptor === void 0) throw new RpcError(`Undeclared application error ${String(candidate.code)}`, ErrorCode.internalError);
1439
+ const data = Object.hasOwn(value, "data") ? { data: value.data } : {};
1440
+ const body = {
1441
+ message: value.message,
1442
+ ...descriptor.type === void 0 ? data : { data: {
1443
+ type: descriptor.type,
1444
+ ...data
1445
+ } }
1446
+ };
1447
+ const parsed = parseDeclaredErrorBody(descriptor, body);
1448
+ const issues = parsed.success ? jsonIssues(body) : parsed.issues;
1449
+ if (issues.length > 0) throw new RpcError(`Invalid data for application error ${descriptor.code}`, ErrorCode.internalError, { issues: issues.map((issue) => ({ ...issue })) });
1450
+ return {
1451
+ code: descriptor.code,
1452
+ ...body
1453
+ };
1454
+ }
1455
+ function validateWireResult(schema, raw, wireMethod) {
1456
+ const value = normalizeWireResult(schema, raw);
1457
+ validateValue(schema, value, `Invalid result for ${wireMethod}`, ErrorCode.internalError);
1458
+ return value;
1459
+ }
1460
+ /** Validate at both ends without stripping extra keys or applying schema transforms. */
1461
+ function validateValue(schema, value, message, code) {
1462
+ const checked = safeParse(schema, value);
1463
+ if (!checked.success) throw new RpcError(message, code, { issues: checked.error.issues });
1464
+ }
1465
+ /** Undo the server's undefined → null convention only when the schema rejects null. */
1466
+ function normalizeWireResult(schema, raw) {
1467
+ if (raw !== null) return raw;
1468
+ if (safeParse(schema, null).success) return null;
1469
+ if (safeParse(schema, void 0).success) return void 0;
1470
+ return null;
1471
+ }
1472
+ /** Service-scoped handle returned by `connection.service(id)`. */
1473
+ var ServiceHandle = class {
1474
+ _connection;
1475
+ _serviceId;
1476
+ constructor(_connection, _serviceId) {
1477
+ this._connection = _connection;
1478
+ this._serviceId = _serviceId;
1479
+ }
1480
+ getResultClient(iface, opts = {}) {
1481
+ return this._connection.getResultClient(iface, {
1482
+ ...opts,
1483
+ serviceId: this._serviceId
1484
+ });
1485
+ }
1486
+ get(iface, opts = {}) {
1487
+ return this._connection.get(iface, {
1488
+ ...opts,
1489
+ serviceId: this._serviceId
1490
+ });
1491
+ }
1492
+ register(ifaceOrTarget, handlers, opts = {}) {
1493
+ return this._connection.register(ifaceOrTarget, handlers, {
1494
+ ...opts,
1495
+ serviceId: this._serviceId
1496
+ });
1497
+ }
1498
+ };
1499
+ function notFound(reason, method) {
1500
+ return { error: {
1501
+ code: ErrorCode.methodNotFound,
1502
+ message: `Method not found: ${method}`,
1503
+ data: {
1504
+ reason,
1505
+ method
1506
+ }
1507
+ } };
1508
+ }
1509
+ //#endregion
1510
+ //#region src/transport/messageTransport.ts
1511
+ /**
1512
+ * Observe every message crossing a transport without changing its buffering or
1513
+ * lifecycle behavior.
1514
+ */
1515
+ function traceMessageTransport(transport, trace) {
1516
+ return {
1517
+ send(message) {
1518
+ trace("send", message);
1519
+ return transport.send(message);
1520
+ },
1521
+ setListener(listener) {
1522
+ transport.setListener(listener === void 0 ? void 0 : (message) => {
1523
+ trace("receive", message);
1524
+ listener(message);
1525
+ });
1526
+ },
1527
+ dispose() {
1528
+ transport.dispose();
1529
+ }
1530
+ };
1531
+ }
1532
+ /**
1533
+ * Two in-memory transports wired back-to-back. Useful for tests and for
1534
+ * same-process bridges.
1535
+ *
1536
+ * Generic in the per-direction payload type so asymmetric pairs (e.g. one
1537
+ * side sends plain `JsonRpcMessage`, the other side sends
1538
+ * `JsonRpcMessage & { context: Participant }`) are expressible at the type
1539
+ * level. At runtime both halves just hand objects through by reference.
1540
+ */
1541
+ var TransportPair = class {
1542
+ a;
1543
+ b;
1544
+ constructor() {
1545
+ const aT = new _InMemoryTransport();
1546
+ const bT = new _InMemoryTransport();
1547
+ aT._peer = bT;
1548
+ bT._peer = aT;
1549
+ this.a = aT;
1550
+ this.b = bT;
1551
+ }
1552
+ };
1553
+ var _InMemoryTransport = class {
1554
+ _peer;
1555
+ _listener;
1556
+ _buffer = [];
1557
+ _closed = false;
1558
+ send(message) {
1559
+ if (this._closed || this._peer._closed) return;
1560
+ this._peer._deliver(message);
1561
+ }
1562
+ setListener(listener) {
1563
+ this._listener = listener;
1564
+ if (listener) while (this._buffer.length > 0 && this._listener) {
1565
+ const m = this._buffer.shift();
1566
+ this._listener(m);
1567
+ }
1568
+ }
1569
+ dispose() {
1570
+ this._closed = true;
1571
+ }
1572
+ _deliver(m) {
1573
+ if (this._listener) this._listener(m);
1574
+ else this._buffer.push(m);
1575
+ }
1576
+ };
1577
+ //#endregion
1578
+ //#region src/connection/endpointUri.ts
1579
+ const TOKEN_PARAM = "token";
1580
+ const BROKER_PARAM = "broker";
1581
+ const COMMAND_PARAM = "command";
1582
+ const ARGV_PARAM = "argv";
1583
+ const PROVISION_SLOT_PARAM = "provisionSlot";
1584
+ const ENV_PARAM = "env";
1585
+ const CWD_PARAM = "cwd";
1586
+ function _tryParseUrl(uri) {
1587
+ try {
1588
+ return new URL(uri);
1589
+ } catch {
1590
+ return;
1591
+ }
1592
+ }
1593
+ function _looksLikeWsUrl(uri) {
1594
+ return /^wss?:\/\//i.test(uri);
1595
+ }
1596
+ /** True for a Windows named-pipe path (`\\.\pipe\…` or `\\?\pipe\…`). */
1597
+ function _isWindowsPipePath(path) {
1598
+ return /^\\\\[.?]\\pipe\\/i.test(path);
1599
+ }
1600
+ function _parseCommand(url) {
1601
+ const argv = url.searchParams.getAll(ARGV_PARAM);
1602
+ if (argv.length > 0) return { argv };
1603
+ const command = url.searchParams.get(COMMAND_PARAM);
1604
+ if (command !== null) return { command };
1605
+ throw new Error(`endpoint '${url.protocol}' requires a '${COMMAND_PARAM}' or '${ARGV_PARAM}' query parameter`);
1606
+ }
1607
+ /** Parse repeated `env=KEY=VALUE` params into a record (first `=` splits). */
1608
+ function _parseEnv(url) {
1609
+ const entries = url.searchParams.getAll(ENV_PARAM);
1610
+ if (entries.length === 0) return;
1611
+ const env = {};
1612
+ for (const entry of entries) {
1613
+ const eq = entry.indexOf("=");
1614
+ if (eq === -1) throw new Error(`endpoint '${ENV_PARAM}' param must be 'KEY=VALUE', got '${entry}'`);
1615
+ env[entry.slice(0, eq)] = entry.slice(eq + 1);
1616
+ }
1617
+ return env;
1618
+ }
1619
+ /**
1620
+ * Parse a strict endpoint URI into an {@link ResolvedEndpoint}. Throws on an
1621
+ * unknown scheme or a malformed command endpoint. A bare (scheme-less) string
1622
+ * is auto-detected as `ws`/`wss` URL or a socket path.
1623
+ */
1624
+ function parseEndpointUri(uri) {
1625
+ const trimmed = uri.trim();
1626
+ if (trimmed === "") throw new Error("endpoint URI is empty");
1627
+ const url = _tryParseUrl(trimmed);
1628
+ if (!url) {
1629
+ if (_looksLikeWsUrl(trimmed)) return {
1630
+ kind: "ws",
1631
+ url: trimmed
1632
+ };
1633
+ return {
1634
+ kind: "socket",
1635
+ path: trimmed
1636
+ };
1637
+ }
1638
+ switch (url.protocol) {
1639
+ case "ws:":
1640
+ case "wss:": {
1641
+ const token = url.searchParams.get(TOKEN_PARAM) ?? void 0;
1642
+ url.searchParams.delete(TOKEN_PARAM);
1643
+ const cleanUrl = url.toString();
1644
+ return token !== void 0 ? {
1645
+ kind: "ws",
1646
+ url: cleanUrl,
1647
+ token
1648
+ } : {
1649
+ kind: "ws",
1650
+ url: cleanUrl
1651
+ };
1652
+ }
1653
+ case "ws-no-init:": return {
1654
+ kind: "ws-no-init",
1655
+ url: new URL(trimmed.replace(/^ws-no-init:/i, "ws:")).toString()
1656
+ };
1657
+ case "unix:": {
1658
+ const token = url.searchParams.get(TOKEN_PARAM) ?? void 0;
1659
+ const brokerMode = _parseBrokerMode(url);
1660
+ return {
1661
+ kind: "socket",
1662
+ path: decodeURIComponent(url.pathname),
1663
+ ...token !== void 0 ? { token } : {},
1664
+ ...brokerMode !== void 0 ? { brokerMode } : {}
1665
+ };
1666
+ }
1667
+ case "npipe:": {
1668
+ const token = url.searchParams.get(TOKEN_PARAM) ?? void 0;
1669
+ const brokerMode = _parseBrokerMode(url);
1670
+ return {
1671
+ kind: "socket",
1672
+ path: `\\\\${url.host === "" ? "." : url.host}${decodeURIComponent(url.pathname).replace(/\//g, "\\")}`,
1673
+ ...token !== void 0 ? { token } : {},
1674
+ ...brokerMode !== void 0 ? { brokerMode } : {}
1675
+ };
1676
+ }
1677
+ case "cmd-stdio:": {
1678
+ const env = _parseEnv(url);
1679
+ const cwd = url.searchParams.get(CWD_PARAM) ?? void 0;
1680
+ return {
1681
+ kind: "cmd-stdio",
1682
+ command: _parseCommand(url),
1683
+ ...env !== void 0 ? { env } : {},
1684
+ ...cwd !== void 0 ? { cwd } : {}
1685
+ };
1686
+ }
1687
+ case "cmd:": {
1688
+ const command = _parseCommand(url);
1689
+ const provisionSlot = url.searchParams.get(PROVISION_SLOT_PARAM) ?? void 0;
1690
+ const env = _parseEnv(url);
1691
+ const cwd = url.searchParams.get(CWD_PARAM) ?? void 0;
1692
+ return {
1693
+ kind: "cmd-env",
1694
+ command,
1695
+ ...provisionSlot !== void 0 ? { provisionSlot } : {},
1696
+ ...env !== void 0 ? { env } : {},
1697
+ ...cwd !== void 0 ? { cwd } : {}
1698
+ };
1699
+ }
1700
+ default: throw new Error(`unsupported endpoint scheme '${url.protocol}' (expected unix:, npipe:, ws:, wss:, ws-no-init:, cmd:, or cmd-stdio:)`);
1701
+ }
1702
+ }
1703
+ const REDACTED = "***";
1704
+ function _appendToken(params, token, reveal) {
1705
+ if (token === void 0) return;
1706
+ params.set(TOKEN_PARAM, REDACTED);
1707
+ }
1708
+ function _parseBrokerMode(url) {
1709
+ const value = url.searchParams.get(BROKER_PARAM);
1710
+ if (value === null) return void 0;
1711
+ if (value === "linkrpc" || value === "raw") return value;
1712
+ throw new Error(`endpoint '${BROKER_PARAM}' param must be 'linkrpc' or 'raw', got '${value}'`);
1713
+ }
1714
+ function _appendCommand(params, command) {
1715
+ if ("argv" in command) for (const a of command.argv) params.append(ARGV_PARAM, a);
1716
+ else params.set(COMMAND_PARAM, command.command);
1717
+ }
1718
+ function _appendEnv(params, env) {
1719
+ if (env === void 0) return;
1720
+ for (const [key, value] of Object.entries(env)) params.append(ENV_PARAM, `${key}=${value}`);
1721
+ }
1722
+ /**
1723
+ * Render an {@link ResolvedEndpoint} back to a canonical strict URI. The token is
1724
+ * redacted (`***`) unless `revealToken` is set, so the result is paste-safe for
1725
+ * logs. Round-trips with {@link parseEndpointUri} when `revealToken` is true.
1726
+ */
1727
+ function formatEndpointUri(spec, options) {
1728
+ switch (spec.kind) {
1729
+ case "socket": {
1730
+ const params = new URLSearchParams();
1731
+ _appendToken(params, spec.token);
1732
+ if (spec.brokerMode !== void 0) params.set(BROKER_PARAM, spec.brokerMode);
1733
+ const query = params.toString();
1734
+ const suffix = query === "" ? "" : `?${query}`;
1735
+ if (_isWindowsPipePath(spec.path)) {
1736
+ const m = spec.path.match(/^\\\\([.?])\\(.*)$/);
1737
+ return `npipe://${m ? m[1] : "."}/${(m ? m[2] : spec.path).replace(/\\/g, "/")}${suffix}`;
1738
+ }
1739
+ return `unix:${spec.path}${suffix}`;
1740
+ }
1741
+ case "ws": {
1742
+ const url = new URL(spec.url);
1743
+ _appendToken(url.searchParams, spec.token);
1744
+ return url.toString();
1745
+ }
1746
+ case "ws-no-init": return spec.url.replace(/^ws:/i, "ws-no-init:");
1747
+ case "cmd-stdio": {
1748
+ const params = new URLSearchParams();
1749
+ _appendCommand(params, spec.command);
1750
+ _appendEnv(params, spec.env);
1751
+ if (spec.cwd !== void 0) params.set(CWD_PARAM, spec.cwd);
1752
+ return `cmd-stdio:?${params.toString()}`;
1753
+ }
1754
+ case "cmd-env": {
1755
+ const params = new URLSearchParams();
1756
+ _appendCommand(params, spec.command);
1757
+ if (spec.provisionSlot !== void 0) params.set(PROVISION_SLOT_PARAM, spec.provisionSlot);
1758
+ _appendEnv(params, spec.env);
1759
+ if (spec.cwd !== void 0) params.set(CWD_PARAM, spec.cwd);
1760
+ return `cmd:?${params.toString()}`;
1761
+ }
1762
+ }
1763
+ }
1764
+ createMergeProxy(globalThis, {});
1765
+ function createMergeProxy(baseObj, extObj) {
1766
+ return new Proxy(baseObj, {
1767
+ get(_target, prop, _receiver) {
1768
+ if (prop in extObj) return extObj[prop];
1769
+ else return baseObj[prop];
1770
+ },
1771
+ set(_target, prop, value) {
1772
+ if (prop in extObj) delete extObj[prop];
1773
+ baseObj[prop] = value;
1774
+ return true;
1775
+ },
1776
+ deleteProperty(_target, prop) {
1777
+ let success = false;
1778
+ if (prop in extObj) {
1779
+ delete extObj[prop];
1780
+ success = true;
1781
+ }
1782
+ if (prop in baseObj) {
1783
+ delete baseObj[prop];
1784
+ success = true;
1785
+ }
1786
+ return success;
1787
+ },
1788
+ ownKeys(_target) {
1789
+ const baseKeys = Reflect.ownKeys(baseObj);
1790
+ const extKeys = Reflect.ownKeys(extObj);
1791
+ const extKeysSet = new Set(extKeys);
1792
+ return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys];
1793
+ },
1794
+ defineProperty(_target, prop, desc) {
1795
+ if (prop in extObj) delete extObj[prop];
1796
+ Reflect.defineProperty(baseObj, prop, desc);
1797
+ return true;
1798
+ },
1799
+ getOwnPropertyDescriptor(_target, prop) {
1800
+ if (prop in extObj) return Reflect.getOwnPropertyDescriptor(extObj, prop);
1801
+ else return Reflect.getOwnPropertyDescriptor(baseObj, prop);
1802
+ },
1803
+ has(_target, prop) {
1804
+ return prop in extObj || prop in baseObj;
1805
+ }
1806
+ });
1807
+ }
1808
+ //#endregion
1809
+ //#region src/identity/identity.interfaces.ts
1810
+ const zB64 = string();
1811
+ /**
1812
+ * Per-participant key oracle. Reached on the participant's root overlay
1813
+ * (form-2 method names like `identity::sign`). Private key material lives
1814
+ * in the executor; the participant only sees the operations.
1815
+ *
1816
+ * Wire shape:
1817
+ * sign / wrap / unwrap all take and return base64url-encoded bytes.
1818
+ * `domain` is a caller-chosen string bound into HPKE info and the AEAD
1819
+ * AAD. `unwrap` succeeds only when invoked with the exact `domain` that
1820
+ * `wrap` was called with. Use a stable, namespaced string (e.g.
1821
+ * `secrets.vault.master-key.v1`).
1822
+ */
1823
+ defineInterface({
1824
+ id: "identity",
1825
+ description: "Per-participant key oracle: exposes signing and HPKE-based wrap/unwrap for the identity the executor has attached to this participant's root overlay. Private keys never leave the executor."
1826
+ }, {
1827
+ getPrincipal: requestType(object({}), object({ principal: string() })),
1828
+ getWrapPublicKey: requestType(object({}), object({ wrapPublicKey: zB64 })),
1829
+ sign: requestType(object({ bytes: zB64 }), object({ signature: zB64 })),
1830
+ wrap: requestType(object({
1831
+ domain: string(),
1832
+ bytes: zB64
1833
+ }), object({ blob: zB64 })),
1834
+ unwrap: requestType(object({
1835
+ domain: string(),
1836
+ blob: zB64
1837
+ }), object({ bytes: zB64 }))
1838
+ });
1839
+ /**
1840
+ * Per-identity persistent key/value store. Reached on the participant's
1841
+ * root overlay as `identity.storage::get` etc. — same overlay that serves
1842
+ * `identity::*`, same single-participant addressability.
1843
+ *
1844
+ * Storage scope is the executor's managed-identity slot (e.g.
1845
+ * `service:demo`). Lifecycle is bound to the identity: when the executor
1846
+ * deletes the identity for a slot, the storage for that slot is wiped in
1847
+ * the same operation. New identity issued for the same slot starts empty.
1848
+ *
1849
+ * The executor encrypts the backing file at rest with the same secret it
1850
+ * uses for identity files — see {@link IdentityKeystore} in
1851
+ * `@hediet/linkrpc/node`.
1852
+ *
1853
+ * Keys must match `^[A-Za-z0-9._/-]{1,256}$`. `/` is conventional for
1854
+ * namespacing (`caps.v1`, `prefs/foo`) and `list({ prefix })` honours
1855
+ * that. Values are arbitrary JSON.
1856
+ */
1857
+ defineInterface({
1858
+ id: "identity.storage",
1859
+ description: "Per-identity persistent key/value store. Scope is the executor's managed-identity slot; lifecycle is tied to the identity itself. Backed by an at-rest-encrypted file in the executor."
1860
+ }, {
1861
+ get: requestType(object({ key: string() }), object({ value: optional(unknown()) })),
1862
+ set: requestType(object({
1863
+ key: string(),
1864
+ value: unknown()
1865
+ }), object({})),
1866
+ delete: requestType(object({ key: string() }), object({ existed: boolean() })),
1867
+ list: requestType(object({ prefix: optional(string()) }), object({ keys: array(string()) }))
1868
+ });
1869
+
1870
+ export { HUBRPC_UNSIGNED_KEY as At, formatEndpointUri as B, stripLinkRpcWireMeta as Dt, TransportPair as G, parseEndpointUri as H, HUBRPC_META_KEY as Ot, directoryInterface as St, zServiceIdPattern as Tt, parseMethodName as Vt, NonCompliantServerError as Xt, JsonRpcChannel as at, LinkRpcConnection as et, serviceIdMatchesScopes as gt, serviceIdMatchesPattern as ht, isInterfaceTarget as it, HUBRPC_SIGNATURE_KEY as kt, defaultInterfaceTarget as nt, Channel as ot, traceMessageTransport as q, RpcError as st, ServiceHandle as tt, schemasInterface as wt, defaultsInterface as xt };
1871
+ //# sourceMappingURL=src-DYVSK_tz.js.map