@camstack/system 1.2.122 → 1.2.124

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.
Files changed (59) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  14. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  15. package/dist/builtins/device-manager/device-bindings-store.d.ts +20 -0
  16. package/dist/builtins/device-manager/device-linked-devices.d.ts +16 -0
  17. package/dist/builtins/device-manager/device-manager.addon.d.ts +6 -0
  18. package/dist/builtins/device-manager/device-manager.addon.js +73 -7
  19. package/dist/builtins/device-manager/device-manager.addon.mjs +73 -7
  20. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  21. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  22. package/dist/builtins/hub-forwarder/index.js +1 -1
  23. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  24. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  25. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  26. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  27. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  28. package/dist/builtins/local-network/local-network.addon.js +1 -1
  29. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  30. package/dist/builtins/loki-logging/index.js +1 -1
  31. package/dist/builtins/loki-logging/index.mjs +1 -1
  32. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  33. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  34. package/dist/builtins/platform-probe/index.js +1 -1
  35. package/dist/builtins/platform-probe/index.mjs +1 -1
  36. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  37. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  38. package/dist/builtins/snapshot/index.js +1 -1
  39. package/dist/builtins/snapshot/index.mjs +1 -1
  40. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  41. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  42. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +2 -2
  43. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
  44. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  45. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  46. package/dist/builtins/system-config/system-config.addon.js +1 -1
  47. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  48. package/dist/builtins/winston-logging/index.js +1 -1
  49. package/dist/builtins/winston-logging/index.mjs +1 -1
  50. package/dist/{dist-DnhGRFEn.js → dist-CIDgcxPk.js} +97 -21
  51. package/dist/{dist-CEhrHp3u.mjs → dist-CvJrtLkw.mjs} +97 -21
  52. package/dist/index.js +2 -2
  53. package/dist/index.mjs +2 -2
  54. package/dist/kernel/heap-watch.d.ts +80 -1
  55. package/dist/{manifest-python-deps-DZSNlHjU.mjs → manifest-python-deps-D1Ydw7J0.mjs} +93 -8
  56. package/dist/{manifest-python-deps-76dZLxD0.js → manifest-python-deps-DB_4H3tR.js} +93 -8
  57. package/dist/{retired-settings-keys-VAyOhu_a.mjs → retired-settings-keys-B1aZcoS0.mjs} +1 -1
  58. package/dist/{retired-settings-keys-D6Jy_SsO.js → retired-settings-keys-D7c0n9jO.js} +1 -1
  59. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_manifest_python_deps = require("./manifest-python-deps-76dZLxD0.js");
2
+ const require_manifest_python_deps = require("./manifest-python-deps-DB_4H3tR.js");
3
3
  const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
4
4
  let node_path = require("node:path");
5
5
  node_path = require_chunk.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { I as createUdsLoggerWithControl, L as LocalChildClient, Pt as startRunnerHeapWatch, at as setWorkerNativeCapsChangeListener, bt as installManifestNativeDeps, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps, xt as resolveAddonClass } from "./manifest-python-deps-DZSNlHjU.mjs";
1
+ import { I as createUdsLoggerWithControl, L as LocalChildClient, Pt as startRunnerHeapWatch, at as setWorkerNativeCapsChangeListener, bt as installManifestNativeDeps, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps, xt as resolveAddonClass } from "./manifest-python-deps-D1Ydw7J0.mjs";
2
2
  import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
3
3
  import { register } from "node:module";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DnhGRFEn.js");
6
+ const require_dist = require("../../dist-CIDgcxPk.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Gt as EventCategory, v as addonPagesCapability, vt as errMsg, yt as BaseAddon } from "../../dist-CEhrHp3u.mjs";
1
+ import { Gt as EventCategory, v as addonPagesCapability, vt as errMsg, yt as BaseAddon } from "../../dist-CvJrtLkw.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DnhGRFEn.js");
6
+ const require_dist = require("../../dist-CIDgcxPk.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Gt as EventCategory, b as addonWidgetsCapability, vt as errMsg, yt as BaseAddon } from "../../dist-CEhrHp3u.mjs";
1
+ import { Gt as EventCategory, b as addonWidgetsCapability, vt as errMsg, yt as BaseAddon } from "../../dist-CvJrtLkw.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DnhGRFEn.js");
6
+ const require_dist = require("../../dist-CIDgcxPk.js");
7
7
  //#region src/builtins/alerts/prune-policy.ts
8
8
  /**
9
9
  * Ids older than `cutoffMs`, oldest first.
@@ -1,4 +1,4 @@
1
- import { Gt as EventCategory, Mt as createEvent, n as AlertSchema, vt as errMsg, x as alertsCapability, yt as BaseAddon } from "../../dist-CEhrHp3u.mjs";
1
+ import { Gt as EventCategory, Mt as createEvent, n as AlertSchema, vt as errMsg, x as alertsCapability, yt as BaseAddon } from "../../dist-CvJrtLkw.mjs";
2
2
  //#region src/builtins/alerts/prune-policy.ts
3
3
  /**
4
4
  * Ids older than `cutoffMs`, oldest first.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DnhGRFEn.js");
6
+ const require_dist = require("../../dist-CIDgcxPk.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let buffer = require("buffer");
9
9
  let node_fs_promises = require("node:fs/promises");
@@ -1,4 +1,4 @@
1
- import { C as backupCapability, Gt as EventCategory, yt as BaseAddon } from "../../dist-CEhrHp3u.mjs";
1
+ import { C as backupCapability, Gt as EventCategory, yt as BaseAddon } from "../../dist-CvJrtLkw.mjs";
2
2
  import { randomBytes, randomUUID } from "node:crypto";
3
3
  import { Buffer as Buffer$1 } from "buffer";
4
4
  import * as fsp from "node:fs/promises";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DnhGRFEn.js");
6
+ const require_dist = require("../../dist-CIDgcxPk.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/console-logging/console-destination.ts
9
9
  var LEVEL_RANK = {
@@ -1,4 +1,4 @@
1
- import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-CEhrHp3u.mjs";
1
+ import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-CvJrtLkw.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/console-logging/console-destination.ts
4
4
  var LEVEL_RANK = {
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-DnhGRFEn.js");
3
+ const require_dist = require("../../dist-CIDgcxPk.js");
4
4
  let node_crypto = require("node:crypto");
5
5
  let node_fs_promises = require("node:fs/promises");
6
6
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -1,4 +1,4 @@
1
- import { D as coreBlockAddonId, O as coreBlockIdFromAddonId, c as CoreBlockSchema, k as coreBlocksCapability, l as DeclaredDevices, o as CORE_BLOCKS_ADDON_ID, s as CORE_BLOCK_ADDON_PREFIX, vt as errMsg, yt as BaseAddon } from "../../dist-CEhrHp3u.mjs";
1
+ import { D as coreBlockAddonId, O as coreBlockIdFromAddonId, c as CoreBlockSchema, k as coreBlocksCapability, l as DeclaredDevices, o as CORE_BLOCKS_ADDON_ID, s as CORE_BLOCK_ADDON_PREFIX, vt as errMsg, yt as BaseAddon } from "../../dist-CvJrtLkw.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import * as fs from "node:fs/promises";
4
4
  import * as path$1 from "node:path";
@@ -128,6 +128,26 @@ export declare function getBindings(deps: BindingsDeps, input: {
128
128
  deviceId: number;
129
129
  entries: DeviceBindingEntry[];
130
130
  }>;
131
+ /**
132
+ * A NAMED set of devices' bindings, in one round-trip.
133
+ *
134
+ * Same resolver as `getBindings`, so the routing rules and the per-device
135
+ * shape are identical by construction. It exists because the gap between one
136
+ * device and all 988 had nothing in it: measured on the live hub 2026-08-25, a
137
+ * caller needing seventy-five paid either 75 round-trips (1211 ms / 118 KB) or
138
+ * `getAllBindings` (511 ms / 679 KB of mostly-unwanted rows).
139
+ *
140
+ * Sequential on purpose, like `getAllBindings`: `getBindings` is a pair of
141
+ * store reads on this process's own event loop, so concurrency buys nothing
142
+ * and a fan-out of `Promise.all` over hundreds of ids only makes the queue
143
+ * less fair to everything else on it.
144
+ */
145
+ export declare function getBindingsBatch(deps: BindingsDeps, input: {
146
+ deviceIds: readonly number[];
147
+ }): Promise<Array<{
148
+ deviceId: number;
149
+ entries: DeviceBindingEntry[];
150
+ }>>;
131
151
  /**
132
152
  * Whole-fleet binding dump. Iterates every device known to the
133
153
  * deviceRegistry and reuses the per-device `getBindings` resolver
@@ -43,8 +43,24 @@ export declare function resolveLinkedDeviceIds(params: {
43
43
  * are ignored.
44
44
  */
45
45
  export declare function resolveTrackedEventDeviceIds(config: Record<string, unknown>, linkedIds: readonly number[]): readonly number[];
46
+ /** The single resolved answer, before it is tagged with its camera. */
47
+ export type ResolvedLinkedDevices = Awaited<ReturnType<IDeviceManagerProvider['getLinkedDevices']>>;
46
48
  /** Cap implementation of `getLinkedDevices`. */
47
49
  export declare function getLinkedDevices(pctx: ProviderContext, input: Parameters<IDeviceManagerProvider['getLinkedDevices']>[0]): ReturnType<IDeviceManagerProvider['getLinkedDevices']>;
50
+ /**
51
+ * Cap implementation of `getLinkedDevicesBatch` — ONE fleet listing for the
52
+ * whole set.
53
+ *
54
+ * That listing is the query's cost (see `resolveFor`), and asking per camera
55
+ * does not amortise it: on the live hub 2026-08-25, 30 concurrent
56
+ * `getLinkedDevices` took 4110 ms wall — a queue of thirty full-fleet sweeps
57
+ * on device-manager's event loop — to produce 7260 bytes.
58
+ *
59
+ * Ids are deduped and the answer is one row per REQUESTED id, in the order
60
+ * asked: a camera whose settings read fails still gets a row (resolved against
61
+ * an empty config, i.e. children-only) rather than dropping out of the array.
62
+ */
63
+ export declare function getLinkedDevicesBatch(pctx: ProviderContext, input: Parameters<IDeviceManagerProvider['getLinkedDevicesBatch']>[0]): ReturnType<IDeviceManagerProvider['getLinkedDevicesBatch']>;
48
64
  /**
49
65
  * "Linked devices" settings section for a CAMERA (null for every other
50
66
  * device type — the association is camera-anchored). Contributed through
@@ -91,6 +91,12 @@ export declare class DeviceManagerAddon extends BaseAddon {
91
91
  deviceId: number;
92
92
  entries: DeviceBindingEntry[];
93
93
  }>;
94
+ getBindingsBatch(input: {
95
+ deviceIds: readonly number[];
96
+ }): Promise<Array<{
97
+ deviceId: number;
98
+ entries: DeviceBindingEntry[];
99
+ }>>;
94
100
  getAllBindings(): Promise<Array<{
95
101
  deviceId: number;
96
102
  entries: DeviceBindingEntry[];
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DnhGRFEn.js");
7
- const require_retired_settings_keys = require("../../retired-settings-keys-D6Jy_SsO.js");
6
+ const require_dist = require("../../dist-CIDgcxPk.js");
7
+ const require_retired_settings_keys = require("../../retired-settings-keys-D7c0n9jO.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let _camstack_types_node = require("@camstack/types/node");
10
10
  //#region src/builtins/device-manager/adoption-job-engine.ts
@@ -697,6 +697,25 @@ async function getBindings(deps, input) {
697
697
  };
698
698
  }
699
699
  /**
700
+ * A NAMED set of devices' bindings, in one round-trip.
701
+ *
702
+ * Same resolver as `getBindings`, so the routing rules and the per-device
703
+ * shape are identical by construction. It exists because the gap between one
704
+ * device and all 988 had nothing in it: measured on the live hub 2026-08-25, a
705
+ * caller needing seventy-five paid either 75 round-trips (1211 ms / 118 KB) or
706
+ * `getAllBindings` (511 ms / 679 KB of mostly-unwanted rows).
707
+ *
708
+ * Sequential on purpose, like `getAllBindings`: `getBindings` is a pair of
709
+ * store reads on this process's own event loop, so concurrency buys nothing
710
+ * and a fan-out of `Promise.all` over hundreds of ids only makes the queue
711
+ * less fair to everything else on it.
712
+ */
713
+ async function getBindingsBatch(deps, input) {
714
+ const out = [];
715
+ for (const deviceId of new Set(input.deviceIds)) out.push(await getBindings(deps, { deviceId }));
716
+ return out;
717
+ }
718
+ /**
700
719
  * Whole-fleet binding dump. Iterates every device known to the
701
720
  * deviceRegistry and reuses the per-device `getBindings` resolver
702
721
  * for each — same routing rules, single round-trip. Used by
@@ -1267,6 +1286,7 @@ async function listAll(pctx, input) {
1267
1286
  const { addonId } = input;
1268
1287
  const slim = input.projection === "slim";
1269
1288
  const camerasOnly = input.isCamera === true;
1289
+ const idFilter = input.deviceIds === void 0 ? null : new Set(input.deviceIds);
1270
1290
  const results = [];
1271
1291
  const seen = /* @__PURE__ */ new Set();
1272
1292
  const fleet = addonId ? await pctx.metaStore.rows.listByAddon(addonId) : await pctx.metaStore.rows.listAll();
@@ -1282,6 +1302,7 @@ async function listAll(pctx, input) {
1282
1302
  const row = rowById.get(device.id);
1283
1303
  const info = toDeviceInfo(aid, device, row?.metadata ?? null, row?.meta ?? null);
1284
1304
  seen.add(key);
1305
+ if (idFilter !== null && !idFilter.has(device.id)) continue;
1285
1306
  if (camerasOnly && !info.isCamera) continue;
1286
1307
  results.push(slim ? {
1287
1308
  ...info,
@@ -1295,6 +1316,7 @@ async function listAll(pctx, input) {
1295
1316
  const aid = m.addonId;
1296
1317
  const stableId = m.stableId;
1297
1318
  if (seen.has(String(m.id))) continue;
1319
+ if (idFilter !== null && !idFilter.has(m.id)) continue;
1298
1320
  const persistedType = m.type;
1299
1321
  if (camerasOnly && persistedType !== require_dist.DeviceType.Camera) continue;
1300
1322
  const persistedConfig = slim ? {} : await pctx.settings.readDeviceStore(m.id);
@@ -1825,12 +1847,16 @@ function resolveTrackedEventDeviceIds(config, linkedIds) {
1825
1847
  }
1826
1848
  return linkedIds.filter((id) => picked.has(id));
1827
1849
  }
1828
- /** Cap implementation of `getLinkedDevices`. */
1829
- async function getLinkedDevices(pctx, input) {
1830
- const { deviceId } = input;
1831
- const [all, blob] = await Promise.all([listAll(pctx, {}), pctx.settings.readDeviceStore(deviceId)]);
1850
+ /**
1851
+ * Resolve ONE camera's linked set against an ALREADY-FETCHED fleet.
1852
+ *
1853
+ * The fleet is a parameter, not a fetch, because it is the entire cost of this
1854
+ * query: candidates are "children ∪ same-location", which needs every device.
1855
+ * Single-device and batch differ only in how many times that listing is paid
1856
+ * for — never in the policy, which lives here and nowhere else.
1857
+ */
1858
+ function resolveFor(deviceId, all, byId, blob) {
1832
1859
  const config = parseLinkedDevicesConfig(blob);
1833
- const byId = new Map(all.map((d) => [d.id, d]));
1834
1860
  const ids = resolveLinkedDeviceIds({
1835
1861
  selfId: deviceId,
1836
1862
  selfLocation: byId.get(deviceId)?.location ?? null,
@@ -1855,6 +1881,41 @@ async function getLinkedDevices(pctx, input) {
1855
1881
  devices
1856
1882
  };
1857
1883
  }
1884
+ /** Cap implementation of `getLinkedDevices`. */
1885
+ async function getLinkedDevices(pctx, input) {
1886
+ const { deviceId } = input;
1887
+ const [all, blob] = await Promise.all([listAll(pctx, {}), pctx.settings.readDeviceStore(deviceId)]);
1888
+ return resolveFor(deviceId, all, new Map(all.map((d) => [d.id, d])), blob);
1889
+ }
1890
+ /**
1891
+ * Cap implementation of `getLinkedDevicesBatch` — ONE fleet listing for the
1892
+ * whole set.
1893
+ *
1894
+ * That listing is the query's cost (see `resolveFor`), and asking per camera
1895
+ * does not amortise it: on the live hub 2026-08-25, 30 concurrent
1896
+ * `getLinkedDevices` took 4110 ms wall — a queue of thirty full-fleet sweeps
1897
+ * on device-manager's event loop — to produce 7260 bytes.
1898
+ *
1899
+ * Ids are deduped and the answer is one row per REQUESTED id, in the order
1900
+ * asked: a camera whose settings read fails still gets a row (resolved against
1901
+ * an empty config, i.e. children-only) rather than dropping out of the array.
1902
+ */
1903
+ async function getLinkedDevicesBatch(pctx, input) {
1904
+ const deviceIds = [...new Set(input.deviceIds)];
1905
+ if (deviceIds.length === 0) return [];
1906
+ const [all, blobs] = await Promise.all([listAll(pctx, {}), Promise.all(deviceIds.map((id) => pctx.settings.readDeviceStore(id).catch((err) => {
1907
+ pctx.host.ctx.logger.warn("getLinkedDevicesBatch: settings read failed — children only", {
1908
+ tags: { deviceId: id },
1909
+ meta: { error: err instanceof Error ? err.message : String(err) }
1910
+ });
1911
+ return {};
1912
+ })))]);
1913
+ const byId = new Map(all.map((d) => [d.id, d]));
1914
+ return deviceIds.map((deviceId, index) => ({
1915
+ deviceId,
1916
+ ...resolveFor(deviceId, all, byId, blobs[index] ?? {})
1917
+ }));
1918
+ }
1858
1919
  /**
1859
1920
  * "Linked devices" settings section for a CAMERA (null for every other
1860
1921
  * device type — the association is camera-anchored). Contributed through
@@ -4930,6 +4991,9 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
4930
4991
  async getBindings(input) {
4931
4992
  return getBindings(this.bindingsDeps, input);
4932
4993
  }
4994
+ async getBindingsBatch(input) {
4995
+ return getBindingsBatch(this.bindingsDeps, input);
4996
+ }
4933
4997
  async getAllBindings() {
4934
4998
  return getAllBindings(this.bindingsDeps);
4935
4999
  }
@@ -5041,6 +5105,7 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
5041
5105
  getDevice: (input) => getDevice(pctx, input),
5042
5106
  getChildren: (input) => getChildren(pctx, input),
5043
5107
  getLinkedDevices: (input) => getLinkedDevices(pctx, input),
5108
+ getLinkedDevicesBatch: (input) => getLinkedDevicesBatch(pctx, input),
5044
5109
  getDeviceSettingsContribution: (input) => buildLinkedDevicesContribution(pctx, input.deviceId),
5045
5110
  getDeviceLiveContribution: async () => null,
5046
5111
  applyDeviceSettingsPatch: (input) => applyLinkedDevicesPatch(pctx, input.deviceId, input.patch),
@@ -5119,6 +5184,7 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
5119
5184
  entries: result.entries
5120
5185
  };
5121
5186
  },
5187
+ getBindingsBatch: (input) => this.getBindingsBatch({ deviceIds: input.deviceIds }),
5122
5188
  getAllBindings: async () => {
5123
5189
  return this.getAllBindings();
5124
5190
  },
@@ -1,5 +1,5 @@
1
- import { Ct as DeviceFeature, E as buildStreamParamsConfigSchema, F as deviceStatusCapability, Gt as EventCategory, Lt as isDeviceConfigCap, N as deviceManagerCapability, Ot as WELL_KNOWN_TAB_MAP, P as deviceStateCapability, R as enumerateItemArrayFields, Tt as DeviceType, Wt as sleep, a as CAP_NAMES_WITH_STATUS, at as runtimeStatePolicyFor, et as normalizeUnit, nt as parseStreamParamsFormPatch, p as STREAM_PROFILE_META, t as ALL_CAPABILITY_DEFINITIONS, u as DeviceStatusSchema, vt as errMsg, wt as DeviceRole, yt as BaseAddon, z as enumerateSchemaFields } from "../../dist-CEhrHp3u.mjs";
2
- import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-VAyOhu_a.mjs";
1
+ import { Ct as DeviceFeature, E as buildStreamParamsConfigSchema, F as deviceStatusCapability, Gt as EventCategory, Lt as isDeviceConfigCap, N as deviceManagerCapability, Ot as WELL_KNOWN_TAB_MAP, P as deviceStateCapability, R as enumerateItemArrayFields, Tt as DeviceType, Wt as sleep, a as CAP_NAMES_WITH_STATUS, at as runtimeStatePolicyFor, et as normalizeUnit, nt as parseStreamParamsFormPatch, p as STREAM_PROFILE_META, t as ALL_CAPABILITY_DEFINITIONS, u as DeviceStatusSchema, vt as errMsg, wt as DeviceRole, yt as BaseAddon, z as enumerateSchemaFields } from "../../dist-CvJrtLkw.mjs";
2
+ import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-B1aZcoS0.mjs";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { canonicalDeviceFingerprint } from "@camstack/types/node";
5
5
  //#region src/builtins/device-manager/adoption-job-engine.ts
@@ -692,6 +692,25 @@ async function getBindings(deps, input) {
692
692
  };
693
693
  }
694
694
  /**
695
+ * A NAMED set of devices' bindings, in one round-trip.
696
+ *
697
+ * Same resolver as `getBindings`, so the routing rules and the per-device
698
+ * shape are identical by construction. It exists because the gap between one
699
+ * device and all 988 had nothing in it: measured on the live hub 2026-08-25, a
700
+ * caller needing seventy-five paid either 75 round-trips (1211 ms / 118 KB) or
701
+ * `getAllBindings` (511 ms / 679 KB of mostly-unwanted rows).
702
+ *
703
+ * Sequential on purpose, like `getAllBindings`: `getBindings` is a pair of
704
+ * store reads on this process's own event loop, so concurrency buys nothing
705
+ * and a fan-out of `Promise.all` over hundreds of ids only makes the queue
706
+ * less fair to everything else on it.
707
+ */
708
+ async function getBindingsBatch(deps, input) {
709
+ const out = [];
710
+ for (const deviceId of new Set(input.deviceIds)) out.push(await getBindings(deps, { deviceId }));
711
+ return out;
712
+ }
713
+ /**
695
714
  * Whole-fleet binding dump. Iterates every device known to the
696
715
  * deviceRegistry and reuses the per-device `getBindings` resolver
697
716
  * for each — same routing rules, single round-trip. Used by
@@ -1262,6 +1281,7 @@ async function listAll(pctx, input) {
1262
1281
  const { addonId } = input;
1263
1282
  const slim = input.projection === "slim";
1264
1283
  const camerasOnly = input.isCamera === true;
1284
+ const idFilter = input.deviceIds === void 0 ? null : new Set(input.deviceIds);
1265
1285
  const results = [];
1266
1286
  const seen = /* @__PURE__ */ new Set();
1267
1287
  const fleet = addonId ? await pctx.metaStore.rows.listByAddon(addonId) : await pctx.metaStore.rows.listAll();
@@ -1277,6 +1297,7 @@ async function listAll(pctx, input) {
1277
1297
  const row = rowById.get(device.id);
1278
1298
  const info = toDeviceInfo(aid, device, row?.metadata ?? null, row?.meta ?? null);
1279
1299
  seen.add(key);
1300
+ if (idFilter !== null && !idFilter.has(device.id)) continue;
1280
1301
  if (camerasOnly && !info.isCamera) continue;
1281
1302
  results.push(slim ? {
1282
1303
  ...info,
@@ -1290,6 +1311,7 @@ async function listAll(pctx, input) {
1290
1311
  const aid = m.addonId;
1291
1312
  const stableId = m.stableId;
1292
1313
  if (seen.has(String(m.id))) continue;
1314
+ if (idFilter !== null && !idFilter.has(m.id)) continue;
1293
1315
  const persistedType = m.type;
1294
1316
  if (camerasOnly && persistedType !== DeviceType.Camera) continue;
1295
1317
  const persistedConfig = slim ? {} : await pctx.settings.readDeviceStore(m.id);
@@ -1820,12 +1842,16 @@ function resolveTrackedEventDeviceIds(config, linkedIds) {
1820
1842
  }
1821
1843
  return linkedIds.filter((id) => picked.has(id));
1822
1844
  }
1823
- /** Cap implementation of `getLinkedDevices`. */
1824
- async function getLinkedDevices(pctx, input) {
1825
- const { deviceId } = input;
1826
- const [all, blob] = await Promise.all([listAll(pctx, {}), pctx.settings.readDeviceStore(deviceId)]);
1845
+ /**
1846
+ * Resolve ONE camera's linked set against an ALREADY-FETCHED fleet.
1847
+ *
1848
+ * The fleet is a parameter, not a fetch, because it is the entire cost of this
1849
+ * query: candidates are "children ∪ same-location", which needs every device.
1850
+ * Single-device and batch differ only in how many times that listing is paid
1851
+ * for — never in the policy, which lives here and nowhere else.
1852
+ */
1853
+ function resolveFor(deviceId, all, byId, blob) {
1827
1854
  const config = parseLinkedDevicesConfig(blob);
1828
- const byId = new Map(all.map((d) => [d.id, d]));
1829
1855
  const ids = resolveLinkedDeviceIds({
1830
1856
  selfId: deviceId,
1831
1857
  selfLocation: byId.get(deviceId)?.location ?? null,
@@ -1850,6 +1876,41 @@ async function getLinkedDevices(pctx, input) {
1850
1876
  devices
1851
1877
  };
1852
1878
  }
1879
+ /** Cap implementation of `getLinkedDevices`. */
1880
+ async function getLinkedDevices(pctx, input) {
1881
+ const { deviceId } = input;
1882
+ const [all, blob] = await Promise.all([listAll(pctx, {}), pctx.settings.readDeviceStore(deviceId)]);
1883
+ return resolveFor(deviceId, all, new Map(all.map((d) => [d.id, d])), blob);
1884
+ }
1885
+ /**
1886
+ * Cap implementation of `getLinkedDevicesBatch` — ONE fleet listing for the
1887
+ * whole set.
1888
+ *
1889
+ * That listing is the query's cost (see `resolveFor`), and asking per camera
1890
+ * does not amortise it: on the live hub 2026-08-25, 30 concurrent
1891
+ * `getLinkedDevices` took 4110 ms wall — a queue of thirty full-fleet sweeps
1892
+ * on device-manager's event loop — to produce 7260 bytes.
1893
+ *
1894
+ * Ids are deduped and the answer is one row per REQUESTED id, in the order
1895
+ * asked: a camera whose settings read fails still gets a row (resolved against
1896
+ * an empty config, i.e. children-only) rather than dropping out of the array.
1897
+ */
1898
+ async function getLinkedDevicesBatch(pctx, input) {
1899
+ const deviceIds = [...new Set(input.deviceIds)];
1900
+ if (deviceIds.length === 0) return [];
1901
+ const [all, blobs] = await Promise.all([listAll(pctx, {}), Promise.all(deviceIds.map((id) => pctx.settings.readDeviceStore(id).catch((err) => {
1902
+ pctx.host.ctx.logger.warn("getLinkedDevicesBatch: settings read failed — children only", {
1903
+ tags: { deviceId: id },
1904
+ meta: { error: err instanceof Error ? err.message : String(err) }
1905
+ });
1906
+ return {};
1907
+ })))]);
1908
+ const byId = new Map(all.map((d) => [d.id, d]));
1909
+ return deviceIds.map((deviceId, index) => ({
1910
+ deviceId,
1911
+ ...resolveFor(deviceId, all, byId, blobs[index] ?? {})
1912
+ }));
1913
+ }
1853
1914
  /**
1854
1915
  * "Linked devices" settings section for a CAMERA (null for every other
1855
1916
  * device type — the association is camera-anchored). Contributed through
@@ -4925,6 +4986,9 @@ var DeviceManagerAddon = class extends BaseAddon {
4925
4986
  async getBindings(input) {
4926
4987
  return getBindings(this.bindingsDeps, input);
4927
4988
  }
4989
+ async getBindingsBatch(input) {
4990
+ return getBindingsBatch(this.bindingsDeps, input);
4991
+ }
4928
4992
  async getAllBindings() {
4929
4993
  return getAllBindings(this.bindingsDeps);
4930
4994
  }
@@ -5036,6 +5100,7 @@ var DeviceManagerAddon = class extends BaseAddon {
5036
5100
  getDevice: (input) => getDevice(pctx, input),
5037
5101
  getChildren: (input) => getChildren(pctx, input),
5038
5102
  getLinkedDevices: (input) => getLinkedDevices(pctx, input),
5103
+ getLinkedDevicesBatch: (input) => getLinkedDevicesBatch(pctx, input),
5039
5104
  getDeviceSettingsContribution: (input) => buildLinkedDevicesContribution(pctx, input.deviceId),
5040
5105
  getDeviceLiveContribution: async () => null,
5041
5106
  applyDeviceSettingsPatch: (input) => applyLinkedDevicesPatch(pctx, input.deviceId, input.patch),
@@ -5114,6 +5179,7 @@ var DeviceManagerAddon = class extends BaseAddon {
5114
5179
  entries: result.entries
5115
5180
  };
5116
5181
  },
5182
+ getBindingsBatch: (input) => this.getBindingsBatch({ deviceIds: input.deviceIds }),
5117
5183
  getAllBindings: async () => {
5118
5184
  return this.getAllBindings();
5119
5185
  },
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DnhGRFEn.js");
6
+ const require_dist = require("../../dist-CIDgcxPk.js");
7
7
  //#region src/builtins/doorbell/trigger-engine.ts
8
8
  /**
9
9
  * Pure trigger logic for the virtual-doorbell builtin — edge detection over
@@ -1,4 +1,4 @@
1
- import { G as isSameAddonId, Gt as EventCategory, I as doorbellCapability, Mt as createEvent, Tt as DeviceType, vt as errMsg, w as bareAddonId, yt as BaseAddon } from "../../dist-CEhrHp3u.mjs";
1
+ import { G as isSameAddonId, Gt as EventCategory, I as doorbellCapability, Mt as createEvent, Tt as DeviceType, vt as errMsg, w as bareAddonId, yt as BaseAddon } from "../../dist-CvJrtLkw.mjs";
2
2
  //#region src/builtins/doorbell/trigger-engine.ts
3
3
  /**
4
4
  * Pure trigger logic for the virtual-doorbell builtin — edge detection over
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DnhGRFEn.js");
6
+ const require_dist = require("../../dist-CIDgcxPk.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
9
9
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -1,4 +1,4 @@
1
- import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-CEhrHp3u.mjs";
1
+ import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-CvJrtLkw.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
4
4
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-DnhGRFEn.js");
3
+ const require_dist = require("../../dist-CIDgcxPk.js");
4
4
  //#region src/builtins/liveness-monitor/liveness-checks.ts
5
5
  var NO_DEVICES = "liveness:no-devices";
6
6
  var ALL_OFFLINE = "liveness:all-devices-offline";
@@ -1,4 +1,4 @@
1
- import { Gt as EventCategory, Mt as createEvent, vt as errMsg, yt as BaseAddon } from "../../dist-CEhrHp3u.mjs";
1
+ import { Gt as EventCategory, Mt as createEvent, vt as errMsg, yt as BaseAddon } from "../../dist-CvJrtLkw.mjs";
2
2
  //#region src/builtins/liveness-monitor/liveness-checks.ts
3
3
  var NO_DEVICES = "liveness:no-devices";
4
4
  var ALL_OFFLINE = "liveness:all-devices-offline";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DnhGRFEn.js");
6
+ const require_dist = require("../../dist-CIDgcxPk.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  node_crypto = require_chunk.__toESM(node_crypto);
9
9
  let crypto$1 = require("crypto");
@@ -1,5 +1,5 @@
1
1
  import { i as __require, o as __toESM, t as __commonJSMin } from "../../chunk-CNf5ZN-e.mjs";
2
- import { S as authProviderCapability, _ as UserRecordSchema, ht as userManagementCapability, m as ScopedTokenSchema, r as ApiKeyRecordSchema, yt as BaseAddon } from "../../dist-CEhrHp3u.mjs";
2
+ import { S as authProviderCapability, _ as UserRecordSchema, ht as userManagementCapability, m as ScopedTokenSchema, r as ApiKeyRecordSchema, yt as BaseAddon } from "../../dist-CvJrtLkw.mjs";
3
3
  import * as crypto$2 from "node:crypto";
4
4
  import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
5
5
  import nodeCrypto from "crypto";
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-DnhGRFEn.js");
3
+ const require_dist = require("../../dist-CIDgcxPk.js");
4
4
  const require_lan_http_bind = require("../../lan-http-bind-DmgpFP6_.js");
5
5
  let node_fs_promises = require("node:fs/promises");
6
6
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Gt as EventCategory, Y as localNetworkCapability, yt as BaseAddon } from "../../dist-CEhrHp3u.mjs";
1
+ import { Gt as EventCategory, Y as localNetworkCapability, yt as BaseAddon } from "../../dist-CvJrtLkw.mjs";
2
2
  import { c as readExtraSans, d as writeExtraSans, f as writeTlsMode, g as reissueTlsLeaf, l as readTlsAccessStatus, m as ensureTlsCert, o as readLanHttpState, p as validateUploadedTls, r as applyLanHttp, t as DEFAULT_LAN_HTTP_PORT, u as readTlsMode } from "../../lan-http-bind-jKrj6OjQ.mjs";
3
3
  import { mkdir, writeFile } from "node:fs/promises";
4
4
  import { join } from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DnhGRFEn.js");
6
+ const require_dist = require("../../dist-CIDgcxPk.js");
7
7
  //#region src/builtins/loki-logging/loki-payload.ts
8
8
  /**
9
9
  * Loki rejects a label name that is not a valid Prometheus label
@@ -1,4 +1,4 @@
1
- import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-CEhrHp3u.mjs";
1
+ import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-CvJrtLkw.mjs";
2
2
  //#region src/builtins/loki-logging/loki-payload.ts
3
3
  /**
4
4
  * Loki rejects a label name that is not a valid Prometheus label
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DnhGRFEn.js");
6
+ const require_dist = require("../../dist-CIDgcxPk.js");
7
7
  let node_child_process = require("node:child_process");
8
8
  let node_util = require("node:util");
9
9
  let node_os = require("node:os");
@@ -1,4 +1,4 @@
1
- import { $ as metricsProviderCapability, Gt as EventCategory, Mt as createEvent, yt as BaseAddon } from "../../dist-CEhrHp3u.mjs";
1
+ import { $ as metricsProviderCapability, Gt as EventCategory, Mt as createEvent, yt as BaseAddon } from "../../dist-CvJrtLkw.mjs";
2
2
  import { execFile, execFileSync } from "node:child_process";
3
3
  import { promisify } from "node:util";
4
4
  import * as os from "node:os";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DnhGRFEn.js");
6
+ const require_dist = require("../../dist-CIDgcxPk.js");
7
7
  let node_child_process = require("node:child_process");
8
8
  let node_util = require("node:util");
9
9
  let node_os = require("node:os");
@@ -1,5 +1,5 @@
1
1
  import { o as __toESM } from "../../chunk-CNf5ZN-e.mjs";
2
- import { Gt as EventCategory, L as enumerateInferenceDevices, Pt as emitReadiness, ct as scoreRuntimes, rt as platformProbeCapability, vt as errMsg, yt as BaseAddon } from "../../dist-CEhrHp3u.mjs";
2
+ import { Gt as EventCategory, L as enumerateInferenceDevices, Pt as emitReadiness, ct as scoreRuntimes, rt as platformProbeCapability, vt as errMsg, yt as BaseAddon } from "../../dist-CvJrtLkw.mjs";
3
3
  import { execFile } from "node:child_process";
4
4
  import { promisify } from "node:util";
5
5
  import * as os from "node:os";