@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.
- package/dist/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -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-CQs1b7uh.js → dist-BVU5JADq.js} +488 -29
- package/dist/{dist-CVuVKA-O.mjs → dist-CG7kwIZp.mjs} +488 -29
- package/dist/index.js +28 -9
- package/dist/index.mjs +28 -9
- package/dist/kernel/index.d.ts +1 -1
- package/dist/kernel/moleculer/children-batch-loader.d.ts +46 -0
- package/dist/kernel/moleculer/core-cap-service.d.ts +17 -2
- package/dist/kernel/moleculer/process-service.d.ts +15 -6
- package/dist/{manifest-python-deps-BuhBXCg0.mjs → manifest-python-deps-BRyNCnnG.mjs} +191 -1
- package/dist/{manifest-python-deps-B_mCU6gz.js → manifest-python-deps-GjlyPjm0.js} +191 -1
- package/dist/{retired-settings-keys-DnMYSIEr.mjs → retired-settings-keys-Dm1z4mO4.mjs} +1 -1
- package/dist/{retired-settings-keys-Dvei9nhG.js → retired-settings-keys-_PLI9w0k.js} +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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
|
const require_builtins_alerts_alerts_addon = require("./builtins/alerts/alerts.addon.js");
|
|
5
5
|
require("./builtins/alerts/index.js");
|
|
6
6
|
const require_formatter = require("./formatter-DqAKDlvN.js");
|
|
@@ -26,7 +26,7 @@ require("./builtins/system-config/index.js");
|
|
|
26
26
|
const require_builtins_winston_logging_index = require("./builtins/winston-logging/index.js");
|
|
27
27
|
const require_file_data_plane = require("./file-data-plane-DO8KbxCe.js");
|
|
28
28
|
const require_tls$1 = require("./tls-u8QCJCFE.js");
|
|
29
|
-
const require_manifest_python_deps = require("./manifest-python-deps-
|
|
29
|
+
const require_manifest_python_deps = require("./manifest-python-deps-GjlyPjm0.js");
|
|
30
30
|
const require_resource_monitor = require("./resource-monitor-CdnzxBLP.js");
|
|
31
31
|
const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
|
|
32
32
|
let zod = require("zod");
|
|
@@ -93435,13 +93435,23 @@ function createCoreCapService(options) {
|
|
|
93435
93435
|
const actions = {};
|
|
93436
93436
|
for (const { actionName, invoke } of options.actions) actions[actionName] = { handler: async (ctx) => {
|
|
93437
93437
|
const raw = require_manifest_python_deps.deserializeTypedArrays(ctx.params);
|
|
93438
|
-
return invoke(raw !== null && typeof raw === "object" && Object.keys(raw).length === 0 ? void 0 : raw);
|
|
93438
|
+
return invoke(raw !== null && typeof raw === "object" && Object.keys(raw).length === 0 ? void 0 : raw, readOrigin(ctx));
|
|
93439
93439
|
} };
|
|
93440
93440
|
return {
|
|
93441
93441
|
name: options.serviceName ?? "$core-caps",
|
|
93442
93442
|
actions
|
|
93443
93443
|
};
|
|
93444
93444
|
}
|
|
93445
|
+
/** Structural read of the calling node/action. Anything unrecognised is `null`. */
|
|
93446
|
+
function readOrigin(ctx) {
|
|
93447
|
+
return {
|
|
93448
|
+
nodeId: asName(ctx.nodeID),
|
|
93449
|
+
caller: asName(ctx.caller)
|
|
93450
|
+
};
|
|
93451
|
+
}
|
|
93452
|
+
function asName(value) {
|
|
93453
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
93454
|
+
}
|
|
93445
93455
|
//#endregion
|
|
93446
93456
|
//#region src/kernel/moleculer/hub-log-forwarder.ts
|
|
93447
93457
|
/**
|
|
@@ -94339,10 +94349,20 @@ function isHeavyRunner(addons) {
|
|
|
94339
94349
|
* one-off burst becomes the runner's permanent RSS.
|
|
94340
94350
|
*
|
|
94341
94351
|
* Measured on the live hub, 2026-08-17: the `recorder` runner reaches **2122 MB RSS** and stays
|
|
94342
|
-
* there
|
|
94343
|
-
*
|
|
94344
|
-
*
|
|
94345
|
-
*
|
|
94352
|
+
* there. The same addon on two agent nodes carrying the same code and a smaller archive sits at
|
|
94353
|
+
* **58 MB**.
|
|
94354
|
+
*
|
|
94355
|
+
* ⚠ This paragraph used to attribute that ~2 GB to "walk garbage" from the deferred archive walk,
|
|
94356
|
+
* and to argue from a ~21 MB `RecordingIndex`. Both were wrong, and the error cost a session on
|
|
94357
|
+
* 2026-08-27. The deferred walk had NO CALLER and never ran (it has since been deleted). And the
|
|
94358
|
+
* index is not 21 MB: a 2026-08-27 profile of the live recorder read `heapUsed = 1606 MB` against
|
|
94359
|
+
* `external = 8 MB`, with an `smaps` census putting 1510 MB in V8's 256 KB page ranges against
|
|
94360
|
+
* 54 MB of native heap — LIVE JavaScript retainers, not high-water garbage. The retained set is
|
|
94361
|
+
* ~7.1 M `SegmentRow`s plus their path strings and Map entries (13 cameras x 2 profiles x 8 640
|
|
94362
|
+
* segments/day x ~30 days), reseeded at boot by `SegmentHourLedger.hydrateIndex`.
|
|
94363
|
+
*
|
|
94364
|
+
* The lesson for anyone sizing this flag: a heap number needs a RETAINER, and "garbage the GC has
|
|
94365
|
+
* no reason to collect" is the explanation to reach for last, not first.
|
|
94346
94366
|
*
|
|
94347
94367
|
* ## Why 1024
|
|
94348
94368
|
*
|
|
@@ -94359,8 +94379,7 @@ function isHeavyRunner(addons) {
|
|
|
94359
94379
|
*
|
|
94360
94380
|
* A runner whose working set genuinely does not fit declares its own number in its manifest
|
|
94361
94381
|
* (`execution.maxOldSpaceMb`) — the number belongs next to the addon that knows its working set.
|
|
94362
|
-
* `recorder` (3072 MB; peak 2341 MB heapTotal
|
|
94363
|
-
* shipped addon that does. `stream-broker` does NOT, despite 1585 MB peak RSS: ~1 GB of that is
|
|
94382
|
+
* `recorder` (3072 MB; peak 2341 MB heapTotal) is the one shipped addon that does. `stream-broker` does NOT, despite 1585 MB peak RSS: ~1 GB of that is
|
|
94364
94383
|
* `arrayBuffers` (47 RTP rings), which this flag does not bound and never will — its actual old
|
|
94365
94384
|
* space peaks at 224 MB. Size the declaration from `heapTotal` in the `[mem]` heartbeat, never
|
|
94366
94385
|
* from RSS.
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
|
|
2
|
-
import { $ as kebabToCamel, Bt as emitDownForOwnedCaps, C as addonSettingsCapability, Dt as DATAPLANE_SECRET_HEADER$1, G as extractNestedAddonId, Ht as expandCapMethods, It as asJsonObject$1, J as isCollectionArrayMethod, Jt as readinessKey, Kt as parseJsonObject, Lt as asNumber, Nt as ReadinessRegistry, Ot as DEVICE_SETTINGS_CONTRIBUTION_METHODS, Pt as ReadinessTimeoutError, Q as isVoidInput, Qt as EventCategory$1, Rt as asString$1, Tt as errMsg$1, Xt as scopeKey, Y as isObjectInput, Yt as resolveCapMount, dt as resolveMethodAuth, et as lifecycleJobSchema, f as LOG_LEVEL_RANK, it as looseSchema, kt as DEVICE_STATUS_METHOD, m as RUNTIME_DEFAULTS, mt as scopesAllowDeviceCap, p as METHOD_ACCESS_MAP, pt as scopesAllowAddon, q as isArrayOutputSchema, qt as parseJsonUnknown$1, rt as logLevelAtMost, st as objectInputDeclaresAddonId, t as ALL_CAPABILITY_DEFINITIONS, ut as procedureAuthKey, zt as createEvent } from "./dist-
|
|
2
|
+
import { $ as kebabToCamel, Bt as emitDownForOwnedCaps, C as addonSettingsCapability, Dt as DATAPLANE_SECRET_HEADER$1, G as extractNestedAddonId, Ht as expandCapMethods, It as asJsonObject$1, J as isCollectionArrayMethod, Jt as readinessKey, Kt as parseJsonObject, Lt as asNumber, Nt as ReadinessRegistry, Ot as DEVICE_SETTINGS_CONTRIBUTION_METHODS, Pt as ReadinessTimeoutError, Q as isVoidInput, Qt as EventCategory$1, Rt as asString$1, Tt as errMsg$1, Xt as scopeKey, Y as isObjectInput, Yt as resolveCapMount, dt as resolveMethodAuth, et as lifecycleJobSchema, f as LOG_LEVEL_RANK, it as looseSchema, kt as DEVICE_STATUS_METHOD, m as RUNTIME_DEFAULTS, mt as scopesAllowDeviceCap, p as METHOD_ACCESS_MAP, pt as scopesAllowAddon, q as isArrayOutputSchema, qt as parseJsonUnknown$1, rt as logLevelAtMost, st as objectInputDeclaresAddonId, t as ALL_CAPABILITY_DEFINITIONS, ut as procedureAuthKey, zt as createEvent } from "./dist-CG7kwIZp.mjs";
|
|
3
3
|
import { AlertCenterAddon } from "./builtins/alerts/alerts.addon.mjs";
|
|
4
4
|
import "./builtins/alerts/index.mjs";
|
|
5
5
|
import { t as formatLogLine } from "./formatter-B7qW8bPJ.mjs";
|
|
@@ -25,7 +25,7 @@ import "./builtins/system-config/index.mjs";
|
|
|
25
25
|
import { WinstonDestination, WinstonLoggingAddon } from "./builtins/winston-logging/index.mjs";
|
|
26
26
|
import { a as parseTokenizedUrl, c as collectModelFiles, d as downloadModel, f as ensureModel, h as isModelDownloaded, i as parseRangeHeader, l as deleteModelFromDisk, m as getModelFilePath, n as contentTypeFor, o as resolveFilePath, p as fetchJson, r as createAuthenticatedFileServer, s as ModelDownloadService, t as createFileDataPlaneHandler, u as downloadFile } from "./file-data-plane-BhKdxJgf.mjs";
|
|
27
27
|
import { C as registerLanHttpHandler, S as readLanHttpState, _ as DEFAULT_LAN_HTTP_PORT, a as writeTlsMode, b as bindPendingLanHttp, c as loadTlsCert, d as collectCertIdentity, f as CA_VALIDITY_DAYS, g as evaluateExistingCert, h as SERVER_AUTH_OID, i as writeExtraSans, l as reissueTlsLeaf, m as MAX_LEAF_VALIDITY_DAYS, n as readTlsAccessStatus, o as validateUploadedTls, p as LEAF_RENEWAL_WINDOW_DAYS, r as readTlsMode, s as ensureTlsCert, t as readExtraSans, u as CA_COMMON_NAME, v as allFamiliesListenHost, x as closeLanHttp, y as applyLanHttp } from "./tls-CQhPGSJm.mjs";
|
|
28
|
-
import { $ as buildUdsNativeCapProxy, A as createParentUnownedCallHandler, At as createV8Reclaimer, B as AGENT_CAP_FWD_SERVICE, C as brokerTransportLink, Ct as createAddonDataPlaneFacility, D as HUB_CAP_FWD_ACTION, Dt as HEAP_WATCH_WARN_RATIO, E as localProviderLink, Et as HEAP_WATCH_INTERVAL_MS, F as createUdsLoggerWithControl, G as createLocalTransport, H as CapRouteError, I as LocalChildClient, J as SocketChannel, K as UdsLocalTransportClient, L as LocalChildRegistry, M as createUdsEventBus, Mt as startHeapWatch, N as udsChildLogToWorkerEntry, Nt as startRunnerHeapWatch, O as HUB_CAP_FWD_SERVICE, Ot as RUNNER_HEAP_WATCH_INTERVAL_MS, P as createUdsLogger, Pt as strandedMb, Q as buildNativeCapProxy, R as UDS_NO_ROUTE_PREFIX, S as brokerCallForCap, St as runNpm, T as ipcParentLink, Tt as HEAP_RECLAIM_TRIGGER_MB, U as classifyCapRoute, V as CapRouteResolver, W as callWithServiceDiscovery, X as FrameDecoder, Y as localEndpointPath, Z as encodeFrame, _ as createKernelHwAccel, _t as CapabilityUnavailableError, a as getWorkerDeviceRegistry, at as createAddonService, b as __resetCapUsageRegistryForTests, bt as resolveAddonClass, c as setHubConnected, ct as capActionName, d as getBrokerEventBus, dt as capServiceName, et as createBrokerDeviceManagerApi, f as getMoleculerEventStats, ft as parseCapAction, g as AddonDepsManager, gt as CapabilityHandle, h as subscribePassthrough, ht as DeviceRegistry, i as createUdsAddonContext, j as createUdsEventBridge, jt as shouldReclaim, k as createHubCapForwardService, kt as buildHeapSample, l as EVENT_TOPIC_PREFIX, lt as capActionSuffix, m as setNodeEventInterest, mt as serializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as validateProviderRegistrations, p as registerEventBusService, pt as deserializeTypedArrays, q as UdsLocalTransportServer, r as createAddonContext, rt as mountNativeCapService, s as getOrInitReadinessRegistryForClient, st as NATIVE_PROVIDER_SERVICE_INFIX, t as installManifestPythonDeps, u as clusterEventTopic, ut as capBareAction, v as resolveHwAccel, vt as copyBundledNativeModules, w as buildLinkChain, wt as HEAP_RECLAIM_MIN_INTERVAL_MS, x as getCapUsageRegistry, xt as resolveNpmInvocation, y as CapUsageRegistry, yt as installManifestNativeDeps, z as AGENT_CAP_FWD_ACTION } from "./manifest-python-deps-
|
|
28
|
+
import { $ as buildUdsNativeCapProxy, A as createParentUnownedCallHandler, At as createV8Reclaimer, B as AGENT_CAP_FWD_SERVICE, C as brokerTransportLink, Ct as createAddonDataPlaneFacility, D as HUB_CAP_FWD_ACTION, Dt as HEAP_WATCH_WARN_RATIO, E as localProviderLink, Et as HEAP_WATCH_INTERVAL_MS, F as createUdsLoggerWithControl, G as createLocalTransport, H as CapRouteError, I as LocalChildClient, J as SocketChannel, K as UdsLocalTransportClient, L as LocalChildRegistry, M as createUdsEventBus, Mt as startHeapWatch, N as udsChildLogToWorkerEntry, Nt as startRunnerHeapWatch, O as HUB_CAP_FWD_SERVICE, Ot as RUNNER_HEAP_WATCH_INTERVAL_MS, P as createUdsLogger, Pt as strandedMb, Q as buildNativeCapProxy, R as UDS_NO_ROUTE_PREFIX, S as brokerCallForCap, St as runNpm, T as ipcParentLink, Tt as HEAP_RECLAIM_TRIGGER_MB, U as classifyCapRoute, V as CapRouteResolver, W as callWithServiceDiscovery, X as FrameDecoder, Y as localEndpointPath, Z as encodeFrame, _ as createKernelHwAccel, _t as CapabilityUnavailableError, a as getWorkerDeviceRegistry, at as createAddonService, b as __resetCapUsageRegistryForTests, bt as resolveAddonClass, c as setHubConnected, ct as capActionName, d as getBrokerEventBus, dt as capServiceName, et as createBrokerDeviceManagerApi, f as getMoleculerEventStats, ft as parseCapAction, g as AddonDepsManager, gt as CapabilityHandle, h as subscribePassthrough, ht as DeviceRegistry, i as createUdsAddonContext, j as createUdsEventBridge, jt as shouldReclaim, k as createHubCapForwardService, kt as buildHeapSample, l as EVENT_TOPIC_PREFIX, lt as capActionSuffix, m as setNodeEventInterest, mt as serializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as validateProviderRegistrations, p as registerEventBusService, pt as deserializeTypedArrays, q as UdsLocalTransportServer, r as createAddonContext, rt as mountNativeCapService, s as getOrInitReadinessRegistryForClient, st as NATIVE_PROVIDER_SERVICE_INFIX, t as installManifestPythonDeps, u as clusterEventTopic, ut as capBareAction, v as resolveHwAccel, vt as copyBundledNativeModules, w as buildLinkChain, wt as HEAP_RECLAIM_MIN_INTERVAL_MS, x as getCapUsageRegistry, xt as resolveNpmInvocation, y as CapUsageRegistry, yt as installManifestNativeDeps, z as AGENT_CAP_FWD_ACTION } from "./manifest-python-deps-BRyNCnnG.mjs";
|
|
29
29
|
import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-BWmQ5i-o.mjs";
|
|
30
30
|
import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
|
|
31
31
|
import { z } from "zod";
|
|
@@ -93428,13 +93428,23 @@ function createCoreCapService(options) {
|
|
|
93428
93428
|
const actions = {};
|
|
93429
93429
|
for (const { actionName, invoke } of options.actions) actions[actionName] = { handler: async (ctx) => {
|
|
93430
93430
|
const raw = deserializeTypedArrays(ctx.params);
|
|
93431
|
-
return invoke(raw !== null && typeof raw === "object" && Object.keys(raw).length === 0 ? void 0 : raw);
|
|
93431
|
+
return invoke(raw !== null && typeof raw === "object" && Object.keys(raw).length === 0 ? void 0 : raw, readOrigin(ctx));
|
|
93432
93432
|
} };
|
|
93433
93433
|
return {
|
|
93434
93434
|
name: options.serviceName ?? "$core-caps",
|
|
93435
93435
|
actions
|
|
93436
93436
|
};
|
|
93437
93437
|
}
|
|
93438
|
+
/** Structural read of the calling node/action. Anything unrecognised is `null`. */
|
|
93439
|
+
function readOrigin(ctx) {
|
|
93440
|
+
return {
|
|
93441
|
+
nodeId: asName(ctx.nodeID),
|
|
93442
|
+
caller: asName(ctx.caller)
|
|
93443
|
+
};
|
|
93444
|
+
}
|
|
93445
|
+
function asName(value) {
|
|
93446
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
93447
|
+
}
|
|
93438
93448
|
//#endregion
|
|
93439
93449
|
//#region src/kernel/moleculer/hub-log-forwarder.ts
|
|
93440
93450
|
/**
|
|
@@ -94332,10 +94342,20 @@ function isHeavyRunner(addons) {
|
|
|
94332
94342
|
* one-off burst becomes the runner's permanent RSS.
|
|
94333
94343
|
*
|
|
94334
94344
|
* Measured on the live hub, 2026-08-17: the `recorder` runner reaches **2122 MB RSS** and stays
|
|
94335
|
-
* there
|
|
94336
|
-
*
|
|
94337
|
-
*
|
|
94338
|
-
*
|
|
94345
|
+
* there. The same addon on two agent nodes carrying the same code and a smaller archive sits at
|
|
94346
|
+
* **58 MB**.
|
|
94347
|
+
*
|
|
94348
|
+
* ⚠ This paragraph used to attribute that ~2 GB to "walk garbage" from the deferred archive walk,
|
|
94349
|
+
* and to argue from a ~21 MB `RecordingIndex`. Both were wrong, and the error cost a session on
|
|
94350
|
+
* 2026-08-27. The deferred walk had NO CALLER and never ran (it has since been deleted). And the
|
|
94351
|
+
* index is not 21 MB: a 2026-08-27 profile of the live recorder read `heapUsed = 1606 MB` against
|
|
94352
|
+
* `external = 8 MB`, with an `smaps` census putting 1510 MB in V8's 256 KB page ranges against
|
|
94353
|
+
* 54 MB of native heap — LIVE JavaScript retainers, not high-water garbage. The retained set is
|
|
94354
|
+
* ~7.1 M `SegmentRow`s plus their path strings and Map entries (13 cameras x 2 profiles x 8 640
|
|
94355
|
+
* segments/day x ~30 days), reseeded at boot by `SegmentHourLedger.hydrateIndex`.
|
|
94356
|
+
*
|
|
94357
|
+
* The lesson for anyone sizing this flag: a heap number needs a RETAINER, and "garbage the GC has
|
|
94358
|
+
* no reason to collect" is the explanation to reach for last, not first.
|
|
94339
94359
|
*
|
|
94340
94360
|
* ## Why 1024
|
|
94341
94361
|
*
|
|
@@ -94352,8 +94372,7 @@ function isHeavyRunner(addons) {
|
|
|
94352
94372
|
*
|
|
94353
94373
|
* A runner whose working set genuinely does not fit declares its own number in its manifest
|
|
94354
94374
|
* (`execution.maxOldSpaceMb`) — the number belongs next to the addon that knows its working set.
|
|
94355
|
-
* `recorder` (3072 MB; peak 2341 MB heapTotal
|
|
94356
|
-
* shipped addon that does. `stream-broker` does NOT, despite 1585 MB peak RSS: ~1 GB of that is
|
|
94375
|
+
* `recorder` (3072 MB; peak 2341 MB heapTotal) is the one shipped addon that does. `stream-broker` does NOT, despite 1585 MB peak RSS: ~1 GB of that is
|
|
94357
94376
|
* `arrayBuffers` (47 RTP rings), which this flag does not bound and never will — its actual old
|
|
94358
94377
|
* space peaks at 224 MB. Size the declaration from `heapTotal` in the `[mem]` heartbeat, never
|
|
94359
94378
|
* from RSS.
|
package/dist/kernel/index.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export type { BrokerConfig } from './moleculer/broker-factory.js';
|
|
|
37
37
|
export { type AddonDeploySource, isAddonDeploySource } from './moleculer/addon-deploy-source.js';
|
|
38
38
|
export { createAddonService, validateProviderRegistrations, } from './moleculer/addon-service-factory.js';
|
|
39
39
|
export { createCoreCapService, CORE_CAP_SERVICE_NAME } from './moleculer/core-cap-service.js';
|
|
40
|
-
export type { CoreCapAction, CoreCapServiceOptions } from './moleculer/core-cap-service.js';
|
|
40
|
+
export type { CoreCapAction, CoreCapCallOrigin, CoreCapServiceOptions, } from './moleculer/core-cap-service.js';
|
|
41
41
|
export { createAddonContext, createUdsAddonContext, setHubConnected, adaptBrokerToCluster, registerEventBusService, setNodeEventInterest, getMoleculerEventStats, subscribePassthrough, getBrokerEventBus, clusterEventTopic, EVENT_TOPIC_PREFIX, } from './moleculer/addon-context-factory.js';
|
|
42
42
|
export type { MoleculerEventStats } from './moleculer/addon-context-factory.js';
|
|
43
43
|
export { HubLogForwarder } from './moleculer/hub-log-forwarder.js';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { IScopedLogger } from '@camstack/types';
|
|
2
|
+
/**
|
|
3
|
+
* The child fields the accessory reconcile reads. Deliberately narrower than
|
|
4
|
+
* `DeviceInfo`: the loader is a transport, and a transport that pins the whole
|
|
5
|
+
* projection would have to change every time an unrelated field does.
|
|
6
|
+
*/
|
|
7
|
+
export interface ReconcileChildRow {
|
|
8
|
+
readonly id: number;
|
|
9
|
+
readonly stableId: string;
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly linkDeviceId?: number | null;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Narrow an untyped cap answer to the fields the reconcile reads.
|
|
15
|
+
*
|
|
16
|
+
* `callDeviceManager` is an untyped boundary, and the answer may come from a
|
|
17
|
+
* hub of a different vintage. A row that does not carry an id, a stableId and
|
|
18
|
+
* a name is not a child this code can prune ON, so it is dropped rather than
|
|
19
|
+
* cast into shape — the prune step DELETES, and a half-parsed row is the input
|
|
20
|
+
* that would make it delete the wrong thing.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseChildRows(value: unknown): readonly ReconcileChildRow[] | null;
|
|
23
|
+
/**
|
|
24
|
+
* Narrow a `getChildrenBatch` answer: a record keyed by parent id as a string.
|
|
25
|
+
*
|
|
26
|
+
* An EMPTY record is a valid answer — "none of these parents has children" —
|
|
27
|
+
* and so is what a hub that answers the call but knows none of the parents
|
|
28
|
+
* would send. Both mean "nothing to prune", which is the safe direction. Only
|
|
29
|
+
* a shape that is not a record at all is `null`, and the loader treats that as
|
|
30
|
+
* a failed batch and re-asks per parent.
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseChildrenByParent(value: unknown): ReadonlyMap<number, readonly ReconcileChildRow[]> | null;
|
|
33
|
+
export interface ChildrenBatchLoaderOptions {
|
|
34
|
+
/** One `device-manager.getChildrenBatch` call. Rejects like any RPC. */
|
|
35
|
+
readonly fetchBatch: (parentDeviceIds: readonly number[]) => Promise<ReadonlyMap<number, readonly ReconcileChildRow[]>>;
|
|
36
|
+
/** One `device-manager.getChildren` call — the fallback, and the old path. */
|
|
37
|
+
readonly fetchOne: (parentDeviceId: number) => Promise<readonly ReconcileChildRow[]>;
|
|
38
|
+
readonly logger: IScopedLogger;
|
|
39
|
+
/** Test seam. Production takes {@link DEVICE_CHILDREN_BATCH_MAX}. */
|
|
40
|
+
readonly maxBatch?: number;
|
|
41
|
+
}
|
|
42
|
+
export interface ChildrenBatchLoader {
|
|
43
|
+
/** The direct children of one parent, coalesced with every other in-flight ask. */
|
|
44
|
+
load(parentDeviceId: number): Promise<readonly ReconcileChildRow[]>;
|
|
45
|
+
}
|
|
46
|
+
export declare function createChildrenBatchLoader(opts: ChildrenBatchLoaderOptions): ChildrenBatchLoader;
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { ServiceSchema } from 'moleculer';
|
|
2
|
+
/**
|
|
3
|
+
* Who issued a `$core-caps` action. Both fields are `null` when Moleculer did
|
|
4
|
+
* not carry the information — never blank, so an absent origin reads as absent
|
|
5
|
+
* rather than as a formatting defect.
|
|
6
|
+
*/
|
|
7
|
+
export interface CoreCapCallOrigin {
|
|
8
|
+
readonly nodeId: string | null;
|
|
9
|
+
readonly caller: string | null;
|
|
10
|
+
}
|
|
2
11
|
/**
|
|
3
12
|
* Default Moleculer service name for the hub core-capability bridge.
|
|
4
13
|
*
|
|
@@ -22,8 +31,14 @@ export declare const CORE_CAP_SERVICE_NAME = "$core-caps";
|
|
|
22
31
|
export interface CoreCapAction {
|
|
23
32
|
/** Kebab-cased `<capName>.<method>` — e.g. `system.info`, `addon-settings-raw.getGlobal`. */
|
|
24
33
|
readonly actionName: string;
|
|
25
|
-
/**
|
|
26
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Invoke the underlying tRPC procedure with a trusted mesh context.
|
|
36
|
+
*
|
|
37
|
+
* `origin` is diagnostics only — the transport census uses it to name the
|
|
38
|
+
* caller of an in-process call, which has no request and therefore no other
|
|
39
|
+
* identity. No implementation may gate on it.
|
|
40
|
+
*/
|
|
41
|
+
readonly invoke: (input: unknown, origin: CoreCapCallOrigin) => Promise<unknown> | unknown;
|
|
27
42
|
}
|
|
28
43
|
export interface CoreCapServiceOptions {
|
|
29
44
|
/** Moleculer service name. Defaults to {@link CORE_CAP_SERVICE_NAME}. */
|
|
@@ -16,10 +16,20 @@ interface RunnerAddonSpec {
|
|
|
16
16
|
* one-off burst becomes the runner's permanent RSS.
|
|
17
17
|
*
|
|
18
18
|
* Measured on the live hub, 2026-08-17: the `recorder` runner reaches **2122 MB RSS** and stays
|
|
19
|
-
* there
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* there. The same addon on two agent nodes carrying the same code and a smaller archive sits at
|
|
20
|
+
* **58 MB**.
|
|
21
|
+
*
|
|
22
|
+
* ⚠ This paragraph used to attribute that ~2 GB to "walk garbage" from the deferred archive walk,
|
|
23
|
+
* and to argue from a ~21 MB `RecordingIndex`. Both were wrong, and the error cost a session on
|
|
24
|
+
* 2026-08-27. The deferred walk had NO CALLER and never ran (it has since been deleted). And the
|
|
25
|
+
* index is not 21 MB: a 2026-08-27 profile of the live recorder read `heapUsed = 1606 MB` against
|
|
26
|
+
* `external = 8 MB`, with an `smaps` census putting 1510 MB in V8's 256 KB page ranges against
|
|
27
|
+
* 54 MB of native heap — LIVE JavaScript retainers, not high-water garbage. The retained set is
|
|
28
|
+
* ~7.1 M `SegmentRow`s plus their path strings and Map entries (13 cameras x 2 profiles x 8 640
|
|
29
|
+
* segments/day x ~30 days), reseeded at boot by `SegmentHourLedger.hydrateIndex`.
|
|
30
|
+
*
|
|
31
|
+
* The lesson for anyone sizing this flag: a heap number needs a RETAINER, and "garbage the GC has
|
|
32
|
+
* no reason to collect" is the explanation to reach for last, not first.
|
|
23
33
|
*
|
|
24
34
|
* ## Why 1024
|
|
25
35
|
*
|
|
@@ -36,8 +46,7 @@ interface RunnerAddonSpec {
|
|
|
36
46
|
*
|
|
37
47
|
* A runner whose working set genuinely does not fit declares its own number in its manifest
|
|
38
48
|
* (`execution.maxOldSpaceMb`) — the number belongs next to the addon that knows its working set.
|
|
39
|
-
* `recorder` (3072 MB; peak 2341 MB heapTotal
|
|
40
|
-
* shipped addon that does. `stream-broker` does NOT, despite 1585 MB peak RSS: ~1 GB of that is
|
|
49
|
+
* `recorder` (3072 MB; peak 2341 MB heapTotal) is the one shipped addon that does. `stream-broker` does NOT, despite 1585 MB peak RSS: ~1 GB of that is
|
|
41
50
|
* `arrayBuffers` (47 RTP rings), which this flag does not bound and never will — its actual old
|
|
42
51
|
* space peaks at 224 MB. Size the declaration from `heapTotal` in the `[mem]` heartbeat, never
|
|
43
52
|
* from RSS.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "./dist-CG7kwIZp.mjs";
|
|
1
2
|
import * as crypto$1 from "node:crypto";
|
|
2
3
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
3
4
|
import { ensureBinary, ensureFfmpeg, ensurePython, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
|
|
@@ -1649,6 +1650,182 @@ async function readModifyWrite(input) {
|
|
|
1649
1650
|
return true;
|
|
1650
1651
|
}
|
|
1651
1652
|
//#endregion
|
|
1653
|
+
//#region src/kernel/moleculer/children-batch-loader.ts
|
|
1654
|
+
/**
|
|
1655
|
+
* "Who are my children?" asked by a thousand devices, answered in a handful of
|
|
1656
|
+
* calls.
|
|
1657
|
+
*
|
|
1658
|
+
* ## The number
|
|
1659
|
+
*
|
|
1660
|
+
* Every `BaseDevice` inherits a `getAccessoryChildren()` that returns `[]`, so
|
|
1661
|
+
* the accessory reconcile in `device-cap-proxy.ts` runs its orphan-prune step
|
|
1662
|
+
* for EVERY registered device — including the leaf accessories that can never
|
|
1663
|
+
* own one. Each pass asked `device-manager.getChildren` for its own id.
|
|
1664
|
+
* Measured on the live hub 2026-08-27, 120-second boot window, 1 017 devices:
|
|
1665
|
+
* **1 024 calls returning 919 rows in total**. A thousand round-trips, and a
|
|
1666
|
+
* thousand indexed scans behind them, to move less than one row each.
|
|
1667
|
+
*
|
|
1668
|
+
* ## Why a loader and not a caller that loops
|
|
1669
|
+
*
|
|
1670
|
+
* There is no loop to unroll. Reconcile is per-device by construction: it runs
|
|
1671
|
+
* inside `register()`, and again whenever a late feature-probe writes the
|
|
1672
|
+
* slice. The repetition is a THOUSAND INDEPENDENT CALLERS, not one caller
|
|
1673
|
+
* asking a thousand times — so the collapse has to happen between them.
|
|
1674
|
+
*
|
|
1675
|
+
* ## Why the batch is self-clocking, with no window to tune
|
|
1676
|
+
*
|
|
1677
|
+
* At most ONE batch call is in flight. Anything that arrives while it is in
|
|
1678
|
+
* flight lands in the next batch, dispatched the moment the current one
|
|
1679
|
+
* settles. That makes the batch size adapt to the arrival rate on its own:
|
|
1680
|
+
*
|
|
1681
|
+
* - Idle: the first caller flushes on the next macrotask. No added wait, and
|
|
1682
|
+
* no timer to pick a value for and be wrong about.
|
|
1683
|
+
* - Boot: `BaseDeviceProvider.onRestoreDevices` issues every top-level
|
|
1684
|
+
* `create()` at once and `runBounded(specs, 12)` spawns accessory children
|
|
1685
|
+
* twelve wide, so reconciles arrive in bursts — measured as 2 372 device
|
|
1686
|
+
* log lines in one 5-second bucket at boot+16s, against 62 at boot+142s.
|
|
1687
|
+
* Every arrival inside one round-trip collapses into the next call.
|
|
1688
|
+
* - Worst case: arrivals slower than a round-trip give batches of one, which
|
|
1689
|
+
* is exactly today's cost. The loader can never be worse than what it
|
|
1690
|
+
* replaces.
|
|
1691
|
+
*
|
|
1692
|
+
* A fixed debounce window would have to be guessed against that distribution,
|
|
1693
|
+
* and would tax the idle case to pay for the busy one.
|
|
1694
|
+
*
|
|
1695
|
+
* ## Degrading, rather than breaking, against an older hub
|
|
1696
|
+
*
|
|
1697
|
+
* `getChildrenBatch` is a NEW method: a hub that predates it rejects the call
|
|
1698
|
+
* (it does not silently strip the input and answer a different question, which
|
|
1699
|
+
* is what adding a `parentDeviceIds` FIELD to `getChildren` would have done).
|
|
1700
|
+
* Every waiter in a failed batch is then answered by the per-parent
|
|
1701
|
+
* `getChildren` it would have used anyway, so the reconcile is never denied
|
|
1702
|
+
* its answer. A rejection that names the method as unknown latches the
|
|
1703
|
+
* fallback permanently — re-probing a method the hub will never have, once per
|
|
1704
|
+
* batch for a whole boot, is the storm this exists to remove.
|
|
1705
|
+
*/
|
|
1706
|
+
/**
|
|
1707
|
+
* Narrow an untyped cap answer to the fields the reconcile reads.
|
|
1708
|
+
*
|
|
1709
|
+
* `callDeviceManager` is an untyped boundary, and the answer may come from a
|
|
1710
|
+
* hub of a different vintage. A row that does not carry an id, a stableId and
|
|
1711
|
+
* a name is not a child this code can prune ON, so it is dropped rather than
|
|
1712
|
+
* cast into shape — the prune step DELETES, and a half-parsed row is the input
|
|
1713
|
+
* that would make it delete the wrong thing.
|
|
1714
|
+
*/
|
|
1715
|
+
function parseChildRows(value) {
|
|
1716
|
+
if (!Array.isArray(value)) return null;
|
|
1717
|
+
const rows = [];
|
|
1718
|
+
for (const entry of value) {
|
|
1719
|
+
if (typeof entry !== "object" || entry === null) return null;
|
|
1720
|
+
const id = Reflect.get(entry, "id");
|
|
1721
|
+
const stableId = Reflect.get(entry, "stableId");
|
|
1722
|
+
const name = Reflect.get(entry, "name");
|
|
1723
|
+
const linkDeviceId = Reflect.get(entry, "linkDeviceId");
|
|
1724
|
+
if (typeof id !== "number" || typeof stableId !== "string" || typeof name !== "string") return null;
|
|
1725
|
+
rows.push({
|
|
1726
|
+
id,
|
|
1727
|
+
stableId,
|
|
1728
|
+
name,
|
|
1729
|
+
...typeof linkDeviceId === "number" || linkDeviceId === null ? { linkDeviceId } : {}
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
return rows;
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Narrow a `getChildrenBatch` answer: a record keyed by parent id as a string.
|
|
1736
|
+
*
|
|
1737
|
+
* An EMPTY record is a valid answer — "none of these parents has children" —
|
|
1738
|
+
* and so is what a hub that answers the call but knows none of the parents
|
|
1739
|
+
* would send. Both mean "nothing to prune", which is the safe direction. Only
|
|
1740
|
+
* a shape that is not a record at all is `null`, and the loader treats that as
|
|
1741
|
+
* a failed batch and re-asks per parent.
|
|
1742
|
+
*/
|
|
1743
|
+
function parseChildrenByParent(value) {
|
|
1744
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
1745
|
+
const out = /* @__PURE__ */ new Map();
|
|
1746
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1747
|
+
const parentDeviceId = Number(key);
|
|
1748
|
+
if (!Number.isInteger(parentDeviceId)) return null;
|
|
1749
|
+
const rows = parseChildRows(entry);
|
|
1750
|
+
if (rows === null) return null;
|
|
1751
|
+
out.set(parentDeviceId, rows);
|
|
1752
|
+
}
|
|
1753
|
+
return out;
|
|
1754
|
+
}
|
|
1755
|
+
/**
|
|
1756
|
+
* True when an error says the hub has no such procedure — the version-skew
|
|
1757
|
+
* case, and the only one worth latching on. Anything else (a timeout, a busy
|
|
1758
|
+
* broker) is transient and must not disable batching for the process lifetime.
|
|
1759
|
+
*/
|
|
1760
|
+
function looksLikeUnknownMethod(err) {
|
|
1761
|
+
const lowered = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
1762
|
+
return lowered.includes("not_found") || lowered.includes("no procedure") || lowered.includes("unknown method") || lowered.includes("no such method") || lowered.includes("not available");
|
|
1763
|
+
}
|
|
1764
|
+
function createChildrenBatchLoader(opts) {
|
|
1765
|
+
const maxBatch = Math.max(1, opts.maxBatch ?? 256);
|
|
1766
|
+
let pending = [];
|
|
1767
|
+
let flushScheduled = false;
|
|
1768
|
+
let flushing = false;
|
|
1769
|
+
let batchUnsupported = false;
|
|
1770
|
+
const answerOneByOne = async (waiters) => {
|
|
1771
|
+
await Promise.all(waiters.map(async (waiter) => {
|
|
1772
|
+
try {
|
|
1773
|
+
waiter.resolve(await opts.fetchOne(waiter.parentDeviceId));
|
|
1774
|
+
} catch (err) {
|
|
1775
|
+
waiter.reject(err);
|
|
1776
|
+
}
|
|
1777
|
+
}));
|
|
1778
|
+
};
|
|
1779
|
+
const runBatch = async (waiters) => {
|
|
1780
|
+
const parentDeviceIds = [...new Set(waiters.map((w) => w.parentDeviceId))];
|
|
1781
|
+
try {
|
|
1782
|
+
const byParent = await opts.fetchBatch(parentDeviceIds);
|
|
1783
|
+
for (const waiter of waiters) waiter.resolve(byParent.get(waiter.parentDeviceId) ?? []);
|
|
1784
|
+
} catch (err) {
|
|
1785
|
+
const unknown = looksLikeUnknownMethod(err);
|
|
1786
|
+
if (unknown) batchUnsupported = true;
|
|
1787
|
+
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: {
|
|
1788
|
+
parents: parentDeviceIds.length,
|
|
1789
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1790
|
+
} });
|
|
1791
|
+
await answerOneByOne(waiters);
|
|
1792
|
+
}
|
|
1793
|
+
};
|
|
1794
|
+
const flush = async () => {
|
|
1795
|
+
flushing = true;
|
|
1796
|
+
try {
|
|
1797
|
+
while (pending.length > 0) {
|
|
1798
|
+
const batch = pending.slice(0, maxBatch);
|
|
1799
|
+
pending = pending.slice(maxBatch);
|
|
1800
|
+
if (batchUnsupported) await answerOneByOne(batch);
|
|
1801
|
+
else await runBatch(batch);
|
|
1802
|
+
}
|
|
1803
|
+
} finally {
|
|
1804
|
+
flushing = false;
|
|
1805
|
+
}
|
|
1806
|
+
};
|
|
1807
|
+
const scheduleFlush = () => {
|
|
1808
|
+
if (flushScheduled || flushing) return;
|
|
1809
|
+
flushScheduled = true;
|
|
1810
|
+
setTimeout(() => {
|
|
1811
|
+
flushScheduled = false;
|
|
1812
|
+
flush().catch((err) => {
|
|
1813
|
+
opts.logger.warn("children batch flush threw", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
1814
|
+
});
|
|
1815
|
+
}, 0);
|
|
1816
|
+
};
|
|
1817
|
+
return { load(parentDeviceId) {
|
|
1818
|
+
return new Promise((resolve, reject) => {
|
|
1819
|
+
pending.push({
|
|
1820
|
+
parentDeviceId,
|
|
1821
|
+
resolve,
|
|
1822
|
+
reject
|
|
1823
|
+
});
|
|
1824
|
+
scheduleFlush();
|
|
1825
|
+
});
|
|
1826
|
+
} };
|
|
1827
|
+
}
|
|
1828
|
+
//#endregion
|
|
1652
1829
|
//#region src/kernel/moleculer/device-cap-proxy.ts
|
|
1653
1830
|
/**
|
|
1654
1831
|
* Max number of accessory children spawned concurrently during a single
|
|
@@ -1939,6 +2116,19 @@ function createBrokerDeviceManagerApi(opts) {
|
|
|
1939
2116
|
api
|
|
1940
2117
|
});
|
|
1941
2118
|
let selfApi;
|
|
2119
|
+
const childrenLoader = createChildrenBatchLoader({
|
|
2120
|
+
logger: opts.logger,
|
|
2121
|
+
fetchBatch: async (parentDeviceIds) => {
|
|
2122
|
+
const parsed = parseChildrenByParent(await callDeviceManager(api, "getChildrenBatch", { parentDeviceIds }));
|
|
2123
|
+
if (parsed === null) throw new Error("getChildrenBatch returned a shape this kernel cannot read");
|
|
2124
|
+
return parsed;
|
|
2125
|
+
},
|
|
2126
|
+
fetchOne: async (parentDeviceId) => {
|
|
2127
|
+
const parsed = parseChildRows(await callDeviceManager(api, "getChildren", { parentDeviceId }));
|
|
2128
|
+
if (parsed === null) throw new Error("getChildren returned a shape this kernel cannot read");
|
|
2129
|
+
return parsed;
|
|
2130
|
+
}
|
|
2131
|
+
});
|
|
1942
2132
|
const deviceRebuildFactories = /* @__PURE__ */ new Map();
|
|
1943
2133
|
const buildContext = (stableId, id, parentDeviceId = null, initialRuntimeState = {}, persistedConfig = {}, deviceMeta = null) => {
|
|
1944
2134
|
let runtimeStateRef = null;
|
|
@@ -2356,7 +2546,7 @@ function createBrokerDeviceManagerApi(opts) {
|
|
|
2356
2546
|
return;
|
|
2357
2547
|
}
|
|
2358
2548
|
try {
|
|
2359
|
-
const currentChildren = await
|
|
2549
|
+
const currentChildren = await childrenLoader.load(device.id);
|
|
2360
2550
|
for (const child of currentChildren) {
|
|
2361
2551
|
if (child.linkDeviceId !== device.id) continue;
|
|
2362
2552
|
if (expected.has(child.stableId)) continue;
|