@camstack/server 0.1.7 → 0.2.0

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 (135) hide show
  1. package/package.json +11 -9
  2. package/src/__tests__/addon-install-e2e.test.ts +0 -1
  3. package/src/__tests__/addon-pages-e2e.test.ts +40 -18
  4. package/src/__tests__/addon-settings-router.spec.ts +6 -1
  5. package/src/__tests__/addon-upload.spec.ts +91 -29
  6. package/src/__tests__/agent-registry.spec.ts +26 -9
  7. package/src/__tests__/agent-status-page.spec.ts +1 -3
  8. package/src/__tests__/auth-session-cookie.test.ts +28 -1
  9. package/src/__tests__/bulk-update-coordinator.spec.ts +48 -31
  10. package/src/__tests__/cap-ownership-authority.spec.ts +39 -8
  11. package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +206 -0
  12. package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +17 -3
  13. package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +57 -11
  14. package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +292 -0
  15. package/src/__tests__/cap-providers-bulk-update.spec.ts +27 -7
  16. package/src/__tests__/cap-route-adapter.spec.ts +28 -15
  17. package/src/__tests__/cap-routers/_meta.spec.ts +6 -7
  18. package/src/__tests__/cap-routers/addon-settings.router.spec.ts +19 -10
  19. package/src/__tests__/cap-routers/broker-routing.router.spec.ts +177 -0
  20. package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +3 -1
  21. package/src/__tests__/cap-routers/capabilities-node.spec.ts +18 -5
  22. package/src/__tests__/cap-routers/device-link-overlay.spec.ts +137 -0
  23. package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +72 -20
  24. package/src/__tests__/cap-routers/harness.ts +11 -7
  25. package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +17 -3
  26. package/src/__tests__/cap-routers/null-provider-guard.spec.ts +5 -7
  27. package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +35 -11
  28. package/src/__tests__/cap-routers/settings-store.router.spec.ts +59 -15
  29. package/src/__tests__/capability-e2e.test.ts +9 -11
  30. package/src/__tests__/cli-e2e.test.ts +80 -59
  31. package/src/__tests__/core-cap-bridge.spec.ts +3 -1
  32. package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +12 -2
  33. package/src/__tests__/device-settings-contribution-dispatch.spec.ts +61 -30
  34. package/src/__tests__/embedded-deps-e2e.test.ts +35 -19
  35. package/src/__tests__/event-bus-proxy-router.spec.ts +3 -0
  36. package/src/__tests__/framework-allowlist.spec.ts +5 -4
  37. package/src/__tests__/https-e2e.test.ts +12 -6
  38. package/src/__tests__/lifecycle-e2e.test.ts +60 -11
  39. package/src/__tests__/live-events-subscription.spec.ts +17 -18
  40. package/src/__tests__/moleculer/uds-readiness.spec.ts +11 -4
  41. package/src/__tests__/moleculer/uds-topology.spec.ts +39 -11
  42. package/src/__tests__/moleculer/uds-unowned-call.spec.ts +265 -5
  43. package/src/__tests__/moleculer-register-node-idempotency.spec.ts +16 -7
  44. package/src/__tests__/native-cap-route.spec.ts +42 -19
  45. package/src/__tests__/oauth2-account-linking.spec.ts +63 -17
  46. package/src/__tests__/singleton-contention.test.ts +23 -11
  47. package/src/__tests__/streaming-diagnostic.test.ts +156 -53
  48. package/src/__tests__/streaming-scale.test.ts +69 -35
  49. package/src/__tests__/uds-addon-call-wiring.spec.ts +6 -1
  50. package/src/agent-status-page.ts +4 -3
  51. package/src/api/__tests__/addons-custom.spec.ts +22 -8
  52. package/src/api/__tests__/capabilities.router.test.ts +18 -9
  53. package/src/api/addon-upload.ts +46 -15
  54. package/src/api/addons-custom.router.ts +7 -6
  55. package/src/api/auth-whoami.ts +3 -1
  56. package/src/api/bridge-addons.router.ts +3 -1
  57. package/src/api/capabilities.router.ts +117 -78
  58. package/src/api/core/__tests__/auth-router-totp.spec.ts +57 -16
  59. package/src/api/core/__tests__/integration-markers.spec.ts +10 -0
  60. package/src/api/core/addon-settings.router.ts +4 -1
  61. package/src/api/core/agents.router.ts +52 -53
  62. package/src/api/core/auth.router.ts +55 -36
  63. package/src/api/core/bulk-update-coordinator.ts +25 -22
  64. package/src/api/core/cap-providers.ts +459 -166
  65. package/src/api/core/capabilities.router.ts +30 -23
  66. package/src/api/core/hwaccel.router.ts +37 -10
  67. package/src/api/core/live-events.router.ts +16 -9
  68. package/src/api/core/logs.router.ts +58 -25
  69. package/src/api/core/notifications.router.ts +2 -1
  70. package/src/api/core/repl.router.ts +1 -3
  71. package/src/api/core/settings-backend.router.ts +68 -70
  72. package/src/api/core/system-events.router.ts +41 -32
  73. package/src/api/health/health.routes.ts +7 -13
  74. package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +12 -2
  75. package/src/api/oauth2/consent-page.ts +4 -3
  76. package/src/api/oauth2/oauth2-routes.ts +41 -12
  77. package/src/api/trpc/__tests__/client-ip.spec.ts +27 -1
  78. package/src/api/trpc/__tests__/scope-access-device.spec.ts +68 -23
  79. package/src/api/trpc/__tests__/scope-access.spec.ts +8 -13
  80. package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +136 -0
  81. package/src/api/trpc/cap-mount-helpers.ts +64 -44
  82. package/src/api/trpc/cap-route-error-formatter.ts +17 -9
  83. package/src/api/trpc/client-ip.ts +17 -0
  84. package/src/api/trpc/core-cap-bridge.ts +3 -1
  85. package/src/api/trpc/generated-cap-mounts.ts +801 -286
  86. package/src/api/trpc/generated-cap-routers.ts +5723 -719
  87. package/src/api/trpc/scope-access.ts +7 -7
  88. package/src/api/trpc/trpc.context.ts +7 -4
  89. package/src/api/trpc/trpc.middleware.ts +4 -2
  90. package/src/api/trpc/trpc.router.ts +117 -48
  91. package/src/auth/session-cookie.ts +10 -0
  92. package/src/boot/__tests__/integration-id-backfill.spec.ts +131 -0
  93. package/src/boot/boot-config.ts +103 -122
  94. package/src/boot/integration-id-backfill.ts +109 -0
  95. package/src/boot/post-boot.service.ts +5 -3
  96. package/src/core/addon/__tests__/addon-registry-capability.test.ts +12 -3
  97. package/src/core/addon/__tests__/addon-row-manifest.spec.ts +62 -0
  98. package/src/core/addon/addon-call-gateway.ts +20 -6
  99. package/src/core/addon/addon-package.service.ts +183 -89
  100. package/src/core/addon/addon-registry.service.ts +1212 -1267
  101. package/src/core/addon/addon-row-manifest.ts +29 -0
  102. package/src/core/addon/addon-search.service.ts +2 -1
  103. package/src/core/addon/addon-settings-provider.ts +27 -7
  104. package/src/core/addon-bridge/addon-bridge.service.ts +11 -6
  105. package/src/core/addon-pages/addon-pages.service.ts +3 -1
  106. package/src/core/addon-widgets/addon-widgets.service.ts +5 -2
  107. package/src/core/agent/agent-registry.service.ts +60 -38
  108. package/src/core/auth/auth.service.spec.ts +6 -8
  109. package/src/core/config/config.service.spec.ts +1 -1
  110. package/src/core/events/event-bus.service.spec.ts +44 -21
  111. package/src/core/events/event-bus.service.ts +5 -1
  112. package/src/core/feature/feature.service.spec.ts +4 -1
  113. package/src/core/lifecycle/lifecycle-state-machine.spec.ts +8 -10
  114. package/src/core/logging/logging.service.spec.ts +61 -21
  115. package/src/core/logging/logging.service.ts +19 -5
  116. package/src/core/moleculer/cap-call-fn.spec.ts +17 -10
  117. package/src/core/moleculer/cap-call-fn.ts +5 -1
  118. package/src/core/moleculer/cap-route-authority.ts +18 -6
  119. package/src/core/moleculer/moleculer.service.ts +145 -29
  120. package/src/core/network/network-quality.service.spec.ts +7 -1
  121. package/src/core/notification/notification-wrapper.service.ts +1 -3
  122. package/src/core/notification/toast-wrapper.service.ts +1 -5
  123. package/src/core/repl/repl-engine.service.spec.ts +66 -39
  124. package/src/core/repl/repl-engine.service.ts +11 -12
  125. package/src/core/storage/storage-location-manager.spec.ts +12 -3
  126. package/src/core/streaming/stream-probe.service.ts +22 -13
  127. package/src/core/topology/topology-emitter.service.ts +5 -1
  128. package/src/launcher.ts +14 -9
  129. package/src/main.ts +658 -495
  130. package/src/manual-boot.ts +133 -154
  131. package/tsconfig.json +20 -8
  132. package/src/core/storage/settings-store.spec.ts +0 -213
  133. package/src/core/storage/settings-store.ts +0 -2
  134. package/src/core/storage/sql-schema.spec.ts +0 -140
  135. package/src/core/storage/sql-schema.ts +0 -3
@@ -1,4 +1,3 @@
1
-
2
1
  /**
3
2
  * Embedded dependencies E2E — verify ffmpeg/python resolution and download.
4
3
  *
@@ -11,9 +10,19 @@ import * as path from 'node:path'
11
10
  import * as os from 'node:os'
12
11
  // Import directly from source submodules because vitest+swc doesn't resolve
13
12
  // `export * from './deps/index.js'` barrel re-exports in the @camstack/core index.
14
- import { findInPath, getPlatformInfo, buildBinaryPath } from '../../../../packages/core/src/deps/binary-downloader'
15
- import { getFfmpegDownloadUrl, ensureFfmpeg } from '../../../../packages/core/src/deps/ffmpeg-downloader'
16
- import { getPythonDownloadUrl, ensurePython } from '../../../../packages/core/src/deps/python-downloader'
13
+ import {
14
+ findInPath,
15
+ getPlatformInfo,
16
+ buildBinaryPath,
17
+ } from '../../../../packages/core/src/deps/binary-downloader'
18
+ import {
19
+ getFfmpegDownloadUrl,
20
+ ensureFfmpeg,
21
+ } from '../../../../packages/core/src/deps/ffmpeg-downloader'
22
+ import {
23
+ getPythonDownloadUrl,
24
+ ensurePython,
25
+ } from '../../../../packages/core/src/deps/python-downloader'
17
26
  import type { IScopedLogger } from '@camstack/types'
18
27
 
19
28
  function createMockLogger(): IScopedLogger {
@@ -27,7 +36,6 @@ function createMockLogger(): IScopedLogger {
27
36
  return logger
28
37
  }
29
38
 
30
-
31
39
  const mockLogger = createMockLogger()
32
40
 
33
41
  describe('Embedded Dependencies E2E', () => {
@@ -71,11 +79,15 @@ describe('Embedded Dependencies E2E', () => {
71
79
 
72
80
  // Only run download test if explicitly enabled (downloads ~80MB)
73
81
  const downloadTest = process.env.CAMSTACK_TEST_DOWNLOAD === 'true' ? it : it.skip
74
- downloadTest('downloads ffmpeg binary', async () => {
75
- const ffmpegPath = await ensureFfmpeg(tmpDir, mockLogger)
76
- expect(ffmpegPath).toBeTruthy()
77
- expect(fs.existsSync(ffmpegPath)).toBe(true)
78
- }, 120000)
82
+ downloadTest(
83
+ 'downloads ffmpeg binary',
84
+ async () => {
85
+ const ffmpegPath = await ensureFfmpeg(tmpDir, mockLogger)
86
+ expect(ffmpegPath).toBeTruthy()
87
+ expect(fs.existsSync(ffmpegPath)).toBe(true)
88
+ },
89
+ 120000,
90
+ )
79
91
  })
80
92
 
81
93
  describe('Python', () => {
@@ -95,15 +107,19 @@ describe('Embedded Dependencies E2E', () => {
95
107
 
96
108
  // Only run download test if explicitly enabled (downloads ~25MB)
97
109
  const downloadTest = process.env.CAMSTACK_TEST_DOWNLOAD === 'true' ? it : it.skip
98
- downloadTest('downloads portable Python', async () => {
99
- const pythonPath = await ensurePython(tmpDir, mockLogger)
100
- expect(pythonPath).toBeTruthy()
101
- expect(fs.existsSync(pythonPath!)).toBe(true)
110
+ downloadTest(
111
+ 'downloads portable Python',
112
+ async () => {
113
+ const pythonPath = await ensurePython(tmpDir, mockLogger)
114
+ expect(pythonPath).toBeTruthy()
115
+ expect(fs.existsSync(pythonPath!)).toBe(true)
102
116
 
103
- // Verify it actually runs
104
- const { execFileSync } = await import('node:child_process')
105
- const version = execFileSync(pythonPath!, ['--version'], { encoding: 'utf8' }).trim()
106
- expect(version).toMatch(/Python 3\.12/)
107
- }, 120000)
117
+ // Verify it actually runs
118
+ const { execFileSync } = await import('node:child_process')
119
+ const version = execFileSync(pythonPath!, ['--version'], { encoding: 'utf8' }).trim()
120
+ expect(version).toMatch(/Python 3\.12/)
121
+ },
122
+ 120000,
123
+ )
108
124
  })
109
125
  })
@@ -20,6 +20,7 @@ describe('event-bus-proxy router', () => {
20
20
  id: 'evt-1',
21
21
  timestamp: '2026-01-15T10:00:00.000Z',
22
22
  source: { type: 'addon', id: 'my-addon' },
23
+ // biome-ignore lint/plugin: synthetic test-only event category
23
24
  category: 'motion',
24
25
  data: { zone: 'front' },
25
26
  })
@@ -43,6 +44,7 @@ describe('event-bus-proxy router', () => {
43
44
  id: 'evt-2',
44
45
  timestamp: '2026-01-15T10:00:00.000Z',
45
46
  source: { type: 'device', id: 'cam-1' },
47
+ // biome-ignore lint/plugin: synthetic test-only event category
46
48
  category: 'alert',
47
49
  data: {},
48
50
  })
@@ -61,6 +63,7 @@ describe('event-bus-proxy router', () => {
61
63
  id: 'evt-3',
62
64
  timestamp: isoString,
63
65
  source: { type: 'addon', id: 'test' },
66
+ // biome-ignore lint/plugin: synthetic test-only event category
64
67
  category: 'info',
65
68
  data: {},
66
69
  })
@@ -37,10 +37,11 @@ function readPackageJson(pkgJsonPath: string): PackageJsonView | null {
37
37
  const name = obj['name']
38
38
  if (typeof name !== 'string') return null
39
39
  const camstack = obj['camstack']
40
- const isSystem = camstack !== null
41
- && typeof camstack === 'object'
42
- && !Array.isArray(camstack)
43
- && (camstack as Record<string, unknown>)['system'] === true
40
+ const isSystem =
41
+ camstack !== null &&
42
+ typeof camstack === 'object' &&
43
+ !Array.isArray(camstack) &&
44
+ (camstack as Record<string, unknown>)['system'] === true
44
45
  return { name, system: isSystem, path: pkgJsonPath }
45
46
  } catch {
46
47
  return null
@@ -74,7 +74,9 @@ describe('HTTPS E2E', () => {
74
74
  { hostname: '127.0.0.1', port, path: '/', method: 'GET', rejectUnauthorized: false },
75
75
  (res) => {
76
76
  let data = ''
77
- res.on('data', (c) => { data += c })
77
+ res.on('data', (c) => {
78
+ data += c
79
+ })
78
80
  res.on('end', () => resolve(data))
79
81
  },
80
82
  )
@@ -103,11 +105,15 @@ describe('HTTPS E2E', () => {
103
105
 
104
106
  try {
105
107
  const body = await new Promise<string>((resolve, reject) => {
106
- http.get(`http://127.0.0.1:${port}/`, (res) => {
107
- let data = ''
108
- res.on('data', (c) => { data += c })
109
- res.on('end', () => resolve(data))
110
- }).on('error', reject)
108
+ http
109
+ .get(`http://127.0.0.1:${port}/`, (res) => {
110
+ let data = ''
111
+ res.on('data', (c) => {
112
+ data += c
113
+ })
114
+ res.on('end', () => resolve(data))
115
+ })
116
+ .on('error', reject)
111
117
  })
112
118
 
113
119
  expect(body).toBe('http-ok')
@@ -1,4 +1,3 @@
1
-
2
1
  import { describe, it, expect, vi } from 'vitest'
3
2
  import { CapabilityRegistry, isInfraCapability } from '@camstack/kernel'
4
3
  import type { IScopedLogger } from '@camstack/types'
@@ -36,12 +35,42 @@ describe('Full Lifecycle E2E (mock, no external services)', () => {
36
35
  it('registers singleton and collection capabilities correctly', () => {
37
36
  const registry = createRegistry()
38
37
 
39
- registry.declareCapability({ name: 'storage', scope: 'system', mode: 'singleton', methods: {} })
40
- registry.declareCapability({ name: 'log-destination', scope: 'system', mode: 'collection', methods: {} })
41
- registry.declareCapability({ name: 'streaming-engine', scope: 'system', mode: 'singleton', methods: {} })
42
- registry.declareCapability({ name: 'analysis-pipeline', scope: 'system', mode: 'singleton', methods: {} })
43
- registry.declareCapability({ name: 'device-provider', scope: 'system', mode: 'collection', methods: {} })
44
- registry.declareCapability({ name: 'admin-ui', scope: 'system', mode: 'singleton', methods: {} })
38
+ registry.declareCapability({
39
+ name: 'storage',
40
+ scope: 'system',
41
+ mode: 'singleton',
42
+ methods: {},
43
+ })
44
+ registry.declareCapability({
45
+ name: 'log-destination',
46
+ scope: 'system',
47
+ mode: 'collection',
48
+ methods: {},
49
+ })
50
+ registry.declareCapability({
51
+ name: 'streaming-engine',
52
+ scope: 'system',
53
+ mode: 'singleton',
54
+ methods: {},
55
+ })
56
+ registry.declareCapability({
57
+ name: 'analysis-pipeline',
58
+ scope: 'system',
59
+ mode: 'singleton',
60
+ methods: {},
61
+ })
62
+ registry.declareCapability({
63
+ name: 'device-provider',
64
+ scope: 'system',
65
+ mode: 'collection',
66
+ methods: {},
67
+ })
68
+ registry.declareCapability({
69
+ name: 'admin-ui',
70
+ scope: 'system',
71
+ mode: 'singleton',
72
+ methods: {},
73
+ })
45
74
 
46
75
  const mockStorage = { id: 'sqlite' }
47
76
  const mockLogger = { id: 'winston' }
@@ -79,7 +108,12 @@ describe('Full Lifecycle E2E (mock, no external services)', () => {
79
108
  describe('Singleton swap', () => {
80
109
  it('swapping analysis provider changes getSingleton result', async () => {
81
110
  const registry = createRegistry()
82
- registry.declareCapability({ name: 'analysis-pipeline', scope: 'system', mode: 'singleton', methods: {} })
111
+ registry.declareCapability({
112
+ name: 'analysis-pipeline',
113
+ scope: 'system',
114
+ mode: 'singleton',
115
+ methods: {},
116
+ })
83
117
 
84
118
  const analysisA = { id: 'analysis-a', processFrame: vi.fn() }
85
119
  const analysisB = { id: 'analysis-b', processFrame: vi.fn() }
@@ -97,7 +131,12 @@ describe('Full Lifecycle E2E (mock, no external services)', () => {
97
131
  describe('Collection capability add/remove', () => {
98
132
  it('adding and removing device providers', () => {
99
133
  const registry = createRegistry()
100
- registry.declareCapability({ name: 'device-provider', scope: 'system', mode: 'collection', methods: {} })
134
+ registry.declareCapability({
135
+ name: 'device-provider',
136
+ scope: 'system',
137
+ mode: 'collection',
138
+ methods: {},
139
+ })
101
140
 
102
141
  const frigate = { id: 'frigate', type: 'frigate' }
103
142
  const onvif = { id: 'onvif', type: 'onvif' }
@@ -116,8 +155,18 @@ describe('Full Lifecycle E2E (mock, no external services)', () => {
116
155
  describe('Capability introspection', () => {
117
156
  it('listCapabilities returns full info', () => {
118
157
  const registry = createRegistry()
119
- registry.declareCapability({ name: 'storage', scope: 'system', mode: 'singleton', methods: {} })
120
- registry.declareCapability({ name: 'device-provider', scope: 'system', mode: 'collection', methods: {} })
158
+ registry.declareCapability({
159
+ name: 'storage',
160
+ scope: 'system',
161
+ mode: 'singleton',
162
+ methods: {},
163
+ })
164
+ registry.declareCapability({
165
+ name: 'device-provider',
166
+ scope: 'system',
167
+ mode: 'collection',
168
+ methods: {},
169
+ })
121
170
 
122
171
  registry.registerProvider('storage', 'sqlite', { id: 'sqlite' })
123
172
  registry.registerProvider('device-provider', 'frigate', { id: 'frigate' })
@@ -30,10 +30,9 @@ function makeEvent(category: string, data: Record<string, unknown> = {}) {
30
30
  describe('live events subscription', () => {
31
31
  it('delivers events emitted AFTER subscription', async () => {
32
32
  const bus = new SystemEventBus() as unknown as EventBusService
33
- const router = createLiveEventsRouter(
34
- bus,
35
- { getDeviceRegistry: () => ({ getAll: () => [], getAllForAddon: () => [] }) } as never,
36
- )
33
+ const router = createLiveEventsRouter(bus, {
34
+ getDeviceRegistry: () => ({ getAll: () => [], getAllForAddon: () => [] }),
35
+ } as never)
37
36
  const caller = router.createCaller(makeCtx('admin'))
38
37
 
39
38
  const iter = await caller.onEvent({ category: 'benchmark.progress' })
@@ -63,10 +62,9 @@ describe('live events subscription', () => {
63
62
 
64
63
  it('filters events that do not match the requested category', async () => {
65
64
  const bus = new SystemEventBus() as unknown as EventBusService
66
- const router = createLiveEventsRouter(
67
- bus,
68
- { getDeviceRegistry: () => ({ getAll: () => [], getAllForAddon: () => [] }) } as never,
69
- )
65
+ const router = createLiveEventsRouter(bus, {
66
+ getDeviceRegistry: () => ({ getAll: () => [], getAllForAddon: () => [] }),
67
+ } as never)
70
68
  const caller = router.createCaller(makeCtx('admin'))
71
69
 
72
70
  const iter = await caller.onEvent({ category: 'benchmark.progress' })
@@ -76,7 +74,10 @@ describe('live events subscription', () => {
76
74
  const collector = (async () => {
77
75
  for await (const ev of iter) {
78
76
  received.push(ev.category)
79
- if (received.length >= 2) { stop(); return }
77
+ if (received.length >= 2) {
78
+ stop()
79
+ return
80
+ }
80
81
  }
81
82
  })()
82
83
 
@@ -97,10 +98,9 @@ describe('live events subscription', () => {
97
98
 
98
99
  it('receives recent events via recentSystemEvents query', async () => {
99
100
  const bus = new SystemEventBus() as unknown as EventBusService
100
- const router = createLiveEventsRouter(
101
- bus,
102
- { getDeviceRegistry: () => ({ getAll: () => [], getAllForAddon: () => [] }) } as never,
103
- )
101
+ const router = createLiveEventsRouter(bus, {
102
+ getDeviceRegistry: () => ({ getAll: () => [], getAllForAddon: () => [] }),
103
+ } as never)
104
104
  const caller = router.createCaller(makeCtx('admin'))
105
105
 
106
106
  bus.emit(makeEvent('benchmark.progress', { iteration: 1 }))
@@ -110,7 +110,7 @@ describe('live events subscription', () => {
110
110
  const recent = await caller.recentSystemEvents({ category: 'benchmark.progress', limit: 10 })
111
111
  expect(recent).toHaveLength(2)
112
112
  // recentSystemEvents returns newest-first
113
- const iterations = recent.map((e) => e.data.iteration as number).sort((a, b) => a - b)
113
+ const iterations = recent.map((e) => e.data.iteration as number).toSorted((a, b) => a - b)
114
114
  expect(iterations).toEqual([1, 2])
115
115
  })
116
116
 
@@ -118,10 +118,9 @@ describe('live events subscription', () => {
118
118
  // Reproduces the benchmark scenario where runBenchmark emits 3 events
119
119
  // back-to-back; the subscription should buffer them and deliver all 3.
120
120
  const bus = new SystemEventBus() as unknown as EventBusService
121
- const router = createLiveEventsRouter(
122
- bus,
123
- { getDeviceRegistry: () => ({ getAll: () => [], getAllForAddon: () => [] }) } as never,
124
- )
121
+ const router = createLiveEventsRouter(bus, {
122
+ getDeviceRegistry: () => ({ getAll: () => [], getAllForAddon: () => [] }),
123
+ } as never)
125
124
  const caller = router.createCaller(makeCtx('admin'))
126
125
 
127
126
  const iter = await caller.onEvent({ category: 'benchmark.progress' })
@@ -71,7 +71,9 @@ describe('D1 — wireReadinessSnapshotHandler', () => {
71
71
 
72
72
  let capturedHandler: (() => readonly IReadinessRegistryRecord[]) | null = null
73
73
  const childReg: FakeLocalChildRegistry = {
74
- onReadinessSnapshotRequest: (h) => { capturedHandler = h },
74
+ onReadinessSnapshotRequest: (h) => {
75
+ capturedHandler = h
76
+ },
75
77
  }
76
78
  const readinessReg: FakeReadinessRegistry = {
77
79
  getSnapshotForTransport: () => records,
@@ -85,7 +87,8 @@ describe('D1 — wireReadinessSnapshotHandler', () => {
85
87
  })
86
88
 
87
89
  it('handler calls getSnapshotForTransport on every invocation (live snapshot)', () => {
88
- const getSnapshot = vi.fn<[], readonly IReadinessRegistryRecord[]>()
90
+ const getSnapshot = vi
91
+ .fn<[], readonly IReadinessRegistryRecord[]>()
89
92
  .mockReturnValueOnce([])
90
93
  .mockReturnValueOnce([
91
94
  {
@@ -101,7 +104,9 @@ describe('D1 — wireReadinessSnapshotHandler', () => {
101
104
 
102
105
  let capturedHandler: (() => readonly IReadinessRegistryRecord[]) | null = null
103
106
  const childReg: FakeLocalChildRegistry = {
104
- onReadinessSnapshotRequest: (h) => { capturedHandler = h },
107
+ onReadinessSnapshotRequest: (h) => {
108
+ capturedHandler = h
109
+ },
105
110
  }
106
111
  const readinessReg: FakeReadinessRegistry = { getSnapshotForTransport: getSnapshot }
107
112
 
@@ -120,7 +125,9 @@ describe('D1 — wireReadinessSnapshotHandler', () => {
120
125
 
121
126
  let capturedHandler: (() => readonly IReadinessRegistryRecord[]) | null = null
122
127
  const childReg: FakeLocalChildRegistry = {
123
- onReadinessSnapshotRequest: (h) => { capturedHandler = h },
128
+ onReadinessSnapshotRequest: (h) => {
129
+ capturedHandler = h
130
+ },
124
131
  }
125
132
 
126
133
  wireReadinessSnapshotHandler(childReg, readinessReg)
@@ -97,8 +97,12 @@ describe('E1 — wireHubChildManifest', () => {
97
97
  let capturedGoneHandler: ((childId: string) => void) | null = null
98
98
 
99
99
  const registry: FakeLocalChildRegistry = {
100
- onChildRegistered: (h) => { capturedRegisteredHandler = h },
101
- onChildGone: (h) => { capturedGoneHandler = h },
100
+ onChildRegistered: (h) => {
101
+ capturedRegisteredHandler = h
102
+ },
103
+ onChildGone: (h) => {
104
+ capturedGoneHandler = h
105
+ },
102
106
  setChildLogLevel: vi.fn<[string, string], boolean>().mockReturnValue(false),
103
107
  }
104
108
 
@@ -130,7 +134,9 @@ describe('E1 — wireHubChildManifest', () => {
130
134
 
131
135
  const registry: FakeLocalChildRegistry = {
132
136
  onChildRegistered: vi.fn(),
133
- onChildGone: (h) => { capturedGoneHandler = h },
137
+ onChildGone: (h) => {
138
+ capturedGoneHandler = h
139
+ },
134
140
  setChildLogLevel: vi.fn<[string, string], boolean>().mockReturnValue(false),
135
141
  }
136
142
 
@@ -155,8 +161,12 @@ describe('E1 — wireHubChildManifest', () => {
155
161
  let goneHandler: ((childId: string) => void) | null = null
156
162
 
157
163
  const registry: FakeLocalChildRegistry = {
158
- onChildRegistered: (h) => { registeredHandler = h },
159
- onChildGone: (h) => { goneHandler = h },
164
+ onChildRegistered: (h) => {
165
+ registeredHandler = h
166
+ },
167
+ onChildGone: (h) => {
168
+ goneHandler = h
169
+ },
160
170
  setChildLogLevel: vi.fn<[string, string], boolean>().mockReturnValue(false),
161
171
  }
162
172
 
@@ -249,11 +259,15 @@ describe('E1 — idempotency (double-registration via RPC + UDS)', () => {
249
259
  // so the second call is a no-op at the CapabilityRegistry level.
250
260
  // Here we verify that onChildRegistered fires ONCE per UDS connect.
251
261
  let callCount = 0
252
- const applyChildManifest = vi.fn<[string, readonly ChildCapDescriptor[]], void>(() => { callCount++ })
262
+ const applyChildManifest = vi.fn<[string, readonly ChildCapDescriptor[]], void>(() => {
263
+ callCount++
264
+ })
253
265
 
254
266
  let registeredHandler: ((child: RegisteredChild) => void) | null = null
255
267
  const registry: FakeLocalChildRegistry = {
256
- onChildRegistered: (h) => { registeredHandler = h },
268
+ onChildRegistered: (h) => {
269
+ registeredHandler = h
270
+ },
257
271
  onChildGone: vi.fn(),
258
272
  setChildLogLevel: vi.fn<[string, string], boolean>().mockReturnValue(false),
259
273
  }
@@ -287,7 +301,9 @@ describe('E1 — idempotency (double-registration via RPC + UDS)', () => {
287
301
  let registeredHandler: ((child: RegisteredChild) => void) | null = null
288
302
 
289
303
  const registry: FakeLocalChildRegistry = {
290
- onChildRegistered: (h) => { registeredHandler = h },
304
+ onChildRegistered: (h) => {
305
+ registeredHandler = h
306
+ },
291
307
  onChildGone: vi.fn(),
292
308
  setChildLogLevel: vi.fn<[string, string], boolean>().mockReturnValue(false),
293
309
  }
@@ -333,7 +349,11 @@ describe('E2 — LocalChildRegistry.setChildLogLevel (real implementation)', ()
333
349
  const result = registry.setChildLogLevel('unknown-child', 'debug')
334
350
  expect(result).toBe(false)
335
351
  // Cleanup: close the server without starting (no-op on most implementations).
336
- try { await registry.close() } catch { /* server was never started */ }
352
+ try {
353
+ await registry.close()
354
+ } catch {
355
+ /* server was never started */
356
+ }
337
357
  })
338
358
 
339
359
  it('setProcessLogLevel falls back to Moleculer when setChildLogLevel returns false', () => {
@@ -344,7 +364,11 @@ describe('E2 — LocalChildRegistry.setChildLogLevel (real implementation)', ()
344
364
  // and the Moleculer fallback is invoked.
345
365
  const moleculerFallback = vi.fn<[string, string], void>()
346
366
 
347
- const simulateSetProcessLogLevel = (nodeId: string, level: string, udsResult: boolean): void => {
367
+ const simulateSetProcessLogLevel = (
368
+ nodeId: string,
369
+ level: string,
370
+ udsResult: boolean,
371
+ ): void => {
348
372
  if (!udsResult) {
349
373
  moleculerFallback(nodeId, level)
350
374
  }
@@ -367,7 +391,11 @@ describe('E2 — LocalChildRegistry.setChildLogLevel (real implementation)', ()
367
391
  // We test the routing logic inline (no full MoleculerService instantiation).
368
392
  const hubNodeId = 'hub'
369
393
 
370
- const routeSetLogLevel = (nodeId: string, level: string, registry: { setChildLogLevel(id: string, lvl: string): boolean } | null): boolean => {
394
+ const routeSetLogLevel = (
395
+ nodeId: string,
396
+ level: string,
397
+ registry: { setChildLogLevel(id: string, lvl: string): boolean } | null,
398
+ ): boolean => {
371
399
  if (!nodeId.startsWith(`${hubNodeId}/`)) return false
372
400
  const childId = nodeId.slice(hubNodeId.length + 1)
373
401
  if (registry === null) return false