@hediet/linkrpc 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +383 -0
- package/dist/chunks/_empty-crypto-Bi0tGx5K.js +8 -0
- package/dist/chunks/boundedTrafficSubscription-1L592xc7.js +1126 -0
- package/dist/chunks/boundedTrafficSubscription-1L592xc7.js.map +1 -0
- package/dist/chunks/hub.interfaces-BzWfsVT2.js +526 -0
- package/dist/chunks/hub.interfaces-BzWfsVT2.js.map +1 -0
- package/dist/chunks/hubAccess-DwTZPiI8.d.ts +79 -0
- package/dist/chunks/hubAccess-DwTZPiI8.d.ts.map +1 -0
- package/dist/chunks/hubFacade-CQflVkVC.js +85 -0
- package/dist/chunks/hubFacade-CQflVkVC.js.map +1 -0
- package/dist/chunks/hubFacade-Dkgw2pTi.d.ts +101 -0
- package/dist/chunks/hubFacade-Dkgw2pTi.d.ts.map +1 -0
- package/dist/chunks/linkRpcConnection-CtlQmetO.d.ts +3623 -0
- package/dist/chunks/linkRpcConnection-CtlQmetO.d.ts.map +1 -0
- package/dist/chunks/rolldown-runtime-4LSo1kEK.js +17 -0
- package/dist/chunks/src-D3NUIwyo.js +7795 -0
- package/dist/chunks/src-D3NUIwyo.js.map +1 -0
- package/dist/hub/client/index.d.ts +50 -0
- package/dist/hub/client/index.d.ts.map +1 -0
- package/dist/hub/client/index.js +97 -0
- package/dist/hub/client/index.js.map +1 -0
- package/dist/hub/common/index.d.ts +1189 -0
- package/dist/hub/common/index.d.ts.map +1 -0
- package/dist/hub/common/index.js +267 -0
- package/dist/hub/common/index.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/inspection/index.d.ts +66 -0
- package/dist/inspection/index.d.ts.map +1 -0
- package/dist/inspection/index.js +6 -0
- package/dist/node.d.ts +548 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +1087 -0
- package/dist/node.js.map +1 -0
- package/dist/web.d.ts +44 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +58 -0
- package/dist/web.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,1126 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
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";
|
|
7
|
+
//#region src/schema/normalize.ts
|
|
8
|
+
/**
|
|
9
|
+
* Keys that describe structural shape and are part of the decidable subset.
|
|
10
|
+
* Everything else gets stripped during normalization.
|
|
11
|
+
*
|
|
12
|
+
* Note: `not` is intentionally NOT in this list. The special case `{"not":{}}`
|
|
13
|
+
* (i.e. matches-nothing) is mapped to the literal `false` bottom; any other
|
|
14
|
+
* `not` is dropped.
|
|
15
|
+
*/
|
|
16
|
+
const KEPT_KEYS = /* @__PURE__ */ new Set([
|
|
17
|
+
"type",
|
|
18
|
+
"format",
|
|
19
|
+
"properties",
|
|
20
|
+
"required",
|
|
21
|
+
"additionalProperties",
|
|
22
|
+
"items",
|
|
23
|
+
"prefixItems",
|
|
24
|
+
"const",
|
|
25
|
+
"enum",
|
|
26
|
+
"anyOf",
|
|
27
|
+
"oneOf",
|
|
28
|
+
"discriminator",
|
|
29
|
+
"$ref",
|
|
30
|
+
"title",
|
|
31
|
+
"description"
|
|
32
|
+
]);
|
|
33
|
+
/**
|
|
34
|
+
* Normalize raw JSON Schema output (e.g. from `z.toJSONSchema`) into the
|
|
35
|
+
* SvcJsonSchema subset:
|
|
36
|
+
*
|
|
37
|
+
* - drop annotation keys (`examples`, `default`, `$comment`, `readOnly`, ...)
|
|
38
|
+
* - drop out-of-subset refinements (`pattern`, `minimum`, `multipleOf`,
|
|
39
|
+
* `allOf`, `oneOf`, `if/then/else`, `patternProperties`, ...)
|
|
40
|
+
* - empty schema `{}` ⇒ `true` (top)
|
|
41
|
+
* - `{"not":{}}` ⇒ `false` (bottom); any other `not` is stripped
|
|
42
|
+
* - `type:"object"` without `additionalProperties` ⇒ closed (`false`),
|
|
43
|
+
* matching linkrpc's stricter contract
|
|
44
|
+
*
|
|
45
|
+
* The output is canonical: structurally equal inputs produce structurally
|
|
46
|
+
* equal outputs, which is what `computeInterfaceHash` relies on.
|
|
47
|
+
*/
|
|
48
|
+
function normalizeJsonSchema(raw) {
|
|
49
|
+
if (raw === true || raw === false) return raw;
|
|
50
|
+
if (raw === null || typeof raw !== "object") throw new Error(`normalizeJsonSchema: expected object, got ${typeof raw}`);
|
|
51
|
+
if (Array.isArray(raw)) throw new Error("normalizeJsonSchema: expected object, got array");
|
|
52
|
+
const r = raw;
|
|
53
|
+
if ("not" in r && isEmptyObject(r["not"])) return false;
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const [k, v] of Object.entries(r)) {
|
|
56
|
+
if (!KEPT_KEYS.has(k)) continue;
|
|
57
|
+
const normalized = normalizeChild(k, v);
|
|
58
|
+
if (normalized === void 0) continue;
|
|
59
|
+
out[k] = normalized;
|
|
60
|
+
}
|
|
61
|
+
if (out["type"] === "object" && !("additionalProperties" in out)) out["additionalProperties"] = false;
|
|
62
|
+
if (Array.isArray(out["oneOf"]) && !("discriminator" in out)) {
|
|
63
|
+
const detected = _detectDiscriminator(out["oneOf"]);
|
|
64
|
+
if (detected !== void 0) out["discriminator"] = detected;
|
|
65
|
+
}
|
|
66
|
+
if ("discriminator" in out && !Array.isArray(out["oneOf"])) delete out["discriminator"];
|
|
67
|
+
if (Object.keys(out).length === 0) return true;
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
function normalizeChild(key, v) {
|
|
71
|
+
switch (key) {
|
|
72
|
+
case "properties": {
|
|
73
|
+
if (!isPlainObject(v)) return {};
|
|
74
|
+
const out = {};
|
|
75
|
+
for (const [pk, pv] of Object.entries(v)) out[pk] = normalizeJsonSchema(pv);
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
case "items":
|
|
79
|
+
case "additionalProperties":
|
|
80
|
+
if (v === false) return false;
|
|
81
|
+
return normalizeJsonSchema(v);
|
|
82
|
+
case "prefixItems":
|
|
83
|
+
case "anyOf":
|
|
84
|
+
case "oneOf":
|
|
85
|
+
if (!Array.isArray(v)) return [];
|
|
86
|
+
return v.map((s) => normalizeJsonSchema(s));
|
|
87
|
+
case "discriminator": {
|
|
88
|
+
if (!isPlainObject(v)) return void 0;
|
|
89
|
+
const name = v["propertyName"];
|
|
90
|
+
if (typeof name !== "string" || name.length === 0) return void 0;
|
|
91
|
+
return { propertyName: name };
|
|
92
|
+
}
|
|
93
|
+
case "enum":
|
|
94
|
+
if (!Array.isArray(v)) return [];
|
|
95
|
+
return v.slice();
|
|
96
|
+
case "required":
|
|
97
|
+
if (!Array.isArray(v)) return [];
|
|
98
|
+
return v.slice().sort();
|
|
99
|
+
default: return v;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function isEmptyObject(v) {
|
|
103
|
+
return isPlainObject(v) && Object.keys(v).length === 0;
|
|
104
|
+
}
|
|
105
|
+
function isPlainObject(v) {
|
|
106
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Inspect a `oneOf` branch list and return a `discriminator` hint when
|
|
110
|
+
* every branch is an object schema and there is exactly one property name
|
|
111
|
+
* that appears in every branch with a distinct `const`-valued schema.
|
|
112
|
+
*
|
|
113
|
+
* This is the canonical JSON-Schema-only encoding of a tagged union, and
|
|
114
|
+
* the shape `z.toJSONSchema(z.discriminatedUnion(...))` emits.
|
|
115
|
+
*/
|
|
116
|
+
function _detectDiscriminator(branches) {
|
|
117
|
+
if (branches.length < 2) return void 0;
|
|
118
|
+
const branchPropsList = [];
|
|
119
|
+
for (const b of branches) {
|
|
120
|
+
if (b === true || b === false) return void 0;
|
|
121
|
+
if (b.type !== "object") return void 0;
|
|
122
|
+
const props = b.properties;
|
|
123
|
+
if (!props) return void 0;
|
|
124
|
+
branchPropsList.push(props);
|
|
125
|
+
}
|
|
126
|
+
let candidates = _constPropNames(branchPropsList[0]);
|
|
127
|
+
for (let i = 1; i < branchPropsList.length; i++) {
|
|
128
|
+
candidates = candidates.filter((n) => _constPropNames(branchPropsList[i]).includes(n));
|
|
129
|
+
if (candidates.length === 0) return void 0;
|
|
130
|
+
}
|
|
131
|
+
for (const name of candidates) {
|
|
132
|
+
const seen = /* @__PURE__ */ new Set();
|
|
133
|
+
let allDistinct = true;
|
|
134
|
+
for (const props of branchPropsList) {
|
|
135
|
+
const s = props[name];
|
|
136
|
+
const key = JSON.stringify(s.const);
|
|
137
|
+
if (seen.has(key)) {
|
|
138
|
+
allDistinct = false;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
seen.add(key);
|
|
142
|
+
}
|
|
143
|
+
if (allDistinct) return { propertyName: name };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function _constPropNames(props) {
|
|
147
|
+
const out = [];
|
|
148
|
+
for (const [k, v] of Object.entries(props)) {
|
|
149
|
+
if (v === true || v === false) continue;
|
|
150
|
+
if ("const" in v) out.push(k);
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/schema/memberTypes.ts
|
|
156
|
+
var RequestType = class RequestType {
|
|
157
|
+
paramsSchema;
|
|
158
|
+
resultSchema;
|
|
159
|
+
errorSchema;
|
|
160
|
+
docs;
|
|
161
|
+
clientStreamSchema;
|
|
162
|
+
serverStreamSchema;
|
|
163
|
+
kind = "request";
|
|
164
|
+
constructor(paramsSchema, resultSchema, errorSchema, docs = {}, clientStreamSchema, serverStreamSchema) {
|
|
165
|
+
this.paramsSchema = paramsSchema;
|
|
166
|
+
this.resultSchema = resultSchema;
|
|
167
|
+
this.errorSchema = errorSchema;
|
|
168
|
+
this.docs = docs;
|
|
169
|
+
this.clientStreamSchema = clientStreamSchema;
|
|
170
|
+
this.serverStreamSchema = serverStreamSchema;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Return a copy of this request type with stream payload schemas
|
|
174
|
+
* attached. Pass `undefined` for either direction to leave it
|
|
175
|
+
* closed.
|
|
176
|
+
*/
|
|
177
|
+
withStream(opts) {
|
|
178
|
+
return new RequestType(this.paramsSchema, this.resultSchema, this.errorSchema, this.docs, opts.client, opts.server);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
var NotificationType = class {
|
|
182
|
+
paramsSchema;
|
|
183
|
+
docs;
|
|
184
|
+
kind = "notification";
|
|
185
|
+
constructor(paramsSchema, docs = {}) {
|
|
186
|
+
this.paramsSchema = paramsSchema;
|
|
187
|
+
this.docs = docs;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* Define a request method.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* bar: requestType(z.object({ to: z.string() }), z.string(), {
|
|
195
|
+
* description: "MUST resolve before the next call from the same caller.",
|
|
196
|
+
* })
|
|
197
|
+
*/
|
|
198
|
+
function requestType(params, result, docsOrError, maybeDocs) {
|
|
199
|
+
const isErrorSchema = (v) => typeof v === "object" && v !== null && "_zod" in v;
|
|
200
|
+
const error = isErrorSchema(docsOrError) ? docsOrError : void 0;
|
|
201
|
+
const docs = (!isErrorSchema(docsOrError) ? docsOrError : maybeDocs) ?? {};
|
|
202
|
+
return new RequestType(params, result ?? void$1(), error ?? void$1(), docs);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Define a notification method.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* foo: notificationType(z.object({ message: z.string() }))
|
|
209
|
+
*/
|
|
210
|
+
function notificationType(params, docs = {}) {
|
|
211
|
+
return new NotificationType(params, docs);
|
|
212
|
+
}
|
|
213
|
+
/** Convert a zod schema to our restricted SvcJsonSchema subset. */
|
|
214
|
+
function zodToSvcJsonSchema(schema, options) {
|
|
215
|
+
const type = schema._zod?.def?.type;
|
|
216
|
+
if (type === "void" || type === "undefined") return true;
|
|
217
|
+
return normalizeZodJsonSchema(toJSONSchema(schema), options);
|
|
218
|
+
}
|
|
219
|
+
function normalizeZodJsonSchema(raw, options) {
|
|
220
|
+
if (!isRecord(raw)) return normalizeJsonSchema(raw);
|
|
221
|
+
const definitions = isRecord(raw.$defs) ? raw.$defs : {};
|
|
222
|
+
const localRefs = collectLocalRefs(raw);
|
|
223
|
+
if (Object.keys(definitions).length === 0 && localRefs.size === 0) return normalizeJsonSchema(raw);
|
|
224
|
+
if (options === void 0) throw new Error("zodToSvcJsonSchema: local references require a component destination");
|
|
225
|
+
const componentNamePrefix = `method=${encodeComponentNamePart(options.methodName)}&schema=${encodeComponentNamePart(options.schemaPosition)}`;
|
|
226
|
+
const definitionComponents = /* @__PURE__ */ new Map();
|
|
227
|
+
for (const definitionName of Object.keys(definitions)) definitionComponents.set(definitionName, `${componentNamePrefix}&def=${encodeComponentNamePart(definitionName)}`);
|
|
228
|
+
const rootComponent = localRefs.has("#") ? `${componentNamePrefix}&root` : void 0;
|
|
229
|
+
const rewrite = (value) => rewriteLocalRefs(value, definitionComponents, rootComponent);
|
|
230
|
+
for (const [definitionName, definition] of Object.entries(definitions)) {
|
|
231
|
+
const componentName = definitionComponents.get(definitionName);
|
|
232
|
+
if (componentName === void 0) throw new Error(`zodToSvcJsonSchema: missing component for "${definitionName}"`);
|
|
233
|
+
addComponent(options.components, componentName, normalizeJsonSchema(rewrite(definition)));
|
|
234
|
+
}
|
|
235
|
+
const root = rewrite(raw);
|
|
236
|
+
if (rootComponent !== void 0) {
|
|
237
|
+
addComponent(options.components, rootComponent, normalizeJsonSchema(root));
|
|
238
|
+
return { $ref: componentRef(rootComponent) };
|
|
239
|
+
}
|
|
240
|
+
return normalizeJsonSchema(root);
|
|
241
|
+
}
|
|
242
|
+
function collectLocalRefs(value, refs = /* @__PURE__ */ new Set()) {
|
|
243
|
+
if (Array.isArray(value)) {
|
|
244
|
+
for (const child of value) collectLocalRefs(child, refs);
|
|
245
|
+
return refs;
|
|
246
|
+
}
|
|
247
|
+
if (!isRecord(value)) return refs;
|
|
248
|
+
if (typeof value.$ref === "string" && value.$ref.startsWith("#")) refs.add(value.$ref);
|
|
249
|
+
for (const child of Object.values(value)) collectLocalRefs(child, refs);
|
|
250
|
+
return refs;
|
|
251
|
+
}
|
|
252
|
+
function rewriteLocalRefs(value, definitionComponents, rootComponent) {
|
|
253
|
+
if (Array.isArray(value)) return value.map((child) => rewriteLocalRefs(child, definitionComponents, rootComponent));
|
|
254
|
+
if (!isRecord(value)) return value;
|
|
255
|
+
const result = {};
|
|
256
|
+
for (const [key, child] of Object.entries(value)) {
|
|
257
|
+
if (key === "$defs") continue;
|
|
258
|
+
if (key === "$ref" && typeof child === "string") result[key] = rewriteLocalRef(child, definitionComponents, rootComponent);
|
|
259
|
+
else result[key] = rewriteLocalRefs(child, definitionComponents, rootComponent);
|
|
260
|
+
}
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
function rewriteLocalRef(ref, definitionComponents, rootComponent) {
|
|
264
|
+
if (ref === "#") {
|
|
265
|
+
if (rootComponent === void 0) throw new Error("zodToSvcJsonSchema: root reference has no component destination");
|
|
266
|
+
return componentRef(rootComponent);
|
|
267
|
+
}
|
|
268
|
+
if (ref.startsWith("#/$defs/")) {
|
|
269
|
+
const encodedName = ref.slice(8);
|
|
270
|
+
const definitionName = [...definitionComponents.keys()].find((name) => encodeJsonPointerSegment(name) === encodedName);
|
|
271
|
+
const componentName = definitionName === void 0 ? void 0 : definitionComponents.get(definitionName);
|
|
272
|
+
if (componentName === void 0) throw new Error(`zodToSvcJsonSchema: dangling local reference "${ref}"`);
|
|
273
|
+
return componentRef(componentName);
|
|
274
|
+
}
|
|
275
|
+
if (ref.startsWith("#") && !ref.startsWith("#/components/schemas/")) throw new Error(`zodToSvcJsonSchema: unsupported local reference "${ref}"`);
|
|
276
|
+
return ref;
|
|
277
|
+
}
|
|
278
|
+
function addComponent(components, name, schema) {
|
|
279
|
+
if (Object.hasOwn(components, name)) throw new Error(`zodToSvcJsonSchema: duplicate component name "${name}"`);
|
|
280
|
+
components[name] = schema;
|
|
281
|
+
}
|
|
282
|
+
function componentRef(name) {
|
|
283
|
+
return `#/components/schemas/${encodeJsonPointerSegment(name)}`;
|
|
284
|
+
}
|
|
285
|
+
function encodeJsonPointerSegment(value) {
|
|
286
|
+
return value.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
287
|
+
}
|
|
288
|
+
function encodeComponentNamePart(value) {
|
|
289
|
+
return encodeURIComponent(value).replace(/~/g, "%7E");
|
|
290
|
+
}
|
|
291
|
+
function isRecord(value) {
|
|
292
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
293
|
+
}
|
|
294
|
+
//#endregion
|
|
295
|
+
//#region src/protocol/jcs.ts
|
|
296
|
+
/**
|
|
297
|
+
* RFC 8785 JSON Canonicalization Scheme (JCS).
|
|
298
|
+
*
|
|
299
|
+
* Used as the byte-deterministic encoding under every linkrpc signature
|
|
300
|
+
* (RPC calls, capabilities, hub-signed previews). Both signer and verifier
|
|
301
|
+
* canonicalize the same value object and obtain byte-identical UTF-8 bytes.
|
|
302
|
+
*
|
|
303
|
+
* Implementation inlined from the `canonicalize` npm package (Apache-2.0,
|
|
304
|
+
* https://github.com/erdtman/canonicalize) so this module stays
|
|
305
|
+
* zero-dependency for browser bundlers that don't resolve transitive
|
|
306
|
+
* npm specifiers (e.g. the in-house app-bundler).
|
|
307
|
+
*/
|
|
308
|
+
/** RFC 8785 canonical JSON string for `value`. */
|
|
309
|
+
function jcsCanonicalize(value) {
|
|
310
|
+
const out = _canonicalize(value);
|
|
311
|
+
if (out === void 0) throw new Error("jcsCanonicalize: value is not JSON-representable");
|
|
312
|
+
return out;
|
|
313
|
+
}
|
|
314
|
+
const _encoder = new TextEncoder();
|
|
315
|
+
/** UTF-8 bytes of the RFC 8785 canonical JSON for `value`. The thing actually signed / hashed. */
|
|
316
|
+
function jcsCanonicalizeBytes(value) {
|
|
317
|
+
return _encoder.encode(jcsCanonicalize(value));
|
|
318
|
+
}
|
|
319
|
+
function _canonicalize(object, seen = /* @__PURE__ */ new Set()) {
|
|
320
|
+
if (typeof object === "number" && Number.isNaN(object)) throw new Error("NaN is not allowed");
|
|
321
|
+
if (typeof object === "number" && !Number.isFinite(object)) throw new Error("Infinity is not allowed");
|
|
322
|
+
if (object === null || typeof object !== "object") return JSON.stringify(object);
|
|
323
|
+
const obj = object;
|
|
324
|
+
if (typeof obj.toJSON === "function") {
|
|
325
|
+
if (seen.has(obj)) throw new Error("Circular reference detected");
|
|
326
|
+
seen.add(obj);
|
|
327
|
+
const result = _canonicalize(obj.toJSON(), seen);
|
|
328
|
+
seen.delete(obj);
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
if (seen.has(obj)) throw new Error("Circular reference detected");
|
|
332
|
+
seen.add(obj);
|
|
333
|
+
let result;
|
|
334
|
+
if (Array.isArray(obj)) result = `[${obj.map((cv) => {
|
|
335
|
+
return _canonicalize(cv === void 0 || typeof cv === "symbol" ? null : cv, seen);
|
|
336
|
+
}).join(",")}]`;
|
|
337
|
+
else {
|
|
338
|
+
const parts = [];
|
|
339
|
+
for (const key of Object.keys(obj).sort()) {
|
|
340
|
+
const v = obj[key];
|
|
341
|
+
if (v === void 0 || typeof v === "symbol") continue;
|
|
342
|
+
parts.push(`${JSON.stringify(key)}:${_canonicalize(v, seen)}`);
|
|
343
|
+
}
|
|
344
|
+
result = `{${parts.join(",")}}`;
|
|
345
|
+
}
|
|
346
|
+
seen.delete(obj);
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
//#endregion
|
|
350
|
+
//#region src/crypto/sha256.ts
|
|
351
|
+
/**
|
|
352
|
+
* Pure-JS SHA-256 (RFC 6234 / FIPS 180-4). Synchronous, dependency-free,
|
|
353
|
+
* isomorphic. Used internally by linkrpc for content-hashing interface
|
|
354
|
+
* schemas and deriving identity slot names; also surfaced through the
|
|
355
|
+
* `crypto` API as `crypto.sha256`. For signature work we go through the
|
|
356
|
+
* `crypto` module (`./identity/crypto`).
|
|
357
|
+
*
|
|
358
|
+
* Replaces the previous `@noble/hashes/sha2` import so the bundle has
|
|
359
|
+
* no runtime npm dependencies. Trades the well-vetted dep for ~80 lines
|
|
360
|
+
* of straight-line code that has no branching on input contents and is
|
|
361
|
+
* exercised by the existing test vectors plus `sha256.test.ts`.
|
|
362
|
+
*/
|
|
363
|
+
const K = new Uint32Array([
|
|
364
|
+
1116352408,
|
|
365
|
+
1899447441,
|
|
366
|
+
3049323471,
|
|
367
|
+
3921009573,
|
|
368
|
+
961987163,
|
|
369
|
+
1508970993,
|
|
370
|
+
2453635748,
|
|
371
|
+
2870763221,
|
|
372
|
+
3624381080,
|
|
373
|
+
310598401,
|
|
374
|
+
607225278,
|
|
375
|
+
1426881987,
|
|
376
|
+
1925078388,
|
|
377
|
+
2162078206,
|
|
378
|
+
2614888103,
|
|
379
|
+
3248222580,
|
|
380
|
+
3835390401,
|
|
381
|
+
4022224774,
|
|
382
|
+
264347078,
|
|
383
|
+
604807628,
|
|
384
|
+
770255983,
|
|
385
|
+
1249150122,
|
|
386
|
+
1555081692,
|
|
387
|
+
1996064986,
|
|
388
|
+
2554220882,
|
|
389
|
+
2821834349,
|
|
390
|
+
2952996808,
|
|
391
|
+
3210313671,
|
|
392
|
+
3336571891,
|
|
393
|
+
3584528711,
|
|
394
|
+
113926993,
|
|
395
|
+
338241895,
|
|
396
|
+
666307205,
|
|
397
|
+
773529912,
|
|
398
|
+
1294757372,
|
|
399
|
+
1396182291,
|
|
400
|
+
1695183700,
|
|
401
|
+
1986661051,
|
|
402
|
+
2177026350,
|
|
403
|
+
2456956037,
|
|
404
|
+
2730485921,
|
|
405
|
+
2820302411,
|
|
406
|
+
3259730800,
|
|
407
|
+
3345764771,
|
|
408
|
+
3516065817,
|
|
409
|
+
3600352804,
|
|
410
|
+
4094571909,
|
|
411
|
+
275423344,
|
|
412
|
+
430227734,
|
|
413
|
+
506948616,
|
|
414
|
+
659060556,
|
|
415
|
+
883997877,
|
|
416
|
+
958139571,
|
|
417
|
+
1322822218,
|
|
418
|
+
1537002063,
|
|
419
|
+
1747873779,
|
|
420
|
+
1955562222,
|
|
421
|
+
2024104815,
|
|
422
|
+
2227730452,
|
|
423
|
+
2361852424,
|
|
424
|
+
2428436474,
|
|
425
|
+
2756734187,
|
|
426
|
+
3204031479,
|
|
427
|
+
3329325298
|
|
428
|
+
]);
|
|
429
|
+
function _rotr(x, n) {
|
|
430
|
+
return x >>> n | x << 32 - n;
|
|
431
|
+
}
|
|
432
|
+
function sha256(data) {
|
|
433
|
+
const inputLen = data.length;
|
|
434
|
+
const bitLen = inputLen * 8;
|
|
435
|
+
const paddedLen = inputLen + 9 + 63 & -64;
|
|
436
|
+
const padded = new Uint8Array(paddedLen);
|
|
437
|
+
padded.set(data);
|
|
438
|
+
padded[inputLen] = 128;
|
|
439
|
+
const view = new DataView(padded.buffer);
|
|
440
|
+
view.setUint32(paddedLen - 8, Math.floor(bitLen / 4294967296), false);
|
|
441
|
+
view.setUint32(paddedLen - 4, bitLen >>> 0, false);
|
|
442
|
+
const H = new Uint32Array([
|
|
443
|
+
1779033703,
|
|
444
|
+
3144134277,
|
|
445
|
+
1013904242,
|
|
446
|
+
2773480762,
|
|
447
|
+
1359893119,
|
|
448
|
+
2600822924,
|
|
449
|
+
528734635,
|
|
450
|
+
1541459225
|
|
451
|
+
]);
|
|
452
|
+
const W = /* @__PURE__ */ new Uint32Array(64);
|
|
453
|
+
for (let block = 0; block < paddedLen; block += 64) {
|
|
454
|
+
for (let t = 0; t < 16; t++) W[t] = view.getUint32(block + t * 4, false);
|
|
455
|
+
for (let t = 16; t < 64; t++) {
|
|
456
|
+
const w15 = W[t - 15];
|
|
457
|
+
const w2 = W[t - 2];
|
|
458
|
+
const s0 = _rotr(w15, 7) ^ _rotr(w15, 18) ^ w15 >>> 3;
|
|
459
|
+
const s1 = _rotr(w2, 17) ^ _rotr(w2, 19) ^ w2 >>> 10;
|
|
460
|
+
W[t] = W[t - 16] + s0 + W[t - 7] + s1 >>> 0;
|
|
461
|
+
}
|
|
462
|
+
let a = H[0], b = H[1], c = H[2], d = H[3];
|
|
463
|
+
let e = H[4], f = H[5], g = H[6], h = H[7];
|
|
464
|
+
for (let t = 0; t < 64; t++) {
|
|
465
|
+
const S1 = _rotr(e, 6) ^ _rotr(e, 11) ^ _rotr(e, 25);
|
|
466
|
+
const ch = e & f ^ ~e & g;
|
|
467
|
+
const temp1 = h + S1 + ch + K[t] + W[t] >>> 0;
|
|
468
|
+
const temp2 = (_rotr(a, 2) ^ _rotr(a, 13) ^ _rotr(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
|
|
469
|
+
h = g;
|
|
470
|
+
g = f;
|
|
471
|
+
f = e;
|
|
472
|
+
e = d + temp1 >>> 0;
|
|
473
|
+
d = c;
|
|
474
|
+
c = b;
|
|
475
|
+
b = a;
|
|
476
|
+
a = temp1 + temp2 >>> 0;
|
|
477
|
+
}
|
|
478
|
+
H[0] = H[0] + a >>> 0;
|
|
479
|
+
H[1] = H[1] + b >>> 0;
|
|
480
|
+
H[2] = H[2] + c >>> 0;
|
|
481
|
+
H[3] = H[3] + d >>> 0;
|
|
482
|
+
H[4] = H[4] + e >>> 0;
|
|
483
|
+
H[5] = H[5] + f >>> 0;
|
|
484
|
+
H[6] = H[6] + g >>> 0;
|
|
485
|
+
H[7] = H[7] + h >>> 0;
|
|
486
|
+
}
|
|
487
|
+
const out = /* @__PURE__ */ new Uint8Array(32);
|
|
488
|
+
const outView = new DataView(out.buffer);
|
|
489
|
+
for (let i = 0; i < 8; i++) outView.setUint32(i * 4, H[i], false);
|
|
490
|
+
return out;
|
|
491
|
+
}
|
|
492
|
+
//#endregion
|
|
493
|
+
//#region src/schema/hash.ts
|
|
494
|
+
const methodSchemaFields = [
|
|
495
|
+
"params",
|
|
496
|
+
"result",
|
|
497
|
+
"clientStream",
|
|
498
|
+
"serverStream"
|
|
499
|
+
];
|
|
500
|
+
/**
|
|
501
|
+
* Compute the interface hash: SHA-256 of the canonicalized schema, truncated
|
|
502
|
+
* to 16 hex chars (64 bits of collision budget per id).
|
|
503
|
+
*
|
|
504
|
+
* Canonicalization strips the schema down to its **normative wire-contract
|
|
505
|
+
* projection** before hashing, so a single interface document can carry richer
|
|
506
|
+
* non-normative material (codegen hints, safety expressions, notes) without
|
|
507
|
+
* changing identity:
|
|
508
|
+
*
|
|
509
|
+
* 1. Normalize every JSON-Schema position (`params`, `result`, streams, and
|
|
510
|
+
* `components.schemas`) onto the decidable linkrpc subset.
|
|
511
|
+
* 2. Strip every `comment` field (non-normative — must not affect identity).
|
|
512
|
+
* `description` is NORMATIVE and kept in the hash.
|
|
513
|
+
* 3. Strip every **specification-extension** field — any object key whose name
|
|
514
|
+
* begins with `x-` — at every level of the document (see
|
|
515
|
+
* {@link EXTENSION_PREFIX}). This is the minimal, explicit "one document,
|
|
516
|
+
* two views" mechanism: the stored document keeps the rich `x-…`
|
|
517
|
+
* expressions; identity hashes only the simple contract. It mirrors
|
|
518
|
+
* OpenRPC/OpenAPI specification extensions (this schema format is a subset
|
|
519
|
+
* of OpenRPC 1.x). Editing an `x-…` value never changes the hash; changing
|
|
520
|
+
* a wire field (`params`, `result`, member names, `type`, `required`,
|
|
521
|
+
* `description`, `annotations`, …) does.
|
|
522
|
+
* 4. Omit the top-level `hash` field itself.
|
|
523
|
+
* 5. RFC 8785 JCS encode (recursive key sort, no whitespace) via {@link jcsCanonicalize}.
|
|
524
|
+
*
|
|
525
|
+
* Because no interface schema uses `x-…` keys today, this preserves every
|
|
526
|
+
* existing hash: stripping a set of keys that are always absent is a no-op.
|
|
527
|
+
*
|
|
528
|
+
* > Reservation. `x-…` is reserved for non-normative extensions at every level.
|
|
529
|
+
* > Member names cannot collide (they are alphanumeric per chapter 01 §2), and
|
|
530
|
+
* > object property names in the JSON Schema subset MUST NOT begin with `x-`.
|
|
531
|
+
*/
|
|
532
|
+
function computeInterfaceHash(schema) {
|
|
533
|
+
const json = jcsCanonicalize(stripNonNormative(normalizeSchemaPositions(schema), true));
|
|
534
|
+
const digest = sha256(new TextEncoder().encode(json));
|
|
535
|
+
return Array.from(digest.subarray(0, 8), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
536
|
+
}
|
|
537
|
+
function normalizeSchemaPositions(schema) {
|
|
538
|
+
const methods = Object.fromEntries(Object.entries(schema.methods).map(([name, method]) => {
|
|
539
|
+
const normalized = { ...method };
|
|
540
|
+
for (const field of methodSchemaFields) {
|
|
541
|
+
const value = normalized[field];
|
|
542
|
+
if (value !== void 0) normalized[field] = normalizeJsonSchema(value);
|
|
543
|
+
}
|
|
544
|
+
return [name, normalized];
|
|
545
|
+
}));
|
|
546
|
+
const componentSchemas = schema.components?.schemas;
|
|
547
|
+
const components = componentSchemas === void 0 ? schema.components : {
|
|
548
|
+
...schema.components,
|
|
549
|
+
schemas: Object.fromEntries(Object.entries(componentSchemas).map(([name, value]) => [name, normalizeJsonSchema(value)]))
|
|
550
|
+
};
|
|
551
|
+
return {
|
|
552
|
+
...schema,
|
|
553
|
+
methods,
|
|
554
|
+
...components === void 0 ? {} : { components }
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Prefix marking a non-normative **specification-extension** key. Any object
|
|
559
|
+
* key beginning with this prefix is stripped before hashing (at every level),
|
|
560
|
+
* exactly like `comment`. Reserved for rich, identity-neutral material such as
|
|
561
|
+
* codegen directives, richer validation/safety expressions, or tooling hints.
|
|
562
|
+
*/
|
|
563
|
+
const EXTENSION_PREFIX = "x-";
|
|
564
|
+
/** True for a key that must not contribute to the interface hash. */
|
|
565
|
+
function isNonNormativeKey(key, isRoot) {
|
|
566
|
+
return key === "comment" || key.startsWith("x-") || isRoot && key === "hash";
|
|
567
|
+
}
|
|
568
|
+
function stripNonNormative(value, isRoot = false) {
|
|
569
|
+
if (value === null || typeof value !== "object") return value;
|
|
570
|
+
if (Array.isArray(value)) return value.map((v) => stripNonNormative(v));
|
|
571
|
+
const out = {};
|
|
572
|
+
for (const k of Object.keys(value)) {
|
|
573
|
+
if (isNonNormativeKey(k, isRoot)) continue;
|
|
574
|
+
const v = value[k];
|
|
575
|
+
if (v === void 0) continue;
|
|
576
|
+
out[k] = stripNonNormative(v);
|
|
577
|
+
}
|
|
578
|
+
return out;
|
|
579
|
+
}
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region src/connection/interfaceDefinition.ts
|
|
582
|
+
const interfaceDefinitionState = /* @__PURE__ */ new WeakMap();
|
|
583
|
+
var InterfaceDefinition = class {
|
|
584
|
+
info;
|
|
585
|
+
members;
|
|
586
|
+
/** Typed wire references for capability and access-request construction. */
|
|
587
|
+
ref;
|
|
588
|
+
constructor(info, members, opts = {}) {
|
|
589
|
+
this.info = info;
|
|
590
|
+
this.members = members;
|
|
591
|
+
interfaceDefinitionState.set(this, {
|
|
592
|
+
frozenSchema: opts.frozenSchema,
|
|
593
|
+
schemaCache: void 0,
|
|
594
|
+
hashCache: void 0
|
|
595
|
+
});
|
|
596
|
+
this.ref = Object.fromEntries(Object.keys(members).map((member) => [member, {
|
|
597
|
+
interfaceId: this.info.id,
|
|
598
|
+
interfaceHash: this.schemaHash,
|
|
599
|
+
member
|
|
600
|
+
}]));
|
|
601
|
+
if (info.hash !== void 0 && this.schemaHash !== info.hash) throw new Error(`Interface hash mismatch for "${info.id}": expected "${info.hash}", got "${this.schemaHash}". The interface's wire contract changed — update the pinned hash to "${this.schemaHash}" after reviewing the change.`);
|
|
602
|
+
}
|
|
603
|
+
/** Content hash of this interface (see `computeInterfaceHash`). */
|
|
604
|
+
get schemaHash() {
|
|
605
|
+
const state = interfaceDefinitionState.get(this);
|
|
606
|
+
if (state.hashCache === void 0) state.hashCache = computeInterfaceHash(state.frozenSchema ?? buildSchema(this.info, this.members, ""));
|
|
607
|
+
return state.hashCache;
|
|
608
|
+
}
|
|
609
|
+
/** Lower the definition to a wire-format `LinkRpcInterfaceSchema`, hash filled in. */
|
|
610
|
+
toSchema() {
|
|
611
|
+
const state = interfaceDefinitionState.get(this);
|
|
612
|
+
if (state.schemaCache === void 0) state.schemaCache = state.frozenSchema !== void 0 ? {
|
|
613
|
+
...state.frozenSchema,
|
|
614
|
+
hash: this.schemaHash
|
|
615
|
+
} : buildSchema(this.info, this.members, this.schemaHash);
|
|
616
|
+
return state.schemaCache;
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
function buildSchema(info, members, hash) {
|
|
620
|
+
const methods = {};
|
|
621
|
+
const components = {};
|
|
622
|
+
for (const [name, member] of Object.entries(members)) methods[name] = toMethodSchema(name, member, components);
|
|
623
|
+
const schema = {
|
|
624
|
+
id: info.id,
|
|
625
|
+
hash,
|
|
626
|
+
methods
|
|
627
|
+
};
|
|
628
|
+
if (Object.keys(components).length > 0) schema.components = { schemas: components };
|
|
629
|
+
if (info.description !== void 0) schema.description = info.description;
|
|
630
|
+
if (info.comment !== void 0) schema.comment = info.comment;
|
|
631
|
+
return schema;
|
|
632
|
+
}
|
|
633
|
+
function toMethodSchema(name, member, components) {
|
|
634
|
+
const docs = member.docs;
|
|
635
|
+
if (member.kind === "request") {
|
|
636
|
+
const m = {
|
|
637
|
+
params: convertMemberSchema(name, "params", member.paramsSchema, components),
|
|
638
|
+
result: convertMemberSchema(name, "result", member.resultSchema, components)
|
|
639
|
+
};
|
|
640
|
+
if (member.clientStreamSchema !== void 0) m.clientStream = convertMemberSchema(name, "clientStream", member.clientStreamSchema, components);
|
|
641
|
+
if (member.serverStreamSchema !== void 0) m.serverStream = convertMemberSchema(name, "serverStream", member.serverStreamSchema, components);
|
|
642
|
+
if (docs.description !== void 0) m.description = docs.description;
|
|
643
|
+
if (docs.comment !== void 0) m.comment = docs.comment;
|
|
644
|
+
if (docs.annotations !== void 0) m.annotations = docs.annotations;
|
|
645
|
+
return m;
|
|
646
|
+
}
|
|
647
|
+
const m = { params: convertMemberSchema(name, "params", member.paramsSchema, components) };
|
|
648
|
+
if (docs.description !== void 0) m.description = docs.description;
|
|
649
|
+
if (docs.comment !== void 0) m.comment = docs.comment;
|
|
650
|
+
if (docs.annotations !== void 0) m.annotations = docs.annotations;
|
|
651
|
+
return m;
|
|
652
|
+
}
|
|
653
|
+
function convertMemberSchema(methodName, position, schema, components) {
|
|
654
|
+
return zodToSvcJsonSchema(schema, {
|
|
655
|
+
methodName,
|
|
656
|
+
schemaPosition: position,
|
|
657
|
+
components
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Convenient builder for an interface definition. Tracks TypeScript types
|
|
662
|
+
* through `requestType` / `notificationType` so client and server code can
|
|
663
|
+
* derive their shapes from the definition.
|
|
664
|
+
*
|
|
665
|
+
* @example
|
|
666
|
+
* const myInterface = defineInterface(
|
|
667
|
+
* { id: "de.hediet.notification-target" },
|
|
668
|
+
* {
|
|
669
|
+
* send: requestType(z.object({ to: z.string() }), z.string()),
|
|
670
|
+
* notify: notificationType(z.object({ message: z.string() })),
|
|
671
|
+
* },
|
|
672
|
+
* );
|
|
673
|
+
*/
|
|
674
|
+
function defineInterface(info, members) {
|
|
675
|
+
return new InterfaceDefinition(info, members);
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Build a runtime {@link InterfaceDefinition} from a previously-published
|
|
679
|
+
* {@link LinkRpcInterfaceSchema} — typically one received over the wire (e.g.
|
|
680
|
+
* from `hubrpc.schemas::get`) or generated at runtime by tooling that does
|
|
681
|
+
* not have the original zod sources at hand (codegen, faker / mock
|
|
682
|
+
* services, dynamic gateways).
|
|
683
|
+
*
|
|
684
|
+
* The original schema is kept verbatim: `toSchema()` returns it (with
|
|
685
|
+
* `hash` overlaid) and `schemaHash` is computed from it, so reflection
|
|
686
|
+
* consumers see the real wire contract. Member-level params / results /
|
|
687
|
+
* streams use `z.any()` because no zod source is available — call-site
|
|
688
|
+
* validation is therefore a no-op and the caller is responsible for
|
|
689
|
+
* shape-checking inputs and outputs.
|
|
690
|
+
*
|
|
691
|
+
* Methods with no `result` descriptor become notifications; methods with
|
|
692
|
+
* `clientStream` / `serverStream` get pass-through stream payload
|
|
693
|
+
* schemas attached.
|
|
694
|
+
*/
|
|
695
|
+
function interfaceFromSchema(schema) {
|
|
696
|
+
const members = {};
|
|
697
|
+
for (const [name, method] of Object.entries(schema.methods)) {
|
|
698
|
+
if (method.result === void 0) {
|
|
699
|
+
members[name] = new NotificationType(any());
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
const base = new RequestType(any(), any(), void$1());
|
|
703
|
+
members[name] = method.clientStream !== void 0 || method.serverStream !== void 0 ? base.withStream({
|
|
704
|
+
client: method.clientStream !== void 0 ? any() : void 0,
|
|
705
|
+
server: method.serverStream !== void 0 ? any() : void 0
|
|
706
|
+
}) : base;
|
|
707
|
+
}
|
|
708
|
+
const info = { id: schema.id };
|
|
709
|
+
if (schema.description !== void 0) info.description = schema.description;
|
|
710
|
+
if (schema.comment !== void 0) info.comment = schema.comment;
|
|
711
|
+
return new InterfaceDefinition(info, members, { frozenSchema: schema });
|
|
712
|
+
}
|
|
713
|
+
//#endregion
|
|
714
|
+
//#region src/inspection/inspection.interfaces.ts
|
|
715
|
+
const zTopologyPort = object({
|
|
716
|
+
portId: string(),
|
|
717
|
+
label: optional(string())
|
|
718
|
+
});
|
|
719
|
+
const zParticipantDescriptor = object({
|
|
720
|
+
type: optional(string()),
|
|
721
|
+
label: optional(string()),
|
|
722
|
+
processId: optional(number()),
|
|
723
|
+
processType: optional(string()),
|
|
724
|
+
nodeStatusServiceId: optional(string()),
|
|
725
|
+
metadata: optional(record(string(), union([
|
|
726
|
+
string(),
|
|
727
|
+
number(),
|
|
728
|
+
boolean()
|
|
729
|
+
])))
|
|
730
|
+
});
|
|
731
|
+
const zParticipantDescriptorSource = object({
|
|
732
|
+
source: enum$1(["self", "attacher"]),
|
|
733
|
+
descriptor: zParticipantDescriptor
|
|
734
|
+
});
|
|
735
|
+
const zTopologyNode = object({
|
|
736
|
+
nodeId: string(),
|
|
737
|
+
kind: optional(enum$1(["endpoint", "hub"])),
|
|
738
|
+
label: optional(string()),
|
|
739
|
+
descriptors: optional(array(zParticipantDescriptorSource)),
|
|
740
|
+
ports: array(zTopologyPort)
|
|
741
|
+
});
|
|
742
|
+
const zTopologyLinkEndpoint = object({
|
|
743
|
+
nodeId: string(),
|
|
744
|
+
portId: string()
|
|
745
|
+
});
|
|
746
|
+
const zTopologyTransportEndpoint = object({
|
|
747
|
+
address: optional(string()),
|
|
748
|
+
port: optional(number().check(int(), nonnegative()))
|
|
749
|
+
});
|
|
750
|
+
/**
|
|
751
|
+
* Transport details as observed at a topology link. `local` corresponds to the
|
|
752
|
+
* link's `from` endpoint and `remote` to its `to` endpoint.
|
|
753
|
+
*/
|
|
754
|
+
const zTopologyTransportInfo = object({
|
|
755
|
+
type: string(),
|
|
756
|
+
local: optional(zTopologyTransportEndpoint),
|
|
757
|
+
remote: optional(zTopologyTransportEndpoint),
|
|
758
|
+
path: optional(string()),
|
|
759
|
+
metadata: optional(record(string(), union([
|
|
760
|
+
string(),
|
|
761
|
+
number(),
|
|
762
|
+
boolean()
|
|
763
|
+
])))
|
|
764
|
+
});
|
|
765
|
+
const zTopologyLink = object({
|
|
766
|
+
from: zTopologyLinkEndpoint,
|
|
767
|
+
to: zTopologyLinkEndpoint,
|
|
768
|
+
label: optional(string()),
|
|
769
|
+
peerState: optional(enum$1([
|
|
770
|
+
"identified",
|
|
771
|
+
"pending",
|
|
772
|
+
"unsupported",
|
|
773
|
+
"error"
|
|
774
|
+
])),
|
|
775
|
+
transport: optional(zTopologyTransportInfo)
|
|
776
|
+
});
|
|
777
|
+
const zRouteClaim = object({
|
|
778
|
+
serviceId: string(),
|
|
779
|
+
nodeId: string(),
|
|
780
|
+
portId: string(),
|
|
781
|
+
match: enum$1(["exact", "prefix"])
|
|
782
|
+
});
|
|
783
|
+
const zTopologyGraph = object({
|
|
784
|
+
observerServiceId: string(),
|
|
785
|
+
entryNodeId: string(),
|
|
786
|
+
nodes: array(zTopologyNode),
|
|
787
|
+
links: array(zTopologyLink),
|
|
788
|
+
routes: array(zRouteClaim)
|
|
789
|
+
});
|
|
790
|
+
const topologyInterface = defineInterface({
|
|
791
|
+
id: "hubrpc.topology",
|
|
792
|
+
description: "Inspect the transport graph visible from a service. A watch emits invalidation ticks; consumers re-fetch getGraph after each tick."
|
|
793
|
+
}, {
|
|
794
|
+
getGraph: requestType(object({}), zTopologyGraph).withStream({ client: object({}) }),
|
|
795
|
+
watchGraph: requestType(object({}), object({})).withStream({ server: object({}) })
|
|
796
|
+
});
|
|
797
|
+
const zTrafficTransitEndpoint = object({
|
|
798
|
+
edgeId: string(),
|
|
799
|
+
portId: string(),
|
|
800
|
+
requestId: optional(union([number(), string()]))
|
|
801
|
+
});
|
|
802
|
+
const zTrafficTransitEvent = object({
|
|
803
|
+
type: literal("transit"),
|
|
804
|
+
timeMs: number(),
|
|
805
|
+
nodeId: string(),
|
|
806
|
+
in: optional(zTrafficTransitEndpoint),
|
|
807
|
+
out: optional(zTrafficTransitEndpoint),
|
|
808
|
+
disposition: enum$1([
|
|
809
|
+
"forwarded",
|
|
810
|
+
"consumed",
|
|
811
|
+
"dropped",
|
|
812
|
+
"unroutable"
|
|
813
|
+
]),
|
|
814
|
+
kind: enum$1([
|
|
815
|
+
"request",
|
|
816
|
+
"notification",
|
|
817
|
+
"response",
|
|
818
|
+
"stream"
|
|
819
|
+
]),
|
|
820
|
+
method: optional(string()),
|
|
821
|
+
params: optional(unknown()),
|
|
822
|
+
result: optional(unknown()),
|
|
823
|
+
error: optional(object({
|
|
824
|
+
code: number(),
|
|
825
|
+
message: string(),
|
|
826
|
+
data: optional(unknown())
|
|
827
|
+
}))
|
|
828
|
+
});
|
|
829
|
+
const zTrafficOverflowEvent = object({
|
|
830
|
+
type: literal("overflow"),
|
|
831
|
+
dropped: number().check(int(), nonnegative())
|
|
832
|
+
});
|
|
833
|
+
const zTrafficEvent = union([zTrafficTransitEvent, zTrafficOverflowEvent]);
|
|
834
|
+
const zTrafficWatchResult = object({
|
|
835
|
+
delivered: number().check(int(), nonnegative()),
|
|
836
|
+
dropped: number().check(int(), nonnegative())
|
|
837
|
+
});
|
|
838
|
+
const zTrafficWatchParams = object({
|
|
839
|
+
methodPrefix: optional(string()),
|
|
840
|
+
trafficIgnoreKey: optional(string()),
|
|
841
|
+
focusRequest: optional(object({
|
|
842
|
+
portId: string(),
|
|
843
|
+
requestId: union([number(), string()])
|
|
844
|
+
}))
|
|
845
|
+
});
|
|
846
|
+
const zTrafficWatchWithPayloadsParams = object({
|
|
847
|
+
methodPrefix: optional(string()),
|
|
848
|
+
trafficIgnoreKey: optional(string()),
|
|
849
|
+
focusRequest: optional(object({
|
|
850
|
+
portId: string(),
|
|
851
|
+
requestId: union([number(), string()])
|
|
852
|
+
})),
|
|
853
|
+
maxPayloadBytes: number().check(int(), nonnegative())
|
|
854
|
+
});
|
|
855
|
+
/**
|
|
856
|
+
* Observe raw message transits at the node hosting the addressed service.
|
|
857
|
+
* Consumers may correlate adjacent transits by shared `(portId, requestId)`.
|
|
858
|
+
*/
|
|
859
|
+
const trafficInterface = defineInterface({
|
|
860
|
+
id: "hubrpc.traffic",
|
|
861
|
+
description: "Stream raw message transits for the entire node hosting the addressed service. Payload-free and explicitly payload-bearing variants keep disclosure opt-in."
|
|
862
|
+
}, {
|
|
863
|
+
watch: requestType(zTrafficWatchParams, zTrafficWatchResult).withStream({ server: zTrafficEvent }),
|
|
864
|
+
watchWithPayloads: requestType(zTrafficWatchWithPayloadsParams, zTrafficWatchResult).withStream({ server: zTrafficEvent })
|
|
865
|
+
});
|
|
866
|
+
//#endregion
|
|
867
|
+
//#region src/inspection/node.interfaces.ts
|
|
868
|
+
const zNodeInfo = object({
|
|
869
|
+
/**
|
|
870
|
+
* Random identity of this connection's node. It is a topology-correlation
|
|
871
|
+
* label only and must not be used for authentication or authorization.
|
|
872
|
+
*/
|
|
873
|
+
nodeId: string(),
|
|
874
|
+
/** Random identity of this connection's port on its node. */
|
|
875
|
+
portId: string(),
|
|
876
|
+
/** Optional diagnostic descriptors; never used for authorization. */
|
|
877
|
+
descriptors: optional(array(zParticipantDescriptorSource))
|
|
878
|
+
});
|
|
879
|
+
/**
|
|
880
|
+
* Minimal root service for aligning the independently observed topologies at
|
|
881
|
+
* both ends of a connection.
|
|
882
|
+
*/
|
|
883
|
+
const nodeInterface = defineInterface({
|
|
884
|
+
id: "hubrpc.node",
|
|
885
|
+
description: "Topology bootstrap: identify the node and port at this end of the direct connection."
|
|
886
|
+
}, { getNodeId: requestType(object({}), zNodeInfo) });
|
|
887
|
+
//#endregion
|
|
888
|
+
//#region src/inspection/trafficFlowFilter.ts
|
|
889
|
+
var TrafficFlowFilter = class {
|
|
890
|
+
_focused;
|
|
891
|
+
constructor(options) {
|
|
892
|
+
this._focused = options.focus !== void 0 ? new CorrelatedFlow([options.focus]) : void 0;
|
|
893
|
+
}
|
|
894
|
+
shouldInclude(transit) {
|
|
895
|
+
return this._focused?.accept(transit).matches ?? true;
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
var TrafficWatchFlowTracker = class {
|
|
899
|
+
_active = /* @__PURE__ */ new Set();
|
|
900
|
+
_unclaimed = /* @__PURE__ */ new Map();
|
|
901
|
+
accept(transit) {
|
|
902
|
+
if (transit.kind === "request" && isTrafficWatchMethod(transit.method)) {
|
|
903
|
+
const trafficIgnoreKey = readTrafficIgnoreKey(transit.params);
|
|
904
|
+
let flow = this._findFlow(transit);
|
|
905
|
+
if (flow === void 0) {
|
|
906
|
+
flow = new CorrelatedFlow(requestRefsOf(transit), completionRefOf(transit));
|
|
907
|
+
if (!isTerminal(transit)) this._active.add(flow);
|
|
908
|
+
} else flow.accept(transit);
|
|
909
|
+
if (isTerminal(transit)) {
|
|
910
|
+
this._removeFlow(flow);
|
|
911
|
+
return true;
|
|
912
|
+
}
|
|
913
|
+
if (trafficIgnoreKey !== void 0) this._unclaimed.set(trafficIgnoreKey, flow);
|
|
914
|
+
return true;
|
|
915
|
+
}
|
|
916
|
+
for (const flow of this._active) {
|
|
917
|
+
const match = flow.accept(transit);
|
|
918
|
+
if (!match.matches) continue;
|
|
919
|
+
if (match.completed) this._removeFlow(flow);
|
|
920
|
+
return true;
|
|
921
|
+
}
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
_findFlow(transit) {
|
|
925
|
+
for (const flow of this._active) if (flow.matches(transit)) return flow;
|
|
926
|
+
}
|
|
927
|
+
_removeFlow(flow) {
|
|
928
|
+
this._active.delete(flow);
|
|
929
|
+
for (const [key, pending] of this._unclaimed) if (pending === flow) this._unclaimed.delete(key);
|
|
930
|
+
}
|
|
931
|
+
claim(trafficIgnoreKey) {
|
|
932
|
+
return this._unclaimed.delete(trafficIgnoreKey);
|
|
933
|
+
}
|
|
934
|
+
clear() {
|
|
935
|
+
this._active.clear();
|
|
936
|
+
this._unclaimed.clear();
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
var CorrelatedFlow = class {
|
|
940
|
+
_endpoints = /* @__PURE__ */ new Set();
|
|
941
|
+
_completionEndpoint;
|
|
942
|
+
constructor(endpoints, completionEndpoint = endpoints[0]) {
|
|
943
|
+
for (const endpoint of endpoints) this._endpoints.add(endpointKey(endpoint));
|
|
944
|
+
this._completionEndpoint = completionEndpoint === void 0 ? void 0 : endpointKey(completionEndpoint);
|
|
945
|
+
}
|
|
946
|
+
matches(transit) {
|
|
947
|
+
return requestRefsOf(transit).some((endpoint) => this._endpoints.has(endpointKey(endpoint)));
|
|
948
|
+
}
|
|
949
|
+
accept(transit) {
|
|
950
|
+
const endpoints = requestRefsOf(transit);
|
|
951
|
+
if (!this.matches(transit)) return {
|
|
952
|
+
matches: false,
|
|
953
|
+
completed: false
|
|
954
|
+
};
|
|
955
|
+
for (const endpoint of endpoints) this._endpoints.add(endpointKey(endpoint));
|
|
956
|
+
const completed = transit.kind === "response" && (isTerminal(transit) || this._completionEndpoint !== void 0 && endpoints.some((endpoint) => endpointKey(endpoint) === this._completionEndpoint));
|
|
957
|
+
if (completed) this._endpoints.clear();
|
|
958
|
+
return {
|
|
959
|
+
matches: true,
|
|
960
|
+
completed
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
function isTerminal(transit) {
|
|
965
|
+
return transit.disposition === "dropped" || transit.disposition === "unroutable";
|
|
966
|
+
}
|
|
967
|
+
function requestRefsOf(transit) {
|
|
968
|
+
const result = [];
|
|
969
|
+
if (transit.in?.requestId !== void 0) result.push(requestRef(transit.in));
|
|
970
|
+
if (transit.out?.requestId !== void 0) result.push(requestRef(transit.out));
|
|
971
|
+
return result;
|
|
972
|
+
}
|
|
973
|
+
function requestRef(endpoint) {
|
|
974
|
+
return {
|
|
975
|
+
portId: endpoint.portId,
|
|
976
|
+
requestId: endpoint.requestId
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
function completionRefOf(transit) {
|
|
980
|
+
if (transit.in?.requestId !== void 0) return requestRef(transit.in);
|
|
981
|
+
if (transit.out?.requestId !== void 0) return requestRef(transit.out);
|
|
982
|
+
}
|
|
983
|
+
function endpointKey(endpoint) {
|
|
984
|
+
return JSON.stringify([
|
|
985
|
+
endpoint.portId,
|
|
986
|
+
typeof endpoint.requestId,
|
|
987
|
+
endpoint.requestId
|
|
988
|
+
]);
|
|
989
|
+
}
|
|
990
|
+
function isTrafficWatchMethod(method) {
|
|
991
|
+
return method?.endsWith("::hubrpc.traffic::watch") === true || method?.endsWith("::hubrpc.traffic::watchWithPayloads") === true || method === "hubrpc.traffic::watch" || method === "hubrpc.traffic::watchWithPayloads";
|
|
992
|
+
}
|
|
993
|
+
function readTrafficIgnoreKey(params) {
|
|
994
|
+
if (params === null || Array.isArray(params) || typeof params !== "object" || !("trafficIgnoreKey" in params)) return;
|
|
995
|
+
const value = params.trafficIgnoreKey;
|
|
996
|
+
return typeof value === "string" ? value : void 0;
|
|
997
|
+
}
|
|
998
|
+
//#endregion
|
|
999
|
+
//#region src/inspection/boundedTrafficSubscription.ts
|
|
1000
|
+
const DEFAULT_QUEUE_LIMIT = 256;
|
|
1001
|
+
/**
|
|
1002
|
+
* Applies per-subscriber filtering and payload policy while isolating traffic
|
|
1003
|
+
* producers from slow or failed stream consumers.
|
|
1004
|
+
*/
|
|
1005
|
+
var BoundedTrafficSubscription = class {
|
|
1006
|
+
_options;
|
|
1007
|
+
_send;
|
|
1008
|
+
_onDispose;
|
|
1009
|
+
_queueLimit;
|
|
1010
|
+
_queue = [];
|
|
1011
|
+
_active = true;
|
|
1012
|
+
_pumping = false;
|
|
1013
|
+
_overflowPending = 0;
|
|
1014
|
+
_delivered = 0;
|
|
1015
|
+
_dropped = 0;
|
|
1016
|
+
_resolveClosed;
|
|
1017
|
+
_filter;
|
|
1018
|
+
closed;
|
|
1019
|
+
constructor(_options, _send, _onDispose, _queueLimit = DEFAULT_QUEUE_LIMIT) {
|
|
1020
|
+
this._options = _options;
|
|
1021
|
+
this._send = _send;
|
|
1022
|
+
this._onDispose = _onDispose;
|
|
1023
|
+
this._queueLimit = _queueLimit;
|
|
1024
|
+
let resolve;
|
|
1025
|
+
this.closed = new Promise((r) => resolve = r);
|
|
1026
|
+
this._resolveClosed = resolve;
|
|
1027
|
+
this._filter = new TrafficFlowFilter({ focus: _options.focusRequest });
|
|
1028
|
+
}
|
|
1029
|
+
enqueue(transit) {
|
|
1030
|
+
if (!this._active) return;
|
|
1031
|
+
if (!this._filter.shouldInclude(transit)) return;
|
|
1032
|
+
if (this._options.methodPrefix !== void 0 && !transit.method?.startsWith(this._options.methodPrefix)) return;
|
|
1033
|
+
const event = this._options.maxPayloadBytes === void 0 ? withoutPayloads(transit) : withCappedPayloads(transit, this._options.maxPayloadBytes);
|
|
1034
|
+
if (this._queue.length >= this._queueLimit) {
|
|
1035
|
+
this._queue.shift();
|
|
1036
|
+
this._dropped++;
|
|
1037
|
+
this._overflowPending++;
|
|
1038
|
+
}
|
|
1039
|
+
this._queue.push(event);
|
|
1040
|
+
if (!this._pumping) {
|
|
1041
|
+
this._pumping = true;
|
|
1042
|
+
queueMicrotask(() => void this._pump());
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
dispose() {
|
|
1046
|
+
if (!this._active) return;
|
|
1047
|
+
this._active = false;
|
|
1048
|
+
this._dropped += this._queue.length;
|
|
1049
|
+
this._queue.length = 0;
|
|
1050
|
+
this._overflowPending = 0;
|
|
1051
|
+
this._onDispose();
|
|
1052
|
+
if (!this._pumping) this._complete();
|
|
1053
|
+
}
|
|
1054
|
+
async _pump() {
|
|
1055
|
+
try {
|
|
1056
|
+
while (this._active) {
|
|
1057
|
+
if (this._overflowPending > 0) {
|
|
1058
|
+
const dropped = this._overflowPending;
|
|
1059
|
+
this._overflowPending = 0;
|
|
1060
|
+
await this._send({
|
|
1061
|
+
type: "overflow",
|
|
1062
|
+
dropped
|
|
1063
|
+
});
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
const event = this._queue.shift();
|
|
1067
|
+
if (event === void 0) break;
|
|
1068
|
+
await this._send(event);
|
|
1069
|
+
this._delivered++;
|
|
1070
|
+
}
|
|
1071
|
+
} catch {
|
|
1072
|
+
this.dispose();
|
|
1073
|
+
} finally {
|
|
1074
|
+
this._pumping = false;
|
|
1075
|
+
if (!this._active) this._complete();
|
|
1076
|
+
else if (this._queue.length > 0 || this._overflowPending > 0) {
|
|
1077
|
+
this._pumping = true;
|
|
1078
|
+
queueMicrotask(() => void this._pump());
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
_complete() {
|
|
1083
|
+
this._resolveClosed({
|
|
1084
|
+
delivered: this._delivered,
|
|
1085
|
+
dropped: this._dropped
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
function withoutPayloads(transit) {
|
|
1090
|
+
const { params: _params, result: _result, error, ...rest } = transit;
|
|
1091
|
+
if (error === void 0) return rest;
|
|
1092
|
+
return {
|
|
1093
|
+
...rest,
|
|
1094
|
+
error: {
|
|
1095
|
+
code: error.code,
|
|
1096
|
+
message: error.message
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
function withCappedPayloads(transit, maxPayloadBytes) {
|
|
1101
|
+
return {
|
|
1102
|
+
...transit,
|
|
1103
|
+
params: capPayload(transit.params, maxPayloadBytes),
|
|
1104
|
+
result: capPayload(transit.result, maxPayloadBytes),
|
|
1105
|
+
error: transit.error === void 0 ? void 0 : {
|
|
1106
|
+
...transit.error,
|
|
1107
|
+
data: capPayload(transit.error.data, maxPayloadBytes)
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
function capPayload(value, maxBytes) {
|
|
1112
|
+
if (value === void 0) return void 0;
|
|
1113
|
+
if (maxBytes === Number.POSITIVE_INFINITY) return value;
|
|
1114
|
+
const json = JSON.stringify(value);
|
|
1115
|
+
if (json === void 0) return void 0;
|
|
1116
|
+
const encoded = new TextEncoder().encode(json);
|
|
1117
|
+
if (encoded.byteLength <= maxBytes) return value;
|
|
1118
|
+
const encoder = new TextEncoder();
|
|
1119
|
+
let preview = new TextDecoder().decode(encoded.slice(0, maxBytes));
|
|
1120
|
+
while (encoder.encode(preview).byteLength > maxBytes) preview = preview.slice(0, -1);
|
|
1121
|
+
return preview;
|
|
1122
|
+
}
|
|
1123
|
+
//#endregion
|
|
1124
|
+
export { NotificationType as A, defineInterface as C, sha256 as D, computeInterfaceHash as E, normalizeJsonSchema as F, notificationType as M, requestType as N, jcsCanonicalize as O, zodToSvcJsonSchema as P, InterfaceDefinition as S, EXTENSION_PREFIX as T, 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, RequestType as j, jcsCanonicalizeBytes 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 };
|
|
1125
|
+
|
|
1126
|
+
//# sourceMappingURL=boundedTrafficSubscription-1L592xc7.js.map
|