@camstack/server 0.1.3
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/.env.example +17 -0
- package/package.json +55 -0
- package/src/__tests__/addon-install-e2e.test.ts +75 -0
- package/src/__tests__/addon-pages-e2e.test.ts +178 -0
- package/src/__tests__/addon-route-session.test.ts +17 -0
- package/src/__tests__/addon-settings-router.spec.ts +62 -0
- package/src/__tests__/addon-upload.spec.ts +355 -0
- package/src/__tests__/agent-registry.spec.ts +162 -0
- package/src/__tests__/agent-status-page.spec.ts +84 -0
- package/src/__tests__/auth-session-cookie.test.ts +21 -0
- package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +23 -0
- package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +64 -0
- package/src/__tests__/cap-routers/_meta.spec.ts +200 -0
- package/src/__tests__/cap-routers/addon-settings.router.spec.ts +106 -0
- package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +142 -0
- package/src/__tests__/cap-routers/harness.ts +159 -0
- package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +119 -0
- package/src/__tests__/cap-routers/null-provider-guard.spec.ts +66 -0
- package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +135 -0
- package/src/__tests__/cap-routers/settings-store.router.spec.ts +247 -0
- package/src/__tests__/capability-e2e.test.ts +386 -0
- package/src/__tests__/cli-e2e.test.ts +129 -0
- package/src/__tests__/core-cap-bridge.spec.ts +89 -0
- package/src/__tests__/embedded-deps-e2e.test.ts +109 -0
- package/src/__tests__/event-bus-proxy-router.spec.ts +72 -0
- package/src/__tests__/fixtures/mock-analysis-addon-a.ts +37 -0
- package/src/__tests__/fixtures/mock-analysis-addon-b.ts +37 -0
- package/src/__tests__/fixtures/mock-log-addon.ts +37 -0
- package/src/__tests__/fixtures/mock-storage-addon.ts +40 -0
- package/src/__tests__/framework-allowlist.spec.ts +95 -0
- package/src/__tests__/https-e2e.test.ts +118 -0
- package/src/__tests__/lifecycle-e2e.test.ts +140 -0
- package/src/__tests__/live-events-subscription.spec.ts +150 -0
- package/src/__tests__/moleculer-register-node-idempotency.spec.ts +229 -0
- package/src/__tests__/oauth2-account-linking.spec.ts +736 -0
- package/src/__tests__/post-boot-restart.spec.ts +161 -0
- package/src/__tests__/singleton-contention.test.ts +487 -0
- package/src/__tests__/streaming-diagnostic.test.ts +512 -0
- package/src/__tests__/streaming-scale.test.ts +280 -0
- package/src/agent-status-page.ts +121 -0
- package/src/api/__tests__/addons-custom.spec.ts +134 -0
- package/src/api/__tests__/capabilities.router.test.ts +47 -0
- package/src/api/addon-upload.ts +472 -0
- package/src/api/addons-custom.router.ts +100 -0
- package/src/api/auth-whoami.ts +99 -0
- package/src/api/bridge-addons.router.ts +120 -0
- package/src/api/capabilities.router.ts +226 -0
- package/src/api/core/__tests__/auth-router-totp.spec.ts +256 -0
- package/src/api/core/addon-settings.router.ts +124 -0
- package/src/api/core/agents.router.ts +87 -0
- package/src/api/core/auth.router.ts +303 -0
- package/src/api/core/cap-providers.ts +993 -0
- package/src/api/core/capabilities.router.ts +119 -0
- package/src/api/core/collection-preference.ts +40 -0
- package/src/api/core/event-bus-proxy.router.ts +45 -0
- package/src/api/core/hwaccel.router.ts +81 -0
- package/src/api/core/live-events.router.ts +60 -0
- package/src/api/core/logs.router.ts +162 -0
- package/src/api/core/notifications.router.ts +65 -0
- package/src/api/core/repl.router.ts +41 -0
- package/src/api/core/settings-backend.router.ts +142 -0
- package/src/api/core/stream-probe.router.ts +57 -0
- package/src/api/core/system-events.router.ts +116 -0
- package/src/api/health/health.routes.ts +123 -0
- package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +52 -0
- package/src/api/oauth2/consent-page.ts +42 -0
- package/src/api/oauth2/oauth2-routes.ts +248 -0
- package/src/api/trpc/__tests__/scope-access-device.spec.ts +223 -0
- package/src/api/trpc/__tests__/scope-access.spec.ts +107 -0
- package/src/api/trpc/cap-mount-helpers.ts +225 -0
- package/src/api/trpc/core-cap-bridge.ts +152 -0
- package/src/api/trpc/generated-cap-mounts.ts +707 -0
- package/src/api/trpc/generated-cap-routers.ts +6340 -0
- package/src/api/trpc/scope-access.ts +110 -0
- package/src/api/trpc/trpc.context.ts +255 -0
- package/src/api/trpc/trpc.middleware.ts +140 -0
- package/src/api/trpc/trpc.router.ts +275 -0
- package/src/auth/session-cookie.ts +44 -0
- package/src/boot/boot-config.ts +278 -0
- package/src/boot/post-boot.service.ts +103 -0
- package/src/core/addon/__tests__/addon-registry-capability.test.ts +53 -0
- package/src/core/addon/addon-package.service.ts +1684 -0
- package/src/core/addon/addon-registry.service.ts +2926 -0
- package/src/core/addon/addon-search.service.ts +90 -0
- package/src/core/addon/addon-settings-provider.ts +276 -0
- package/src/core/addon/addon.tokens.ts +2 -0
- package/src/core/addon-bridge/addon-bridge.service.ts +125 -0
- package/src/core/addon-pages/addon-pages.service.spec.ts +117 -0
- package/src/core/addon-pages/addon-pages.service.ts +80 -0
- package/src/core/addon-widgets/addon-widgets.service.ts +92 -0
- package/src/core/agent/agent-registry.service.ts +507 -0
- package/src/core/auth/auth.service.spec.ts +88 -0
- package/src/core/auth/auth.service.ts +8 -0
- package/src/core/capability/capability.service.ts +57 -0
- package/src/core/config/config.schema.ts +3 -0
- package/src/core/config/config.service.spec.ts +175 -0
- package/src/core/config/config.service.ts +7 -0
- package/src/core/events/event-bus.service.spec.ts +212 -0
- package/src/core/events/event-bus.service.ts +85 -0
- package/src/core/feature/feature.service.spec.ts +96 -0
- package/src/core/feature/feature.service.ts +8 -0
- package/src/core/lifecycle/lifecycle-state-machine.spec.ts +168 -0
- package/src/core/lifecycle/lifecycle-state-machine.ts +3 -0
- package/src/core/logging/log-ring-buffer.ts +3 -0
- package/src/core/logging/logging.service.spec.ts +247 -0
- package/src/core/logging/logging.service.ts +129 -0
- package/src/core/logging/scoped-logger.ts +3 -0
- package/src/core/moleculer/moleculer.service.ts +612 -0
- package/src/core/network/network-quality.service.spec.ts +47 -0
- package/src/core/network/network-quality.service.ts +5 -0
- package/src/core/notification/notification-wrapper.service.ts +36 -0
- package/src/core/notification/toast-wrapper.service.ts +31 -0
- package/src/core/provider/provider.tokens.ts +1 -0
- package/src/core/repl/repl-engine.service.spec.ts +417 -0
- package/src/core/repl/repl-engine.service.ts +156 -0
- package/src/core/storage/fs-storage-backend.spec.ts +70 -0
- package/src/core/storage/fs-storage-backend.ts +3 -0
- package/src/core/storage/settings-store.spec.ts +213 -0
- package/src/core/storage/settings-store.ts +2 -0
- package/src/core/storage/sql-schema.spec.ts +140 -0
- package/src/core/storage/sql-schema.ts +3 -0
- package/src/core/storage/storage-location-manager.spec.ts +121 -0
- package/src/core/storage/storage-location-manager.ts +3 -0
- package/src/core/storage/storage.service.spec.ts +73 -0
- package/src/core/storage/storage.service.ts +3 -0
- package/src/core/streaming/stream-probe.service.ts +212 -0
- package/src/core/topology/topology-emitter.service.ts +101 -0
- package/src/launcher.ts +309 -0
- package/src/main.ts +1049 -0
- package/src/manual-boot.ts +322 -0
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +26 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot — constructs every service by hand in dependency order, runs
|
|
3
|
+
* `onModuleInit` hooks in sequence, and exposes a `BootedApp` facade
|
|
4
|
+
* around the Fastify instance. This is the only boot path; NestJS was
|
|
5
|
+
* removed to drop the accidental DI layer on top of an already-explicit
|
|
6
|
+
* dependency graph.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import Fastify from "fastify";
|
|
10
|
+
import type { FastifyInstance } from "fastify";
|
|
11
|
+
import type { InfraContext } from "./boot/boot-config";
|
|
12
|
+
|
|
13
|
+
import { ConfigService } from "./core/config/config.service";
|
|
14
|
+
import { LoggingService } from "./core/logging/logging.service";
|
|
15
|
+
import { EventBusService } from "./core/events/event-bus.service";
|
|
16
|
+
import { StorageService } from "./core/storage/storage.service";
|
|
17
|
+
import { CapabilityService } from "./core/capability/capability.service";
|
|
18
|
+
import { FeatureService } from "./core/feature/feature.service";
|
|
19
|
+
import { AuthService } from "./core/auth/auth.service";
|
|
20
|
+
import { StreamProbeService } from "./core/streaming/stream-probe.service";
|
|
21
|
+
import { NetworkQualityService } from "./core/network/network-quality.service";
|
|
22
|
+
import { ToastServiceWrapper } from "./core/notification/toast-wrapper.service";
|
|
23
|
+
import { NotificationServiceWrapper } from "./core/notification/notification-wrapper.service";
|
|
24
|
+
import { AddonPagesService } from "./core/addon-pages/addon-pages.service";
|
|
25
|
+
import { AddonWidgetsService } from "./core/addon-widgets/addon-widgets.service";
|
|
26
|
+
import { AddonBridgeService } from "./core/addon-bridge/addon-bridge.service";
|
|
27
|
+
import { MoleculerService } from "./core/moleculer/moleculer.service";
|
|
28
|
+
import { AgentRegistryService } from "./core/agent/agent-registry.service";
|
|
29
|
+
import { AddonRegistryService } from "./core/addon/addon-registry.service";
|
|
30
|
+
import { AddonSearchService } from "./core/addon/addon-search.service";
|
|
31
|
+
import { AddonPackageService } from "./core/addon/addon-package.service";
|
|
32
|
+
import { ReplEngineService } from "./core/repl/repl-engine.service";
|
|
33
|
+
import { TopologyEmitterService } from "./core/topology/topology-emitter.service";
|
|
34
|
+
import { PostBootService } from "./boot/post-boot.service";
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Types
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
type AnyCtor<T> = abstract new (...args: never[]) => T;
|
|
41
|
+
|
|
42
|
+
export interface BootedApp {
|
|
43
|
+
/** Resolve a service by its constructor. Throws if unregistered. */
|
|
44
|
+
get<T>(ctor: AnyCtor<T>): T;
|
|
45
|
+
/** Returns the Fastify instance for plugin registration. */
|
|
46
|
+
getHttpAdapter(): { getInstance(): FastifyInstance };
|
|
47
|
+
/** Register a permissive CORS hook (allow all origins). */
|
|
48
|
+
enableCors(): void;
|
|
49
|
+
/** Register SIGTERM/SIGINT → close() for graceful shutdown. */
|
|
50
|
+
enableShutdownHooks(): void;
|
|
51
|
+
/** Run OnModuleInit hooks in the order Nest's dependency graph produces. */
|
|
52
|
+
init(): Promise<void>;
|
|
53
|
+
/** Start the Fastify HTTP server. */
|
|
54
|
+
listen(port: number, host: string): Promise<void>;
|
|
55
|
+
/** Run OnModuleDestroy hooks in reverse init order, then close Fastify. */
|
|
56
|
+
close(): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface BootOptions {
|
|
60
|
+
infra: InfraContext;
|
|
61
|
+
fastifyOpts: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Service container — narrowing via `instanceof`, no casts.
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
class ServiceContainer {
|
|
69
|
+
private readonly services = new Map<unknown, unknown>();
|
|
70
|
+
|
|
71
|
+
register<T extends object>(ctor: AnyCtor<T>, instance: T): void {
|
|
72
|
+
this.services.set(ctor, instance);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get<T>(ctor: AnyCtor<T>): T {
|
|
76
|
+
const inst = this.services.get(ctor);
|
|
77
|
+
if (inst === undefined) {
|
|
78
|
+
throw new Error(`ServiceContainer: ${ctor.name} is not registered`);
|
|
79
|
+
}
|
|
80
|
+
if (!(inst instanceof ctor)) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`ServiceContainer: ${ctor.name} instance type mismatch at resolve time`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return inst;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Manual boot
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
export async function bootManual(opts: BootOptions): Promise<BootedApp> {
|
|
94
|
+
const { infra, fastifyOpts } = opts;
|
|
95
|
+
void infra; // infraContext is consumed by main.ts directly (locationManager, etc.)
|
|
96
|
+
|
|
97
|
+
const configPath =
|
|
98
|
+
process.env.CONFIG_PATH ?? "camstack-data/config.yaml";
|
|
99
|
+
|
|
100
|
+
// ---- Layer 0: services with no service deps -----------------------------
|
|
101
|
+
const configService = new ConfigService(configPath);
|
|
102
|
+
const storageService = new StorageService();
|
|
103
|
+
const capabilityService = new CapabilityService();
|
|
104
|
+
const networkQualityService = new NetworkQualityService();
|
|
105
|
+
const toastWrapper = new ToastServiceWrapper();
|
|
106
|
+
|
|
107
|
+
// ---- Layer 1: need ConfigService / LoggingService -----------------------
|
|
108
|
+
const loggingService = new LoggingService(configService);
|
|
109
|
+
const eventBusService = new EventBusService(configService);
|
|
110
|
+
// Wire device-name cache for log formatter — subscribes here so the
|
|
111
|
+
// very first DeviceRegistered event populates the cache before any
|
|
112
|
+
// log line needing `deviceName` is emitted.
|
|
113
|
+
loggingService.attachDeviceNameStream(eventBusService);
|
|
114
|
+
const featureService = new FeatureService(configService);
|
|
115
|
+
const authService = new AuthService(configService);
|
|
116
|
+
const streamProbeService = new StreamProbeService(loggingService);
|
|
117
|
+
const addonBridgeService = new AddonBridgeService(loggingService);
|
|
118
|
+
const addonSearchService = new AddonSearchService(loggingService);
|
|
119
|
+
|
|
120
|
+
// ---- Layer 2: need Layer 0/1 -------------------------------------------
|
|
121
|
+
const notificationWrapper = new NotificationServiceWrapper(
|
|
122
|
+
capabilityService,
|
|
123
|
+
loggingService,
|
|
124
|
+
);
|
|
125
|
+
const addonPagesService = new AddonPagesService(
|
|
126
|
+
loggingService,
|
|
127
|
+
configService,
|
|
128
|
+
capabilityService,
|
|
129
|
+
);
|
|
130
|
+
const moleculerService = new MoleculerService(
|
|
131
|
+
eventBusService,
|
|
132
|
+
configService,
|
|
133
|
+
loggingService,
|
|
134
|
+
capabilityService,
|
|
135
|
+
streamProbeService,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// ---- Layer 3: need Moleculer / AddonRegistry ---------------------------
|
|
139
|
+
const agentRegistryService = new AgentRegistryService(
|
|
140
|
+
eventBusService,
|
|
141
|
+
moleculerService,
|
|
142
|
+
capabilityService,
|
|
143
|
+
);
|
|
144
|
+
const addonRegistryService = new AddonRegistryService(
|
|
145
|
+
loggingService,
|
|
146
|
+
eventBusService,
|
|
147
|
+
configService,
|
|
148
|
+
storageService,
|
|
149
|
+
capabilityService,
|
|
150
|
+
moleculerService,
|
|
151
|
+
streamProbeService,
|
|
152
|
+
);
|
|
153
|
+
// AgentRegistryService needs the hub addon registry for placement-based
|
|
154
|
+
// reconciliation, but is constructed first (it has no other dependency
|
|
155
|
+
// on AddonRegistryService) — wire it post-construction.
|
|
156
|
+
agentRegistryService.setAddonRegistry(addonRegistryService);
|
|
157
|
+
|
|
158
|
+
// ---- Layer 4: need AddonRegistry ---------------------------------------
|
|
159
|
+
// AddonWidgetsService — needs AddonRegistryService for the bundled-addon
|
|
160
|
+
// dist sub-folder lookup (see service docstring).
|
|
161
|
+
const addonWidgetsService = new AddonWidgetsService(
|
|
162
|
+
loggingService,
|
|
163
|
+
capabilityService,
|
|
164
|
+
addonRegistryService,
|
|
165
|
+
);
|
|
166
|
+
const addonPackageService = new AddonPackageService(
|
|
167
|
+
loggingService,
|
|
168
|
+
eventBusService,
|
|
169
|
+
configService,
|
|
170
|
+
addonRegistryService,
|
|
171
|
+
notificationWrapper,
|
|
172
|
+
toastWrapper,
|
|
173
|
+
);
|
|
174
|
+
const replEngineService = new ReplEngineService(
|
|
175
|
+
addonRegistryService,
|
|
176
|
+
eventBusService,
|
|
177
|
+
loggingService,
|
|
178
|
+
);
|
|
179
|
+
const topologyEmitterService = new TopologyEmitterService(
|
|
180
|
+
eventBusService,
|
|
181
|
+
agentRegistryService,
|
|
182
|
+
addonRegistryService,
|
|
183
|
+
);
|
|
184
|
+
const postBootService = new PostBootService(
|
|
185
|
+
addonRegistryService,
|
|
186
|
+
eventBusService,
|
|
187
|
+
loggingService,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// ---- Container ---------------------------------------------------------
|
|
191
|
+
const container = new ServiceContainer();
|
|
192
|
+
container.register(ConfigService, configService);
|
|
193
|
+
container.register(LoggingService, loggingService);
|
|
194
|
+
container.register(EventBusService, eventBusService);
|
|
195
|
+
container.register(StorageService, storageService);
|
|
196
|
+
container.register(CapabilityService, capabilityService);
|
|
197
|
+
container.register(FeatureService, featureService);
|
|
198
|
+
container.register(AuthService, authService);
|
|
199
|
+
container.register(StreamProbeService, streamProbeService);
|
|
200
|
+
container.register(NetworkQualityService, networkQualityService);
|
|
201
|
+
container.register(ToastServiceWrapper, toastWrapper);
|
|
202
|
+
container.register(NotificationServiceWrapper, notificationWrapper);
|
|
203
|
+
container.register(AddonPagesService, addonPagesService);
|
|
204
|
+
container.register(AddonWidgetsService, addonWidgetsService);
|
|
205
|
+
container.register(AddonBridgeService, addonBridgeService);
|
|
206
|
+
container.register(MoleculerService, moleculerService);
|
|
207
|
+
container.register(AgentRegistryService, agentRegistryService);
|
|
208
|
+
container.register(AddonRegistryService, addonRegistryService);
|
|
209
|
+
container.register(AddonSearchService, addonSearchService);
|
|
210
|
+
container.register(AddonPackageService, addonPackageService);
|
|
211
|
+
container.register(ReplEngineService, replEngineService);
|
|
212
|
+
container.register(TopologyEmitterService, topologyEmitterService);
|
|
213
|
+
container.register(PostBootService, postBootService);
|
|
214
|
+
|
|
215
|
+
// ---- Fastify instance --------------------------------------------------
|
|
216
|
+
const fastify = Fastify(fastifyOpts);
|
|
217
|
+
|
|
218
|
+
// /health and /health/* — registered in `main.ts` via
|
|
219
|
+
// `registerHealthRoutes` once MoleculerService + AgentRegistryService are
|
|
220
|
+
// available. The detailed shape is shared with each agent's `/health`
|
|
221
|
+
// (see `packages/agent/src/agent-http.ts`).
|
|
222
|
+
|
|
223
|
+
// ---- Shutdown coordination --------------------------------------------
|
|
224
|
+
let closed = false;
|
|
225
|
+
let shutdownHooksRegistered = false;
|
|
226
|
+
|
|
227
|
+
const close = async (): Promise<void> => {
|
|
228
|
+
if (closed) return;
|
|
229
|
+
closed = true;
|
|
230
|
+
const logErr = (label: string, err: unknown): void => {
|
|
231
|
+
console.error(`[manual-boot] ${label} destroy failed:`, err);
|
|
232
|
+
};
|
|
233
|
+
try {
|
|
234
|
+
topologyEmitterService.onModuleDestroy();
|
|
235
|
+
} catch (err) {
|
|
236
|
+
logErr("TopologyEmitter", err);
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
await addonRegistryService.onModuleDestroy();
|
|
240
|
+
} catch (err) {
|
|
241
|
+
logErr("AddonRegistry", err);
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
await addonBridgeService.onModuleDestroy();
|
|
245
|
+
} catch (err) {
|
|
246
|
+
logErr("AddonBridge", err);
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
await moleculerService.onModuleDestroy();
|
|
250
|
+
} catch (err) {
|
|
251
|
+
logErr("Moleculer", err);
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
await fastify.close();
|
|
255
|
+
} catch (err) {
|
|
256
|
+
logErr("Fastify", err);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
get<T>(ctor: AnyCtor<T>): T {
|
|
262
|
+
return container.get(ctor);
|
|
263
|
+
},
|
|
264
|
+
getHttpAdapter() {
|
|
265
|
+
return { getInstance: () => fastify };
|
|
266
|
+
},
|
|
267
|
+
enableCors() {
|
|
268
|
+
fastify.addHook("onRequest", async (request, reply) => {
|
|
269
|
+
reply.header("access-control-allow-origin", "*");
|
|
270
|
+
reply.header(
|
|
271
|
+
"access-control-allow-methods",
|
|
272
|
+
"GET,POST,PUT,DELETE,OPTIONS,PATCH",
|
|
273
|
+
);
|
|
274
|
+
reply.header(
|
|
275
|
+
"access-control-allow-headers",
|
|
276
|
+
"content-type,authorization",
|
|
277
|
+
);
|
|
278
|
+
if (request.method === "OPTIONS") {
|
|
279
|
+
reply.status(204).send();
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
},
|
|
283
|
+
enableShutdownHooks() {
|
|
284
|
+
if (shutdownHooksRegistered) return;
|
|
285
|
+
shutdownHooksRegistered = true;
|
|
286
|
+
const handler = (): void => {
|
|
287
|
+
close().catch((err) => {
|
|
288
|
+
console.error("[manual-boot] shutdown close failed:", err);
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
process.once("SIGTERM", handler);
|
|
292
|
+
process.once("SIGINT", handler);
|
|
293
|
+
},
|
|
294
|
+
async init() {
|
|
295
|
+
// Init order (dependency-graph-derived):
|
|
296
|
+
// 1. MoleculerService — starts broker and calls EventBus.attachBroker()
|
|
297
|
+
// 2. AddonBridgeService — no cross-service deps
|
|
298
|
+
// 3. AgentRegistryService — subscribes to broker.localBus.on('$node.connected')
|
|
299
|
+
// 4. AddonRegistryService — boots every addon (needs broker live + config)
|
|
300
|
+
await moleculerService.onModuleInit();
|
|
301
|
+
await addonBridgeService.onModuleInit();
|
|
302
|
+
agentRegistryService.onModuleInit();
|
|
303
|
+
await addonRegistryService.onModuleInit();
|
|
304
|
+
// Reconcile already-connected agents now that the hub's installed
|
|
305
|
+
// addon set is populated. Agents online before the hub started never
|
|
306
|
+
// fire a fresh `$node.connected` event, so the per-connect trigger
|
|
307
|
+
// alone would miss them — this catches the boot-time case (e.g. a
|
|
308
|
+
// long-running dev-agent with a now-stale addon). Fire-and-forget so
|
|
309
|
+
// an unreachable agent never delays boot.
|
|
310
|
+
void agentRegistryService.reconcileConnectedAgents();
|
|
311
|
+
// Topology emitter must boot AFTER AgentRegistry + AddonRegistry —
|
|
312
|
+
// its initial snapshot reads both. Subscribes to lifecycle events
|
|
313
|
+
// for incremental snapshots and emits a periodic safety net.
|
|
314
|
+
topologyEmitterService.onModuleInit();
|
|
315
|
+
},
|
|
316
|
+
async listen(port, host) {
|
|
317
|
+
await fastify.listen({ port, host });
|
|
318
|
+
},
|
|
319
|
+
close,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"module": "CommonJS",
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"lib": ["ES2022"],
|
|
8
|
+
"outDir": "dist",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"experimentalDecorators": true,
|
|
12
|
+
"emitDecoratorMetadata": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"types": ["node"],
|
|
17
|
+
"baseUrl": "../.."
|
|
18
|
+
},
|
|
19
|
+
"include": ["src"],
|
|
20
|
+
"exclude": ["src/scripts", "src/**/__tests__", "src/**/*.spec.ts", "src/**/*.test.ts"]
|
|
21
|
+
}
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config'
|
|
2
|
+
import swc from 'unplugin-swc'
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
test: {
|
|
6
|
+
globals: true,
|
|
7
|
+
root: './src',
|
|
8
|
+
// Use threads pool (default) - inherits env vars
|
|
9
|
+
fileParallelism: false,
|
|
10
|
+
// Exclude e2e/smoke tests — they boot the full server and hang in CI
|
|
11
|
+
exclude: [
|
|
12
|
+
'**/*.e2e.test.ts',
|
|
13
|
+
'**/*e2e*.test.ts',
|
|
14
|
+
'**/*smoke*.test.ts',
|
|
15
|
+
'**/*diagnostic*.test.ts',
|
|
16
|
+
'**/*scale*.test.ts',
|
|
17
|
+
'**/node_modules/**',
|
|
18
|
+
],
|
|
19
|
+
testTimeout: 30_000,
|
|
20
|
+
teardownTimeout: 5_000,
|
|
21
|
+
hookTimeout: 10_000,
|
|
22
|
+
// Use forks pool to isolate tests in separate processes (prevents hanging from open handles)
|
|
23
|
+
pool: 'forks',
|
|
24
|
+
},
|
|
25
|
+
plugins: [swc.vite({ module: { type: 'es6' } })],
|
|
26
|
+
})
|