@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.
Files changed (61) hide show
  1. package/dist/addon-runner.js +224 -224
  2. package/dist/addon-runner.mjs +224 -224
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  14. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  15. package/dist/builtins/device-manager/device-bindings-store.d.ts +20 -0
  16. package/dist/builtins/device-manager/device-linked-devices.d.ts +16 -0
  17. package/dist/builtins/device-manager/device-manager.addon.d.ts +6 -0
  18. package/dist/builtins/device-manager/device-manager.addon.js +73 -7
  19. package/dist/builtins/device-manager/device-manager.addon.mjs +73 -7
  20. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  21. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  22. package/dist/builtins/hub-forwarder/index.js +1 -1
  23. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  24. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  25. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  26. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  27. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  28. package/dist/builtins/local-network/local-network.addon.js +1 -1
  29. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  30. package/dist/builtins/loki-logging/index.js +1 -1
  31. package/dist/builtins/loki-logging/index.mjs +1 -1
  32. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  33. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  34. package/dist/builtins/platform-probe/index.js +1 -1
  35. package/dist/builtins/platform-probe/index.mjs +1 -1
  36. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  37. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  38. package/dist/builtins/snapshot/index.js +1 -1
  39. package/dist/builtins/snapshot/index.mjs +1 -1
  40. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  41. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  42. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +2 -2
  43. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
  44. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  45. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  46. package/dist/builtins/system-config/system-config.addon.js +1 -1
  47. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  48. package/dist/builtins/winston-logging/index.js +1 -1
  49. package/dist/builtins/winston-logging/index.mjs +1 -1
  50. package/dist/{dist-DnhGRFEn.js → dist-CIDgcxPk.js} +97 -21
  51. package/dist/{dist-CEhrHp3u.mjs → dist-CvJrtLkw.mjs} +97 -21
  52. package/dist/index.js +43 -4
  53. package/dist/index.mjs +43 -4
  54. package/dist/kernel/heap-watch.d.ts +12 -9
  55. package/dist/kernel/moleculer/process-service.d.ts +12 -1
  56. package/dist/kernel/moleculer/runner-native-allocator.d.ts +95 -0
  57. package/dist/{manifest-python-deps-B3_4YiDK.js → manifest-python-deps-BTkFwAk_.js} +15 -12
  58. package/dist/{manifest-python-deps-DIIXlR1x.mjs → manifest-python-deps-Dmcnb287.mjs} +15 -12
  59. package/dist/{retired-settings-keys-VAyOhu_a.mjs → retired-settings-keys-B1aZcoS0.mjs} +1 -1
  60. package/dist/{retired-settings-keys-D6Jy_SsO.js → retired-settings-keys-D7c0n9jO.js} +1 -1
  61. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_manifest_python_deps = require("./manifest-python-deps-B3_4YiDK.js");
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.