@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
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-Cek0wNdY.js");
3
- const require_dist = require("./dist-BOWdLjoK.js");
4
- const require_device_ca = require("./device-ca-DHG9OW2m.js");
3
+ const require_dist = require("./dist-BiTCcaf-.js");
4
+ const require_device_ca = require("./device-ca-196DAEZT.js");
5
5
  const require_builtins_alerts_alerts_addon = require("./builtins/alerts/alerts.addon.js");
6
6
  require("./builtins/alerts/index.js");
7
7
  const require_builtins_calendars_calendars_addon = require("./builtins/calendars/calendars.addon.js");
@@ -10,8 +10,11 @@ const require_builtins_composer_composer_addon = require("./builtins/composer/co
10
10
  require("./builtins/composer/index.js");
11
11
  const require_formatter = require("./formatter-DqAKDlvN.js");
12
12
  const require_builtins_console_logging_index = require("./builtins/console-logging/index.js");
13
+ const require_prefix_range = require("./prefix-range-D_KueXbQ.js");
14
+ const require_prefix_walk = require("./prefix-walk-Dv6MLZlM.js");
13
15
  const require_builtins_core_blocks_core_blocks_addon = require("./builtins/core-blocks/core-blocks.addon.js");
14
16
  require("./builtins/core-blocks/index.js");
17
+ const require_retired_settings_keys = require("./retired-settings-keys-D_VwZiDt.js");
15
18
  const require_builtins_device_manager_device_manager_addon = require("./builtins/device-manager/device-manager.addon.js");
16
19
  require("./builtins/device-manager/index.js");
17
20
  const require_builtins_hub_forwarder_index = require("./builtins/hub-forwarder/index.js");
@@ -22,21 +25,21 @@ require("./builtins/local-auth/index.js");
22
25
  const require_builtins_loki_logging_index = require("./builtins/loki-logging/index.js");
23
26
  const require_builtins_native_metrics_native_metrics_addon = require("./builtins/native-metrics/native-metrics.addon.js");
24
27
  const require_builtins_sqlite_storage_filesystem_storage_addon = require("./builtins/sqlite-storage/filesystem-storage.addon.js");
25
- const require_wal_maintenance = require("./wal-maintenance-DEL_jeG0.js");
28
+ const require_wal_maintenance = require("./wal-maintenance-CgZTNWAz.js");
26
29
  const require_builtins_sqlite_storage_sqlite_settings_addon = require("./builtins/sqlite-storage/sqlite-settings.addon.js");
27
30
  const require_builtins_sqlite_storage_index = require("./builtins/sqlite-storage/index.js");
28
31
  const require_builtins_storage_orchestrator_storage_orchestrator_addon = require("./builtins/storage-orchestrator/storage-orchestrator.addon.js");
29
32
  require("./builtins/storage-orchestrator/index.js");
30
33
  const require_agent_https = require("./agent-https-DlpTMDqA.js");
31
- const require_constants = require("./constants-BRsjQwi-.js");
34
+ const require_constants = require("./constants-UojhAJcm.js");
32
35
  const require_builtins_system_config_system_config_addon = require("./builtins/system-config/system-config.addon.js");
33
36
  require("./builtins/system-config/index.js");
34
37
  const require_builtins_winston_logging_index = require("./builtins/winston-logging/index.js");
35
- const require_custom_action_registry = require("./custom-action-registry-Bf_YnagS.js");
36
- const require_hub_mdns_ciao = require("./hub-mdns-ciao-D7S5AJRU.js");
38
+ const require_custom_action_registry = require("./custom-action-registry-BKmoaqG0.js");
39
+ const require_hub_mdns_ciao = require("./hub-mdns-ciao-DOBTq8qr.js");
37
40
  const require_platform_key_source = require("./platform-key-source-Di7HrlGR.js");
38
41
  const require_resource_monitor = require("./resource-monitor-mio9dSCT.js");
39
- const require_restore_archive = require("./restore-archive-DiGiI6Jp.js");
42
+ const require_restore_archive = require("./restore-archive-CfSSjKnA.js");
40
43
  let zod = require("zod");
41
44
  let node_crypto = require("node:crypto");
42
45
  node_crypto = require_chunk.__toESM(node_crypto);
@@ -48,13 +51,13 @@ node_path = require_chunk.__toESM(node_path);
48
51
  let node_os = require("node:os");
49
52
  node_os = require_chunk.__toESM(node_os);
50
53
  let node_url = require("node:url");
54
+ let _camstack_types_addon = require("@camstack/types/addon");
51
55
  let node_fs = require("node:fs");
52
56
  node_fs = require_chunk.__toESM(node_fs);
53
57
  let node_child_process = require("node:child_process");
54
58
  let node_util = require("node:util");
55
59
  node_util = require_chunk.__toESM(node_util);
56
60
  let node_events = require("node:events");
57
- let _camstack_types_addon = require("@camstack/types/addon");
58
61
  let node_http = require("node:http");
59
62
  let node_v8 = require("node:v8");
60
63
  node_v8 = require_chunk.__toESM(node_v8);
@@ -1174,23 +1177,23 @@ function formatSocketPlane(window) {
1174
1177
  }
1175
1178
  //#endregion
1176
1179
  //#region src/kernel/moleculer/mesh-queue-report.ts
1177
- function isRecord$6(value) {
1180
+ function isRecord$7(value) {
1178
1181
  return typeof value === "object" && value !== null;
1179
1182
  }
1180
1183
  /** The shape `TcpWriter` keeps its sockets in. Anything else is not a mesh. */
1181
1184
  function writerSocketsOf(broker) {
1182
- if (!isRecord$6(broker)) return void 0;
1185
+ if (!isRecord$7(broker)) return void 0;
1183
1186
  const transit = broker["transit"];
1184
- if (!isRecord$6(transit)) return void 0;
1187
+ if (!isRecord$7(transit)) return void 0;
1185
1188
  const transporter = transit["tx"];
1186
- if (!isRecord$6(transporter)) return void 0;
1189
+ if (!isRecord$7(transporter)) return void 0;
1187
1190
  const writer = transporter["writer"];
1188
- if (!isRecord$6(writer)) return void 0;
1191
+ if (!isRecord$7(writer)) return void 0;
1189
1192
  const sockets = writer["sockets"];
1190
1193
  return sockets instanceof Map ? sockets : void 0;
1191
1194
  }
1192
1195
  function readSocket(nodeId, socket) {
1193
- if (typeof nodeId !== "string" || !isRecord$6(socket)) return void 0;
1196
+ if (typeof nodeId !== "string" || !isRecord$7(socket)) return void 0;
1194
1197
  const queuedBytes = socket["writableLength"];
1195
1198
  const bytesWritten = socket["bytesWritten"];
1196
1199
  if (typeof queuedBytes !== "number" || typeof bytesWritten !== "number") return void 0;
@@ -6310,7 +6313,7 @@ async function resolveUncached(options) {
6310
6313
  }
6311
6314
  //#endregion
6312
6315
  //#region src/kernel/addon-class-resolver.ts
6313
- function isRecord$5(value) {
6316
+ function isRecord$6(value) {
6314
6317
  return typeof value === "object" && value !== null && !Array.isArray(value);
6315
6318
  }
6316
6319
  /**
@@ -6334,14 +6337,14 @@ function isRecord$5(value) {
6334
6337
  function isAddonConstructor(fn) {
6335
6338
  if (typeof fn !== "function") return false;
6336
6339
  const proto = fn.prototype;
6337
- if (!isRecord$5(proto)) return false;
6340
+ if (!isRecord$6(proto)) return false;
6338
6341
  return typeof proto["initialize"] === "function";
6339
6342
  }
6340
6343
  function resolveAddonClass(mod) {
6341
6344
  let candidate = mod["default"];
6342
- if (isRecord$5(candidate) && "default" in candidate) candidate = candidate["default"];
6345
+ if (isRecord$6(candidate) && "default" in candidate) candidate = candidate["default"];
6343
6346
  if (isAddonConstructor(candidate)) return candidate;
6344
- if (isRecord$5(candidate)) {
6347
+ if (isRecord$6(candidate)) {
6345
6348
  const fn = Object.values(candidate).find(isAddonConstructor);
6346
6349
  if (fn) return fn;
6347
6350
  }
@@ -6350,7 +6353,7 @@ function resolveAddonClass(mod) {
6350
6353
  }
6351
6354
  //#endregion
6352
6355
  //#region src/kernel/addon-loader.ts
6353
- function isRecord$4(value) {
6356
+ function isRecord$5(value) {
6354
6357
  return typeof value === "object" && value !== null && !Array.isArray(value);
6355
6358
  }
6356
6359
  /**
@@ -6448,9 +6451,9 @@ async function importAddonModuleFresh(entryPath) {
6448
6451
  * AddonPackageManifest + AddonDeclaration.
6449
6452
  */
6450
6453
  function toAddonPackageManifest(value) {
6451
- if (!isRecord$4(value)) return void 0;
6454
+ if (!isRecord$5(value)) return void 0;
6452
6455
  if (!Array.isArray(value.addons)) return void 0;
6453
- if (!value.addons.every((a) => isRecord$4(a) && typeof a.id === "string")) return void 0;
6456
+ if (!value.addons.every((a) => isRecord$5(a) && typeof a.id === "string")) return void 0;
6454
6457
  return value;
6455
6458
  }
6456
6459
  /** Minimal no-op logger for default parameter */
@@ -6600,7 +6603,7 @@ var AddonLoader = class {
6600
6603
  }
6601
6604
  if (!node_fs.existsSync(entryPath)) throw new Error(`Entry not found: ${entryPath}`);
6602
6605
  const modUnknown = await importAddonModuleFresh(entryPath);
6603
- const mod = isRecord$4(modUnknown) ? modUnknown : {};
6606
+ const mod = isRecord$5(modUnknown) ? modUnknown : {};
6604
6607
  const AddonClass = resolveAddonClass(mod);
6605
6608
  if (!AddonClass) throw new Error(`No addon class in ${entryPath}`);
6606
6609
  this.addons.set(declaration.id, {
@@ -6616,7 +6619,7 @@ var AddonLoader = class {
6616
6619
  /** Load addon from a direct path (for development/testing) */
6617
6620
  async loadFromPath(addonId, modulePath, packageName, declaration, packageVersion = "0.0.0") {
6618
6621
  const modUnknown = await importAddonModuleFresh(modulePath);
6619
- const mod = isRecord$4(modUnknown) ? modUnknown : {};
6622
+ const mod = isRecord$5(modUnknown) ? modUnknown : {};
6620
6623
  const AddonClass = resolveAddonClass(mod);
6621
6624
  if (!AddonClass) throw new Error(`Module ${modulePath} has no default export`);
6622
6625
  this.addons.set(addonId, {
@@ -11536,92 +11539,11 @@ async function runHubAddonBoot(steps) {
11536
11539
  }
11537
11540
  //#endregion
11538
11541
  //#region src/kernel/settings-door-view.ts
11539
- function isRecord$3(value) {
11542
+ function isRecord$4(value) {
11540
11543
  return typeof value === "object" && value !== null && !Array.isArray(value);
11541
11544
  }
11542
11545
  function asBlob(value) {
11543
- return isRecord$3(value) ? value : {};
11544
- }
11545
- function unwrapValue(parsed) {
11546
- if (!isRecord$3(parsed) || !("value" in parsed)) return parsed;
11547
- return parsed.value;
11548
- }
11549
- /**
11550
- * Rows one page of a prefix read asks for.
11551
- *
11552
- * Below `SETTINGS_QUERY_DEFAULT_ROW_CAP` (2 000, `query-bounds.ts`) on purpose:
11553
- * this read now names its own bound, so the engine's defensive cap is never the
11554
- * thing that decides how much of the answer the caller gets.
11555
- */
11556
- var PREFIX_PAGE_ROWS = 1e3;
11557
- /**
11558
- * Pages one prefix read may take before it refuses.
11559
- *
11560
- * 512 pages is 512 000 rows — three orders of magnitude past the largest live
11561
- * scope (`addon-device-settings` for one addon over a ~1 019-device fleet) and
11562
- * still a bound, because a walk with no bound is the defect this file was
11563
- * fixing, not one to reintroduce.
11564
- */
11565
- var PREFIX_PAGE_BUDGET = 512;
11566
- /**
11567
- * Every row under `prefix`, or a throw — never the first 2 000 of them.
11568
- *
11569
- * **The defect.** This issued ONE `query` with no `limit`, and
11570
- * `queryDeclared` resolves an absent limit to `SETTINGS_QUERY_DEFAULT_ROW_CAP`
11571
- * and cuts the result there. On the live hub that fired 138 times in two hours
11572
- * (`settings-store query truncated by a defensive row cap`, collection
11573
- * `addon-device-settings`, `rowCap: 2000`, `requestedLimit: null`): a
11574
- * ~1 019-device fleet with more than two stored keys per device puts one
11575
- * addon's `<addonId>:` range past the cap. The engine warned; the door did not
11576
- * notice, and handed its caller a short map that is **shape-identical to a
11577
- * complete one**. Every device whose rows sort past the cap reads as "has no
11578
- * stored settings", which is exactly what a device with none looks like.
11579
- *
11580
- * **The fix is a keyset walk, not a bigger limit.** The cursor is the last id
11581
- * of the previous page, INCLUSIVE — so the page boundary re-reads exactly one
11582
- * row (ids are unique, so a full page always advances) and no row can fall
11583
- * between two pages the way an `OFFSET` walk drops rows under a concurrent
11584
- * write (D295 rule 1). Re-reading is free here: the accumulator is keyed by
11585
- * suffix, so the duplicate overwrites itself with its own value.
11586
- *
11587
- * `ORDER BY id` is not decoration — the cursor is only meaningful if the
11588
- * engine returns the range in the order the cursor advances through. It is the
11589
- * primary key, so the ordering is the range scan's own.
11590
- *
11591
- * @throws when the prefix cannot be expressed as a range and the collection is
11592
- * larger than one page, or when the walk exceeds {@link PREFIX_PAGE_BUDGET}.
11593
- * A throw is the point: a caller that cannot be answered completely must not
11594
- * be handed something that looks like a complete answer.
11595
- */
11596
- async function loadPrefixed(door, collection, prefix, decode = unwrapValue) {
11597
- const range = require_wal_maintenance.prefixRange(prefix);
11598
- const result = {};
11599
- let cursor = range?.lo ?? null;
11600
- for (let page = 0;; page++) {
11601
- if (page >= PREFIX_PAGE_BUDGET) throw new Error(`settings door: reading "${prefix}" from "${collection}" exceeded ${PREFIX_PAGE_BUDGET} pages of ${PREFIX_PAGE_ROWS} rows. Refusing to return the part that was read — a partial scope read is indistinguishable from an empty one.`);
11602
- const rows = await door.query({
11603
- collection,
11604
- filter: {
11605
- ...range !== null && cursor !== null ? { whereBetween: { id: [cursor, range.hi] } } : {},
11606
- orderBy: {
11607
- field: "id",
11608
- direction: "asc"
11609
- },
11610
- limit: PREFIX_PAGE_ROWS
11611
- }
11612
- });
11613
- for (const row of rows) {
11614
- if (range !== null && (row.id < range.lo || row.id >= range.hi)) continue;
11615
- if (!row.id.startsWith(prefix)) continue;
11616
- result[row.id.slice(prefix.length)] = decode(row.data);
11617
- }
11618
- if (rows.length < PREFIX_PAGE_ROWS) return result;
11619
- const last = rows[rows.length - 1];
11620
- if (last === void 0) return result;
11621
- if (range === null) throw new Error(`settings door: prefix "${prefix}" on "${collection}" cannot be expressed as an id range, and the unfiltered read filled a page — the answer would be partial. Use an ASCII, non-empty prefix.`);
11622
- if (last.id === cursor) throw new Error(`settings door: reading "${prefix}" from "${collection}" made no progress at id "${last.id}" — ids under a prefix must be unique.`);
11623
- cursor = last.id;
11624
- }
11546
+ return isRecord$4(value) ? value : {};
11625
11547
  }
11626
11548
  /**
11627
11549
  * Every `system-settings` row under `prefix`, keyed by the suffix — or a throw.
@@ -11638,7 +11560,7 @@ async function loadPrefixed(door, collection, prefix, decode = unwrapValue) {
11638
11560
  * form opens, and the capability preferences once at boot.
11639
11561
  */
11640
11562
  async function loadSystemSettingsPrefix(door, prefix, mayRead = () => true) {
11641
- const listed = await loadPrefixed(door, "system-settings", prefix, () => null);
11563
+ const listed = await require_prefix_walk.loadPrefixed(door, "system-settings", prefix, () => null);
11642
11564
  const result = {};
11643
11565
  for (const suffix of Object.keys(listed).filter(mayRead)) {
11644
11566
  const value = await door.get({
@@ -11649,106 +11571,34 @@ async function loadSystemSettingsPrefix(door, prefix, mayRead = () => true) {
11649
11571
  }
11650
11572
  return result;
11651
11573
  }
11652
- async function replacePrefixed(door, collection, prefix, values, wrap) {
11653
- const range = require_wal_maintenance.prefixRange(prefix);
11654
- if (range !== null) await door.deleteWhere({
11655
- collection,
11656
- filter: { whereBetween: { id: [range.lo, range.hi] } }
11657
- });
11658
- for (const [key, value] of Object.entries(values)) {
11659
- if (value === void 0) continue;
11660
- await door.set({
11661
- collection,
11662
- key: `${prefix}${key}`,
11663
- value: wrap(key, value)
11664
- });
11665
- }
11666
- }
11667
11574
  function createDoorSettingsView(addonId, door, sections) {
11668
- const addonPrefix = `${addonId}.`;
11575
+ const addonStore = require_builtins_core_blocks_core_blocks_addon.createAddonSettingsStore(door, addonId);
11576
+ const deviceStore = require_builtins_device_manager_device_manager_addon.createDeviceSettingsStore(door, addonId);
11669
11577
  return {
11670
11578
  async readAddonStore() {
11671
- return loadPrefixed(door, "addon-settings", addonPrefix);
11672
- },
11673
- async readAddonStoreKeyResult(key) {
11674
- try {
11675
- const row = await door.get({
11676
- collection: "addon-settings",
11677
- key: `${addonPrefix}${key}`
11678
- });
11679
- return row === void 0 ? { kind: "absent" } : {
11680
- kind: "present",
11681
- value: unwrapValue(row)
11682
- };
11683
- } catch (err) {
11684
- return {
11685
- kind: "unreadable",
11686
- reason: "store-read-failed",
11687
- error: err instanceof Error ? err.message : String(err)
11688
- };
11579
+ const read = await addonStore.read();
11580
+ switch (read.kind) {
11581
+ case "present": return { ...read.value };
11582
+ case "absent": return {};
11583
+ case "unreadable": throw new Error(`settings of "${addonId}" could not be read: ${read.error}`);
11689
11584
  }
11690
11585
  },
11691
- async writeAddonStore(patch) {
11692
- await replacePrefixed(door, "addon-settings", addonPrefix, {
11693
- ...await loadPrefixed(door, "addon-settings", addonPrefix),
11694
- ...patch
11695
- }, (key, value) => ({
11696
- addonId,
11697
- key,
11698
- value
11699
- }));
11586
+ async readAddonStoreKeyResult(key) {
11587
+ const read = await addonStore.readKey(key);
11588
+ return read.kind === "unreadable" ? {
11589
+ kind: "unreadable",
11590
+ reason: "store-read-failed",
11591
+ error: read.error
11592
+ } : read;
11700
11593
  },
11594
+ writeAddonStore: (patch) => addonStore.merge(patch),
11701
11595
  async readDeviceStore(deviceId) {
11702
- return loadPrefixed(door, "addon-device-settings", `${addonId}:${String(deviceId)}.`);
11703
- },
11704
- async readDeviceStoreBatch(deviceIds) {
11705
- const out = /* @__PURE__ */ new Map();
11706
- if (deviceIds.length === 0) return out;
11707
- for (const id of deviceIds) out.set(id, {});
11708
- const wanted = new Set(deviceIds);
11709
- const all = await loadPrefixed(door, "addon-device-settings", `${addonId}:`);
11710
- for (const [suffix, value] of Object.entries(all)) {
11711
- const dot = suffix.indexOf(".");
11712
- if (dot <= 0) continue;
11713
- const deviceId = Number(suffix.slice(0, dot));
11714
- if (!Number.isInteger(deviceId) || !wanted.has(deviceId)) continue;
11715
- const blob = out.get(deviceId);
11716
- if (blob === void 0) continue;
11717
- blob[suffix.slice(dot + 1)] = value;
11718
- }
11719
- return out;
11720
- },
11721
- async listDeviceStoreIds() {
11722
- const all = await loadPrefixed(door, "addon-device-settings", `${addonId}:`, () => null);
11723
- const ids = /* @__PURE__ */ new Set();
11724
- for (const suffix of Object.keys(all)) {
11725
- const dot = suffix.indexOf(".");
11726
- if (dot <= 0) continue;
11727
- const deviceId = Number(suffix.slice(0, dot));
11728
- if (Number.isInteger(deviceId) && deviceId > 0) ids.add(deviceId);
11729
- }
11730
- return [...ids].toSorted((a, b) => a - b);
11731
- },
11732
- async writeDeviceStore(deviceId, patch) {
11733
- const scope = `${addonId}:${String(deviceId)}.`;
11734
- await replacePrefixed(door, "addon-device-settings", scope, {
11735
- ...await loadPrefixed(door, "addon-device-settings", scope),
11736
- ...patch
11737
- }, (key, value) => ({
11738
- addonId,
11739
- deviceId: String(deviceId),
11740
- key,
11741
- value
11742
- }));
11743
- },
11744
- async clearDeviceStore(deviceId) {
11745
- const range = require_wal_maintenance.prefixRange(`${addonId}:${String(deviceId)}.`);
11746
- if (range === null) return;
11747
- await door.deleteWhere({
11748
- collection: "addon-device-settings",
11749
- filter: { whereBetween: { id: [range.lo, range.hi] } }
11750
- });
11596
+ return require_builtins_device_manager_device_manager_addon.readDeviceOrThrow(deviceStore, deviceId);
11751
11597
  },
11598
+ readDeviceStoreBatch: (deviceIds) => deviceStore.readBatch(deviceIds),
11599
+ listDeviceStoreIds: () => deviceStore.listIds(),
11600
+ writeDeviceStore: (deviceId, patch) => deviceStore.merge(deviceId, patch),
11601
+ clearDeviceStore: (deviceId) => deviceStore.clear(deviceId),
11752
11602
  async readDeviceRuntimeState(deviceId) {
11753
11603
  return asBlob(await door.get({
11754
11604
  collection: "device-runtime-state",
@@ -14262,7 +14112,7 @@ import_js_yaml.default;
14262
14112
  * so that file stays one class. The `console.*` calls are the documented
14263
14113
  * residuals described on `ConfigManager` (no logger exists yet at boot).
14264
14114
  */
14265
- function isRecord$2(value) {
14115
+ function isRecord$3(value) {
14266
14116
  return typeof value === "object" && value !== null && !Array.isArray(value);
14267
14117
  }
14268
14118
  /** Sections that live in config.yaml. Everything else goes to the settings-store. */
@@ -14353,7 +14203,7 @@ function setNested(obj, configPath, value) {
14353
14203
  [head]: value
14354
14204
  };
14355
14205
  const existing = obj[head];
14356
- const child = isRecord$2(existing) ? existing : {};
14206
+ const child = isRecord$3(existing) ? existing : {};
14357
14207
  return {
14358
14208
  ...obj,
14359
14209
  [head]: setNested(child, rest.join("."), value)
@@ -14386,8 +14236,10 @@ function loadYaml(configPath) {
14386
14236
  //#endregion
14387
14237
  //#region src/kernel/store-settings-view.ts
14388
14238
  function createStoreSettingsView(addonId, cm) {
14239
+ const refuseDeviceSettings = async () => {
14240
+ throw new Error(`device settings of addon "${addonId}" are served by the settings-store door only`);
14241
+ };
14389
14242
  const viewAddonConfig = () => !cm.hasSyncStore() ? (0, _camstack_types_addon.asJsonObject)(cm.getBootstrap(`addons.${addonId}`)) ?? {} : cm.getAddonConfig(addonId);
14390
- const viewAddonDevice = (deviceId) => !cm.hasSyncStore() ? {} : cm.getAddonDevice(addonId, deviceId);
14391
14243
  return {
14392
14244
  async readAddonStore() {
14393
14245
  return viewAddonConfig();
@@ -14419,29 +14271,11 @@ function createStoreSettingsView(addonId, cm) {
14419
14271
  ...patch
14420
14272
  });
14421
14273
  },
14422
- async readDeviceStore(deviceId) {
14423
- return viewAddonDevice(String(deviceId));
14424
- },
14425
- async readDeviceStoreBatch(deviceIds) {
14426
- const out = /* @__PURE__ */ new Map();
14427
- for (const deviceId of deviceIds) try {
14428
- out.set(deviceId, viewAddonDevice(String(deviceId)));
14429
- } catch {
14430
- out.set(deviceId, {});
14431
- }
14432
- return out;
14433
- },
14434
- async writeDeviceStore(deviceId, patch) {
14435
- const key = String(deviceId);
14436
- const existing = viewAddonDevice(key);
14437
- cm.setAddonDevice(addonId, key, {
14438
- ...existing,
14439
- ...patch
14440
- });
14441
- },
14442
- async clearDeviceStore(deviceId) {
14443
- cm.clearAddonDevice(addonId, String(deviceId));
14444
- },
14274
+ readDeviceStore: refuseDeviceSettings,
14275
+ readDeviceStoreBatch: refuseDeviceSettings,
14276
+ listDeviceStoreIds: refuseDeviceSettings,
14277
+ writeDeviceStore: refuseDeviceSettings,
14278
+ clearDeviceStore: refuseDeviceSettings,
14445
14279
  async readDeviceRuntimeState(deviceId) {
14446
14280
  return cm.getDeviceRuntimeState(String(deviceId));
14447
14281
  },
@@ -14626,7 +14460,7 @@ var SystemSettingsMirror = class {
14626
14460
  };
14627
14461
  //#endregion
14628
14462
  //#region src/kernel/config-manager.ts
14629
- function isRecord$1(value) {
14463
+ function isRecord$2(value) {
14630
14464
  return typeof value === "object" && value !== null && !Array.isArray(value);
14631
14465
  }
14632
14466
  function errorText(err) {
@@ -14815,7 +14649,7 @@ var ConfigManager = class {
14815
14649
  async readAddonDeviceStrict(addonId, deviceId) {
14816
14650
  const door = this.settingsDoor;
14817
14651
  switch (this.backendMode()) {
14818
- case "store": return this.getAddonDevice(addonId, String(deviceId));
14652
+ case "store": break;
14819
14653
  case "door":
14820
14654
  if (door === null) break;
14821
14655
  try {
@@ -14833,34 +14667,6 @@ var ConfigManager = class {
14833
14667
  this.settingsStore.setAllAddon(addonId, config);
14834
14668
  }
14835
14669
  /**
14836
- * Read per-device overrides for a specific addon. Returns a flat record
14837
- * of field → value. Missing keys fall back to the addon-global value via
14838
- * `getAddonConfig(addonId)` — that composition is the resolver service's
14839
- * responsibility, not ConfigManager's.
14840
- * SYNC store only — THROWS without one (D675); async: {@link readAddonDeviceStrict}.
14841
- */
14842
- getAddonDevice(addonId, deviceId) {
14843
- if (this.settingsStore === null) throw this.refuseSyncRead("getAddonDevice", `addon "${addonId}" device "${deviceId}"`);
14844
- return this.settingsStore.getAddonDevice(addonId, deviceId);
14845
- }
14846
- /**
14847
- * Write (bulk-replace) per-device overrides for a specific addon.
14848
- * Throws if the settings store is not yet wired.
14849
- */
14850
- setAddonDevice(addonId, deviceId, values) {
14851
- if (this.settingsStore === null) throw new Error("[ConfigManager] SettingsStore not initialized -- call setSettingsStore() first");
14852
- this.settingsStore.setAddonDevice(addonId, deviceId, values);
14853
- }
14854
- /**
14855
- * Clear all per-device overrides for a specific addon/device pair.
14856
- * Resolution falls back to the addon-global value after this call.
14857
- * No-op if the settings store is not yet wired.
14858
- */
14859
- clearAddonDevice(addonId, deviceId) {
14860
- if (this.settingsStore === null) return;
14861
- this.settingsStore.clearAddonDevice(addonId, deviceId);
14862
- }
14863
- /**
14864
14670
  * The device's persisted runtime-state blob. THROWS before the store is
14865
14671
  * wired: a store not asked is not an empty row (D677, D675).
14866
14672
  */
@@ -14937,11 +14743,7 @@ var ConfigManager = class {
14937
14743
  writeAddonStore: (patch) => active().writeAddonStore(patch),
14938
14744
  readDeviceStore: (deviceId) => active().readDeviceStore(deviceId),
14939
14745
  readDeviceStoreBatch: (deviceIds) => active().readDeviceStoreBatch(deviceIds),
14940
- listDeviceStoreIds: async () => {
14941
- const list = active().listDeviceStoreIds;
14942
- if (list === void 0) throw new Error("listDeviceStoreIds is not available on this settings view");
14943
- return list.call(active());
14944
- },
14746
+ listDeviceStoreIds: () => active().listDeviceStoreIds(),
14945
14747
  writeDeviceStore: (deviceId, patch) => active().writeDeviceStore(deviceId, patch),
14946
14748
  clearDeviceStore: (deviceId) => active().clearDeviceStore(deviceId),
14947
14749
  getSection: (section) => active().getSection(section),
@@ -15017,7 +14819,7 @@ var ConfigManager = class {
15017
14819
  const keys = configPath.split(".");
15018
14820
  let current = this.bootstrapConfig;
15019
14821
  for (const key of keys) {
15020
- if (!isRecord$1(current)) return void 0;
14822
+ if (!isRecord$2(current)) return void 0;
15021
14823
  current = current[key];
15022
14824
  }
15023
14825
  return current;
@@ -15868,27 +15670,6 @@ function asName(value) {
15868
15670
  return typeof value === "string" && value.length > 0 ? value : null;
15869
15671
  }
15870
15672
  //#endregion
15871
- //#region src/kernel/moleculer/settings-read-guard.ts
15872
- /**
15873
- * Merge `patch` over the current value and write it — unless the read failed,
15874
- * in which case write NOTHING.
15875
- *
15876
- * Returns whether the write was attempted, so a caller can surface the refusal.
15877
- */
15878
- async function readModifyWrite(input) {
15879
- const current = await input.read();
15880
- if (current.kind === "failed") {
15881
- input.onRefused(current.error);
15882
- return false;
15883
- }
15884
- const base = current.kind === "value" ? current.value : {};
15885
- await input.write({
15886
- ...base,
15887
- ...input.patch
15888
- });
15889
- return true;
15890
- }
15891
- //#endregion
15892
15673
  //#region src/kernel/moleculer/children-batch-loader.ts
15893
15674
  /**
15894
15675
  * Narrow an untyped cap answer to the fields the reconcile reads.
@@ -16023,7 +15804,7 @@ var RuntimeStateLoadError = class extends Error {
16023
15804
  this.name = "RuntimeStateLoadError";
16024
15805
  }
16025
15806
  };
16026
- function isRecord(value) {
15807
+ function isRecord$1(value) {
16027
15808
  return typeof value === "object" && value !== null && !Array.isArray(value);
16028
15809
  }
16029
15810
  function errorMessage(err) {
@@ -16054,7 +15835,7 @@ async function loadRuntimeStateOrRefuse(load, logger, target) {
16054
15835
  } catch (err) {
16055
15836
  return refuse(errorMessage(err), err);
16056
15837
  }
16057
- if (!isRecord(answer)) return refuse(`answered ${answer === null ? "null" : typeof answer}`);
15838
+ if (!isRecord$1(answer)) return refuse(`answered ${answer === null ? "null" : typeof answer}`);
16058
15839
  return answer;
16059
15840
  }
16060
15841
  //#endregion
@@ -23145,6 +22926,261 @@ function createRemoteProviderProxy(broker, serviceName, capName, nodeId, methods
23145
22926
  return dispatcher;
23146
22927
  }
23147
22928
  //#endregion
22929
+ //#region src/kernel/sections-store.ts
22930
+ function createSectionsStore(door) {
22931
+ return {
22932
+ read: (section) => loadSystemSettingsPrefix(door, `${section}.`, (key) => storeMayAnswer(`${section}.${key}`)),
22933
+ write: async (section, patch) => {
22934
+ for (const [key, value] of Object.entries(patch)) await door.set({
22935
+ collection: "system-settings",
22936
+ key: `${section}.${key}`,
22937
+ value
22938
+ });
22939
+ }
22940
+ };
22941
+ }
22942
+ //#endregion
22943
+ //#region src/kernel/moleculer/runner-addon-settings.ts
22944
+ /** @param client the hub's `settingsStore` client; `undefined` makes every call THROW. */
22945
+ function createRunnerAddonSettings(client, addonId, before = async () => {}) {
22946
+ if (client === void 0) {
22947
+ const refuse = async () => {
22948
+ throw new Error(`settings of "${addonId}": settings store not wired`);
22949
+ };
22950
+ return {
22951
+ readAddonStore: refuse,
22952
+ readAddonStoreResult: async () => ({
22953
+ kind: "failed",
22954
+ error: "settings store not wired"
22955
+ }),
22956
+ readAddonStoreKeyResult: async () => ({
22957
+ kind: "unreadable",
22958
+ reason: "store-read-failed",
22959
+ error: "settings store not wired"
22960
+ }),
22961
+ writeAddonStore: refuse,
22962
+ getSection: refuse,
22963
+ setSection: refuse
22964
+ };
22965
+ }
22966
+ const door = {
22967
+ query: async (input) => {
22968
+ await before();
22969
+ return client.query.query(input);
22970
+ },
22971
+ get: async (input) => {
22972
+ await before();
22973
+ return client.get.query(input);
22974
+ },
22975
+ set: async (input) => {
22976
+ await before();
22977
+ return client.set.mutate(input);
22978
+ },
22979
+ transact: async (input) => {
22980
+ await before();
22981
+ return client.transact.mutate(input);
22982
+ }
22983
+ };
22984
+ const store = require_builtins_core_blocks_core_blocks_addon.createAddonSettingsStore(door, addonId);
22985
+ const sections = createSectionsStore(door);
22986
+ return {
22987
+ readAddonStore: async () => {
22988
+ const read = await store.read();
22989
+ switch (read.kind) {
22990
+ case "present": return { ...read.value };
22991
+ case "absent": return {};
22992
+ case "unreadable": throw new Error(`settings of "${addonId}" could not be read: ${read.error}`);
22993
+ }
22994
+ },
22995
+ readAddonStoreResult: async () => {
22996
+ const read = await store.read();
22997
+ switch (read.kind) {
22998
+ case "present": return {
22999
+ kind: "value",
23000
+ value: { ...read.value }
23001
+ };
23002
+ case "absent": return { kind: "missing" };
23003
+ case "unreadable": return {
23004
+ kind: "failed",
23005
+ error: read.error
23006
+ };
23007
+ }
23008
+ },
23009
+ readAddonStoreKeyResult: async (key) => {
23010
+ const read = await store.readKey(key);
23011
+ return read.kind === "unreadable" ? {
23012
+ kind: "unreadable",
23013
+ reason: "store-read-failed",
23014
+ error: read.error
23015
+ } : read;
23016
+ },
23017
+ writeAddonStore: (patch) => store.merge(patch),
23018
+ getSection: (section) => sections.read(section),
23019
+ setSection: (section, patch) => sections.write(section, patch)
23020
+ };
23021
+ }
23022
+ //#endregion
23023
+ //#region src/kernel/legacy-blob-guard.ts
23024
+ /** Root keys retired long before D870: they never count as "settings still to migrate". */
23025
+ var OWN_RETIRED_ROOT_KEYS = new Set([
23026
+ "recordingConfigs",
23027
+ "recordingExports",
23028
+ "recordingOpsLog",
23029
+ "probeSnapshots",
23030
+ "rtspTokens",
23031
+ "profileMap"
23032
+ ]);
23033
+ /** Is `key` of `addonId` a root key that was retired (and must not be migrated)? */
23034
+ function isRetiredRootKey(addonId, key) {
23035
+ if (OWN_RETIRED_ROOT_KEYS.has(key)) return true;
23036
+ return require_retired_settings_keys.RETIRED_SETTINGS_KEYS.some((spec) => spec.namespace === addonId && (spec.keys.includes(key) || spec.perNodeKeys.some((k) => key === k || key.startsWith(`${k}@`))));
23037
+ }
23038
+ var UnmigratedSettingsError = class extends Error {
23039
+ constructor(addonId, what) {
23040
+ super(`addon "${addonId}" refuses to start: ${what} - the settings it had are still in the per-addon tables and the standard store holds none. Run scripts/hub/migrate-device-store.ts --copy --apply (D870) before starting it.`);
23041
+ this.name = "UnmigratedSettingsError";
23042
+ }
23043
+ };
23044
+ async function countOf(client, input) {
23045
+ const n = await client.count.query(input);
23046
+ if (typeof n !== "number") throw new Error("settings store answered a non-number to a count");
23047
+ return n;
23048
+ }
23049
+ var LEGACY_PAGE = 500;
23050
+ var LEGACY_PAGE_BUDGET = 200;
23051
+ var ID_TAIL = "";
23052
+ function isRecord(value) {
23053
+ return typeof value === "object" && value !== null && !Array.isArray(value);
23054
+ }
23055
+ /**
23056
+ * Does the addon's per-device blob table hold a row the copy would actually
23057
+ * migrate - a positive numeric id with a non-empty record? An addon whose rows
23058
+ * the copy skips anyway must not be refused for them.
23059
+ */
23060
+ async function hasMigratableDeviceRow(client, addonId) {
23061
+ let cursor = "";
23062
+ for (let page = 0; page < LEGACY_PAGE_BUDGET; page++) {
23063
+ const rows = await client.query.query({
23064
+ namespace: addonId,
23065
+ collection: "addon-devices",
23066
+ filter: {
23067
+ whereBetween: { id: [cursor, ID_TAIL] },
23068
+ orderBy: {
23069
+ field: "id",
23070
+ direction: "asc"
23071
+ },
23072
+ limit: LEGACY_PAGE
23073
+ }
23074
+ });
23075
+ if (!Array.isArray(rows)) throw new Error("settings store answered a non-list to a query");
23076
+ let lastId = cursor;
23077
+ for (const row of rows) {
23078
+ if (!isRecord(row) || typeof row["id"] !== "string") continue;
23079
+ lastId = row["id"];
23080
+ const id = Number(row["id"]);
23081
+ const data = row["data"];
23082
+ if (Number.isInteger(id) && id > 0 && isRecord(data) && Object.keys(data).length > 0) return true;
23083
+ }
23084
+ if (rows.length < LEGACY_PAGE || lastId === cursor) return false;
23085
+ cursor = lastId;
23086
+ }
23087
+ throw new Error(`addon "${addonId}": more legacy blob pages than the guard will read`);
23088
+ }
23089
+ /**
23090
+ * THROWS when the addon has blob settings and the standard store has none of
23091
+ * its own. A store that cannot be asked also throws: it is not "no blobs".
23092
+ */
23093
+ async function assertLegacyBlobsMigrated(client, addonId, logger) {
23094
+ const legacyDevices = await hasMigratableDeviceRow(client, addonId);
23095
+ const root = await client.get.query({
23096
+ namespace: addonId,
23097
+ collection: "addon-settings",
23098
+ key: "root"
23099
+ });
23100
+ const legacySettings = isRecord(root) && Object.keys(root).some((key) => !isRetiredRootKey(addonId, key));
23101
+ if (!legacyDevices && !legacySettings) return;
23102
+ const rangeOf = (prefix) => {
23103
+ const range = require_prefix_range.prefixRange(prefix);
23104
+ if (range === null) throw new Error(`no id range for addon "${addonId}"`);
23105
+ return { whereBetween: { id: [range.lo, range.hi] } };
23106
+ };
23107
+ const newDevices = await countOf(client, {
23108
+ collection: "addon-device-settings",
23109
+ filter: rangeOf(`${addonId}:`)
23110
+ });
23111
+ const newSettings = await countOf(client, {
23112
+ collection: "addon-settings",
23113
+ filter: rangeOf(`${addonId}.`)
23114
+ });
23115
+ const problems = [];
23116
+ if (legacyDevices && newDevices === 0) problems.push("per-device blobs");
23117
+ if (legacySettings && newSettings === 0) problems.push("an addon settings blob");
23118
+ if (problems.length === 0) return;
23119
+ const error = new UnmigratedSettingsError(addonId, problems.join(" and "));
23120
+ logger.error(error.message, { meta: {
23121
+ addonId,
23122
+ newDevices,
23123
+ newSettings
23124
+ } });
23125
+ throw error;
23126
+ }
23127
+ //#endregion
23128
+ //#region src/kernel/moleculer/runner-device-settings.ts
23129
+ /**
23130
+ * The forked runner's `ctx.settings` device methods (D870).
23131
+ *
23132
+ * A runner does not hold the settings door; it holds a tRPC client of the hub's
23133
+ * `settings-store` cap. This file adapts that client to the door slice the
23134
+ * per-device store needs and hands back the five view methods — the SAME module
23135
+ * hub-main's view uses, so a device's settings are one set of rows whichever
23136
+ * process asks. Before D870 the runner kept its own `<addonId>:addon-devices`
23137
+ * blob per device; a hub-side write to the rows was invisible to it.
23138
+ */
23139
+ /** The door slice the per-device store needs, over a tRPC client. */
23140
+ function doorOverClient(client, before = async () => {}) {
23141
+ return {
23142
+ query: async (input) => {
23143
+ await before();
23144
+ return client.query.query(input);
23145
+ },
23146
+ transact: async (input) => {
23147
+ await before();
23148
+ return client.transact.mutate(input);
23149
+ },
23150
+ deleteWhere: async (input) => {
23151
+ await before();
23152
+ return client.deleteWhere.mutate(input);
23153
+ }
23154
+ };
23155
+ }
23156
+ /**
23157
+ * @param client the hub's `settingsStore` client, or `undefined` when the
23158
+ * runner has none — every call then THROWS: a store that could not be asked
23159
+ * is not an empty one.
23160
+ */
23161
+ function createRunnerDeviceSettings(client, addonId, before = async () => {}) {
23162
+ if (client === void 0) {
23163
+ const refuse = () => {
23164
+ throw new Error(`device settings of "${addonId}": settings store not wired`);
23165
+ };
23166
+ return {
23167
+ readDeviceStore: async () => refuse(),
23168
+ readDeviceStoreBatch: async () => refuse(),
23169
+ listDeviceStoreIds: async () => refuse(),
23170
+ writeDeviceStore: async () => refuse(),
23171
+ clearDeviceStore: async () => refuse()
23172
+ };
23173
+ }
23174
+ const store = require_builtins_device_manager_device_manager_addon.createDeviceSettingsStore(doorOverClient(client, before), addonId);
23175
+ return {
23176
+ readDeviceStore: (deviceId) => require_builtins_device_manager_device_manager_addon.readDeviceOrThrow(store, deviceId),
23177
+ readDeviceStoreBatch: (ids) => store.readBatch(ids),
23178
+ listDeviceStoreIds: () => store.listIds(),
23179
+ writeDeviceStore: (deviceId, patch) => store.merge(deviceId, patch),
23180
+ clearDeviceStore: (deviceId) => store.clear(deviceId)
23181
+ };
23182
+ }
23183
+ //#endregion
23148
23184
  //#region src/kernel/moleculer/addon-context-factory.ts
23149
23185
  /** This runtime's node id, regardless of mode (broker.nodeID or the UDS child nodeId). */
23150
23186
  function runtimeNodeId(runtime) {
@@ -23411,20 +23447,6 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
23411
23447
  }
23412
23448
  return r.kind === "value" ? r.value : {};
23413
23449
  };
23414
- /** Read-modify-write that REFUSES to write when the read failed. */
23415
- const mergeBlob = async (coll, key, patch, namespace) => {
23416
- await readModifyWrite({
23417
- read: () => readBlobResult(coll, key, namespace),
23418
- write: (next) => writeBlob(coll, key, next, namespace),
23419
- patch,
23420
- onRefused: (error) => {
23421
- scopedLogger.error(`settings merge REFUSED (${coll}/${key}) — read failed, refusing to overwrite with a partial blob`, { meta: {
23422
- error,
23423
- patchKeys: Object.keys(patch)
23424
- } });
23425
- }
23426
- });
23427
- };
23428
23450
  const writeBlob = async (coll, key, value, namespace) => {
23429
23451
  if (!settingsApi) return;
23430
23452
  try {
@@ -23447,38 +23469,24 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
23447
23469
  } });
23448
23470
  }
23449
23471
  };
23450
- const rmwChains = /* @__PURE__ */ new Map();
23451
- const serializeRmw = (coll, key, op) => {
23452
- const chainKey = `${coll}/${key}`;
23453
- const next = (rmwChains.get(chainKey) ?? Promise.resolve()).catch(() => void 0).then(op);
23454
- rmwChains.set(chainKey, next);
23455
- next.finally(() => {
23456
- if (rmwChains.get(chainKey) === next) rmwChains.delete(chainKey);
23457
- }).catch(() => void 0);
23458
- return next;
23472
+ const deviceSettingsClient = Reflect.get(api, "settingsStore");
23473
+ let blobsChecked = null;
23474
+ const bootGuard = () => {
23475
+ const client = deviceSettingsClient;
23476
+ if (client === void 0) return Promise.resolve();
23477
+ blobsChecked ??= assertLegacyBlobsMigrated({
23478
+ get: { query: (input) => client.get.query(input) },
23479
+ query: { query: (input) => client.query.query(input) },
23480
+ count: { query: (input) => client.count.query(input) }
23481
+ }, addonId, scopedLogger).catch((err) => {
23482
+ blobsChecked = null;
23483
+ throw err;
23484
+ });
23485
+ return blobsChecked;
23459
23486
  };
23460
23487
  const settingsView = {
23461
- async readAddonStore() {
23462
- return readBlob("addon-settings", "root", addonId);
23463
- },
23464
- readAddonStoreResult: () => readBlobResult("addon-settings", "root", addonId),
23465
- async writeAddonStore(patch) {
23466
- return serializeRmw("addon-settings", "root", async () => {
23467
- await mergeBlob("addon-settings", "root", patch, addonId);
23468
- });
23469
- },
23470
- async readDeviceStore(deviceId) {
23471
- return readBlob("addon-devices", String(deviceId), addonId);
23472
- },
23473
- async writeDeviceStore(deviceId, patch) {
23474
- const key = String(deviceId);
23475
- return serializeRmw("addon-devices", key, async () => {
23476
- await mergeBlob("addon-devices", key, patch, addonId);
23477
- });
23478
- },
23479
- async clearDeviceStore(deviceId) {
23480
- await writeBlob("addon-devices", String(deviceId), {}, addonId);
23481
- },
23488
+ ...createRunnerAddonSettings(deviceSettingsClient, addonId, bootGuard),
23489
+ ...createRunnerDeviceSettings(deviceSettingsClient, addonId, bootGuard),
23482
23490
  async readDeviceRuntimeState(deviceId) {
23483
23491
  return readBlob("device-runtime-state", String(deviceId));
23484
23492
  },
@@ -23487,14 +23495,6 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
23487
23495
  },
23488
23496
  async clearDeviceRuntimeState(deviceId) {
23489
23497
  await writeBlob("device-runtime-state", String(deviceId), {});
23490
- },
23491
- async getSection(section) {
23492
- return readBlob("sections", section);
23493
- },
23494
- async setSection(section, patch) {
23495
- return serializeRmw("sections", section, async () => {
23496
- await mergeBlob("sections", section, patch);
23497
- });
23498
23498
  }
23499
23499
  };
23500
23500
  const readinessRegistry = () => runtime.mode === "broker" ? getOrInitReadinessRegistry(runtime.broker, scopedEventBus, scopedLogger) : getOrInitReadinessRegistryForClient(runtime.client, scopedEventBus, scopedLogger, nodeId);