@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
@@ -18,21 +18,21 @@ export function createCapabilitiesRouter(
18
18
  .input(z.void())
19
19
  .query(() => registry?.listCapabilities() ?? []),
20
20
 
21
- getCapability: adminProcedure
22
- .input(z.object({ name: z.string() }))
23
- .query(({ input }) => {
24
- if (!registry) return null
25
- return registry.listCapabilities().find(
26
- (c: { name: string }) => c.name === input.name,
27
- ) ?? null
28
- }),
21
+ getCapability: adminProcedure.input(z.object({ name: z.string() })).query(({ input }) => {
22
+ if (!registry) return null
23
+ return (
24
+ registry.listCapabilities().find((c: { name: string }) => c.name === input.name) ?? null
25
+ )
26
+ }),
29
27
 
30
28
  setActiveSingleton: adminProcedure
31
- .input(z.object({
32
- capability: z.string(),
33
- addonId: z.string(),
34
- nodeId: z.string().optional(),
35
- }))
29
+ .input(
30
+ z.object({
31
+ capability: z.string(),
32
+ addonId: z.string(),
33
+ nodeId: z.string().optional(),
34
+ }),
35
+ )
36
36
  .output(z.void())
37
37
  .mutation(async ({ input }) => {
38
38
  if (!registry) throw new Error('Capability registry unavailable')
@@ -40,7 +40,10 @@ export function createCapabilitiesRouter(
40
40
  if (input.nodeId !== undefined) {
41
41
  // Per-node override: persist under the node-qualified key so the boot
42
42
  // restorer (`setNodeConfigReader`) replays it on restart.
43
- config.set(`capabilities.singletonNode.${input.capability}.${input.nodeId}`, input.addonId)
43
+ config.set(
44
+ `capabilities.singletonNode.${input.capability}.${input.nodeId}`,
45
+ input.addonId,
46
+ )
44
47
  } else {
45
48
  // Cluster-global default: persist to the SAME key the boot restorer reads
46
49
  // (addon-registry `setConfigReader` → `capabilities.singleton.<cap>`).
@@ -67,10 +70,12 @@ export function createCapabilitiesRouter(
67
70
  * save endpoint for a bulk multi-select UI.
68
71
  */
69
72
  setCollectionEnabledProviders: adminProcedure
70
- .input(z.object({
71
- capability: z.string(),
72
- enabledAddonIds: z.array(z.string()),
73
- }))
73
+ .input(
74
+ z.object({
75
+ capability: z.string(),
76
+ enabledAddonIds: z.array(z.string()),
77
+ }),
78
+ )
74
79
  .output(z.void())
75
80
  .mutation(({ input }) => {
76
81
  if (!registry) throw new Error('Capability registry unavailable')
@@ -120,11 +125,13 @@ export function createCapabilitiesRouter(
120
125
  }),
121
126
 
122
127
  setDeviceCollectionFilter: adminProcedure
123
- .input(z.object({
124
- deviceId: z.string(),
125
- capability: z.string(),
126
- addonIds: z.array(z.string()),
127
- }))
128
+ .input(
129
+ z.object({
130
+ deviceId: z.string(),
131
+ capability: z.string(),
132
+ addonIds: z.array(z.string()),
133
+ }),
134
+ )
128
135
  .output(z.void())
129
136
  .mutation(({ input }) => {
130
137
  if (!registry) throw new Error('Capability registry unavailable')
@@ -18,11 +18,22 @@ import type { HwAccelResolution } from '@camstack/types'
18
18
  import { trpcRouter, adminProcedure } from '../trpc/trpc.middleware.js'
19
19
 
20
20
  const HwAccelBackendSchema = z.enum([
21
- 'videotoolbox', 'cuda', 'nvdec', 'vaapi', 'qsv',
22
- 'd3d11va', 'dxva2', 'amf', 'vdpau', 'drm',
21
+ 'videotoolbox',
22
+ 'cuda',
23
+ 'nvdec',
24
+ 'vaapi',
25
+ 'qsv',
26
+ 'd3d11va',
27
+ 'dxva2',
28
+ 'amf',
29
+ 'vdpau',
30
+ 'drm',
23
31
  ])
24
32
 
25
- const HwAccelPreferSchema = z.union([HwAccelBackendSchema, z.literal('none')]).nullable().optional()
33
+ const HwAccelPreferSchema = z
34
+ .union([HwAccelBackendSchema, z.literal('none')])
35
+ .nullable()
36
+ .optional()
26
37
 
27
38
  const HwAccelResolutionSchema = z.object({
28
39
  preferred: z.array(HwAccelBackendSchema).readonly(),
@@ -48,23 +59,39 @@ export function createHwAccelRouter(broker: ServiceBroker | null) {
48
59
  .query(async ({ input }) => {
49
60
  if (!broker) throw new Error('Moleculer broker not available')
50
61
  const params = { prefer: input.prefer ?? null }
51
- return broker.call('$hwaccel.resolve', params, { nodeID: input.nodeId }) as Promise<HwAccelResolution>
62
+ return broker.call('$hwaccel.resolve', params, {
63
+ nodeID: input.nodeId,
64
+ }) as Promise<HwAccelResolution>
52
65
  }),
53
66
 
54
67
  /** List every node currently reachable and the hwaccel each resolves to. */
55
68
  resolveAll: adminProcedure
56
- .output(z.array(z.object({
57
- nodeId: z.string(),
58
- resolution: HwAccelResolutionSchema,
59
- })))
69
+ .output(
70
+ z.array(
71
+ z.object({
72
+ nodeId: z.string(),
73
+ resolution: HwAccelResolutionSchema,
74
+ }),
75
+ ),
76
+ )
60
77
  .query(async () => {
61
78
  if (!broker) throw new Error('Moleculer broker not available')
62
- const registry = (broker as unknown as { registry: { getNodeList: (opts: { onlyAvailable: boolean }) => readonly { id: string }[] } }).registry
79
+ const registry = (
80
+ broker as unknown as {
81
+ registry: {
82
+ getNodeList: (opts: { onlyAvailable: boolean }) => readonly { id: string }[]
83
+ }
84
+ }
85
+ ).registry
63
86
  const nodes = registry.getNodeList({ onlyAvailable: true })
64
87
  const results = await Promise.all(
65
88
  nodes.map(async (n) => {
66
89
  try {
67
- const resolution = await broker.call('$hwaccel.resolve', { prefer: null }, { nodeID: n.id }) as HwAccelResolution
90
+ const resolution = (await broker.call(
91
+ '$hwaccel.resolve',
92
+ { prefer: null },
93
+ { nodeID: n.id },
94
+ )) as HwAccelResolution
68
95
  return { nodeId: n.id, resolution }
69
96
  } catch (err) {
70
97
  const msg = err instanceof Error ? err.message : String(err)
@@ -9,22 +9,29 @@ import type { EventBusService } from '../../core/events/event-bus.service.js'
9
9
  import type { AddonRegistryService } from '../../core/addon/addon-registry.service.js'
10
10
  import { trpcRouter, protectedProcedure, iterableSubscription } from '../trpc/trpc.middleware.js'
11
11
 
12
- export function createLiveEventsRouter(
13
- eb: EventBusService,
14
- ar: AddonRegistryService,
15
- ) {
12
+ export function createLiveEventsRouter(eb: EventBusService, ar: AddonRegistryService) {
16
13
  return trpcRouter({
17
14
  recentSystemEvents: protectedProcedure
18
- .input(z.object({
19
- category: z.string().optional(),
20
- limit: z.number().optional(),
21
- }).optional())
15
+ .input(
16
+ z
17
+ .object({
18
+ category: z.string().optional(),
19
+ limit: z.number().optional(),
20
+ })
21
+ .optional(),
22
+ )
22
23
  .query(({ input }) => eb.getRecent(input ?? {}, input?.limit ?? 50)),
23
24
 
24
25
  onEvent: protectedProcedure
25
26
  .input(z.object({ category: z.string().optional() }))
26
27
  .subscription(({ input }) => {
27
- return iterableSubscription<{ id: string; timestamp: Date; source: { type: string; id: string | number }; category: string; data: Record<string, unknown> }>((push) => {
28
+ return iterableSubscription<{
29
+ id: string
30
+ timestamp: Date
31
+ source: { type: string; id: string | number }
32
+ category: string
33
+ data: Record<string, unknown>
34
+ }>((push) => {
28
35
  const filter: Record<string, unknown> = {}
29
36
  if (input.category) filter.category = input.category
30
37
  return eb.subscribe(filter, push)
@@ -8,7 +8,12 @@
8
8
  */
9
9
  import { z } from 'zod'
10
10
  import type { LoggingService } from '../../core/logging/logging.service.js'
11
- import { trpcRouter, protectedProcedure, adminProcedure, iterableSubscription } from '../trpc/trpc.middleware.js'
11
+ import {
12
+ trpcRouter,
13
+ protectedProcedure,
14
+ adminProcedure,
15
+ iterableSubscription,
16
+ } from '../trpc/trpc.middleware.js'
12
17
  import type { LogEntry } from '@camstack/types'
13
18
 
14
19
  const LogLevelSchema = z.enum(['debug', 'info', 'warn', 'error'])
@@ -59,13 +64,15 @@ const LogEntryArraySchema = z.array(LogEntrySchema)
59
64
  export function createLogsRouter(logging: LoggingService) {
60
65
  return trpcRouter({
61
66
  query: adminProcedure
62
- .input(z.object({
63
- level: LogLevelSchema.optional(),
64
- since: z.number().optional(),
65
- until: z.number().optional(),
66
- limit: z.number().optional(),
67
- tags: LogTagsSchema.optional(),
68
- }))
67
+ .input(
68
+ z.object({
69
+ level: LogLevelSchema.optional(),
70
+ since: z.number().optional(),
71
+ until: z.number().optional(),
72
+ limit: z.number().optional(),
73
+ tags: LogTagsSchema.optional(),
74
+ }),
75
+ )
69
76
  .output(LogEntryArraySchema)
70
77
  .query(({ input, ctx }) => {
71
78
  const filter: Record<string, unknown> = { limit: input.limit ?? 100 }
@@ -79,10 +86,10 @@ export function createLogsRouter(logging: LoggingService) {
79
86
  const ap = ctx.user.permissions?.allowedProviders
80
87
  if (ap && ap !== '*') {
81
88
  const allowed = ap as string[]
82
- return entries.filter(e => {
89
+ return entries.filter((e) => {
83
90
  const addonId = e.tags?.addonId
84
91
  if (typeof addonId !== 'string') return false
85
- return allowed.some(p => {
92
+ return allowed.some((p) => {
86
93
  const bare = p.startsWith('addon:') ? p.slice('addon:'.length) : p
87
94
  return addonId === bare || addonId.startsWith(bare)
88
95
  })
@@ -91,7 +98,7 @@ export function createLogsRouter(logging: LoggingService) {
91
98
  }
92
99
  return entries
93
100
  })()
94
- return filtered.map(e => ({
101
+ return filtered.map((e) => ({
95
102
  timestamp: e.timestamp instanceof Date ? e.timestamp.toISOString() : String(e.timestamp),
96
103
  level: e.level,
97
104
  ...(e.scope !== undefined ? { scope: e.scope } : {}),
@@ -102,7 +109,10 @@ export function createLogsRouter(logging: LoggingService) {
102
109
  // the same normalisation applied in `subscribe` below.
103
110
  tags: e.tags
104
111
  ? Object.fromEntries(
105
- Object.entries(e.tags).map(([k, v]) => [k, v === undefined ? undefined : String(v)]),
112
+ Object.entries(e.tags).map(([k, v]) => [
113
+ k,
114
+ v === undefined ? undefined : String(v),
115
+ ]),
106
116
  )
107
117
  : undefined,
108
118
  ...(e.meta !== undefined ? { meta: e.meta } : {}),
@@ -117,12 +127,14 @@ export function createLogsRouter(logging: LoggingService) {
117
127
  * are reading.
118
128
  */
119
129
  clear: adminProcedure
120
- .input(z.object({
121
- level: LogLevelSchema.optional(),
122
- since: z.number().optional(),
123
- until: z.number().optional(),
124
- tags: LogTagsSchema.optional(),
125
- }))
130
+ .input(
131
+ z.object({
132
+ level: LogLevelSchema.optional(),
133
+ since: z.number().optional(),
134
+ until: z.number().optional(),
135
+ tags: LogTagsSchema.optional(),
136
+ }),
137
+ )
126
138
  .output(z.object({ removed: z.number().int().nonnegative() }))
127
139
  .mutation(({ input }) => {
128
140
  const filter: Record<string, unknown> = {}
@@ -135,10 +147,12 @@ export function createLogsRouter(logging: LoggingService) {
135
147
  }),
136
148
 
137
149
  subscribe: protectedProcedure
138
- .input(z.object({
139
- level: LogLevelSchema.optional(),
140
- tags: LogTagsSchema.optional(),
141
- }))
150
+ .input(
151
+ z.object({
152
+ level: LogLevelSchema.optional(),
153
+ tags: LogTagsSchema.optional(),
154
+ }),
155
+ )
142
156
  .subscription(({ input }) => {
143
157
  return iterableSubscription<unknown>((push) => {
144
158
  return logging.subscribe(
@@ -146,12 +160,27 @@ export function createLogsRouter(logging: LoggingService) {
146
160
  level: input.level,
147
161
  tags: input.tags as Record<string, string> | undefined,
148
162
  },
149
- (entry: { timestamp: Date | string | number; level: string; message: string; scope?: string; tags?: Record<string, string | number | undefined>; meta?: Record<string, unknown> }) => {
163
+ (entry: {
164
+ timestamp: Date | string | number
165
+ level: string
166
+ message: string
167
+ scope?: string
168
+ tags?: Record<string, string | number | undefined>
169
+ meta?: Record<string, unknown>
170
+ }) => {
150
171
  const stringifiedTags = entry.tags
151
- ? Object.fromEntries(Object.entries(entry.tags).map(([k, v]) => [k, v === undefined ? undefined : String(v)]))
172
+ ? Object.fromEntries(
173
+ Object.entries(entry.tags).map(([k, v]) => [
174
+ k,
175
+ v === undefined ? undefined : String(v),
176
+ ]),
177
+ )
152
178
  : undefined
153
179
  push({
154
- timestamp: entry.timestamp instanceof Date ? entry.timestamp.toISOString() : String(entry.timestamp),
180
+ timestamp:
181
+ entry.timestamp instanceof Date
182
+ ? entry.timestamp.toISOString()
183
+ : String(entry.timestamp),
155
184
  level: entry.level,
156
185
  message: entry.message,
157
186
  scope: entry.scope,
@@ -58,7 +58,8 @@ export function createNotificationsRouter(ns: NotificationService | null) {
58
58
  if (!ns) return { success: false, error: 'Notification service unavailable' }
59
59
  const output = ns.getOutput(input.outputId)
60
60
  if (!output) return { success: false, error: `Output "${input.outputId}" not found` }
61
- if (!output.sendTest) return { success: false, error: 'Output does not support test notifications' }
61
+ if (!output.sendTest)
62
+ return { success: false, error: 'Output does not support test notifications' }
62
63
  return output.sendTest()
63
64
  }),
64
65
  })
@@ -27,9 +27,7 @@ export function createReplRouter(repl: ReplEngineService) {
27
27
  execute: adminProcedure
28
28
  .input(z.object({ code: z.string().max(10000), scope: ReplScopeSchema }))
29
29
  .output(ReplResultSchema)
30
- .mutation(({ input }) =>
31
- repl.execute(input.code, { scope: input.scope, variables: {} }),
32
- ),
30
+ .mutation(({ input }) => repl.execute(input.code, { scope: input.scope, variables: {} })),
33
31
 
34
32
  completions: adminProcedure
35
33
  .input(z.object({ partial: z.string(), scope: ReplScopeSchema }))
@@ -27,17 +27,21 @@ const SetValueSchema = z.object({
27
27
  value: z.unknown(),
28
28
  })
29
29
 
30
- const QueryFilterSchema = z.object({
31
- where: z.record(z.string(), z.unknown()).optional(),
32
- whereIn: z.record(z.string(), z.array(z.unknown())).optional(),
33
- whereBetween: z.record(z.string(), z.tuple([z.unknown(), z.unknown()])).optional(),
34
- orderBy: z.object({
35
- field: z.string(),
36
- direction: z.enum(['asc', 'desc']),
37
- }).optional(),
38
- limit: z.number().optional(),
39
- offset: z.number().optional(),
40
- }).optional()
30
+ const QueryFilterSchema = z
31
+ .object({
32
+ where: z.record(z.string(), z.unknown()).optional(),
33
+ whereIn: z.record(z.string(), z.array(z.unknown())).optional(),
34
+ whereBetween: z.record(z.string(), z.tuple([z.unknown(), z.unknown()])).optional(),
35
+ orderBy: z
36
+ .object({
37
+ field: z.string(),
38
+ direction: z.enum(['asc', 'desc']),
39
+ })
40
+ .optional(),
41
+ limit: z.number().optional(),
42
+ offset: z.number().optional(),
43
+ })
44
+ .optional()
41
45
 
42
46
  const QueryInputSchema = z.object({
43
47
  collection: z.string(),
@@ -71,72 +75,66 @@ const IsEmptyInputSchema = z.object({
71
75
  // Router factory
72
76
  // ---------------------------------------------------------------------------
73
77
 
74
- export function createSettingsBackendRouter(
75
- getBackend: () => ISettingsBackend | null,
76
- ) {
78
+ export function createSettingsBackendRouter(getBackend: () => ISettingsBackend | null) {
77
79
  const requireBackend = (): ISettingsBackend => {
78
80
  const backend = getBackend()
79
81
  if (!backend) {
80
- throw new Error('Settings backend not available — settings-store addon may not be initialized yet')
82
+ throw new Error(
83
+ 'Settings backend not available — settings-store addon may not be initialized yet',
84
+ )
81
85
  }
82
86
  return backend
83
87
  }
84
88
 
85
89
  return trpcRouter({
86
- get: protectedProcedure
87
- .input(CollectionKeySchema)
88
- .query(async ({ input }) => {
89
- const result = await requireBackend().get(input)
90
- return { value: result }
91
- }),
92
-
93
- set: protectedProcedure
94
- .input(SetValueSchema)
95
- .mutation(async ({ input }) => {
96
- await requireBackend().set({ collection: input.collection, key: input.key, value: input.value })
97
- return { success: true as const }
98
- }),
99
-
100
- query: protectedProcedure
101
- .input(QueryInputSchema)
102
- .query(async ({ input }) => {
103
- const records = await requireBackend().query({ collection: input.collection, filter: input.filter ?? undefined })
104
- return { records: records.map(r => ({ id: r.id, data: r.data })) }
105
- }),
106
-
107
- insert: protectedProcedure
108
- .input(InsertInputSchema)
109
- .mutation(async ({ input }) => {
110
- await requireBackend().insert(input)
111
- return { success: true as const }
112
- }),
113
-
114
- update: protectedProcedure
115
- .input(UpdateInputSchema)
116
- .mutation(async ({ input }) => {
117
- await requireBackend().update(input)
118
- return { success: true as const }
119
- }),
120
-
121
- delete: protectedProcedure
122
- .input(CollectionKeySchema)
123
- .mutation(async ({ input }) => {
124
- await requireBackend().delete(input)
125
- return { success: true as const }
126
- }),
127
-
128
- count: protectedProcedure
129
- .input(CountInputSchema)
130
- .query(async ({ input }) => {
131
- const result = await requireBackend().count({ collection: input.collection, filter: input.filter ?? undefined })
132
- return { count: result }
133
- }),
134
-
135
- isEmpty: protectedProcedure
136
- .input(IsEmptyInputSchema)
137
- .query(async ({ input }) => {
138
- const result = await requireBackend().isEmpty(input)
139
- return { empty: result }
140
- }),
90
+ get: protectedProcedure.input(CollectionKeySchema).query(async ({ input }) => {
91
+ const result = await requireBackend().get(input)
92
+ return { value: result }
93
+ }),
94
+
95
+ set: protectedProcedure.input(SetValueSchema).mutation(async ({ input }) => {
96
+ await requireBackend().set({
97
+ collection: input.collection,
98
+ key: input.key,
99
+ value: input.value,
100
+ })
101
+ return { success: true as const }
102
+ }),
103
+
104
+ query: protectedProcedure.input(QueryInputSchema).query(async ({ input }) => {
105
+ const records = await requireBackend().query({
106
+ collection: input.collection,
107
+ filter: input.filter ?? undefined,
108
+ })
109
+ return { records: records.map((r) => ({ id: r.id, data: r.data })) }
110
+ }),
111
+
112
+ insert: protectedProcedure.input(InsertInputSchema).mutation(async ({ input }) => {
113
+ await requireBackend().insert(input)
114
+ return { success: true as const }
115
+ }),
116
+
117
+ update: protectedProcedure.input(UpdateInputSchema).mutation(async ({ input }) => {
118
+ await requireBackend().update(input)
119
+ return { success: true as const }
120
+ }),
121
+
122
+ delete: protectedProcedure.input(CollectionKeySchema).mutation(async ({ input }) => {
123
+ await requireBackend().delete(input)
124
+ return { success: true as const }
125
+ }),
126
+
127
+ count: protectedProcedure.input(CountInputSchema).query(async ({ input }) => {
128
+ const result = await requireBackend().count({
129
+ collection: input.collection,
130
+ filter: input.filter ?? undefined,
131
+ })
132
+ return { count: result }
133
+ }),
134
+
135
+ isEmpty: protectedProcedure.input(IsEmptyInputSchema).query(async ({ input }) => {
136
+ const result = await requireBackend().isEmpty(input)
137
+ return { empty: result }
138
+ }),
141
139
  })
142
140
  }
@@ -14,7 +14,13 @@ import { trpcRouter, protectedProcedure, iterableSubscription } from '../trpc/tr
14
14
  type SerializedEvent = {
15
15
  id: string
16
16
  timestamp: string
17
- source: { type: string; id: string | number; nodeId?: string; addonId?: string; deviceId?: number }
17
+ source: {
18
+ type: string
19
+ id: string | number
20
+ nodeId?: string
21
+ addonId?: string
22
+ deviceId?: number
23
+ }
18
24
  category: string
19
25
  data: unknown
20
26
  }
@@ -42,10 +48,12 @@ function serialize(e: SystemEvent): SerializedEvent {
42
48
  */
43
49
  const ScopeFieldsSchema = z.object({
44
50
  /** Legacy source filter (exact type+id match). */
45
- source: z.object({
46
- type: z.string(),
47
- id: z.union([z.string(), z.number()]),
48
- }).optional(),
51
+ source: z
52
+ .object({
53
+ type: z.string(),
54
+ id: z.union([z.string(), z.number()]),
55
+ })
56
+ .optional(),
49
57
  /** Agent/node filter (prefix match: 'hub' matches 'hub/pipeline'). */
50
58
  agentId: z.string().optional(),
51
59
  /** Addon filter. Matches source.addonId or source.id when type='addon'. */
@@ -84,33 +92,34 @@ const SubscribeInputSchema = ScopeFieldsSchema.extend({
84
92
 
85
93
  export function createSystemEventsRouter(eb: EventBusService) {
86
94
  return trpcRouter({
87
- getRecent: protectedProcedure
88
- .input(GetRecentInputSchema)
89
- .query(({ input }) => {
90
- return eb.getRecent({
91
- ...(input.source ? { source: input.source } : {}),
92
- ...(input.agentId ? { agentId: input.agentId } : {}),
93
- ...(input.addonId ? { addonId: input.addonId } : {}),
94
- ...(input.deviceId !== undefined ? { deviceId: input.deviceId } : {}),
95
- ...(input.category ? { category: input.category } : {}),
96
- }, input.limit).map(serialize)
97
- }),
95
+ getRecent: protectedProcedure.input(GetRecentInputSchema).query(({ input }) => {
96
+ return eb
97
+ .getRecent(
98
+ {
99
+ ...(input.source ? { source: input.source } : {}),
100
+ ...(input.agentId ? { agentId: input.agentId } : {}),
101
+ ...(input.addonId ? { addonId: input.addonId } : {}),
102
+ ...(input.deviceId !== undefined ? { deviceId: input.deviceId } : {}),
103
+ ...(input.category ? { category: input.category } : {}),
104
+ },
105
+ input.limit,
106
+ )
107
+ .map(serialize)
108
+ }),
98
109
 
99
- subscribe: protectedProcedure
100
- .input(SubscribeInputSchema)
101
- .subscription(({ input }) => {
102
- return iterableSubscription<unknown>((push) => {
103
- return eb.subscribe(
104
- {
105
- ...(input.source ? { source: input.source } : {}),
106
- ...(input.agentId ? { agentId: input.agentId } : {}),
107
- ...(input.addonId ? { addonId: input.addonId } : {}),
108
- ...(input.deviceId !== undefined ? { deviceId: input.deviceId } : {}),
109
- ...(input.category ? { category: input.category } : {}),
110
- },
111
- (event: SystemEvent) => push(serialize(event)),
112
- )
113
- })
114
- }),
110
+ subscribe: protectedProcedure.input(SubscribeInputSchema).subscription(({ input }) => {
111
+ return iterableSubscription<unknown>((push) => {
112
+ return eb.subscribe(
113
+ {
114
+ ...(input.source ? { source: input.source } : {}),
115
+ ...(input.agentId ? { agentId: input.agentId } : {}),
116
+ ...(input.addonId ? { addonId: input.addonId } : {}),
117
+ ...(input.deviceId !== undefined ? { deviceId: input.deviceId } : {}),
118
+ ...(input.category ? { category: input.category } : {}),
119
+ },
120
+ (event: SystemEvent) => push(serialize(event)),
121
+ )
122
+ })
123
+ }),
115
124
  })
116
125
  }
@@ -14,12 +14,7 @@
14
14
  * and monitors that talk directly to an agent see identical payloads.
15
15
  */
16
16
  import type { FastifyInstance, FastifyReply } from 'fastify'
17
- import type {
18
- AgentHealth,
19
- AgentHealthError,
20
- ClusterHealth,
21
- HubHealth,
22
- } from '@camstack/types'
17
+ import type { AgentHealth, AgentHealthError, ClusterHealth, HubHealth } from '@camstack/types'
23
18
  import type { MoleculerService } from '../../core/moleculer/moleculer.service'
24
19
  import type { AgentRegistryService } from '../../core/agent/agent-registry.service'
25
20
 
@@ -42,7 +37,10 @@ function nowIso(): string {
42
37
  return new Date().toISOString()
43
38
  }
44
39
 
45
- async function buildHubHealth(deps: HealthRoutesDeps, proc: ProcessLike = process): Promise<HubHealth> {
40
+ async function buildHubHealth(
41
+ deps: HealthRoutesDeps,
42
+ proc: ProcessLike = process,
43
+ ): Promise<HubHealth> {
46
44
  const nodes = await deps.agentRegistry.listNodes()
47
45
  const remote = nodes.filter((n) => !n.isHub)
48
46
  const online = remote.filter((n) => n.isOnline !== false).length
@@ -89,9 +87,7 @@ export function registerHealthRoutes(fastify: FastifyInstance, deps: HealthRoute
89
87
  fastify.get('/health/agents', async (): Promise<{ readonly agents: readonly string[] }> => {
90
88
  const nodes = await deps.agentRegistry.listNodes()
91
89
  return {
92
- agents: nodes
93
- .filter((n) => !n.isHub && n.isOnline !== false)
94
- .map((n) => n.info.id),
90
+ agents: nodes.filter((n) => !n.isHub && n.isOnline !== false).map((n) => n.info.id),
95
91
  }
96
92
  })
97
93
 
@@ -114,9 +110,7 @@ export function registerHealthRoutes(fastify: FastifyInstance, deps: HealthRoute
114
110
  const hub = await buildHubHealth(deps)
115
111
  const nodes = await deps.agentRegistry.listNodes()
116
112
  const remote = nodes.filter((n) => !n.isHub && n.isOnline !== false)
117
- const agents = await Promise.all(
118
- remote.map((n) => fetchAgentHealth(deps, n.info.id)),
119
- )
113
+ const agents = await Promise.all(remote.map((n) => fetchAgentHealth(deps, n.info.id)))
120
114
  const ok = hub.ok && agents.every((a) => a.ok)
121
115
  return { ok, hub, agents, checkedAt: nowIso() }
122
116
  })