@framework-m/plugin-sdk 0.2.3
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/LICENSE +202 -0
- package/dist/context/PluginRegistryContext.d.ts +23 -0
- package/dist/context/PluginRegistryContext.d.ts.map +1 -0
- package/dist/core/PluginRegistry.d.ts +130 -0
- package/dist/core/PluginRegistry.d.ts.map +1 -0
- package/dist/core/ServiceContainer.d.ts +31 -0
- package/dist/core/ServiceContainer.d.ts.map +1 -0
- package/dist/hooks/usePlugin.d.ts +3 -0
- package/dist/hooks/usePlugin.d.ts.map +1 -0
- package/dist/hooks/usePluginMenu.d.ts +3 -0
- package/dist/hooks/usePluginMenu.d.ts.map +1 -0
- package/dist/hooks/useService.d.ts +24 -0
- package/dist/hooks/useService.d.ts.map +1 -0
- package/dist/hooks/useWidgets.d.ts +3 -0
- package/dist/hooks/useWidgets.d.ts.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +594 -0
- package/dist/index.js.map +1 -0
- package/dist/types/plugin.d.ts +157 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,YAAY,EACV,QAAQ,EACR,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,QAAQ,EACR,gBAAgB,EAChB,MAAM,EACN,gBAAgB,EAChB,mBAAmB,EACnB,wBAAwB,EACxB,gCAAgC,GACjC,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAG3D,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,iCAAiC,CAAC;AACzC,YAAY,EAAE,2BAA2B,EAAE,MAAM,iCAAiC,CAAC;AAGnF,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
var P = Object.defineProperty;
|
|
2
|
+
var C = (r, e, t) => e in r ? P(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t;
|
|
3
|
+
var c = (r, e, t) => C(r, typeof e != "symbol" ? e + "" : e, t);
|
|
4
|
+
import { jsx as S } from "react/jsx-runtime";
|
|
5
|
+
import { createContext as O, useState as l, useEffect as d, useContext as f, useMemo as p } from "react";
|
|
6
|
+
class N {
|
|
7
|
+
constructor() {
|
|
8
|
+
c(this, "factories", /* @__PURE__ */ new Map());
|
|
9
|
+
c(this, "instances", /* @__PURE__ */ new Map());
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Register a service factory.
|
|
13
|
+
*
|
|
14
|
+
* @param name — unique service name
|
|
15
|
+
* @param factory — factory function (sync or async)
|
|
16
|
+
*/
|
|
17
|
+
register(e, t) {
|
|
18
|
+
this.factories.set(e, t), this.instances.delete(e);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get a service by name. Lazily instantiates on first call (singleton).
|
|
22
|
+
*
|
|
23
|
+
* @throws Error if service is not registered
|
|
24
|
+
*/
|
|
25
|
+
async get(e) {
|
|
26
|
+
if (this.instances.has(e))
|
|
27
|
+
return this.instances.get(e);
|
|
28
|
+
const t = this.factories.get(e);
|
|
29
|
+
if (!t)
|
|
30
|
+
throw new Error(`Service "${e}" is not registered`);
|
|
31
|
+
const i = await Promise.resolve(t());
|
|
32
|
+
return this.instances.set(e, i), i;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if a service is registered.
|
|
36
|
+
*/
|
|
37
|
+
has(e) {
|
|
38
|
+
return this.factories.has(e);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get all registered service names.
|
|
42
|
+
*/
|
|
43
|
+
getAll() {
|
|
44
|
+
return Array.from(this.factories.keys());
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Clear all registered factories and cached instances.
|
|
48
|
+
*/
|
|
49
|
+
clear() {
|
|
50
|
+
this.factories.clear(), this.instances.clear();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const v = "0.1.0", w = {
|
|
54
|
+
Sales: "shopping-cart",
|
|
55
|
+
Inventory: "package",
|
|
56
|
+
HR: "users",
|
|
57
|
+
Finance: "dollar-sign",
|
|
58
|
+
Core: "settings",
|
|
59
|
+
Other: "folder"
|
|
60
|
+
};
|
|
61
|
+
function M() {
|
|
62
|
+
const r = globalThis.__FRAMEWORK_M_PLUGIN_DEBUG__;
|
|
63
|
+
if (typeof r == "boolean")
|
|
64
|
+
return r;
|
|
65
|
+
if (typeof r == "string")
|
|
66
|
+
return ["1", "true", "yes", "on"].includes(r.toLowerCase());
|
|
67
|
+
const e = globalThis.process;
|
|
68
|
+
if (e != null && e.env) {
|
|
69
|
+
const t = e.env.FRAMEWORK_M_PLUGIN_DEBUG;
|
|
70
|
+
if (typeof t == "string")
|
|
71
|
+
return ["1", "true", "yes", "on"].includes(t.toLowerCase());
|
|
72
|
+
}
|
|
73
|
+
return !1;
|
|
74
|
+
}
|
|
75
|
+
const I = {
|
|
76
|
+
debug(r, ...e) {
|
|
77
|
+
M() && console.debug(`[PluginRegistry] ${r}`, ...e);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
class R {
|
|
81
|
+
constructor() {
|
|
82
|
+
c(this, "plugins", /* @__PURE__ */ new Map());
|
|
83
|
+
c(this, "menuCache", null);
|
|
84
|
+
c(this, "serviceContainer", new N());
|
|
85
|
+
c(this, "routeOwners", /* @__PURE__ */ new Map());
|
|
86
|
+
c(this, "menuNameOwners", /* @__PURE__ */ new Map());
|
|
87
|
+
c(this, "menuRouteOwners", /* @__PURE__ */ new Map());
|
|
88
|
+
c(this, "serviceOwners", /* @__PURE__ */ new Map());
|
|
89
|
+
c(this, "diagnostics", []);
|
|
90
|
+
c(this, "permissionChecker", null);
|
|
91
|
+
c(this, "eventListeners", {
|
|
92
|
+
"plugin:registered": /* @__PURE__ */ new Set(),
|
|
93
|
+
"plugin:error": /* @__PURE__ */ new Set()
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Register a plugin with the registry.
|
|
98
|
+
*
|
|
99
|
+
* validates that the plugin has name and version, checks collisions,
|
|
100
|
+
* stores it, registers any services into the ServiceContainer,
|
|
101
|
+
* and invalidates the menu cache.
|
|
102
|
+
*
|
|
103
|
+
* @throws Error if plugin has no name or version
|
|
104
|
+
*/
|
|
105
|
+
async register(e) {
|
|
106
|
+
if (!e.name || !e.version)
|
|
107
|
+
throw new Error("Plugin must have name and version");
|
|
108
|
+
const t = this.validateRegistration(e), i = t.filter((o) => o.severity === "error");
|
|
109
|
+
if (i.length > 0) {
|
|
110
|
+
const o = new Error(
|
|
111
|
+
`Plugin "${e.name}" registration failed: ${i.map((a) => a.message).join("; ")}`
|
|
112
|
+
);
|
|
113
|
+
throw this.emit("plugin:error", { plugin: e, error: o }), o;
|
|
114
|
+
}
|
|
115
|
+
const s = this.plugins.get(e.name);
|
|
116
|
+
this.plugins.set(e.name, e), this.rebuildIndexesAndServices(), this.menuCache = null;
|
|
117
|
+
try {
|
|
118
|
+
e.onInit && await Promise.resolve(e.onInit());
|
|
119
|
+
} catch (o) {
|
|
120
|
+
throw s ? this.plugins.set(e.name, s) : this.plugins.delete(e.name), this.rebuildIndexesAndServices(), this.menuCache = null, this.emit("plugin:error", { plugin: e, error: o }), o;
|
|
121
|
+
}
|
|
122
|
+
s != null && s.onDestroy && s !== e && await Promise.resolve(s.onDestroy()), this.emit("plugin:registered", { plugin: e });
|
|
123
|
+
const n = t.filter((o) => o.severity === "warning");
|
|
124
|
+
n.length > 0 && I.debug(
|
|
125
|
+
`Registered plugin "${e.name}" with warnings`,
|
|
126
|
+
n
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get merged menu tree from all plugins.
|
|
131
|
+
*
|
|
132
|
+
* Collects all MenuItem entries, groups them by `module` (default: "Other"),
|
|
133
|
+
* optionally sub-groups by `category`, and sorts by `order`.
|
|
134
|
+
* Result is cached until a new plugin is registered.
|
|
135
|
+
*/
|
|
136
|
+
getMenu() {
|
|
137
|
+
if (this.menuCache) return this.menuCache;
|
|
138
|
+
const e = [];
|
|
139
|
+
for (const t of this.plugins.values())
|
|
140
|
+
t.menu && e.push(...t.menu);
|
|
141
|
+
return e.length === 0 ? (this.menuCache = [], this.menuCache) : (this.menuCache = this.mergeMenus(e), this.menuCache);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get aggregated routes from all plugins.
|
|
145
|
+
*/
|
|
146
|
+
getRoutes() {
|
|
147
|
+
const e = [];
|
|
148
|
+
for (const t of this.plugins.values())
|
|
149
|
+
t.routes && e.push(...t.routes);
|
|
150
|
+
return e;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get a specific plugin by name.
|
|
154
|
+
*/
|
|
155
|
+
getPlugin(e) {
|
|
156
|
+
return this.plugins.get(e);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get all registered plugins.
|
|
160
|
+
*/
|
|
161
|
+
getAllPlugins() {
|
|
162
|
+
return Array.from(this.plugins.values());
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get the service container for DI.
|
|
166
|
+
*/
|
|
167
|
+
getServiceContainer() {
|
|
168
|
+
return this.serviceContainer;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Unregister a plugin by name and run cleanup lifecycle if provided.
|
|
172
|
+
*/
|
|
173
|
+
async unregister(e) {
|
|
174
|
+
const t = this.plugins.get(e);
|
|
175
|
+
return t ? (this.plugins.delete(e), this.rebuildIndexesAndServices(), this.menuCache = null, t.onDestroy && await Promise.resolve(t.onDestroy()), !0) : !1;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Resolve a service instance from the registry-level DI container.
|
|
179
|
+
*/
|
|
180
|
+
async getService(e) {
|
|
181
|
+
return this.serviceContainer.get(e);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Aggregate all widgets contributed by registered plugins.
|
|
185
|
+
*/
|
|
186
|
+
getWidgets() {
|
|
187
|
+
const e = [];
|
|
188
|
+
for (const t of this.plugins.values())
|
|
189
|
+
t.widgets && e.push(...t.widgets);
|
|
190
|
+
return e;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Configure a permission checker used by consumer hooks.
|
|
194
|
+
*/
|
|
195
|
+
setPermissionChecker(e) {
|
|
196
|
+
this.permissionChecker = e;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get the configured permission checker.
|
|
200
|
+
*/
|
|
201
|
+
getPermissionChecker() {
|
|
202
|
+
return this.permissionChecker;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Subscribe to plugin lifecycle events.
|
|
206
|
+
*/
|
|
207
|
+
on(e, t) {
|
|
208
|
+
return this.eventListeners[e].add(
|
|
209
|
+
t
|
|
210
|
+
), () => {
|
|
211
|
+
this.eventListeners[e].delete(
|
|
212
|
+
t
|
|
213
|
+
);
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get emitted diagnostics.
|
|
218
|
+
*/
|
|
219
|
+
getDiagnostics(e) {
|
|
220
|
+
return e ? this.diagnostics.filter((t) => t.severity === e) : [...this.diagnostics];
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Clear diagnostics.
|
|
224
|
+
*/
|
|
225
|
+
clearDiagnostics() {
|
|
226
|
+
this.diagnostics = [];
|
|
227
|
+
}
|
|
228
|
+
// -------------------------------------------------------------------------
|
|
229
|
+
// Version Compatibility
|
|
230
|
+
// -------------------------------------------------------------------------
|
|
231
|
+
/**
|
|
232
|
+
* Check compatibility of all registered plugins.
|
|
233
|
+
*
|
|
234
|
+
* Returns a report of all plugins with their compatibility status:
|
|
235
|
+
* - SDK version compatibility
|
|
236
|
+
* - Peer plugin dependency satisfaction
|
|
237
|
+
*/
|
|
238
|
+
checkCompatibility() {
|
|
239
|
+
const e = [];
|
|
240
|
+
for (const t of this.plugins.values()) {
|
|
241
|
+
const i = {
|
|
242
|
+
name: t.name,
|
|
243
|
+
version: t.version,
|
|
244
|
+
sdkCompatible: !0,
|
|
245
|
+
missingPeerPlugins: []
|
|
246
|
+
};
|
|
247
|
+
if (t.minSdkVersion && (i.sdkCompatible = this.checkSemverCompat(
|
|
248
|
+
v,
|
|
249
|
+
t.minSdkVersion
|
|
250
|
+
)), t.peerPlugins)
|
|
251
|
+
for (const s of t.peerPlugins)
|
|
252
|
+
this.plugins.has(s) || i.missingPeerPlugins.push(s);
|
|
253
|
+
e.push(i);
|
|
254
|
+
}
|
|
255
|
+
return e;
|
|
256
|
+
}
|
|
257
|
+
// -------------------------------------------------------------------------
|
|
258
|
+
// Private helpers
|
|
259
|
+
// -------------------------------------------------------------------------
|
|
260
|
+
/**
|
|
261
|
+
* Simple semver compatibility check.
|
|
262
|
+
* Checks if `current` satisfies `>=required`.
|
|
263
|
+
*/
|
|
264
|
+
checkSemverCompat(e, t) {
|
|
265
|
+
const i = t.replace(/^>=?/, ""), [s, n = 0, o = 0] = e.split(".").map(Number), [a, u = 0, m = 0] = i.split(".").map(Number);
|
|
266
|
+
return s !== a ? s > a : n !== u ? n > u : o >= m;
|
|
267
|
+
}
|
|
268
|
+
validateRegistration(e) {
|
|
269
|
+
return [
|
|
270
|
+
...this.validateSdkCompatibility(e),
|
|
271
|
+
...this.validateDuplicatePlugin(e),
|
|
272
|
+
...this.validateRouteCollisions(e),
|
|
273
|
+
...this.validateMenuCollisions(e),
|
|
274
|
+
...this.validateServiceCollisions(e)
|
|
275
|
+
];
|
|
276
|
+
}
|
|
277
|
+
validateSdkCompatibility(e) {
|
|
278
|
+
const t = [];
|
|
279
|
+
return e.minSdkVersion && (this.checkSemverCompat(
|
|
280
|
+
v,
|
|
281
|
+
e.minSdkVersion
|
|
282
|
+
) || t.push(
|
|
283
|
+
this.addDiagnostic({
|
|
284
|
+
code: "SDK_INCOMPATIBLE",
|
|
285
|
+
severity: "warning",
|
|
286
|
+
message: `Plugin ${e.name} requires SDK >=${e.minSdkVersion}, current SDK is ${v}. Plugin may not work correctly.`,
|
|
287
|
+
pluginName: e.name,
|
|
288
|
+
key: e.minSdkVersion
|
|
289
|
+
})
|
|
290
|
+
)), t;
|
|
291
|
+
}
|
|
292
|
+
validateDuplicatePlugin(e) {
|
|
293
|
+
const t = [];
|
|
294
|
+
return this.plugins.has(e.name) && t.push(
|
|
295
|
+
this.addDiagnostic({
|
|
296
|
+
code: "PLUGIN_DUPLICATE",
|
|
297
|
+
severity: "warning",
|
|
298
|
+
message: `Plugin ${e.name} is already registered and will be replaced`,
|
|
299
|
+
pluginName: e.name,
|
|
300
|
+
conflictingPluginName: e.name,
|
|
301
|
+
key: e.name
|
|
302
|
+
})
|
|
303
|
+
), t;
|
|
304
|
+
}
|
|
305
|
+
validateRouteCollisions(e) {
|
|
306
|
+
const t = [], i = /* @__PURE__ */ new Set();
|
|
307
|
+
for (const s of e.routes ?? []) {
|
|
308
|
+
if (i.has(s.path)) {
|
|
309
|
+
t.push(
|
|
310
|
+
this.addDiagnostic({
|
|
311
|
+
code: "ROUTE_COLLISION",
|
|
312
|
+
severity: "error",
|
|
313
|
+
message: `Duplicate route path "${s.path}" inside plugin ${e.name}`,
|
|
314
|
+
pluginName: e.name,
|
|
315
|
+
conflictingPluginName: e.name,
|
|
316
|
+
key: s.path
|
|
317
|
+
})
|
|
318
|
+
);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
i.add(s.path);
|
|
322
|
+
const n = this.routeOwners.get(s.path);
|
|
323
|
+
n && n !== e.name && t.push(
|
|
324
|
+
this.addDiagnostic({
|
|
325
|
+
code: "ROUTE_COLLISION",
|
|
326
|
+
severity: "error",
|
|
327
|
+
message: `Route path "${s.path}" from plugin ${e.name} conflicts with plugin ${n}`,
|
|
328
|
+
pluginName: e.name,
|
|
329
|
+
conflictingPluginName: n,
|
|
330
|
+
key: s.path
|
|
331
|
+
})
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
return t;
|
|
335
|
+
}
|
|
336
|
+
validateMenuCollisions(e) {
|
|
337
|
+
const t = [], i = /* @__PURE__ */ new Set(), s = /* @__PURE__ */ new Set();
|
|
338
|
+
for (const n of e.menu ?? []) {
|
|
339
|
+
if (i.has(n.name))
|
|
340
|
+
t.push(
|
|
341
|
+
this.addDiagnostic({
|
|
342
|
+
code: "MENU_NAME_COLLISION",
|
|
343
|
+
severity: "error",
|
|
344
|
+
message: `Duplicate menu name "${n.name}" inside plugin ${e.name}`,
|
|
345
|
+
pluginName: e.name,
|
|
346
|
+
conflictingPluginName: e.name,
|
|
347
|
+
key: n.name
|
|
348
|
+
})
|
|
349
|
+
);
|
|
350
|
+
else {
|
|
351
|
+
i.add(n.name);
|
|
352
|
+
const o = this.menuNameOwners.get(n.name);
|
|
353
|
+
o && o !== e.name && t.push(
|
|
354
|
+
this.addDiagnostic({
|
|
355
|
+
code: "MENU_NAME_COLLISION",
|
|
356
|
+
severity: "error",
|
|
357
|
+
message: `Menu name "${n.name}" from plugin ${e.name} conflicts with plugin ${o}`,
|
|
358
|
+
pluginName: e.name,
|
|
359
|
+
conflictingPluginName: o,
|
|
360
|
+
key: n.name
|
|
361
|
+
})
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
if (s.has(n.route))
|
|
365
|
+
t.push(
|
|
366
|
+
this.addDiagnostic({
|
|
367
|
+
code: "MENU_ROUTE_COLLISION",
|
|
368
|
+
severity: "error",
|
|
369
|
+
message: `Duplicate menu route "${n.route}" inside plugin ${e.name}`,
|
|
370
|
+
pluginName: e.name,
|
|
371
|
+
conflictingPluginName: e.name,
|
|
372
|
+
key: n.route
|
|
373
|
+
})
|
|
374
|
+
);
|
|
375
|
+
else {
|
|
376
|
+
s.add(n.route);
|
|
377
|
+
const o = this.menuRouteOwners.get(n.route);
|
|
378
|
+
o && o !== e.name && t.push(
|
|
379
|
+
this.addDiagnostic({
|
|
380
|
+
code: "MENU_ROUTE_COLLISION",
|
|
381
|
+
severity: "error",
|
|
382
|
+
message: `Menu route "${n.route}" from plugin ${e.name} conflicts with plugin ${o}`,
|
|
383
|
+
pluginName: e.name,
|
|
384
|
+
conflictingPluginName: o,
|
|
385
|
+
key: n.route
|
|
386
|
+
})
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return t;
|
|
391
|
+
}
|
|
392
|
+
validateServiceCollisions(e) {
|
|
393
|
+
const t = [], i = /* @__PURE__ */ new Set();
|
|
394
|
+
for (const s of Object.keys(e.services ?? {})) {
|
|
395
|
+
if (i.has(s)) {
|
|
396
|
+
t.push(
|
|
397
|
+
this.addDiagnostic({
|
|
398
|
+
code: "SERVICE_COLLISION",
|
|
399
|
+
severity: "error",
|
|
400
|
+
message: `Duplicate service "${s}" inside plugin ${e.name}`,
|
|
401
|
+
pluginName: e.name,
|
|
402
|
+
conflictingPluginName: e.name,
|
|
403
|
+
key: s
|
|
404
|
+
})
|
|
405
|
+
);
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
i.add(s);
|
|
409
|
+
const n = this.serviceOwners.get(s);
|
|
410
|
+
n && n !== e.name && t.push(
|
|
411
|
+
this.addDiagnostic({
|
|
412
|
+
code: "SERVICE_COLLISION",
|
|
413
|
+
severity: "error",
|
|
414
|
+
message: `Service "${s}" from plugin ${e.name} conflicts with plugin ${n}`,
|
|
415
|
+
pluginName: e.name,
|
|
416
|
+
conflictingPluginName: n,
|
|
417
|
+
key: s
|
|
418
|
+
})
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
return t;
|
|
422
|
+
}
|
|
423
|
+
addDiagnostic(e) {
|
|
424
|
+
const t = {
|
|
425
|
+
...e,
|
|
426
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
427
|
+
};
|
|
428
|
+
return this.diagnostics.push(t), t;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Merge flat menu items into a grouped tree:
|
|
432
|
+
* Module → (optional) Category → Items
|
|
433
|
+
*
|
|
434
|
+
* Items without a `module` are placed under "Other".
|
|
435
|
+
*/
|
|
436
|
+
mergeMenus(e) {
|
|
437
|
+
var i;
|
|
438
|
+
const t = /* @__PURE__ */ new Map();
|
|
439
|
+
for (const s of e) {
|
|
440
|
+
const n = s.module || "Other";
|
|
441
|
+
t.has(n) || t.set(n, {
|
|
442
|
+
name: n.toLowerCase(),
|
|
443
|
+
label: n,
|
|
444
|
+
route: `/${n.toLowerCase()}`,
|
|
445
|
+
icon: this.getModuleIcon(n),
|
|
446
|
+
order: s.order,
|
|
447
|
+
children: []
|
|
448
|
+
});
|
|
449
|
+
const o = t.get(n);
|
|
450
|
+
if (s.order !== void 0 && (o.order === void 0 || s.order < o.order) && (o.order = s.order), s.category) {
|
|
451
|
+
let a = (i = o.children) == null ? void 0 : i.find(
|
|
452
|
+
(u) => u.label === s.category
|
|
453
|
+
);
|
|
454
|
+
a || (a = {
|
|
455
|
+
name: s.category.toLowerCase(),
|
|
456
|
+
label: s.category,
|
|
457
|
+
route: `/${n.toLowerCase()}/${s.category.toLowerCase()}`,
|
|
458
|
+
children: []
|
|
459
|
+
}, o.children.push(a)), a.children.push(s);
|
|
460
|
+
} else
|
|
461
|
+
o.children.push(s);
|
|
462
|
+
}
|
|
463
|
+
return Array.from(t.values()).sort(
|
|
464
|
+
(s, n) => (s.order ?? 999) - (n.order ?? 999)
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
getModuleIcon(e) {
|
|
468
|
+
return w[e] || w.Other;
|
|
469
|
+
}
|
|
470
|
+
rebuildIndexesAndServices() {
|
|
471
|
+
this.routeOwners.clear(), this.menuNameOwners.clear(), this.menuRouteOwners.clear(), this.serviceOwners.clear(), this.serviceContainer.clear();
|
|
472
|
+
for (const e of this.plugins.values())
|
|
473
|
+
this.registerPluginServices(e), this.registerPluginRoutes(e), this.registerPluginMenuItems(e);
|
|
474
|
+
}
|
|
475
|
+
registerPluginServices(e) {
|
|
476
|
+
for (const [t, i] of Object.entries(e.services ?? {}))
|
|
477
|
+
this.serviceContainer.register(t, i), this.serviceOwners.set(t, e.name);
|
|
478
|
+
}
|
|
479
|
+
registerPluginRoutes(e) {
|
|
480
|
+
for (const t of e.routes ?? [])
|
|
481
|
+
this.routeOwners.set(t.path, e.name);
|
|
482
|
+
}
|
|
483
|
+
registerPluginMenuItems(e) {
|
|
484
|
+
for (const t of e.menu ?? [])
|
|
485
|
+
this.menuNameOwners.set(t.name, e.name), this.menuRouteOwners.set(t.route, e.name);
|
|
486
|
+
}
|
|
487
|
+
emit(e, t) {
|
|
488
|
+
for (const i of this.eventListeners[e])
|
|
489
|
+
i(t);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const h = O(null);
|
|
493
|
+
function b({
|
|
494
|
+
children: r,
|
|
495
|
+
registry: e
|
|
496
|
+
}) {
|
|
497
|
+
const [t] = l(
|
|
498
|
+
() => e ?? new R()
|
|
499
|
+
), [i, s] = l(!1);
|
|
500
|
+
return d(() => {
|
|
501
|
+
s(!0);
|
|
502
|
+
}, []), i ? /* @__PURE__ */ S(h.Provider, { value: t, children: r }) : null;
|
|
503
|
+
}
|
|
504
|
+
async function y(r, e) {
|
|
505
|
+
const t = [];
|
|
506
|
+
for (const i of r) {
|
|
507
|
+
if (i.hidden || i.permissions && i.permissions.length > 0 && !await Promise.resolve(e(i.permissions)))
|
|
508
|
+
continue;
|
|
509
|
+
const s = i.children ? await y(i.children, e) : void 0;
|
|
510
|
+
i.children && ((s == null ? void 0 : s.length) ?? 0) === 0 || t.push({ ...i, children: s });
|
|
511
|
+
}
|
|
512
|
+
return t;
|
|
513
|
+
}
|
|
514
|
+
function $() {
|
|
515
|
+
const r = f(h), [e, t] = l([]);
|
|
516
|
+
if (!r)
|
|
517
|
+
throw new Error("usePluginMenu must be used within PluginRegistryProvider");
|
|
518
|
+
return d(() => {
|
|
519
|
+
let i = !1;
|
|
520
|
+
return (async () => {
|
|
521
|
+
const n = r.getMenu(), o = r.getPermissionChecker();
|
|
522
|
+
if (!o) {
|
|
523
|
+
i || t(n.filter((u) => !u.hidden));
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const a = await y(n, o);
|
|
527
|
+
i || t(a);
|
|
528
|
+
})(), () => {
|
|
529
|
+
i = !0;
|
|
530
|
+
};
|
|
531
|
+
}, [r]), e;
|
|
532
|
+
}
|
|
533
|
+
function _(r) {
|
|
534
|
+
const e = f(h);
|
|
535
|
+
if (!e)
|
|
536
|
+
throw new Error("usePlugin must be used within PluginRegistryProvider");
|
|
537
|
+
return p(() => e.getPlugin(r), [e, r]);
|
|
538
|
+
}
|
|
539
|
+
function A(r) {
|
|
540
|
+
const e = f(h);
|
|
541
|
+
if (!e)
|
|
542
|
+
throw new Error("useService must be used within PluginRegistryProvider");
|
|
543
|
+
const [t, i] = l(null), [s, n] = l(!0), [o, a] = l(null), u = p(
|
|
544
|
+
() => e.getService(r),
|
|
545
|
+
[e, r]
|
|
546
|
+
);
|
|
547
|
+
return d(() => {
|
|
548
|
+
let m = !1;
|
|
549
|
+
return n(!0), a(null), u.then((g) => {
|
|
550
|
+
m || (i(g), n(!1));
|
|
551
|
+
}).catch((g) => {
|
|
552
|
+
m || (a(g instanceof Error ? g : new Error(String(g))), n(!1));
|
|
553
|
+
}), () => {
|
|
554
|
+
m = !0;
|
|
555
|
+
};
|
|
556
|
+
}, [u]), { service: t, isLoading: s, error: o };
|
|
557
|
+
}
|
|
558
|
+
async function k(r, e) {
|
|
559
|
+
const t = [];
|
|
560
|
+
for (const i of r)
|
|
561
|
+
i.permissions && i.permissions.length > 0 && !await Promise.resolve(e(i.permissions)) || t.push(i);
|
|
562
|
+
return t;
|
|
563
|
+
}
|
|
564
|
+
function U() {
|
|
565
|
+
const r = f(h), [e, t] = l([]);
|
|
566
|
+
if (!r)
|
|
567
|
+
throw new Error("useWidgets must be used within PluginRegistryProvider");
|
|
568
|
+
return d(() => {
|
|
569
|
+
let i = !1;
|
|
570
|
+
return (async () => {
|
|
571
|
+
const n = r.getWidgets(), o = r.getPermissionChecker();
|
|
572
|
+
if (!o) {
|
|
573
|
+
i || t(n);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const a = await k(n, o);
|
|
577
|
+
i || t(a);
|
|
578
|
+
})(), () => {
|
|
579
|
+
i = !0;
|
|
580
|
+
};
|
|
581
|
+
}, [r]), e;
|
|
582
|
+
}
|
|
583
|
+
export {
|
|
584
|
+
R as PluginRegistry,
|
|
585
|
+
h as PluginRegistryContext,
|
|
586
|
+
b as PluginRegistryProvider,
|
|
587
|
+
v as SDK_VERSION,
|
|
588
|
+
N as ServiceContainer,
|
|
589
|
+
_ as usePlugin,
|
|
590
|
+
$ as usePluginMenu,
|
|
591
|
+
A as useService,
|
|
592
|
+
U as useWidgets
|
|
593
|
+
};
|
|
594
|
+
//# sourceMappingURL=index.js.map
|