@camstack/server 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/package.json +11 -9
  2. package/src/__tests__/addon-install-e2e.test.ts +0 -1
  3. package/src/__tests__/addon-pages-e2e.test.ts +40 -18
  4. package/src/__tests__/addon-settings-router.spec.ts +6 -1
  5. package/src/__tests__/addon-upload.spec.ts +91 -29
  6. package/src/__tests__/agent-registry.spec.ts +26 -9
  7. package/src/__tests__/agent-status-page.spec.ts +1 -3
  8. package/src/__tests__/auth-session-cookie.test.ts +28 -1
  9. package/src/__tests__/bulk-update-coordinator.spec.ts +48 -31
  10. package/src/__tests__/cap-ownership-authority.spec.ts +39 -8
  11. package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +206 -0
  12. package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +17 -3
  13. package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +57 -11
  14. package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +292 -0
  15. package/src/__tests__/cap-providers-bulk-update.spec.ts +27 -7
  16. package/src/__tests__/cap-route-adapter.spec.ts +28 -15
  17. package/src/__tests__/cap-routers/_meta.spec.ts +6 -7
  18. package/src/__tests__/cap-routers/addon-settings.router.spec.ts +19 -10
  19. package/src/__tests__/cap-routers/broker-routing.router.spec.ts +177 -0
  20. package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +3 -1
  21. package/src/__tests__/cap-routers/capabilities-node.spec.ts +18 -5
  22. package/src/__tests__/cap-routers/device-link-overlay.spec.ts +137 -0
  23. package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +72 -20
  24. package/src/__tests__/cap-routers/harness.ts +11 -7
  25. package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +17 -3
  26. package/src/__tests__/cap-routers/null-provider-guard.spec.ts +5 -7
  27. package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +35 -11
  28. package/src/__tests__/cap-routers/settings-store.router.spec.ts +59 -15
  29. package/src/__tests__/capability-e2e.test.ts +9 -11
  30. package/src/__tests__/cli-e2e.test.ts +80 -59
  31. package/src/__tests__/core-cap-bridge.spec.ts +3 -1
  32. package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +12 -2
  33. package/src/__tests__/device-settings-contribution-dispatch.spec.ts +61 -30
  34. package/src/__tests__/embedded-deps-e2e.test.ts +35 -19
  35. package/src/__tests__/event-bus-proxy-router.spec.ts +3 -0
  36. package/src/__tests__/framework-allowlist.spec.ts +5 -4
  37. package/src/__tests__/https-e2e.test.ts +12 -6
  38. package/src/__tests__/lifecycle-e2e.test.ts +60 -11
  39. package/src/__tests__/live-events-subscription.spec.ts +17 -18
  40. package/src/__tests__/moleculer/uds-readiness.spec.ts +11 -4
  41. package/src/__tests__/moleculer/uds-topology.spec.ts +39 -11
  42. package/src/__tests__/moleculer/uds-unowned-call.spec.ts +265 -5
  43. package/src/__tests__/moleculer-register-node-idempotency.spec.ts +16 -7
  44. package/src/__tests__/native-cap-route.spec.ts +42 -19
  45. package/src/__tests__/oauth2-account-linking.spec.ts +63 -17
  46. package/src/__tests__/singleton-contention.test.ts +23 -11
  47. package/src/__tests__/streaming-diagnostic.test.ts +156 -53
  48. package/src/__tests__/streaming-scale.test.ts +69 -35
  49. package/src/__tests__/uds-addon-call-wiring.spec.ts +6 -1
  50. package/src/agent-status-page.ts +4 -3
  51. package/src/api/__tests__/addons-custom.spec.ts +22 -8
  52. package/src/api/__tests__/capabilities.router.test.ts +18 -9
  53. package/src/api/addon-upload.ts +46 -15
  54. package/src/api/addons-custom.router.ts +7 -6
  55. package/src/api/auth-whoami.ts +3 -1
  56. package/src/api/bridge-addons.router.ts +3 -1
  57. package/src/api/capabilities.router.ts +117 -78
  58. package/src/api/core/__tests__/auth-router-totp.spec.ts +57 -16
  59. package/src/api/core/__tests__/integration-markers.spec.ts +10 -0
  60. package/src/api/core/addon-settings.router.ts +4 -1
  61. package/src/api/core/agents.router.ts +52 -53
  62. package/src/api/core/auth.router.ts +55 -36
  63. package/src/api/core/bulk-update-coordinator.ts +25 -22
  64. package/src/api/core/cap-providers.ts +459 -166
  65. package/src/api/core/capabilities.router.ts +30 -23
  66. package/src/api/core/hwaccel.router.ts +37 -10
  67. package/src/api/core/live-events.router.ts +16 -9
  68. package/src/api/core/logs.router.ts +58 -25
  69. package/src/api/core/notifications.router.ts +2 -1
  70. package/src/api/core/repl.router.ts +1 -3
  71. package/src/api/core/settings-backend.router.ts +68 -70
  72. package/src/api/core/system-events.router.ts +41 -32
  73. package/src/api/health/health.routes.ts +7 -13
  74. package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +12 -2
  75. package/src/api/oauth2/consent-page.ts +4 -3
  76. package/src/api/oauth2/oauth2-routes.ts +41 -12
  77. package/src/api/trpc/__tests__/client-ip.spec.ts +27 -1
  78. package/src/api/trpc/__tests__/scope-access-device.spec.ts +68 -23
  79. package/src/api/trpc/__tests__/scope-access.spec.ts +8 -13
  80. package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +136 -0
  81. package/src/api/trpc/cap-mount-helpers.ts +64 -44
  82. package/src/api/trpc/cap-route-error-formatter.ts +17 -9
  83. package/src/api/trpc/client-ip.ts +17 -0
  84. package/src/api/trpc/core-cap-bridge.ts +3 -1
  85. package/src/api/trpc/generated-cap-mounts.ts +801 -286
  86. package/src/api/trpc/generated-cap-routers.ts +5723 -719
  87. package/src/api/trpc/scope-access.ts +7 -7
  88. package/src/api/trpc/trpc.context.ts +7 -4
  89. package/src/api/trpc/trpc.middleware.ts +4 -2
  90. package/src/api/trpc/trpc.router.ts +117 -48
  91. package/src/auth/session-cookie.ts +10 -0
  92. package/src/boot/__tests__/integration-id-backfill.spec.ts +131 -0
  93. package/src/boot/boot-config.ts +103 -122
  94. package/src/boot/integration-id-backfill.ts +109 -0
  95. package/src/boot/post-boot.service.ts +5 -3
  96. package/src/core/addon/__tests__/addon-registry-capability.test.ts +12 -3
  97. package/src/core/addon/__tests__/addon-row-manifest.spec.ts +62 -0
  98. package/src/core/addon/addon-call-gateway.ts +20 -6
  99. package/src/core/addon/addon-package.service.ts +183 -89
  100. package/src/core/addon/addon-registry.service.ts +1212 -1267
  101. package/src/core/addon/addon-row-manifest.ts +29 -0
  102. package/src/core/addon/addon-search.service.ts +2 -1
  103. package/src/core/addon/addon-settings-provider.ts +27 -7
  104. package/src/core/addon-bridge/addon-bridge.service.ts +11 -6
  105. package/src/core/addon-pages/addon-pages.service.ts +3 -1
  106. package/src/core/addon-widgets/addon-widgets.service.ts +5 -2
  107. package/src/core/agent/agent-registry.service.ts +60 -38
  108. package/src/core/auth/auth.service.spec.ts +6 -8
  109. package/src/core/config/config.service.spec.ts +1 -1
  110. package/src/core/events/event-bus.service.spec.ts +44 -21
  111. package/src/core/events/event-bus.service.ts +5 -1
  112. package/src/core/feature/feature.service.spec.ts +4 -1
  113. package/src/core/lifecycle/lifecycle-state-machine.spec.ts +8 -10
  114. package/src/core/logging/logging.service.spec.ts +61 -21
  115. package/src/core/logging/logging.service.ts +19 -5
  116. package/src/core/moleculer/cap-call-fn.spec.ts +17 -10
  117. package/src/core/moleculer/cap-call-fn.ts +5 -1
  118. package/src/core/moleculer/cap-route-authority.ts +18 -6
  119. package/src/core/moleculer/moleculer.service.ts +145 -29
  120. package/src/core/network/network-quality.service.spec.ts +7 -1
  121. package/src/core/notification/notification-wrapper.service.ts +1 -3
  122. package/src/core/notification/toast-wrapper.service.ts +1 -5
  123. package/src/core/repl/repl-engine.service.spec.ts +66 -39
  124. package/src/core/repl/repl-engine.service.ts +11 -12
  125. package/src/core/storage/storage-location-manager.spec.ts +12 -3
  126. package/src/core/streaming/stream-probe.service.ts +22 -13
  127. package/src/core/topology/topology-emitter.service.ts +5 -1
  128. package/src/launcher.ts +14 -9
  129. package/src/main.ts +658 -495
  130. package/src/manual-boot.ts +133 -154
  131. package/tsconfig.json +20 -8
  132. package/src/core/storage/settings-store.spec.ts +0 -213
  133. package/src/core/storage/settings-store.ts +0 -2
  134. package/src/core/storage/sql-schema.spec.ts +0 -140
  135. package/src/core/storage/sql-schema.ts +0 -3
@@ -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'])
@@ -18,6 +23,10 @@ const LogTagsSchema = z.object({
18
23
  nodeId: z.string().optional(),
19
24
  /** Numeric progressive id (or its string form for legacy callers). */
20
25
  deviceId: z.union([z.string(), z.number()]).optional(),
26
+ /** Parent container id — set on every accessory child's logs (and on the
27
+ * container's own logs). Filtering by it returns the whole container subtree
28
+ * (container + all children) in one query. */
29
+ containerDeviceId: z.union([z.string(), z.number()]).optional(),
21
30
  deviceName: z.string().optional(),
22
31
  integrationId: z.string().optional(),
23
32
  addonId: z.string().optional(),
@@ -55,13 +64,15 @@ const LogEntryArraySchema = z.array(LogEntrySchema)
55
64
  export function createLogsRouter(logging: LoggingService) {
56
65
  return trpcRouter({
57
66
  query: adminProcedure
58
- .input(z.object({
59
- level: LogLevelSchema.optional(),
60
- since: z.number().optional(),
61
- until: z.number().optional(),
62
- limit: z.number().optional(),
63
- tags: LogTagsSchema.optional(),
64
- }))
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
+ )
65
76
  .output(LogEntryArraySchema)
66
77
  .query(({ input, ctx }) => {
67
78
  const filter: Record<string, unknown> = { limit: input.limit ?? 100 }
@@ -75,10 +86,10 @@ export function createLogsRouter(logging: LoggingService) {
75
86
  const ap = ctx.user.permissions?.allowedProviders
76
87
  if (ap && ap !== '*') {
77
88
  const allowed = ap as string[]
78
- return entries.filter(e => {
89
+ return entries.filter((e) => {
79
90
  const addonId = e.tags?.addonId
80
91
  if (typeof addonId !== 'string') return false
81
- return allowed.some(p => {
92
+ return allowed.some((p) => {
82
93
  const bare = p.startsWith('addon:') ? p.slice('addon:'.length) : p
83
94
  return addonId === bare || addonId.startsWith(bare)
84
95
  })
@@ -87,7 +98,7 @@ export function createLogsRouter(logging: LoggingService) {
87
98
  }
88
99
  return entries
89
100
  })()
90
- return filtered.map(e => ({
101
+ return filtered.map((e) => ({
91
102
  timestamp: e.timestamp instanceof Date ? e.timestamp.toISOString() : String(e.timestamp),
92
103
  level: e.level,
93
104
  ...(e.scope !== undefined ? { scope: e.scope } : {}),
@@ -98,7 +109,10 @@ export function createLogsRouter(logging: LoggingService) {
98
109
  // the same normalisation applied in `subscribe` below.
99
110
  tags: e.tags
100
111
  ? Object.fromEntries(
101
- 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
+ ]),
102
116
  )
103
117
  : undefined,
104
118
  ...(e.meta !== undefined ? { meta: e.meta } : {}),
@@ -113,12 +127,14 @@ export function createLogsRouter(logging: LoggingService) {
113
127
  * are reading.
114
128
  */
115
129
  clear: adminProcedure
116
- .input(z.object({
117
- level: LogLevelSchema.optional(),
118
- since: z.number().optional(),
119
- until: z.number().optional(),
120
- tags: LogTagsSchema.optional(),
121
- }))
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
+ )
122
138
  .output(z.object({ removed: z.number().int().nonnegative() }))
123
139
  .mutation(({ input }) => {
124
140
  const filter: Record<string, unknown> = {}
@@ -131,10 +147,12 @@ export function createLogsRouter(logging: LoggingService) {
131
147
  }),
132
148
 
133
149
  subscribe: protectedProcedure
134
- .input(z.object({
135
- level: LogLevelSchema.optional(),
136
- tags: LogTagsSchema.optional(),
137
- }))
150
+ .input(
151
+ z.object({
152
+ level: LogLevelSchema.optional(),
153
+ tags: LogTagsSchema.optional(),
154
+ }),
155
+ )
138
156
  .subscription(({ input }) => {
139
157
  return iterableSubscription<unknown>((push) => {
140
158
  return logging.subscribe(
@@ -142,12 +160,27 @@ export function createLogsRouter(logging: LoggingService) {
142
160
  level: input.level,
143
161
  tags: input.tags as Record<string, string> | undefined,
144
162
  },
145
- (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
+ }) => {
146
171
  const stringifiedTags = entry.tags
147
- ? 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
+ )
148
178
  : undefined
149
179
  push({
150
- 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),
151
184
  level: entry.level,
152
185
  message: entry.message,
153
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
  }