@camstack/system 1.2.136 → 1.2.138
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/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-bindings-store.d.ts +10 -4
- package/dist/builtins/device-manager/device-manager.addon.js +189 -25
- package/dist/builtins/device-manager/device-manager.addon.mjs +189 -25
- package/dist/builtins/device-manager/device-row-store.d.ts +20 -1
- package/dist/builtins/device-manager/fleet-read-census.d.ts +47 -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.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -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-Bh25kEUH.mjs → dist-DMOTtKH8.mjs} +310 -63
- package/dist/{dist-D-R0SuP-.js → dist-mQ3BkrJd.js} +310 -63
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{retired-settings-keys-BZbOXqch.mjs → retired-settings-keys-CPRvyt-E.mjs} +1 -1
- package/dist/{retired-settings-keys-CqD6rSQx.js → retired-settings-keys-Dvkcu_zf.js} +1 -1
- package/package.json +1 -1
|
@@ -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-mQ3BkrJd.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 { Gt as EventCategory, v as addonPagesCapability, vt as errMsg, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { Gt as EventCategory, v as addonPagesCapability, vt as errMsg, yt as BaseAddon } from "../../dist-DMOTtKH8.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-mQ3BkrJd.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 { Gt as EventCategory, b as addonWidgetsCapability, vt as errMsg, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { Gt as EventCategory, b as addonWidgetsCapability, vt as errMsg, yt as BaseAddon } from "../../dist-DMOTtKH8.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-mQ3BkrJd.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 { Gt as EventCategory, Mt as createEvent, n as AlertSchema, vt as errMsg, x as alertsCapability, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { Gt as EventCategory, Mt as createEvent, n as AlertSchema, vt as errMsg, x as alertsCapability, yt as BaseAddon } from "../../dist-DMOTtKH8.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-mQ3BkrJd.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 { C as backupCapability, Gt as EventCategory, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { C as backupCapability, Gt as EventCategory, yt as BaseAddon } from "../../dist-DMOTtKH8.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-mQ3BkrJd.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 { X as logDestinationCapability, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-DMOTtKH8.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-mQ3BkrJd.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 { D as coreBlockAddonId, O as coreBlockIdFromAddonId, c as CoreBlockSchema, k as coreBlocksCapability, l as DeclaredDevices, o as CORE_BLOCKS_ADDON_ID, s as CORE_BLOCK_ADDON_PREFIX, vt as errMsg, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { D as coreBlockAddonId, O as coreBlockIdFromAddonId, c as CoreBlockSchema, k as coreBlocksCapability, l as DeclaredDevices, o as CORE_BLOCKS_ADDON_ID, s as CORE_BLOCK_ADDON_PREFIX, vt as errMsg, yt as BaseAddon } from "../../dist-DMOTtKH8.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";
|
|
@@ -137,10 +137,16 @@ export declare function getBindings(deps: BindingsDeps, input: {
|
|
|
137
137
|
* caller needing seventy-five paid either 75 round-trips (1211 ms / 118 KB) or
|
|
138
138
|
* `getAllBindings` (511 ms / 679 KB of mostly-unwanted rows).
|
|
139
139
|
*
|
|
140
|
-
* Sequential on purpose, like `getAllBindings`: `
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
140
|
+
* Sequential on purpose, like `getAllBindings`: `resolveBindingsForDevice`'s
|
|
141
|
+
* remaining per-device work (the bindings-store read, the routing rules) runs
|
|
142
|
+
* on this process's own event loop, so concurrency buys nothing there and a
|
|
143
|
+
* fan-out of `Promise.all` over hundreds of ids only makes the queue less fair
|
|
144
|
+
* to everything else on it. The one part that DOES amortise across the whole
|
|
145
|
+
* set — the device row lookup — is fetched ONCE, ahead of the loop
|
|
146
|
+
* ({@link DeviceRowStore.getMany}), rather than once per iteration: measured
|
|
147
|
+
* on the live hub 2026-08-25, `get device-manager:devices` ran 1 656
|
|
148
|
+
* calls/min steady-state, one row per call, which is exactly what looping
|
|
149
|
+
* `getBindings`'s own `rows.get(deviceId)` here produced.
|
|
144
150
|
*/
|
|
145
151
|
export declare function getBindingsBatch(deps: BindingsDeps, input: {
|
|
146
152
|
deviceIds: readonly number[];
|
|
@@ -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-mQ3BkrJd.js");
|
|
7
|
+
const require_retired_settings_keys = require("../../retired-settings-keys-Dvkcu_zf.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
|
|
@@ -594,16 +594,33 @@ function capAppliesToDeviceType(def, deviceType) {
|
|
|
594
594
|
return declared.some((t) => t === deviceType);
|
|
595
595
|
}
|
|
596
596
|
async function getBindings(deps, input) {
|
|
597
|
-
const storeKey = String(input.deviceId);
|
|
598
|
-
const perDevice = (await readBindingsStore(deps)).deviceBindings[storeKey] ?? {};
|
|
599
597
|
const row = await deps.rows.get(input.deviceId);
|
|
600
|
-
|
|
601
|
-
|
|
598
|
+
return resolveBindingsForDevice(deps, input.deviceId, row);
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* The `getBindings` resolver, taking the device's row as a PARAMETER instead
|
|
602
|
+
* of fetching it.
|
|
603
|
+
*
|
|
604
|
+
* `getBindings` (one device) and `getBindingsBatch` / `getAllBindings` (many)
|
|
605
|
+
* share this — the routing rules and the per-device shape stay identical by
|
|
606
|
+
* construction. The split exists so the batch callers can resolve every row
|
|
607
|
+
* they need in ONE store round trip ({@link DeviceRowStore.getMany} /
|
|
608
|
+
* {@link DeviceRowStore.listAll}) instead of looping this function's own
|
|
609
|
+
* `deps.rows.get(deviceId)`, which is the primary-key-lookup-per-loop-
|
|
610
|
+
* iteration shape measured on the live hub 2026-08-25: `get
|
|
611
|
+
* device-manager:devices` at 1 656 calls/min steady-state and 17 687 during
|
|
612
|
+
* boot, one row per call.
|
|
613
|
+
*/
|
|
614
|
+
async function resolveBindingsForDevice(deps, deviceId, row) {
|
|
615
|
+
const storeKey = String(deviceId);
|
|
616
|
+
const perDevice = (await readBindingsStore(deps)).deviceBindings[storeKey] ?? {};
|
|
617
|
+
const deviceType = resolveDeviceType(deps, row, deviceId);
|
|
618
|
+
const presence = await resolveDevicePresence(deps, row, deviceId);
|
|
602
619
|
const entries = [];
|
|
603
620
|
const seenCaps = /* @__PURE__ */ new Set();
|
|
604
|
-
const resolveRemote = (capName) => deps.remoteNativeCaps.get(
|
|
621
|
+
const resolveRemote = (capName) => deps.remoteNativeCaps.get(deviceId)?.get(capName) ?? resolveRemoteNativeCapFromRegistry(deps, capName, deviceId);
|
|
605
622
|
for (const [capName, { wrapperAddonId }] of Object.entries(perDevice)) {
|
|
606
|
-
const hubLocalNative = deps.capabilityRegistry?.getNativeAddonId(capName,
|
|
623
|
+
const hubLocalNative = deps.capabilityRegistry?.getNativeAddonId(capName, deviceId) ?? null;
|
|
607
624
|
const remoteNative = resolveRemote(capName);
|
|
608
625
|
const nativeAddonId = hubLocalNative ?? remoteNative?.addonId ?? "";
|
|
609
626
|
const nativeNodeId = hubLocalNative ? deps.ctx.kernel.localNodeId ?? "hub" : remoteNative?.nodeId ?? deps.ctx.kernel.localNodeId ?? "hub";
|
|
@@ -620,7 +637,7 @@ async function getBindings(deps, input) {
|
|
|
620
637
|
});
|
|
621
638
|
seenCaps.add(capName);
|
|
622
639
|
}
|
|
623
|
-
if (presence === "absent") deps.ctx.logger.debug("bindings requested for absent device — returning none", { tags: { deviceId
|
|
640
|
+
if (presence === "absent") deps.ctx.logger.debug("bindings requested for absent device — returning none", { tags: { deviceId } });
|
|
624
641
|
else if (deps.capabilityRegistry) {
|
|
625
642
|
const skippedForType = [];
|
|
626
643
|
for (const capName of deps.capabilityRegistry.getCapsWithDefaultWrapper()) {
|
|
@@ -631,7 +648,7 @@ async function getBindings(deps, input) {
|
|
|
631
648
|
}
|
|
632
649
|
const defaultWrapperAddonId = deps.capabilityRegistry.getDefaultWrapperForCap(capName);
|
|
633
650
|
if (!defaultWrapperAddonId) continue;
|
|
634
|
-
const hubLocalNative = deps.capabilityRegistry.getNativeAddonId(capName,
|
|
651
|
+
const hubLocalNative = deps.capabilityRegistry.getNativeAddonId(capName, deviceId) ?? null;
|
|
635
652
|
const remoteNative = resolveRemote(capName);
|
|
636
653
|
const nativeAddonId = hubLocalNative ?? remoteNative?.addonId ?? "";
|
|
637
654
|
entries.push({
|
|
@@ -644,16 +661,16 @@ async function getBindings(deps, input) {
|
|
|
644
661
|
seenCaps.add(capName);
|
|
645
662
|
}
|
|
646
663
|
if (skippedForType.length > 0) deps.ctx.logger.debug("getBindings: default wrappers skipped — deviceTypes mismatch", {
|
|
647
|
-
tags: { deviceId
|
|
664
|
+
tags: { deviceId },
|
|
648
665
|
meta: {
|
|
649
666
|
deviceType,
|
|
650
667
|
skipped: skippedForType
|
|
651
668
|
}
|
|
652
669
|
});
|
|
653
670
|
}
|
|
654
|
-
if (deps.capabilityRegistry) for (const capName of deps.capabilityRegistry.getNativeCapsForDevice(
|
|
671
|
+
if (deps.capabilityRegistry) for (const capName of deps.capabilityRegistry.getNativeCapsForDevice(deviceId)) {
|
|
655
672
|
if (seenCaps.has(capName)) continue;
|
|
656
|
-
const nativeAddonId = deps.capabilityRegistry.getNativeAddonId(capName,
|
|
673
|
+
const nativeAddonId = deps.capabilityRegistry.getNativeAddonId(capName, deviceId) ?? "";
|
|
657
674
|
entries.push({
|
|
658
675
|
capName,
|
|
659
676
|
kind: "native",
|
|
@@ -663,7 +680,7 @@ async function getBindings(deps, input) {
|
|
|
663
680
|
});
|
|
664
681
|
seenCaps.add(capName);
|
|
665
682
|
}
|
|
666
|
-
const pushFed = deps.remoteNativeCaps.get(
|
|
683
|
+
const pushFed = deps.remoteNativeCaps.get(deviceId);
|
|
667
684
|
if (pushFed) for (const [capName, info] of pushFed) {
|
|
668
685
|
if (seenCaps.has(capName)) continue;
|
|
669
686
|
entries.push({
|
|
@@ -675,9 +692,9 @@ async function getBindings(deps, input) {
|
|
|
675
692
|
});
|
|
676
693
|
seenCaps.add(capName);
|
|
677
694
|
}
|
|
678
|
-
const clusterCaps = deps.ctx.kernel.listClusterNativeCapsForDevice?.(
|
|
695
|
+
const clusterCaps = deps.ctx.kernel.listClusterNativeCapsForDevice?.(deviceId) ?? deps.ctx.kernel.listClusterNativeCaps?.();
|
|
679
696
|
if (clusterCaps) for (const entry of clusterCaps) {
|
|
680
|
-
if (entry.deviceId !==
|
|
697
|
+
if (entry.deviceId !== deviceId) continue;
|
|
681
698
|
if (seenCaps.has(entry.capName)) continue;
|
|
682
699
|
if (!entry.addonId) continue;
|
|
683
700
|
const localNodeId = deps.ctx.kernel.localNodeId ?? "hub";
|
|
@@ -692,7 +709,7 @@ async function getBindings(deps, input) {
|
|
|
692
709
|
seenCaps.add(entry.capName);
|
|
693
710
|
}
|
|
694
711
|
return {
|
|
695
|
-
deviceId
|
|
712
|
+
deviceId,
|
|
696
713
|
entries
|
|
697
714
|
};
|
|
698
715
|
}
|
|
@@ -705,14 +722,22 @@ async function getBindings(deps, input) {
|
|
|
705
722
|
* caller needing seventy-five paid either 75 round-trips (1211 ms / 118 KB) or
|
|
706
723
|
* `getAllBindings` (511 ms / 679 KB of mostly-unwanted rows).
|
|
707
724
|
*
|
|
708
|
-
* Sequential on purpose, like `getAllBindings`: `
|
|
709
|
-
*
|
|
710
|
-
*
|
|
711
|
-
*
|
|
725
|
+
* Sequential on purpose, like `getAllBindings`: `resolveBindingsForDevice`'s
|
|
726
|
+
* remaining per-device work (the bindings-store read, the routing rules) runs
|
|
727
|
+
* on this process's own event loop, so concurrency buys nothing there and a
|
|
728
|
+
* fan-out of `Promise.all` over hundreds of ids only makes the queue less fair
|
|
729
|
+
* to everything else on it. The one part that DOES amortise across the whole
|
|
730
|
+
* set — the device row lookup — is fetched ONCE, ahead of the loop
|
|
731
|
+
* ({@link DeviceRowStore.getMany}), rather than once per iteration: measured
|
|
732
|
+
* on the live hub 2026-08-25, `get device-manager:devices` ran 1 656
|
|
733
|
+
* calls/min steady-state, one row per call, which is exactly what looping
|
|
734
|
+
* `getBindings`'s own `rows.get(deviceId)` here produced.
|
|
712
735
|
*/
|
|
713
736
|
async function getBindingsBatch(deps, input) {
|
|
737
|
+
const ids = [...new Set(input.deviceIds)];
|
|
738
|
+
const rows = await deps.rows.getMany(ids);
|
|
714
739
|
const out = [];
|
|
715
|
-
for (const deviceId of
|
|
740
|
+
for (const deviceId of ids) out.push(await resolveBindingsForDevice(deps, deviceId, rows.get(deviceId) ?? null));
|
|
716
741
|
return out;
|
|
717
742
|
}
|
|
718
743
|
/**
|
|
@@ -727,8 +752,13 @@ async function getBindingsBatch(deps, input) {
|
|
|
727
752
|
* payload periodically.
|
|
728
753
|
*/
|
|
729
754
|
async function getAllBindings(deps) {
|
|
755
|
+
const fleet = await deps.rows.listAll();
|
|
756
|
+
const rowById = /* @__PURE__ */ new Map();
|
|
730
757
|
const ids = /* @__PURE__ */ new Set();
|
|
731
|
-
for (const row of
|
|
758
|
+
for (const row of fleet) {
|
|
759
|
+
rowById.set(row.meta.id, row);
|
|
760
|
+
ids.add(row.meta.id);
|
|
761
|
+
}
|
|
732
762
|
const registered = deps.ctx.kernel?.deviceRegistry?.getAll() ?? [];
|
|
733
763
|
for (const device of registered) ids.add(device.id);
|
|
734
764
|
if (ids.size === 0) {
|
|
@@ -736,7 +766,7 @@ async function getAllBindings(deps) {
|
|
|
736
766
|
return [];
|
|
737
767
|
}
|
|
738
768
|
const out = [];
|
|
739
|
-
for (const deviceId of [...ids].sort((a, b) => a - b)) out.push(await
|
|
769
|
+
for (const deviceId of [...ids].sort((a, b) => a - b)) out.push(await resolveBindingsForDevice(deps, deviceId, rowById.get(deviceId) ?? null));
|
|
740
770
|
return out;
|
|
741
771
|
}
|
|
742
772
|
/**
|
|
@@ -3919,6 +3949,112 @@ var DeviceMetaStore = class {
|
|
|
3919
3949
|
};
|
|
3920
3950
|
};
|
|
3921
3951
|
//#endregion
|
|
3952
|
+
//#region src/builtins/device-manager/fleet-read-census.ts
|
|
3953
|
+
/** How many stack frames identify a call site. One frame is a method; three are
|
|
3954
|
+
* a cause — `listFleet < listAll < onDeviceRegistered` is the answer, `listAll`
|
|
3955
|
+
* alone is not. Same choice, for the same reason, as `read-census.ts`. */
|
|
3956
|
+
var CENSUS_STACK_FRAMES = 3;
|
|
3957
|
+
function siteFromStack(stack) {
|
|
3958
|
+
if (stack === void 0) return "unknown";
|
|
3959
|
+
const frames = stack.split("\n").slice(1).map((line) => line.trim().replace(/^at\s+/, "")).filter((line) => line.length > 0).slice(0, CENSUS_STACK_FRAMES).map((line) => {
|
|
3960
|
+
const open = line.indexOf(" (");
|
|
3961
|
+
return (open > 0 ? line.slice(0, open) : line).replace(/^async\s+/, "");
|
|
3962
|
+
});
|
|
3963
|
+
return frames.length === 0 ? "unknown" : frames.join(" < ");
|
|
3964
|
+
}
|
|
3965
|
+
var FleetReadCensus = class FleetReadCensus {
|
|
3966
|
+
/**
|
|
3967
|
+
* Longest window the census may be armed for.
|
|
3968
|
+
*
|
|
3969
|
+
* Ten minutes covers a boot several times over and bounds what an operator
|
|
3970
|
+
* pays if they set the variable and forget it. A diagnostic that can be left
|
|
3971
|
+
* on becomes a tax nobody remembers enabling.
|
|
3972
|
+
*/
|
|
3973
|
+
static MAX_WINDOW_MS = 10 * 6e4;
|
|
3974
|
+
windowMs;
|
|
3975
|
+
closesAt;
|
|
3976
|
+
counts = /* @__PURE__ */ new Map();
|
|
3977
|
+
reported = false;
|
|
3978
|
+
constructor(rawEnv, nowMs) {
|
|
3979
|
+
const parsed = rawEnv === void 0 ? NaN : Number(rawEnv);
|
|
3980
|
+
const valid = Number.isFinite(parsed) && parsed > 0;
|
|
3981
|
+
this.windowMs = valid ? Math.min(parsed, FleetReadCensus.MAX_WINDOW_MS) : 0;
|
|
3982
|
+
this.closesAt = valid ? nowMs + this.windowMs : 0;
|
|
3983
|
+
}
|
|
3984
|
+
get armed() {
|
|
3985
|
+
return this.windowMs > 0;
|
|
3986
|
+
}
|
|
3987
|
+
/** Test seam: pretend the window closed. Production reads the clock. */
|
|
3988
|
+
closeAt(nowMs) {
|
|
3989
|
+
if (nowMs >= this.closesAt) this.forceClosed = true;
|
|
3990
|
+
}
|
|
3991
|
+
forceClosed = false;
|
|
3992
|
+
isOpen(nowMs) {
|
|
3993
|
+
if (!this.armed || this.forceClosed) return false;
|
|
3994
|
+
return nowMs < this.closesAt;
|
|
3995
|
+
}
|
|
3996
|
+
/**
|
|
3997
|
+
* Capture the caller, or `null` when the window is shut.
|
|
3998
|
+
*
|
|
3999
|
+
* Called at the START of a read. A read that began inside the window belongs
|
|
4000
|
+
* to it even if it lands after the close — otherwise the slow reads at the
|
|
4001
|
+
* edge would be attributed to nobody, and the slow ones are the interesting
|
|
4002
|
+
* ones.
|
|
4003
|
+
*/
|
|
4004
|
+
siteOf(nowMs = Date.now()) {
|
|
4005
|
+
if (!this.isOpen(nowMs)) return null;
|
|
4006
|
+
const holder = {};
|
|
4007
|
+
Error.captureStackTrace(holder, this.siteOf);
|
|
4008
|
+
return siteFromStack(holder.stack);
|
|
4009
|
+
}
|
|
4010
|
+
/** Count one read at an already-captured site. A `null` site is not counted. */
|
|
4011
|
+
record(site, rowCount) {
|
|
4012
|
+
if (site === null) return;
|
|
4013
|
+
const existing = this.counts.get(site);
|
|
4014
|
+
if (existing === void 0) {
|
|
4015
|
+
this.counts.set(site, {
|
|
4016
|
+
site,
|
|
4017
|
+
calls: 1,
|
|
4018
|
+
rows: rowCount
|
|
4019
|
+
});
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
4022
|
+
existing.calls += 1;
|
|
4023
|
+
existing.rows += rowCount;
|
|
4024
|
+
}
|
|
4025
|
+
/** Attributed sites, busiest first. */
|
|
4026
|
+
rows() {
|
|
4027
|
+
return [...this.counts.values()].map((c) => ({
|
|
4028
|
+
site: c.site,
|
|
4029
|
+
calls: c.calls,
|
|
4030
|
+
rows: c.rows
|
|
4031
|
+
})).sort((a, b) => b.calls - a.calls);
|
|
4032
|
+
}
|
|
4033
|
+
/**
|
|
4034
|
+
* Emit the report ONCE.
|
|
4035
|
+
*
|
|
4036
|
+
* `perMin` is included because the raw count is unreadable without the window
|
|
4037
|
+
* it was taken over — "4 149" means nothing until you know it was 66 seconds.
|
|
4038
|
+
*/
|
|
4039
|
+
report(logger, elapsedMs) {
|
|
4040
|
+
if (this.reported || !this.armed) return;
|
|
4041
|
+
this.reported = true;
|
|
4042
|
+
const rows = this.rows();
|
|
4043
|
+
const totalCalls = rows.reduce((acc, r) => acc + r.calls, 0);
|
|
4044
|
+
logger.info("fleet read census", { meta: {
|
|
4045
|
+
elapsedMs,
|
|
4046
|
+
sites: rows.length,
|
|
4047
|
+
totalCalls,
|
|
4048
|
+
rows: rows.map((r) => ({
|
|
4049
|
+
site: r.site,
|
|
4050
|
+
calls: r.calls,
|
|
4051
|
+
rows: r.rows,
|
|
4052
|
+
perMin: elapsedMs === 0 ? 0 : Math.round(r.calls * 6e4 / elapsedMs)
|
|
4053
|
+
}))
|
|
4054
|
+
} });
|
|
4055
|
+
}
|
|
4056
|
+
};
|
|
4057
|
+
//#endregion
|
|
3922
4058
|
//#region src/builtins/device-manager/device-row-store.ts
|
|
3923
4059
|
/**
|
|
3924
4060
|
* @durable class=ledger owner=device-manager
|
|
@@ -4248,9 +4384,17 @@ var DeviceRowStore = class {
|
|
|
4248
4384
|
backend;
|
|
4249
4385
|
logger;
|
|
4250
4386
|
declared = null;
|
|
4251
|
-
|
|
4387
|
+
/** Call-site attribution for fleet reads. Armed only by
|
|
4388
|
+
* `CAMSTACK_FLEET_READ_CENSUS_MS`; see `fleet-read-census.ts`. */
|
|
4389
|
+
census;
|
|
4390
|
+
constructor(backend, logger, census) {
|
|
4252
4391
|
this.backend = backend;
|
|
4253
4392
|
this.logger = logger;
|
|
4393
|
+
this.census = census ?? new FleetReadCensus(process.env["CAMSTACK_FLEET_READ_CENSUS_MS"], Date.now());
|
|
4394
|
+
if (this.census.armed) {
|
|
4395
|
+
const startedAt = Date.now();
|
|
4396
|
+
setTimeout(() => this.census.report(this.logger, Date.now() - startedAt), this.census.windowMs).unref?.();
|
|
4397
|
+
}
|
|
4254
4398
|
}
|
|
4255
4399
|
/**
|
|
4256
4400
|
* Lazy, idempotent `declareCollection`, memoised on the PROMISE so N
|
|
@@ -4329,6 +4473,24 @@ var DeviceRowStore = class {
|
|
|
4329
4473
|
}, opts.slim === true ? DEVICE_ROWS_SLIM_COLUMNS : void 0);
|
|
4330
4474
|
}
|
|
4331
4475
|
/**
|
|
4476
|
+
* Many devices, by numeric id, in ONE query.
|
|
4477
|
+
*
|
|
4478
|
+
* `get(id)` called once per id in a loop is the primary-key lookup this
|
|
4479
|
+
* store exists to make cheap, repeated until it isn't — measured on the
|
|
4480
|
+
* live hub 2026-08-25: `get device-manager:devices` at 1 656 calls/min
|
|
4481
|
+
* steady-state and 17 687 during boot, exactly one row per call. This
|
|
4482
|
+
* collapses that into the same `whereIn` {@link listFleet} already does for
|
|
4483
|
+
* a fleet read, keyed by id.
|
|
4484
|
+
*
|
|
4485
|
+
* A requested id the fleet does not know is simply ABSENT from the map —
|
|
4486
|
+
* never a fabricated row, matching {@link get}'s `null`. An empty `ids`
|
|
4487
|
+
* reads nothing at all, same as `listFleet({ deviceIds: [] })`.
|
|
4488
|
+
*/
|
|
4489
|
+
async getMany(ids) {
|
|
4490
|
+
const rows = await this.listFleet({ deviceIds: ids });
|
|
4491
|
+
return new Map(rows.map((row) => [row.meta.id, row]));
|
|
4492
|
+
}
|
|
4493
|
+
/**
|
|
4332
4494
|
* The device an addon knows as `stableId`, or `null`.
|
|
4333
4495
|
*
|
|
4334
4496
|
* `(addonId, stableId)` is the addon-facing identity — unique by
|
|
@@ -4401,11 +4563,13 @@ var DeviceRowStore = class {
|
|
|
4401
4563
|
}
|
|
4402
4564
|
async list(filter, columns) {
|
|
4403
4565
|
await this.declare();
|
|
4566
|
+
const site = this.census.siteOf();
|
|
4404
4567
|
const records = await this.backend.query({
|
|
4405
4568
|
collection: DEVICE_ROWS_COLLECTION,
|
|
4406
4569
|
filter,
|
|
4407
4570
|
...columns === void 0 ? {} : { columns }
|
|
4408
4571
|
});
|
|
4572
|
+
this.census.record(site, records.length);
|
|
4409
4573
|
const out = [];
|
|
4410
4574
|
for (const record of records) {
|
|
4411
4575
|
const decoded = decodeDeviceRow(record.data);
|