@camstack/system 1.2.144 → 1.2.146
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.
- package/dist/addon-runner.js +2 -1
- package/dist/addon-runner.mjs +2 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +157 -35
- package/dist/builtins/device-manager/device-manager.addon.mjs +157 -35
- package/dist/builtins/device-manager/device-queries.d.ts +22 -0
- package/dist/builtins/device-manager/device-row-store.d.ts +18 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +14 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +2 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +2 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-D3lqzV40.js → dist-BVU5JADq.js} +401 -24
- package/dist/{dist-6rM7chzb.mjs → dist-CG7kwIZp.mjs} +401 -24
- package/dist/index.d.ts +2 -2
- package/dist/index.js +60 -11
- package/dist/index.mjs +52 -12
- package/dist/kernel/heap-watch.d.ts +209 -1
- package/dist/kernel/moleculer/children-batch-loader.d.ts +46 -0
- package/dist/kernel/moleculer/process-service.d.ts +15 -6
- package/dist/{manifest-python-deps-BuhBXCg0.mjs → manifest-python-deps-CVeckyXx.mjs} +390 -8
- package/dist/{manifest-python-deps-B_mCU6gz.js → manifest-python-deps-CktMcXzS.js} +443 -7
- package/dist/{retired-settings-keys-BtvClSPu.mjs → retired-settings-keys-Dm1z4mO4.mjs} +1 -1
- package/dist/{retired-settings-keys-OtUQtEbq.js → retired-settings-keys-_PLI9w0k.js} +1 -1
- package/package.json +1 -1
package/dist/addon-runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
2
|
-
const require_manifest_python_deps = require("./manifest-python-deps-
|
|
2
|
+
const require_manifest_python_deps = require("./manifest-python-deps-CktMcXzS.js");
|
|
3
3
|
const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
|
|
4
4
|
let node_path = require("node:path");
|
|
5
5
|
node_path = require_chunk.__toESM(node_path);
|
|
@@ -692,6 +692,7 @@ async function main() {
|
|
|
692
692
|
const stopHeapWatch = require_manifest_python_deps.startRunnerHeapWatch({
|
|
693
693
|
heapProfile: process.env["CAMSTACK_RUNNER_HEAP_PROFILE"],
|
|
694
694
|
reclaimSwitch: process.env["CAMSTACK_RUNNER_HEAP_RECLAIM"],
|
|
695
|
+
rssBudgetMb: process.env[require_manifest_python_deps.RUNNER_RSS_BUDGET_ENV],
|
|
695
696
|
label: nodeId,
|
|
696
697
|
sink: {
|
|
697
698
|
info: (line) => {
|
package/dist/addon-runner.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as createUdsLoggerWithControl, I as LocalChildClient, Nt as
|
|
1
|
+
import { F as createUdsLoggerWithControl, Ht as startRunnerHeapWatch, I as LocalChildClient, Nt as RUNNER_RSS_BUDGET_ENV, bt as resolveAddonClass, i as createUdsAddonContext, it as setWorkerNativeCapsChangeListener, nt as getWorkerNativeCapSnapshot, ot as validateProviderRegistrations, t as installManifestPythonDeps, tt as getWorkerNativeCapProvider, yt as installManifestNativeDeps } from "./manifest-python-deps-CVeckyXx.mjs";
|
|
2
2
|
import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
|
|
3
3
|
import { register } from "node:module";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -688,6 +688,7 @@ async function main() {
|
|
|
688
688
|
const stopHeapWatch = startRunnerHeapWatch({
|
|
689
689
|
heapProfile: process.env["CAMSTACK_RUNNER_HEAP_PROFILE"],
|
|
690
690
|
reclaimSwitch: process.env["CAMSTACK_RUNNER_HEAP_RECLAIM"],
|
|
691
|
+
rssBudgetMb: process.env[RUNNER_RSS_BUDGET_ENV],
|
|
691
692
|
label: nodeId,
|
|
692
693
|
sink: {
|
|
693
694
|
info: (line) => {
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BVU5JADq.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Et as BaseAddon, Qt as EventCategory, S as addonPagesCapability, Tt as errMsg } from "../../dist-
|
|
1
|
+
import { Et as BaseAddon, Qt as EventCategory, S as addonPagesCapability, Tt as errMsg } from "../../dist-CG7kwIZp.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BVU5JADq.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Et as BaseAddon, Qt as EventCategory, Tt as errMsg, w as addonWidgetsCapability } from "../../dist-
|
|
1
|
+
import { Et as BaseAddon, Qt as EventCategory, Tt as errMsg, w as addonWidgetsCapability } from "../../dist-CG7kwIZp.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BVU5JADq.js");
|
|
7
7
|
//#region src/builtins/alerts/prune-policy.ts
|
|
8
8
|
/**
|
|
9
9
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Et as BaseAddon, Qt as EventCategory, T as alertsCapability, Tt as errMsg, n as AlertSchema, zt as createEvent } from "../../dist-
|
|
1
|
+
import { Et as BaseAddon, Qt as EventCategory, T as alertsCapability, Tt as errMsg, n as AlertSchema, zt as createEvent } from "../../dist-CG7kwIZp.mjs";
|
|
2
2
|
//#region src/builtins/alerts/prune-policy.ts
|
|
3
3
|
/**
|
|
4
4
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BVU5JADq.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
let buffer = require("buffer");
|
|
9
9
|
let node_fs_promises = require("node:fs/promises");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as backupCapability, Et as BaseAddon, Qt as EventCategory } from "../../dist-
|
|
1
|
+
import { D as backupCapability, Et as BaseAddon, Qt as EventCategory } from "../../dist-CG7kwIZp.mjs";
|
|
2
2
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
3
3
|
import { Buffer as Buffer$1 } from "buffer";
|
|
4
4
|
import * as fsp from "node:fs/promises";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BVU5JADq.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
//#region src/builtins/console-logging/console-destination.ts
|
|
9
9
|
var LEVEL_RANK = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Et as BaseAddon, nt as logDestinationCapability } from "../../dist-
|
|
1
|
+
import { Et as BaseAddon, nt as logDestinationCapability } from "../../dist-CG7kwIZp.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
//#region src/builtins/console-logging/console-destination.ts
|
|
4
4
|
var LEVEL_RANK = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-BVU5JADq.js");
|
|
4
4
|
let node_crypto = require("node:crypto");
|
|
5
5
|
let node_fs_promises = require("node:fs/promises");
|
|
6
6
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Et as BaseAddon, M as coreBlockIdFromAddonId, N as coreBlocksCapability, Tt as errMsg, c as CoreBlockSchema, j as coreBlockAddonId, o as CORE_BLOCKS_ADDON_ID, s as CORE_BLOCK_ADDON_PREFIX, u as DeclaredDevices } from "../../dist-
|
|
1
|
+
import { Et as BaseAddon, M as coreBlockIdFromAddonId, N as coreBlocksCapability, Tt as errMsg, c as CoreBlockSchema, j as coreBlockAddonId, o as CORE_BLOCKS_ADDON_ID, s as CORE_BLOCK_ADDON_PREFIX, u as DeclaredDevices } from "../../dist-CG7kwIZp.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
7
|
-
const require_retired_settings_keys = require("../../retired-settings-keys-
|
|
6
|
+
const require_dist = require("../../dist-BVU5JADq.js");
|
|
7
|
+
const require_retired_settings_keys = require("../../retired-settings-keys-_PLI9w0k.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let _camstack_types_node = require("@camstack/types/node");
|
|
10
10
|
//#region src/builtins/device-manager/adoption-job-engine.ts
|
|
@@ -1465,30 +1465,25 @@ async function getDevice(pctx, input) {
|
|
|
1465
1465
|
...sourceInfoGetDevice !== void 0 ? { sourceInfo: sourceInfoGetDevice } : {}
|
|
1466
1466
|
};
|
|
1467
1467
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
}
|
|
1468
|
+
/**
|
|
1469
|
+
* The children of ONE parent, projected from rows and live devices the caller
|
|
1470
|
+
* has ALREADY read.
|
|
1471
|
+
*
|
|
1472
|
+
* Extracted so `getChildren` and `getChildrenBatch` share one projection: the
|
|
1473
|
+
* batch differs only in HOW the rows were fetched (one `listByParentMany`
|
|
1474
|
+
* instead of N `listByParent`), never in what a parent's answer looks like.
|
|
1475
|
+
* Byte-for-byte the body `getChildren` had.
|
|
1476
|
+
*/
|
|
1477
|
+
async function projectChildren(pctx, parentDeviceId, ownerAddonId, childRows, liveChildren) {
|
|
1479
1478
|
const results = [];
|
|
1480
1479
|
const seen = /* @__PURE__ */ new Set();
|
|
1481
|
-
const childRows = await pctx.metaStore.rows.listByParent(parentDeviceId);
|
|
1482
1480
|
const rowById = /* @__PURE__ */ new Map();
|
|
1483
1481
|
for (const row of childRows) rowById.set(row.meta.id, row);
|
|
1484
|
-
|
|
1485
|
-
const
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
results.push(toDeviceInfo(ownerAddonId, device, row?.metadata ?? null, row?.meta ?? null));
|
|
1490
|
-
seen.add(key);
|
|
1491
|
-
}
|
|
1482
|
+
for (const device of liveChildren) {
|
|
1483
|
+
const key = String(device.id);
|
|
1484
|
+
const row = rowById.get(device.id);
|
|
1485
|
+
results.push(toDeviceInfo(ownerAddonId, device, row?.metadata ?? null, row?.meta ?? null));
|
|
1486
|
+
seen.add(key);
|
|
1492
1487
|
}
|
|
1493
1488
|
for (const row of childRows) {
|
|
1494
1489
|
const m = row.meta;
|
|
@@ -1524,6 +1519,86 @@ async function getChildren(pctx, input) {
|
|
|
1524
1519
|
}
|
|
1525
1520
|
return results;
|
|
1526
1521
|
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Owning addonId for a parent, or `null` when the fleet does not know it.
|
|
1524
|
+
*
|
|
1525
|
+
* Children are always registered under the same addonId as their parent, so
|
|
1526
|
+
* the persisted-child projection is scoped by it. The live registry answers
|
|
1527
|
+
* for free; only a parent it does not hold costs a row read.
|
|
1528
|
+
*/
|
|
1529
|
+
function liveOwnerOf(pctx, parentDeviceId) {
|
|
1530
|
+
if (!pctx.registry) return null;
|
|
1531
|
+
if (!pctx.registry.getById(parentDeviceId)) return null;
|
|
1532
|
+
return pctx.registry.getAddonId(parentDeviceId);
|
|
1533
|
+
}
|
|
1534
|
+
async function getChildren(pctx, input) {
|
|
1535
|
+
const { parentDeviceId } = input;
|
|
1536
|
+
let ownerAddonId = liveOwnerOf(pctx, parentDeviceId);
|
|
1537
|
+
if (!ownerAddonId) {
|
|
1538
|
+
const persisted = await pctx.metaStore.resolvePersistedById(parentDeviceId);
|
|
1539
|
+
if (!persisted) return [];
|
|
1540
|
+
ownerAddonId = persisted.addonId;
|
|
1541
|
+
}
|
|
1542
|
+
const childRows = await pctx.metaStore.rows.listByParent(parentDeviceId);
|
|
1543
|
+
const liveChildren = pctx.registry?.getChildren(parentDeviceId) ?? [];
|
|
1544
|
+
return [...await projectChildren(pctx, parentDeviceId, ownerAddonId, childRows, liveChildren)];
|
|
1545
|
+
}
|
|
1546
|
+
/**
|
|
1547
|
+
* `getChildren` for a set of parents — TWO store reads for the whole set, not
|
|
1548
|
+
* two per parent.
|
|
1549
|
+
*
|
|
1550
|
+
* The single-parent path costs one `resolvePersistedById` (a primary-key read)
|
|
1551
|
+
* plus one `listByParent` (an indexed scan) EACH. Asked once per device at
|
|
1552
|
+
* boot that was 1 024 of each, measured on the live hub 2026-08-27 — see the
|
|
1553
|
+
* cap method's note. Here the ownership lookup for every parent the live
|
|
1554
|
+
* registry does not hold is ONE `getMany`, and the children of every parent
|
|
1555
|
+
* are ONE `listByParentMany`.
|
|
1556
|
+
*
|
|
1557
|
+
* The live half is indexed once too: `registry.getChildren` is
|
|
1558
|
+
* `getAll().filter(…)`, so calling it per parent would trade an RPC N+1 for an
|
|
1559
|
+
* in-memory one — 256 full sweeps of the registry per batch. One pass over
|
|
1560
|
+
* `getAll()` buckets every live device by its parent instead.
|
|
1561
|
+
*
|
|
1562
|
+
* A parent with no children at all is ABSENT from the record; so is a parent
|
|
1563
|
+
* the fleet does not know. Same contract as `DeviceRowStore.getMany` — the
|
|
1564
|
+
* caller asked about it and gets no row, rather than an invented empty one it
|
|
1565
|
+
* cannot tell apart from a real childless parent.
|
|
1566
|
+
*/
|
|
1567
|
+
async function getChildrenBatch(pctx, input) {
|
|
1568
|
+
const parentIds = [...new Set(input.parentDeviceIds)];
|
|
1569
|
+
const out = {};
|
|
1570
|
+
if (parentIds.length === 0) return out;
|
|
1571
|
+
const owners = /* @__PURE__ */ new Map();
|
|
1572
|
+
const unownedIds = [];
|
|
1573
|
+
for (const parentId of parentIds) {
|
|
1574
|
+
const live = liveOwnerOf(pctx, parentId);
|
|
1575
|
+
if (live !== null) owners.set(parentId, live);
|
|
1576
|
+
else unownedIds.push(parentId);
|
|
1577
|
+
}
|
|
1578
|
+
if (unownedIds.length > 0) {
|
|
1579
|
+
const rows = await pctx.metaStore.rows.getMany(unownedIds);
|
|
1580
|
+
for (const [parentId, row] of rows) owners.set(parentId, row.meta.addonId);
|
|
1581
|
+
}
|
|
1582
|
+
const requested = new Set(parentIds);
|
|
1583
|
+
const rowsByParent = await pctx.metaStore.rows.listByParentMany([...owners.keys()]);
|
|
1584
|
+
const liveByParent = /* @__PURE__ */ new Map();
|
|
1585
|
+
for (const device of pctx.registry?.getAll() ?? []) {
|
|
1586
|
+
const parentId = device.parentDeviceId;
|
|
1587
|
+
if (parentId === null || parentId === void 0) continue;
|
|
1588
|
+
if (!requested.has(parentId)) continue;
|
|
1589
|
+
const bucket = liveByParent.get(parentId);
|
|
1590
|
+
if (bucket === void 0) liveByParent.set(parentId, [device]);
|
|
1591
|
+
else bucket.push(device);
|
|
1592
|
+
}
|
|
1593
|
+
for (const [parentId, ownerAddonId] of owners) {
|
|
1594
|
+
const childRows = rowsByParent.get(parentId) ?? [];
|
|
1595
|
+
const liveChildren = liveByParent.get(parentId) ?? [];
|
|
1596
|
+
if (childRows.length === 0 && liveChildren.length === 0) continue;
|
|
1597
|
+
const projected = await projectChildren(pctx, parentId, ownerAddonId, childRows, liveChildren);
|
|
1598
|
+
if (projected.length > 0) out[String(parentId)] = [...projected];
|
|
1599
|
+
}
|
|
1600
|
+
return out;
|
|
1601
|
+
}
|
|
1527
1602
|
async function getStreamSources(pctx, input) {
|
|
1528
1603
|
const { deviceId } = input;
|
|
1529
1604
|
if (pctx.registry) {
|
|
@@ -3395,27 +3470,33 @@ async function setName(pctx, input) {
|
|
|
3395
3470
|
});
|
|
3396
3471
|
}
|
|
3397
3472
|
/**
|
|
3398
|
-
* Every descendant of `rootId`, walked via `parentDeviceId` chains
|
|
3399
|
-
*
|
|
3400
|
-
*
|
|
3401
|
-
*
|
|
3402
|
-
*
|
|
3403
|
-
*
|
|
3404
|
-
*
|
|
3473
|
+
* Every descendant of `rootId`, walked via `parentDeviceId` chains.
|
|
3474
|
+
*
|
|
3475
|
+
* BFS one LEVEL per query, not one node per query: `listByParentMany` asks for
|
|
3476
|
+
* the whole frontier's children in a single indexed `whereIn`, so a tree costs
|
|
3477
|
+
* as many reads as it is DEEP, never as many as it has nodes. It used to be
|
|
3478
|
+
* `listByParent` per node — the same N+1 the boot census measured at 1 024
|
|
3479
|
+
* calls on `getChildren`.
|
|
3480
|
+
*
|
|
3481
|
+
* `visited` seeded with `rootId` guards a pathological parent-cycle in
|
|
3482
|
+
* persisted data from recursing forever; a row already seen is never re-queued
|
|
3483
|
+
* or re-emitted. Depth is unbounded on purpose — a nested container's own
|
|
3484
|
+
* children are still `rootId`'s descendants and must align too.
|
|
3405
3485
|
*/
|
|
3406
3486
|
async function collectDescendants(metaStore, rootId) {
|
|
3407
3487
|
const out = [];
|
|
3408
3488
|
const visited = new Set([rootId]);
|
|
3409
|
-
|
|
3410
|
-
while (
|
|
3411
|
-
const
|
|
3412
|
-
|
|
3413
|
-
for (const row of
|
|
3489
|
+
let frontier = [rootId];
|
|
3490
|
+
while (frontier.length > 0) {
|
|
3491
|
+
const byParent = await metaStore.rows.listByParentMany(frontier);
|
|
3492
|
+
const next = [];
|
|
3493
|
+
for (const parentId of frontier) for (const row of byParent.get(parentId) ?? []) {
|
|
3414
3494
|
if (visited.has(row.meta.id)) continue;
|
|
3415
3495
|
visited.add(row.meta.id);
|
|
3416
3496
|
out.push(row.meta);
|
|
3417
|
-
|
|
3497
|
+
next.push(row.meta.id);
|
|
3418
3498
|
}
|
|
3499
|
+
frontier = next;
|
|
3419
3500
|
}
|
|
3420
3501
|
return out;
|
|
3421
3502
|
}
|
|
@@ -4745,6 +4826,46 @@ var DeviceRowStore = class {
|
|
|
4745
4826
|
limit: DEVICE_ROWS_FLEET_LIMIT
|
|
4746
4827
|
});
|
|
4747
4828
|
}
|
|
4829
|
+
/**
|
|
4830
|
+
* Direct children of MANY devices, in ONE query.
|
|
4831
|
+
*
|
|
4832
|
+
* `listByParent` called once per parent is the same shape `get` was before
|
|
4833
|
+
* {@link getMany}: an indexed lookup this store makes cheap, repeated until
|
|
4834
|
+
* it isn't. Measured on the live hub 2026-08-27, 120-second boot window:
|
|
4835
|
+
* `DeviceRowStore.list < DeviceRowStore.listByParent < getChildren` at
|
|
4836
|
+
* **1 024 calls** — one per device — for 919 rows in total. The BFS in
|
|
4837
|
+
* `device-meta-actions.ts#collectDescendants` walked a container tree the
|
|
4838
|
+
* same way, one query per node.
|
|
4839
|
+
*
|
|
4840
|
+
* `whereIn` on the indexed `parentDeviceId` column collapses a level of that
|
|
4841
|
+
* walk into a single statement. Contract, verbatim from {@link getMany}: an
|
|
4842
|
+
* EMPTY `parentDeviceIds` reads nothing at all rather than degrading to
|
|
4843
|
+
* "everything", and a parent with no children — or one the fleet does not
|
|
4844
|
+
* know — is simply ABSENT from the map, never an invented empty row.
|
|
4845
|
+
*/
|
|
4846
|
+
async listByParentMany(parentDeviceIds) {
|
|
4847
|
+
const out = /* @__PURE__ */ new Map();
|
|
4848
|
+
const unique = [...new Set(parentDeviceIds)];
|
|
4849
|
+
if (unique.length === 0) return out;
|
|
4850
|
+
const rows = await this.list({
|
|
4851
|
+
whereIn: { parentDeviceId: unique },
|
|
4852
|
+
orderBy: {
|
|
4853
|
+
field: "deviceId",
|
|
4854
|
+
direction: "asc"
|
|
4855
|
+
},
|
|
4856
|
+
limit: DEVICE_ROWS_FLEET_LIMIT
|
|
4857
|
+
});
|
|
4858
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
4859
|
+
for (const row of rows) {
|
|
4860
|
+
const parent = row.meta.parentDeviceId;
|
|
4861
|
+
if (parent === null || parent === void 0) continue;
|
|
4862
|
+
const bucket = byParent.get(parent);
|
|
4863
|
+
if (bucket === void 0) byParent.set(parent, [row]);
|
|
4864
|
+
else bucket.push(row);
|
|
4865
|
+
}
|
|
4866
|
+
for (const [parent, bucket] of byParent) out.set(parent, bucket);
|
|
4867
|
+
return out;
|
|
4868
|
+
}
|
|
4748
4869
|
/** How many devices the fleet holds. Used to tell "empty store" from "gone device". */
|
|
4749
4870
|
async count() {
|
|
4750
4871
|
await this.declare();
|
|
@@ -5565,6 +5686,7 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
|
|
|
5565
5686
|
listAll: (input) => listAll(pctx, input),
|
|
5566
5687
|
getDevice: (input) => getDevice(pctx, input),
|
|
5567
5688
|
getChildren: (input) => getChildren(pctx, input),
|
|
5689
|
+
getChildrenBatch: (input) => getChildrenBatch(pctx, input),
|
|
5568
5690
|
getLinkedDevices: (input) => getLinkedDevices(pctx, input),
|
|
5569
5691
|
getLinkedDevicesBatch: (input) => getLinkedDevicesBatch(pctx, input),
|
|
5570
5692
|
getDeviceSettingsContribution: (input) => buildLinkedDevicesContribution(pctx, input.deviceId),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as buildStreamParamsConfigSchema, At as DeviceFeature, Et as BaseAddon, Ft as WELL_KNOWN_TAB_MAP, H as enumerateItemArrayFields, L as deviceManagerCapability, Mt as DeviceType, Qt as EventCategory, R as deviceStateCapability, Tt as errMsg, U as enumerateSchemaFields, Wt as isDeviceConfigCap, Zt as sleep, _ as STREAM_PROFILE_META, a as CAP_NAMES_WITH_STATUS, ct as parseStreamParamsFormPatch, d as DeviceStatusSchema, ft as runtimeStatePolicyFor, jt as DeviceRole, ot as normalizeUnit, t as ALL_CAPABILITY_DEFINITIONS, z as deviceStatusCapability } from "../../dist-
|
|
2
|
-
import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-
|
|
1
|
+
import { A as buildStreamParamsConfigSchema, At as DeviceFeature, Et as BaseAddon, Ft as WELL_KNOWN_TAB_MAP, H as enumerateItemArrayFields, L as deviceManagerCapability, Mt as DeviceType, Qt as EventCategory, R as deviceStateCapability, Tt as errMsg, U as enumerateSchemaFields, Wt as isDeviceConfigCap, Zt as sleep, _ as STREAM_PROFILE_META, a as CAP_NAMES_WITH_STATUS, ct as parseStreamParamsFormPatch, d as DeviceStatusSchema, ft as runtimeStatePolicyFor, jt as DeviceRole, ot as normalizeUnit, t as ALL_CAPABILITY_DEFINITIONS, z as deviceStatusCapability } from "../../dist-CG7kwIZp.mjs";
|
|
2
|
+
import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-Dm1z4mO4.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { canonicalDeviceFingerprint } from "@camstack/types/node";
|
|
5
5
|
//#region src/builtins/device-manager/adoption-job-engine.ts
|
|
@@ -1460,30 +1460,25 @@ async function getDevice(pctx, input) {
|
|
|
1460
1460
|
...sourceInfoGetDevice !== void 0 ? { sourceInfo: sourceInfoGetDevice } : {}
|
|
1461
1461
|
};
|
|
1462
1462
|
}
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
}
|
|
1463
|
+
/**
|
|
1464
|
+
* The children of ONE parent, projected from rows and live devices the caller
|
|
1465
|
+
* has ALREADY read.
|
|
1466
|
+
*
|
|
1467
|
+
* Extracted so `getChildren` and `getChildrenBatch` share one projection: the
|
|
1468
|
+
* batch differs only in HOW the rows were fetched (one `listByParentMany`
|
|
1469
|
+
* instead of N `listByParent`), never in what a parent's answer looks like.
|
|
1470
|
+
* Byte-for-byte the body `getChildren` had.
|
|
1471
|
+
*/
|
|
1472
|
+
async function projectChildren(pctx, parentDeviceId, ownerAddonId, childRows, liveChildren) {
|
|
1474
1473
|
const results = [];
|
|
1475
1474
|
const seen = /* @__PURE__ */ new Set();
|
|
1476
|
-
const childRows = await pctx.metaStore.rows.listByParent(parentDeviceId);
|
|
1477
1475
|
const rowById = /* @__PURE__ */ new Map();
|
|
1478
1476
|
for (const row of childRows) rowById.set(row.meta.id, row);
|
|
1479
|
-
|
|
1480
|
-
const
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
results.push(toDeviceInfo(ownerAddonId, device, row?.metadata ?? null, row?.meta ?? null));
|
|
1485
|
-
seen.add(key);
|
|
1486
|
-
}
|
|
1477
|
+
for (const device of liveChildren) {
|
|
1478
|
+
const key = String(device.id);
|
|
1479
|
+
const row = rowById.get(device.id);
|
|
1480
|
+
results.push(toDeviceInfo(ownerAddonId, device, row?.metadata ?? null, row?.meta ?? null));
|
|
1481
|
+
seen.add(key);
|
|
1487
1482
|
}
|
|
1488
1483
|
for (const row of childRows) {
|
|
1489
1484
|
const m = row.meta;
|
|
@@ -1519,6 +1514,86 @@ async function getChildren(pctx, input) {
|
|
|
1519
1514
|
}
|
|
1520
1515
|
return results;
|
|
1521
1516
|
}
|
|
1517
|
+
/**
|
|
1518
|
+
* Owning addonId for a parent, or `null` when the fleet does not know it.
|
|
1519
|
+
*
|
|
1520
|
+
* Children are always registered under the same addonId as their parent, so
|
|
1521
|
+
* the persisted-child projection is scoped by it. The live registry answers
|
|
1522
|
+
* for free; only a parent it does not hold costs a row read.
|
|
1523
|
+
*/
|
|
1524
|
+
function liveOwnerOf(pctx, parentDeviceId) {
|
|
1525
|
+
if (!pctx.registry) return null;
|
|
1526
|
+
if (!pctx.registry.getById(parentDeviceId)) return null;
|
|
1527
|
+
return pctx.registry.getAddonId(parentDeviceId);
|
|
1528
|
+
}
|
|
1529
|
+
async function getChildren(pctx, input) {
|
|
1530
|
+
const { parentDeviceId } = input;
|
|
1531
|
+
let ownerAddonId = liveOwnerOf(pctx, parentDeviceId);
|
|
1532
|
+
if (!ownerAddonId) {
|
|
1533
|
+
const persisted = await pctx.metaStore.resolvePersistedById(parentDeviceId);
|
|
1534
|
+
if (!persisted) return [];
|
|
1535
|
+
ownerAddonId = persisted.addonId;
|
|
1536
|
+
}
|
|
1537
|
+
const childRows = await pctx.metaStore.rows.listByParent(parentDeviceId);
|
|
1538
|
+
const liveChildren = pctx.registry?.getChildren(parentDeviceId) ?? [];
|
|
1539
|
+
return [...await projectChildren(pctx, parentDeviceId, ownerAddonId, childRows, liveChildren)];
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* `getChildren` for a set of parents — TWO store reads for the whole set, not
|
|
1543
|
+
* two per parent.
|
|
1544
|
+
*
|
|
1545
|
+
* The single-parent path costs one `resolvePersistedById` (a primary-key read)
|
|
1546
|
+
* plus one `listByParent` (an indexed scan) EACH. Asked once per device at
|
|
1547
|
+
* boot that was 1 024 of each, measured on the live hub 2026-08-27 — see the
|
|
1548
|
+
* cap method's note. Here the ownership lookup for every parent the live
|
|
1549
|
+
* registry does not hold is ONE `getMany`, and the children of every parent
|
|
1550
|
+
* are ONE `listByParentMany`.
|
|
1551
|
+
*
|
|
1552
|
+
* The live half is indexed once too: `registry.getChildren` is
|
|
1553
|
+
* `getAll().filter(…)`, so calling it per parent would trade an RPC N+1 for an
|
|
1554
|
+
* in-memory one — 256 full sweeps of the registry per batch. One pass over
|
|
1555
|
+
* `getAll()` buckets every live device by its parent instead.
|
|
1556
|
+
*
|
|
1557
|
+
* A parent with no children at all is ABSENT from the record; so is a parent
|
|
1558
|
+
* the fleet does not know. Same contract as `DeviceRowStore.getMany` — the
|
|
1559
|
+
* caller asked about it and gets no row, rather than an invented empty one it
|
|
1560
|
+
* cannot tell apart from a real childless parent.
|
|
1561
|
+
*/
|
|
1562
|
+
async function getChildrenBatch(pctx, input) {
|
|
1563
|
+
const parentIds = [...new Set(input.parentDeviceIds)];
|
|
1564
|
+
const out = {};
|
|
1565
|
+
if (parentIds.length === 0) return out;
|
|
1566
|
+
const owners = /* @__PURE__ */ new Map();
|
|
1567
|
+
const unownedIds = [];
|
|
1568
|
+
for (const parentId of parentIds) {
|
|
1569
|
+
const live = liveOwnerOf(pctx, parentId);
|
|
1570
|
+
if (live !== null) owners.set(parentId, live);
|
|
1571
|
+
else unownedIds.push(parentId);
|
|
1572
|
+
}
|
|
1573
|
+
if (unownedIds.length > 0) {
|
|
1574
|
+
const rows = await pctx.metaStore.rows.getMany(unownedIds);
|
|
1575
|
+
for (const [parentId, row] of rows) owners.set(parentId, row.meta.addonId);
|
|
1576
|
+
}
|
|
1577
|
+
const requested = new Set(parentIds);
|
|
1578
|
+
const rowsByParent = await pctx.metaStore.rows.listByParentMany([...owners.keys()]);
|
|
1579
|
+
const liveByParent = /* @__PURE__ */ new Map();
|
|
1580
|
+
for (const device of pctx.registry?.getAll() ?? []) {
|
|
1581
|
+
const parentId = device.parentDeviceId;
|
|
1582
|
+
if (parentId === null || parentId === void 0) continue;
|
|
1583
|
+
if (!requested.has(parentId)) continue;
|
|
1584
|
+
const bucket = liveByParent.get(parentId);
|
|
1585
|
+
if (bucket === void 0) liveByParent.set(parentId, [device]);
|
|
1586
|
+
else bucket.push(device);
|
|
1587
|
+
}
|
|
1588
|
+
for (const [parentId, ownerAddonId] of owners) {
|
|
1589
|
+
const childRows = rowsByParent.get(parentId) ?? [];
|
|
1590
|
+
const liveChildren = liveByParent.get(parentId) ?? [];
|
|
1591
|
+
if (childRows.length === 0 && liveChildren.length === 0) continue;
|
|
1592
|
+
const projected = await projectChildren(pctx, parentId, ownerAddonId, childRows, liveChildren);
|
|
1593
|
+
if (projected.length > 0) out[String(parentId)] = [...projected];
|
|
1594
|
+
}
|
|
1595
|
+
return out;
|
|
1596
|
+
}
|
|
1522
1597
|
async function getStreamSources(pctx, input) {
|
|
1523
1598
|
const { deviceId } = input;
|
|
1524
1599
|
if (pctx.registry) {
|
|
@@ -3390,27 +3465,33 @@ async function setName(pctx, input) {
|
|
|
3390
3465
|
});
|
|
3391
3466
|
}
|
|
3392
3467
|
/**
|
|
3393
|
-
* Every descendant of `rootId`, walked via `parentDeviceId` chains
|
|
3394
|
-
*
|
|
3395
|
-
*
|
|
3396
|
-
*
|
|
3397
|
-
*
|
|
3398
|
-
*
|
|
3399
|
-
*
|
|
3468
|
+
* Every descendant of `rootId`, walked via `parentDeviceId` chains.
|
|
3469
|
+
*
|
|
3470
|
+
* BFS one LEVEL per query, not one node per query: `listByParentMany` asks for
|
|
3471
|
+
* the whole frontier's children in a single indexed `whereIn`, so a tree costs
|
|
3472
|
+
* as many reads as it is DEEP, never as many as it has nodes. It used to be
|
|
3473
|
+
* `listByParent` per node — the same N+1 the boot census measured at 1 024
|
|
3474
|
+
* calls on `getChildren`.
|
|
3475
|
+
*
|
|
3476
|
+
* `visited` seeded with `rootId` guards a pathological parent-cycle in
|
|
3477
|
+
* persisted data from recursing forever; a row already seen is never re-queued
|
|
3478
|
+
* or re-emitted. Depth is unbounded on purpose — a nested container's own
|
|
3479
|
+
* children are still `rootId`'s descendants and must align too.
|
|
3400
3480
|
*/
|
|
3401
3481
|
async function collectDescendants(metaStore, rootId) {
|
|
3402
3482
|
const out = [];
|
|
3403
3483
|
const visited = new Set([rootId]);
|
|
3404
|
-
|
|
3405
|
-
while (
|
|
3406
|
-
const
|
|
3407
|
-
|
|
3408
|
-
for (const row of
|
|
3484
|
+
let frontier = [rootId];
|
|
3485
|
+
while (frontier.length > 0) {
|
|
3486
|
+
const byParent = await metaStore.rows.listByParentMany(frontier);
|
|
3487
|
+
const next = [];
|
|
3488
|
+
for (const parentId of frontier) for (const row of byParent.get(parentId) ?? []) {
|
|
3409
3489
|
if (visited.has(row.meta.id)) continue;
|
|
3410
3490
|
visited.add(row.meta.id);
|
|
3411
3491
|
out.push(row.meta);
|
|
3412
|
-
|
|
3492
|
+
next.push(row.meta.id);
|
|
3413
3493
|
}
|
|
3494
|
+
frontier = next;
|
|
3414
3495
|
}
|
|
3415
3496
|
return out;
|
|
3416
3497
|
}
|
|
@@ -4740,6 +4821,46 @@ var DeviceRowStore = class {
|
|
|
4740
4821
|
limit: DEVICE_ROWS_FLEET_LIMIT
|
|
4741
4822
|
});
|
|
4742
4823
|
}
|
|
4824
|
+
/**
|
|
4825
|
+
* Direct children of MANY devices, in ONE query.
|
|
4826
|
+
*
|
|
4827
|
+
* `listByParent` called once per parent is the same shape `get` was before
|
|
4828
|
+
* {@link getMany}: an indexed lookup this store makes cheap, repeated until
|
|
4829
|
+
* it isn't. Measured on the live hub 2026-08-27, 120-second boot window:
|
|
4830
|
+
* `DeviceRowStore.list < DeviceRowStore.listByParent < getChildren` at
|
|
4831
|
+
* **1 024 calls** — one per device — for 919 rows in total. The BFS in
|
|
4832
|
+
* `device-meta-actions.ts#collectDescendants` walked a container tree the
|
|
4833
|
+
* same way, one query per node.
|
|
4834
|
+
*
|
|
4835
|
+
* `whereIn` on the indexed `parentDeviceId` column collapses a level of that
|
|
4836
|
+
* walk into a single statement. Contract, verbatim from {@link getMany}: an
|
|
4837
|
+
* EMPTY `parentDeviceIds` reads nothing at all rather than degrading to
|
|
4838
|
+
* "everything", and a parent with no children — or one the fleet does not
|
|
4839
|
+
* know — is simply ABSENT from the map, never an invented empty row.
|
|
4840
|
+
*/
|
|
4841
|
+
async listByParentMany(parentDeviceIds) {
|
|
4842
|
+
const out = /* @__PURE__ */ new Map();
|
|
4843
|
+
const unique = [...new Set(parentDeviceIds)];
|
|
4844
|
+
if (unique.length === 0) return out;
|
|
4845
|
+
const rows = await this.list({
|
|
4846
|
+
whereIn: { parentDeviceId: unique },
|
|
4847
|
+
orderBy: {
|
|
4848
|
+
field: "deviceId",
|
|
4849
|
+
direction: "asc"
|
|
4850
|
+
},
|
|
4851
|
+
limit: DEVICE_ROWS_FLEET_LIMIT
|
|
4852
|
+
});
|
|
4853
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
4854
|
+
for (const row of rows) {
|
|
4855
|
+
const parent = row.meta.parentDeviceId;
|
|
4856
|
+
if (parent === null || parent === void 0) continue;
|
|
4857
|
+
const bucket = byParent.get(parent);
|
|
4858
|
+
if (bucket === void 0) byParent.set(parent, [row]);
|
|
4859
|
+
else bucket.push(row);
|
|
4860
|
+
}
|
|
4861
|
+
for (const [parent, bucket] of byParent) out.set(parent, bucket);
|
|
4862
|
+
return out;
|
|
4863
|
+
}
|
|
4743
4864
|
/** How many devices the fleet holds. Used to tell "empty store" from "gone device". */
|
|
4744
4865
|
async count() {
|
|
4745
4866
|
await this.declare();
|
|
@@ -5560,6 +5681,7 @@ var DeviceManagerAddon = class extends BaseAddon {
|
|
|
5560
5681
|
listAll: (input) => listAll(pctx, input),
|
|
5561
5682
|
getDevice: (input) => getDevice(pctx, input),
|
|
5562
5683
|
getChildren: (input) => getChildren(pctx, input),
|
|
5684
|
+
getChildrenBatch: (input) => getChildrenBatch(pctx, input),
|
|
5563
5685
|
getLinkedDevices: (input) => getLinkedDevices(pctx, input),
|
|
5564
5686
|
getLinkedDevicesBatch: (input) => getLinkedDevicesBatch(pctx, input),
|
|
5565
5687
|
getDeviceSettingsContribution: (input) => buildLinkedDevicesContribution(pctx, input.deviceId),
|