@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.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
2
|
-
const require_manifest_python_deps = require("./manifest-python-deps-
|
|
2
|
+
const require_manifest_python_deps = require("./manifest-python-deps-BTkFwAk_.js");
|
|
3
3
|
const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
|
|
4
4
|
let node_path = require("node:path");
|
|
5
5
|
node_path = require_chunk.__toESM(node_path);
|
|
@@ -10,229 +10,6 @@ let node_v8 = require("node:v8");
|
|
|
10
10
|
node_v8 = require_chunk.__toESM(node_v8);
|
|
11
11
|
let _camstack_types_addon = require("@camstack/types/addon");
|
|
12
12
|
let node_url = require("node:url");
|
|
13
|
-
//#region src/kernel/moleculer/worker-device-restore.ts
|
|
14
|
-
/**
|
|
15
|
-
* Worker-side device restore helper.
|
|
16
|
-
*
|
|
17
|
-
* Shared by `process-runner` (single-addon subprocess) and
|
|
18
|
-
* `group-runner` (group subprocess). Calls the worker addon's
|
|
19
|
-
* `restoreDevices(SavedDevice[])` with devices read from the hub's
|
|
20
|
-
* `device-manager` capability. Blocks on the `system.ready-state`
|
|
21
|
-
* readiness of the hub's `device-manager` via
|
|
22
|
-
* `ReadinessRegistry.awaitReady`, then issues the calls ONCE.
|
|
23
|
-
*
|
|
24
|
-
* Lives in its own module so it can be imported without triggering
|
|
25
|
-
* the runner entry-point side effects (each runner is also an
|
|
26
|
-
* executable that calls `main()` at import time).
|
|
27
|
-
*/
|
|
28
|
-
async function runWorkerDeviceRestoreWithRetry(addon, context, addonId, sourceNodeId) {
|
|
29
|
-
const log = context.logger;
|
|
30
|
-
log?.debug?.(`[worker-restore] entry: addon="${addonId}" sourceNodeId="${sourceNodeId}"`);
|
|
31
|
-
const restoreFn = addon.restoreDevices;
|
|
32
|
-
if (typeof restoreFn !== "function") {
|
|
33
|
-
log?.warn?.(`[worker-restore] "${addonId}": no restoreDevices function — skipping`);
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
const api = context.api;
|
|
37
|
-
if (!api) {
|
|
38
|
-
log?.warn?.(`[worker-restore] "${addonId}": context.api missing — skipping`);
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
const bus = context.eventBus;
|
|
42
|
-
if (!bus) {
|
|
43
|
-
log?.warn?.(`[worker-restore] "${addonId}": context.eventBus missing — skipping`);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
const shared = context.kernel?.readinessRegistry ?? null;
|
|
47
|
-
const registry = shared ?? new _camstack_types_addon.ReadinessRegistry({
|
|
48
|
-
eventBus: bus,
|
|
49
|
-
sourceNodeId,
|
|
50
|
-
logger: context.logger
|
|
51
|
-
});
|
|
52
|
-
log?.debug?.(`[worker-restore] "${addonId}": awaiting device-manager readiness on hub...`);
|
|
53
|
-
try {
|
|
54
|
-
await registry.awaitReady("device-manager", {
|
|
55
|
-
type: "node",
|
|
56
|
-
nodeId: "hub"
|
|
57
|
-
}, { timeoutMs: 6e4 });
|
|
58
|
-
log?.debug?.(`[worker-restore] "${addonId}": device-manager READY`);
|
|
59
|
-
} catch (err) {
|
|
60
|
-
if (err instanceof _camstack_types_addon.ReadinessTimeoutError) {
|
|
61
|
-
context.logger?.warn?.(`[worker-restore] device-manager not ready within ${err.waitedMs}ms — skipping for "${addonId}"`);
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
throw err;
|
|
65
|
-
} finally {
|
|
66
|
-
if (!shared && registry instanceof _camstack_types_addon.ReadinessRegistry) registry.close();
|
|
67
|
-
}
|
|
68
|
-
try {
|
|
69
|
-
const deviceManager = Reflect.get(api, "deviceManager");
|
|
70
|
-
log?.debug?.(`[worker-restore] "${addonId}": calling listPersistedByAddon...`);
|
|
71
|
-
const rowsResult = await deviceManager.listPersistedByAddon.query({ addonId });
|
|
72
|
-
log?.debug?.(`[worker-restore] "${addonId}": got ${Array.isArray(rowsResult) ? rowsResult.length : 0} row(s)`);
|
|
73
|
-
const rows = Array.isArray(rowsResult) ? rowsResult : [];
|
|
74
|
-
if (rows.length === 0) {
|
|
75
|
-
context.logger?.info?.(`[worker-restore] no persisted devices to restore for addon "${addonId}"`);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const savedDevices = await Promise.all(rows.map(async (row) => {
|
|
79
|
-
const config = await deviceManager.loadConfig.query({ deviceId: row.id });
|
|
80
|
-
return {
|
|
81
|
-
id: row.id,
|
|
82
|
-
stableId: row.stableId,
|
|
83
|
-
type: row.type,
|
|
84
|
-
name: row.name,
|
|
85
|
-
parentDeviceId: row.parentDeviceId,
|
|
86
|
-
config: config ?? {}
|
|
87
|
-
};
|
|
88
|
-
}));
|
|
89
|
-
await restoreFn.call(addon, savedDevices);
|
|
90
|
-
log?.info?.(`[worker-restore] "${addonId}": restored ${savedDevices.length} device(s)`);
|
|
91
|
-
} catch (err) {
|
|
92
|
-
log?.warn?.(`[worker-restore] "${addonId}": restoreDevices threw: ${(0, _camstack_types_addon.errMsg)(err)}`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
//#endregion
|
|
96
|
-
//#region src/kernel/moleculer/register-framework-resolver.ts
|
|
97
|
-
/**
|
|
98
|
-
* Register the ESM resolver hook so this runner's addon imports of the
|
|
99
|
-
* host-provided packages (@camstack/system, @camstack/shm-ring, …) resolve from
|
|
100
|
-
* `frameworkDir/node_modules` instead of failing to walk up from the addon's
|
|
101
|
-
* isolated `/data/addons/<addon>` folder. No-op in dev (frameworkDir unset),
|
|
102
|
-
* where workspace symlinks already resolve the framework.
|
|
103
|
-
*/
|
|
104
|
-
function registerFrameworkResolver(frameworkDir) {
|
|
105
|
-
if (!frameworkDir) return;
|
|
106
|
-
(0, node_module.register)((0, node_url.pathToFileURL)(node_path.join(__dirname, "framework-resolver-hook.mjs")), {
|
|
107
|
-
parentURL: (0, node_url.pathToFileURL)(`${__dirname}/`).href,
|
|
108
|
-
data: { frameworkDir }
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Start warning if `initialize()` has not returned within `firstMs`.
|
|
113
|
-
*
|
|
114
|
-
* The caller MUST `clear()` in a `finally`, so a throwing initialize (which the
|
|
115
|
-
* runner turns into `process.exit(1)`) does not keep the timer alive.
|
|
116
|
-
*/
|
|
117
|
-
function startInitWatchdog(options) {
|
|
118
|
-
const now = options.now ?? Date.now;
|
|
119
|
-
const firstMs = options.firstMs ?? 6e4;
|
|
120
|
-
const repeatMs = options.repeatMs ?? 3e5;
|
|
121
|
-
const startedAt = now();
|
|
122
|
-
let timer;
|
|
123
|
-
let cleared = false;
|
|
124
|
-
const schedule = (ms) => {
|
|
125
|
-
timer = setTimeout(fire, ms);
|
|
126
|
-
timer.unref?.();
|
|
127
|
-
};
|
|
128
|
-
function fire() {
|
|
129
|
-
if (cleared) return;
|
|
130
|
-
const elapsedMs = now() - startedAt;
|
|
131
|
-
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"`, {
|
|
132
|
-
addonId: options.addonId,
|
|
133
|
-
elapsedMs,
|
|
134
|
-
unpublishedCapabilities: [...options.declaredCapabilities]
|
|
135
|
-
});
|
|
136
|
-
schedule(repeatMs);
|
|
137
|
-
}
|
|
138
|
-
schedule(firstMs);
|
|
139
|
-
return { clear() {
|
|
140
|
-
cleared = true;
|
|
141
|
-
if (timer !== void 0) {
|
|
142
|
-
clearTimeout(timer);
|
|
143
|
-
timer = void 0;
|
|
144
|
-
}
|
|
145
|
-
} };
|
|
146
|
-
}
|
|
147
|
-
//#endregion
|
|
148
|
-
//#region src/kernel/moleculer/child-cap-dispatch.ts
|
|
149
|
-
/** Type guard narrowing a dynamically-read provider member to a callable method. */
|
|
150
|
-
function isCapMethod(value) {
|
|
151
|
-
return typeof value === "function";
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Extract a string `addonId` out of arbitrary method args — the fallback
|
|
155
|
-
* provider-selection hint for calls that arrive WITHOUT the out-of-band
|
|
156
|
-
* `CapCallMessage.addonId` (e.g. a sibling child's unowned collection call
|
|
157
|
-
* forwarded by the parent, where the raw input still carries `addonId`).
|
|
158
|
-
*/
|
|
159
|
-
function extractArgsAddonId(args) {
|
|
160
|
-
if (args === null || typeof args !== "object") return void 0;
|
|
161
|
-
const raw = Reflect.get(args, "addonId");
|
|
162
|
-
return typeof raw === "string" ? raw : void 0;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Creates a unified dispatcher that routes a {@link CapCallInput} to either a
|
|
166
|
-
* device-scoped or singleton provider, then invokes the requested method.
|
|
167
|
-
*
|
|
168
|
-
* Pure module — no Moleculer, no broker, no addon-runner dependency.
|
|
169
|
-
*/
|
|
170
|
-
function createChildCapDispatch(deps) {
|
|
171
|
-
return async (call) => {
|
|
172
|
-
const { capName, method, args, deviceId } = call;
|
|
173
|
-
const addonId = call.addonId ?? extractArgsAddonId(args);
|
|
174
|
-
let provider;
|
|
175
|
-
if (deviceId !== void 0) {
|
|
176
|
-
provider = deps.getDeviceProvider(capName, deviceId) ?? deps.getSingletonProvider(capName, addonId);
|
|
177
|
-
if (provider == null) throw new Error(`child-cap-dispatch: no provider for '${capName}' (device '${String(deviceId)}' or singleton)`);
|
|
178
|
-
} else {
|
|
179
|
-
provider = deps.getSingletonProvider(capName, addonId);
|
|
180
|
-
if (provider == null) throw new Error(`child-cap-dispatch: no singleton provider for '${capName}'`);
|
|
181
|
-
}
|
|
182
|
-
const fn = Reflect.get(provider, method);
|
|
183
|
-
if (!isCapMethod(fn)) {
|
|
184
|
-
if (deps.isCollectionListMethod(capName, method)) return [];
|
|
185
|
-
throw new Error(`child-cap-dispatch: method '${method}' not found on provider '${capName}'`);
|
|
186
|
-
}
|
|
187
|
-
return await fn.call(provider, args);
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
//#endregion
|
|
191
|
-
//#region src/kernel/moleculer/child-cap-descriptors.ts
|
|
192
|
-
/**
|
|
193
|
-
* Builds the {@link ChildCapDescriptor} array that the UDS child transport
|
|
194
|
-
* sends to the parent on registration.
|
|
195
|
-
*
|
|
196
|
-
* - Singleton caps come from the runner UNION manifest (all caps registered by
|
|
197
|
-
* every addon in this runner). One descriptor per unique (addonId, capName)
|
|
198
|
-
* pair, mode `'singleton'` and no `deviceId`. The `addonId` stamp lets the
|
|
199
|
-
* parent's manifest builder register each hosted addon's caps under the
|
|
200
|
-
* REAL addon id — a GROUPED runner (`execution.group`) hosts several addons,
|
|
201
|
-
* and deduplicating by capName alone would collapse two co-located providers
|
|
202
|
-
* of the same collection cap (e.g. `network-access` from cloudflare-tunnel
|
|
203
|
-
* AND tailscale-ingress) into one descriptor registered under a synthetic
|
|
204
|
-
* `addonId = childId` (the group name) — the "@camstack/addon-net-access"
|
|
205
|
-
* admin-UI bug.
|
|
206
|
-
* - Device-scoped caps come from the native-cap snapshot — one descriptor per
|
|
207
|
-
* (capName, deviceId) pair, mode `'collection'`, stamped with the owning
|
|
208
|
-
* addon's id.
|
|
209
|
-
*
|
|
210
|
-
* Pure module — extracted from `addon-runner.ts` (whose `main()` runs at
|
|
211
|
-
* import time) so it is unit-testable.
|
|
212
|
-
*/
|
|
213
|
-
function buildChildCapDescriptors(manifest, nativeCapSnapshot) {
|
|
214
|
-
const descriptors = [];
|
|
215
|
-
const seen = /* @__PURE__ */ new Set();
|
|
216
|
-
for (const entry of manifest) for (const capName of entry.capabilities) {
|
|
217
|
-
const key = `${entry.addonId}::${capName}`;
|
|
218
|
-
if (!seen.has(key)) {
|
|
219
|
-
seen.add(key);
|
|
220
|
-
descriptors.push({
|
|
221
|
-
capName,
|
|
222
|
-
mode: "singleton",
|
|
223
|
-
addonId: entry.addonId
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
for (const { capName, addonId, deviceIds } of nativeCapSnapshot) for (const deviceId of deviceIds) descriptors.push({
|
|
228
|
-
capName,
|
|
229
|
-
mode: "collection",
|
|
230
|
-
deviceId,
|
|
231
|
-
addonId
|
|
232
|
-
});
|
|
233
|
-
return descriptors;
|
|
234
|
-
}
|
|
235
|
-
//#endregion
|
|
236
13
|
//#region src/kernel/moleculer/child-addon-call-dispatch.ts
|
|
237
14
|
function isRecord$1(value) {
|
|
238
15
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -324,6 +101,229 @@ function createChildAddonCallDispatch(deps) {
|
|
|
324
101
|
};
|
|
325
102
|
}
|
|
326
103
|
//#endregion
|
|
104
|
+
//#region src/kernel/moleculer/child-cap-descriptors.ts
|
|
105
|
+
/**
|
|
106
|
+
* Builds the {@link ChildCapDescriptor} array that the UDS child transport
|
|
107
|
+
* sends to the parent on registration.
|
|
108
|
+
*
|
|
109
|
+
* - Singleton caps come from the runner UNION manifest (all caps registered by
|
|
110
|
+
* every addon in this runner). One descriptor per unique (addonId, capName)
|
|
111
|
+
* pair, mode `'singleton'` and no `deviceId`. The `addonId` stamp lets the
|
|
112
|
+
* parent's manifest builder register each hosted addon's caps under the
|
|
113
|
+
* REAL addon id — a GROUPED runner (`execution.group`) hosts several addons,
|
|
114
|
+
* and deduplicating by capName alone would collapse two co-located providers
|
|
115
|
+
* of the same collection cap (e.g. `network-access` from cloudflare-tunnel
|
|
116
|
+
* AND tailscale-ingress) into one descriptor registered under a synthetic
|
|
117
|
+
* `addonId = childId` (the group name) — the "@camstack/addon-net-access"
|
|
118
|
+
* admin-UI bug.
|
|
119
|
+
* - Device-scoped caps come from the native-cap snapshot — one descriptor per
|
|
120
|
+
* (capName, deviceId) pair, mode `'collection'`, stamped with the owning
|
|
121
|
+
* addon's id.
|
|
122
|
+
*
|
|
123
|
+
* Pure module — extracted from `addon-runner.ts` (whose `main()` runs at
|
|
124
|
+
* import time) so it is unit-testable.
|
|
125
|
+
*/
|
|
126
|
+
function buildChildCapDescriptors(manifest, nativeCapSnapshot) {
|
|
127
|
+
const descriptors = [];
|
|
128
|
+
const seen = /* @__PURE__ */ new Set();
|
|
129
|
+
for (const entry of manifest) for (const capName of entry.capabilities) {
|
|
130
|
+
const key = `${entry.addonId}::${capName}`;
|
|
131
|
+
if (!seen.has(key)) {
|
|
132
|
+
seen.add(key);
|
|
133
|
+
descriptors.push({
|
|
134
|
+
capName,
|
|
135
|
+
mode: "singleton",
|
|
136
|
+
addonId: entry.addonId
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
for (const { capName, addonId, deviceIds } of nativeCapSnapshot) for (const deviceId of deviceIds) descriptors.push({
|
|
141
|
+
capName,
|
|
142
|
+
mode: "collection",
|
|
143
|
+
deviceId,
|
|
144
|
+
addonId
|
|
145
|
+
});
|
|
146
|
+
return descriptors;
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/kernel/moleculer/child-cap-dispatch.ts
|
|
150
|
+
/** Type guard narrowing a dynamically-read provider member to a callable method. */
|
|
151
|
+
function isCapMethod(value) {
|
|
152
|
+
return typeof value === "function";
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Extract a string `addonId` out of arbitrary method args — the fallback
|
|
156
|
+
* provider-selection hint for calls that arrive WITHOUT the out-of-band
|
|
157
|
+
* `CapCallMessage.addonId` (e.g. a sibling child's unowned collection call
|
|
158
|
+
* forwarded by the parent, where the raw input still carries `addonId`).
|
|
159
|
+
*/
|
|
160
|
+
function extractArgsAddonId(args) {
|
|
161
|
+
if (args === null || typeof args !== "object") return void 0;
|
|
162
|
+
const raw = Reflect.get(args, "addonId");
|
|
163
|
+
return typeof raw === "string" ? raw : void 0;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Creates a unified dispatcher that routes a {@link CapCallInput} to either a
|
|
167
|
+
* device-scoped or singleton provider, then invokes the requested method.
|
|
168
|
+
*
|
|
169
|
+
* Pure module — no Moleculer, no broker, no addon-runner dependency.
|
|
170
|
+
*/
|
|
171
|
+
function createChildCapDispatch(deps) {
|
|
172
|
+
return async (call) => {
|
|
173
|
+
const { capName, method, args, deviceId } = call;
|
|
174
|
+
const addonId = call.addonId ?? extractArgsAddonId(args);
|
|
175
|
+
let provider;
|
|
176
|
+
if (deviceId !== void 0) {
|
|
177
|
+
provider = deps.getDeviceProvider(capName, deviceId) ?? deps.getSingletonProvider(capName, addonId);
|
|
178
|
+
if (provider == null) throw new Error(`child-cap-dispatch: no provider for '${capName}' (device '${String(deviceId)}' or singleton)`);
|
|
179
|
+
} else {
|
|
180
|
+
provider = deps.getSingletonProvider(capName, addonId);
|
|
181
|
+
if (provider == null) throw new Error(`child-cap-dispatch: no singleton provider for '${capName}'`);
|
|
182
|
+
}
|
|
183
|
+
const fn = Reflect.get(provider, method);
|
|
184
|
+
if (!isCapMethod(fn)) {
|
|
185
|
+
if (deps.isCollectionListMethod(capName, method)) return [];
|
|
186
|
+
throw new Error(`child-cap-dispatch: method '${method}' not found on provider '${capName}'`);
|
|
187
|
+
}
|
|
188
|
+
return await fn.call(provider, args);
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Start warning if `initialize()` has not returned within `firstMs`.
|
|
193
|
+
*
|
|
194
|
+
* The caller MUST `clear()` in a `finally`, so a throwing initialize (which the
|
|
195
|
+
* runner turns into `process.exit(1)`) does not keep the timer alive.
|
|
196
|
+
*/
|
|
197
|
+
function startInitWatchdog(options) {
|
|
198
|
+
const now = options.now ?? Date.now;
|
|
199
|
+
const firstMs = options.firstMs ?? 6e4;
|
|
200
|
+
const repeatMs = options.repeatMs ?? 3e5;
|
|
201
|
+
const startedAt = now();
|
|
202
|
+
let timer;
|
|
203
|
+
let cleared = false;
|
|
204
|
+
const schedule = (ms) => {
|
|
205
|
+
timer = setTimeout(fire, ms);
|
|
206
|
+
timer.unref?.();
|
|
207
|
+
};
|
|
208
|
+
function fire() {
|
|
209
|
+
if (cleared) return;
|
|
210
|
+
const elapsedMs = now() - startedAt;
|
|
211
|
+
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"`, {
|
|
212
|
+
addonId: options.addonId,
|
|
213
|
+
elapsedMs,
|
|
214
|
+
unpublishedCapabilities: [...options.declaredCapabilities]
|
|
215
|
+
});
|
|
216
|
+
schedule(repeatMs);
|
|
217
|
+
}
|
|
218
|
+
schedule(firstMs);
|
|
219
|
+
return { clear() {
|
|
220
|
+
cleared = true;
|
|
221
|
+
if (timer !== void 0) {
|
|
222
|
+
clearTimeout(timer);
|
|
223
|
+
timer = void 0;
|
|
224
|
+
}
|
|
225
|
+
} };
|
|
226
|
+
}
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region src/kernel/moleculer/register-framework-resolver.ts
|
|
229
|
+
/**
|
|
230
|
+
* Register the ESM resolver hook so this runner's addon imports of the
|
|
231
|
+
* host-provided packages (@camstack/system, @camstack/shm-ring, …) resolve from
|
|
232
|
+
* `frameworkDir/node_modules` instead of failing to walk up from the addon's
|
|
233
|
+
* isolated `/data/addons/<addon>` folder. No-op in dev (frameworkDir unset),
|
|
234
|
+
* where workspace symlinks already resolve the framework.
|
|
235
|
+
*/
|
|
236
|
+
function registerFrameworkResolver(frameworkDir) {
|
|
237
|
+
if (!frameworkDir) return;
|
|
238
|
+
(0, node_module.register)((0, node_url.pathToFileURL)(node_path.join(__dirname, "framework-resolver-hook.mjs")), {
|
|
239
|
+
parentURL: (0, node_url.pathToFileURL)(`${__dirname}/`).href,
|
|
240
|
+
data: { frameworkDir }
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/kernel/moleculer/worker-device-restore.ts
|
|
245
|
+
/**
|
|
246
|
+
* Worker-side device restore helper.
|
|
247
|
+
*
|
|
248
|
+
* Shared by `process-runner` (single-addon subprocess) and
|
|
249
|
+
* `group-runner` (group subprocess). Calls the worker addon's
|
|
250
|
+
* `restoreDevices(SavedDevice[])` with devices read from the hub's
|
|
251
|
+
* `device-manager` capability. Blocks on the `system.ready-state`
|
|
252
|
+
* readiness of the hub's `device-manager` via
|
|
253
|
+
* `ReadinessRegistry.awaitReady`, then issues the calls ONCE.
|
|
254
|
+
*
|
|
255
|
+
* Lives in its own module so it can be imported without triggering
|
|
256
|
+
* the runner entry-point side effects (each runner is also an
|
|
257
|
+
* executable that calls `main()` at import time).
|
|
258
|
+
*/
|
|
259
|
+
async function runWorkerDeviceRestoreWithRetry(addon, context, addonId, sourceNodeId) {
|
|
260
|
+
const log = context.logger;
|
|
261
|
+
log?.debug?.(`[worker-restore] entry: addon="${addonId}" sourceNodeId="${sourceNodeId}"`);
|
|
262
|
+
const restoreFn = addon.restoreDevices;
|
|
263
|
+
if (typeof restoreFn !== "function") {
|
|
264
|
+
log?.warn?.(`[worker-restore] "${addonId}": no restoreDevices function — skipping`);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const api = context.api;
|
|
268
|
+
if (!api) {
|
|
269
|
+
log?.warn?.(`[worker-restore] "${addonId}": context.api missing — skipping`);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const bus = context.eventBus;
|
|
273
|
+
if (!bus) {
|
|
274
|
+
log?.warn?.(`[worker-restore] "${addonId}": context.eventBus missing — skipping`);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const shared = context.kernel?.readinessRegistry ?? null;
|
|
278
|
+
const registry = shared ?? new _camstack_types_addon.ReadinessRegistry({
|
|
279
|
+
eventBus: bus,
|
|
280
|
+
sourceNodeId,
|
|
281
|
+
logger: context.logger
|
|
282
|
+
});
|
|
283
|
+
log?.debug?.(`[worker-restore] "${addonId}": awaiting device-manager readiness on hub...`);
|
|
284
|
+
try {
|
|
285
|
+
await registry.awaitReady("device-manager", {
|
|
286
|
+
type: "node",
|
|
287
|
+
nodeId: "hub"
|
|
288
|
+
}, { timeoutMs: 6e4 });
|
|
289
|
+
log?.debug?.(`[worker-restore] "${addonId}": device-manager READY`);
|
|
290
|
+
} catch (err) {
|
|
291
|
+
if (err instanceof _camstack_types_addon.ReadinessTimeoutError) {
|
|
292
|
+
context.logger?.warn?.(`[worker-restore] device-manager not ready within ${err.waitedMs}ms — skipping for "${addonId}"`);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
throw err;
|
|
296
|
+
} finally {
|
|
297
|
+
if (!shared && registry instanceof _camstack_types_addon.ReadinessRegistry) registry.close();
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
const deviceManager = Reflect.get(api, "deviceManager");
|
|
301
|
+
log?.debug?.(`[worker-restore] "${addonId}": calling listPersistedByAddon...`);
|
|
302
|
+
const rowsResult = await deviceManager.listPersistedByAddon.query({ addonId });
|
|
303
|
+
log?.debug?.(`[worker-restore] "${addonId}": got ${Array.isArray(rowsResult) ? rowsResult.length : 0} row(s)`);
|
|
304
|
+
const rows = Array.isArray(rowsResult) ? rowsResult : [];
|
|
305
|
+
if (rows.length === 0) {
|
|
306
|
+
context.logger?.info?.(`[worker-restore] no persisted devices to restore for addon "${addonId}"`);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const savedDevices = await Promise.all(rows.map(async (row) => {
|
|
310
|
+
const config = await deviceManager.loadConfig.query({ deviceId: row.id });
|
|
311
|
+
return {
|
|
312
|
+
id: row.id,
|
|
313
|
+
stableId: row.stableId,
|
|
314
|
+
type: row.type,
|
|
315
|
+
name: row.name,
|
|
316
|
+
parentDeviceId: row.parentDeviceId,
|
|
317
|
+
config: config ?? {}
|
|
318
|
+
};
|
|
319
|
+
}));
|
|
320
|
+
await restoreFn.call(addon, savedDevices);
|
|
321
|
+
log?.info?.(`[worker-restore] "${addonId}": restored ${savedDevices.length} device(s)`);
|
|
322
|
+
} catch (err) {
|
|
323
|
+
log?.warn?.(`[worker-restore] "${addonId}": restoreDevices threw: ${(0, _camstack_types_addon.errMsg)(err)}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
//#endregion
|
|
327
327
|
//#region src/kernel/moleculer/addon-runner.ts
|
|
328
328
|
/**
|
|
329
329
|
* Addon Runner — THE unified entry point for spawned subprocesses.
|