@mermanjs/node 0.8.0-alpha.5

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 (47) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE-APACHE +201 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +11 -0
  5. package/THIRD_PARTY_LICENSES/beautiful-mermaid/LICENSE +21 -0
  6. package/THIRD_PARTY_LICENSES/cose-base-v1/LICENSE +21 -0
  7. package/THIRD_PARTY_LICENSES/cose-base-v2/LICENSE +21 -0
  8. package/THIRD_PARTY_LICENSES/cytoscape/LICENSE +19 -0
  9. package/THIRD_PARTY_LICENSES/cytoscape-cose-bilkent/LICENSE +21 -0
  10. package/THIRD_PARTY_LICENSES/cytoscape-fcose/LICENSE +19 -0
  11. package/THIRD_PARTY_LICENSES/d3-shape/LICENSE +13 -0
  12. package/THIRD_PARTY_LICENSES/dagre/LICENSE +19 -0
  13. package/THIRD_PARTY_LICENSES/dompurify/LICENSE +202 -0
  14. package/THIRD_PARTY_LICENSES/eclipse-elk/LICENSE.md +264 -0
  15. package/THIRD_PARTY_LICENSES/elkjs/LICENSE.md +264 -0
  16. package/THIRD_PARTY_LICENSES/fmin/LICENSE +27 -0
  17. package/THIRD_PARTY_LICENSES/graphlib/LICENSE +19 -0
  18. package/THIRD_PARTY_LICENSES/katex-fonts/FONT_NOTICE.txt +12 -0
  19. package/THIRD_PARTY_LICENSES/katex-fonts/OFL.txt +85 -0
  20. package/THIRD_PARTY_LICENSES/layout-base-v1/LICENSE +21 -0
  21. package/THIRD_PARTY_LICENSES/layout-base-v2/LICENSE +21 -0
  22. package/THIRD_PARTY_LICENSES/mermaid/LICENSE +21 -0
  23. package/THIRD_PARTY_LICENSES/mermaid-ascii/LICENSE +21 -0
  24. package/THIRD_PARTY_LICENSES/mermaid-rs-renderer/LICENSE +21 -0
  25. package/THIRD_PARTY_LICENSES/ratex/LICENSE +27 -0
  26. package/THIRD_PARTY_LICENSES/ratex/THIRD_PARTY_NOTICES.txt +19 -0
  27. package/THIRD_PARTY_LICENSES/rough-rs/LICENSE +21 -0
  28. package/THIRD_PARTY_LICENSES/roughjs/LICENSE +21 -0
  29. package/THIRD_PARTY_LICENSES/rust-cargo-dependencies.json +5562 -0
  30. package/THIRD_PARTY_LICENSES/sanitize-url/LICENSE +21 -0
  31. package/THIRD_PARTY_LICENSES/venn-js/LICENSE +22 -0
  32. package/THIRD_PARTY_LICENSES/wasm-minimal-protocol/LICENSE +24 -0
  33. package/THIRD_PARTY_LICENSES/zenuml-core/LICENSE +21 -0
  34. package/THIRD_PARTY_NOTICES.md +524 -0
  35. package/dist/bounded-executor.mjs +132 -0
  36. package/dist/candidates/native.mjs +51 -0
  37. package/dist/candidates/wrap-engine.mjs +28 -0
  38. package/dist/engine.mjs +1255 -0
  39. package/dist/errors.mjs +1199 -0
  40. package/dist/generated/binding-contract.mjs +873 -0
  41. package/dist/generated/capability-surface.mjs +66 -0
  42. package/dist/generated/node-wire-contract.json +122 -0
  43. package/dist/index.d.ts +239 -0
  44. package/dist/index.mjs +22 -0
  45. package/dist/native-loader.mjs +112 -0
  46. package/dist/transport-contract.mjs +87 -0
  47. package/package.json +50 -0
@@ -0,0 +1,1199 @@
1
+ import {
2
+ BINDING_OPERATION_METADATA_CONTRACT,
3
+ BINDING_PAYLOAD_SCHEMAS,
4
+ RUNTIME_CATALOG_MAX_SAFE_INTEGER,
5
+ } from "./generated/binding-contract.mjs";
6
+ import { CAPABILITY_DESCRIPTOR_DIGEST } from "./generated/capability-surface.mjs";
7
+ import {
8
+ NODE_TRANSPORT_FIELD_LIMITS,
9
+ NODE_TRANSPORT_LIMITS,
10
+ NODE_WIRE_CONTRACT,
11
+ } from "./transport-contract.mjs";
12
+
13
+ const BINDING_RESULT_SCHEMA = BINDING_PAYLOAD_SCHEMAS.find(
14
+ (schema) => schema.id === "binding-result",
15
+ );
16
+ if (BINDING_RESULT_SCHEMA === undefined) {
17
+ throw new Error("Generated binding contract is missing the binding-result schema.");
18
+ }
19
+ const BINDING_RESULT_PAYLOAD_VERSION = BINDING_RESULT_SCHEMA.version;
20
+ const BINDING_STATUS_NAME_BY_CODE = new Map([
21
+ [1, "MERMAN_INVALID_ARGUMENT"],
22
+ [2, "MERMAN_UTF8_ERROR"],
23
+ [3, "MERMAN_OPTIONS_JSON_ERROR"],
24
+ [4, "MERMAN_NO_DIAGRAM"],
25
+ [5, "MERMAN_PARSE_ERROR"],
26
+ [6, "MERMAN_RENDER_ERROR"],
27
+ [7, "MERMAN_UNSUPPORTED_OPERATION"],
28
+ [8, "MERMAN_PANIC"],
29
+ [9, "MERMAN_INTERNAL_ERROR"],
30
+ [10, "MERMAN_RESOURCE_LIMIT_EXCEEDED"],
31
+ [11, "MERMAN_BUSY"],
32
+ ]);
33
+ const JSON_NUMBER_TOKEN = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
34
+ const JSON_NUMBER_PARTS = /^-?(0|[1-9]\d*)(?:\.(\d+))?(?:[eE]([+-]?)(\d+))?$/;
35
+ const MAX_SAFE_INTEGER_DECIMAL = String(RUNTIME_CATALOG_MAX_SAFE_INTEGER);
36
+ const U64_MAX_DECIMAL = "18446744073709551615";
37
+ const CANONICAL_WIDE_UNSIGNED_DECIMAL = /^[1-9]\d*$/;
38
+ const RUNTIME_CATALOG_EXACT_SAFE_INTEGER_PATHS = Object.freeze([
39
+ ["schema_version"],
40
+ ["transport_api_version"],
41
+ ["options_schema_versions", "*"],
42
+ ["payload_schemas", "*", "version"],
43
+ ["capabilities", "text_measurement", "protocol_version"],
44
+ ["constructor_service_contracts", "*", "resource_limits", "*", "value"],
45
+ [
46
+ "output_contracts",
47
+ "*",
48
+ "embedded_images",
49
+ "limits",
50
+ "max_bytes_per_image",
51
+ ],
52
+ ["output_contracts", "*", "embedded_images", "limits", "max_total_bytes"],
53
+ [
54
+ "output_contracts",
55
+ "*",
56
+ "embedded_images",
57
+ "limits",
58
+ "max_pixels_per_image",
59
+ ],
60
+ ["output_contracts", "*", "embedded_images", "limits", "max_total_pixels"],
61
+ ["registry", "diagram_family_count"],
62
+ ["resources", "limits", "*", "minimum_value"],
63
+ ["resources", "profiles", "*", "limits", "*"],
64
+ ]);
65
+ const RESPONSE_EXACT_SAFE_INTEGER_PATHS = Object.freeze([
66
+ ["version"],
67
+ ["error", "code"],
68
+ ["error", "details", "resource", "actual"],
69
+ ["error", "details", "resource", "max"],
70
+ ["error", "details", "icon_registry", "pack_index"],
71
+ ]);
72
+ const OPERATION_METADATA_EXACT_SAFE_INTEGER_PATHS = Object.freeze([
73
+ ...BINDING_OPERATION_METADATA_CONTRACT.fields
74
+ .filter((field) => field.json_type === "unsigned-integer")
75
+ .map((field) => [field.name]),
76
+ ...BINDING_OPERATION_METADATA_CONTRACT.output_plans.flatMap((plan) =>
77
+ plan.fields
78
+ .filter((field) => field.json_type === "unsigned-integer")
79
+ .map((field) => ["output_plan", field.name])
80
+ ),
81
+ ]);
82
+
83
+ export class MermanError extends Error {
84
+ constructor(message, { code = "MERMAN_NODE_ERROR", cause } = {}) {
85
+ super(message, { cause });
86
+ this.name = new.target.name;
87
+ this.code = code;
88
+ }
89
+ }
90
+
91
+ export class MermanOperationError extends MermanError {
92
+ constructor(payload) {
93
+ super(payload.message, { code: payload.code_name ?? "MERMAN_OPERATION_ERROR" });
94
+ this.status = payload.code ?? null;
95
+ this.codeName = payload.code_name ?? null;
96
+ this.kind = payload.kind ?? "generic";
97
+ this.capabilityId = payload.capability_id ?? null;
98
+ this.resourceDetails = payload.details?.resource ?? null;
99
+ }
100
+ }
101
+
102
+ export class MermanQueueSaturatedError extends MermanError {
103
+ constructor(maxQueue) {
104
+ super(`Merman queue is full (maxQueue=${maxQueue}).`, {
105
+ code: "MERMAN_QUEUE_SATURATED",
106
+ });
107
+ this.maxQueue = maxQueue;
108
+ }
109
+ }
110
+
111
+ export class MermanDisposedError extends MermanError {
112
+ constructor() {
113
+ super("Merman engine has been disposed.", { code: "MERMAN_ENGINE_DISPOSED" });
114
+ }
115
+ }
116
+
117
+ export class MermanLifecycleError extends MermanError {
118
+ constructor(message) {
119
+ super(message, { code: "MERMAN_LIFECYCLE_ERROR" });
120
+ }
121
+ }
122
+
123
+ export class MermanUnsupportedTargetError extends MermanError {
124
+ constructor({ platform, arch, libc = null, reason = null }) {
125
+ const target = [platform, arch, libc].filter(Boolean).join("-");
126
+ super(reason ?? `Unsupported Merman Node target: ${target}.`, {
127
+ code: "MERMAN_UNSUPPORTED_TARGET",
128
+ });
129
+ this.platform = platform;
130
+ this.arch = arch;
131
+ this.libc = libc;
132
+ }
133
+ }
134
+
135
+ export class MermanMissingPlatformPackageError extends MermanError {
136
+ constructor({ packageName, target, cause }) {
137
+ super(
138
+ `The required Merman platform package ${packageName} is not installed for ${target}.`,
139
+ { code: "MERMAN_MISSING_PLATFORM_PACKAGE", cause },
140
+ );
141
+ this.packageName = packageName;
142
+ this.target = target;
143
+ }
144
+ }
145
+
146
+ export class MermanInvalidTransportError extends MermanError {
147
+ constructor(message, cause) {
148
+ super(message, { code: "MERMAN_INVALID_TRANSPORT", cause });
149
+ }
150
+ }
151
+
152
+ export { NODE_TRANSPORT_FIELD_LIMITS, NODE_TRANSPORT_LIMITS, NODE_WIRE_CONTRACT };
153
+
154
+ export function parseTransportJsonText(value, label, limits) {
155
+ if (typeof value !== "string") {
156
+ throw new MermanInvalidTransportError(`Merman transport ${label} must be JSON text.`);
157
+ }
158
+ if (!limits || typeof limits !== "object") {
159
+ throw new TypeError(`Missing Node wire limits for ${label}.`);
160
+ }
161
+ const byteLength = strictUtf8ByteLength(value, label);
162
+ if (byteLength > limits.max_utf8_bytes) {
163
+ throw new MermanInvalidTransportError(
164
+ `Merman transport ${label} exceeds the ${limits.max_utf8_bytes}-byte wire limit.`,
165
+ );
166
+ }
167
+ scanBoundedJsonText(value, label, limits);
168
+ try {
169
+ return JSON.parse(value);
170
+ } catch (cause) {
171
+ throw new MermanInvalidTransportError(
172
+ `Merman transport returned invalid ${label} JSON.`,
173
+ cause,
174
+ );
175
+ }
176
+ }
177
+
178
+ export function encodeTransportJson(value, label, limits) {
179
+ if (!limits || typeof limits !== "object") {
180
+ throw new TypeError(`Missing Node wire limits for ${label}.`);
181
+ }
182
+ let boundedValue;
183
+ try {
184
+ boundedValue = cloneJsonWireValueWithinLimit(value, label, limits);
185
+ } catch (cause) {
186
+ if (cause instanceof MermanInvalidTransportError) throw cause;
187
+ throw new MermanInvalidTransportError(
188
+ `Merman could not encode ${label} as JSON text.`,
189
+ cause,
190
+ );
191
+ }
192
+ let text;
193
+ try {
194
+ text = JSON.stringify(boundedValue);
195
+ } catch (cause) {
196
+ throw new MermanInvalidTransportError(
197
+ `Merman could not encode ${label} as JSON text.`,
198
+ cause,
199
+ );
200
+ }
201
+ parseTransportJsonText(text, label, limits);
202
+ return text;
203
+ }
204
+
205
+ function cloneJsonWireValueWithinLimit(value, label, limits) {
206
+ const budget = { label, max: limits.max_utf8_bytes, used: 0 };
207
+ const active = new WeakSet();
208
+ return cloneJsonWireValue(value, label, limits, budget, active, 1);
209
+ }
210
+
211
+ function cloneJsonWireValue(value, label, limits, budget, active, depth) {
212
+ if (value === null) {
213
+ addJsonWireBytes(budget, 4);
214
+ return null;
215
+ }
216
+ if (typeof value === "boolean") {
217
+ addJsonWireBytes(budget, value ? 4 : 5);
218
+ return value;
219
+ }
220
+ if (typeof value === "string") {
221
+ addJsonStringWireBytes(value, label, budget);
222
+ return value;
223
+ }
224
+ if (typeof value === "number") {
225
+ if (!Number.isFinite(value)) {
226
+ throw new TypeError(`${label} must be a finite JSON number.`);
227
+ }
228
+ addJsonWireBytes(budget, JSON.stringify(value).length);
229
+ return value;
230
+ }
231
+ if (!value || typeof value !== "object") {
232
+ throw new TypeError(`${label} is not a JSON wire value.`);
233
+ }
234
+ if (depth > limits.max_depth) {
235
+ throw new RangeError(`${label} exceeds the structural depth limit ${limits.max_depth}.`);
236
+ }
237
+ if (active.has(value)) {
238
+ throw new TypeError(`${label} must not contain cyclic references.`);
239
+ }
240
+
241
+ const isArray = Array.isArray(value);
242
+ const prototype = Object.getPrototypeOf(value);
243
+ if (!isArray && prototype !== Object.prototype && prototype !== null) {
244
+ throw new TypeError(`${label} must contain only arrays and plain objects.`);
245
+ }
246
+
247
+ active.add(value);
248
+ try {
249
+ if (isArray) {
250
+ return cloneJsonWireArray(value, label, limits, budget, active, depth);
251
+ }
252
+ return cloneJsonWireObject(value, label, limits, budget, active, depth);
253
+ } finally {
254
+ active.delete(value);
255
+ }
256
+ }
257
+
258
+ function cloneJsonWireArray(value, label, limits, budget, active, depth) {
259
+ const keys = Reflect.ownKeys(value);
260
+ if (keys.some((key) => typeof key === "symbol")) {
261
+ throw new TypeError(`${label} must not contain symbol keys.`);
262
+ }
263
+ if (keys.length !== value.length + 1) {
264
+ throw new TypeError(`${label} arrays must not contain custom properties.`);
265
+ }
266
+
267
+ const clone = [];
268
+ Object.defineProperty(clone, "toJSON", { value: undefined });
269
+ addJsonWireBytes(budget, 1);
270
+ for (let index = 0; index < value.length; index += 1) {
271
+ if (!Object.hasOwn(value, index)) {
272
+ throw new TypeError(`${label} arrays must not contain holes.`);
273
+ }
274
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
275
+ if (!descriptor?.enumerable || !("value" in descriptor)) {
276
+ throw new TypeError(`${label}[${index}] must be an enumerable data property.`);
277
+ }
278
+ if (index > 0) addJsonWireBytes(budget, 1);
279
+ clone[index] = cloneJsonWireValue(
280
+ descriptor.value,
281
+ `${label}[${index}]`,
282
+ limits,
283
+ budget,
284
+ active,
285
+ depth + 1,
286
+ );
287
+ }
288
+ addJsonWireBytes(budget, 1);
289
+ return clone;
290
+ }
291
+
292
+ function cloneJsonWireObject(value, label, limits, budget, active, depth) {
293
+ const clone = Object.create(null);
294
+ addJsonWireBytes(budget, 1);
295
+ let member = 0;
296
+ for (const key of Reflect.ownKeys(value)) {
297
+ if (typeof key === "symbol") {
298
+ throw new TypeError(`${label} must not contain symbol keys.`);
299
+ }
300
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
301
+ if (!descriptor?.enumerable || !("value" in descriptor)) {
302
+ throw new TypeError(`${label}.${key} must be an enumerable data property.`);
303
+ }
304
+ if (member > 0) addJsonWireBytes(budget, 1);
305
+ addJsonStringWireBytes(key, `${label} object key`, budget);
306
+ addJsonWireBytes(budget, 1);
307
+ const child = cloneJsonWireValue(
308
+ descriptor.value,
309
+ `${label}.${key}`,
310
+ limits,
311
+ budget,
312
+ active,
313
+ depth + 1,
314
+ );
315
+ Object.defineProperty(clone, key, {
316
+ configurable: true,
317
+ enumerable: true,
318
+ value: child,
319
+ writable: true,
320
+ });
321
+ member += 1;
322
+ }
323
+ addJsonWireBytes(budget, 1);
324
+ return clone;
325
+ }
326
+
327
+ function addJsonStringWireBytes(value, label, budget) {
328
+ addJsonWireBytes(budget, 1);
329
+ for (let index = 0; index < value.length; index += 1) {
330
+ const codeUnit = value.charCodeAt(index);
331
+ if (codeUnit === 0x22 || codeUnit === 0x5c) {
332
+ addJsonWireBytes(budget, 2);
333
+ } else if (
334
+ codeUnit === 0x08 ||
335
+ codeUnit === 0x09 ||
336
+ codeUnit === 0x0a ||
337
+ codeUnit === 0x0c ||
338
+ codeUnit === 0x0d
339
+ ) {
340
+ addJsonWireBytes(budget, 2);
341
+ } else if (codeUnit <= 0x1f) {
342
+ addJsonWireBytes(budget, 6);
343
+ } else if (codeUnit <= 0x7f) {
344
+ addJsonWireBytes(budget, 1);
345
+ } else if (codeUnit <= 0x7ff) {
346
+ addJsonWireBytes(budget, 2);
347
+ } else if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) {
348
+ const low = value.charCodeAt(index + 1);
349
+ if (!(low >= 0xdc00 && low <= 0xdfff)) {
350
+ throw new MermanInvalidTransportError(
351
+ `Merman transport ${label} contains an isolated UTF-16 surrogate.`,
352
+ );
353
+ }
354
+ addJsonWireBytes(budget, 4);
355
+ index += 1;
356
+ } else if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) {
357
+ throw new MermanInvalidTransportError(
358
+ `Merman transport ${label} contains an isolated UTF-16 surrogate.`,
359
+ );
360
+ } else {
361
+ addJsonWireBytes(budget, 3);
362
+ }
363
+ }
364
+ addJsonWireBytes(budget, 1);
365
+ }
366
+
367
+ function addJsonWireBytes(budget, bytes) {
368
+ if (bytes > budget.max - budget.used) {
369
+ throw new MermanInvalidTransportError(
370
+ `Merman transport ${budget.label} exceeds the ${budget.max}-byte wire limit.`,
371
+ );
372
+ }
373
+ budget.used += bytes;
374
+ }
375
+
376
+ export function assertUtf8Field(value, label, maxUtf8Bytes) {
377
+ const bytes = strictUtf8ByteLength(value, label);
378
+ if (bytes > maxUtf8Bytes) {
379
+ throw new MermanInvalidTransportError(
380
+ `Merman transport ${label} exceeds the ${maxUtf8Bytes}-byte field limit.`,
381
+ );
382
+ }
383
+ return bytes;
384
+ }
385
+
386
+ export function parseRuntimeCatalogJsonText(value, label = "runtime catalog") {
387
+ const parsed = parseTransportJsonText(
388
+ value,
389
+ label,
390
+ NODE_TRANSPORT_LIMITS.runtime_catalog,
391
+ );
392
+ assertExactSafeIntegerJsonTokens(
393
+ value,
394
+ label,
395
+ RUNTIME_CATALOG_EXACT_SAFE_INTEGER_PATHS,
396
+ );
397
+ return parsed;
398
+ }
399
+
400
+ export function validateTransportIdentityJson(
401
+ value,
402
+ { expectedTransport, expectedPackageVersion },
403
+ ) {
404
+ const identity = parseTransportJsonText(
405
+ value,
406
+ "identity",
407
+ NODE_TRANSPORT_LIMITS.identity,
408
+ );
409
+ if (
410
+ !isPlainJsonObject(identity) ||
411
+ identity.schema_version !== 1 ||
412
+ identity.package_id !== NODE_WIRE_CONTRACT.package_id ||
413
+ identity.artifact_id !== NODE_WIRE_CONTRACT.artifact_id ||
414
+ identity.package_version !== expectedPackageVersion ||
415
+ identity.transport_kind !== expectedTransport ||
416
+ identity.transport_api_version !== NODE_WIRE_CONTRACT.transport_api_version ||
417
+ identity.binding_result_payload_version !==
418
+ NODE_WIRE_CONTRACT.binding_result_payload_version ||
419
+ identity.capability_descriptor_digest !== CAPABILITY_DESCRIPTOR_DIGEST ||
420
+ !sameJsonValue(identity.wire_contract, NODE_WIRE_CONTRACT)
421
+ ) {
422
+ throw new MermanInvalidTransportError(
423
+ "The Merman candidate module identity is incompatible with this loader package.",
424
+ );
425
+ }
426
+ assertUtf8Field(
427
+ identity.package_version,
428
+ "identity package_version",
429
+ NODE_TRANSPORT_FIELD_LIMITS.package_version_utf8_bytes,
430
+ );
431
+ assertUtf8Field(
432
+ identity.capability_descriptor_digest,
433
+ "identity capability_descriptor_digest",
434
+ NODE_TRANSPORT_FIELD_LIMITS.contract_digest_utf8_bytes,
435
+ );
436
+ return identity;
437
+ }
438
+
439
+ export function validateTransportIdentityExport(
440
+ readIdentity,
441
+ { expectedTransport, expectedPackageVersion, label },
442
+ ) {
443
+ if (typeof readIdentity !== "function") {
444
+ throw new MermanInvalidTransportError(
445
+ `${label} does not export transportIdentityJson().`,
446
+ );
447
+ }
448
+ let value;
449
+ try {
450
+ value = readIdentity();
451
+ } catch (cause) {
452
+ throw new MermanInvalidTransportError(
453
+ `${label} transport identity preflight failed.`,
454
+ cause,
455
+ );
456
+ }
457
+ return validateTransportIdentityJson(value, {
458
+ expectedPackageVersion,
459
+ expectedTransport,
460
+ });
461
+ }
462
+
463
+ export function strictUtf8ByteLength(value, label = "text") {
464
+ let bytes = 0;
465
+ for (let index = 0; index < value.length; index += 1) {
466
+ const codeUnit = value.charCodeAt(index);
467
+ if (codeUnit <= 0x7f) {
468
+ bytes += 1;
469
+ } else if (codeUnit <= 0x7ff) {
470
+ bytes += 2;
471
+ } else if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) {
472
+ const low = value.charCodeAt(index + 1);
473
+ if (!(low >= 0xdc00 && low <= 0xdfff)) {
474
+ throw new MermanInvalidTransportError(
475
+ `Merman transport ${label} contains an isolated UTF-16 surrogate.`,
476
+ );
477
+ }
478
+ bytes += 4;
479
+ index += 1;
480
+ } else if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) {
481
+ throw new MermanInvalidTransportError(
482
+ `Merman transport ${label} contains an isolated UTF-16 surrogate.`,
483
+ );
484
+ } else {
485
+ bytes += 3;
486
+ }
487
+ }
488
+ return bytes;
489
+ }
490
+
491
+ function scanBoundedJsonText(source, label, limits) {
492
+ let depth = 0;
493
+ let members = 0;
494
+ let tokens = 0;
495
+ const stack = [];
496
+ for (let index = 0; index < source.length;) {
497
+ const codeUnit = source.charCodeAt(index);
498
+ if (isJsonWhitespaceCodeUnit(codeUnit)) {
499
+ index += 1;
500
+ continue;
501
+ }
502
+ const character = source[index];
503
+ if (character === '"') {
504
+ const scanned = scanJsonStringToken(source, index, label);
505
+ if (scanned.utf8Bytes > limits.max_string_utf8_bytes) {
506
+ throw new MermanInvalidTransportError(
507
+ `Merman transport ${label} contains a string exceeding the ${limits.max_string_utf8_bytes}-byte field limit.`,
508
+ );
509
+ }
510
+ tokens += 1;
511
+ index = scanned.end;
512
+ } else if (character === "{" || character === "[") {
513
+ stack.push(character);
514
+ depth += 1;
515
+ tokens += 1;
516
+ if (depth > limits.max_depth) {
517
+ throw new MermanInvalidTransportError(
518
+ `Merman transport ${label} exceeds the structural depth limit ${limits.max_depth}.`,
519
+ );
520
+ }
521
+ index += 1;
522
+ } else if (character === "}" || character === "]") {
523
+ const expected = character === "}" ? "{" : "[";
524
+ if (stack.at(-1) !== expected) break;
525
+ stack.pop();
526
+ depth -= 1;
527
+ index += 1;
528
+ } else if (character === ":") {
529
+ members += 1;
530
+ index += 1;
531
+ } else if (character === "-" || (character >= "0" && character <= "9")) {
532
+ JSON_NUMBER_TOKEN.lastIndex = index;
533
+ const match = JSON_NUMBER_TOKEN.exec(source);
534
+ if (match === null) break;
535
+ if (!Number.isFinite(Number(match[0]))) {
536
+ throw new MermanInvalidTransportError(
537
+ `Merman transport ${label} contains a number outside the finite JSON range.`,
538
+ );
539
+ }
540
+ tokens += 1;
541
+ index = JSON_NUMBER_TOKEN.lastIndex;
542
+ } else if (source.startsWith("true", index)) {
543
+ tokens += 1;
544
+ index += 4;
545
+ } else if (source.startsWith("false", index)) {
546
+ tokens += 1;
547
+ index += 5;
548
+ } else if (source.startsWith("null", index)) {
549
+ tokens += 1;
550
+ index += 4;
551
+ } else {
552
+ index += 1;
553
+ }
554
+ if (members > limits.max_members) {
555
+ throw new MermanInvalidTransportError(
556
+ `Merman transport ${label} exceeds the member-work limit ${limits.max_members}.`,
557
+ );
558
+ }
559
+ if (tokens > limits.max_tokens) {
560
+ throw new MermanInvalidTransportError(
561
+ `Merman transport ${label} exceeds the token-work limit ${limits.max_tokens}.`,
562
+ );
563
+ }
564
+ }
565
+ }
566
+
567
+ function scanJsonStringToken(source, start, label) {
568
+ let utf8Bytes = 0;
569
+ for (let index = start + 1; index < source.length; index += 1) {
570
+ const codeUnit = source.charCodeAt(index);
571
+ if (codeUnit === 0x22) return { end: index + 1, utf8Bytes };
572
+ if (codeUnit === 0x5c) {
573
+ const escaped = source[index + 1];
574
+ if (escaped === "u") {
575
+ const first = parseJsonUnicodeEscape(source, index, label);
576
+ if (first >= 0xd800 && first <= 0xdbff) {
577
+ if (source[index + 6] !== "\\" || source[index + 7] !== "u") {
578
+ throw new MermanInvalidTransportError(
579
+ `Merman transport ${label} contains an isolated JSON surrogate escape.`,
580
+ );
581
+ }
582
+ const second = parseJsonUnicodeEscape(source, index + 6, label);
583
+ if (!(second >= 0xdc00 && second <= 0xdfff)) {
584
+ throw new MermanInvalidTransportError(
585
+ `Merman transport ${label} contains an isolated JSON surrogate escape.`,
586
+ );
587
+ }
588
+ utf8Bytes += 4;
589
+ index += 11;
590
+ continue;
591
+ }
592
+ if (first >= 0xdc00 && first <= 0xdfff) {
593
+ throw new MermanInvalidTransportError(
594
+ `Merman transport ${label} contains an isolated JSON surrogate escape.`,
595
+ );
596
+ }
597
+ utf8Bytes += utf8LengthOfCodeUnit(first);
598
+ index += 5;
599
+ continue;
600
+ }
601
+ utf8Bytes += 1;
602
+ index += 1;
603
+ continue;
604
+ }
605
+ if (codeUnit <= 0x7f) {
606
+ utf8Bytes += 1;
607
+ } else if (codeUnit <= 0x7ff) {
608
+ utf8Bytes += 2;
609
+ } else if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) {
610
+ const low = source.charCodeAt(index + 1);
611
+ if (!(low >= 0xdc00 && low <= 0xdfff)) {
612
+ throw new MermanInvalidTransportError(
613
+ `Merman transport ${label} contains an isolated UTF-16 surrogate.`,
614
+ );
615
+ }
616
+ utf8Bytes += 4;
617
+ index += 1;
618
+ } else if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) {
619
+ throw new MermanInvalidTransportError(
620
+ `Merman transport ${label} contains an isolated UTF-16 surrogate.`,
621
+ );
622
+ } else {
623
+ utf8Bytes += 3;
624
+ }
625
+ }
626
+ return { end: source.length, utf8Bytes };
627
+ }
628
+
629
+ function parseJsonUnicodeEscape(source, slashIndex, label) {
630
+ const digits = source.slice(slashIndex + 2, slashIndex + 6);
631
+ if (!/^[0-9a-fA-F]{4}$/.test(digits)) {
632
+ throw new MermanInvalidTransportError(
633
+ `Merman transport ${label} contains an invalid JSON Unicode escape.`,
634
+ );
635
+ }
636
+ return Number.parseInt(digits, 16);
637
+ }
638
+
639
+ function utf8LengthOfCodeUnit(codeUnit) {
640
+ if (codeUnit <= 0x7f) return 1;
641
+ if (codeUnit <= 0x7ff) return 2;
642
+ return 3;
643
+ }
644
+
645
+ function isJsonWhitespaceCodeUnit(codeUnit) {
646
+ return codeUnit === 0x20 || codeUnit === 0x09 || codeUnit === 0x0a || codeUnit === 0x0d;
647
+ }
648
+
649
+ function assertExactSafeIntegerJsonTokens(value, label, paths) {
650
+ // JSON.parse rounds before semantic validators can observe the original token. Follow only the
651
+ // known integer paths so additive schema-1 subtrees retain ordinary JSON number semantics.
652
+ const candidates = paths.map((_, index) => index);
653
+ scanJsonValue(value, skipJsonWhitespace(value, 0), paths, candidates, 0, (token) => {
654
+ if (!isExactSafeIntegerJsonToken(token, value.length)) {
655
+ throw new MermanInvalidTransportError(
656
+ `Merman transport ${label} must encode known integer fields as exact JSON-safe integers.`,
657
+ );
658
+ }
659
+ });
660
+ }
661
+
662
+ function scanJsonValue(source, index, paths, candidates, depth, visitNumber) {
663
+ index = skipJsonWhitespace(source, index);
664
+ const character = source[index];
665
+ const terminal = candidates.some((candidate) => paths[candidate].length === depth);
666
+ if (terminal && isJsonNumberStart(character)) {
667
+ return visitJsonNumber(source, index, visitNumber);
668
+ }
669
+ const continuing = candidates.filter((candidate) => paths[candidate].length > depth);
670
+ // Once no known path can match, skip the complete subtree without materializing a token tree.
671
+ if (continuing.length === 0) return skipJsonValue(source, index);
672
+ if (character === "{") {
673
+ return scanJsonObject(source, index, paths, continuing, depth, visitNumber);
674
+ }
675
+ if (character === "[") {
676
+ return scanJsonArray(source, index, paths, continuing, depth, visitNumber);
677
+ }
678
+ return skipJsonValue(source, index);
679
+ }
680
+
681
+ function scanJsonObject(source, index, paths, candidates, depth, visitNumber) {
682
+ index = skipJsonWhitespace(source, index + 1);
683
+ if (source[index] === "}") return index + 1;
684
+ while (index < source.length) {
685
+ const keyEnd = skipJsonString(source, index + 1);
686
+ const key = JSON.parse(source.slice(index, keyEnd));
687
+ index = skipJsonWhitespace(source, keyEnd);
688
+ index = skipJsonWhitespace(source, index + 1);
689
+ index = scanJsonValue(
690
+ source,
691
+ index,
692
+ paths,
693
+ advancePathCandidates(paths, candidates, depth, key),
694
+ depth + 1,
695
+ visitNumber,
696
+ );
697
+ index = skipJsonWhitespace(source, index);
698
+ if (source[index] === "}") return index + 1;
699
+ index = skipJsonWhitespace(source, index + 1);
700
+ }
701
+ return index;
702
+ }
703
+
704
+ function scanJsonArray(source, index, paths, candidates, depth, visitNumber) {
705
+ const elementCandidates = advancePathCandidates(paths, candidates, depth, "*");
706
+ index = skipJsonWhitespace(source, index + 1);
707
+ if (source[index] === "]") return index + 1;
708
+ while (index < source.length) {
709
+ index = scanJsonValue(
710
+ source,
711
+ index,
712
+ paths,
713
+ elementCandidates,
714
+ depth + 1,
715
+ visitNumber,
716
+ );
717
+ index = skipJsonWhitespace(source, index);
718
+ if (source[index] === "]") return index + 1;
719
+ index = skipJsonWhitespace(source, index + 1);
720
+ }
721
+ return index;
722
+ }
723
+
724
+ function advancePathCandidates(paths, candidates, depth, segment) {
725
+ return candidates.filter((candidate) => {
726
+ const expected = paths[candidate][depth];
727
+ return expected === "*" || expected === segment;
728
+ });
729
+ }
730
+
731
+ function skipJsonValue(source, index) {
732
+ index = skipJsonWhitespace(source, index);
733
+ const character = source[index];
734
+ if (character === '"') return skipJsonString(source, index + 1);
735
+ if (isJsonNumberStart(character)) return visitJsonNumber(source, index, () => {});
736
+ if (character !== "{" && character !== "[") {
737
+ if (source.startsWith("true", index)) return index + 4;
738
+ if (source.startsWith("false", index)) return index + 5;
739
+ return index + 4;
740
+ }
741
+ let nesting = 0;
742
+ while (index < source.length) {
743
+ if (source[index] === '"') {
744
+ index = skipJsonString(source, index + 1);
745
+ continue;
746
+ }
747
+ if (source[index] === "{" || source[index] === "[") {
748
+ nesting += 1;
749
+ } else if (source[index] === "}" || source[index] === "]") {
750
+ nesting -= 1;
751
+ if (nesting === 0) return index + 1;
752
+ }
753
+ index += 1;
754
+ }
755
+ return index;
756
+ }
757
+
758
+ function visitJsonNumber(source, index, visit) {
759
+ JSON_NUMBER_TOKEN.lastIndex = index;
760
+ const match = JSON_NUMBER_TOKEN.exec(source);
761
+ if (match === null) return index + 1;
762
+ visit(match[0]);
763
+ return JSON_NUMBER_TOKEN.lastIndex;
764
+ }
765
+
766
+ function isJsonNumberStart(character) {
767
+ return character === "-" || (character >= "0" && character <= "9");
768
+ }
769
+
770
+ function skipJsonWhitespace(source, index) {
771
+ while (
772
+ source[index] === " " ||
773
+ source[index] === "\n" ||
774
+ source[index] === "\r" ||
775
+ source[index] === "\t"
776
+ ) {
777
+ index += 1;
778
+ }
779
+ return index;
780
+ }
781
+
782
+ function skipJsonString(value, index) {
783
+ while (index < value.length) {
784
+ if (value[index] === "\\") {
785
+ index += 2;
786
+ } else if (value[index] === '"') {
787
+ return index + 1;
788
+ } else {
789
+ index += 1;
790
+ }
791
+ }
792
+ return index;
793
+ }
794
+
795
+ function isExactSafeIntegerJsonToken(token, sourceLength) {
796
+ const match = JSON_NUMBER_PARTS.exec(token);
797
+ if (match === null) return false;
798
+ const [, integerDigits, fractionDigits = "", exponentSign = "", exponentDigits = "0"] = match;
799
+ const significantDigits = `${integerDigits}${fractionDigits}`.replace(/^0+/, "");
800
+ if (significantDigits.length === 0) return true;
801
+
802
+ const normalizedExponentDigits = exponentDigits.replace(/^0+/, "") || "0";
803
+ const maximumRelevantExponent = sourceLength + MAX_SAFE_INTEGER_DECIMAL.length;
804
+ const maximumRelevantExponentText = String(maximumRelevantExponent);
805
+ if (
806
+ normalizedExponentDigits.length > maximumRelevantExponentText.length ||
807
+ (normalizedExponentDigits.length === maximumRelevantExponentText.length &&
808
+ normalizedExponentDigits > maximumRelevantExponentText)
809
+ ) {
810
+ return false;
811
+ }
812
+ const exponentMagnitude = Number(normalizedExponentDigits);
813
+ const exponent = exponentSign === "-" ? -exponentMagnitude : exponentMagnitude;
814
+ const decimalShift = exponent - fractionDigits.length;
815
+
816
+ let integerValueDigits;
817
+ if (decimalShift >= 0) {
818
+ if (significantDigits.length + decimalShift > MAX_SAFE_INTEGER_DECIMAL.length) {
819
+ return false;
820
+ }
821
+ integerValueDigits = `${significantDigits}${"0".repeat(decimalShift)}`;
822
+ } else {
823
+ const fractionalDigits = -decimalShift;
824
+ if (fractionalDigits >= significantDigits.length) return false;
825
+ if (!/^0+$/.test(significantDigits.slice(-fractionalDigits))) return false;
826
+ integerValueDigits = significantDigits.slice(0, -fractionalDigits);
827
+ }
828
+
829
+ return integerValueDigits.length < MAX_SAFE_INTEGER_DECIMAL.length ||
830
+ (integerValueDigits.length === MAX_SAFE_INTEGER_DECIMAL.length &&
831
+ integerValueDigits <= MAX_SAFE_INTEGER_DECIMAL);
832
+ }
833
+
834
+ export function abortError() {
835
+ if (typeof DOMException === "function") {
836
+ return new DOMException("The queued Merman operation was aborted.", "AbortError");
837
+ }
838
+ const error = new Error("The queued Merman operation was aborted.");
839
+ error.name = "AbortError";
840
+ return error;
841
+ }
842
+
843
+ export function decodeWireResponse(
844
+ value,
845
+ expectation,
846
+ { requireUnavailable = false } = {},
847
+ ) {
848
+ const envelope = parseTransportJsonText(
849
+ value,
850
+ "response",
851
+ NODE_TRANSPORT_LIMITS.response,
852
+ );
853
+ assertExactSafeIntegerJsonTokens(
854
+ value,
855
+ "response",
856
+ RESPONSE_EXACT_SAFE_INTEGER_PATHS,
857
+ );
858
+ validateEnvelopeHeader(envelope, "response");
859
+ if (envelope.ok === false) {
860
+ parseTransportJsonText(value, "error response", NODE_TRANSPORT_LIMITS.error);
861
+ if (Object.hasOwn(envelope, "result") || !Object.hasOwn(envelope, "error")) {
862
+ throw new MermanInvalidTransportError(
863
+ "Merman transport error envelopes must contain error only.",
864
+ );
865
+ }
866
+ const error = validateErrorPayload(envelope.error, expectation, requireUnavailable);
867
+ throw new MermanOperationError(error);
868
+ }
869
+ if (envelope.ok !== true || Object.hasOwn(envelope, "error")) {
870
+ throw new MermanInvalidTransportError(
871
+ "Merman transport success envelopes must contain result only.",
872
+ );
873
+ }
874
+ if (requireUnavailable) {
875
+ throw new MermanInvalidTransportError(
876
+ "Merman transport executed an operation that its runtime catalog does not advertise.",
877
+ );
878
+ }
879
+ return validateSuccessResult(envelope.result, expectation);
880
+ }
881
+
882
+ export function decodeWireCreationError(cause, label) {
883
+ return decodeWireFailure(cause, `${label} failed to initialize.`);
884
+ }
885
+
886
+ export function decodeWireInvocationError(cause, label) {
887
+ return decodeWireFailure(cause, `${label} failed.`);
888
+ }
889
+
890
+ function decodeWireFailure(cause, fallbackMessage) {
891
+ if (cause instanceof MermanError) return cause;
892
+ const value = cause instanceof Error ? cause.message : cause;
893
+ try {
894
+ const envelope = parseTransportJsonText(
895
+ value,
896
+ "thrown error",
897
+ NODE_TRANSPORT_LIMITS.error,
898
+ );
899
+ validateEnvelopeHeader(envelope, "thrown error");
900
+ if (
901
+ envelope.ok === false &&
902
+ !Object.hasOwn(envelope, "result") &&
903
+ Object.hasOwn(envelope, "error")
904
+ ) {
905
+ return new MermanOperationError(validateErrorPayload(envelope.error));
906
+ }
907
+ } catch (causeError) {
908
+ if (causeError instanceof MermanOperationError) return causeError;
909
+ // Fall through to the transport-level error below.
910
+ }
911
+ return new MermanInvalidTransportError(fallbackMessage, cause);
912
+ }
913
+
914
+ function validateEnvelopeHeader(envelope, label) {
915
+ if (!isPlainJsonObject(envelope) || envelope.version !== BINDING_RESULT_PAYLOAD_VERSION) {
916
+ throw new MermanInvalidTransportError(
917
+ `Merman transport returned an unsupported ${label} envelope.`,
918
+ );
919
+ }
920
+ if (typeof envelope.ok !== "boolean") {
921
+ throw new MermanInvalidTransportError(`Merman transport ${label} envelope has invalid ok.`);
922
+ }
923
+ }
924
+
925
+ function validateSuccessResult(result, expectation) {
926
+ if (
927
+ !isPlainJsonObject(result) ||
928
+ typeof result.operation_id !== "string" ||
929
+ result.operation_id.length === 0 ||
930
+ typeof result.media_type !== "string" ||
931
+ result.media_type.length === 0 ||
932
+ typeof result.data !== "string" ||
933
+ typeof result.metadata_json !== "string"
934
+ ) {
935
+ throw new MermanInvalidTransportError("Merman transport returned an invalid result envelope.");
936
+ }
937
+ assertUtf8Field(
938
+ result.operation_id,
939
+ "response operation_id",
940
+ NODE_TRANSPORT_FIELD_LIMITS.operation_id_utf8_bytes,
941
+ );
942
+ assertUtf8Field(
943
+ result.media_type,
944
+ "response media_type",
945
+ NODE_TRANSPORT_FIELD_LIMITS.media_type_utf8_bytes,
946
+ );
947
+ const dataBytes = assertUtf8Field(
948
+ result.data,
949
+ "response data",
950
+ NODE_TRANSPORT_FIELD_LIMITS.data_utf8_bytes,
951
+ );
952
+ assertUtf8Field(
953
+ result.metadata_json,
954
+ "response metadata_json",
955
+ NODE_TRANSPORT_FIELD_LIMITS.metadata_json_utf8_bytes,
956
+ );
957
+ if (
958
+ !expectation ||
959
+ result.operation_id !== expectation.operation_id ||
960
+ result.media_type !== expectation.media_type
961
+ ) {
962
+ throw new MermanInvalidTransportError(
963
+ "Merman transport result identity does not match the requested operation contract.",
964
+ );
965
+ }
966
+ const metadata = parseTransportJsonText(
967
+ result.metadata_json,
968
+ "nested operation metadata",
969
+ NODE_TRANSPORT_LIMITS.metadata,
970
+ );
971
+ assertExactSafeIntegerJsonTokens(
972
+ result.metadata_json,
973
+ "nested operation metadata",
974
+ OPERATION_METADATA_EXACT_SAFE_INTEGER_PATHS,
975
+ );
976
+ if (
977
+ !isPlainJsonObject(metadata) ||
978
+ metadata.version !== expectation.metadata_schema_version ||
979
+ metadata.operation_id !== expectation.operation_id ||
980
+ metadata.media_type !== expectation.media_type ||
981
+ metadata.runtime_policy !== "deterministic" ||
982
+ !Number.isSafeInteger(metadata.byte_length) ||
983
+ metadata.byte_length < 0 ||
984
+ metadata.byte_length !== dataBytes
985
+ ) {
986
+ throw new MermanInvalidTransportError(
987
+ "Merman transport operation metadata does not match the result envelope.",
988
+ );
989
+ }
990
+ return result;
991
+ }
992
+
993
+ function validateErrorPayload(error, expectation = null, requireUnavailable = false) {
994
+ if (
995
+ !isPlainJsonObject(error) ||
996
+ !Number.isSafeInteger(error.code) ||
997
+ error.code <= 0 ||
998
+ typeof error.code_name !== "string" ||
999
+ !/^MERMAN_[A-Z0-9_]+$/.test(error.code_name) ||
1000
+ typeof error.kind !== "string" ||
1001
+ error.kind.length === 0 ||
1002
+ !Object.hasOwn(error, "capability_id") ||
1003
+ (error.capability_id !== null &&
1004
+ (typeof error.capability_id !== "string" || error.capability_id.length === 0)) ||
1005
+ typeof error.message !== "string"
1006
+ ) {
1007
+ throw new MermanInvalidTransportError("Merman transport returned an invalid error payload.");
1008
+ }
1009
+ assertUtf8Field(
1010
+ error.code_name,
1011
+ "error code_name",
1012
+ NODE_TRANSPORT_FIELD_LIMITS.error_code_name_utf8_bytes,
1013
+ );
1014
+ assertUtf8Field(
1015
+ error.kind,
1016
+ "error kind",
1017
+ NODE_TRANSPORT_FIELD_LIMITS.error_kind_utf8_bytes,
1018
+ );
1019
+ assertUtf8Field(
1020
+ error.message,
1021
+ "error message",
1022
+ NODE_TRANSPORT_FIELD_LIMITS.error_message_utf8_bytes,
1023
+ );
1024
+ if (error.capability_id !== null) {
1025
+ assertUtf8Field(
1026
+ error.capability_id,
1027
+ "error capability_id",
1028
+ NODE_TRANSPORT_FIELD_LIMITS.capability_id_utf8_bytes,
1029
+ );
1030
+ }
1031
+ validateKnownErrorRelations(error);
1032
+ validateResourceDetails(error.details);
1033
+
1034
+ if (requireUnavailable && expectation?.unavailable) {
1035
+ const unavailable = expectation.unavailable;
1036
+ if (
1037
+ error.code !== unavailable.status_code ||
1038
+ error.code_name !== unavailable.status_name ||
1039
+ error.kind !== unavailable.error_kind ||
1040
+ error.capability_id !== unavailable.capability_id
1041
+ ) {
1042
+ throw new MermanInvalidTransportError(
1043
+ "Merman transport returned the wrong capability-gated operation error.",
1044
+ );
1045
+ }
1046
+ } else if (
1047
+ expectation &&
1048
+ (error.kind === "missing-capability" || error.kind === "unknown-operation")
1049
+ ) {
1050
+ throw new MermanInvalidTransportError(
1051
+ "Merman transport contradicted its advertised operation catalog.",
1052
+ );
1053
+ }
1054
+ return error;
1055
+ }
1056
+
1057
+ function validateKnownErrorRelations(error) {
1058
+ if (BINDING_STATUS_NAME_BY_CODE.get(error.code) !== error.code_name) {
1059
+ throw new MermanInvalidTransportError(
1060
+ "Merman transport returned an inconsistent error status name.",
1061
+ );
1062
+ }
1063
+ if (error.kind === "unknown-operation") {
1064
+ if (
1065
+ error.code !== 7 ||
1066
+ error.code_name !== "MERMAN_UNSUPPORTED_OPERATION" ||
1067
+ error.capability_id !== null
1068
+ ) {
1069
+ throw new MermanInvalidTransportError(
1070
+ "Merman transport returned an inconsistent unknown-operation error.",
1071
+ );
1072
+ }
1073
+ } else if (error.kind === "missing-capability") {
1074
+ if (
1075
+ error.code !== 7 ||
1076
+ error.code_name !== "MERMAN_UNSUPPORTED_OPERATION" ||
1077
+ error.capability_id === null
1078
+ ) {
1079
+ throw new MermanInvalidTransportError(
1080
+ "Merman transport returned an inconsistent missing-capability error.",
1081
+ );
1082
+ }
1083
+ } else if (error.kind === "busy") {
1084
+ if (error.code !== 11 || error.code_name !== "MERMAN_BUSY") {
1085
+ throw new MermanInvalidTransportError(
1086
+ "Merman transport returned an inconsistent busy error.",
1087
+ );
1088
+ }
1089
+ } else if (error.kind === "reentrant-call") {
1090
+ if (
1091
+ error.code !== 1 ||
1092
+ error.code_name !== "MERMAN_INVALID_ARGUMENT" ||
1093
+ error.capability_id !== null
1094
+ ) {
1095
+ throw new MermanInvalidTransportError(
1096
+ "Merman transport returned an inconsistent reentrant-call error.",
1097
+ );
1098
+ }
1099
+ } else if (error.kind === "generic") {
1100
+ if (error.capability_id !== null) {
1101
+ throw new MermanInvalidTransportError(
1102
+ "Merman transport returned an inconsistent generic error.",
1103
+ );
1104
+ }
1105
+ } else {
1106
+ throw new MermanInvalidTransportError("Merman transport returned an unknown error kind.");
1107
+ }
1108
+ }
1109
+
1110
+ function validateResourceDetails(details) {
1111
+ if (details === undefined) return;
1112
+ if (!isPlainJsonObject(details)) {
1113
+ throw new MermanInvalidTransportError("Merman transport error details must be an object.");
1114
+ }
1115
+ if (details.resource !== undefined) {
1116
+ const resource = details.resource;
1117
+ if (
1118
+ !isPlainJsonObject(resource) ||
1119
+ typeof resource.cause !== "string" ||
1120
+ resource.cause.length === 0 ||
1121
+ typeof resource.limit_id !== "string" ||
1122
+ resource.limit_id.length === 0 ||
1123
+ typeof resource.phase !== "string" ||
1124
+ resource.phase.length === 0 ||
1125
+ typeof resource.profile !== "string" ||
1126
+ resource.profile.length === 0 ||
1127
+ !isBindingResourceCount(resource.actual) ||
1128
+ !isBindingResourceCount(resource.max)
1129
+ ) {
1130
+ throw new MermanInvalidTransportError(
1131
+ "Merman transport returned invalid resource error details.",
1132
+ );
1133
+ }
1134
+ }
1135
+ if (details.icon_registry !== undefined) {
1136
+ const iconRegistry = details.icon_registry;
1137
+ if (
1138
+ !isPlainJsonObject(iconRegistry) ||
1139
+ typeof iconRegistry.kind_id !== "string" ||
1140
+ iconRegistry.kind_id.length === 0 ||
1141
+ (iconRegistry.pack_index !== null &&
1142
+ (!Number.isSafeInteger(iconRegistry.pack_index) || iconRegistry.pack_index < 0)) ||
1143
+ (iconRegistry.registration_name !== null &&
1144
+ typeof iconRegistry.registration_name !== "string")
1145
+ ) {
1146
+ throw new MermanInvalidTransportError(
1147
+ "Merman transport returned invalid icon-registry error details.",
1148
+ );
1149
+ }
1150
+ }
1151
+ }
1152
+
1153
+ function isBindingResourceCount(value) {
1154
+ if (typeof value === "number") {
1155
+ return Number.isSafeInteger(value) && value >= 0;
1156
+ }
1157
+ if (typeof value !== "string" || !CANONICAL_WIDE_UNSIGNED_DECIMAL.test(value)) {
1158
+ return false;
1159
+ }
1160
+ return compareCanonicalUnsignedDecimals(value, MAX_SAFE_INTEGER_DECIMAL) > 0 &&
1161
+ compareCanonicalUnsignedDecimals(value, U64_MAX_DECIMAL) <= 0;
1162
+ }
1163
+
1164
+ function compareCanonicalUnsignedDecimals(left, right) {
1165
+ if (left.length !== right.length) return left.length < right.length ? -1 : 1;
1166
+ if (left === right) return 0;
1167
+ return left < right ? -1 : 1;
1168
+ }
1169
+
1170
+ function isPlainJsonObject(value) {
1171
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
1172
+ }
1173
+
1174
+ export function sameJsonValue(left, right) {
1175
+ const stack = [[left, right]];
1176
+ while (stack.length > 0) {
1177
+ const [leftValue, rightValue] = stack.pop();
1178
+ if (Object.is(leftValue, rightValue)) continue;
1179
+ if (
1180
+ !leftValue ||
1181
+ !rightValue ||
1182
+ typeof leftValue !== "object" ||
1183
+ typeof rightValue !== "object" ||
1184
+ Array.isArray(leftValue) !== Array.isArray(rightValue)
1185
+ ) {
1186
+ return false;
1187
+ }
1188
+ const leftKeys = Object.keys(leftValue);
1189
+ const rightKeys = Object.keys(rightValue);
1190
+ if (
1191
+ leftKeys.length !== rightKeys.length ||
1192
+ leftKeys.some((key) => !Object.hasOwn(rightValue, key))
1193
+ ) {
1194
+ return false;
1195
+ }
1196
+ for (const key of leftKeys) stack.push([leftValue[key], rightValue[key]]);
1197
+ }
1198
+ return true;
1199
+ }