@camstack/core 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +220 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js.map +1 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +9 -0
- package/dist/builtins/addon-pages-aggregator/index.js +222 -0
- package/dist/builtins/addon-pages-aggregator/index.js.map +1 -0
- package/dist/builtins/addon-pages-aggregator/index.mjs +9 -0
- package/dist/builtins/addon-pages-aggregator/index.mjs.map +1 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +200 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js.map +1 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +9 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs.map +1 -0
- package/dist/builtins/addon-widgets-aggregator/index.js +202 -0
- package/dist/builtins/addon-widgets-aggregator/index.js.map +1 -0
- package/dist/builtins/addon-widgets-aggregator/index.mjs +9 -0
- package/dist/builtins/addon-widgets-aggregator/index.mjs.map +1 -0
- package/dist/builtins/alerts/alerts.addon.js +443 -0
- package/dist/builtins/alerts/alerts.addon.js.map +1 -0
- package/dist/builtins/alerts/alerts.addon.mjs +9 -0
- package/dist/builtins/alerts/alerts.addon.mjs.map +1 -0
- package/dist/builtins/alerts/index.js +443 -0
- package/dist/builtins/alerts/index.js.map +1 -0
- package/dist/builtins/alerts/index.mjs +8 -0
- package/dist/builtins/alerts/index.mjs.map +1 -0
- package/dist/builtins/console-logging/index.js +242 -0
- package/dist/builtins/console-logging/index.js.map +1 -0
- package/dist/builtins/console-logging/index.mjs +11 -0
- package/dist/builtins/console-logging/index.mjs.map +1 -0
- package/dist/builtins/device-manager/device-manager.addon.js +2155 -0
- package/dist/builtins/device-manager/device-manager.addon.js.map +1 -0
- package/dist/builtins/device-manager/device-manager.addon.mjs +9 -0
- package/dist/builtins/device-manager/device-manager.addon.mjs.map +1 -0
- package/dist/builtins/device-manager/index.js +2157 -0
- package/dist/builtins/device-manager/index.js.map +1 -0
- package/dist/builtins/device-manager/index.mjs +10 -0
- package/dist/builtins/device-manager/index.mjs.map +1 -0
- package/dist/builtins/hub-forwarder/index.js +297 -0
- package/dist/builtins/hub-forwarder/index.js.map +1 -0
- package/dist/builtins/hub-forwarder/index.mjs +11 -0
- package/dist/builtins/hub-forwarder/index.mjs.map +1 -0
- package/dist/builtins/local-auth/index.js +623 -0
- package/dist/builtins/local-auth/index.js.map +1 -0
- package/dist/builtins/local-auth/index.mjs +8 -0
- package/dist/builtins/local-auth/index.mjs.map +1 -0
- package/dist/builtins/local-auth/local-auth.addon.js +623 -0
- package/dist/builtins/local-auth/local-auth.addon.js.map +1 -0
- package/dist/builtins/local-auth/local-auth.addon.mjs +9 -0
- package/dist/builtins/local-auth/local-auth.addon.mjs.map +1 -0
- package/dist/builtins/local-backup/index.js +53 -68
- package/dist/builtins/local-backup/index.js.map +1 -1
- package/dist/builtins/local-backup/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +898 -0
- package/dist/builtins/native-metrics/native-metrics.addon.js.map +1 -0
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +7 -0
- package/dist/builtins/native-metrics/native-metrics.addon.mjs.map +1 -0
- package/dist/builtins/snapshot/index.js +504 -0
- package/dist/builtins/snapshot/index.js.map +1 -0
- package/dist/builtins/snapshot/index.mjs +477 -0
- package/dist/builtins/snapshot/index.mjs.map +1 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +16 -166
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js.map +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/index.js +554 -621
- package/dist/builtins/sqlite-storage/index.js.map +1 -1
- package/dist/builtins/sqlite-storage/index.mjs +9 -11
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +368 -130
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js.map +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
- package/dist/builtins/system-config/index.js +189 -0
- package/dist/builtins/system-config/index.js.map +1 -0
- package/dist/builtins/system-config/index.mjs +10 -0
- package/dist/builtins/system-config/index.mjs.map +1 -0
- package/dist/builtins/system-config/system-config.addon.js +187 -0
- package/dist/builtins/system-config/system-config.addon.js.map +1 -0
- package/dist/builtins/system-config/system-config.addon.mjs +9 -0
- package/dist/builtins/system-config/system-config.addon.mjs.map +1 -0
- package/dist/builtins/winston-logging/index.js +185 -65
- package/dist/builtins/winston-logging/index.js.map +1 -1
- package/dist/builtins/winston-logging/index.mjs +2 -1
- package/dist/chunk-2CIYKDRN.mjs +1 -0
- package/dist/chunk-2CIYKDRN.mjs.map +1 -0
- package/dist/chunk-2F76X6NL.mjs +136 -0
- package/dist/chunk-2F76X6NL.mjs.map +1 -0
- package/dist/chunk-2QUFBZ7M.mjs +1 -0
- package/dist/chunk-2QUFBZ7M.mjs.map +1 -0
- package/dist/chunk-3BK2Y7GY.mjs +593 -0
- package/dist/chunk-3BK2Y7GY.mjs.map +1 -0
- package/dist/chunk-4OOHFJHT.mjs +421 -0
- package/dist/chunk-4OOHFJHT.mjs.map +1 -0
- package/dist/chunk-4XHB7IHT.mjs +809 -0
- package/dist/chunk-4XHB7IHT.mjs.map +1 -0
- package/dist/{chunk-2F3XZYRW.mjs → chunk-6M2HSSTQ.mjs} +16 -7
- package/dist/chunk-6M2HSSTQ.mjs.map +1 -0
- package/dist/{chunk-SO4LROOT.mjs → chunk-7FI7SQS7.mjs} +54 -69
- package/dist/chunk-7FI7SQS7.mjs.map +1 -0
- package/dist/chunk-ED57RCQE.mjs +171 -0
- package/dist/chunk-ED57RCQE.mjs.map +1 -0
- package/dist/chunk-FZN56HGQ.mjs +626 -0
- package/dist/chunk-FZN56HGQ.mjs.map +1 -0
- package/dist/chunk-GL4OOB25.mjs +51 -0
- package/dist/chunk-GL4OOB25.mjs.map +1 -0
- package/dist/chunk-KDG2NTDB.mjs +137 -0
- package/dist/chunk-KDG2NTDB.mjs.map +1 -0
- package/dist/chunk-NRBQWBDM.mjs +191 -0
- package/dist/chunk-NRBQWBDM.mjs.map +1 -0
- package/dist/chunk-O4V246GG.mjs +2137 -0
- package/dist/chunk-O4V246GG.mjs.map +1 -0
- package/dist/chunk-QT57H266.mjs +163 -0
- package/dist/chunk-QT57H266.mjs.map +1 -0
- package/dist/chunk-QX4RH25I.mjs +141 -0
- package/dist/chunk-QX4RH25I.mjs.map +1 -0
- package/dist/chunk-TB562PZX.mjs +86 -0
- package/dist/chunk-TB562PZX.mjs.map +1 -0
- package/dist/chunk-TDYPZXK5.mjs +1 -0
- package/dist/chunk-TDYPZXK5.mjs.map +1 -0
- package/dist/chunk-UJI4LN5P.mjs +36 -0
- package/dist/chunk-UJI4LN5P.mjs.map +1 -0
- package/dist/chunk-W6RTHQGP.mjs +1 -0
- package/dist/chunk-W6RTHQGP.mjs.map +1 -0
- package/dist/chunk-ZELBCPDC.mjs +369 -0
- package/dist/chunk-ZELBCPDC.mjs.map +1 -0
- package/dist/index.d.mts +1103 -544
- package/dist/index.d.ts +1103 -544
- package/dist/index.js +7032 -6033
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +568 -2226
- package/dist/index.mjs.map +1 -1
- package/dist/resource-monitor-UZUGPIAU.mjs +9 -0
- package/dist/resource-monitor-UZUGPIAU.mjs.map +1 -0
- package/dist/storage-location-manager-HFNB3PCS.mjs +7 -0
- package/dist/storage-location-manager-HFNB3PCS.mjs.map +1 -0
- package/package.json +123 -2
- package/dist/builtins/local-backup/index.d.mts +0 -42
- package/dist/builtins/local-backup/index.d.ts +0 -42
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.mts +0 -2
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.ts +0 -2
- package/dist/builtins/sqlite-storage/index.d.mts +0 -4
- package/dist/builtins/sqlite-storage/index.d.ts +0 -4
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.mts +0 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +0 -2
- package/dist/builtins/winston-logging/index.d.mts +0 -30
- package/dist/builtins/winston-logging/index.d.ts +0 -30
- package/dist/chunk-2F3XZYRW.mjs.map +0 -1
- package/dist/chunk-LQFPAEQF.mjs +0 -147
- package/dist/chunk-LQFPAEQF.mjs.map +0 -1
- package/dist/chunk-R3DIIBBX.mjs +0 -532
- package/dist/chunk-R3DIIBBX.mjs.map +0 -1
- package/dist/chunk-SMNR44VG.mjs +0 -386
- package/dist/chunk-SMNR44VG.mjs.map +0 -1
- package/dist/chunk-SO4LROOT.mjs.map +0 -1
- package/dist/chunk-SPA4JBKN.mjs +0 -175
- package/dist/chunk-SPA4JBKN.mjs.map +0 -1
- package/dist/dist-3BY63UQ5.mjs +0 -2151
- package/dist/dist-3BY63UQ5.mjs.map +0 -1
- package/dist/filesystem-storage.addon-C42r589X.d.mts +0 -57
- package/dist/filesystem-storage.addon-C42r589X.d.ts +0 -57
- package/dist/sql-schema-CKz78rId.d.mts +0 -97
- package/dist/sql-schema-CKz78rId.d.ts +0 -97
- package/dist/sqlite-settings.addon-KwG-uKMP.d.mts +0 -79
- package/dist/sqlite-settings.addon-KwG-uKMP.d.ts +0 -79
- package/dist/storage-location-manager-KKDQNAKA.mjs +0 -7
- /package/dist/{storage-location-manager-KKDQNAKA.mjs.map → builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs.map} +0 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.ts
|
|
31
|
+
var addon_pages_aggregator_addon_exports = {};
|
|
32
|
+
__export(addon_pages_aggregator_addon_exports, {
|
|
33
|
+
AddonPagesAggregatorAddon: () => AddonPagesAggregatorAddon,
|
|
34
|
+
default: () => addon_pages_aggregator_addon_default
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(addon_pages_aggregator_addon_exports);
|
|
37
|
+
var path = __toESM(require("path"));
|
|
38
|
+
var fs = __toESM(require("fs"));
|
|
39
|
+
var import_node_crypto = require("crypto");
|
|
40
|
+
var import_types = require("@camstack/types");
|
|
41
|
+
var RETRY_BACKOFF_MS = [500, 1500, 4e3];
|
|
42
|
+
var AddonPagesAggregatorAddon = class extends import_types.BaseAddon {
|
|
43
|
+
id = "addon-pages-aggregator";
|
|
44
|
+
resolvedPaths = null;
|
|
45
|
+
/**
|
|
46
|
+
* Last successful `listPages()` snapshot per source. Used as the
|
|
47
|
+
* "stale-but-valid" fallback when a source transiently fails — drops
|
|
48
|
+
* happen often enough during boot (Moleculer service-discovery
|
|
49
|
+
* window) that swallowing the error and returning empty leaves the
|
|
50
|
+
* sidebar with nothing for several seconds. Keeping the previous
|
|
51
|
+
* good entry means a flake is invisible to the operator.
|
|
52
|
+
*/
|
|
53
|
+
lastGood = /* @__PURE__ */ new Map();
|
|
54
|
+
/** In-flight retry guards keyed by sourceId. Avoids double-scheduling. */
|
|
55
|
+
retryTimers = /* @__PURE__ */ new Map();
|
|
56
|
+
constructor() {
|
|
57
|
+
super({});
|
|
58
|
+
}
|
|
59
|
+
async onInitialize() {
|
|
60
|
+
this.resolvedPaths = await this.resolvePaths();
|
|
61
|
+
const provider = {
|
|
62
|
+
listPages: async () => this.aggregate()
|
|
63
|
+
};
|
|
64
|
+
this.ctx.logger.info("Initialized \u2014 aggregating addon-pages-source providers");
|
|
65
|
+
return [{ capability: import_types.addonPagesCapability, provider }];
|
|
66
|
+
}
|
|
67
|
+
async onShutdown() {
|
|
68
|
+
for (const t of this.retryTimers.values()) clearTimeout(t);
|
|
69
|
+
this.retryTimers.clear();
|
|
70
|
+
this.lastGood.clear();
|
|
71
|
+
}
|
|
72
|
+
// ── Aggregation ───────────────────────────────────────────────────
|
|
73
|
+
async aggregate() {
|
|
74
|
+
const sources = this.capabilities?.getCollection("addon-pages-source") ?? [];
|
|
75
|
+
const out = [];
|
|
76
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
77
|
+
for (const source of sources) {
|
|
78
|
+
seenIds.add(source.id);
|
|
79
|
+
try {
|
|
80
|
+
const pages = await Promise.resolve(source.listPages());
|
|
81
|
+
const enriched = pages.map((page) => ({
|
|
82
|
+
addonId: source.id,
|
|
83
|
+
page,
|
|
84
|
+
bundleUrl: this.makeBundleUrl(source.id, page.bundle)
|
|
85
|
+
}));
|
|
86
|
+
for (const item of enriched) out.push(item);
|
|
87
|
+
this.lastGood.set(source.id, enriched);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
const message = (0, import_types.errMsg)(err);
|
|
90
|
+
this.ctx.logger.warn("addon-pages-source provider failed", {
|
|
91
|
+
meta: { sourceId: source.id, error: message }
|
|
92
|
+
});
|
|
93
|
+
const cached = this.lastGood.get(source.id);
|
|
94
|
+
if (cached !== void 0) {
|
|
95
|
+
for (const item of cached) out.push(item);
|
|
96
|
+
this.ctx.logger.info("addon-pages-source falling back to cached snapshot", {
|
|
97
|
+
meta: { sourceId: source.id, cachedPages: cached.length }
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
this.scheduleRetry(source.id);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
for (const cachedId of this.lastGood.keys()) {
|
|
104
|
+
if (!seenIds.has(cachedId)) this.lastGood.delete(cachedId);
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
// ── Retry on transient Moleculer race ─────────────────────────────
|
|
109
|
+
/**
|
|
110
|
+
* Schedule background re-call of `listPages()` on a source that just
|
|
111
|
+
* failed. Walks `RETRY_BACKOFF_MS` from index 0 — each successful
|
|
112
|
+
* call updates the cache and emits `AddonPageReady` so the admin UI
|
|
113
|
+
* invalidates its query. Stops on first success or after the schedule
|
|
114
|
+
* is exhausted.
|
|
115
|
+
*/
|
|
116
|
+
scheduleRetry(sourceId, attempt = 0) {
|
|
117
|
+
if (attempt >= RETRY_BACKOFF_MS.length) return;
|
|
118
|
+
if (this.retryTimers.has(sourceId)) return;
|
|
119
|
+
const delayMs = RETRY_BACKOFF_MS[attempt] ?? RETRY_BACKOFF_MS[RETRY_BACKOFF_MS.length - 1];
|
|
120
|
+
const timer = setTimeout(() => {
|
|
121
|
+
this.retryTimers.delete(sourceId);
|
|
122
|
+
void this.retrySource(sourceId, attempt);
|
|
123
|
+
}, delayMs);
|
|
124
|
+
this.retryTimers.set(sourceId, timer);
|
|
125
|
+
}
|
|
126
|
+
async retrySource(sourceId, attempt) {
|
|
127
|
+
const sources = this.capabilities?.getCollection("addon-pages-source") ?? [];
|
|
128
|
+
const source = sources.find((s) => s.id === sourceId);
|
|
129
|
+
if (!source) return;
|
|
130
|
+
try {
|
|
131
|
+
const pages = await Promise.resolve(source.listPages());
|
|
132
|
+
const enriched = pages.map((page) => ({
|
|
133
|
+
addonId: source.id,
|
|
134
|
+
page,
|
|
135
|
+
bundleUrl: this.makeBundleUrl(source.id, page.bundle)
|
|
136
|
+
}));
|
|
137
|
+
this.lastGood.set(source.id, enriched);
|
|
138
|
+
this.ctx.logger.info("addon-pages-source recovered after retry", {
|
|
139
|
+
meta: { sourceId, attempt: attempt + 1, pages: enriched.length }
|
|
140
|
+
});
|
|
141
|
+
this.ctx.eventBus.emit({
|
|
142
|
+
id: (0, import_node_crypto.randomUUID)(),
|
|
143
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
144
|
+
source: { type: "addon", id: this.id },
|
|
145
|
+
category: import_types.EventCategory.AddonPageReady,
|
|
146
|
+
data: { addonId: sourceId, recovered: true }
|
|
147
|
+
});
|
|
148
|
+
} catch (err) {
|
|
149
|
+
this.ctx.logger.debug("addon-pages-source retry failed", {
|
|
150
|
+
meta: { sourceId, attempt: attempt + 1, error: (0, import_types.errMsg)(err) }
|
|
151
|
+
});
|
|
152
|
+
this.scheduleRetry(sourceId, attempt + 1);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// ── Bundle URL stamping ──────────────────────────────────────────
|
|
156
|
+
/**
|
|
157
|
+
* Build `/api/addon-pages/<addonId>/<bundle>?v=<mtime>`. Falls back
|
|
158
|
+
* to `Date.now()` when the bundle path can't be stat'd (remote addon
|
|
159
|
+
* with no local file, addon not yet on disk, etc.) — the browser
|
|
160
|
+
* just gets a fresh URL on each call instead of cache-friendly mtime.
|
|
161
|
+
*/
|
|
162
|
+
makeBundleUrl(addonId, bundle) {
|
|
163
|
+
const bundlePath = this.resolveBundlePath(addonId, bundle);
|
|
164
|
+
let mtime = Date.now();
|
|
165
|
+
if (bundlePath !== null) {
|
|
166
|
+
try {
|
|
167
|
+
mtime = fs.statSync(bundlePath).mtimeMs;
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const v = Math.floor(mtime);
|
|
172
|
+
return `/api/addon-pages/${addonId}/${bundle}?v=${v}`;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Resolve the local filesystem path for an addon's bundle. Mirrors
|
|
176
|
+
* `AddonPagesService.resolveBundle` (server-side), but without the
|
|
177
|
+
* existence / traversal checks — those still live on the server's
|
|
178
|
+
* static file route. We only need a stat-able path here for the
|
|
179
|
+
* `mtime` cache-buster.
|
|
180
|
+
*/
|
|
181
|
+
resolveBundlePath(addonId, bundle) {
|
|
182
|
+
const paths = this.resolvedPaths;
|
|
183
|
+
if (!paths) return null;
|
|
184
|
+
const addonDistPath = path.join(paths.addonsDir, "@camstack", `addon-${addonId}`, "dist");
|
|
185
|
+
const resolvedBase = path.resolve(addonDistPath);
|
|
186
|
+
const resolvedFile = path.resolve(addonDistPath, bundle);
|
|
187
|
+
if (!resolvedFile.startsWith(resolvedBase + path.sep) && resolvedFile !== resolvedBase) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
return resolvedFile;
|
|
191
|
+
}
|
|
192
|
+
// ── Path resolution ──────────────────────────────────────────────
|
|
193
|
+
/**
|
|
194
|
+
* Read `server.dataPath` from the cluster yml-backed sections (same
|
|
195
|
+
* source the server uses at boot), then derive the addons directory
|
|
196
|
+
* from it. Falls back to `camstack-data/addons` when no settings API
|
|
197
|
+
* is available — agents and isolated tests don't see this addon, so
|
|
198
|
+
* the fallback is purely defensive.
|
|
199
|
+
*/
|
|
200
|
+
async resolvePaths() {
|
|
201
|
+
const fallback = { addonsDir: path.resolve("camstack-data", "addons") };
|
|
202
|
+
if (!this.ctx.settings) return fallback;
|
|
203
|
+
try {
|
|
204
|
+
const server = await this.ctx.settings.getSection("server");
|
|
205
|
+
const dataPath = typeof server["dataPath"] === "string" && server["dataPath"] ? server["dataPath"] : "camstack-data";
|
|
206
|
+
return { addonsDir: path.resolve(dataPath, "addons") };
|
|
207
|
+
} catch (err) {
|
|
208
|
+
this.ctx.logger.debug("Failed to read server.dataPath \u2014 falling back", {
|
|
209
|
+
meta: { error: (0, import_types.errMsg)(err) }
|
|
210
|
+
});
|
|
211
|
+
return fallback;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
var addon_pages_aggregator_addon_default = AddonPagesAggregatorAddon;
|
|
216
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
217
|
+
0 && (module.exports = {
|
|
218
|
+
AddonPagesAggregatorAddon
|
|
219
|
+
});
|
|
220
|
+
//# sourceMappingURL=addon-pages-aggregator.addon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.ts"],"sourcesContent":["/**\n * Addon Pages Aggregator — hub-local builtin that owns the singleton\n * `addon-pages` cap.\n *\n * Walks every registered `addon-pages-source` (collection) provider and\n * emits an enriched `AddonPageInfo[]` list with versioned `bundleUrl`s\n * pointing at `/api/addon-pages/<addonId>/<bundle>?v=<mtime>`. The\n * filesystem `mtime` cache-buster lets the browser pick up addon\n * rebuilds without manual reload — same scheme the original\n * hand-written `AddonPagesService.listPages` used before this split.\n *\n * The static file endpoint (`/api/addon-pages/:addonId/*`) is still\n * served by `AddonPagesService.resolveBundle()` on the server side; this\n * addon only owns the listing surface.\n *\n * Why a builtin rather than a cap-providers helper:\n * - The aggregator is conceptually the \"addon-pages provider\" — addons\n * own caps, not the server. Living in `@camstack/core/builtins` keeps\n * the surface symmetrical with `system-config`, `local-auth`, etc.\n * - Lets the aggregator subscribe to source readiness without piping a\n * CapabilityRegistry handle through `RouterServices`.\n */\nimport * as path from 'node:path'\nimport * as fs from 'node:fs'\nimport { randomUUID } from 'node:crypto'\nimport {\n BaseAddon,\n EventCategory,\n addonPagesCapability,\n errMsg,\n type AddonPageInfo,\n type IAddonPageProvider,\n type IAddonPagesAggregatorProvider,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface ResolvedPaths {\n readonly addonsDir: string\n}\n\n/**\n * Backoff schedule (ms) used to retry sources that failed during a\n * `listPages()` 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 '...listPages'\n * is not found on '<node>'` raced ahead of the call.\n *\n * We schedule one retry per entry; if every retry fails we stop\n * (the next aggregate() run will pick the source up again). On\n * success we re-emit `AddonPageReady` so admin-ui invalidates its\n * `addonPages.listPages` query and the sidebar populates without a\n * page reload.\n */\nconst RETRY_BACKOFF_MS: readonly number[] = [500, 1500, 4000]\n\nexport class AddonPagesAggregatorAddon extends BaseAddon {\n readonly id = 'addon-pages-aggregator'\n\n private resolvedPaths: ResolvedPaths | null = null\n\n /**\n * Last successful `listPages()` 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 leaves the\n * sidebar with nothing for several seconds. Keeping the previous\n * good entry means a flake is invisible to the operator.\n */\n private readonly lastGood = new Map<string, readonly AddonPageInfo[]>()\n\n /** In-flight retry guards keyed by sourceId. 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: IAddonPagesAggregatorProvider = {\n listPages: async (): Promise<readonly AddonPageInfo[]> => this.aggregate(),\n }\n\n this.ctx.logger.info('Initialized — aggregating addon-pages-source providers')\n return [{ capability: addonPagesCapability, 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 AddonPageInfo[]> {\n const sources = this.capabilities?.getCollection<IAddonPageProvider>('addon-pages-source') ?? []\n const out: AddonPageInfo[] = []\n const seenIds = new Set<string>()\n\n for (const source of sources) {\n seenIds.add(source.id)\n try {\n // listPages() may be async for remote Moleculer providers.\n const pages = await Promise.resolve(source.listPages())\n const enriched: AddonPageInfo[] = pages.map((page) => ({\n addonId: source.id,\n page,\n bundleUrl: this.makeBundleUrl(source.id, page.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(source.id, enriched)\n } catch (err: unknown) {\n const message = errMsg(err)\n this.ctx.logger.warn('addon-pages-source provider failed', {\n meta: { sourceId: source.id, error: message },\n })\n // Fall back to the last-good snapshot for this source so a\n // transient Moleculer service-discovery race (cap registered\n // but action not yet callable) doesn't blank the sidebar.\n const cached = this.lastGood.get(source.id)\n if (cached !== undefined) {\n for (const item of cached) out.push(item)\n this.ctx.logger.info('addon-pages-source falling back to cached snapshot', {\n meta: { sourceId: source.id, cachedPages: cached.length },\n })\n }\n // Schedule a background retry. On success we re-emit\n // `AddonPageReady` so admin-ui's queryClient invalidates and\n // any newly-loaded pages show up without a manual reload.\n this.scheduleRetry(source.id)\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 /**\n * Schedule background re-call of `listPages()` on a source that just\n * failed. Walks `RETRY_BACKOFF_MS` from index 0 — each successful\n * call updates the cache and emits `AddonPageReady` so the admin UI\n * invalidates its query. Stops on first success or after the schedule\n * is exhausted.\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 sources = this.capabilities?.getCollection<IAddonPageProvider>('addon-pages-source') ?? []\n const source = sources.find((s) => s.id === sourceId)\n if (!source) return // provider went away; nothing to retry\n\n try {\n const pages = await Promise.resolve(source.listPages())\n const enriched: AddonPageInfo[] = pages.map((page) => ({\n addonId: source.id,\n page,\n bundleUrl: this.makeBundleUrl(source.id, page.bundle),\n }))\n this.lastGood.set(source.id, enriched)\n this.ctx.logger.info('addon-pages-source recovered after retry', {\n meta: { sourceId, attempt: attempt + 1, pages: enriched.length },\n })\n // Re-emit AddonPageReady 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.AddonPageReady,\n data: { addonId: sourceId, recovered: true },\n })\n } catch (err: unknown) {\n this.ctx.logger.debug('addon-pages-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-pages/<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-pages/${addonId}/${bundle}?v=${v}`\n }\n\n /**\n * Resolve the local filesystem path for an addon's bundle. Mirrors\n * `AddonPagesService.resolveBundle` (server-side), but without the\n * existence / traversal checks — those still live on the server's\n * static file route. We only need a stat-able path here for the\n * `mtime` cache-buster.\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 /**\n * Read `server.dataPath` from the cluster yml-backed sections (same\n * source the server uses at boot), then derive the addons directory\n * from it. Falls back to `camstack-data/addons` when no settings API\n * is available — agents and isolated tests don't see this addon, so\n * the fallback is purely defensive.\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 AddonPagesAggregatorAddon\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AddonPagesAggregatorAddon,
|
|
3
|
+
addon_pages_aggregator_addon_default
|
|
4
|
+
} from "../../chunk-NRBQWBDM.mjs";
|
|
5
|
+
export {
|
|
6
|
+
AddonPagesAggregatorAddon,
|
|
7
|
+
addon_pages_aggregator_addon_default as default
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=addon-pages-aggregator.addon.mjs.map
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/builtins/addon-pages-aggregator/index.ts
|
|
31
|
+
var addon_pages_aggregator_exports = {};
|
|
32
|
+
__export(addon_pages_aggregator_exports, {
|
|
33
|
+
AddonPagesAggregatorAddon: () => AddonPagesAggregatorAddon,
|
|
34
|
+
default: () => addon_pages_aggregator_addon_default
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(addon_pages_aggregator_exports);
|
|
37
|
+
|
|
38
|
+
// src/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.ts
|
|
39
|
+
var path = __toESM(require("path"));
|
|
40
|
+
var fs = __toESM(require("fs"));
|
|
41
|
+
var import_node_crypto = require("crypto");
|
|
42
|
+
var import_types = require("@camstack/types");
|
|
43
|
+
var RETRY_BACKOFF_MS = [500, 1500, 4e3];
|
|
44
|
+
var AddonPagesAggregatorAddon = class extends import_types.BaseAddon {
|
|
45
|
+
id = "addon-pages-aggregator";
|
|
46
|
+
resolvedPaths = null;
|
|
47
|
+
/**
|
|
48
|
+
* Last successful `listPages()` snapshot per source. Used as the
|
|
49
|
+
* "stale-but-valid" fallback when a source transiently fails — drops
|
|
50
|
+
* happen often enough during boot (Moleculer service-discovery
|
|
51
|
+
* window) that swallowing the error and returning empty leaves the
|
|
52
|
+
* sidebar with nothing for several seconds. Keeping the previous
|
|
53
|
+
* good entry means a flake is invisible to the operator.
|
|
54
|
+
*/
|
|
55
|
+
lastGood = /* @__PURE__ */ new Map();
|
|
56
|
+
/** In-flight retry guards keyed by sourceId. Avoids double-scheduling. */
|
|
57
|
+
retryTimers = /* @__PURE__ */ new Map();
|
|
58
|
+
constructor() {
|
|
59
|
+
super({});
|
|
60
|
+
}
|
|
61
|
+
async onInitialize() {
|
|
62
|
+
this.resolvedPaths = await this.resolvePaths();
|
|
63
|
+
const provider = {
|
|
64
|
+
listPages: async () => this.aggregate()
|
|
65
|
+
};
|
|
66
|
+
this.ctx.logger.info("Initialized \u2014 aggregating addon-pages-source providers");
|
|
67
|
+
return [{ capability: import_types.addonPagesCapability, provider }];
|
|
68
|
+
}
|
|
69
|
+
async onShutdown() {
|
|
70
|
+
for (const t of this.retryTimers.values()) clearTimeout(t);
|
|
71
|
+
this.retryTimers.clear();
|
|
72
|
+
this.lastGood.clear();
|
|
73
|
+
}
|
|
74
|
+
// ── Aggregation ───────────────────────────────────────────────────
|
|
75
|
+
async aggregate() {
|
|
76
|
+
const sources = this.capabilities?.getCollection("addon-pages-source") ?? [];
|
|
77
|
+
const out = [];
|
|
78
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
79
|
+
for (const source of sources) {
|
|
80
|
+
seenIds.add(source.id);
|
|
81
|
+
try {
|
|
82
|
+
const pages = await Promise.resolve(source.listPages());
|
|
83
|
+
const enriched = pages.map((page) => ({
|
|
84
|
+
addonId: source.id,
|
|
85
|
+
page,
|
|
86
|
+
bundleUrl: this.makeBundleUrl(source.id, page.bundle)
|
|
87
|
+
}));
|
|
88
|
+
for (const item of enriched) out.push(item);
|
|
89
|
+
this.lastGood.set(source.id, enriched);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
const message = (0, import_types.errMsg)(err);
|
|
92
|
+
this.ctx.logger.warn("addon-pages-source provider failed", {
|
|
93
|
+
meta: { sourceId: source.id, error: message }
|
|
94
|
+
});
|
|
95
|
+
const cached = this.lastGood.get(source.id);
|
|
96
|
+
if (cached !== void 0) {
|
|
97
|
+
for (const item of cached) out.push(item);
|
|
98
|
+
this.ctx.logger.info("addon-pages-source falling back to cached snapshot", {
|
|
99
|
+
meta: { sourceId: source.id, cachedPages: cached.length }
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
this.scheduleRetry(source.id);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (const cachedId of this.lastGood.keys()) {
|
|
106
|
+
if (!seenIds.has(cachedId)) this.lastGood.delete(cachedId);
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
// ── Retry on transient Moleculer race ─────────────────────────────
|
|
111
|
+
/**
|
|
112
|
+
* Schedule background re-call of `listPages()` on a source that just
|
|
113
|
+
* failed. Walks `RETRY_BACKOFF_MS` from index 0 — each successful
|
|
114
|
+
* call updates the cache and emits `AddonPageReady` so the admin UI
|
|
115
|
+
* invalidates its query. Stops on first success or after the schedule
|
|
116
|
+
* is exhausted.
|
|
117
|
+
*/
|
|
118
|
+
scheduleRetry(sourceId, attempt = 0) {
|
|
119
|
+
if (attempt >= RETRY_BACKOFF_MS.length) return;
|
|
120
|
+
if (this.retryTimers.has(sourceId)) return;
|
|
121
|
+
const delayMs = RETRY_BACKOFF_MS[attempt] ?? RETRY_BACKOFF_MS[RETRY_BACKOFF_MS.length - 1];
|
|
122
|
+
const timer = setTimeout(() => {
|
|
123
|
+
this.retryTimers.delete(sourceId);
|
|
124
|
+
void this.retrySource(sourceId, attempt);
|
|
125
|
+
}, delayMs);
|
|
126
|
+
this.retryTimers.set(sourceId, timer);
|
|
127
|
+
}
|
|
128
|
+
async retrySource(sourceId, attempt) {
|
|
129
|
+
const sources = this.capabilities?.getCollection("addon-pages-source") ?? [];
|
|
130
|
+
const source = sources.find((s) => s.id === sourceId);
|
|
131
|
+
if (!source) return;
|
|
132
|
+
try {
|
|
133
|
+
const pages = await Promise.resolve(source.listPages());
|
|
134
|
+
const enriched = pages.map((page) => ({
|
|
135
|
+
addonId: source.id,
|
|
136
|
+
page,
|
|
137
|
+
bundleUrl: this.makeBundleUrl(source.id, page.bundle)
|
|
138
|
+
}));
|
|
139
|
+
this.lastGood.set(source.id, enriched);
|
|
140
|
+
this.ctx.logger.info("addon-pages-source recovered after retry", {
|
|
141
|
+
meta: { sourceId, attempt: attempt + 1, pages: enriched.length }
|
|
142
|
+
});
|
|
143
|
+
this.ctx.eventBus.emit({
|
|
144
|
+
id: (0, import_node_crypto.randomUUID)(),
|
|
145
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
146
|
+
source: { type: "addon", id: this.id },
|
|
147
|
+
category: import_types.EventCategory.AddonPageReady,
|
|
148
|
+
data: { addonId: sourceId, recovered: true }
|
|
149
|
+
});
|
|
150
|
+
} catch (err) {
|
|
151
|
+
this.ctx.logger.debug("addon-pages-source retry failed", {
|
|
152
|
+
meta: { sourceId, attempt: attempt + 1, error: (0, import_types.errMsg)(err) }
|
|
153
|
+
});
|
|
154
|
+
this.scheduleRetry(sourceId, attempt + 1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// ── Bundle URL stamping ──────────────────────────────────────────
|
|
158
|
+
/**
|
|
159
|
+
* Build `/api/addon-pages/<addonId>/<bundle>?v=<mtime>`. Falls back
|
|
160
|
+
* to `Date.now()` when the bundle path can't be stat'd (remote addon
|
|
161
|
+
* with no local file, addon not yet on disk, etc.) — the browser
|
|
162
|
+
* just gets a fresh URL on each call instead of cache-friendly mtime.
|
|
163
|
+
*/
|
|
164
|
+
makeBundleUrl(addonId, bundle) {
|
|
165
|
+
const bundlePath = this.resolveBundlePath(addonId, bundle);
|
|
166
|
+
let mtime = Date.now();
|
|
167
|
+
if (bundlePath !== null) {
|
|
168
|
+
try {
|
|
169
|
+
mtime = fs.statSync(bundlePath).mtimeMs;
|
|
170
|
+
} catch {
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const v = Math.floor(mtime);
|
|
174
|
+
return `/api/addon-pages/${addonId}/${bundle}?v=${v}`;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Resolve the local filesystem path for an addon's bundle. Mirrors
|
|
178
|
+
* `AddonPagesService.resolveBundle` (server-side), but without the
|
|
179
|
+
* existence / traversal checks — those still live on the server's
|
|
180
|
+
* static file route. We only need a stat-able path here for the
|
|
181
|
+
* `mtime` cache-buster.
|
|
182
|
+
*/
|
|
183
|
+
resolveBundlePath(addonId, bundle) {
|
|
184
|
+
const paths = this.resolvedPaths;
|
|
185
|
+
if (!paths) return null;
|
|
186
|
+
const addonDistPath = path.join(paths.addonsDir, "@camstack", `addon-${addonId}`, "dist");
|
|
187
|
+
const resolvedBase = path.resolve(addonDistPath);
|
|
188
|
+
const resolvedFile = path.resolve(addonDistPath, bundle);
|
|
189
|
+
if (!resolvedFile.startsWith(resolvedBase + path.sep) && resolvedFile !== resolvedBase) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
return resolvedFile;
|
|
193
|
+
}
|
|
194
|
+
// ── Path resolution ──────────────────────────────────────────────
|
|
195
|
+
/**
|
|
196
|
+
* Read `server.dataPath` from the cluster yml-backed sections (same
|
|
197
|
+
* source the server uses at boot), then derive the addons directory
|
|
198
|
+
* from it. Falls back to `camstack-data/addons` when no settings API
|
|
199
|
+
* is available — agents and isolated tests don't see this addon, so
|
|
200
|
+
* the fallback is purely defensive.
|
|
201
|
+
*/
|
|
202
|
+
async resolvePaths() {
|
|
203
|
+
const fallback = { addonsDir: path.resolve("camstack-data", "addons") };
|
|
204
|
+
if (!this.ctx.settings) return fallback;
|
|
205
|
+
try {
|
|
206
|
+
const server = await this.ctx.settings.getSection("server");
|
|
207
|
+
const dataPath = typeof server["dataPath"] === "string" && server["dataPath"] ? server["dataPath"] : "camstack-data";
|
|
208
|
+
return { addonsDir: path.resolve(dataPath, "addons") };
|
|
209
|
+
} catch (err) {
|
|
210
|
+
this.ctx.logger.debug("Failed to read server.dataPath \u2014 falling back", {
|
|
211
|
+
meta: { error: (0, import_types.errMsg)(err) }
|
|
212
|
+
});
|
|
213
|
+
return fallback;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
var addon_pages_aggregator_addon_default = AddonPagesAggregatorAddon;
|
|
218
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
219
|
+
0 && (module.exports = {
|
|
220
|
+
AddonPagesAggregatorAddon
|
|
221
|
+
});
|
|
222
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/builtins/addon-pages-aggregator/index.ts","../../../src/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.ts"],"sourcesContent":["export { AddonPagesAggregatorAddon, default } from './addon-pages-aggregator.addon.js'\n","/**\n * Addon Pages Aggregator — hub-local builtin that owns the singleton\n * `addon-pages` cap.\n *\n * Walks every registered `addon-pages-source` (collection) provider and\n * emits an enriched `AddonPageInfo[]` list with versioned `bundleUrl`s\n * pointing at `/api/addon-pages/<addonId>/<bundle>?v=<mtime>`. The\n * filesystem `mtime` cache-buster lets the browser pick up addon\n * rebuilds without manual reload — same scheme the original\n * hand-written `AddonPagesService.listPages` used before this split.\n *\n * The static file endpoint (`/api/addon-pages/:addonId/*`) is still\n * served by `AddonPagesService.resolveBundle()` on the server side; this\n * addon only owns the listing surface.\n *\n * Why a builtin rather than a cap-providers helper:\n * - The aggregator is conceptually the \"addon-pages provider\" — addons\n * own caps, not the server. Living in `@camstack/core/builtins` keeps\n * the surface symmetrical with `system-config`, `local-auth`, etc.\n * - Lets the aggregator subscribe to source readiness without piping a\n * CapabilityRegistry handle through `RouterServices`.\n */\nimport * as path from 'node:path'\nimport * as fs from 'node:fs'\nimport { randomUUID } from 'node:crypto'\nimport {\n BaseAddon,\n EventCategory,\n addonPagesCapability,\n errMsg,\n type AddonPageInfo,\n type IAddonPageProvider,\n type IAddonPagesAggregatorProvider,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface ResolvedPaths {\n readonly addonsDir: string\n}\n\n/**\n * Backoff schedule (ms) used to retry sources that failed during a\n * `listPages()` 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 '...listPages'\n * is not found on '<node>'` raced ahead of the call.\n *\n * We schedule one retry per entry; if every retry fails we stop\n * (the next aggregate() run will pick the source up again). On\n * success we re-emit `AddonPageReady` so admin-ui invalidates its\n * `addonPages.listPages` query and the sidebar populates without a\n * page reload.\n */\nconst RETRY_BACKOFF_MS: readonly number[] = [500, 1500, 4000]\n\nexport class AddonPagesAggregatorAddon extends BaseAddon {\n readonly id = 'addon-pages-aggregator'\n\n private resolvedPaths: ResolvedPaths | null = null\n\n /**\n * Last successful `listPages()` 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 leaves the\n * sidebar with nothing for several seconds. Keeping the previous\n * good entry means a flake is invisible to the operator.\n */\n private readonly lastGood = new Map<string, readonly AddonPageInfo[]>()\n\n /** In-flight retry guards keyed by sourceId. 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: IAddonPagesAggregatorProvider = {\n listPages: async (): Promise<readonly AddonPageInfo[]> => this.aggregate(),\n }\n\n this.ctx.logger.info('Initialized — aggregating addon-pages-source providers')\n return [{ capability: addonPagesCapability, 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 AddonPageInfo[]> {\n const sources = this.capabilities?.getCollection<IAddonPageProvider>('addon-pages-source') ?? []\n const out: AddonPageInfo[] = []\n const seenIds = new Set<string>()\n\n for (const source of sources) {\n seenIds.add(source.id)\n try {\n // listPages() may be async for remote Moleculer providers.\n const pages = await Promise.resolve(source.listPages())\n const enriched: AddonPageInfo[] = pages.map((page) => ({\n addonId: source.id,\n page,\n bundleUrl: this.makeBundleUrl(source.id, page.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(source.id, enriched)\n } catch (err: unknown) {\n const message = errMsg(err)\n this.ctx.logger.warn('addon-pages-source provider failed', {\n meta: { sourceId: source.id, error: message },\n })\n // Fall back to the last-good snapshot for this source so a\n // transient Moleculer service-discovery race (cap registered\n // but action not yet callable) doesn't blank the sidebar.\n const cached = this.lastGood.get(source.id)\n if (cached !== undefined) {\n for (const item of cached) out.push(item)\n this.ctx.logger.info('addon-pages-source falling back to cached snapshot', {\n meta: { sourceId: source.id, cachedPages: cached.length },\n })\n }\n // Schedule a background retry. On success we re-emit\n // `AddonPageReady` so admin-ui's queryClient invalidates and\n // any newly-loaded pages show up without a manual reload.\n this.scheduleRetry(source.id)\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 /**\n * Schedule background re-call of `listPages()` on a source that just\n * failed. Walks `RETRY_BACKOFF_MS` from index 0 — each successful\n * call updates the cache and emits `AddonPageReady` so the admin UI\n * invalidates its query. Stops on first success or after the schedule\n * is exhausted.\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 sources = this.capabilities?.getCollection<IAddonPageProvider>('addon-pages-source') ?? []\n const source = sources.find((s) => s.id === sourceId)\n if (!source) return // provider went away; nothing to retry\n\n try {\n const pages = await Promise.resolve(source.listPages())\n const enriched: AddonPageInfo[] = pages.map((page) => ({\n addonId: source.id,\n page,\n bundleUrl: this.makeBundleUrl(source.id, page.bundle),\n }))\n this.lastGood.set(source.id, enriched)\n this.ctx.logger.info('addon-pages-source recovered after retry', {\n meta: { sourceId, attempt: attempt + 1, pages: enriched.length },\n })\n // Re-emit AddonPageReady 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.AddonPageReady,\n data: { addonId: sourceId, recovered: true },\n })\n } catch (err: unknown) {\n this.ctx.logger.debug('addon-pages-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-pages/<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-pages/${addonId}/${bundle}?v=${v}`\n }\n\n /**\n * Resolve the local filesystem path for an addon's bundle. Mirrors\n * `AddonPagesService.resolveBundle` (server-side), but without the\n * existence / traversal checks — those still live on the server's\n * static file route. We only need a stat-able path here for the\n * `mtime` cache-buster.\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 /**\n * Read `server.dataPath` from the cluster yml-backed sections (same\n * source the server uses at boot), then derive the addons directory\n * from it. Falls back to `camstack-data/addons` when no settings API\n * is available — agents and isolated tests don't see this addon, so\n * the fallback is purely defensive.\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 AddonPagesAggregatorAddon\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|