@camstack/system 1.2.439 → 1.2.441

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 (103) hide show
  1. package/dist/addon-runner.js +743 -79
  2. package/dist/addon-runner.mjs +743 -79
  3. package/dist/addon-utils.js +1 -1
  4. package/dist/addon-utils.mjs +1 -1
  5. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  9. package/dist/builtins/alerts/alerts.addon.js +1 -1
  10. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  11. package/dist/builtins/autotrack/index.js +1 -1
  12. package/dist/builtins/autotrack/index.mjs +1 -1
  13. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
  14. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
  15. package/dist/builtins/calendars/calendars.addon.js +1 -1
  16. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  17. package/dist/builtins/camera-grid/index.js +1 -1
  18. package/dist/builtins/camera-grid/index.mjs +1 -1
  19. package/dist/builtins/composer/composer.addon.js +2 -2
  20. package/dist/builtins/composer/composer.addon.mjs +2 -2
  21. package/dist/builtins/console-logging/index.js +1 -1
  22. package/dist/builtins/console-logging/index.mjs +1 -1
  23. package/dist/builtins/core-blocks/block-settings-store.d.ts +1 -1
  24. package/dist/builtins/core-blocks/core-blocks.addon.js +77 -11
  25. package/dist/builtins/core-blocks/core-blocks.addon.mjs +77 -12
  26. package/dist/builtins/core-blocks/index.mjs +1 -1
  27. package/dist/builtins/device-manager/device-manager.addon.js +181 -59
  28. package/dist/builtins/device-manager/device-manager.addon.mjs +180 -60
  29. package/dist/builtins/device-manager/device-removal-hooks.d.ts +0 -11
  30. package/dist/builtins/hub-forwarder/index.js +1 -1
  31. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  32. package/dist/builtins/keychain/index.js +29 -11
  33. package/dist/builtins/keychain/index.mjs +29 -11
  34. package/dist/builtins/keychain/keychain-service.d.ts +8 -0
  35. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  36. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  37. package/dist/builtins/local-auth/local-auth.addon.js +2 -2
  38. package/dist/builtins/local-auth/local-auth.addon.mjs +2 -2
  39. package/dist/builtins/local-network/local-network.addon.js +2 -2
  40. package/dist/builtins/local-network/local-network.addon.mjs +2 -2
  41. package/dist/builtins/loki-logging/index.js +1 -1
  42. package/dist/builtins/loki-logging/index.mjs +1 -1
  43. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  44. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  45. package/dist/builtins/platform-probe/index.js +1 -1
  46. package/dist/builtins/platform-probe/index.mjs +1 -1
  47. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  48. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  49. package/dist/builtins/snapshot/index.js +1 -1
  50. package/dist/builtins/snapshot/index.mjs +1 -1
  51. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  52. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  53. package/dist/builtins/sqlite-storage/index.js +1 -1
  54. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  55. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +38 -7
  56. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +69 -3
  57. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +69 -3
  58. package/dist/builtins/sqlite-storage/storage-admin-provider.d.ts +31 -0
  59. package/dist/builtins/storage-orchestrator/hub-settings-forward.d.ts +3 -3
  60. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +4 -4
  61. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +4 -4
  62. package/dist/builtins/system-config/system-config.addon.js +2 -2
  63. package/dist/builtins/system-config/system-config.addon.mjs +2 -2
  64. package/dist/builtins/winston-logging/index.js +1 -1
  65. package/dist/builtins/winston-logging/index.mjs +1 -1
  66. package/dist/{composition-sources-B4jC2FC7.js → composition-sources-BG-W2U0d.js} +1 -1
  67. package/dist/{composition-sources-BGOSCGWa.mjs → composition-sources-ByNISOnW.mjs} +1 -1
  68. package/dist/{constants-B92BjObl.mjs → constants-DJILnUSb.mjs} +1 -1
  69. package/dist/{constants-BRsjQwi-.js → constants-UojhAJcm.js} +1 -1
  70. package/dist/{device-ca-DHG9OW2m.js → device-ca-196DAEZT.js} +1 -1
  71. package/dist/{device-ca-46BUsWD1.mjs → device-ca-D8Ri9TVz.mjs} +1 -1
  72. package/dist/{dist-yI6kF6r6.mjs → dist-BWvkuc31.mjs} +248 -27
  73. package/dist/{dist-BOWdLjoK.js → dist-BiTCcaf-.js} +265 -26
  74. package/dist/file-host-worker.js +1 -1
  75. package/dist/file-host-worker.mjs +1 -1
  76. package/dist/{hub-mdns-ciao-DGwVc2uh.mjs → hub-mdns-ciao-Cnd0bTB9.mjs} +1 -1
  77. package/dist/{hub-mdns-ciao-D7S5AJRU.js → hub-mdns-ciao-DOBTq8qr.js} +1 -1
  78. package/dist/index.js +336 -336
  79. package/dist/index.mjs +337 -337
  80. package/dist/kernel/addon-settings-store.d.ts +43 -0
  81. package/dist/kernel/config-manager.d.ts +0 -19
  82. package/dist/kernel/device-settings-store.d.ts +74 -0
  83. package/dist/kernel/legacy-blob-guard.d.ts +58 -0
  84. package/dist/kernel/moleculer/runner-addon-settings.d.ts +34 -0
  85. package/dist/kernel/moleculer/runner-device-settings.d.ts +39 -0
  86. package/dist/kernel/prefix-walk.d.ts +39 -0
  87. package/dist/kernel/sections-store.d.ts +20 -0
  88. package/dist/kernel/settings-door-view.d.ts +2 -2
  89. package/dist/kernel/settings-view-types.d.ts +5 -30
  90. package/dist/kernel/store-settings-view.d.ts +0 -3
  91. package/dist/prefix-range-D_KueXbQ.js +27 -0
  92. package/dist/prefix-range-Dl0znowN.mjs +22 -0
  93. package/dist/prefix-walk-DAOBSyH1.mjs +88 -0
  94. package/dist/prefix-walk-Dv6MLZlM.js +99 -0
  95. package/dist/{restore-archive-CpdUO-uS.mjs → restore-archive-4ziluTU3.mjs} +1 -1
  96. package/dist/{restore-archive-DiGiI6Jp.js → restore-archive-CfSSjKnA.js} +1 -1
  97. package/dist/{retired-settings-keys-UsVtLOT0.mjs → retired-settings-keys-Bhwe4ymb.mjs} +2 -2
  98. package/dist/{retired-settings-keys-CyQ7uvta.js → retired-settings-keys-D_VwZiDt.js} +10 -4
  99. package/dist/{wal-maintenance-xsvdT5Go.mjs → wal-maintenance-B57SAp4a.mjs} +0 -0
  100. package/dist/{wal-maintenance-DEL_jeG0.js → wal-maintenance-CgZTNWAz.js} +0 -0
  101. package/package.json +5 -2
  102. package/dist/{custom-action-registry-Bf_YnagS.js → custom-action-registry-BKmoaqG0.js} +1 -1
  103. package/dist/{custom-action-registry-BQnbN0DP.mjs → custom-action-registry-CyUuhATc.mjs} +1 -1
@@ -3,8 +3,10 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BOWdLjoK.js");
7
- const require_retired_settings_keys = require("../../retired-settings-keys-CyQ7uvta.js");
6
+ const require_dist = require("../../dist-BiTCcaf-.js");
7
+ const require_prefix_range = require("../../prefix-range-D_KueXbQ.js");
8
+ const require_prefix_walk = require("../../prefix-walk-Dv6MLZlM.js");
9
+ const require_retired_settings_keys = require("../../retired-settings-keys-D_VwZiDt.js");
8
10
  let zod = require("zod");
9
11
  let node_crypto = require("node:crypto");
10
12
  let _camstack_types_node = require("@camstack/types/node");
@@ -2730,30 +2732,13 @@ async function listPersistedByAddon(pctx, input) {
2730
2732
  return saved;
2731
2733
  }
2732
2734
  /**
2733
- * Every requested device's persisted config, in as few store round trips as the
2734
- * settings view allows.
2735
- *
2736
- * One call when the view can batch (the in-process door, which keys
2737
- * `addon-device-settings` by `<addonId>:<deviceId>.` and answers a whole addon
2738
- * with one indexed range scan). Otherwise the per-device form, unchanged — a
2739
- * fallback and not a silent downgrade: `readDeviceStoreBatch` is optional
2740
- * precisely because a forked addon's view has nothing to batch, and refusing
2741
- * would break the only implementation that cannot comply.
2742
- *
2743
- * A read that THROWS yields `{}` for that device rather than failing the whole
2744
- * listing: one unreadable settings row must not make the fleet unlistable.
2735
+ * Every requested device's persisted config in ONE store round trip (D870). A
2736
+ * store that cannot be asked THROWS and the listing fails with it: an
2737
+ * unreadable row is not a device with no settings (D49).
2745
2738
  */
2746
2739
  async function readDeviceConfigs(pctx, deviceIds) {
2747
2740
  if (deviceIds.length === 0) return /* @__PURE__ */ new Map();
2748
- const batch = pctx.settings.readDeviceStoreBatch;
2749
- if (batch !== void 0) return batch.call(pctx.settings, deviceIds);
2750
- const out = /* @__PURE__ */ new Map();
2751
- for (const id of deviceIds) try {
2752
- out.set(id, await pctx.settings.readDeviceStore(id));
2753
- } catch {
2754
- out.set(id, {});
2755
- }
2756
- return out;
2741
+ return pctx.settings.readDeviceStoreBatch(deviceIds);
2757
2742
  }
2758
2743
  async function listAll(pctx, input) {
2759
2744
  const ownerFilter = Reflect.get(input, "addonId");
@@ -4856,6 +4841,170 @@ function createDeviceManagerExtensionProvider(pctx) {
4856
4841
  });
4857
4842
  }
4858
4843
  //#endregion
4844
+ //#region src/kernel/device-settings-store.ts
4845
+ /** The collection every addon's per-device settings share. Named nowhere else. */
4846
+ var DEVICE_SETTINGS_COLLECTION = "addon-device-settings";
4847
+ function scopeOf(addonId, deviceId) {
4848
+ return `${addonId}:${String(deviceId)}.`;
4849
+ }
4850
+ function assertDeviceId(deviceId) {
4851
+ if (!Number.isInteger(deviceId) || deviceId <= 0) throw new Error(`device settings: "${String(deviceId)}" is not a device id`);
4852
+ }
4853
+ function deviceIdOfSuffix(suffix) {
4854
+ const dot = suffix.indexOf(".");
4855
+ if (dot <= 0) return null;
4856
+ const id = Number(suffix.slice(0, dot));
4857
+ return Number.isInteger(id) && id > 0 ? id : null;
4858
+ }
4859
+ /** Every field row of one device scope, or a throw. */
4860
+ async function readScope(door, addonId, deviceId) {
4861
+ return require_prefix_walk.loadPrefixed(door, DEVICE_SETTINGS_COLLECTION, scopeOf(addonId, deviceId));
4862
+ }
4863
+ function createDeviceSettingsStore(door, addonId) {
4864
+ return {
4865
+ async read(deviceId) {
4866
+ try {
4867
+ assertDeviceId(deviceId);
4868
+ const value = await readScope(door, addonId, deviceId);
4869
+ return Object.keys(value).length === 0 ? { kind: "absent" } : {
4870
+ kind: "present",
4871
+ value
4872
+ };
4873
+ } catch (err) {
4874
+ return {
4875
+ kind: "unreadable",
4876
+ error: err instanceof Error ? err.message : String(err)
4877
+ };
4878
+ }
4879
+ },
4880
+ async readBatch(deviceIds) {
4881
+ const out = /* @__PURE__ */ new Map();
4882
+ if (deviceIds.length === 0) return out;
4883
+ for (const id of deviceIds) out.set(id, {});
4884
+ const wanted = new Set(deviceIds);
4885
+ const all = await require_prefix_walk.loadPrefixed(door, DEVICE_SETTINGS_COLLECTION, `${addonId}:`);
4886
+ for (const [suffix, value] of Object.entries(all)) {
4887
+ const id = deviceIdOfSuffix(suffix);
4888
+ if (id === null || !wanted.has(id)) continue;
4889
+ const blob = out.get(id);
4890
+ if (blob !== void 0) blob[suffix.slice(suffix.indexOf(".") + 1)] = value;
4891
+ }
4892
+ return out;
4893
+ },
4894
+ async listIds() {
4895
+ const all = await require_prefix_walk.loadPrefixed(door, DEVICE_SETTINGS_COLLECTION, `${addonId}:`, () => null);
4896
+ const ids = /* @__PURE__ */ new Set();
4897
+ for (const suffix of Object.keys(all)) {
4898
+ const id = deviceIdOfSuffix(suffix);
4899
+ if (id !== null) ids.add(id);
4900
+ }
4901
+ return [...ids].toSorted((a, b) => a - b);
4902
+ },
4903
+ async merge(deviceId, patch) {
4904
+ assertDeviceId(deviceId);
4905
+ const scope = scopeOf(addonId, deviceId);
4906
+ const records = [];
4907
+ const removed = [];
4908
+ for (const [key, value] of Object.entries(patch)) if (value === void 0) removed.push(`${scope}${key}`);
4909
+ else records.push({
4910
+ id: `${scope}${key}`,
4911
+ data: {
4912
+ addonId,
4913
+ deviceId: String(deviceId),
4914
+ key,
4915
+ value
4916
+ }
4917
+ });
4918
+ if (records.length === 0 && removed.length === 0) return;
4919
+ await door.transact({ ops: [...removed.length > 0 ? [{
4920
+ op: "deleteWhere",
4921
+ collection: DEVICE_SETTINGS_COLLECTION,
4922
+ filter: { whereIn: { id: removed } }
4923
+ }] : [], ...records.length > 0 ? [{
4924
+ op: "upsertMany",
4925
+ collection: DEVICE_SETTINGS_COLLECTION,
4926
+ conflict: ["id"],
4927
+ records
4928
+ }] : []] });
4929
+ },
4930
+ async clear(deviceId) {
4931
+ assertDeviceId(deviceId);
4932
+ const range = require_prefix_range.prefixRange(scopeOf(addonId, deviceId));
4933
+ if (range === null) throw new Error(`device settings: no id range for addon "${addonId}"`);
4934
+ await door.deleteWhere({
4935
+ collection: DEVICE_SETTINGS_COLLECTION,
4936
+ filter: { whereBetween: { id: [range.lo, range.hi] } }
4937
+ });
4938
+ }
4939
+ };
4940
+ }
4941
+ /** The `Record` form of a read: `{}` for a store that answered with no row, a THROW for one that did not. */
4942
+ async function readDeviceOrThrow(store, deviceId) {
4943
+ const read = await store.read(deviceId);
4944
+ switch (read.kind) {
4945
+ case "present": return { ...read.value };
4946
+ case "absent": return {};
4947
+ case "unreadable": throw new Error(`device settings of ${String(deviceId)} could not be read: ${read.error}`);
4948
+ }
4949
+ }
4950
+ /**
4951
+ * Remove a device's rows for EVERY addon, in one statement (D865's central
4952
+ * delete). Matched on the device id each row carries, so no addon needs a
4953
+ * handler. Returns how many rows went.
4954
+ */
4955
+ async function clearDeviceSettings(door, deviceId) {
4956
+ assertDeviceId(deviceId);
4957
+ const { deleted } = await door.deleteWhere({
4958
+ collection: DEVICE_SETTINGS_COLLECTION,
4959
+ filter: { where: { deviceId: String(deviceId) } }
4960
+ });
4961
+ return deleted;
4962
+ }
4963
+ /** Rows one page of the scope walk asks for. */
4964
+ var SCOPE_PAGE_ROWS = 1e3;
4965
+ /** Pages the walk may take before it refuses (a bound, never a silent cut). */
4966
+ var SCOPE_PAGE_BUDGET = 512;
4967
+ /** Above every id: ids are ASCII. */
4968
+ var SCOPE_TAIL = "";
4969
+ /**
4970
+ * Every (addon, device) pair holding rows, across all addons - the admin
4971
+ * "deleted devices" listing. A keyset walk by primary key (never one query cut
4972
+ * at a row cap): a read that cannot be completed THROWS.
4973
+ */
4974
+ async function listDeviceSettingsScopes(door) {
4975
+ const seen = /* @__PURE__ */ new Map();
4976
+ let cursor = "";
4977
+ for (let page = 0;; page++) {
4978
+ if (page >= SCOPE_PAGE_BUDGET) throw new Error(`device settings scopes: more than ${SCOPE_PAGE_BUDGET} pages - refusing a partial list`);
4979
+ const rows = await door.query({
4980
+ collection: DEVICE_SETTINGS_COLLECTION,
4981
+ filter: {
4982
+ whereBetween: { id: [cursor, SCOPE_TAIL] },
4983
+ orderBy: {
4984
+ field: "id",
4985
+ direction: "asc"
4986
+ },
4987
+ limit: SCOPE_PAGE_ROWS
4988
+ }
4989
+ });
4990
+ for (const row of rows) {
4991
+ const addonId = row.data["addonId"];
4992
+ const raw = row.data["deviceId"];
4993
+ if (typeof addonId !== "string" || typeof raw !== "string") continue;
4994
+ const deviceId = Number(raw);
4995
+ if (!Number.isInteger(deviceId) || deviceId <= 0) continue;
4996
+ seen.set(`${addonId}:${deviceId}`, {
4997
+ deviceId,
4998
+ addonId
4999
+ });
5000
+ }
5001
+ const last = rows[rows.length - 1];
5002
+ if (rows.length < SCOPE_PAGE_ROWS || last === void 0) return [...seen.values()];
5003
+ if (last.id === cursor) throw new Error(`device settings scopes: no progress at "${last.id}"`);
5004
+ cursor = last.id;
5005
+ }
5006
+ }
5007
+ //#endregion
4859
5008
  //#region src/builtins/device-manager/migration-refused.ts
4860
5009
  /**
4861
5010
  * A migration step that REFUSED with nothing moved.
@@ -5788,41 +5937,16 @@ async function cleanupDeletedDevicesData(deps, input) {
5788
5937
  unreachable: [...unreachable]
5789
5938
  };
5790
5939
  }
5791
- /** The settings-store collection every addon's per-device settings share. */
5792
- var ADDON_DEVICE_SETTINGS_COLLECTION = "addon-device-settings";
5793
- /** The engine's hard ceiling on one read; a result this long may be a truncated one. */
5794
- var SCOPE_READ_LIMIT = 2e4;
5795
5940
  /** Scope access over the settings store; undefined when the store is unavailable. */
5796
5941
  function settingsScopes(store) {
5797
5942
  if (store === void 0) return void 0;
5943
+ const door = {
5944
+ query: (input) => store.query.query(input),
5945
+ deleteWhere: (input) => store.deleteWhere.mutate(input)
5946
+ };
5798
5947
  return {
5799
- list: async () => {
5800
- const rows = await store.query.query({
5801
- collection: ADDON_DEVICE_SETTINGS_COLLECTION,
5802
- filter: { limit: SCOPE_READ_LIMIT }
5803
- });
5804
- if (rows.length >= SCOPE_READ_LIMIT) throw new Error(`the settings read returned ${rows.length} rows — possibly truncated`);
5805
- const seen = /* @__PURE__ */ new Map();
5806
- for (const row of rows) {
5807
- const addonId = row.data["addonId"];
5808
- const raw = row.data["deviceId"];
5809
- if (typeof addonId !== "string" || typeof raw !== "string") continue;
5810
- const deviceId = Number(raw);
5811
- if (!Number.isInteger(deviceId) || deviceId <= 0) continue;
5812
- seen.set(`${addonId}:${deviceId}`, {
5813
- deviceId,
5814
- addonId
5815
- });
5816
- }
5817
- return [...seen.values()];
5818
- },
5819
- clear: async (deviceId) => {
5820
- const { deleted } = await store.deleteWhere.mutate({
5821
- collection: ADDON_DEVICE_SETTINGS_COLLECTION,
5822
- filter: { where: { deviceId: String(deviceId) } }
5823
- });
5824
- return deleted;
5825
- }
5948
+ list: () => listDeviceSettingsScopes(door),
5949
+ clear: (deviceId) => clearDeviceSettings(door, deviceId)
5826
5950
  };
5827
5951
  }
5828
5952
  //#endregion
@@ -6267,11 +6391,7 @@ function removalSteps(pctx, deviceId, providerAddonId, deletedAt) {
6267
6391
  run: async () => {
6268
6392
  const api = pctx.host.ctx.api?.settingsStore;
6269
6393
  if (api === void 0) throw new Error("settings-store api not available");
6270
- const { deleted } = await api.deleteWhere.mutate({
6271
- collection: "addon-device-settings",
6272
- filter: { where: { deviceId: String(deviceId) } }
6273
- });
6274
- return deleted;
6394
+ return clearDeviceSettings({ deleteWhere: (input) => api.deleteWhere.mutate(input) }, deviceId);
6275
6395
  }
6276
6396
  },
6277
6397
  {
@@ -11089,5 +11209,7 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
11089
11209
  //#endregion
11090
11210
  exports.DeviceManagerAddon = DeviceManagerAddon;
11091
11211
  exports.default = DeviceManagerAddon;
11212
+ exports.createDeviceSettingsStore = createDeviceSettingsStore;
11092
11213
  exports.mergeAggregates = mergeAggregates;
11214
+ exports.readDeviceOrThrow = readDeviceOrThrow;
11093
11215
  exports.setWrapperActive = setWrapperActive;
@@ -1,5 +1,7 @@
1
- import { Bt as enumerateItemArrayFields, C as DAY_NAMES, Dn as parseStreamParamsFormPatch, Dr as errMsg, Er as DeviceType, Et as createDeviceExtensionProvider, Ft as deviceExtensionCapability, Ir as SECRET_CLEAR, It as deviceManagerCapability, Lr as WELL_KNOWN_TAB_MAP, Lt as deviceStateCapability, N as FieldClaimModeSchema, Or as BaseAddon, P as FieldClaimSchema, Qr as isDeviceConfigCap, Rt as deviceStatusCapability, Tr as DeviceRole, Un as runtimeStatePolicyFor, Ur as collectZodSecretPaths, V as MINUTES_PER_DAY, Vt as enumerateSchemaFields, Wt as expandContainer, Xt as formatMinutes, Yn as siteZoneKey, _i as transformSecretWrite, c as CAMERA_SWITCH_ORDER, di as resolveSecretMarkers, ei as isKeychainStoredField, gi as toSecretFieldView, gt as canonicalIconKey, j as DeviceStatusSchema, jt as defineExtensionSlotProvider, ln as legacyAutoLinkIds, mi as sleep$1, ni as isSecretMarker, pi as secretTreeFromPaths, pn as locationIconKey, pt as buildStreamParamsConfigSchema, q as STREAM_PROFILE_META, r as ALL_CAPABILITY_DEFINITIONS, ri as isSecretMarkerLike, ti as isSecretClear, u as CAP_NAMES_WITH_STATUS, vi as userError, wn as normalizeUnit, wr as DeviceFeature, yi as EventCategory } from "../../dist-yI6kF6r6.mjs";
2
- import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-UsVtLOT0.mjs";
1
+ import { Ar as errMsg, Br as WELL_KNOWN_TAB_MAP, Bt as deviceStatusCapability, C as DAY_NAMES, Dr as DeviceFeature, En as normalizeUnit, Gn as runtimeStatePolicyFor, Ht as enumerateItemArrayFields, Kr as collectZodSecretPaths, Kt as expandContainer, Lt as deviceExtensionCapability, N as FieldClaimModeSchema, Nt as defineExtensionSlotProvider, Or as DeviceRole, Ot as createDeviceExtensionProvider, P as FieldClaimSchema, Qt as formatMinutes, Rt as deviceManagerCapability, Si as EventCategory, Ut as enumerateSchemaFields, V as MINUTES_PER_DAY, Y as STREAM_PROFILE_META, Zn as siteZoneKey, _i as sleep$1, ai as isSecretMarker, bi as transformSecretWrite, c as CAMERA_SWITCH_ORDER, dn as legacyAutoLinkIds, gi as secretTreeFromPaths, hn as locationIconKey, ht as buildStreamParamsConfigSchema, ii as isSecretClear, j as DeviceStatusSchema, jr as BaseAddon, kn as parseStreamParamsFormPatch, kr as DeviceType, mi as resolveSecretMarkers, oi as isSecretMarkerLike, r as ALL_CAPABILITY_DEFINITIONS, ri as isKeychainStoredField, ti as isDeviceConfigCap, u as CAP_NAMES_WITH_STATUS, vt as canonicalIconKey, xi as userError, yi as toSecretFieldView, zr as SECRET_CLEAR, zt as deviceStateCapability } from "../../dist-BWvkuc31.mjs";
2
+ import { t as prefixRange } from "../../prefix-range-Dl0znowN.mjs";
3
+ import { t as loadPrefixed } from "../../prefix-walk-DAOBSyH1.mjs";
4
+ import { r as purgeRetiredSettingsRows } from "../../retired-settings-keys-Bhwe4ymb.mjs";
3
5
  import { z } from "zod";
4
6
  import { randomUUID } from "node:crypto";
5
7
  import { canonicalDeviceFingerprint } from "@camstack/types/node";
@@ -2725,30 +2727,13 @@ async function listPersistedByAddon(pctx, input) {
2725
2727
  return saved;
2726
2728
  }
2727
2729
  /**
2728
- * Every requested device's persisted config, in as few store round trips as the
2729
- * settings view allows.
2730
- *
2731
- * One call when the view can batch (the in-process door, which keys
2732
- * `addon-device-settings` by `<addonId>:<deviceId>.` and answers a whole addon
2733
- * with one indexed range scan). Otherwise the per-device form, unchanged — a
2734
- * fallback and not a silent downgrade: `readDeviceStoreBatch` is optional
2735
- * precisely because a forked addon's view has nothing to batch, and refusing
2736
- * would break the only implementation that cannot comply.
2737
- *
2738
- * A read that THROWS yields `{}` for that device rather than failing the whole
2739
- * listing: one unreadable settings row must not make the fleet unlistable.
2730
+ * Every requested device's persisted config in ONE store round trip (D870). A
2731
+ * store that cannot be asked THROWS and the listing fails with it: an
2732
+ * unreadable row is not a device with no settings (D49).
2740
2733
  */
2741
2734
  async function readDeviceConfigs(pctx, deviceIds) {
2742
2735
  if (deviceIds.length === 0) return /* @__PURE__ */ new Map();
2743
- const batch = pctx.settings.readDeviceStoreBatch;
2744
- if (batch !== void 0) return batch.call(pctx.settings, deviceIds);
2745
- const out = /* @__PURE__ */ new Map();
2746
- for (const id of deviceIds) try {
2747
- out.set(id, await pctx.settings.readDeviceStore(id));
2748
- } catch {
2749
- out.set(id, {});
2750
- }
2751
- return out;
2736
+ return pctx.settings.readDeviceStoreBatch(deviceIds);
2752
2737
  }
2753
2738
  async function listAll(pctx, input) {
2754
2739
  const ownerFilter = Reflect.get(input, "addonId");
@@ -4851,6 +4836,170 @@ function createDeviceManagerExtensionProvider(pctx) {
4851
4836
  });
4852
4837
  }
4853
4838
  //#endregion
4839
+ //#region src/kernel/device-settings-store.ts
4840
+ /** The collection every addon's per-device settings share. Named nowhere else. */
4841
+ var DEVICE_SETTINGS_COLLECTION = "addon-device-settings";
4842
+ function scopeOf(addonId, deviceId) {
4843
+ return `${addonId}:${String(deviceId)}.`;
4844
+ }
4845
+ function assertDeviceId(deviceId) {
4846
+ if (!Number.isInteger(deviceId) || deviceId <= 0) throw new Error(`device settings: "${String(deviceId)}" is not a device id`);
4847
+ }
4848
+ function deviceIdOfSuffix(suffix) {
4849
+ const dot = suffix.indexOf(".");
4850
+ if (dot <= 0) return null;
4851
+ const id = Number(suffix.slice(0, dot));
4852
+ return Number.isInteger(id) && id > 0 ? id : null;
4853
+ }
4854
+ /** Every field row of one device scope, or a throw. */
4855
+ async function readScope(door, addonId, deviceId) {
4856
+ return loadPrefixed(door, DEVICE_SETTINGS_COLLECTION, scopeOf(addonId, deviceId));
4857
+ }
4858
+ function createDeviceSettingsStore(door, addonId) {
4859
+ return {
4860
+ async read(deviceId) {
4861
+ try {
4862
+ assertDeviceId(deviceId);
4863
+ const value = await readScope(door, addonId, deviceId);
4864
+ return Object.keys(value).length === 0 ? { kind: "absent" } : {
4865
+ kind: "present",
4866
+ value
4867
+ };
4868
+ } catch (err) {
4869
+ return {
4870
+ kind: "unreadable",
4871
+ error: err instanceof Error ? err.message : String(err)
4872
+ };
4873
+ }
4874
+ },
4875
+ async readBatch(deviceIds) {
4876
+ const out = /* @__PURE__ */ new Map();
4877
+ if (deviceIds.length === 0) return out;
4878
+ for (const id of deviceIds) out.set(id, {});
4879
+ const wanted = new Set(deviceIds);
4880
+ const all = await loadPrefixed(door, DEVICE_SETTINGS_COLLECTION, `${addonId}:`);
4881
+ for (const [suffix, value] of Object.entries(all)) {
4882
+ const id = deviceIdOfSuffix(suffix);
4883
+ if (id === null || !wanted.has(id)) continue;
4884
+ const blob = out.get(id);
4885
+ if (blob !== void 0) blob[suffix.slice(suffix.indexOf(".") + 1)] = value;
4886
+ }
4887
+ return out;
4888
+ },
4889
+ async listIds() {
4890
+ const all = await loadPrefixed(door, DEVICE_SETTINGS_COLLECTION, `${addonId}:`, () => null);
4891
+ const ids = /* @__PURE__ */ new Set();
4892
+ for (const suffix of Object.keys(all)) {
4893
+ const id = deviceIdOfSuffix(suffix);
4894
+ if (id !== null) ids.add(id);
4895
+ }
4896
+ return [...ids].toSorted((a, b) => a - b);
4897
+ },
4898
+ async merge(deviceId, patch) {
4899
+ assertDeviceId(deviceId);
4900
+ const scope = scopeOf(addonId, deviceId);
4901
+ const records = [];
4902
+ const removed = [];
4903
+ for (const [key, value] of Object.entries(patch)) if (value === void 0) removed.push(`${scope}${key}`);
4904
+ else records.push({
4905
+ id: `${scope}${key}`,
4906
+ data: {
4907
+ addonId,
4908
+ deviceId: String(deviceId),
4909
+ key,
4910
+ value
4911
+ }
4912
+ });
4913
+ if (records.length === 0 && removed.length === 0) return;
4914
+ await door.transact({ ops: [...removed.length > 0 ? [{
4915
+ op: "deleteWhere",
4916
+ collection: DEVICE_SETTINGS_COLLECTION,
4917
+ filter: { whereIn: { id: removed } }
4918
+ }] : [], ...records.length > 0 ? [{
4919
+ op: "upsertMany",
4920
+ collection: DEVICE_SETTINGS_COLLECTION,
4921
+ conflict: ["id"],
4922
+ records
4923
+ }] : []] });
4924
+ },
4925
+ async clear(deviceId) {
4926
+ assertDeviceId(deviceId);
4927
+ const range = prefixRange(scopeOf(addonId, deviceId));
4928
+ if (range === null) throw new Error(`device settings: no id range for addon "${addonId}"`);
4929
+ await door.deleteWhere({
4930
+ collection: DEVICE_SETTINGS_COLLECTION,
4931
+ filter: { whereBetween: { id: [range.lo, range.hi] } }
4932
+ });
4933
+ }
4934
+ };
4935
+ }
4936
+ /** The `Record` form of a read: `{}` for a store that answered with no row, a THROW for one that did not. */
4937
+ async function readDeviceOrThrow(store, deviceId) {
4938
+ const read = await store.read(deviceId);
4939
+ switch (read.kind) {
4940
+ case "present": return { ...read.value };
4941
+ case "absent": return {};
4942
+ case "unreadable": throw new Error(`device settings of ${String(deviceId)} could not be read: ${read.error}`);
4943
+ }
4944
+ }
4945
+ /**
4946
+ * Remove a device's rows for EVERY addon, in one statement (D865's central
4947
+ * delete). Matched on the device id each row carries, so no addon needs a
4948
+ * handler. Returns how many rows went.
4949
+ */
4950
+ async function clearDeviceSettings(door, deviceId) {
4951
+ assertDeviceId(deviceId);
4952
+ const { deleted } = await door.deleteWhere({
4953
+ collection: DEVICE_SETTINGS_COLLECTION,
4954
+ filter: { where: { deviceId: String(deviceId) } }
4955
+ });
4956
+ return deleted;
4957
+ }
4958
+ /** Rows one page of the scope walk asks for. */
4959
+ var SCOPE_PAGE_ROWS = 1e3;
4960
+ /** Pages the walk may take before it refuses (a bound, never a silent cut). */
4961
+ var SCOPE_PAGE_BUDGET = 512;
4962
+ /** Above every id: ids are ASCII. */
4963
+ var SCOPE_TAIL = "";
4964
+ /**
4965
+ * Every (addon, device) pair holding rows, across all addons - the admin
4966
+ * "deleted devices" listing. A keyset walk by primary key (never one query cut
4967
+ * at a row cap): a read that cannot be completed THROWS.
4968
+ */
4969
+ async function listDeviceSettingsScopes(door) {
4970
+ const seen = /* @__PURE__ */ new Map();
4971
+ let cursor = "";
4972
+ for (let page = 0;; page++) {
4973
+ if (page >= SCOPE_PAGE_BUDGET) throw new Error(`device settings scopes: more than ${SCOPE_PAGE_BUDGET} pages - refusing a partial list`);
4974
+ const rows = await door.query({
4975
+ collection: DEVICE_SETTINGS_COLLECTION,
4976
+ filter: {
4977
+ whereBetween: { id: [cursor, SCOPE_TAIL] },
4978
+ orderBy: {
4979
+ field: "id",
4980
+ direction: "asc"
4981
+ },
4982
+ limit: SCOPE_PAGE_ROWS
4983
+ }
4984
+ });
4985
+ for (const row of rows) {
4986
+ const addonId = row.data["addonId"];
4987
+ const raw = row.data["deviceId"];
4988
+ if (typeof addonId !== "string" || typeof raw !== "string") continue;
4989
+ const deviceId = Number(raw);
4990
+ if (!Number.isInteger(deviceId) || deviceId <= 0) continue;
4991
+ seen.set(`${addonId}:${deviceId}`, {
4992
+ deviceId,
4993
+ addonId
4994
+ });
4995
+ }
4996
+ const last = rows[rows.length - 1];
4997
+ if (rows.length < SCOPE_PAGE_ROWS || last === void 0) return [...seen.values()];
4998
+ if (last.id === cursor) throw new Error(`device settings scopes: no progress at "${last.id}"`);
4999
+ cursor = last.id;
5000
+ }
5001
+ }
5002
+ //#endregion
4854
5003
  //#region src/builtins/device-manager/migration-refused.ts
4855
5004
  /**
4856
5005
  * A migration step that REFUSED with nothing moved.
@@ -5783,41 +5932,16 @@ async function cleanupDeletedDevicesData(deps, input) {
5783
5932
  unreachable: [...unreachable]
5784
5933
  };
5785
5934
  }
5786
- /** The settings-store collection every addon's per-device settings share. */
5787
- var ADDON_DEVICE_SETTINGS_COLLECTION = "addon-device-settings";
5788
- /** The engine's hard ceiling on one read; a result this long may be a truncated one. */
5789
- var SCOPE_READ_LIMIT = 2e4;
5790
5935
  /** Scope access over the settings store; undefined when the store is unavailable. */
5791
5936
  function settingsScopes(store) {
5792
5937
  if (store === void 0) return void 0;
5938
+ const door = {
5939
+ query: (input) => store.query.query(input),
5940
+ deleteWhere: (input) => store.deleteWhere.mutate(input)
5941
+ };
5793
5942
  return {
5794
- list: async () => {
5795
- const rows = await store.query.query({
5796
- collection: ADDON_DEVICE_SETTINGS_COLLECTION,
5797
- filter: { limit: SCOPE_READ_LIMIT }
5798
- });
5799
- if (rows.length >= SCOPE_READ_LIMIT) throw new Error(`the settings read returned ${rows.length} rows — possibly truncated`);
5800
- const seen = /* @__PURE__ */ new Map();
5801
- for (const row of rows) {
5802
- const addonId = row.data["addonId"];
5803
- const raw = row.data["deviceId"];
5804
- if (typeof addonId !== "string" || typeof raw !== "string") continue;
5805
- const deviceId = Number(raw);
5806
- if (!Number.isInteger(deviceId) || deviceId <= 0) continue;
5807
- seen.set(`${addonId}:${deviceId}`, {
5808
- deviceId,
5809
- addonId
5810
- });
5811
- }
5812
- return [...seen.values()];
5813
- },
5814
- clear: async (deviceId) => {
5815
- const { deleted } = await store.deleteWhere.mutate({
5816
- collection: ADDON_DEVICE_SETTINGS_COLLECTION,
5817
- filter: { where: { deviceId: String(deviceId) } }
5818
- });
5819
- return deleted;
5820
- }
5943
+ list: () => listDeviceSettingsScopes(door),
5944
+ clear: (deviceId) => clearDeviceSettings(door, deviceId)
5821
5945
  };
5822
5946
  }
5823
5947
  //#endregion
@@ -6262,11 +6386,7 @@ function removalSteps(pctx, deviceId, providerAddonId, deletedAt) {
6262
6386
  run: async () => {
6263
6387
  const api = pctx.host.ctx.api?.settingsStore;
6264
6388
  if (api === void 0) throw new Error("settings-store api not available");
6265
- const { deleted } = await api.deleteWhere.mutate({
6266
- collection: "addon-device-settings",
6267
- filter: { where: { deviceId: String(deviceId) } }
6268
- });
6269
- return deleted;
6389
+ return clearDeviceSettings({ deleteWhere: (input) => api.deleteWhere.mutate(input) }, deviceId);
6270
6390
  }
6271
6391
  },
6272
6392
  {
@@ -11082,4 +11202,4 @@ var DeviceManagerAddon = class extends BaseAddon {
11082
11202
  }
11083
11203
  };
11084
11204
  //#endregion
11085
- export { DeviceManagerAddon, DeviceManagerAddon as default, mergeAggregates, setWrapperActive as t };
11205
+ export { DeviceManagerAddon, DeviceManagerAddon as default, mergeAggregates, readDeviceOrThrow as n, setWrapperActive as r, createDeviceSettingsStore as t };
@@ -1,12 +1,3 @@
1
- /**
2
- * device-manager's side of `device-removal-hook` (D865): tell every implementer
3
- * a device was deleted, and answer the admin question "which stored data belongs
4
- * to devices that no longer exist?".
5
- *
6
- * Two entry points, one rule: **an implementer is told to delete only on a
7
- * recorded delete (a tombstone), or on an explicit operator confirm.** Nothing
8
- * here infers a deletion from a device being absent from a single read.
9
- */
10
1
  import { type CleanupDeletedDevicesResult, type DeviceDataEntry, type DeviceRemovedInput, type DeviceRemovedResult, type IScopedLogger, type OrphanDeviceData, type SettingsStoreClient } from "@camstack/types";
11
2
  import type { RemovalCleanup, RemovalStep } from "./removal-cleanup.js";
12
3
  /** The capability name implementers register under. */
@@ -97,7 +88,5 @@ export interface CleanupDeps {
97
88
  export declare function cleanupDeletedDevicesData(deps: CleanupDeps, input: {
98
89
  readonly confirm: boolean;
99
90
  }): Promise<CleanupDeletedDevicesResult>;
100
- /** The settings-store collection every addon's per-device settings share. */
101
- export declare const ADDON_DEVICE_SETTINGS_COLLECTION = "addon-device-settings";
102
91
  /** Scope access over the settings store; undefined when the store is unavailable. */
103
92
  export declare function settingsScopes(store: Pick<SettingsStoreClient, "query" | "deleteWhere"> | undefined): SettingsScopeAccess | undefined;
@@ -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-BOWdLjoK.js");
6
+ const require_dist = require("../../dist-BiTCcaf-.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 { Or as BaseAddon, mn as logDestinationCapability } from "../../dist-yI6kF6r6.mjs";
1
+ import { gn as logDestinationCapability, jr as BaseAddon } from "../../dist-BWvkuc31.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;