@objectstack/core 4.0.4 → 4.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 (75) hide show
  1. package/README.md +95 -10
  2. package/dist/index.cjs +172 -507
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +24 -223
  5. package/dist/index.d.ts +24 -223
  6. package/dist/index.js +178 -505
  7. package/dist/index.js.map +1 -1
  8. package/dist/logger.cjs +177 -0
  9. package/dist/logger.cjs.map +1 -0
  10. package/dist/logger.d.cts +26 -0
  11. package/dist/logger.d.ts +26 -0
  12. package/dist/logger.js +158 -0
  13. package/dist/logger.js.map +1 -0
  14. package/package.json +36 -15
  15. package/.turbo/turbo-build.log +0 -22
  16. package/ADVANCED_FEATURES.md +0 -380
  17. package/API_REGISTRY.md +0 -392
  18. package/CHANGELOG.md +0 -472
  19. package/PHASE2_IMPLEMENTATION.md +0 -388
  20. package/REFACTORING_SUMMARY.md +0 -40
  21. package/examples/api-registry-example.ts +0 -559
  22. package/examples/kernel-features-example.ts +0 -311
  23. package/examples/phase2-integration.ts +0 -357
  24. package/src/api-registry-plugin.test.ts +0 -393
  25. package/src/api-registry-plugin.ts +0 -89
  26. package/src/api-registry.test.ts +0 -1089
  27. package/src/api-registry.ts +0 -739
  28. package/src/contracts/data-engine.ts +0 -57
  29. package/src/contracts/http-server.ts +0 -151
  30. package/src/contracts/logger.ts +0 -72
  31. package/src/dependency-resolver.test.ts +0 -287
  32. package/src/dependency-resolver.ts +0 -390
  33. package/src/fallbacks/fallbacks.test.ts +0 -281
  34. package/src/fallbacks/index.ts +0 -26
  35. package/src/fallbacks/memory-cache.ts +0 -34
  36. package/src/fallbacks/memory-i18n.ts +0 -112
  37. package/src/fallbacks/memory-job.ts +0 -23
  38. package/src/fallbacks/memory-metadata.ts +0 -50
  39. package/src/fallbacks/memory-queue.ts +0 -28
  40. package/src/health-monitor.test.ts +0 -81
  41. package/src/health-monitor.ts +0 -318
  42. package/src/hot-reload.ts +0 -382
  43. package/src/index.ts +0 -50
  44. package/src/kernel-base.ts +0 -273
  45. package/src/kernel.test.ts +0 -624
  46. package/src/kernel.ts +0 -631
  47. package/src/lite-kernel.test.ts +0 -248
  48. package/src/lite-kernel.ts +0 -137
  49. package/src/logger.test.ts +0 -116
  50. package/src/logger.ts +0 -355
  51. package/src/namespace-resolver.test.ts +0 -130
  52. package/src/namespace-resolver.ts +0 -188
  53. package/src/package-manager.test.ts +0 -225
  54. package/src/package-manager.ts +0 -428
  55. package/src/plugin-loader.test.ts +0 -421
  56. package/src/plugin-loader.ts +0 -484
  57. package/src/qa/adapter.ts +0 -16
  58. package/src/qa/http-adapter.ts +0 -116
  59. package/src/qa/index.ts +0 -5
  60. package/src/qa/runner.ts +0 -189
  61. package/src/security/index.ts +0 -50
  62. package/src/security/permission-manager.test.ts +0 -256
  63. package/src/security/permission-manager.ts +0 -338
  64. package/src/security/plugin-config-validator.test.ts +0 -276
  65. package/src/security/plugin-config-validator.ts +0 -193
  66. package/src/security/plugin-permission-enforcer.test.ts +0 -251
  67. package/src/security/plugin-permission-enforcer.ts +0 -436
  68. package/src/security/plugin-signature-verifier.ts +0 -403
  69. package/src/security/sandbox-runtime.ts +0 -462
  70. package/src/security/security-scanner.ts +0 -367
  71. package/src/types.ts +0 -120
  72. package/src/utils/env.test.ts +0 -62
  73. package/src/utils/env.ts +0 -53
  74. package/tsconfig.json +0 -10
  75. package/vitest.config.ts +0 -10
@@ -1,34 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- /**
4
- * In-memory Map-backed cache fallback.
5
- *
6
- * Implements the ICacheService contract with basic get/set/delete/has/clear
7
- * and TTL expiry. Used by ObjectKernel as an automatic fallback when no
8
- * real cache plugin (e.g. Redis) is registered.
9
- */
10
- export function createMemoryCache() {
11
- const store = new Map<string, { value: unknown; expires?: number }>();
12
- let hits = 0;
13
- let misses = 0;
14
- return {
15
- _fallback: true, _serviceName: 'cache',
16
- async get<T = unknown>(key: string): Promise<T | undefined> {
17
- const entry = store.get(key);
18
- if (!entry || (entry.expires && Date.now() > entry.expires)) {
19
- store.delete(key);
20
- misses++;
21
- return undefined;
22
- }
23
- hits++;
24
- return entry.value as T;
25
- },
26
- async set<T = unknown>(key: string, value: T, ttl?: number): Promise<void> {
27
- store.set(key, { value, expires: ttl ? Date.now() + ttl * 1000 : undefined });
28
- },
29
- async delete(key: string): Promise<boolean> { return store.delete(key); },
30
- async has(key: string): Promise<boolean> { return store.has(key); },
31
- async clear(): Promise<void> { store.clear(); },
32
- async stats() { return { hits, misses, keyCount: store.size }; },
33
- };
34
- }
@@ -1,112 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- /**
4
- * Resolve a locale code against available locales with fallback.
5
- *
6
- * Fallback chain:
7
- * 1. Exact match (e.g. `zh-CN` → `zh-CN`)
8
- * 2. Case-insensitive match (e.g. `zh-cn` → `zh-CN`)
9
- * 3. Base language match (e.g. `zh-CN` → `zh`)
10
- * 4. Variant expansion (e.g. `zh` → `zh-CN`)
11
- *
12
- * Returns the matched locale code, or `undefined` when no match is found.
13
- */
14
- export function resolveLocale(requestedLocale: string, availableLocales: string[]): string | undefined {
15
- if (availableLocales.length === 0) return undefined;
16
-
17
- // 1. Exact match
18
- if (availableLocales.includes(requestedLocale)) return requestedLocale;
19
-
20
- // 2. Case-insensitive match
21
- const lower = requestedLocale.toLowerCase();
22
- const caseMatch = availableLocales.find(l => l.toLowerCase() === lower);
23
- if (caseMatch) return caseMatch;
24
-
25
- // 3. Base language match (zh-CN → zh)
26
- const baseLang = requestedLocale.split('-')[0].toLowerCase();
27
- const baseMatch = availableLocales.find(l => l.toLowerCase() === baseLang);
28
- if (baseMatch) return baseMatch;
29
-
30
- // 4. Variant expansion (zh → zh-CN, zh-TW, etc. — first match wins)
31
- const variantMatch = availableLocales.find(l => l.split('-')[0].toLowerCase() === baseLang);
32
- if (variantMatch) return variantMatch;
33
-
34
- return undefined;
35
- }
36
-
37
- /**
38
- * In-memory i18n service fallback.
39
- *
40
- * Implements the II18nService contract with basic translate/load/getLocales
41
- * operations. Used by ObjectKernel as an automatic fallback when no real
42
- * i18n plugin (e.g. I18nServicePlugin) is registered.
43
- *
44
- * Supports runtime translation loading, locale management, and
45
- * locale code fallback (e.g. `zh` → `zh-CN`).
46
- * Does not load files from disk — operates purely in-memory.
47
- */
48
- export function createMemoryI18n() {
49
- const translations = new Map<string, Record<string, unknown>>();
50
- let defaultLocale = 'en';
51
-
52
- /**
53
- * Resolve a dot-notation key from a nested object.
54
- */
55
- function resolveKey(data: Record<string, unknown>, key: string): string | undefined {
56
- const parts = key.split('.');
57
- let current: unknown = data;
58
- for (const part of parts) {
59
- if (current == null || typeof current !== 'object') return undefined;
60
- current = (current as Record<string, unknown>)[part];
61
- }
62
- return typeof current === 'string' ? current : undefined;
63
- }
64
-
65
- /**
66
- * Find translation data for a locale, with fallback resolution.
67
- */
68
- function resolveTranslations(locale: string): Record<string, unknown> | undefined {
69
- // Exact match
70
- if (translations.has(locale)) return translations.get(locale);
71
-
72
- // Locale fallback (zh → zh-CN, en-us → en-US, etc.)
73
- const resolved = resolveLocale(locale, [...translations.keys()]);
74
- if (resolved) return translations.get(resolved);
75
-
76
- return undefined;
77
- }
78
-
79
- return {
80
- _fallback: true, _serviceName: 'i18n',
81
-
82
- t(key: string, locale: string, params?: Record<string, unknown>): string {
83
- const data = resolveTranslations(locale) ?? translations.get(defaultLocale);
84
- const value = data ? resolveKey(data, key) : undefined;
85
- if (value == null) return key;
86
- if (!params) return value;
87
- // Interpolation format: {{paramName}} — matches FileI18nAdapter convention
88
- return value.replace(/\{\{(\w+)\}\}/g, (_, name) => String(params[name] ?? `{{${name}}}`));
89
- },
90
-
91
- getTranslations(locale: string): Record<string, unknown> {
92
- return resolveTranslations(locale) ?? {};
93
- },
94
-
95
- loadTranslations(locale: string, data: Record<string, unknown>): void {
96
- const existing = translations.get(locale) ?? {};
97
- translations.set(locale, { ...existing, ...data });
98
- },
99
-
100
- getLocales(): string[] {
101
- return [...translations.keys()];
102
- },
103
-
104
- getDefaultLocale(): string {
105
- return defaultLocale;
106
- },
107
-
108
- setDefaultLocale(locale: string): void {
109
- defaultLocale = locale;
110
- },
111
- };
112
- }
@@ -1,23 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- /**
4
- * In-memory job scheduler fallback.
5
- *
6
- * Implements the IJobService contract with basic schedule/cancel/trigger
7
- * operations. Used by ObjectKernel as an automatic fallback when no real
8
- * job plugin (e.g. Agenda / BullMQ) is registered.
9
- */
10
- export function createMemoryJob() {
11
- const jobs = new Map<string, any>();
12
- return {
13
- _fallback: true, _serviceName: 'job',
14
- async schedule(name: string, schedule: any, handler: any): Promise<void> { jobs.set(name, { schedule, handler }); },
15
- async cancel(name: string): Promise<void> { jobs.delete(name); },
16
- async trigger(name: string, data?: unknown): Promise<void> {
17
- const job = jobs.get(name);
18
- if (job?.handler) await job.handler({ jobId: name, data });
19
- },
20
- async getExecutions(): Promise<any[]> { return []; },
21
- async listJobs(): Promise<string[]> { return [...jobs.keys()]; },
22
- };
23
- }
@@ -1,50 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- /**
4
- * In-memory metadata service fallback.
5
- *
6
- * Implements the IMetadataService contract with a simple Map-of-Maps store.
7
- * Used by ObjectKernel as an automatic fallback when no real metadata plugin
8
- * (e.g. MetadataPlugin with file-system persistence) is registered.
9
- */
10
- export function createMemoryMetadata() {
11
- // type -> name -> data
12
- const store = new Map<string, Map<string, any>>();
13
-
14
- function getTypeMap(type: string): Map<string, any> {
15
- let map = store.get(type);
16
- if (!map) {
17
- map = new Map();
18
- store.set(type, map);
19
- }
20
- return map;
21
- }
22
-
23
- return {
24
- _fallback: true, _serviceName: 'metadata',
25
- async register(type: string, name: string, data: any): Promise<void> {
26
- getTypeMap(type).set(name, data);
27
- },
28
- async get(type: string, name: string): Promise<any> {
29
- return getTypeMap(type).get(name);
30
- },
31
- async list(type: string): Promise<any[]> {
32
- return Array.from(getTypeMap(type).values());
33
- },
34
- async unregister(type: string, name: string): Promise<void> {
35
- getTypeMap(type).delete(name);
36
- },
37
- async exists(type: string, name: string): Promise<boolean> {
38
- return getTypeMap(type).has(name);
39
- },
40
- async listNames(type: string): Promise<string[]> {
41
- return Array.from(getTypeMap(type).keys());
42
- },
43
- async getObject(name: string): Promise<any> {
44
- return getTypeMap('object').get(name);
45
- },
46
- async listObjects(): Promise<any[]> {
47
- return Array.from(getTypeMap('object').values());
48
- },
49
- };
50
- }
@@ -1,28 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- /**
4
- * In-memory publish/subscribe queue fallback.
5
- *
6
- * Implements the IQueueService contract with synchronous in-process delivery.
7
- * Used by ObjectKernel as an automatic fallback when no real queue plugin
8
- * (e.g. BullMQ / RabbitMQ) is registered.
9
- */
10
- export function createMemoryQueue() {
11
- const handlers = new Map<string, Function[]>();
12
- let msgId = 0;
13
- return {
14
- _fallback: true, _serviceName: 'queue',
15
- async publish<T = unknown>(queue: string, data: T): Promise<string> {
16
- const id = `fallback-msg-${++msgId}`;
17
- const fns = handlers.get(queue) ?? [];
18
- for (const fn of fns) fn({ id, data, attempts: 1, timestamp: Date.now() });
19
- return id;
20
- },
21
- async subscribe(queue: string, handler: (msg: any) => Promise<void>): Promise<void> {
22
- handlers.set(queue, [...(handlers.get(queue) ?? []), handler]);
23
- },
24
- async unsubscribe(queue: string): Promise<void> { handlers.delete(queue); },
25
- async getQueueSize(): Promise<number> { return 0; },
26
- async purge(queue: string): Promise<void> { handlers.delete(queue); },
27
- };
28
- }
@@ -1,81 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { PluginHealthMonitor } from './health-monitor.js';
3
- import { createLogger } from './logger.js';
4
- import type { PluginHealthCheck } from '@objectstack/spec/kernel';
5
-
6
- describe('PluginHealthMonitor', () => {
7
- let monitor: PluginHealthMonitor;
8
- let logger: ReturnType<typeof createLogger>;
9
-
10
- beforeEach(() => {
11
- logger = createLogger({ level: 'silent' });
12
- monitor = new PluginHealthMonitor(logger);
13
- });
14
-
15
- it('should register plugin for health monitoring', () => {
16
- const config: PluginHealthCheck = {
17
- interval: 5000,
18
- timeout: 1000,
19
- failureThreshold: 3,
20
- successThreshold: 1,
21
- autoRestart: false,
22
- maxRestartAttempts: 3,
23
- restartBackoff: 'exponential',
24
- };
25
-
26
- monitor.registerPlugin('test-plugin', config);
27
- expect(monitor.getHealthStatus('test-plugin')).toBe('unknown');
28
- });
29
-
30
- it('should report healthy status initially', () => {
31
- const config: PluginHealthCheck = {
32
- interval: 5000,
33
- timeout: 1000,
34
- failureThreshold: 3,
35
- successThreshold: 1,
36
- autoRestart: false,
37
- maxRestartAttempts: 3,
38
- restartBackoff: 'fixed',
39
- };
40
-
41
- monitor.registerPlugin('test-plugin', config);
42
- expect(monitor.getHealthStatus('test-plugin')).toBe('unknown');
43
- });
44
-
45
- it('should get all health statuses', () => {
46
- const config: PluginHealthCheck = {
47
- interval: 5000,
48
- timeout: 1000,
49
- failureThreshold: 3,
50
- successThreshold: 1,
51
- autoRestart: false,
52
- maxRestartAttempts: 3,
53
- restartBackoff: 'linear',
54
- };
55
-
56
- monitor.registerPlugin('plugin1', config);
57
- monitor.registerPlugin('plugin2', config);
58
-
59
- const statuses = monitor.getAllHealthStatuses();
60
- expect(statuses.size).toBe(2);
61
- expect(statuses.has('plugin1')).toBe(true);
62
- expect(statuses.has('plugin2')).toBe(true);
63
- });
64
-
65
- it('should shutdown cleanly', () => {
66
- const config: PluginHealthCheck = {
67
- interval: 5000,
68
- timeout: 1000,
69
- failureThreshold: 3,
70
- successThreshold: 1,
71
- autoRestart: false,
72
- maxRestartAttempts: 3,
73
- restartBackoff: 'exponential',
74
- };
75
-
76
- monitor.registerPlugin('test-plugin', config);
77
- monitor.shutdown();
78
-
79
- expect(monitor.getAllHealthStatuses().size).toBe(0);
80
- });
81
- });
@@ -1,318 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import type {
4
- PluginHealthStatus,
5
- PluginHealthCheck,
6
- PluginHealthReport
7
- } from '@objectstack/spec/kernel';
8
- import type { ObjectLogger } from './logger.js';
9
- import type { Plugin } from './types.js';
10
-
11
- /**
12
- * Plugin Health Monitor
13
- *
14
- * Monitors plugin health status and performs automatic recovery actions.
15
- * Implements the advanced lifecycle health monitoring protocol.
16
- */
17
- export class PluginHealthMonitor {
18
- private logger: ObjectLogger;
19
- private healthChecks = new Map<string, PluginHealthCheck>();
20
- private healthStatus = new Map<string, PluginHealthStatus>();
21
- private healthReports = new Map<string, PluginHealthReport>();
22
- private checkIntervals = new Map<string, NodeJS.Timeout>();
23
- private failureCounters = new Map<string, number>();
24
- private successCounters = new Map<string, number>();
25
- private restartAttempts = new Map<string, number>();
26
-
27
- constructor(logger: ObjectLogger) {
28
- this.logger = logger.child({ component: 'HealthMonitor' });
29
- }
30
-
31
- /**
32
- * Register a plugin for health monitoring
33
- */
34
- registerPlugin(pluginName: string, config: PluginHealthCheck): void {
35
- this.healthChecks.set(pluginName, config);
36
- this.healthStatus.set(pluginName, 'unknown');
37
- this.failureCounters.set(pluginName, 0);
38
- this.successCounters.set(pluginName, 0);
39
- this.restartAttempts.set(pluginName, 0);
40
-
41
- this.logger.info('Plugin registered for health monitoring', {
42
- plugin: pluginName,
43
- interval: config.interval
44
- });
45
- }
46
-
47
- /**
48
- * Start monitoring a plugin
49
- */
50
- startMonitoring(pluginName: string, plugin: Plugin): void {
51
- const config = this.healthChecks.get(pluginName);
52
- if (!config) {
53
- this.logger.warn('Cannot start monitoring - plugin not registered', { plugin: pluginName });
54
- return;
55
- }
56
-
57
- // Clear any existing interval
58
- this.stopMonitoring(pluginName);
59
-
60
- // Set up periodic health checks
61
- const interval = setInterval(() => {
62
- this.performHealthCheck(pluginName, plugin, config).catch(error => {
63
- this.logger.error('Health check failed with error', {
64
- plugin: pluginName,
65
- error
66
- });
67
- });
68
- }, config.interval);
69
-
70
- this.checkIntervals.set(pluginName, interval);
71
- this.logger.info('Health monitoring started', { plugin: pluginName });
72
-
73
- // Perform initial health check
74
- this.performHealthCheck(pluginName, plugin, config).catch(error => {
75
- this.logger.error('Initial health check failed', {
76
- plugin: pluginName,
77
- error
78
- });
79
- });
80
- }
81
-
82
- /**
83
- * Stop monitoring a plugin
84
- */
85
- stopMonitoring(pluginName: string): void {
86
- const interval = this.checkIntervals.get(pluginName);
87
- if (interval) {
88
- clearInterval(interval);
89
- this.checkIntervals.delete(pluginName);
90
- this.logger.info('Health monitoring stopped', { plugin: pluginName });
91
- }
92
- }
93
-
94
- /**
95
- * Perform a health check on a plugin
96
- */
97
- private async performHealthCheck(
98
- pluginName: string,
99
- plugin: Plugin,
100
- config: PluginHealthCheck
101
- ): Promise<void> {
102
- const startTime = Date.now();
103
- let status: PluginHealthStatus = 'healthy';
104
- let message: string | undefined;
105
- const checks: Array<{ name: string; status: 'passed' | 'failed' | 'warning'; message?: string }> = [];
106
-
107
- try {
108
- // Check if plugin has a custom health check method
109
- if (config.checkMethod && typeof (plugin as any)[config.checkMethod] === 'function') {
110
- const checkResult = await Promise.race([
111
- (plugin as any)[config.checkMethod](),
112
- this.timeout(config.timeout, `Health check timeout after ${config.timeout}ms`)
113
- ]);
114
-
115
- if (checkResult === false || (checkResult && checkResult.status === 'unhealthy')) {
116
- status = 'unhealthy';
117
- message = checkResult?.message || 'Custom health check failed';
118
- checks.push({ name: config.checkMethod, status: 'failed', message });
119
- } else {
120
- checks.push({ name: config.checkMethod, status: 'passed' });
121
- }
122
- } else {
123
- // Default health check - just verify plugin is loaded
124
- checks.push({ name: 'plugin-loaded', status: 'passed' });
125
- }
126
-
127
- // Update counters based on result
128
- if (status === 'healthy') {
129
- this.successCounters.set(pluginName, (this.successCounters.get(pluginName) || 0) + 1);
130
- this.failureCounters.set(pluginName, 0);
131
-
132
- // Recover from unhealthy state if we have enough successes
133
- const currentStatus = this.healthStatus.get(pluginName);
134
- if (currentStatus === 'unhealthy' || currentStatus === 'degraded') {
135
- const successCount = this.successCounters.get(pluginName) || 0;
136
- if (successCount >= config.successThreshold) {
137
- this.healthStatus.set(pluginName, 'healthy');
138
- this.logger.info('Plugin recovered to healthy state', { plugin: pluginName });
139
- } else {
140
- this.healthStatus.set(pluginName, 'recovering');
141
- }
142
- } else {
143
- this.healthStatus.set(pluginName, 'healthy');
144
- }
145
- } else {
146
- this.failureCounters.set(pluginName, (this.failureCounters.get(pluginName) || 0) + 1);
147
- this.successCounters.set(pluginName, 0);
148
-
149
- const failureCount = this.failureCounters.get(pluginName) || 0;
150
- if (failureCount >= config.failureThreshold) {
151
- this.healthStatus.set(pluginName, 'unhealthy');
152
- this.logger.warn('Plugin marked as unhealthy', {
153
- plugin: pluginName,
154
- failures: failureCount
155
- });
156
-
157
- // Attempt auto-restart if configured
158
- if (config.autoRestart) {
159
- await this.attemptRestart(pluginName, plugin, config);
160
- }
161
- } else {
162
- this.healthStatus.set(pluginName, 'degraded');
163
- }
164
- }
165
- } catch (error) {
166
- status = 'failed';
167
- message = error instanceof Error ? error.message : 'Unknown error';
168
- this.failureCounters.set(pluginName, (this.failureCounters.get(pluginName) || 0) + 1);
169
- this.healthStatus.set(pluginName, 'failed');
170
-
171
- checks.push({
172
- name: 'health-check',
173
- status: 'failed',
174
- message: message
175
- });
176
-
177
- this.logger.error('Health check exception', {
178
- plugin: pluginName,
179
- error
180
- });
181
- }
182
-
183
- // Create health report
184
- const report: PluginHealthReport = {
185
- status: this.healthStatus.get(pluginName) || 'unknown',
186
- timestamp: new Date().toISOString(),
187
- message,
188
- metrics: {
189
- uptime: Date.now() - startTime,
190
- },
191
- checks: checks.length > 0 ? checks : undefined,
192
- };
193
-
194
- this.healthReports.set(pluginName, report);
195
- }
196
-
197
- /**
198
- * Attempt to restart a plugin
199
- */
200
- private async attemptRestart(
201
- pluginName: string,
202
- plugin: Plugin,
203
- config: PluginHealthCheck
204
- ): Promise<void> {
205
- const attempts = this.restartAttempts.get(pluginName) || 0;
206
-
207
- if (attempts >= config.maxRestartAttempts) {
208
- this.logger.error('Max restart attempts reached, giving up', {
209
- plugin: pluginName,
210
- attempts
211
- });
212
- this.healthStatus.set(pluginName, 'failed');
213
- return;
214
- }
215
-
216
- this.restartAttempts.set(pluginName, attempts + 1);
217
-
218
- // Calculate backoff delay
219
- const delay = this.calculateBackoff(attempts, config.restartBackoff);
220
-
221
- this.logger.info('Scheduling plugin restart', {
222
- plugin: pluginName,
223
- attempt: attempts + 1,
224
- delay
225
- });
226
-
227
- await new Promise(resolve => setTimeout(resolve, delay));
228
-
229
- try {
230
- // Call destroy and init to restart
231
- if (plugin.destroy) {
232
- await plugin.destroy();
233
- }
234
-
235
- // Note: Full restart would require kernel context
236
- // This is a simplified version - actual implementation would need kernel integration
237
- this.logger.info('Plugin restarted', { plugin: pluginName });
238
-
239
- // Reset counters on successful restart
240
- this.failureCounters.set(pluginName, 0);
241
- this.successCounters.set(pluginName, 0);
242
- this.healthStatus.set(pluginName, 'recovering');
243
- } catch (error) {
244
- this.logger.error('Plugin restart failed', {
245
- plugin: pluginName,
246
- error
247
- });
248
- this.healthStatus.set(pluginName, 'failed');
249
- }
250
- }
251
-
252
- /**
253
- * Calculate backoff delay for restarts
254
- */
255
- private calculateBackoff(attempt: number, strategy: 'fixed' | 'linear' | 'exponential'): number {
256
- const baseDelay = 1000; // 1 second base
257
-
258
- switch (strategy) {
259
- case 'fixed':
260
- return baseDelay;
261
- case 'linear':
262
- return baseDelay * (attempt + 1);
263
- case 'exponential':
264
- return baseDelay * Math.pow(2, attempt);
265
- default:
266
- return baseDelay;
267
- }
268
- }
269
-
270
- /**
271
- * Get current health status of a plugin
272
- */
273
- getHealthStatus(pluginName: string): PluginHealthStatus | undefined {
274
- return this.healthStatus.get(pluginName);
275
- }
276
-
277
- /**
278
- * Get latest health report for a plugin
279
- */
280
- getHealthReport(pluginName: string): PluginHealthReport | undefined {
281
- return this.healthReports.get(pluginName);
282
- }
283
-
284
- /**
285
- * Get all health statuses
286
- */
287
- getAllHealthStatuses(): Map<string, PluginHealthStatus> {
288
- return new Map(this.healthStatus);
289
- }
290
-
291
- /**
292
- * Shutdown health monitor
293
- */
294
- shutdown(): void {
295
- // Stop all monitoring intervals
296
- for (const pluginName of this.checkIntervals.keys()) {
297
- this.stopMonitoring(pluginName);
298
- }
299
-
300
- this.healthChecks.clear();
301
- this.healthStatus.clear();
302
- this.healthReports.clear();
303
- this.failureCounters.clear();
304
- this.successCounters.clear();
305
- this.restartAttempts.clear();
306
-
307
- this.logger.info('Health monitor shutdown complete');
308
- }
309
-
310
- /**
311
- * Timeout helper
312
- */
313
- private timeout<T>(ms: number, message: string): Promise<T> {
314
- return new Promise((_, reject) => {
315
- setTimeout(() => reject(new Error(message)), ms);
316
- });
317
- }
318
- }