@objectstack/core 0.9.1 → 0.9.2

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 (89) hide show
  1. package/{ENHANCED_FEATURES.md → ADVANCED_FEATURES.md} +13 -13
  2. package/CHANGELOG.md +7 -0
  3. package/PHASE2_IMPLEMENTATION.md +388 -0
  4. package/README.md +24 -11
  5. package/REFACTORING_SUMMARY.md +40 -0
  6. package/dist/api-registry-plugin.test.js +20 -20
  7. package/dist/dependency-resolver.d.ts +62 -0
  8. package/dist/dependency-resolver.d.ts.map +1 -0
  9. package/dist/dependency-resolver.js +317 -0
  10. package/dist/dependency-resolver.test.d.ts +2 -0
  11. package/dist/dependency-resolver.test.d.ts.map +1 -0
  12. package/dist/dependency-resolver.test.js +241 -0
  13. package/dist/health-monitor.d.ts +65 -0
  14. package/dist/health-monitor.d.ts.map +1 -0
  15. package/dist/health-monitor.js +269 -0
  16. package/dist/health-monitor.test.d.ts +2 -0
  17. package/dist/health-monitor.test.d.ts.map +1 -0
  18. package/dist/health-monitor.test.js +68 -0
  19. package/dist/hot-reload.d.ts +79 -0
  20. package/dist/hot-reload.d.ts.map +1 -0
  21. package/dist/hot-reload.js +313 -0
  22. package/dist/index.d.ts +4 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +5 -1
  25. package/dist/kernel-base.d.ts +2 -2
  26. package/dist/kernel-base.js +2 -2
  27. package/dist/kernel.d.ts +79 -31
  28. package/dist/kernel.d.ts.map +1 -1
  29. package/dist/kernel.js +383 -73
  30. package/dist/kernel.test.js +373 -122
  31. package/dist/lite-kernel.d.ts +55 -0
  32. package/dist/lite-kernel.d.ts.map +1 -0
  33. package/dist/lite-kernel.js +112 -0
  34. package/dist/lite-kernel.test.d.ts +2 -0
  35. package/dist/lite-kernel.test.d.ts.map +1 -0
  36. package/dist/lite-kernel.test.js +161 -0
  37. package/dist/logger.d.ts +2 -2
  38. package/dist/logger.d.ts.map +1 -1
  39. package/dist/logger.js +26 -7
  40. package/dist/plugin-loader.d.ts +11 -0
  41. package/dist/plugin-loader.d.ts.map +1 -1
  42. package/dist/plugin-loader.js +34 -10
  43. package/dist/plugin-loader.test.js +9 -0
  44. package/dist/security/index.d.ts +3 -0
  45. package/dist/security/index.d.ts.map +1 -1
  46. package/dist/security/index.js +4 -0
  47. package/dist/security/permission-manager.d.ts +96 -0
  48. package/dist/security/permission-manager.d.ts.map +1 -0
  49. package/dist/security/permission-manager.js +235 -0
  50. package/dist/security/permission-manager.test.d.ts +2 -0
  51. package/dist/security/permission-manager.test.d.ts.map +1 -0
  52. package/dist/security/permission-manager.test.js +220 -0
  53. package/dist/security/sandbox-runtime.d.ts +115 -0
  54. package/dist/security/sandbox-runtime.d.ts.map +1 -0
  55. package/dist/security/sandbox-runtime.js +310 -0
  56. package/dist/security/security-scanner.d.ts +92 -0
  57. package/dist/security/security-scanner.d.ts.map +1 -0
  58. package/dist/security/security-scanner.js +273 -0
  59. package/examples/{enhanced-kernel-example.ts → kernel-features-example.ts} +6 -6
  60. package/examples/phase2-integration.ts +355 -0
  61. package/package.json +2 -2
  62. package/src/api-registry-plugin.test.ts +20 -20
  63. package/src/dependency-resolver.test.ts +287 -0
  64. package/src/dependency-resolver.ts +388 -0
  65. package/src/health-monitor.test.ts +81 -0
  66. package/src/health-monitor.ts +316 -0
  67. package/src/hot-reload.ts +388 -0
  68. package/src/index.ts +6 -1
  69. package/src/kernel-base.ts +2 -2
  70. package/src/kernel.test.ts +469 -134
  71. package/src/kernel.ts +464 -78
  72. package/src/lite-kernel.test.ts +200 -0
  73. package/src/lite-kernel.ts +135 -0
  74. package/src/logger.ts +28 -7
  75. package/src/plugin-loader.test.ts +10 -1
  76. package/src/plugin-loader.ts +42 -13
  77. package/src/security/index.ts +19 -0
  78. package/src/security/permission-manager.test.ts +256 -0
  79. package/src/security/permission-manager.ts +336 -0
  80. package/src/security/sandbox-runtime.ts +432 -0
  81. package/src/security/security-scanner.ts +365 -0
  82. package/dist/enhanced-kernel.d.ts +0 -103
  83. package/dist/enhanced-kernel.d.ts.map +0 -1
  84. package/dist/enhanced-kernel.js +0 -403
  85. package/dist/enhanced-kernel.test.d.ts +0 -2
  86. package/dist/enhanced-kernel.test.d.ts.map +0 -1
  87. package/dist/enhanced-kernel.test.js +0 -412
  88. package/src/enhanced-kernel.test.ts +0 -535
  89. package/src/enhanced-kernel.ts +0 -496
@@ -9,7 +9,7 @@ describe('API Registry Plugin', () => {
9
9
  });
10
10
  describe('Plugin Registration', () => {
11
11
  it('should register API Registry as a service', async () => {
12
- kernel.use(createApiRegistryPlugin());
12
+ await kernel.use(createApiRegistryPlugin());
13
13
  await kernel.bootstrap();
14
14
  const registry = kernel.getService('api-registry');
15
15
  expect(registry).toBeDefined();
@@ -17,7 +17,7 @@ describe('API Registry Plugin', () => {
17
17
  await kernel.shutdown();
18
18
  });
19
19
  it('should register with custom conflict resolution', async () => {
20
- kernel.use(createApiRegistryPlugin({
20
+ await kernel.use(createApiRegistryPlugin({
21
21
  conflictResolution: 'priority',
22
22
  version: '2.0.0',
23
23
  }));
@@ -31,7 +31,7 @@ describe('API Registry Plugin', () => {
31
31
  });
32
32
  describe('Integration with Plugins', () => {
33
33
  it('should allow plugins to register APIs', async () => {
34
- kernel.use(createApiRegistryPlugin());
34
+ await kernel.use(createApiRegistryPlugin());
35
35
  const testPlugin = {
36
36
  name: 'test-plugin',
37
37
  init: async (ctx) => {
@@ -60,7 +60,7 @@ describe('API Registry Plugin', () => {
60
60
  registry.registerApi(api);
61
61
  },
62
62
  };
63
- kernel.use(testPlugin);
63
+ await kernel.use(testPlugin);
64
64
  await kernel.bootstrap();
65
65
  const registry = kernel.getService('api-registry');
66
66
  const api = registry.getApi('test_api');
@@ -70,7 +70,7 @@ describe('API Registry Plugin', () => {
70
70
  await kernel.shutdown();
71
71
  });
72
72
  it('should allow multiple plugins to register APIs', async () => {
73
- kernel.use(createApiRegistryPlugin());
73
+ await kernel.use(createApiRegistryPlugin());
74
74
  const plugin1 = {
75
75
  name: 'plugin-1',
76
76
  init: async (ctx) => {
@@ -112,8 +112,8 @@ describe('API Registry Plugin', () => {
112
112
  });
113
113
  },
114
114
  };
115
- kernel.use(plugin1);
116
- kernel.use(plugin2);
115
+ await kernel.use(plugin1);
116
+ await kernel.use(plugin2);
117
117
  await kernel.bootstrap();
118
118
  const registry = kernel.getService('api-registry');
119
119
  const stats = registry.getStats();
@@ -123,7 +123,7 @@ describe('API Registry Plugin', () => {
123
123
  await kernel.shutdown();
124
124
  });
125
125
  it('should support API discovery across plugins', async () => {
126
- kernel.use(createApiRegistryPlugin());
126
+ await kernel.use(createApiRegistryPlugin());
127
127
  const dataPlugin = {
128
128
  name: 'data-plugin',
129
129
  init: async (ctx) => {
@@ -172,8 +172,8 @@ describe('API Registry Plugin', () => {
172
172
  });
173
173
  },
174
174
  };
175
- kernel.use(dataPlugin);
176
- kernel.use(analyticsPlugin);
175
+ await kernel.use(dataPlugin);
176
+ await kernel.use(analyticsPlugin);
177
177
  await kernel.bootstrap();
178
178
  const registry = kernel.getService('api-registry');
179
179
  // Find all data APIs
@@ -189,7 +189,7 @@ describe('API Registry Plugin', () => {
189
189
  await kernel.shutdown();
190
190
  });
191
191
  it('should handle route conflicts based on strategy', async () => {
192
- kernel.use(createApiRegistryPlugin({
192
+ await kernel.use(createApiRegistryPlugin({
193
193
  conflictResolution: 'priority',
194
194
  }));
195
195
  const corePlugin = {
@@ -238,8 +238,8 @@ describe('API Registry Plugin', () => {
238
238
  });
239
239
  },
240
240
  };
241
- kernel.use(corePlugin);
242
- kernel.use(pluginOverride);
241
+ await kernel.use(corePlugin);
242
+ await kernel.use(pluginOverride);
243
243
  await kernel.bootstrap();
244
244
  const registry = kernel.getService('api-registry');
245
245
  const result = registry.findEndpointByRoute('GET', '/api/data/:object');
@@ -249,7 +249,7 @@ describe('API Registry Plugin', () => {
249
249
  await kernel.shutdown();
250
250
  });
251
251
  it('should support cleanup on plugin unload', async () => {
252
- kernel.use(createApiRegistryPlugin());
252
+ await kernel.use(createApiRegistryPlugin());
253
253
  const dynamicPlugin = {
254
254
  name: 'dynamic-plugin',
255
255
  init: async (ctx) => {
@@ -275,7 +275,7 @@ describe('API Registry Plugin', () => {
275
275
  // For now, we'll test the registry's unregister capability
276
276
  },
277
277
  };
278
- kernel.use(dynamicPlugin);
278
+ await kernel.use(dynamicPlugin);
279
279
  await kernel.bootstrap();
280
280
  const registry = kernel.getService('api-registry');
281
281
  expect(registry.getApi('dynamic_api')).toBeDefined();
@@ -287,7 +287,7 @@ describe('API Registry Plugin', () => {
287
287
  });
288
288
  describe('API Registry Lifecycle', () => {
289
289
  it('should be available during plugin start phase', async () => {
290
- kernel.use(createApiRegistryPlugin());
290
+ await kernel.use(createApiRegistryPlugin());
291
291
  let registryAvailable = false;
292
292
  const testPlugin = {
293
293
  name: 'test-plugin',
@@ -300,13 +300,13 @@ describe('API Registry Plugin', () => {
300
300
  registryAvailable = registry !== undefined;
301
301
  },
302
302
  };
303
- kernel.use(testPlugin);
303
+ await kernel.use(testPlugin);
304
304
  await kernel.bootstrap();
305
305
  expect(registryAvailable).toBe(true);
306
306
  await kernel.shutdown();
307
307
  });
308
308
  it('should provide consistent registry across all plugins', async () => {
309
- kernel.use(createApiRegistryPlugin());
309
+ await kernel.use(createApiRegistryPlugin());
310
310
  let registry1;
311
311
  let registry2;
312
312
  const plugin1 = {
@@ -321,8 +321,8 @@ describe('API Registry Plugin', () => {
321
321
  registry2 = ctx.getService('api-registry');
322
322
  },
323
323
  };
324
- kernel.use(plugin1);
325
- kernel.use(plugin2);
324
+ await kernel.use(plugin1);
325
+ await kernel.use(plugin2);
326
326
  await kernel.bootstrap();
327
327
  // Same registry instance should be shared
328
328
  expect(registry1).toBe(registry2);
@@ -0,0 +1,62 @@
1
+ import type { SemanticVersion, VersionConstraint, CompatibilityLevel, DependencyConflict } from '@objectstack/spec/system';
2
+ import type { ObjectLogger } from './logger.js';
3
+ /**
4
+ * Semantic Version Parser and Comparator
5
+ *
6
+ * Implements semantic versioning comparison and constraint matching
7
+ */
8
+ export declare class SemanticVersionManager {
9
+ /**
10
+ * Parse a version string into semantic version components
11
+ */
12
+ static parse(versionStr: string): SemanticVersion;
13
+ /**
14
+ * Convert semantic version back to string
15
+ */
16
+ static toString(version: SemanticVersion): string;
17
+ /**
18
+ * Compare two semantic versions
19
+ * Returns: -1 if a < b, 0 if a === b, 1 if a > b
20
+ */
21
+ static compare(a: SemanticVersion, b: SemanticVersion): number;
22
+ /**
23
+ * Check if version satisfies constraint
24
+ */
25
+ static satisfies(version: SemanticVersion, constraint: VersionConstraint): boolean;
26
+ /**
27
+ * Determine compatibility level between two versions
28
+ */
29
+ static getCompatibilityLevel(from: SemanticVersion, to: SemanticVersion): CompatibilityLevel;
30
+ }
31
+ /**
32
+ * Plugin Dependency Resolver
33
+ *
34
+ * Resolves plugin dependencies using topological sorting and conflict detection
35
+ */
36
+ export declare class DependencyResolver {
37
+ private logger;
38
+ constructor(logger: ObjectLogger);
39
+ /**
40
+ * Resolve dependencies using topological sort
41
+ */
42
+ resolve(plugins: Map<string, {
43
+ version?: string;
44
+ dependencies?: string[];
45
+ }>): string[];
46
+ /**
47
+ * Detect dependency conflicts
48
+ */
49
+ detectConflicts(plugins: Map<string, {
50
+ version: string;
51
+ dependencies?: Record<string, VersionConstraint>;
52
+ }>): DependencyConflict[];
53
+ /**
54
+ * Find best version that satisfies all constraints
55
+ */
56
+ findBestVersion(availableVersions: string[], constraints: VersionConstraint[]): string | undefined;
57
+ /**
58
+ * Check if dependencies form a valid DAG (no cycles)
59
+ */
60
+ isAcyclic(dependencies: Map<string, string[]>): boolean;
61
+ }
62
+ //# sourceMappingURL=dependency-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependency-resolver.d.ts","sourceRoot":"","sources":["../src/dependency-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;GAIG;AACH,qBAAa,sBAAsB;IACjC;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe;IAsBjD;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM;IAWjD;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,eAAe,GAAG,MAAM;IAkB9D;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,iBAAiB,GAAG,OAAO;IAoElF;;OAEG;IACH,MAAM,CAAC,qBAAqB,CAAC,IAAI,EAAE,eAAe,EAAE,EAAE,EAAE,eAAe,GAAG,kBAAkB;CA0B7F;AAED;;;;GAIG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAe;gBAEjB,MAAM,EAAE,YAAY;IAIhC;;OAEG;IACH,OAAO,CACL,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,GAClE,MAAM,EAAE;IAkEX;;OAEG;IACH,eAAe,CACb,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;KAAE,CAAC,GAC1F,kBAAkB,EAAE;IA+EvB;;OAEG;IACH,eAAe,CACb,iBAAiB,EAAE,MAAM,EAAE,EAC3B,WAAW,EAAE,iBAAiB,EAAE,GAC/B,MAAM,GAAG,SAAS;IAoBrB;;OAEG;IACH,SAAS,CAAC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO;CAcxD"}
@@ -0,0 +1,317 @@
1
+ /**
2
+ * Semantic Version Parser and Comparator
3
+ *
4
+ * Implements semantic versioning comparison and constraint matching
5
+ */
6
+ export class SemanticVersionManager {
7
+ /**
8
+ * Parse a version string into semantic version components
9
+ */
10
+ static parse(versionStr) {
11
+ // Remove 'v' prefix if present
12
+ const cleanVersion = versionStr.replace(/^v/, '');
13
+ // Match semver pattern: major.minor.patch[-prerelease][+build]
14
+ const match = cleanVersion.match(/^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9.-]+))?(?:\+([a-zA-Z0-9.-]+))?$/);
15
+ if (!match) {
16
+ throw new Error(`Invalid semantic version: ${versionStr}`);
17
+ }
18
+ return {
19
+ major: parseInt(match[1], 10),
20
+ minor: parseInt(match[2], 10),
21
+ patch: parseInt(match[3], 10),
22
+ preRelease: match[4],
23
+ build: match[5],
24
+ };
25
+ }
26
+ /**
27
+ * Convert semantic version back to string
28
+ */
29
+ static toString(version) {
30
+ let str = `${version.major}.${version.minor}.${version.patch}`;
31
+ if (version.preRelease) {
32
+ str += `-${version.preRelease}`;
33
+ }
34
+ if (version.build) {
35
+ str += `+${version.build}`;
36
+ }
37
+ return str;
38
+ }
39
+ /**
40
+ * Compare two semantic versions
41
+ * Returns: -1 if a < b, 0 if a === b, 1 if a > b
42
+ */
43
+ static compare(a, b) {
44
+ // Compare major, minor, patch
45
+ if (a.major !== b.major)
46
+ return a.major - b.major;
47
+ if (a.minor !== b.minor)
48
+ return a.minor - b.minor;
49
+ if (a.patch !== b.patch)
50
+ return a.patch - b.patch;
51
+ // Pre-release versions have lower precedence
52
+ if (a.preRelease && !b.preRelease)
53
+ return -1;
54
+ if (!a.preRelease && b.preRelease)
55
+ return 1;
56
+ // Compare pre-release versions
57
+ if (a.preRelease && b.preRelease) {
58
+ return a.preRelease.localeCompare(b.preRelease);
59
+ }
60
+ return 0;
61
+ }
62
+ /**
63
+ * Check if version satisfies constraint
64
+ */
65
+ static satisfies(version, constraint) {
66
+ const constraintStr = constraint;
67
+ // Any version
68
+ if (constraintStr === '*' || constraintStr === 'latest') {
69
+ return true;
70
+ }
71
+ // Exact version
72
+ if (/^[\d.]+$/.test(constraintStr)) {
73
+ const exact = this.parse(constraintStr);
74
+ return this.compare(version, exact) === 0;
75
+ }
76
+ // Caret range (^): Compatible with version
77
+ if (constraintStr.startsWith('^')) {
78
+ const base = this.parse(constraintStr.slice(1));
79
+ return (version.major === base.major &&
80
+ this.compare(version, base) >= 0);
81
+ }
82
+ // Tilde range (~): Approximately equivalent
83
+ if (constraintStr.startsWith('~')) {
84
+ const base = this.parse(constraintStr.slice(1));
85
+ return (version.major === base.major &&
86
+ version.minor === base.minor &&
87
+ this.compare(version, base) >= 0);
88
+ }
89
+ // Greater than or equal
90
+ if (constraintStr.startsWith('>=')) {
91
+ const base = this.parse(constraintStr.slice(2));
92
+ return this.compare(version, base) >= 0;
93
+ }
94
+ // Greater than
95
+ if (constraintStr.startsWith('>')) {
96
+ const base = this.parse(constraintStr.slice(1));
97
+ return this.compare(version, base) > 0;
98
+ }
99
+ // Less than or equal
100
+ if (constraintStr.startsWith('<=')) {
101
+ const base = this.parse(constraintStr.slice(2));
102
+ return this.compare(version, base) <= 0;
103
+ }
104
+ // Less than
105
+ if (constraintStr.startsWith('<')) {
106
+ const base = this.parse(constraintStr.slice(1));
107
+ return this.compare(version, base) < 0;
108
+ }
109
+ // Range (1.2.3 - 2.3.4)
110
+ const rangeMatch = constraintStr.match(/^([\d.]+)\s*-\s*([\d.]+)$/);
111
+ if (rangeMatch) {
112
+ const min = this.parse(rangeMatch[1]);
113
+ const max = this.parse(rangeMatch[2]);
114
+ return this.compare(version, min) >= 0 && this.compare(version, max) <= 0;
115
+ }
116
+ return false;
117
+ }
118
+ /**
119
+ * Determine compatibility level between two versions
120
+ */
121
+ static getCompatibilityLevel(from, to) {
122
+ const cmp = this.compare(from, to);
123
+ // Same version
124
+ if (cmp === 0) {
125
+ return 'fully-compatible';
126
+ }
127
+ // Major version changed - breaking changes
128
+ if (from.major !== to.major) {
129
+ return 'breaking-changes';
130
+ }
131
+ // Minor version increased - backward compatible
132
+ if (from.minor < to.minor) {
133
+ return 'backward-compatible';
134
+ }
135
+ // Patch version increased - fully compatible
136
+ if (from.patch < to.patch) {
137
+ return 'fully-compatible';
138
+ }
139
+ // Downgrade - incompatible
140
+ return 'incompatible';
141
+ }
142
+ }
143
+ /**
144
+ * Plugin Dependency Resolver
145
+ *
146
+ * Resolves plugin dependencies using topological sorting and conflict detection
147
+ */
148
+ export class DependencyResolver {
149
+ constructor(logger) {
150
+ this.logger = logger.child({ component: 'DependencyResolver' });
151
+ }
152
+ /**
153
+ * Resolve dependencies using topological sort
154
+ */
155
+ resolve(plugins) {
156
+ const graph = new Map();
157
+ const inDegree = new Map();
158
+ // Build dependency graph
159
+ for (const [pluginName, pluginInfo] of plugins) {
160
+ if (!graph.has(pluginName)) {
161
+ graph.set(pluginName, []);
162
+ inDegree.set(pluginName, 0);
163
+ }
164
+ const deps = pluginInfo.dependencies || [];
165
+ for (const dep of deps) {
166
+ // Check if dependency exists
167
+ if (!plugins.has(dep)) {
168
+ throw new Error(`Missing dependency: ${pluginName} requires ${dep}`);
169
+ }
170
+ // Add edge
171
+ if (!graph.has(dep)) {
172
+ graph.set(dep, []);
173
+ inDegree.set(dep, 0);
174
+ }
175
+ graph.get(dep).push(pluginName);
176
+ inDegree.set(pluginName, (inDegree.get(pluginName) || 0) + 1);
177
+ }
178
+ }
179
+ // Topological sort using Kahn's algorithm
180
+ const queue = [];
181
+ const result = [];
182
+ // Add all nodes with no incoming edges
183
+ for (const [node, degree] of inDegree) {
184
+ if (degree === 0) {
185
+ queue.push(node);
186
+ }
187
+ }
188
+ while (queue.length > 0) {
189
+ const node = queue.shift();
190
+ result.push(node);
191
+ // Reduce in-degree for dependent nodes
192
+ const dependents = graph.get(node) || [];
193
+ for (const dependent of dependents) {
194
+ const newDegree = (inDegree.get(dependent) || 0) - 1;
195
+ inDegree.set(dependent, newDegree);
196
+ if (newDegree === 0) {
197
+ queue.push(dependent);
198
+ }
199
+ }
200
+ }
201
+ // Check for circular dependencies
202
+ if (result.length !== plugins.size) {
203
+ const remaining = Array.from(plugins.keys()).filter(p => !result.includes(p));
204
+ this.logger.error('Circular dependency detected', { remaining });
205
+ throw new Error(`Circular dependency detected among: ${remaining.join(', ')}`);
206
+ }
207
+ this.logger.debug('Dependencies resolved', { order: result });
208
+ return result;
209
+ }
210
+ /**
211
+ * Detect dependency conflicts
212
+ */
213
+ detectConflicts(plugins) {
214
+ const conflicts = [];
215
+ const versionRequirements = new Map();
216
+ // Collect all version requirements
217
+ for (const [pluginName, pluginInfo] of plugins) {
218
+ if (!pluginInfo.dependencies)
219
+ continue;
220
+ for (const [depName, constraint] of Object.entries(pluginInfo.dependencies)) {
221
+ if (!versionRequirements.has(depName)) {
222
+ versionRequirements.set(depName, new Map());
223
+ }
224
+ versionRequirements.get(depName).set(pluginName, constraint);
225
+ }
226
+ }
227
+ // Check for version mismatches
228
+ for (const [depName, requirements] of versionRequirements) {
229
+ const depInfo = plugins.get(depName);
230
+ if (!depInfo)
231
+ continue;
232
+ const depVersion = SemanticVersionManager.parse(depInfo.version);
233
+ const unsatisfied = [];
234
+ for (const [requiringPlugin, constraint] of requirements) {
235
+ if (!SemanticVersionManager.satisfies(depVersion, constraint)) {
236
+ unsatisfied.push({
237
+ pluginId: requiringPlugin,
238
+ version: constraint,
239
+ });
240
+ }
241
+ }
242
+ if (unsatisfied.length > 0) {
243
+ conflicts.push({
244
+ type: 'version-mismatch',
245
+ severity: 'error',
246
+ description: `Version mismatch for ${depName}: detected ${unsatisfied.length} unsatisfied requirements`,
247
+ plugins: [
248
+ { pluginId: depName, version: depInfo.version },
249
+ ...unsatisfied,
250
+ ],
251
+ resolutions: [{
252
+ strategy: 'upgrade',
253
+ description: `Upgrade ${depName} to satisfy all constraints`,
254
+ targetPlugins: [depName],
255
+ automatic: false,
256
+ }],
257
+ });
258
+ }
259
+ }
260
+ // Check for circular dependencies (will be caught by resolve())
261
+ try {
262
+ this.resolve(new Map(Array.from(plugins.entries()).map(([name, info]) => [
263
+ name,
264
+ { version: info.version, dependencies: info.dependencies ? Object.keys(info.dependencies) : [] }
265
+ ])));
266
+ }
267
+ catch (error) {
268
+ if (error instanceof Error && error.message.includes('Circular dependency')) {
269
+ conflicts.push({
270
+ type: 'circular-dependency',
271
+ severity: 'critical',
272
+ description: error.message,
273
+ plugins: [], // Would need to extract from error
274
+ resolutions: [{
275
+ strategy: 'manual',
276
+ description: 'Remove circular dependency by restructuring plugins',
277
+ automatic: false,
278
+ }],
279
+ });
280
+ }
281
+ }
282
+ return conflicts;
283
+ }
284
+ /**
285
+ * Find best version that satisfies all constraints
286
+ */
287
+ findBestVersion(availableVersions, constraints) {
288
+ // Parse and sort versions (highest first)
289
+ const versions = availableVersions
290
+ .map(v => ({ str: v, parsed: SemanticVersionManager.parse(v) }))
291
+ .sort((a, b) => -SemanticVersionManager.compare(a.parsed, b.parsed));
292
+ // Find highest version that satisfies all constraints
293
+ for (const version of versions) {
294
+ const satisfiesAll = constraints.every(constraint => SemanticVersionManager.satisfies(version.parsed, constraint));
295
+ if (satisfiesAll) {
296
+ return version.str;
297
+ }
298
+ }
299
+ return undefined;
300
+ }
301
+ /**
302
+ * Check if dependencies form a valid DAG (no cycles)
303
+ */
304
+ isAcyclic(dependencies) {
305
+ try {
306
+ const plugins = new Map(Array.from(dependencies.entries()).map(([name, deps]) => [
307
+ name,
308
+ { dependencies: deps }
309
+ ]));
310
+ this.resolve(plugins);
311
+ return true;
312
+ }
313
+ catch {
314
+ return false;
315
+ }
316
+ }
317
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dependency-resolver.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependency-resolver.test.d.ts","sourceRoot":"","sources":["../src/dependency-resolver.test.ts"],"names":[],"mappings":""}