@camstack/server 1.1.21 → 1.1.23

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.
@@ -16,6 +16,7 @@
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.createNodeCapAuthority = createNodeCapAuthority;
18
18
  exports.createInProcessProviderLookup = createInProcessProviderLookup;
19
+ exports.createHubResidentProviderLookup = createHubResidentProviderLookup;
19
20
  /**
20
21
  * Build a NodeCapAuthority backed by a HubNodeRegistry.
21
22
  *
@@ -107,16 +108,42 @@ function createInProcessProviderLookup(capabilityService) {
107
108
  capabilityService.getSingleton(capName);
108
109
  if (provider === null || provider === undefined)
109
110
  return null;
110
- const ref = {
111
- invoke: (method, args) => {
112
- const fn = provider[method];
113
- if (typeof fn !== 'function') {
114
- return Promise.reject(new Error(`method "${method}" not found on cap "${capName}"`));
115
- }
116
- const result = fn.call(provider, args);
117
- return Promise.resolve(result);
118
- },
119
- };
120
- return ref;
111
+ return buildProviderRef(capName, provider);
112
+ };
113
+ }
114
+ /**
115
+ * NODE-ACCURATE variant of {@link createInProcessProviderLookup}: resolves a
116
+ * provider ONLY when one is physically resident on the hub node
117
+ * (`getSingletonForNode(cap, 'hub')`) — no fallback to the cluster-elected
118
+ * ACTIVE singleton.
119
+ *
120
+ * Rationale: `getSingleton(cap)` returns the elected provider OBJECT, which
121
+ * for a remote election is a Moleculer-proxy registered in the hub registry.
122
+ * That fallback is correct for UNPINNED singleton dispatch (election
123
+ * adherence) but must never satisfy an explicit `nodePin('hub')` — the
124
+ * "hub-in-process" route would silently execute on another node (the shm
125
+ * frame-plane cross-node decoder-session storm: a `decoder` call pinned to
126
+ * the hub landed on a remote agent whenever the hub's decoder runner was
127
+ * circuit-broken). Wired into `CapRouteResolver.hubResidentProviders`.
128
+ */
129
+ function createHubResidentProviderLookup(capabilityService) {
130
+ return (capName) => {
131
+ const provider = capabilityService.getSingletonForNode?.(capName, 'hub');
132
+ if (provider === null || provider === undefined)
133
+ return null;
134
+ return buildProviderRef(capName, provider);
135
+ };
136
+ }
137
+ /** Shared cast-free InProcessProviderRef builder over a provider object. */
138
+ function buildProviderRef(capName, provider) {
139
+ return {
140
+ invoke: (method, args) => {
141
+ const fn = provider[method];
142
+ if (typeof fn !== 'function') {
143
+ return Promise.reject(new Error(`method "${method}" not found on cap "${capName}"`));
144
+ }
145
+ const result = fn.call(provider, args);
146
+ return Promise.resolve(result);
147
+ },
121
148
  };
122
149
  }
@@ -2,12 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MoleculerService = void 0;
4
4
  exports.buildChildUdsManifest = buildChildUdsManifest;
5
+ const node_crypto_1 = require("node:crypto");
5
6
  const system_1 = require("@camstack/system");
7
+ const types_1 = require("@camstack/types");
6
8
  const cap_router_runtime_js_1 = require("../../api/trpc/cap-router-runtime.js");
7
9
  const cap_call_fn_js_1 = require("./cap-call-fn.js");
8
10
  const cap_route_authority_js_1 = require("./cap-route-authority.js");
9
- const types_1 = require("@camstack/types");
10
- const node_crypto_1 = require("node:crypto");
11
11
  class MoleculerService {
12
12
  eventBus;
13
13
  config;
@@ -411,6 +411,12 @@ class MoleculerService {
411
411
  null,
412
412
  }),
413
413
  inProcessProviders: (0, cap_route_authority_js_1.createInProcessProviderLookup)(this.capabilityService),
414
+ // Node-accurate lookup for EXPLICITLY hub-pinned calls: unlike the
415
+ // lookup above (which falls back to the cluster-elected ACTIVE
416
+ // singleton — possibly a remote proxy), this resolves only providers
417
+ // physically resident on the hub, so a `nodePin('hub')` fails precisely
418
+ // instead of silently executing on another node.
419
+ hubResidentProviders: (0, cap_route_authority_js_1.createHubResidentProviderLookup)(this.capabilityService),
414
420
  // Per-cap-method RPC timeout override (e.g. model-convert.convert, which
415
421
  // runs for minutes) — read from the cap definition's declared timeoutMs.
416
422
  capTimeoutMs: (capName, method) => this.capabilityService.getRegistry()?.getDefinition(capName)?.methods?.[method]?.timeoutMs,
@@ -436,7 +442,20 @@ class MoleculerService {
436
442
  registry: this.localChildRegistry,
437
443
  parentBus: this.eventBus,
438
444
  parentNodeId: hubNodeId,
445
+ // D2: relay via pass-through so the bridge's wildcard is not counted as
446
+ // local interest (uniform with the agent; harmless here since the hub
447
+ // retains and always accepts inbound cross-node events anyway).
448
+ subscribePassthrough: (handler) => (0, system_1.subscribePassthrough)(this.broker, handler),
439
449
  });
450
+ // D2: expose the hub's aggregate child interest to its `$event-bus`
451
+ // inbound gate for uniformity with the agent. NOTE: the hub main process
452
+ // opts into `retainRecent` (it serves `getRecent` + the admin-ui live
453
+ // event stream), so `crossNodeAcceptsCategory` always accepts every
454
+ // inbound category here — the hub never filters agent-origin events. This
455
+ // wiring is therefore harmless-but-uniform; the real efficiency win is on
456
+ // agents (non-retaining nodes).
457
+ const hubChildRegistry = this.localChildRegistry;
458
+ (0, system_1.setNodeEventInterest)(this.broker, () => hubChildRegistry.aggregateEventInterest());
440
459
  }
441
460
  }
442
461
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.1.21",
3
+ "version": "1.1.23",
4
4
  "private": false,
5
5
  "files": [
6
6
  "dist",