@camstack/system 1.1.22 → 1.1.24

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.
@@ -97,6 +97,27 @@ export declare function setDeviceLinks(pctx: ProviderContext, input: Parameters<
97
97
  * `setIntegrationId`). Idempotent. `null` clears a previous role.
98
98
  */
99
99
  export declare function setRole(pctx: ProviderContext, input: Parameters<IDeviceManagerProvider['setRole']>[0]): ReturnType<IDeviceManagerProvider['setRole']>;
100
+ /**
101
+ * Set (or clear) the per-device display override on a device's meta row.
102
+ * Mirrors `setChildLayout` persistence; `null` REMOVES the `display` key
103
+ * entirely (immutable rest-destructure — matches the "absent ⇒ no override"
104
+ * projection contract, never persists `display: undefined`). Override units are
105
+ * normalized at write so the render path always looks up canonical spellings.
106
+ */
107
+ export declare function setDisplay(pctx: ProviderContext, input: Parameters<IDeviceManagerProvider['setDisplay']>[0]): ReturnType<IDeviceManagerProvider['setDisplay']>;
108
+ /**
109
+ * Read the operator-authored per-role display defaults. Empty record when none
110
+ * set. Not per-device — a plain top-level-key read.
111
+ */
112
+ export declare function getRoleDisplayDefaults(pctx: ProviderContext, _input: Parameters<IDeviceManagerProvider['getRoleDisplayDefaults']>[0]): ReturnType<IDeviceManagerProvider['getRoleDisplayDefaults']>;
113
+ /**
114
+ * Replace the per-role display defaults whole-record (full replace). Override
115
+ * units are normalized (`normalizeUnit`) at write so the render path always
116
+ * looks up canonical spellings. Single-writer top-level key — no lock, no RMW,
117
+ * no interaction with the `deviceMeta` write lock. Not per-device, so no event
118
+ * is emitted; the UI invalidates its own query on mutate.
119
+ */
120
+ export declare function setRoleDisplayDefaults(pctx: ProviderContext, input: Parameters<IDeviceManagerProvider['setRoleDisplayDefaults']>[0]): ReturnType<IDeviceManagerProvider['setRoleDisplayDefaults']>;
100
121
  /**
101
122
  * Batched meta pre-seed. Applies every provided field to the
102
123
  * device's meta row in ONE read-modify-write under a single
@@ -62,6 +62,12 @@ export declare class DeviceMetaStore {
62
62
  * whose stableId is `${parentStableId}-${sourceKey}`. Null when absent.
63
63
  * Pure over a pre-read meta map so a multi-link resolve reads the store once. */
64
64
  resolveSourceDeviceId: (parentStableId: string, sourceKey: string, meta: Record<string, PersistedDeviceMeta>) => number | null;
65
+ /** Resolve a GLOBAL link source (P2e) to a live device id: the device whose
66
+ * FULL stableId equals `sourceStableId`, regardless of parent container.
67
+ * First match wins (stableIds are effectively unique cluster-wide — see the
68
+ * `DeviceLinkGlobalSource` docblock). Null when absent. Pure over a
69
+ * pre-read meta map so a multi-link resolve reads the store once. */
70
+ resolveGlobalSourceDeviceId: (sourceStableId: string, meta: Record<string, PersistedDeviceMeta>) => number | null;
65
71
  allocateNextDeviceId: () => Promise<number>;
66
72
  /** Rebuild the `linkTargets` / `linkDependents` reverse-index maps from the
67
73
  * current persisted meta. Called at boot (once the `devicesWithLinks` seed
@@ -1,4 +1,4 @@
1
- import { ChildLayout, DeviceLinks } from '@camstack/types';
1
+ import { ChildLayout, DeviceDisplayOverride, DeviceLinks, RoleDisplayDefault } from '@camstack/types';
2
2
  /**
3
3
  * Wire shape matching `z.infer<typeof SettingsSchemaWithValuesSchema>` —
4
4
  * duplicated as a plain interface because importing the Zod schema across
@@ -132,6 +132,11 @@ export interface PersistedDeviceMeta {
132
132
  * Optional: only present for accessory children that carry a known role
133
133
  * (e.g. 'numeric-sensor', 'binary-sensor'). `null` clears a previous role. */
134
134
  role?: string | null;
135
+ /** Operator-authored per-device display override (icon/label/unit/precision/
136
+ * hidden), set via `setDisplay`. Same create/persist/project/restore
137
+ * lifecycle as `deviceLinks`. Absent ⇒ no override. Applied at render time
138
+ * only — storage stays in source units. */
139
+ display?: DeviceDisplayOverride;
135
140
  }
136
141
  export interface AddonStore {
137
142
  deviceIndex?: Record<string, string[]>;
@@ -166,4 +171,10 @@ export interface AddonStore {
166
171
  * the operator forgot to register a label before assigning it.
167
172
  */
168
173
  locations?: readonly string[];
174
+ /** Operator-authored per-role display defaults (unit/precision/icon), keyed
175
+ * by `DeviceRole` string. Resolution merges these UNDER any per-device
176
+ * `display` override. Written whole-record by `setRoleDisplayDefaults`
177
+ * (full replace — no read-modify-write, single writer, so it never
178
+ * interacts with the `deviceMeta` write lock). */
179
+ roleDisplayDefaults?: Record<string, RoleDisplayDefault>;
169
180
  }
@@ -47,6 +47,7 @@ export declare function isDeviceRole(value: string): value is DeviceRole;
47
47
  * never leaks an off-enum string onto the wire shape. */
48
48
  export declare function toDeviceRole(value: string | null | undefined): DeviceRole | null;
49
49
  export declare function toDeviceInfo(addonId: string, device: IDevice, metadata?: Record<string, unknown> | null, metaRow?: PersistedDeviceMeta | null): {
50
+ display?: import('@camstack/types').DeviceDisplayOverride | undefined;
50
51
  deviceLinks?: import('@camstack/types').DeviceLinks | undefined;
51
52
  childLayout?: import('@camstack/types').ChildLayout | undefined;
52
53
  primaryChildEntityId?: string | null | undefined;
@@ -14,6 +14,10 @@ export interface ProviderHost {
14
14
  readonly devicesWithLinks: Set<number>;
15
15
  /** Expected link-source stableIds, gating the `registerDevice` rebuild. */
16
16
  readonly expectedSourceStableIds: Set<string>;
17
+ /** In-flight `${deviceId}:${cap}` pairs of `resolveLinkedStatus` — the
18
+ * defensive re-entrancy guard that bounds link cycles at resolve time
19
+ * (P2e). A nested resolve of a pair already in flight returns null. */
20
+ readonly linkResolveInFlight: Set<string>;
17
21
  /** Drop a removed device's overlay-emit guard entries (state-mirror keyed
18
22
  * `${deviceId}:${cap}`). Called from `removeDevice`. */
19
23
  dropDeviceOverlays(deviceId: number): void;
@@ -108,18 +108,26 @@ export declare class DeviceStateMirror {
108
108
  * Read-time overlay of a cap slice with its cross-device linked values.
109
109
  * Returns a cloned raw mirror slice when the (device, cap) pair has no
110
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.
111
+ * cross-process call. When the raw slice is ABSENT but the cap is a link
112
+ * target, the slice is SYNTHESIZED: the merge base is seeded from the
113
+ * cap's declared `status.empty` default; a failed/empty synthesize
114
+ * returns null (no phantom slices). The disk writer must NOT use this
115
+ * method; it must persist raw provider truth via snapshotForDevice.
113
116
  */
114
117
  overlayedSlice(deviceId: number, cap: string): Record<string, unknown> | null;
115
118
  /**
116
119
  * Like snapshotForDevice but applies the device-link overlay per cap.
117
120
  * 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).
121
+ * getAllSnapshots) so callers see overlayed values. SYNTHESIZE-only
122
+ * link-target caps (no base mirror slice) are unioned into the iterated
123
+ * key set so they appear at warm-load — `overlayedSlice` builds them from
124
+ * the cap's `status.empty` (and drops them when nothing resolves). The
125
+ * debounced disk writer must continue to call snapshotForDevice (raw truth).
120
126
  */
121
127
  snapshotForDeviceOverlayed(deviceId: number): Record<string, Record<string, unknown>>;
122
- /** Whole-system mirror dump (overlaid). Backs `deviceState.getAllSnapshots`. */
128
+ /** Whole-system mirror dump (overlaid). Backs `deviceState.getAllSnapshots`.
129
+ * Devices that only exist as link TARGETS (no mirror slice yet) are
130
+ * included via the link reverse-index so their synthesized caps appear. */
123
131
  allSnapshotsOverlayed(): Record<string, Record<string, Record<string, unknown>>>;
124
132
  private emitStateChanged;
125
133
  /** Emit DeviceStateChanged for (deviceId, cap) using the OVERLAID slice,
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ const require_builtins_local_auth_local_auth_addon = require("./builtins/local-a
20
20
  require("./builtins/local-auth/index.js");
21
21
  const require_builtins_device_manager_device_manager_addon = require("./builtins/device-manager/device-manager.addon.js");
22
22
  require("./builtins/device-manager/index.js");
23
- const require_manifest_python_deps = require("./manifest-python-deps-D7iR07uA.js");
23
+ const require_manifest_python_deps = require("./manifest-python-deps-XWJwKYDx.js");
24
24
  const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
25
25
  let _camstack_types_node = require("@camstack/types/node");
26
26
  let node_http = require("node:http");
package/dist/index.mjs CHANGED
@@ -18,7 +18,7 @@ import { LocalAuthAddon, a as require_ms, c as __esmMin, d as __toCommonJS, f as
18
18
  import "./builtins/local-auth/index.mjs";
19
19
  import { DeviceManagerAddon } from "./builtins/device-manager/device-manager.addon.mjs";
20
20
  import "./builtins/device-manager/index.mjs";
21
- import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as resolveHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as resolveAddonClass, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getMoleculerEventStats, dt as capBareAction, et as buildUdsNativeCapProxy, f as registerEventBusService, ft as capServiceName, g as createKernelHwAccel, gt as DeviceRegistry, h as AddonDepsManager, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as subscribePassthrough, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as setNodeEventInterest, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as getBrokerEventBus, ut as capActionSuffix, v as createHwAccelService, vt as CapabilityUnavailableError, w as buildLinkChain, x as getCapUsageRegistry, y as CapUsageRegistry, yt as installManifestNativeDeps, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-hDWMDe_b.mjs";
21
+ import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as resolveHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as resolveAddonClass, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getMoleculerEventStats, dt as capBareAction, et as buildUdsNativeCapProxy, f as registerEventBusService, ft as capServiceName, g as createKernelHwAccel, gt as DeviceRegistry, h as AddonDepsManager, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as subscribePassthrough, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as setNodeEventInterest, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as getBrokerEventBus, ut as capActionSuffix, v as createHwAccelService, vt as CapabilityUnavailableError, w as buildLinkChain, x as getCapUsageRegistry, y as CapUsageRegistry, yt as installManifestNativeDeps, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-CPJXzrZt.mjs";
22
22
  import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
23
23
  import { PYTHON_VERSION, buildBinaryPath, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
24
24
  import { request } from "node:http";
@@ -1423,6 +1423,18 @@ function createBrokerDeviceManagerApi(opts) {
1423
1423
  meta: { error: err instanceof Error ? err.message : String(err) }
1424
1424
  });
1425
1425
  });
1426
+ if (initialMeta.display !== void 0) await callDeviceManager(api, "setDisplay", {
1427
+ deviceId: id,
1428
+ display: initialMeta.display
1429
+ }).catch((err) => {
1430
+ opts.logger.warn("create: setDisplay pre-seed failed", {
1431
+ tags: {
1432
+ stableId,
1433
+ deviceId: id
1434
+ },
1435
+ meta: { error: err instanceof Error ? err.message : String(err) }
1436
+ });
1437
+ });
1426
1438
  }
1427
1439
  if (Object.keys(config).length > 0) await callDeviceManager(api, "persistConfig", {
1428
1440
  deviceId: id,
@@ -1425,6 +1425,18 @@ function createBrokerDeviceManagerApi(opts) {
1425
1425
  meta: { error: err instanceof Error ? err.message : String(err) }
1426
1426
  });
1427
1427
  });
1428
+ if (initialMeta.display !== void 0) await callDeviceManager(api, "setDisplay", {
1429
+ deviceId: id,
1430
+ display: initialMeta.display
1431
+ }).catch((err) => {
1432
+ opts.logger.warn("create: setDisplay pre-seed failed", {
1433
+ tags: {
1434
+ stableId,
1435
+ deviceId: id
1436
+ },
1437
+ meta: { error: err instanceof Error ? err.message : String(err) }
1438
+ });
1439
+ });
1428
1440
  }
1429
1441
  if (Object.keys(config).length > 0) await callDeviceManager(api, "persistConfig", {
1430
1442
  deviceId: id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.1.22",
3
+ "version": "1.1.24",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",