|
1 | 1 | import { Plugin, PluginContext } from './types.js'; |
2 | 2 | import type { Logger } from '@objectstack/spec/contracts'; |
3 | 3 | import { z } from 'zod'; |
| 4 | +import { PluginConfigValidator } from './security/plugin-config-validator.js'; |
4 | 5 |
|
5 | 6 | /** |
6 | 7 | * Service Lifecycle Types |
@@ -112,13 +113,16 @@ export interface VersionCompatibility { |
112 | 113 | export class PluginLoader { |
113 | 114 | private logger: Logger; |
114 | 115 | private context?: PluginContext; |
| 116 | + private configValidator: PluginConfigValidator; |
115 | 117 | private loadedPlugins: Map<string, PluginMetadata> = new Map(); |
116 | 118 | private serviceFactories: Map<string, ServiceRegistration> = new Map(); |
117 | 119 | private serviceInstances: Map<string, any> = new Map(); |
118 | 120 | private scopedServices: Map<string, Map<string, any>> = new Map(); |
| 121 | + private creating: Set<string> = new Set(); |
119 | 122 |
|
120 | 123 | constructor(logger: Logger) { |
121 | 124 | this.logger = logger; |
| 125 | + this.configValidator = new PluginConfigValidator(logger); |
122 | 126 | } |
123 | 127 |
|
124 | 128 | /** |
@@ -386,16 +390,16 @@ export class PluginLoader { |
386 | 390 | if (!plugin.configSchema) { |
387 | 391 | return; |
388 | 392 | } |
389 | | - |
390 | | - if (!config) { |
391 | | - this.logger.debug(`Plugin ${plugin.name} has configuration schema but no config provided`); |
392 | | - return; |
| 393 | + |
| 394 | + if (config === undefined) { |
| 395 | + // In loadPlugin, we often don't have the config yet. |
| 396 | + // We skip validation here or valid against empty object if schema allows? |
| 397 | + // For now, let's keep the logging behavior but note it's delegating |
| 398 | + this.logger.debug(`Plugin ${plugin.name} has configuration schema (config validation postponed)`); |
| 399 | + return; |
393 | 400 | } |
394 | | - |
395 | | - // Configuration validation is now implemented in PluginConfigValidator |
396 | | - // This is a placeholder that logs the validation would happen |
397 | | - // The actual validation should be done by the caller when config is available |
398 | | - this.logger.debug(`Plugin ${plugin.name} has configuration schema (use PluginConfigValidator for validation)`); |
| 401 | + |
| 402 | + this.configValidator.validatePluginConfig(plugin, config); |
399 | 403 | } |
400 | 404 |
|
401 | 405 | private async verifyPluginSignature(plugin: PluginMetadata): Promise<void> { |
@@ -449,6 +453,16 @@ export class PluginLoader { |
449 | 453 | if (!this.context) { |
450 | 454 | throw new Error(`[PluginLoader] Context not set - cannot create service '${registration.name}'`); |
451 | 455 | } |
452 | | - return await registration.factory(this.context); |
| 456 | + |
| 457 | + if (this.creating.has(registration.name)) { |
| 458 | + throw new Error(`Circular dependency detected: ${Array.from(this.creating).join(' -> ')} -> ${registration.name}`); |
| 459 | + } |
| 460 | + |
| 461 | + this.creating.add(registration.name); |
| 462 | + try { |
| 463 | + return await registration.factory(this.context); |
| 464 | + } finally { |
| 465 | + this.creating.delete(registration.name); |
| 466 | + } |
453 | 467 | } |
454 | 468 | } |
0 commit comments