@camstack/core 0.1.38 → 0.1.40

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 (52) hide show
  1. package/dist/auth/auth-manager.d.ts +12 -1
  2. package/dist/auth/auth-manager.d.ts.map +1 -1
  3. package/dist/auth/scope-matcher.d.ts +8 -0
  4. package/dist/auth/scope-matcher.d.ts.map +1 -0
  5. package/dist/auth/totp-manager.d.ts +0 -1
  6. package/dist/auth/totp-manager.d.ts.map +1 -1
  7. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.d.ts +15 -0
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.d.ts.map +1 -1
  9. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +27 -6
  10. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js.map +1 -1
  11. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +27 -6
  12. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs.map +1 -1
  13. package/dist/builtins/device-manager/device-config-contribution.d.ts +33 -0
  14. package/dist/builtins/device-manager/device-config-contribution.d.ts.map +1 -0
  15. package/dist/builtins/device-manager/device-manager.addon.d.ts +52 -17
  16. package/dist/builtins/device-manager/device-manager.addon.d.ts.map +1 -1
  17. package/dist/builtins/device-manager/device-manager.addon.js +285 -161
  18. package/dist/builtins/device-manager/device-manager.addon.js.map +1 -1
  19. package/dist/builtins/device-manager/device-manager.addon.mjs +286 -162
  20. package/dist/builtins/device-manager/device-manager.addon.mjs.map +1 -1
  21. package/dist/builtins/local-auth/auth-schema.d.ts +1 -0
  22. package/dist/builtins/local-auth/auth-schema.d.ts.map +1 -1
  23. package/dist/builtins/local-auth/local-auth.addon.d.ts +1 -0
  24. package/dist/builtins/local-auth/local-auth.addon.d.ts.map +1 -1
  25. package/dist/builtins/local-auth/local-auth.addon.js +354 -3
  26. package/dist/builtins/local-auth/local-auth.addon.js.map +1 -1
  27. package/dist/builtins/local-auth/local-auth.addon.mjs +355 -3
  28. package/dist/builtins/local-auth/local-auth.addon.mjs.map +1 -1
  29. package/dist/builtins/local-auth/oauth-grants.d.ts +46 -0
  30. package/dist/builtins/local-auth/oauth-grants.d.ts.map +1 -0
  31. package/dist/builtins/local-auth/oauth-session-manager.d.ts +51 -0
  32. package/dist/builtins/local-auth/oauth-session-manager.d.ts.map +1 -0
  33. package/dist/builtins/remote-access-orchestrator/enabled-providers-reconcile.d.ts +97 -0
  34. package/dist/builtins/remote-access-orchestrator/enabled-providers-reconcile.d.ts.map +1 -0
  35. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.d.ts +17 -0
  36. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.d.ts.map +1 -1
  37. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +95 -5
  38. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js.map +1 -1
  39. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +95 -5
  40. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs.map +1 -1
  41. package/dist/builtins/snapshot/index.js +1 -3
  42. package/dist/builtins/snapshot/index.js.map +1 -1
  43. package/dist/builtins/snapshot/index.mjs +1 -3
  44. package/dist/builtins/snapshot/index.mjs.map +1 -1
  45. package/dist/builtins/snapshot/snapshot.addon.d.ts.map +1 -1
  46. package/dist/index.d.ts +1 -0
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +419 -97
  49. package/dist/index.js.map +1 -1
  50. package/dist/index.mjs +419 -98
  51. package/dist/index.mjs.map +1 -1
  52. package/package.json +19 -1
@@ -1,4 +1,4 @@
1
- import { TokenPayload, IScopedLogger } from '@camstack/types';
1
+ import { TokenPayload, IScopedLogger, TokenScope } from '@camstack/types';
2
2
  export type { TokenPayload };
3
3
  export type AuthConfigReader = {
4
4
  get<T>(path: string): T;
@@ -90,6 +90,17 @@ export interface SsoBridgeClaims {
90
90
  * issuers set it.
91
91
  */
92
92
  readonly hubUrl?: string;
93
+ /** Permission scopes baked into the token by the OAuth account-linking
94
+ * grant. Absent on ordinary SSO-login bridge tokens. */
95
+ readonly scopes?: readonly TokenScope[];
96
+ /** OAuth authorization-code binding — set only on `oauth-code` tokens. */
97
+ readonly redirectUri?: string;
98
+ readonly integrationId?: string;
99
+ /** JWT ID — unique per issued code; consumed-set enforces single-use. */
100
+ readonly jti?: string;
101
+ /** OAuth session registry id — set on `oauth-access`/`oauth-refresh`
102
+ * tokens so the verify path can check the session is not revoked. */
103
+ readonly sessionId?: string;
93
104
  }
94
105
  export interface TotpChallengeClaims {
95
106
  readonly userId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"auth-manager.d.ts","sourceRoot":"","sources":["../../src/auth/auth-manager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAYlE,YAAY,EAAE,YAAY,EAAE,CAAA;AAE5B,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAAA;IACvB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CAC7D,CAAA;AAED,qBAAa,WAAW;IAIV,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;gBAET,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAE,aAA0B;IAczF,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,KAAK,GAAG,KAAK,CAAC,GAAG,MAAM;IAI7D,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY;IAIlC,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI/C,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIvE,cAAc,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;IAOjE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO;IAK1D;;;OAGG;IACH,kBAAkB,CAAC,IAAI,EAAE;QACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAC5B,GAAG,MAAM;IAuBV;;;;;;;;;;;;;;OAcG;IACH,kBAAkB,CAAC,OAAO,EAAE,eAAe,EAAE,MAAM,GAAE,MAAY,GAAG,MAAM;IAc1E;;;;;OAKG;IACH,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IA4B3D;;;;;;;;;;;;;;OAcG;IACH,sBAAsB,CAAC,OAAO,EAAE,mBAAmB,EAAE,MAAM,GAAE,MAAY,GAAG,MAAM;IAalF;;;;;;OAMG;IACH,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI;CAepE;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAC1B"}
1
+ {"version":3,"file":"auth-manager.d.ts","sourceRoot":"","sources":["../../src/auth/auth-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { TokenScope, MethodAccess } from '@camstack/types';
2
+ /**
3
+ * True if the scope set grants `access` on device-scoped capabilities.
4
+ * A `category:device` grant covers every device cap (the broad grant).
5
+ * Mirrors the OR-semantics of the existing token scope matcher.
6
+ */
7
+ export declare function scopesAllowDeviceCap(scopes: readonly TokenScope[], access: MethodAccess): boolean;
8
+ //# sourceMappingURL=scope-matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope-matcher.d.ts","sourceRoot":"","sources":["../../src/auth/scope-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAE/D;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAIjG"}
@@ -9,7 +9,6 @@ export interface TotpStatus {
9
9
  }
10
10
  export declare class TotpManager {
11
11
  private readonly store;
12
- private readonly opts;
13
12
  constructor(store: SettingsStoreClient, opts?: {
14
13
  /** ±N 30-second windows tolerated. Default 1 = ±30 s clock skew. */
15
14
  readonly window?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"totp-manager.d.ts","sourceRoot":"","sources":["../../src/auth/totp-manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAoD1D,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CACpC;AAED,qBAAa,WAAW;IAEpB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADJ,KAAK,EAAE,mBAAmB,EAC1B,IAAI,GAAE;QACrB,oEAAoE;QACpE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KACpB;IAOR;;;;;;;;OAQG;IACG,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAoBvE;;;;;;OAMG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAe7D;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5C;;;;;OAKG;IACG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAO5D;;;OAGG;IACG,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;YAStC,IAAI;CAQnB"}
1
+ {"version":3,"file":"totp-manager.d.ts","sourceRoot":"","sources":["../../src/auth/totp-manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAoD1D,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CACpC;AAED,qBAAa,WAAW;IAEpB,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,mBAAmB,EAC3C,IAAI,GAAE;QACJ,oEAAoE;QACpE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KACpB;IAOR;;;;;;;;OAQG;IACG,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAoBvE;;;;;;OAMG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAe7D;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5C;;;;;OAKG;IACG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAO5D;;;OAGG;IACG,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;YAStC,IAAI;CAQnB"}
@@ -16,6 +16,21 @@ export declare class AddonWidgetsAggregatorAddon extends BaseAddon {
16
16
  constructor();
17
17
  protected onInitialize(): Promise<ProviderRegistration[]>;
18
18
  protected onShutdown(): Promise<void>;
19
+ /**
20
+ * Strip the `@<nodeId>` suffix that the CapabilityBridge appends to
21
+ * collection-provider registry keys for cross-node addons (see
22
+ * `moleculer.service.ts` — `registryKey = ${addonId}@${nodeId}`).
23
+ *
24
+ * The widget bundle is hub-resident (the same npm package ships to
25
+ * every node and the hub keeps a copy on disk keyed by the bare
26
+ * manifest id), so both the static-file URL and the admin-ui widget
27
+ * namespace must use the bare addon id. Without this, a widget
28
+ * source running on a remote agent yields a `bundleUrl` like
29
+ * `/api/addon-widgets/pipeline-analytics@agent-0/pipeline-analytics/remoteEntry.js`
30
+ * — the embedded `@<node>/<group>` makes the static-file route's
31
+ * `:addonId` param mismatch the registered provider and 404.
32
+ */
33
+ private bareAddonId;
19
34
  private aggregate;
20
35
  private scheduleRetry;
21
36
  private retrySource;
@@ -1 +1 @@
1
- {"version":3,"file":"addon-widgets-aggregator.addon.d.ts","sourceRoot":"","sources":["../../../src/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.ts"],"names":[],"mappings":"AAuBA,OAAO,EACL,SAAS,EAMT,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAA;AA4BxB,qBAAa,2BAA4B,SAAQ,SAAS;IACxD,QAAQ,CAAC,EAAE,8BAA6B;IAExC,OAAO,CAAC,aAAa,CAA6B;IAElD;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA+C;IAExE,+EAA+E;IAC/E,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoC;;cAIhD,YAAY,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;cAW/C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAQ7B,SAAS;IAsDvB,OAAO,CAAC,aAAa;YAYP,WAAW;IAmCzB;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,iBAAiB;YAcX,YAAY;CAgB3B;AAED,eAAe,2BAA2B,CAAA"}
1
+ {"version":3,"file":"addon-widgets-aggregator.addon.d.ts","sourceRoot":"","sources":["../../../src/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.ts"],"names":[],"mappings":"AAuBA,OAAO,EACL,SAAS,EAMT,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAA;AA4BxB,qBAAa,2BAA4B,SAAQ,SAAS;IACxD,QAAQ,CAAC,EAAE,8BAA6B;IAExC,OAAO,CAAC,aAAa,CAA6B;IAElD;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA+C;IAExE,+EAA+E;IAC/E,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoC;;cAIhD,YAAY,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;cAW/C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3C;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,WAAW;YAKL,SAAS;IA2DvB,OAAO,CAAC,aAAa;YAYP,WAAW;IAoCzB;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,iBAAiB;YAcX,YAAY;CAgB3B;AAED,eAAe,2BAA2B,CAAA"}
@@ -77,17 +77,37 @@ var AddonWidgetsAggregatorAddon = class extends _camstack_types.BaseAddon {
77
77
  this.retryTimers.clear();
78
78
  this.lastGood.clear();
79
79
  }
80
+ /**
81
+ * Strip the `@<nodeId>` suffix that the CapabilityBridge appends to
82
+ * collection-provider registry keys for cross-node addons (see
83
+ * `moleculer.service.ts` — `registryKey = ${addonId}@${nodeId}`).
84
+ *
85
+ * The widget bundle is hub-resident (the same npm package ships to
86
+ * every node and the hub keeps a copy on disk keyed by the bare
87
+ * manifest id), so both the static-file URL and the admin-ui widget
88
+ * namespace must use the bare addon id. Without this, a widget
89
+ * source running on a remote agent yields a `bundleUrl` like
90
+ * `/api/addon-widgets/pipeline-analytics@agent-0/pipeline-analytics/remoteEntry.js`
91
+ * — the embedded `@<node>/<group>` makes the static-file route's
92
+ * `:addonId` param mismatch the registered provider and 404.
93
+ */
94
+ bareAddonId(registryKey) {
95
+ const at = registryKey.indexOf("@");
96
+ return at === -1 ? registryKey : registryKey.slice(0, at);
97
+ }
80
98
  async aggregate() {
81
99
  const entries = this.capabilities?.getCollectionEntries("addon-widgets-source") ?? [];
82
100
  const out = [];
83
101
  const seenIds = /* @__PURE__ */ new Set();
84
- for (const [addonId, source] of entries) {
102
+ for (const [registryKey, source] of entries) {
103
+ const addonId = registryKey;
104
+ const publicAddonId = this.bareAddonId(registryKey);
85
105
  seenIds.add(addonId);
86
106
  try {
87
107
  const enriched = (await Promise.resolve(source.listWidgets())).map((w) => ({
88
108
  ...w,
89
- addonId,
90
- bundleUrl: this.makeBundleUrl(addonId, w.bundle)
109
+ addonId: publicAddonId,
110
+ bundleUrl: this.makeBundleUrl(publicAddonId, w.bundle)
91
111
  }));
92
112
  for (const item of enriched) out.push(item);
93
113
  this.lastGood.set(addonId, enriched);
@@ -125,11 +145,12 @@ var AddonWidgetsAggregatorAddon = class extends _camstack_types.BaseAddon {
125
145
  const found = (this.capabilities?.getCollectionEntries("addon-widgets-source") ?? []).find(([id]) => id === sourceId);
126
146
  if (!found) return;
127
147
  const [addonId, source] = found;
148
+ const publicAddonId = this.bareAddonId(addonId);
128
149
  try {
129
150
  const enriched = (await Promise.resolve(source.listWidgets())).map((w) => ({
130
151
  ...w,
131
- addonId,
132
- bundleUrl: this.makeBundleUrl(addonId, w.bundle)
152
+ addonId: publicAddonId,
153
+ bundleUrl: this.makeBundleUrl(publicAddonId, w.bundle)
133
154
  }));
134
155
  this.lastGood.set(addonId, enriched);
135
156
  this.ctx.logger.info("addon-widgets-source recovered after retry", { meta: {
@@ -146,7 +167,7 @@ var AddonWidgetsAggregatorAddon = class extends _camstack_types.BaseAddon {
146
167
  },
147
168
  category: _camstack_types.EventCategory.AddonWidgetReady,
148
169
  data: {
149
- addonId,
170
+ addonId: publicAddonId,
150
171
  recovered: true
151
172
  }
152
173
  });
@@ -1 +1 @@
1
- {"version":3,"file":"addon-widgets-aggregator.addon.js","names":[],"sources":["../../../src/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.ts"],"sourcesContent":["/**\n * Addon Widgets Aggregator — hub-local builtin that owns the singleton\n * `addon-widgets` cap.\n *\n * Mirrors `addon-pages-aggregator` exactly: walks every registered\n * `addon-widgets-source` (collection) provider and emits an enriched\n * widget metadata list with versioned `bundleUrl`s pointing at\n * `/api/addon-widgets/<addonId>/<bundle>?v=<mtime>`. The filesystem\n * `mtime` cache-buster lets the browser pick up addon rebuilds without\n * manual reload.\n *\n * The static file endpoint (`/api/addon-widgets/:addonId/*`) is served\n * by `AddonWidgetsService.resolveBundle()` on the server side; this\n * addon only owns the listing surface.\n *\n * Why a builtin: same reasoning as `addon-pages-aggregator`. The\n * aggregator is the de-facto \"addon-widgets provider\" — addons own caps,\n * not the server. Living in `@camstack/core/builtins` keeps the surface\n * symmetrical with `system-config`, `local-auth`, etc.\n */\nimport * as path from 'node:path'\nimport * as fs from 'node:fs'\nimport { randomUUID } from 'node:crypto'\nimport {\n BaseAddon,\n EventCategory,\n addonWidgetsCapability,\n errMsg,\n type IAddonWidgetsAggregatorProvider,\n type IAddonWidgetsSourceProvider,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface ResolvedPaths {\n readonly addonsDir: string\n}\n\n/**\n * Inferred from the cap definition — equivalent to:\n * `z.infer<typeof EnrichedWidgetMetadataSchema>` but reuses the\n * provider's return type so the aggregator stays in lockstep with the\n * cap if its shape evolves.\n */\ntype EnrichedWidget = Awaited<ReturnType<IAddonWidgetsAggregatorProvider['listWidgets']>>[number]\ntype RawWidget = Awaited<ReturnType<IAddonWidgetsSourceProvider['listWidgets']>>[number]\n\n/**\n * Backoff schedule (ms) used to retry sources that failed during a\n * `listWidgets()` round-trip — typically because the cap was just\n * registered (provider connected via Moleculer) but the worker-side\n * action registration hadn't propagated yet, so `Service '...listWidgets'\n * is not found on '<node>'` raced ahead of the call.\n *\n * On success we re-emit `AddonWidgetReady` so admin-ui invalidates its\n * `addonWidgets.listWidgets` query and the registry populates without a\n * page reload.\n */\nconst RETRY_BACKOFF_MS: readonly number[] = [500, 1500, 4000]\n\nexport class AddonWidgetsAggregatorAddon extends BaseAddon {\n readonly id = 'addon-widgets-aggregator'\n\n private resolvedPaths: ResolvedPaths | null = null\n\n /**\n * Last successful `listWidgets()` snapshot per source. Used as the\n * \"stale-but-valid\" fallback when a source transiently fails — drops\n * happen often enough during boot (Moleculer service-discovery\n * window) that swallowing the error and returning empty would leave\n * the dashboard with nothing for several seconds. Keeping the\n * previous good entry means a flake is invisible to the operator.\n */\n private readonly lastGood = new Map<string, readonly EnrichedWidget[]>()\n\n /** In-flight retry guards keyed by sourceAddonId. Avoids double-scheduling. */\n private readonly retryTimers = new Map<string, NodeJS.Timeout>()\n\n constructor() { super({}) }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n this.resolvedPaths = await this.resolvePaths()\n\n const provider: IAddonWidgetsAggregatorProvider = {\n listWidgets: async (): Promise<readonly EnrichedWidget[]> => this.aggregate(),\n }\n\n this.ctx.logger.info('Initialized — aggregating addon-widgets-source providers')\n return [{ capability: addonWidgetsCapability, provider }]\n }\n\n protected async onShutdown(): Promise<void> {\n for (const t of this.retryTimers.values()) clearTimeout(t)\n this.retryTimers.clear()\n this.lastGood.clear()\n }\n\n // ── Aggregation ───────────────────────────────────────────────────\n\n private async aggregate(): Promise<readonly EnrichedWidget[]> {\n // `getCollectionEntries` returns `[addonId, provider]` tuples — the\n // raw `addon-widgets-source` cap doesn't carry an `id` on the\n // provider (unlike the legacy `IAddonPageProvider`), so we lean on\n // the registry to attribute each contribution back to its addon.\n const entries = this.capabilities?.getCollectionEntries<IAddonWidgetsSourceProvider>('addon-widgets-source') ?? []\n const out: EnrichedWidget[] = []\n const seenIds = new Set<string>()\n\n for (const [addonId, source] of entries) {\n seenIds.add(addonId)\n try {\n const widgets = await Promise.resolve(source.listWidgets())\n const enriched: EnrichedWidget[] = widgets.map((w: RawWidget) => ({\n ...w,\n addonId,\n bundleUrl: this.makeBundleUrl(addonId, w.bundle),\n }))\n for (const item of enriched) out.push(item)\n // Cache successful snapshot — used as fallback on next failure.\n this.lastGood.set(addonId, enriched)\n } catch (err: unknown) {\n const message = errMsg(err)\n this.ctx.logger.warn('addon-widgets-source provider failed', {\n meta: { sourceId: addonId, error: message },\n })\n // Fall back to the last-good snapshot for this source so a\n // transient Moleculer service-discovery race doesn't blank\n // the dashboard.\n const cached = this.lastGood.get(addonId)\n if (cached !== undefined) {\n for (const item of cached) out.push(item)\n this.ctx.logger.info('addon-widgets-source falling back to cached snapshot', {\n meta: { sourceId: addonId, cachedWidgets: cached.length },\n })\n }\n // Schedule a background retry. On success we re-emit\n // `AddonWidgetReady` so admin-ui's queryClient invalidates and\n // any newly-loaded widgets show up without a manual reload.\n this.scheduleRetry(addonId)\n }\n }\n\n // Drop cache entries for sources that have disappeared from the\n // registry — keeps the fallback aligned with the live collection.\n for (const cachedId of this.lastGood.keys()) {\n if (!seenIds.has(cachedId)) this.lastGood.delete(cachedId)\n }\n\n return out\n }\n\n // ── Retry on transient Moleculer race ─────────────────────────────\n\n private scheduleRetry(sourceId: string, attempt = 0): void {\n if (attempt >= RETRY_BACKOFF_MS.length) return\n if (this.retryTimers.has(sourceId)) return // already pending\n\n const delayMs = RETRY_BACKOFF_MS[attempt] ?? RETRY_BACKOFF_MS[RETRY_BACKOFF_MS.length - 1]!\n const timer = setTimeout(() => {\n this.retryTimers.delete(sourceId)\n void this.retrySource(sourceId, attempt)\n }, delayMs)\n this.retryTimers.set(sourceId, timer)\n }\n\n private async retrySource(sourceId: string, attempt: number): Promise<void> {\n const entries = this.capabilities?.getCollectionEntries<IAddonWidgetsSourceProvider>('addon-widgets-source') ?? []\n const found = entries.find(([id]) => id === sourceId)\n if (!found) return // provider went away; nothing to retry\n const [addonId, source] = found\n\n try {\n const widgets = await Promise.resolve(source.listWidgets())\n const enriched: EnrichedWidget[] = widgets.map((w: RawWidget) => ({\n ...w,\n addonId,\n bundleUrl: this.makeBundleUrl(addonId, w.bundle),\n }))\n this.lastGood.set(addonId, enriched)\n this.ctx.logger.info('addon-widgets-source recovered after retry', {\n meta: { sourceId: addonId, attempt: attempt + 1, widgets: enriched.length },\n })\n // Re-emit AddonWidgetReady so admin-ui invalidates and refetches.\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'addon', id: this.id },\n category: EventCategory.AddonWidgetReady,\n data: { addonId, recovered: true },\n })\n } catch (err: unknown) {\n this.ctx.logger.debug('addon-widgets-source retry failed', {\n meta: { sourceId, attempt: attempt + 1, error: errMsg(err) },\n })\n this.scheduleRetry(sourceId, attempt + 1)\n }\n }\n\n // ── Bundle URL stamping ──────────────────────────────────────────\n\n /**\n * Build `/api/addon-widgets/<addonId>/<bundle>?v=<mtime>`. Falls back\n * to `Date.now()` when the bundle path can't be stat'd (remote addon\n * with no local file, addon not yet on disk, etc.) — the browser\n * just gets a fresh URL on each call instead of cache-friendly mtime.\n */\n private makeBundleUrl(addonId: string, bundle: string): string {\n const bundlePath = this.resolveBundlePath(addonId, bundle)\n let mtime = Date.now()\n if (bundlePath !== null) {\n try { mtime = fs.statSync(bundlePath).mtimeMs }\n catch { /* remote addon — no local file */ }\n }\n const v = Math.floor(mtime)\n return `/api/addon-widgets/${addonId}/${bundle}?v=${v}`\n }\n\n private resolveBundlePath(addonId: string, bundle: string): string | null {\n const paths = this.resolvedPaths\n if (!paths) return null\n const addonDistPath = path.join(paths.addonsDir, '@camstack', `addon-${addonId}`, 'dist')\n const resolvedBase = path.resolve(addonDistPath)\n const resolvedFile = path.resolve(addonDistPath, bundle)\n if (!resolvedFile.startsWith(resolvedBase + path.sep) && resolvedFile !== resolvedBase) {\n return null\n }\n return resolvedFile\n }\n\n // ── Path resolution ──────────────────────────────────────────────\n\n private async resolvePaths(): Promise<ResolvedPaths> {\n const fallback: ResolvedPaths = { addonsDir: path.resolve('camstack-data', 'addons') }\n if (!this.ctx.settings) return fallback\n try {\n const server = await this.ctx.settings.getSection('server')\n const dataPath = typeof server['dataPath'] === 'string' && server['dataPath']\n ? server['dataPath']\n : 'camstack-data'\n return { addonsDir: path.resolve(dataPath, 'addons') }\n } catch (err: unknown) {\n this.ctx.logger.debug('Failed to read server.dataPath — falling back', {\n meta: { error: errMsg(err) },\n })\n return fallback\n }\n }\n}\n\nexport default AddonWidgetsAggregatorAddon\n"],"mappings":"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"}
1
+ {"version":3,"file":"addon-widgets-aggregator.addon.js","names":[],"sources":["../../../src/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.ts"],"sourcesContent":["/**\n * Addon Widgets Aggregator — hub-local builtin that owns the singleton\n * `addon-widgets` cap.\n *\n * Mirrors `addon-pages-aggregator` exactly: walks every registered\n * `addon-widgets-source` (collection) provider and emits an enriched\n * widget metadata list with versioned `bundleUrl`s pointing at\n * `/api/addon-widgets/<addonId>/<bundle>?v=<mtime>`. The filesystem\n * `mtime` cache-buster lets the browser pick up addon rebuilds without\n * manual reload.\n *\n * The static file endpoint (`/api/addon-widgets/:addonId/*`) is served\n * by `AddonWidgetsService.resolveBundle()` on the server side; this\n * addon only owns the listing surface.\n *\n * Why a builtin: same reasoning as `addon-pages-aggregator`. The\n * aggregator is the de-facto \"addon-widgets provider\" — addons own caps,\n * not the server. Living in `@camstack/core/builtins` keeps the surface\n * symmetrical with `system-config`, `local-auth`, etc.\n */\nimport * as path from 'node:path'\nimport * as fs from 'node:fs'\nimport { randomUUID } from 'node:crypto'\nimport {\n BaseAddon,\n EventCategory,\n addonWidgetsCapability,\n errMsg,\n type IAddonWidgetsAggregatorProvider,\n type IAddonWidgetsSourceProvider,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface ResolvedPaths {\n readonly addonsDir: string\n}\n\n/**\n * Inferred from the cap definition — equivalent to:\n * `z.infer<typeof EnrichedWidgetMetadataSchema>` but reuses the\n * provider's return type so the aggregator stays in lockstep with the\n * cap if its shape evolves.\n */\ntype EnrichedWidget = Awaited<ReturnType<IAddonWidgetsAggregatorProvider['listWidgets']>>[number]\ntype RawWidget = Awaited<ReturnType<IAddonWidgetsSourceProvider['listWidgets']>>[number]\n\n/**\n * Backoff schedule (ms) used to retry sources that failed during a\n * `listWidgets()` round-trip — typically because the cap was just\n * registered (provider connected via Moleculer) but the worker-side\n * action registration hadn't propagated yet, so `Service '...listWidgets'\n * is not found on '<node>'` raced ahead of the call.\n *\n * On success we re-emit `AddonWidgetReady` so admin-ui invalidates its\n * `addonWidgets.listWidgets` query and the registry populates without a\n * page reload.\n */\nconst RETRY_BACKOFF_MS: readonly number[] = [500, 1500, 4000]\n\nexport class AddonWidgetsAggregatorAddon extends BaseAddon {\n readonly id = 'addon-widgets-aggregator'\n\n private resolvedPaths: ResolvedPaths | null = null\n\n /**\n * Last successful `listWidgets()` snapshot per source. Used as the\n * \"stale-but-valid\" fallback when a source transiently fails — drops\n * happen often enough during boot (Moleculer service-discovery\n * window) that swallowing the error and returning empty would leave\n * the dashboard with nothing for several seconds. Keeping the\n * previous good entry means a flake is invisible to the operator.\n */\n private readonly lastGood = new Map<string, readonly EnrichedWidget[]>()\n\n /** In-flight retry guards keyed by sourceAddonId. Avoids double-scheduling. */\n private readonly retryTimers = new Map<string, NodeJS.Timeout>()\n\n constructor() { super({}) }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n this.resolvedPaths = await this.resolvePaths()\n\n const provider: IAddonWidgetsAggregatorProvider = {\n listWidgets: async (): Promise<readonly EnrichedWidget[]> => this.aggregate(),\n }\n\n this.ctx.logger.info('Initialized — aggregating addon-widgets-source providers')\n return [{ capability: addonWidgetsCapability, provider }]\n }\n\n protected async onShutdown(): Promise<void> {\n for (const t of this.retryTimers.values()) clearTimeout(t)\n this.retryTimers.clear()\n this.lastGood.clear()\n }\n\n // ── Aggregation ───────────────────────────────────────────────────\n\n /**\n * Strip the `@<nodeId>` suffix that the CapabilityBridge appends to\n * collection-provider registry keys for cross-node addons (see\n * `moleculer.service.ts` — `registryKey = ${addonId}@${nodeId}`).\n *\n * The widget bundle is hub-resident (the same npm package ships to\n * every node and the hub keeps a copy on disk keyed by the bare\n * manifest id), so both the static-file URL and the admin-ui widget\n * namespace must use the bare addon id. Without this, a widget\n * source running on a remote agent yields a `bundleUrl` like\n * `/api/addon-widgets/pipeline-analytics@agent-0/pipeline-analytics/remoteEntry.js`\n * — the embedded `@<node>/<group>` makes the static-file route's\n * `:addonId` param mismatch the registered provider and 404.\n */\n private bareAddonId(registryKey: string): string {\n const at = registryKey.indexOf('@')\n return at === -1 ? registryKey : registryKey.slice(0, at)\n }\n\n private async aggregate(): Promise<readonly EnrichedWidget[]> {\n // `getCollectionEntries` returns `[addonId, provider]` tuples — the\n // raw `addon-widgets-source` cap doesn't carry an `id` on the\n // provider (unlike the legacy `IAddonPageProvider`), so we lean on\n // the registry to attribute each contribution back to its addon.\n const entries = this.capabilities?.getCollectionEntries<IAddonWidgetsSourceProvider>('addon-widgets-source') ?? []\n const out: EnrichedWidget[] = []\n const seenIds = new Set<string>()\n\n for (const [registryKey, source] of entries) {\n // Cache/retry bookkeeping keys on the full registry key so the\n // same addon on multiple nodes stays distinct; the emitted widget\n // metadata uses the bare id (node-agnostic, filesystem-resolvable).\n const addonId = registryKey\n const publicAddonId = this.bareAddonId(registryKey)\n seenIds.add(addonId)\n try {\n const widgets = await Promise.resolve(source.listWidgets())\n const enriched: EnrichedWidget[] = widgets.map((w: RawWidget) => ({\n ...w,\n addonId: publicAddonId,\n bundleUrl: this.makeBundleUrl(publicAddonId, w.bundle),\n }))\n for (const item of enriched) out.push(item)\n // Cache successful snapshot — used as fallback on next failure.\n this.lastGood.set(addonId, enriched)\n } catch (err: unknown) {\n const message = errMsg(err)\n this.ctx.logger.warn('addon-widgets-source provider failed', {\n meta: { sourceId: addonId, error: message },\n })\n // Fall back to the last-good snapshot for this source so a\n // transient Moleculer service-discovery race doesn't blank\n // the dashboard.\n const cached = this.lastGood.get(addonId)\n if (cached !== undefined) {\n for (const item of cached) out.push(item)\n this.ctx.logger.info('addon-widgets-source falling back to cached snapshot', {\n meta: { sourceId: addonId, cachedWidgets: cached.length },\n })\n }\n // Schedule a background retry. On success we re-emit\n // `AddonWidgetReady` so admin-ui's queryClient invalidates and\n // any newly-loaded widgets show up without a manual reload.\n this.scheduleRetry(addonId)\n }\n }\n\n // Drop cache entries for sources that have disappeared from the\n // registry — keeps the fallback aligned with the live collection.\n for (const cachedId of this.lastGood.keys()) {\n if (!seenIds.has(cachedId)) this.lastGood.delete(cachedId)\n }\n\n return out\n }\n\n // ── Retry on transient Moleculer race ─────────────────────────────\n\n private scheduleRetry(sourceId: string, attempt = 0): void {\n if (attempt >= RETRY_BACKOFF_MS.length) return\n if (this.retryTimers.has(sourceId)) return // already pending\n\n const delayMs = RETRY_BACKOFF_MS[attempt] ?? RETRY_BACKOFF_MS[RETRY_BACKOFF_MS.length - 1]!\n const timer = setTimeout(() => {\n this.retryTimers.delete(sourceId)\n void this.retrySource(sourceId, attempt)\n }, delayMs)\n this.retryTimers.set(sourceId, timer)\n }\n\n private async retrySource(sourceId: string, attempt: number): Promise<void> {\n const entries = this.capabilities?.getCollectionEntries<IAddonWidgetsSourceProvider>('addon-widgets-source') ?? []\n const found = entries.find(([id]) => id === sourceId)\n if (!found) return // provider went away; nothing to retry\n const [addonId, source] = found\n const publicAddonId = this.bareAddonId(addonId)\n\n try {\n const widgets = await Promise.resolve(source.listWidgets())\n const enriched: EnrichedWidget[] = widgets.map((w: RawWidget) => ({\n ...w,\n addonId: publicAddonId,\n bundleUrl: this.makeBundleUrl(publicAddonId, w.bundle),\n }))\n this.lastGood.set(addonId, enriched)\n this.ctx.logger.info('addon-widgets-source recovered after retry', {\n meta: { sourceId: addonId, attempt: attempt + 1, widgets: enriched.length },\n })\n // Re-emit AddonWidgetReady so admin-ui invalidates and refetches.\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'addon', id: this.id },\n category: EventCategory.AddonWidgetReady,\n data: { addonId: publicAddonId, recovered: true },\n })\n } catch (err: unknown) {\n this.ctx.logger.debug('addon-widgets-source retry failed', {\n meta: { sourceId, attempt: attempt + 1, error: errMsg(err) },\n })\n this.scheduleRetry(sourceId, attempt + 1)\n }\n }\n\n // ── Bundle URL stamping ──────────────────────────────────────────\n\n /**\n * Build `/api/addon-widgets/<addonId>/<bundle>?v=<mtime>`. Falls back\n * to `Date.now()` when the bundle path can't be stat'd (remote addon\n * with no local file, addon not yet on disk, etc.) — the browser\n * just gets a fresh URL on each call instead of cache-friendly mtime.\n */\n private makeBundleUrl(addonId: string, bundle: string): string {\n const bundlePath = this.resolveBundlePath(addonId, bundle)\n let mtime = Date.now()\n if (bundlePath !== null) {\n try { mtime = fs.statSync(bundlePath).mtimeMs }\n catch { /* remote addon — no local file */ }\n }\n const v = Math.floor(mtime)\n return `/api/addon-widgets/${addonId}/${bundle}?v=${v}`\n }\n\n private resolveBundlePath(addonId: string, bundle: string): string | null {\n const paths = this.resolvedPaths\n if (!paths) return null\n const addonDistPath = path.join(paths.addonsDir, '@camstack', `addon-${addonId}`, 'dist')\n const resolvedBase = path.resolve(addonDistPath)\n const resolvedFile = path.resolve(addonDistPath, bundle)\n if (!resolvedFile.startsWith(resolvedBase + path.sep) && resolvedFile !== resolvedBase) {\n return null\n }\n return resolvedFile\n }\n\n // ── Path resolution ──────────────────────────────────────────────\n\n private async resolvePaths(): Promise<ResolvedPaths> {\n const fallback: ResolvedPaths = { addonsDir: path.resolve('camstack-data', 'addons') }\n if (!this.ctx.settings) return fallback\n try {\n const server = await this.ctx.settings.getSection('server')\n const dataPath = typeof server['dataPath'] === 'string' && server['dataPath']\n ? server['dataPath']\n : 'camstack-data'\n return { addonsDir: path.resolve(dataPath, 'addons') }\n } catch (err: unknown) {\n this.ctx.logger.debug('Failed to read server.dataPath — falling back', {\n meta: { error: errMsg(err) },\n })\n return fallback\n }\n }\n}\n\nexport default AddonWidgetsAggregatorAddon\n"],"mappings":"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"}
@@ -70,17 +70,37 @@ var AddonWidgetsAggregatorAddon = class extends BaseAddon {
70
70
  this.retryTimers.clear();
71
71
  this.lastGood.clear();
72
72
  }
73
+ /**
74
+ * Strip the `@<nodeId>` suffix that the CapabilityBridge appends to
75
+ * collection-provider registry keys for cross-node addons (see
76
+ * `moleculer.service.ts` — `registryKey = ${addonId}@${nodeId}`).
77
+ *
78
+ * The widget bundle is hub-resident (the same npm package ships to
79
+ * every node and the hub keeps a copy on disk keyed by the bare
80
+ * manifest id), so both the static-file URL and the admin-ui widget
81
+ * namespace must use the bare addon id. Without this, a widget
82
+ * source running on a remote agent yields a `bundleUrl` like
83
+ * `/api/addon-widgets/pipeline-analytics@agent-0/pipeline-analytics/remoteEntry.js`
84
+ * — the embedded `@<node>/<group>` makes the static-file route's
85
+ * `:addonId` param mismatch the registered provider and 404.
86
+ */
87
+ bareAddonId(registryKey) {
88
+ const at = registryKey.indexOf("@");
89
+ return at === -1 ? registryKey : registryKey.slice(0, at);
90
+ }
73
91
  async aggregate() {
74
92
  const entries = this.capabilities?.getCollectionEntries("addon-widgets-source") ?? [];
75
93
  const out = [];
76
94
  const seenIds = /* @__PURE__ */ new Set();
77
- for (const [addonId, source] of entries) {
95
+ for (const [registryKey, source] of entries) {
96
+ const addonId = registryKey;
97
+ const publicAddonId = this.bareAddonId(registryKey);
78
98
  seenIds.add(addonId);
79
99
  try {
80
100
  const enriched = (await Promise.resolve(source.listWidgets())).map((w) => ({
81
101
  ...w,
82
- addonId,
83
- bundleUrl: this.makeBundleUrl(addonId, w.bundle)
102
+ addonId: publicAddonId,
103
+ bundleUrl: this.makeBundleUrl(publicAddonId, w.bundle)
84
104
  }));
85
105
  for (const item of enriched) out.push(item);
86
106
  this.lastGood.set(addonId, enriched);
@@ -118,11 +138,12 @@ var AddonWidgetsAggregatorAddon = class extends BaseAddon {
118
138
  const found = (this.capabilities?.getCollectionEntries("addon-widgets-source") ?? []).find(([id]) => id === sourceId);
119
139
  if (!found) return;
120
140
  const [addonId, source] = found;
141
+ const publicAddonId = this.bareAddonId(addonId);
121
142
  try {
122
143
  const enriched = (await Promise.resolve(source.listWidgets())).map((w) => ({
123
144
  ...w,
124
- addonId,
125
- bundleUrl: this.makeBundleUrl(addonId, w.bundle)
145
+ addonId: publicAddonId,
146
+ bundleUrl: this.makeBundleUrl(publicAddonId, w.bundle)
126
147
  }));
127
148
  this.lastGood.set(addonId, enriched);
128
149
  this.ctx.logger.info("addon-widgets-source recovered after retry", { meta: {
@@ -139,7 +160,7 @@ var AddonWidgetsAggregatorAddon = class extends BaseAddon {
139
160
  },
140
161
  category: EventCategory.AddonWidgetReady,
141
162
  data: {
142
- addonId,
163
+ addonId: publicAddonId,
143
164
  recovered: true
144
165
  }
145
166
  });
@@ -1 +1 @@
1
- {"version":3,"file":"addon-widgets-aggregator.addon.mjs","names":[],"sources":["../../../src/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.ts"],"sourcesContent":["/**\n * Addon Widgets Aggregator — hub-local builtin that owns the singleton\n * `addon-widgets` cap.\n *\n * Mirrors `addon-pages-aggregator` exactly: walks every registered\n * `addon-widgets-source` (collection) provider and emits an enriched\n * widget metadata list with versioned `bundleUrl`s pointing at\n * `/api/addon-widgets/<addonId>/<bundle>?v=<mtime>`. The filesystem\n * `mtime` cache-buster lets the browser pick up addon rebuilds without\n * manual reload.\n *\n * The static file endpoint (`/api/addon-widgets/:addonId/*`) is served\n * by `AddonWidgetsService.resolveBundle()` on the server side; this\n * addon only owns the listing surface.\n *\n * Why a builtin: same reasoning as `addon-pages-aggregator`. The\n * aggregator is the de-facto \"addon-widgets provider\" — addons own caps,\n * not the server. Living in `@camstack/core/builtins` keeps the surface\n * symmetrical with `system-config`, `local-auth`, etc.\n */\nimport * as path from 'node:path'\nimport * as fs from 'node:fs'\nimport { randomUUID } from 'node:crypto'\nimport {\n BaseAddon,\n EventCategory,\n addonWidgetsCapability,\n errMsg,\n type IAddonWidgetsAggregatorProvider,\n type IAddonWidgetsSourceProvider,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface ResolvedPaths {\n readonly addonsDir: string\n}\n\n/**\n * Inferred from the cap definition — equivalent to:\n * `z.infer<typeof EnrichedWidgetMetadataSchema>` but reuses the\n * provider's return type so the aggregator stays in lockstep with the\n * cap if its shape evolves.\n */\ntype EnrichedWidget = Awaited<ReturnType<IAddonWidgetsAggregatorProvider['listWidgets']>>[number]\ntype RawWidget = Awaited<ReturnType<IAddonWidgetsSourceProvider['listWidgets']>>[number]\n\n/**\n * Backoff schedule (ms) used to retry sources that failed during a\n * `listWidgets()` round-trip — typically because the cap was just\n * registered (provider connected via Moleculer) but the worker-side\n * action registration hadn't propagated yet, so `Service '...listWidgets'\n * is not found on '<node>'` raced ahead of the call.\n *\n * On success we re-emit `AddonWidgetReady` so admin-ui invalidates its\n * `addonWidgets.listWidgets` query and the registry populates without a\n * page reload.\n */\nconst RETRY_BACKOFF_MS: readonly number[] = [500, 1500, 4000]\n\nexport class AddonWidgetsAggregatorAddon extends BaseAddon {\n readonly id = 'addon-widgets-aggregator'\n\n private resolvedPaths: ResolvedPaths | null = null\n\n /**\n * Last successful `listWidgets()` snapshot per source. Used as the\n * \"stale-but-valid\" fallback when a source transiently fails — drops\n * happen often enough during boot (Moleculer service-discovery\n * window) that swallowing the error and returning empty would leave\n * the dashboard with nothing for several seconds. Keeping the\n * previous good entry means a flake is invisible to the operator.\n */\n private readonly lastGood = new Map<string, readonly EnrichedWidget[]>()\n\n /** In-flight retry guards keyed by sourceAddonId. Avoids double-scheduling. */\n private readonly retryTimers = new Map<string, NodeJS.Timeout>()\n\n constructor() { super({}) }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n this.resolvedPaths = await this.resolvePaths()\n\n const provider: IAddonWidgetsAggregatorProvider = {\n listWidgets: async (): Promise<readonly EnrichedWidget[]> => this.aggregate(),\n }\n\n this.ctx.logger.info('Initialized — aggregating addon-widgets-source providers')\n return [{ capability: addonWidgetsCapability, provider }]\n }\n\n protected async onShutdown(): Promise<void> {\n for (const t of this.retryTimers.values()) clearTimeout(t)\n this.retryTimers.clear()\n this.lastGood.clear()\n }\n\n // ── Aggregation ───────────────────────────────────────────────────\n\n private async aggregate(): Promise<readonly EnrichedWidget[]> {\n // `getCollectionEntries` returns `[addonId, provider]` tuples — the\n // raw `addon-widgets-source` cap doesn't carry an `id` on the\n // provider (unlike the legacy `IAddonPageProvider`), so we lean on\n // the registry to attribute each contribution back to its addon.\n const entries = this.capabilities?.getCollectionEntries<IAddonWidgetsSourceProvider>('addon-widgets-source') ?? []\n const out: EnrichedWidget[] = []\n const seenIds = new Set<string>()\n\n for (const [addonId, source] of entries) {\n seenIds.add(addonId)\n try {\n const widgets = await Promise.resolve(source.listWidgets())\n const enriched: EnrichedWidget[] = widgets.map((w: RawWidget) => ({\n ...w,\n addonId,\n bundleUrl: this.makeBundleUrl(addonId, w.bundle),\n }))\n for (const item of enriched) out.push(item)\n // Cache successful snapshot — used as fallback on next failure.\n this.lastGood.set(addonId, enriched)\n } catch (err: unknown) {\n const message = errMsg(err)\n this.ctx.logger.warn('addon-widgets-source provider failed', {\n meta: { sourceId: addonId, error: message },\n })\n // Fall back to the last-good snapshot for this source so a\n // transient Moleculer service-discovery race doesn't blank\n // the dashboard.\n const cached = this.lastGood.get(addonId)\n if (cached !== undefined) {\n for (const item of cached) out.push(item)\n this.ctx.logger.info('addon-widgets-source falling back to cached snapshot', {\n meta: { sourceId: addonId, cachedWidgets: cached.length },\n })\n }\n // Schedule a background retry. On success we re-emit\n // `AddonWidgetReady` so admin-ui's queryClient invalidates and\n // any newly-loaded widgets show up without a manual reload.\n this.scheduleRetry(addonId)\n }\n }\n\n // Drop cache entries for sources that have disappeared from the\n // registry — keeps the fallback aligned with the live collection.\n for (const cachedId of this.lastGood.keys()) {\n if (!seenIds.has(cachedId)) this.lastGood.delete(cachedId)\n }\n\n return out\n }\n\n // ── Retry on transient Moleculer race ─────────────────────────────\n\n private scheduleRetry(sourceId: string, attempt = 0): void {\n if (attempt >= RETRY_BACKOFF_MS.length) return\n if (this.retryTimers.has(sourceId)) return // already pending\n\n const delayMs = RETRY_BACKOFF_MS[attempt] ?? RETRY_BACKOFF_MS[RETRY_BACKOFF_MS.length - 1]!\n const timer = setTimeout(() => {\n this.retryTimers.delete(sourceId)\n void this.retrySource(sourceId, attempt)\n }, delayMs)\n this.retryTimers.set(sourceId, timer)\n }\n\n private async retrySource(sourceId: string, attempt: number): Promise<void> {\n const entries = this.capabilities?.getCollectionEntries<IAddonWidgetsSourceProvider>('addon-widgets-source') ?? []\n const found = entries.find(([id]) => id === sourceId)\n if (!found) return // provider went away; nothing to retry\n const [addonId, source] = found\n\n try {\n const widgets = await Promise.resolve(source.listWidgets())\n const enriched: EnrichedWidget[] = widgets.map((w: RawWidget) => ({\n ...w,\n addonId,\n bundleUrl: this.makeBundleUrl(addonId, w.bundle),\n }))\n this.lastGood.set(addonId, enriched)\n this.ctx.logger.info('addon-widgets-source recovered after retry', {\n meta: { sourceId: addonId, attempt: attempt + 1, widgets: enriched.length },\n })\n // Re-emit AddonWidgetReady so admin-ui invalidates and refetches.\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'addon', id: this.id },\n category: EventCategory.AddonWidgetReady,\n data: { addonId, recovered: true },\n })\n } catch (err: unknown) {\n this.ctx.logger.debug('addon-widgets-source retry failed', {\n meta: { sourceId, attempt: attempt + 1, error: errMsg(err) },\n })\n this.scheduleRetry(sourceId, attempt + 1)\n }\n }\n\n // ── Bundle URL stamping ──────────────────────────────────────────\n\n /**\n * Build `/api/addon-widgets/<addonId>/<bundle>?v=<mtime>`. Falls back\n * to `Date.now()` when the bundle path can't be stat'd (remote addon\n * with no local file, addon not yet on disk, etc.) — the browser\n * just gets a fresh URL on each call instead of cache-friendly mtime.\n */\n private makeBundleUrl(addonId: string, bundle: string): string {\n const bundlePath = this.resolveBundlePath(addonId, bundle)\n let mtime = Date.now()\n if (bundlePath !== null) {\n try { mtime = fs.statSync(bundlePath).mtimeMs }\n catch { /* remote addon — no local file */ }\n }\n const v = Math.floor(mtime)\n return `/api/addon-widgets/${addonId}/${bundle}?v=${v}`\n }\n\n private resolveBundlePath(addonId: string, bundle: string): string | null {\n const paths = this.resolvedPaths\n if (!paths) return null\n const addonDistPath = path.join(paths.addonsDir, '@camstack', `addon-${addonId}`, 'dist')\n const resolvedBase = path.resolve(addonDistPath)\n const resolvedFile = path.resolve(addonDistPath, bundle)\n if (!resolvedFile.startsWith(resolvedBase + path.sep) && resolvedFile !== resolvedBase) {\n return null\n }\n return resolvedFile\n }\n\n // ── Path resolution ──────────────────────────────────────────────\n\n private async resolvePaths(): Promise<ResolvedPaths> {\n const fallback: ResolvedPaths = { addonsDir: path.resolve('camstack-data', 'addons') }\n if (!this.ctx.settings) return fallback\n try {\n const server = await this.ctx.settings.getSection('server')\n const dataPath = typeof server['dataPath'] === 'string' && server['dataPath']\n ? server['dataPath']\n : 'camstack-data'\n return { addonsDir: path.resolve(dataPath, 'addons') }\n } catch (err: unknown) {\n this.ctx.logger.debug('Failed to read server.dataPath — falling back', {\n meta: { error: errMsg(err) },\n })\n return fallback\n }\n }\n}\n\nexport default AddonWidgetsAggregatorAddon\n"],"mappings":"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"}
1
+ {"version":3,"file":"addon-widgets-aggregator.addon.mjs","names":[],"sources":["../../../src/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.ts"],"sourcesContent":["/**\n * Addon Widgets Aggregator — hub-local builtin that owns the singleton\n * `addon-widgets` cap.\n *\n * Mirrors `addon-pages-aggregator` exactly: walks every registered\n * `addon-widgets-source` (collection) provider and emits an enriched\n * widget metadata list with versioned `bundleUrl`s pointing at\n * `/api/addon-widgets/<addonId>/<bundle>?v=<mtime>`. The filesystem\n * `mtime` cache-buster lets the browser pick up addon rebuilds without\n * manual reload.\n *\n * The static file endpoint (`/api/addon-widgets/:addonId/*`) is served\n * by `AddonWidgetsService.resolveBundle()` on the server side; this\n * addon only owns the listing surface.\n *\n * Why a builtin: same reasoning as `addon-pages-aggregator`. The\n * aggregator is the de-facto \"addon-widgets provider\" — addons own caps,\n * not the server. Living in `@camstack/core/builtins` keeps the surface\n * symmetrical with `system-config`, `local-auth`, etc.\n */\nimport * as path from 'node:path'\nimport * as fs from 'node:fs'\nimport { randomUUID } from 'node:crypto'\nimport {\n BaseAddon,\n EventCategory,\n addonWidgetsCapability,\n errMsg,\n type IAddonWidgetsAggregatorProvider,\n type IAddonWidgetsSourceProvider,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface ResolvedPaths {\n readonly addonsDir: string\n}\n\n/**\n * Inferred from the cap definition — equivalent to:\n * `z.infer<typeof EnrichedWidgetMetadataSchema>` but reuses the\n * provider's return type so the aggregator stays in lockstep with the\n * cap if its shape evolves.\n */\ntype EnrichedWidget = Awaited<ReturnType<IAddonWidgetsAggregatorProvider['listWidgets']>>[number]\ntype RawWidget = Awaited<ReturnType<IAddonWidgetsSourceProvider['listWidgets']>>[number]\n\n/**\n * Backoff schedule (ms) used to retry sources that failed during a\n * `listWidgets()` round-trip — typically because the cap was just\n * registered (provider connected via Moleculer) but the worker-side\n * action registration hadn't propagated yet, so `Service '...listWidgets'\n * is not found on '<node>'` raced ahead of the call.\n *\n * On success we re-emit `AddonWidgetReady` so admin-ui invalidates its\n * `addonWidgets.listWidgets` query and the registry populates without a\n * page reload.\n */\nconst RETRY_BACKOFF_MS: readonly number[] = [500, 1500, 4000]\n\nexport class AddonWidgetsAggregatorAddon extends BaseAddon {\n readonly id = 'addon-widgets-aggregator'\n\n private resolvedPaths: ResolvedPaths | null = null\n\n /**\n * Last successful `listWidgets()` snapshot per source. Used as the\n * \"stale-but-valid\" fallback when a source transiently fails — drops\n * happen often enough during boot (Moleculer service-discovery\n * window) that swallowing the error and returning empty would leave\n * the dashboard with nothing for several seconds. Keeping the\n * previous good entry means a flake is invisible to the operator.\n */\n private readonly lastGood = new Map<string, readonly EnrichedWidget[]>()\n\n /** In-flight retry guards keyed by sourceAddonId. Avoids double-scheduling. */\n private readonly retryTimers = new Map<string, NodeJS.Timeout>()\n\n constructor() { super({}) }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n this.resolvedPaths = await this.resolvePaths()\n\n const provider: IAddonWidgetsAggregatorProvider = {\n listWidgets: async (): Promise<readonly EnrichedWidget[]> => this.aggregate(),\n }\n\n this.ctx.logger.info('Initialized — aggregating addon-widgets-source providers')\n return [{ capability: addonWidgetsCapability, provider }]\n }\n\n protected async onShutdown(): Promise<void> {\n for (const t of this.retryTimers.values()) clearTimeout(t)\n this.retryTimers.clear()\n this.lastGood.clear()\n }\n\n // ── Aggregation ───────────────────────────────────────────────────\n\n /**\n * Strip the `@<nodeId>` suffix that the CapabilityBridge appends to\n * collection-provider registry keys for cross-node addons (see\n * `moleculer.service.ts` — `registryKey = ${addonId}@${nodeId}`).\n *\n * The widget bundle is hub-resident (the same npm package ships to\n * every node and the hub keeps a copy on disk keyed by the bare\n * manifest id), so both the static-file URL and the admin-ui widget\n * namespace must use the bare addon id. Without this, a widget\n * source running on a remote agent yields a `bundleUrl` like\n * `/api/addon-widgets/pipeline-analytics@agent-0/pipeline-analytics/remoteEntry.js`\n * — the embedded `@<node>/<group>` makes the static-file route's\n * `:addonId` param mismatch the registered provider and 404.\n */\n private bareAddonId(registryKey: string): string {\n const at = registryKey.indexOf('@')\n return at === -1 ? registryKey : registryKey.slice(0, at)\n }\n\n private async aggregate(): Promise<readonly EnrichedWidget[]> {\n // `getCollectionEntries` returns `[addonId, provider]` tuples — the\n // raw `addon-widgets-source` cap doesn't carry an `id` on the\n // provider (unlike the legacy `IAddonPageProvider`), so we lean on\n // the registry to attribute each contribution back to its addon.\n const entries = this.capabilities?.getCollectionEntries<IAddonWidgetsSourceProvider>('addon-widgets-source') ?? []\n const out: EnrichedWidget[] = []\n const seenIds = new Set<string>()\n\n for (const [registryKey, source] of entries) {\n // Cache/retry bookkeeping keys on the full registry key so the\n // same addon on multiple nodes stays distinct; the emitted widget\n // metadata uses the bare id (node-agnostic, filesystem-resolvable).\n const addonId = registryKey\n const publicAddonId = this.bareAddonId(registryKey)\n seenIds.add(addonId)\n try {\n const widgets = await Promise.resolve(source.listWidgets())\n const enriched: EnrichedWidget[] = widgets.map((w: RawWidget) => ({\n ...w,\n addonId: publicAddonId,\n bundleUrl: this.makeBundleUrl(publicAddonId, w.bundle),\n }))\n for (const item of enriched) out.push(item)\n // Cache successful snapshot — used as fallback on next failure.\n this.lastGood.set(addonId, enriched)\n } catch (err: unknown) {\n const message = errMsg(err)\n this.ctx.logger.warn('addon-widgets-source provider failed', {\n meta: { sourceId: addonId, error: message },\n })\n // Fall back to the last-good snapshot for this source so a\n // transient Moleculer service-discovery race doesn't blank\n // the dashboard.\n const cached = this.lastGood.get(addonId)\n if (cached !== undefined) {\n for (const item of cached) out.push(item)\n this.ctx.logger.info('addon-widgets-source falling back to cached snapshot', {\n meta: { sourceId: addonId, cachedWidgets: cached.length },\n })\n }\n // Schedule a background retry. On success we re-emit\n // `AddonWidgetReady` so admin-ui's queryClient invalidates and\n // any newly-loaded widgets show up without a manual reload.\n this.scheduleRetry(addonId)\n }\n }\n\n // Drop cache entries for sources that have disappeared from the\n // registry — keeps the fallback aligned with the live collection.\n for (const cachedId of this.lastGood.keys()) {\n if (!seenIds.has(cachedId)) this.lastGood.delete(cachedId)\n }\n\n return out\n }\n\n // ── Retry on transient Moleculer race ─────────────────────────────\n\n private scheduleRetry(sourceId: string, attempt = 0): void {\n if (attempt >= RETRY_BACKOFF_MS.length) return\n if (this.retryTimers.has(sourceId)) return // already pending\n\n const delayMs = RETRY_BACKOFF_MS[attempt] ?? RETRY_BACKOFF_MS[RETRY_BACKOFF_MS.length - 1]!\n const timer = setTimeout(() => {\n this.retryTimers.delete(sourceId)\n void this.retrySource(sourceId, attempt)\n }, delayMs)\n this.retryTimers.set(sourceId, timer)\n }\n\n private async retrySource(sourceId: string, attempt: number): Promise<void> {\n const entries = this.capabilities?.getCollectionEntries<IAddonWidgetsSourceProvider>('addon-widgets-source') ?? []\n const found = entries.find(([id]) => id === sourceId)\n if (!found) return // provider went away; nothing to retry\n const [addonId, source] = found\n const publicAddonId = this.bareAddonId(addonId)\n\n try {\n const widgets = await Promise.resolve(source.listWidgets())\n const enriched: EnrichedWidget[] = widgets.map((w: RawWidget) => ({\n ...w,\n addonId: publicAddonId,\n bundleUrl: this.makeBundleUrl(publicAddonId, w.bundle),\n }))\n this.lastGood.set(addonId, enriched)\n this.ctx.logger.info('addon-widgets-source recovered after retry', {\n meta: { sourceId: addonId, attempt: attempt + 1, widgets: enriched.length },\n })\n // Re-emit AddonWidgetReady so admin-ui invalidates and refetches.\n this.ctx.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'addon', id: this.id },\n category: EventCategory.AddonWidgetReady,\n data: { addonId: publicAddonId, recovered: true },\n })\n } catch (err: unknown) {\n this.ctx.logger.debug('addon-widgets-source retry failed', {\n meta: { sourceId, attempt: attempt + 1, error: errMsg(err) },\n })\n this.scheduleRetry(sourceId, attempt + 1)\n }\n }\n\n // ── Bundle URL stamping ──────────────────────────────────────────\n\n /**\n * Build `/api/addon-widgets/<addonId>/<bundle>?v=<mtime>`. Falls back\n * to `Date.now()` when the bundle path can't be stat'd (remote addon\n * with no local file, addon not yet on disk, etc.) — the browser\n * just gets a fresh URL on each call instead of cache-friendly mtime.\n */\n private makeBundleUrl(addonId: string, bundle: string): string {\n const bundlePath = this.resolveBundlePath(addonId, bundle)\n let mtime = Date.now()\n if (bundlePath !== null) {\n try { mtime = fs.statSync(bundlePath).mtimeMs }\n catch { /* remote addon — no local file */ }\n }\n const v = Math.floor(mtime)\n return `/api/addon-widgets/${addonId}/${bundle}?v=${v}`\n }\n\n private resolveBundlePath(addonId: string, bundle: string): string | null {\n const paths = this.resolvedPaths\n if (!paths) return null\n const addonDistPath = path.join(paths.addonsDir, '@camstack', `addon-${addonId}`, 'dist')\n const resolvedBase = path.resolve(addonDistPath)\n const resolvedFile = path.resolve(addonDistPath, bundle)\n if (!resolvedFile.startsWith(resolvedBase + path.sep) && resolvedFile !== resolvedBase) {\n return null\n }\n return resolvedFile\n }\n\n // ── Path resolution ──────────────────────────────────────────────\n\n private async resolvePaths(): Promise<ResolvedPaths> {\n const fallback: ResolvedPaths = { addonsDir: path.resolve('camstack-data', 'addons') }\n if (!this.ctx.settings) return fallback\n try {\n const server = await this.ctx.settings.getSection('server')\n const dataPath = typeof server['dataPath'] === 'string' && server['dataPath']\n ? server['dataPath']\n : 'camstack-data'\n return { addonsDir: path.resolve(dataPath, 'addons') }\n } catch (err: unknown) {\n this.ctx.logger.debug('Failed to read server.dataPath — falling back', {\n meta: { error: errMsg(err) },\n })\n return fallback\n }\n }\n}\n\nexport default AddonWidgetsAggregatorAddon\n"],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { StreamProfile, StreamProfilePatch } from '@camstack/types';
2
+ /** A framework-derived contribution shape — sections[] (+ optional tabs[]). */
3
+ export interface DerivedContributionShape {
4
+ readonly tabs?: ReadonlyArray<{
5
+ id: string;
6
+ label: string;
7
+ icon: string;
8
+ order?: number;
9
+ }>;
10
+ readonly sections: ReadonlyArray<{
11
+ id: string;
12
+ title: string;
13
+ tab?: string;
14
+ order?: number;
15
+ description?: string;
16
+ columns?: 1 | 2 | 3 | 4;
17
+ fields: readonly unknown[];
18
+ }>;
19
+ }
20
+ /** A per-profile mutation callback the cap's provider supplies. */
21
+ export type ProfileSetter = (profile: StreamProfile, patch: StreamProfilePatch) => Promise<void>;
22
+ /** The single registered builderId today. New device-config caps add their own. */
23
+ export declare const STREAM_PARAMS_BUILDER_ID: "stream-params";
24
+ /**
25
+ * Build the device-detail form section for a `derived-form` device-config
26
+ * cap. Returns null when the camera exposes no configurable property.
27
+ */
28
+ export declare function deriveFormContribution(builderId: string, options: unknown, status: unknown): DerivedContributionShape | null;
29
+ /**
30
+ * Route a flat form patch back through the cap's per-profile setter.
31
+ */
32
+ export declare function applyDerivedFormPatch(builderId: string, patch: Record<string, unknown>, setProfile: ProfileSetter): Promise<void>;
33
+ //# sourceMappingURL=device-config-contribution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-config-contribution.d.ts","sourceRoot":"","sources":["../../../src/builtins/device-manager/device-config-contribution.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAGV,aAAa,EACb,kBAAkB,EAEnB,MAAM,iBAAiB,CAAA;AAExB,+EAA+E;AAC/E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC1F,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;QAC/B,EAAE,EAAE,MAAM,CAAA;QACV,KAAK,EAAE,MAAM,CAAA;QACb,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACvB,MAAM,EAAE,SAAS,OAAO,EAAE,CAAA;KAC3B,CAAC,CAAA;CACH;AAED,mEAAmE;AACnE,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAEhG,mFAAmF;AACnF,eAAO,MAAM,wBAAwB,EAAG,eAAwB,CAAA;AAEhE;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,OAAO,GACd,wBAAwB,GAAG,IAAI,CAoBjC;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,UAAU,EAAE,aAAa,GACxB,OAAO,CAAC,IAAI,CAAC,CAQf"}
@@ -65,11 +65,21 @@ export declare class DeviceManagerAddon extends BaseAddon {
65
65
  private static readonly RUNTIME_STATE_DEBOUNCE_MS;
66
66
  /**
67
67
  * Cross-process native-provider cache: deviceId (numeric) → capName → { addonId, nodeId }.
68
- * Kept in sync with `device.bindings-changed` events emitted by forked
69
- * workers. Union'd into `getBindings` so hub-side consumers see every
70
- * native cap regardless of which process owns the IDevice. No persistence
71
- * entries re-register when the worker restarts. Purged on
72
- * `$node.disconnected` to avoid stale routing after a worker crash.
68
+ * Kept in sync with `DeviceBindingsChanged` push events emitted by forked
69
+ * workers on `ctx.registerNativeCap` / device removal. Union'd into
70
+ * `getBindings` so hub-side consumers see every native cap regardless of
71
+ * which process owns the IDevice.
72
+ *
73
+ * No persistence — entries re-populate when the worker re-handshakes or
74
+ * re-emits its native-cap registrations after restart. Entries that were
75
+ * lost in the Moleculer transport handshake window are recovered lazily:
76
+ * `resolveNativeCapOwnerSync` and `getBindings` fall through to
77
+ * `ctx.kernel.listClusterNativeCaps()` (the handshake-fed
78
+ * `HubNodeRegistry`) when the push-based cache misses.
79
+ *
80
+ * The previous pull-based recovery (`syncWorkerNativeCaps`, driven by
81
+ * `$node.connected` + `addon.restarted`) has been removed in Task 13 —
82
+ * the D3 re-handshake after device restore is the reliable replacement.
73
83
  */
74
84
  private remoteNativeCaps;
75
85
  /** Wait for a device-provider by addonId, returning null on timeout. */
@@ -80,20 +90,17 @@ export declare class DeviceManagerAddon extends BaseAddon {
80
90
  private writeBindingsStore;
81
91
  private resolveWrapperNodeId;
82
92
  /**
83
- * Active discovery of native caps registered on a worker node.
84
- * Called from the `$node.connected` handler to recover from lost
85
- * `DeviceBindingsChanged` broadcasts after worker restart.
86
- *
87
- * Iterates the broker's service registry, picks
88
- * `<addonId>.native-provider.<capName>` services owned by the
89
- * connected node, calls each service's `$listDeviceIds` action to
90
- * fetch the deviceIds the worker has registered that cap on, then
91
- * writes entries into `remoteNativeCaps`.
93
+ * Resolve a remote native cap entry for a given `(capName, deviceId)` by
94
+ * consulting the handshake-fed `HubNodeRegistry` via
95
+ * `ctx.kernel.listClusterNativeCaps()`. Called when the push-based
96
+ * `remoteNativeCaps` cache misses — covers the Moleculer transport
97
+ * handshake window where `DeviceBindingsChanged` events were lost but the
98
+ * D3 re-handshake (post device restore) has already populated the registry.
92
99
  *
93
- * Best-effort: per-service failures are logged and swallowed so a
94
- * single bad service doesn't abort the whole rebuild.
100
+ * Returns `null` when the entry is genuinely not present in the cluster
101
+ * view (cap not registered on any worker for that device).
95
102
  */
96
- private discoverWorkerNativeCaps;
103
+ private resolveRemoteNativeCapFromRegistry;
97
104
  getBindings(input: {
98
105
  deviceId: number;
99
106
  }): Promise<{
@@ -124,6 +131,21 @@ export declare class DeviceManagerAddon extends BaseAddon {
124
131
  */
125
132
  private lookupPersistedStableId;
126
133
  getDeviceAggregate(deviceId: number, kind: 'settings' | 'live'): Promise<ContributionShape | null>;
134
+ /**
135
+ * D14: framework-derived device-config contribution.
136
+ *
137
+ * `kind === 'live'` — device-config caps contribute nothing to the live
138
+ * aggregate (they hold editable config, not live observables).
139
+ *
140
+ * `ui.kind === 'widget'` — emits a single structural `type:'widget'`
141
+ * section; the widget self-persists via the cap's own mutations.
142
+ *
143
+ * `ui.kind === 'derived-form'` — calls `getOptions`/`getStatus` on the
144
+ * bound provider, runs the registered pure builder, and returns the
145
+ * derived form sections. Returns null when the camera exposes nothing
146
+ * configurable or the provider is not yet registered.
147
+ */
148
+ private deriveDeviceConfigContribution;
127
149
  /**
128
150
  * Build the device-manager's own contribution to the aggregator — the
129
151
  * device identity (id, stableId, addonId, type, online) + the
@@ -172,6 +194,11 @@ export declare class DeviceManagerAddon extends BaseAddon {
172
194
  * devices call `getSettingsUISchema()` directly; forked-worker devices
173
195
  * go through the `device-ops.getSettingsSchema` cap method on the
174
196
  * numeric-id-keyed native registry.
197
+ *
198
+ * Returns a discriminated result so callers can distinguish three states:
199
+ * 'ok' – schema obtained successfully
200
+ * 'none' – driver genuinely has no settings schema
201
+ * 'unavailable' – worker was unreachable after retries (transient)
175
202
  */
176
203
  private resolveDriverConfigSchema;
177
204
  updateDeviceField(input: {
@@ -183,6 +210,14 @@ export declare class DeviceManagerAddon extends BaseAddon {
183
210
  }): Promise<{
184
211
  success: true;
185
212
  }>;
213
+ /**
214
+ * Resolve the `DeviceSettingsContribution` provider that owns a tagged
215
+ * field. System-scoped caps resolve via `getProviderByAddon`; native
216
+ * device-scoped caps (registered per-device via `registerNativeCap`)
217
+ * live in the separate native map and resolve via `getNativeProvider`.
218
+ * Throws only if BOTH lookups miss.
219
+ */
220
+ private resolveContributionProvider;
186
221
  /**
187
222
  * Batched counterpart of `updateDeviceField`. Groups changes by
188
223
  * `(writerCapName, writerAddonId)` so each contributor receives a
@@ -1 +1 @@
1
- {"version":3,"file":"device-manager.addon.d.ts","sourceRoot":"","sources":["../../../src/builtins/device-manager/device-manager.addon.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"device-manager.addon.d.ts","sourceRoot":"","sources":["../../../src/builtins/device-manager/device-manager.addon.ts"],"names":[],"mappings":"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"}