@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
|
@@ -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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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 {
|
|
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>([
|
|
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(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
76
|
+
return (
|
|
77
|
+
def !== null && typeof def === 'object' && (def as { procedure?: unknown }).procedure === true
|
|
78
|
+
)
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
interface DiscoveredProcedure {
|