@hediet/linkrpc 0.0.2 → 0.0.3-next.20260922.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +266 -31
  2. package/dist/chunks/{hub.interfaces-Bf4VfWNX.js → hub.interfaces-Deslgtgr.js} +2 -2
  3. package/dist/chunks/{hub.interfaces-Bf4VfWNX.js.map → hub.interfaces-Deslgtgr.js.map} +1 -1
  4. package/dist/chunks/{hubAccess-C06qAKao.d.ts → hubAccess-BmPvNNaM.d.ts} +2 -2
  5. package/dist/chunks/{hubAccess-C06qAKao.d.ts.map → hubAccess-BmPvNNaM.d.ts.map} +1 -1
  6. package/dist/chunks/{hubFacade-BIB9CoAv.js → hubFacade-BKTQXCdF.js} +2 -2
  7. package/dist/chunks/{hubFacade-BIB9CoAv.js.map → hubFacade-BKTQXCdF.js.map} +1 -1
  8. package/dist/chunks/{hubFacade-DtQpGpUy.d.ts → hubFacade-Bmh-SFWd.d.ts} +2 -2
  9. package/dist/chunks/{hubFacade-DtQpGpUy.d.ts.map → hubFacade-Bmh-SFWd.d.ts.map} +1 -1
  10. package/dist/chunks/{boundedTrafficSubscription-XZ1KwXZC.js → inspectionHost-BIkLKvr1.js} +1014 -32
  11. package/dist/chunks/inspectionHost-BIkLKvr1.js.map +1 -0
  12. package/dist/chunks/{linkRpcConnection-BadD37ok.d.ts → linkRpcConnection-Bbq20hQG.d.ts} +2215 -1699
  13. package/dist/chunks/linkRpcConnection-Bbq20hQG.d.ts.map +1 -0
  14. package/dist/chunks/{src-CAhXne71.js → src-DKjTceek.js} +1975 -2051
  15. package/dist/chunks/src-DKjTceek.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
@@ -2,8 +2,8 @@
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Licensed under the MIT License. See License.txt in the project root for license information.
4
4
  *--------------------------------------------------------------------------------------------*/
5
- import { any, array, boolean, enum as enum$1, int, literal, nonnegative, number, object, optional, record, string, union, unknown, void as void$1 } from "zod/mini";
6
- import { toJSONSchema } from "zod/v4/core";
5
+ import { any, array, boolean, enum as enum$1, int, literal, nonnegative, number, object, optional, record, strictObject, string, union, unknown, void as void$1 } from "zod/mini";
6
+ import { safeParse, toJSONSchema } from "zod/v4/core";
7
7
  import { z } from "zod";
8
8
  //#region src/schema/normalize.ts
9
9
  /**
@@ -153,9 +153,171 @@ function _constPropNames(props) {
153
153
  return out;
154
154
  }
155
155
  //#endregion
156
- //#region src/schema/memberTypes.ts
156
+ //#region src/connection/rpcFailure.ts
157
+ const rpcFailureBrand = Symbol("linkrpc.rpcFailure");
158
+ /** A nominal failure wrapper: successful application objects can never collide with it. */
159
+ var RpcFailure = class {
160
+ error;
161
+ [rpcFailureBrand] = true;
162
+ constructor(error) {
163
+ this.error = error;
164
+ Object.freeze(this);
165
+ }
166
+ };
167
+ function isRpcFailure(value) {
168
+ return typeof value === "object" && value !== null && value[rpcFailureBrand] === true;
169
+ }
170
+ //#endregion
171
+ //#region src/protocol/jsonRpc.ts
172
+ const ErrorCode = {
173
+ /** Default code for named LinkRPC application errors. */
174
+ applicationError: 1,
175
+ parseError: -32700,
176
+ invalidRequest: -32600,
177
+ methodNotFound: -32601,
178
+ invalidParams: -32602,
179
+ internalError: -32603,
180
+ /** Caller is authenticated but lacks a capability covering the call. */
181
+ permissionRequired: -32401,
182
+ /** The peer a request was routed to detached before it could respond. */
183
+ peerDisconnected: -32402,
184
+ /** The request exceeded the hub's idle timeout with no stream activity. */
185
+ requestTimeout: -32403,
186
+ /** The request was cancelled (by the caller, or by the hub on disconnect). */
187
+ cancelled: -32800
188
+ };
189
+ function isRequest(m) {
190
+ return typeof m.method === "string" && m.id !== void 0;
191
+ }
192
+ function isNotification(m) {
193
+ return typeof m.method === "string" && m.id === void 0;
194
+ }
195
+ function isResponse(m) {
196
+ return typeof m.method !== "string";
197
+ }
198
+ //#endregion
199
+ //#region src/schema/applicationErrorBrand.ts
157
200
  const applicationErrorBrand = Symbol("linkrpc.applicationError");
158
- function applicationError(code, message, dataSchema) {
201
+ function brandApplicationError(value) {
202
+ return Object.freeze(Object.defineProperty(value, applicationErrorBrand, { value: true }));
203
+ }
204
+ //#endregion
205
+ //#region src/schema/schemaSource.ts
206
+ const schemaSources = /* @__PURE__ */ new WeakMap();
207
+ //#endregion
208
+ //#region src/schema/validationIssue.ts
209
+ function jsonPointerSegment(key) {
210
+ return `/${String(key).replace(/~/g, "~0").replace(/\//g, "~1")}`;
211
+ }
212
+ //#endregion
213
+ //#region src/schema/errorValidation.ts
214
+ const bodySchemas = /* @__PURE__ */ new WeakMap();
215
+ function parseDeclaredErrorBody(descriptor, body) {
216
+ return parseErrorBody(descriptor.bodySchema ?? errorBodySchema(descriptor), body);
217
+ }
218
+ function errorBodySchema(descriptor) {
219
+ let schema = bodySchemas.get(descriptor);
220
+ if (schema === void 0) {
221
+ const payload = descriptor.dataSchema === void 0 ? {} : { data: descriptor.dataSchema };
222
+ schema = descriptor.type === void 0 ? strictObject({
223
+ message: literal(descriptor.message),
224
+ ...payload
225
+ }) : strictObject({
226
+ message: string(),
227
+ data: strictObject({
228
+ type: literal(descriptor.type),
229
+ ...payload
230
+ })
231
+ });
232
+ bodySchemas.set(descriptor, schema);
233
+ }
234
+ return schema;
235
+ }
236
+ function parseErrorBody(schema, body) {
237
+ const parsed = safeParse(schema, body);
238
+ if (!parsed.success) return {
239
+ success: false,
240
+ issues: validationIssues(parsed.error.issues)
241
+ };
242
+ if (typeof parsed.data !== "object" || parsed.data === null || !("message" in parsed.data) || typeof parsed.data.message !== "string") return {
243
+ success: false,
244
+ issues: [{
245
+ path: "/message",
246
+ message: "Expected a string message"
247
+ }]
248
+ };
249
+ return {
250
+ success: true,
251
+ data: {
252
+ ...parsed.data,
253
+ message: parsed.data.message
254
+ }
255
+ };
256
+ }
257
+ function validationIssues(issues, prefix = []) {
258
+ return issues.flatMap((issue) => {
259
+ const path = [...prefix, ...issue.path];
260
+ if (issue.code === "invalid_union") return issue.errors.flatMap((branch) => validationIssues(branch, path));
261
+ if (issue.code === "unrecognized_keys") return issue.keys.map((key) => ({
262
+ path: pointer([...path, key]),
263
+ message: "Unexpected property"
264
+ }));
265
+ return [{
266
+ path: pointer(path),
267
+ message: issue.message
268
+ }];
269
+ });
270
+ }
271
+ function pointer(path) {
272
+ return path.map(jsonPointerSegment).join("");
273
+ }
274
+ function jsonIssues(value, path = [], ancestors = /* @__PURE__ */ new Set()) {
275
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) return [];
276
+ if (typeof value !== "object" || ancestors.has(value) || !Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return [{
277
+ path: pointer(path),
278
+ message: "Expected a JSON value"
279
+ }];
280
+ ancestors.add(value);
281
+ const issues = (Array.isArray(value) ? Array.from(value.entries()) : Object.entries(value)).flatMap(([key, child]) => jsonIssues(child, [...path, key], ancestors));
282
+ ancestors.delete(value);
283
+ return issues;
284
+ }
285
+ //#endregion
286
+ //#region src/schema/memberTypes.ts
287
+ function rpcError(code, schemaOrShape) {
288
+ assertErrorCode(code);
289
+ const bodySchema = "_zod" in schemaOrShape ? schemaOrShape : object({
290
+ message: schemaOrShape.message ?? string(),
291
+ ...schemaOrShape.data === void 0 ? {} : { data: schemaOrShape.data }
292
+ });
293
+ return Object.freeze({
294
+ code,
295
+ bodySchema,
296
+ create(body) {
297
+ return brandApplicationError({
298
+ kind: "application",
299
+ code,
300
+ message: body.message,
301
+ ...Object.hasOwn(body, "data") ? { data: body.data } : {}
302
+ });
303
+ },
304
+ is(value) {
305
+ const payload = isRpcFailure(value) ? value.error : value;
306
+ if (!isApplicationErrorValue(payload) || payload.code !== code || payload.type !== void 0) return false;
307
+ return parseErrorBody(bodySchema, {
308
+ message: payload.message,
309
+ ...Object.hasOwn(payload, "data") ? { data: payload.data } : {}
310
+ }).success;
311
+ }
312
+ });
313
+ }
314
+ function applicationError(codeOrType, messageOrOptions, dataSchema) {
315
+ const type = typeof codeOrType === "string" ? codeOrType : void 0;
316
+ if (type === "") throw new Error("Application error type must be a nonempty string.");
317
+ const options = typeof messageOrOptions === "object" ? messageOrOptions : void 0;
318
+ const code = typeof codeOrType === "number" ? codeOrType : options?.code ?? ErrorCode.applicationError;
319
+ const message = type === void 0 ? messageOrOptions : options?.message ?? type;
320
+ if (type !== void 0) dataSchema = options?.data;
159
321
  assertApplicationErrorCode(code);
160
322
  if (typeof message !== "string") throw new Error("Application error message must be a string.");
161
323
  const create = dataSchema === void 0 ? function() {
@@ -164,6 +326,7 @@ function applicationError(code, message, dataSchema) {
164
326
  kind: "application",
165
327
  code,
166
328
  message,
329
+ ...type === void 0 ? {} : { type },
167
330
  [applicationErrorBrand]: true
168
331
  });
169
332
  } : function(data) {
@@ -172,25 +335,44 @@ function applicationError(code, message, dataSchema) {
172
335
  kind: "application",
173
336
  code,
174
337
  message,
338
+ ...type === void 0 ? {} : { type },
175
339
  [applicationErrorBrand]: true,
176
340
  data
177
341
  });
178
342
  };
179
- return Object.freeze({
343
+ const descriptor = Object.freeze({
344
+ type,
180
345
  code,
181
346
  message,
182
347
  ...dataSchema === void 0 ? {} : { dataSchema },
183
- create
348
+ create,
349
+ is(value) {
350
+ const payload = isRpcFailure(value) ? value.error : value;
351
+ if (!isApplicationErrorValue(payload)) return false;
352
+ if (payload.kind !== "application" || payload.code !== code || payload.type !== type) return false;
353
+ const data = Object.hasOwn(payload, "data") ? { data: payload.data } : {};
354
+ return parseDeclaredErrorBody(descriptor, {
355
+ message: payload.message,
356
+ ...type === void 0 ? data : { data: {
357
+ type,
358
+ ...data
359
+ } }
360
+ }).success;
361
+ }
184
362
  });
363
+ return descriptor;
185
364
  }
186
365
  /** Runtime nominal check for values produced by an application-error descriptor. */
187
366
  function isApplicationErrorValue(value) {
188
367
  return typeof value === "object" && value !== null && value[applicationErrorBrand] === true;
189
368
  }
190
369
  function assertApplicationErrorCode(code) {
191
- if (!Number.isInteger(code) || code < -2147483648 || code > 2147483647) throw new Error(`Application error code ${code} must be a signed 32-bit integer.`);
370
+ assertErrorCode(code);
192
371
  if (code >= -32768 && code <= -32e3 || code === -32800) throw new Error(`Application error code ${code} is reserved by JSON-RPC or LinkRPC.`);
193
372
  }
373
+ function assertErrorCode(code) {
374
+ if (!Number.isInteger(code) || code < -2147483648 || code > 2147483647) throw new Error(`Application error code ${code} must be a signed 32-bit integer.`);
375
+ }
194
376
  var RequestType = class RequestType {
195
377
  paramsSchema;
196
378
  resultSchema;
@@ -210,10 +392,20 @@ var RequestType = class RequestType {
210
392
  this.applicationErrors = applicationErrors;
211
393
  const seen = /* @__PURE__ */ new Set();
212
394
  for (const error of applicationErrors) {
395
+ if (applicationErrors.some((other) => other !== error && other.code === error.code && (other.bodySchema !== void 0 || error.bodySchema !== void 0))) throw new Error(`Raw error code ${error.code} cannot be shared.`);
396
+ if (error.bodySchema !== void 0) {
397
+ assertErrorCode(error.code);
398
+ const key = `code:${error.code}`;
399
+ if (seen.has(key)) throw new Error(`Duplicate application error ${key}.`);
400
+ seen.add(key);
401
+ continue;
402
+ }
213
403
  assertApplicationErrorCode(error.code);
214
404
  if (typeof error.message !== "string") throw new Error("Application error message must be a string.");
215
- if (seen.has(error.code)) throw new Error(`Duplicate application error code ${error.code}.`);
216
- seen.add(error.code);
405
+ if (error.type !== void 0 && (typeof error.type !== "string" || error.type.length === 0)) throw new Error("Application error type must be a nonempty string.");
406
+ const key = error.type === void 0 ? `code:${error.code}` : `type:${error.type}`;
407
+ if (seen.has(key)) throw new Error(`Duplicate application error ${key}.`);
408
+ seen.add(key);
217
409
  }
218
410
  this.applicationErrors = Object.freeze([...applicationErrors]);
219
411
  this.errors = this.applicationErrors;
@@ -273,6 +465,14 @@ function notificationType(params, docs = {}) {
273
465
  }
274
466
  /** Convert a zod schema to our restricted SvcJsonSchema subset. */
275
467
  function zodToSvcJsonSchema(schema, options) {
468
+ const source = schemaSources.get(schema);
469
+ if (source !== void 0) {
470
+ for (const [name, component] of Object.entries(source.components)) {
471
+ if (options === void 0) throw new Error("Imported schema references require a component destination");
472
+ addComponent(options.components, name, normalizeJsonSchema(component));
473
+ }
474
+ return normalizeJsonSchema(source.schema);
475
+ }
276
476
  const type = schema._zod?.def?.type;
277
477
  if (type === "void" || type === "undefined") return true;
278
478
  return normalizeZodJsonSchema(toJSONSchema(schema), options);
@@ -602,6 +802,13 @@ function normalizeSchemaPositions(schema) {
602
802
  const value = normalized[field];
603
803
  if (value !== void 0) normalized[field] = normalizeJsonSchema(value);
604
804
  }
805
+ if (method.errors !== void 0) normalized.errors = method.errors.map((error) => error.schema !== void 0 ? {
806
+ ...error,
807
+ schema: normalizeJsonSchema(error.schema)
808
+ } : {
809
+ ...error,
810
+ ...error.data === void 0 ? {} : { data: normalizeJsonSchema(error.data) }
811
+ });
605
812
  return [name, normalized];
606
813
  }));
607
814
  const componentSchemas = schema.components?.schemas;
@@ -757,6 +964,13 @@ function createSchemaToZod(components = {}) {
757
964
  if (schema.additionalProperties === false) base = object.strict();
758
965
  else if (schema.additionalProperties === true) base = object.loose();
759
966
  else base = object.catchall(compile(schema.additionalProperties));
967
+ base = base.superRefine((value, context) => {
968
+ for (const name of required) if (typeof value === "object" && value !== null && !Object.hasOwn(value, name)) context.addIssue({
969
+ code: "custom",
970
+ path: [name],
971
+ message: "Required property is missing"
972
+ });
973
+ });
760
974
  break;
761
975
  }
762
976
  }
@@ -764,7 +978,12 @@ function createSchemaToZod(components = {}) {
764
978
  };
765
979
  return { toZod(schema) {
766
980
  assertSchemaReferences([schema], components);
767
- return compile(schema);
981
+ const result = compile(schema);
982
+ schemaSources.set(result, {
983
+ schema,
984
+ components
985
+ });
986
+ return result;
768
987
  } };
769
988
  }
770
989
  function isAnnotationOnlySchema(schema) {
@@ -814,6 +1033,34 @@ function stringSchema(format) {
814
1033
  }
815
1034
  }
816
1035
  //#endregion
1036
+ //#region src/schema/validateInterfaceErrors.ts
1037
+ function validateInterfaceErrors(schema) {
1038
+ if (schema === void 0) return;
1039
+ for (const [methodName, method] of Object.entries(schema.methods)) {
1040
+ if (method.result === void 0 && method.errors !== void 0) throw new Error(`Notification "${methodName}" cannot declare application errors.`);
1041
+ const seen = /* @__PURE__ */ new Set();
1042
+ const errors = method.errors ?? [];
1043
+ for (const error of errors) {
1044
+ if (!Number.isInteger(error.code) || error.code < -2147483648 || error.code > 2147483647) throw new Error(`Application error code ${error.code} on "${methodName}" must be a signed 32-bit integer.`);
1045
+ if (errors.some((other) => other !== error && other.code === error.code && (other.schema !== void 0 || error.schema !== void 0))) throw new Error(`Raw error code ${error.code} on "${methodName}" cannot be shared.`);
1046
+ if (error.schema !== void 0) {
1047
+ if ([
1048
+ "message",
1049
+ "type",
1050
+ "data"
1051
+ ].some((key) => Object.hasOwn(error, key))) throw new Error(`Raw error ${error.code} on "${methodName}" must contain only code and schema.`);
1052
+ } else {
1053
+ if (typeof error.message !== "string") throw new Error(`Application error message on "${methodName}" must be a string.`);
1054
+ if (error.code >= -32768 && error.code <= -32e3 || error.code === -32800) throw new Error(`Application error code ${error.code} on "${methodName}" is reserved by JSON-RPC or LinkRPC.`);
1055
+ if (error.type !== void 0 && (typeof error.type !== "string" || error.type.length === 0)) throw new Error(`Application error type on "${methodName}" must be a nonempty string.`);
1056
+ }
1057
+ const key = error.type === void 0 ? `code:${error.code}` : `type:${error.type}`;
1058
+ if (seen.has(key)) throw new Error(`Duplicate application error ${key} on "${methodName}".`);
1059
+ seen.add(key);
1060
+ }
1061
+ }
1062
+ }
1063
+ //#endregion
817
1064
  //#region src/connection/interfaceDefinition.ts
818
1065
  const interfaceDefinitionState = /* @__PURE__ */ new WeakMap();
819
1066
  var InterfaceDefinition = class {
@@ -877,9 +1124,14 @@ function toMethodSchema(name, member, components) {
877
1124
  if (member.clientStreamSchema !== void 0) m.clientStream = convertMemberSchema(name, "clientStream", member.clientStreamSchema, components);
878
1125
  if (member.serverStreamSchema !== void 0) m.serverStream = convertMemberSchema(name, "serverStream", member.serverStreamSchema, components);
879
1126
  if (member.applicationErrors.length > 0) m.errors = member.applicationErrors.map((error) => {
1127
+ if (error.bodySchema !== void 0) return {
1128
+ code: error.code,
1129
+ schema: convertMemberSchema(name, `error=${error.code}`, error.bodySchema, components)
1130
+ };
880
1131
  const result = {
881
1132
  code: error.code,
882
- message: error.message
1133
+ message: error.message,
1134
+ ...error.type === void 0 ? {} : { type: error.type }
883
1135
  };
884
1136
  if (error.dataSchema !== void 0) result.data = convertMemberSchema(name, `error=${error.code}`, error.dataSchema, components);
885
1137
  return result;
@@ -895,20 +1147,6 @@ function toMethodSchema(name, member, components) {
895
1147
  if (docs.annotations !== void 0) m.annotations = docs.annotations;
896
1148
  return m;
897
1149
  }
898
- function validateInterfaceErrors(schema) {
899
- if (schema === void 0) return;
900
- for (const [methodName, method] of Object.entries(schema.methods)) {
901
- if (method.result === void 0 && method.errors !== void 0) throw new Error(`Notification "${methodName}" cannot declare application errors.`);
902
- const seen = /* @__PURE__ */ new Set();
903
- for (const error of method.errors ?? []) {
904
- if (typeof error.message !== "string") throw new Error(`Application error message on "${methodName}" must be a string.`);
905
- if (!Number.isInteger(error.code) || error.code < -2147483648 || error.code > 2147483647) throw new Error(`Application error code ${error.code} on "${methodName}" must be a signed 32-bit integer.`);
906
- if (error.code >= -32768 && error.code <= -32e3 || error.code === -32800) throw new Error(`Application error code ${error.code} on "${methodName}" is reserved by JSON-RPC or LinkRPC.`);
907
- if (seen.has(error.code)) throw new Error(`Duplicate application error code ${error.code} on "${methodName}".`);
908
- seen.add(error.code);
909
- }
910
- }
911
- }
912
1150
  function convertMemberSchema(methodName, position, schema, components) {
913
1151
  return zodToSvcJsonSchema(schema, {
914
1152
  methodName,
@@ -946,21 +1184,29 @@ function defineInterface(info, members) {
946
1184
  * streams use `z.any()` because no zod source is available — call-site
947
1185
  * validation is therefore a no-op and the caller is responsible for
948
1186
  * shape-checking inputs and outputs. Declared application-error payloads
949
- * are materialized and validated because recognizing a typed error must
950
- * never rely on its code alone.
1187
+ * are materialized and validated after code selection. A known code whose
1188
+ * body fails all declared branches is a noncompliant-server failure.
951
1189
  *
952
1190
  * Methods with no `result` descriptor become notifications; methods with
953
1191
  * `clientStream` / `serverStream` get pass-through stream payload
954
1192
  * schemas attached.
955
1193
  */
956
1194
  function interfaceFromSchema(schema) {
1195
+ validateInterfaceErrors(schema);
957
1196
  const members = {};
958
1197
  for (const [name, method] of Object.entries(schema.methods)) {
959
1198
  if (method.result === void 0) {
960
1199
  members[name] = new NotificationType(any());
961
1200
  continue;
962
1201
  }
963
- const base = new RequestType(any(), any(), void$1()).withErrors((method.errors ?? []).map((error) => error.data === void 0 ? applicationError(error.code, error.message) : applicationError(error.code, error.message, schemaToZod(error.data, schema.components?.schemas))));
1202
+ const base = new RequestType(any(), any(), void$1()).withErrors((method.errors ?? []).map((error) => error.schema !== void 0 ? rpcError(error.code, schemaToZod(error.schema, schema.components?.schemas)) : error.type !== void 0 ? error.data === void 0 ? applicationError(error.type, {
1203
+ code: error.code,
1204
+ message: error.message
1205
+ }) : applicationError(error.type, {
1206
+ code: error.code,
1207
+ message: error.message,
1208
+ data: schemaToZod(error.data, schema.components?.schemas)
1209
+ }) : error.data === void 0 ? applicationError(error.code, error.message) : applicationError(error.code, error.message, schemaToZod(error.data, schema.components?.schemas))));
964
1210
  members[name] = method.clientStream !== void 0 || method.serverStream !== void 0 ? base.withStream({
965
1211
  client: method.clientStream !== void 0 ? any() : void 0,
966
1212
  server: method.serverStream !== void 0 ? any() : void 0
@@ -972,6 +1218,109 @@ function interfaceFromSchema(schema) {
972
1218
  return new InterfaceDefinition(info, members, { frozenSchema: schema });
973
1219
  }
974
1220
  //#endregion
1221
+ //#region src/crypto/cryptoProvider.ts
1222
+ const _b64UrlAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
1223
+ const _b64UrlReverse = (() => {
1224
+ const m = {};
1225
+ for (let i = 0; i < 64; i++) m[_b64UrlAlphabet[i]] = i;
1226
+ return m;
1227
+ })();
1228
+ function bytesToBase64Url(bytes) {
1229
+ let out = "";
1230
+ let i = 0;
1231
+ for (; i + 3 <= bytes.length; i += 3) {
1232
+ const n = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
1233
+ out += _b64UrlAlphabet[n >> 18 & 63] + _b64UrlAlphabet[n >> 12 & 63] + _b64UrlAlphabet[n >> 6 & 63] + _b64UrlAlphabet[n & 63];
1234
+ }
1235
+ const rem = bytes.length - i;
1236
+ if (rem === 1) {
1237
+ const n = bytes[i] << 16;
1238
+ out += _b64UrlAlphabet[n >> 18 & 63] + _b64UrlAlphabet[n >> 12 & 63];
1239
+ } else if (rem === 2) {
1240
+ const n = bytes[i] << 16 | bytes[i + 1] << 8;
1241
+ out += _b64UrlAlphabet[n >> 18 & 63] + _b64UrlAlphabet[n >> 12 & 63] + _b64UrlAlphabet[n >> 6 & 63];
1242
+ }
1243
+ return out;
1244
+ }
1245
+ function base64UrlToBytes(s) {
1246
+ const len = s.length;
1247
+ const fullGroups = Math.floor(len / 4);
1248
+ const rem = len % 4;
1249
+ if (rem === 1) throw new Error("base64UrlToBytes: invalid length");
1250
+ const outLen = fullGroups * 3 + (rem === 0 ? 0 : rem - 1);
1251
+ const out = new Uint8Array(outLen);
1252
+ let oi = 0;
1253
+ let i = 0;
1254
+ for (let g = 0; g < fullGroups; g++, i += 4) {
1255
+ const n = _lookup(s, i) << 18 | _lookup(s, i + 1) << 12 | _lookup(s, i + 2) << 6 | _lookup(s, i + 3);
1256
+ out[oi++] = n >> 16 & 255;
1257
+ out[oi++] = n >> 8 & 255;
1258
+ out[oi++] = n & 255;
1259
+ }
1260
+ if (rem === 2) {
1261
+ const n = _lookup(s, i) << 18 | _lookup(s, i + 1) << 12;
1262
+ out[oi++] = n >> 16 & 255;
1263
+ } else if (rem === 3) {
1264
+ const n = _lookup(s, i) << 18 | _lookup(s, i + 1) << 12 | _lookup(s, i + 2) << 6;
1265
+ out[oi++] = n >> 16 & 255;
1266
+ out[oi++] = n >> 8 & 255;
1267
+ }
1268
+ return out;
1269
+ }
1270
+ function _lookup(s, i) {
1271
+ const v = _b64UrlReverse[s[i]];
1272
+ if (v === void 0) throw new Error(`base64UrlToBytes: invalid char at ${i}`);
1273
+ return v;
1274
+ }
1275
+ /** Role-tag prefix on a {@link KeyId}'s inline-key form. */
1276
+ const KEY_ID_PREFIX = "key:";
1277
+ /** Role-tag prefix that wraps a genesis {@link KeyId} into a {@link PrincipalId}. */
1278
+ const PRINCIPAL_PREFIX = "id:";
1279
+ /** The inline {@link KeyId} for a raw public key: `"key:" + b64url(pk)`. */
1280
+ function keyIdForPublicKey(pk) {
1281
+ return KEY_ID_PREFIX + bytesToBase64Url(pk);
1282
+ }
1283
+ /** The perpetual {@link PrincipalId} for a raw public key: `"id:key:" + b64url(pk)`. */
1284
+ function principalForPublicKey(pk) {
1285
+ return "id:" + keyIdForPublicKey(pk);
1286
+ }
1287
+ /**
1288
+ * The genesis {@link KeyId} embedded in a perpetual {@link PrincipalId}
1289
+ * (`"id:" + keyId`). Throws if `principal` is not a well-formed `id:` name.
1290
+ */
1291
+ function keyIdForPrincipal(principal) {
1292
+ if (!principal.startsWith("id:")) throw new Error(`malformed principal (missing "id:" prefix): ${principal}`);
1293
+ return principal.slice(3);
1294
+ }
1295
+ /**
1296
+ * The raw public key carried inline by a `"key:"` {@link KeyId}. Throws on a
1297
+ * missing prefix or malformed base64url.
1298
+ */
1299
+ function publicKeyForKeyId(keyId) {
1300
+ if (!keyId.startsWith("key:")) throw new Error(`unsupported keyId (expected "${KEY_ID_PREFIX}" prefix): ${keyId}`);
1301
+ return base64UrlToBytes(keyId.slice(4));
1302
+ }
1303
+ /**
1304
+ * Resolve the verifying public key for `(principal, keyId)`, or `undefined`
1305
+ * to **reject** (fail closed). This is the single seam every verifier goes
1306
+ * through, replacing the old "the id *is* the key" decode.
1307
+ *
1308
+ * Phase 1 supports only inline genesis keys: the `keyId` must be a `"key:"`
1309
+ * form and the principal must be exactly `"id:" + keyId` (genesis
1310
+ * self-certification by string equality). `keydoc:`/rotation forms resolve
1311
+ * through `bindings`/`timeMs` later and are intentionally not handled here.
1312
+ */
1313
+ function resolveSigningKey(args) {
1314
+ const { principal, keyId } = args;
1315
+ if (!keyId.startsWith("key:")) return;
1316
+ if ("id:" + keyId !== principal) return;
1317
+ try {
1318
+ return { publicKey: base64UrlToBytes(keyId.slice(4)) };
1319
+ } catch {
1320
+ return;
1321
+ }
1322
+ }
1323
+ //#endregion
975
1324
  //#region src/inspection/inspection.interfaces.ts
976
1325
  const zTopologyPort = object({
977
1326
  portId: string(),
@@ -1146,6 +1495,142 @@ const nodeInterface = defineInterface({
1146
1495
  description: "Topology bootstrap: identify the node and port at this end of the direct connection."
1147
1496
  }, { getNodeId: requestType(object({}), zNodeInfo) });
1148
1497
  //#endregion
1498
+ //#region src/connection/streaming.ts
1499
+ /**
1500
+ * Direction a {@link STREAM_METHOD} message travels, relative to the
1501
+ * request it is correlated with. Made explicit on the wire so that a
1502
+ * middlebox (the hub) can *author* a stream message — e.g. inject a
1503
+ * cancel when a caller disconnects — without an inbound message whose
1504
+ * arrival link it could infer the direction from.
1505
+ */
1506
+ const StreamDir = {
1507
+ /** callee → caller: progress, partial results. Routes like a response. */
1508
+ toCaller: "toCaller",
1509
+ /** caller → callee: input, cancellation, keepalive ping. */
1510
+ toCallee: "toCallee"
1511
+ };
1512
+ /**
1513
+ * Reserved control verbs. A control message carries no app `payload`; it
1514
+ * is interpreted by the runtime / hub itself, independently of the
1515
+ * originating method's stream schemas, so *any* request is cancellable
1516
+ * and keep-alive-able even when it declares no app stream.
1517
+ */
1518
+ const StreamControlType = {
1519
+ /**
1520
+ * Ask the callee to abort the in-flight request. Always {@link
1521
+ * StreamDir.toCallee}. The callee surfaces this as an `AbortSignal`
1522
+ * and is expected to settle the request (typically with a
1523
+ * `cancelled` error response).
1524
+ */
1525
+ cancel: "cancel",
1526
+ /**
1527
+ * Keepalive / liveness probe. Resets the hub's per-request idle timer
1528
+ * so a long-running call is not reaped, and lets either side actively
1529
+ * probe the peer. Carries a `nonce` the peer echoes in its {@link
1530
+ * StreamControlType.pong}. May travel in either direction. Emitted
1531
+ * automatically (without awaiting a pong) by the channel for streaming
1532
+ * calls; see {@link STREAM_METHOD}.
1533
+ */
1534
+ ping: "ping",
1535
+ /**
1536
+ * Reply to a {@link StreamControlType.ping}, echoing the ping's
1537
+ * `nonce` so the prober can correlate it. Travels opposite the ping.
1538
+ * Carries no app effect — purely a liveness acknowledgement.
1539
+ */
1540
+ pong: "pong"
1541
+ };
1542
+ /**
1543
+ * Well-known `control.reason` strings. The set is **open** — any string
1544
+ * is valid on the wire; these are the reasons linkrpc itself emits.
1545
+ */
1546
+ const StreamControlReason = {
1547
+ /**
1548
+ * The caller's transport dropped while the request was in flight; the
1549
+ * hub cancels the callee's now-orphaned work.
1550
+ */
1551
+ clientDisconnected: "clientDisconnected",
1552
+ /**
1553
+ * The request exceeded the hub's idle timeout with no stream activity
1554
+ * (no ping, no stream message). See {@link STREAM_METHOD} docs.
1555
+ */
1556
+ idleTimeout: "idleTimeout"
1557
+ };
1558
+ /**
1559
+ * Streaming sub-protocol. Long-running requests can emit additional
1560
+ * notifications correlated with the original call in either direction
1561
+ * (callee → caller progress, caller → callee input / cancellation /
1562
+ * keepalive).
1563
+ *
1564
+ * Wire form: a JSON-RPC notification whose method is
1565
+ * `${streamInterface.info.id}::send` and whose params match
1566
+ * `streamInterface.members.send.paramsSchema`. The hub routes purely on
1567
+ * `requestId` (following the originating request's established path, like
1568
+ * a response); the originating request's capability authorization covers
1569
+ * its full stream lifetime, so stream notifications carry no method /
1570
+ * interface namespace of their own. App `payload` contents are typed per
1571
+ * request via the originating method's
1572
+ * `RequestType.clientStreamSchema` / `serverStreamSchema`; reserved
1573
+ * `control` messages are schema-independent.
1574
+ *
1575
+ * Idle timeout: a request that never streams (no `control` ping, no
1576
+ * stream message) is subject to the hub's per-request idle timeout
1577
+ * (default 30 minutes) — at which point it is cancelled and its caller
1578
+ * gets a `requestTimeout` error. This bounds the hub's pending-request
1579
+ * table, which is also what protects it from a slow-loris / DDoS that
1580
+ * opens requests and never completes them. Streaming-enabled calls keep
1581
+ * themselves alive by emitting a periodic {@link StreamControlType.ping}.
1582
+ *
1583
+ * The interface is declarative: nobody calls
1584
+ * `LinkRpcConnection.register(streamInterface, ...)`. The channel and the
1585
+ * hub intercept `$stream::send` directly. The definition exists so
1586
+ * reflection / consent UIs can describe the wire shape and so all wire
1587
+ * constants derive from one source.
1588
+ */
1589
+ const streamInterface = defineInterface({
1590
+ id: "$stream",
1591
+ description: "Reserved sub-protocol for in-flight stream messages correlated to a request by `requestId`."
1592
+ }, { send: notificationType(object({
1593
+ /**
1594
+ * Id of the in-flight request the message belongs to.
1595
+ * The hub rewrites this across the forwarding boundary
1596
+ * in the same way it rewrites response ids.
1597
+ */
1598
+ requestId: union([number(), string()]),
1599
+ /**
1600
+ * Which way this message travels relative to the request.
1601
+ * Explicit so the hub can author messages (e.g. cancel on
1602
+ * disconnect) without inferring direction from arrival.
1603
+ */
1604
+ dir: enum$1(["toCaller", "toCallee"]),
1605
+ /**
1606
+ * Reserved control verb (cancel / ping / pong). Mutually
1607
+ * exclusive with `payload` in practice: a control message
1608
+ * is interpreted by the runtime, not the application.
1609
+ */
1610
+ control: optional(object({
1611
+ type: enum$1([
1612
+ "cancel",
1613
+ "ping",
1614
+ "pong"
1615
+ ]),
1616
+ /** Open-set human/diagnostic reason; see {@link StreamControlReason}. */
1617
+ reason: optional(string()),
1618
+ /**
1619
+ * Correlation token. A `ping` carries a fresh nonce;
1620
+ * the matching `pong` echoes it back so the prober
1621
+ * can resolve the right outstanding probe.
1622
+ */
1623
+ nonce: optional(string())
1624
+ })),
1625
+ /** Opaque app payload — typed per call by the request's stream schema. */
1626
+ payload: optional(unknown())
1627
+ }), { description: "Emit a stream message tied to the request `requestId`." }) });
1628
+ /**
1629
+ * Full wire method name for stream notifications. Pulled from the
1630
+ * interface object so we have a single source of truth.
1631
+ */
1632
+ const STREAM_METHOD = `${streamInterface.info.id}::send`;
1633
+ //#endregion
1149
1634
  //#region src/inspection/trafficFlowFilter.ts
1150
1635
  var TrafficFlowFilter = class {
1151
1636
  _focused;
@@ -1196,24 +1681,37 @@ var TrafficWatchFlowTracker = class {
1196
1681
  this._active.clear();
1197
1682
  this._unclaimed.clear();
1198
1683
  }
1684
+ forgetPort(portId) {
1685
+ for (const flow of this._active) if (flow.hasPort(portId)) this._removeFlow(flow);
1686
+ }
1199
1687
  };
1200
1688
  var CorrelatedFlow = class {
1201
1689
  _endpoints = /* @__PURE__ */ new Set();
1690
+ _ports = /* @__PURE__ */ new Set();
1202
1691
  _completionEndpoint;
1203
1692
  constructor(endpoints, completionEndpoint = endpoints[0]) {
1204
- for (const endpoint of endpoints) this._endpoints.add(endpointKey(endpoint));
1693
+ for (const endpoint of endpoints) {
1694
+ this._endpoints.add(endpointKey(endpoint));
1695
+ this._ports.add(endpoint.portId);
1696
+ }
1205
1697
  this._completionEndpoint = completionEndpoint === void 0 ? void 0 : endpointKey(completionEndpoint);
1206
1698
  }
1207
1699
  matches(transit) {
1208
1700
  return requestRefsOf(transit).some((endpoint) => this._endpoints.has(endpointKey(endpoint)));
1209
1701
  }
1702
+ hasPort(portId) {
1703
+ return this._ports.has(portId);
1704
+ }
1210
1705
  accept(transit) {
1211
1706
  const endpoints = requestRefsOf(transit);
1212
1707
  if (!this.matches(transit)) return {
1213
1708
  matches: false,
1214
1709
  completed: false
1215
1710
  };
1216
- for (const endpoint of endpoints) this._endpoints.add(endpointKey(endpoint));
1711
+ for (const endpoint of endpoints) {
1712
+ this._endpoints.add(endpointKey(endpoint));
1713
+ this._ports.add(endpoint.portId);
1714
+ }
1217
1715
  const completed = transit.kind === "response" && (isTerminal(transit) || this._completionEndpoint !== void 0 && endpoints.some((endpoint) => endpointKey(endpoint) === this._completionEndpoint));
1218
1716
  if (completed) this._endpoints.clear();
1219
1717
  return {
@@ -1382,6 +1880,490 @@ function capPayload(value, maxBytes) {
1382
1880
  return preview;
1383
1881
  }
1384
1882
  //#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 };
1883
+ //#region src/inspection/endpointTrafficInspector.ts
1884
+ const REQUEST_CORRELATION_RETENTION_MS = 18e5;
1885
+ const MAX_REQUEST_CORRELATIONS = 4096;
1886
+ var EndpointTrafficInspector = class {
1887
+ _nodeId;
1888
+ _portId;
1889
+ _emit;
1890
+ _isObserved;
1891
+ _outboundRequests = /* @__PURE__ */ new Map();
1892
+ _inboundRequests = /* @__PURE__ */ new Map();
1893
+ observe = (direction, message) => {
1894
+ this._onMessage(direction, message);
1895
+ };
1896
+ constructor(_nodeId, _portId, _emit, _isObserved) {
1897
+ this._nodeId = _nodeId;
1898
+ this._portId = _portId;
1899
+ this._emit = _emit;
1900
+ this._isObserved = _isObserved;
1901
+ }
1902
+ dispose() {
1903
+ this._outboundRequests.clear();
1904
+ this._inboundRequests.clear();
1905
+ }
1906
+ _onMessage(direction, message) {
1907
+ const now = Date.now();
1908
+ this._pruneRequestCorrelations(now);
1909
+ const publiclyObserved = this._isObserved();
1910
+ if (!publiclyObserved) this.dispose();
1911
+ let transit;
1912
+ const endpoint = this._endpoint("id" in message ? message.id ?? void 0 : void 0);
1913
+ const base = {
1914
+ type: "transit",
1915
+ timeMs: now,
1916
+ nodeId: this._nodeId,
1917
+ ...direction === "inbound" ? { in: endpoint } : { out: endpoint },
1918
+ disposition: direction === "inbound" ? "consumed" : "forwarded"
1919
+ };
1920
+ if (isRequest(message)) {
1921
+ const correlation = {
1922
+ method: message.method,
1923
+ timeMs: now
1924
+ };
1925
+ if (publiclyObserved) {
1926
+ const map = this._requestMap(direction);
1927
+ const key = requestKey(message.id);
1928
+ map.delete(key);
1929
+ map.set(key, correlation);
1930
+ }
1931
+ transit = {
1932
+ ...base,
1933
+ kind: "request",
1934
+ method: message.method,
1935
+ params: message.params
1936
+ };
1937
+ } else if (isResponse(message)) {
1938
+ const responseMap = direction === "inbound" ? this._outboundRequests : this._inboundRequests;
1939
+ const key = requestKey(message.id);
1940
+ const correlation = responseMap.get(key);
1941
+ responseMap.delete(key);
1942
+ transit = {
1943
+ ...base,
1944
+ kind: "response",
1945
+ method: correlation?.method,
1946
+ result: "result" in message ? message.result : void 0,
1947
+ error: "error" in message ? message.error : void 0
1948
+ };
1949
+ } else if (isNotification(message) && message.method === STREAM_METHOD) {
1950
+ const params = message.params;
1951
+ const streamEndpoint = this._endpoint(params?.requestId);
1952
+ transit = {
1953
+ ...base,
1954
+ ...direction === "inbound" ? { in: streamEndpoint } : { out: streamEndpoint },
1955
+ kind: "stream",
1956
+ method: STREAM_METHOD,
1957
+ params: message.params
1958
+ };
1959
+ } else transit = {
1960
+ ...base,
1961
+ kind: "notification",
1962
+ method: message.method,
1963
+ params: message.params
1964
+ };
1965
+ function requestKey(requestId) {
1966
+ return JSON.stringify([typeof requestId, requestId]);
1967
+ }
1968
+ this._emit(transit);
1969
+ }
1970
+ _requestMap(direction) {
1971
+ return direction === "outbound" ? this._outboundRequests : this._inboundRequests;
1972
+ }
1973
+ _endpoint(requestId) {
1974
+ return {
1975
+ edgeId: this._portId,
1976
+ portId: this._portId,
1977
+ ...requestId !== void 0 ? { requestId } : {}
1978
+ };
1979
+ }
1980
+ _pruneRequestCorrelations(timeMs) {
1981
+ this._pruneRequestMap(this._outboundRequests, timeMs);
1982
+ this._pruneRequestMap(this._inboundRequests, timeMs);
1983
+ }
1984
+ _pruneRequestMap(map, timeMs) {
1985
+ while (map.size !== 0) {
1986
+ const first = map.entries().next().value;
1987
+ if (first === void 0 || map.size <= MAX_REQUEST_CORRELATIONS && timeMs - first[1].timeMs <= REQUEST_CORRELATION_RETENTION_MS) return;
1988
+ map.delete(first[0]);
1989
+ }
1990
+ }
1991
+ };
1992
+ //#endregion
1993
+ //#region src/inspection/connectionInspectionSource.ts
1994
+ var ConnectionInspectionSource = class {
1995
+ _connection;
1996
+ _nodeId;
1997
+ _options;
1998
+ _isObserved;
1999
+ constructor(_connection, _nodeId, _options, _isObserved) {
2000
+ this._connection = _connection;
2001
+ this._nodeId = _nodeId;
2002
+ this._options = _options;
2003
+ this._isObserved = _isObserved;
2004
+ }
2005
+ snapshotTopology() {
2006
+ const { portId, label, peer, transport } = this._options;
2007
+ const nodes = [{
2008
+ nodeId: this._nodeId,
2009
+ ports: [{
2010
+ portId,
2011
+ ...label === void 0 ? {} : { label }
2012
+ }]
2013
+ }];
2014
+ const links = [];
2015
+ if (peer !== void 0) {
2016
+ nodes.push({
2017
+ nodeId: peer.nodeId,
2018
+ kind: "endpoint",
2019
+ ...peer.label === void 0 ? {} : { label: peer.label },
2020
+ ports: [{ portId: peer.portId }]
2021
+ });
2022
+ links.push({
2023
+ from: {
2024
+ nodeId: this._nodeId,
2025
+ portId
2026
+ },
2027
+ to: {
2028
+ nodeId: peer.nodeId,
2029
+ portId: peer.portId
2030
+ },
2031
+ peerState: "identified",
2032
+ ...transport === void 0 ? {} : { transport }
2033
+ });
2034
+ }
2035
+ return {
2036
+ nodes,
2037
+ links,
2038
+ routes: []
2039
+ };
2040
+ }
2041
+ onTopologyChanged(_listener) {
2042
+ return { dispose: () => {} };
2043
+ }
2044
+ observeTraffic(listener) {
2045
+ const inspector = new EndpointTrafficInspector(this._nodeId, this._options.portId, listener, this._isObserved);
2046
+ const observation = this._connection.observeWireMessages(inspector.observe);
2047
+ return { dispose: () => {
2048
+ observation.dispose();
2049
+ inspector.dispose();
2050
+ } };
2051
+ }
2052
+ };
2053
+ //#endregion
2054
+ //#region src/inspection/inspectionHost.ts
2055
+ /** A shared inspection scope, independent of both routing and its RPC connections. */
2056
+ var InspectionHost = class {
2057
+ _options;
2058
+ _sources = /* @__PURE__ */ new Map();
2059
+ _trackedConnections = /* @__PURE__ */ new Set();
2060
+ _bindings = /* @__PURE__ */ new Set();
2061
+ _subscribers = /* @__PURE__ */ new Set();
2062
+ _observers = /* @__PURE__ */ new Set();
2063
+ _topologyListeners = /* @__PURE__ */ new Set();
2064
+ _trafficSources = /* @__PURE__ */ new Set();
2065
+ _trafficWatches = new TrafficWatchFlowTracker();
2066
+ _disposed = false;
2067
+ nodeId;
2068
+ constructor(_options) {
2069
+ this._options = _options;
2070
+ this.nodeId = _options.nodeId;
2071
+ }
2072
+ get observerCount() {
2073
+ return this._subscribers.size + this._observers.size;
2074
+ }
2075
+ addSource(source) {
2076
+ this._assertActive();
2077
+ if (this._sources.has(source)) throw new Error("Inspection source is already registered");
2078
+ const readPorts = () => new Set(source.snapshotTopology().nodes.flatMap((node) => node.ports.map((port) => port.portId)));
2079
+ let ports = readPorts();
2080
+ const topology = source.onTopologyChanged(() => {
2081
+ const nextPorts = readPorts();
2082
+ const removed = [...ports].filter((port) => !nextPorts.has(port));
2083
+ ports = nextPorts;
2084
+ this._forgetUnusedPorts(removed);
2085
+ this._invalidateTopology();
2086
+ });
2087
+ let traffic;
2088
+ try {
2089
+ traffic = source.observeTraffic((event) => this.publishTransit(event));
2090
+ } catch (error) {
2091
+ topology.dispose();
2092
+ throw error;
2093
+ }
2094
+ const registration = { dispose: () => {
2095
+ if (this._sources.get(source) !== registration) return;
2096
+ this._sources.delete(source);
2097
+ topology.dispose();
2098
+ traffic.dispose();
2099
+ this._forgetUnusedPorts([...ports]);
2100
+ this._invalidateTopology();
2101
+ } };
2102
+ this._sources.set(source, registration);
2103
+ this._invalidateTopology();
2104
+ return registration;
2105
+ }
2106
+ trackConnection(connection, options) {
2107
+ this._assertActive();
2108
+ if (this.getGraph("").nodes.some((node) => node.ports.some((port) => port.portId === options.portId))) throw new Error(`Inspection port "${options.portId}" is already registered`);
2109
+ const source = new ConnectionInspectionSource(connection, this.nodeId, options, () => this.observerCount !== 0);
2110
+ const registration = this.addSource(source);
2111
+ let close;
2112
+ try {
2113
+ close = connection.onDidClose(() => result.dispose());
2114
+ } catch (error) {
2115
+ registration.dispose();
2116
+ throw error;
2117
+ }
2118
+ const result = { dispose: () => {
2119
+ this._trackedConnections.delete(result);
2120
+ close.dispose();
2121
+ registration.dispose();
2122
+ } };
2123
+ this._trackedConnections.add(result);
2124
+ return result;
2125
+ }
2126
+ expose(connection, options) {
2127
+ this._assertActive();
2128
+ const portId = options.portId ?? createPortId();
2129
+ const binding = new InspectionBinding(portId);
2130
+ const registrations = [];
2131
+ this._bindings.add(binding);
2132
+ const registerOptions = { serviceId: options.serviceId };
2133
+ try {
2134
+ const controls = new EndpointTrafficInspector(this.nodeId, portId, (event) => this._trafficWatches.accept(event), () => false);
2135
+ registrations.push(connection.observeWireMessages(controls.observe));
2136
+ registrations.push({ dispose: () => controls.dispose() });
2137
+ registrations.push(connection.registerInspection(nodeInterface, { getNodeId: () => ({
2138
+ nodeId: this.nodeId,
2139
+ portId,
2140
+ ...(options.descriptors ?? this._options.descriptors) === void 0 ? {} : { descriptors: [...options.descriptors ?? this._options.descriptors ?? []] }
2141
+ }) }, registerOptions));
2142
+ registrations.push(connection.registerInspection(topologyInterface, {
2143
+ getGraph: () => this.getGraph(options.serviceId, options.includeRoute === false ? void 0 : portId),
2144
+ watchGraph: (_params, _ctx, stream) => this._watchGraph(binding, stream)
2145
+ }, registerOptions));
2146
+ registrations.push(connection.registerInspection(trafficInterface, {
2147
+ watch: (params, _ctx, stream) => this._watchTraffic(binding, params, stream),
2148
+ watchWithPayloads: (params, _ctx, stream) => this._watchTraffic(binding, params, stream)
2149
+ }, registerOptions));
2150
+ } catch (error) {
2151
+ for (const registration of registrations.reverse()) registration.dispose();
2152
+ this._bindings.delete(binding);
2153
+ throw error;
2154
+ }
2155
+ binding.onDispose = () => {
2156
+ this._bindings.delete(binding);
2157
+ for (const registration of registrations.reverse()) registration.dispose();
2158
+ this._forgetUnusedPorts([portId]);
2159
+ this._invalidateTopology();
2160
+ };
2161
+ registrations.push(connection.onDidClose(() => binding.dispose()));
2162
+ this._invalidateTopology();
2163
+ return binding;
2164
+ }
2165
+ getGraph(observerServiceId, routePortId) {
2166
+ this._assertActive();
2167
+ const nodes = /* @__PURE__ */ new Map();
2168
+ const mergeNode = (node) => {
2169
+ const previous = nodes.get(node.nodeId);
2170
+ const ports = new Map(previous?.ports.map((port) => [port.portId, port]));
2171
+ for (const port of node.ports) ports.set(port.portId, {
2172
+ ...ports.get(port.portId),
2173
+ ...port
2174
+ });
2175
+ nodes.set(node.nodeId, {
2176
+ ...previous,
2177
+ ...node,
2178
+ ports: [...ports.values()]
2179
+ });
2180
+ };
2181
+ mergeNode({
2182
+ nodeId: this.nodeId,
2183
+ kind: this._options.kind ?? "endpoint",
2184
+ ...this._options.label === void 0 ? {} : { label: this._options.label },
2185
+ ...this._options.descriptors === void 0 ? {} : { descriptors: [...this._options.descriptors] },
2186
+ ports: []
2187
+ });
2188
+ const links = [];
2189
+ const routes = [];
2190
+ for (const source of this._sources.keys()) {
2191
+ const fragment = source.snapshotTopology();
2192
+ for (const node of fragment.nodes) mergeNode(node);
2193
+ links.push(...fragment.links);
2194
+ routes.push(...fragment.routes);
2195
+ }
2196
+ for (const binding of this._bindings) mergeNode({
2197
+ nodeId: this.nodeId,
2198
+ ports: [{ portId: binding.portId }]
2199
+ });
2200
+ if (routePortId !== void 0) routes.push({
2201
+ serviceId: observerServiceId,
2202
+ nodeId: this.nodeId,
2203
+ portId: routePortId,
2204
+ match: "exact"
2205
+ });
2206
+ return {
2207
+ observerServiceId,
2208
+ entryNodeId: this.nodeId,
2209
+ nodes: [...nodes.values()],
2210
+ links,
2211
+ routes
2212
+ };
2213
+ }
2214
+ publishTransit(event) {
2215
+ if (this._disposed || this._trafficWatches.accept(event)) return;
2216
+ for (const observer of [...this._observers]) try {
2217
+ observer(event);
2218
+ } catch {}
2219
+ for (const subscriber of this._subscribers) subscriber.enqueue(event);
2220
+ }
2221
+ subscribe(options, send) {
2222
+ this._assertActive();
2223
+ if (options.trafficIgnoreKey !== void 0 && !this._trafficWatches.claim(options.trafficIgnoreKey)) throw new Error("Traffic watch request was not observed before subscription");
2224
+ const subscriber = new BoundedTrafficSubscription(options, send, () => {
2225
+ this._subscribers.delete(subscriber);
2226
+ this._stopUnusedTrafficSources();
2227
+ });
2228
+ this._subscribers.add(subscriber);
2229
+ try {
2230
+ this._startTrafficSources();
2231
+ } catch (error) {
2232
+ subscriber.dispose();
2233
+ throw error;
2234
+ }
2235
+ return subscriber;
2236
+ }
2237
+ observeTraffic(observer) {
2238
+ this._assertActive();
2239
+ this._observers.add(observer);
2240
+ const registration = { dispose: () => {
2241
+ this._observers.delete(observer);
2242
+ this._stopUnusedTrafficSources();
2243
+ } };
2244
+ try {
2245
+ this._startTrafficSources();
2246
+ } catch (error) {
2247
+ registration.dispose();
2248
+ throw error;
2249
+ }
2250
+ return registration;
2251
+ }
2252
+ addTrafficSource(source) {
2253
+ this._assertActive();
2254
+ const entry = { source };
2255
+ this._trafficSources.add(entry);
2256
+ try {
2257
+ this._startTrafficSources();
2258
+ } catch (error) {
2259
+ this._trafficSources.delete(entry);
2260
+ throw error;
2261
+ }
2262
+ return { dispose: () => {
2263
+ this._trafficSources.delete(entry);
2264
+ entry.observation?.dispose();
2265
+ entry.observation = void 0;
2266
+ } };
2267
+ }
2268
+ dispose() {
2269
+ if (this._disposed) return;
2270
+ this._disposed = true;
2271
+ for (const binding of [...this._bindings]) binding.dispose();
2272
+ for (const subscriber of [...this._subscribers]) subscriber.dispose();
2273
+ this._observers.clear();
2274
+ this._stopUnusedTrafficSources();
2275
+ this._trafficSources.clear();
2276
+ for (const connection of [...this._trackedConnections]) connection.dispose();
2277
+ for (const source of [...this._sources.values()]) source.dispose();
2278
+ this._topologyListeners.clear();
2279
+ this._trafficWatches.clear();
2280
+ }
2281
+ _startTrafficSources() {
2282
+ if (this.observerCount === 0) return;
2283
+ for (const entry of this._trafficSources) entry.observation ??= entry.source.observe((event) => this.publishTransit(event));
2284
+ }
2285
+ _stopUnusedTrafficSources() {
2286
+ if (this.observerCount !== 0) return;
2287
+ for (const entry of this._trafficSources) {
2288
+ entry.observation?.dispose();
2289
+ entry.observation = void 0;
2290
+ }
2291
+ }
2292
+ _invalidateTopology() {
2293
+ if (this._disposed) return;
2294
+ for (const listener of this._topologyListeners) listener();
2295
+ }
2296
+ _forgetUnusedPorts(portIds) {
2297
+ if (this._disposed) return;
2298
+ const remaining = new Set(this.getGraph("").nodes.flatMap((node) => node.ports.map((port) => port.portId)));
2299
+ for (const portId of portIds) if (!remaining.has(portId)) this._trafficWatches.forgetPort(portId);
2300
+ }
2301
+ _watchGraph(binding, stream) {
2302
+ return new Promise((resolve, reject) => {
2303
+ let pending = false;
2304
+ let active = true;
2305
+ const finish = () => {
2306
+ active = false;
2307
+ this._topologyListeners.delete(invalidate);
2308
+ stream.signal.removeEventListener("abort", finish);
2309
+ binding.abort.signal.removeEventListener("abort", finish);
2310
+ resolve({});
2311
+ };
2312
+ const invalidate = () => {
2313
+ if (pending || !active) return;
2314
+ pending = true;
2315
+ queueMicrotask(() => {
2316
+ pending = false;
2317
+ if (!active) return;
2318
+ stream.send({}).catch((error) => {
2319
+ reject(error);
2320
+ finish();
2321
+ });
2322
+ });
2323
+ };
2324
+ this._topologyListeners.add(invalidate);
2325
+ stream.signal.addEventListener("abort", finish, { once: true });
2326
+ binding.abort.signal.addEventListener("abort", finish, { once: true });
2327
+ if (stream.signal.aborted || binding.abort.signal.aborted) finish();
2328
+ });
2329
+ }
2330
+ async _watchTraffic(binding, options, stream) {
2331
+ const subscription = this.subscribe(options, (event) => stream.send(event));
2332
+ const dispose = () => subscription.dispose();
2333
+ stream.signal.addEventListener("abort", dispose, { once: true });
2334
+ binding.abort.signal.addEventListener("abort", dispose, { once: true });
2335
+ if (stream.signal.aborted || binding.abort.signal.aborted) dispose();
2336
+ try {
2337
+ return await subscription.closed;
2338
+ } finally {
2339
+ stream.signal.removeEventListener("abort", dispose);
2340
+ binding.abort.signal.removeEventListener("abort", dispose);
2341
+ subscription.dispose();
2342
+ }
2343
+ }
2344
+ _assertActive() {
2345
+ if (this._disposed) throw new Error("Inspection host is disposed");
2346
+ }
2347
+ };
2348
+ var InspectionBinding = class {
2349
+ portId;
2350
+ abort = new AbortController();
2351
+ onDispose = () => {};
2352
+ constructor(portId) {
2353
+ this.portId = portId;
2354
+ }
2355
+ dispose() {
2356
+ if (this.abort.signal.aborted) return;
2357
+ this.abort.abort();
2358
+ this.onDispose();
2359
+ }
2360
+ };
2361
+ function createPortId() {
2362
+ const bytes = /* @__PURE__ */ new Uint8Array(16);
2363
+ globalThis.crypto.getRandomValues(bytes);
2364
+ return `port:${bytesToBase64Url(bytes)}`;
2365
+ }
2366
+ //#endregion
2367
+ export { RequestType as $, PRINCIPAL_PREFIX as A, interfaceFromSchema as B, zTopologyTransportInfo as C, zTrafficTransitEvent as D, zTrafficTransitEndpoint as E, principalForPublicKey as F, componentSchemaName as G, createSchemaToZod as H, publicKeyForKeyId as I, computeInterfaceHash as J, componentSchemaRef as K, resolveSigningKey as L, bytesToBase64Url as M, keyIdForPrincipal as N, zTrafficWatchResult as O, keyIdForPublicKey as P, NotificationType as Q, InterfaceDefinition as R, zTopologyTransportEndpoint as S, zTrafficOverflowEvent as T, schemaToZod as U, validateInterfaceErrors as V, assertSchemaReferences as W, jcsCanonicalize as X, sha256 as Y, jcsCanonicalizeBytes as Z, zTopologyGraph as _, TrafficWatchFlowTracker as a, zodToSvcJsonSchema as at, zTopologyNode as b, StreamControlType as c, jsonPointerSegment as ct, nodeInterface as d, isNotification as dt, applicationError as et, topologyInterface as f, isRequest as ft, zRouteClaim as g, normalizeJsonSchema as gt, zParticipantDescriptorSource as h, isRpcFailure as ht, TrafficFlowFilter as i, rpcError as it, base64UrlToBytes as j, KEY_ID_PREFIX as k, StreamDir as l, brandApplicationError as lt, zParticipantDescriptor as m, RpcFailure as mt, ConnectionInspectionSource as n, notificationType as nt, STREAM_METHOD as o, jsonIssues as ot, trafficInterface as p, isResponse as pt, EXTENSION_PREFIX as q, BoundedTrafficSubscription as r, requestType as rt, StreamControlReason as s, parseDeclaredErrorBody as st, InspectionHost as t, isApplicationErrorValue as tt, streamInterface as u, ErrorCode as ut, zTopologyLink as v, zTrafficEvent as w, zTopologyPort as x, zTopologyLinkEndpoint as y, defineInterface as z };
1386
2368
 
1387
- //# sourceMappingURL=boundedTrafficSubscription-XZ1KwXZC.js.map
2369
+ //# sourceMappingURL=inspectionHost-BIkLKvr1.js.map