@camstack/system 1.2.438 → 1.2.440

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 (120) 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/{agent-https-BZIwS4xF.js → agent-https-DlpTMDqA.js} +252 -22
  6. package/dist/{agent-https-BPfHrI4c.mjs → agent-https-U0d4uvn2.mjs} +211 -23
  7. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  8. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  9. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  10. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  11. package/dist/builtins/alerts/alerts.addon.js +1 -1
  12. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  13. package/dist/builtins/autotrack/index.js +1 -1
  14. package/dist/builtins/autotrack/index.mjs +1 -1
  15. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
  16. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
  17. package/dist/builtins/calendars/calendars.addon.js +1 -1
  18. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  19. package/dist/builtins/camera-grid/index.js +1 -1
  20. package/dist/builtins/camera-grid/index.mjs +1 -1
  21. package/dist/builtins/composer/composer.addon.js +2 -2
  22. package/dist/builtins/composer/composer.addon.mjs +2 -2
  23. package/dist/builtins/console-logging/index.js +1 -1
  24. package/dist/builtins/console-logging/index.mjs +1 -1
  25. package/dist/builtins/core-blocks/block-settings-store.d.ts +1 -1
  26. package/dist/builtins/core-blocks/core-blocks.addon.js +77 -11
  27. package/dist/builtins/core-blocks/core-blocks.addon.mjs +77 -12
  28. package/dist/builtins/core-blocks/index.mjs +1 -1
  29. package/dist/builtins/device-manager/device-manager.addon.js +197 -60
  30. package/dist/builtins/device-manager/device-manager.addon.mjs +196 -61
  31. package/dist/builtins/device-manager/device-removal-hooks.d.ts +0 -11
  32. package/dist/builtins/device-manager/device-tombstone-store.d.ts +12 -2
  33. package/dist/builtins/hub-forwarder/index.js +1 -1
  34. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  35. package/dist/builtins/keychain/index.js +29 -11
  36. package/dist/builtins/keychain/index.mjs +29 -11
  37. package/dist/builtins/keychain/keychain-service.d.ts +8 -0
  38. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  39. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  40. package/dist/builtins/local-auth/local-auth.addon.js +2 -2
  41. package/dist/builtins/local-auth/local-auth.addon.mjs +2 -2
  42. package/dist/builtins/local-network/local-network.addon.js +7 -2
  43. package/dist/builtins/local-network/local-network.addon.mjs +7 -2
  44. package/dist/builtins/loki-logging/index.js +1 -1
  45. package/dist/builtins/loki-logging/index.mjs +1 -1
  46. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  47. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  48. package/dist/builtins/platform-probe/index.js +1 -1
  49. package/dist/builtins/platform-probe/index.mjs +1 -1
  50. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  51. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  52. package/dist/builtins/snapshot/index.js +1 -1
  53. package/dist/builtins/snapshot/index.mjs +1 -1
  54. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  55. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  56. package/dist/builtins/sqlite-storage/index.js +1 -1
  57. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  58. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +38 -7
  59. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +69 -3
  60. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +69 -3
  61. package/dist/builtins/sqlite-storage/storage-admin-provider.d.ts +31 -0
  62. package/dist/builtins/storage-orchestrator/hub-settings-forward.d.ts +3 -3
  63. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +4 -4
  64. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +4 -4
  65. package/dist/builtins/system-config/system-config.addon.js +4 -4
  66. package/dist/builtins/system-config/system-config.addon.mjs +285 -285
  67. package/dist/builtins/winston-logging/index.js +1 -1
  68. package/dist/builtins/winston-logging/index.mjs +1 -1
  69. package/dist/{composition-sources-DpHGCjdL.mjs → composition-sources-CS0D7imL.mjs} +1 -1
  70. package/dist/{composition-sources-DI3Z83vG.js → composition-sources-cBG8fBTu.js} +1 -1
  71. package/dist/{constants-DQK705Sb.mjs → constants-CIzPjw7L.mjs} +1 -1
  72. package/dist/{constants-wkcn5Ij3.js → constants-CRI3QzT4.js} +1 -1
  73. package/dist/{device-ca-DvIZjXIl.mjs → device-ca-DLKXYseS.mjs} +2 -2
  74. package/dist/{device-ca-BSvkXqyI.js → device-ca-DNHjts4_.js} +2 -2
  75. package/dist/{dist-Dc8GKC5H.mjs → dist-9fcsrFU3.mjs} +271 -32
  76. package/dist/{dist-GgG-7amw.js → dist-BKhlcYz6.js} +288 -31
  77. package/dist/file-host-worker.js +1 -1
  78. package/dist/file-host-worker.mjs +1 -1
  79. package/dist/{hub-mdns-ciao-CBLvq2oj.js → hub-mdns-ciao-3Zc9G9MS.js} +377 -69
  80. package/dist/{hub-mdns-ciao-DMlQTdPU.mjs → hub-mdns-ciao-Dy2YjRqC.mjs} +344 -72
  81. package/dist/index.js +372 -339
  82. package/dist/index.mjs +361 -341
  83. package/dist/kernel/addon-settings-store.d.ts +43 -0
  84. package/dist/kernel/config-manager.d.ts +0 -19
  85. package/dist/kernel/device-settings-store.d.ts +74 -0
  86. package/dist/kernel/legacy-blob-guard.d.ts +58 -0
  87. package/dist/kernel/moleculer/runner-addon-settings.d.ts +34 -0
  88. package/dist/kernel/moleculer/runner-device-settings.d.ts +39 -0
  89. package/dist/kernel/prefix-walk.d.ts +39 -0
  90. package/dist/kernel/sections-store.d.ts +20 -0
  91. package/dist/kernel/settings-door-view.d.ts +2 -2
  92. package/dist/kernel/settings-view-types.d.ts +5 -30
  93. package/dist/kernel/store-settings-view.d.ts +0 -3
  94. package/dist/{lib-NNxHSOIE.js → lib-8oZrELLm.js} +1 -1
  95. package/dist/{lib-BeGzVQcd.mjs → lib-DK38ompL.mjs} +1 -1
  96. package/dist/prefix-range-D_KueXbQ.js +27 -0
  97. package/dist/prefix-range-Dl0znowN.mjs +22 -0
  98. package/dist/prefix-walk-DAOBSyH1.mjs +88 -0
  99. package/dist/prefix-walk-Dv6MLZlM.js +99 -0
  100. package/dist/{restore-archive-CTRz8v9o.js → restore-archive-49aZwkp8.js} +1 -1
  101. package/dist/{restore-archive-DPg9duVY.mjs → restore-archive-DgvDecXM.mjs} +82 -82
  102. package/dist/{retired-settings-keys-Is0zO8gX.mjs → retired-settings-keys-Bhwe4ymb.mjs} +2 -2
  103. package/dist/{retired-settings-keys-Dr_4lfKu.js → retired-settings-keys-D_VwZiDt.js} +10 -4
  104. package/dist/tls/agent-https.d.ts +2 -2
  105. package/dist/tls/boot-tls.d.ts +6 -0
  106. package/dist/tls/cert-evaluation.d.ts +1 -1
  107. package/dist/tls/cert-issue.d.ts +5 -1
  108. package/dist/tls/cert-manager.d.ts +18 -0
  109. package/dist/tls/cert-name-constraints.d.ts +53 -0
  110. package/dist/tls/index.d.ts +5 -4
  111. package/dist/tls/tls-actions.d.ts +27 -7
  112. package/dist/tls/tls-status.d.ts +22 -0
  113. package/dist/tls/uploaded-cert.d.ts +36 -4
  114. package/dist/{wal-maintenance-Bx3lcVA7.mjs → wal-maintenance-BZGmF-3U.mjs} +0 -0
  115. package/dist/{wal-maintenance-mjB2i-VR.js → wal-maintenance-C041zn6V.js} +0 -0
  116. package/dist/{x509.es-r7Ht1thA.js → x509.es-C4cOo48M.js} +36 -0
  117. package/dist/{x509.es-CDvj67p1.mjs → x509.es-DK__ETIe.mjs} +1 -1
  118. package/package.json +7 -2
  119. package/dist/{custom-action-registry-Bf_YnagS.js → custom-action-registry-BKmoaqG0.js} +1 -1
  120. package/dist/{custom-action-registry-BQnbN0DP.mjs → custom-action-registry-CyUuhATc.mjs} +1 -1
@@ -1,5 +1,7 @@
1
- import { A as DeclaredDevices, Cn as normalizeCoreBlockWrite, Ct as coreBlockIdFromAddonId, Dr as errMsg, Ln as resolveCoreBlockEntry, Nt as describeComposableCaps, Or as BaseAddon, Pn as replacedFieldKeys, Rn as resolveCoreBlockFiles, St as coreBlockComposition, Tt as coreBlocksCapability, Zr as hydrateSchema, br as compositionSourceKey, bt as compositionSourceRefs, h as CORE_BLOCK_ADDON_PREFIX, hr as composedDeviceRef, kn as planComposition, m as CORE_BLOCKS_ADDON_ID, or as validateComposition, p as COMPOSER_RUNNER_ID, r as ALL_CAPABILITY_DEFINITIONS, ut as buildCapabilityLookup, v as CoreBlockSchema, wt as coreBlockSourceKind, xt as coreBlockAddonId } from "../../dist-Dc8GKC5H.mjs";
2
- import { t as describeCompositionSources } from "../../composition-sources-DpHGCjdL.mjs";
1
+ import { A as DeclaredDevices, Ar as errMsg, Bn as resolveCoreBlockFiles, Cr as compositionSourceKey, Ct as coreBlockAddonId, Dt as coreBlocksCapability, Et as coreBlockSourceKind, Ft as describeComposableCaps, In as replacedFieldKeys, St as compositionSourceRefs, Tn as normalizeCoreBlockWrite, Tt as coreBlockIdFromAddonId, ei as hydrateSchema, ft as buildCapabilityLookup, h as CORE_BLOCK_ADDON_PREFIX, jn as planComposition, jr as BaseAddon, lr as validateComposition, m as CORE_BLOCKS_ADDON_ID, p as COMPOSER_RUNNER_ID, r as ALL_CAPABILITY_DEFINITIONS, v as CoreBlockSchema, vr as composedDeviceRef, wt as coreBlockComposition, zn as resolveCoreBlockEntry } from "../../dist-9fcsrFU3.mjs";
2
+ import { t as describeCompositionSources } from "../../composition-sources-CS0D7imL.mjs";
3
+ import { t as prefixRange } from "../../prefix-range-Dl0znowN.mjs";
4
+ import { n as unwrapStoredValue, t as loadPrefixed } from "../../prefix-walk-DAOBSyH1.mjs";
3
5
  import { createRequire } from "node:module";
4
6
  import { randomUUID } from "node:crypto";
5
7
  import { customizationBlockName } from "@camstack/types/node";
@@ -485,6 +487,72 @@ var BlockSupervisor = class {
485
487
  }
486
488
  };
487
489
  //#endregion
490
+ //#region src/kernel/addon-settings-store.ts
491
+ /** The collection every addon's settings share. Named nowhere else (with sections' own module). */
492
+ var ADDON_SETTINGS_COLLECTION = "addon-settings";
493
+ function createAddonSettingsStore(door, addonId) {
494
+ const prefix = `${addonId}.`;
495
+ const message = (err) => err instanceof Error ? err.message : String(err);
496
+ return {
497
+ async read() {
498
+ try {
499
+ const value = await loadPrefixed(door, ADDON_SETTINGS_COLLECTION, prefix);
500
+ return Object.keys(value).length === 0 ? { kind: "absent" } : {
501
+ kind: "present",
502
+ value
503
+ };
504
+ } catch (err) {
505
+ return {
506
+ kind: "unreadable",
507
+ error: message(err)
508
+ };
509
+ }
510
+ },
511
+ async readKey(key) {
512
+ try {
513
+ const row = await door.get({
514
+ collection: ADDON_SETTINGS_COLLECTION,
515
+ key: `${prefix}${key}`
516
+ });
517
+ return row === void 0 || row === null ? { kind: "absent" } : {
518
+ kind: "present",
519
+ value: unwrapStoredValue(row)
520
+ };
521
+ } catch (err) {
522
+ return {
523
+ kind: "unreadable",
524
+ error: message(err)
525
+ };
526
+ }
527
+ },
528
+ async merge(patch) {
529
+ const records = [];
530
+ const removed = [];
531
+ for (const [key, value] of Object.entries(patch)) if (value === void 0) removed.push(`${prefix}${key}`);
532
+ else records.push({
533
+ id: `${prefix}${key}`,
534
+ data: {
535
+ addonId,
536
+ key,
537
+ value
538
+ }
539
+ });
540
+ if (records.length === 0 && removed.length === 0) return;
541
+ if (prefixRange(prefix) === null) throw new Error(`addon settings: "${addonId}" cannot be expressed as an id range`);
542
+ await door.transact({ ops: [...removed.length > 0 ? [{
543
+ op: "deleteWhere",
544
+ collection: ADDON_SETTINGS_COLLECTION,
545
+ filter: { whereIn: { id: removed } }
546
+ }] : [], ...records.length > 0 ? [{
547
+ op: "upsertMany",
548
+ collection: ADDON_SETTINGS_COLLECTION,
549
+ conflict: ["id"],
550
+ records
551
+ }] : []] });
552
+ }
553
+ };
554
+ }
555
+ //#endregion
488
556
  //#region src/builtins/core-blocks/block-settings-store.ts
489
557
  /**
490
558
  * @durable class=config owner=core-blocks
@@ -545,15 +613,12 @@ var BlockSettingsStore = class {
545
613
  });
546
614
  }
547
615
  async mirrorToBlockAddon(blockId, values) {
548
- const namespace = coreBlockAddonId(blockId);
549
- try {
550
- await this.store.set.mutate({
551
- namespace,
552
- collection: "addon-settings",
553
- key: "root",
554
- value: values
555
- });
556
- } catch {}
616
+ const store = this.store;
617
+ await createAddonSettingsStore({
618
+ query: (input) => store.query.query(input),
619
+ get: (input) => store.get.query(input),
620
+ transact: (input) => store.transact.mutate(input)
621
+ }, coreBlockAddonId(blockId)).merge(values);
557
622
  }
558
623
  };
559
624
  //#endregion
@@ -2258,4 +2323,4 @@ async function loadTranspiler(logger) {
2258
2323
  }
2259
2324
  }
2260
2325
  //#endregion
2261
- export { CoreBlocksAddon, loadBlockTypeDefs as a, BlockSupervisor as c, blockIdFromAddonId as d, materializeBlock as f, compileBlock as i, BLOCK_ADDON_PREFIX as l, CORE_BLOCKS_COLLECTION as n, BlockSettingsStore as o, removeBlockPackage as p, CoreBlockStore as r, CORE_BLOCK_SETTINGS_COLLECTION as s, extractSettingsSchema as t, blockAddonId as u };
2326
+ export { CoreBlocksAddon, loadBlockTypeDefs as a, createAddonSettingsStore as c, blockAddonId as d, blockIdFromAddonId as f, compileBlock as i, BlockSupervisor as l, removeBlockPackage as m, CORE_BLOCKS_COLLECTION as n, BlockSettingsStore as o, materializeBlock as p, CoreBlockStore as r, CORE_BLOCK_SETTINGS_COLLECTION as s, extractSettingsSchema as t, BLOCK_ADDON_PREFIX as u };
@@ -1,2 +1,2 @@
1
- import { CoreBlocksAddon, a as loadBlockTypeDefs, c as BlockSupervisor, d as blockIdFromAddonId, f as materializeBlock, i as compileBlock, l as BLOCK_ADDON_PREFIX, n as CORE_BLOCKS_COLLECTION, o as BlockSettingsStore, p as removeBlockPackage, r as CoreBlockStore, s as CORE_BLOCK_SETTINGS_COLLECTION, t as extractSettingsSchema, u as blockAddonId } from "./core-blocks.addon.mjs";
1
+ import { CoreBlocksAddon, a as loadBlockTypeDefs, d as blockAddonId, f as blockIdFromAddonId, i as compileBlock, l as BlockSupervisor, m as removeBlockPackage, n as CORE_BLOCKS_COLLECTION, o as BlockSettingsStore, p as materializeBlock, r as CoreBlockStore, s as CORE_BLOCK_SETTINGS_COLLECTION, t as extractSettingsSchema, u as BLOCK_ADDON_PREFIX } from "./core-blocks.addon.mjs";
2
2
  export { BLOCK_ADDON_PREFIX, BlockSettingsStore, BlockSupervisor, CORE_BLOCKS_COLLECTION, CORE_BLOCK_SETTINGS_COLLECTION, CoreBlockStore, CoreBlocksAddon, CoreBlocksAddon as default, blockAddonId, blockIdFromAddonId, compileBlock, extractSettingsSchema, loadBlockTypeDefs, materializeBlock, removeBlockPackage };
@@ -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-GgG-7amw.js");
7
- const require_retired_settings_keys = require("../../retired-settings-keys-Dr_4lfKu.js");
6
+ const require_dist = require("../../dist-BKhlcYz6.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
  {
@@ -8740,7 +8860,17 @@ var DeviceTombstoneStore = class {
8740
8860
  this.now = opts.now ?? (() => Date.now());
8741
8861
  this.retentionMs = opts.retentionMs ?? 7776e6;
8742
8862
  }
8743
- /** Lazy, idempotent declaration; a rejection is not latched. */
8863
+ /**
8864
+ * Declare the collection now. Idempotent; a rejection is not latched.
8865
+ *
8866
+ * Called at device-manager init (D871) so the table exists on a hub that has
8867
+ * not deleted a device yet. It used to be declared only by the first
8868
+ * `record` / `listSince`, i.e. by the first delete or by an implementer's
8869
+ * reconcile — so a hub whose implementers had not been redeployed, and where
8870
+ * nothing was deleted, never had the table, and the census read that as
8871
+ * "D865 is not running". A DECLARATION only: nothing is read, written or
8872
+ * purged here.
8873
+ */
8744
8874
  declare() {
8745
8875
  if (this.declared !== null) return this.declared;
8746
8876
  const run = this.backend.declareCollection({
@@ -10738,6 +10868,11 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
10738
10868
  backend: deviceRowBackendOf(settingsStoreApi),
10739
10869
  logger: this.ctx.logger.child("tombstones")
10740
10870
  });
10871
+ try {
10872
+ await tombstones.declare();
10873
+ } catch (err) {
10874
+ this.ctx.logger.warn("device tombstones collection not declared at init — retried on first use", { meta: { error: err instanceof Error ? err.message : String(err) } });
10875
+ }
10741
10876
  this.staleWrapperActivations.checkOnce(() => readBindingsStore(this.bindingsDeps));
10742
10877
  try {
10743
10878
  await require_retired_settings_keys.purgeRetiredSettingsRows(deviceRows.retiredRowStore(), this.ctx.logger.child("RetiredRows"));
@@ -11074,5 +11209,7 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
11074
11209
  //#endregion
11075
11210
  exports.DeviceManagerAddon = DeviceManagerAddon;
11076
11211
  exports.default = DeviceManagerAddon;
11212
+ exports.createDeviceSettingsStore = createDeviceSettingsStore;
11077
11213
  exports.mergeAggregates = mergeAggregates;
11214
+ exports.readDeviceOrThrow = readDeviceOrThrow;
11078
11215
  exports.setWrapperActive = setWrapperActive;