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