@objectstack/core 0.9.1 → 1.0.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 (94) hide show
  1. package/{ENHANCED_FEATURES.md → ADVANCED_FEATURES.md} +13 -13
  2. package/CHANGELOG.md +21 -0
  3. package/PHASE2_IMPLEMENTATION.md +388 -0
  4. package/README.md +12 -341
  5. package/REFACTORING_SUMMARY.md +40 -0
  6. package/dist/api-registry-plugin.test.js +23 -21
  7. package/dist/api-registry.test.js +2 -2
  8. package/dist/dependency-resolver.d.ts +62 -0
  9. package/dist/dependency-resolver.d.ts.map +1 -0
  10. package/dist/dependency-resolver.js +317 -0
  11. package/dist/dependency-resolver.test.d.ts +2 -0
  12. package/dist/dependency-resolver.test.d.ts.map +1 -0
  13. package/dist/dependency-resolver.test.js +241 -0
  14. package/dist/health-monitor.d.ts +65 -0
  15. package/dist/health-monitor.d.ts.map +1 -0
  16. package/dist/health-monitor.js +269 -0
  17. package/dist/health-monitor.test.d.ts +2 -0
  18. package/dist/health-monitor.test.d.ts.map +1 -0
  19. package/dist/health-monitor.test.js +68 -0
  20. package/dist/hot-reload.d.ts +79 -0
  21. package/dist/hot-reload.d.ts.map +1 -0
  22. package/dist/hot-reload.js +313 -0
  23. package/dist/index.d.ts +4 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +5 -1
  26. package/dist/kernel-base.d.ts +2 -2
  27. package/dist/kernel-base.js +2 -2
  28. package/dist/kernel.d.ts +89 -31
  29. package/dist/kernel.d.ts.map +1 -1
  30. package/dist/kernel.js +430 -73
  31. package/dist/kernel.test.js +375 -122
  32. package/dist/lite-kernel.d.ts +55 -0
  33. package/dist/lite-kernel.d.ts.map +1 -0
  34. package/dist/lite-kernel.js +112 -0
  35. package/dist/lite-kernel.test.d.ts +2 -0
  36. package/dist/lite-kernel.test.d.ts.map +1 -0
  37. package/dist/lite-kernel.test.js +161 -0
  38. package/dist/logger.d.ts +2 -2
  39. package/dist/logger.d.ts.map +1 -1
  40. package/dist/logger.js +26 -7
  41. package/dist/plugin-loader.d.ts +15 -0
  42. package/dist/plugin-loader.d.ts.map +1 -1
  43. package/dist/plugin-loader.js +40 -10
  44. package/dist/plugin-loader.test.js +9 -0
  45. package/dist/security/index.d.ts +3 -0
  46. package/dist/security/index.d.ts.map +1 -1
  47. package/dist/security/index.js +4 -0
  48. package/dist/security/permission-manager.d.ts +96 -0
  49. package/dist/security/permission-manager.d.ts.map +1 -0
  50. package/dist/security/permission-manager.js +235 -0
  51. package/dist/security/permission-manager.test.d.ts +2 -0
  52. package/dist/security/permission-manager.test.d.ts.map +1 -0
  53. package/dist/security/permission-manager.test.js +220 -0
  54. package/dist/security/plugin-permission-enforcer.d.ts +1 -1
  55. package/dist/security/sandbox-runtime.d.ts +115 -0
  56. package/dist/security/sandbox-runtime.d.ts.map +1 -0
  57. package/dist/security/sandbox-runtime.js +310 -0
  58. package/dist/security/security-scanner.d.ts +92 -0
  59. package/dist/security/security-scanner.d.ts.map +1 -0
  60. package/dist/security/security-scanner.js +273 -0
  61. package/examples/{enhanced-kernel-example.ts → kernel-features-example.ts} +6 -6
  62. package/examples/phase2-integration.ts +355 -0
  63. package/package.json +3 -2
  64. package/src/api-registry-plugin.test.ts +23 -21
  65. package/src/api-registry.test.ts +2 -2
  66. package/src/dependency-resolver.test.ts +287 -0
  67. package/src/dependency-resolver.ts +388 -0
  68. package/src/health-monitor.test.ts +81 -0
  69. package/src/health-monitor.ts +316 -0
  70. package/src/hot-reload.ts +388 -0
  71. package/src/index.ts +6 -1
  72. package/src/kernel-base.ts +2 -2
  73. package/src/kernel.test.ts +471 -134
  74. package/src/kernel.ts +518 -76
  75. package/src/lite-kernel.test.ts +200 -0
  76. package/src/lite-kernel.ts +135 -0
  77. package/src/logger.ts +28 -7
  78. package/src/plugin-loader.test.ts +10 -1
  79. package/src/plugin-loader.ts +49 -13
  80. package/src/security/index.ts +19 -0
  81. package/src/security/permission-manager.test.ts +256 -0
  82. package/src/security/permission-manager.ts +336 -0
  83. package/src/security/plugin-permission-enforcer.test.ts +1 -1
  84. package/src/security/plugin-permission-enforcer.ts +1 -1
  85. package/src/security/sandbox-runtime.ts +432 -0
  86. package/src/security/security-scanner.ts +365 -0
  87. package/dist/enhanced-kernel.d.ts +0 -103
  88. package/dist/enhanced-kernel.d.ts.map +0 -1
  89. package/dist/enhanced-kernel.js +0 -403
  90. package/dist/enhanced-kernel.test.d.ts +0 -2
  91. package/dist/enhanced-kernel.test.d.ts.map +0 -1
  92. package/dist/enhanced-kernel.test.js +0 -412
  93. package/src/enhanced-kernel.test.ts +0 -535
  94. package/src/enhanced-kernel.ts +0 -496
@@ -0,0 +1,200 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { LiteKernel } from './lite-kernel';
3
+ import type { Plugin } from './types';
4
+
5
+ describe('LiteKernel with Configurable Logger', () => {
6
+ let kernel: LiteKernel;
7
+
8
+ beforeEach(() => {
9
+ kernel = new LiteKernel();
10
+ });
11
+
12
+ describe('Logger Configuration', () => {
13
+ it('should create kernel with default logger', () => {
14
+ expect(kernel).toBeDefined();
15
+ });
16
+
17
+ it('should create kernel with custom logger config', async () => {
18
+ const customKernel = new LiteKernel({
19
+ logger: {
20
+ level: 'debug',
21
+ format: 'pretty',
22
+ sourceLocation: true
23
+ }
24
+ });
25
+
26
+ expect(customKernel).toBeDefined();
27
+
28
+ // Cleanup
29
+ await customKernel.bootstrap();
30
+ await customKernel.shutdown();
31
+ });
32
+
33
+ it('should create kernel with file logging config', async () => {
34
+ const fileKernel = new LiteKernel({
35
+ logger: {
36
+ level: 'info',
37
+ format: 'json',
38
+ file: '/tmp/test-kernel.log'
39
+ }
40
+ });
41
+
42
+ expect(fileKernel).toBeDefined();
43
+
44
+ // Cleanup
45
+ await fileKernel.bootstrap();
46
+ await fileKernel.shutdown();
47
+ });
48
+ });
49
+
50
+ describe('Plugin Context Logger', () => {
51
+ it('should provide logger to plugins', async () => {
52
+ let loggerReceived = false;
53
+
54
+ const testPlugin: Plugin = {
55
+ name: 'test-plugin',
56
+ init: async (ctx) => {
57
+ if (ctx.logger) {
58
+ loggerReceived = true;
59
+ ctx.logger.info('Plugin initialized', { plugin: 'test-plugin' });
60
+ }
61
+ }
62
+ };
63
+
64
+ kernel.use(testPlugin);
65
+ await kernel.bootstrap();
66
+
67
+ expect(loggerReceived).toBe(true);
68
+
69
+ await kernel.shutdown();
70
+ });
71
+
72
+ it('should allow plugins to use all log levels', async () => {
73
+ const logCalls: string[] = [];
74
+
75
+ const loggingPlugin: Plugin = {
76
+ name: 'logging-plugin',
77
+ init: async (ctx) => {
78
+ ctx.logger.debug('Debug message');
79
+ logCalls.push('debug');
80
+
81
+ ctx.logger.info('Info message');
82
+ logCalls.push('info');
83
+
84
+ ctx.logger.warn('Warning message');
85
+ logCalls.push('warn');
86
+
87
+ ctx.logger.error('Error message');
88
+ logCalls.push('error');
89
+ }
90
+ };
91
+
92
+ kernel.use(loggingPlugin);
93
+ await kernel.bootstrap();
94
+
95
+ expect(logCalls).toContain('debug');
96
+ expect(logCalls).toContain('info');
97
+ expect(logCalls).toContain('warn');
98
+ expect(logCalls).toContain('error');
99
+
100
+ await kernel.shutdown();
101
+ });
102
+
103
+ it('should support metadata in logs', async () => {
104
+ const metadataPlugin: Plugin = {
105
+ name: 'metadata-plugin',
106
+ init: async (ctx) => {
107
+ ctx.logger.info('User action', {
108
+ userId: '123',
109
+ action: 'create',
110
+ resource: 'document'
111
+ });
112
+ }
113
+ };
114
+
115
+ kernel.use(metadataPlugin);
116
+ await kernel.bootstrap();
117
+
118
+ await kernel.shutdown();
119
+ });
120
+ });
121
+
122
+ describe('Kernel Lifecycle Logging', () => {
123
+ it('should log bootstrap process', async () => {
124
+ const plugin: Plugin = {
125
+ name: 'lifecycle-test',
126
+ init: async () => {
127
+ // Init logic
128
+ },
129
+ start: async () => {
130
+ // Start logic
131
+ }
132
+ };
133
+
134
+ kernel.use(plugin);
135
+ await kernel.bootstrap();
136
+
137
+ expect(kernel.isRunning()).toBe(true);
138
+
139
+ await kernel.shutdown();
140
+ });
141
+
142
+ it('should log shutdown process', async () => {
143
+ const plugin: Plugin = {
144
+ name: 'shutdown-test',
145
+ init: async () => {},
146
+ destroy: async () => {
147
+ // Cleanup
148
+ }
149
+ };
150
+
151
+ kernel.use(plugin);
152
+ await kernel.bootstrap();
153
+ await kernel.shutdown();
154
+
155
+ expect(kernel.getState()).toBe('stopped');
156
+ });
157
+ });
158
+
159
+ describe('Environment Compatibility', () => {
160
+ it('should work in Node.js environment', async () => {
161
+ const nodeKernel = new LiteKernel({
162
+ logger: {
163
+ level: 'info',
164
+ format: 'json'
165
+ }
166
+ });
167
+
168
+ const plugin: Plugin = {
169
+ name: 'node-test',
170
+ init: async (ctx) => {
171
+ ctx.logger.info('Running in Node.js');
172
+ }
173
+ };
174
+
175
+ nodeKernel.use(plugin);
176
+ await nodeKernel.bootstrap();
177
+ await nodeKernel.shutdown();
178
+ });
179
+
180
+ it('should support browser-friendly logging', async () => {
181
+ const browserKernel = new LiteKernel({
182
+ logger: {
183
+ level: 'info',
184
+ format: 'pretty'
185
+ }
186
+ });
187
+
188
+ const plugin: Plugin = {
189
+ name: 'browser-test',
190
+ init: async (ctx) => {
191
+ ctx.logger.info('Browser-friendly format');
192
+ }
193
+ };
194
+
195
+ browserKernel.use(plugin);
196
+ await browserKernel.bootstrap();
197
+ await browserKernel.shutdown();
198
+ });
199
+ });
200
+ });
@@ -0,0 +1,135 @@
1
+ import { Plugin } from './types.js';
2
+ import { createLogger, ObjectLogger } from './logger.js';
3
+ import type { LoggerConfig } from '@objectstack/spec/system';
4
+ import { ObjectKernelBase } from './kernel-base.js';
5
+
6
+ /**
7
+ * ObjectKernel - MiniKernel Architecture
8
+ *
9
+ * A highly modular, plugin-based microkernel that:
10
+ * - Manages plugin lifecycle (init, start, destroy)
11
+ * - Provides dependency injection via service registry
12
+ * - Implements event/hook system for inter-plugin communication
13
+ * - Handles dependency resolution (topological sort)
14
+ * - Provides configurable logging for server and browser
15
+ *
16
+ * Core philosophy:
17
+ * - Business logic is completely separated into plugins
18
+ * - Kernel only manages lifecycle, DI, and hooks
19
+ * - Plugins are loaded as equal building blocks
20
+ */
21
+ export class LiteKernel extends ObjectKernelBase {
22
+ constructor(config?: { logger?: Partial<LoggerConfig> }) {
23
+ const logger = createLogger(config?.logger);
24
+ super(logger);
25
+
26
+ // Initialize context after logger is created
27
+ this.context = this.createContext();
28
+ }
29
+
30
+ /**
31
+ * Register a plugin
32
+ * @param plugin - Plugin instance
33
+ */
34
+ use(plugin: Plugin): this {
35
+ this.validateIdle();
36
+
37
+ const pluginName = plugin.name;
38
+ if (this.plugins.has(pluginName)) {
39
+ throw new Error(`[Kernel] Plugin '${pluginName}' already registered`);
40
+ }
41
+
42
+ this.plugins.set(pluginName, plugin);
43
+ return this;
44
+ }
45
+
46
+ /**
47
+ * Bootstrap the kernel
48
+ * 1. Resolve dependencies (topological sort)
49
+ * 2. Init phase - plugins register services
50
+ * 3. Start phase - plugins execute business logic
51
+ * 4. Trigger 'kernel:ready' hook
52
+ */
53
+ async bootstrap(): Promise<void> {
54
+ this.validateState('idle');
55
+
56
+ this.state = 'initializing';
57
+ this.logger.info('Bootstrap started');
58
+
59
+ // Resolve dependencies
60
+ const orderedPlugins = this.resolveDependencies();
61
+
62
+ // Phase 1: Init - Plugins register services
63
+ this.logger.info('Phase 1: Init plugins');
64
+ for (const plugin of orderedPlugins) {
65
+ await this.runPluginInit(plugin);
66
+ }
67
+
68
+ // Phase 2: Start - Plugins execute business logic
69
+ this.logger.info('Phase 2: Start plugins');
70
+ this.state = 'running';
71
+
72
+ for (const plugin of orderedPlugins) {
73
+ await this.runPluginStart(plugin);
74
+ }
75
+
76
+ // Trigger ready hook
77
+ await this.triggerHook('kernel:ready');
78
+ this.logger.info('✅ Bootstrap complete', {
79
+ pluginCount: this.plugins.size
80
+ });
81
+ }
82
+
83
+ /**
84
+ * Shutdown the kernel
85
+ * Calls destroy on all plugins in reverse order
86
+ */
87
+ async shutdown(): Promise<void> {
88
+ await this.destroy();
89
+ }
90
+
91
+ /**
92
+ * Graceful shutdown - destroy all plugins in reverse order
93
+ */
94
+ async destroy(): Promise<void> {
95
+ if (this.state === 'stopped') {
96
+ this.logger.warn('Kernel already stopped');
97
+ return;
98
+ }
99
+
100
+ this.state = 'stopping';
101
+ this.logger.info('Shutdown started');
102
+
103
+ // Trigger shutdown hook
104
+ await this.triggerHook('kernel:shutdown');
105
+
106
+ // Destroy plugins in reverse order
107
+ const orderedPlugins = this.resolveDependencies();
108
+ for (const plugin of orderedPlugins.reverse()) {
109
+ await this.runPluginDestroy(plugin);
110
+ }
111
+
112
+ this.state = 'stopped';
113
+ this.logger.info('✅ Shutdown complete');
114
+
115
+ // Cleanup logger resources
116
+ if (this.logger && typeof (this.logger as ObjectLogger).destroy === 'function') {
117
+ await (this.logger as ObjectLogger).destroy();
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Get a service from the registry
123
+ * Convenience method for external access
124
+ */
125
+ getService<T>(name: string): T {
126
+ return this.context.getService<T>(name);
127
+ }
128
+
129
+ /**
130
+ * Check if kernel is running
131
+ */
132
+ isRunning(): boolean {
133
+ return this.state === 'running';
134
+ }
135
+ }
package/src/logger.ts CHANGED
@@ -204,7 +204,8 @@ export class ObjectLogger implements Logger {
204
204
  info: '\x1b[32m', // Green
205
205
  warn: '\x1b[33m', // Yellow
206
206
  error: '\x1b[31m', // Red
207
- fatal: '\x1b[35m' // Magenta
207
+ fatal: '\x1b[35m', // Magenta
208
+ silent: ''
208
209
  };
209
210
  const reset = '\x1b[0m';
210
211
  const color = levelColors[level] || '';
@@ -263,21 +264,41 @@ export class ObjectLogger implements Logger {
263
264
  }
264
265
  }
265
266
 
266
- error(message: string, error?: Error, meta?: Record<string, any>): void {
267
+ error(message: string, errorOrMeta?: Error | Record<string, any>, meta?: Record<string, any>): void {
268
+ let error: Error | undefined;
269
+ let context: Record<string, any> = {};
270
+
271
+ if (errorOrMeta instanceof Error) {
272
+ error = errorOrMeta;
273
+ context = meta || {};
274
+ } else {
275
+ context = errorOrMeta || {};
276
+ }
277
+
267
278
  if (this.isNode && this.pinoLogger) {
268
- const errorContext = error ? { err: error, ...meta } : meta || {};
279
+ const errorContext = error ? { err: error, ...context } : context;
269
280
  this.pinoLogger.error(errorContext, message);
270
281
  } else {
271
- this.logBrowser('error', message, meta, error);
282
+ this.logBrowser('error', message, context, error);
272
283
  }
273
284
  }
274
285
 
275
- fatal(message: string, error?: Error, meta?: Record<string, any>): void {
286
+ fatal(message: string, errorOrMeta?: Error | Record<string, any>, meta?: Record<string, any>): void {
287
+ let error: Error | undefined;
288
+ let context: Record<string, any> = {};
289
+
290
+ if (errorOrMeta instanceof Error) {
291
+ error = errorOrMeta;
292
+ context = meta || {};
293
+ } else {
294
+ context = errorOrMeta || {};
295
+ }
296
+
276
297
  if (this.isNode && this.pinoLogger) {
277
- const errorContext = error ? { err: error, ...meta } : meta || {};
298
+ const errorContext = error ? { err: error, ...context } : context;
278
299
  this.pinoLogger.fatal(errorContext, message);
279
300
  } else {
280
- this.logBrowser('fatal', message, meta, error);
301
+ this.logBrowser('fatal', message, context, error);
281
302
  }
282
303
  }
283
304
 
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest';
2
2
  import { PluginLoader, ServiceLifecycle, PluginMetadata } from './plugin-loader';
3
3
  import { createLogger } from './logger';
4
- import type { Plugin } from './types';
4
+ import type { Plugin, PluginContext } from './types';
5
5
 
6
6
  describe('PluginLoader', () => {
7
7
  let loader: PluginLoader;
@@ -9,6 +9,15 @@ describe('PluginLoader', () => {
9
9
  beforeEach(() => {
10
10
  const logger = createLogger({ level: 'error' }); // Suppress logs in tests
11
11
  loader = new PluginLoader(logger);
12
+ loader.setContext({
13
+ registerService: () => {},
14
+ getService: () => { throw new Error('Mock service not found'); },
15
+ hook: () => {},
16
+ trigger: async () => {},
17
+ getServices: () => new Map(),
18
+ logger: logger,
19
+ getKernel: () => ({}) as any
20
+ } as PluginContext);
12
21
  });
13
22
 
14
23
  describe('Plugin Loading', () => {
@@ -1,6 +1,7 @@
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
@@ -111,13 +112,31 @@ export interface VersionCompatibility {
111
112
  */
112
113
  export class PluginLoader {
113
114
  private logger: Logger;
115
+ private context?: PluginContext;
116
+ private configValidator: PluginConfigValidator;
114
117
  private loadedPlugins: Map<string, PluginMetadata> = new Map();
115
118
  private serviceFactories: Map<string, ServiceRegistration> = new Map();
116
119
  private serviceInstances: Map<string, any> = new Map();
117
120
  private scopedServices: Map<string, Map<string, any>> = new Map();
121
+ private creating: Set<string> = new Set();
118
122
 
119
123
  constructor(logger: Logger) {
120
124
  this.logger = logger;
125
+ this.configValidator = new PluginConfigValidator(logger);
126
+ }
127
+
128
+ /**
129
+ * Set the plugin context for service factories
130
+ */
131
+ setContext(context: PluginContext): void {
132
+ this.context = context;
133
+ }
134
+
135
+ /**
136
+ * Get a synchronous service instance if it exists (Sync Helper)
137
+ */
138
+ getServiceInstance<T>(name: string): T | undefined {
139
+ return this.serviceInstances.get(name) as T;
121
140
  }
122
141
 
123
142
  /**
@@ -227,6 +246,13 @@ export class PluginLoader {
227
246
  this.serviceInstances.set(name, service);
228
247
  }
229
248
 
249
+ /**
250
+ * Check if a service is registered (either as instance or factory)
251
+ */
252
+ hasService(name: string): boolean {
253
+ return this.serviceInstances.has(name) || this.serviceFactories.has(name);
254
+ }
255
+
230
256
  /**
231
257
  * Detect circular dependencies in service factories
232
258
  * Note: This only detects cycles in service dependencies, not plugin dependencies.
@@ -371,16 +397,16 @@ export class PluginLoader {
371
397
  if (!plugin.configSchema) {
372
398
  return;
373
399
  }
374
-
375
- if (!config) {
376
- this.logger.debug(`Plugin ${plugin.name} has configuration schema but no config provided`);
377
- return;
400
+
401
+ if (config === undefined) {
402
+ // In loadPlugin, we often don't have the config yet.
403
+ // We skip validation here or valid against empty object if schema allows?
404
+ // For now, let's keep the logging behavior but note it's delegating
405
+ this.logger.debug(`Plugin ${plugin.name} has configuration schema (config validation postponed)`);
406
+ return;
378
407
  }
379
-
380
- // Configuration validation is now implemented in PluginConfigValidator
381
- // This is a placeholder that logs the validation would happen
382
- // The actual validation should be done by the caller when config is available
383
- this.logger.debug(`Plugin ${plugin.name} has configuration schema (use PluginConfigValidator for validation)`);
408
+
409
+ this.configValidator.validatePluginConfig(plugin, config);
384
410
  }
385
411
 
386
412
  private async verifyPluginSignature(plugin: PluginMetadata): Promise<void> {
@@ -431,9 +457,19 @@ export class PluginLoader {
431
457
  }
432
458
 
433
459
  private async createServiceInstance(registration: ServiceRegistration): Promise<any> {
434
- // This is a simplified version - in real implementation,
435
- // we would need to pass proper context with resolved dependencies
436
- const mockContext = {} as PluginContext;
437
- return await registration.factory(mockContext);
460
+ if (!this.context) {
461
+ throw new Error(`[PluginLoader] Context not set - cannot create service '${registration.name}'`);
462
+ }
463
+
464
+ if (this.creating.has(registration.name)) {
465
+ throw new Error(`Circular dependency detected: ${Array.from(this.creating).join(' -> ')} -> ${registration.name}`);
466
+ }
467
+
468
+ this.creating.add(registration.name);
469
+ try {
470
+ return await registration.factory(this.context);
471
+ } finally {
472
+ this.creating.delete(registration.name);
473
+ }
438
474
  }
439
475
  }
@@ -27,3 +27,22 @@ export {
27
27
  type PluginPermissions,
28
28
  type PermissionCheckResult,
29
29
  } from './plugin-permission-enforcer.js';
30
+
31
+ // Advanced security components (Phase 2)
32
+ export {
33
+ PluginPermissionManager,
34
+ type PermissionGrant,
35
+ type PermissionCheckResult as PluginPermissionCheckResult,
36
+ } from './permission-manager.js';
37
+
38
+ export {
39
+ PluginSandboxRuntime,
40
+ type SandboxContext,
41
+ type ResourceUsage,
42
+ } from './sandbox-runtime.js';
43
+
44
+ export {
45
+ PluginSecurityScanner,
46
+ type ScanTarget,
47
+ type SecurityIssue,
48
+ } from './security-scanner.js';