@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.
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_manifest_python_deps = require("./manifest-python-deps-D7iR07uA.js");
2
+ const require_manifest_python_deps = require("./manifest-python-deps-XWJwKYDx.js");
3
3
  const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
4
4
  let node_fs = require("node:fs");
5
5
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { I as createUdsLoggerWithControl, L as LocalChildClient, at as setWorkerNativeCapsChangeListener, bt as resolveAddonClass, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps, yt as installManifestNativeDeps } from "./manifest-python-deps-hDWMDe_b.mjs";
1
+ import { I as createUdsLoggerWithControl, L as LocalChildClient, at as setWorkerNativeCapsChangeListener, bt as resolveAddonClass, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps, yt as installManifestNativeDeps } from "./manifest-python-deps-CPJXzrZt.mjs";
2
2
  import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
3
3
  import { register } from "node:module";
4
4
  import * as fs from "node:fs";
@@ -214,7 +214,14 @@ export declare function setWrapperActive(deps: AggregationDeps, input: {
214
214
  * bound caps expose, for the operator's cross-device wiring UI. Binding-driven:
215
215
  * walks `getBindings(deviceId)`, skips wrapper caps (their status schemas are
216
216
  * internal book-keeping, not wireable domain data), and enumerates each cap's
217
- * status schema leaf fields. Behavior unchanged from the inline provider method.
217
+ * status schema leaf fields (per-item fields included for item-array caps).
218
+ *
219
+ * `includeSynthesizable: true` (TARGET pickers only) additionally unions in
220
+ * UNBOUND device-scoped caps that declare `status.empty` (the synthesize-target
221
+ * marker) and whose `deviceTypes` admit this device's persisted type — so the
222
+ * FIRST link to a synthesize-only cap (consumables on an HA vacuum) can be
223
+ * authored before any binding exists. Default (absent/false) behavior is
224
+ * byte-identical to the binding-driven catalog.
218
225
  */
219
226
  export declare function getWireableFields(deps: AggregationDeps, input: Parameters<IDeviceManagerProvider['getWireableFields']>[0]): ReturnType<IDeviceManagerProvider['getWireableFields']>;
220
227
  export {};
@@ -14,6 +14,9 @@ export interface BindingsDeps {
14
14
  readonly ctx: AddonContext;
15
15
  readonly capabilityRegistry: ICapabilityRegistry | undefined;
16
16
  readonly remoteNativeCaps: RemoteNativeCaps;
17
+ /** O(1) gate over devices that have `deviceLinks` (the addon's live set).
18
+ * Optional: when absent, getBindings adds no virtual `linked` entries. */
19
+ readonly devicesWithLinks?: ReadonlySet<number>;
17
20
  }
18
21
  /**
19
22
  * Wire the push-fed cross-process native-cap cache (`remoteNativeCaps`).
@@ -0,0 +1,18 @@
1
+ import { DeviceLink } from '@camstack/types';
2
+ /** The slice of a persisted meta row cycle detection needs. Structural subset
3
+ * of `PersistedDeviceMeta` so tests (and future callers) can pass plain rows. */
4
+ export interface LinkCycleMetaRow {
5
+ readonly id: number;
6
+ readonly stableId: string;
7
+ readonly parentDeviceId: number | null;
8
+ readonly deviceLinks?: readonly DeviceLink[];
9
+ }
10
+ /** Node key of the dependency graph: `${deviceId}:${cap}`. */
11
+ type NodeKey = string;
12
+ /**
13
+ * Detect whether replacing `editedDeviceId`'s links with `editedLinks` closes
14
+ * a dependency cycle. Returns the cycle as an ordered list of node keys
15
+ * (first node repeated at the end) or null when the set is safe.
16
+ */
17
+ export declare function findDeviceLinkCycle(allMeta: Record<string, LinkCycleMetaRow>, editedDeviceId: number, editedLinks: readonly DeviceLink[]): readonly NodeKey[] | null;
18
+ export {};
@@ -2,7 +2,12 @@ import { DeviceLink } from '@camstack/types';
2
2
  /** A target link with its source already resolved to a live numeric id. */
3
3
  export interface ResolvedTargetLink {
4
4
  readonly link: DeviceLink;
5
+ /** Resolved source device id for scalar sources; `-1` sentinel for a literal
6
+ * source AND for an expression source (whose per-binding ids live below). */
5
7
  readonly sourceDeviceId: number;
8
+ /** Expression links only: binding name → resolved source device id. Literal
9
+ * bindings and unresolved bindings are ABSENT from the map. */
10
+ readonly bindingSourceIds?: Readonly<Record<string, number>>;
6
11
  }
7
12
  /** A target that depends on a given source slice (for reactive re-emit). */
8
13
  export interface DependentLink {
@@ -15,9 +20,14 @@ export interface LinkIndexes {
15
20
  /** `${sourceDeviceId}:${sourceCap}` → targets to recompute on a source change. */
16
21
  readonly dependents: Map<string, DependentLink[]>;
17
22
  }
23
+ /** One resolved link entry fed to `buildLinkIndexes`. */
24
+ export interface LinkIndexEntry {
25
+ readonly targetDeviceId: number;
26
+ readonly link: DeviceLink;
27
+ /** Resolved scalar source id; `-1` for literal/expression sources. */
28
+ readonly sourceDeviceId: number;
29
+ /** Expression links: binding name → resolved source device id. */
30
+ readonly bindingSourceIds?: Readonly<Record<string, number>>;
31
+ }
18
32
  /** Build both lookup maps from resolved link entries. Pure — order-preserving. */
19
- export declare function buildLinkIndexes(entries: ReadonlyArray<{
20
- targetDeviceId: number;
21
- link: DeviceLink;
22
- sourceDeviceId: number;
23
- }>): LinkIndexes;
33
+ export declare function buildLinkIndexes(entries: ReadonlyArray<LinkIndexEntry>): LinkIndexes;
@@ -1,15 +1,44 @@
1
1
  import { z } from 'zod';
2
- import { DeviceLink } from '@camstack/types';
2
+ import { CapabilityStatusItemArray, DeviceLink } from '@camstack/types';
3
+ import { ResolvedTargetLink } from './device-link-overlay.js';
3
4
  /** One resolved source reading paired with the link that produced it. The
4
5
  * `sourceValue` is already transformed; `undefined` skips the overlay. */
5
6
  export interface ResolvedLinkValue {
6
7
  readonly link: DeviceLink;
7
8
  readonly sourceValue: unknown;
8
9
  }
10
+ /**
11
+ * Resolve one link's transformed value. Field sources read via `readField`
12
+ * (the caller supplies the sibling lookup — sync, e.g. the in-hub state
13
+ * mirror); literal sources use their per-device constant and never consult
14
+ * the reader. Returns `undefined` to skip the overlay (unreadable field).
15
+ * Pure. Async source reads (`resolveLinkedStatus`'s provider `getStatus`
16
+ * path) cannot use this helper for field sources — they branch on
17
+ * `source.kind` directly.
18
+ */
19
+ export declare function resolveLinkValue(link: DeviceLink, readField: (cap: string, fieldPath: string) => unknown): unknown;
20
+ /**
21
+ * Sync-channel expression resolution: bindings read from the mirror via
22
+ * `readField(deviceId, cap, fieldPath)` using the pre-resolved per-binding ids
23
+ * on the `ResolvedTargetLink`. A literal binding uses its constant; an absent
24
+ * binding id (dangling source) or a non-primitive read resolves to `null`.
25
+ * Returns `undefined` on parse/eval failure (skip overlay) — silent by design:
26
+ * the mirror path is hot and churn-free; the async channel logs. Compile
27
+ * failures cost one parse ever thanks to the negative LRU compile cache. Pure.
28
+ */
29
+ export declare function resolveExpressionLinkValue(rl: ResolvedTargetLink, readField: (deviceId: number, cap: string, fieldPath: string) => unknown, now: number): unknown;
9
30
  /**
10
31
  * Overlay transformed source values onto `base` by dot-path, then validate the
11
32
  * result against the target cap's `statusSchema`. On validation failure the
12
33
  * overlay is discarded and `base` is returned unchanged (a misconfigured link
13
34
  * must never corrupt a cap response). Pure — all I/O happens in the caller.
35
+ *
36
+ * Item-array grouping (P2b): when the cap declares `status.itemArray`, links
37
+ * carrying a `target.itemKey` are grouped per key and upserted as items into
38
+ * the array (see `upsertItemArrayLinks`); their `fieldPath` is relative to
39
+ * ONE item ('level', 'status', 'label'). Links WITHOUT an itemKey keep the
40
+ * scalar dot-path behavior unchanged. A link with an itemKey on a cap that
41
+ * declares NO `itemArray` is dropped (its item-relative path must never be
42
+ * scalar-applied to the status root).
14
43
  */
15
- export declare function mergeLinkedStatus(base: Record<string, unknown>, resolved: readonly ResolvedLinkValue[], statusSchema?: z.ZodType): Record<string, unknown>;
44
+ export declare function mergeLinkedStatus(base: Record<string, unknown>, resolved: readonly ResolvedLinkValue[], statusSchema?: z.ZodType, itemArray?: CapabilityStatusItemArray): Record<string, unknown>;
@@ -59,10 +59,15 @@ export declare class DeviceManagerAddon extends BaseAddon {
59
59
  private linkTargets;
60
60
  /** `${sourceDeviceId}:${sourceCap}` → targets to recompute when that source changes. */
61
61
  private linkDependents;
62
- /** Expected source `stableId`s (`${container}-${sourceKey}`) across all links,
62
+ /** Expected source `stableId`s (`${container}-${sourceKey}` for sibling
63
+ * sources, the full `sourceStableId` for global sources) across all links,
63
64
  * resolved or not — gates the `registerDevice` rebuild so only a registering
64
65
  * device that IS a link source triggers a reindex (not every boot restore). */
65
66
  private expectedSourceStableIds;
67
+ /** In-flight `${deviceId}:${cap}` pairs of `resolveLinkedStatus` — the P2e
68
+ * defensive re-entrancy guard bounding link cycles at resolve time. Mutated
69
+ * in place (never reassigned), so the host exposes a direct reference. */
70
+ private readonly linkResolveInFlight;
66
71
  /** Test/diagnostic accessors. */
67
72
  linkTargetKeys(): string[];
68
73
  linkDependentsKeys(): string[];
@@ -104,7 +109,9 @@ export declare class DeviceManagerAddon extends BaseAddon {
104
109
  private discoveryProviderLabel;
105
110
  /** Provider ids that report `supportsDiscovery() === true` (probed in parallel). */
106
111
  private listDiscoveryCapableProviderIds;
107
- /** Build the dependency context the extracted binding resolvers consume. */
112
+ /** Build the dependency context the extracted binding resolvers consume.
113
+ * `devicesWithLinks` is mutated in place (never reassigned) so the direct
114
+ * reference stays live — it gates the virtual `linked` binding step. */
108
115
  private get bindingsDeps();
109
116
  getBindings(input: {
110
117
  deviceId: number;