@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.
- package/package.json +11 -9
- package/src/__tests__/addon-install-e2e.test.ts +0 -1
- package/src/__tests__/addon-pages-e2e.test.ts +40 -18
- package/src/__tests__/addon-settings-router.spec.ts +6 -1
- package/src/__tests__/addon-upload.spec.ts +91 -29
- package/src/__tests__/agent-registry.spec.ts +26 -9
- package/src/__tests__/agent-status-page.spec.ts +1 -3
- package/src/__tests__/auth-session-cookie.test.ts +28 -1
- package/src/__tests__/bulk-update-coordinator.spec.ts +48 -31
- package/src/__tests__/cap-ownership-authority.spec.ts +39 -8
- package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +206 -0
- package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +17 -3
- package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +57 -11
- package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +292 -0
- package/src/__tests__/cap-providers-bulk-update.spec.ts +27 -7
- package/src/__tests__/cap-route-adapter.spec.ts +28 -15
- package/src/__tests__/cap-routers/_meta.spec.ts +6 -7
- package/src/__tests__/cap-routers/addon-settings.router.spec.ts +19 -10
- package/src/__tests__/cap-routers/broker-routing.router.spec.ts +177 -0
- package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +3 -1
- package/src/__tests__/cap-routers/capabilities-node.spec.ts +18 -5
- package/src/__tests__/cap-routers/device-link-overlay.spec.ts +137 -0
- package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +72 -20
- package/src/__tests__/cap-routers/harness.ts +11 -7
- package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +17 -3
- package/src/__tests__/cap-routers/null-provider-guard.spec.ts +5 -7
- package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +35 -11
- package/src/__tests__/cap-routers/settings-store.router.spec.ts +59 -15
- package/src/__tests__/capability-e2e.test.ts +9 -11
- package/src/__tests__/cli-e2e.test.ts +80 -59
- package/src/__tests__/core-cap-bridge.spec.ts +3 -1
- package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +12 -2
- package/src/__tests__/device-settings-contribution-dispatch.spec.ts +61 -30
- package/src/__tests__/embedded-deps-e2e.test.ts +35 -19
- package/src/__tests__/event-bus-proxy-router.spec.ts +3 -0
- package/src/__tests__/framework-allowlist.spec.ts +5 -4
- package/src/__tests__/https-e2e.test.ts +12 -6
- package/src/__tests__/lifecycle-e2e.test.ts +60 -11
- package/src/__tests__/live-events-subscription.spec.ts +17 -18
- package/src/__tests__/moleculer/uds-readiness.spec.ts +11 -4
- package/src/__tests__/moleculer/uds-topology.spec.ts +39 -11
- package/src/__tests__/moleculer/uds-unowned-call.spec.ts +265 -5
- package/src/__tests__/moleculer-register-node-idempotency.spec.ts +16 -7
- package/src/__tests__/native-cap-route.spec.ts +42 -19
- package/src/__tests__/oauth2-account-linking.spec.ts +63 -17
- package/src/__tests__/singleton-contention.test.ts +23 -11
- package/src/__tests__/streaming-diagnostic.test.ts +156 -53
- package/src/__tests__/streaming-scale.test.ts +69 -35
- package/src/__tests__/uds-addon-call-wiring.spec.ts +6 -1
- package/src/agent-status-page.ts +4 -3
- package/src/api/__tests__/addons-custom.spec.ts +22 -8
- package/src/api/__tests__/capabilities.router.test.ts +18 -9
- package/src/api/addon-upload.ts +46 -15
- package/src/api/addons-custom.router.ts +7 -6
- package/src/api/auth-whoami.ts +3 -1
- package/src/api/bridge-addons.router.ts +3 -1
- package/src/api/capabilities.router.ts +117 -78
- package/src/api/core/__tests__/auth-router-totp.spec.ts +57 -16
- package/src/api/core/__tests__/integration-markers.spec.ts +10 -0
- package/src/api/core/addon-settings.router.ts +4 -1
- package/src/api/core/agents.router.ts +52 -53
- package/src/api/core/auth.router.ts +55 -36
- package/src/api/core/bulk-update-coordinator.ts +25 -22
- package/src/api/core/cap-providers.ts +459 -166
- package/src/api/core/capabilities.router.ts +30 -23
- package/src/api/core/hwaccel.router.ts +37 -10
- package/src/api/core/live-events.router.ts +16 -9
- package/src/api/core/logs.router.ts +58 -25
- package/src/api/core/notifications.router.ts +2 -1
- package/src/api/core/repl.router.ts +1 -3
- package/src/api/core/settings-backend.router.ts +68 -70
- package/src/api/core/system-events.router.ts +41 -32
- package/src/api/health/health.routes.ts +7 -13
- package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +12 -2
- package/src/api/oauth2/consent-page.ts +4 -3
- package/src/api/oauth2/oauth2-routes.ts +41 -12
- package/src/api/trpc/__tests__/client-ip.spec.ts +27 -1
- package/src/api/trpc/__tests__/scope-access-device.spec.ts +68 -23
- package/src/api/trpc/__tests__/scope-access.spec.ts +8 -13
- package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +136 -0
- package/src/api/trpc/cap-mount-helpers.ts +64 -44
- package/src/api/trpc/cap-route-error-formatter.ts +17 -9
- package/src/api/trpc/client-ip.ts +17 -0
- package/src/api/trpc/core-cap-bridge.ts +3 -1
- package/src/api/trpc/generated-cap-mounts.ts +801 -286
- package/src/api/trpc/generated-cap-routers.ts +5723 -719
- package/src/api/trpc/scope-access.ts +7 -7
- package/src/api/trpc/trpc.context.ts +7 -4
- package/src/api/trpc/trpc.middleware.ts +4 -2
- package/src/api/trpc/trpc.router.ts +117 -48
- package/src/auth/session-cookie.ts +10 -0
- package/src/boot/__tests__/integration-id-backfill.spec.ts +131 -0
- package/src/boot/boot-config.ts +103 -122
- package/src/boot/integration-id-backfill.ts +109 -0
- package/src/boot/post-boot.service.ts +5 -3
- package/src/core/addon/__tests__/addon-registry-capability.test.ts +12 -3
- package/src/core/addon/__tests__/addon-row-manifest.spec.ts +62 -0
- package/src/core/addon/addon-call-gateway.ts +20 -6
- package/src/core/addon/addon-package.service.ts +183 -89
- package/src/core/addon/addon-registry.service.ts +1212 -1267
- package/src/core/addon/addon-row-manifest.ts +29 -0
- package/src/core/addon/addon-search.service.ts +2 -1
- package/src/core/addon/addon-settings-provider.ts +27 -7
- package/src/core/addon-bridge/addon-bridge.service.ts +11 -6
- package/src/core/addon-pages/addon-pages.service.ts +3 -1
- package/src/core/addon-widgets/addon-widgets.service.ts +5 -2
- package/src/core/agent/agent-registry.service.ts +60 -38
- package/src/core/auth/auth.service.spec.ts +6 -8
- package/src/core/config/config.service.spec.ts +1 -1
- package/src/core/events/event-bus.service.spec.ts +44 -21
- package/src/core/events/event-bus.service.ts +5 -1
- package/src/core/feature/feature.service.spec.ts +4 -1
- package/src/core/lifecycle/lifecycle-state-machine.spec.ts +8 -10
- package/src/core/logging/logging.service.spec.ts +61 -21
- package/src/core/logging/logging.service.ts +19 -5
- package/src/core/moleculer/cap-call-fn.spec.ts +17 -10
- package/src/core/moleculer/cap-call-fn.ts +5 -1
- package/src/core/moleculer/cap-route-authority.ts +18 -6
- package/src/core/moleculer/moleculer.service.ts +145 -29
- package/src/core/network/network-quality.service.spec.ts +7 -1
- package/src/core/notification/notification-wrapper.service.ts +1 -3
- package/src/core/notification/toast-wrapper.service.ts +1 -5
- package/src/core/repl/repl-engine.service.spec.ts +66 -39
- package/src/core/repl/repl-engine.service.ts +11 -12
- package/src/core/storage/storage-location-manager.spec.ts +12 -3
- package/src/core/streaming/stream-probe.service.ts +22 -13
- package/src/core/topology/topology-emitter.service.ts +5 -1
- package/src/launcher.ts +14 -9
- package/src/main.ts +658 -495
- package/src/manual-boot.ts +133 -154
- package/tsconfig.json +20 -8
- package/src/core/storage/settings-store.spec.ts +0 -213
- package/src/core/storage/settings-store.ts +0 -2
- package/src/core/storage/sql-schema.spec.ts +0 -140
- 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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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(
|
|
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(
|
|
71
|
-
|
|
72
|
-
|
|
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(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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',
|
|
22
|
-
'
|
|
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
|
|
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, {
|
|
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(
|
|
57
|
-
|
|
58
|
-
|
|
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 = (
|
|
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(
|
|
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(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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<{
|
|
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 {
|
|
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(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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]) => [
|
|
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(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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(
|
|
135
|
-
|
|
136
|
-
|
|
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: {
|
|
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(
|
|
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:
|
|
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)
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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(
|
|
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
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
query: protectedProcedure
|
|
101
|
-
.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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: {
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
{
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
}
|