@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
@@ -1,99 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2
- import * as fs from 'node:fs'
3
- import * as path from 'node:path'
4
- import * as os from 'node:os'
5
- import * as yaml from 'js-yaml'
6
- import { FeatureService } from './feature.service'
7
- import { ConfigService } from '../config/config.service'
8
- import type { FeatureManifest } from '@camstack/types'
9
-
10
- /**
11
- * Test-only ConfigService subclass that overrides the `features` getter
12
- * to return a fixed manifest. Avoids wiring a full settings-store just to
13
- * drive a FeatureService. The bootstrap YAML still has to be real so the
14
- * ConfigManager constructor succeeds.
15
- */
16
- class StaticFeatureConfigService extends ConfigService {
17
- constructor(
18
- configPath: string,
19
- private readonly staticFeatures: FeatureManifest,
20
- ) {
21
- super(configPath)
22
- }
23
-
24
- override get features(): FeatureManifest {
25
- return this.staticFeatures
26
- }
27
- }
28
-
29
- describe('FeatureService', () => {
30
- let tmpDir: string
31
-
32
- beforeEach(() => {
33
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'camstack-feature-'))
34
- })
35
-
36
- afterEach(() => {
37
- fs.rmSync(tmpDir, { recursive: true, force: true })
38
- })
39
-
40
- const createService = (features: Partial<FeatureManifest> = {}): FeatureService => {
41
- const fullFeatures: FeatureManifest = {
42
- streaming: true,
43
- notifications: true,
44
- objectDetection: false,
45
- remoteAccess: true,
46
- agentCluster: false,
47
- smartHome: true,
48
- recordings: true,
49
- backup: true,
50
- repl: true,
51
- ...features,
52
- }
53
-
54
- const configPath = path.join(tmpDir, 'config.yaml')
55
- fs.writeFileSync(
56
- configPath,
57
- yaml.dump({
58
- server: { port: 4443 },
59
- auth: { adminPassword: 'secret123' },
60
- }),
61
- 'utf-8',
62
- )
63
- const configService = new StaticFeatureConfigService(configPath, fullFeatures)
64
- return new FeatureService(configService)
65
- }
66
-
67
- it('returns true for enabled features', () => {
68
- const service = createService({ streaming: true, notifications: true })
69
-
70
- expect(service.isEnabled('streaming')).toBe(true)
71
- expect(service.isEnabled('notifications')).toBe(true)
72
- expect(service.isEnabled('remoteAccess')).toBe(true)
73
- })
74
-
75
- it('returns false for disabled features', () => {
76
- const service = createService({ objectDetection: false, agentCluster: false })
77
-
78
- expect(service.isEnabled('objectDetection')).toBe(false)
79
- expect(service.isEnabled('agentCluster')).toBe(false)
80
- })
81
-
82
- it('returns full manifest', () => {
83
- const customFeatures: Partial<FeatureManifest> = {
84
- streaming: false,
85
- objectDetection: true,
86
- }
87
- const service = createService(customFeatures)
88
-
89
- const manifest = service.getManifest()
90
-
91
- expect(manifest.streaming).toBe(false)
92
-
93
- expect(manifest.objectDetection).toBe(true)
94
-
95
- expect(manifest.notifications).toBe(true)
96
-
97
- expect(manifest.remoteAccess).toBe(true)
98
- })
99
- })
@@ -1,8 +0,0 @@
1
- import { FeatureManager } from '@camstack/core'
2
- import { ConfigService } from '../config/config.service'
3
-
4
- export class FeatureService extends FeatureManager {
5
- constructor(configService: ConfigService) {
6
- super(configService)
7
- }
8
- }
@@ -1,166 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest'
2
- import { LifecycleStateMachine } from './lifecycle-state-machine'
3
- import type { IEventBus, SystemEvent } from '@camstack/types'
4
- import type { IScopedLogger } from '@camstack/types'
5
-
6
- const createMockEventBus = (): IEventBus & { emitted: SystemEvent[] } => {
7
- const emitted: SystemEvent[] = []
8
- return {
9
- emitted,
10
- emit: vi.fn((event: SystemEvent) => emitted.push(event)),
11
- subscribe: vi.fn(() => () => {}),
12
- getRecent: vi.fn(() => []),
13
- }
14
- }
15
-
16
- const createMockLogger = (): IScopedLogger => ({
17
- debug: vi.fn(),
18
- info: vi.fn(),
19
- warn: vi.fn(),
20
- error: vi.fn(),
21
- child: vi.fn(),
22
- })
23
-
24
- describe('LifecycleStateMachine', () => {
25
- let eventBus: ReturnType<typeof createMockEventBus>
26
- let logger: IScopedLogger
27
- let machine: LifecycleStateMachine
28
-
29
- beforeEach(() => {
30
- eventBus = createMockEventBus()
31
-
32
- logger = createMockLogger()
33
- machine = new LifecycleStateMachine('test-element', 'device', eventBus, logger)
34
- })
35
-
36
- it('starts in stopped state', () => {
37
- expect(machine.state).toBe('stopped')
38
- expect(machine.getStatus().state).toBe('stopped')
39
- expect(machine.getStatus().restartCount).toBe(0)
40
- expect(machine.getStatus().uptime).toBe(0)
41
- })
42
-
43
- it('allows valid transition: stopped → starting → running', () => {
44
- const result1 = machine.transition('starting')
45
- expect(result1).toBe(true)
46
- expect(machine.state).toBe('starting')
47
-
48
- const result2 = machine.transition('running')
49
- expect(result2).toBe(true)
50
- expect(machine.state).toBe('running')
51
- })
52
-
53
- it('rejects invalid transition (e.g. stopped → running directly)', () => {
54
- const result = machine.transition('running')
55
- expect(result).toBe(false)
56
- expect(machine.state).toBe('stopped')
57
-
58
- expect(logger.warn).toHaveBeenCalledWith(
59
- expect.stringContaining('Invalid state transition'),
60
- expect.anything(),
61
- )
62
- })
63
-
64
- it('emits event on bus for each valid transition', () => {
65
- machine.transition('starting')
66
- machine.transition('running')
67
-
68
- expect(eventBus.emit).toHaveBeenCalledTimes(2)
69
-
70
- expect(eventBus.emitted[0]!.category).toBe('device.state.starting')
71
-
72
- expect(eventBus.emitted[0]!.source).toEqual({ type: 'device', id: 'test-element' })
73
-
74
- expect(eventBus.emitted[0]!.data).toMatchObject({
75
- from: 'stopped',
76
- to: 'starting',
77
- elementId: 'test-element',
78
- })
79
-
80
- expect(eventBus.emitted[1]!.category).toBe('device.state.running')
81
-
82
- expect(eventBus.emitted[1]!.data).toMatchObject({
83
- from: 'starting',
84
- to: 'running',
85
- })
86
- })
87
-
88
- it('does not emit event on invalid transition', () => {
89
- machine.transition('running')
90
-
91
- expect(eventBus.emit).not.toHaveBeenCalled()
92
- })
93
-
94
- it('tracks uptime when running', () => {
95
- machine.transition('starting')
96
- machine.transition('running')
97
-
98
- const status = machine.getStatus()
99
- expect(status.uptime).toBeGreaterThanOrEqual(0)
100
- expect(status.startedAt).toBeDefined()
101
- expect(status.stoppedAt).toBeUndefined()
102
- })
103
-
104
- it('records error message in error state', () => {
105
- machine.transition('starting')
106
- machine.transition('error', 'connection refused')
107
-
108
- const status = machine.getStatus()
109
- expect(status.state).toBe('error')
110
- expect(status.error).toBe('connection refused')
111
- expect(status.stoppedAt).toBeDefined()
112
- })
113
-
114
- it('getStatus returns complete info after full lifecycle', () => {
115
- machine.transition('starting')
116
- machine.transition('running')
117
- machine.transition('stopping')
118
- machine.transition('stopped')
119
-
120
- const status = machine.getStatus()
121
- expect(status.state).toBe('stopped')
122
- expect(status.restartCount).toBe(0)
123
- expect(status.stoppedAt).toBeDefined()
124
- expect(status.uptime).toBe(0)
125
- })
126
-
127
- it('increments restart count only on subsequent starts', () => {
128
- // First start
129
- machine.transition('starting')
130
- machine.transition('running')
131
- expect(machine.getStatus().restartCount).toBe(0)
132
-
133
- // Stop and restart
134
- machine.transition('stopping')
135
- machine.transition('stopped')
136
- machine.transition('starting')
137
- machine.transition('running')
138
- expect(machine.getStatus().restartCount).toBe(1)
139
-
140
- // Error and restart
141
- machine.transition('error', 'crash')
142
- machine.transition('starting')
143
- machine.transition('running')
144
- expect(machine.getStatus().restartCount).toBe(2)
145
- })
146
-
147
- it('allows disabled state from stopped', () => {
148
- const result = machine.transition('disabled')
149
- expect(result).toBe(true)
150
- expect(machine.state).toBe('disabled')
151
- })
152
-
153
- it('requires going through stopped to leave disabled', () => {
154
- machine.transition('disabled')
155
-
156
- expect(machine.transition('starting')).toBe(false)
157
- expect(machine.transition('stopped')).toBe(true)
158
- expect(machine.state).toBe('stopped')
159
- })
160
-
161
- it('incrementRestartCount manually increments the counter', () => {
162
- machine.incrementRestartCount()
163
- machine.incrementRestartCount()
164
- expect(machine.getStatus().restartCount).toBe(2)
165
- })
166
- })
@@ -1,3 +0,0 @@
1
- // Re-export from @camstack/core — LifecycleStateMachine is already a plain class
2
- export { LifecycleStateMachine } from '@camstack/core'
3
- export type { ElementState, ElementStatus } from '@camstack/core'
@@ -1,3 +0,0 @@
1
- // Re-export from @camstack/core
2
- export { LogRingBuffer } from '@camstack/core'
3
- export type { LogEntry, LogFilter, LogLevel } from '@camstack/core'
@@ -1,287 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2
- import * as fs from 'node:fs'
3
- import * as path from 'node:path'
4
- import * as os from 'node:os'
5
- import * as yaml from 'js-yaml'
6
- import { LogRingBuffer } from './log-ring-buffer'
7
- import { ScopedLogger } from './scoped-logger'
8
- import { LoggingService } from './logging.service'
9
- import { ConfigService } from '../config/config.service'
10
- import type { ISettingsStore } from '@camstack/kernel'
11
- import type { LogEntry, ILogDestination } from '@camstack/types'
12
-
13
- /**
14
- * In-memory settings store used by the test to drive `ConfigService.get`
15
- * for the `eventBus.ringBufferSize` key. The bootstrap YAML schema does not
16
- * declare `eventBus`, so writing it to the YAML file would be discarded by
17
- * `bootstrapSchema.parse`. Wiring an ISettingsStore is the clean path to
18
- * feed runtime values into `ConfigService` without casts.
19
- */
20
- class InMemorySettingsStore implements ISettingsStore {
21
- private readonly system: Record<string, unknown>
22
-
23
- constructor(seed: Record<string, unknown>) {
24
- this.system = { ...seed }
25
- }
26
-
27
- getSystem(key: string): unknown {
28
- return this.system[key]
29
- }
30
- setSystem(key: string, value: unknown): void {
31
- this.system[key] = value
32
- }
33
- getAllSystem(): Record<string, unknown> {
34
- return { ...this.system }
35
- }
36
-
37
- getAllAddon(_addonId: string): Record<string, unknown> {
38
- return {}
39
- }
40
- setAllAddon(_addonId: string, _config: Record<string, unknown>): void {
41
- /* no-op */
42
- }
43
- getAllProvider(_providerId: string): Record<string, unknown> {
44
- return {}
45
- }
46
- setProvider(_providerId: string, _key: string, _value: unknown): void {
47
- /* no-op */
48
- }
49
- getAllDevice(_deviceId: string): Record<string, unknown> {
50
- return {}
51
- }
52
- setDevice(_deviceId: string, _key: string, _value: unknown): void {
53
- /* no-op */
54
- }
55
- getAddonDevice(_addonId: string, _deviceId: string): Record<string, unknown> {
56
- return {}
57
- }
58
- setAddonDevice(_addonId: string, _deviceId: string, _values: Record<string, unknown>): void {
59
- /* no-op */
60
- }
61
- clearAddonDevice(_addonId: string, _deviceId: string): void {
62
- /* no-op */
63
- }
64
- }
65
-
66
- describe('ScopedLogger', () => {
67
- it('emits entries with a single scope string', () => {
68
- const entries: LogEntry[] = []
69
- const writeFn = (entry: LogEntry) => entries.push(entry)
70
- const logger = new ScopedLogger('config', writeFn)
71
-
72
- logger.info('loaded config')
73
-
74
- expect(entries).toHaveLength(1)
75
- expect(entries[0]!.scope).toBe('config')
76
- expect(entries[0]!.level).toBe('info')
77
- expect(entries[0]!.message).toBe('loaded config')
78
- expect(entries[0]!.timestamp).toBeInstanceOf(Date)
79
- })
80
-
81
- it('child() replaces the scope (no hierarchy)', () => {
82
- const entries: LogEntry[] = []
83
- const writeFn = (entry: LogEntry) => entries.push(entry)
84
- const logger = new ScopedLogger('core', writeFn)
85
- const child = logger.child('http')
86
-
87
- child.warn('timeout', { meta: { ms: 3000 } })
88
-
89
- expect(entries).toHaveLength(1)
90
- expect(entries[0]!.scope).toBe('http')
91
- expect(entries[0]!.level).toBe('warn')
92
- expect(entries[0]!.meta).toEqual({ ms: 3000 })
93
- })
94
-
95
- it('creates entries for all log levels', () => {
96
- const entries: LogEntry[] = []
97
- const writeFn = (entry: LogEntry) => entries.push(entry)
98
- const logger = new ScopedLogger('test', writeFn)
99
-
100
- logger.debug('d')
101
- logger.info('i')
102
- logger.warn('w')
103
- logger.error('e')
104
-
105
- expect(entries.map((e) => e.level)).toEqual(['debug', 'info', 'warn', 'error'])
106
- })
107
-
108
- it('omits the scope field when the logger was created without one', () => {
109
- const entries: LogEntry[] = []
110
- const writeFn = (entry: LogEntry) => entries.push(entry)
111
- const logger = new ScopedLogger(undefined, writeFn)
112
-
113
- logger.info('no scope')
114
-
115
- expect(entries).toHaveLength(1)
116
- expect(entries[0]!.scope).toBeUndefined()
117
- })
118
- })
119
-
120
- describe('LogRingBuffer', () => {
121
- it('stores up to capacity and evicts oldest', () => {
122
- const buffer = new LogRingBuffer(3)
123
-
124
- const makeEntry = (msg: string): LogEntry => ({
125
- timestamp: new Date(),
126
- level: 'info',
127
- scope: 'test',
128
- message: msg,
129
- })
130
-
131
- buffer.push(makeEntry('a'))
132
- buffer.push(makeEntry('b'))
133
- buffer.push(makeEntry('c'))
134
- buffer.push(makeEntry('d'))
135
-
136
- const all = buffer.getAll()
137
- expect(all).toHaveLength(3)
138
- // newest first
139
- expect(all[0]!.message).toBe('d')
140
- expect(all[1]!.message).toBe('c')
141
- expect(all[2]!.message).toBe('b')
142
- })
143
-
144
- it('filters by level', () => {
145
- const buffer = new LogRingBuffer(100)
146
-
147
- buffer.push({ timestamp: new Date(), level: 'debug', scope: 'test', message: 'a' })
148
- buffer.push({ timestamp: new Date(), level: 'error', scope: 'test', message: 'b' })
149
- buffer.push({ timestamp: new Date(), level: 'error', scope: 'test', message: 'c' })
150
-
151
- const result = buffer.query({ level: 'error' })
152
- expect(result).toHaveLength(2)
153
- })
154
-
155
- it('filters by since/until', () => {
156
- const buffer = new LogRingBuffer(100)
157
- const t1 = new Date('2025-01-01T00:00:00Z')
158
- const t2 = new Date('2025-01-02T00:00:00Z')
159
- const t3 = new Date('2025-01-03T00:00:00Z')
160
-
161
- buffer.push({ timestamp: t1, level: 'info', scope: 'test', message: 'a' })
162
- buffer.push({ timestamp: t2, level: 'info', scope: 'test', message: 'b' })
163
- buffer.push({ timestamp: t3, level: 'info', scope: 'test', message: 'c' })
164
-
165
- const result = buffer.query({ since: t2 })
166
- expect(result).toHaveLength(2)
167
- expect(result.map((e) => e.message)).toEqual(['c', 'b'])
168
- })
169
-
170
- it('filters by tags (addonId exact match)', () => {
171
- const buffer = new LogRingBuffer(100)
172
-
173
- buffer.push({
174
- timestamp: new Date(),
175
- level: 'info',
176
- message: 'a',
177
- tags: { addonId: 'stream-broker' },
178
- })
179
- buffer.push({
180
- timestamp: new Date(),
181
- level: 'info',
182
- message: 'b',
183
- tags: { addonId: 'provider-rtsp' },
184
- })
185
- buffer.push({
186
- timestamp: new Date(),
187
- level: 'info',
188
- message: 'c',
189
- tags: { addonId: 'stream-broker' },
190
- })
191
-
192
- const result = buffer.query({ tags: { addonId: 'stream-broker' } })
193
- expect(result).toHaveLength(2)
194
- expect(result.map((e) => e.message).toSorted()).toEqual(['a', 'c'])
195
- })
196
-
197
- it('respects limit', () => {
198
- const buffer = new LogRingBuffer(100)
199
-
200
- for (let i = 0; i < 10; i++) {
201
- buffer.push({ timestamp: new Date(), level: 'info', scope: 'test', message: `msg-${i}` })
202
- }
203
-
204
- const result = buffer.query({ limit: 3 })
205
- expect(result).toHaveLength(3)
206
- })
207
- })
208
-
209
- describe('LoggingService', () => {
210
- let tmpDir: string
211
-
212
- beforeEach(() => {
213
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'camstack-logging-'))
214
- })
215
-
216
- afterEach(() => {
217
- fs.rmSync(tmpDir, { recursive: true, force: true })
218
- })
219
-
220
- const createService = (bufferSize = 100): LoggingService => {
221
- const configPath = path.join(tmpDir, 'config.yaml')
222
- fs.writeFileSync(
223
- configPath,
224
- yaml.dump({
225
- server: { port: 4443 },
226
- auth: { adminPassword: 'secret123' },
227
- }),
228
- 'utf-8',
229
- )
230
- const configService = new ConfigService(configPath)
231
- configService.setSettingsStore(
232
- new InMemorySettingsStore({
233
- 'eventBus.ringBufferSize': bufferSize,
234
- }),
235
- )
236
- return new LoggingService(configService)
237
- }
238
-
239
- it('routes entries to all destinations', () => {
240
- const service = createService()
241
- const written: LogEntry[] = []
242
- const dest: ILogDestination = {
243
- initialize: async () => {},
244
- shutdown: async () => {},
245
- write: (entry) => written.push(entry),
246
- }
247
-
248
- service.addDestination(dest)
249
- const logger = service.createLogger('test')
250
- logger.info('hello')
251
-
252
- expect(written).toHaveLength(1)
253
- expect(written[0]!.message).toBe('hello')
254
- expect(written[0]!.scope).toBe('test')
255
- })
256
-
257
- it('creates scoped loggers that push to ring buffer', () => {
258
- const service = createService()
259
- const logger = service.createLogger('myScope')
260
- logger.error('boom')
261
-
262
- const entries = service.query({})
263
- expect(entries).toHaveLength(1)
264
- expect(entries[0]!.message).toBe('boom')
265
- expect(entries[0]!.scope).toBe('myScope')
266
- })
267
-
268
- it('removes destinations', () => {
269
- const service = createService()
270
- const written: LogEntry[] = []
271
- const dest: ILogDestination = {
272
- initialize: async () => {},
273
- shutdown: async () => {},
274
- write: (entry) => written.push(entry),
275
- }
276
-
277
- service.addDestination(dest)
278
- service.removeDestination(dest)
279
-
280
- const logger = service.createLogger('test')
281
- logger.info('should not reach dest')
282
-
283
- expect(written).toHaveLength(0)
284
- // but still in ring buffer
285
- expect(service.query({})).toHaveLength(1)
286
- })
287
- })