@camstack/server 0.1.8 → 0.2.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 (125) hide show
  1. package/package.json +9 -7
  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 +24 -4
  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 +64 -15
  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 +14 -6
  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 +11 -6
  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 +71 -17
  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/addon-settings.router.ts +4 -1
  60. package/src/api/core/agents.router.ts +52 -53
  61. package/src/api/core/auth.router.ts +55 -36
  62. package/src/api/core/bulk-update-coordinator.ts +25 -22
  63. package/src/api/core/cap-providers.ts +346 -202
  64. package/src/api/core/capabilities.router.ts +30 -23
  65. package/src/api/core/hwaccel.router.ts +37 -10
  66. package/src/api/core/live-events.router.ts +16 -9
  67. package/src/api/core/logs.router.ts +54 -25
  68. package/src/api/core/notifications.router.ts +2 -1
  69. package/src/api/core/repl.router.ts +1 -3
  70. package/src/api/core/settings-backend.router.ts +68 -70
  71. package/src/api/core/system-events.router.ts +41 -32
  72. package/src/api/health/health.routes.ts +7 -13
  73. package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +12 -2
  74. package/src/api/oauth2/consent-page.ts +4 -3
  75. package/src/api/oauth2/oauth2-routes.ts +41 -12
  76. package/src/api/trpc/__tests__/scope-access-device.spec.ts +68 -23
  77. package/src/api/trpc/__tests__/scope-access.spec.ts +8 -13
  78. package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +10 -2
  79. package/src/api/trpc/cap-mount-helpers.ts +64 -55
  80. package/src/api/trpc/cap-route-error-formatter.ts +17 -9
  81. package/src/api/trpc/core-cap-bridge.ts +3 -1
  82. package/src/api/trpc/generated-cap-mounts.ts +593 -351
  83. package/src/api/trpc/generated-cap-routers.ts +3680 -579
  84. package/src/api/trpc/scope-access.ts +7 -7
  85. package/src/api/trpc/trpc.context.ts +7 -4
  86. package/src/api/trpc/trpc.middleware.ts +4 -2
  87. package/src/api/trpc/trpc.router.ts +79 -46
  88. package/src/auth/session-cookie.ts +10 -0
  89. package/src/boot/__tests__/integration-id-backfill.spec.ts +21 -6
  90. package/src/boot/boot-config.ts +103 -122
  91. package/src/boot/post-boot.service.ts +5 -3
  92. package/src/core/addon/__tests__/addon-registry-capability.test.ts +12 -3
  93. package/src/core/addon/addon-call-gateway.ts +20 -6
  94. package/src/core/addon/addon-package.service.ts +183 -89
  95. package/src/core/addon/addon-registry.service.ts +1163 -1305
  96. package/src/core/addon/addon-search.service.ts +2 -1
  97. package/src/core/addon/addon-settings-provider.ts +27 -7
  98. package/src/core/addon-bridge/addon-bridge.service.ts +11 -6
  99. package/src/core/addon-pages/addon-pages.service.ts +3 -1
  100. package/src/core/addon-widgets/addon-widgets.service.ts +5 -2
  101. package/src/core/agent/agent-registry.service.ts +60 -38
  102. package/src/core/auth/auth.service.spec.ts +6 -8
  103. package/src/core/config/config.service.spec.ts +1 -1
  104. package/src/core/events/event-bus.service.spec.ts +44 -21
  105. package/src/core/events/event-bus.service.ts +5 -1
  106. package/src/core/feature/feature.service.spec.ts +4 -1
  107. package/src/core/lifecycle/lifecycle-state-machine.spec.ts +8 -10
  108. package/src/core/logging/logging.service.spec.ts +61 -21
  109. package/src/core/logging/logging.service.ts +12 -3
  110. package/src/core/moleculer/cap-call-fn.spec.ts +17 -10
  111. package/src/core/moleculer/cap-call-fn.ts +5 -1
  112. package/src/core/moleculer/cap-route-authority.ts +18 -6
  113. package/src/core/moleculer/moleculer.service.ts +120 -32
  114. package/src/core/network/network-quality.service.spec.ts +6 -1
  115. package/src/core/notification/notification-wrapper.service.ts +1 -3
  116. package/src/core/notification/toast-wrapper.service.ts +1 -5
  117. package/src/core/repl/repl-engine.service.spec.ts +66 -39
  118. package/src/core/repl/repl-engine.service.ts +11 -12
  119. package/src/core/storage/storage-location-manager.spec.ts +12 -3
  120. package/src/core/streaming/stream-probe.service.ts +22 -13
  121. package/src/core/topology/topology-emitter.service.ts +5 -1
  122. package/src/launcher.ts +14 -9
  123. package/src/main.ts +602 -531
  124. package/src/manual-boot.ts +133 -154
  125. package/tsconfig.json +20 -8
@@ -1,4 +1,3 @@
1
-
2
1
  /**
3
2
  * Router-level spec for `pipelineExecutor` — covers the reference-media
4
3
  * and audio-capabilities endpoints that remain on the cap after the
@@ -11,7 +10,9 @@ import { createCapRouter_pipelineExecutor } from '../../api/trpc/generated-cap-r
11
10
  import type { IPipelineExecutorProvider } from '@camstack/types'
12
11
  import { makeCtx, invokeProcedure } from './harness.js'
13
12
 
14
- function makeMockProvider(overrides: Partial<IPipelineExecutorProvider> = {}): IPipelineExecutorProvider {
13
+ function makeMockProvider(
14
+ overrides: Partial<IPipelineExecutorProvider> = {},
15
+ ): IPipelineExecutorProvider {
15
16
  const defaultEngine = { runtime: 'node' as const, backend: 'cpu', format: 'onnx' as const }
16
17
 
17
18
  return {
@@ -20,7 +21,9 @@ function makeMockProvider(overrides: Partial<IPipelineExecutorProvider> = {}): I
20
21
  setEngine: async () => ({ steps: [] }),
21
22
  getDefaultSteps: async () => [],
22
23
  getSchema: async () => ({
23
- availableEngines: [{ engine: defaultEngine, devices: [{ id: 'cpu', label: 'CPU' }], defaultDevice: 'cpu' }],
24
+ availableEngines: [
25
+ { engine: defaultEngine, devices: [{ id: 'cpu', label: 'CPU' }], defaultDevice: 'cpu' },
26
+ ],
24
27
  selectedEngine: defaultEngine,
25
28
  slots: [],
26
29
  }),
@@ -29,15 +32,28 @@ function makeMockProvider(overrides: Partial<IPipelineExecutorProvider> = {}): I
29
32
  getGlobalPipelineConfig: () => null,
30
33
  getOrchestratorConfigSchema: () => ({ sections: [] }),
31
34
  getOrchestratorSettings: () => ({
32
- motionFps: 4, detectionFps: 10, cooldownMs: 10000, maxConcurrentInferences: null,
35
+ motionFps: 4,
36
+ detectionFps: 10,
37
+ cooldownMs: 10000,
38
+ maxConcurrentInferences: null,
33
39
  }),
34
40
  setOrchestratorSettings: () => {},
35
41
  listTemplates: () => [],
36
42
  saveTemplate: () => ({
37
- id: 'tpl-1', name: 'x', createdAt: '', updatedAt: '', engine: defaultEngine, steps: [],
43
+ id: 'tpl-1',
44
+ name: 'x',
45
+ createdAt: '',
46
+ updatedAt: '',
47
+ engine: defaultEngine,
48
+ steps: [],
38
49
  }),
39
50
  updateTemplate: () => ({
40
- id: 'tpl-1', name: 'x', createdAt: '', updatedAt: '', engine: defaultEngine, steps: [],
51
+ id: 'tpl-1',
52
+ name: 'x',
53
+ createdAt: '',
54
+ updatedAt: '',
55
+ engine: defaultEngine,
56
+ steps: [],
41
57
  }),
42
58
  deleteTemplate: () => {},
43
59
  getCapabilities: async () => {
@@ -64,11 +80,18 @@ function makeMockProvider(overrides: Partial<IPipelineExecutorProvider> = {}): I
64
80
  getReferenceAudio: () => ({ base64: 'AAAA' }),
65
81
  getAudioCapabilities: () => ({
66
82
  activeBackend: 'yamnet-onnx',
67
- availableBackends: [{ id: 'yamnet-onnx', name: 'YAMNet ONNX', description: '', available: true }],
83
+ availableBackends: [
84
+ { id: 'yamnet-onnx', name: 'YAMNet ONNX', description: '', available: true },
85
+ ],
68
86
  sampleRate: 16000,
69
87
  chunkDurationMs: 500,
70
88
  }),
71
- getAddonResolver: () => ({ resolve: async () => { throw new Error('nop') }, shutdownAll: async () => {} }),
89
+ getAddonResolver: () => ({
90
+ resolve: async () => {
91
+ throw new Error('nop')
92
+ },
93
+ shutdownAll: async () => {},
94
+ }),
72
95
  orchestratorStatus: () => null,
73
96
  cameraDetectionStatus: () => null,
74
97
  getDevicePipelineSteps: () => null,
@@ -83,9 +106,10 @@ describe('createCapRouter_pipelineExecutor — reference + audio endpoints', ()
83
106
  it('delegates to provider.listReferenceImages', async () => {
84
107
  const router = createCapRouter_pipelineExecutor(() =>
85
108
  makeMockProvider({
86
- listReferenceImages: () => [
87
- { id: 'persons-cars-animal.jpg', filename: 'persons-cars-animal.jpg', sizeKB: 120 },
88
- ] as never,
109
+ listReferenceImages: () =>
110
+ [
111
+ { id: 'persons-cars-animal.jpg', filename: 'persons-cars-animal.jpg', sizeKB: 120 },
112
+ ] as never,
89
113
  }),
90
114
  )
91
115
 
@@ -1,4 +1,3 @@
1
-
2
1
  /**
3
2
  * Spec for the codegen'd `settings-store` capability router.
4
3
  *
@@ -34,7 +33,10 @@ function makeMockProvider(): ISettingsStoreProvider & { _store: MockStore } {
34
33
  },
35
34
 
36
35
  async set(input) {
37
- store.records.set(input.key, { id: input.key, data: input.value as Record<string, unknown> ?? {} })
36
+ store.records.set(input.key, {
37
+ id: input.key,
38
+ data: (input.value as Record<string, unknown>) ?? {},
39
+ })
38
40
  },
39
41
 
40
42
  async query(input) {
@@ -81,14 +83,20 @@ describe('createCapRouter_settingsStore', () => {
81
83
  it('returns stored value by key', async () => {
82
84
  provider._store.records.set('myKey', { id: 'myKey', data: { foo: 'bar' } })
83
85
  const router = createCapRouter_settingsStore(() => provider)
84
- const result = await invokeProcedure(router, 'get', makeCtx('admin'), { collection: 'test', key: 'myKey' })
86
+ const result = await invokeProcedure(router, 'get', makeCtx('admin'), {
87
+ collection: 'test',
88
+ key: 'myKey',
89
+ })
85
90
  expect(result.ok).toBe(true)
86
91
  if (result.ok) expect(result.value).toMatchObject({ foo: 'bar' })
87
92
  })
88
93
 
89
94
  it('returns null for missing key', async () => {
90
95
  const router = createCapRouter_settingsStore(() => provider)
91
- const result = await invokeProcedure(router, 'get', makeCtx('admin'), { collection: 'test', key: 'nope' })
96
+ const result = await invokeProcedure(router, 'get', makeCtx('admin'), {
97
+ collection: 'test',
98
+ key: 'nope',
99
+ })
92
100
  expect(result.ok).toBe(true)
93
101
  if (result.ok) expect(result.value).toBeNull()
94
102
  })
@@ -97,7 +105,11 @@ describe('createCapRouter_settingsStore', () => {
97
105
  describe('set', () => {
98
106
  it('upserts a value', async () => {
99
107
  const router = createCapRouter_settingsStore(() => provider)
100
- const result = await invokeProcedure(router, 'set', makeCtx('admin'), { collection: 'test', key: 'k1', value: { x: 1 } })
108
+ const result = await invokeProcedure(router, 'set', makeCtx('admin'), {
109
+ collection: 'test',
110
+ key: 'k1',
111
+ value: { x: 1 },
112
+ })
101
113
  expect(result.ok).toBe(true)
102
114
  expect(provider._store.records.has('k1')).toBe(true)
103
115
  })
@@ -108,7 +120,9 @@ describe('createCapRouter_settingsStore', () => {
108
120
  provider._store.records.set('a', { id: 'a', data: { v: 1 } })
109
121
  provider._store.records.set('b', { id: 'b', data: { v: 2 } })
110
122
  const router = createCapRouter_settingsStore(() => provider)
111
- const result = await invokeProcedure(router, 'query', makeCtx('admin'), { collection: 'test' })
123
+ const result = await invokeProcedure(router, 'query', makeCtx('admin'), {
124
+ collection: 'test',
125
+ })
112
126
  expect(result.ok).toBe(true)
113
127
  if (result.ok) expect(result.value).toHaveLength(2)
114
128
  })
@@ -118,7 +132,10 @@ describe('createCapRouter_settingsStore', () => {
118
132
  provider._store.records.set('b', { id: 'b', data: {} })
119
133
  provider._store.records.set('c', { id: 'c', data: {} })
120
134
  const router = createCapRouter_settingsStore(() => provider)
121
- const result = await invokeProcedure(router, 'query', makeCtx('admin'), { collection: 'test', filter: { limit: 2 } })
135
+ const result = await invokeProcedure(router, 'query', makeCtx('admin'), {
136
+ collection: 'test',
137
+ filter: { limit: 2 },
138
+ })
122
139
  expect(result.ok).toBe(true)
123
140
  if (result.ok) expect(result.value).toHaveLength(2)
124
141
  })
@@ -154,7 +171,10 @@ describe('createCapRouter_settingsStore', () => {
154
171
  it('removes a record', async () => {
155
172
  provider._store.records.set('r1', { id: 'r1', data: {} })
156
173
  const router = createCapRouter_settingsStore(() => provider)
157
- const result = await invokeProcedure(router, 'delete', makeCtx('admin'), { collection: 'test', key: 'r1' })
174
+ const result = await invokeProcedure(router, 'delete', makeCtx('admin'), {
175
+ collection: 'test',
176
+ key: 'r1',
177
+ })
158
178
  expect(result.ok).toBe(true)
159
179
  expect(provider._store.records.has('r1')).toBe(false)
160
180
  })
@@ -165,7 +185,9 @@ describe('createCapRouter_settingsStore', () => {
165
185
  provider._store.records.set('a', { id: 'a', data: {} })
166
186
  provider._store.records.set('b', { id: 'b', data: {} })
167
187
  const router = createCapRouter_settingsStore(() => provider)
168
- const result = await invokeProcedure(router, 'count', makeCtx('admin'), { collection: 'test' })
188
+ const result = await invokeProcedure(router, 'count', makeCtx('admin'), {
189
+ collection: 'test',
190
+ })
169
191
  expect(result.ok).toBe(true)
170
192
  if (result.ok) expect(result.value).toBe(2)
171
193
  })
@@ -174,7 +196,9 @@ describe('createCapRouter_settingsStore', () => {
174
196
  describe('isEmpty', () => {
175
197
  it('returns true when empty', async () => {
176
198
  const router = createCapRouter_settingsStore(() => provider)
177
- const result = await invokeProcedure(router, 'isEmpty', makeCtx('admin'), { collection: 'test' })
199
+ const result = await invokeProcedure(router, 'isEmpty', makeCtx('admin'), {
200
+ collection: 'test',
201
+ })
178
202
  expect(result.ok).toBe(true)
179
203
  if (result.ok) expect(result.value).toBe(true)
180
204
  })
@@ -182,7 +206,9 @@ describe('createCapRouter_settingsStore', () => {
182
206
  it('returns false when records exist', async () => {
183
207
  provider._store.records.set('x', { id: 'x', data: {} })
184
208
  const router = createCapRouter_settingsStore(() => provider)
185
- const result = await invokeProcedure(router, 'isEmpty', makeCtx('admin'), { collection: 'test' })
209
+ const result = await invokeProcedure(router, 'isEmpty', makeCtx('admin'), {
210
+ collection: 'test',
211
+ })
186
212
  expect(result.ok).toBe(true)
187
213
  if (result.ok) expect(result.value).toBe(false)
188
214
  })
@@ -215,8 +241,20 @@ describe('createCapRouter_settingsStore', () => {
215
241
  describe('auth', () => {
216
242
  it('rejects anonymous on all methods', async () => {
217
243
  const router = createCapRouter_settingsStore(() => provider)
218
- for (const method of ['get', 'set', 'query', 'insert', 'update', 'delete', 'count', 'isEmpty']) {
219
- const result = await invokeProcedure(router, method, makeCtx('anonymous'), { collection: 'c', key: 'k' })
244
+ for (const method of [
245
+ 'get',
246
+ 'set',
247
+ 'query',
248
+ 'insert',
249
+ 'update',
250
+ 'delete',
251
+ 'count',
252
+ 'isEmpty',
253
+ ]) {
254
+ const result = await invokeProcedure(router, method, makeCtx('anonymous'), {
255
+ collection: 'c',
256
+ key: 'k',
257
+ })
220
258
  expect(result.ok).toBe(false)
221
259
  if (!result.ok) expect(result.code).toBe('UNAUTHORIZED')
222
260
  }
@@ -225,7 +263,10 @@ describe('createCapRouter_settingsStore', () => {
225
263
  it('allows viewer on query methods (protected)', async () => {
226
264
  const router = createCapRouter_settingsStore(() => provider)
227
265
  for (const method of ['get', 'query', 'count', 'isEmpty']) {
228
- const result = await invokeProcedure(router, method, makeCtx('user'), { collection: 'c', key: 'k' })
266
+ const result = await invokeProcedure(router, method, makeCtx('user'), {
267
+ collection: 'c',
268
+ key: 'k',
269
+ })
229
270
  expect(result.ok).toBe(true)
230
271
  }
231
272
  })
@@ -236,7 +277,10 @@ describe('createCapRouter_settingsStore', () => {
236
277
  describe('missing provider', () => {
237
278
  it('throws PRECONDITION_FAILED when provider is null', async () => {
238
279
  const router = createCapRouter_settingsStore(() => null)
239
- const result = await invokeProcedure(router, 'get', makeCtx('admin'), { collection: 'c', key: 'k' })
280
+ const result = await invokeProcedure(router, 'get', makeCtx('admin'), {
281
+ collection: 'c',
282
+ key: 'k',
283
+ })
240
284
  expect(result.ok).toBe(false)
241
285
  if (!result.ok) {
242
286
  expect(result.code).toBe('PRECONDITION_FAILED')
@@ -46,7 +46,6 @@ class TestAddonHarness {
46
46
 
47
47
  /** Register an addon (mimics AddonRegistryService.registerAddon) */
48
48
  registerAddon(addon: ICamstackAddon): void {
49
-
50
49
  this.addonEntries.set(addon.manifest.id, { addon, initialized: false })
51
50
  }
52
51
 
@@ -60,7 +59,7 @@ class TestAddonHarness {
60
59
  for (const cap of caps) {
61
60
  // Create a full definition so the registry has a CapabilityState.
62
61
  // Mode is inferred from the cap name for test convenience.
63
- const mode = cap.name === 'log-destination' ? 'collection' as const : 'singleton' as const
62
+ const mode = cap.name === 'log-destination' ? ('collection' as const) : ('singleton' as const)
64
63
  this.registry.declareCapability({ name: cap.name, scope: 'system', mode, methods: {} })
65
64
  this.registry.declareFromManifest(cap)
66
65
  }
@@ -84,11 +83,12 @@ class TestAddonHarness {
84
83
  // Mirror the real addon-registry.service behaviour: process the return
85
84
  // value which may be ProviderRegistration[] or AddonInitResult.
86
85
  if (result) {
87
- const regs = Array.isArray(result) ? result : (result as any).providers ?? []
86
+ const regs = Array.isArray(result) ? result : ((result as any).providers ?? [])
88
87
  for (const reg of regs) {
89
- const capName: string = typeof reg.capability === 'string'
90
- ? reg.capability
91
- : (reg.capability as any)?.name ?? String(reg.capability)
88
+ const capName: string =
89
+ typeof reg.capability === 'string'
90
+ ? reg.capability
91
+ : ((reg.capability as any)?.name ?? String(reg.capability))
92
92
  self.registry.registerProvider(capName, id, reg.provider)
93
93
  }
94
94
  }
@@ -105,7 +105,6 @@ class TestAddonHarness {
105
105
  this.registry.unregisterProvider(cap.name, id)
106
106
  }
107
107
 
108
-
109
108
  await entry.addon.shutdown()
110
109
  entry.initialized = false
111
110
  }
@@ -153,11 +152,10 @@ class TestAddonHarness {
153
152
  }
154
153
 
155
154
  private getAddonCapabilities(addon: ICamstackAddon): CapabilityDeclaration[] {
156
-
157
155
  const manifest = addon.manifest as any
158
-
156
+
159
157
  if (!manifest.capabilities) return []
160
-
158
+
161
159
  return manifest.capabilities.map((cap: string | CapabilityDeclaration) => {
162
160
  if (typeof cap === 'string') {
163
161
  const decl: CapabilityDeclaration = { name: cap }
@@ -268,7 +266,7 @@ describe('Server E2E: Collection addon add/remove', () => {
268
266
 
269
267
  // Dynamically add a second log destination
270
268
  const logAddon2 = new MockLogAddon()
271
-
269
+
272
270
  ;(logAddon2 as any).manifest = {
273
271
  ...logAddon2.manifest,
274
272
  id: 'mock-log-addon-2',
@@ -33,7 +33,10 @@ function waitForPort(port: number, timeoutMs: number = 15000): Promise<void> {
33
33
  const check = () => {
34
34
  const req = https.request(
35
35
  { hostname: '127.0.0.1', port, path: '/', method: 'GET', rejectUnauthorized: false },
36
- (res) => { res.resume(); resolve() },
36
+ (res) => {
37
+ res.resume()
38
+ resolve()
39
+ },
37
40
  )
38
41
  req.on('error', () => {
39
42
  if (Date.now() > deadline) {
@@ -63,67 +66,85 @@ describe('CLI E2E', () => {
63
66
  // Full server boot tests require the entire build chain
64
67
  const fullTest = process.env.CAMSTACK_TEST_CLI_FULL === 'true' && CLI_EXISTS ? it : it.skip
65
68
 
66
- fullTest('camstack serve boots and responds on HTTPS', async () => {
67
- const port = randomPort()
68
- const proc = fork(CLI_PATH, ['serve', '--port', String(port), '--data', TEST_DATA_DIR], {
69
- stdio: 'pipe',
70
- env: { ...process.env, CAMSTACK_TLS_ENABLED: 'true' },
71
- })
72
- processes.push(proc)
73
-
74
- await waitForPort(port)
69
+ fullTest(
70
+ 'camstack serve boots and responds on HTTPS',
71
+ async () => {
72
+ const port = randomPort()
73
+ const proc = fork(CLI_PATH, ['serve', '--port', String(port), '--data', TEST_DATA_DIR], {
74
+ stdio: 'pipe',
75
+ env: { ...process.env, CAMSTACK_TLS_ENABLED: 'true' },
76
+ })
77
+ processes.push(proc)
78
+
79
+ await waitForPort(port)
80
+
81
+ // Verify HTTPS response
82
+ const body = await new Promise<string>((resolve, reject) => {
83
+ const req = https.request(
84
+ { hostname: '127.0.0.1', port, path: '/', method: 'GET', rejectUnauthorized: false },
85
+ (res) => {
86
+ let data = ''
87
+ res.on('data', (chunk) => {
88
+ data += chunk
89
+ })
90
+ res.on('end', () => resolve(data))
91
+ },
92
+ )
93
+ req.on('error', reject)
94
+ req.end()
95
+ })
75
96
 
76
- // Verify HTTPS response
77
- const body = await new Promise<string>((resolve, reject) => {
78
- const req = https.request(
79
- { hostname: '127.0.0.1', port, path: '/', method: 'GET', rejectUnauthorized: false },
80
- (res) => {
81
- let data = ''
82
- res.on('data', (chunk) => { data += chunk })
83
- res.on('end', () => resolve(data))
97
+ // Should return something (admin UI or health endpoint)
98
+ expect(body.length).toBeGreaterThan(0)
99
+ },
100
+ 30000,
101
+ )
102
+
103
+ fullTest(
104
+ 'camstack agent boots and stays alive',
105
+ async () => {
106
+ const proc = fork(
107
+ CLI_PATH,
108
+ ['agent', '--hub', '127.0.0.1', '--token', 'test_token', '--port', String(randomPort())],
109
+ {
110
+ stdio: 'pipe',
111
+ env: {
112
+ ...process.env,
113
+ CAMSTACK_AGENT_NAME: 'test-agent',
114
+ CAMSTACK_DATA: TEST_DATA_DIR,
115
+ },
84
116
  },
85
117
  )
86
- req.on('error', reject)
87
- req.end()
88
- })
89
-
90
- // Should return something (admin UI or health endpoint)
91
- expect(body.length).toBeGreaterThan(0)
92
- }, 30000)
93
-
94
- fullTest('camstack agent boots and stays alive', async () => {
95
- const proc = fork(CLI_PATH, [
96
- 'agent', '--hub', '127.0.0.1', '--token', 'test_token', '--port', String(randomPort()),
97
- ], {
98
- stdio: 'pipe',
99
- env: {
100
- ...process.env,
101
- CAMSTACK_AGENT_NAME: 'test-agent',
102
- CAMSTACK_DATA: TEST_DATA_DIR,
103
- },
104
- })
105
- processes.push(proc)
106
-
107
- // Agent should start even if hub is unreachable (retries in background)
108
- await new Promise((r) => setTimeout(r, 5000))
109
-
110
- // Process should still be alive (not crashed)
111
- expect(proc.exitCode).toBeNull()
112
- }, 15000)
113
-
114
- it.skipIf(!CLI_EXISTS)('camstack info prints version and platform', async () => {
115
- const output = await new Promise<string>((resolve, reject) => {
116
- let stdout = ''
117
- const proc = fork(CLI_PATH, ['info'], { stdio: 'pipe' })
118
- proc.stdout?.on('data', (chunk) => { stdout += chunk })
119
- proc.on('exit', (code) => {
120
- if (code === 0) resolve(stdout)
121
- else reject(new Error(`Exit code ${code}`))
118
+ processes.push(proc)
119
+
120
+ // Agent should start even if hub is unreachable (retries in background)
121
+ await new Promise((r) => setTimeout(r, 5000))
122
+
123
+ // Process should still be alive (not crashed)
124
+ expect(proc.exitCode).toBeNull()
125
+ },
126
+ 15000,
127
+ )
128
+
129
+ it.skipIf(!CLI_EXISTS)(
130
+ 'camstack info prints version and platform',
131
+ async () => {
132
+ const output = await new Promise<string>((resolve, reject) => {
133
+ let stdout = ''
134
+ const proc = fork(CLI_PATH, ['info'], { stdio: 'pipe' })
135
+ proc.stdout?.on('data', (chunk) => {
136
+ stdout += chunk
137
+ })
138
+ proc.on('exit', (code) => {
139
+ if (code === 0) resolve(stdout)
140
+ else reject(new Error(`Exit code ${code}`))
141
+ })
122
142
  })
123
- })
124
143
 
125
- expect(output).toContain('camstack v')
126
- expect(output).toContain('Platform:')
127
- expect(output).toContain('Node.js:')
128
- }, 10000)
144
+ expect(output).toContain('camstack v')
145
+ expect(output).toContain('Platform:')
146
+ expect(output).toContain('Node.js:')
147
+ },
148
+ 10000,
149
+ )
129
150
  })
@@ -83,7 +83,9 @@ describe('buildCoreCapService', () => {
83
83
  broker.createService(buildCoreCapService(asAppRouter(makeRouter())))
84
84
  await broker.start()
85
85
 
86
- const result = await broker.call(`${CORE_CAP_SERVICE_NAME}.system.setRetentionConfig`, { days: 30 })
86
+ const result = await broker.call(`${CORE_CAP_SERVICE_NAME}.system.setRetentionConfig`, {
87
+ days: 30,
88
+ })
87
89
  expect(result).toEqual({ days: 30 })
88
90
  })
89
91
  })
@@ -7,10 +7,20 @@ describe('dev bootstrap — shm-ring prebuild availability', () => {
7
7
  const platform = process.platform
8
8
  const arch = process.arch
9
9
  const triple = `${platform}-${arch}`
10
- const prebuildsDir = join(__dirname, '..', '..', '..', '..', 'packages', 'shm-ring', 'prebuilds', triple)
10
+ const prebuildsDir = join(
11
+ __dirname,
12
+ '..',
13
+ '..',
14
+ '..',
15
+ '..',
16
+ 'packages',
17
+ 'shm-ring',
18
+ 'prebuilds',
19
+ triple,
20
+ )
11
21
  expect(existsSync(prebuildsDir)).toBe(true)
12
22
  const files = readdirSync(prebuildsDir)
13
- const nodeBinaries = files.filter(f => f.endsWith('.node'))
23
+ const nodeBinaries = files.filter((f) => f.endsWith('.node'))
14
24
  expect(nodeBinaries.length).toBeGreaterThan(0)
15
25
  })
16
26
 
@@ -23,8 +23,11 @@ import { DeviceManagerAddon } from '@camstack/core'
23
23
  import { CapabilityRegistry, DeviceRegistry } from '@camstack/kernel'
24
24
  import { snapshotCapability } from '@camstack/types'
25
25
  import type {
26
- AddonContext, ConfigUISchemaWithValues,
27
- IScopedLogger, IEventBus, ProviderRegistration,
26
+ AddonContext,
27
+ ConfigUISchemaWithValues,
28
+ IScopedLogger,
29
+ IEventBus,
30
+ ProviderRegistration,
28
31
  IDeviceManagerProvider,
29
32
  } from '@camstack/types'
30
33
 
@@ -32,8 +35,12 @@ import type {
32
35
 
33
36
  function makeLogger(): IScopedLogger {
34
37
  const l: IScopedLogger = {
35
- info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(),
36
- child: vi.fn(() => l), withTags: vi.fn(() => l),
38
+ info: vi.fn(),
39
+ warn: vi.fn(),
40
+ error: vi.fn(),
41
+ debug: vi.fn(),
42
+ child: vi.fn(() => l),
43
+ withTags: vi.fn(() => l),
37
44
  }
38
45
  return l
39
46
  }
@@ -63,18 +70,30 @@ function makeSnapshotWrapperProvider() {
63
70
  invalidateCache: vi.fn(async () => undefined),
64
71
 
65
72
  // Contribution methods — owned by the wrapper
66
- getDeviceSettingsContribution: vi.fn(async (_input: { deviceId: number }): Promise<ConfigUISchemaWithValues | null> => ({
67
- sections: [{
68
- id: 'snapshot-settings',
69
- title: 'Snapshot',
70
- tab: 'general',
71
- order: 99,
72
- fields: [
73
- { type: 'text' as const, key: 'preferredStreamId', label: 'Preferred Stream', default: '', value: '' },
73
+ getDeviceSettingsContribution: vi.fn(
74
+ async (_input: { deviceId: number }): Promise<ConfigUISchemaWithValues | null> => ({
75
+ sections: [
76
+ {
77
+ id: 'snapshot-settings',
78
+ title: 'Snapshot',
79
+ tab: 'general',
80
+ order: 99,
81
+ fields: [
82
+ {
83
+ type: 'text' as const,
84
+ key: 'preferredStreamId',
85
+ label: 'Preferred Stream',
86
+ default: '',
87
+ value: '',
88
+ },
89
+ ],
90
+ },
74
91
  ],
75
- }],
76
- })),
77
- getDeviceLiveContribution: vi.fn(async (_input: { deviceId: number }): Promise<ConfigUISchemaWithValues | null> => null),
92
+ }),
93
+ ),
94
+ getDeviceLiveContribution: vi.fn(
95
+ async (_input: { deviceId: number }): Promise<ConfigUISchemaWithValues | null> => null,
96
+ ),
78
97
  applyDeviceSettingsPatch: vi.fn(async () => ({ success: true as const })),
79
98
  }
80
99
  }
@@ -118,7 +137,9 @@ async function setupBug3Scenario() {
118
137
  capabilityRegistry.registerProvider(snapshotCapability.name, 'snapshot-addon', wrapperProvider)
119
138
 
120
139
  // Register the default wrapper so auto-bind picks it up in getBindings step 2.
121
- capabilityRegistry.registerWrapper(snapshotCapability.name, 'snapshot-addon', { defaultActive: true })
140
+ capabilityRegistry.registerWrapper(snapshotCapability.name, 'snapshot-addon', {
141
+ defaultActive: true,
142
+ })
122
143
 
123
144
  const ctx = {
124
145
  id: 'device-manager',
@@ -140,15 +161,28 @@ async function setupBug3Scenario() {
140
161
  const provider = providers[0]!.provider as IDeviceManagerProvider
141
162
 
142
163
  // Allocate and register the device.
143
- const { id: deviceId } = await provider.allocateDeviceId({ addonId: 'reolink-addon', stableId: 'cam-reolink-1' })
164
+ const { id: deviceId } = await provider.allocateDeviceId({
165
+ addonId: 'reolink-addon',
166
+ stableId: 'cam-reolink-1',
167
+ })
144
168
  await provider.registerDevice({
145
- addonId: 'reolink-addon', stableId: 'cam-reolink-1', id: deviceId,
146
- type: 'camera', name: 'Reolink Camera', parentDeviceId: null, config: {},
169
+ addonId: 'reolink-addon',
170
+ stableId: 'cam-reolink-1',
171
+ id: deviceId,
172
+ type: 'camera',
173
+ name: 'Reolink Camera',
174
+ parentDeviceId: null,
175
+ config: {},
147
176
  })
148
177
 
149
178
  // Register the NATIVE provider for the device — this is what a Reolink/ONVIF
150
179
  // driver does. The native does NOT implement contribution methods.
151
- capabilityRegistry.registerNativeProvider(snapshotCapability.name, deviceId, 'reolink-addon', nativeProvider)
180
+ capabilityRegistry.registerNativeProvider(
181
+ snapshotCapability.name,
182
+ deviceId,
183
+ 'reolink-addon',
184
+ nativeProvider,
185
+ )
152
186
 
153
187
  capabilityRegistry.ready()
154
188
 
@@ -166,7 +200,7 @@ describe('device-manager contribution dispatch — Bug-3 regression (snapshot wr
166
200
 
167
201
  expect(aggregate).not.toBeNull()
168
202
  // The wrapper's contribution section must be present.
169
- const snapshotSection = aggregate!.sections.find(s => s.id === 'snapshot-settings')
203
+ const snapshotSection = aggregate!.sections.find((s) => s.id === 'snapshot-settings')
170
204
  expect(snapshotSection).toBeDefined()
171
205
  expect(snapshotSection!.title).toBe('Snapshot')
172
206
 
@@ -204,9 +238,10 @@ describe('device-manager contribution dispatch — Bug-3 regression (snapshot wr
204
238
  expect(aggregate).not.toBeNull()
205
239
 
206
240
  const field = aggregate!.sections
207
- .find(s => s.id === 'snapshot-settings')
208
- ?.fields
209
- .find(f => (f as Record<string, unknown>)['key'] === 'preferredStreamId') as Record<string, unknown> | undefined
241
+ .find((s) => s.id === 'snapshot-settings')
242
+ ?.fields.find((f) => (f as Record<string, unknown>)['key'] === 'preferredStreamId') as
243
+ | Record<string, unknown>
244
+ | undefined
210
245
 
211
246
  expect(field).toBeDefined()
212
247
  expect(field!['writerCapName']).toBe('snapshot')
@@ -238,12 +273,8 @@ describe('device-manager contribution dispatch — Bug-3 regression (snapshot wr
238
273
  const { provider, deviceId } = await setupBug3Scenario()
239
274
 
240
275
  // Must complete without throwing.
241
- await expect(
242
- provider.getDeviceSettingsAggregate({ deviceId }),
243
- ).resolves.not.toBeNull()
276
+ await expect(provider.getDeviceSettingsAggregate({ deviceId })).resolves.not.toBeNull()
244
277
 
245
- await expect(
246
- provider.getDeviceLiveInfoAggregate({ deviceId }),
247
- ).resolves.toBeDefined()
278
+ await expect(provider.getDeviceLiveInfoAggregate({ deviceId })).resolves.toBeDefined()
248
279
  })
249
280
  })