@camstack/system 1.2.105 → 1.2.107

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 (51) 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-manager.addon.js +58 -11
  14. package/dist/builtins/device-manager/device-manager.addon.mjs +58 -11
  15. package/dist/builtins/device-manager/device-meta-store.d.ts +40 -0
  16. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  17. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  18. package/dist/builtins/hub-forwarder/index.js +1 -1
  19. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  20. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  21. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  22. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  23. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  24. package/dist/builtins/local-network/local-network.addon.js +1 -1
  25. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  26. package/dist/builtins/loki-logging/index.js +1 -1
  27. package/dist/builtins/loki-logging/index.mjs +1 -1
  28. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  29. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  30. package/dist/builtins/platform-probe/index.js +1 -1
  31. package/dist/builtins/platform-probe/index.mjs +1 -1
  32. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  33. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  34. package/dist/builtins/snapshot/index.js +1 -1
  35. package/dist/builtins/snapshot/index.mjs +1 -1
  36. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  37. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  38. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +24 -2
  39. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +47 -24
  40. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +47 -24
  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-CB2eCKJb.js → dist-Ctfhlrtl.js} +29 -3
  48. package/dist/{dist-CF6uy00y.mjs → dist-_oC_QkQA.mjs} +29 -3
  49. package/dist/index.js +1 -1
  50. package/dist/index.mjs +1 -1
  51. package/package.json +1 -1
@@ -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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.mjs";
1
+ import { Gt as EventCategory, v as addonPagesCapability, vt as errMsg, yt as BaseAddon } from "../../dist-_oC_QkQA.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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.mjs";
1
+ import { Gt as EventCategory, b as addonWidgetsCapability, vt as errMsg, yt as BaseAddon } from "../../dist-_oC_QkQA.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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.mjs";
1
+ import { Gt as EventCategory, Mt as createEvent, n as AlertSchema, vt as errMsg, x as alertsCapability, yt as BaseAddon } from "../../dist-_oC_QkQA.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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.mjs";
1
+ import { C as backupCapability, Gt as EventCategory, yt as BaseAddon } from "../../dist-_oC_QkQA.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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.mjs";
1
+ import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-_oC_QkQA.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-CB2eCKJb.js");
3
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.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-_oC_QkQA.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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let _camstack_types_node = require("@camstack/types/node");
9
9
  //#region src/builtins/device-manager/adoption-job-engine.ts
@@ -953,7 +953,7 @@ function resolveDeviceById(registry, deviceId) {
953
953
  //#region src/builtins/device-manager/device-queries.ts
954
954
  async function listPersistedByAddon(pctx, input) {
955
955
  const { addonId } = input;
956
- const [index, meta] = await Promise.all([pctx.metaStore.readIndex(), pctx.metaStore.readMeta()]);
956
+ const { index, meta } = await pctx.metaStore.readAll();
957
957
  const stableIds = index[addonId] ?? [];
958
958
  const byStableId = /* @__PURE__ */ new Map();
959
959
  for (const m of Object.values(meta)) if (m.addonId === addonId) byStableId.set(m.stableId, m);
@@ -978,8 +978,7 @@ async function listAll(pctx, input) {
978
978
  const camerasOnly = input.isCamera === true;
979
979
  const results = [];
980
980
  const seen = /* @__PURE__ */ new Set();
981
- const meta = await pctx.metaStore.readMeta();
982
- const metadataMap = await pctx.metaStore.readMetadataMap();
981
+ const { meta, metadata: metadataMap, index } = await pctx.metaStore.readAll();
983
982
  if (pctx.registry) {
984
983
  const liveEntries = addonId ? pctx.registry.getAllForAddon(addonId).map((device) => ({
985
984
  addonId,
@@ -997,7 +996,6 @@ async function listAll(pctx, input) {
997
996
  } : info);
998
997
  }
999
998
  }
1000
- const index = await pctx.metaStore.readIndex();
1001
999
  const metaByAddonStable = /* @__PURE__ */ new Map();
1002
1000
  for (const m of Object.values(meta)) metaByAddonStable.set(`${m.addonId}${m.stableId}`, m);
1003
1001
  const targetAddons = addonId ? [addonId] : Object.keys(index);
@@ -1095,11 +1093,7 @@ async function getChildren(pctx, input) {
1095
1093
  }
1096
1094
  const results = [];
1097
1095
  const seen = /* @__PURE__ */ new Set();
1098
- const [index, meta, metadataMap] = await Promise.all([
1099
- pctx.metaStore.readIndex(),
1100
- pctx.metaStore.readMeta(),
1101
- pctx.metaStore.readMetadataMap()
1102
- ]);
1096
+ const { index, meta, metadata: metadataMap } = await pctx.metaStore.readAll();
1103
1097
  if (pctx.registry) {
1104
1098
  const liveChildren = pctx.registry.getChildren(parentDeviceId);
1105
1099
  for (const device of liveChildren) {
@@ -3875,8 +3869,61 @@ var DeviceMetaStore = class {
3875
3869
  this.settings = settings;
3876
3870
  this.registry = registry;
3877
3871
  }
3872
+ /** The read currently in flight, or null. Never a settled value — see
3873
+ * {@link readStore}. */
3874
+ inFlightRead = null;
3875
+ /**
3876
+ * The whole persisted addon store.
3877
+ *
3878
+ * **Concurrent callers join the read already in flight.** This is not a
3879
+ * cache and nothing survives settlement: a caller that awaited the running
3880
+ * promise could not have observed anything older than its result, so the
3881
+ * only thing that changes is cost. What that cost was, measured on the
3882
+ * 2026-08-19 boot: `readAddonStore` lands in `SqliteSettingsBackend
3883
+ * .getAllAddon`, which reads and `JSON.parse`s this addon's rows —
3884
+ * 625 KB on the live hub (`deviceMeta` 467 KB + `deviceMetadata` 87 KB +
3885
+ * `deviceIndex` 70 KB) — synchronously, on the hub's event loop. A V8
3886
+ * profile of that boot had hub-main's JS thread 99.9% busy with 64% of it
3887
+ * inside `getAllAddon`, ~89% of that entered here, and every runner's first
3888
+ * store read queued behind it (the notification centre's six parallel reads
3889
+ * all resolved together at t+44.7 s).
3890
+ *
3891
+ * A rejection is NOT latched: the slot is cleared before the promise
3892
+ * settles either way, so a failed read costs the joiners that one failure
3893
+ * and the next caller reaches the store again.
3894
+ */
3878
3895
  readStore = async () => {
3879
- return await this.settings.readAddonStore();
3896
+ const existing = this.inFlightRead;
3897
+ if (existing !== null) return existing;
3898
+ const read = (async () => {
3899
+ try {
3900
+ return await this.settings.readAddonStore();
3901
+ } finally {
3902
+ this.inFlightRead = null;
3903
+ }
3904
+ })();
3905
+ this.inFlightRead = read;
3906
+ return read;
3907
+ };
3908
+ /**
3909
+ * The three fleet projections from ONE read.
3910
+ *
3911
+ * `listAll` asked for `deviceMeta`, then `deviceMetadata`, then
3912
+ * `deviceIndex` — three SEQUENTIAL awaits, which {@link readStore}'s
3913
+ * in-flight join cannot collapse because each starts after the previous one
3914
+ * settled. Three full 625 KB parses per call, on a call made once per device
3915
+ * lifecycle event during a 974-device boot: 23% of hub-main's CPU.
3916
+ *
3917
+ * It is also ONE snapshot. Three separate reads could straddle a write and
3918
+ * hand back an index that names a device the meta map no longer has.
3919
+ */
3920
+ readAll = async () => {
3921
+ const store = await this.readStore();
3922
+ return {
3923
+ index: store.deviceIndex ?? {},
3924
+ meta: store.deviceMeta ?? {},
3925
+ metadata: store.deviceMetadata ?? {}
3926
+ };
3880
3927
  };
3881
3928
  readIndex = async () => {
3882
3929
  return (await this.readStore()).deviceIndex ?? {};
@@ -1,4 +1,4 @@
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-CF6uy00y.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-_oC_QkQA.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { canonicalDeviceFingerprint } from "@camstack/types/node";
4
4
  //#region src/builtins/device-manager/adoption-job-engine.ts
@@ -948,7 +948,7 @@ function resolveDeviceById(registry, deviceId) {
948
948
  //#region src/builtins/device-manager/device-queries.ts
949
949
  async function listPersistedByAddon(pctx, input) {
950
950
  const { addonId } = input;
951
- const [index, meta] = await Promise.all([pctx.metaStore.readIndex(), pctx.metaStore.readMeta()]);
951
+ const { index, meta } = await pctx.metaStore.readAll();
952
952
  const stableIds = index[addonId] ?? [];
953
953
  const byStableId = /* @__PURE__ */ new Map();
954
954
  for (const m of Object.values(meta)) if (m.addonId === addonId) byStableId.set(m.stableId, m);
@@ -973,8 +973,7 @@ async function listAll(pctx, input) {
973
973
  const camerasOnly = input.isCamera === true;
974
974
  const results = [];
975
975
  const seen = /* @__PURE__ */ new Set();
976
- const meta = await pctx.metaStore.readMeta();
977
- const metadataMap = await pctx.metaStore.readMetadataMap();
976
+ const { meta, metadata: metadataMap, index } = await pctx.metaStore.readAll();
978
977
  if (pctx.registry) {
979
978
  const liveEntries = addonId ? pctx.registry.getAllForAddon(addonId).map((device) => ({
980
979
  addonId,
@@ -992,7 +991,6 @@ async function listAll(pctx, input) {
992
991
  } : info);
993
992
  }
994
993
  }
995
- const index = await pctx.metaStore.readIndex();
996
994
  const metaByAddonStable = /* @__PURE__ */ new Map();
997
995
  for (const m of Object.values(meta)) metaByAddonStable.set(`${m.addonId}${m.stableId}`, m);
998
996
  const targetAddons = addonId ? [addonId] : Object.keys(index);
@@ -1090,11 +1088,7 @@ async function getChildren(pctx, input) {
1090
1088
  }
1091
1089
  const results = [];
1092
1090
  const seen = /* @__PURE__ */ new Set();
1093
- const [index, meta, metadataMap] = await Promise.all([
1094
- pctx.metaStore.readIndex(),
1095
- pctx.metaStore.readMeta(),
1096
- pctx.metaStore.readMetadataMap()
1097
- ]);
1091
+ const { index, meta, metadata: metadataMap } = await pctx.metaStore.readAll();
1098
1092
  if (pctx.registry) {
1099
1093
  const liveChildren = pctx.registry.getChildren(parentDeviceId);
1100
1094
  for (const device of liveChildren) {
@@ -3870,8 +3864,61 @@ var DeviceMetaStore = class {
3870
3864
  this.settings = settings;
3871
3865
  this.registry = registry;
3872
3866
  }
3867
+ /** The read currently in flight, or null. Never a settled value — see
3868
+ * {@link readStore}. */
3869
+ inFlightRead = null;
3870
+ /**
3871
+ * The whole persisted addon store.
3872
+ *
3873
+ * **Concurrent callers join the read already in flight.** This is not a
3874
+ * cache and nothing survives settlement: a caller that awaited the running
3875
+ * promise could not have observed anything older than its result, so the
3876
+ * only thing that changes is cost. What that cost was, measured on the
3877
+ * 2026-08-19 boot: `readAddonStore` lands in `SqliteSettingsBackend
3878
+ * .getAllAddon`, which reads and `JSON.parse`s this addon's rows —
3879
+ * 625 KB on the live hub (`deviceMeta` 467 KB + `deviceMetadata` 87 KB +
3880
+ * `deviceIndex` 70 KB) — synchronously, on the hub's event loop. A V8
3881
+ * profile of that boot had hub-main's JS thread 99.9% busy with 64% of it
3882
+ * inside `getAllAddon`, ~89% of that entered here, and every runner's first
3883
+ * store read queued behind it (the notification centre's six parallel reads
3884
+ * all resolved together at t+44.7 s).
3885
+ *
3886
+ * A rejection is NOT latched: the slot is cleared before the promise
3887
+ * settles either way, so a failed read costs the joiners that one failure
3888
+ * and the next caller reaches the store again.
3889
+ */
3873
3890
  readStore = async () => {
3874
- return await this.settings.readAddonStore();
3891
+ const existing = this.inFlightRead;
3892
+ if (existing !== null) return existing;
3893
+ const read = (async () => {
3894
+ try {
3895
+ return await this.settings.readAddonStore();
3896
+ } finally {
3897
+ this.inFlightRead = null;
3898
+ }
3899
+ })();
3900
+ this.inFlightRead = read;
3901
+ return read;
3902
+ };
3903
+ /**
3904
+ * The three fleet projections from ONE read.
3905
+ *
3906
+ * `listAll` asked for `deviceMeta`, then `deviceMetadata`, then
3907
+ * `deviceIndex` — three SEQUENTIAL awaits, which {@link readStore}'s
3908
+ * in-flight join cannot collapse because each starts after the previous one
3909
+ * settled. Three full 625 KB parses per call, on a call made once per device
3910
+ * lifecycle event during a 974-device boot: 23% of hub-main's CPU.
3911
+ *
3912
+ * It is also ONE snapshot. Three separate reads could straddle a write and
3913
+ * hand back an index that names a device the meta map no longer has.
3914
+ */
3915
+ readAll = async () => {
3916
+ const store = await this.readStore();
3917
+ return {
3918
+ index: store.deviceIndex ?? {},
3919
+ meta: store.deviceMeta ?? {},
3920
+ metadata: store.deviceMetadata ?? {}
3921
+ };
3875
3922
  };
3876
3923
  readIndex = async () => {
3877
3924
  return (await this.readStore()).deviceIndex ?? {};
@@ -19,7 +19,47 @@ export declare class DeviceMetaStore {
19
19
  constructor(settings: DeviceManagerSettings,
20
20
  /** Hub-process device registry (null when this node owns no registry). */
21
21
  registry: IDeviceRegistry | null);
22
+ /** The read currently in flight, or null. Never a settled value — see
23
+ * {@link readStore}. */
24
+ private inFlightRead;
25
+ /**
26
+ * The whole persisted addon store.
27
+ *
28
+ * **Concurrent callers join the read already in flight.** This is not a
29
+ * cache and nothing survives settlement: a caller that awaited the running
30
+ * promise could not have observed anything older than its result, so the
31
+ * only thing that changes is cost. What that cost was, measured on the
32
+ * 2026-08-19 boot: `readAddonStore` lands in `SqliteSettingsBackend
33
+ * .getAllAddon`, which reads and `JSON.parse`s this addon's rows —
34
+ * 625 KB on the live hub (`deviceMeta` 467 KB + `deviceMetadata` 87 KB +
35
+ * `deviceIndex` 70 KB) — synchronously, on the hub's event loop. A V8
36
+ * profile of that boot had hub-main's JS thread 99.9% busy with 64% of it
37
+ * inside `getAllAddon`, ~89% of that entered here, and every runner's first
38
+ * store read queued behind it (the notification centre's six parallel reads
39
+ * all resolved together at t+44.7 s).
40
+ *
41
+ * A rejection is NOT latched: the slot is cleared before the promise
42
+ * settles either way, so a failed read costs the joiners that one failure
43
+ * and the next caller reaches the store again.
44
+ */
22
45
  readStore: () => Promise<AddonStore>;
46
+ /**
47
+ * The three fleet projections from ONE read.
48
+ *
49
+ * `listAll` asked for `deviceMeta`, then `deviceMetadata`, then
50
+ * `deviceIndex` — three SEQUENTIAL awaits, which {@link readStore}'s
51
+ * in-flight join cannot collapse because each starts after the previous one
52
+ * settled. Three full 625 KB parses per call, on a call made once per device
53
+ * lifecycle event during a 974-device boot: 23% of hub-main's CPU.
54
+ *
55
+ * It is also ONE snapshot. Three separate reads could straddle a write and
56
+ * hand back an index that names a device the meta map no longer has.
57
+ */
58
+ readAll: () => Promise<{
59
+ readonly index: Record<string, string[]>;
60
+ readonly meta: Record<string, PersistedDeviceMeta>;
61
+ readonly metadata: Record<string, Record<string, unknown>>;
62
+ }>;
23
63
  readIndex: () => Promise<Record<string, string[]>>;
24
64
  readMeta: () => Promise<Record<string, PersistedDeviceMeta>>;
25
65
  /** Hardware-identity metadata map. Lives in a sibling key on the
@@ -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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.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-_oC_QkQA.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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.mjs";
1
+ import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-_oC_QkQA.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-CB2eCKJb.js");
3
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.mjs";
1
+ import { Gt as EventCategory, Mt as createEvent, vt as errMsg, yt as BaseAddon } from "../../dist-_oC_QkQA.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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.mjs";
2
+ import { S as authProviderCapability, _ as UserRecordSchema, ht as userManagementCapability, m as ScopedTokenSchema, r as ApiKeyRecordSchema, yt as BaseAddon } from "../../dist-_oC_QkQA.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-CB2eCKJb.js");
3
+ const require_dist = require("../../dist-Ctfhlrtl.js");
4
4
  let node_os = require("node:os");
5
5
  node_os = require_chunk.__toESM(node_os);
6
6
  //#region src/builtins/local-network/local-network.addon.ts
@@ -1,4 +1,4 @@
1
- import { Gt as EventCategory, Y as localNetworkCapability, yt as BaseAddon } from "../../dist-CF6uy00y.mjs";
1
+ import { Gt as EventCategory, Y as localNetworkCapability, yt as BaseAddon } from "../../dist-_oC_QkQA.mjs";
2
2
  import * as os from "node:os";
3
3
  //#region src/builtins/local-network/local-network.addon.ts
4
4
  /**
@@ -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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.mjs";
1
+ import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-_oC_QkQA.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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.mjs";
1
+ import { $ as metricsProviderCapability, Gt as EventCategory, Mt as createEvent, yt as BaseAddon } from "../../dist-_oC_QkQA.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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.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-_oC_QkQA.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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.mjs";
1
+ import { Gt as EventCategory, yt as BaseAddon } from "../../dist-_oC_QkQA.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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.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-_oC_QkQA.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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.mjs";
1
+ import { V as filesystemBrowseCapability, pt as storageProviderCapability, yt as BaseAddon } from "../../dist-_oC_QkQA.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";
@@ -1,4 +1,4 @@
1
- import { CollectionColumn, CollectionIndex, DataStoreEngineInfo, HistogramBucket, ISettingsBackend, SettingsCountInput, SettingsDeleteInput, SettingsGetInput, SettingsHistogramInput, SettingsInsertInput, SettingsIsEmptyInput, SettingsQueryInput, SettingsRecord, SettingsSetInput, SettingsUpdateInput, IScopedLogger } from '@camstack/types';
1
+ import { CollectionColumn, CollectionIndex, DataStoreEngineInfo, HistogramBucket, IScopedLogger, ISettingsBackend, SettingsCountInput, SettingsDeleteInput, SettingsGetInput, SettingsHistogramInput, SettingsInsertInput, SettingsIsEmptyInput, SettingsQueryInput, SettingsRecord, SettingsSetInput, SettingsUpdateInput } from '@camstack/types';
2
2
  import { default as Database } from 'better-sqlite3';
3
3
  import { MutationFilterInput } from './filter-compiler.js';
4
4
  /** Input for {@link SqliteSettingsBackend.deleteWhere}. */
@@ -123,7 +123,29 @@ export declare class SqliteSettingsBackend implements ISettingsBackend {
123
123
  setSystem(key: string, value: unknown): void;
124
124
  /** Get all system settings as flat key-value */
125
125
  getAllSystem(): Record<string, unknown>;
126
- /** Get all settings for an addon */
126
+ /**
127
+ * Get all settings for an addon.
128
+ *
129
+ * Selected by the SAME key range {@link setAllAddon} deletes and re-inserts
130
+ * — `prefixWhere("<addonId>.")` — and for the same reason `getAllScoped`
131
+ * uses it: `"<addonId>.<key>"` is the PRIMARY KEY, so the range is a
132
+ * `SEARCH … USING INDEX` while anything else is a full `SCAN`.
133
+ *
134
+ * It used to select on `json_extract(data, '$.addonId')`, which is a second
135
+ * authority for "this addon's rows" and disagreed with the writer in both
136
+ * directions: a row inside the JSON's idea of the addon but outside the key
137
+ * range was READ and never DELETED (a config key that could not be cleared),
138
+ * and `json_extract` was evaluated on EVERY row — so a four-key addon paid
139
+ * for the whole table and one unparseable neighbour aborted the statement,
140
+ * taking out every addon's config read at once.
141
+ *
142
+ * The cost was not theoretical. On the live hub `addon-settings` is 627 KB
143
+ * in 24 rows, 625 KB of it device-manager's three fleet blobs; a V8 profile
144
+ * of a boot (2026-08-19) had hub-main's JS thread 99.9% busy with **64% of
145
+ * it inside this method**, which is what put a ~45 s queue in front of every
146
+ * runner's first store read. Measured on that table: 0.947 ms → ~0.02 ms for
147
+ * a four-key addon, 2.90 ms → 2.13 ms for device-manager's own.
148
+ */
127
149
  getAllAddon(addonId: string): Record<string, unknown>;
128
150
  /** Bulk-set all settings for an addon */
129
151
  setAllAddon(addonId: string, config: Record<string, unknown>): void;
@@ -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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs = require("node:fs");
9
9
  let node_module = require("node:module");
@@ -321,6 +321,27 @@ function compileFilter$1(filter, shape, mode, serialize) {
321
321
  };
322
322
  }
323
323
  //#endregion
324
+ //#region src/builtins/sqlite-storage/prefix-range.ts
325
+ var MAX_ASCII = 127;
326
+ /**
327
+ * @returns the range for `prefix`, or `null` when the caller must fall back to
328
+ * a `LIKE` scan (empty prefix — which is the whole table and has no upper
329
+ * bound — or any byte outside ASCII).
330
+ */
331
+ function prefixRange(prefix) {
332
+ if (prefix.length === 0) return null;
333
+ for (let i = 0; i < prefix.length; i++) {
334
+ const code = prefix.charCodeAt(i);
335
+ if (code === 0 || code > MAX_ASCII) return null;
336
+ }
337
+ const last = prefix.charCodeAt(prefix.length - 1);
338
+ if (last >= MAX_ASCII) return null;
339
+ return {
340
+ lo: prefix,
341
+ hi: prefix.slice(0, -1) + String.fromCharCode(last + 1)
342
+ };
343
+ }
344
+ //#endregion
324
345
  //#region src/builtins/sqlite-storage/query-bounds.ts
325
346
  /**
326
347
  * Defensive row bounds for `data-store-provider.query`.
@@ -442,27 +463,6 @@ function isImposedBound(source) {
442
463
  return source !== "caller";
443
464
  }
444
465
  //#endregion
445
- //#region src/builtins/sqlite-storage/prefix-range.ts
446
- var MAX_ASCII = 127;
447
- /**
448
- * @returns the range for `prefix`, or `null` when the caller must fall back to
449
- * a `LIKE` scan (empty prefix — which is the whole table and has no upper
450
- * bound — or any byte outside ASCII).
451
- */
452
- function prefixRange(prefix) {
453
- if (prefix.length === 0) return null;
454
- for (let i = 0; i < prefix.length; i++) {
455
- const code = prefix.charCodeAt(i);
456
- if (code === 0 || code > MAX_ASCII) return null;
457
- }
458
- const last = prefix.charCodeAt(prefix.length - 1);
459
- if (last >= MAX_ASCII) return null;
460
- return {
461
- lo: prefix,
462
- hi: prefix.slice(0, -1) + String.fromCharCode(last + 1)
463
- };
464
- }
465
- //#endregion
466
466
  //#region src/builtins/sqlite-storage/sqlite-settings-backend.ts
467
467
  function parseRowData(raw) {
468
468
  return require_dist.asJsonObject(require_dist.parseJsonUnknown(raw)) ?? {};
@@ -890,10 +890,33 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
890
890
  const rows = this.getDb().prepare("SELECT id, data FROM \"system-settings\"").all();
891
891
  return Object.fromEntries(rows.map((r) => [r.id, JSON.parse(r.data)]));
892
892
  }
893
- /** Get all settings for an addon */
893
+ /**
894
+ * Get all settings for an addon.
895
+ *
896
+ * Selected by the SAME key range {@link setAllAddon} deletes and re-inserts
897
+ * — `prefixWhere("<addonId>.")` — and for the same reason `getAllScoped`
898
+ * uses it: `"<addonId>.<key>"` is the PRIMARY KEY, so the range is a
899
+ * `SEARCH … USING INDEX` while anything else is a full `SCAN`.
900
+ *
901
+ * It used to select on `json_extract(data, '$.addonId')`, which is a second
902
+ * authority for "this addon's rows" and disagreed with the writer in both
903
+ * directions: a row inside the JSON's idea of the addon but outside the key
904
+ * range was READ and never DELETED (a config key that could not be cleared),
905
+ * and `json_extract` was evaluated on EVERY row — so a four-key addon paid
906
+ * for the whole table and one unparseable neighbour aborted the statement,
907
+ * taking out every addon's config read at once.
908
+ *
909
+ * The cost was not theoretical. On the live hub `addon-settings` is 627 KB
910
+ * in 24 rows, 625 KB of it device-manager's three fleet blobs; a V8 profile
911
+ * of a boot (2026-08-19) had hub-main's JS thread 99.9% busy with **64% of
912
+ * it inside this method**, which is what put a ~45 s queue in front of every
913
+ * runner's first store read. Measured on that table: 0.947 ms → ~0.02 ms for
914
+ * a four-key addon, 2.90 ms → 2.13 ms for device-manager's own.
915
+ */
894
916
  getAllAddon(addonId) {
895
917
  this.requireDeclared("addon-settings");
896
- const rows = this.getDb().prepare("SELECT id, data FROM \"addon-settings\" WHERE json_extract(data, '$.addonId') = ?").all(addonId);
918
+ const where = this.prefixWhere(`${addonId}.`);
919
+ const rows = this.getDb().prepare(`SELECT id, data FROM "addon-settings" WHERE ${where.sql}`).all(...where.params);
897
920
  if (rows.length === 0) return {};
898
921
  const result = {};
899
922
  for (const row of rows) {
@@ -1,4 +1,4 @@
1
- import { A as dataStoreProviderCapability, Bt as parseJsonUnknown, _t as vectorStoreCapability, f as RUNTIME_DEFAULTS, gt as vectorDimFromBase64, j as decodeVectorBase64, kt as asJsonObject, vt as errMsg, w as bareAddonId, yt as BaseAddon } from "../../dist-CF6uy00y.mjs";
1
+ import { A as dataStoreProviderCapability, Bt as parseJsonUnknown, _t as vectorStoreCapability, f as RUNTIME_DEFAULTS, gt as vectorDimFromBase64, j as decodeVectorBase64, kt as asJsonObject, vt as errMsg, w as bareAddonId, yt as BaseAddon } from "../../dist-_oC_QkQA.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { statSync } from "node:fs";
@@ -315,6 +315,27 @@ function compileFilter$1(filter, shape, mode, serialize) {
315
315
  };
316
316
  }
317
317
  //#endregion
318
+ //#region src/builtins/sqlite-storage/prefix-range.ts
319
+ var MAX_ASCII = 127;
320
+ /**
321
+ * @returns the range for `prefix`, or `null` when the caller must fall back to
322
+ * a `LIKE` scan (empty prefix — which is the whole table and has no upper
323
+ * bound — or any byte outside ASCII).
324
+ */
325
+ function prefixRange(prefix) {
326
+ if (prefix.length === 0) return null;
327
+ for (let i = 0; i < prefix.length; i++) {
328
+ const code = prefix.charCodeAt(i);
329
+ if (code === 0 || code > MAX_ASCII) return null;
330
+ }
331
+ const last = prefix.charCodeAt(prefix.length - 1);
332
+ if (last >= MAX_ASCII) return null;
333
+ return {
334
+ lo: prefix,
335
+ hi: prefix.slice(0, -1) + String.fromCharCode(last + 1)
336
+ };
337
+ }
338
+ //#endregion
318
339
  //#region src/builtins/sqlite-storage/query-bounds.ts
319
340
  /**
320
341
  * Defensive row bounds for `data-store-provider.query`.
@@ -436,27 +457,6 @@ function isImposedBound(source) {
436
457
  return source !== "caller";
437
458
  }
438
459
  //#endregion
439
- //#region src/builtins/sqlite-storage/prefix-range.ts
440
- var MAX_ASCII = 127;
441
- /**
442
- * @returns the range for `prefix`, or `null` when the caller must fall back to
443
- * a `LIKE` scan (empty prefix — which is the whole table and has no upper
444
- * bound — or any byte outside ASCII).
445
- */
446
- function prefixRange(prefix) {
447
- if (prefix.length === 0) return null;
448
- for (let i = 0; i < prefix.length; i++) {
449
- const code = prefix.charCodeAt(i);
450
- if (code === 0 || code > MAX_ASCII) return null;
451
- }
452
- const last = prefix.charCodeAt(prefix.length - 1);
453
- if (last >= MAX_ASCII) return null;
454
- return {
455
- lo: prefix,
456
- hi: prefix.slice(0, -1) + String.fromCharCode(last + 1)
457
- };
458
- }
459
- //#endregion
460
460
  //#region src/builtins/sqlite-storage/sqlite-settings-backend.ts
461
461
  function parseRowData(raw) {
462
462
  return asJsonObject(parseJsonUnknown(raw)) ?? {};
@@ -884,10 +884,33 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
884
884
  const rows = this.getDb().prepare("SELECT id, data FROM \"system-settings\"").all();
885
885
  return Object.fromEntries(rows.map((r) => [r.id, JSON.parse(r.data)]));
886
886
  }
887
- /** Get all settings for an addon */
887
+ /**
888
+ * Get all settings for an addon.
889
+ *
890
+ * Selected by the SAME key range {@link setAllAddon} deletes and re-inserts
891
+ * — `prefixWhere("<addonId>.")` — and for the same reason `getAllScoped`
892
+ * uses it: `"<addonId>.<key>"` is the PRIMARY KEY, so the range is a
893
+ * `SEARCH … USING INDEX` while anything else is a full `SCAN`.
894
+ *
895
+ * It used to select on `json_extract(data, '$.addonId')`, which is a second
896
+ * authority for "this addon's rows" and disagreed with the writer in both
897
+ * directions: a row inside the JSON's idea of the addon but outside the key
898
+ * range was READ and never DELETED (a config key that could not be cleared),
899
+ * and `json_extract` was evaluated on EVERY row — so a four-key addon paid
900
+ * for the whole table and one unparseable neighbour aborted the statement,
901
+ * taking out every addon's config read at once.
902
+ *
903
+ * The cost was not theoretical. On the live hub `addon-settings` is 627 KB
904
+ * in 24 rows, 625 KB of it device-manager's three fleet blobs; a V8 profile
905
+ * of a boot (2026-08-19) had hub-main's JS thread 99.9% busy with **64% of
906
+ * it inside this method**, which is what put a ~45 s queue in front of every
907
+ * runner's first store read. Measured on that table: 0.947 ms → ~0.02 ms for
908
+ * a four-key addon, 2.90 ms → 2.13 ms for device-manager's own.
909
+ */
888
910
  getAllAddon(addonId) {
889
911
  this.requireDeclared("addon-settings");
890
- const rows = this.getDb().prepare("SELECT id, data FROM \"addon-settings\" WHERE json_extract(data, '$.addonId') = ?").all(addonId);
912
+ const where = this.prefixWhere(`${addonId}.`);
913
+ const rows = this.getDb().prepare(`SELECT id, data FROM "addon-settings" WHERE ${where.sql}`).all(...where.params);
891
914
  if (rows.length === 0) return {};
892
915
  const result = {};
893
916
  for (const row of rows) {
@@ -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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.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-_oC_QkQA.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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.mjs";
1
+ import { It as hydrateSchema, vt as errMsg, yt as BaseAddon } from "../../dist-_oC_QkQA.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-CB2eCKJb.js");
6
+ const require_dist = require("../../dist-Ctfhlrtl.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-CF6uy00y.mjs";
1
+ import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-_oC_QkQA.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";
@@ -26633,9 +26633,21 @@ var RecordingLocationUsageSchema = zod.z.object({
26633
26633
  * previously rendered as two identical "disks" with a nonsensical used
26634
26634
  * split — hydrate attribution across aliases is arbitrary by nature. */
26635
26635
  locationIds: zod.z.array(zod.z.string()).optional(),
26636
- /** Bytes of recordings stored on this PHYSICAL volume (all aliases). */
26636
+ /**
26637
+ * Filesystem root of the physical volume this row accounts — the only
26638
+ * operator-legible identity when a location has no `displayName` yet, and
26639
+ * what a "which disk is full" alert has to name. OPTIONAL for the same
26640
+ * train-skew reason as `oldestMs`: a recorder that predates the field omits
26641
+ * it, and this schema must keep validating that older provider's payload.
26642
+ */
26643
+ root: zod.z.string().optional(),
26644
+ /** Bytes of recordings stored on this PHYSICAL volume (all aliases). This is
26645
+ * the footage's SHARE of the volume, not how full the volume is — the disk
26646
+ * also holds everything that is not recordings. Volume fill is
26647
+ * `(totalBytes - availableBytes) / totalBytes`. */
26637
26648
  usedBytes: zod.z.number(),
26638
- /** Free bytes on the location's volume; null when capacity is unknown (remote). */
26649
+ /** Free bytes on the location's volume; null when capacity is unknown
26650
+ * (remote/unstattable — the recorder logs the statfs failure with the root). */
26639
26651
  availableBytes: zod.z.number().nullable(),
26640
26652
  /** Total bytes of the location's volume; null when unknown. */
26641
26653
  totalBytes: zod.z.number().nullable()
@@ -26647,7 +26659,15 @@ var RecordingStorageUsageSchema = zod.z.object({
26647
26659
  nodeId: zod.z.string(),
26648
26660
  totalUsedBytes: zod.z.number(),
26649
26661
  devices: zod.z.array(RecordingDeviceUsageSchema),
26650
- locations: zod.z.array(RecordingLocationUsageSchema)
26662
+ /**
26663
+ * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
26664
+ * of the payload without it: the hub serves this method through a BAKED
26665
+ * router, so a hub whose framework train predates a change to this array
26666
+ * strips it out of an answer a newer recorder happily produced. A UI that
26667
+ * dereferences it unconditionally crashes the whole Storage page over a
26668
+ * missing disk bar.
26669
+ */
26670
+ locations: zod.z.array(RecordingLocationUsageSchema).optional()
26651
26671
  });
26652
26672
  /**
26653
26673
  * The OPERATOR-ARMED half of multi-location recordings (D116).
@@ -37759,8 +37779,14 @@ var SESSION_ONLY_POLICY = {
37759
37779
  durability: "session",
37760
37780
  volatileFields: []
37761
37781
  };
37782
+ /** Schema defaults — an untouched sub-field must author exactly these. */
37783
+ var NC_AUDIO_DEFAULTS = {
37784
+ hitPercent: 60,
37785
+ samplingSeconds: 10
37786
+ };
37762
37787
  new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
37763
37788
  new Set(["devices", "classes"]);
37789
+ NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
37764
37790
  /**
37765
37791
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
37766
37792
  *
@@ -26633,9 +26633,21 @@ var RecordingLocationUsageSchema = z.object({
26633
26633
  * previously rendered as two identical "disks" with a nonsensical used
26634
26634
  * split — hydrate attribution across aliases is arbitrary by nature. */
26635
26635
  locationIds: z.array(z.string()).optional(),
26636
- /** Bytes of recordings stored on this PHYSICAL volume (all aliases). */
26636
+ /**
26637
+ * Filesystem root of the physical volume this row accounts — the only
26638
+ * operator-legible identity when a location has no `displayName` yet, and
26639
+ * what a "which disk is full" alert has to name. OPTIONAL for the same
26640
+ * train-skew reason as `oldestMs`: a recorder that predates the field omits
26641
+ * it, and this schema must keep validating that older provider's payload.
26642
+ */
26643
+ root: z.string().optional(),
26644
+ /** Bytes of recordings stored on this PHYSICAL volume (all aliases). This is
26645
+ * the footage's SHARE of the volume, not how full the volume is — the disk
26646
+ * also holds everything that is not recordings. Volume fill is
26647
+ * `(totalBytes - availableBytes) / totalBytes`. */
26637
26648
  usedBytes: z.number(),
26638
- /** Free bytes on the location's volume; null when capacity is unknown (remote). */
26649
+ /** Free bytes on the location's volume; null when capacity is unknown
26650
+ * (remote/unstattable — the recorder logs the statfs failure with the root). */
26639
26651
  availableBytes: z.number().nullable(),
26640
26652
  /** Total bytes of the location's volume; null when unknown. */
26641
26653
  totalBytes: z.number().nullable()
@@ -26647,7 +26659,15 @@ var RecordingStorageUsageSchema = z.object({
26647
26659
  nodeId: z.string(),
26648
26660
  totalUsedBytes: z.number(),
26649
26661
  devices: z.array(RecordingDeviceUsageSchema),
26650
- locations: z.array(RecordingLocationUsageSchema)
26662
+ /**
26663
+ * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
26664
+ * of the payload without it: the hub serves this method through a BAKED
26665
+ * router, so a hub whose framework train predates a change to this array
26666
+ * strips it out of an answer a newer recorder happily produced. A UI that
26667
+ * dereferences it unconditionally crashes the whole Storage page over a
26668
+ * missing disk bar.
26669
+ */
26670
+ locations: z.array(RecordingLocationUsageSchema).optional()
26651
26671
  });
26652
26672
  /**
26653
26673
  * The OPERATOR-ARMED half of multi-location recordings (D116).
@@ -37759,8 +37779,14 @@ var SESSION_ONLY_POLICY = {
37759
37779
  durability: "session",
37760
37780
  volatileFields: []
37761
37781
  };
37782
+ /** Schema defaults — an untouched sub-field must author exactly these. */
37783
+ var NC_AUDIO_DEFAULTS = {
37784
+ hitPercent: 60,
37785
+ samplingSeconds: 10
37786
+ };
37762
37787
  new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
37763
37788
  new Set(["devices", "classes"]);
37789
+ NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
37764
37790
  /**
37765
37791
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
37766
37792
  *
package/dist/index.js CHANGED
@@ -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-CB2eCKJb.js");
3
+ const require_dist = require("./dist-Ctfhlrtl.js");
4
4
  const require_builtins_alerts_alerts_addon = require("./builtins/alerts/alerts.addon.js");
5
5
  require("./builtins/alerts/index.js");
6
6
  const require_formatter = require("./formatter-DqAKDlvN.js");
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
2
- import { At as asNumber, B as extractNestedAddonId, Bt as parseJsonUnknown$1, Dt as ReadinessTimeoutError, Et as ReadinessRegistry, Ft as expandCapMethods, Gt as EventCategory$1, H as isArrayOutputSchema, Ht as resolveCapMount, J as lifecycleJobSchema, K as isVoidInput, Mt as createEvent, Nt as emitDownForOwnedCaps, Q as looseSchema, St as DEVICE_STATUS_METHOD, U as isCollectionArrayMethod, Ut as scopeKey, Vt as readinessKey, W as isObjectInput, Z as logLevelAtMost, bt as DATAPLANE_SECRET_HEADER$1, d as METHOD_ACCESS_MAP, f as RUNTIME_DEFAULTS, it as procedureAuthKey, jt as asString$1, kt as asJsonObject$1, ot as scopesAllowAddon, q as kebabToCamel, st as scopesAllowDeviceCap, t as ALL_CAPABILITY_DEFINITIONS, tt as objectInputDeclaresAddonId, vt as errMsg$1, xt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, y as addonSettingsCapability, zt as parseJsonObject } from "./dist-CF6uy00y.mjs";
2
+ import { At as asNumber, B as extractNestedAddonId, Bt as parseJsonUnknown$1, Dt as ReadinessTimeoutError, Et as ReadinessRegistry, Ft as expandCapMethods, Gt as EventCategory$1, H as isArrayOutputSchema, Ht as resolveCapMount, J as lifecycleJobSchema, K as isVoidInput, Mt as createEvent, Nt as emitDownForOwnedCaps, Q as looseSchema, St as DEVICE_STATUS_METHOD, U as isCollectionArrayMethod, Ut as scopeKey, Vt as readinessKey, W as isObjectInput, Z as logLevelAtMost, bt as DATAPLANE_SECRET_HEADER$1, d as METHOD_ACCESS_MAP, f as RUNTIME_DEFAULTS, it as procedureAuthKey, jt as asString$1, kt as asJsonObject$1, ot as scopesAllowAddon, q as kebabToCamel, st as scopesAllowDeviceCap, t as ALL_CAPABILITY_DEFINITIONS, tt as objectInputDeclaresAddonId, vt as errMsg$1, xt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, y as addonSettingsCapability, zt as parseJsonObject } from "./dist-_oC_QkQA.mjs";
3
3
  import { AlertCenterAddon } from "./builtins/alerts/alerts.addon.mjs";
4
4
  import "./builtins/alerts/index.mjs";
5
5
  import { t as formatLogLine } from "./formatter-B7qW8bPJ.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.105",
3
+ "version": "1.2.107",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",