@camstack/system 1.2.135 → 1.2.137

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 (53) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  8. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  9. package/dist/builtins/console-logging/index.js +1 -1
  10. package/dist/builtins/console-logging/index.mjs +1 -1
  11. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  12. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  13. package/dist/builtins/device-manager/device-bindings-store.d.ts +10 -4
  14. package/dist/builtins/device-manager/device-manager.addon.js +136 -27
  15. package/dist/builtins/device-manager/device-manager.addon.mjs +136 -27
  16. package/dist/builtins/device-manager/device-row-store.d.ts +65 -0
  17. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  18. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  19. package/dist/builtins/hub-forwarder/index.js +1 -1
  20. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  21. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  22. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  23. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  24. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  25. package/dist/builtins/local-network/local-network.addon.js +1 -1
  26. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  27. package/dist/builtins/loki-logging/index.js +1 -1
  28. package/dist/builtins/loki-logging/index.mjs +1 -1
  29. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  30. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  31. package/dist/builtins/platform-probe/index.js +1 -1
  32. package/dist/builtins/platform-probe/index.mjs +1 -1
  33. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  34. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  35. package/dist/builtins/snapshot/index.js +1 -1
  36. package/dist/builtins/snapshot/index.mjs +1 -1
  37. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  38. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  39. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  40. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  41. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  42. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  43. package/dist/builtins/system-config/system-config.addon.js +1 -1
  44. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  45. package/dist/builtins/winston-logging/index.js +1 -1
  46. package/dist/builtins/winston-logging/index.mjs +1 -1
  47. package/dist/{dist-Bh25kEUH.mjs → dist-DMOTtKH8.mjs} +310 -63
  48. package/dist/{dist-D-R0SuP-.js → dist-mQ3BkrJd.js} +310 -63
  49. package/dist/index.js +1 -1
  50. package/dist/index.mjs +1 -1
  51. package/dist/{retired-settings-keys-BZbOXqch.mjs → retired-settings-keys-CPRvyt-E.mjs} +1 -1
  52. package/dist/{retired-settings-keys-CqD6rSQx.js → retired-settings-keys-Dvkcu_zf.js} +1 -1
  53. package/package.json +1 -1
@@ -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-Bh25kEUH.mjs";
2
- import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-BZbOXqch.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-DMOTtKH8.mjs";
2
+ import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-CPRvyt-E.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
@@ -589,16 +589,33 @@ function capAppliesToDeviceType(def, deviceType) {
589
589
  return declared.some((t) => t === deviceType);
590
590
  }
591
591
  async function getBindings(deps, input) {
592
- const storeKey = String(input.deviceId);
593
- const perDevice = (await readBindingsStore(deps)).deviceBindings[storeKey] ?? {};
594
592
  const row = await deps.rows.get(input.deviceId);
595
- const deviceType = resolveDeviceType(deps, row, input.deviceId);
596
- const presence = await resolveDevicePresence(deps, row, input.deviceId);
593
+ return resolveBindingsForDevice(deps, input.deviceId, row);
594
+ }
595
+ /**
596
+ * The `getBindings` resolver, taking the device's row as a PARAMETER instead
597
+ * of fetching it.
598
+ *
599
+ * `getBindings` (one device) and `getBindingsBatch` / `getAllBindings` (many)
600
+ * share this — the routing rules and the per-device shape stay identical by
601
+ * construction. The split exists so the batch callers can resolve every row
602
+ * they need in ONE store round trip ({@link DeviceRowStore.getMany} /
603
+ * {@link DeviceRowStore.listAll}) instead of looping this function's own
604
+ * `deps.rows.get(deviceId)`, which is the primary-key-lookup-per-loop-
605
+ * iteration shape measured on the live hub 2026-08-25: `get
606
+ * device-manager:devices` at 1 656 calls/min steady-state and 17 687 during
607
+ * boot, one row per call.
608
+ */
609
+ async function resolveBindingsForDevice(deps, deviceId, row) {
610
+ const storeKey = String(deviceId);
611
+ const perDevice = (await readBindingsStore(deps)).deviceBindings[storeKey] ?? {};
612
+ const deviceType = resolveDeviceType(deps, row, deviceId);
613
+ const presence = await resolveDevicePresence(deps, row, deviceId);
597
614
  const entries = [];
598
615
  const seenCaps = /* @__PURE__ */ new Set();
599
- const resolveRemote = (capName) => deps.remoteNativeCaps.get(input.deviceId)?.get(capName) ?? resolveRemoteNativeCapFromRegistry(deps, capName, input.deviceId);
616
+ const resolveRemote = (capName) => deps.remoteNativeCaps.get(deviceId)?.get(capName) ?? resolveRemoteNativeCapFromRegistry(deps, capName, deviceId);
600
617
  for (const [capName, { wrapperAddonId }] of Object.entries(perDevice)) {
601
- const hubLocalNative = deps.capabilityRegistry?.getNativeAddonId(capName, input.deviceId) ?? null;
618
+ const hubLocalNative = deps.capabilityRegistry?.getNativeAddonId(capName, deviceId) ?? null;
602
619
  const remoteNative = resolveRemote(capName);
603
620
  const nativeAddonId = hubLocalNative ?? remoteNative?.addonId ?? "";
604
621
  const nativeNodeId = hubLocalNative ? deps.ctx.kernel.localNodeId ?? "hub" : remoteNative?.nodeId ?? deps.ctx.kernel.localNodeId ?? "hub";
@@ -615,7 +632,7 @@ async function getBindings(deps, input) {
615
632
  });
616
633
  seenCaps.add(capName);
617
634
  }
618
- if (presence === "absent") deps.ctx.logger.debug("bindings requested for absent device — returning none", { tags: { deviceId: input.deviceId } });
635
+ if (presence === "absent") deps.ctx.logger.debug("bindings requested for absent device — returning none", { tags: { deviceId } });
619
636
  else if (deps.capabilityRegistry) {
620
637
  const skippedForType = [];
621
638
  for (const capName of deps.capabilityRegistry.getCapsWithDefaultWrapper()) {
@@ -626,7 +643,7 @@ async function getBindings(deps, input) {
626
643
  }
627
644
  const defaultWrapperAddonId = deps.capabilityRegistry.getDefaultWrapperForCap(capName);
628
645
  if (!defaultWrapperAddonId) continue;
629
- const hubLocalNative = deps.capabilityRegistry.getNativeAddonId(capName, input.deviceId) ?? null;
646
+ const hubLocalNative = deps.capabilityRegistry.getNativeAddonId(capName, deviceId) ?? null;
630
647
  const remoteNative = resolveRemote(capName);
631
648
  const nativeAddonId = hubLocalNative ?? remoteNative?.addonId ?? "";
632
649
  entries.push({
@@ -639,16 +656,16 @@ async function getBindings(deps, input) {
639
656
  seenCaps.add(capName);
640
657
  }
641
658
  if (skippedForType.length > 0) deps.ctx.logger.debug("getBindings: default wrappers skipped — deviceTypes mismatch", {
642
- tags: { deviceId: input.deviceId },
659
+ tags: { deviceId },
643
660
  meta: {
644
661
  deviceType,
645
662
  skipped: skippedForType
646
663
  }
647
664
  });
648
665
  }
649
- if (deps.capabilityRegistry) for (const capName of deps.capabilityRegistry.getNativeCapsForDevice(input.deviceId)) {
666
+ if (deps.capabilityRegistry) for (const capName of deps.capabilityRegistry.getNativeCapsForDevice(deviceId)) {
650
667
  if (seenCaps.has(capName)) continue;
651
- const nativeAddonId = deps.capabilityRegistry.getNativeAddonId(capName, input.deviceId) ?? "";
668
+ const nativeAddonId = deps.capabilityRegistry.getNativeAddonId(capName, deviceId) ?? "";
652
669
  entries.push({
653
670
  capName,
654
671
  kind: "native",
@@ -658,7 +675,7 @@ async function getBindings(deps, input) {
658
675
  });
659
676
  seenCaps.add(capName);
660
677
  }
661
- const pushFed = deps.remoteNativeCaps.get(input.deviceId);
678
+ const pushFed = deps.remoteNativeCaps.get(deviceId);
662
679
  if (pushFed) for (const [capName, info] of pushFed) {
663
680
  if (seenCaps.has(capName)) continue;
664
681
  entries.push({
@@ -670,9 +687,9 @@ async function getBindings(deps, input) {
670
687
  });
671
688
  seenCaps.add(capName);
672
689
  }
673
- const clusterCaps = deps.ctx.kernel.listClusterNativeCapsForDevice?.(input.deviceId) ?? deps.ctx.kernel.listClusterNativeCaps?.();
690
+ const clusterCaps = deps.ctx.kernel.listClusterNativeCapsForDevice?.(deviceId) ?? deps.ctx.kernel.listClusterNativeCaps?.();
674
691
  if (clusterCaps) for (const entry of clusterCaps) {
675
- if (entry.deviceId !== input.deviceId) continue;
692
+ if (entry.deviceId !== deviceId) continue;
676
693
  if (seenCaps.has(entry.capName)) continue;
677
694
  if (!entry.addonId) continue;
678
695
  const localNodeId = deps.ctx.kernel.localNodeId ?? "hub";
@@ -687,7 +704,7 @@ async function getBindings(deps, input) {
687
704
  seenCaps.add(entry.capName);
688
705
  }
689
706
  return {
690
- deviceId: input.deviceId,
707
+ deviceId,
691
708
  entries
692
709
  };
693
710
  }
@@ -700,14 +717,22 @@ async function getBindings(deps, input) {
700
717
  * caller needing seventy-five paid either 75 round-trips (1211 ms / 118 KB) or
701
718
  * `getAllBindings` (511 ms / 679 KB of mostly-unwanted rows).
702
719
  *
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.
720
+ * Sequential on purpose, like `getAllBindings`: `resolveBindingsForDevice`'s
721
+ * remaining per-device work (the bindings-store read, the routing rules) runs
722
+ * on this process's own event loop, so concurrency buys nothing there and a
723
+ * fan-out of `Promise.all` over hundreds of ids only makes the queue less fair
724
+ * to everything else on it. The one part that DOES amortise across the whole
725
+ * set — the device row lookup — is fetched ONCE, ahead of the loop
726
+ * ({@link DeviceRowStore.getMany}), rather than once per iteration: measured
727
+ * on the live hub 2026-08-25, `get device-manager:devices` ran 1 656
728
+ * calls/min steady-state, one row per call, which is exactly what looping
729
+ * `getBindings`'s own `rows.get(deviceId)` here produced.
707
730
  */
708
731
  async function getBindingsBatch(deps, input) {
732
+ const ids = [...new Set(input.deviceIds)];
733
+ const rows = await deps.rows.getMany(ids);
709
734
  const out = [];
710
- for (const deviceId of new Set(input.deviceIds)) out.push(await getBindings(deps, { deviceId }));
735
+ for (const deviceId of ids) out.push(await resolveBindingsForDevice(deps, deviceId, rows.get(deviceId) ?? null));
711
736
  return out;
712
737
  }
713
738
  /**
@@ -722,8 +747,13 @@ async function getBindingsBatch(deps, input) {
722
747
  * payload periodically.
723
748
  */
724
749
  async function getAllBindings(deps) {
750
+ const fleet = await deps.rows.listAll();
751
+ const rowById = /* @__PURE__ */ new Map();
725
752
  const ids = /* @__PURE__ */ new Set();
726
- for (const row of await deps.rows.listAll()) ids.add(row.meta.id);
753
+ for (const row of fleet) {
754
+ rowById.set(row.meta.id, row);
755
+ ids.add(row.meta.id);
756
+ }
727
757
  const registered = deps.ctx.kernel?.deviceRegistry?.getAll() ?? [];
728
758
  for (const device of registered) ids.add(device.id);
729
759
  if (ids.size === 0) {
@@ -731,7 +761,7 @@ async function getAllBindings(deps) {
731
761
  return [];
732
762
  }
733
763
  const out = [];
734
- for (const deviceId of [...ids].sort((a, b) => a - b)) out.push(await getBindings(deps, { deviceId }));
764
+ for (const deviceId of [...ids].sort((a, b) => a - b)) out.push(await resolveBindingsForDevice(deps, deviceId, rowById.get(deviceId) ?? null));
735
765
  return out;
736
766
  }
737
767
  /**
@@ -1310,7 +1340,10 @@ async function listAll(pctx, input) {
1310
1340
  const idFilter = input.deviceIds === void 0 ? null : new Set(input.deviceIds);
1311
1341
  const results = [];
1312
1342
  const seen = /* @__PURE__ */ new Set();
1313
- const fleet = addonId ? await pctx.metaStore.rows.listByAddon(addonId) : await pctx.metaStore.rows.listAll();
1343
+ const fleet = addonId ? await pctx.metaStore.rows.listByAddon(addonId) : await pctx.metaStore.rows.listFleet({
1344
+ ...input.deviceIds !== void 0 ? { deviceIds: input.deviceIds } : {},
1345
+ slim
1346
+ });
1314
1347
  const rowById = /* @__PURE__ */ new Map();
1315
1348
  for (const row of fleet) rowById.set(row.meta.id, row);
1316
1349
  if (pctx.registry) {
@@ -4046,6 +4079,36 @@ var DEVICE_ROWS_COLUMNS = [
4046
4079
  type: "JSON"
4047
4080
  }
4048
4081
  ];
4082
+ /**
4083
+ * The columns a SLIM fleet read selects: every declared column except the
4084
+ * primary key (always returned) and `metadata`.
4085
+ *
4086
+ * ## Why this is a DENYLIST and must stay one
4087
+ *
4088
+ * `decodeDeviceRow` returns `null` when `deviceId`, `addonId`, `stableId`,
4089
+ * `type` or `name` is missing, and `list()` then SKIPS the row with a warn. A
4090
+ * hand-written allowlist that forgot one of the five would not make the fleet
4091
+ * slower — it would make devices vanish from every listing, quietly, and the
4092
+ * only trace would be a log line nobody is reading at the time. Deriving the
4093
+ * list by subtraction means a column added tomorrow is carried by default and
4094
+ * the only way to lose one is to name it here.
4095
+ *
4096
+ * `metadata` is the one slim provably discards: `device-queries.ts#listAll`
4097
+ * sets `metadata: null` on every slim row. It is also the largest JSON column
4098
+ * on the row, which is why this is worth doing at all.
4099
+ *
4100
+ * ## What is NOT here, and will not be
4101
+ *
4102
+ * There is no `where: { type: 'camera' }` companion to this. `isCameraDevice`
4103
+ * is a method-presence check (`getStreamSources`), not the persisted `type`, so
4104
+ * a doorbell with a camera answers YES while its row says `doorbell`. Filtering
4105
+ * the rows by type would strip exactly those devices of their row — and
4106
+ * `toDeviceInfo` reads `name` / `location` / `disabled` from the ROW precisely
4107
+ * because the live device's copies are stale by construction. The fleet would
4108
+ * keep working and start reporting pre-rename names on doorbells. The
4109
+ * `isCamera` filter stays in JS.
4110
+ */
4111
+ var DEVICE_ROWS_SLIM_COLUMNS = DEVICE_ROWS_COLUMNS.map((c) => c.name).filter((name) => name !== "id" && name !== "metadata");
4049
4112
  var DEVICE_ROWS_INDEXES = [
4050
4113
  {
4051
4114
  name: "idx_dm_devices_addon_stable",
@@ -4264,6 +4327,51 @@ var DeviceRowStore = class {
4264
4327
  });
4265
4328
  }
4266
4329
  /**
4330
+ * The fleet, read as narrowly as the question allows.
4331
+ *
4332
+ * `listAll` is a full-table `SELECT … ORDER BY "deviceId"` — ~1 017 rows a
4333
+ * call on the live hub, 14–54 calls a minute — and its callers then filtered
4334
+ * the result in JS. The two filters that CAN be pushed down are here:
4335
+ *
4336
+ * - `deviceIds` becomes a `whereIn`. The viewer's linked-devices panel asks
4337
+ * about ~8 named ids; it was answered by reading the whole ledger. An
4338
+ * EMPTY set reads nothing at all rather than degrading to "everything" —
4339
+ * a request for no devices is a request, not an omission.
4340
+ * - `slim` becomes a column projection, {@link DEVICE_ROWS_SLIM_COLUMNS}.
4341
+ *
4342
+ * `isCamera` is deliberately NOT one of them — see that constant's note and
4343
+ * `device-row-store-filtered.spec.ts`.
4344
+ */
4345
+ async listFleet(opts) {
4346
+ if (opts.deviceIds !== void 0 && opts.deviceIds.length === 0) return [];
4347
+ return this.list({
4348
+ ...opts.deviceIds !== void 0 ? { whereIn: { deviceId: [...new Set(opts.deviceIds)] } } : {},
4349
+ orderBy: {
4350
+ field: "deviceId",
4351
+ direction: "asc"
4352
+ },
4353
+ limit: DEVICE_ROWS_FLEET_LIMIT
4354
+ }, opts.slim === true ? DEVICE_ROWS_SLIM_COLUMNS : void 0);
4355
+ }
4356
+ /**
4357
+ * Many devices, by numeric id, in ONE query.
4358
+ *
4359
+ * `get(id)` called once per id in a loop is the primary-key lookup this
4360
+ * store exists to make cheap, repeated until it isn't — measured on the
4361
+ * live hub 2026-08-25: `get device-manager:devices` at 1 656 calls/min
4362
+ * steady-state and 17 687 during boot, exactly one row per call. This
4363
+ * collapses that into the same `whereIn` {@link listFleet} already does for
4364
+ * a fleet read, keyed by id.
4365
+ *
4366
+ * A requested id the fleet does not know is simply ABSENT from the map —
4367
+ * never a fabricated row, matching {@link get}'s `null`. An empty `ids`
4368
+ * reads nothing at all, same as `listFleet({ deviceIds: [] })`.
4369
+ */
4370
+ async getMany(ids) {
4371
+ const rows = await this.listFleet({ deviceIds: ids });
4372
+ return new Map(rows.map((row) => [row.meta.id, row]));
4373
+ }
4374
+ /**
4267
4375
  * The device an addon knows as `stableId`, or `null`.
4268
4376
  *
4269
4377
  * `(addonId, stableId)` is the addon-facing identity — unique by
@@ -4334,11 +4442,12 @@ var DeviceRowStore = class {
4334
4442
  await this.declare();
4335
4443
  return this.backend.count({ collection: DEVICE_ROWS_COLLECTION });
4336
4444
  }
4337
- async list(filter) {
4445
+ async list(filter, columns) {
4338
4446
  await this.declare();
4339
4447
  const records = await this.backend.query({
4340
4448
  collection: DEVICE_ROWS_COLLECTION,
4341
- filter
4449
+ filter,
4450
+ ...columns === void 0 ? {} : { columns }
4342
4451
  });
4343
4452
  const out = [];
4344
4453
  for (const record of records) {
@@ -43,6 +43,36 @@ export declare const DEVICE_ROWS_COLLECTION = "device-manager:devices";
43
43
  */
44
44
  export declare const DEVICE_ROWS_FLEET_LIMIT = 20000;
45
45
  export declare const DEVICE_ROWS_COLUMNS: readonly CollectionColumn[];
46
+ /**
47
+ * The columns a SLIM fleet read selects: every declared column except the
48
+ * primary key (always returned) and `metadata`.
49
+ *
50
+ * ## Why this is a DENYLIST and must stay one
51
+ *
52
+ * `decodeDeviceRow` returns `null` when `deviceId`, `addonId`, `stableId`,
53
+ * `type` or `name` is missing, and `list()` then SKIPS the row with a warn. A
54
+ * hand-written allowlist that forgot one of the five would not make the fleet
55
+ * slower — it would make devices vanish from every listing, quietly, and the
56
+ * only trace would be a log line nobody is reading at the time. Deriving the
57
+ * list by subtraction means a column added tomorrow is carried by default and
58
+ * the only way to lose one is to name it here.
59
+ *
60
+ * `metadata` is the one slim provably discards: `device-queries.ts#listAll`
61
+ * sets `metadata: null` on every slim row. It is also the largest JSON column
62
+ * on the row, which is why this is worth doing at all.
63
+ *
64
+ * ## What is NOT here, and will not be
65
+ *
66
+ * There is no `where: { type: 'camera' }` companion to this. `isCameraDevice`
67
+ * is a method-presence check (`getStreamSources`), not the persisted `type`, so
68
+ * a doorbell with a camera answers YES while its row says `doorbell`. Filtering
69
+ * the rows by type would strip exactly those devices of their row — and
70
+ * `toDeviceInfo` reads `name` / `location` / `disabled` from the ROW precisely
71
+ * because the live device's copies are stale by construction. The fleet would
72
+ * keep working and start reporting pre-rename names on doorbells. The
73
+ * `isCamera` filter stays in JS.
74
+ */
75
+ export declare const DEVICE_ROWS_SLIM_COLUMNS: readonly string[];
46
76
  export declare const DEVICE_ROWS_INDEXES: readonly CollectionIndex[];
47
77
  /**
48
78
  * The settings-store door this store needs, as a plain object surface.
@@ -193,6 +223,41 @@ export declare class DeviceRowStore {
193
223
  get(deviceId: number): Promise<DeviceRow | null>;
194
224
  /** Every device, ordered by numeric id. */
195
225
  listAll(): Promise<readonly DeviceRow[]>;
226
+ /**
227
+ * The fleet, read as narrowly as the question allows.
228
+ *
229
+ * `listAll` is a full-table `SELECT … ORDER BY "deviceId"` — ~1 017 rows a
230
+ * call on the live hub, 14–54 calls a minute — and its callers then filtered
231
+ * the result in JS. The two filters that CAN be pushed down are here:
232
+ *
233
+ * - `deviceIds` becomes a `whereIn`. The viewer's linked-devices panel asks
234
+ * about ~8 named ids; it was answered by reading the whole ledger. An
235
+ * EMPTY set reads nothing at all rather than degrading to "everything" —
236
+ * a request for no devices is a request, not an omission.
237
+ * - `slim` becomes a column projection, {@link DEVICE_ROWS_SLIM_COLUMNS}.
238
+ *
239
+ * `isCamera` is deliberately NOT one of them — see that constant's note and
240
+ * `device-row-store-filtered.spec.ts`.
241
+ */
242
+ listFleet(opts: {
243
+ readonly deviceIds?: readonly number[];
244
+ readonly slim?: boolean;
245
+ }): Promise<readonly DeviceRow[]>;
246
+ /**
247
+ * Many devices, by numeric id, in ONE query.
248
+ *
249
+ * `get(id)` called once per id in a loop is the primary-key lookup this
250
+ * store exists to make cheap, repeated until it isn't — measured on the
251
+ * live hub 2026-08-25: `get device-manager:devices` at 1 656 calls/min
252
+ * steady-state and 17 687 during boot, exactly one row per call. This
253
+ * collapses that into the same `whereIn` {@link listFleet} already does for
254
+ * a fleet read, keyed by id.
255
+ *
256
+ * A requested id the fleet does not know is simply ABSENT from the map —
257
+ * never a fabricated row, matching {@link get}'s `null`. An empty `ids`
258
+ * reads nothing at all, same as `listFleet({ deviceIds: [] })`.
259
+ */
260
+ getMany(ids: readonly number[]): Promise<ReadonlyMap<number, DeviceRow>>;
196
261
  /**
197
262
  * The device an addon knows as `stableId`, or `null`.
198
263
  *
@@ -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-D-R0SuP-.js");
6
+ const require_dist = require("../../dist-mQ3BkrJd.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-Bh25kEUH.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-DMOTtKH8.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-D-R0SuP-.js");
6
+ const require_dist = require("../../dist-mQ3BkrJd.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-Bh25kEUH.mjs";
1
+ import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-DMOTtKH8.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-D-R0SuP-.js");
3
+ const require_dist = require("../../dist-mQ3BkrJd.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-Bh25kEUH.mjs";
1
+ import { Gt as EventCategory, Mt as createEvent, vt as errMsg, yt as BaseAddon } from "../../dist-DMOTtKH8.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-D-R0SuP-.js");
6
+ const require_dist = require("../../dist-mQ3BkrJd.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-Bh25kEUH.mjs";
2
+ import { S as authProviderCapability, _ as UserRecordSchema, ht as userManagementCapability, m as ScopedTokenSchema, r as ApiKeyRecordSchema, yt as BaseAddon } from "../../dist-DMOTtKH8.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-D-R0SuP-.js");
3
+ const require_dist = require("../../dist-mQ3BkrJd.js");
4
4
  const require_tls = require("../../tls-u8QCJCFE.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-Bh25kEUH.mjs";
1
+ import { Gt as EventCategory, Y as localNetworkCapability, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
2
2
  import { S as readLanHttpState, _ as DEFAULT_LAN_HTTP_PORT, a as writeTlsMode, i as writeExtraSans, l as reissueTlsLeaf, n as readTlsAccessStatus, o as validateUploadedTls, r as readTlsMode, s as ensureTlsCert, t as readExtraSans, y as applyLanHttp } from "../../tls-CQhPGSJm.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-D-R0SuP-.js");
6
+ const require_dist = require("../../dist-mQ3BkrJd.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-Bh25kEUH.mjs";
1
+ import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-DMOTtKH8.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-D-R0SuP-.js");
6
+ const require_dist = require("../../dist-mQ3BkrJd.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-Bh25kEUH.mjs";
1
+ import { $ as metricsProviderCapability, Gt as EventCategory, Mt as createEvent, yt as BaseAddon } from "../../dist-DMOTtKH8.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-D-R0SuP-.js");
6
+ const require_dist = require("../../dist-mQ3BkrJd.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-Bh25kEUH.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-DMOTtKH8.mjs";
3
3
  import { execFile } from "node:child_process";
4
4
  import { promisify } from "node:util";
5
5
  import * as os from "node:os";
@@ -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-D-R0SuP-.js");
6
+ const require_dist = require("../../dist-mQ3BkrJd.js");
7
7
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
8
8
  /**
9
9
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -1,4 +1,4 @@
1
- import { Gt as EventCategory, yt as BaseAddon } from "../../dist-Bh25kEUH.mjs";
1
+ import { Gt as EventCategory, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
2
2
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
3
3
  /**
4
4
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -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-D-R0SuP-.js");
6
+ const require_dist = require("../../dist-mQ3BkrJd.js");
7
7
  let zod = require("zod");
8
8
  let node_crypto = require("node:crypto");
9
9
  let _camstack_types_node = require("@camstack/types/node");
@@ -1,4 +1,4 @@
1
- import { Ct as DeviceFeature, Gt as EventCategory, M as deriveBatteryPresence, Rt as nodePin, T as batteryCapability, Tt as DeviceType, i as BatteryStatusSchema, mt as streamQualityLabel, ut as snapshotCapability, vt as errMsg, w as bareAddonId, yt as BaseAddon } from "../../dist-Bh25kEUH.mjs";
1
+ import { Ct as DeviceFeature, Gt as EventCategory, M as deriveBatteryPresence, Rt as nodePin, T as batteryCapability, Tt as DeviceType, i as BatteryStatusSchema, mt as streamQualityLabel, ut as snapshotCapability, vt as errMsg, w as bareAddonId, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
2
2
  import { z } from "zod";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { signExpiringUrl, verifyExpiringUrl } from "@camstack/types/node";
@@ -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-D-R0SuP-.js");
6
+ const require_dist = require("../../dist-mQ3BkrJd.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs_promises = require("node:fs/promises");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { V as filesystemBrowseCapability, pt as storageProviderCapability, yt as BaseAddon } from "../../dist-Bh25kEUH.mjs";
1
+ import { V as filesystemBrowseCapability, pt as storageProviderCapability, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { mkdir, readdir, realpath, statfs } from "node:fs/promises";
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-D-R0SuP-.js");
6
+ const require_dist = require("../../dist-mQ3BkrJd.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs_promises = require("node:fs/promises");
9
9
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -1,4 +1,4 @@
1
- import { dt as storageCapability, ft as storageMigrationCapability, g as StorageMigrationJobSchema, h as StorageLocationTypeSchema, lt as settingsStoreCapability, yt as BaseAddon, zt as parseJsonObject } from "../../dist-Bh25kEUH.mjs";
1
+ import { dt as storageCapability, ft as storageMigrationCapability, g as StorageMigrationJobSchema, h as StorageLocationTypeSchema, lt as settingsStoreCapability, yt as BaseAddon, zt as parseJsonObject } from "../../dist-DMOTtKH8.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";
@@ -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-D-R0SuP-.js");
6
+ const require_dist = require("../../dist-mQ3BkrJd.js");
7
7
  //#region src/builtins/system-config/system-config.addon.ts
8
8
  /**
9
9
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -1,4 +1,4 @@
1
- import { It as hydrateSchema, vt as errMsg, yt as BaseAddon } from "../../dist-Bh25kEUH.mjs";
1
+ import { It as hydrateSchema, vt as errMsg, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
2
2
  //#region src/builtins/system-config/system-config.addon.ts
3
3
  /**
4
4
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -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-D-R0SuP-.js");
6
+ const require_dist = require("../../dist-mQ3BkrJd.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  let node_path = require("node:path");
9
9
  node_path = require_chunk.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-Bh25kEUH.mjs";
1
+ import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-DMOTtKH8.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  import * as path$1 from "node:path";
4
4
  import path from "node:path";