@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
@@ -64,38 +64,60 @@ export function requireDeviceScoped<K extends keyof CapabilityProviderMap>(
64
64
  // a function that, on call, looks up the per-device native and
65
65
  // forwards the call. No caching — the lookup is cheap (Map.get) and
66
66
  // re-doing it per call lets devices come/go without stale refs.
67
- const dispatcher = new Proxy({}, {
68
- get(_target, prop: string | symbol) {
69
- if (typeof prop !== 'string') return undefined
70
- return async (input: { deviceId?: number } & Record<string, unknown>) => {
71
- const deviceId = input?.deviceId
72
- if (typeof deviceId !== 'number') {
73
- throw new TRPCError({
74
- code: 'BAD_REQUEST',
75
- message: `${String(capName)}.${prop}: input must carry numeric "deviceId"`,
76
- })
67
+ const dispatcher = new Proxy(
68
+ {},
69
+ {
70
+ get(_target, prop: string | symbol) {
71
+ if (typeof prop !== 'string') return undefined
72
+ return async (input: { deviceId?: number } & Record<string, unknown>) => {
73
+ const deviceId = input?.deviceId
74
+ if (typeof deviceId !== 'number') {
75
+ throw new TRPCError({
76
+ code: 'BAD_REQUEST',
77
+ message: `${String(capName)}.${prop}: input must carry numeric "deviceId"`,
78
+ })
79
+ }
80
+ const native = registry.getNativeProvider<Record<string, (i: unknown) => unknown>>(
81
+ capName,
82
+ deviceId,
83
+ )
84
+ if (!native) {
85
+ throw new TRPCError({
86
+ code: 'PRECONDITION_FAILED',
87
+ message: `Capability "${String(capName)}" not registered for device ${deviceId}`,
88
+ })
89
+ }
90
+ const fn = native[prop]
91
+ if (typeof fn !== 'function') {
92
+ throw new TRPCError({
93
+ code: 'NOT_IMPLEMENTED',
94
+ message: `Capability "${String(capName)}" provider for device ${deviceId} does not implement "${prop}"`,
95
+ })
96
+ }
97
+ const result = await fn.call(native, input)
98
+ // Device-property-wiring overlay (read-time): only `getStatus`, and only
99
+ // when the device has links for this cap (resolveLinkedStatus returns
100
+ // null otherwise → base result untouched). One in-process singleton hop.
101
+ if (prop === 'getStatus') {
102
+ const deviceManager = registry.getSingleton<{
103
+ resolveLinkedStatus?: (i: {
104
+ deviceId: number
105
+ cap: string
106
+ baseStatus: unknown
107
+ }) => Promise<Record<string, unknown> | null>
108
+ }>('device-manager')
109
+ const overlaid = await deviceManager?.resolveLinkedStatus?.({
110
+ deviceId,
111
+ cap: String(capName),
112
+ baseStatus: result,
113
+ })
114
+ if (overlaid != null) return overlaid
115
+ }
116
+ return result
77
117
  }
78
- const native = registry.getNativeProvider<Record<string, (i: unknown) => unknown>>(
79
- capName,
80
- deviceId,
81
- )
82
- if (!native) {
83
- throw new TRPCError({
84
- code: 'PRECONDITION_FAILED',
85
- message: `Capability "${String(capName)}" not registered for device ${deviceId}`,
86
- })
87
- }
88
- const fn = native[prop]
89
- if (typeof fn !== 'function') {
90
- throw new TRPCError({
91
- code: 'NOT_IMPLEMENTED',
92
- message: `Capability "${String(capName)}" provider for device ${deviceId} does not implement "${prop}"`,
93
- })
94
- }
95
- return fn.call(native, input)
96
- }
118
+ },
97
119
  },
98
- })
120
+ )
99
121
  return dispatcher as unknown as CapabilityProviderMap[K]
100
122
  }
101
123
 
@@ -103,16 +125,16 @@ export function requireDeviceScoped<K extends keyof CapabilityProviderMap>(
103
125
 
104
126
  /** A key on T whose value is a function with array / promise-array return. */
105
127
  type ArrayReturningMethodKey<T> = {
106
- [K in keyof T]: T[K] extends (...args: infer _A) => readonly unknown[] | Promise<readonly unknown[]>
128
+ [K in keyof T]: T[K] extends (
129
+ ...args: infer _A
130
+ ) => readonly unknown[] | Promise<readonly unknown[]>
107
131
  ? K
108
132
  : never
109
133
  }[keyof T]
110
134
 
111
135
  /** A key on T whose value is a function returning boolean / promise-boolean. */
112
136
  type BoolReturningMethodKey<T> = {
113
- [K in keyof T]: T[K] extends (...args: infer _A) => boolean | Promise<boolean>
114
- ? K
115
- : never
137
+ [K in keyof T]: T[K] extends (...args: infer _A) => boolean | Promise<boolean> ? K : never
116
138
  }[keyof T]
117
139
 
118
140
  /**
@@ -120,10 +142,7 @@ type BoolReturningMethodKey<T> = {
120
142
  * and concatenates their array results. Useful for contribution-style
121
143
  * caps where each provider adds to a shared pool.
122
144
  */
123
- export function concatCollection<
124
- T extends object,
125
- K extends ArrayReturningMethodKey<T>,
126
- >(
145
+ export function concatCollection<T extends object, K extends ArrayReturningMethodKey<T>>(
127
146
  providers: readonly T[],
128
147
  method: K,
129
148
  ): T[K] extends (...args: infer A) => readonly (infer R)[] | Promise<readonly (infer R)[]>
@@ -147,7 +166,9 @@ export function concatCollection<
147
166
  // matches the declared generic conditional return; TypeScript's
148
167
  // conditional types can't be narrowed inside a function body, so this
149
168
  // boundary assertion is required.
150
- return wrapper as T[K] extends (...args: infer A) => readonly (infer R)[] | Promise<readonly (infer R)[]>
169
+ return wrapper as T[K] extends (
170
+ ...args: infer A
171
+ ) => readonly (infer R)[] | Promise<readonly (infer R)[]>
151
172
  ? (...args: A) => Promise<readonly R[]>
152
173
  : never
153
174
  }
@@ -198,10 +219,7 @@ export function firstSupported<
198
219
  * Convenience for collection caps that want a "logical OR of probes"
199
220
  * (e.g. `supportsDevice` across every snapshot-provider).
200
221
  */
201
- export function anySupports<
202
- T extends object,
203
- K extends BoolReturningMethodKey<T>,
204
- >(
222
+ export function anySupports<T extends object, K extends BoolReturningMethodKey<T>>(
205
223
  providers: readonly T[],
206
224
  probe: K,
207
225
  ): T[K] extends (...args: infer A) => boolean | Promise<boolean>
@@ -214,7 +232,9 @@ export function anySupports<
214
232
  try {
215
233
  const result: unknown = await Reflect.apply(member, p, args)
216
234
  if (result === true) return true
217
- } catch { /* next */ }
235
+ } catch {
236
+ /* next */
237
+ }
218
238
  }
219
239
  return false
220
240
  }
@@ -38,7 +38,12 @@ export interface AugmentedErrorShape extends DefaultErrorShape {
38
38
  // ---------------------------------------------------------------------------
39
39
 
40
40
  /** Known CapRouteError reason values — used as a runtime safety rail. */
41
- const KNOWN_REASONS = new Set<string>(['no-provider', 'node-offline', 'cap-unknown', 'transport-failed'])
41
+ const KNOWN_REASONS = new Set<string>([
42
+ 'no-provider',
43
+ 'node-offline',
44
+ 'cap-unknown',
45
+ 'transport-failed',
46
+ ])
42
47
 
43
48
  /** Narrows a plain string to the `CapRouteError['reason']` union. */
44
49
  function isCapRouteReason(r: string): r is CapRouteError['reason'] {
@@ -101,14 +106,17 @@ function extractCapRouteError(err: unknown): CapRouteError | null {
101
106
  const capName: string = typeof rawCapName === 'string' ? rawCapName : '(unknown)'
102
107
 
103
108
  // Build a minimal object with the same shape — enough for the formatter.
104
- const synthetic = Object.assign(new CapRouteError(capName, undefined, {
105
- reason,
106
- rejected,
107
- ...(typeof nodeId === 'string' ? { nodeId } : {}),
108
- }), {
109
- // Override message from the original if available
110
- message: typeof message === 'string' ? message : '(duck-typed CapRouteError)',
111
- })
109
+ const synthetic = Object.assign(
110
+ new CapRouteError(capName, undefined, {
111
+ reason,
112
+ rejected,
113
+ ...(typeof nodeId === 'string' ? { nodeId } : {}),
114
+ }),
115
+ {
116
+ // Override message from the original if available
117
+ message: typeof message === 'string' ? message : '(duck-typed CapRouteError)',
118
+ },
119
+ )
112
120
  return synthetic
113
121
  }
114
122
  }
@@ -53,6 +53,23 @@ export function extractClientIp(req: ClientRequest | undefined): string | null {
53
53
  return null
54
54
  }
55
55
 
56
+ /**
57
+ * Best-effort read of the originating client's `User-Agent` header. Both
58
+ * request shapes in the union (`FastifyRequest` and the raw WS-transport
59
+ * `IncomingMessage`) expose `.headers['user-agent']`. Returns `null` when
60
+ * absent (mesh-originated calls carry no HTTP request, and some clients
61
+ * omit the header). Used by the `webrtc-session` mount to tag a browser
62
+ * subscriber's broker attribution with the viewer's UA — the SERVER reads
63
+ * it from the request context; any client-supplied value is ignored.
64
+ */
65
+ export function extractUserAgent(req: ClientRequest | undefined): string | null {
66
+ if (!req) return null
67
+ const ua = req.headers['user-agent']
68
+ const value = Array.isArray(ua) ? ua[0] : ua
69
+ if (typeof value === 'string' && value.length > 0) return value
70
+ return null
71
+ }
72
+
56
73
  /**
57
74
  * Strip an IPv4-mapped IPv6 prefix (`::ffff:192.168.1.5` → `192.168.1.5`)
58
75
  * and any zone id (`fe80::1%en0` → `fe80::1`) so the range checks below
@@ -73,7 +73,9 @@ function isProcedureNode(value: unknown): value is ProcedureNode {
73
73
  // stores the procedure function directly, not a wrapper object.
74
74
  if (value === null || (typeof value !== 'object' && typeof value !== 'function')) return false
75
75
  const def: unknown = (value as { _def?: unknown })._def
76
- return def !== null && typeof def === 'object' && (def as { procedure?: unknown }).procedure === true
76
+ return (
77
+ def !== null && typeof def === 'object' && (def as { procedure?: unknown }).procedure === true
78
+ )
77
79
  }
78
80
 
79
81
  interface DiscoveredProcedure {