@camstack/system 1.2.123 → 1.2.125
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 +224 -224
- package/dist/addon-runner.mjs +224 -224
- 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 +20 -0
- package/dist/builtins/device-manager/device-linked-devices.d.ts +16 -0
- package/dist/builtins/device-manager/device-manager.addon.d.ts +6 -0
- package/dist/builtins/device-manager/device-manager.addon.js +73 -7
- package/dist/builtins/device-manager/device-manager.addon.mjs +73 -7
- 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 +2 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
- 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-DnhGRFEn.js → dist-CIDgcxPk.js} +97 -21
- package/dist/{dist-CEhrHp3u.mjs → dist-CvJrtLkw.mjs} +97 -21
- package/dist/index.js +43 -4
- package/dist/index.mjs +43 -4
- package/dist/kernel/heap-watch.d.ts +12 -9
- package/dist/kernel/moleculer/process-service.d.ts +12 -1
- package/dist/kernel/moleculer/runner-native-allocator.d.ts +95 -0
- package/dist/{manifest-python-deps-B3_4YiDK.js → manifest-python-deps-BTkFwAk_.js} +15 -12
- package/dist/{manifest-python-deps-DIIXlR1x.mjs → manifest-python-deps-Dmcnb287.mjs} +15 -12
- package/dist/{retired-settings-keys-VAyOhu_a.mjs → retired-settings-keys-B1aZcoS0.mjs} +1 -1
- package/dist/{retired-settings-keys-D6Jy_SsO.js → retired-settings-keys-D7c0n9jO.js} +1 -1
- package/package.json +1 -1
package/dist/addon-runner.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { I as createUdsLoggerWithControl, L as LocalChildClient, Pt as startRunnerHeapWatch, at as setWorkerNativeCapsChangeListener, bt as installManifestNativeDeps, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps, xt as resolveAddonClass } from "./manifest-python-deps-
|
|
1
|
+
import { I as createUdsLoggerWithControl, L as LocalChildClient, Pt as startRunnerHeapWatch, at as setWorkerNativeCapsChangeListener, bt as installManifestNativeDeps, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps, xt as resolveAddonClass } from "./manifest-python-deps-Dmcnb287.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";
|
|
@@ -6,229 +6,6 @@ import * as fs from "node:fs";
|
|
|
6
6
|
import * as v8 from "node:v8";
|
|
7
7
|
import { ReadinessRegistry, ReadinessTimeoutError, errMsg, isCollectionArrayMethodName, normalizeAddonInitResult } from "@camstack/types/addon";
|
|
8
8
|
import { pathToFileURL } from "node:url";
|
|
9
|
-
//#region src/kernel/moleculer/worker-device-restore.ts
|
|
10
|
-
/**
|
|
11
|
-
* Worker-side device restore helper.
|
|
12
|
-
*
|
|
13
|
-
* Shared by `process-runner` (single-addon subprocess) and
|
|
14
|
-
* `group-runner` (group subprocess). Calls the worker addon's
|
|
15
|
-
* `restoreDevices(SavedDevice[])` with devices read from the hub's
|
|
16
|
-
* `device-manager` capability. Blocks on the `system.ready-state`
|
|
17
|
-
* readiness of the hub's `device-manager` via
|
|
18
|
-
* `ReadinessRegistry.awaitReady`, then issues the calls ONCE.
|
|
19
|
-
*
|
|
20
|
-
* Lives in its own module so it can be imported without triggering
|
|
21
|
-
* the runner entry-point side effects (each runner is also an
|
|
22
|
-
* executable that calls `main()` at import time).
|
|
23
|
-
*/
|
|
24
|
-
async function runWorkerDeviceRestoreWithRetry(addon, context, addonId, sourceNodeId) {
|
|
25
|
-
const log = context.logger;
|
|
26
|
-
log?.debug?.(`[worker-restore] entry: addon="${addonId}" sourceNodeId="${sourceNodeId}"`);
|
|
27
|
-
const restoreFn = addon.restoreDevices;
|
|
28
|
-
if (typeof restoreFn !== "function") {
|
|
29
|
-
log?.warn?.(`[worker-restore] "${addonId}": no restoreDevices function — skipping`);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const api = context.api;
|
|
33
|
-
if (!api) {
|
|
34
|
-
log?.warn?.(`[worker-restore] "${addonId}": context.api missing — skipping`);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const bus = context.eventBus;
|
|
38
|
-
if (!bus) {
|
|
39
|
-
log?.warn?.(`[worker-restore] "${addonId}": context.eventBus missing — skipping`);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
const shared = context.kernel?.readinessRegistry ?? null;
|
|
43
|
-
const registry = shared ?? new ReadinessRegistry({
|
|
44
|
-
eventBus: bus,
|
|
45
|
-
sourceNodeId,
|
|
46
|
-
logger: context.logger
|
|
47
|
-
});
|
|
48
|
-
log?.debug?.(`[worker-restore] "${addonId}": awaiting device-manager readiness on hub...`);
|
|
49
|
-
try {
|
|
50
|
-
await registry.awaitReady("device-manager", {
|
|
51
|
-
type: "node",
|
|
52
|
-
nodeId: "hub"
|
|
53
|
-
}, { timeoutMs: 6e4 });
|
|
54
|
-
log?.debug?.(`[worker-restore] "${addonId}": device-manager READY`);
|
|
55
|
-
} catch (err) {
|
|
56
|
-
if (err instanceof ReadinessTimeoutError) {
|
|
57
|
-
context.logger?.warn?.(`[worker-restore] device-manager not ready within ${err.waitedMs}ms — skipping for "${addonId}"`);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
throw err;
|
|
61
|
-
} finally {
|
|
62
|
-
if (!shared && registry instanceof ReadinessRegistry) registry.close();
|
|
63
|
-
}
|
|
64
|
-
try {
|
|
65
|
-
const deviceManager = Reflect.get(api, "deviceManager");
|
|
66
|
-
log?.debug?.(`[worker-restore] "${addonId}": calling listPersistedByAddon...`);
|
|
67
|
-
const rowsResult = await deviceManager.listPersistedByAddon.query({ addonId });
|
|
68
|
-
log?.debug?.(`[worker-restore] "${addonId}": got ${Array.isArray(rowsResult) ? rowsResult.length : 0} row(s)`);
|
|
69
|
-
const rows = Array.isArray(rowsResult) ? rowsResult : [];
|
|
70
|
-
if (rows.length === 0) {
|
|
71
|
-
context.logger?.info?.(`[worker-restore] no persisted devices to restore for addon "${addonId}"`);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
const savedDevices = await Promise.all(rows.map(async (row) => {
|
|
75
|
-
const config = await deviceManager.loadConfig.query({ deviceId: row.id });
|
|
76
|
-
return {
|
|
77
|
-
id: row.id,
|
|
78
|
-
stableId: row.stableId,
|
|
79
|
-
type: row.type,
|
|
80
|
-
name: row.name,
|
|
81
|
-
parentDeviceId: row.parentDeviceId,
|
|
82
|
-
config: config ?? {}
|
|
83
|
-
};
|
|
84
|
-
}));
|
|
85
|
-
await restoreFn.call(addon, savedDevices);
|
|
86
|
-
log?.info?.(`[worker-restore] "${addonId}": restored ${savedDevices.length} device(s)`);
|
|
87
|
-
} catch (err) {
|
|
88
|
-
log?.warn?.(`[worker-restore] "${addonId}": restoreDevices threw: ${errMsg(err)}`);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
//#endregion
|
|
92
|
-
//#region src/kernel/moleculer/register-framework-resolver.ts
|
|
93
|
-
/**
|
|
94
|
-
* Register the ESM resolver hook so this runner's addon imports of the
|
|
95
|
-
* host-provided packages (@camstack/system, @camstack/shm-ring, …) resolve from
|
|
96
|
-
* `frameworkDir/node_modules` instead of failing to walk up from the addon's
|
|
97
|
-
* isolated `/data/addons/<addon>` folder. No-op in dev (frameworkDir unset),
|
|
98
|
-
* where workspace symlinks already resolve the framework.
|
|
99
|
-
*/
|
|
100
|
-
function registerFrameworkResolver(frameworkDir) {
|
|
101
|
-
if (!frameworkDir) return;
|
|
102
|
-
register(pathToFileURL(path$1.join(__dirname, "framework-resolver-hook.mjs")), {
|
|
103
|
-
parentURL: pathToFileURL(`${__dirname}/`).href,
|
|
104
|
-
data: { frameworkDir }
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Start warning if `initialize()` has not returned within `firstMs`.
|
|
109
|
-
*
|
|
110
|
-
* The caller MUST `clear()` in a `finally`, so a throwing initialize (which the
|
|
111
|
-
* runner turns into `process.exit(1)`) does not keep the timer alive.
|
|
112
|
-
*/
|
|
113
|
-
function startInitWatchdog(options) {
|
|
114
|
-
const now = options.now ?? Date.now;
|
|
115
|
-
const firstMs = options.firstMs ?? 6e4;
|
|
116
|
-
const repeatMs = options.repeatMs ?? 3e5;
|
|
117
|
-
const startedAt = now();
|
|
118
|
-
let timer;
|
|
119
|
-
let cleared = false;
|
|
120
|
-
const schedule = (ms) => {
|
|
121
|
-
timer = setTimeout(fire, ms);
|
|
122
|
-
timer.unref?.();
|
|
123
|
-
};
|
|
124
|
-
function fire() {
|
|
125
|
-
if (cleared) return;
|
|
126
|
-
const elapsedMs = now() - startedAt;
|
|
127
|
-
options.warn(`addon "${options.addonId}" initialize() has not returned after ${Math.round(elapsedMs / 1e3)}s — its capability manifest is NOT published, so every caller of ${options.declaredCapabilities.length > 0 ? options.declaredCapabilities.join(", ") : "(none declared)"} sees "no provider registered"`, {
|
|
128
|
-
addonId: options.addonId,
|
|
129
|
-
elapsedMs,
|
|
130
|
-
unpublishedCapabilities: [...options.declaredCapabilities]
|
|
131
|
-
});
|
|
132
|
-
schedule(repeatMs);
|
|
133
|
-
}
|
|
134
|
-
schedule(firstMs);
|
|
135
|
-
return { clear() {
|
|
136
|
-
cleared = true;
|
|
137
|
-
if (timer !== void 0) {
|
|
138
|
-
clearTimeout(timer);
|
|
139
|
-
timer = void 0;
|
|
140
|
-
}
|
|
141
|
-
} };
|
|
142
|
-
}
|
|
143
|
-
//#endregion
|
|
144
|
-
//#region src/kernel/moleculer/child-cap-dispatch.ts
|
|
145
|
-
/** Type guard narrowing a dynamically-read provider member to a callable method. */
|
|
146
|
-
function isCapMethod(value) {
|
|
147
|
-
return typeof value === "function";
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Extract a string `addonId` out of arbitrary method args — the fallback
|
|
151
|
-
* provider-selection hint for calls that arrive WITHOUT the out-of-band
|
|
152
|
-
* `CapCallMessage.addonId` (e.g. a sibling child's unowned collection call
|
|
153
|
-
* forwarded by the parent, where the raw input still carries `addonId`).
|
|
154
|
-
*/
|
|
155
|
-
function extractArgsAddonId(args) {
|
|
156
|
-
if (args === null || typeof args !== "object") return void 0;
|
|
157
|
-
const raw = Reflect.get(args, "addonId");
|
|
158
|
-
return typeof raw === "string" ? raw : void 0;
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Creates a unified dispatcher that routes a {@link CapCallInput} to either a
|
|
162
|
-
* device-scoped or singleton provider, then invokes the requested method.
|
|
163
|
-
*
|
|
164
|
-
* Pure module — no Moleculer, no broker, no addon-runner dependency.
|
|
165
|
-
*/
|
|
166
|
-
function createChildCapDispatch(deps) {
|
|
167
|
-
return async (call) => {
|
|
168
|
-
const { capName, method, args, deviceId } = call;
|
|
169
|
-
const addonId = call.addonId ?? extractArgsAddonId(args);
|
|
170
|
-
let provider;
|
|
171
|
-
if (deviceId !== void 0) {
|
|
172
|
-
provider = deps.getDeviceProvider(capName, deviceId) ?? deps.getSingletonProvider(capName, addonId);
|
|
173
|
-
if (provider == null) throw new Error(`child-cap-dispatch: no provider for '${capName}' (device '${String(deviceId)}' or singleton)`);
|
|
174
|
-
} else {
|
|
175
|
-
provider = deps.getSingletonProvider(capName, addonId);
|
|
176
|
-
if (provider == null) throw new Error(`child-cap-dispatch: no singleton provider for '${capName}'`);
|
|
177
|
-
}
|
|
178
|
-
const fn = Reflect.get(provider, method);
|
|
179
|
-
if (!isCapMethod(fn)) {
|
|
180
|
-
if (deps.isCollectionListMethod(capName, method)) return [];
|
|
181
|
-
throw new Error(`child-cap-dispatch: method '${method}' not found on provider '${capName}'`);
|
|
182
|
-
}
|
|
183
|
-
return await fn.call(provider, args);
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
//#endregion
|
|
187
|
-
//#region src/kernel/moleculer/child-cap-descriptors.ts
|
|
188
|
-
/**
|
|
189
|
-
* Builds the {@link ChildCapDescriptor} array that the UDS child transport
|
|
190
|
-
* sends to the parent on registration.
|
|
191
|
-
*
|
|
192
|
-
* - Singleton caps come from the runner UNION manifest (all caps registered by
|
|
193
|
-
* every addon in this runner). One descriptor per unique (addonId, capName)
|
|
194
|
-
* pair, mode `'singleton'` and no `deviceId`. The `addonId` stamp lets the
|
|
195
|
-
* parent's manifest builder register each hosted addon's caps under the
|
|
196
|
-
* REAL addon id — a GROUPED runner (`execution.group`) hosts several addons,
|
|
197
|
-
* and deduplicating by capName alone would collapse two co-located providers
|
|
198
|
-
* of the same collection cap (e.g. `network-access` from cloudflare-tunnel
|
|
199
|
-
* AND tailscale-ingress) into one descriptor registered under a synthetic
|
|
200
|
-
* `addonId = childId` (the group name) — the "@camstack/addon-net-access"
|
|
201
|
-
* admin-UI bug.
|
|
202
|
-
* - Device-scoped caps come from the native-cap snapshot — one descriptor per
|
|
203
|
-
* (capName, deviceId) pair, mode `'collection'`, stamped with the owning
|
|
204
|
-
* addon's id.
|
|
205
|
-
*
|
|
206
|
-
* Pure module — extracted from `addon-runner.ts` (whose `main()` runs at
|
|
207
|
-
* import time) so it is unit-testable.
|
|
208
|
-
*/
|
|
209
|
-
function buildChildCapDescriptors(manifest, nativeCapSnapshot) {
|
|
210
|
-
const descriptors = [];
|
|
211
|
-
const seen = /* @__PURE__ */ new Set();
|
|
212
|
-
for (const entry of manifest) for (const capName of entry.capabilities) {
|
|
213
|
-
const key = `${entry.addonId}::${capName}`;
|
|
214
|
-
if (!seen.has(key)) {
|
|
215
|
-
seen.add(key);
|
|
216
|
-
descriptors.push({
|
|
217
|
-
capName,
|
|
218
|
-
mode: "singleton",
|
|
219
|
-
addonId: entry.addonId
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
for (const { capName, addonId, deviceIds } of nativeCapSnapshot) for (const deviceId of deviceIds) descriptors.push({
|
|
224
|
-
capName,
|
|
225
|
-
mode: "collection",
|
|
226
|
-
deviceId,
|
|
227
|
-
addonId
|
|
228
|
-
});
|
|
229
|
-
return descriptors;
|
|
230
|
-
}
|
|
231
|
-
//#endregion
|
|
232
9
|
//#region src/kernel/moleculer/child-addon-call-dispatch.ts
|
|
233
10
|
function isRecord$1(value) {
|
|
234
11
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -320,6 +97,229 @@ function createChildAddonCallDispatch(deps) {
|
|
|
320
97
|
};
|
|
321
98
|
}
|
|
322
99
|
//#endregion
|
|
100
|
+
//#region src/kernel/moleculer/child-cap-descriptors.ts
|
|
101
|
+
/**
|
|
102
|
+
* Builds the {@link ChildCapDescriptor} array that the UDS child transport
|
|
103
|
+
* sends to the parent on registration.
|
|
104
|
+
*
|
|
105
|
+
* - Singleton caps come from the runner UNION manifest (all caps registered by
|
|
106
|
+
* every addon in this runner). One descriptor per unique (addonId, capName)
|
|
107
|
+
* pair, mode `'singleton'` and no `deviceId`. The `addonId` stamp lets the
|
|
108
|
+
* parent's manifest builder register each hosted addon's caps under the
|
|
109
|
+
* REAL addon id — a GROUPED runner (`execution.group`) hosts several addons,
|
|
110
|
+
* and deduplicating by capName alone would collapse two co-located providers
|
|
111
|
+
* of the same collection cap (e.g. `network-access` from cloudflare-tunnel
|
|
112
|
+
* AND tailscale-ingress) into one descriptor registered under a synthetic
|
|
113
|
+
* `addonId = childId` (the group name) — the "@camstack/addon-net-access"
|
|
114
|
+
* admin-UI bug.
|
|
115
|
+
* - Device-scoped caps come from the native-cap snapshot — one descriptor per
|
|
116
|
+
* (capName, deviceId) pair, mode `'collection'`, stamped with the owning
|
|
117
|
+
* addon's id.
|
|
118
|
+
*
|
|
119
|
+
* Pure module — extracted from `addon-runner.ts` (whose `main()` runs at
|
|
120
|
+
* import time) so it is unit-testable.
|
|
121
|
+
*/
|
|
122
|
+
function buildChildCapDescriptors(manifest, nativeCapSnapshot) {
|
|
123
|
+
const descriptors = [];
|
|
124
|
+
const seen = /* @__PURE__ */ new Set();
|
|
125
|
+
for (const entry of manifest) for (const capName of entry.capabilities) {
|
|
126
|
+
const key = `${entry.addonId}::${capName}`;
|
|
127
|
+
if (!seen.has(key)) {
|
|
128
|
+
seen.add(key);
|
|
129
|
+
descriptors.push({
|
|
130
|
+
capName,
|
|
131
|
+
mode: "singleton",
|
|
132
|
+
addonId: entry.addonId
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
for (const { capName, addonId, deviceIds } of nativeCapSnapshot) for (const deviceId of deviceIds) descriptors.push({
|
|
137
|
+
capName,
|
|
138
|
+
mode: "collection",
|
|
139
|
+
deviceId,
|
|
140
|
+
addonId
|
|
141
|
+
});
|
|
142
|
+
return descriptors;
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/kernel/moleculer/child-cap-dispatch.ts
|
|
146
|
+
/** Type guard narrowing a dynamically-read provider member to a callable method. */
|
|
147
|
+
function isCapMethod(value) {
|
|
148
|
+
return typeof value === "function";
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Extract a string `addonId` out of arbitrary method args — the fallback
|
|
152
|
+
* provider-selection hint for calls that arrive WITHOUT the out-of-band
|
|
153
|
+
* `CapCallMessage.addonId` (e.g. a sibling child's unowned collection call
|
|
154
|
+
* forwarded by the parent, where the raw input still carries `addonId`).
|
|
155
|
+
*/
|
|
156
|
+
function extractArgsAddonId(args) {
|
|
157
|
+
if (args === null || typeof args !== "object") return void 0;
|
|
158
|
+
const raw = Reflect.get(args, "addonId");
|
|
159
|
+
return typeof raw === "string" ? raw : void 0;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Creates a unified dispatcher that routes a {@link CapCallInput} to either a
|
|
163
|
+
* device-scoped or singleton provider, then invokes the requested method.
|
|
164
|
+
*
|
|
165
|
+
* Pure module — no Moleculer, no broker, no addon-runner dependency.
|
|
166
|
+
*/
|
|
167
|
+
function createChildCapDispatch(deps) {
|
|
168
|
+
return async (call) => {
|
|
169
|
+
const { capName, method, args, deviceId } = call;
|
|
170
|
+
const addonId = call.addonId ?? extractArgsAddonId(args);
|
|
171
|
+
let provider;
|
|
172
|
+
if (deviceId !== void 0) {
|
|
173
|
+
provider = deps.getDeviceProvider(capName, deviceId) ?? deps.getSingletonProvider(capName, addonId);
|
|
174
|
+
if (provider == null) throw new Error(`child-cap-dispatch: no provider for '${capName}' (device '${String(deviceId)}' or singleton)`);
|
|
175
|
+
} else {
|
|
176
|
+
provider = deps.getSingletonProvider(capName, addonId);
|
|
177
|
+
if (provider == null) throw new Error(`child-cap-dispatch: no singleton provider for '${capName}'`);
|
|
178
|
+
}
|
|
179
|
+
const fn = Reflect.get(provider, method);
|
|
180
|
+
if (!isCapMethod(fn)) {
|
|
181
|
+
if (deps.isCollectionListMethod(capName, method)) return [];
|
|
182
|
+
throw new Error(`child-cap-dispatch: method '${method}' not found on provider '${capName}'`);
|
|
183
|
+
}
|
|
184
|
+
return await fn.call(provider, args);
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Start warning if `initialize()` has not returned within `firstMs`.
|
|
189
|
+
*
|
|
190
|
+
* The caller MUST `clear()` in a `finally`, so a throwing initialize (which the
|
|
191
|
+
* runner turns into `process.exit(1)`) does not keep the timer alive.
|
|
192
|
+
*/
|
|
193
|
+
function startInitWatchdog(options) {
|
|
194
|
+
const now = options.now ?? Date.now;
|
|
195
|
+
const firstMs = options.firstMs ?? 6e4;
|
|
196
|
+
const repeatMs = options.repeatMs ?? 3e5;
|
|
197
|
+
const startedAt = now();
|
|
198
|
+
let timer;
|
|
199
|
+
let cleared = false;
|
|
200
|
+
const schedule = (ms) => {
|
|
201
|
+
timer = setTimeout(fire, ms);
|
|
202
|
+
timer.unref?.();
|
|
203
|
+
};
|
|
204
|
+
function fire() {
|
|
205
|
+
if (cleared) return;
|
|
206
|
+
const elapsedMs = now() - startedAt;
|
|
207
|
+
options.warn(`addon "${options.addonId}" initialize() has not returned after ${Math.round(elapsedMs / 1e3)}s — its capability manifest is NOT published, so every caller of ${options.declaredCapabilities.length > 0 ? options.declaredCapabilities.join(", ") : "(none declared)"} sees "no provider registered"`, {
|
|
208
|
+
addonId: options.addonId,
|
|
209
|
+
elapsedMs,
|
|
210
|
+
unpublishedCapabilities: [...options.declaredCapabilities]
|
|
211
|
+
});
|
|
212
|
+
schedule(repeatMs);
|
|
213
|
+
}
|
|
214
|
+
schedule(firstMs);
|
|
215
|
+
return { clear() {
|
|
216
|
+
cleared = true;
|
|
217
|
+
if (timer !== void 0) {
|
|
218
|
+
clearTimeout(timer);
|
|
219
|
+
timer = void 0;
|
|
220
|
+
}
|
|
221
|
+
} };
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region src/kernel/moleculer/register-framework-resolver.ts
|
|
225
|
+
/**
|
|
226
|
+
* Register the ESM resolver hook so this runner's addon imports of the
|
|
227
|
+
* host-provided packages (@camstack/system, @camstack/shm-ring, …) resolve from
|
|
228
|
+
* `frameworkDir/node_modules` instead of failing to walk up from the addon's
|
|
229
|
+
* isolated `/data/addons/<addon>` folder. No-op in dev (frameworkDir unset),
|
|
230
|
+
* where workspace symlinks already resolve the framework.
|
|
231
|
+
*/
|
|
232
|
+
function registerFrameworkResolver(frameworkDir) {
|
|
233
|
+
if (!frameworkDir) return;
|
|
234
|
+
register(pathToFileURL(path$1.join(__dirname, "framework-resolver-hook.mjs")), {
|
|
235
|
+
parentURL: pathToFileURL(`${__dirname}/`).href,
|
|
236
|
+
data: { frameworkDir }
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
//#endregion
|
|
240
|
+
//#region src/kernel/moleculer/worker-device-restore.ts
|
|
241
|
+
/**
|
|
242
|
+
* Worker-side device restore helper.
|
|
243
|
+
*
|
|
244
|
+
* Shared by `process-runner` (single-addon subprocess) and
|
|
245
|
+
* `group-runner` (group subprocess). Calls the worker addon's
|
|
246
|
+
* `restoreDevices(SavedDevice[])` with devices read from the hub's
|
|
247
|
+
* `device-manager` capability. Blocks on the `system.ready-state`
|
|
248
|
+
* readiness of the hub's `device-manager` via
|
|
249
|
+
* `ReadinessRegistry.awaitReady`, then issues the calls ONCE.
|
|
250
|
+
*
|
|
251
|
+
* Lives in its own module so it can be imported without triggering
|
|
252
|
+
* the runner entry-point side effects (each runner is also an
|
|
253
|
+
* executable that calls `main()` at import time).
|
|
254
|
+
*/
|
|
255
|
+
async function runWorkerDeviceRestoreWithRetry(addon, context, addonId, sourceNodeId) {
|
|
256
|
+
const log = context.logger;
|
|
257
|
+
log?.debug?.(`[worker-restore] entry: addon="${addonId}" sourceNodeId="${sourceNodeId}"`);
|
|
258
|
+
const restoreFn = addon.restoreDevices;
|
|
259
|
+
if (typeof restoreFn !== "function") {
|
|
260
|
+
log?.warn?.(`[worker-restore] "${addonId}": no restoreDevices function — skipping`);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const api = context.api;
|
|
264
|
+
if (!api) {
|
|
265
|
+
log?.warn?.(`[worker-restore] "${addonId}": context.api missing — skipping`);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const bus = context.eventBus;
|
|
269
|
+
if (!bus) {
|
|
270
|
+
log?.warn?.(`[worker-restore] "${addonId}": context.eventBus missing — skipping`);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const shared = context.kernel?.readinessRegistry ?? null;
|
|
274
|
+
const registry = shared ?? new ReadinessRegistry({
|
|
275
|
+
eventBus: bus,
|
|
276
|
+
sourceNodeId,
|
|
277
|
+
logger: context.logger
|
|
278
|
+
});
|
|
279
|
+
log?.debug?.(`[worker-restore] "${addonId}": awaiting device-manager readiness on hub...`);
|
|
280
|
+
try {
|
|
281
|
+
await registry.awaitReady("device-manager", {
|
|
282
|
+
type: "node",
|
|
283
|
+
nodeId: "hub"
|
|
284
|
+
}, { timeoutMs: 6e4 });
|
|
285
|
+
log?.debug?.(`[worker-restore] "${addonId}": device-manager READY`);
|
|
286
|
+
} catch (err) {
|
|
287
|
+
if (err instanceof ReadinessTimeoutError) {
|
|
288
|
+
context.logger?.warn?.(`[worker-restore] device-manager not ready within ${err.waitedMs}ms — skipping for "${addonId}"`);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
throw err;
|
|
292
|
+
} finally {
|
|
293
|
+
if (!shared && registry instanceof ReadinessRegistry) registry.close();
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
const deviceManager = Reflect.get(api, "deviceManager");
|
|
297
|
+
log?.debug?.(`[worker-restore] "${addonId}": calling listPersistedByAddon...`);
|
|
298
|
+
const rowsResult = await deviceManager.listPersistedByAddon.query({ addonId });
|
|
299
|
+
log?.debug?.(`[worker-restore] "${addonId}": got ${Array.isArray(rowsResult) ? rowsResult.length : 0} row(s)`);
|
|
300
|
+
const rows = Array.isArray(rowsResult) ? rowsResult : [];
|
|
301
|
+
if (rows.length === 0) {
|
|
302
|
+
context.logger?.info?.(`[worker-restore] no persisted devices to restore for addon "${addonId}"`);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const savedDevices = await Promise.all(rows.map(async (row) => {
|
|
306
|
+
const config = await deviceManager.loadConfig.query({ deviceId: row.id });
|
|
307
|
+
return {
|
|
308
|
+
id: row.id,
|
|
309
|
+
stableId: row.stableId,
|
|
310
|
+
type: row.type,
|
|
311
|
+
name: row.name,
|
|
312
|
+
parentDeviceId: row.parentDeviceId,
|
|
313
|
+
config: config ?? {}
|
|
314
|
+
};
|
|
315
|
+
}));
|
|
316
|
+
await restoreFn.call(addon, savedDevices);
|
|
317
|
+
log?.info?.(`[worker-restore] "${addonId}": restored ${savedDevices.length} device(s)`);
|
|
318
|
+
} catch (err) {
|
|
319
|
+
log?.warn?.(`[worker-restore] "${addonId}": restoreDevices threw: ${errMsg(err)}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
//#endregion
|
|
323
323
|
//#region src/kernel/moleculer/addon-runner.ts
|
|
324
324
|
/**
|
|
325
325
|
* Addon Runner — THE unified entry point for spawned subprocesses.
|
|
@@ -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-CIDgcxPk.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-CvJrtLkw.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-CIDgcxPk.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-CvJrtLkw.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-CIDgcxPk.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-CvJrtLkw.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-CIDgcxPk.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-CvJrtLkw.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-CIDgcxPk.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-CvJrtLkw.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-CIDgcxPk.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-CvJrtLkw.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";
|
|
@@ -128,6 +128,26 @@ export declare function getBindings(deps: BindingsDeps, input: {
|
|
|
128
128
|
deviceId: number;
|
|
129
129
|
entries: DeviceBindingEntry[];
|
|
130
130
|
}>;
|
|
131
|
+
/**
|
|
132
|
+
* A NAMED set of devices' bindings, in one round-trip.
|
|
133
|
+
*
|
|
134
|
+
* Same resolver as `getBindings`, so the routing rules and the per-device
|
|
135
|
+
* shape are identical by construction. It exists because the gap between one
|
|
136
|
+
* device and all 988 had nothing in it: measured on the live hub 2026-08-25, a
|
|
137
|
+
* caller needing seventy-five paid either 75 round-trips (1211 ms / 118 KB) or
|
|
138
|
+
* `getAllBindings` (511 ms / 679 KB of mostly-unwanted rows).
|
|
139
|
+
*
|
|
140
|
+
* Sequential on purpose, like `getAllBindings`: `getBindings` is a pair of
|
|
141
|
+
* store reads on this process's own event loop, so concurrency buys nothing
|
|
142
|
+
* and a fan-out of `Promise.all` over hundreds of ids only makes the queue
|
|
143
|
+
* less fair to everything else on it.
|
|
144
|
+
*/
|
|
145
|
+
export declare function getBindingsBatch(deps: BindingsDeps, input: {
|
|
146
|
+
deviceIds: readonly number[];
|
|
147
|
+
}): Promise<Array<{
|
|
148
|
+
deviceId: number;
|
|
149
|
+
entries: DeviceBindingEntry[];
|
|
150
|
+
}>>;
|
|
131
151
|
/**
|
|
132
152
|
* Whole-fleet binding dump. Iterates every device known to the
|
|
133
153
|
* deviceRegistry and reuses the per-device `getBindings` resolver
|
|
@@ -43,8 +43,24 @@ export declare function resolveLinkedDeviceIds(params: {
|
|
|
43
43
|
* are ignored.
|
|
44
44
|
*/
|
|
45
45
|
export declare function resolveTrackedEventDeviceIds(config: Record<string, unknown>, linkedIds: readonly number[]): readonly number[];
|
|
46
|
+
/** The single resolved answer, before it is tagged with its camera. */
|
|
47
|
+
export type ResolvedLinkedDevices = Awaited<ReturnType<IDeviceManagerProvider['getLinkedDevices']>>;
|
|
46
48
|
/** Cap implementation of `getLinkedDevices`. */
|
|
47
49
|
export declare function getLinkedDevices(pctx: ProviderContext, input: Parameters<IDeviceManagerProvider['getLinkedDevices']>[0]): ReturnType<IDeviceManagerProvider['getLinkedDevices']>;
|
|
50
|
+
/**
|
|
51
|
+
* Cap implementation of `getLinkedDevicesBatch` — ONE fleet listing for the
|
|
52
|
+
* whole set.
|
|
53
|
+
*
|
|
54
|
+
* That listing is the query's cost (see `resolveFor`), and asking per camera
|
|
55
|
+
* does not amortise it: on the live hub 2026-08-25, 30 concurrent
|
|
56
|
+
* `getLinkedDevices` took 4110 ms wall — a queue of thirty full-fleet sweeps
|
|
57
|
+
* on device-manager's event loop — to produce 7260 bytes.
|
|
58
|
+
*
|
|
59
|
+
* Ids are deduped and the answer is one row per REQUESTED id, in the order
|
|
60
|
+
* asked: a camera whose settings read fails still gets a row (resolved against
|
|
61
|
+
* an empty config, i.e. children-only) rather than dropping out of the array.
|
|
62
|
+
*/
|
|
63
|
+
export declare function getLinkedDevicesBatch(pctx: ProviderContext, input: Parameters<IDeviceManagerProvider['getLinkedDevicesBatch']>[0]): ReturnType<IDeviceManagerProvider['getLinkedDevicesBatch']>;
|
|
48
64
|
/**
|
|
49
65
|
* "Linked devices" settings section for a CAMERA (null for every other
|
|
50
66
|
* device type — the association is camera-anchored). Contributed through
|
|
@@ -91,6 +91,12 @@ export declare class DeviceManagerAddon extends BaseAddon {
|
|
|
91
91
|
deviceId: number;
|
|
92
92
|
entries: DeviceBindingEntry[];
|
|
93
93
|
}>;
|
|
94
|
+
getBindingsBatch(input: {
|
|
95
|
+
deviceIds: readonly number[];
|
|
96
|
+
}): Promise<Array<{
|
|
97
|
+
deviceId: number;
|
|
98
|
+
entries: DeviceBindingEntry[];
|
|
99
|
+
}>>;
|
|
94
100
|
getAllBindings(): Promise<Array<{
|
|
95
101
|
deviceId: number;
|
|
96
102
|
entries: DeviceBindingEntry[];
|