@veap/plugins 0.0.0-stage → 0.1.0

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 (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +131 -2
  3. package/dist/actions/index.d.ts +1 -0
  4. package/dist/actions/index.js +1 -0
  5. package/dist/actions/plugin.actions.d.ts +6 -0
  6. package/dist/actions/plugin.actions.js +60 -0
  7. package/dist/actions.d.ts +1 -0
  8. package/dist/actions.js +1 -0
  9. package/dist/cli/add.d.ts +4 -0
  10. package/dist/cli/add.js +191 -0
  11. package/dist/cli/eject.d.ts +2 -0
  12. package/dist/cli/eject.js +136 -0
  13. package/dist/cli/generate-plugin.d.ts +3 -0
  14. package/dist/cli/generate-plugin.js +48 -0
  15. package/dist/cli/index.d.ts +6 -0
  16. package/dist/cli/index.js +6 -0
  17. package/dist/cli/package-manager.d.ts +14 -0
  18. package/dist/cli/package-manager.js +62 -0
  19. package/dist/cli/stubs.d.ts +16 -0
  20. package/dist/cli/stubs.js +42 -0
  21. package/dist/cli/utils.d.ts +3 -0
  22. package/dist/cli/utils.js +164 -0
  23. package/dist/client.d.ts +6 -0
  24. package/dist/client.js +8 -0
  25. package/dist/common/context.d.ts +26 -0
  26. package/dist/common/context.js +26 -0
  27. package/dist/common/events.d.ts +19 -0
  28. package/dist/common/events.js +26 -0
  29. package/dist/common/index.d.ts +3 -0
  30. package/dist/common/index.js +3 -0
  31. package/dist/common/types.d.ts +195 -0
  32. package/dist/common/types.js +40 -0
  33. package/dist/index.d.ts +5 -0
  34. package/dist/index.js +5 -0
  35. package/dist/migrations/0001_create_plugins_tables.d.ts +4 -0
  36. package/dist/migrations/0001_create_plugins_tables.js +29 -0
  37. package/dist/migrations/index.d.ts +4 -0
  38. package/dist/migrations/index.js +7 -0
  39. package/dist/models/SystemPlugin.d.ts +19 -0
  40. package/dist/models/SystemPlugin.js +14 -0
  41. package/dist/models/SystemUserWidget.d.ts +19 -0
  42. package/dist/models/SystemUserWidget.js +17 -0
  43. package/dist/models/index.d.ts +2 -0
  44. package/dist/models/index.js +2 -0
  45. package/dist/models.d.ts +1 -0
  46. package/dist/models.js +1 -0
  47. package/dist/navigation/breadcrumbs.d.ts +2 -0
  48. package/dist/navigation/breadcrumbs.js +84 -0
  49. package/dist/navigation/index.d.ts +2 -0
  50. package/dist/navigation/index.js +2 -0
  51. package/dist/navigation/navigation.service.d.ts +27 -0
  52. package/dist/navigation/navigation.service.js +183 -0
  53. package/dist/providers/index.d.ts +1 -0
  54. package/dist/providers/index.js +1 -0
  55. package/dist/providers/plugins.provider.d.ts +20 -0
  56. package/dist/providers/plugins.provider.js +142 -0
  57. package/dist/registry/contracts.d.ts +72 -0
  58. package/dist/registry/contracts.js +4 -0
  59. package/dist/registry/facade.d.ts +45 -0
  60. package/dist/registry/facade.js +49 -0
  61. package/dist/registry/index.d.ts +5 -0
  62. package/dist/registry/index.js +5 -0
  63. package/dist/registry/migration-runner.d.ts +8 -0
  64. package/dist/registry/migration-runner.js +12 -0
  65. package/dist/registry/plugin.repository.d.ts +19 -0
  66. package/dist/registry/plugin.repository.js +74 -0
  67. package/dist/registry/registry.service.d.ts +69 -0
  68. package/dist/registry/registry.service.js +535 -0
  69. package/dist/router/discovery.d.ts +27 -0
  70. package/dist/router/discovery.js +37 -0
  71. package/dist/router/handler.d.ts +6 -0
  72. package/dist/router/handler.js +40 -0
  73. package/dist/router/index.d.ts +13 -0
  74. package/dist/router/index.js +13 -0
  75. package/dist/router/matcher.d.ts +5 -0
  76. package/dist/router/matcher.js +32 -0
  77. package/dist/router/middlewares.d.ts +25 -0
  78. package/dist/router/middlewares.js +119 -0
  79. package/dist/router/react/error-boundary.d.ts +30 -0
  80. package/dist/router/react/error-boundary.js +68 -0
  81. package/dist/router/react/index.d.ts +4 -0
  82. package/dist/router/react/index.js +4 -0
  83. package/dist/router/react/renderer.d.ts +13 -0
  84. package/dist/router/react/renderer.js +159 -0
  85. package/dist/router/react/soft-navigation.d.ts +4 -0
  86. package/dist/router/react/soft-navigation.js +34 -0
  87. package/dist/router/react/with-router.d.ts +22 -0
  88. package/dist/router/react/with-router.js +19 -0
  89. package/dist/router/route-tree.d.ts +177 -0
  90. package/dist/router/route-tree.js +633 -0
  91. package/dist/router/router.service.d.ts +25 -0
  92. package/dist/router/router.service.js +127 -0
  93. package/dist/router/scanner.d.ts +4 -0
  94. package/dist/router/scanner.js +337 -0
  95. package/dist/router/utils.d.ts +10 -0
  96. package/dist/router/utils.js +48 -0
  97. package/dist/router.d.ts +6 -0
  98. package/dist/router.js +6 -0
  99. package/dist/server.d.ts +11 -0
  100. package/dist/server.js +17 -0
  101. package/dist/widgets/index.d.ts +8 -0
  102. package/dist/widgets/index.js +8 -0
  103. package/dist/widgets/path-prefix-context.d.ts +5 -0
  104. package/dist/widgets/path-prefix-context.js +9 -0
  105. package/dist/widgets/plugin-extension-point-client.d.ts +15 -0
  106. package/dist/widgets/plugin-extension-point-client.js +44 -0
  107. package/dist/widgets/plugin-extension-point.d.ts +14 -0
  108. package/dist/widgets/plugin-extension-point.js +51 -0
  109. package/dist/widgets/plugin-widget-area-client.d.ts +11 -0
  110. package/dist/widgets/plugin-widget-area-client.js +31 -0
  111. package/dist/widgets/plugin-widget-area.d.ts +10 -0
  112. package/dist/widgets/plugin-widget-area.js +19 -0
  113. package/dist/widgets/use-confirm-action.d.ts +19 -0
  114. package/dist/widgets/use-confirm-action.js +68 -0
  115. package/dist/widgets/widget-composer-client.d.ts +21 -0
  116. package/dist/widgets/widget-composer-client.js +281 -0
  117. package/dist/widgets/widget-composer.d.ts +8 -0
  118. package/dist/widgets/widget-composer.js +28 -0
  119. package/package.json +115 -4
@@ -0,0 +1,535 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { AppError, EVENT_BUS, Inject, Injectable, LOGGER, } from "@veap/kernel";
14
+ import { MIGRATION_RUNNER, PLUGIN_REPOSITORY, } from "./contracts";
15
+ /**
16
+ * Normalizes an npm dependency string (e.g. "@veap/media-plugin") to a registered plugin id (e.g. "media-plugin").
17
+ */
18
+ const resolveDependencyId = (depId) => depId.replace(/^@[^/]+\//, "");
19
+ let PluginRegistry = class PluginRegistry {
20
+ repository;
21
+ migrationRunner;
22
+ logger;
23
+ eventBus;
24
+ plugins = new Map();
25
+ pluginStatus = new Map();
26
+ processingPlugins = new Set();
27
+ initPromise = null;
28
+ initialized = false;
29
+ constructor(repository, migrationRunner, logger, eventBus) {
30
+ this.repository = repository;
31
+ this.migrationRunner = migrationRunner;
32
+ this.logger = logger;
33
+ this.eventBus = eventBus;
34
+ }
35
+ /** Lookup a plugin by id, falling back to the npm-name form. */
36
+ findPlugin(id) {
37
+ return this.plugins.get(id) ?? this.plugins.get(resolveDependencyId(id));
38
+ }
39
+ register(plugin) {
40
+ if (this.plugins.has(plugin.manifest.id)) {
41
+ // return;
42
+ }
43
+ this.plugins.set(plugin.manifest.id, plugin);
44
+ // Rekurencyjna rejestracja wtyczek zależnych
45
+ if (plugin.plugins && Array.isArray(plugin.plugins)) {
46
+ for (const nested of plugin.plugins) {
47
+ this.register(nested);
48
+ }
49
+ }
50
+ if (process.env.NODE_ENV === "development") {
51
+ // In development, we want to allow re-registration to pick up HMR changes
52
+ this.plugins.set(plugin.manifest.id, plugin);
53
+ }
54
+ }
55
+ async updateStatus(id, update) {
56
+ const current = this.pluginStatus.get(id) || {
57
+ enabled: false,
58
+ installed: false,
59
+ lastStep: null,
60
+ };
61
+ const next = { ...current, ...update };
62
+ this.pluginStatus.set(id, next);
63
+ if (update.lastStep !== undefined && update.lastStep !== null) {
64
+ this.logger.info("PluginRegistry", `"${id}" step: ${update.lastStep}`);
65
+ }
66
+ const isStateTransition = update.enabled !== undefined || update.installed !== undefined;
67
+ try {
68
+ await this.repository.upsertStatus(id, update, {
69
+ system: this.plugins.get(id)?.manifest.system ?? false,
70
+ });
71
+ }
72
+ catch (e) {
73
+ if (isStateTransition) {
74
+ // Rollback in-memory state on DB failure to avoid split-brain
75
+ this.pluginStatus.set(id, current);
76
+ this.logger.error("PluginRegistry", `Critical: DB status sync failed for plugin "${id}". Rolling back in-memory state:`, e);
77
+ throw e;
78
+ }
79
+ else {
80
+ // Ephemeral progress steps (lastStep) can safely fail without aborting
81
+ this.logger.warn("PluginRegistry", `DB step sync failed for "${id}":`, e);
82
+ }
83
+ }
84
+ }
85
+ async updateStep(id, step) {
86
+ await this.updateStatus(id, { lastStep: step });
87
+ }
88
+ sortPlugins(plugins) {
89
+ const sorted = [];
90
+ const visited = new Set();
91
+ const processing = new Set();
92
+ const visit = (plugin) => {
93
+ if (visited.has(plugin.manifest.id))
94
+ return;
95
+ if (processing.has(plugin.manifest.id)) {
96
+ throw AppError.Internal(`Circular dependency detected: ${plugin.manifest.id}`);
97
+ }
98
+ processing.add(plugin.manifest.id);
99
+ const dependencies = plugin.manifest.dependencies || [];
100
+ for (const depId of dependencies) {
101
+ const depPlugin = this.findPlugin(depId);
102
+ if (depPlugin) {
103
+ visit(depPlugin);
104
+ }
105
+ else {
106
+ this.logger.warn("PluginRegistry", `Missing dependency: ${depId} for plugin ${plugin.manifest.id}`);
107
+ }
108
+ }
109
+ processing.delete(plugin.manifest.id);
110
+ visited.add(plugin.manifest.id);
111
+ sorted.push(plugin);
112
+ };
113
+ // First, visit system plugins
114
+ for (const plugin of plugins) {
115
+ if (plugin.manifest.system) {
116
+ visit(plugin);
117
+ }
118
+ }
119
+ // Then visit the rest
120
+ for (const plugin of plugins) {
121
+ visit(plugin);
122
+ }
123
+ return sorted;
124
+ }
125
+ async init() {
126
+ // if (this.initialized && process.env.NODE_ENV !== "development") return;
127
+ if (this.initPromise) {
128
+ return this.initPromise;
129
+ }
130
+ this.initPromise = (async () => {
131
+ try {
132
+ await this.eventBus.publish("system:plugins:init:start", {
133
+ timestamp: Date.now(),
134
+ });
135
+ // 1. Sync with database
136
+ const pluginValues = Array.from(this.plugins.values()).map((plugin) => ({
137
+ id: plugin.manifest.id,
138
+ enabled: plugin.manifest.system ?? false,
139
+ installed: plugin.manifest.system ?? false,
140
+ system: plugin.manifest.system ?? false,
141
+ }));
142
+ if (pluginValues.length > 0) {
143
+ try {
144
+ await this.repository.seed(pluginValues);
145
+ }
146
+ catch (_e) {
147
+ // Ignore errors if table doesn't exist yet during bootstrap
148
+ }
149
+ }
150
+ // 2. Fetch current status from DB
151
+ try {
152
+ const dbPlugins = await this.repository.findAll();
153
+ for (const dbp of dbPlugins) {
154
+ this.pluginStatus.set(dbp.id, {
155
+ enabled: dbp.enabled || dbp.system,
156
+ installed: dbp.installed || dbp.system,
157
+ lastStep: dbp.lastStep,
158
+ });
159
+ }
160
+ }
161
+ catch (_e) {
162
+ for (const plugin of this.plugins.values()) {
163
+ this.pluginStatus.set(plugin.manifest.id, {
164
+ enabled: plugin.manifest.system ?? false,
165
+ installed: true,
166
+ lastStep: null,
167
+ });
168
+ }
169
+ }
170
+ // 3. Sort plugins by dependencies
171
+ const sortedPlugins = this.sortPlugins(Array.from(this.plugins.values()));
172
+ // 4. Initialize enabled plugins
173
+ for (const plugin of sortedPlugins) {
174
+ const status = this.pluginStatus.get(plugin.manifest.id);
175
+ if (status?.enabled) {
176
+ if (plugin.migrations && plugin.migrations.length > 0) {
177
+ await this.migrationRunner.run(plugin.manifest.id, plugin.migrations);
178
+ }
179
+ if (!status.installed) {
180
+ await this.updateStep(plugin.manifest.id, "Installing...");
181
+ if (plugin.onMigrate)
182
+ await plugin.onMigrate();
183
+ if (plugin.onEnable)
184
+ await plugin.onEnable();
185
+ await this.updateStatus(plugin.manifest.id, {
186
+ installed: true,
187
+ lastStep: null,
188
+ });
189
+ }
190
+ if (plugin.init)
191
+ await plugin.init();
192
+ }
193
+ }
194
+ this.initialized = true;
195
+ this.logger.info("veap:plugins", `Initialized with ${this.plugins.size} plugins`);
196
+ await this.eventBus.publish("system:plugins:init:end", {
197
+ timestamp: Date.now(),
198
+ pluginCount: this.plugins.size,
199
+ });
200
+ }
201
+ catch (error) {
202
+ this.initPromise = null;
203
+ throw error;
204
+ }
205
+ })();
206
+ return this.initPromise;
207
+ }
208
+ getPlugins() {
209
+ return Array.from(this.plugins.values());
210
+ }
211
+ getEnabledPlugins() {
212
+ return this.getPlugins().filter((p) => {
213
+ const status = this.pluginStatus.get(p.manifest.id);
214
+ return status?.enabled ?? false;
215
+ });
216
+ }
217
+ getPlugin(id) {
218
+ return this.plugins.get(id);
219
+ }
220
+ async getPluginsStatus() {
221
+ try {
222
+ const dbPlugins = await this.repository.findAll();
223
+ const dbStatusMap = new Map(dbPlugins.map((p) => [p.id, p]));
224
+ return this.getPlugins().map((p) => {
225
+ const dbp = dbStatusMap.get(p.manifest.id);
226
+ const memStatus = this.pluginStatus.get(p.manifest.id);
227
+ return {
228
+ ...p.manifest,
229
+ enabled: dbp?.enabled ?? memStatus?.enabled ?? false,
230
+ installed: dbp?.installed ?? memStatus?.installed ?? false,
231
+ lastStep: memStatus?.lastStep ?? dbp?.lastStep ?? null,
232
+ };
233
+ });
234
+ }
235
+ catch (_e) {
236
+ // Fallback for bootstrap phase
237
+ return this.getPlugins().map((p) => {
238
+ const status = this.pluginStatus.get(p.manifest.id);
239
+ return {
240
+ ...p.manifest,
241
+ enabled: status?.enabled ?? false,
242
+ installed: status?.installed ?? false,
243
+ lastStep: status?.lastStep ?? null,
244
+ };
245
+ });
246
+ }
247
+ }
248
+ async getPluginStatus(id) {
249
+ const plugin = this.plugins.get(id);
250
+ if (!plugin)
251
+ return null;
252
+ const memStatus = this.pluginStatus.get(id);
253
+ const isProcessing = this.processingPlugins.has(id);
254
+ try {
255
+ const dbp = await this.repository.findById(id);
256
+ return {
257
+ ...plugin.manifest,
258
+ enabled: dbp?.enabled ?? memStatus?.enabled ?? false,
259
+ installed: dbp?.installed ?? memStatus?.installed ?? false,
260
+ // In production, the DB is the only shared truth between processes.
261
+ // We use DB's lastStep first, then memory, then "Processing..." indicator.
262
+ lastStep: dbp?.lastStep ??
263
+ memStatus?.lastStep ??
264
+ (isProcessing ? "Processing..." : null),
265
+ };
266
+ }
267
+ catch (_e) {
268
+ // Fallback to in-memory if DB fails
269
+ return {
270
+ ...plugin.manifest,
271
+ enabled: memStatus?.enabled ?? false,
272
+ installed: memStatus?.installed ?? false,
273
+ lastStep: memStatus?.lastStep ?? (isProcessing ? "Processing..." : null),
274
+ };
275
+ }
276
+ }
277
+ async getHooks(point) {
278
+ const hooks = [];
279
+ for (const plugin of this.getEnabledPlugins()) {
280
+ if (plugin.hooks) {
281
+ hooks.push(...plugin.hooks.filter((h) => h.point === point));
282
+ }
283
+ }
284
+ // Items without priority default to 100 to appear later
285
+ return hooks.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
286
+ }
287
+ async getExtensions(target, point, includeDisabled = false, context) {
288
+ const extensions = [];
289
+ const pluginsToScan = includeDisabled
290
+ ? this.getPlugins()
291
+ : this.getEnabledPlugins();
292
+ for (const plugin of pluginsToScan) {
293
+ if (plugin.extensions) {
294
+ extensions.push(...plugin.extensions.filter((e) => {
295
+ if (e.target !== target || e.point !== point)
296
+ return false;
297
+ if (context) {
298
+ const userRoles = context.roles || [];
299
+ const userPermissions = context.permissions || [];
300
+ if (e.roles && e.roles.length > 0) {
301
+ if (!e.roles.some((role) => userRoles.includes(role))) {
302
+ return false;
303
+ }
304
+ }
305
+ if (e.permissions && e.permissions.length > 0) {
306
+ if (!e.permissions.every((perm) => userPermissions.includes(perm))) {
307
+ return false;
308
+ }
309
+ }
310
+ }
311
+ return true;
312
+ }));
313
+ }
314
+ }
315
+ // Items without priority default to 100 to appear later
316
+ return extensions.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
317
+ }
318
+ async getWidgets(area, context) {
319
+ const widgets = [];
320
+ for (const plugin of this.getEnabledPlugins()) {
321
+ if (plugin.widgets) {
322
+ widgets.push(...plugin.widgets.filter((w) => {
323
+ if (w.area !== area)
324
+ return false;
325
+ if (context) {
326
+ const userRoles = context.roles || [];
327
+ const userPermissions = context.permissions || [];
328
+ if (w.roles && w.roles.length > 0) {
329
+ if (!w.roles.some((role) => userRoles.includes(role))) {
330
+ return false;
331
+ }
332
+ }
333
+ if (w.permissions && w.permissions.length > 0) {
334
+ if (!w.permissions.every((perm) => userPermissions.includes(perm))) {
335
+ return false;
336
+ }
337
+ }
338
+ }
339
+ return true;
340
+ }));
341
+ }
342
+ }
343
+ // Items without priority default to 100 to appear later
344
+ return widgets.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
345
+ }
346
+ async delay(ms = 500) {
347
+ if (process.env.NODE_ENV === "production" ||
348
+ process.env.VERCEL ||
349
+ process.env.NEXT_RUNTIME === "edge")
350
+ return;
351
+ await new Promise((resolve) => setTimeout(resolve, ms));
352
+ }
353
+ async togglePlugin(id, enabled, context) {
354
+ const plugin = this.findPlugin(id);
355
+ if (!plugin)
356
+ throw AppError.Internal(`Plugin ${id} not found`);
357
+ // Canonicalize to the registered id so status lookups match
358
+ id = plugin.manifest.id;
359
+ const currentStatus = this.pluginStatus.get(id);
360
+ if (currentStatus?.enabled === enabled)
361
+ return;
362
+ // Zapobieganie nieskończonej rekurencji (cyklom zależności)
363
+ if (this.processingPlugins.has(id)) {
364
+ return;
365
+ }
366
+ this.processingPlugins.add(id);
367
+ try {
368
+ if (enabled) {
369
+ // activation process
370
+ // 1. Check if the plugin requires any plugins
371
+ await this.updateStep(id, "Checking dependencies...");
372
+ await this.delay();
373
+ const dependencies = plugin.manifest.dependencies || [];
374
+ // 2. Aktywacja pluginów wymaganych
375
+ for (const depId of dependencies) {
376
+ const depPlugin = this.findPlugin(depId);
377
+ const canonicalDepId = depPlugin?.manifest.id ?? resolveDependencyId(depId);
378
+ const depStatus = this.pluginStatus.get(canonicalDepId);
379
+ if (!depStatus?.enabled) {
380
+ const depName = depPlugin?.manifest.name || canonicalDepId;
381
+ await this.updateStep(id, `Waiting for dependency activation: ${depName}`);
382
+ // Recursive call for dependencies
383
+ await this.togglePlugin(canonicalDepId, true, context);
384
+ await this.delay();
385
+ }
386
+ }
387
+ // 3. Before running the onEnable hook, we perform migrations
388
+ if (plugin.migrations?.length) {
389
+ await this.updateStep(id, "Migrating the database...");
390
+ await this.delay();
391
+ await this.migrationRunner.run(id, plugin.migrations);
392
+ }
393
+ // 4. Once migrations are complete, run the onEnable hook
394
+ if (plugin.onEnable) {
395
+ await this.updateStep(id, "Running the onEnable hook...");
396
+ await this.delay();
397
+ await plugin.onEnable(context);
398
+ }
399
+ // 5. Finally, we turn on the plugin
400
+ await this.updateStatus(id, {
401
+ enabled: true,
402
+ installed: true,
403
+ lastStep: null,
404
+ });
405
+ await this.delay();
406
+ }
407
+ else {
408
+ // deactivation process
409
+ // 1. Check for plugins that depend on us (automatic deactivation "upwards")
410
+ // We MUST deactivate dependents FIRST, otherwise they would be broken when we disable ourselves
411
+ // Match both the canonical id and the scoped npm-name form
412
+ const dependents = Array.from(this.plugins.values()).filter((p) => p.manifest.dependencies?.some((dep) => resolveDependencyId(dep) === resolveDependencyId(id)));
413
+ for (const dependent of dependents) {
414
+ const depStatus = this.pluginStatus.get(dependent.manifest.id);
415
+ if (depStatus?.enabled &&
416
+ !this.processingPlugins.has(dependent.manifest.id)) {
417
+ await this.updateStep(id, `Deactivating dependent plugin: ${dependent.manifest.name}`);
418
+ await this.togglePlugin(dependent.manifest.id, false, context);
419
+ await this.delay();
420
+ }
421
+ }
422
+ // 2. Run the onDisable hook
423
+ if (plugin.onDisable) {
424
+ await this.updateStep(id, "Running onDisable hook...");
425
+ await this.delay();
426
+ await plugin.onDisable();
427
+ }
428
+ // 3. Run the rollback migrations
429
+ if (plugin.migrations?.length) {
430
+ await this.updateStep(id, "Rolling back database migrations...");
431
+ await this.delay();
432
+ try {
433
+ await this.migrationRunner.rollback(id, plugin.migrations);
434
+ }
435
+ catch (e) {
436
+ this.logger.error("PluginRegistry", `Rollback failed for ${id}:`, e);
437
+ }
438
+ }
439
+ // 4. Disable the plugin itself
440
+ await this.updateStatus(id, { enabled: false, lastStep: null });
441
+ await this.delay();
442
+ // 5. Finally, proceed to deactivate unused dependencies
443
+ const dependencies = plugin.manifest.dependencies || [];
444
+ for (const depId of dependencies) {
445
+ const depPlugin = this.findPlugin(depId);
446
+ const canonicalDepId = depPlugin?.manifest.id ?? resolveDependencyId(depId);
447
+ const depStatus = this.pluginStatus.get(canonicalDepId);
448
+ if (depStatus?.enabled) {
449
+ // Check if anyone else is using this dependency (excluding those in the middle of deactivation)
450
+ const otherDependents = Array.from(this.plugins.values()).some((p) => p.manifest.id !== id &&
451
+ this.pluginStatus.get(p.manifest.id)?.enabled &&
452
+ !this.processingPlugins.has(p.manifest.id) &&
453
+ p.manifest.dependencies?.some((dep) => resolveDependencyId(dep) ===
454
+ resolveDependencyId(canonicalDepId)));
455
+ if (!otherDependents) {
456
+ const depName = depPlugin?.manifest.name || canonicalDepId;
457
+ await this.updateStep(id, `Cleaning up unused dependency: ${depName}`);
458
+ await this.togglePlugin(canonicalDepId, false, context);
459
+ await this.delay();
460
+ }
461
+ }
462
+ }
463
+ }
464
+ // Important: init() is only called if we are ENABLING
465
+ if (enabled && plugin.init)
466
+ await plugin.init();
467
+ // Ensure lastStep is cleared at the end of a SUCCESSFUL toggle
468
+ await this.updateStep(id, null);
469
+ }
470
+ catch (error) {
471
+ this.logger.warn("veap:plugins", `Error toggling plugin ${id}: ${error.message}`);
472
+ await this.updateStep(id, `Error: ${error.message}`);
473
+ throw error;
474
+ }
475
+ finally {
476
+ this.processingPlugins.delete(id);
477
+ // Final safety clear: if we're not in an error state, make sure lastStep is null
478
+ const finalStatus = this.pluginStatus.get(id);
479
+ if (finalStatus?.lastStep &&
480
+ !finalStatus.lastStep.startsWith("Error:") &&
481
+ !finalStatus.lastStep.includes("...")) {
482
+ await this.updateStep(id, null);
483
+ }
484
+ }
485
+ await this.eventBus.publish("system:plugin:toggle", {
486
+ pluginId: id,
487
+ isEnabled: enabled,
488
+ });
489
+ }
490
+ async hasHooks(point) {
491
+ for (const plugin of this.getEnabledPlugins()) {
492
+ if (plugin.hooks?.some((h) => h.point === point))
493
+ return true;
494
+ }
495
+ return false;
496
+ }
497
+ async hasExtension(target, point) {
498
+ for (const plugin of this.getEnabledPlugins()) {
499
+ if (plugin.extensions) {
500
+ if (plugin.extensions.some((e) => e.target === target && (point ? e.point === point : true)))
501
+ return true;
502
+ }
503
+ }
504
+ return false;
505
+ }
506
+ async getConfig(id) {
507
+ try {
508
+ const record = await this.repository.findById(id);
509
+ return record?.config ? JSON.parse(record.config) : null;
510
+ }
511
+ catch {
512
+ return null;
513
+ }
514
+ }
515
+ async updateConfig(id, config) {
516
+ try {
517
+ await this.repository.updateConfig(id, JSON.stringify(config));
518
+ }
519
+ catch (_e) { }
520
+ }
521
+ };
522
+ PluginRegistry = __decorate([
523
+ Injectable(),
524
+ __param(0, Inject(PLUGIN_REPOSITORY)),
525
+ __param(1, Inject(MIGRATION_RUNNER)),
526
+ __param(2, Inject(LOGGER)),
527
+ __param(3, Inject(EVENT_BUS)),
528
+ __metadata("design:paramtypes", [Object, Object, Object, Object])
529
+ ], PluginRegistry);
530
+ export { PluginRegistry };
531
+ // ─── Backwards-compatible re-exports ─────────────────────────────────────────
532
+ // The implementations live in `facade.ts`; this keeps
533
+ // `export * from "../application/plugins/registry"` in the entry barrels
534
+ // working for all module-level helpers.
535
+ export { applyPluginFilters, ensurePluginsInitialized, getPluginConfig, getPluginStatus, getPluginsStatus, hasPluginExtension, hasPluginHooks, registerPlugins, togglePluginState, updatePluginConfig, } from "./facade";
@@ -0,0 +1,27 @@
1
+ import { matchRoute } from "./matcher";
2
+ export { matchRoute };
3
+ export declare function getPluginsWithHomepage(): Promise<{
4
+ id: string;
5
+ name: string;
6
+ }[]>;
7
+ export { getPluginsWithHomepage as getModulesWithHomepage };
8
+ /**
9
+ * Builds a unified `RouteTree` from all enabled plugins.
10
+ *
11
+ * All trees are merged into a single `RouteTree` instance that can be
12
+ * passed to the `VeapRouter` component.
13
+ *
14
+ * @param includePrivate - If true, includes private routes prefixed with the configured privatePath.\n * @returns A fully merged `RouteTree` instance.
15
+ */
16
+ export declare const buildRouteTree: (includePrivate?: boolean) => Promise<import("./route-tree").RouteTree>;
17
+ /**
18
+ * Registers an internal route rewrite rule in RouterService.
19
+ *
20
+ * @param from - Incoming public path (e.g. "/logowanie")
21
+ * @param to - Target virtual path (e.g. "/signin")
22
+ */
23
+ export declare function addRouteRewrite(from: string, to: string): Promise<void>;
24
+ /**
25
+ * Returns all registered route rewrites.
26
+ */
27
+ export declare function getRouteRewrites(): Promise<Map<string, string>>;
@@ -0,0 +1,37 @@
1
+ import { app } from "@veap/kernel";
2
+ import { cache } from "react";
3
+ import { matchRoute } from "./matcher";
4
+ import { RouterService } from "./router.service";
5
+ export { matchRoute };
6
+ export async function getPluginsWithHomepage() {
7
+ return (await app(RouterService)).getPluginsWithHomepage();
8
+ }
9
+ export { getPluginsWithHomepage as getModulesWithHomepage };
10
+ /**
11
+ * Builds a unified `RouteTree` from all enabled plugins.
12
+ *
13
+ * All trees are merged into a single `RouteTree` instance that can be
14
+ * passed to the `VeapRouter` component.
15
+ *
16
+ * @param includePrivate - If true, includes private routes prefixed with the configured privatePath.\n * @returns A fully merged `RouteTree` instance.
17
+ */
18
+ export const buildRouteTree = cache(async (includePrivate = false) => {
19
+ return (await app(RouterService)).buildRouteTree(includePrivate);
20
+ });
21
+ /**
22
+ * Registers an internal route rewrite rule in RouterService.
23
+ *
24
+ * @param from - Incoming public path (e.g. "/logowanie")
25
+ * @param to - Target virtual path (e.g. "/signin")
26
+ */
27
+ export async function addRouteRewrite(from, to) {
28
+ const router = await app(RouterService);
29
+ router.addRewrite(from, to);
30
+ }
31
+ /**
32
+ * Returns all registered route rewrites.
33
+ */
34
+ export async function getRouteRewrites() {
35
+ const router = await app(RouterService);
36
+ return router.getRewrites();
37
+ }
@@ -0,0 +1,6 @@
1
+ import type { RouteTree } from "./route-tree";
2
+ export declare function handleVeapApiRequest(request: Request, { tree, path, searchParams, }: {
3
+ tree: RouteTree;
4
+ path: string;
5
+ searchParams: Record<string, any>;
6
+ }): Promise<Response>;
@@ -0,0 +1,40 @@
1
+ import { ApiEnsuredAuth, runApiPipeline } from "./middlewares";
2
+ import { collectApiMiddlewares, collectAuthRequirements } from "./utils";
3
+ export async function handleVeapApiRequest(request, { tree, path, searchParams, }) {
4
+ const match = tree.match(path);
5
+ if (!match) {
6
+ return new Response(JSON.stringify({ error: "Not Found" }), {
7
+ status: 404,
8
+ headers: { "Content-Type": "application/json" },
9
+ });
10
+ }
11
+ if (!match.isExact || !match.node.route) {
12
+ return new Response(JSON.stringify({ error: "Method Not Allowed or Not Found" }), {
13
+ status: 404,
14
+ headers: { "Content-Type": "application/json" },
15
+ });
16
+ }
17
+ const method = request.method;
18
+ const handler = match.node.route[method] || match.node.route[method.toLowerCase()];
19
+ if (!handler) {
20
+ return new Response(JSON.stringify({ error: "Method Not Allowed" }), {
21
+ status: 405,
22
+ headers: { "Content-Type": "application/json" },
23
+ });
24
+ }
25
+ const allMiddlewares = collectApiMiddlewares(match.layoutChain, match.node);
26
+ const auth = collectAuthRequirements(match.layoutChain, match.node);
27
+ if (auth.needsAuth) {
28
+ allMiddlewares.unshift(ApiEnsuredAuth);
29
+ }
30
+ const context = {
31
+ params: match.params,
32
+ searchParams,
33
+ path,
34
+ roles: auth.roles.length > 0 ? auth.roles : undefined,
35
+ permissions: auth.permissions.length > 0 ? auth.permissions : undefined,
36
+ };
37
+ return runApiPipeline(allMiddlewares, request, context, async (req, ctx) => {
38
+ return await handler(req, ctx);
39
+ });
40
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @module router
3
+ * @description Virtual Router API for Veap Plugin system.
4
+ */
5
+ export { addRouteRewrite, buildRouteTree, getModulesWithHomepage, getPluginsWithHomepage, getRouteRewrites, matchRoute, } from "./discovery";
6
+ export { handleVeapApiRequest } from "./handler";
7
+ export { matchRoute as matchVirtualRoute } from "./matcher";
8
+ export { ApiEnsuredAuth, ApiSameOrigin, EnsuredAuth, EnsuredGuest, EnsuredUser, runApiPipeline, runPipeline, SameOrigin, SkipSecurity, } from "./middlewares";
9
+ export { RouterErrorBoundary, SoftNavigationInterceptor, VeapRouter, type WithRouterConfig, withRouter, } from "./react";
10
+ export { extractParamName, isCatchAllSegment, isDynamicSegment, isGroupSegment, isOptionalCatchAllSegment, isParallelSlot, type LayoutChainEntry, type MatchResult, type RouteNode, RouteTree, resolveMagicPrefix, segmentMatchesUrlPart, } from "./route-tree";
11
+ export { RouterService } from "./router.service";
12
+ export { discoverRoutes, generateRouteManifest, globToTree, } from "./scanner";
13
+ export { collectApiMiddlewares, collectAuthRequirements, collectMiddlewares, } from "./utils";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @module router
3
+ * @description Virtual Router API for Veap Plugin system.
4
+ */
5
+ export { addRouteRewrite, buildRouteTree, getModulesWithHomepage, getPluginsWithHomepage, getRouteRewrites, matchRoute, } from "./discovery";
6
+ export { handleVeapApiRequest } from "./handler";
7
+ export { matchRoute as matchVirtualRoute } from "./matcher";
8
+ export { ApiEnsuredAuth, ApiSameOrigin, EnsuredAuth, EnsuredGuest, EnsuredUser, runApiPipeline, runPipeline, SameOrigin, SkipSecurity, } from "./middlewares";
9
+ export { RouterErrorBoundary, SoftNavigationInterceptor, VeapRouter, withRouter, } from "./react";
10
+ export { extractParamName, isCatchAllSegment, isDynamicSegment, isGroupSegment, isOptionalCatchAllSegment, isParallelSlot, RouteTree, resolveMagicPrefix, segmentMatchesUrlPart, } from "./route-tree";
11
+ export { RouterService } from "./router.service";
12
+ export { discoverRoutes, generateRouteManifest, globToTree, } from "./scanner";
13
+ export { collectApiMiddlewares, collectAuthRequirements, collectMiddlewares, } from "./utils";