@camstack/server 1.0.0 → 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.
Files changed (234) hide show
  1. package/{src/agent-status-page.ts → dist/agent-status-page.js} +30 -45
  2. package/dist/api/addon-upload.js +441 -0
  3. package/dist/api/addons-custom.router.js +91 -0
  4. package/dist/api/auth-whoami.js +55 -0
  5. package/dist/api/bridge-addons.router.js +109 -0
  6. package/dist/api/capabilities.router.js +229 -0
  7. package/dist/api/core/addon-settings.router.js +117 -0
  8. package/dist/api/core/agents.router.js +73 -0
  9. package/dist/api/core/auth.router.js +286 -0
  10. package/dist/api/core/bulk-update-coordinator.js +229 -0
  11. package/dist/api/core/cap-providers.js +1124 -0
  12. package/dist/api/core/capabilities.router.js +138 -0
  13. package/dist/api/core/collection-preference.js +17 -0
  14. package/dist/api/core/event-bus-proxy.router.js +45 -0
  15. package/dist/api/core/hwaccel.router.js +91 -0
  16. package/dist/api/core/live-events.router.js +61 -0
  17. package/dist/api/core/logs.router.js +172 -0
  18. package/dist/api/core/notifications.router.js +67 -0
  19. package/dist/api/core/repl.router.js +35 -0
  20. package/dist/api/core/settings-backend.router.js +121 -0
  21. package/dist/api/core/stream-probe.router.js +58 -0
  22. package/dist/api/core/system-events.router.js +100 -0
  23. package/dist/api/health/health.routes.js +68 -0
  24. package/{src/api/oauth2/consent-page.ts → dist/api/oauth2/consent-page.js} +11 -20
  25. package/dist/api/oauth2/oauth2-routes.js +219 -0
  26. package/dist/api/trpc/cap-mount-helpers.js +194 -0
  27. package/dist/api/trpc/cap-route-error-formatter.js +133 -0
  28. package/dist/api/trpc/client-ip.js +147 -0
  29. package/dist/api/trpc/core-cap-bridge.js +115 -0
  30. package/dist/api/trpc/generated-cap-mounts.js +388 -0
  31. package/dist/api/trpc/generated-cap-routers.js +7635 -0
  32. package/dist/api/trpc/scope-access.js +93 -0
  33. package/dist/api/trpc/trpc.context.js +184 -0
  34. package/dist/api/trpc/trpc.middleware.js +139 -0
  35. package/dist/api/trpc/trpc.router.js +188 -0
  36. package/dist/auth/session-cookie.js +47 -0
  37. package/dist/boot/boot-config.js +241 -0
  38. package/dist/boot/integration-id-backfill.js +76 -0
  39. package/dist/boot/post-boot.service.js +85 -0
  40. package/dist/core/addon/addon-call-gateway.js +99 -0
  41. package/dist/core/addon/addon-package.service.js +1560 -0
  42. package/dist/core/addon/addon-registry.service.js +2739 -0
  43. package/{src/core/addon/addon-row-manifest.ts → dist/core/addon/addon-row-manifest.js} +5 -5
  44. package/dist/core/addon/addon-search.service.js +62 -0
  45. package/dist/core/addon/addon-settings-provider.js +102 -0
  46. package/dist/core/addon/addon.tokens.js +5 -0
  47. package/dist/core/addon-bridge/addon-bridge.service.js +145 -0
  48. package/dist/core/addon-pages/addon-pages.service.js +107 -0
  49. package/dist/core/addon-widgets/addon-widgets.service.js +120 -0
  50. package/dist/core/agent/agent-registry.service.js +477 -0
  51. package/dist/core/auth/auth.service.js +10 -0
  52. package/dist/core/capability/capability.service.js +58 -0
  53. package/dist/core/config/config.schema.js +7 -0
  54. package/dist/core/config/config.service.js +10 -0
  55. package/dist/core/events/event-bus.service.js +83 -0
  56. package/dist/core/feature/feature.service.js +10 -0
  57. package/dist/core/lifecycle/lifecycle-state-machine.js +6 -0
  58. package/dist/core/logging/log-ring-buffer.js +6 -0
  59. package/dist/core/logging/logging.service.js +130 -0
  60. package/dist/core/logging/scoped-logger.js +6 -0
  61. package/dist/core/moleculer/cap-call-fn.js +50 -0
  62. package/dist/core/moleculer/cap-route-authority.js +122 -0
  63. package/dist/core/moleculer/moleculer.service.js +898 -0
  64. package/dist/core/network/network-quality.service.js +7 -0
  65. package/dist/core/notification/notification-wrapper.service.js +33 -0
  66. package/dist/core/notification/toast-wrapper.service.js +25 -0
  67. package/dist/core/provider/provider.tokens.js +4 -0
  68. package/dist/core/repl/repl-engine.service.js +140 -0
  69. package/dist/core/storage/fs-storage-backend.js +6 -0
  70. package/dist/core/storage/storage-location-manager.js +6 -0
  71. package/dist/core/storage/storage.service.js +7 -0
  72. package/dist/core/streaming/stream-probe.service.js +209 -0
  73. package/dist/core/topology/topology-emitter.service.js +106 -0
  74. package/dist/launcher.js +325 -0
  75. package/dist/main.js +1098 -0
  76. package/dist/manual-boot.js +227 -0
  77. package/package.json +5 -1
  78. package/src/__tests__/addon-install-e2e.test.ts +0 -74
  79. package/src/__tests__/addon-pages-e2e.test.ts +0 -200
  80. package/src/__tests__/addon-route-session.test.ts +0 -17
  81. package/src/__tests__/addon-settings-router.spec.ts +0 -67
  82. package/src/__tests__/addon-upload.spec.ts +0 -475
  83. package/src/__tests__/agent-registry.spec.ts +0 -179
  84. package/src/__tests__/agent-status-page.spec.ts +0 -82
  85. package/src/__tests__/auth-session-cookie.test.ts +0 -48
  86. package/src/__tests__/bulk-update-coordinator.spec.ts +0 -303
  87. package/src/__tests__/cap-ownership-authority.spec.ts +0 -431
  88. package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +0 -206
  89. package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +0 -37
  90. package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +0 -110
  91. package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +0 -292
  92. package/src/__tests__/cap-providers-bulk-update.spec.ts +0 -408
  93. package/src/__tests__/cap-route-adapter.spec.ts +0 -302
  94. package/src/__tests__/cap-routers/_meta.spec.ts +0 -199
  95. package/src/__tests__/cap-routers/addon-settings.router.spec.ts +0 -115
  96. package/src/__tests__/cap-routers/broker-routing.router.spec.ts +0 -177
  97. package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +0 -125
  98. package/src/__tests__/cap-routers/capabilities-node.spec.ts +0 -68
  99. package/src/__tests__/cap-routers/device-link-overlay.spec.ts +0 -137
  100. package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +0 -194
  101. package/src/__tests__/cap-routers/harness.ts +0 -163
  102. package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +0 -133
  103. package/src/__tests__/cap-routers/null-provider-guard.spec.ts +0 -64
  104. package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +0 -159
  105. package/src/__tests__/cap-routers/settings-store.router.spec.ts +0 -291
  106. package/src/__tests__/capability-e2e.test.ts +0 -384
  107. package/src/__tests__/cli-e2e.test.ts +0 -150
  108. package/src/__tests__/core-cap-bridge.spec.ts +0 -91
  109. package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +0 -40
  110. package/src/__tests__/device-settings-contribution-dispatch.spec.ts +0 -280
  111. package/src/__tests__/embedded-deps-e2e.test.ts +0 -125
  112. package/src/__tests__/event-bus-proxy-router.spec.ts +0 -75
  113. package/src/__tests__/fixtures/mock-analysis-addon-a.ts +0 -37
  114. package/src/__tests__/fixtures/mock-analysis-addon-b.ts +0 -37
  115. package/src/__tests__/fixtures/mock-log-addon.ts +0 -37
  116. package/src/__tests__/fixtures/mock-storage-addon.ts +0 -40
  117. package/src/__tests__/framework-allowlist.spec.ts +0 -96
  118. package/src/__tests__/framework-installer-defer-restart.spec.ts +0 -165
  119. package/src/__tests__/https-e2e.test.ts +0 -124
  120. package/src/__tests__/lifecycle-e2e.test.ts +0 -189
  121. package/src/__tests__/live-events-subscription.spec.ts +0 -149
  122. package/src/__tests__/moleculer/uds-readiness.spec.ts +0 -150
  123. package/src/__tests__/moleculer/uds-topology.spec.ts +0 -418
  124. package/src/__tests__/moleculer/uds-unowned-call.spec.ts +0 -383
  125. package/src/__tests__/moleculer-register-node-idempotency.spec.ts +0 -273
  126. package/src/__tests__/native-cap-route.spec.ts +0 -427
  127. package/src/__tests__/oauth2-account-linking.spec.ts +0 -867
  128. package/src/__tests__/post-boot-restart.spec.ts +0 -161
  129. package/src/__tests__/singleton-contention.test.ts +0 -499
  130. package/src/__tests__/streaming-diagnostic.test.ts +0 -615
  131. package/src/__tests__/streaming-scale.test.ts +0 -314
  132. package/src/__tests__/uds-addon-call-wiring.spec.ts +0 -242
  133. package/src/__tests__/uds-log-ingest.spec.ts +0 -183
  134. package/src/api/__tests__/addons-custom.spec.ts +0 -148
  135. package/src/api/__tests__/capabilities.router.test.ts +0 -56
  136. package/src/api/addon-upload.ts +0 -529
  137. package/src/api/addons-custom.router.ts +0 -101
  138. package/src/api/auth-whoami.ts +0 -101
  139. package/src/api/bridge-addons.router.ts +0 -122
  140. package/src/api/capabilities.router.ts +0 -265
  141. package/src/api/core/__tests__/auth-router-totp.spec.ts +0 -297
  142. package/src/api/core/__tests__/integration-markers.spec.ts +0 -10
  143. package/src/api/core/addon-settings.router.ts +0 -127
  144. package/src/api/core/agents.router.ts +0 -86
  145. package/src/api/core/auth.router.ts +0 -322
  146. package/src/api/core/bulk-update-coordinator.ts +0 -305
  147. package/src/api/core/cap-providers.ts +0 -1339
  148. package/src/api/core/capabilities.router.ts +0 -149
  149. package/src/api/core/collection-preference.ts +0 -40
  150. package/src/api/core/event-bus-proxy.router.ts +0 -45
  151. package/src/api/core/hwaccel.router.ts +0 -108
  152. package/src/api/core/live-events.router.ts +0 -67
  153. package/src/api/core/logs.router.ts +0 -195
  154. package/src/api/core/notifications.router.ts +0 -66
  155. package/src/api/core/repl.router.ts +0 -39
  156. package/src/api/core/settings-backend.router.ts +0 -140
  157. package/src/api/core/stream-probe.router.ts +0 -57
  158. package/src/api/core/system-events.router.ts +0 -125
  159. package/src/api/health/health.routes.ts +0 -117
  160. package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +0 -62
  161. package/src/api/oauth2/oauth2-routes.ts +0 -281
  162. package/src/api/trpc/__tests__/client-ip.spec.ts +0 -146
  163. package/src/api/trpc/__tests__/scope-access-device.spec.ts +0 -268
  164. package/src/api/trpc/__tests__/scope-access.spec.ts +0 -102
  165. package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +0 -136
  166. package/src/api/trpc/cap-mount-helpers.ts +0 -245
  167. package/src/api/trpc/cap-route-error-formatter.ts +0 -171
  168. package/src/api/trpc/client-ip.ts +0 -147
  169. package/src/api/trpc/core-cap-bridge.ts +0 -154
  170. package/src/api/trpc/generated-cap-mounts.ts +0 -1240
  171. package/src/api/trpc/generated-cap-routers.ts +0 -11523
  172. package/src/api/trpc/scope-access.ts +0 -110
  173. package/src/api/trpc/trpc.context.ts +0 -258
  174. package/src/api/trpc/trpc.middleware.ts +0 -146
  175. package/src/api/trpc/trpc.router.ts +0 -389
  176. package/src/auth/session-cookie.ts +0 -54
  177. package/src/boot/__tests__/integration-id-backfill.spec.ts +0 -131
  178. package/src/boot/boot-config.ts +0 -259
  179. package/src/boot/integration-id-backfill.ts +0 -109
  180. package/src/boot/post-boot.service.ts +0 -105
  181. package/src/core/addon/__tests__/addon-registry-capability.test.ts +0 -62
  182. package/src/core/addon/__tests__/addon-row-manifest.spec.ts +0 -62
  183. package/src/core/addon/addon-call-gateway.ts +0 -171
  184. package/src/core/addon/addon-package.service.ts +0 -1787
  185. package/src/core/addon/addon-registry.service.ts +0 -3130
  186. package/src/core/addon/addon-search.service.ts +0 -91
  187. package/src/core/addon/addon-settings-provider.ts +0 -220
  188. package/src/core/addon/addon.tokens.ts +0 -2
  189. package/src/core/addon-bridge/addon-bridge.service.ts +0 -130
  190. package/src/core/addon-pages/addon-pages.service.spec.ts +0 -117
  191. package/src/core/addon-pages/addon-pages.service.ts +0 -82
  192. package/src/core/addon-widgets/addon-widgets.service.ts +0 -95
  193. package/src/core/agent/agent-registry.service.ts +0 -529
  194. package/src/core/auth/auth.service.spec.ts +0 -86
  195. package/src/core/auth/auth.service.ts +0 -8
  196. package/src/core/capability/capability.service.ts +0 -66
  197. package/src/core/config/config.schema.ts +0 -3
  198. package/src/core/config/config.service.spec.ts +0 -175
  199. package/src/core/config/config.service.ts +0 -7
  200. package/src/core/events/event-bus.service.spec.ts +0 -235
  201. package/src/core/events/event-bus.service.ts +0 -89
  202. package/src/core/feature/feature.service.spec.ts +0 -99
  203. package/src/core/feature/feature.service.ts +0 -8
  204. package/src/core/lifecycle/lifecycle-state-machine.spec.ts +0 -166
  205. package/src/core/lifecycle/lifecycle-state-machine.ts +0 -3
  206. package/src/core/logging/log-ring-buffer.ts +0 -3
  207. package/src/core/logging/logging.service.spec.ts +0 -287
  208. package/src/core/logging/logging.service.ts +0 -143
  209. package/src/core/logging/scoped-logger.ts +0 -3
  210. package/src/core/moleculer/cap-call-fn.spec.ts +0 -173
  211. package/src/core/moleculer/cap-call-fn.ts +0 -107
  212. package/src/core/moleculer/cap-route-authority.ts +0 -194
  213. package/src/core/moleculer/moleculer.service.ts +0 -1072
  214. package/src/core/network/network-quality.service.spec.ts +0 -53
  215. package/src/core/network/network-quality.service.ts +0 -5
  216. package/src/core/notification/notification-wrapper.service.ts +0 -34
  217. package/src/core/notification/toast-wrapper.service.ts +0 -27
  218. package/src/core/provider/provider.tokens.ts +0 -1
  219. package/src/core/repl/repl-engine.service.spec.ts +0 -444
  220. package/src/core/repl/repl-engine.service.ts +0 -155
  221. package/src/core/storage/fs-storage-backend.spec.ts +0 -70
  222. package/src/core/storage/fs-storage-backend.ts +0 -3
  223. package/src/core/storage/storage-location-manager.spec.ts +0 -130
  224. package/src/core/storage/storage-location-manager.ts +0 -3
  225. package/src/core/storage/storage.service.spec.ts +0 -73
  226. package/src/core/storage/storage.service.ts +0 -3
  227. package/src/core/streaming/stream-probe.service.ts +0 -221
  228. package/src/core/topology/topology-emitter.service.ts +0 -105
  229. package/src/launcher.ts +0 -314
  230. package/src/main.ts +0 -1245
  231. package/src/manual-boot.ts +0 -301
  232. package/tsconfig.build.json +0 -8
  233. package/tsconfig.json +0 -33
  234. package/vitest.config.ts +0 -26
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ /**
3
+ * Boot — constructs every service by hand in dependency order, runs
4
+ * `onModuleInit` hooks in sequence, and exposes a `BootedApp` facade
5
+ * around the Fastify instance. This is the only boot path; NestJS was
6
+ * removed to drop the accidental DI layer on top of an already-explicit
7
+ * dependency graph.
8
+ */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.bootManual = bootManual;
14
+ const fastify_1 = __importDefault(require("fastify"));
15
+ const config_service_1 = require("./core/config/config.service");
16
+ const logging_service_1 = require("./core/logging/logging.service");
17
+ const event_bus_service_1 = require("./core/events/event-bus.service");
18
+ const storage_service_1 = require("./core/storage/storage.service");
19
+ const capability_service_1 = require("./core/capability/capability.service");
20
+ const feature_service_1 = require("./core/feature/feature.service");
21
+ const auth_service_1 = require("./core/auth/auth.service");
22
+ const stream_probe_service_1 = require("./core/streaming/stream-probe.service");
23
+ const network_quality_service_1 = require("./core/network/network-quality.service");
24
+ const toast_wrapper_service_1 = require("./core/notification/toast-wrapper.service");
25
+ const notification_wrapper_service_1 = require("./core/notification/notification-wrapper.service");
26
+ const addon_pages_service_1 = require("./core/addon-pages/addon-pages.service");
27
+ const addon_widgets_service_1 = require("./core/addon-widgets/addon-widgets.service");
28
+ const addon_bridge_service_1 = require("./core/addon-bridge/addon-bridge.service");
29
+ const moleculer_service_1 = require("./core/moleculer/moleculer.service");
30
+ const agent_registry_service_1 = require("./core/agent/agent-registry.service");
31
+ const addon_registry_service_1 = require("./core/addon/addon-registry.service");
32
+ const addon_search_service_1 = require("./core/addon/addon-search.service");
33
+ const addon_package_service_1 = require("./core/addon/addon-package.service");
34
+ const repl_engine_service_1 = require("./core/repl/repl-engine.service");
35
+ const topology_emitter_service_1 = require("./core/topology/topology-emitter.service");
36
+ const post_boot_service_1 = require("./boot/post-boot.service");
37
+ // ---------------------------------------------------------------------------
38
+ // Service container — narrowing via `instanceof`, no casts.
39
+ // ---------------------------------------------------------------------------
40
+ class ServiceContainer {
41
+ services = new Map();
42
+ register(ctor, instance) {
43
+ this.services.set(ctor, instance);
44
+ }
45
+ get(ctor) {
46
+ const inst = this.services.get(ctor);
47
+ if (inst === undefined) {
48
+ throw new Error(`ServiceContainer: ${ctor.name} is not registered`);
49
+ }
50
+ if (!(inst instanceof ctor)) {
51
+ throw new Error(`ServiceContainer: ${ctor.name} instance type mismatch at resolve time`);
52
+ }
53
+ return inst;
54
+ }
55
+ }
56
+ // ---------------------------------------------------------------------------
57
+ // Manual boot
58
+ // ---------------------------------------------------------------------------
59
+ async function bootManual(opts) {
60
+ const { infra, fastifyOpts } = opts;
61
+ void infra; // infraContext is consumed by main.ts directly (locationManager, etc.)
62
+ const configPath = process.env.CONFIG_PATH ?? 'camstack-data/config.yaml';
63
+ // ---- Layer 0: services with no service deps -----------------------------
64
+ const configService = new config_service_1.ConfigService(configPath);
65
+ const storageService = new storage_service_1.StorageService();
66
+ const capabilityService = new capability_service_1.CapabilityService();
67
+ const networkQualityService = new network_quality_service_1.NetworkQualityService();
68
+ const toastWrapper = new toast_wrapper_service_1.ToastServiceWrapper();
69
+ // ---- Layer 1: need ConfigService / LoggingService -----------------------
70
+ const loggingService = new logging_service_1.LoggingService(configService);
71
+ const eventBusService = new event_bus_service_1.EventBusService(configService);
72
+ // Wire device-name cache for log formatter — subscribes here so the
73
+ // very first DeviceRegistered event populates the cache before any
74
+ // log line needing `deviceName` is emitted.
75
+ loggingService.attachDeviceNameStream(eventBusService);
76
+ const featureService = new feature_service_1.FeatureService(configService);
77
+ const authService = new auth_service_1.AuthService(configService);
78
+ const streamProbeService = new stream_probe_service_1.StreamProbeService(loggingService);
79
+ const addonBridgeService = new addon_bridge_service_1.AddonBridgeService(loggingService);
80
+ const addonSearchService = new addon_search_service_1.AddonSearchService(loggingService);
81
+ // ---- Layer 2: need Layer 0/1 -------------------------------------------
82
+ const notificationWrapper = new notification_wrapper_service_1.NotificationServiceWrapper(capabilityService, loggingService);
83
+ const addonPagesService = new addon_pages_service_1.AddonPagesService(loggingService, configService, capabilityService);
84
+ const moleculerService = new moleculer_service_1.MoleculerService(eventBusService, configService, loggingService, capabilityService, streamProbeService);
85
+ // ---- Layer 3: need Moleculer / AddonRegistry ---------------------------
86
+ const agentRegistryService = new agent_registry_service_1.AgentRegistryService(eventBusService, moleculerService, capabilityService);
87
+ const addonRegistryService = new addon_registry_service_1.AddonRegistryService(loggingService, eventBusService, configService, storageService, capabilityService, moleculerService, streamProbeService);
88
+ // AgentRegistryService needs the hub addon registry for placement-based
89
+ // reconciliation, but is constructed first (it has no other dependency
90
+ // on AddonRegistryService) — wire it post-construction.
91
+ agentRegistryService.setAddonRegistry(addonRegistryService);
92
+ // ---- Layer 4: need AddonRegistry ---------------------------------------
93
+ // AddonWidgetsService — needs AddonRegistryService for the bundled-addon
94
+ // dist sub-folder lookup (see service docstring).
95
+ const addonWidgetsService = new addon_widgets_service_1.AddonWidgetsService(loggingService, capabilityService, addonRegistryService);
96
+ const addonPackageService = new addon_package_service_1.AddonPackageService(loggingService, eventBusService, configService, addonRegistryService, notificationWrapper, toastWrapper);
97
+ const replEngineService = new repl_engine_service_1.ReplEngineService(addonRegistryService, eventBusService, loggingService);
98
+ const topologyEmitterService = new topology_emitter_service_1.TopologyEmitterService(eventBusService, agentRegistryService, addonRegistryService);
99
+ const postBootService = new post_boot_service_1.PostBootService(addonRegistryService, eventBusService, loggingService);
100
+ // ---- Container ---------------------------------------------------------
101
+ const container = new ServiceContainer();
102
+ container.register(config_service_1.ConfigService, configService);
103
+ container.register(logging_service_1.LoggingService, loggingService);
104
+ container.register(event_bus_service_1.EventBusService, eventBusService);
105
+ container.register(storage_service_1.StorageService, storageService);
106
+ container.register(capability_service_1.CapabilityService, capabilityService);
107
+ container.register(feature_service_1.FeatureService, featureService);
108
+ container.register(auth_service_1.AuthService, authService);
109
+ container.register(stream_probe_service_1.StreamProbeService, streamProbeService);
110
+ container.register(network_quality_service_1.NetworkQualityService, networkQualityService);
111
+ container.register(toast_wrapper_service_1.ToastServiceWrapper, toastWrapper);
112
+ container.register(notification_wrapper_service_1.NotificationServiceWrapper, notificationWrapper);
113
+ container.register(addon_pages_service_1.AddonPagesService, addonPagesService);
114
+ container.register(addon_widgets_service_1.AddonWidgetsService, addonWidgetsService);
115
+ container.register(addon_bridge_service_1.AddonBridgeService, addonBridgeService);
116
+ container.register(moleculer_service_1.MoleculerService, moleculerService);
117
+ container.register(agent_registry_service_1.AgentRegistryService, agentRegistryService);
118
+ container.register(addon_registry_service_1.AddonRegistryService, addonRegistryService);
119
+ container.register(addon_search_service_1.AddonSearchService, addonSearchService);
120
+ container.register(addon_package_service_1.AddonPackageService, addonPackageService);
121
+ container.register(repl_engine_service_1.ReplEngineService, replEngineService);
122
+ container.register(topology_emitter_service_1.TopologyEmitterService, topologyEmitterService);
123
+ container.register(post_boot_service_1.PostBootService, postBootService);
124
+ // ---- Fastify instance --------------------------------------------------
125
+ const fastify = (0, fastify_1.default)(fastifyOpts);
126
+ // /health and /health/* — registered in `main.ts` via
127
+ // `registerHealthRoutes` once MoleculerService + AgentRegistryService are
128
+ // available. The detailed shape is shared with each agent's `/health`
129
+ // (see `packages/agent/src/agent-http.ts`).
130
+ // ---- Shutdown coordination --------------------------------------------
131
+ let closed = false;
132
+ let shutdownHooksRegistered = false;
133
+ const close = async () => {
134
+ if (closed)
135
+ return;
136
+ closed = true;
137
+ const logErr = (label, err) => {
138
+ console.error(`[manual-boot] ${label} destroy failed:`, err);
139
+ };
140
+ try {
141
+ topologyEmitterService.onModuleDestroy();
142
+ }
143
+ catch (err) {
144
+ logErr('TopologyEmitter', err);
145
+ }
146
+ try {
147
+ await addonRegistryService.onModuleDestroy();
148
+ }
149
+ catch (err) {
150
+ logErr('AddonRegistry', err);
151
+ }
152
+ try {
153
+ await addonBridgeService.onModuleDestroy();
154
+ }
155
+ catch (err) {
156
+ logErr('AddonBridge', err);
157
+ }
158
+ try {
159
+ await moleculerService.onModuleDestroy();
160
+ }
161
+ catch (err) {
162
+ logErr('Moleculer', err);
163
+ }
164
+ try {
165
+ await fastify.close();
166
+ }
167
+ catch (err) {
168
+ logErr('Fastify', err);
169
+ }
170
+ };
171
+ return {
172
+ get(ctor) {
173
+ return container.get(ctor);
174
+ },
175
+ getHttpAdapter() {
176
+ return { getInstance: () => fastify };
177
+ },
178
+ enableCors() {
179
+ fastify.addHook('onRequest', async (request, reply) => {
180
+ reply.header('access-control-allow-origin', '*');
181
+ reply.header('access-control-allow-methods', 'GET,POST,PUT,DELETE,OPTIONS,PATCH');
182
+ reply.header('access-control-allow-headers', 'content-type,authorization');
183
+ if (request.method === 'OPTIONS') {
184
+ reply.status(204).send();
185
+ }
186
+ });
187
+ },
188
+ enableShutdownHooks() {
189
+ if (shutdownHooksRegistered)
190
+ return;
191
+ shutdownHooksRegistered = true;
192
+ const handler = () => {
193
+ close().catch((err) => {
194
+ console.error('[manual-boot] shutdown close failed:', err);
195
+ });
196
+ };
197
+ process.once('SIGTERM', handler);
198
+ process.once('SIGINT', handler);
199
+ },
200
+ async init() {
201
+ // Init order (dependency-graph-derived):
202
+ // 1. MoleculerService — starts broker and calls EventBus.attachBroker()
203
+ // 2. AddonBridgeService — no cross-service deps
204
+ // 3. AgentRegistryService — subscribes to broker.localBus.on('$node.connected')
205
+ // 4. AddonRegistryService — boots every addon (needs broker live + config)
206
+ await moleculerService.onModuleInit();
207
+ await addonBridgeService.onModuleInit();
208
+ agentRegistryService.onModuleInit();
209
+ await addonRegistryService.onModuleInit();
210
+ // Reconcile already-connected agents now that the hub's installed
211
+ // addon set is populated. Agents online before the hub started never
212
+ // fire a fresh `$node.connected` event, so the per-connect trigger
213
+ // alone would miss them — this catches the boot-time case (e.g. a
214
+ // long-running dev-agent with a now-stale addon). Fire-and-forget so
215
+ // an unreachable agent never delays boot.
216
+ void agentRegistryService.reconcileConnectedAgents();
217
+ // Topology emitter must boot AFTER AgentRegistry + AddonRegistry —
218
+ // its initial snapshot reads both. Subscribes to lifecycle events
219
+ // for incremental snapshots and emits a periodic safety net.
220
+ topologyEmitterService.onModuleInit();
221
+ },
222
+ async listen(port, host) {
223
+ await fastify.listen({ port, host });
224
+ },
225
+ close,
226
+ };
227
+ }
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "private": false,
5
+ "files": [
6
+ "dist",
7
+ ".env.example"
8
+ ],
5
9
  "exports": {
6
10
  "./package.json": "./package.json",
7
11
  "./main.js": "./dist/main.js",
@@ -1,74 +0,0 @@
1
- // server/backend/src/__tests__/addon-install-e2e.test.ts
2
- //
3
- // E2E test: simulates addon directory scanning and verifies
4
- // addon packages with package.json + dist/ can be loaded.
5
- //
6
- import { describe, it, expect, beforeAll, afterAll } from 'vitest'
7
- import * as fs from 'node:fs'
8
- import * as path from 'node:path'
9
-
10
- const TEST_ADDONS_DIR = path.resolve('test-output/fresh-addons')
11
-
12
- describe('Addon Directory Loading', () => {
13
- beforeAll(() => {
14
- // Clean slate — delete the test addons directory
15
- fs.rmSync(TEST_ADDONS_DIR, { recursive: true, force: true })
16
- fs.mkdirSync(TEST_ADDONS_DIR, { recursive: true })
17
- })
18
-
19
- afterAll(() => {
20
- // Cleanup
21
- fs.rmSync(TEST_ADDONS_DIR, { recursive: true, force: true })
22
- })
23
-
24
- it('AddonInstaller initializes the addons directory', async () => {
25
- const { AddonInstaller } = await import('@camstack/kernel')
26
-
27
- // Constructor creates the addons directory if it doesn't exist
28
- const installer = new AddonInstaller({
29
- addonsDir: TEST_ADDONS_DIR,
30
- })
31
-
32
- // Verify directory exists and installer was created
33
- expect(installer.addonsDir).toBe(TEST_ADDONS_DIR)
34
- expect(fs.existsSync(TEST_ADDONS_DIR)).toBe(true)
35
- })
36
-
37
- it('AddonInstaller lists installed addon packages', async () => {
38
- const { AddonInstaller } = await import('@camstack/kernel')
39
-
40
- // Create a mock addon directory
41
- const addonDir = path.join(TEST_ADDONS_DIR, 'addon-test')
42
- fs.mkdirSync(addonDir, { recursive: true })
43
- fs.writeFileSync(
44
- path.join(addonDir, 'package.json'),
45
- JSON.stringify({
46
- name: '@camstack/addon-test',
47
- version: '1.0.0',
48
- camstack: { addons: [{ id: 'test', entry: './dist/index.js', slot: 'detector' }] },
49
- }),
50
- )
51
-
52
- const installer = new AddonInstaller({ addonsDir: TEST_ADDONS_DIR })
53
- const installed = installer.listInstalled()
54
-
55
- expect(installed).toHaveLength(1)
56
- expect(installed[0]).toMatchObject({
57
- name: '@camstack/addon-test',
58
- version: '1.0.0',
59
- })
60
-
61
- // Cleanup
62
- fs.rmSync(addonDir, { recursive: true, force: true })
63
- })
64
-
65
- it('AddonLoader scans addons directory and loads packages', async () => {
66
- const { AddonLoader } = await import('@camstack/kernel')
67
-
68
- const loader = new AddonLoader()
69
-
70
- // With an empty directory, no addons should be loaded
71
- await loader.loadFromDirectory(TEST_ADDONS_DIR)
72
- expect(loader.listAddons()).toHaveLength(0)
73
- })
74
- })
@@ -1,200 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access -- test file: mock AddonContext for `addon.initialize` is deliberately loose */
2
- // server/backend/src/__tests__/addon-pages-e2e.test.ts
3
- //
4
- // Integration tests: verify addon-pages capability wiring end-to-end,
5
- // BenchmarkAddon page registration, and AdminUIAddon capability provider.
6
- //
7
- import { describe, it, expect, vi } from 'vitest'
8
- import { CapabilityRegistry } from '@camstack/kernel'
9
- import type { AddonContext, IScopedLogger, ProviderRegistration } from '@camstack/types'
10
-
11
- // ---------------------------------------------------------------------------
12
- // Optional-module loader — returns the AdminUIAddon class if the admin-ui
13
- // addon is built and resolvable, otherwise null. Typed against a narrow
14
- // structural signature so the test never leaks `any` through the dynamic
15
- // import (the module exports its own dist path which may not exist when
16
- // the backend workspace is built in isolation).
17
- // ---------------------------------------------------------------------------
18
-
19
- interface AdminUIAddonInstance {
20
- readonly id: string
21
- initialize(ctx: AddonContext): Promise<
22
- | ProviderRegistration[]
23
- | void
24
- | undefined
25
- | {
26
- readonly providers?: readonly ProviderRegistration[]
27
- }
28
- >
29
- }
30
-
31
- type AdminUIAddonCtor = new () => AdminUIAddonInstance
32
-
33
- async function loadAdminUIAddon(): Promise<AdminUIAddonCtor | null> {
34
- try {
35
- const mod = (await import('@camstack/addon-admin-ui/server/addon')) as {
36
- AdminUIAddon?: AdminUIAddonCtor
37
- }
38
- return mod.AdminUIAddon ?? null
39
- } catch {
40
- return null
41
- }
42
- }
43
-
44
- function createMockLogger(): IScopedLogger {
45
- const logger: IScopedLogger = {
46
- error: vi.fn(),
47
- warn: vi.fn(),
48
- info: vi.fn(),
49
- debug: vi.fn(),
50
- child: vi.fn(() => logger),
51
- }
52
- return logger
53
- }
54
-
55
- describe('Addon Pages Integration', () => {
56
- it('BenchmarkAddon registers addon-pages capability and provides page list', async () => {
57
- const { BenchmarkAddon } = await import('@camstack/addon-benchmark')
58
- const addon = new BenchmarkAddon()
59
-
60
- // Addon has id directly — manifest is attached by AddonLoader at boot time
61
- expect(addon.id).toBe('benchmark')
62
-
63
- // Initialize with a mock context to capture registered providers.
64
- // Addons may register providers either via context.registerProvider() or
65
- // by returning AddonInitResult / ProviderRegistration[] from initialize().
66
- const providers = new Map<string, unknown>()
67
- const mockContext = {
68
- registerProvider: (capName: string, provider: unknown) => {
69
- providers.set(capName, provider)
70
- },
71
- }
72
- const result = await addon.initialize(mockContext as any)
73
- // Process return-value registrations (AddonInitResult.providers or ProviderRegistration[])
74
- if (result) {
75
- const regs = Array.isArray(result) ? result : ((result as any).providers ?? [])
76
- for (const reg of regs as ProviderRegistration[]) {
77
- const capName =
78
- typeof reg.capability === 'string'
79
- ? reg.capability
80
- : ((reg.capability as any)?.name ?? String(reg.capability))
81
- providers.set(capName, reg.provider)
82
- }
83
- }
84
-
85
- const provider = providers.get('addon-pages') as { getPages(): unknown[] } | undefined
86
- expect(provider).toBeDefined()
87
-
88
- const pages = provider!.getPages()
89
- expect(pages).toHaveLength(1)
90
-
91
- expect(pages[0]).toMatchObject({
92
- id: 'benchmark',
93
- label: 'Benchmark',
94
- icon: 'gauge',
95
- path: '/addon/benchmark',
96
- })
97
- })
98
-
99
- it('AdminUIAddon provides admin-ui capability with static dir and version', async () => {
100
- // Load `@camstack/addon-admin-ui/server/addon` lazily — the package exports
101
- // a dist/server path that may not exist when the backend workspace is
102
- // built in isolation (admin-ui has its own Vite-based build). Wrapping the
103
- // dynamic import in a helper means the test can be typed cleanly without
104
- // `@ts-expect-error`: when the module is absent we skip instead of
105
- // leaking `any` into the rest of the spec.
106
- const AdminUIAddonCtor = await loadAdminUIAddon()
107
- if (!AdminUIAddonCtor) {
108
- console.warn('[addon-pages-e2e] Skipping AdminUIAddon test — module not built')
109
- return
110
- }
111
-
112
- const addon = new AdminUIAddonCtor()
113
- expect(addon.id).toBe('admin-ui')
114
-
115
- const providers = new Map<string, unknown>()
116
- const mockContext = {
117
- registerProvider: (capName: string, provider: unknown) => {
118
- providers.set(capName, provider)
119
- },
120
- }
121
- const result = await addon.initialize(mockContext as any)
122
- // Process return-value registrations (AddonInitResult.providers or ProviderRegistration[])
123
- if (result) {
124
- const regs = Array.isArray(result) ? result : ((result as any).providers ?? [])
125
- for (const reg of regs as ProviderRegistration[]) {
126
- const capName =
127
- typeof reg.capability === 'string'
128
- ? reg.capability
129
- : ((reg.capability as any)?.name ?? String(reg.capability))
130
- providers.set(capName, reg.provider)
131
- }
132
- }
133
-
134
- const ui = providers.get('admin-ui') as
135
- | {
136
- getStaticDir(): Promise<{ readonly staticDir: string }>
137
- getVersion(): Promise<{ readonly version: string }>
138
- }
139
- | undefined
140
- expect(ui).toBeDefined()
141
- expect(typeof ui!.getStaticDir).toBe('function')
142
- expect(typeof ui!.getVersion).toBe('function')
143
- const version = await ui!.getVersion()
144
- expect(version.version).toBe('0.1.0')
145
- })
146
-
147
- it('CapabilityRegistry wires addon-pages collection providers correctly', () => {
148
- const registry = new CapabilityRegistry(createMockLogger())
149
- registry.ready()
150
-
151
- registry.declareCapability({
152
- name: 'addon-pages',
153
- scope: 'system',
154
- mode: 'collection',
155
- methods: {},
156
- })
157
-
158
- registry.registerProvider('addon-pages', 'benchmark', {
159
- id: 'benchmark',
160
- getPages: () => [
161
- {
162
- id: 'benchmark',
163
- label: 'Benchmark',
164
- icon: 'gauge',
165
- path: '/addon/benchmark',
166
- bundle: 'dist/pages/benchmark.js',
167
- element: 'camstack-benchmark',
168
- },
169
- ],
170
- })
171
-
172
- const providers = registry.getCollection<{ getPages(): unknown[] }>('addon-pages')
173
- expect(providers).toHaveLength(1)
174
- expect(providers[0].getPages()).toHaveLength(1)
175
- })
176
-
177
- it('CapabilityRegistry wires admin-ui singleton correctly', async () => {
178
- const registry = new CapabilityRegistry(createMockLogger())
179
- registry.ready()
180
-
181
- registry.declareCapability({
182
- name: 'admin-ui',
183
- scope: 'system',
184
- mode: 'singleton',
185
- methods: {},
186
- })
187
-
188
- registry.registerProvider('admin-ui', 'admin-ui', {
189
- getStaticDir: async () => ({ staticDir: '/some/path/dist' }),
190
- getVersion: async () => ({ version: '0.1.0' }),
191
- })
192
-
193
- const ui = registry.getSingleton<{ getVersion(): Promise<{ readonly version: string }> }>(
194
- 'admin-ui',
195
- )
196
- expect(ui).toBeDefined()
197
- const version = await ui!.getVersion()
198
- expect(version.version).toBe('0.1.0')
199
- })
200
- })
@@ -1,17 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { shouldRedirectToLogin } from '../auth/session-cookie.js'
3
-
4
- describe('shouldRedirectToLogin', () => {
5
- it('true for an HTML GET with no credentials', () => {
6
- expect(shouldRedirectToLogin('GET', 'text/html,application/xhtml+xml')).toBe(true)
7
- })
8
- it('false for a non-GET', () => {
9
- expect(shouldRedirectToLogin('POST', 'text/html')).toBe(false)
10
- })
11
- it('false for an API GET (JSON accept)', () => {
12
- expect(shouldRedirectToLogin('GET', 'application/json')).toBe(false)
13
- })
14
- it('false when Accept is absent', () => {
15
- expect(shouldRedirectToLogin('GET', undefined)).toBe(false)
16
- })
17
- })
@@ -1,67 +0,0 @@
1
- /* eslint-disable @typescript-eslint/consistent-type-assertions -- test mock typing */
2
- import { describe, it, expect, beforeEach } from 'vitest'
3
- import { createAddonSettingsRouter } from '../api/core/addon-settings.router.js'
4
- import { makeCtx } from './cap-routers/harness.js'
5
-
6
- function createMockConfigService() {
7
- const addonStore = new Map<string, Record<string, unknown>>()
8
- const deviceStore = new Map<string, Record<string, unknown>>()
9
-
10
- return {
11
- getAddonConfig: (id: string) => addonStore.get(id) ?? {},
12
- setAddonConfig: (id: string, v: Record<string, unknown>) => {
13
- addonStore.set(id, v)
14
- },
15
- getAddonDevice: (id: string, devId: string) => deviceStore.get(`${id}:${devId}`) ?? {},
16
- setAddonDevice: (id: string, devId: string, v: Record<string, unknown>) => {
17
- deviceStore.set(`${id}:${devId}`, v)
18
- },
19
- // Cast to satisfy the ConfigService type expected by the router factory.
20
- // Only the four methods above are exercised by the router.
21
- } as Parameters<typeof createAddonSettingsRouter>[0]
22
- }
23
-
24
- describe('addon-settings router', () => {
25
- let caller: ReturnType<ReturnType<typeof createAddonSettingsRouter>['createCaller']>
26
-
27
- beforeEach(() => {
28
- const cfg = createMockConfigService()
29
- const router = createAddonSettingsRouter(cfg)
30
- const ctx = makeCtx('admin')
31
- caller = router.createCaller(ctx)
32
- })
33
-
34
- it('getGlobal returns empty object when no stored config', async () => {
35
- const result = await caller.getGlobal({ addonId: 'my-addon' })
36
- expect(result).toEqual({})
37
- })
38
-
39
- it('updateGlobal stores a field and getGlobal reflects it', async () => {
40
- await caller.updateGlobal({ addonId: 'my-addon', field: 'threshold', value: 42 })
41
- const result = await caller.getGlobal({ addonId: 'my-addon' })
42
- expect(result).toEqual({ threshold: 42 })
43
- })
44
-
45
- it('getDeviceOverrides returns empty object when no stored overrides', async () => {
46
- const result = await caller.getDeviceOverrides({ addonId: 'my-addon', deviceId: 'cam-1' })
47
- expect(result).toEqual({})
48
- })
49
-
50
- it('updateDevice stores a field and getDeviceOverrides reflects it', async () => {
51
- await caller.updateDevice({
52
- addonId: 'my-addon',
53
- deviceId: 'cam-1',
54
- field: 'resolution',
55
- value: '1080p',
56
- })
57
- const result = await caller.getDeviceOverrides({ addonId: 'my-addon', deviceId: 'cam-1' })
58
- expect(result).toEqual({ resolution: '1080p' })
59
- })
60
-
61
- it('updateGlobal merges with existing config without overwriting other fields', async () => {
62
- await caller.updateGlobal({ addonId: 'my-addon', field: 'alpha', value: 1 })
63
- await caller.updateGlobal({ addonId: 'my-addon', field: 'beta', value: 2 })
64
- const result = await caller.getGlobal({ addonId: 'my-addon' })
65
- expect(result).toEqual({ alpha: 1, beta: 2 })
66
- })
67
- })