@camstack/system 1.2.143 → 1.2.145

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 (62) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  14. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  15. package/dist/builtins/device-manager/device-manager.addon.js +157 -35
  16. package/dist/builtins/device-manager/device-manager.addon.mjs +157 -35
  17. package/dist/builtins/device-manager/device-queries.d.ts +22 -0
  18. package/dist/builtins/device-manager/device-row-store.d.ts +18 -0
  19. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  20. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  21. package/dist/builtins/hub-forwarder/index.js +1 -1
  22. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  23. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  24. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  25. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  26. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  27. package/dist/builtins/local-network/local-network.addon.js +1 -1
  28. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  29. package/dist/builtins/loki-logging/index.js +1 -1
  30. package/dist/builtins/loki-logging/index.mjs +1 -1
  31. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  32. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  33. package/dist/builtins/platform-probe/index.js +1 -1
  34. package/dist/builtins/platform-probe/index.mjs +1 -1
  35. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  36. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  37. package/dist/builtins/snapshot/index.js +1 -1
  38. package/dist/builtins/snapshot/index.mjs +1 -1
  39. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  40. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  41. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +14 -1
  42. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  43. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  44. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +2 -1
  45. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +2 -1
  46. package/dist/builtins/system-config/system-config.addon.js +1 -1
  47. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  48. package/dist/builtins/winston-logging/index.js +1 -1
  49. package/dist/builtins/winston-logging/index.mjs +1 -1
  50. package/dist/{dist-CQs1b7uh.js → dist-BVU5JADq.js} +488 -29
  51. package/dist/{dist-CVuVKA-O.mjs → dist-CG7kwIZp.mjs} +488 -29
  52. package/dist/index.js +28 -9
  53. package/dist/index.mjs +28 -9
  54. package/dist/kernel/index.d.ts +1 -1
  55. package/dist/kernel/moleculer/children-batch-loader.d.ts +46 -0
  56. package/dist/kernel/moleculer/core-cap-service.d.ts +17 -2
  57. package/dist/kernel/moleculer/process-service.d.ts +15 -6
  58. package/dist/{manifest-python-deps-BuhBXCg0.mjs → manifest-python-deps-BRyNCnnG.mjs} +191 -1
  59. package/dist/{manifest-python-deps-B_mCU6gz.js → manifest-python-deps-GjlyPjm0.js} +191 -1
  60. package/dist/{retired-settings-keys-DnMYSIEr.mjs → retired-settings-keys-Dm1z4mO4.mjs} +1 -1
  61. package/dist/{retired-settings-keys-Dvei9nhG.js → retired-settings-keys-_PLI9w0k.js} +1 -1
  62. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
+ require("./dist-BVU5JADq.js");
2
3
  let node_crypto = require("node:crypto");
3
4
  node_crypto = require_chunk.__toESM(node_crypto);
4
5
  let _camstack_types_node = require("@camstack/types/node");
@@ -1653,6 +1654,182 @@ async function readModifyWrite(input) {
1653
1654
  return true;
1654
1655
  }
1655
1656
  //#endregion
1657
+ //#region src/kernel/moleculer/children-batch-loader.ts
1658
+ /**
1659
+ * "Who are my children?" asked by a thousand devices, answered in a handful of
1660
+ * calls.
1661
+ *
1662
+ * ## The number
1663
+ *
1664
+ * Every `BaseDevice` inherits a `getAccessoryChildren()` that returns `[]`, so
1665
+ * the accessory reconcile in `device-cap-proxy.ts` runs its orphan-prune step
1666
+ * for EVERY registered device — including the leaf accessories that can never
1667
+ * own one. Each pass asked `device-manager.getChildren` for its own id.
1668
+ * Measured on the live hub 2026-08-27, 120-second boot window, 1 017 devices:
1669
+ * **1 024 calls returning 919 rows in total**. A thousand round-trips, and a
1670
+ * thousand indexed scans behind them, to move less than one row each.
1671
+ *
1672
+ * ## Why a loader and not a caller that loops
1673
+ *
1674
+ * There is no loop to unroll. Reconcile is per-device by construction: it runs
1675
+ * inside `register()`, and again whenever a late feature-probe writes the
1676
+ * slice. The repetition is a THOUSAND INDEPENDENT CALLERS, not one caller
1677
+ * asking a thousand times — so the collapse has to happen between them.
1678
+ *
1679
+ * ## Why the batch is self-clocking, with no window to tune
1680
+ *
1681
+ * At most ONE batch call is in flight. Anything that arrives while it is in
1682
+ * flight lands in the next batch, dispatched the moment the current one
1683
+ * settles. That makes the batch size adapt to the arrival rate on its own:
1684
+ *
1685
+ * - Idle: the first caller flushes on the next macrotask. No added wait, and
1686
+ * no timer to pick a value for and be wrong about.
1687
+ * - Boot: `BaseDeviceProvider.onRestoreDevices` issues every top-level
1688
+ * `create()` at once and `runBounded(specs, 12)` spawns accessory children
1689
+ * twelve wide, so reconciles arrive in bursts — measured as 2 372 device
1690
+ * log lines in one 5-second bucket at boot+16s, against 62 at boot+142s.
1691
+ * Every arrival inside one round-trip collapses into the next call.
1692
+ * - Worst case: arrivals slower than a round-trip give batches of one, which
1693
+ * is exactly today's cost. The loader can never be worse than what it
1694
+ * replaces.
1695
+ *
1696
+ * A fixed debounce window would have to be guessed against that distribution,
1697
+ * and would tax the idle case to pay for the busy one.
1698
+ *
1699
+ * ## Degrading, rather than breaking, against an older hub
1700
+ *
1701
+ * `getChildrenBatch` is a NEW method: a hub that predates it rejects the call
1702
+ * (it does not silently strip the input and answer a different question, which
1703
+ * is what adding a `parentDeviceIds` FIELD to `getChildren` would have done).
1704
+ * Every waiter in a failed batch is then answered by the per-parent
1705
+ * `getChildren` it would have used anyway, so the reconcile is never denied
1706
+ * its answer. A rejection that names the method as unknown latches the
1707
+ * fallback permanently — re-probing a method the hub will never have, once per
1708
+ * batch for a whole boot, is the storm this exists to remove.
1709
+ */
1710
+ /**
1711
+ * Narrow an untyped cap answer to the fields the reconcile reads.
1712
+ *
1713
+ * `callDeviceManager` is an untyped boundary, and the answer may come from a
1714
+ * hub of a different vintage. A row that does not carry an id, a stableId and
1715
+ * a name is not a child this code can prune ON, so it is dropped rather than
1716
+ * cast into shape — the prune step DELETES, and a half-parsed row is the input
1717
+ * that would make it delete the wrong thing.
1718
+ */
1719
+ function parseChildRows(value) {
1720
+ if (!Array.isArray(value)) return null;
1721
+ const rows = [];
1722
+ for (const entry of value) {
1723
+ if (typeof entry !== "object" || entry === null) return null;
1724
+ const id = Reflect.get(entry, "id");
1725
+ const stableId = Reflect.get(entry, "stableId");
1726
+ const name = Reflect.get(entry, "name");
1727
+ const linkDeviceId = Reflect.get(entry, "linkDeviceId");
1728
+ if (typeof id !== "number" || typeof stableId !== "string" || typeof name !== "string") return null;
1729
+ rows.push({
1730
+ id,
1731
+ stableId,
1732
+ name,
1733
+ ...typeof linkDeviceId === "number" || linkDeviceId === null ? { linkDeviceId } : {}
1734
+ });
1735
+ }
1736
+ return rows;
1737
+ }
1738
+ /**
1739
+ * Narrow a `getChildrenBatch` answer: a record keyed by parent id as a string.
1740
+ *
1741
+ * An EMPTY record is a valid answer — "none of these parents has children" —
1742
+ * and so is what a hub that answers the call but knows none of the parents
1743
+ * would send. Both mean "nothing to prune", which is the safe direction. Only
1744
+ * a shape that is not a record at all is `null`, and the loader treats that as
1745
+ * a failed batch and re-asks per parent.
1746
+ */
1747
+ function parseChildrenByParent(value) {
1748
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
1749
+ const out = /* @__PURE__ */ new Map();
1750
+ for (const [key, entry] of Object.entries(value)) {
1751
+ const parentDeviceId = Number(key);
1752
+ if (!Number.isInteger(parentDeviceId)) return null;
1753
+ const rows = parseChildRows(entry);
1754
+ if (rows === null) return null;
1755
+ out.set(parentDeviceId, rows);
1756
+ }
1757
+ return out;
1758
+ }
1759
+ /**
1760
+ * True when an error says the hub has no such procedure — the version-skew
1761
+ * case, and the only one worth latching on. Anything else (a timeout, a busy
1762
+ * broker) is transient and must not disable batching for the process lifetime.
1763
+ */
1764
+ function looksLikeUnknownMethod(err) {
1765
+ const lowered = (err instanceof Error ? err.message : String(err)).toLowerCase();
1766
+ return lowered.includes("not_found") || lowered.includes("no procedure") || lowered.includes("unknown method") || lowered.includes("no such method") || lowered.includes("not available");
1767
+ }
1768
+ function createChildrenBatchLoader(opts) {
1769
+ const maxBatch = Math.max(1, opts.maxBatch ?? 256);
1770
+ let pending = [];
1771
+ let flushScheduled = false;
1772
+ let flushing = false;
1773
+ let batchUnsupported = false;
1774
+ const answerOneByOne = async (waiters) => {
1775
+ await Promise.all(waiters.map(async (waiter) => {
1776
+ try {
1777
+ waiter.resolve(await opts.fetchOne(waiter.parentDeviceId));
1778
+ } catch (err) {
1779
+ waiter.reject(err);
1780
+ }
1781
+ }));
1782
+ };
1783
+ const runBatch = async (waiters) => {
1784
+ const parentDeviceIds = [...new Set(waiters.map((w) => w.parentDeviceId))];
1785
+ try {
1786
+ const byParent = await opts.fetchBatch(parentDeviceIds);
1787
+ for (const waiter of waiters) waiter.resolve(byParent.get(waiter.parentDeviceId) ?? []);
1788
+ } catch (err) {
1789
+ const unknown = looksLikeUnknownMethod(err);
1790
+ if (unknown) batchUnsupported = true;
1791
+ opts.logger.warn(unknown ? "device-manager.getChildrenBatch is not available on this hub — falling back to per-parent getChildren for the process lifetime" : "device-manager.getChildrenBatch failed — answering this batch per parent", { meta: {
1792
+ parents: parentDeviceIds.length,
1793
+ error: err instanceof Error ? err.message : String(err)
1794
+ } });
1795
+ await answerOneByOne(waiters);
1796
+ }
1797
+ };
1798
+ const flush = async () => {
1799
+ flushing = true;
1800
+ try {
1801
+ while (pending.length > 0) {
1802
+ const batch = pending.slice(0, maxBatch);
1803
+ pending = pending.slice(maxBatch);
1804
+ if (batchUnsupported) await answerOneByOne(batch);
1805
+ else await runBatch(batch);
1806
+ }
1807
+ } finally {
1808
+ flushing = false;
1809
+ }
1810
+ };
1811
+ const scheduleFlush = () => {
1812
+ if (flushScheduled || flushing) return;
1813
+ flushScheduled = true;
1814
+ setTimeout(() => {
1815
+ flushScheduled = false;
1816
+ flush().catch((err) => {
1817
+ opts.logger.warn("children batch flush threw", { meta: { error: err instanceof Error ? err.message : String(err) } });
1818
+ });
1819
+ }, 0);
1820
+ };
1821
+ return { load(parentDeviceId) {
1822
+ return new Promise((resolve, reject) => {
1823
+ pending.push({
1824
+ parentDeviceId,
1825
+ resolve,
1826
+ reject
1827
+ });
1828
+ scheduleFlush();
1829
+ });
1830
+ } };
1831
+ }
1832
+ //#endregion
1656
1833
  //#region src/kernel/moleculer/device-cap-proxy.ts
1657
1834
  /**
1658
1835
  * Max number of accessory children spawned concurrently during a single
@@ -1943,6 +2120,19 @@ function createBrokerDeviceManagerApi(opts) {
1943
2120
  api
1944
2121
  });
1945
2122
  let selfApi;
2123
+ const childrenLoader = createChildrenBatchLoader({
2124
+ logger: opts.logger,
2125
+ fetchBatch: async (parentDeviceIds) => {
2126
+ const parsed = parseChildrenByParent(await callDeviceManager(api, "getChildrenBatch", { parentDeviceIds }));
2127
+ if (parsed === null) throw new Error("getChildrenBatch returned a shape this kernel cannot read");
2128
+ return parsed;
2129
+ },
2130
+ fetchOne: async (parentDeviceId) => {
2131
+ const parsed = parseChildRows(await callDeviceManager(api, "getChildren", { parentDeviceId }));
2132
+ if (parsed === null) throw new Error("getChildren returned a shape this kernel cannot read");
2133
+ return parsed;
2134
+ }
2135
+ });
1946
2136
  const deviceRebuildFactories = /* @__PURE__ */ new Map();
1947
2137
  const buildContext = (stableId, id, parentDeviceId = null, initialRuntimeState = {}, persistedConfig = {}, deviceMeta = null) => {
1948
2138
  let runtimeStateRef = null;
@@ -2360,7 +2550,7 @@ function createBrokerDeviceManagerApi(opts) {
2360
2550
  return;
2361
2551
  }
2362
2552
  try {
2363
- const currentChildren = await callDeviceManager(api, "getChildren", { parentDeviceId: device.id });
2553
+ const currentChildren = await childrenLoader.load(device.id);
2364
2554
  for (const child of currentChildren) {
2365
2555
  if (child.linkDeviceId !== device.id) continue;
2366
2556
  if (expected.has(child.stableId)) continue;
@@ -1,4 +1,4 @@
1
- import { It as asJsonObject } from "./dist-CVuVKA-O.mjs";
1
+ import { It as asJsonObject } from "./dist-CG7kwIZp.mjs";
2
2
  //#region src/builtins/sqlite-storage/retired-settings-keys.ts
3
3
  /**
4
4
  * Is THIS node the one whose settings store is the cluster's authority?
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-CQs1b7uh.js");
1
+ const require_dist = require("./dist-BVU5JADq.js");
2
2
  //#region src/builtins/sqlite-storage/retired-settings-keys.ts
3
3
  /**
4
4
  * Is THIS node the one whose settings store is the cluster's authority?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.143",
3
+ "version": "1.2.145",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",