@camstack/system 1.1.0 → 1.1.2
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/dist/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/addon-utils.d.ts +1 -1
- package/dist/addon-utils.js +3 -1
- package/dist/addon-utils.mjs +2 -2
- package/dist/download/model-downloader.d.ts +25 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -2
- package/dist/index.mjs +3 -3
- package/dist/kernel/moleculer/trpc-links.d.ts +11 -0
- package/dist/kernel/transport/cap-route-resolver.d.ts +15 -0
- package/dist/{manifest-python-deps-BcrTzHH_.mjs → manifest-python-deps-8Wvwz-d1.mjs} +865 -837
- package/dist/{manifest-python-deps-BWURo7dc.js → manifest-python-deps-RihGbmpb.js} +864 -836
- package/dist/{model-download-service-RxAOiYvX.mjs → model-download-service-C-IHWnXx.mjs} +34 -9
- package/dist/{model-download-service-1eEOkNeS.js → model-download-service-D-Umz4-L.js} +39 -8
- package/package.json +1 -1
|
@@ -3351,926 +3351,945 @@ function createLocalTransport() {
|
|
|
3351
3351
|
};
|
|
3352
3352
|
}
|
|
3353
3353
|
//#endregion
|
|
3354
|
-
//#region src/kernel/
|
|
3355
|
-
/**
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3354
|
+
//#region src/kernel/moleculer/resilient-cap-call.ts
|
|
3355
|
+
/** Moleculer error `type` values meaning "the service is not (yet) routable". */
|
|
3356
|
+
var DISCOVERY_ERROR_TYPES = new Set(["SERVICE_NOT_FOUND", "SERVICE_NOT_AVAILABLE"]);
|
|
3357
|
+
/** Default ceiling for the discovery wait — fail-fast past this. */
|
|
3358
|
+
var DEFAULT_DISCOVERY_TIMEOUT_MS$1 = 3e4;
|
|
3359
|
+
function isDiscoveryError(err) {
|
|
3360
|
+
if (typeof err !== "object" || err === null) return false;
|
|
3361
|
+
const type = err.type;
|
|
3362
|
+
return typeof type === "string" && DISCOVERY_ERROR_TYPES.has(type);
|
|
3363
|
+
}
|
|
3362
3364
|
/**
|
|
3363
|
-
*
|
|
3364
|
-
*
|
|
3365
|
-
* connect; the registry routes `(capName, deviceId?)` cap calls to the
|
|
3366
|
-
* owning child over the channel and drops a child's caps on disconnect.
|
|
3365
|
+
* Call a Moleculer action; on a service-discovery error, wait for the
|
|
3366
|
+
* named service to be discovered and retry the call exactly once.
|
|
3367
3367
|
*/
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
server;
|
|
3376
|
-
onUnownedCall;
|
|
3377
|
-
logger;
|
|
3378
|
-
getActiveSingletonAddonId;
|
|
3379
|
-
/** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
|
|
3380
|
-
egressRoutedCaps = /* @__PURE__ */ new Set();
|
|
3381
|
-
/**
|
|
3382
|
-
* Accepts either a plain positional `server` argument (backward-compatible)
|
|
3383
|
-
* or a full `LocalChildRegistryOptions` object.
|
|
3384
|
-
*
|
|
3385
|
-
* Positional overloads (existing call sites are unchanged):
|
|
3386
|
-
* new LocalChildRegistry(server)
|
|
3387
|
-
* new LocalChildRegistry(server, onUnownedCall)
|
|
3388
|
-
*
|
|
3389
|
-
* Options object (new call sites that pass a logger):
|
|
3390
|
-
* new LocalChildRegistry({ server, onUnownedCall, logger })
|
|
3391
|
-
*/
|
|
3392
|
-
constructor(serverOrOptions, onUnownedCallArg) {
|
|
3393
|
-
if (serverOrOptions && !("listen" in serverOrOptions)) {
|
|
3394
|
-
const opts = serverOrOptions;
|
|
3395
|
-
this.server = opts.server;
|
|
3396
|
-
this.onUnownedCall = opts.onUnownedCall;
|
|
3397
|
-
this.logger = opts.logger;
|
|
3398
|
-
this.getActiveSingletonAddonId = opts.getActiveSingletonAddonId;
|
|
3399
|
-
} else {
|
|
3400
|
-
this.server = serverOrOptions;
|
|
3401
|
-
this.onUnownedCall = onUnownedCallArg;
|
|
3402
|
-
}
|
|
3403
|
-
}
|
|
3404
|
-
async start() {
|
|
3405
|
-
this.server.onConnection((channel) => this.onConnection(channel));
|
|
3406
|
-
await this.server.listen();
|
|
3368
|
+
async function callWithServiceDiscovery(broker, serviceName, action, params, opts, discoveryTimeoutMs = DEFAULT_DISCOVERY_TIMEOUT_MS$1) {
|
|
3369
|
+
try {
|
|
3370
|
+
return await broker.call(action, params, opts);
|
|
3371
|
+
} catch (err) {
|
|
3372
|
+
if (!isDiscoveryError(err)) throw err;
|
|
3373
|
+
await broker.waitForServices([serviceName], discoveryTimeoutMs);
|
|
3374
|
+
return await broker.call(action, params, opts);
|
|
3407
3375
|
}
|
|
3376
|
+
}
|
|
3377
|
+
//#endregion
|
|
3378
|
+
//#region src/kernel/transport/cap-route.ts
|
|
3379
|
+
function buildMessage(capName, method, detail) {
|
|
3380
|
+
const call = method !== void 0 ? `${capName}.${method}` : capName;
|
|
3381
|
+
const target = detail.nodeId !== void 0 ? ` to node ${detail.nodeId}` : "";
|
|
3382
|
+
const rejectedStr = detail.rejected.map((r) => `${r.kind}=${r.why}`).join("; ");
|
|
3383
|
+
const rejectedClause = rejectedStr.length > 0 ? ` (rejected: ${rejectedStr})` : "";
|
|
3384
|
+
return `${call} not routable${target}: ${detail.reason}${rejectedClause}`;
|
|
3385
|
+
}
|
|
3386
|
+
var CapRouteError = class extends Error {
|
|
3387
|
+
reason;
|
|
3388
|
+
nodeId;
|
|
3389
|
+
rejected;
|
|
3408
3390
|
/**
|
|
3409
|
-
*
|
|
3410
|
-
* `
|
|
3411
|
-
*
|
|
3412
|
-
* `deviceId` is a routing HINT, not a hard filter. A singleton cap
|
|
3413
|
-
* (`pipeline-runner`, `stream-broker`, …) addresses devices through its
|
|
3414
|
-
* METHOD ARGUMENTS — `attachCamera({ deviceId })` is one provider serving
|
|
3415
|
-
* many cameras — so its descriptor carries no `deviceId`. Resolving on
|
|
3416
|
-
* `deviceId` alone would never match it and would force the call onto the
|
|
3417
|
-
* broker fallback (which then hangs in service discovery). Hence:
|
|
3418
|
-
* 1. exact device-scoped owner (native per-device caps where each child
|
|
3419
|
-
* owns a disjoint device subset) is preferred, then
|
|
3420
|
-
* 2. a singleton owner (deviceId-less descriptor) is the fallback.
|
|
3421
|
-
* A cap is globally singleton XOR device-scoped, so the two tiers never
|
|
3422
|
-
* compete for the same capName.
|
|
3391
|
+
* @param cause Optional original error that triggered this routing failure.
|
|
3392
|
+
* Stored as `Error.cause` (TC39 standard option, Node 16.9+).
|
|
3393
|
+
* Dispatchers wrapping transport errors MUST pass the original.
|
|
3423
3394
|
*/
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
if (candidates.length === 0) return null;
|
|
3431
|
-
if (candidates.length === 1) return candidates[0];
|
|
3432
|
-
const preferred = this.getActiveSingletonAddonId?.(capName) ?? null;
|
|
3433
|
-
if (preferred !== null && candidates.includes(preferred)) return preferred;
|
|
3434
|
-
return candidates[0];
|
|
3435
|
-
}
|
|
3436
|
-
/** First child whose cap manifest contains a descriptor matching `predicate`. */
|
|
3437
|
-
findChildId(predicate) {
|
|
3438
|
-
for (const entry of this.children.values()) if (entry.caps.some(predicate)) return entry.childId;
|
|
3439
|
-
return null;
|
|
3395
|
+
constructor(capName, method, detail, cause) {
|
|
3396
|
+
super(buildMessage(capName, method, detail), cause !== void 0 ? { cause } : void 0);
|
|
3397
|
+
this.name = "CapRouteError";
|
|
3398
|
+
this.reason = detail.reason;
|
|
3399
|
+
this.nodeId = detail.nodeId;
|
|
3400
|
+
this.rejected = detail.rejected;
|
|
3440
3401
|
}
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3402
|
+
};
|
|
3403
|
+
/**
|
|
3404
|
+
* Classifies a (capName, opts) pair into a typed CapRoute dispatch descriptor.
|
|
3405
|
+
*
|
|
3406
|
+
* Precedence (explicit nodeId path):
|
|
3407
|
+
* 1. hub-in-process — hub node + hubInProcessProvides
|
|
3408
|
+
* 2. hub-local-uds — hub node + hubLocalChildProvides
|
|
3409
|
+
* 3. node-offline → CapRouteError{reason:'node-offline'}
|
|
3410
|
+
* 4. agent-child-forward — node is an agent AND nodeKnowsCap
|
|
3411
|
+
* 5. remote-moleculer — any other online non-agent node
|
|
3412
|
+
*
|
|
3413
|
+
* Singleton path (no nodeId):
|
|
3414
|
+
* 1. hub-in-process (hub provides in-process)
|
|
3415
|
+
* 2. hub-local-uds (a hub-local UDS child provides it)
|
|
3416
|
+
* 3. remote-moleculer (any online, non-hub node that knows it)
|
|
3417
|
+
* 4. → CapRouteError{reason:'no-provider', rejected: all considered routes}
|
|
3418
|
+
*
|
|
3419
|
+
* PURE: no side effects, no async, no broker/registry imports.
|
|
3420
|
+
*/
|
|
3421
|
+
function classifyCapRoute(capName, opts, snapshot) {
|
|
3422
|
+
const rejected = [];
|
|
3423
|
+
if (opts.nodeId !== void 0) return classifyExplicitNode(capName, opts.nodeId, opts.deviceId, snapshot, rejected);
|
|
3424
|
+
return classifySingleton(capName, opts.deviceId, snapshot, rejected);
|
|
3425
|
+
}
|
|
3426
|
+
function classifyExplicitNode(capName, nodeId, deviceId, snap, rejected) {
|
|
3427
|
+
if (nodeId === snap.hubNodeId) {
|
|
3428
|
+
if (snap.hubInProcessProvides(capName)) {
|
|
3429
|
+
const ref = snap.getInProcessProviderRef?.(capName) ?? null;
|
|
3430
|
+
if (ref !== null) return {
|
|
3431
|
+
kind: "hub-in-process",
|
|
3432
|
+
capName,
|
|
3433
|
+
ref
|
|
3434
|
+
};
|
|
3435
|
+
rejected.push({
|
|
3436
|
+
kind: "hub-in-process",
|
|
3437
|
+
why: "provider ref not available in snapshot"
|
|
3438
|
+
});
|
|
3439
|
+
throw new CapRouteError(capName, void 0, {
|
|
3440
|
+
reason: "no-provider",
|
|
3441
|
+
nodeId,
|
|
3442
|
+
rejected
|
|
3443
|
+
});
|
|
3444
|
+
}
|
|
3445
|
+
if (snap.hubLocalChildProvides(capName, deviceId)) {
|
|
3446
|
+
const childId = snap.getHubLocalChildId?.(capName, deviceId) ?? null;
|
|
3447
|
+
if (childId !== null) return {
|
|
3448
|
+
kind: "hub-local-uds",
|
|
3449
|
+
capName,
|
|
3450
|
+
childId
|
|
3451
|
+
};
|
|
3452
|
+
rejected.push({
|
|
3453
|
+
kind: "hub-local-uds",
|
|
3454
|
+
why: "child id not resolvable from snapshot"
|
|
3455
|
+
});
|
|
3456
|
+
throw new CapRouteError(capName, void 0, {
|
|
3457
|
+
reason: "no-provider",
|
|
3458
|
+
nodeId,
|
|
3459
|
+
rejected
|
|
3460
|
+
});
|
|
3461
|
+
}
|
|
3462
|
+
rejected.push({
|
|
3463
|
+
kind: "hub-in-process",
|
|
3464
|
+
why: "hub does not provide this cap in-process"
|
|
3465
|
+
});
|
|
3466
|
+
rejected.push({
|
|
3467
|
+
kind: "hub-local-uds",
|
|
3468
|
+
why: "no hub-local child provides this cap"
|
|
3469
|
+
});
|
|
3470
|
+
throw new CapRouteError(capName, void 0, {
|
|
3471
|
+
reason: "no-provider",
|
|
3472
|
+
nodeId,
|
|
3473
|
+
rejected
|
|
3474
|
+
});
|
|
3446
3475
|
}
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
* (singleton/collection) descriptor counts.
|
|
3458
|
-
*/
|
|
3459
|
-
/**
|
|
3460
|
-
* Is `childId` currently connected (has it completed its UDS handshake)?
|
|
3461
|
-
* Coarser than {@link childProvides}: it answers "is the child reachable
|
|
3462
|
-
* over UDS at all", regardless of which caps it has announced yet. Used by
|
|
3463
|
-
* the route-mount fallback to decide between the handler-stripped
|
|
3464
|
-
* `callAddonOnChild(target:'routes')` path (child reachable) and awaiting a
|
|
3465
|
-
* cap proxy's `getRoutes()` (child not yet UDS-registered).
|
|
3466
|
-
*/
|
|
3467
|
-
isChildKnown(childId) {
|
|
3468
|
-
return this.children.has(childId);
|
|
3476
|
+
if (!snap.nodeOnline(nodeId)) {
|
|
3477
|
+
rejected.push({
|
|
3478
|
+
kind: "remote-moleculer",
|
|
3479
|
+
why: `node ${nodeId} is offline`
|
|
3480
|
+
});
|
|
3481
|
+
throw new CapRouteError(capName, void 0, {
|
|
3482
|
+
reason: "node-offline",
|
|
3483
|
+
nodeId,
|
|
3484
|
+
rejected
|
|
3485
|
+
});
|
|
3469
3486
|
}
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3487
|
+
if (snap.nodeIsAgent(nodeId)) {
|
|
3488
|
+
if (snap.nodeKnowsCap(nodeId, capName)) return {
|
|
3489
|
+
kind: "agent-child-forward",
|
|
3490
|
+
capName,
|
|
3491
|
+
agentNodeId: nodeId,
|
|
3492
|
+
childId: snap.getAgentChildId?.(nodeId, capName) ?? void 0
|
|
3493
|
+
};
|
|
3494
|
+
rejected.push({
|
|
3495
|
+
kind: "agent-child-forward",
|
|
3496
|
+
why: `agent ${nodeId} does not know cap ${capName}`
|
|
3497
|
+
});
|
|
3498
|
+
throw new CapRouteError(capName, void 0, {
|
|
3499
|
+
reason: "no-provider",
|
|
3500
|
+
nodeId,
|
|
3501
|
+
rejected
|
|
3502
|
+
});
|
|
3475
3503
|
}
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
const
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
}
|
|
3490
|
-
return entry.channel.request(this.toCapCall(input));
|
|
3491
|
-
}
|
|
3492
|
-
/**
|
|
3493
|
-
* Forward an ADDON-LEVEL call (routes / custom-action) to a SPECIFIC child
|
|
3494
|
-
* by id over UDS; rejects if that child is absent.
|
|
3495
|
-
*
|
|
3496
|
-
* The childId for a hub-local single-addon runner equals the addonId
|
|
3497
|
-
* (`resolveRunnerId` returns the addonId when no `execution.group` is
|
|
3498
|
-
* declared — no shipped addon declares one). Mirrors `callCapOnChild` for
|
|
3499
|
-
* the cap plane; carries the two surfaces the removed per-addon Moleculer
|
|
3500
|
-
* broker used to serve (`getRoutes` + `custom.<action>`).
|
|
3501
|
-
*/
|
|
3502
|
-
async callAddonOnChild(childId, input) {
|
|
3503
|
-
const entry = this.children.get(childId);
|
|
3504
|
-
if (!entry) throw new Error(`no local child "${childId}" for addon-call (${input.target})`);
|
|
3505
|
-
return entry.channel.request(this.toAddonCall(input));
|
|
3506
|
-
}
|
|
3507
|
-
/** Build the parent→child `addon-call` wire message from an addon-call input. */
|
|
3508
|
-
toAddonCall(input) {
|
|
3509
|
-
return {
|
|
3510
|
-
kind: "addon-call",
|
|
3511
|
-
addonId: input.addonId,
|
|
3512
|
-
target: input.target,
|
|
3513
|
-
...input.action !== void 0 ? { action: input.action } : {},
|
|
3514
|
-
...input.method !== void 0 ? { method: input.method } : {},
|
|
3515
|
-
...input.args !== void 0 ? { args: input.args } : {}
|
|
3504
|
+
return {
|
|
3505
|
+
kind: "remote-moleculer",
|
|
3506
|
+
capName,
|
|
3507
|
+
nodeId
|
|
3508
|
+
};
|
|
3509
|
+
}
|
|
3510
|
+
function classifySingleton(capName, deviceId, snap, rejected) {
|
|
3511
|
+
if (snap.hubInProcessProvides(capName)) {
|
|
3512
|
+
const ref = snap.getInProcessProviderRef?.(capName) ?? null;
|
|
3513
|
+
if (ref !== null) return {
|
|
3514
|
+
kind: "hub-in-process",
|
|
3515
|
+
capName,
|
|
3516
|
+
ref
|
|
3516
3517
|
};
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3518
|
+
rejected.push({
|
|
3519
|
+
kind: "hub-in-process",
|
|
3520
|
+
why: "provider ref not available in snapshot"
|
|
3521
|
+
});
|
|
3522
|
+
} else rejected.push({
|
|
3523
|
+
kind: "hub-in-process",
|
|
3524
|
+
why: "hub does not provide this cap in-process"
|
|
3525
|
+
});
|
|
3526
|
+
if (snap.hubLocalChildProvides(capName, deviceId)) {
|
|
3527
|
+
const childId = snap.getHubLocalChildId?.(capName, deviceId) ?? null;
|
|
3528
|
+
if (childId !== null) return {
|
|
3529
|
+
kind: "hub-local-uds",
|
|
3530
|
+
capName,
|
|
3531
|
+
childId
|
|
3526
3532
|
};
|
|
3533
|
+
rejected.push({
|
|
3534
|
+
kind: "hub-local-uds",
|
|
3535
|
+
why: "child id not resolvable from snapshot"
|
|
3536
|
+
});
|
|
3537
|
+
} else rejected.push({
|
|
3538
|
+
kind: "hub-local-uds",
|
|
3539
|
+
why: "no hub-local child provides this cap"
|
|
3540
|
+
});
|
|
3541
|
+
const knownNodes = snap.listKnownNodeIds?.() ?? [];
|
|
3542
|
+
for (const nodeId of knownNodes) if (nodeId !== snap.hubNodeId && snap.nodeOnline(nodeId) && snap.nodeKnowsCap(nodeId, capName)) return {
|
|
3543
|
+
kind: "remote-moleculer",
|
|
3544
|
+
capName,
|
|
3545
|
+
nodeId
|
|
3546
|
+
};
|
|
3547
|
+
rejected.push({
|
|
3548
|
+
kind: "remote-moleculer",
|
|
3549
|
+
why: "no online remote node knows this cap"
|
|
3550
|
+
});
|
|
3551
|
+
throw new CapRouteError(capName, void 0, {
|
|
3552
|
+
reason: "no-provider",
|
|
3553
|
+
rejected
|
|
3554
|
+
});
|
|
3555
|
+
}
|
|
3556
|
+
//#endregion
|
|
3557
|
+
//#region src/kernel/transport/cap-route-resolver.ts
|
|
3558
|
+
/** The Moleculer service name that Task 6's agent registers. */
|
|
3559
|
+
var AGENT_CAP_FWD_SERVICE = "$agent-cap-fwd";
|
|
3560
|
+
/** The Moleculer action (service.action) for agent cap forwarding. */
|
|
3561
|
+
var AGENT_CAP_FWD_ACTION = `${AGENT_CAP_FWD_SERVICE}.forward`;
|
|
3562
|
+
/** Default timeout for remote Moleculer cap calls (ms). */
|
|
3563
|
+
var REMOTE_CALL_TIMEOUT_MS = 6e4;
|
|
3564
|
+
function extractDeviceId(args) {
|
|
3565
|
+
if (args === null || typeof args !== "object") return void 0;
|
|
3566
|
+
const raw = Reflect.get(args, "deviceId");
|
|
3567
|
+
return typeof raw === "number" ? raw : void 0;
|
|
3568
|
+
}
|
|
3569
|
+
/**
|
|
3570
|
+
* Extract an inline `nodeId` string from a cap call's args — the per-call node
|
|
3571
|
+
* pin a forked addon expresses by carrying `nodeId` in the input (the SAME way
|
|
3572
|
+
* device-scoped caps carry `deviceId`, and the way the generated cap-router on
|
|
3573
|
+
* the hub already honours an inline pin). Mirrors {@link extractDeviceId}.
|
|
3574
|
+
*
|
|
3575
|
+
* This is the routing source for hub→remote-node EXECUTION pinning (e.g. the
|
|
3576
|
+
* benchmark addon running a synthetic/decoder workload ON a chosen agent). The
|
|
3577
|
+
* out-of-band `nodePin(op.context)` path does NOT survive the forked addon →
|
|
3578
|
+
* hub UDS link chain, so `onUnownedCall` reads the inline pin instead. Provider
|
|
3579
|
+
* methods that don't declare `nodeId` simply ignore the extra field (they
|
|
3580
|
+
* destructure the fields they need); methods that DO declare it (e.g.
|
|
3581
|
+
* `getEngineProvisioning({nodeId})`) still receive it unchanged.
|
|
3582
|
+
*/
|
|
3583
|
+
function extractNodeId(args) {
|
|
3584
|
+
if (args === null || typeof args !== "object") return void 0;
|
|
3585
|
+
const raw = Reflect.get(args, "nodeId");
|
|
3586
|
+
return typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
3587
|
+
}
|
|
3588
|
+
var CapRouteResolver = class {
|
|
3589
|
+
hubNodeId;
|
|
3590
|
+
broker;
|
|
3591
|
+
hubLocalRegistry;
|
|
3592
|
+
nodeAuthority;
|
|
3593
|
+
inProcessProviders;
|
|
3594
|
+
snapshot;
|
|
3595
|
+
constructor(deps) {
|
|
3596
|
+
this.hubNodeId = deps.hubNodeId;
|
|
3597
|
+
this.broker = deps.broker;
|
|
3598
|
+
this.hubLocalRegistry = deps.hubLocalRegistry;
|
|
3599
|
+
this.nodeAuthority = deps.nodeAuthority;
|
|
3600
|
+
this.inProcessProviders = deps.inProcessProviders;
|
|
3601
|
+
this.snapshot = this.buildSnapshot();
|
|
3527
3602
|
}
|
|
3528
|
-
|
|
3529
|
-
return
|
|
3530
|
-
childId: e.childId,
|
|
3531
|
-
caps: e.caps
|
|
3532
|
-
}));
|
|
3533
|
-
}
|
|
3534
|
-
/** Register the (single) child-registered handler. Only one handler is active at a time. */
|
|
3535
|
-
onChildRegistered(handler) {
|
|
3536
|
-
this.registeredHandler = handler;
|
|
3537
|
-
}
|
|
3538
|
-
/** Register the (single) child-gone handler. Only one handler is active at a time. */
|
|
3539
|
-
onChildGone(handler) {
|
|
3540
|
-
this.goneHandler = handler;
|
|
3541
|
-
}
|
|
3542
|
-
/**
|
|
3543
|
-
* Register the (single) child-event handler. Invoked when a child sends an
|
|
3544
|
-
* event via `LocalChildClient.emitEvent`. Only one handler is active at a
|
|
3545
|
-
* time; a new handler replaces the prior one. Pass `null` to clear the
|
|
3546
|
-
* handler entirely (used by the UDS event bridge disposer on shutdown).
|
|
3547
|
-
*/
|
|
3548
|
-
onChildEvent(handler) {
|
|
3549
|
-
this.eventHandler = handler;
|
|
3550
|
-
}
|
|
3551
|
-
/**
|
|
3552
|
-
* Register the (single) child-log handler. Invoked when a child sends a log
|
|
3553
|
-
* entry via `LocalChildClient.sendLog`. Only one handler is active.
|
|
3554
|
-
*/
|
|
3555
|
-
onChildLog(handler) {
|
|
3556
|
-
this.logHandler = handler;
|
|
3557
|
-
}
|
|
3558
|
-
/**
|
|
3559
|
-
* Register the handler that supplies the authoritative readiness snapshot
|
|
3560
|
-
* when a child sends a `readiness-request`. Only one handler is active.
|
|
3561
|
-
*/
|
|
3562
|
-
onReadinessSnapshotRequest(handler) {
|
|
3563
|
-
this.readinessHandler = handler;
|
|
3603
|
+
resolveCapRoute(capName, opts) {
|
|
3604
|
+
return classifyCapRoute(capName, opts, this.snapshot);
|
|
3564
3605
|
}
|
|
3565
3606
|
/**
|
|
3566
|
-
*
|
|
3567
|
-
*
|
|
3607
|
+
* Resolve the hub-local-uds route for a cap owned by a forked hub-local
|
|
3608
|
+
* child, IGNORING any in-hub provider registered for the same cap name.
|
|
3609
|
+
*
|
|
3610
|
+
* `resolveCapRoute` gives Priority 1 to `hub-in-process`: when an in-hub
|
|
3611
|
+
* provider (e.g. a wrapper) is registered for the cap, the route always
|
|
3612
|
+
* classifies as `hub-in-process` and the hub-local-uds NATIVE child is never
|
|
3613
|
+
* reached. That is correct for the generic dispatch path (the wrapper is the
|
|
3614
|
+
* active provider), but WRONG for the native-cap fallback
|
|
3615
|
+
* (`setNativeFallback`), whose contract is to reach the NATIVE provider in
|
|
3616
|
+
* the forked vendor child so a wrapper can delegate to it. A wrapper cap
|
|
3617
|
+
* with a forked native (today: `snapshot`) otherwise resolves to the wrapper
|
|
3618
|
+
* itself, the native is never invoked, and the wrapper silently falls
|
|
3619
|
+
* through to its secondary strategy.
|
|
3620
|
+
*
|
|
3621
|
+
* This method consults ONLY the hub-local-child authority
|
|
3622
|
+
* (`hubLocalChildProvides` + `getHubLocalChildId`, both deviceId-aware), so
|
|
3623
|
+
* it returns the native child route regardless of any in-process shadow.
|
|
3624
|
+
* Returns null when no hub-local child owns `(capName, deviceId)` — the
|
|
3625
|
+
* caller then falls through to its remote-resolution branch.
|
|
3568
3626
|
*/
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3627
|
+
resolveHubLocalUdsRoute(capName, deviceId) {
|
|
3628
|
+
if (!this.snapshot.hubLocalChildProvides(capName, deviceId)) return null;
|
|
3629
|
+
const childId = this.snapshot.getHubLocalChildId?.(capName, deviceId) ?? null;
|
|
3630
|
+
if (childId === null) return null;
|
|
3631
|
+
return {
|
|
3632
|
+
kind: "hub-local-uds",
|
|
3633
|
+
capName,
|
|
3634
|
+
childId
|
|
3576
3635
|
};
|
|
3577
|
-
entry.channel.emit(msg);
|
|
3578
3636
|
}
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3637
|
+
async dispatch(route, method, args) {
|
|
3638
|
+
try {
|
|
3639
|
+
return await this.dispatchInner(route, method, args);
|
|
3640
|
+
} catch (err) {
|
|
3641
|
+
if (err instanceof CapRouteError) throw err;
|
|
3642
|
+
const nodeId = this.routeNodeId(route);
|
|
3643
|
+
const cause = err instanceof Error ? err : new Error(String(err));
|
|
3644
|
+
throw new CapRouteError(route.capName, method, {
|
|
3645
|
+
reason: "transport-failed",
|
|
3646
|
+
nodeId,
|
|
3647
|
+
rejected: [{
|
|
3648
|
+
kind: route.kind,
|
|
3649
|
+
why: cause.message
|
|
3650
|
+
}]
|
|
3651
|
+
}, cause);
|
|
3592
3652
|
}
|
|
3593
3653
|
}
|
|
3594
3654
|
/**
|
|
3595
|
-
*
|
|
3596
|
-
*
|
|
3597
|
-
* emitted; `false` if the child is not connected (no-op). The `false`
|
|
3598
|
-
* return lets the caller (MoleculerService.setChildLogLevelByNodeId) fall
|
|
3599
|
-
* back to the Moleculer `$node-mgmt.setLogLevel` action for the node.
|
|
3600
|
-
* Mirrors the `$node-mgmt.setLogLevel` Moleculer action for UDS children.
|
|
3655
|
+
* Inner dispatch: may throw CapRouteError (validation failures) or arbitrary
|
|
3656
|
+
* transport errors. The outer `dispatch` wraps non-CapRouteErrors.
|
|
3601
3657
|
*/
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
return;
|
|
3622
|
-
}
|
|
3623
|
-
if (msg.kind === "log") {
|
|
3624
|
-
if (childId !== null) this.logHandler?.(childId, msg);
|
|
3625
|
-
return;
|
|
3658
|
+
async dispatchInner(route, method, args) {
|
|
3659
|
+
switch (route.kind) {
|
|
3660
|
+
case "hub-in-process": return route.ref.invoke(method, args);
|
|
3661
|
+
case "hub-local-uds": {
|
|
3662
|
+
const registry = this.hubLocalRegistry;
|
|
3663
|
+
if (registry === null) throw new CapRouteError(route.capName, method, {
|
|
3664
|
+
reason: "no-provider",
|
|
3665
|
+
rejected: [{
|
|
3666
|
+
kind: "hub-local-uds",
|
|
3667
|
+
why: "UDS registry not available"
|
|
3668
|
+
}]
|
|
3669
|
+
});
|
|
3670
|
+
const deviceId = extractDeviceId(args);
|
|
3671
|
+
const input = {
|
|
3672
|
+
capName: route.capName,
|
|
3673
|
+
method,
|
|
3674
|
+
args,
|
|
3675
|
+
...deviceId !== void 0 ? { deviceId } : {}
|
|
3676
|
+
};
|
|
3677
|
+
return registry.callCapOnChild(route.childId, input);
|
|
3626
3678
|
}
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
caps: msg.caps
|
|
3679
|
+
case "remote-moleculer": {
|
|
3680
|
+
const addonId = this.nodeAuthority.getAddonId(route.nodeId, route.capName);
|
|
3681
|
+
if (addonId === null) throw new CapRouteError(route.capName, method, {
|
|
3682
|
+
reason: "no-provider",
|
|
3683
|
+
nodeId: route.nodeId,
|
|
3684
|
+
rejected: [{
|
|
3685
|
+
kind: "remote-moleculer",
|
|
3686
|
+
why: `no addonId known for ${route.nodeId}/${route.capName}`
|
|
3687
|
+
}]
|
|
3637
3688
|
});
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3689
|
+
const deviceId = extractDeviceId(args);
|
|
3690
|
+
const isNative = this.nodeAuthority.isNativeCap(route.nodeId, route.capName, deviceId);
|
|
3691
|
+
const action = capActionName(addonId, route.capName, method, isNative);
|
|
3692
|
+
return callWithServiceDiscovery(this.broker, addonId, action, args, {
|
|
3693
|
+
nodeID: route.nodeId,
|
|
3694
|
+
timeout: REMOTE_CALL_TIMEOUT_MS
|
|
3641
3695
|
});
|
|
3642
|
-
return { ok: true };
|
|
3643
3696
|
}
|
|
3644
|
-
|
|
3645
|
-
const
|
|
3646
|
-
const
|
|
3647
|
-
capName:
|
|
3648
|
-
method
|
|
3649
|
-
args
|
|
3650
|
-
...
|
|
3651
|
-
...
|
|
3697
|
+
case "agent-child-forward": {
|
|
3698
|
+
const deviceId = extractDeviceId(args);
|
|
3699
|
+
const params = {
|
|
3700
|
+
capName: route.capName,
|
|
3701
|
+
method,
|
|
3702
|
+
args,
|
|
3703
|
+
...route.childId !== void 0 ? { childId: route.childId } : {},
|
|
3704
|
+
...deviceId !== void 0 ? { deviceId } : {}
|
|
3652
3705
|
};
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
}
|
|
3658
|
-
return this.callCap(input);
|
|
3659
|
-
}
|
|
3660
|
-
if (this.onUnownedCall !== void 0) return this.onUnownedCall(input);
|
|
3661
|
-
throw new Error(`${UDS_NO_ROUTE_PREFIX}: cap-call-out has no local provider for "${out.capName}" and no fallback`);
|
|
3706
|
+
return callWithServiceDiscovery(this.broker, AGENT_CAP_FWD_SERVICE, AGENT_CAP_FWD_ACTION, params, {
|
|
3707
|
+
nodeID: route.agentNodeId,
|
|
3708
|
+
timeout: REMOTE_CALL_TIMEOUT_MS
|
|
3709
|
+
});
|
|
3662
3710
|
}
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
buildSnapshot() {
|
|
3714
|
+
const hubLocalRegistry = this.hubLocalRegistry;
|
|
3715
|
+
const nodeAuthority = this.nodeAuthority;
|
|
3716
|
+
const inProcessProviders = this.inProcessProviders;
|
|
3717
|
+
return {
|
|
3718
|
+
hubNodeId: this.hubNodeId,
|
|
3719
|
+
hubInProcessProvides: (cap) => inProcessProviders(cap) !== null,
|
|
3720
|
+
hubLocalChildProvides: (cap, deviceId) => hubLocalRegistry !== null && hubLocalRegistry.resolveChildId(cap, deviceId) !== null,
|
|
3721
|
+
nodeKnowsCap: (nodeId, cap) => nodeAuthority.nodeKnowsCap(nodeId, cap),
|
|
3722
|
+
nodeIsAgent: (nodeId) => nodeAuthority.nodeIsAgent(nodeId),
|
|
3723
|
+
nodeOnline: (nodeId) => nodeAuthority.nodeOnline(nodeId),
|
|
3724
|
+
listKnownNodeIds: () => nodeAuthority.listNodeIds(),
|
|
3725
|
+
getInProcessProviderRef: (cap) => inProcessProviders(cap),
|
|
3726
|
+
getHubLocalChildId: (cap, deviceId) => hubLocalRegistry !== null ? hubLocalRegistry.resolveChildId(cap, deviceId) : null,
|
|
3727
|
+
getAgentChildId: (agentNodeId, cap) => nodeAuthority.getAgentChildId(agentNodeId, cap)
|
|
3728
|
+
};
|
|
3729
|
+
}
|
|
3730
|
+
/** Extract a nodeId string from a route for error reporting, or undefined. */
|
|
3731
|
+
routeNodeId(route) {
|
|
3732
|
+
switch (route.kind) {
|
|
3733
|
+
case "hub-in-process": return this.hubNodeId;
|
|
3734
|
+
case "hub-local-uds": return `${this.hubNodeId}/${route.childId}`;
|
|
3735
|
+
case "remote-moleculer": return route.nodeId;
|
|
3736
|
+
case "agent-child-forward": return route.agentNodeId;
|
|
3737
|
+
}
|
|
3672
3738
|
}
|
|
3673
3739
|
};
|
|
3674
3740
|
//#endregion
|
|
3675
|
-
//#region src/kernel/transport/local-child-
|
|
3741
|
+
//#region src/kernel/transport/local-child-registry.ts
|
|
3676
3742
|
/**
|
|
3677
|
-
*
|
|
3678
|
-
*
|
|
3679
|
-
*
|
|
3680
|
-
*
|
|
3681
|
-
*
|
|
3682
|
-
* Additional channels beyond cap-call:
|
|
3683
|
-
* - `emitEvent` fire-and-forget event toward the parent
|
|
3684
|
-
* - `sendLog` fire-and-forget log entry toward the parent
|
|
3685
|
-
* - `requestReadinessSnapshot` request/response snapshot of readiness records
|
|
3686
|
-
* - `onEvent` register a handler for parent→child events
|
|
3687
|
-
*
|
|
3688
|
-
* Events and logs emitted before `start()` resolves are buffered and flushed
|
|
3689
|
-
* on connect (mirrors the `updateCaps`/`latestCaps` pattern).
|
|
3743
|
+
* Sentinel prefix used in the no-route error thrown when `cap-call-out` has no
|
|
3744
|
+
* local sibling and no `onUnownedCall` fallback. `ipcParentLink` detects this
|
|
3745
|
+
* prefix to distinguish routing failures (safe to retry via broker) from real
|
|
3746
|
+
* provider errors (must NOT retry to avoid double-executing side effects).
|
|
3690
3747
|
*/
|
|
3691
|
-
var
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
/** Events and logs queued while the channel is not yet open. */
|
|
3703
|
-
pendingEmits = [];
|
|
3704
|
-
/** Handler for parent→child events. Registered via `onEvent`. */
|
|
3748
|
+
var UDS_NO_ROUTE_PREFIX = "UDS_NO_ROUTE";
|
|
3749
|
+
/**
|
|
3750
|
+
* Parent-side authority for local addon-runners reachable over a
|
|
3751
|
+
* `LocalTransportServer` (UDS). Children register their cap manifest on
|
|
3752
|
+
* connect; the registry routes `(capName, deviceId?)` cap calls to the
|
|
3753
|
+
* owning child over the channel and drops a child's caps on disconnect.
|
|
3754
|
+
*/
|
|
3755
|
+
var LocalChildRegistry = class {
|
|
3756
|
+
children = /* @__PURE__ */ new Map();
|
|
3757
|
+
registeredHandler = () => {};
|
|
3758
|
+
goneHandler = () => {};
|
|
3705
3759
|
eventHandler = null;
|
|
3760
|
+
logHandler = null;
|
|
3761
|
+
readinessHandler = null;
|
|
3762
|
+
server;
|
|
3763
|
+
onUnownedCall;
|
|
3764
|
+
logger;
|
|
3765
|
+
getActiveSingletonAddonId;
|
|
3766
|
+
/** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
|
|
3767
|
+
egressRoutedCaps = /* @__PURE__ */ new Set();
|
|
3706
3768
|
/**
|
|
3707
|
-
*
|
|
3708
|
-
*
|
|
3709
|
-
*
|
|
3710
|
-
*
|
|
3711
|
-
*
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
*
|
|
3716
|
-
* Registered via `onSetLogLevel`. The handler applies the new level to the
|
|
3717
|
-
* child's local Moleculer logger (mirrors `$node-mgmt.setLogLevel`).
|
|
3718
|
-
* E2: wired in `addon-runner.ts` to forward to the broker logger.
|
|
3769
|
+
* Accepts either a plain positional `server` argument (backward-compatible)
|
|
3770
|
+
* or a full `LocalChildRegistryOptions` object.
|
|
3771
|
+
*
|
|
3772
|
+
* Positional overloads (existing call sites are unchanged):
|
|
3773
|
+
* new LocalChildRegistry(server)
|
|
3774
|
+
* new LocalChildRegistry(server, onUnownedCall)
|
|
3775
|
+
*
|
|
3776
|
+
* Options object (new call sites that pass a logger):
|
|
3777
|
+
* new LocalChildRegistry({ server, onUnownedCall, logger })
|
|
3719
3778
|
*/
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3779
|
+
constructor(serverOrOptions, onUnownedCallArg) {
|
|
3780
|
+
if (serverOrOptions && !("listen" in serverOrOptions)) {
|
|
3781
|
+
const opts = serverOrOptions;
|
|
3782
|
+
this.server = opts.server;
|
|
3783
|
+
this.onUnownedCall = opts.onUnownedCall;
|
|
3784
|
+
this.logger = opts.logger;
|
|
3785
|
+
this.getActiveSingletonAddonId = opts.getActiveSingletonAddonId;
|
|
3786
|
+
} else {
|
|
3787
|
+
this.server = serverOrOptions;
|
|
3788
|
+
this.onUnownedCall = onUnownedCallArg;
|
|
3789
|
+
}
|
|
3726
3790
|
}
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
* are called in registration order. Used by readiness-context in UDS mode
|
|
3731
|
-
* to trigger a snapshot hydrate on connect/reconnect.
|
|
3732
|
-
*/
|
|
3733
|
-
onConnected(handler) {
|
|
3734
|
-
this.connectedHandlers.push(handler);
|
|
3791
|
+
async start() {
|
|
3792
|
+
this.server.onConnection((channel) => this.onConnection(channel));
|
|
3793
|
+
await this.server.listen();
|
|
3735
3794
|
}
|
|
3736
3795
|
/**
|
|
3737
|
-
*
|
|
3738
|
-
*
|
|
3739
|
-
*
|
|
3740
|
-
*
|
|
3796
|
+
* Child id that can service a call to `capName` (optionally addressing
|
|
3797
|
+
* `deviceId`), or null.
|
|
3798
|
+
*
|
|
3799
|
+
* `deviceId` is a routing HINT, not a hard filter. A singleton cap
|
|
3800
|
+
* (`pipeline-runner`, `stream-broker`, …) addresses devices through its
|
|
3801
|
+
* METHOD ARGUMENTS — `attachCamera({ deviceId })` is one provider serving
|
|
3802
|
+
* many cameras — so its descriptor carries no `deviceId`. Resolving on
|
|
3803
|
+
* `deviceId` alone would never match it and would force the call onto the
|
|
3804
|
+
* broker fallback (which then hangs in service discovery). Hence:
|
|
3805
|
+
* 1. exact device-scoped owner (native per-device caps where each child
|
|
3806
|
+
* owns a disjoint device subset) is preferred, then
|
|
3807
|
+
* 2. a singleton owner (deviceId-less descriptor) is the fallback.
|
|
3808
|
+
* A cap is globally singleton XOR device-scoped, so the two tiers never
|
|
3809
|
+
* compete for the same capName.
|
|
3741
3810
|
*/
|
|
3742
|
-
|
|
3743
|
-
|
|
3811
|
+
resolveChildId(capName, deviceId) {
|
|
3812
|
+
if (deviceId !== void 0) {
|
|
3813
|
+
const deviceOwner = this.findChildId((cap) => cap.capName === capName && cap.deviceId === deviceId);
|
|
3814
|
+
if (deviceOwner !== null) return deviceOwner;
|
|
3815
|
+
}
|
|
3816
|
+
const candidates = this.findAllChildIds((cap) => cap.capName === capName && cap.deviceId === void 0);
|
|
3817
|
+
if (candidates.length === 0) return null;
|
|
3818
|
+
if (candidates.length === 1) return candidates[0];
|
|
3819
|
+
const preferred = this.getActiveSingletonAddonId?.(capName) ?? null;
|
|
3820
|
+
if (preferred !== null && candidates.includes(preferred)) return preferred;
|
|
3821
|
+
return candidates[0];
|
|
3744
3822
|
}
|
|
3745
|
-
/**
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
this.
|
|
3823
|
+
/** First child whose cap manifest contains a descriptor matching `predicate`. */
|
|
3824
|
+
findChildId(predicate) {
|
|
3825
|
+
for (const entry of this.children.values()) if (entry.caps.some(predicate)) return entry.childId;
|
|
3826
|
+
return null;
|
|
3827
|
+
}
|
|
3828
|
+
/** Every child whose cap manifest contains a descriptor matching `predicate`. */
|
|
3829
|
+
findAllChildIds(predicate) {
|
|
3830
|
+
const out = [];
|
|
3831
|
+
for (const entry of this.children.values()) if (entry.caps.some(predicate)) out.push(entry.childId);
|
|
3832
|
+
return out;
|
|
3754
3833
|
}
|
|
3755
3834
|
/**
|
|
3756
|
-
*
|
|
3757
|
-
*
|
|
3758
|
-
*
|
|
3759
|
-
*
|
|
3835
|
+
* Does the named child currently provide `(capName, deviceId?)`?
|
|
3836
|
+
*
|
|
3837
|
+
* Used by the hub proxy seam, which knows the EXACT addon (→ runner →
|
|
3838
|
+
* childId) a provider belongs to. Unlike `resolveChildId` (which picks the
|
|
3839
|
+
* first child owning `capName`), this targets one child — required for
|
|
3840
|
+
* COLLECTION caps (`addon-widgets-source`, …) where many children register
|
|
3841
|
+
* the same capName: routing by capName alone collapses every provider to
|
|
3842
|
+
* the first child. Same deviceId-as-hint semantics as `resolveChildId`:
|
|
3843
|
+
* a device-scoped descriptor matching `deviceId` OR a deviceId-less
|
|
3844
|
+
* (singleton/collection) descriptor counts.
|
|
3760
3845
|
*/
|
|
3761
|
-
onSetLogLevel(handler) {
|
|
3762
|
-
this.setLogLevelHandler = handler;
|
|
3763
|
-
}
|
|
3764
3846
|
/**
|
|
3765
|
-
*
|
|
3766
|
-
*
|
|
3847
|
+
* Is `childId` currently connected (has it completed its UDS handshake)?
|
|
3848
|
+
* Coarser than {@link childProvides}: it answers "is the child reachable
|
|
3849
|
+
* over UDS at all", regardless of which caps it has announced yet. Used by
|
|
3850
|
+
* the route-mount fallback to decide between the handler-stripped
|
|
3851
|
+
* `callAddonOnChild(target:'routes')` path (child reachable) and awaiting a
|
|
3852
|
+
* cap proxy's `getRoutes()` (child not yet UDS-registered).
|
|
3767
3853
|
*/
|
|
3768
|
-
|
|
3769
|
-
return this.
|
|
3854
|
+
isChildKnown(childId) {
|
|
3855
|
+
return this.children.has(childId);
|
|
3770
3856
|
}
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
const msg = body;
|
|
3777
|
-
if (msg.kind === "cap-call") return this.options.dispatch({
|
|
3778
|
-
capName: msg.capName,
|
|
3779
|
-
method: msg.method,
|
|
3780
|
-
args: msg.args,
|
|
3781
|
-
...msg.deviceId !== void 0 ? { deviceId: msg.deviceId } : {}
|
|
3782
|
-
});
|
|
3783
|
-
if (msg.kind === "addon-call") {
|
|
3784
|
-
if (this.addonCallHandler === null) throw new Error(`LocalChildClient: addon-call for "${msg.addonId}" arrived but no onAddonCall handler is registered`);
|
|
3785
|
-
return this.addonCallHandler({
|
|
3786
|
-
addonId: msg.addonId,
|
|
3787
|
-
target: msg.target,
|
|
3788
|
-
...msg.action !== void 0 ? { action: msg.action } : {},
|
|
3789
|
-
...msg.method !== void 0 ? { method: msg.method } : {},
|
|
3790
|
-
...msg.args !== void 0 ? { args: msg.args } : {}
|
|
3791
|
-
});
|
|
3792
|
-
}
|
|
3793
|
-
throw new Error(`unknown parent request kind: ${msg.kind}`);
|
|
3794
|
-
});
|
|
3795
|
-
channel.onEvent((body) => {
|
|
3796
|
-
const msg = body;
|
|
3797
|
-
if (msg.kind === "event") {
|
|
3798
|
-
this.eventHandler?.(msg.event);
|
|
3799
|
-
return;
|
|
3800
|
-
}
|
|
3801
|
-
if (msg.kind === "set-log-level") this.setLogLevelHandler?.(msg.level);
|
|
3802
|
-
});
|
|
3803
|
-
const register = {
|
|
3804
|
-
kind: "register",
|
|
3805
|
-
childId: this.options.childId,
|
|
3806
|
-
caps: this.latestCaps
|
|
3807
|
-
};
|
|
3808
|
-
try {
|
|
3809
|
-
await channel.request(register);
|
|
3810
|
-
} catch (err) {
|
|
3811
|
-
await client.close();
|
|
3812
|
-
throw err;
|
|
3813
|
-
}
|
|
3814
|
-
this.client = client;
|
|
3815
|
-
this.channel = channel;
|
|
3816
|
-
this.flushPending(channel);
|
|
3817
|
-
for (const cb of this.connectedHandlers) cb();
|
|
3857
|
+
childProvides(childId, capName, deviceId) {
|
|
3858
|
+
const entry = this.children.get(childId);
|
|
3859
|
+
if (!entry) return false;
|
|
3860
|
+
if (deviceId !== void 0 && entry.caps.some((cap) => cap.capName === capName && cap.deviceId === deviceId)) return true;
|
|
3861
|
+
return entry.caps.some((cap) => cap.capName === capName && cap.deviceId === void 0);
|
|
3818
3862
|
}
|
|
3819
|
-
/**
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3863
|
+
/** Forward a cap method call to a SPECIFIC child by id over UDS; rejects if that child is absent. */
|
|
3864
|
+
async callCapOnChild(childId, input) {
|
|
3865
|
+
const entry = this.children.get(childId);
|
|
3866
|
+
if (!entry) throw new Error(`no local child "${childId}" for cap "${input.capName}"`);
|
|
3867
|
+
return entry.channel.request(this.toCapCall(input));
|
|
3868
|
+
}
|
|
3869
|
+
/** Forward a cap method call to the owning child over UDS; rejects if none. */
|
|
3870
|
+
async callCap(input) {
|
|
3871
|
+
const childId = this.resolveChildId(input.capName, input.deviceId);
|
|
3872
|
+
const entry = childId === null ? void 0 : this.children.get(childId);
|
|
3873
|
+
if (!entry) {
|
|
3874
|
+
const where = input.deviceId === void 0 ? "" : ` for device ${input.deviceId}`;
|
|
3875
|
+
throw new Error(`no local child provides cap "${input.capName}"${where}`);
|
|
3876
|
+
}
|
|
3877
|
+
return entry.channel.request(this.toCapCall(input));
|
|
3823
3878
|
}
|
|
3824
3879
|
/**
|
|
3825
|
-
*
|
|
3826
|
-
*
|
|
3827
|
-
* registered native (device-scoped) caps become routable over UDS.
|
|
3880
|
+
* Forward an ADDON-LEVEL call (routes / custom-action) to a SPECIFIC child
|
|
3881
|
+
* by id over UDS; rejects if that child is absent.
|
|
3828
3882
|
*
|
|
3829
|
-
*
|
|
3830
|
-
*
|
|
3831
|
-
*
|
|
3883
|
+
* The childId for a hub-local single-addon runner equals the addonId
|
|
3884
|
+
* (`resolveRunnerId` returns the addonId when no `execution.group` is
|
|
3885
|
+
* declared — no shipped addon declares one). Mirrors `callCapOnChild` for
|
|
3886
|
+
* the cap plane; carries the two surfaces the removed per-addon Moleculer
|
|
3887
|
+
* broker used to serve (`getRoutes` + `custom.<action>`).
|
|
3832
3888
|
*/
|
|
3833
|
-
async
|
|
3834
|
-
|
|
3835
|
-
if (
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3889
|
+
async callAddonOnChild(childId, input) {
|
|
3890
|
+
const entry = this.children.get(childId);
|
|
3891
|
+
if (!entry) throw new Error(`no local child "${childId}" for addon-call (${input.target})`);
|
|
3892
|
+
return entry.channel.request(this.toAddonCall(input));
|
|
3893
|
+
}
|
|
3894
|
+
/** Build the parent→child `addon-call` wire message from an addon-call input. */
|
|
3895
|
+
toAddonCall(input) {
|
|
3896
|
+
return {
|
|
3897
|
+
kind: "addon-call",
|
|
3898
|
+
addonId: input.addonId,
|
|
3899
|
+
target: input.target,
|
|
3900
|
+
...input.action !== void 0 ? { action: input.action } : {},
|
|
3901
|
+
...input.method !== void 0 ? { method: input.method } : {},
|
|
3902
|
+
...input.args !== void 0 ? { args: input.args } : {}
|
|
3840
3903
|
};
|
|
3841
|
-
|
|
3904
|
+
}
|
|
3905
|
+
/** Build the parent→child `cap-call` wire message from a routing input. */
|
|
3906
|
+
toCapCall(input) {
|
|
3907
|
+
return {
|
|
3908
|
+
kind: "cap-call",
|
|
3909
|
+
capName: input.capName,
|
|
3910
|
+
method: input.method,
|
|
3911
|
+
args: input.args,
|
|
3912
|
+
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {}
|
|
3913
|
+
};
|
|
3914
|
+
}
|
|
3915
|
+
listChildren() {
|
|
3916
|
+
return [...this.children.values()].map((e) => ({
|
|
3917
|
+
childId: e.childId,
|
|
3918
|
+
caps: e.caps
|
|
3919
|
+
}));
|
|
3920
|
+
}
|
|
3921
|
+
/** Register the (single) child-registered handler. Only one handler is active at a time. */
|
|
3922
|
+
onChildRegistered(handler) {
|
|
3923
|
+
this.registeredHandler = handler;
|
|
3924
|
+
}
|
|
3925
|
+
/** Register the (single) child-gone handler. Only one handler is active at a time. */
|
|
3926
|
+
onChildGone(handler) {
|
|
3927
|
+
this.goneHandler = handler;
|
|
3842
3928
|
}
|
|
3843
3929
|
/**
|
|
3844
|
-
*
|
|
3845
|
-
*
|
|
3846
|
-
*
|
|
3930
|
+
* Register the (single) child-event handler. Invoked when a child sends an
|
|
3931
|
+
* event via `LocalChildClient.emitEvent`. Only one handler is active at a
|
|
3932
|
+
* time; a new handler replaces the prior one. Pass `null` to clear the
|
|
3933
|
+
* handler entirely (used by the UDS event bridge disposer on shutdown).
|
|
3847
3934
|
*/
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
kind: "event",
|
|
3851
|
-
event
|
|
3852
|
-
};
|
|
3853
|
-
if (this.channel !== null) this.channel.emit(msg);
|
|
3854
|
-
else this.pendingEmits.push({
|
|
3855
|
-
kind: "event",
|
|
3856
|
-
msg
|
|
3857
|
-
});
|
|
3935
|
+
onChildEvent(handler) {
|
|
3936
|
+
this.eventHandler = handler;
|
|
3858
3937
|
}
|
|
3859
3938
|
/**
|
|
3860
|
-
*
|
|
3861
|
-
*
|
|
3939
|
+
* Register the (single) child-log handler. Invoked when a child sends a log
|
|
3940
|
+
* entry via `LocalChildClient.sendLog`. Only one handler is active.
|
|
3862
3941
|
*/
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
kind: "log",
|
|
3866
|
-
...entry
|
|
3867
|
-
};
|
|
3868
|
-
if (this.channel !== null) this.channel.emit(msg);
|
|
3869
|
-
else this.pendingEmits.push({
|
|
3870
|
-
kind: "log",
|
|
3871
|
-
msg
|
|
3872
|
-
});
|
|
3942
|
+
onChildLog(handler) {
|
|
3943
|
+
this.logHandler = handler;
|
|
3873
3944
|
}
|
|
3874
3945
|
/**
|
|
3875
|
-
*
|
|
3876
|
-
*
|
|
3877
|
-
* Requires `start()` to have been called; throws with a clear message if not.
|
|
3946
|
+
* Register the handler that supplies the authoritative readiness snapshot
|
|
3947
|
+
* when a child sends a `readiness-request`. Only one handler is active.
|
|
3878
3948
|
*/
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
return (await this.channel.request({ kind: "readiness-request" })).records;
|
|
3949
|
+
onReadinessSnapshotRequest(handler) {
|
|
3950
|
+
this.readinessHandler = handler;
|
|
3882
3951
|
}
|
|
3883
3952
|
/**
|
|
3884
|
-
*
|
|
3885
|
-
*
|
|
3886
|
-
* to its `onUnownedCall` fallback (the cluster CapabilityRegistry in
|
|
3887
|
-
* production). Throws if called before `start()`.
|
|
3953
|
+
* Push a parent→child event to a specific child. Fire-and-forget (uses the
|
|
3954
|
+
* one-way `emit` path on the channel). No-op if the child is not connected.
|
|
3888
3955
|
*/
|
|
3889
|
-
|
|
3890
|
-
|
|
3956
|
+
sendEventToChild(childId, event, sourceNodeId) {
|
|
3957
|
+
const entry = this.children.get(childId);
|
|
3958
|
+
if (entry === void 0) return;
|
|
3891
3959
|
const msg = {
|
|
3892
|
-
kind: "
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
args: input.args,
|
|
3896
|
-
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
|
|
3897
|
-
...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {}
|
|
3960
|
+
kind: "event",
|
|
3961
|
+
event,
|
|
3962
|
+
sourceNodeId
|
|
3898
3963
|
};
|
|
3899
|
-
|
|
3900
|
-
}
|
|
3901
|
-
/** Disconnect from the parent. Safe to call before `start()` (no-op) and idempotent. */
|
|
3902
|
-
async close() {
|
|
3903
|
-
await this.client?.close();
|
|
3904
|
-
this.client = null;
|
|
3905
|
-
this.channel = null;
|
|
3964
|
+
entry.channel.emit(msg);
|
|
3906
3965
|
}
|
|
3907
|
-
};
|
|
3908
|
-
//#endregion
|
|
3909
|
-
//#region src/kernel/transport/cap-route.ts
|
|
3910
|
-
function buildMessage(capName, method, detail) {
|
|
3911
|
-
const call = method !== void 0 ? `${capName}.${method}` : capName;
|
|
3912
|
-
const target = detail.nodeId !== void 0 ? ` to node ${detail.nodeId}` : "";
|
|
3913
|
-
const rejectedStr = detail.rejected.map((r) => `${r.kind}=${r.why}`).join("; ");
|
|
3914
|
-
const rejectedClause = rejectedStr.length > 0 ? ` (rejected: ${rejectedStr})` : "";
|
|
3915
|
-
return `${call} not routable${target}: ${detail.reason}${rejectedClause}`;
|
|
3916
|
-
}
|
|
3917
|
-
var CapRouteError = class extends Error {
|
|
3918
|
-
reason;
|
|
3919
|
-
nodeId;
|
|
3920
|
-
rejected;
|
|
3921
3966
|
/**
|
|
3922
|
-
*
|
|
3923
|
-
*
|
|
3924
|
-
* Dispatchers wrapping transport errors MUST pass the original.
|
|
3967
|
+
* Push a parent→child event to every registered child, optionally skipping
|
|
3968
|
+
* one (the originating child, to avoid echo). Fire-and-forget.
|
|
3925
3969
|
*/
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
};
|
|
3934
|
-
/**
|
|
3935
|
-
* Classifies a (capName, opts) pair into a typed CapRoute dispatch descriptor.
|
|
3936
|
-
*
|
|
3937
|
-
* Precedence (explicit nodeId path):
|
|
3938
|
-
* 1. hub-in-process — hub node + hubInProcessProvides
|
|
3939
|
-
* 2. hub-local-uds — hub node + hubLocalChildProvides
|
|
3940
|
-
* 3. node-offline → CapRouteError{reason:'node-offline'}
|
|
3941
|
-
* 4. agent-child-forward — node is an agent AND nodeKnowsCap
|
|
3942
|
-
* 5. remote-moleculer — any other online non-agent node
|
|
3943
|
-
*
|
|
3944
|
-
* Singleton path (no nodeId):
|
|
3945
|
-
* 1. hub-in-process (hub provides in-process)
|
|
3946
|
-
* 2. hub-local-uds (a hub-local UDS child provides it)
|
|
3947
|
-
* 3. remote-moleculer (any online, non-hub node that knows it)
|
|
3948
|
-
* 4. → CapRouteError{reason:'no-provider', rejected: all considered routes}
|
|
3949
|
-
*
|
|
3950
|
-
* PURE: no side effects, no async, no broker/registry imports.
|
|
3951
|
-
*/
|
|
3952
|
-
function classifyCapRoute(capName, opts, snapshot) {
|
|
3953
|
-
const rejected = [];
|
|
3954
|
-
if (opts.nodeId !== void 0) return classifyExplicitNode(capName, opts.nodeId, opts.deviceId, snapshot, rejected);
|
|
3955
|
-
return classifySingleton(capName, opts.deviceId, snapshot, rejected);
|
|
3956
|
-
}
|
|
3957
|
-
function classifyExplicitNode(capName, nodeId, deviceId, snap, rejected) {
|
|
3958
|
-
if (nodeId === snap.hubNodeId) {
|
|
3959
|
-
if (snap.hubInProcessProvides(capName)) {
|
|
3960
|
-
const ref = snap.getInProcessProviderRef?.(capName) ?? null;
|
|
3961
|
-
if (ref !== null) return {
|
|
3962
|
-
kind: "hub-in-process",
|
|
3963
|
-
capName,
|
|
3964
|
-
ref
|
|
3965
|
-
};
|
|
3966
|
-
rejected.push({
|
|
3967
|
-
kind: "hub-in-process",
|
|
3968
|
-
why: "provider ref not available in snapshot"
|
|
3969
|
-
});
|
|
3970
|
-
throw new CapRouteError(capName, void 0, {
|
|
3971
|
-
reason: "no-provider",
|
|
3972
|
-
nodeId,
|
|
3973
|
-
rejected
|
|
3974
|
-
});
|
|
3975
|
-
}
|
|
3976
|
-
if (snap.hubLocalChildProvides(capName, deviceId)) {
|
|
3977
|
-
const childId = snap.getHubLocalChildId?.(capName, deviceId) ?? null;
|
|
3978
|
-
if (childId !== null) return {
|
|
3979
|
-
kind: "hub-local-uds",
|
|
3980
|
-
capName,
|
|
3981
|
-
childId
|
|
3970
|
+
broadcastEventToChildren(event, sourceNodeId, exceptChildId) {
|
|
3971
|
+
for (const entry of this.children.values()) {
|
|
3972
|
+
if (entry.childId === exceptChildId) continue;
|
|
3973
|
+
const msg = {
|
|
3974
|
+
kind: "event",
|
|
3975
|
+
event,
|
|
3976
|
+
sourceNodeId
|
|
3982
3977
|
};
|
|
3983
|
-
|
|
3984
|
-
kind: "hub-local-uds",
|
|
3985
|
-
why: "child id not resolvable from snapshot"
|
|
3986
|
-
});
|
|
3987
|
-
throw new CapRouteError(capName, void 0, {
|
|
3988
|
-
reason: "no-provider",
|
|
3989
|
-
nodeId,
|
|
3990
|
-
rejected
|
|
3991
|
-
});
|
|
3978
|
+
entry.channel.emit(msg);
|
|
3992
3979
|
}
|
|
3993
|
-
rejected.push({
|
|
3994
|
-
kind: "hub-in-process",
|
|
3995
|
-
why: "hub does not provide this cap in-process"
|
|
3996
|
-
});
|
|
3997
|
-
rejected.push({
|
|
3998
|
-
kind: "hub-local-uds",
|
|
3999
|
-
why: "no hub-local child provides this cap"
|
|
4000
|
-
});
|
|
4001
|
-
throw new CapRouteError(capName, void 0, {
|
|
4002
|
-
reason: "no-provider",
|
|
4003
|
-
nodeId,
|
|
4004
|
-
rejected
|
|
4005
|
-
});
|
|
4006
3980
|
}
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
capName,
|
|
4022
|
-
agentNodeId: nodeId,
|
|
4023
|
-
childId: snap.getAgentChildId?.(nodeId, capName) ?? void 0
|
|
3981
|
+
/**
|
|
3982
|
+
* E2: Send a `set-log-level` control message to a specific child.
|
|
3983
|
+
* Returns `true` if the child is currently connected and the message was
|
|
3984
|
+
* emitted; `false` if the child is not connected (no-op). The `false`
|
|
3985
|
+
* return lets the caller (MoleculerService.setChildLogLevelByNodeId) fall
|
|
3986
|
+
* back to the Moleculer `$node-mgmt.setLogLevel` action for the node.
|
|
3987
|
+
* Mirrors the `$node-mgmt.setLogLevel` Moleculer action for UDS children.
|
|
3988
|
+
*/
|
|
3989
|
+
setChildLogLevel(childId, level) {
|
|
3990
|
+
const entry = this.children.get(childId);
|
|
3991
|
+
if (entry === void 0) return false;
|
|
3992
|
+
const msg = {
|
|
3993
|
+
kind: "set-log-level",
|
|
3994
|
+
level
|
|
4024
3995
|
};
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
why: `agent ${nodeId} does not know cap ${capName}`
|
|
4028
|
-
});
|
|
4029
|
-
throw new CapRouteError(capName, void 0, {
|
|
4030
|
-
reason: "no-provider",
|
|
4031
|
-
nodeId,
|
|
4032
|
-
rejected
|
|
4033
|
-
});
|
|
3996
|
+
entry.channel.emit(msg);
|
|
3997
|
+
return true;
|
|
4034
3998
|
}
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
kind: "hub-in-process",
|
|
4051
|
-
why: "provider ref not available in snapshot"
|
|
3999
|
+
async close() {
|
|
4000
|
+
await this.server.close();
|
|
4001
|
+
}
|
|
4002
|
+
onConnection(channel) {
|
|
4003
|
+
let childId = null;
|
|
4004
|
+
channel.onEvent((body) => {
|
|
4005
|
+
const msg = body;
|
|
4006
|
+
if (msg.kind === "event") {
|
|
4007
|
+
if (childId !== null) this.eventHandler?.(childId, msg.event);
|
|
4008
|
+
return;
|
|
4009
|
+
}
|
|
4010
|
+
if (msg.kind === "log") {
|
|
4011
|
+
if (childId !== null) this.logHandler?.(childId, msg);
|
|
4012
|
+
return;
|
|
4013
|
+
}
|
|
4052
4014
|
});
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4015
|
+
channel.onRequest(async (body) => {
|
|
4016
|
+
const msg = body;
|
|
4017
|
+
if (msg.kind === "register") {
|
|
4018
|
+
if (childId !== null && childId !== msg.childId) throw new Error(`child attempted to change identity from "${childId}" to "${msg.childId}"`);
|
|
4019
|
+
childId = msg.childId;
|
|
4020
|
+
this.children.set(msg.childId, {
|
|
4021
|
+
childId: msg.childId,
|
|
4022
|
+
channel,
|
|
4023
|
+
caps: msg.caps
|
|
4024
|
+
});
|
|
4025
|
+
this.registeredHandler({
|
|
4026
|
+
childId: msg.childId,
|
|
4027
|
+
caps: msg.caps
|
|
4028
|
+
});
|
|
4029
|
+
return { ok: true };
|
|
4030
|
+
}
|
|
4031
|
+
if (msg.kind === "cap-call-out") {
|
|
4032
|
+
const out = msg;
|
|
4033
|
+
const input = {
|
|
4034
|
+
capName: out.capName,
|
|
4035
|
+
method: out.method,
|
|
4036
|
+
args: out.args,
|
|
4037
|
+
...out.deviceId !== void 0 ? { deviceId: out.deviceId } : {},
|
|
4038
|
+
...out.nodeId !== void 0 ? { nodeId: out.nodeId } : {}
|
|
4039
|
+
};
|
|
4040
|
+
if (((out.nodeId ?? extractNodeId(out.args)) === void 0 ? this.resolveChildId(out.capName, out.deviceId) : null) !== null) {
|
|
4041
|
+
if (!this.egressRoutedCaps.has(out.capName)) {
|
|
4042
|
+
this.egressRoutedCaps.add(out.capName);
|
|
4043
|
+
this.logger?.info("routed child egress over UDS", { capName: out.capName });
|
|
4044
|
+
}
|
|
4045
|
+
return this.callCap(input);
|
|
4046
|
+
}
|
|
4047
|
+
if (this.onUnownedCall !== void 0) return this.onUnownedCall(input);
|
|
4048
|
+
throw new Error(`${UDS_NO_ROUTE_PREFIX}: cap-call-out has no local provider for "${out.capName}" and no fallback`);
|
|
4049
|
+
}
|
|
4050
|
+
if (msg.kind === "readiness-request") return {
|
|
4051
|
+
kind: "readiness-snapshot",
|
|
4052
|
+
records: this.readinessHandler?.() ?? []
|
|
4053
|
+
};
|
|
4054
|
+
throw new Error(`unknown child request kind: ${msg.kind}`);
|
|
4067
4055
|
});
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
}
|
|
4072
|
-
|
|
4073
|
-
for (const nodeId of knownNodes) if (nodeId !== snap.hubNodeId && snap.nodeOnline(nodeId) && snap.nodeKnowsCap(nodeId, capName)) return {
|
|
4074
|
-
kind: "remote-moleculer",
|
|
4075
|
-
capName,
|
|
4076
|
-
nodeId
|
|
4077
|
-
};
|
|
4078
|
-
rejected.push({
|
|
4079
|
-
kind: "remote-moleculer",
|
|
4080
|
-
why: "no online remote node knows this cap"
|
|
4081
|
-
});
|
|
4082
|
-
throw new CapRouteError(capName, void 0, {
|
|
4083
|
-
reason: "no-provider",
|
|
4084
|
-
rejected
|
|
4085
|
-
});
|
|
4086
|
-
}
|
|
4056
|
+
channel.onClose(() => {
|
|
4057
|
+
if (childId !== null && this.children.delete(childId)) this.goneHandler(childId);
|
|
4058
|
+
});
|
|
4059
|
+
}
|
|
4060
|
+
};
|
|
4087
4061
|
//#endregion
|
|
4088
|
-
//#region src/kernel/
|
|
4089
|
-
/** Moleculer error `type` values meaning "the service is not (yet) routable". */
|
|
4090
|
-
var DISCOVERY_ERROR_TYPES = new Set(["SERVICE_NOT_FOUND", "SERVICE_NOT_AVAILABLE"]);
|
|
4091
|
-
/** Default ceiling for the discovery wait — fail-fast past this. */
|
|
4092
|
-
var DEFAULT_DISCOVERY_TIMEOUT_MS$1 = 3e4;
|
|
4093
|
-
function isDiscoveryError(err) {
|
|
4094
|
-
if (typeof err !== "object" || err === null) return false;
|
|
4095
|
-
const type = err.type;
|
|
4096
|
-
return typeof type === "string" && DISCOVERY_ERROR_TYPES.has(type);
|
|
4097
|
-
}
|
|
4062
|
+
//#region src/kernel/transport/local-child-client.ts
|
|
4098
4063
|
/**
|
|
4099
|
-
*
|
|
4100
|
-
*
|
|
4064
|
+
* Child side of the local UDS transport. Connects to its parent (hub or
|
|
4065
|
+
* agent), registers its cap manifest, and serves parent→child cap calls by
|
|
4066
|
+
* delegating to `dispatch`. The provider implementation lives in the child;
|
|
4067
|
+
* only routing keys + call arguments cross the wire.
|
|
4068
|
+
*
|
|
4069
|
+
* Additional channels beyond cap-call:
|
|
4070
|
+
* - `emitEvent` fire-and-forget event toward the parent
|
|
4071
|
+
* - `sendLog` fire-and-forget log entry toward the parent
|
|
4072
|
+
* - `requestReadinessSnapshot` request/response snapshot of readiness records
|
|
4073
|
+
* - `onEvent` register a handler for parent→child events
|
|
4074
|
+
*
|
|
4075
|
+
* Events and logs emitted before `start()` resolves are buffered and flushed
|
|
4076
|
+
* on connect (mirrors the `updateCaps`/`latestCaps` pattern).
|
|
4101
4077
|
*/
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4078
|
+
var LocalChildClient = class {
|
|
4079
|
+
options;
|
|
4080
|
+
client = null;
|
|
4081
|
+
channel = null;
|
|
4082
|
+
/**
|
|
4083
|
+
* The cap set `start()` will register, kept current by `updateCaps`. Native
|
|
4084
|
+
* device caps register on device-restore, which can race AHEAD of the UDS
|
|
4085
|
+
* connect — buffering here lets a pre-start `updateCaps` survive (start sends
|
|
4086
|
+
* the latest set) instead of being lost or throwing.
|
|
4087
|
+
*/
|
|
4088
|
+
latestCaps;
|
|
4089
|
+
/** Events and logs queued while the channel is not yet open. */
|
|
4090
|
+
pendingEmits = [];
|
|
4091
|
+
/** Handler for parent→child events. Registered via `onEvent`. */
|
|
4092
|
+
eventHandler = null;
|
|
4093
|
+
/**
|
|
4094
|
+
* Handler for parent→child addon-level calls (`routes` / `custom`).
|
|
4095
|
+
* Registered via `onAddonCall`. Resolves the loaded addon instance and
|
|
4096
|
+
* invokes its `addon-routes.getRoutes()` or its custom-action handler.
|
|
4097
|
+
* Replaces the per-addon Moleculer `getRoutes` / `custom.<action>` actions
|
|
4098
|
+
* removed in F1/F2. Only one handler is active at a time.
|
|
4099
|
+
*/
|
|
4100
|
+
addonCallHandler = null;
|
|
4101
|
+
/**
|
|
4102
|
+
* Handler for `set-log-level` messages pushed by the parent.
|
|
4103
|
+
* Registered via `onSetLogLevel`. The handler applies the new level to the
|
|
4104
|
+
* child's local Moleculer logger (mirrors `$node-mgmt.setLogLevel`).
|
|
4105
|
+
* E2: wired in `addon-runner.ts` to forward to the broker logger.
|
|
4106
|
+
*/
|
|
4107
|
+
setLogLevelHandler = null;
|
|
4108
|
+
/** Callbacks registered via `onConnected`. Fired on every (re)connect. */
|
|
4109
|
+
connectedHandlers = [];
|
|
4110
|
+
constructor(options) {
|
|
4111
|
+
this.options = options;
|
|
4112
|
+
this.latestCaps = options.caps;
|
|
4109
4113
|
}
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
this.
|
|
4137
|
-
|
|
4114
|
+
/**
|
|
4115
|
+
* Register a callback that fires each time the client successfully connects
|
|
4116
|
+
* (or reconnects) to its parent. Multiple handlers may be registered; all
|
|
4117
|
+
* are called in registration order. Used by readiness-context in UDS mode
|
|
4118
|
+
* to trigger a snapshot hydrate on connect/reconnect.
|
|
4119
|
+
*/
|
|
4120
|
+
onConnected(handler) {
|
|
4121
|
+
this.connectedHandlers.push(handler);
|
|
4122
|
+
}
|
|
4123
|
+
/**
|
|
4124
|
+
* Register a handler for events pushed from the parent to this child.
|
|
4125
|
+
* Must be called before `start()` to avoid missing early events (though
|
|
4126
|
+
* registration after start is also safe for events not yet delivered).
|
|
4127
|
+
* Replaces any previously registered handler.
|
|
4128
|
+
*/
|
|
4129
|
+
onEvent(handler) {
|
|
4130
|
+
this.eventHandler = handler;
|
|
4131
|
+
}
|
|
4132
|
+
/**
|
|
4133
|
+
* Register the handler invoked when the parent sends an `addon-call` (the
|
|
4134
|
+
* addon-level routes / custom-action plane). The addon-runner wires this to
|
|
4135
|
+
* resolve the loaded addon by id and dispatch to its `getRoutes()` or its
|
|
4136
|
+
* custom-action handler. Safe to register before or after `start()`;
|
|
4137
|
+
* replaces any prior handler.
|
|
4138
|
+
*/
|
|
4139
|
+
onAddonCall(handler) {
|
|
4140
|
+
this.addonCallHandler = handler;
|
|
4141
|
+
}
|
|
4142
|
+
/**
|
|
4143
|
+
* E2: Register a handler invoked when the parent sends a `set-log-level`
|
|
4144
|
+
* message to this child. The handler should forward the new level to the
|
|
4145
|
+
* child's local Moleculer broker logger (mirrors `$node-mgmt.setLogLevel`).
|
|
4146
|
+
* Safe to register before or after `start()`. Replaces any prior handler.
|
|
4147
|
+
*/
|
|
4148
|
+
onSetLogLevel(handler) {
|
|
4149
|
+
this.setLogLevelHandler = handler;
|
|
4150
|
+
}
|
|
4151
|
+
/**
|
|
4152
|
+
* True once `start()` has successfully connected and registered. Used by
|
|
4153
|
+
* callers (event-bus/logger bridges) to know the channel is live.
|
|
4154
|
+
*/
|
|
4155
|
+
get isConnected() {
|
|
4156
|
+
return this.channel !== null;
|
|
4138
4157
|
}
|
|
4139
|
-
|
|
4140
|
-
|
|
4158
|
+
async start() {
|
|
4159
|
+
if (this.client !== null) throw new Error("LocalChildClient: already started — call close() first");
|
|
4160
|
+
const client = createLocalTransport().createClient(this.options.nodeId);
|
|
4161
|
+
const channel = await client.connect();
|
|
4162
|
+
channel.onRequest(async (body) => {
|
|
4163
|
+
const msg = body;
|
|
4164
|
+
if (msg.kind === "cap-call") return this.options.dispatch({
|
|
4165
|
+
capName: msg.capName,
|
|
4166
|
+
method: msg.method,
|
|
4167
|
+
args: msg.args,
|
|
4168
|
+
...msg.deviceId !== void 0 ? { deviceId: msg.deviceId } : {}
|
|
4169
|
+
});
|
|
4170
|
+
if (msg.kind === "addon-call") {
|
|
4171
|
+
if (this.addonCallHandler === null) throw new Error(`LocalChildClient: addon-call for "${msg.addonId}" arrived but no onAddonCall handler is registered`);
|
|
4172
|
+
return this.addonCallHandler({
|
|
4173
|
+
addonId: msg.addonId,
|
|
4174
|
+
target: msg.target,
|
|
4175
|
+
...msg.action !== void 0 ? { action: msg.action } : {},
|
|
4176
|
+
...msg.method !== void 0 ? { method: msg.method } : {},
|
|
4177
|
+
...msg.args !== void 0 ? { args: msg.args } : {}
|
|
4178
|
+
});
|
|
4179
|
+
}
|
|
4180
|
+
throw new Error(`unknown parent request kind: ${msg.kind}`);
|
|
4181
|
+
});
|
|
4182
|
+
channel.onEvent((body) => {
|
|
4183
|
+
const msg = body;
|
|
4184
|
+
if (msg.kind === "event") {
|
|
4185
|
+
this.eventHandler?.(msg.event);
|
|
4186
|
+
return;
|
|
4187
|
+
}
|
|
4188
|
+
if (msg.kind === "set-log-level") this.setLogLevelHandler?.(msg.level);
|
|
4189
|
+
});
|
|
4190
|
+
const register = {
|
|
4191
|
+
kind: "register",
|
|
4192
|
+
childId: this.options.childId,
|
|
4193
|
+
caps: this.latestCaps
|
|
4194
|
+
};
|
|
4195
|
+
try {
|
|
4196
|
+
await channel.request(register);
|
|
4197
|
+
} catch (err) {
|
|
4198
|
+
await client.close();
|
|
4199
|
+
throw err;
|
|
4200
|
+
}
|
|
4201
|
+
this.client = client;
|
|
4202
|
+
this.channel = channel;
|
|
4203
|
+
this.flushPending(channel);
|
|
4204
|
+
for (const cb of this.connectedHandlers) cb();
|
|
4205
|
+
}
|
|
4206
|
+
/** Flush buffered pre-start emits over the now-open channel. */
|
|
4207
|
+
flushPending(channel) {
|
|
4208
|
+
for (const item of this.pendingEmits) channel.emit(item.msg);
|
|
4209
|
+
this.pendingEmits.length = 0;
|
|
4141
4210
|
}
|
|
4142
4211
|
/**
|
|
4143
|
-
*
|
|
4144
|
-
*
|
|
4145
|
-
*
|
|
4146
|
-
* `resolveCapRoute` gives Priority 1 to `hub-in-process`: when an in-hub
|
|
4147
|
-
* provider (e.g. a wrapper) is registered for the cap, the route always
|
|
4148
|
-
* classifies as `hub-in-process` and the hub-local-uds NATIVE child is never
|
|
4149
|
-
* reached. That is correct for the generic dispatch path (the wrapper is the
|
|
4150
|
-
* active provider), but WRONG for the native-cap fallback
|
|
4151
|
-
* (`setNativeFallback`), whose contract is to reach the NATIVE provider in
|
|
4152
|
-
* the forked vendor child so a wrapper can delegate to it. A wrapper cap
|
|
4153
|
-
* with a forked native (today: `snapshot`) otherwise resolves to the wrapper
|
|
4154
|
-
* itself, the native is never invoked, and the wrapper silently falls
|
|
4155
|
-
* through to its secondary strategy.
|
|
4212
|
+
* Re-send the cap manifest to the parent, atomically replacing the child's
|
|
4213
|
+
* registered descriptor set. Call this after device-restore so newly
|
|
4214
|
+
* registered native (device-scoped) caps become routable over UDS.
|
|
4156
4215
|
*
|
|
4157
|
-
*
|
|
4158
|
-
* (
|
|
4159
|
-
*
|
|
4160
|
-
* Returns null when no hub-local child owns `(capName, deviceId)` — the
|
|
4161
|
-
* caller then falls through to its remote-resolution branch.
|
|
4216
|
+
* Safe to call before `start()`: the new set is buffered and `start()` sends
|
|
4217
|
+
* it (device-restore can race ahead of the UDS connect). After `start()`,
|
|
4218
|
+
* the set is sent immediately.
|
|
4162
4219
|
*/
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
childId
|
|
4220
|
+
async updateCaps(caps) {
|
|
4221
|
+
this.latestCaps = caps;
|
|
4222
|
+
if (this.channel === null) return;
|
|
4223
|
+
const register = {
|
|
4224
|
+
kind: "register",
|
|
4225
|
+
childId: this.options.childId,
|
|
4226
|
+
caps
|
|
4171
4227
|
};
|
|
4228
|
+
await this.channel.request(register);
|
|
4172
4229
|
}
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
}
|
|
4230
|
+
/**
|
|
4231
|
+
* Fire-and-forget: send a system event to the parent for forwarding to the
|
|
4232
|
+
* hub event bus. Safe to call before `start()` — events are buffered and
|
|
4233
|
+
* flushed on connect.
|
|
4234
|
+
*/
|
|
4235
|
+
emitEvent(event) {
|
|
4236
|
+
const msg = {
|
|
4237
|
+
kind: "event",
|
|
4238
|
+
event
|
|
4239
|
+
};
|
|
4240
|
+
if (this.channel !== null) this.channel.emit(msg);
|
|
4241
|
+
else this.pendingEmits.push({
|
|
4242
|
+
kind: "event",
|
|
4243
|
+
msg
|
|
4244
|
+
});
|
|
4189
4245
|
}
|
|
4190
4246
|
/**
|
|
4191
|
-
*
|
|
4192
|
-
*
|
|
4247
|
+
* Fire-and-forget: send a structured log entry to the parent. Safe to call
|
|
4248
|
+
* before `start()` — log entries are buffered and flushed on connect.
|
|
4193
4249
|
*/
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
}]
|
|
4205
|
-
});
|
|
4206
|
-
const deviceId = extractDeviceId(args);
|
|
4207
|
-
const input = {
|
|
4208
|
-
capName: route.capName,
|
|
4209
|
-
method,
|
|
4210
|
-
args,
|
|
4211
|
-
...deviceId !== void 0 ? { deviceId } : {}
|
|
4212
|
-
};
|
|
4213
|
-
return registry.callCapOnChild(route.childId, input);
|
|
4214
|
-
}
|
|
4215
|
-
case "remote-moleculer": {
|
|
4216
|
-
const addonId = this.nodeAuthority.getAddonId(route.nodeId, route.capName);
|
|
4217
|
-
if (addonId === null) throw new CapRouteError(route.capName, method, {
|
|
4218
|
-
reason: "no-provider",
|
|
4219
|
-
nodeId: route.nodeId,
|
|
4220
|
-
rejected: [{
|
|
4221
|
-
kind: "remote-moleculer",
|
|
4222
|
-
why: `no addonId known for ${route.nodeId}/${route.capName}`
|
|
4223
|
-
}]
|
|
4224
|
-
});
|
|
4225
|
-
const deviceId = extractDeviceId(args);
|
|
4226
|
-
const isNative = this.nodeAuthority.isNativeCap(route.nodeId, route.capName, deviceId);
|
|
4227
|
-
const action = capActionName(addonId, route.capName, method, isNative);
|
|
4228
|
-
return callWithServiceDiscovery(this.broker, addonId, action, args, {
|
|
4229
|
-
nodeID: route.nodeId,
|
|
4230
|
-
timeout: REMOTE_CALL_TIMEOUT_MS
|
|
4231
|
-
});
|
|
4232
|
-
}
|
|
4233
|
-
case "agent-child-forward": {
|
|
4234
|
-
const deviceId = extractDeviceId(args);
|
|
4235
|
-
const params = {
|
|
4236
|
-
capName: route.capName,
|
|
4237
|
-
method,
|
|
4238
|
-
args,
|
|
4239
|
-
...route.childId !== void 0 ? { childId: route.childId } : {},
|
|
4240
|
-
...deviceId !== void 0 ? { deviceId } : {}
|
|
4241
|
-
};
|
|
4242
|
-
return callWithServiceDiscovery(this.broker, AGENT_CAP_FWD_SERVICE, AGENT_CAP_FWD_ACTION, params, {
|
|
4243
|
-
nodeID: route.agentNodeId,
|
|
4244
|
-
timeout: REMOTE_CALL_TIMEOUT_MS
|
|
4245
|
-
});
|
|
4246
|
-
}
|
|
4247
|
-
}
|
|
4250
|
+
sendLog(entry) {
|
|
4251
|
+
const msg = {
|
|
4252
|
+
kind: "log",
|
|
4253
|
+
...entry
|
|
4254
|
+
};
|
|
4255
|
+
if (this.channel !== null) this.channel.emit(msg);
|
|
4256
|
+
else this.pendingEmits.push({
|
|
4257
|
+
kind: "log",
|
|
4258
|
+
msg
|
|
4259
|
+
});
|
|
4248
4260
|
}
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4261
|
+
/**
|
|
4262
|
+
* Request the current readiness snapshot from the parent. Returns the
|
|
4263
|
+
* authoritative set of `IReadinessRegistryRecord` entries the hub holds.
|
|
4264
|
+
* Requires `start()` to have been called; throws with a clear message if not.
|
|
4265
|
+
*/
|
|
4266
|
+
async requestReadinessSnapshot() {
|
|
4267
|
+
if (this.channel === null) throw new Error("LocalChildClient: requestReadinessSnapshot called before start()");
|
|
4268
|
+
return (await this.channel.request({ kind: "readiness-request" })).records;
|
|
4269
|
+
}
|
|
4270
|
+
/**
|
|
4271
|
+
* Ask the parent to execute a cap call this child does NOT own. The parent
|
|
4272
|
+
* routes to the owning local sibling over UDS, or — if no sibling owns it —
|
|
4273
|
+
* to its `onUnownedCall` fallback (the cluster CapabilityRegistry in
|
|
4274
|
+
* production). Throws if called before `start()`.
|
|
4275
|
+
*/
|
|
4276
|
+
async callOut(input) {
|
|
4277
|
+
if (this.channel === null) throw new Error("LocalChildClient: callOut before start");
|
|
4278
|
+
const msg = {
|
|
4279
|
+
kind: "cap-call-out",
|
|
4280
|
+
capName: input.capName,
|
|
4281
|
+
method: input.method,
|
|
4282
|
+
args: input.args,
|
|
4283
|
+
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
|
|
4284
|
+
...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {}
|
|
4264
4285
|
};
|
|
4286
|
+
return this.channel.request(msg);
|
|
4265
4287
|
}
|
|
4266
|
-
/**
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
case "remote-moleculer": return route.nodeId;
|
|
4272
|
-
case "agent-child-forward": return route.agentNodeId;
|
|
4273
|
-
}
|
|
4288
|
+
/** Disconnect from the parent. Safe to call before `start()` (no-op) and idempotent. */
|
|
4289
|
+
async close() {
|
|
4290
|
+
await this.client?.close();
|
|
4291
|
+
this.client = null;
|
|
4292
|
+
this.channel = null;
|
|
4274
4293
|
}
|
|
4275
4294
|
};
|
|
4276
4295
|
//#endregion
|
|
@@ -4586,10 +4605,11 @@ var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
4586
4605
|
function createParentUnownedCallHandler(deps) {
|
|
4587
4606
|
return async (input) => {
|
|
4588
4607
|
const deviceId = input.deviceId ?? extractDeviceId(input.args);
|
|
4608
|
+
const nodeId = input.nodeId ?? extractNodeId(input.args);
|
|
4589
4609
|
const resolver = deps.getResolver();
|
|
4590
4610
|
if (resolver !== null) try {
|
|
4591
4611
|
const route = resolver.resolveCapRoute(input.capName, {
|
|
4592
|
-
...
|
|
4612
|
+
...nodeId !== void 0 ? { nodeId } : {},
|
|
4593
4613
|
...deviceId !== void 0 ? { deviceId } : {}
|
|
4594
4614
|
});
|
|
4595
4615
|
return await resolver.dispatch(route, input.method, input.args);
|
|
@@ -4666,10 +4686,15 @@ function parsePath(path) {
|
|
|
4666
4686
|
* on a forked worker that has a co-located pipeline-executor.
|
|
4667
4687
|
*/
|
|
4668
4688
|
function localProviderLink(resolver, observerOpts) {
|
|
4689
|
+
const localBase = resolver.localNodeId !== void 0 ? resolver.localNodeId.split("/")[0] ?? resolver.localNodeId : void 0;
|
|
4669
4690
|
return () => {
|
|
4670
4691
|
return ({ op, next }) => {
|
|
4671
4692
|
const parsed = parsePath(op.path);
|
|
4672
4693
|
if (!parsed) return next(op);
|
|
4694
|
+
if (localBase !== void 0) {
|
|
4695
|
+
const pinnedNodeId = readNodePin(op.context);
|
|
4696
|
+
if (pinnedNodeId !== void 0 && pinnedNodeId !== localBase) return next(op);
|
|
4697
|
+
}
|
|
4673
4698
|
const provider = resolver.getByName(parsed.capName);
|
|
4674
4699
|
if (!provider || typeof provider !== "object") return next(op);
|
|
4675
4700
|
const fn = Reflect.get(provider, parsed.method);
|
|
@@ -6218,15 +6243,18 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
|
|
|
6218
6243
|
fs.mkdirSync(addonDataDir, { recursive: true });
|
|
6219
6244
|
if (runtime.mode === "broker") ensureBindingSubscription(runtime.broker);
|
|
6220
6245
|
const regs = getRegistries(nodeId);
|
|
6221
|
-
const workerResolver = {
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
const
|
|
6225
|
-
if (
|
|
6246
|
+
const workerResolver = {
|
|
6247
|
+
localNodeId: nodeId,
|
|
6248
|
+
getByName(capabilityName) {
|
|
6249
|
+
const preferred = regs.preferred.get(capabilityName);
|
|
6250
|
+
if (preferred) {
|
|
6251
|
+
const provider = regs.byAddonId.get(capabilityName)?.get(preferred);
|
|
6252
|
+
if (provider) return provider;
|
|
6253
|
+
}
|
|
6254
|
+
const providers = regs.providers.get(capabilityName);
|
|
6255
|
+
return (providers && providers.length > 0 ? providers[0] : void 0) ?? null;
|
|
6226
6256
|
}
|
|
6227
|
-
|
|
6228
|
-
return (providers && providers.length > 0 ? providers[0] : void 0) ?? null;
|
|
6229
|
-
} };
|
|
6257
|
+
};
|
|
6230
6258
|
const usageRegistry = getCapUsageRegistry();
|
|
6231
6259
|
const observerOpts = {
|
|
6232
6260
|
callerAddonId: addonId,
|
|
@@ -6546,4 +6574,4 @@ async function installManifestPythonDeps(declaration, addonDir, deps, logger) {
|
|
|
6546
6574
|
await deps.installPythonRequirements(reqAbs);
|
|
6547
6575
|
}
|
|
6548
6576
|
//#endregion
|
|
6549
|
-
export { setWorkerNativeCapsChangeListener as $, createUdsLoggerWithControl as A, createLocalTransport as B, ipcParentLink as C, createUdsEventBus as D, createUdsEventBridge as E,
|
|
6577
|
+
export { setWorkerNativeCapsChangeListener as $, createUdsLoggerWithControl as A, createLocalTransport as B, ipcParentLink as C, createUdsEventBus as D, createUdsEventBridge as E, AGENT_CAP_FWD_SERVICE as F, FrameDecoder as G, UdsLocalTransportServer as H, CapRouteResolver as I, buildUdsNativeCapProxy as J, encodeFrame as K, CapRouteError as L, LocalChildRegistry as M, UDS_NO_ROUTE_PREFIX as N, udsChildLogToWorkerEntry as O, AGENT_CAP_FWD_ACTION as P, mountNativeCapService as Q, classifyCapRoute as R, ipcChildLink as S, createParentUnownedCallHandler as T, SocketChannel as U, UdsLocalTransportClient as V, localEndpointPath as W, getWorkerNativeCapProvider as X, createBrokerDeviceManagerApi as Y, getWorkerNativeCapSnapshot as Z, __resetCapUsageRegistryForTests as _, getWorkerDeviceRegistry as a, capBareAction as at, brokerTransportLink as b, setHubConnected as c, deserializeTypedArrays as ct, registerEventBusService as d, CapabilityHandle as dt, createAddonService as et, AddonDepsManager as f, CapabilityUnavailableError as ft, CapUsageRegistry as g, createHwAccelService as h, createUdsAddonContext as i, capActionSuffix as it, LocalChildClient as j, createUdsLogger as k, EVENT_TOPIC_PREFIX as l, serializeTypedArrays as lt, resolveHwAccel as m, resolveAddonClass as mt, adaptBrokerToCluster as n, NATIVE_PROVIDER_SERVICE_INFIX as nt, getOrInitReadinessRegistry as o, capServiceName as ot, createKernelHwAccel as p, installManifestNativeDeps as pt, buildNativeCapProxy as q, createAddonContext as r, capActionName as rt, getOrInitReadinessRegistryForClient as s, parseCapAction as st, installManifestPythonDeps as t, validateProviderRegistrations as tt, getBrokerEventBus as u, DeviceRegistry as ut, getCapUsageRegistry as v, localProviderLink as w, buildLinkChain as x, brokerCallForCap as y, callWithServiceDiscovery as z };
|