@camstack/system 1.1.11 → 1.1.13
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/dist/addon-runner.mjs +1 -1
- package/dist/builtins/device-manager/device-aggregation-merge.d.ts +22 -0
- package/dist/builtins/device-manager/device-aggregation.d.ts +220 -0
- package/dist/builtins/device-manager/device-bindings-store.d.ts +100 -0
- package/dist/builtins/device-manager/device-manager.addon.d.ts +41 -332
- package/dist/builtins/device-manager/device-manager.addon.js +3293 -2871
- package/dist/builtins/device-manager/device-manager.addon.mjs +3293 -2871
- package/dist/builtins/device-manager/device-meta-actions.d.ts +159 -0
- package/dist/builtins/device-manager/device-meta-store.d.ts +72 -0
- package/dist/builtins/device-manager/device-meta-types.d.ts +169 -0
- package/dist/builtins/device-manager/device-projection.d.ts +74 -0
- package/dist/builtins/device-manager/device-provider-context.d.ts +65 -0
- package/dist/builtins/device-manager/device-queries.d.ts +38 -0
- package/dist/builtins/device-manager/device-state-mirror.d.ts +137 -0
- package/dist/builtins/device-manager/device-sync-resolvers.d.ts +41 -0
- package/dist/index.js +215 -0
- package/dist/index.mjs +216 -3
- package/dist/kernel/cap-router-builder.d.ts +78 -0
- package/dist/kernel/index.d.ts +2 -0
- package/dist/{manifest-python-deps-1zTy3vXe.mjs → manifest-python-deps-BfJEdXhi.mjs} +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { AddonContext, ICapabilityRegistry } from '@camstack/types';
|
|
2
|
+
import { ResolvedTargetLink, DependentLink } from './device-link-overlay.js';
|
|
3
|
+
import { DeviceManagerSettings } from './device-meta-store.js';
|
|
4
|
+
/**
|
|
5
|
+
* Live view of the addon's cross-device link reverse-index + capability
|
|
6
|
+
* registry the mirror reads when overlaying linked values. Exposed via getters
|
|
7
|
+
* because the link maps are reassigned on every `rebuildLinkDependents`.
|
|
8
|
+
*/
|
|
9
|
+
export interface LinkOverlayHost {
|
|
10
|
+
readonly linkTargets: Map<string, ResolvedTargetLink[]>;
|
|
11
|
+
readonly linkDependents: Map<string, DependentLink[]>;
|
|
12
|
+
readonly capabilityRegistry: ICapabilityRegistry | undefined;
|
|
13
|
+
}
|
|
14
|
+
export declare class DeviceStateMirror {
|
|
15
|
+
private readonly ctx;
|
|
16
|
+
private readonly linkHost;
|
|
17
|
+
/**
|
|
18
|
+
* Hub-side mirror of every device's cap-keyed runtime state.
|
|
19
|
+
* Key: deviceId. Value: per-cap slice map. Empty by default —
|
|
20
|
+
* slices show up as `setCapSlice` calls trickle in.
|
|
21
|
+
*/
|
|
22
|
+
private readonly stateMirror;
|
|
23
|
+
/**
|
|
24
|
+
* Per-device disk-write debouncer for runtime-state. `setCapSlice`
|
|
25
|
+
* updates the in-memory mirror synchronously and emits the change
|
|
26
|
+
* event immediately, but the disk write is coalesced.
|
|
27
|
+
*/
|
|
28
|
+
private readonly runtimeStateDebounce;
|
|
29
|
+
private static readonly RUNTIME_STATE_DEBOUNCE_MS;
|
|
30
|
+
/** Loop/churn guard: last overlaid slice emitted per `${deviceId}:${cap}`. */
|
|
31
|
+
private readonly lastEmittedOverlay;
|
|
32
|
+
constructor(ctx: AddonContext, linkHost: LinkOverlayHost);
|
|
33
|
+
/**
|
|
34
|
+
* Single-cap mirror update — diff against the current mirror,
|
|
35
|
+
* persist the new slice in-memory, emit `DeviceStateChanged` for
|
|
36
|
+
* this cap. No-op on identical writes (both same shape and same
|
|
37
|
+
* values). Called by `setCapSlice` provider.
|
|
38
|
+
*/
|
|
39
|
+
applySingleCapUpdate(deviceId: number, capName: string, slice: Record<string, unknown>): void;
|
|
40
|
+
/**
|
|
41
|
+
* Debounced disk writer. Coalesces frequent writes (motion phase
|
|
42
|
+
* transitions, battery pushes) into one `writeDeviceRuntimeState`
|
|
43
|
+
* per `RUNTIME_STATE_DEBOUNCE_MS` window. Reads the per-device
|
|
44
|
+
* blob from the live mirror at flush time so the disk picture is
|
|
45
|
+
* always the latest state — no risk of writing a stale snapshot.
|
|
46
|
+
*/
|
|
47
|
+
scheduleRuntimeStateDiskWrite(deviceId: number, settings: DeviceManagerSettings): void;
|
|
48
|
+
/**
|
|
49
|
+
* One-shot mirror seed used by `loadRuntimeState` at boot so the
|
|
50
|
+
* hub knows about every persisted slice without waiting for the
|
|
51
|
+
* first `setCapSlice` call. No events emitted — this is
|
|
52
|
+
* initial-state population, not a transition.
|
|
53
|
+
*
|
|
54
|
+
* Callers that must not carry a stale per-session probe across a
|
|
55
|
+
* restart pass the blob through `withResetSessionProbe` first (see
|
|
56
|
+
* `loadRuntimeState`).
|
|
57
|
+
*/
|
|
58
|
+
seedMirror(deviceId: number, blob: Record<string, unknown>): void;
|
|
59
|
+
/**
|
|
60
|
+
* The hub mirror's `feature-probe.lastProbedAt` is a PER-SESSION liveness
|
|
61
|
+
* signal — it means "this worker process completed a probe THIS session".
|
|
62
|
+
* Persisted runtime state carries the PRE-RESTART timestamp, which is stale
|
|
63
|
+
* after a hub or worker restart: the device has not re-probed yet. Seeding it
|
|
64
|
+
* verbatim makes `resolveDeviceProbed` report `probed:true` during the
|
|
65
|
+
* restart→reprobe window, which defeats the export carry-forward gate
|
|
66
|
+
* (`resolveExportFingerprint`, gated on `device.probed`) and posts a spurious
|
|
67
|
+
* partial `AddOrUpdateReport` to Alexa/HAP before the real probe lands.
|
|
68
|
+
*
|
|
69
|
+
* Reset `lastProbedAt` to 0 for the MIRROR seed only — `probed:false` carries
|
|
70
|
+
* the last-advertised fingerprint forward until the worker republishes a
|
|
71
|
+
* fresh probe (its post-probe `setCapSlice` raises `lastProbedAt` again, which
|
|
72
|
+
* also fires `DeviceReady`). The worker's returned `initialRuntimeState` blob
|
|
73
|
+
* is untouched, and every non-probe slice (e.g. `device-status`/online) is
|
|
74
|
+
* preserved.
|
|
75
|
+
*/
|
|
76
|
+
withResetSessionProbe(blob: Record<string, unknown>): Record<string, unknown>;
|
|
77
|
+
/**
|
|
78
|
+
* Resolve a device's REAL `online` flag for the persisted/forked-worker
|
|
79
|
+
* list branch. Forked workers own the live `IDevice` in their own process,
|
|
80
|
+
* so the hub registry can't read `device.online` directly. The owning
|
|
81
|
+
* driver instead publishes its liveness through the auto-registered
|
|
82
|
+
* `device-status` runtime-state slice (`markOnline` → `setCapState`), which
|
|
83
|
+
* the canonical `deviceState.setCapSlice` write entrypoint mirrors into the
|
|
84
|
+
* hub-side `stateMirror`. We read that mirrored slice here so the list
|
|
85
|
+
* payload reflects the device's actual reachability instead of a constant.
|
|
86
|
+
*
|
|
87
|
+
* Fallback (`fallbackOnline`) preserves the legacy behaviour when no slice
|
|
88
|
+
* has been published yet: a persisted device with a live registry is
|
|
89
|
+
* assumed online (it was successfully registered by its owning process),
|
|
90
|
+
* and the null-registry "offline view" keeps reporting offline. We never
|
|
91
|
+
* regress a device to offline merely because its mirror is empty.
|
|
92
|
+
*/
|
|
93
|
+
resolveDeviceOnline(deviceId: number, fallbackOnline: boolean): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Derive the `probed` flag for an offline-view (forked-worker, not
|
|
96
|
+
* live in the hub registry) device projection. Reads the mirrored
|
|
97
|
+
* `feature-probe` slice the owning worker publishes. Mirrors the
|
|
98
|
+
* `toDeviceInfo` rule: no mirrored slice → ready (`true`, no probe seen);
|
|
99
|
+
* slice present → ready iff `lastProbedAt` has advanced past 0. The
|
|
100
|
+
* mirror is populated when the worker's first `setCapSlice` RPC arrives
|
|
101
|
+
* (BaseDevice seeds `feature-probe` `lastProbedAt:0` at construction, but
|
|
102
|
+
* cross-process delivery is async); until then the no-entry path returns
|
|
103
|
+
* `true` — a brief transient window, same as `resolveDeviceOnline`.
|
|
104
|
+
*/
|
|
105
|
+
resolveDeviceProbed(deviceId: number): boolean;
|
|
106
|
+
snapshotForDevice(deviceId: number): Record<string, Record<string, unknown>>;
|
|
107
|
+
/**
|
|
108
|
+
* Read-time overlay of a cap slice with its cross-device linked values.
|
|
109
|
+
* Returns a cloned raw mirror slice when the (device, cap) pair has no
|
|
110
|
+
* links. Sources are read from the same in-hub stateMirror — sync, no
|
|
111
|
+
* cross-process call. The disk writer must NOT use this method; it must
|
|
112
|
+
* persist raw provider truth via snapshotForDevice.
|
|
113
|
+
*/
|
|
114
|
+
overlayedSlice(deviceId: number, cap: string): Record<string, unknown> | null;
|
|
115
|
+
/**
|
|
116
|
+
* Like snapshotForDevice but applies the device-link overlay per cap.
|
|
117
|
+
* Used exclusively by the device-state READ methods (getSnapshot,
|
|
118
|
+
* getAllSnapshots) so callers see overlayed values. The debounced disk
|
|
119
|
+
* writer must continue to call snapshotForDevice (raw truth).
|
|
120
|
+
*/
|
|
121
|
+
snapshotForDeviceOverlayed(deviceId: number): Record<string, Record<string, unknown>>;
|
|
122
|
+
/** Whole-system mirror dump (overlaid). Backs `deviceState.getAllSnapshots`. */
|
|
123
|
+
allSnapshotsOverlayed(): Record<string, Record<string, Record<string, unknown>>>;
|
|
124
|
+
private emitStateChanged;
|
|
125
|
+
/** Emit DeviceStateChanged for (deviceId, cap) using the OVERLAID slice,
|
|
126
|
+
* skipping when the overlay is unchanged since the last emit (loop/churn
|
|
127
|
+
* guard). Used for the written pair AND its dependent targets. */
|
|
128
|
+
private emitOverlayed;
|
|
129
|
+
/** Drop a removed device's overlay-emit guard entries (keyed
|
|
130
|
+
* `${deviceId}:${cap}`) so the map doesn't retain rows for a removed device.
|
|
131
|
+
* Called from `removeDevice`. */
|
|
132
|
+
dropDeviceOverlays(deviceId: number): void;
|
|
133
|
+
/** Flush every pending debounced disk write (graceful shutdown). Clears the
|
|
134
|
+
* debounce slots after awaiting in-flight + scheduled writes so shutdown is
|
|
135
|
+
* lossless. */
|
|
136
|
+
flushPendingWrites(settings: DeviceManagerSettings | undefined): Promise<void>;
|
|
137
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ProviderContext } from './device-provider-context.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sync ownership lookup backing persistence fallbacks (e.g. remove()
|
|
4
|
+
* when the owning worker is offline). Ownership is keyed by numeric
|
|
5
|
+
* deviceId → owning addonId as recorded in the persisted meta. NOT
|
|
6
|
+
* a native-cap lookup: an addon can own a device without registering
|
|
7
|
+
* every possible cap natively (e.g. RtspCamera without snapshotUrl
|
|
8
|
+
* doesn't register the snapshot cap). Use `resolveNativeCapOwnerSync`
|
|
9
|
+
* for cap-resolution paths.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveDeviceOwnerSync(pctx: ProviderContext, deviceId: number): string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Sync lookup for the addon that registered a native provider for
|
|
14
|
+
* `(capName, deviceId)`. Backs `CapabilityRegistry`'s native fallback
|
|
15
|
+
* so the hub only synthesizes a cross-process proxy when the cap is
|
|
16
|
+
* actually published — never on speculative device ownership.
|
|
17
|
+
*
|
|
18
|
+
* Resolution order:
|
|
19
|
+
* 1. Hub-local `capabilityRegistry` (in-process natives — fastest path).
|
|
20
|
+
* 2. Push-fed `remoteNativeCaps` cache (`DeviceBindingsChanged` events
|
|
21
|
+
* from forked workers — accurate in steady state).
|
|
22
|
+
* 3. Handshake-fed `HubNodeRegistry` via `listClusterNativeCaps()`
|
|
23
|
+
* (D3 re-handshake after device restore — covers the Moleculer
|
|
24
|
+
* transport window where push events were lost). This is the
|
|
25
|
+
* reliable replacement for the deleted `syncWorkerNativeCaps` pull.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveNativeCapOwnerSync(pctx: ProviderContext, capName: string, deviceId: number): {
|
|
28
|
+
addonId: string;
|
|
29
|
+
nodeId: string;
|
|
30
|
+
} | null;
|
|
31
|
+
/**
|
|
32
|
+
* Read-time overlay: merge cross-device linked values onto a target cap's
|
|
33
|
+
* status. Returns null when the device has no links for `cap` (caller uses
|
|
34
|
+
* the base status untouched). Reads each source cap via the hub registry's
|
|
35
|
+
* `getProviderForDevice` (routes cross-process); merge is pure.
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveLinkedStatus(pctx: ProviderContext, input: {
|
|
38
|
+
deviceId: number;
|
|
39
|
+
cap: string;
|
|
40
|
+
baseStatus: unknown;
|
|
41
|
+
}): Promise<Record<string, unknown> | null>;
|
package/dist/index.js
CHANGED
|
@@ -4896,6 +4896,219 @@ var CapabilityRegistry = class CapabilityRegistry {
|
|
|
4896
4896
|
}
|
|
4897
4897
|
};
|
|
4898
4898
|
//#endregion
|
|
4899
|
+
//#region src/kernel/cap-router-builder.ts
|
|
4900
|
+
/**
|
|
4901
|
+
* Runtime cap-router builder (BIG PLAN 2).
|
|
4902
|
+
*
|
|
4903
|
+
* Builds the per-capability tRPC routers DYNAMICALLY at hub boot by
|
|
4904
|
+
* iterating `ALL_CAPABILITY_DEFINITIONS`, instead of importing the
|
|
4905
|
+
* statically codegen'd `createCapRouter_<Cap>` value functions baked into
|
|
4906
|
+
* `@camstack/server`. Moving the CONSTRUCTION here means a capability
|
|
4907
|
+
* update ships via the framework plugin (`@camstack/types` +
|
|
4908
|
+
* `@camstack/system`, live-updated together) WITHOUT a `@camstack/server`
|
|
4909
|
+
* image rebuild.
|
|
4910
|
+
*
|
|
4911
|
+
* Type vs value split:
|
|
4912
|
+
* - The runtime VALUE is built here, generically.
|
|
4913
|
+
* - The static AppRouter TYPE stays codegen'd into `@camstack/types`
|
|
4914
|
+
* (`generated/addon-api.ts`) via the `generated-cap-routers.ts`
|
|
4915
|
+
* per-cap router factory TYPES. The server delegation site assigns
|
|
4916
|
+
* this builder's output to that static type at the single documented
|
|
4917
|
+
* boundary, so the SDK keeps full procedure inference.
|
|
4918
|
+
*
|
|
4919
|
+
* Behaviour MUST stay byte-identical to the codegen output
|
|
4920
|
+
* (`scripts/generate-cap-routers.ts`). The per-method procedure shape is
|
|
4921
|
+
* mechanically derived from each `cap.methods.*` (input/output zod schema
|
|
4922
|
+
* + kind) + the auto-injected `getStatus` / 3 settings-contribution
|
|
4923
|
+
* methods (via `expandCapMethods`). Provider resolution is derived from
|
|
4924
|
+
* the declarative `mount` hint (`resolveCapMount`) — the same decision the
|
|
4925
|
+
* codegen `generate-cap-mounts.ts` baked in.
|
|
4926
|
+
*
|
|
4927
|
+
* This module owns NO tRPC primitives of its own: `@camstack/system`
|
|
4928
|
+
* cannot import the server's `TrpcContext`-bound procedures. The server
|
|
4929
|
+
* passes them in via {@link CapRouterPrimitives} (the `t` half of the
|
|
4930
|
+
* `buildCapRouters(t, services)` contract), keeping the kernel free of any
|
|
4931
|
+
* backend coupling (self-sufficiency invariant).
|
|
4932
|
+
*/
|
|
4933
|
+
/** Auth level → procedure key. Mirrors codegen's AUTH_PROCEDURE_MAP. */
|
|
4934
|
+
function procedureKeyFor(auth) {
|
|
4935
|
+
if (auth === "public") return "public";
|
|
4936
|
+
if (auth === "admin" || auth === "superAdmin") return "admin";
|
|
4937
|
+
return "protected";
|
|
4938
|
+
}
|
|
4939
|
+
/** Zod schema is `z.void()` — input carries no data. Mirrors codegen's isVoidInput. */
|
|
4940
|
+
function isVoidInput(schema) {
|
|
4941
|
+
const def = schema._def;
|
|
4942
|
+
return schema.constructor?.name === "ZodVoid" || def?.type === "void" || def?.typeName === "ZodVoid";
|
|
4943
|
+
}
|
|
4944
|
+
/** Zod schema is `z.object()` — supports `.loose()` for nodeId passthrough. Mirrors codegen's isObjectInput. */
|
|
4945
|
+
function isObjectInput(schema) {
|
|
4946
|
+
const def = schema._def;
|
|
4947
|
+
return schema.constructor?.name === "ZodObject" || def?.type === "object" || def?.typeName === "ZodObject";
|
|
4948
|
+
}
|
|
4949
|
+
/**
|
|
4950
|
+
* Apply `.loose()` to an object input schema when available (Zod 4) so the
|
|
4951
|
+
* router accepts the out-of-band `nodeId` / `addonId` selector keys
|
|
4952
|
+
* without stripping them — matching the codegen's `.input(schema.loose())`.
|
|
4953
|
+
* Falls back to the schema unchanged when `.loose()` is absent.
|
|
4954
|
+
*/
|
|
4955
|
+
function looseSchema(schema) {
|
|
4956
|
+
const loose = schema.loose;
|
|
4957
|
+
return typeof loose === "function" ? loose.call(schema) : schema;
|
|
4958
|
+
}
|
|
4959
|
+
function methodsFor(def) {
|
|
4960
|
+
const effective = (0, _camstack_types.expandCapMethods)(def);
|
|
4961
|
+
return Object.entries(effective).map(([name, schema]) => {
|
|
4962
|
+
const m = schema;
|
|
4963
|
+
return {
|
|
4964
|
+
name,
|
|
4965
|
+
kind: m.kind ?? "query",
|
|
4966
|
+
auth: m.auth ?? "protected",
|
|
4967
|
+
schema: m,
|
|
4968
|
+
isVoid: isVoidInput(m.input),
|
|
4969
|
+
isObject: isObjectInput(m.input)
|
|
4970
|
+
};
|
|
4971
|
+
});
|
|
4972
|
+
}
|
|
4973
|
+
/** Strip a set of selector keys from an input object, returning a NEW object. */
|
|
4974
|
+
function stripKeys(input, keys) {
|
|
4975
|
+
const src = input ?? {};
|
|
4976
|
+
const rest = {};
|
|
4977
|
+
const picked = {};
|
|
4978
|
+
for (const [k, v] of Object.entries(src)) if (keys.includes(k)) picked[k] = v;
|
|
4979
|
+
else rest[k] = v;
|
|
4980
|
+
return {
|
|
4981
|
+
rest,
|
|
4982
|
+
picked
|
|
4983
|
+
};
|
|
4984
|
+
}
|
|
4985
|
+
/**
|
|
4986
|
+
* Resolve the provider object for a NON-void/object method call, applying
|
|
4987
|
+
* the same local-first → remote-proxy routing the codegen's
|
|
4988
|
+
* `resolveProvider` does. `nodeId` undefined / `'hub'` resolves the local
|
|
4989
|
+
* provider; otherwise it crosses to the named node via the proxy.
|
|
4990
|
+
*/
|
|
4991
|
+
function resolveProviderForCall(capName, nodeId, getLocal, services) {
|
|
4992
|
+
if (!nodeId || nodeId === "hub") {
|
|
4993
|
+
const local = getLocal();
|
|
4994
|
+
if (local !== null && local !== void 0) return local;
|
|
4995
|
+
const proxy = services.remoteProxy(capName, "hub");
|
|
4996
|
+
if (proxy) return proxy;
|
|
4997
|
+
return services.noProvider(capName);
|
|
4998
|
+
}
|
|
4999
|
+
const proxy = services.remoteProxy(capName, nodeId);
|
|
5000
|
+
if (!proxy) return services.noProvider(capName, nodeId);
|
|
5001
|
+
return proxy;
|
|
5002
|
+
}
|
|
5003
|
+
/** Require a local provider (singleton-style, no node routing) or raise. */
|
|
5004
|
+
function requireLocal(capName, getLocal, services) {
|
|
5005
|
+
const p = getLocal();
|
|
5006
|
+
if (p === null || p === void 0) return services.noProvider(capName);
|
|
5007
|
+
return p;
|
|
5008
|
+
}
|
|
5009
|
+
/**
|
|
5010
|
+
* Build a single capability's router (the runtime equivalent of one
|
|
5011
|
+
* codegen'd `createCapRouter_<Cap>`). Each method becomes a typed
|
|
5012
|
+
* procedure with the SAME routing branch the codegen emits.
|
|
5013
|
+
*/
|
|
5014
|
+
function buildOneCapRouter(def, primitives, services) {
|
|
5015
|
+
const capName = def.name;
|
|
5016
|
+
const mount = (0, _camstack_types.resolveCapMount)(def);
|
|
5017
|
+
const isCollection = mount.kind === "collection";
|
|
5018
|
+
const nodeIdData = def.nodeIdMode === "data";
|
|
5019
|
+
const procedures = {};
|
|
5020
|
+
const getLocal = (ctx, addonId) => services.getLocalProvider(capName, mount.kind, ctx, addonId);
|
|
5021
|
+
for (const method of methodsFor(def)) {
|
|
5022
|
+
const base = primitives.procedures[procedureKeyFor(method.auth)];
|
|
5023
|
+
const inputSchema = method.schema.input;
|
|
5024
|
+
const outputSchema = method.schema.output;
|
|
5025
|
+
if (method.kind === "subscription") {
|
|
5026
|
+
procedures[method.name] = base.input(inputSchema).subscription(({ input, ctx }) => {
|
|
5027
|
+
const provider = requireLocal(capName, () => getLocal(ctx), services);
|
|
5028
|
+
return primitives.iterableSubscription((push) => {
|
|
5029
|
+
const fn = provider[method.name];
|
|
5030
|
+
const unsub = fn?.(input, push);
|
|
5031
|
+
return typeof unsub === "function" ? unsub : () => {};
|
|
5032
|
+
});
|
|
5033
|
+
});
|
|
5034
|
+
continue;
|
|
5035
|
+
}
|
|
5036
|
+
const procKind = method.kind === "mutation" ? "mutation" : "query";
|
|
5037
|
+
if (method.isVoid) {
|
|
5038
|
+
const voidSelector = isCollection ? zod.z.object({
|
|
5039
|
+
nodeId: zod.z.string().optional(),
|
|
5040
|
+
addonId: zod.z.string().optional()
|
|
5041
|
+
}).optional() : zod.z.object({ nodeId: zod.z.string().optional() }).optional();
|
|
5042
|
+
procedures[method.name] = base.input(voidSelector).output(outputSchema)[procKind](({ input, ctx }) => {
|
|
5043
|
+
const sel = input ?? {};
|
|
5044
|
+
return (nodeIdData ? requireLocal(capName, () => getLocal(ctx, isCollection ? sel.addonId : void 0), services) : resolveProviderForCall(capName, sel.nodeId, () => getLocal(ctx, isCollection ? sel.addonId : void 0), services))[method.name]?.();
|
|
5045
|
+
});
|
|
5046
|
+
continue;
|
|
5047
|
+
}
|
|
5048
|
+
if (method.isObject) {
|
|
5049
|
+
const schema = nodeIdData ? inputSchema : looseSchema(inputSchema);
|
|
5050
|
+
procedures[method.name] = base.input(schema).output(outputSchema)[procKind](({ input, ctx }) => {
|
|
5051
|
+
if (nodeIdData) return requireLocal(capName, () => getLocal(ctx), services)[method.name]?.(input);
|
|
5052
|
+
if (isCollection) {
|
|
5053
|
+
const { rest, picked } = stripKeys(input, ["nodeId", "addonId"]);
|
|
5054
|
+
const nodeId = typeof picked.nodeId === "string" ? picked.nodeId : void 0;
|
|
5055
|
+
const addonId = typeof picked.addonId === "string" ? picked.addonId : void 0;
|
|
5056
|
+
return resolveProviderForCall(capName, nodeId, () => getLocal(ctx, addonId), services)[method.name]?.(rest);
|
|
5057
|
+
}
|
|
5058
|
+
const { rest, picked } = stripKeys(input, ["nodeId"]);
|
|
5059
|
+
return resolveProviderForCall(capName, typeof picked.nodeId === "string" ? picked.nodeId : void 0, () => getLocal(ctx), services)[method.name]?.(rest);
|
|
5060
|
+
});
|
|
5061
|
+
continue;
|
|
5062
|
+
}
|
|
5063
|
+
procedures[method.name] = base.input(inputSchema).output(outputSchema)[procKind](({ input, ctx }) => {
|
|
5064
|
+
return requireLocal(capName, () => getLocal(ctx), services)[method.name]?.(input);
|
|
5065
|
+
});
|
|
5066
|
+
}
|
|
5067
|
+
return primitives.router(procedures);
|
|
5068
|
+
}
|
|
5069
|
+
/** kebab-case → camelCase, matching the codegen router-map key naming. */
|
|
5070
|
+
function kebabToCamel(s) {
|
|
5071
|
+
return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
5072
|
+
}
|
|
5073
|
+
/**
|
|
5074
|
+
* Mount kinds the runtime builder constructs. `service-backed` IS built
|
|
5075
|
+
* (the server supplies the provider via `getLocalProvider`); `custom` and
|
|
5076
|
+
* `skip` are NOT (the server's hand-written override fills the slot for
|
|
5077
|
+
* `custom`; `skip` is never mounted). `hub-only` is built but its remote
|
|
5078
|
+
* leg is `null` — handled by the server's `remoteProxy` returning null for
|
|
5079
|
+
* that cap.
|
|
5080
|
+
*/
|
|
5081
|
+
function isBuilderMounted(kind) {
|
|
5082
|
+
return kind !== "custom" && kind !== "skip";
|
|
5083
|
+
}
|
|
5084
|
+
/**
|
|
5085
|
+
* Build the runtime cap-router map: `{ <camelCapName>: <router> }` for
|
|
5086
|
+
* every capability the builder owns. Caps with `mount.kind` `custom` /
|
|
5087
|
+
* `skip` are omitted (the server overrides / never mounts them). The
|
|
5088
|
+
* server spreads this BEFORE its hand-written overrides + core routers so
|
|
5089
|
+
* override-by-spread precedence is preserved.
|
|
5090
|
+
*/
|
|
5091
|
+
function buildCapRouters(primitives, services) {
|
|
5092
|
+
const out = {};
|
|
5093
|
+
for (const def of _camstack_types.ALL_CAPABILITY_DEFINITIONS) {
|
|
5094
|
+
if (!isBuilderMounted((0, _camstack_types.resolveCapMount)(def).kind)) continue;
|
|
5095
|
+
const effective = (0, _camstack_types.expandCapMethods)(def);
|
|
5096
|
+
if (Object.keys(effective).length === 0) continue;
|
|
5097
|
+
out[kebabToCamel(def.name)] = buildOneCapRouter(def, primitives, services);
|
|
5098
|
+
}
|
|
5099
|
+
return out;
|
|
5100
|
+
}
|
|
5101
|
+
/** The set of cap camelNames the builder owns (for drift checks / tests). */
|
|
5102
|
+
function builderMountedCapNames() {
|
|
5103
|
+
const names = [];
|
|
5104
|
+
for (const def of _camstack_types.ALL_CAPABILITY_DEFINITIONS) {
|
|
5105
|
+
if (!isBuilderMounted((0, _camstack_types.resolveCapMount)(def).kind)) continue;
|
|
5106
|
+
if (Object.keys((0, _camstack_types.expandCapMethods)(def)).length === 0) continue;
|
|
5107
|
+
names.push(kebabToCamel(def.name));
|
|
5108
|
+
}
|
|
5109
|
+
return names;
|
|
5110
|
+
}
|
|
5111
|
+
//#endregion
|
|
4899
5112
|
//#region src/kernel/provider-kind-drift.ts
|
|
4900
5113
|
/**
|
|
4901
5114
|
* Compare a runtime registration hint against the authoritative cap
|
|
@@ -93094,11 +93307,13 @@ Object.defineProperty(exports, "buildBinaryPath", {
|
|
|
93094
93307
|
return _camstack_types_node.buildBinaryPath;
|
|
93095
93308
|
}
|
|
93096
93309
|
});
|
|
93310
|
+
exports.buildCapRouters = buildCapRouters;
|
|
93097
93311
|
exports.buildLinkChain = require_manifest_python_deps.buildLinkChain;
|
|
93098
93312
|
exports.buildNativeCapProxy = require_manifest_python_deps.buildNativeCapProxy;
|
|
93099
93313
|
exports.buildNodeManifest = buildNodeManifest;
|
|
93100
93314
|
exports.buildStorageLocationRegistry = buildStorageLocationRegistry;
|
|
93101
93315
|
exports.buildUdsNativeCapProxy = require_manifest_python_deps.buildUdsNativeCapProxy;
|
|
93316
|
+
exports.builderMountedCapNames = builderMountedCapNames;
|
|
93102
93317
|
exports.callRegisterNodeWithRetry = callRegisterNodeWithRetry;
|
|
93103
93318
|
exports.callWithServiceDiscovery = require_manifest_python_deps.callWithServiceDiscovery;
|
|
93104
93319
|
exports.capActionName = require_manifest_python_deps.capActionName;
|