@camstack/server 0.2.2 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{src/agent-status-page.ts → dist/agent-status-page.js} +30 -45
- package/dist/api/addon-upload.js +441 -0
- package/dist/api/addons-custom.router.js +91 -0
- package/dist/api/auth-whoami.js +55 -0
- package/dist/api/bridge-addons.router.js +109 -0
- package/dist/api/capabilities.router.js +229 -0
- package/dist/api/core/addon-settings.router.js +117 -0
- package/dist/api/core/agents.router.js +73 -0
- package/dist/api/core/auth.router.js +286 -0
- package/dist/api/core/bulk-update-coordinator.js +229 -0
- package/dist/api/core/cap-providers.js +1124 -0
- package/dist/api/core/capabilities.router.js +138 -0
- package/dist/api/core/collection-preference.js +17 -0
- package/dist/api/core/event-bus-proxy.router.js +45 -0
- package/dist/api/core/hwaccel.router.js +91 -0
- package/dist/api/core/live-events.router.js +61 -0
- package/dist/api/core/logs.router.js +172 -0
- package/dist/api/core/notifications.router.js +67 -0
- package/dist/api/core/repl.router.js +35 -0
- package/dist/api/core/settings-backend.router.js +121 -0
- package/dist/api/core/stream-probe.router.js +58 -0
- package/dist/api/core/system-events.router.js +100 -0
- package/dist/api/health/health.routes.js +68 -0
- package/{src/api/oauth2/consent-page.ts → dist/api/oauth2/consent-page.js} +11 -20
- package/dist/api/oauth2/oauth2-routes.js +219 -0
- package/dist/api/trpc/cap-mount-helpers.js +194 -0
- package/dist/api/trpc/cap-route-error-formatter.js +133 -0
- package/dist/api/trpc/client-ip.js +147 -0
- package/dist/api/trpc/core-cap-bridge.js +115 -0
- package/dist/api/trpc/generated-cap-mounts.js +388 -0
- package/dist/api/trpc/generated-cap-routers.js +7635 -0
- package/dist/api/trpc/scope-access.js +93 -0
- package/dist/api/trpc/trpc.context.js +184 -0
- package/dist/api/trpc/trpc.middleware.js +139 -0
- package/dist/api/trpc/trpc.router.js +188 -0
- package/dist/auth/session-cookie.js +47 -0
- package/dist/boot/boot-config.js +241 -0
- package/dist/boot/integration-id-backfill.js +76 -0
- package/dist/boot/post-boot.service.js +85 -0
- package/dist/core/addon/addon-call-gateway.js +99 -0
- package/dist/core/addon/addon-package.service.js +1560 -0
- package/dist/core/addon/addon-registry.service.js +2739 -0
- package/{src/core/addon/addon-row-manifest.ts → dist/core/addon/addon-row-manifest.js} +5 -5
- package/dist/core/addon/addon-search.service.js +62 -0
- package/dist/core/addon/addon-settings-provider.js +102 -0
- package/dist/core/addon/addon.tokens.js +5 -0
- package/dist/core/addon-bridge/addon-bridge.service.js +145 -0
- package/dist/core/addon-pages/addon-pages.service.js +107 -0
- package/dist/core/addon-widgets/addon-widgets.service.js +120 -0
- package/dist/core/agent/agent-registry.service.js +477 -0
- package/dist/core/auth/auth.service.js +10 -0
- package/dist/core/capability/capability.service.js +58 -0
- package/dist/core/config/config.schema.js +7 -0
- package/dist/core/config/config.service.js +10 -0
- package/dist/core/events/event-bus.service.js +83 -0
- package/dist/core/feature/feature.service.js +10 -0
- package/dist/core/lifecycle/lifecycle-state-machine.js +6 -0
- package/dist/core/logging/log-ring-buffer.js +6 -0
- package/dist/core/logging/logging.service.js +130 -0
- package/dist/core/logging/scoped-logger.js +6 -0
- package/dist/core/moleculer/cap-call-fn.js +50 -0
- package/dist/core/moleculer/cap-route-authority.js +122 -0
- package/dist/core/moleculer/moleculer.service.js +898 -0
- package/dist/core/network/network-quality.service.js +7 -0
- package/dist/core/notification/notification-wrapper.service.js +33 -0
- package/dist/core/notification/toast-wrapper.service.js +25 -0
- package/dist/core/provider/provider.tokens.js +4 -0
- package/dist/core/repl/repl-engine.service.js +140 -0
- package/dist/core/storage/fs-storage-backend.js +6 -0
- package/dist/core/storage/storage-location-manager.js +6 -0
- package/dist/core/storage/storage.service.js +7 -0
- package/dist/core/streaming/stream-probe.service.js +209 -0
- package/dist/core/topology/topology-emitter.service.js +106 -0
- package/dist/launcher.js +325 -0
- package/dist/main.js +1098 -0
- package/dist/manual-boot.js +227 -0
- package/package.json +5 -1
- package/src/__tests__/addon-install-e2e.test.ts +0 -74
- package/src/__tests__/addon-pages-e2e.test.ts +0 -200
- package/src/__tests__/addon-route-session.test.ts +0 -17
- package/src/__tests__/addon-settings-router.spec.ts +0 -67
- package/src/__tests__/addon-upload.spec.ts +0 -475
- package/src/__tests__/agent-registry.spec.ts +0 -179
- package/src/__tests__/agent-status-page.spec.ts +0 -82
- package/src/__tests__/auth-session-cookie.test.ts +0 -48
- package/src/__tests__/bulk-update-coordinator.spec.ts +0 -303
- package/src/__tests__/cap-ownership-authority.spec.ts +0 -431
- package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +0 -206
- package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +0 -37
- package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +0 -110
- package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +0 -292
- package/src/__tests__/cap-providers-bulk-update.spec.ts +0 -408
- package/src/__tests__/cap-route-adapter.spec.ts +0 -302
- package/src/__tests__/cap-routers/_meta.spec.ts +0 -199
- package/src/__tests__/cap-routers/addon-settings.router.spec.ts +0 -115
- package/src/__tests__/cap-routers/broker-routing.router.spec.ts +0 -177
- package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +0 -125
- package/src/__tests__/cap-routers/capabilities-node.spec.ts +0 -68
- package/src/__tests__/cap-routers/device-link-overlay.spec.ts +0 -137
- package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +0 -194
- package/src/__tests__/cap-routers/harness.ts +0 -163
- package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +0 -133
- package/src/__tests__/cap-routers/null-provider-guard.spec.ts +0 -64
- package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +0 -159
- package/src/__tests__/cap-routers/settings-store.router.spec.ts +0 -291
- package/src/__tests__/capability-e2e.test.ts +0 -384
- package/src/__tests__/cli-e2e.test.ts +0 -150
- package/src/__tests__/core-cap-bridge.spec.ts +0 -91
- package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +0 -40
- package/src/__tests__/device-settings-contribution-dispatch.spec.ts +0 -280
- package/src/__tests__/embedded-deps-e2e.test.ts +0 -125
- package/src/__tests__/event-bus-proxy-router.spec.ts +0 -75
- package/src/__tests__/fixtures/mock-analysis-addon-a.ts +0 -37
- package/src/__tests__/fixtures/mock-analysis-addon-b.ts +0 -37
- package/src/__tests__/fixtures/mock-log-addon.ts +0 -37
- package/src/__tests__/fixtures/mock-storage-addon.ts +0 -40
- package/src/__tests__/framework-allowlist.spec.ts +0 -96
- package/src/__tests__/framework-installer-defer-restart.spec.ts +0 -165
- package/src/__tests__/https-e2e.test.ts +0 -124
- package/src/__tests__/lifecycle-e2e.test.ts +0 -189
- package/src/__tests__/live-events-subscription.spec.ts +0 -149
- package/src/__tests__/moleculer/uds-readiness.spec.ts +0 -150
- package/src/__tests__/moleculer/uds-topology.spec.ts +0 -418
- package/src/__tests__/moleculer/uds-unowned-call.spec.ts +0 -383
- package/src/__tests__/moleculer-register-node-idempotency.spec.ts +0 -273
- package/src/__tests__/native-cap-route.spec.ts +0 -427
- package/src/__tests__/oauth2-account-linking.spec.ts +0 -867
- package/src/__tests__/post-boot-restart.spec.ts +0 -161
- package/src/__tests__/singleton-contention.test.ts +0 -499
- package/src/__tests__/streaming-diagnostic.test.ts +0 -615
- package/src/__tests__/streaming-scale.test.ts +0 -314
- package/src/__tests__/uds-addon-call-wiring.spec.ts +0 -242
- package/src/__tests__/uds-log-ingest.spec.ts +0 -183
- package/src/api/__tests__/addons-custom.spec.ts +0 -148
- package/src/api/__tests__/capabilities.router.test.ts +0 -56
- package/src/api/addon-upload.ts +0 -529
- package/src/api/addons-custom.router.ts +0 -101
- package/src/api/auth-whoami.ts +0 -101
- package/src/api/bridge-addons.router.ts +0 -122
- package/src/api/capabilities.router.ts +0 -265
- package/src/api/core/__tests__/auth-router-totp.spec.ts +0 -297
- package/src/api/core/__tests__/integration-markers.spec.ts +0 -10
- package/src/api/core/addon-settings.router.ts +0 -127
- package/src/api/core/agents.router.ts +0 -86
- package/src/api/core/auth.router.ts +0 -322
- package/src/api/core/bulk-update-coordinator.ts +0 -305
- package/src/api/core/cap-providers.ts +0 -1339
- package/src/api/core/capabilities.router.ts +0 -149
- package/src/api/core/collection-preference.ts +0 -40
- package/src/api/core/event-bus-proxy.router.ts +0 -45
- package/src/api/core/hwaccel.router.ts +0 -108
- package/src/api/core/live-events.router.ts +0 -67
- package/src/api/core/logs.router.ts +0 -195
- package/src/api/core/notifications.router.ts +0 -66
- package/src/api/core/repl.router.ts +0 -39
- package/src/api/core/settings-backend.router.ts +0 -140
- package/src/api/core/stream-probe.router.ts +0 -57
- package/src/api/core/system-events.router.ts +0 -125
- package/src/api/health/health.routes.ts +0 -117
- package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +0 -62
- package/src/api/oauth2/oauth2-routes.ts +0 -281
- package/src/api/trpc/__tests__/client-ip.spec.ts +0 -146
- package/src/api/trpc/__tests__/scope-access-device.spec.ts +0 -268
- package/src/api/trpc/__tests__/scope-access.spec.ts +0 -102
- package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +0 -136
- package/src/api/trpc/cap-mount-helpers.ts +0 -245
- package/src/api/trpc/cap-route-error-formatter.ts +0 -171
- package/src/api/trpc/client-ip.ts +0 -147
- package/src/api/trpc/core-cap-bridge.ts +0 -154
- package/src/api/trpc/generated-cap-mounts.ts +0 -1240
- package/src/api/trpc/generated-cap-routers.ts +0 -11523
- package/src/api/trpc/scope-access.ts +0 -110
- package/src/api/trpc/trpc.context.ts +0 -258
- package/src/api/trpc/trpc.middleware.ts +0 -146
- package/src/api/trpc/trpc.router.ts +0 -389
- package/src/auth/session-cookie.ts +0 -54
- package/src/boot/__tests__/integration-id-backfill.spec.ts +0 -131
- package/src/boot/boot-config.ts +0 -259
- package/src/boot/integration-id-backfill.ts +0 -109
- package/src/boot/post-boot.service.ts +0 -105
- package/src/core/addon/__tests__/addon-registry-capability.test.ts +0 -62
- package/src/core/addon/__tests__/addon-row-manifest.spec.ts +0 -62
- package/src/core/addon/addon-call-gateway.ts +0 -171
- package/src/core/addon/addon-package.service.ts +0 -1787
- package/src/core/addon/addon-registry.service.ts +0 -3130
- package/src/core/addon/addon-search.service.ts +0 -91
- package/src/core/addon/addon-settings-provider.ts +0 -220
- package/src/core/addon/addon.tokens.ts +0 -2
- package/src/core/addon-bridge/addon-bridge.service.ts +0 -130
- package/src/core/addon-pages/addon-pages.service.spec.ts +0 -117
- package/src/core/addon-pages/addon-pages.service.ts +0 -82
- package/src/core/addon-widgets/addon-widgets.service.ts +0 -95
- package/src/core/agent/agent-registry.service.ts +0 -529
- package/src/core/auth/auth.service.spec.ts +0 -86
- package/src/core/auth/auth.service.ts +0 -8
- package/src/core/capability/capability.service.ts +0 -66
- package/src/core/config/config.schema.ts +0 -3
- package/src/core/config/config.service.spec.ts +0 -175
- package/src/core/config/config.service.ts +0 -7
- package/src/core/events/event-bus.service.spec.ts +0 -235
- package/src/core/events/event-bus.service.ts +0 -89
- package/src/core/feature/feature.service.spec.ts +0 -99
- package/src/core/feature/feature.service.ts +0 -8
- package/src/core/lifecycle/lifecycle-state-machine.spec.ts +0 -166
- package/src/core/lifecycle/lifecycle-state-machine.ts +0 -3
- package/src/core/logging/log-ring-buffer.ts +0 -3
- package/src/core/logging/logging.service.spec.ts +0 -287
- package/src/core/logging/logging.service.ts +0 -143
- package/src/core/logging/scoped-logger.ts +0 -3
- package/src/core/moleculer/cap-call-fn.spec.ts +0 -173
- package/src/core/moleculer/cap-call-fn.ts +0 -107
- package/src/core/moleculer/cap-route-authority.ts +0 -194
- package/src/core/moleculer/moleculer.service.ts +0 -1072
- package/src/core/network/network-quality.service.spec.ts +0 -53
- package/src/core/network/network-quality.service.ts +0 -5
- package/src/core/notification/notification-wrapper.service.ts +0 -34
- package/src/core/notification/toast-wrapper.service.ts +0 -27
- package/src/core/provider/provider.tokens.ts +0 -1
- package/src/core/repl/repl-engine.service.spec.ts +0 -444
- package/src/core/repl/repl-engine.service.ts +0 -155
- package/src/core/storage/fs-storage-backend.spec.ts +0 -70
- package/src/core/storage/fs-storage-backend.ts +0 -3
- package/src/core/storage/storage-location-manager.spec.ts +0 -130
- package/src/core/storage/storage-location-manager.ts +0 -3
- package/src/core/storage/storage.service.spec.ts +0 -73
- package/src/core/storage/storage.service.ts +0 -3
- package/src/core/streaming/stream-probe.service.ts +0 -221
- package/src/core/topology/topology-emitter.service.ts +0 -105
- package/src/launcher.ts +0 -314
- package/src/main.ts +0 -1245
- package/src/manual-boot.ts +0 -301
- package/tsconfig.build.json +0 -8
- package/tsconfig.json +0 -33
- package/vitest.config.ts +0 -26
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AgentRegistryService = void 0;
|
|
37
|
+
const node_crypto_1 = require("node:crypto");
|
|
38
|
+
const os = __importStar(require("node:os"));
|
|
39
|
+
const types_1 = require("@camstack/types");
|
|
40
|
+
/** Per-call timeout for `$agent.*` RPC during reconciliation. */
|
|
41
|
+
const AGENT_RECONCILE_RPC_TIMEOUT_MS = 8_000;
|
|
42
|
+
class AgentRegistryService {
|
|
43
|
+
eventBus;
|
|
44
|
+
moleculer;
|
|
45
|
+
capabilityService;
|
|
46
|
+
// Role assignments: key = `${cameraId}:${role}`
|
|
47
|
+
assignments = new Map();
|
|
48
|
+
bootTimestamp = Date.now();
|
|
49
|
+
/**
|
|
50
|
+
* Hub addon registry — the source of truth for which addons are
|
|
51
|
+
* installed and their `execution.placement`. Injected via
|
|
52
|
+
* `setAddonRegistry()` rather than the constructor because
|
|
53
|
+
* `AddonRegistryService` is built AFTER this service in the boot
|
|
54
|
+
* order (see `manual-boot.ts`).
|
|
55
|
+
*/
|
|
56
|
+
addonRegistry = null;
|
|
57
|
+
constructor(eventBus, moleculer, capabilityService) {
|
|
58
|
+
this.eventBus = eventBus;
|
|
59
|
+
this.moleculer = moleculer;
|
|
60
|
+
this.capabilityService = capabilityService;
|
|
61
|
+
}
|
|
62
|
+
/** Wire the hub addon registry once it has been constructed. */
|
|
63
|
+
setAddonRegistry(addonRegistry) {
|
|
64
|
+
this.addonRegistry = addonRegistry;
|
|
65
|
+
}
|
|
66
|
+
/** Typed view of the Moleculer broker — single documented cast. */
|
|
67
|
+
get broker() {
|
|
68
|
+
return this.moleculer.broker;
|
|
69
|
+
}
|
|
70
|
+
onModuleInit() {
|
|
71
|
+
const classifyNode = (id) => {
|
|
72
|
+
if (id === 'hub')
|
|
73
|
+
return null;
|
|
74
|
+
if (id.includes('/'))
|
|
75
|
+
return 'worker';
|
|
76
|
+
return 'agent';
|
|
77
|
+
};
|
|
78
|
+
// D3: reconcile placement when an agent completes the $hub.registerNode
|
|
79
|
+
// handshake — the manifest is authoritative and complete at that point,
|
|
80
|
+
// no grace delay needed. MoleculerService fires this callback from its
|
|
81
|
+
// onRegisterNode dep for every bare-ID agent node.
|
|
82
|
+
this.moleculer.setOnAgentRegistered((agentId) => {
|
|
83
|
+
void this.reconcileAgentAddons(agentId);
|
|
84
|
+
});
|
|
85
|
+
this.broker.localBus.on('$node.connected', ({ node }) => {
|
|
86
|
+
const kind = classifyNode(node.id);
|
|
87
|
+
if (!kind)
|
|
88
|
+
return;
|
|
89
|
+
if (kind === 'agent') {
|
|
90
|
+
console.log(`[agent-registry] Agent connected: ${node.id}`);
|
|
91
|
+
this.eventBus.emit({
|
|
92
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
93
|
+
timestamp: new Date(),
|
|
94
|
+
source: { type: 'core', id: 'agent-registry' },
|
|
95
|
+
category: types_1.EventCategory.AgentOnline,
|
|
96
|
+
data: { agentId: node.id },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
this.eventBus.emit({
|
|
101
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
102
|
+
timestamp: new Date(),
|
|
103
|
+
source: { type: 'core', id: 'agent-registry' },
|
|
104
|
+
category: types_1.EventCategory.WorkerOnline,
|
|
105
|
+
data: { workerId: node.id },
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
this.broker.localBus.on('$node.disconnected', ({ node }) => {
|
|
110
|
+
const kind = classifyNode(node.id);
|
|
111
|
+
if (!kind)
|
|
112
|
+
return;
|
|
113
|
+
if (kind === 'agent') {
|
|
114
|
+
console.log(`[agent-registry] Agent disconnected: ${node.id}`);
|
|
115
|
+
this.eventBus.emit({
|
|
116
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
117
|
+
timestamp: new Date(),
|
|
118
|
+
source: { type: 'core', id: 'agent-registry' },
|
|
119
|
+
category: types_1.EventCategory.AgentOffline,
|
|
120
|
+
data: { agentId: node.id },
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
this.eventBus.emit({
|
|
125
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
126
|
+
timestamp: new Date(),
|
|
127
|
+
source: { type: 'core', id: 'agent-registry' },
|
|
128
|
+
category: types_1.EventCategory.WorkerOffline,
|
|
129
|
+
data: { workerId: node.id },
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
// Retroactive scan: emit lifecycle events for nodes already
|
|
134
|
+
// connected when the listener registers. Classifies each as
|
|
135
|
+
// agent or worker — same logic as the live handlers above.
|
|
136
|
+
try {
|
|
137
|
+
const registry = this.moleculer.broker.registry;
|
|
138
|
+
const existing = registry?.getNodeList?.({ onlyAvailable: true }) ?? [];
|
|
139
|
+
for (const { id } of existing) {
|
|
140
|
+
const kind = classifyNode(id);
|
|
141
|
+
if (!kind)
|
|
142
|
+
continue;
|
|
143
|
+
if (kind === 'agent') {
|
|
144
|
+
this.eventBus.emit({
|
|
145
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
146
|
+
timestamp: new Date(),
|
|
147
|
+
source: { type: 'core', id: 'agent-registry' },
|
|
148
|
+
category: types_1.EventCategory.AgentOnline,
|
|
149
|
+
data: { agentId: id },
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
this.eventBus.emit({
|
|
154
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
155
|
+
timestamp: new Date(),
|
|
156
|
+
source: { type: 'core', id: 'agent-registry' },
|
|
157
|
+
category: types_1.EventCategory.WorkerOnline,
|
|
158
|
+
data: { workerId: id },
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Registry shape varies across Moleculer versions — never fatal.
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// ---- Placement reconciliation ----
|
|
168
|
+
/**
|
|
169
|
+
* Boot-time reconciliation pass. Agents already connected when the hub
|
|
170
|
+
* starts never fire a fresh `$node.connected` event, so the per-connect
|
|
171
|
+
* trigger would miss them — this method walks the current node list once
|
|
172
|
+
* and reconciles every connected agent. Must be invoked AFTER
|
|
173
|
+
* `AddonRegistryService.onModuleInit()` so the hub's installed-addon set
|
|
174
|
+
* is populated. Per-agent failures are isolated — one unreachable agent
|
|
175
|
+
* must not abort the pass or hub boot.
|
|
176
|
+
*/
|
|
177
|
+
async reconcileConnectedAgents() {
|
|
178
|
+
const registry = this.moleculer.broker.registry;
|
|
179
|
+
const nodes = registry?.getNodeList?.({ onlyAvailable: true }) ?? [];
|
|
180
|
+
const agentIds = nodes.map((n) => n.id).filter((id) => id !== 'hub' && !id.includes('/'));
|
|
181
|
+
if (agentIds.length === 0)
|
|
182
|
+
return;
|
|
183
|
+
console.log(`[agent-registry] Boot reconcile: ${agentIds.length} connected agent(s)`);
|
|
184
|
+
for (const agentId of agentIds) {
|
|
185
|
+
await this.reconcileAgentAddons(agentId);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Reconcile a single agent's deployed addons against the hub's installed
|
|
190
|
+
* set + placements. An addon running on the agent is STALE — and must be
|
|
191
|
+
* undeployed — when it is either:
|
|
192
|
+
* - not installed on the hub at all, or
|
|
193
|
+
* - installed but declared `execution.placement: 'hub-only'`.
|
|
194
|
+
*
|
|
195
|
+
* `agent-only` / `any-node` addons that ARE installed on the hub are
|
|
196
|
+
* legitimate agent residents and left untouched.
|
|
197
|
+
*
|
|
198
|
+
* Matching is by addon DECLARATION id: `$agent.status` reports
|
|
199
|
+
* `addons[].id` (the decl id), and the hub's `listAddons()` rows expose
|
|
200
|
+
* the same decl id at `manifest.id`. Package names are NOT used for the
|
|
201
|
+
* match — a single package can ship multiple addons with distinct ids
|
|
202
|
+
* and placements.
|
|
203
|
+
*
|
|
204
|
+
* All errors are caught and logged so a single bad agent never breaks
|
|
205
|
+
* the caller (connect handler or boot pass).
|
|
206
|
+
*/
|
|
207
|
+
async reconcileAgentAddons(agentId) {
|
|
208
|
+
if (!this.addonRegistry) {
|
|
209
|
+
console.warn(`[agent-registry] Reconcile skipped for ${agentId}: addon registry not wired`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const broker = this.broker;
|
|
214
|
+
const statusRaw = await broker.call('$agent.status', {}, {
|
|
215
|
+
nodeID: agentId,
|
|
216
|
+
timeout: AGENT_RECONCILE_RPC_TIMEOUT_MS,
|
|
217
|
+
});
|
|
218
|
+
const agentAddons = this.extractAgentAddons(statusRaw);
|
|
219
|
+
if (agentAddons.length === 0)
|
|
220
|
+
return;
|
|
221
|
+
// Build the hub's placement map: decl id → placement. Absence from
|
|
222
|
+
// this map means "not installed on the hub".
|
|
223
|
+
const hubPlacements = new Map();
|
|
224
|
+
for (const row of this.addonRegistry.listAddons()) {
|
|
225
|
+
const declId = row.manifest.id;
|
|
226
|
+
if (typeof declId !== 'string')
|
|
227
|
+
continue;
|
|
228
|
+
const decl = row.declaration ?? row.manifest;
|
|
229
|
+
hubPlacements.set(declId, (0, types_1.resolveAddonPlacement)(decl));
|
|
230
|
+
}
|
|
231
|
+
const stale = agentAddons.filter((addon) => {
|
|
232
|
+
const placement = hubPlacements.get(addon.id);
|
|
233
|
+
// Not installed on the hub → stale.
|
|
234
|
+
if (placement === undefined)
|
|
235
|
+
return true;
|
|
236
|
+
// Installed but pinned to the hub → must not run on an agent.
|
|
237
|
+
return placement === 'hub-only';
|
|
238
|
+
});
|
|
239
|
+
if (stale.length === 0) {
|
|
240
|
+
console.log(`[agent-registry] Reconcile ${agentId}: no stale addons (${agentAddons.length} checked)`);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
for (const addon of stale) {
|
|
244
|
+
const reason = hubPlacements.has(addon.id)
|
|
245
|
+
? 'placement is hub-only'
|
|
246
|
+
: 'not installed on hub';
|
|
247
|
+
try {
|
|
248
|
+
await broker.call('$agent.undeploy', { addonId: addon.id }, {
|
|
249
|
+
nodeID: agentId,
|
|
250
|
+
timeout: AGENT_RECONCILE_RPC_TIMEOUT_MS,
|
|
251
|
+
});
|
|
252
|
+
console.log(`[agent-registry] Reconcile ${agentId}: undeployed stale addon "${addon.id}" (${reason})`);
|
|
253
|
+
this.eventBus.emit({
|
|
254
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
255
|
+
timestamp: new Date(),
|
|
256
|
+
source: { type: 'core', id: 'agent-registry' },
|
|
257
|
+
category: types_1.EventCategory.AddonUninstalled,
|
|
258
|
+
data: { addonId: addon.id, agentId, reason },
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
console.error(`[agent-registry] Reconcile ${agentId}: failed to undeploy "${addon.id}":`, err instanceof Error ? err.message : String(err));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
console.error(`[agent-registry] Reconcile failed for agent ${agentId}:`, err instanceof Error ? err.message : String(err));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/** Narrow the `$agent.status` response down to its addon list. */
|
|
271
|
+
extractAgentAddons(statusRaw) {
|
|
272
|
+
if (statusRaw === null || typeof statusRaw !== 'object')
|
|
273
|
+
return [];
|
|
274
|
+
const addons = statusRaw.addons;
|
|
275
|
+
if (!Array.isArray(addons))
|
|
276
|
+
return [];
|
|
277
|
+
const result = [];
|
|
278
|
+
for (const entry of addons) {
|
|
279
|
+
if (entry === null || typeof entry !== 'object')
|
|
280
|
+
continue;
|
|
281
|
+
const id = entry.id;
|
|
282
|
+
if (typeof id !== 'string' || id.length === 0)
|
|
283
|
+
continue;
|
|
284
|
+
result.push({ id });
|
|
285
|
+
}
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
/** Log an agent rename (name changes are persisted via $agent.rename RPC). */
|
|
289
|
+
updateAgentName(nodeId, name) {
|
|
290
|
+
console.log(`[agent-registry] Agent renamed: "${nodeId}" → "${name}"`);
|
|
291
|
+
}
|
|
292
|
+
async listNodes() {
|
|
293
|
+
// Get child processes for hub via $process.list
|
|
294
|
+
let hubProcesses = [];
|
|
295
|
+
try {
|
|
296
|
+
const processes = (await this.broker.call('$process.list'));
|
|
297
|
+
hubProcesses = processes.map((p) => ({
|
|
298
|
+
pid: p.pid ?? 0,
|
|
299
|
+
name: p.name ?? '',
|
|
300
|
+
command: 'moleculer-service',
|
|
301
|
+
state: p.state ?? 'running',
|
|
302
|
+
cpuPercent: p.cpuPercent ?? 0,
|
|
303
|
+
memoryRss: p.memoryRss ?? 0,
|
|
304
|
+
uptimeSeconds: p.uptimeSeconds ?? 0,
|
|
305
|
+
addonIds: p.addonIds ?? [],
|
|
306
|
+
groupId: p.groupId ?? null,
|
|
307
|
+
}));
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
// $process service may not be ready yet
|
|
311
|
+
}
|
|
312
|
+
const hubEntry = await this.buildHubEntry(hubProcesses);
|
|
313
|
+
const remoteEntries = [];
|
|
314
|
+
const registry = this.moleculer.broker.registry;
|
|
315
|
+
const nodes = registry?.getNodeList?.({ onlyAvailable: true }) ?? [];
|
|
316
|
+
for (const node of nodes) {
|
|
317
|
+
const nodeId = node.id;
|
|
318
|
+
// Skip hub (already included) and child processes (contain '/')
|
|
319
|
+
if (nodeId === 'hub' || nodeId.includes('/'))
|
|
320
|
+
continue;
|
|
321
|
+
try {
|
|
322
|
+
const status = (await this.broker.call('$agent.status', {}, {
|
|
323
|
+
nodeID: nodeId,
|
|
324
|
+
timeout: 5000,
|
|
325
|
+
}));
|
|
326
|
+
// Get real sub-process stats from the agent's $process.list
|
|
327
|
+
let subProcesses = [];
|
|
328
|
+
try {
|
|
329
|
+
const processes = (await this.broker.call('$process.list', {}, {
|
|
330
|
+
nodeID: nodeId,
|
|
331
|
+
timeout: 5000,
|
|
332
|
+
}));
|
|
333
|
+
subProcesses = processes.map((p) => ({
|
|
334
|
+
pid: p.pid ?? 0,
|
|
335
|
+
name: p.name ?? '',
|
|
336
|
+
command: 'moleculer-service',
|
|
337
|
+
state: p.state ?? 'running',
|
|
338
|
+
cpuPercent: p.cpuPercent ?? 0,
|
|
339
|
+
memoryRss: p.memoryRss ?? 0,
|
|
340
|
+
uptimeSeconds: p.uptimeSeconds ?? 0,
|
|
341
|
+
addonIds: p.addonIds ?? [],
|
|
342
|
+
groupId: p.groupId ?? null,
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
// Fall back to addon list from $agent.status (no stats)
|
|
347
|
+
subProcesses =
|
|
348
|
+
status.addons?.map((a) => ({
|
|
349
|
+
pid: 0,
|
|
350
|
+
name: a.id ?? '',
|
|
351
|
+
command: 'moleculer-service',
|
|
352
|
+
state: (a.status ?? 'running'),
|
|
353
|
+
cpuPercent: 0,
|
|
354
|
+
memoryRss: 0,
|
|
355
|
+
uptimeSeconds: 0,
|
|
356
|
+
})) ?? [];
|
|
357
|
+
}
|
|
358
|
+
// Extract addon IDs from $agent.status
|
|
359
|
+
const agentAddons = status.addons?.map((a) => a.id) ?? [];
|
|
360
|
+
const hostname = typeof status.hostname === 'string' ? status.hostname : null;
|
|
361
|
+
const agentName = typeof status.name === 'string' ? status.name : nodeId;
|
|
362
|
+
remoteEntries.push({
|
|
363
|
+
info: {
|
|
364
|
+
id: nodeId,
|
|
365
|
+
name: agentName,
|
|
366
|
+
hostname: hostname ?? nodeId,
|
|
367
|
+
capabilities: [],
|
|
368
|
+
platform: status.platform ?? 'unknown',
|
|
369
|
+
arch: status.arch ?? 'unknown',
|
|
370
|
+
cpuCores: status.cpuCores ?? 0,
|
|
371
|
+
memoryMB: status.totalMemoryMB ?? 0,
|
|
372
|
+
cpuModel: status.cpuModel,
|
|
373
|
+
},
|
|
374
|
+
localIps: Array.isArray(status.localIps) ? status.localIps : [],
|
|
375
|
+
status: {
|
|
376
|
+
activeCameras: 0,
|
|
377
|
+
cpuPercent: status.cpuPercent ?? 0,
|
|
378
|
+
memoryPercent: status.memoryPercent ?? 0,
|
|
379
|
+
fps: {},
|
|
380
|
+
errors: [],
|
|
381
|
+
},
|
|
382
|
+
connectedSince: typeof status.uptime === 'number'
|
|
383
|
+
? Date.now() - status.uptime * 1000
|
|
384
|
+
: Date.now(),
|
|
385
|
+
isHub: false,
|
|
386
|
+
subProcesses,
|
|
387
|
+
agentAddons,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
// Skip nodes without $agent service
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// TODO(D3 follow-up): offline-agent history dropped with knownAgents.
|
|
395
|
+
// Previously, agents that disconnected were kept in a shadow map and
|
|
396
|
+
// surfaced here as offline rows. listNodes now reflects only live
|
|
397
|
+
// broker.registry nodes.
|
|
398
|
+
return [hubEntry, ...remoteEntries];
|
|
399
|
+
}
|
|
400
|
+
async buildHubEntry(subProcesses = []) {
|
|
401
|
+
const cpus = os.cpus();
|
|
402
|
+
// Get live metrics from the metrics-provider capability (NativeMetricsProvider).
|
|
403
|
+
// The cap contract only exposes async snapshots; the cached read is cheap
|
|
404
|
+
// because the addon's background sampler keeps the snapshot warm.
|
|
405
|
+
let cpuPercent = 0;
|
|
406
|
+
let memoryPercent = 0;
|
|
407
|
+
const registry = this.capabilityService.getRegistry();
|
|
408
|
+
if (registry) {
|
|
409
|
+
const metricsProvider = registry.getSingleton('metrics-provider');
|
|
410
|
+
if (metricsProvider) {
|
|
411
|
+
const snapshot = await metricsProvider.getCached();
|
|
412
|
+
if (snapshot) {
|
|
413
|
+
cpuPercent = snapshot.cpu.total;
|
|
414
|
+
memoryPercent = snapshot.memory.percent;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
info: {
|
|
420
|
+
id: 'hub',
|
|
421
|
+
name: 'Hub',
|
|
422
|
+
hostname: os.hostname(),
|
|
423
|
+
capabilities: [],
|
|
424
|
+
platform: os.platform(),
|
|
425
|
+
arch: os.arch(),
|
|
426
|
+
cpuCores: cpus.length,
|
|
427
|
+
memoryMB: Math.round(os.totalmem() / 1024 / 1024),
|
|
428
|
+
cpuModel: cpus[0]?.model,
|
|
429
|
+
},
|
|
430
|
+
status: {
|
|
431
|
+
activeCameras: 0,
|
|
432
|
+
cpuPercent,
|
|
433
|
+
memoryPercent,
|
|
434
|
+
fps: {},
|
|
435
|
+
errors: [],
|
|
436
|
+
},
|
|
437
|
+
connectedSince: this.bootTimestamp,
|
|
438
|
+
isHub: true,
|
|
439
|
+
subProcesses,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
// ---- Role assignments ----
|
|
443
|
+
getAssignments(cameraId) {
|
|
444
|
+
const all = [...this.assignments.values()];
|
|
445
|
+
if (cameraId !== undefined)
|
|
446
|
+
return all.filter((a) => a.cameraId === cameraId);
|
|
447
|
+
return all;
|
|
448
|
+
}
|
|
449
|
+
setAssignment(assignment) {
|
|
450
|
+
const key = `${assignment.cameraId}:${assignment.role}`;
|
|
451
|
+
this.assignments.set(key, { ...assignment });
|
|
452
|
+
}
|
|
453
|
+
removeAssignment(cameraId, role) {
|
|
454
|
+
const key = `${cameraId}:${role}`;
|
|
455
|
+
this.assignments.delete(key);
|
|
456
|
+
}
|
|
457
|
+
activateBackup(cameraId, role) {
|
|
458
|
+
const primaryKey = `${cameraId}:${role}`;
|
|
459
|
+
const primary = this.assignments.get(primaryKey);
|
|
460
|
+
const backup = [...this.assignments.values()].find((a) => a.cameraId === cameraId && a.role === role && a.priority === 'backup');
|
|
461
|
+
if (!backup)
|
|
462
|
+
return;
|
|
463
|
+
if (primary) {
|
|
464
|
+
this.assignments.delete(primaryKey);
|
|
465
|
+
}
|
|
466
|
+
const promoted = { ...backup, priority: 'primary' };
|
|
467
|
+
this.assignments.set(primaryKey, promoted);
|
|
468
|
+
this.eventBus.emit({
|
|
469
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
470
|
+
timestamp: new Date(),
|
|
471
|
+
source: { type: 'core', id: 'agent-registry' },
|
|
472
|
+
category: types_1.EventCategory.AgentBackupActivated,
|
|
473
|
+
data: { cameraId, role, agentId: promoted.agentId },
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
exports.AgentRegistryService = AgentRegistryService;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AuthService = void 0;
|
|
4
|
+
const core_1 = require("@camstack/core");
|
|
5
|
+
class AuthService extends core_1.AuthManager {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
super(config);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.AuthService = AuthService;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CapabilityService — NestJS-injectable wrapper around the CapabilityRegistry.
|
|
4
|
+
*
|
|
5
|
+
* Server services inject this instead of accessing the registry directly.
|
|
6
|
+
* The registry reference is set once during boot by AddonRegistryService.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.CapabilityService = void 0;
|
|
10
|
+
class CapabilityService {
|
|
11
|
+
registry = null;
|
|
12
|
+
/** Called once during boot by AddonRegistryService to wire the registry */
|
|
13
|
+
setRegistry(registry) {
|
|
14
|
+
this.registry = registry;
|
|
15
|
+
}
|
|
16
|
+
/** Get the underlying registry (may be null before boot completes) */
|
|
17
|
+
getRegistry() {
|
|
18
|
+
return this.registry;
|
|
19
|
+
}
|
|
20
|
+
/** Get the active singleton provider for a capability */
|
|
21
|
+
getSingleton(capability) {
|
|
22
|
+
return this.registry?.getSingleton(capability) ?? null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the hub-local provider honoring the 'hub' per-node singleton override.
|
|
26
|
+
* Delegates to `CapabilityRegistry.getSingletonForNode` so the in-process lookup
|
|
27
|
+
* respects per-node overrides set by the operator.
|
|
28
|
+
*/
|
|
29
|
+
getSingletonForNode(capability, nodeId) {
|
|
30
|
+
return this.registry?.getSingletonForNode(capability, nodeId) ?? null;
|
|
31
|
+
}
|
|
32
|
+
/** Get the addon ID of the active singleton provider for a capability */
|
|
33
|
+
getSingletonAddonId(capability) {
|
|
34
|
+
return this.registry?.getSingletonAddonId(capability) ?? null;
|
|
35
|
+
}
|
|
36
|
+
/** Get all active collection providers for a capability */
|
|
37
|
+
getCollection(capability) {
|
|
38
|
+
return this.registry?.getCollection(capability) ?? [];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Like {@link getCollection} but returns `[addonId, provider]` tuples
|
|
42
|
+
* so callers can attribute work back to the contributing addon —
|
|
43
|
+
* required by the addon-widgets static file route to validate that
|
|
44
|
+
* the requested `addonId` is a registered widget provider.
|
|
45
|
+
*/
|
|
46
|
+
getCollectionEntries(capability) {
|
|
47
|
+
return this.registry?.getCollectionEntries(capability) ?? [];
|
|
48
|
+
}
|
|
49
|
+
/** Resolve a singleton provider for a specific device (with per-device override support) */
|
|
50
|
+
resolveForDevice(capability, deviceId) {
|
|
51
|
+
return this.registry?.resolveForDevice(capability, deviceId) ?? null;
|
|
52
|
+
}
|
|
53
|
+
/** Resolve collection providers for a specific device (with per-device filter support) */
|
|
54
|
+
resolveCollectionForDevice(capability, deviceId) {
|
|
55
|
+
return this.registry?.resolveCollectionForDevice(capability, deviceId) ?? [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.CapabilityService = CapabilityService;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RUNTIME_DEFAULTS = exports.bootstrapSchema = void 0;
|
|
4
|
+
// Re-export from @camstack/kernel
|
|
5
|
+
var kernel_1 = require("@camstack/kernel");
|
|
6
|
+
Object.defineProperty(exports, "bootstrapSchema", { enumerable: true, get: function () { return kernel_1.bootstrapSchema; } });
|
|
7
|
+
Object.defineProperty(exports, "RUNTIME_DEFAULTS", { enumerable: true, get: function () { return kernel_1.RUNTIME_DEFAULTS; } });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConfigService = void 0;
|
|
4
|
+
const kernel_1 = require("@camstack/kernel");
|
|
5
|
+
class ConfigService extends kernel_1.ConfigManager {
|
|
6
|
+
constructor(configPath) {
|
|
7
|
+
super(configPath);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.ConfigService = ConfigService;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EventBusService = void 0;
|
|
4
|
+
const kernel_1 = require("@camstack/kernel");
|
|
5
|
+
/**
|
|
6
|
+
* Hub-side event bus. Pre-broker boot the service buffers nothing —
|
|
7
|
+
* subscribers / emits are no-ops until `attachBroker` lands the
|
|
8
|
+
* underlying per-broker shared bus. Once attached every operation
|
|
9
|
+
* delegates to the unified `getBrokerEventBus(broker)` instance,
|
|
10
|
+
* which is the SAME bus that group-runner / agent subprocess addons
|
|
11
|
+
* see when they call `ctx.eventBus.emit`. Single in-process delivery
|
|
12
|
+
* map per broker, single `broker.broadcast` for cross-broker
|
|
13
|
+
* delivery — no more dual hub-vs-subprocess implementations.
|
|
14
|
+
*
|
|
15
|
+
* **Persistence scope**: the in-memory ring buffer (10 000 events,
|
|
16
|
+
* owned by `getBrokerEventBus`) survives admin-ui browser refreshes
|
|
17
|
+
* but NOT server restarts — that's intentional. UI panels read
|
|
18
|
+
* `getRecent` on mount and live-subscribe via `live.onEvent` /
|
|
19
|
+
* `systemEvents.subscribe`, which is enough for the operator to see
|
|
20
|
+
* continuity across page reloads.
|
|
21
|
+
*/
|
|
22
|
+
class EventBusService {
|
|
23
|
+
broker = null;
|
|
24
|
+
inner = null;
|
|
25
|
+
// Used only as a loose ring buffer for the very first events emitted
|
|
26
|
+
// before `attachBroker` runs (NestJS-era boot legacy). Drained into
|
|
27
|
+
// the real bus on attach.
|
|
28
|
+
pending = [];
|
|
29
|
+
deferredSubs = [];
|
|
30
|
+
constructor(_configService) {
|
|
31
|
+
// The shared bus owns the ring-buffer size — hub-side config is
|
|
32
|
+
// accepted for API compatibility but no longer drives the bus.
|
|
33
|
+
void _configService;
|
|
34
|
+
}
|
|
35
|
+
attachBroker(broker) {
|
|
36
|
+
if (this.broker === broker)
|
|
37
|
+
return;
|
|
38
|
+
this.broker = broker;
|
|
39
|
+
const inner = (0, kernel_1.getBrokerEventBus)(broker);
|
|
40
|
+
this.inner = inner;
|
|
41
|
+
// Replay deferred subscriptions onto the real bus.
|
|
42
|
+
for (const sub of this.deferredSubs) {
|
|
43
|
+
sub.unsub = inner.subscribe(sub.filter, sub.handler);
|
|
44
|
+
}
|
|
45
|
+
// Flush events emitted before the broker was ready.
|
|
46
|
+
for (const evt of this.pending) {
|
|
47
|
+
inner.emit(evt);
|
|
48
|
+
}
|
|
49
|
+
this.pending = [];
|
|
50
|
+
}
|
|
51
|
+
detachBroker() {
|
|
52
|
+
for (const sub of this.deferredSubs) {
|
|
53
|
+
sub.unsub?.();
|
|
54
|
+
sub.unsub = undefined;
|
|
55
|
+
}
|
|
56
|
+
this.broker = null;
|
|
57
|
+
this.inner = null;
|
|
58
|
+
}
|
|
59
|
+
emit(event) {
|
|
60
|
+
if (!this.inner) {
|
|
61
|
+
this.pending.push(event);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.inner.emit(event);
|
|
65
|
+
}
|
|
66
|
+
subscribe(filter, handler) {
|
|
67
|
+
if (this.inner)
|
|
68
|
+
return this.inner.subscribe(filter, handler);
|
|
69
|
+
const sub = { filter, handler, unsub: undefined };
|
|
70
|
+
this.deferredSubs.push(sub);
|
|
71
|
+
return () => {
|
|
72
|
+
if (sub.unsub)
|
|
73
|
+
sub.unsub();
|
|
74
|
+
const idx = this.deferredSubs.indexOf(sub);
|
|
75
|
+
if (idx >= 0)
|
|
76
|
+
this.deferredSubs.splice(idx, 1);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
getRecent(filter, limit) {
|
|
80
|
+
return this.inner?.getRecent(filter, limit) ?? [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.EventBusService = EventBusService;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FeatureService = void 0;
|
|
4
|
+
const core_1 = require("@camstack/core");
|
|
5
|
+
class FeatureService extends core_1.FeatureManager {
|
|
6
|
+
constructor(configService) {
|
|
7
|
+
super(configService);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.FeatureService = FeatureService;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LifecycleStateMachine = void 0;
|
|
4
|
+
// Re-export from @camstack/core — LifecycleStateMachine is already a plain class
|
|
5
|
+
var core_1 = require("@camstack/core");
|
|
6
|
+
Object.defineProperty(exports, "LifecycleStateMachine", { enumerable: true, get: function () { return core_1.LifecycleStateMachine; } });
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LogRingBuffer = void 0;
|
|
4
|
+
// Re-export from @camstack/core
|
|
5
|
+
var core_1 = require("@camstack/core");
|
|
6
|
+
Object.defineProperty(exports, "LogRingBuffer", { enumerable: true, get: function () { return core_1.LogRingBuffer; } });
|