@camstack/server 1.1.10 → 1.1.12
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.
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.requireSingleton = requireSingleton;
|
|
4
|
-
exports.requireDeviceScoped = requireDeviceScoped;
|
|
5
|
-
exports.concatCollection = concatCollection;
|
|
6
|
-
exports.firstSupported = firstSupported;
|
|
7
|
-
exports.anySupports = anySupports;
|
|
8
2
|
/**
|
|
9
3
|
* Small helpers for wiring capability routers to the `CapabilityRegistry`
|
|
10
4
|
* in `trpc.router.ts`. These functions don't generate code — they just
|
|
@@ -25,6 +19,12 @@ exports.anySupports = anySupports;
|
|
|
25
19
|
* provider responsible for a given `streamId`) are NOT covered here —
|
|
26
20
|
* they have app-specific routing logic that belongs in the mount.
|
|
27
21
|
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.requireSingleton = requireSingleton;
|
|
24
|
+
exports.requireDeviceScoped = requireDeviceScoped;
|
|
25
|
+
exports.concatCollection = concatCollection;
|
|
26
|
+
exports.firstSupported = firstSupported;
|
|
27
|
+
exports.anySupports = anySupports;
|
|
28
28
|
const server_1 = require("@trpc/server");
|
|
29
29
|
/**
|
|
30
30
|
* Fetch the currently active singleton provider for a capability.
|
|
@@ -34,27 +34,6 @@ const server_1 = require("@trpc/server");
|
|
|
34
34
|
function requireSingleton(registry, capName) {
|
|
35
35
|
return registry?.getSingleton(capName) ?? null;
|
|
36
36
|
}
|
|
37
|
-
/**
|
|
38
|
-
* Build a per-device dispatcher that satisfies the singleton-provider
|
|
39
|
-
* shape but resolves the actual implementation lazily via
|
|
40
|
-
* `registry.getNativeProvider(capName, deviceId)` on every method call.
|
|
41
|
-
*
|
|
42
|
-
* Use for device-scoped caps that have NO system-level wrapper (PTZ,
|
|
43
|
-
* reboot, doorbell, brightness, motion-trigger, switch, …) — drivers
|
|
44
|
-
* register per-device native providers via
|
|
45
|
-
* `DeviceContext.registerNativeCap`, and this helper bridges the cap-
|
|
46
|
-
* router's "fetch a singleton then call methods on it" flow into a
|
|
47
|
-
* "resolve native by deviceId per call" flow.
|
|
48
|
-
*
|
|
49
|
-
* Method input MUST carry `deviceId: number`. Methods without that
|
|
50
|
-
* field (auto-injected `getStatus({deviceId})` for caps with a status
|
|
51
|
-
* block, every business method that follows the cap-definition
|
|
52
|
-
* convention) work transparently.
|
|
53
|
-
*
|
|
54
|
-
* Throws PRECONDITION_FAILED with a device-specific message when no
|
|
55
|
-
* native provider exists for the requested deviceId — much friendlier
|
|
56
|
-
* than the singleton fallthrough's "no provider" generic error.
|
|
57
|
-
*/
|
|
58
37
|
function requireDeviceScoped(registry, capName) {
|
|
59
38
|
if (!registry)
|
|
60
39
|
return null;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Server-side glue for the runtime cap-router builder (BIG PLAN 2).
|
|
4
|
+
*
|
|
5
|
+
* `@camstack/system` `buildCapRouters(primitives, services)` owns the
|
|
6
|
+
* per-cap router CONSTRUCTION (procedure shapes + node routing), but it
|
|
7
|
+
* holds no tRPC primitives and no backend services. This module supplies
|
|
8
|
+
* both:
|
|
9
|
+
*
|
|
10
|
+
* - `primitives` — the `t`-half: `trpcRouter`, the auth procedures, and
|
|
11
|
+
* `iterableSubscription`, taken from `trpc.middleware`.
|
|
12
|
+
* - `services` — the provider-resolution half: `getLocalProvider`
|
|
13
|
+
* (mirrors the codegen `generate-cap-mounts.ts` decision per mount
|
|
14
|
+
* kind), the cross-node `remoteProxy`, and the canonical tRPC error
|
|
15
|
+
* raisers (the server owns `TRPCError`).
|
|
16
|
+
*
|
|
17
|
+
* The result is spread into the AppRouter BEFORE the hand-written core
|
|
18
|
+
* routers + the `custom`-mount overrides (snapshot-provider, webrtc-
|
|
19
|
+
* session), so override-by-spread precedence is preserved exactly as it
|
|
20
|
+
* was with the old static `mountAllCaps()` spread.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.createCapRouterPrimitives = createCapRouterPrimitives;
|
|
24
|
+
exports.createCapRouterServices = createCapRouterServices;
|
|
25
|
+
exports.buildRuntimeCapRouters = buildRuntimeCapRouters;
|
|
26
|
+
const system_1 = require("@camstack/system");
|
|
27
|
+
const types_1 = require("@camstack/types");
|
|
28
|
+
const server_1 = require("@trpc/server");
|
|
29
|
+
const cap_mount_helpers_js_1 = require("./cap-mount-helpers.js");
|
|
30
|
+
const trpc_middleware_js_1 = require("./trpc.middleware.js");
|
|
31
|
+
/**
|
|
32
|
+
* Pre-computed set of capName → array-output method names for collection
|
|
33
|
+
* caps, derived once from the cap defs at module init. Mirrors
|
|
34
|
+
* `generate-cap-mounts.ts` `isArrayOutputSchema` so the runtime collection
|
|
35
|
+
* resolver fans the SAME methods the codegen used to.
|
|
36
|
+
*/
|
|
37
|
+
const COLLECTION_ARRAY_METHODS = computeArrayMethods();
|
|
38
|
+
function computeArrayMethods() {
|
|
39
|
+
const out = new Map();
|
|
40
|
+
for (const def of types_1.ALL_CAPABILITY_DEFINITIONS) {
|
|
41
|
+
if (def.mode !== 'collection')
|
|
42
|
+
continue;
|
|
43
|
+
const names = [];
|
|
44
|
+
for (const [name, schema] of Object.entries(def.methods)) {
|
|
45
|
+
const m = schema;
|
|
46
|
+
if (m.kind === 'subscription')
|
|
47
|
+
continue;
|
|
48
|
+
if (isArrayOutputSchema(m.output))
|
|
49
|
+
names.push(name);
|
|
50
|
+
}
|
|
51
|
+
if (names.length > 0)
|
|
52
|
+
out.set(def.name, names);
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Structural "is this Zod schema an array output" check — bottoms out at
|
|
58
|
+
* `ZodArray` through the canonical Zod 4 wrappers. Identical logic to
|
|
59
|
+
* `scripts/generate-cap-mounts.ts`.
|
|
60
|
+
*/
|
|
61
|
+
function isArrayOutputSchema(schema) {
|
|
62
|
+
let cur = schema;
|
|
63
|
+
for (let depth = 0; depth < 8 && cur != null; depth += 1) {
|
|
64
|
+
const def = cur._def;
|
|
65
|
+
const ctor = cur.constructor?.name;
|
|
66
|
+
if (ctor === 'ZodArray' || def?.type === 'array')
|
|
67
|
+
return true;
|
|
68
|
+
const wraps = ctor === 'ZodReadonly' ||
|
|
69
|
+
def?.type === 'readonly' ||
|
|
70
|
+
ctor === 'ZodOptional' ||
|
|
71
|
+
def?.type === 'optional' ||
|
|
72
|
+
ctor === 'ZodNullable' ||
|
|
73
|
+
def?.type === 'nullable' ||
|
|
74
|
+
ctor === 'ZodDefault' ||
|
|
75
|
+
def?.type === 'default';
|
|
76
|
+
if (!wraps || def?.innerType == null)
|
|
77
|
+
return false;
|
|
78
|
+
cur = def.innerType;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Resolve a collection cap's provider exactly as the codegen-emitted
|
|
84
|
+
* mount did:
|
|
85
|
+
* - `addonId` set → that addon's provider directly (bypasses the
|
|
86
|
+
* enabled/disabled filter — an explicit addonId must always hit that
|
|
87
|
+
* exact provider).
|
|
88
|
+
* - else, for caps with array-output methods → an AGGREGATE provider
|
|
89
|
+
* whose array methods are `concatCollection`-fanned across every
|
|
90
|
+
* ENABLED provider; non-array methods fall through to the first.
|
|
91
|
+
* - else → the first enabled provider.
|
|
92
|
+
*/
|
|
93
|
+
function resolveCollectionProvider(reg, capName, addonId) {
|
|
94
|
+
if (!reg)
|
|
95
|
+
return null;
|
|
96
|
+
if (addonId !== undefined) {
|
|
97
|
+
return reg.getProviderByAddonId(capName, addonId);
|
|
98
|
+
}
|
|
99
|
+
const entries = reg.getCollectionEntries(capName);
|
|
100
|
+
if (entries.length === 0)
|
|
101
|
+
return null;
|
|
102
|
+
const providers = entries.map(([, p]) => p);
|
|
103
|
+
const first = providers[0];
|
|
104
|
+
if (first === undefined)
|
|
105
|
+
return null;
|
|
106
|
+
const arrayMethods = COLLECTION_ARRAY_METHODS.get(capName);
|
|
107
|
+
if (!arrayMethods || arrayMethods.length === 0)
|
|
108
|
+
return first;
|
|
109
|
+
const aggregate = { ...first };
|
|
110
|
+
for (const method of arrayMethods) {
|
|
111
|
+
aggregate[method] = concatArrayMethod(providers, method);
|
|
112
|
+
}
|
|
113
|
+
return aggregate;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Fan an array-returning method across every provider and flatten the
|
|
117
|
+
* results — the runtime equivalent of `concatCollection` for the
|
|
118
|
+
* structural `ResolvedProvider` shape (which a string method key can't
|
|
119
|
+
* satisfy `concatCollection`'s `ArrayReturningMethodKey<T>` constraint).
|
|
120
|
+
* Behaviour matches `concatCollection`: non-function / non-array results
|
|
121
|
+
* contribute nothing.
|
|
122
|
+
*/
|
|
123
|
+
function concatArrayMethod(providers, method) {
|
|
124
|
+
return async (input) => {
|
|
125
|
+
const results = await Promise.all(providers.map(async (p) => {
|
|
126
|
+
const fn = p[method];
|
|
127
|
+
if (typeof fn !== 'function')
|
|
128
|
+
return [];
|
|
129
|
+
const out = await fn(input);
|
|
130
|
+
return Array.isArray(out) ? out : [];
|
|
131
|
+
}));
|
|
132
|
+
return results.flat();
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/** Build the `primitives` half of `buildCapRouters(primitives, services)`. */
|
|
136
|
+
function createCapRouterPrimitives() {
|
|
137
|
+
const procedures = {
|
|
138
|
+
public: trpc_middleware_js_1.publicProcedure,
|
|
139
|
+
protected: trpc_middleware_js_1.protectedProcedure,
|
|
140
|
+
admin: trpc_middleware_js_1.adminProcedure,
|
|
141
|
+
};
|
|
142
|
+
return {
|
|
143
|
+
// Documented type boundary: the builder hands a record of REAL tRPC
|
|
144
|
+
// procedures typed structurally as `unknown` (it can't see the
|
|
145
|
+
// server's procedure types). `trpcRouter` (`t.router`) expects
|
|
146
|
+
// `CreateRouterOptions`; the runtime values satisfy it. Narrow at this
|
|
147
|
+
// single seam so the rest of the builder stays free of `@trpc/server`
|
|
148
|
+
// internal types — mirrors the existing `remoteCapProxy` cast pattern.
|
|
149
|
+
router: (procs) => (0, trpc_middleware_js_1.trpcRouter)(procs),
|
|
150
|
+
procedures,
|
|
151
|
+
iterableSubscription: trpc_middleware_js_1.iterableSubscription,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/** Build the `services` half of `buildCapRouters(primitives, services)`. */
|
|
155
|
+
function createCapRouterServices(deps) {
|
|
156
|
+
const reg = deps.capabilityRegistry;
|
|
157
|
+
const remoteProxy = (capName, nodeId) => deps.moleculer.createCapabilityProxy(capName, nodeId);
|
|
158
|
+
const getLocalProvider = (capName, mountKind, ctx, addonId) => {
|
|
159
|
+
switch (mountKind) {
|
|
160
|
+
case 'service-backed': {
|
|
161
|
+
const factory = deps.serviceProviders[capName];
|
|
162
|
+
return factory ? factory(ctx) : null;
|
|
163
|
+
}
|
|
164
|
+
case 'device-native':
|
|
165
|
+
return (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, capName);
|
|
166
|
+
case 'collection':
|
|
167
|
+
return resolveCollectionProvider(reg, capName, addonId);
|
|
168
|
+
// singleton / per-node / hub-only resolve the active singleton.
|
|
169
|
+
default:
|
|
170
|
+
return reg?.getSingleton(capName) ?? null;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
return {
|
|
174
|
+
getLocalProvider,
|
|
175
|
+
remoteProxy,
|
|
176
|
+
noProvider: (capName, nodeId) => {
|
|
177
|
+
if (nodeId !== undefined && nodeId !== 'hub') {
|
|
178
|
+
throw new server_1.TRPCError({
|
|
179
|
+
code: 'PRECONDITION_FAILED',
|
|
180
|
+
message: `Capability "${capName}" not available on node "${nodeId}"`,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
throw new server_1.TRPCError({
|
|
184
|
+
code: 'PRECONDITION_FAILED',
|
|
185
|
+
message: `Capability "${capName}" provider not available`,
|
|
186
|
+
});
|
|
187
|
+
},
|
|
188
|
+
noNodeRouting: (capName) => {
|
|
189
|
+
throw new server_1.TRPCError({
|
|
190
|
+
code: 'BAD_REQUEST',
|
|
191
|
+
message: `Node routing not available for "${capName}"`,
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
badRequest: (message) => {
|
|
195
|
+
throw new server_1.TRPCError({ code: 'BAD_REQUEST', message });
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Build the runtime cap-router map (the dynamic value). Returns
|
|
201
|
+
* `Record<string, unknown>` — the caller assigns it to the static
|
|
202
|
+
* `ReturnType<typeof mountAllCaps>` type at the single documented
|
|
203
|
+
* boundary so the AppRouter TYPE stays codegen-derived while the VALUE is
|
|
204
|
+
* built at runtime from `ALL_CAPABILITY_DEFINITIONS`.
|
|
205
|
+
*/
|
|
206
|
+
function buildRuntimeCapRouters(deps) {
|
|
207
|
+
return (0, system_1.buildCapRouters)(createCapRouterPrimitives(), createCapRouterServices(deps));
|
|
208
|
+
}
|
|
@@ -1669,6 +1669,24 @@ function createCapRouter_climateControl(getProvider, createRemoteProxy) {
|
|
|
1669
1669
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
1670
1670
|
return p.setTargetHumidity(methodInput);
|
|
1671
1671
|
}),
|
|
1672
|
+
setSwingVertical: trpc_middleware_js_1.adminProcedure
|
|
1673
|
+
.input(types_28.climateControlCapability.methods.setSwingVertical.input.loose())
|
|
1674
|
+
.output(types_28.climateControlCapability.methods.setSwingVertical.output)
|
|
1675
|
+
.mutation(async ({ input, ctx }) => {
|
|
1676
|
+
const { nodeId, ...methodInput } = input;
|
|
1677
|
+
const p = resolveProvider('climate-control', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
1678
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
1679
|
+
return p.setSwingVertical(methodInput);
|
|
1680
|
+
}),
|
|
1681
|
+
setSwingHorizontal: trpc_middleware_js_1.adminProcedure
|
|
1682
|
+
.input(types_28.climateControlCapability.methods.setSwingHorizontal.input.loose())
|
|
1683
|
+
.output(types_28.climateControlCapability.methods.setSwingHorizontal.output)
|
|
1684
|
+
.mutation(async ({ input, ctx }) => {
|
|
1685
|
+
const { nodeId, ...methodInput } = input;
|
|
1686
|
+
const p = resolveProvider('climate-control', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
1687
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
1688
|
+
return p.setSwingHorizontal(methodInput);
|
|
1689
|
+
}),
|
|
1672
1690
|
});
|
|
1673
1691
|
}
|
|
1674
1692
|
function createCapRouter_color(getProvider, createRemoteProxy) {
|
|
@@ -3,24 +3,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.enrichInputWithUserAgent = enrichInputWithUserAgent;
|
|
4
4
|
exports.wrapWebrtcSessionProviderWithRelay = wrapWebrtcSessionProviderWithRelay;
|
|
5
5
|
exports.buildAppRouter = buildAppRouter;
|
|
6
|
-
const trpc_middleware_1 = require("./trpc.middleware");
|
|
7
|
-
const generated_cap_routers_1 = require("./generated-cap-routers");
|
|
8
|
-
const generated_cap_mounts_js_1 = require("./generated-cap-mounts.js");
|
|
9
|
-
const cap_providers_js_1 = require("../core/cap-providers.js");
|
|
10
|
-
const auth_router_js_1 = require("../core/auth.router.js");
|
|
11
6
|
const addon_settings_router_js_1 = require("../core/addon-settings.router.js");
|
|
12
|
-
const
|
|
7
|
+
const auth_router_js_1 = require("../core/auth.router.js");
|
|
8
|
+
const cap_providers_js_1 = require("../core/cap-providers.js");
|
|
9
|
+
const capabilities_router_js_1 = require("../core/capabilities.router.js");
|
|
13
10
|
const event_bus_proxy_router_js_1 = require("../core/event-bus-proxy.router.js");
|
|
14
|
-
const
|
|
15
|
-
const notifications_router_js_1 = require("../core/notifications.router.js");
|
|
16
|
-
const logs_router_js_1 = require("../core/logs.router.js");
|
|
17
|
-
const system_events_router_js_1 = require("../core/system-events.router.js");
|
|
11
|
+
const hwaccel_router_js_1 = require("../core/hwaccel.router.js");
|
|
18
12
|
const live_events_router_js_1 = require("../core/live-events.router.js");
|
|
19
|
-
const
|
|
13
|
+
const logs_router_js_1 = require("../core/logs.router.js");
|
|
14
|
+
const notifications_router_js_1 = require("../core/notifications.router.js");
|
|
15
|
+
const repl_router_js_1 = require("../core/repl.router.js");
|
|
16
|
+
const settings_backend_router_js_1 = require("../core/settings-backend.router.js");
|
|
20
17
|
const stream_probe_router_js_1 = require("../core/stream-probe.router.js");
|
|
21
|
-
const
|
|
18
|
+
const system_events_router_js_1 = require("../core/system-events.router.js");
|
|
22
19
|
const cap_mount_helpers_js_1 = require("./cap-mount-helpers.js");
|
|
20
|
+
const cap_router_runtime_js_1 = require("./cap-router-runtime.js");
|
|
23
21
|
const client_ip_js_1 = require("./client-ip.js");
|
|
22
|
+
const generated_cap_routers_1 = require("./generated-cap-routers");
|
|
23
|
+
const trpc_middleware_1 = require("./trpc.middleware");
|
|
24
24
|
/**
|
|
25
25
|
* Merge the server-read User-Agent into a signaling call's
|
|
26
26
|
* `consumerAttribution`, building a NEW input object (immutable — never
|
|
@@ -66,31 +66,73 @@ function wrapWebrtcSessionProviderWithRelay(provider, ctx) {
|
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
|
-
* Build the
|
|
70
|
-
* mount
|
|
71
|
-
*
|
|
72
|
-
*
|
|
69
|
+
* Build the service-backed provider factory map (BIG PLAN 2). Caps marked
|
|
70
|
+
* `mount: { kind: 'service-backed' }` have NO addon-registered provider —
|
|
71
|
+
* the provider is composed from backend services. The runtime builder
|
|
72
|
+
* resolves them through this map (keyed by cap NAME) instead of the
|
|
73
|
+
* registry, keeping backend composition in `@camstack/server` (the
|
|
74
|
+
* self-sufficiency invariant — `@camstack/system` never touches a backend
|
|
75
|
+
* service). Each factory receives the per-request tRPC `ctx`.
|
|
76
|
+
*/
|
|
77
|
+
function buildServiceProviders(services) {
|
|
78
|
+
const wrap = (fn) => (ctx) => fn(ctx);
|
|
79
|
+
return {
|
|
80
|
+
'network-quality': wrap(() => (0, cap_providers_js_1.buildNetworkQualityProvider)(services.networkQualityService)),
|
|
81
|
+
system: wrap(() => (0, cap_providers_js_1.buildSystemProvider)(services.featureService, services.capabilityRegistry)),
|
|
82
|
+
toast: wrap((ctx) => (0, cap_providers_js_1.buildToastProvider)(services.toastService, ctx)),
|
|
83
|
+
integrations: wrap(() => (0, cap_providers_js_1.buildIntegrationsProvider)(services.addonRegistry, services.eventBus, services.loggingService, services.capabilityRegistry)),
|
|
84
|
+
nodes: wrap(() => (0, cap_providers_js_1.buildNodesProvider)(services.agentRegistry, services.moleculer, services.addonRegistry)),
|
|
85
|
+
addons: wrap((ctx) => (0, cap_providers_js_1.buildAddonsProvider)(services.addonRegistry, services.addonPackageService, services.loggingService, services.moleculer, services.configService, ctx)),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Build the AppRouter capability track (BIG PLAN 2).
|
|
73
90
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* `
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
91
|
+
* The per-cap router CONSTRUCTION now lives in `@camstack/system`
|
|
92
|
+
* (`buildCapRouters`), driven at runtime by `ALL_CAPABILITY_DEFINITIONS`
|
|
93
|
+
* + the declarative `mount` hint on each cap def. A capability update
|
|
94
|
+
* therefore ships via the framework plugin (`@camstack/types` +
|
|
95
|
+
* `@camstack/system`) with NO `@camstack/server` rebuild — the value is
|
|
96
|
+
* dynamic.
|
|
97
|
+
*
|
|
98
|
+
* The AppRouter TYPE stays codegen-derived: the dynamic value is assigned
|
|
99
|
+
* to `ReturnType<typeof mountAllCaps>` (the generated per-cap router type
|
|
100
|
+
* map) at the single documented type boundary below, so the SDK keeps
|
|
101
|
+
* full procedure inference and `generate-api-types.ts` resolves the same
|
|
102
|
+
* expanded type as before.
|
|
103
|
+
*
|
|
104
|
+
* Override-by-spread is preserved: spread the dynamic caps first, then the
|
|
105
|
+
* hand-written core routers + the two `custom`-mount overrides
|
|
106
|
+
* (snapshot-provider's probe fan-out, webrtc-session's relay enrichment),
|
|
107
|
+
* which the generic builder intentionally leaves unmounted for the server
|
|
108
|
+
* to fill.
|
|
80
109
|
*/
|
|
81
110
|
function buildCapabilityRouters(services) {
|
|
111
|
+
// ── Dynamic cap routers (value) → static cap-router map (type). ──────
|
|
112
|
+
// The runtime builder returns `Record<string, unknown>`; assigning it to
|
|
113
|
+
// `ReturnType<typeof mountAllCaps>` is the documented type boundary that
|
|
114
|
+
// keeps the AppRouter type codegen-derived while the value is built from
|
|
115
|
+
// the cap defs at runtime. `mountAllCaps` is imported for its TYPE only
|
|
116
|
+
// (`import type`) — its value is never called; the codegen
|
|
117
|
+
// `generated-cap-mounts.ts` / `generated-cap-routers.ts` remain the TYPE
|
|
118
|
+
// source of record.
|
|
119
|
+
const dynamicCaps = (0, cap_router_runtime_js_1.buildRuntimeCapRouters)({
|
|
120
|
+
capabilityRegistry: services.capabilityRegistry,
|
|
121
|
+
moleculer: services.moleculer,
|
|
122
|
+
serviceProviders: buildServiceProviders(services),
|
|
123
|
+
});
|
|
82
124
|
return {
|
|
83
|
-
// ──
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
...
|
|
125
|
+
// ── Runtime-built cap routers: every cap whose mount is
|
|
126
|
+
// `singleton` / `collection` / `device-native` / `service-backed`
|
|
127
|
+
// / `hub-only`. `custom` + `skip` caps are NOT here — `skip` is
|
|
128
|
+
// never mounted; `custom` is filled by the overrides below. ──────
|
|
129
|
+
...dynamicCaps,
|
|
88
130
|
// ── Non-cap (core) routers — hand-written, single-impl ──────────
|
|
89
131
|
notifications: (0, notifications_router_js_1.createNotificationsRouter)(services.notificationService),
|
|
90
132
|
// Raw DB proxy for forked workers to read/write addon store.
|
|
91
133
|
// Workers use ctx.api.addonSettingsRaw.getGlobal.query({...}).
|
|
92
|
-
// NOT the three-level settings gateway — that's the
|
|
93
|
-
// `addonSettings` cap router (mounted
|
|
134
|
+
// NOT the three-level settings gateway — that's the
|
|
135
|
+
// `addonSettings` cap router (mounted dynamically above).
|
|
94
136
|
addonSettingsRaw: (0, addon_settings_router_js_1.createAddonSettingsRouter)(services.configService),
|
|
95
137
|
settingsBackend: (0, settings_backend_router_js_1.createSettingsBackendRouter)(() => services.addonRegistry.getSettingsBackend()),
|
|
96
138
|
eventBusProxy: (0, event_bus_proxy_router_js_1.createEventBusProxyRouter)(services.eventBus),
|
|
@@ -106,46 +148,11 @@ function buildCapabilityRouters(services) {
|
|
|
106
148
|
// which hardware backend each agent will use.
|
|
107
149
|
hwaccel: (0, hwaccel_router_js_1.createHwAccelRouter)(services.moleculer?.broker ?? null),
|
|
108
150
|
auth: (0, auth_router_js_1.createAuthRouter)(services.authService, services.capabilityRegistry),
|
|
109
|
-
// ── Cap overrides:
|
|
110
|
-
// These caps don't have an addon registering a provider in the
|
|
111
|
-
// CapabilityRegistry — the provider is built on-demand from
|
|
112
|
-
// backend services. `mountAllCaps` would return `null` for them
|
|
113
|
-
// (registry lookup miss), so we re-mount with `buildXProvider`.
|
|
114
|
-
networkQuality: (0, generated_cap_routers_1.createCapRouter_networkQuality)((_ctx) => (0, cap_providers_js_1.buildNetworkQualityProvider)(services.networkQualityService)),
|
|
115
|
-
system: (0, generated_cap_routers_1.createCapRouter_system)((_ctx) => (0, cap_providers_js_1.buildSystemProvider)(services.featureService, services.capabilityRegistry)),
|
|
116
|
-
toast: (0, generated_cap_routers_1.createCapRouter_toast)((ctx) => (0, cap_providers_js_1.buildToastProvider)(services.toastService, ctx)),
|
|
117
|
-
integrations: (0, generated_cap_routers_1.createCapRouter_integrations)((_ctx) => (0, cap_providers_js_1.buildIntegrationsProvider)(services.addonRegistry, services.eventBus, services.loggingService, services.capabilityRegistry)),
|
|
118
|
-
nodes: (0, generated_cap_routers_1.createCapRouter_nodes)((_ctx) => (0, cap_providers_js_1.buildNodesProvider)(services.agentRegistry, services.moleculer, services.addonRegistry)),
|
|
119
|
-
addons: (0, generated_cap_routers_1.createCapRouter_addons)((ctx) => (0, cap_providers_js_1.buildAddonsProvider)(services.addonRegistry, services.addonPackageService, services.loggingService, services.moleculer, services.configService, ctx)),
|
|
120
|
-
// ── Cap overrides: cross-node remote-proxy cast ─────────────────
|
|
121
|
-
// These caps' providers have manual interface types that pre-date
|
|
122
|
-
// `InferProvider<typeof xCap>` — structurally identical, nominally
|
|
123
|
-
// distinct. Casting at the override site is cheaper than reworking
|
|
124
|
-
// the provider declarations. Auto-mount can't infer the cast.
|
|
125
|
-
pipelineExecutor: (0, generated_cap_routers_1.createCapRouter_pipelineExecutor)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'pipeline-executor'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
|
|
126
|
-
pipelineRunner: (0, generated_cap_routers_1.createCapRouter_pipelineRunner)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'pipeline-runner'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
|
|
127
|
-
pipelineOrchestrator: (0, generated_cap_routers_1.createCapRouter_pipelineOrchestrator)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'pipeline-orchestrator'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
|
|
128
|
-
audioAnalyzer: (0, generated_cap_routers_1.createCapRouter_audioAnalyzer)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'audio-analyzer'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
|
|
129
|
-
audioCodec: (0, generated_cap_routers_1.createCapRouter_audioCodec)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'audio-codec'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
|
|
130
|
-
decoder: (0, generated_cap_routers_1.createCapRouter_decoder)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'decoder'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
|
|
131
|
-
modelConvert: (0, generated_cap_routers_1.createCapRouter_modelConvert)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'model-convert'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
|
|
132
|
-
platformProbe: (0, generated_cap_routers_1.createCapRouter_platformProbe)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'platform-probe'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
|
|
133
|
-
// ── Cap overrides: hub-only, no remote fallback ─────────────────
|
|
134
|
-
// The cap is intentionally single-node; agents are not directly
|
|
135
|
-
// addressable. Auto-mount would still set up a proxy factory; we
|
|
136
|
-
// explicitly return `null` to short-circuit any cross-node attempt.
|
|
137
|
-
localNetwork: (0, generated_cap_routers_1.createCapRouter_localNetwork)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'local-network'), (_capName, _nodeId) => null),
|
|
138
|
-
// ── Cap overrides: collection dispatch (contribution / probe) ──
|
|
139
|
-
// `turn-provider.getTurnServers` is now handled generically by the
|
|
140
|
-
// auto-mount: it's an array-output method on a `collection` cap, so
|
|
141
|
-
// `mountAllCaps` fans it across every enabled provider via
|
|
142
|
-
// `concatCollection` when no `addonId` is supplied. No hand-written
|
|
143
|
-
// override needed.
|
|
144
|
-
//
|
|
151
|
+
// ── Cap overrides: `mount: { kind: 'custom' }` ──────────────────
|
|
145
152
|
// `snapshot-provider.supportsDevice` is an OR across providers;
|
|
146
153
|
// `getSnapshot` picks the first one that claims the device. The
|
|
147
|
-
// generic first-provider resolver
|
|
148
|
-
// this — we hand-write the probe + fan-out logic.
|
|
154
|
+
// generic builder's first-provider/concat collection resolver can't
|
|
155
|
+
// model this — we hand-write the probe + fan-out logic.
|
|
149
156
|
snapshotProvider: (0, generated_cap_routers_1.createCapRouter_snapshotProvider)((_ctx) => {
|
|
150
157
|
const reg = services.capabilityRegistry;
|
|
151
158
|
if (!reg)
|
|
@@ -157,29 +164,25 @@ function buildCapabilityRouters(services) {
|
|
|
157
164
|
const getSnapshot = (0, cap_mount_helpers_js_1.firstSupported)(providers, 'supportsDevice', 'getSnapshot');
|
|
158
165
|
return { supportsDevice, getSnapshot };
|
|
159
166
|
}),
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
// `
|
|
164
|
-
// `
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
167
|
+
// `webrtc-session` — server-detected remote → relay-only. The broker
|
|
168
|
+
// (a forked addon) can't see the HTTP request, so it can't tell a LAN
|
|
169
|
+
// viewer from a remote one. We override `getProvider` to return a
|
|
170
|
+
// per-request provider whose `createSession` carries a server-computed
|
|
171
|
+
// `relayOnly` flag derived from the client IP in `ctx.req`. Remote
|
|
172
|
+
// (CGNAT/4G) viewers force TURN-relay-only ICE; LAN viewers keep the
|
|
173
|
+
// direct host/srflx path. All other methods delegate straight through,
|
|
174
|
+
// and the cross-node remote-proxy routing is preserved (forked/agent-
|
|
175
|
+
// hosted brokers).
|
|
169
176
|
webrtcSession: (0, generated_cap_routers_1.createCapRouter_webrtcSession)((ctx) => {
|
|
170
177
|
const provider = services.capabilityRegistry?.getSingleton('webrtc-session') ?? null;
|
|
171
178
|
return provider ? wrapWebrtcSessionProviderWithRelay(provider, ctx) : null;
|
|
172
179
|
}, (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
|
|
173
|
-
// NOT MOUNTED —
|
|
174
|
-
// returns) that don't match the codegen
|
|
175
|
-
// Promise<T> contract.
|
|
176
|
-
//
|
|
177
|
-
// - addon-routes
|
|
178
|
-
// -
|
|
179
|
-
// - log-destination (ILogDestination: positional + extra lifecycle)
|
|
180
|
-
// - restreamer (IRestreamer: registerDevice positional)
|
|
181
|
-
// - streaming-engine (IStreamingEngine: registerStream positional)
|
|
182
|
-
// - webrtc (IWebRtcProvider: missing hasAdaptiveBitrate)
|
|
180
|
+
// NOT MOUNTED — `mount: { kind: 'skip' }` legacy provider shapes
|
|
181
|
+
// (positional args / sync returns) that don't match the codegen
|
|
182
|
+
// routers' {input}-object + Promise<T> contract. The runtime builder
|
|
183
|
+
// skips them via `resolveCapMount` (task #195):
|
|
184
|
+
// - addon-routes / auth-provider / log-destination
|
|
185
|
+
// - restreamer / streaming-engine / webrtc
|
|
183
186
|
};
|
|
184
187
|
}
|
|
185
188
|
function buildAppRouter(services) {
|