@objectstack/core 1.0.4 → 1.0.6

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 (127) hide show
  1. package/.turbo/turbo-build.log +22 -0
  2. package/CHANGELOG.md +19 -0
  3. package/dist/index.cjs +4304 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +1777 -0
  6. package/dist/index.d.ts +1776 -21
  7. package/dist/index.js +4246 -23
  8. package/dist/index.js.map +1 -0
  9. package/package.json +5 -5
  10. package/src/logger.ts +2 -2
  11. package/src/security/plugin-signature-verifier.ts +12 -11
  12. package/tsconfig.json +1 -3
  13. package/dist/api-registry-plugin.d.ts +0 -54
  14. package/dist/api-registry-plugin.d.ts.map +0 -1
  15. package/dist/api-registry-plugin.js +0 -53
  16. package/dist/api-registry-plugin.test.d.ts +0 -2
  17. package/dist/api-registry-plugin.test.d.ts.map +0 -1
  18. package/dist/api-registry-plugin.test.js +0 -334
  19. package/dist/api-registry.d.ts +0 -259
  20. package/dist/api-registry.d.ts.map +0 -1
  21. package/dist/api-registry.js +0 -600
  22. package/dist/api-registry.test.d.ts +0 -2
  23. package/dist/api-registry.test.d.ts.map +0 -1
  24. package/dist/api-registry.test.js +0 -957
  25. package/dist/contracts/data-engine.d.ts +0 -62
  26. package/dist/contracts/data-engine.d.ts.map +0 -1
  27. package/dist/contracts/data-engine.js +0 -1
  28. package/dist/contracts/http-server.d.ts +0 -119
  29. package/dist/contracts/http-server.d.ts.map +0 -1
  30. package/dist/contracts/http-server.js +0 -11
  31. package/dist/contracts/logger.d.ts +0 -63
  32. package/dist/contracts/logger.d.ts.map +0 -1
  33. package/dist/contracts/logger.js +0 -1
  34. package/dist/dependency-resolver.d.ts +0 -62
  35. package/dist/dependency-resolver.d.ts.map +0 -1
  36. package/dist/dependency-resolver.js +0 -317
  37. package/dist/dependency-resolver.test.d.ts +0 -2
  38. package/dist/dependency-resolver.test.d.ts.map +0 -1
  39. package/dist/dependency-resolver.test.js +0 -241
  40. package/dist/health-monitor.d.ts +0 -65
  41. package/dist/health-monitor.d.ts.map +0 -1
  42. package/dist/health-monitor.js +0 -269
  43. package/dist/health-monitor.test.d.ts +0 -2
  44. package/dist/health-monitor.test.d.ts.map +0 -1
  45. package/dist/health-monitor.test.js +0 -68
  46. package/dist/hot-reload.d.ts +0 -79
  47. package/dist/hot-reload.d.ts.map +0 -1
  48. package/dist/hot-reload.js +0 -313
  49. package/dist/index.d.ts.map +0 -1
  50. package/dist/kernel-base.d.ts +0 -84
  51. package/dist/kernel-base.d.ts.map +0 -1
  52. package/dist/kernel-base.js +0 -219
  53. package/dist/kernel.d.ts +0 -113
  54. package/dist/kernel.d.ts.map +0 -1
  55. package/dist/kernel.js +0 -472
  56. package/dist/kernel.test.d.ts +0 -2
  57. package/dist/kernel.test.d.ts.map +0 -1
  58. package/dist/kernel.test.js +0 -414
  59. package/dist/lite-kernel.d.ts +0 -55
  60. package/dist/lite-kernel.d.ts.map +0 -1
  61. package/dist/lite-kernel.js +0 -112
  62. package/dist/lite-kernel.test.d.ts +0 -2
  63. package/dist/lite-kernel.test.d.ts.map +0 -1
  64. package/dist/lite-kernel.test.js +0 -161
  65. package/dist/logger.d.ts +0 -71
  66. package/dist/logger.d.ts.map +0 -1
  67. package/dist/logger.js +0 -312
  68. package/dist/logger.test.d.ts +0 -2
  69. package/dist/logger.test.d.ts.map +0 -1
  70. package/dist/logger.test.js +0 -92
  71. package/dist/plugin-loader.d.ts +0 -164
  72. package/dist/plugin-loader.d.ts.map +0 -1
  73. package/dist/plugin-loader.js +0 -319
  74. package/dist/plugin-loader.test.d.ts +0 -2
  75. package/dist/plugin-loader.test.d.ts.map +0 -1
  76. package/dist/plugin-loader.test.js +0 -348
  77. package/dist/qa/adapter.d.ts +0 -14
  78. package/dist/qa/adapter.d.ts.map +0 -1
  79. package/dist/qa/adapter.js +0 -1
  80. package/dist/qa/http-adapter.d.ts +0 -16
  81. package/dist/qa/http-adapter.d.ts.map +0 -1
  82. package/dist/qa/http-adapter.js +0 -107
  83. package/dist/qa/index.d.ts +0 -4
  84. package/dist/qa/index.d.ts.map +0 -1
  85. package/dist/qa/index.js +0 -3
  86. package/dist/qa/runner.d.ts +0 -27
  87. package/dist/qa/runner.d.ts.map +0 -1
  88. package/dist/qa/runner.js +0 -157
  89. package/dist/security/index.d.ts +0 -17
  90. package/dist/security/index.d.ts.map +0 -1
  91. package/dist/security/index.js +0 -17
  92. package/dist/security/permission-manager.d.ts +0 -96
  93. package/dist/security/permission-manager.d.ts.map +0 -1
  94. package/dist/security/permission-manager.js +0 -235
  95. package/dist/security/permission-manager.test.d.ts +0 -2
  96. package/dist/security/permission-manager.test.d.ts.map +0 -1
  97. package/dist/security/permission-manager.test.js +0 -220
  98. package/dist/security/plugin-config-validator.d.ts +0 -79
  99. package/dist/security/plugin-config-validator.d.ts.map +0 -1
  100. package/dist/security/plugin-config-validator.js +0 -166
  101. package/dist/security/plugin-config-validator.test.d.ts +0 -2
  102. package/dist/security/plugin-config-validator.test.d.ts.map +0 -1
  103. package/dist/security/plugin-config-validator.test.js +0 -223
  104. package/dist/security/plugin-permission-enforcer.d.ts +0 -154
  105. package/dist/security/plugin-permission-enforcer.d.ts.map +0 -1
  106. package/dist/security/plugin-permission-enforcer.js +0 -323
  107. package/dist/security/plugin-permission-enforcer.test.d.ts +0 -2
  108. package/dist/security/plugin-permission-enforcer.test.d.ts.map +0 -1
  109. package/dist/security/plugin-permission-enforcer.test.js +0 -205
  110. package/dist/security/plugin-signature-verifier.d.ts +0 -96
  111. package/dist/security/plugin-signature-verifier.d.ts.map +0 -1
  112. package/dist/security/plugin-signature-verifier.js +0 -250
  113. package/dist/security/sandbox-runtime.d.ts +0 -115
  114. package/dist/security/sandbox-runtime.d.ts.map +0 -1
  115. package/dist/security/sandbox-runtime.js +0 -311
  116. package/dist/security/security-scanner.d.ts +0 -92
  117. package/dist/security/security-scanner.d.ts.map +0 -1
  118. package/dist/security/security-scanner.js +0 -273
  119. package/dist/types.d.ts +0 -89
  120. package/dist/types.d.ts.map +0 -1
  121. package/dist/types.js +0 -1
  122. package/dist/utils/env.d.ts +0 -20
  123. package/dist/utils/env.d.ts.map +0 -1
  124. package/dist/utils/env.js +0 -46
  125. package/dist/utils/env.test.d.ts +0 -2
  126. package/dist/utils/env.test.d.ts.map +0 -1
  127. package/dist/utils/env.test.js +0 -52
package/dist/index.cjs ADDED
@@ -0,0 +1,4304 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ApiRegistry: () => ApiRegistry,
34
+ DependencyResolver: () => DependencyResolver,
35
+ HotReloadManager: () => HotReloadManager,
36
+ LiteKernel: () => LiteKernel,
37
+ ObjectKernel: () => ObjectKernel,
38
+ ObjectKernelBase: () => ObjectKernelBase,
39
+ ObjectLogger: () => ObjectLogger,
40
+ PluginConfigValidator: () => PluginConfigValidator,
41
+ PluginHealthMonitor: () => PluginHealthMonitor,
42
+ PluginLoader: () => PluginLoader,
43
+ PluginPermissionEnforcer: () => PluginPermissionEnforcer,
44
+ PluginPermissionManager: () => PluginPermissionManager,
45
+ PluginSandboxRuntime: () => PluginSandboxRuntime,
46
+ PluginSecurityScanner: () => PluginSecurityScanner,
47
+ PluginSignatureVerifier: () => PluginSignatureVerifier,
48
+ QA: () => qa_exports,
49
+ SecurePluginContext: () => SecurePluginContext,
50
+ SemanticVersionManager: () => SemanticVersionManager,
51
+ ServiceLifecycle: () => ServiceLifecycle,
52
+ createApiRegistryPlugin: () => createApiRegistryPlugin,
53
+ createLogger: () => createLogger,
54
+ createPluginConfigValidator: () => createPluginConfigValidator,
55
+ createPluginPermissionEnforcer: () => createPluginPermissionEnforcer,
56
+ getEnv: () => getEnv,
57
+ getMemoryUsage: () => getMemoryUsage,
58
+ isNode: () => isNode,
59
+ safeExit: () => safeExit
60
+ });
61
+ module.exports = __toCommonJS(index_exports);
62
+
63
+ // src/kernel-base.ts
64
+ var ObjectKernelBase = class {
65
+ constructor(logger) {
66
+ this.plugins = /* @__PURE__ */ new Map();
67
+ this.services = /* @__PURE__ */ new Map();
68
+ this.hooks = /* @__PURE__ */ new Map();
69
+ this.state = "idle";
70
+ this.logger = logger;
71
+ }
72
+ /**
73
+ * Validate kernel state
74
+ * @param requiredState - Required state for the operation
75
+ * @throws Error if current state doesn't match
76
+ */
77
+ validateState(requiredState) {
78
+ if (this.state !== requiredState) {
79
+ throw new Error(
80
+ `[Kernel] Invalid state: expected '${requiredState}', got '${this.state}'`
81
+ );
82
+ }
83
+ }
84
+ /**
85
+ * Validate kernel is in idle state (for plugin registration)
86
+ */
87
+ validateIdle() {
88
+ if (this.state !== "idle") {
89
+ throw new Error("[Kernel] Cannot register plugins after bootstrap has started");
90
+ }
91
+ }
92
+ /**
93
+ * Create the plugin context
94
+ * Subclasses can override to customize context creation
95
+ */
96
+ createContext() {
97
+ return {
98
+ registerService: (name, service) => {
99
+ if (this.services instanceof Map) {
100
+ if (this.services.has(name)) {
101
+ throw new Error(`[Kernel] Service '${name}' already registered`);
102
+ }
103
+ this.services.set(name, service);
104
+ } else {
105
+ this.services.register(name, service);
106
+ }
107
+ this.logger.info(`Service '${name}' registered`, { service: name });
108
+ },
109
+ getService: (name) => {
110
+ if (this.services instanceof Map) {
111
+ const service = this.services.get(name);
112
+ if (!service) {
113
+ throw new Error(`[Kernel] Service '${name}' not found`);
114
+ }
115
+ return service;
116
+ } else {
117
+ return this.services.get(name);
118
+ }
119
+ },
120
+ hook: (name, handler) => {
121
+ if (!this.hooks.has(name)) {
122
+ this.hooks.set(name, []);
123
+ }
124
+ this.hooks.get(name).push(handler);
125
+ },
126
+ trigger: async (name, ...args) => {
127
+ const handlers = this.hooks.get(name) || [];
128
+ for (const handler of handlers) {
129
+ await handler(...args);
130
+ }
131
+ },
132
+ getServices: () => {
133
+ if (this.services instanceof Map) {
134
+ return new Map(this.services);
135
+ } else {
136
+ return /* @__PURE__ */ new Map();
137
+ }
138
+ },
139
+ logger: this.logger,
140
+ getKernel: () => this
141
+ };
142
+ }
143
+ /**
144
+ * Resolve plugin dependencies using topological sort
145
+ * @returns Ordered list of plugins (dependencies first)
146
+ */
147
+ resolveDependencies() {
148
+ const resolved = [];
149
+ const visited = /* @__PURE__ */ new Set();
150
+ const visiting = /* @__PURE__ */ new Set();
151
+ const visit = (pluginName) => {
152
+ if (visited.has(pluginName)) return;
153
+ if (visiting.has(pluginName)) {
154
+ throw new Error(`[Kernel] Circular dependency detected: ${pluginName}`);
155
+ }
156
+ const plugin = this.plugins.get(pluginName);
157
+ if (!plugin) {
158
+ throw new Error(`[Kernel] Plugin '${pluginName}' not found`);
159
+ }
160
+ visiting.add(pluginName);
161
+ const deps = plugin.dependencies || [];
162
+ for (const dep of deps) {
163
+ if (!this.plugins.has(dep)) {
164
+ throw new Error(
165
+ `[Kernel] Dependency '${dep}' not found for plugin '${pluginName}'`
166
+ );
167
+ }
168
+ visit(dep);
169
+ }
170
+ visiting.delete(pluginName);
171
+ visited.add(pluginName);
172
+ resolved.push(plugin);
173
+ };
174
+ for (const pluginName of this.plugins.keys()) {
175
+ visit(pluginName);
176
+ }
177
+ return resolved;
178
+ }
179
+ /**
180
+ * Run plugin init phase
181
+ * @param plugin - Plugin to initialize
182
+ */
183
+ async runPluginInit(plugin) {
184
+ const pluginName = plugin.name;
185
+ this.logger.info(`Initializing plugin: ${pluginName}`);
186
+ try {
187
+ await plugin.init(this.context);
188
+ this.logger.info(`Plugin initialized: ${pluginName}`);
189
+ } catch (error) {
190
+ this.logger.error(`Plugin init failed: ${pluginName}`, error);
191
+ throw error;
192
+ }
193
+ }
194
+ /**
195
+ * Run plugin start phase
196
+ * @param plugin - Plugin to start
197
+ */
198
+ async runPluginStart(plugin) {
199
+ if (!plugin.start) return;
200
+ const pluginName = plugin.name;
201
+ this.logger.info(`Starting plugin: ${pluginName}`);
202
+ try {
203
+ await plugin.start(this.context);
204
+ this.logger.info(`Plugin started: ${pluginName}`);
205
+ } catch (error) {
206
+ this.logger.error(`Plugin start failed: ${pluginName}`, error);
207
+ throw error;
208
+ }
209
+ }
210
+ /**
211
+ * Run plugin destroy phase
212
+ * @param plugin - Plugin to destroy
213
+ */
214
+ async runPluginDestroy(plugin) {
215
+ if (!plugin.destroy) return;
216
+ const pluginName = plugin.name;
217
+ this.logger.info(`Destroying plugin: ${pluginName}`);
218
+ try {
219
+ await plugin.destroy();
220
+ this.logger.info(`Plugin destroyed: ${pluginName}`);
221
+ } catch (error) {
222
+ this.logger.error(`Plugin destroy failed: ${pluginName}`, error);
223
+ throw error;
224
+ }
225
+ }
226
+ /**
227
+ * Trigger a hook with all registered handlers
228
+ * @param name - Hook name
229
+ * @param args - Arguments to pass to handlers
230
+ */
231
+ async triggerHook(name, ...args) {
232
+ const handlers = this.hooks.get(name) || [];
233
+ this.logger.debug(`Triggering hook: ${name}`, {
234
+ hook: name,
235
+ handlerCount: handlers.length
236
+ });
237
+ for (const handler of handlers) {
238
+ try {
239
+ await handler(...args);
240
+ } catch (error) {
241
+ this.logger.error(`Hook handler failed: ${name}`, error);
242
+ }
243
+ }
244
+ }
245
+ /**
246
+ * Get current kernel state
247
+ */
248
+ getState() {
249
+ return this.state;
250
+ }
251
+ /**
252
+ * Get all registered plugins
253
+ */
254
+ getPlugins() {
255
+ return new Map(this.plugins);
256
+ }
257
+ };
258
+
259
+ // src/utils/env.ts
260
+ var isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
261
+ function getEnv(key, defaultValue) {
262
+ if (typeof process !== "undefined" && process.env) {
263
+ return process.env[key] || defaultValue;
264
+ }
265
+ try {
266
+ if (typeof globalThis !== "undefined" && globalThis.process?.env) {
267
+ return globalThis.process.env[key] || defaultValue;
268
+ }
269
+ } catch (e) {
270
+ }
271
+ return defaultValue;
272
+ }
273
+ function safeExit(code = 0) {
274
+ if (isNode) {
275
+ process.exit(code);
276
+ }
277
+ }
278
+ function getMemoryUsage() {
279
+ if (isNode) {
280
+ return process.memoryUsage();
281
+ }
282
+ return { heapUsed: 0, heapTotal: 0 };
283
+ }
284
+
285
+ // src/logger.ts
286
+ var import_meta = {};
287
+ var ObjectLogger = class _ObjectLogger {
288
+ // CommonJS require function for Node.js
289
+ constructor(config = {}) {
290
+ this.isNode = isNode;
291
+ this.config = {
292
+ name: config.name,
293
+ level: config.level ?? "info",
294
+ format: config.format ?? (this.isNode ? "json" : "pretty"),
295
+ redact: config.redact ?? ["password", "token", "secret", "key"],
296
+ sourceLocation: config.sourceLocation ?? false,
297
+ file: config.file,
298
+ rotation: config.rotation ?? {
299
+ maxSize: "10m",
300
+ maxFiles: 5
301
+ }
302
+ };
303
+ if (this.isNode) {
304
+ this.initPinoLogger();
305
+ }
306
+ }
307
+ /**
308
+ * Initialize Pino logger for Node.js
309
+ */
310
+ async initPinoLogger() {
311
+ if (!this.isNode) return;
312
+ try {
313
+ const { createRequire } = await import("module");
314
+ this.require = createRequire(import_meta.url);
315
+ const pino = this.require("pino");
316
+ const pinoOptions = {
317
+ level: this.config.level,
318
+ redact: {
319
+ paths: this.config.redact,
320
+ censor: "***REDACTED***"
321
+ }
322
+ };
323
+ if (this.config.name) {
324
+ pinoOptions.name = this.config.name;
325
+ }
326
+ const targets = [];
327
+ if (this.config.format === "pretty") {
328
+ let hasPretty = false;
329
+ try {
330
+ this.require.resolve("pino-pretty");
331
+ hasPretty = true;
332
+ } catch (e) {
333
+ }
334
+ if (hasPretty) {
335
+ targets.push({
336
+ target: "pino-pretty",
337
+ options: {
338
+ colorize: true,
339
+ translateTime: "SYS:standard",
340
+ ignore: "pid,hostname"
341
+ },
342
+ level: this.config.level
343
+ });
344
+ } else {
345
+ console.warn("[Logger] pino-pretty not found. Install it for pretty logging: pnpm add -D pino-pretty");
346
+ targets.push({
347
+ target: "pino/file",
348
+ options: { destination: 1 },
349
+ level: this.config.level
350
+ });
351
+ }
352
+ } else if (this.config.format === "json") {
353
+ targets.push({
354
+ target: "pino/file",
355
+ options: { destination: 1 },
356
+ // stdout
357
+ level: this.config.level
358
+ });
359
+ } else {
360
+ targets.push({
361
+ target: "pino/file",
362
+ options: { destination: 1 },
363
+ level: this.config.level
364
+ });
365
+ }
366
+ if (this.config.file) {
367
+ targets.push({
368
+ target: "pino/file",
369
+ options: {
370
+ destination: this.config.file,
371
+ mkdir: true
372
+ },
373
+ level: this.config.level
374
+ });
375
+ }
376
+ if (targets.length > 0) {
377
+ pinoOptions.transport = targets.length === 1 ? targets[0] : { targets };
378
+ }
379
+ this.pinoInstance = pino(pinoOptions);
380
+ this.pinoLogger = this.pinoInstance;
381
+ } catch (error) {
382
+ console.warn("[Logger] Pino not available, falling back to console:", error);
383
+ this.pinoLogger = null;
384
+ }
385
+ }
386
+ /**
387
+ * Redact sensitive keys from context object (for browser)
388
+ */
389
+ redactSensitive(obj) {
390
+ if (!obj || typeof obj !== "object") return obj;
391
+ const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
392
+ for (const key in redacted) {
393
+ const lowerKey = key.toLowerCase();
394
+ const shouldRedact = this.config.redact.some(
395
+ (pattern) => lowerKey.includes(pattern.toLowerCase())
396
+ );
397
+ if (shouldRedact) {
398
+ redacted[key] = "***REDACTED***";
399
+ } else if (typeof redacted[key] === "object" && redacted[key] !== null) {
400
+ redacted[key] = this.redactSensitive(redacted[key]);
401
+ }
402
+ }
403
+ return redacted;
404
+ }
405
+ /**
406
+ * Format log entry for browser
407
+ */
408
+ formatBrowserLog(level, message, context) {
409
+ if (this.config.format === "json") {
410
+ return JSON.stringify({
411
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
412
+ level,
413
+ message,
414
+ ...context
415
+ });
416
+ }
417
+ if (this.config.format === "text") {
418
+ const parts = [(/* @__PURE__ */ new Date()).toISOString(), level.toUpperCase(), message];
419
+ if (context && Object.keys(context).length > 0) {
420
+ parts.push(JSON.stringify(context));
421
+ }
422
+ return parts.join(" | ");
423
+ }
424
+ const levelColors = {
425
+ debug: "\x1B[36m",
426
+ // Cyan
427
+ info: "\x1B[32m",
428
+ // Green
429
+ warn: "\x1B[33m",
430
+ // Yellow
431
+ error: "\x1B[31m",
432
+ // Red
433
+ fatal: "\x1B[35m",
434
+ // Magenta
435
+ silent: ""
436
+ };
437
+ const reset = "\x1B[0m";
438
+ const color = levelColors[level] || "";
439
+ let output = `${color}[${level.toUpperCase()}]${reset} ${message}`;
440
+ if (context && Object.keys(context).length > 0) {
441
+ output += ` ${JSON.stringify(context, null, 2)}`;
442
+ }
443
+ return output;
444
+ }
445
+ /**
446
+ * Log using browser console
447
+ */
448
+ logBrowser(level, message, context, error) {
449
+ const redactedContext = context ? this.redactSensitive(context) : void 0;
450
+ const mergedContext = error ? { ...redactedContext, error: { message: error.message, stack: error.stack } } : redactedContext;
451
+ const formatted = this.formatBrowserLog(level, message, mergedContext);
452
+ const consoleMethod = level === "debug" ? "debug" : level === "info" ? "log" : level === "warn" ? "warn" : level === "error" || level === "fatal" ? "error" : "log";
453
+ console[consoleMethod](formatted);
454
+ }
455
+ /**
456
+ * Public logging methods
457
+ */
458
+ debug(message, meta) {
459
+ if (this.isNode && this.pinoLogger) {
460
+ this.pinoLogger.debug(meta || {}, message);
461
+ } else {
462
+ this.logBrowser("debug", message, meta);
463
+ }
464
+ }
465
+ info(message, meta) {
466
+ if (this.isNode && this.pinoLogger) {
467
+ this.pinoLogger.info(meta || {}, message);
468
+ } else {
469
+ this.logBrowser("info", message, meta);
470
+ }
471
+ }
472
+ warn(message, meta) {
473
+ if (this.isNode && this.pinoLogger) {
474
+ this.pinoLogger.warn(meta || {}, message);
475
+ } else {
476
+ this.logBrowser("warn", message, meta);
477
+ }
478
+ }
479
+ error(message, errorOrMeta, meta) {
480
+ let error;
481
+ let context = {};
482
+ if (errorOrMeta instanceof Error) {
483
+ error = errorOrMeta;
484
+ context = meta || {};
485
+ } else {
486
+ context = errorOrMeta || {};
487
+ }
488
+ if (this.isNode && this.pinoLogger) {
489
+ const errorContext = error ? { err: error, ...context } : context;
490
+ this.pinoLogger.error(errorContext, message);
491
+ } else {
492
+ this.logBrowser("error", message, context, error);
493
+ }
494
+ }
495
+ fatal(message, errorOrMeta, meta) {
496
+ let error;
497
+ let context = {};
498
+ if (errorOrMeta instanceof Error) {
499
+ error = errorOrMeta;
500
+ context = meta || {};
501
+ } else {
502
+ context = errorOrMeta || {};
503
+ }
504
+ if (this.isNode && this.pinoLogger) {
505
+ const errorContext = error ? { err: error, ...context } : context;
506
+ this.pinoLogger.fatal(errorContext, message);
507
+ } else {
508
+ this.logBrowser("fatal", message, context, error);
509
+ }
510
+ }
511
+ /**
512
+ * Create a child logger with additional context
513
+ * Note: Child loggers share the parent's Pino instance
514
+ */
515
+ child(context) {
516
+ const childLogger = new _ObjectLogger(this.config);
517
+ if (this.isNode && this.pinoInstance) {
518
+ childLogger.pinoLogger = this.pinoInstance.child(context);
519
+ childLogger.pinoInstance = this.pinoInstance;
520
+ }
521
+ return childLogger;
522
+ }
523
+ /**
524
+ * Set trace context for distributed tracing
525
+ */
526
+ withTrace(traceId, spanId) {
527
+ return this.child({ traceId, spanId });
528
+ }
529
+ /**
530
+ * Cleanup resources
531
+ */
532
+ async destroy() {
533
+ if (this.pinoLogger && this.pinoLogger.flush) {
534
+ await new Promise((resolve) => {
535
+ this.pinoLogger.flush(() => resolve());
536
+ });
537
+ }
538
+ }
539
+ /**
540
+ * Compatibility method for console.log usage
541
+ */
542
+ log(message, ...args) {
543
+ this.info(message, args.length > 0 ? { args } : void 0);
544
+ }
545
+ };
546
+ function createLogger(config) {
547
+ return new ObjectLogger(config);
548
+ }
549
+
550
+ // src/kernel.ts
551
+ var import_system = require("@objectstack/spec/system");
552
+
553
+ // src/security/plugin-config-validator.ts
554
+ var import_zod = require("zod");
555
+ var PluginConfigValidator = class {
556
+ constructor(logger) {
557
+ this.logger = logger;
558
+ }
559
+ /**
560
+ * Validate plugin configuration against its Zod schema
561
+ *
562
+ * @param plugin - Plugin metadata with configSchema
563
+ * @param config - User-provided configuration
564
+ * @returns Validated and typed configuration
565
+ * @throws Error with detailed validation errors
566
+ */
567
+ validatePluginConfig(plugin, config) {
568
+ if (!plugin.configSchema) {
569
+ this.logger.debug(`Plugin ${plugin.name} has no config schema - skipping validation`);
570
+ return config;
571
+ }
572
+ try {
573
+ const validatedConfig = plugin.configSchema.parse(config);
574
+ this.logger.debug(`\u2705 Plugin config validated: ${plugin.name}`, {
575
+ plugin: plugin.name,
576
+ configKeys: Object.keys(config || {}).length
577
+ });
578
+ return validatedConfig;
579
+ } catch (error) {
580
+ if (error instanceof import_zod.z.ZodError) {
581
+ const formattedErrors = this.formatZodErrors(error);
582
+ const errorMessage = [
583
+ `Plugin ${plugin.name} configuration validation failed:`,
584
+ ...formattedErrors.map((e) => ` - ${e.path}: ${e.message}`)
585
+ ].join("\n");
586
+ this.logger.error(errorMessage, void 0, {
587
+ plugin: plugin.name,
588
+ errors: formattedErrors
589
+ });
590
+ throw new Error(errorMessage);
591
+ }
592
+ throw error;
593
+ }
594
+ }
595
+ /**
596
+ * Validate partial configuration (for incremental updates)
597
+ *
598
+ * @param plugin - Plugin metadata
599
+ * @param partialConfig - Partial configuration to validate
600
+ * @returns Validated partial configuration
601
+ */
602
+ validatePartialConfig(plugin, partialConfig) {
603
+ if (!plugin.configSchema) {
604
+ return partialConfig;
605
+ }
606
+ try {
607
+ const partialSchema = plugin.configSchema.partial();
608
+ const validatedConfig = partialSchema.parse(partialConfig);
609
+ this.logger.debug(`\u2705 Partial config validated: ${plugin.name}`);
610
+ return validatedConfig;
611
+ } catch (error) {
612
+ if (error instanceof import_zod.z.ZodError) {
613
+ const formattedErrors = this.formatZodErrors(error);
614
+ const errorMessage = [
615
+ `Plugin ${plugin.name} partial configuration validation failed:`,
616
+ ...formattedErrors.map((e) => ` - ${e.path}: ${e.message}`)
617
+ ].join("\n");
618
+ throw new Error(errorMessage);
619
+ }
620
+ throw error;
621
+ }
622
+ }
623
+ /**
624
+ * Get default configuration from schema
625
+ *
626
+ * @param plugin - Plugin metadata
627
+ * @returns Default configuration object
628
+ */
629
+ getDefaultConfig(plugin) {
630
+ if (!plugin.configSchema) {
631
+ return void 0;
632
+ }
633
+ try {
634
+ const defaults = plugin.configSchema.parse({});
635
+ this.logger.debug(`Default config extracted: ${plugin.name}`);
636
+ return defaults;
637
+ } catch (error) {
638
+ this.logger.debug(`No default config available: ${plugin.name}`);
639
+ return void 0;
640
+ }
641
+ }
642
+ /**
643
+ * Check if configuration is valid without throwing
644
+ *
645
+ * @param plugin - Plugin metadata
646
+ * @param config - Configuration to check
647
+ * @returns True if valid, false otherwise
648
+ */
649
+ isConfigValid(plugin, config) {
650
+ if (!plugin.configSchema) {
651
+ return true;
652
+ }
653
+ const result = plugin.configSchema.safeParse(config);
654
+ return result.success;
655
+ }
656
+ /**
657
+ * Get configuration errors without throwing
658
+ *
659
+ * @param plugin - Plugin metadata
660
+ * @param config - Configuration to check
661
+ * @returns Array of validation errors, or empty array if valid
662
+ */
663
+ getConfigErrors(plugin, config) {
664
+ if (!plugin.configSchema) {
665
+ return [];
666
+ }
667
+ const result = plugin.configSchema.safeParse(config);
668
+ if (result.success) {
669
+ return [];
670
+ }
671
+ return this.formatZodErrors(result.error);
672
+ }
673
+ // Private methods
674
+ formatZodErrors(error) {
675
+ return error.issues.map((e) => ({
676
+ path: e.path.join(".") || "root",
677
+ message: e.message
678
+ }));
679
+ }
680
+ };
681
+ function createPluginConfigValidator(logger) {
682
+ return new PluginConfigValidator(logger);
683
+ }
684
+
685
+ // src/plugin-loader.ts
686
+ var ServiceLifecycle = /* @__PURE__ */ ((ServiceLifecycle2) => {
687
+ ServiceLifecycle2["SINGLETON"] = "singleton";
688
+ ServiceLifecycle2["TRANSIENT"] = "transient";
689
+ ServiceLifecycle2["SCOPED"] = "scoped";
690
+ return ServiceLifecycle2;
691
+ })(ServiceLifecycle || {});
692
+ var PluginLoader = class {
693
+ constructor(logger) {
694
+ this.loadedPlugins = /* @__PURE__ */ new Map();
695
+ this.serviceFactories = /* @__PURE__ */ new Map();
696
+ this.serviceInstances = /* @__PURE__ */ new Map();
697
+ this.scopedServices = /* @__PURE__ */ new Map();
698
+ this.creating = /* @__PURE__ */ new Set();
699
+ this.logger = logger;
700
+ this.configValidator = new PluginConfigValidator(logger);
701
+ }
702
+ /**
703
+ * Set the plugin context for service factories
704
+ */
705
+ setContext(context) {
706
+ this.context = context;
707
+ }
708
+ /**
709
+ * Get a synchronous service instance if it exists (Sync Helper)
710
+ */
711
+ getServiceInstance(name) {
712
+ return this.serviceInstances.get(name);
713
+ }
714
+ /**
715
+ * Load a plugin asynchronously with validation
716
+ */
717
+ async loadPlugin(plugin) {
718
+ const startTime = Date.now();
719
+ try {
720
+ this.logger.info(`Loading plugin: ${plugin.name}`);
721
+ const metadata = this.toPluginMetadata(plugin);
722
+ this.validatePluginStructure(metadata);
723
+ const versionCheck = this.checkVersionCompatibility(metadata);
724
+ if (!versionCheck.compatible) {
725
+ throw new Error(`Version incompatible: ${versionCheck.message}`);
726
+ }
727
+ if (metadata.configSchema) {
728
+ this.validatePluginConfig(metadata);
729
+ }
730
+ if (metadata.signature) {
731
+ await this.verifyPluginSignature(metadata);
732
+ }
733
+ this.loadedPlugins.set(metadata.name, metadata);
734
+ const loadTime = Date.now() - startTime;
735
+ this.logger.info(`Plugin loaded: ${plugin.name} (${loadTime}ms)`);
736
+ return {
737
+ success: true,
738
+ plugin: metadata,
739
+ loadTime
740
+ };
741
+ } catch (error) {
742
+ this.logger.error(`Failed to load plugin: ${plugin.name}`, error);
743
+ return {
744
+ success: false,
745
+ error,
746
+ loadTime: Date.now() - startTime
747
+ };
748
+ }
749
+ }
750
+ /**
751
+ * Register a service with factory function
752
+ */
753
+ registerServiceFactory(registration) {
754
+ if (this.serviceFactories.has(registration.name)) {
755
+ throw new Error(`Service factory '${registration.name}' already registered`);
756
+ }
757
+ this.serviceFactories.set(registration.name, registration);
758
+ this.logger.debug(`Service factory registered: ${registration.name} (${registration.lifecycle})`);
759
+ }
760
+ /**
761
+ * Get or create a service instance based on lifecycle type
762
+ */
763
+ async getService(name, scopeId) {
764
+ const registration = this.serviceFactories.get(name);
765
+ if (!registration) {
766
+ const instance = this.serviceInstances.get(name);
767
+ if (!instance) {
768
+ throw new Error(`Service '${name}' not found`);
769
+ }
770
+ return instance;
771
+ }
772
+ switch (registration.lifecycle) {
773
+ case "singleton" /* SINGLETON */:
774
+ return await this.getSingletonService(registration);
775
+ case "transient" /* TRANSIENT */:
776
+ return await this.createTransientService(registration);
777
+ case "scoped" /* SCOPED */:
778
+ if (!scopeId) {
779
+ throw new Error(`Scope ID required for scoped service '${name}'`);
780
+ }
781
+ return await this.getScopedService(registration, scopeId);
782
+ default:
783
+ throw new Error(`Unknown service lifecycle: ${registration.lifecycle}`);
784
+ }
785
+ }
786
+ /**
787
+ * Register a static service instance (legacy support)
788
+ */
789
+ registerService(name, service) {
790
+ if (this.serviceInstances.has(name)) {
791
+ throw new Error(`Service '${name}' already registered`);
792
+ }
793
+ this.serviceInstances.set(name, service);
794
+ }
795
+ /**
796
+ * Check if a service is registered (either as instance or factory)
797
+ */
798
+ hasService(name) {
799
+ return this.serviceInstances.has(name) || this.serviceFactories.has(name);
800
+ }
801
+ /**
802
+ * Detect circular dependencies in service factories
803
+ * Note: This only detects cycles in service dependencies, not plugin dependencies.
804
+ * Plugin dependency cycles are detected in the kernel's resolveDependencies method.
805
+ */
806
+ detectCircularDependencies() {
807
+ const cycles = [];
808
+ const visited = /* @__PURE__ */ new Set();
809
+ const visiting = /* @__PURE__ */ new Set();
810
+ const visit = (serviceName, path = []) => {
811
+ if (visiting.has(serviceName)) {
812
+ const cycle = [...path, serviceName].join(" -> ");
813
+ cycles.push(cycle);
814
+ return;
815
+ }
816
+ if (visited.has(serviceName)) {
817
+ return;
818
+ }
819
+ visiting.add(serviceName);
820
+ const registration = this.serviceFactories.get(serviceName);
821
+ if (registration?.dependencies) {
822
+ for (const dep of registration.dependencies) {
823
+ visit(dep, [...path, serviceName]);
824
+ }
825
+ }
826
+ visiting.delete(serviceName);
827
+ visited.add(serviceName);
828
+ };
829
+ for (const serviceName of this.serviceFactories.keys()) {
830
+ visit(serviceName);
831
+ }
832
+ return cycles;
833
+ }
834
+ /**
835
+ * Check plugin health
836
+ */
837
+ async checkPluginHealth(pluginName) {
838
+ const plugin = this.loadedPlugins.get(pluginName);
839
+ if (!plugin) {
840
+ return {
841
+ healthy: false,
842
+ message: "Plugin not found",
843
+ lastCheck: /* @__PURE__ */ new Date()
844
+ };
845
+ }
846
+ if (!plugin.healthCheck) {
847
+ return {
848
+ healthy: true,
849
+ message: "No health check defined",
850
+ lastCheck: /* @__PURE__ */ new Date()
851
+ };
852
+ }
853
+ try {
854
+ const status = await plugin.healthCheck();
855
+ return {
856
+ ...status,
857
+ lastCheck: /* @__PURE__ */ new Date()
858
+ };
859
+ } catch (error) {
860
+ return {
861
+ healthy: false,
862
+ message: `Health check failed: ${error.message}`,
863
+ lastCheck: /* @__PURE__ */ new Date()
864
+ };
865
+ }
866
+ }
867
+ /**
868
+ * Clear scoped services for a scope
869
+ */
870
+ clearScope(scopeId) {
871
+ this.scopedServices.delete(scopeId);
872
+ this.logger.debug(`Cleared scope: ${scopeId}`);
873
+ }
874
+ /**
875
+ * Get all loaded plugins
876
+ */
877
+ getLoadedPlugins() {
878
+ return new Map(this.loadedPlugins);
879
+ }
880
+ // Private helper methods
881
+ toPluginMetadata(plugin) {
882
+ return {
883
+ ...plugin,
884
+ version: plugin.version || "0.0.0"
885
+ };
886
+ }
887
+ validatePluginStructure(plugin) {
888
+ if (!plugin.name) {
889
+ throw new Error("Plugin name is required");
890
+ }
891
+ if (!plugin.init) {
892
+ throw new Error("Plugin init function is required");
893
+ }
894
+ if (!this.isValidSemanticVersion(plugin.version)) {
895
+ throw new Error(`Invalid semantic version: ${plugin.version}`);
896
+ }
897
+ }
898
+ checkVersionCompatibility(plugin) {
899
+ const version = plugin.version;
900
+ if (!this.isValidSemanticVersion(version)) {
901
+ return {
902
+ compatible: false,
903
+ pluginVersion: version,
904
+ message: "Invalid semantic version format"
905
+ };
906
+ }
907
+ return {
908
+ compatible: true,
909
+ pluginVersion: version
910
+ };
911
+ }
912
+ isValidSemanticVersion(version) {
913
+ const semverRegex = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/;
914
+ return semverRegex.test(version);
915
+ }
916
+ validatePluginConfig(plugin, config) {
917
+ if (!plugin.configSchema) {
918
+ return;
919
+ }
920
+ if (config === void 0) {
921
+ this.logger.debug(`Plugin ${plugin.name} has configuration schema (config validation postponed)`);
922
+ return;
923
+ }
924
+ this.configValidator.validatePluginConfig(plugin, config);
925
+ }
926
+ async verifyPluginSignature(plugin) {
927
+ if (!plugin.signature) {
928
+ return;
929
+ }
930
+ this.logger.debug(`Plugin ${plugin.name} has signature (use PluginSignatureVerifier for verification)`);
931
+ }
932
+ async getSingletonService(registration) {
933
+ let instance = this.serviceInstances.get(registration.name);
934
+ if (!instance) {
935
+ instance = await this.createServiceInstance(registration);
936
+ this.serviceInstances.set(registration.name, instance);
937
+ this.logger.debug(`Singleton service created: ${registration.name}`);
938
+ }
939
+ return instance;
940
+ }
941
+ async createTransientService(registration) {
942
+ const instance = await this.createServiceInstance(registration);
943
+ this.logger.debug(`Transient service created: ${registration.name}`);
944
+ return instance;
945
+ }
946
+ async getScopedService(registration, scopeId) {
947
+ if (!this.scopedServices.has(scopeId)) {
948
+ this.scopedServices.set(scopeId, /* @__PURE__ */ new Map());
949
+ }
950
+ const scope = this.scopedServices.get(scopeId);
951
+ let instance = scope.get(registration.name);
952
+ if (!instance) {
953
+ instance = await this.createServiceInstance(registration);
954
+ scope.set(registration.name, instance);
955
+ this.logger.debug(`Scoped service created: ${registration.name} (scope: ${scopeId})`);
956
+ }
957
+ return instance;
958
+ }
959
+ async createServiceInstance(registration) {
960
+ if (!this.context) {
961
+ throw new Error(`[PluginLoader] Context not set - cannot create service '${registration.name}'`);
962
+ }
963
+ if (this.creating.has(registration.name)) {
964
+ throw new Error(`Circular dependency detected: ${Array.from(this.creating).join(" -> ")} -> ${registration.name}`);
965
+ }
966
+ this.creating.add(registration.name);
967
+ try {
968
+ return await registration.factory(this.context);
969
+ } finally {
970
+ this.creating.delete(registration.name);
971
+ }
972
+ }
973
+ };
974
+
975
+ // src/kernel.ts
976
+ var ObjectKernel = class {
977
+ constructor(config = {}) {
978
+ this.plugins = /* @__PURE__ */ new Map();
979
+ this.services = /* @__PURE__ */ new Map();
980
+ this.hooks = /* @__PURE__ */ new Map();
981
+ this.state = "idle";
982
+ this.startedPlugins = /* @__PURE__ */ new Set();
983
+ this.pluginStartTimes = /* @__PURE__ */ new Map();
984
+ this.shutdownHandlers = [];
985
+ this.config = {
986
+ defaultStartupTimeout: 3e4,
987
+ // 30 seconds
988
+ gracefulShutdown: true,
989
+ shutdownTimeout: 6e4,
990
+ // 60 seconds
991
+ rollbackOnFailure: true,
992
+ ...config
993
+ };
994
+ this.logger = createLogger(config.logger);
995
+ this.pluginLoader = new PluginLoader(this.logger);
996
+ this.context = {
997
+ registerService: (name, service) => {
998
+ this.registerService(name, service);
999
+ },
1000
+ getService: (name) => {
1001
+ const service = this.services.get(name);
1002
+ if (service) {
1003
+ return service;
1004
+ }
1005
+ const loaderService = this.pluginLoader.getServiceInstance(name);
1006
+ if (loaderService) {
1007
+ this.services.set(name, loaderService);
1008
+ return loaderService;
1009
+ }
1010
+ try {
1011
+ const service2 = this.pluginLoader.getService(name);
1012
+ if (service2 instanceof Promise) {
1013
+ throw new Error(`Service '${name}' is async - use await`);
1014
+ }
1015
+ return service2;
1016
+ } catch (error) {
1017
+ if (error.message?.includes("is async")) {
1018
+ throw error;
1019
+ }
1020
+ const isNotFoundError = error.message === `Service '${name}' not found`;
1021
+ if (!isNotFoundError) {
1022
+ throw error;
1023
+ }
1024
+ throw new Error(`[Kernel] Service '${name}' not found`);
1025
+ }
1026
+ },
1027
+ hook: (name, handler) => {
1028
+ if (!this.hooks.has(name)) {
1029
+ this.hooks.set(name, []);
1030
+ }
1031
+ this.hooks.get(name).push(handler);
1032
+ },
1033
+ trigger: async (name, ...args) => {
1034
+ const handlers = this.hooks.get(name) || [];
1035
+ for (const handler of handlers) {
1036
+ await handler(...args);
1037
+ }
1038
+ },
1039
+ getServices: () => {
1040
+ return new Map(this.services);
1041
+ },
1042
+ logger: this.logger,
1043
+ getKernel: () => this
1044
+ // Type compatibility
1045
+ };
1046
+ this.pluginLoader.setContext(this.context);
1047
+ if (this.config.gracefulShutdown) {
1048
+ this.registerShutdownSignals();
1049
+ }
1050
+ }
1051
+ /**
1052
+ * Register a plugin with enhanced validation
1053
+ */
1054
+ async use(plugin) {
1055
+ if (this.state !== "idle") {
1056
+ throw new Error("[Kernel] Cannot register plugins after bootstrap has started");
1057
+ }
1058
+ const result = await this.pluginLoader.loadPlugin(plugin);
1059
+ if (!result.success || !result.plugin) {
1060
+ throw new Error(`Failed to load plugin: ${plugin.name} - ${result.error?.message}`);
1061
+ }
1062
+ const pluginMeta = result.plugin;
1063
+ this.plugins.set(pluginMeta.name, pluginMeta);
1064
+ this.logger.info(`Plugin registered: ${pluginMeta.name}@${pluginMeta.version}`, {
1065
+ plugin: pluginMeta.name,
1066
+ version: pluginMeta.version
1067
+ });
1068
+ return this;
1069
+ }
1070
+ /**
1071
+ * Register a service instance directly
1072
+ */
1073
+ registerService(name, service) {
1074
+ if (this.services.has(name)) {
1075
+ throw new Error(`[Kernel] Service '${name}' already registered`);
1076
+ }
1077
+ this.services.set(name, service);
1078
+ this.pluginLoader.registerService(name, service);
1079
+ this.logger.info(`Service '${name}' registered`, { service: name });
1080
+ return this;
1081
+ }
1082
+ /**
1083
+ * Register a service factory with lifecycle management
1084
+ */
1085
+ registerServiceFactory(name, factory, lifecycle = "singleton" /* SINGLETON */, dependencies) {
1086
+ this.pluginLoader.registerServiceFactory({
1087
+ name,
1088
+ factory,
1089
+ lifecycle,
1090
+ dependencies
1091
+ });
1092
+ return this;
1093
+ }
1094
+ /**
1095
+ * Validate Critical System Requirements
1096
+ */
1097
+ validateSystemRequirements() {
1098
+ if (this.config.skipSystemValidation) {
1099
+ this.logger.debug("System requirement validation skipped");
1100
+ return;
1101
+ }
1102
+ this.logger.debug("Validating system service requirements...");
1103
+ const missingServices = [];
1104
+ const missingCoreServices = [];
1105
+ for (const [serviceName, criticality] of Object.entries(import_system.ServiceRequirementDef)) {
1106
+ const hasService = this.services.has(serviceName) || this.pluginLoader.hasService(serviceName);
1107
+ if (!hasService) {
1108
+ if (criticality === "required") {
1109
+ this.logger.error(`CRITICAL: Required service missing: ${serviceName}`);
1110
+ missingServices.push(serviceName);
1111
+ } else if (criticality === "core") {
1112
+ this.logger.warn(`CORE: Core service missing, functionality may be degraded: ${serviceName}`);
1113
+ missingCoreServices.push(serviceName);
1114
+ } else {
1115
+ this.logger.info(`Info: Optional service not present: ${serviceName}`);
1116
+ }
1117
+ }
1118
+ }
1119
+ if (missingServices.length > 0) {
1120
+ const errorMsg = `System failed to start. Missing critical services: ${missingServices.join(", ")}`;
1121
+ this.logger.error(errorMsg);
1122
+ throw new Error(errorMsg);
1123
+ }
1124
+ if (missingCoreServices.length > 0) {
1125
+ this.logger.warn(`System started with degraded capabilities. Missing core services: ${missingCoreServices.join(", ")}`);
1126
+ }
1127
+ this.logger.info("System requirement check passed");
1128
+ }
1129
+ /**
1130
+ * Bootstrap the kernel with enhanced features
1131
+ */
1132
+ async bootstrap() {
1133
+ if (this.state !== "idle") {
1134
+ throw new Error("[Kernel] Kernel already bootstrapped");
1135
+ }
1136
+ this.state = "initializing";
1137
+ this.logger.info("Bootstrap started");
1138
+ try {
1139
+ const cycles = this.pluginLoader.detectCircularDependencies();
1140
+ if (cycles.length > 0) {
1141
+ this.logger.warn("Circular service dependencies detected:", { cycles });
1142
+ }
1143
+ const orderedPlugins = this.resolveDependencies();
1144
+ this.logger.info("Phase 1: Init plugins");
1145
+ for (const plugin of orderedPlugins) {
1146
+ await this.initPluginWithTimeout(plugin);
1147
+ }
1148
+ this.logger.info("Phase 2: Start plugins");
1149
+ this.state = "running";
1150
+ for (const plugin of orderedPlugins) {
1151
+ const result = await this.startPluginWithTimeout(plugin);
1152
+ if (!result.success) {
1153
+ this.logger.error(`Plugin startup failed: ${plugin.name}`, result.error);
1154
+ if (this.config.rollbackOnFailure) {
1155
+ this.logger.warn("Rolling back started plugins...");
1156
+ await this.rollbackStartedPlugins();
1157
+ throw new Error(`Plugin ${plugin.name} failed to start - rollback complete`);
1158
+ }
1159
+ }
1160
+ }
1161
+ this.validateSystemRequirements();
1162
+ this.logger.debug("Triggering kernel:ready hook");
1163
+ await this.context.trigger("kernel:ready");
1164
+ this.logger.info("\u2705 Bootstrap complete");
1165
+ } catch (error) {
1166
+ this.state = "stopped";
1167
+ throw error;
1168
+ }
1169
+ }
1170
+ /**
1171
+ * Graceful shutdown with timeout
1172
+ */
1173
+ async shutdown() {
1174
+ if (this.state === "stopped" || this.state === "stopping") {
1175
+ this.logger.warn("Kernel already stopped or stopping");
1176
+ return;
1177
+ }
1178
+ if (this.state !== "running") {
1179
+ throw new Error("[Kernel] Kernel not running");
1180
+ }
1181
+ this.state = "stopping";
1182
+ this.logger.info("Graceful shutdown started");
1183
+ try {
1184
+ const shutdownPromise = this.performShutdown();
1185
+ const timeoutPromise = new Promise((_, reject) => {
1186
+ setTimeout(() => {
1187
+ reject(new Error("Shutdown timeout exceeded"));
1188
+ }, this.config.shutdownTimeout);
1189
+ });
1190
+ await Promise.race([shutdownPromise, timeoutPromise]);
1191
+ this.state = "stopped";
1192
+ this.logger.info("\u2705 Graceful shutdown complete");
1193
+ } catch (error) {
1194
+ this.logger.error("Shutdown error - forcing stop", error);
1195
+ this.state = "stopped";
1196
+ throw error;
1197
+ } finally {
1198
+ await this.logger.destroy();
1199
+ }
1200
+ }
1201
+ /**
1202
+ * Check health of a specific plugin
1203
+ */
1204
+ async checkPluginHealth(pluginName) {
1205
+ return await this.pluginLoader.checkPluginHealth(pluginName);
1206
+ }
1207
+ /**
1208
+ * Check health of all plugins
1209
+ */
1210
+ async checkAllPluginsHealth() {
1211
+ const results = /* @__PURE__ */ new Map();
1212
+ for (const pluginName of this.plugins.keys()) {
1213
+ const health = await this.checkPluginHealth(pluginName);
1214
+ results.set(pluginName, health);
1215
+ }
1216
+ return results;
1217
+ }
1218
+ /**
1219
+ * Get plugin startup metrics
1220
+ */
1221
+ getPluginMetrics() {
1222
+ return new Map(this.pluginStartTimes);
1223
+ }
1224
+ /**
1225
+ * Get a service (sync helper)
1226
+ */
1227
+ getService(name) {
1228
+ return this.context.getService(name);
1229
+ }
1230
+ /**
1231
+ * Get a service asynchronously (supports factories)
1232
+ */
1233
+ async getServiceAsync(name, scopeId) {
1234
+ return await this.pluginLoader.getService(name, scopeId);
1235
+ }
1236
+ /**
1237
+ * Check if kernel is running
1238
+ */
1239
+ isRunning() {
1240
+ return this.state === "running";
1241
+ }
1242
+ /**
1243
+ * Get kernel state
1244
+ */
1245
+ getState() {
1246
+ return this.state;
1247
+ }
1248
+ // Private methods
1249
+ async initPluginWithTimeout(plugin) {
1250
+ const timeout = plugin.startupTimeout || this.config.defaultStartupTimeout;
1251
+ this.logger.debug(`Init: ${plugin.name}`, { plugin: plugin.name });
1252
+ const initPromise = plugin.init(this.context);
1253
+ const timeoutPromise = new Promise((_, reject) => {
1254
+ setTimeout(() => {
1255
+ reject(new Error(`Plugin ${plugin.name} init timeout after ${timeout}ms`));
1256
+ }, timeout);
1257
+ });
1258
+ await Promise.race([initPromise, timeoutPromise]);
1259
+ }
1260
+ async startPluginWithTimeout(plugin) {
1261
+ if (!plugin.start) {
1262
+ return { success: true, pluginName: plugin.name };
1263
+ }
1264
+ const timeout = plugin.startupTimeout || this.config.defaultStartupTimeout;
1265
+ const startTime = Date.now();
1266
+ this.logger.debug(`Start: ${plugin.name}`, { plugin: plugin.name });
1267
+ try {
1268
+ const startPromise = plugin.start(this.context);
1269
+ const timeoutPromise = new Promise((_, reject) => {
1270
+ setTimeout(() => {
1271
+ reject(new Error(`Plugin ${plugin.name} start timeout after ${timeout}ms`));
1272
+ }, timeout);
1273
+ });
1274
+ await Promise.race([startPromise, timeoutPromise]);
1275
+ const duration = Date.now() - startTime;
1276
+ this.startedPlugins.add(plugin.name);
1277
+ this.pluginStartTimes.set(plugin.name, duration);
1278
+ this.logger.debug(`Plugin started: ${plugin.name} (${duration}ms)`);
1279
+ return {
1280
+ success: true,
1281
+ pluginName: plugin.name,
1282
+ startTime: duration
1283
+ };
1284
+ } catch (error) {
1285
+ const duration = Date.now() - startTime;
1286
+ const isTimeout = error.message.includes("timeout");
1287
+ return {
1288
+ success: false,
1289
+ pluginName: plugin.name,
1290
+ error,
1291
+ startTime: duration,
1292
+ timedOut: isTimeout
1293
+ };
1294
+ }
1295
+ }
1296
+ async rollbackStartedPlugins() {
1297
+ const pluginsToRollback = Array.from(this.startedPlugins).reverse();
1298
+ for (const pluginName of pluginsToRollback) {
1299
+ const plugin = this.plugins.get(pluginName);
1300
+ if (plugin?.destroy) {
1301
+ try {
1302
+ this.logger.debug(`Rollback: ${pluginName}`);
1303
+ await plugin.destroy();
1304
+ } catch (error) {
1305
+ this.logger.error(`Rollback failed for ${pluginName}`, error);
1306
+ }
1307
+ }
1308
+ }
1309
+ this.startedPlugins.clear();
1310
+ }
1311
+ async performShutdown() {
1312
+ await this.context.trigger("kernel:shutdown");
1313
+ const orderedPlugins = Array.from(this.plugins.values()).reverse();
1314
+ for (const plugin of orderedPlugins) {
1315
+ if (plugin.destroy) {
1316
+ this.logger.debug(`Destroy: ${plugin.name}`, { plugin: plugin.name });
1317
+ try {
1318
+ await plugin.destroy();
1319
+ } catch (error) {
1320
+ this.logger.error(`Error destroying plugin ${plugin.name}`, error);
1321
+ }
1322
+ }
1323
+ }
1324
+ for (const handler of this.shutdownHandlers) {
1325
+ try {
1326
+ await handler();
1327
+ } catch (error) {
1328
+ this.logger.error("Shutdown handler error", error);
1329
+ }
1330
+ }
1331
+ }
1332
+ resolveDependencies() {
1333
+ const resolved = [];
1334
+ const visited = /* @__PURE__ */ new Set();
1335
+ const visiting = /* @__PURE__ */ new Set();
1336
+ const visit = (pluginName) => {
1337
+ if (visited.has(pluginName)) return;
1338
+ if (visiting.has(pluginName)) {
1339
+ throw new Error(`[Kernel] Circular dependency detected: ${pluginName}`);
1340
+ }
1341
+ const plugin = this.plugins.get(pluginName);
1342
+ if (!plugin) {
1343
+ throw new Error(`[Kernel] Plugin '${pluginName}' not found`);
1344
+ }
1345
+ visiting.add(pluginName);
1346
+ const deps = plugin.dependencies || [];
1347
+ for (const dep of deps) {
1348
+ if (!this.plugins.has(dep)) {
1349
+ throw new Error(`[Kernel] Dependency '${dep}' not found for plugin '${pluginName}'`);
1350
+ }
1351
+ visit(dep);
1352
+ }
1353
+ visiting.delete(pluginName);
1354
+ visited.add(pluginName);
1355
+ resolved.push(plugin);
1356
+ };
1357
+ for (const pluginName of this.plugins.keys()) {
1358
+ visit(pluginName);
1359
+ }
1360
+ return resolved;
1361
+ }
1362
+ registerShutdownSignals() {
1363
+ const signals = ["SIGINT", "SIGTERM", "SIGQUIT"];
1364
+ let shutdownInProgress = false;
1365
+ const handleShutdown = async (signal) => {
1366
+ if (shutdownInProgress) {
1367
+ this.logger.warn(`Shutdown already in progress, ignoring ${signal}`);
1368
+ return;
1369
+ }
1370
+ shutdownInProgress = true;
1371
+ this.logger.info(`Received ${signal} - initiating graceful shutdown`);
1372
+ try {
1373
+ await this.shutdown();
1374
+ safeExit(0);
1375
+ } catch (error) {
1376
+ this.logger.error("Shutdown failed", error);
1377
+ safeExit(1);
1378
+ }
1379
+ };
1380
+ if (isNode) {
1381
+ for (const signal of signals) {
1382
+ process.on(signal, () => handleShutdown(signal));
1383
+ }
1384
+ }
1385
+ }
1386
+ /**
1387
+ * Register a custom shutdown handler
1388
+ */
1389
+ onShutdown(handler) {
1390
+ this.shutdownHandlers.push(handler);
1391
+ }
1392
+ };
1393
+
1394
+ // src/lite-kernel.ts
1395
+ var LiteKernel = class extends ObjectKernelBase {
1396
+ constructor(config) {
1397
+ const logger = createLogger(config?.logger);
1398
+ super(logger);
1399
+ this.context = this.createContext();
1400
+ }
1401
+ /**
1402
+ * Register a plugin
1403
+ * @param plugin - Plugin instance
1404
+ */
1405
+ use(plugin) {
1406
+ this.validateIdle();
1407
+ const pluginName = plugin.name;
1408
+ if (this.plugins.has(pluginName)) {
1409
+ throw new Error(`[Kernel] Plugin '${pluginName}' already registered`);
1410
+ }
1411
+ this.plugins.set(pluginName, plugin);
1412
+ return this;
1413
+ }
1414
+ /**
1415
+ * Bootstrap the kernel
1416
+ * 1. Resolve dependencies (topological sort)
1417
+ * 2. Init phase - plugins register services
1418
+ * 3. Start phase - plugins execute business logic
1419
+ * 4. Trigger 'kernel:ready' hook
1420
+ */
1421
+ async bootstrap() {
1422
+ this.validateState("idle");
1423
+ this.state = "initializing";
1424
+ this.logger.info("Bootstrap started");
1425
+ const orderedPlugins = this.resolveDependencies();
1426
+ this.logger.info("Phase 1: Init plugins");
1427
+ for (const plugin of orderedPlugins) {
1428
+ await this.runPluginInit(plugin);
1429
+ }
1430
+ this.logger.info("Phase 2: Start plugins");
1431
+ this.state = "running";
1432
+ for (const plugin of orderedPlugins) {
1433
+ await this.runPluginStart(plugin);
1434
+ }
1435
+ await this.triggerHook("kernel:ready");
1436
+ this.logger.info("\u2705 Bootstrap complete", {
1437
+ pluginCount: this.plugins.size
1438
+ });
1439
+ }
1440
+ /**
1441
+ * Shutdown the kernel
1442
+ * Calls destroy on all plugins in reverse order
1443
+ */
1444
+ async shutdown() {
1445
+ await this.destroy();
1446
+ }
1447
+ /**
1448
+ * Graceful shutdown - destroy all plugins in reverse order
1449
+ */
1450
+ async destroy() {
1451
+ if (this.state === "stopped") {
1452
+ this.logger.warn("Kernel already stopped");
1453
+ return;
1454
+ }
1455
+ this.state = "stopping";
1456
+ this.logger.info("Shutdown started");
1457
+ await this.triggerHook("kernel:shutdown");
1458
+ const orderedPlugins = this.resolveDependencies();
1459
+ for (const plugin of orderedPlugins.reverse()) {
1460
+ await this.runPluginDestroy(plugin);
1461
+ }
1462
+ this.state = "stopped";
1463
+ this.logger.info("\u2705 Shutdown complete");
1464
+ if (this.logger && typeof this.logger.destroy === "function") {
1465
+ await this.logger.destroy();
1466
+ }
1467
+ }
1468
+ /**
1469
+ * Get a service from the registry
1470
+ * Convenience method for external access
1471
+ */
1472
+ getService(name) {
1473
+ return this.context.getService(name);
1474
+ }
1475
+ /**
1476
+ * Check if kernel is running
1477
+ */
1478
+ isRunning() {
1479
+ return this.state === "running";
1480
+ }
1481
+ };
1482
+
1483
+ // src/api-registry.ts
1484
+ var import_api = require("@objectstack/spec/api");
1485
+ var ApiRegistry = class {
1486
+ constructor(logger, conflictResolution = "error", version = "1.0.0") {
1487
+ this.apis = /* @__PURE__ */ new Map();
1488
+ this.endpoints = /* @__PURE__ */ new Map();
1489
+ this.routes = /* @__PURE__ */ new Map();
1490
+ // Performance optimization: Auxiliary indices for O(1) lookups
1491
+ this.apisByType = /* @__PURE__ */ new Map();
1492
+ this.apisByTag = /* @__PURE__ */ new Map();
1493
+ this.apisByStatus = /* @__PURE__ */ new Map();
1494
+ this.logger = logger;
1495
+ this.conflictResolution = conflictResolution;
1496
+ this.version = version;
1497
+ this.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1498
+ }
1499
+ /**
1500
+ * Register an API with its endpoints
1501
+ *
1502
+ * @param api - API registry entry
1503
+ * @throws Error if API already registered or route conflicts detected
1504
+ */
1505
+ registerApi(api) {
1506
+ if (this.apis.has(api.id)) {
1507
+ throw new Error(`[ApiRegistry] API '${api.id}' already registered`);
1508
+ }
1509
+ const fullApi = import_api.ApiRegistryEntrySchema.parse(api);
1510
+ for (const endpoint of fullApi.endpoints) {
1511
+ this.validateEndpoint(endpoint, fullApi.id);
1512
+ }
1513
+ this.apis.set(fullApi.id, fullApi);
1514
+ for (const endpoint of fullApi.endpoints) {
1515
+ this.registerEndpoint(fullApi.id, endpoint);
1516
+ }
1517
+ this.updateIndices(fullApi);
1518
+ this.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1519
+ this.logger.info(`API registered: ${fullApi.id}`, {
1520
+ api: fullApi.id,
1521
+ type: fullApi.type,
1522
+ endpointCount: fullApi.endpoints.length
1523
+ });
1524
+ }
1525
+ /**
1526
+ * Unregister an API and all its endpoints
1527
+ *
1528
+ * @param apiId - API identifier
1529
+ */
1530
+ unregisterApi(apiId) {
1531
+ const api = this.apis.get(apiId);
1532
+ if (!api) {
1533
+ throw new Error(`[ApiRegistry] API '${apiId}' not found`);
1534
+ }
1535
+ for (const endpoint of api.endpoints) {
1536
+ this.unregisterEndpoint(apiId, endpoint.id);
1537
+ }
1538
+ this.removeFromIndices(api);
1539
+ this.apis.delete(apiId);
1540
+ this.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1541
+ this.logger.info(`API unregistered: ${apiId}`);
1542
+ }
1543
+ /**
1544
+ * Register a single endpoint
1545
+ *
1546
+ * @param apiId - API identifier
1547
+ * @param endpoint - Endpoint registration
1548
+ * @throws Error if route conflict detected
1549
+ */
1550
+ registerEndpoint(apiId, endpoint) {
1551
+ const endpointKey = `${apiId}:${endpoint.id}`;
1552
+ if (this.endpoints.has(endpointKey)) {
1553
+ throw new Error(`[ApiRegistry] Endpoint '${endpoint.id}' already registered for API '${apiId}'`);
1554
+ }
1555
+ this.endpoints.set(endpointKey, { api: apiId, endpoint });
1556
+ if (endpoint.path) {
1557
+ this.registerRoute(apiId, endpoint);
1558
+ }
1559
+ }
1560
+ /**
1561
+ * Unregister a single endpoint
1562
+ *
1563
+ * @param apiId - API identifier
1564
+ * @param endpointId - Endpoint identifier
1565
+ */
1566
+ unregisterEndpoint(apiId, endpointId) {
1567
+ const endpointKey = `${apiId}:${endpointId}`;
1568
+ const entry = this.endpoints.get(endpointKey);
1569
+ if (!entry) {
1570
+ return;
1571
+ }
1572
+ if (entry.endpoint.path) {
1573
+ const routeKey = this.getRouteKey(entry.endpoint);
1574
+ this.routes.delete(routeKey);
1575
+ }
1576
+ this.endpoints.delete(endpointKey);
1577
+ }
1578
+ /**
1579
+ * Register a route with conflict detection
1580
+ *
1581
+ * @param apiId - API identifier
1582
+ * @param endpoint - Endpoint registration
1583
+ * @throws Error if route conflict detected (based on strategy)
1584
+ */
1585
+ registerRoute(apiId, endpoint) {
1586
+ const routeKey = this.getRouteKey(endpoint);
1587
+ const priority = endpoint.priority ?? 100;
1588
+ const existingRoute = this.routes.get(routeKey);
1589
+ if (existingRoute) {
1590
+ this.handleRouteConflict(routeKey, apiId, endpoint, existingRoute, priority);
1591
+ return;
1592
+ }
1593
+ this.routes.set(routeKey, {
1594
+ api: apiId,
1595
+ endpointId: endpoint.id,
1596
+ priority
1597
+ });
1598
+ }
1599
+ /**
1600
+ * Handle route conflict based on resolution strategy
1601
+ *
1602
+ * @param routeKey - Route key
1603
+ * @param apiId - New API identifier
1604
+ * @param endpoint - New endpoint
1605
+ * @param existingRoute - Existing route registration
1606
+ * @param newPriority - New endpoint priority
1607
+ * @throws Error if strategy is 'error'
1608
+ */
1609
+ handleRouteConflict(routeKey, apiId, endpoint, existingRoute, newPriority) {
1610
+ const strategy = this.conflictResolution;
1611
+ switch (strategy) {
1612
+ case "error":
1613
+ throw new Error(
1614
+ `[ApiRegistry] Route conflict detected: '${routeKey}' is already registered by API '${existingRoute.api}' endpoint '${existingRoute.endpointId}'`
1615
+ );
1616
+ case "priority":
1617
+ if (newPriority > existingRoute.priority) {
1618
+ this.logger.warn(
1619
+ `Route conflict: replacing '${routeKey}' (priority ${existingRoute.priority} -> ${newPriority})`,
1620
+ {
1621
+ oldApi: existingRoute.api,
1622
+ oldEndpoint: existingRoute.endpointId,
1623
+ newApi: apiId,
1624
+ newEndpoint: endpoint.id
1625
+ }
1626
+ );
1627
+ this.routes.set(routeKey, {
1628
+ api: apiId,
1629
+ endpointId: endpoint.id,
1630
+ priority: newPriority
1631
+ });
1632
+ } else {
1633
+ this.logger.warn(
1634
+ `Route conflict: keeping existing '${routeKey}' (priority ${existingRoute.priority} >= ${newPriority})`,
1635
+ {
1636
+ existingApi: existingRoute.api,
1637
+ existingEndpoint: existingRoute.endpointId,
1638
+ newApi: apiId,
1639
+ newEndpoint: endpoint.id
1640
+ }
1641
+ );
1642
+ }
1643
+ break;
1644
+ case "first-wins":
1645
+ this.logger.warn(
1646
+ `Route conflict: keeping first registered '${routeKey}'`,
1647
+ {
1648
+ existingApi: existingRoute.api,
1649
+ newApi: apiId
1650
+ }
1651
+ );
1652
+ break;
1653
+ case "last-wins":
1654
+ this.logger.warn(
1655
+ `Route conflict: replacing with last registered '${routeKey}'`,
1656
+ {
1657
+ oldApi: existingRoute.api,
1658
+ newApi: apiId
1659
+ }
1660
+ );
1661
+ this.routes.set(routeKey, {
1662
+ api: apiId,
1663
+ endpointId: endpoint.id,
1664
+ priority: newPriority
1665
+ });
1666
+ break;
1667
+ default:
1668
+ throw new Error(`[ApiRegistry] Unknown conflict resolution strategy: ${strategy}`);
1669
+ }
1670
+ }
1671
+ /**
1672
+ * Generate a unique route key for conflict detection
1673
+ *
1674
+ * NOTE: This implementation uses exact string matching for route conflict detection.
1675
+ * It works well for static paths but has limitations with parameterized routes.
1676
+ * For example, `/api/users/:id` and `/api/users/:userId` will NOT be detected as conflicts
1677
+ * even though they are semantically identical parameterized patterns. Similarly,
1678
+ * `/api/:resource/list` and `/api/:entity/list` would also not be detected as conflicting.
1679
+ *
1680
+ * For more advanced conflict detection (e.g., path-to-regexp pattern matching),
1681
+ * consider integrating with your routing library's conflict detection mechanism.
1682
+ *
1683
+ * @param endpoint - Endpoint registration
1684
+ * @returns Route key (e.g., "GET:/api/v1/customers/:id")
1685
+ */
1686
+ getRouteKey(endpoint) {
1687
+ const method = endpoint.method || "ANY";
1688
+ return `${method}:${endpoint.path}`;
1689
+ }
1690
+ /**
1691
+ * Validate endpoint registration
1692
+ *
1693
+ * @param endpoint - Endpoint to validate
1694
+ * @param apiId - API identifier (for error messages)
1695
+ * @throws Error if endpoint is invalid
1696
+ */
1697
+ validateEndpoint(endpoint, apiId) {
1698
+ if (!endpoint.id) {
1699
+ throw new Error(`[ApiRegistry] Endpoint in API '${apiId}' missing 'id' field`);
1700
+ }
1701
+ if (!endpoint.path) {
1702
+ throw new Error(`[ApiRegistry] Endpoint '${endpoint.id}' in API '${apiId}' missing 'path' field`);
1703
+ }
1704
+ }
1705
+ /**
1706
+ * Get an API by ID
1707
+ *
1708
+ * @param apiId - API identifier
1709
+ * @returns API registry entry or undefined
1710
+ */
1711
+ getApi(apiId) {
1712
+ return this.apis.get(apiId);
1713
+ }
1714
+ /**
1715
+ * Get all registered APIs
1716
+ *
1717
+ * @returns Array of all APIs
1718
+ */
1719
+ getAllApis() {
1720
+ return Array.from(this.apis.values());
1721
+ }
1722
+ /**
1723
+ * Find APIs matching query criteria
1724
+ *
1725
+ * Performance optimized with auxiliary indices for O(1) lookups on type, tags, and status.
1726
+ *
1727
+ * @param query - Discovery query parameters
1728
+ * @returns Matching APIs
1729
+ */
1730
+ findApis(query) {
1731
+ let resultIds;
1732
+ if (query.type) {
1733
+ const typeIds = this.apisByType.get(query.type);
1734
+ if (!typeIds || typeIds.size === 0) {
1735
+ return { apis: [], total: 0, filters: query };
1736
+ }
1737
+ resultIds = new Set(typeIds);
1738
+ }
1739
+ if (query.status) {
1740
+ const statusIds = this.apisByStatus.get(query.status);
1741
+ if (!statusIds || statusIds.size === 0) {
1742
+ return { apis: [], total: 0, filters: query };
1743
+ }
1744
+ if (resultIds) {
1745
+ resultIds = new Set([...resultIds].filter((id) => statusIds.has(id)));
1746
+ } else {
1747
+ resultIds = new Set(statusIds);
1748
+ }
1749
+ if (resultIds.size === 0) {
1750
+ return { apis: [], total: 0, filters: query };
1751
+ }
1752
+ }
1753
+ if (query.tags && query.tags.length > 0) {
1754
+ const tagMatches = /* @__PURE__ */ new Set();
1755
+ for (const tag of query.tags) {
1756
+ const tagIds = this.apisByTag.get(tag);
1757
+ if (tagIds) {
1758
+ tagIds.forEach((id) => tagMatches.add(id));
1759
+ }
1760
+ }
1761
+ if (tagMatches.size === 0) {
1762
+ return { apis: [], total: 0, filters: query };
1763
+ }
1764
+ if (resultIds) {
1765
+ resultIds = new Set([...resultIds].filter((id) => tagMatches.has(id)));
1766
+ } else {
1767
+ resultIds = tagMatches;
1768
+ }
1769
+ if (resultIds.size === 0) {
1770
+ return { apis: [], total: 0, filters: query };
1771
+ }
1772
+ }
1773
+ let results;
1774
+ if (resultIds) {
1775
+ results = Array.from(resultIds).map((id) => this.apis.get(id)).filter((api) => api !== void 0);
1776
+ } else {
1777
+ results = Array.from(this.apis.values());
1778
+ }
1779
+ if (query.pluginSource) {
1780
+ results = results.filter(
1781
+ (api) => api.metadata?.pluginSource === query.pluginSource
1782
+ );
1783
+ }
1784
+ if (query.version) {
1785
+ results = results.filter((api) => api.version === query.version);
1786
+ }
1787
+ if (query.search) {
1788
+ const searchLower = query.search.toLowerCase();
1789
+ results = results.filter(
1790
+ (api) => api.name.toLowerCase().includes(searchLower) || api.description && api.description.toLowerCase().includes(searchLower)
1791
+ );
1792
+ }
1793
+ return {
1794
+ apis: results,
1795
+ total: results.length,
1796
+ filters: query
1797
+ };
1798
+ }
1799
+ /**
1800
+ * Get endpoint by API ID and endpoint ID
1801
+ *
1802
+ * @param apiId - API identifier
1803
+ * @param endpointId - Endpoint identifier
1804
+ * @returns Endpoint registration or undefined
1805
+ */
1806
+ getEndpoint(apiId, endpointId) {
1807
+ const key = `${apiId}:${endpointId}`;
1808
+ return this.endpoints.get(key)?.endpoint;
1809
+ }
1810
+ /**
1811
+ * Find endpoint by route (method + path)
1812
+ *
1813
+ * @param method - HTTP method
1814
+ * @param path - URL path
1815
+ * @returns Endpoint registration or undefined
1816
+ */
1817
+ findEndpointByRoute(method, path) {
1818
+ const routeKey = `${method}:${path}`;
1819
+ const route = this.routes.get(routeKey);
1820
+ if (!route) {
1821
+ return void 0;
1822
+ }
1823
+ const api = this.apis.get(route.api);
1824
+ const endpoint = this.getEndpoint(route.api, route.endpointId);
1825
+ if (!api || !endpoint) {
1826
+ return void 0;
1827
+ }
1828
+ return { api, endpoint };
1829
+ }
1830
+ /**
1831
+ * Get complete registry snapshot
1832
+ *
1833
+ * @returns Current registry state
1834
+ */
1835
+ getRegistry() {
1836
+ const apis = Array.from(this.apis.values());
1837
+ const byType = {};
1838
+ for (const api of apis) {
1839
+ if (!byType[api.type]) {
1840
+ byType[api.type] = [];
1841
+ }
1842
+ byType[api.type].push(api);
1843
+ }
1844
+ const byStatus = {};
1845
+ for (const api of apis) {
1846
+ const status = api.metadata?.status || "active";
1847
+ if (!byStatus[status]) {
1848
+ byStatus[status] = [];
1849
+ }
1850
+ byStatus[status].push(api);
1851
+ }
1852
+ const totalEndpoints = apis.reduce(
1853
+ (sum, api) => sum + api.endpoints.length,
1854
+ 0
1855
+ );
1856
+ return {
1857
+ version: this.version,
1858
+ conflictResolution: this.conflictResolution,
1859
+ apis,
1860
+ totalApis: apis.length,
1861
+ totalEndpoints,
1862
+ byType,
1863
+ byStatus,
1864
+ updatedAt: this.updatedAt
1865
+ };
1866
+ }
1867
+ /**
1868
+ * Clear all registered APIs
1869
+ *
1870
+ * **⚠️ SAFETY WARNING:**
1871
+ * This method clears all registered APIs and should be used with caution.
1872
+ *
1873
+ * **Usage Restrictions:**
1874
+ * - In production environments (NODE_ENV=production), a `force: true` parameter is required
1875
+ * - Primarily intended for testing and development hot-reload scenarios
1876
+ *
1877
+ * @param options - Clear options
1878
+ * @param options.force - Force clear in production environment (default: false)
1879
+ * @throws Error if called in production without force flag
1880
+ *
1881
+ * @example Safe usage in tests
1882
+ * ```typescript
1883
+ * beforeEach(() => {
1884
+ * registry.clear(); // OK in test environment
1885
+ * });
1886
+ * ```
1887
+ *
1888
+ * @example Usage in production (requires explicit force)
1889
+ * ```typescript
1890
+ * // In production, explicit force is required
1891
+ * registry.clear({ force: true });
1892
+ * ```
1893
+ */
1894
+ clear(options = {}) {
1895
+ const isProduction = this.isProductionEnvironment();
1896
+ if (isProduction && !options.force) {
1897
+ throw new Error(
1898
+ "[ApiRegistry] Cannot clear registry in production environment without force flag. Use clear({ force: true }) if you really want to clear the registry."
1899
+ );
1900
+ }
1901
+ this.apis.clear();
1902
+ this.endpoints.clear();
1903
+ this.routes.clear();
1904
+ this.apisByType.clear();
1905
+ this.apisByTag.clear();
1906
+ this.apisByStatus.clear();
1907
+ this.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1908
+ if (isProduction) {
1909
+ this.logger.warn("API registry forcefully cleared in production", { force: options.force });
1910
+ } else {
1911
+ this.logger.info("API registry cleared");
1912
+ }
1913
+ }
1914
+ /**
1915
+ * Get registry statistics
1916
+ *
1917
+ * @returns Registry statistics
1918
+ */
1919
+ getStats() {
1920
+ const apis = Array.from(this.apis.values());
1921
+ const apisByType = {};
1922
+ for (const api of apis) {
1923
+ apisByType[api.type] = (apisByType[api.type] || 0) + 1;
1924
+ }
1925
+ const endpointsByApi = {};
1926
+ for (const api of apis) {
1927
+ endpointsByApi[api.id] = api.endpoints.length;
1928
+ }
1929
+ return {
1930
+ totalApis: this.apis.size,
1931
+ totalEndpoints: this.endpoints.size,
1932
+ totalRoutes: this.routes.size,
1933
+ apisByType,
1934
+ endpointsByApi
1935
+ };
1936
+ }
1937
+ /**
1938
+ * Update auxiliary indices when an API is registered
1939
+ *
1940
+ * @param api - API entry to index
1941
+ * @private
1942
+ * @internal
1943
+ */
1944
+ updateIndices(api) {
1945
+ this.ensureIndexSet(this.apisByType, api.type).add(api.id);
1946
+ const status = api.metadata?.status || "active";
1947
+ this.ensureIndexSet(this.apisByStatus, status).add(api.id);
1948
+ const tags = api.metadata?.tags || [];
1949
+ for (const tag of tags) {
1950
+ this.ensureIndexSet(this.apisByTag, tag).add(api.id);
1951
+ }
1952
+ }
1953
+ /**
1954
+ * Remove API from auxiliary indices when unregistered
1955
+ *
1956
+ * @param api - API entry to remove from indices
1957
+ * @private
1958
+ * @internal
1959
+ */
1960
+ removeFromIndices(api) {
1961
+ this.removeFromIndexSet(this.apisByType, api.type, api.id);
1962
+ const status = api.metadata?.status || "active";
1963
+ this.removeFromIndexSet(this.apisByStatus, status, api.id);
1964
+ const tags = api.metadata?.tags || [];
1965
+ for (const tag of tags) {
1966
+ this.removeFromIndexSet(this.apisByTag, tag, api.id);
1967
+ }
1968
+ }
1969
+ /**
1970
+ * Helper to ensure an index set exists and return it
1971
+ *
1972
+ * @param map - Index map
1973
+ * @param key - Index key
1974
+ * @returns The Set for this key (created if needed)
1975
+ * @private
1976
+ * @internal
1977
+ */
1978
+ ensureIndexSet(map, key) {
1979
+ let set = map.get(key);
1980
+ if (!set) {
1981
+ set = /* @__PURE__ */ new Set();
1982
+ map.set(key, set);
1983
+ }
1984
+ return set;
1985
+ }
1986
+ /**
1987
+ * Helper to remove an ID from an index set and clean up empty sets
1988
+ *
1989
+ * @param map - Index map
1990
+ * @param key - Index key
1991
+ * @param id - API ID to remove
1992
+ * @private
1993
+ * @internal
1994
+ */
1995
+ removeFromIndexSet(map, key, id) {
1996
+ const set = map.get(key);
1997
+ if (set) {
1998
+ set.delete(id);
1999
+ if (set.size === 0) {
2000
+ map.delete(key);
2001
+ }
2002
+ }
2003
+ }
2004
+ /**
2005
+ * Check if running in production environment
2006
+ *
2007
+ * @returns true if NODE_ENV is 'production'
2008
+ * @private
2009
+ * @internal
2010
+ */
2011
+ isProductionEnvironment() {
2012
+ return getEnv("NODE_ENV") === "production";
2013
+ }
2014
+ };
2015
+
2016
+ // src/api-registry-plugin.ts
2017
+ function createApiRegistryPlugin(config = {}) {
2018
+ const {
2019
+ conflictResolution = "error",
2020
+ version = "1.0.0"
2021
+ } = config;
2022
+ return {
2023
+ name: "com.objectstack.core.api-registry",
2024
+ version: "1.0.0",
2025
+ init: async (ctx) => {
2026
+ const registry = new ApiRegistry(
2027
+ ctx.logger,
2028
+ conflictResolution,
2029
+ version
2030
+ );
2031
+ ctx.registerService("api-registry", registry);
2032
+ ctx.logger.info("API Registry plugin initialized", {
2033
+ conflictResolution,
2034
+ version
2035
+ });
2036
+ }
2037
+ };
2038
+ }
2039
+
2040
+ // src/qa/index.ts
2041
+ var qa_exports = {};
2042
+ __export(qa_exports, {
2043
+ HttpTestAdapter: () => HttpTestAdapter,
2044
+ TestRunner: () => TestRunner
2045
+ });
2046
+
2047
+ // src/qa/runner.ts
2048
+ var TestRunner = class {
2049
+ constructor(adapter) {
2050
+ this.adapter = adapter;
2051
+ }
2052
+ async runSuite(suite) {
2053
+ const results = [];
2054
+ for (const scenario of suite.scenarios) {
2055
+ results.push(await this.runScenario(scenario));
2056
+ }
2057
+ return results;
2058
+ }
2059
+ async runScenario(scenario) {
2060
+ const startTime = Date.now();
2061
+ const context = {};
2062
+ if (scenario.setup) {
2063
+ for (const step of scenario.setup) {
2064
+ try {
2065
+ await this.runStep(step, context);
2066
+ } catch (e) {
2067
+ return {
2068
+ scenarioId: scenario.id,
2069
+ passed: false,
2070
+ steps: [],
2071
+ error: `Setup failed: ${e instanceof Error ? e.message : String(e)}`,
2072
+ duration: Date.now() - startTime
2073
+ };
2074
+ }
2075
+ }
2076
+ }
2077
+ const stepResults = [];
2078
+ let scenarioPassed = true;
2079
+ let scenarioError = void 0;
2080
+ for (const step of scenario.steps) {
2081
+ const stepStartTime = Date.now();
2082
+ try {
2083
+ const output = await this.runStep(step, context);
2084
+ stepResults.push({
2085
+ stepName: step.name,
2086
+ passed: true,
2087
+ output,
2088
+ duration: Date.now() - stepStartTime
2089
+ });
2090
+ } catch (e) {
2091
+ scenarioPassed = false;
2092
+ scenarioError = e;
2093
+ stepResults.push({
2094
+ stepName: step.name,
2095
+ passed: false,
2096
+ error: e,
2097
+ duration: Date.now() - stepStartTime
2098
+ });
2099
+ break;
2100
+ }
2101
+ }
2102
+ if (scenario.teardown) {
2103
+ for (const step of scenario.teardown) {
2104
+ try {
2105
+ await this.runStep(step, context);
2106
+ } catch (e) {
2107
+ if (scenarioPassed) {
2108
+ scenarioPassed = false;
2109
+ scenarioError = `Teardown failed: ${e instanceof Error ? e.message : String(e)}`;
2110
+ }
2111
+ }
2112
+ }
2113
+ }
2114
+ return {
2115
+ scenarioId: scenario.id,
2116
+ passed: scenarioPassed,
2117
+ steps: stepResults,
2118
+ error: scenarioError,
2119
+ duration: Date.now() - startTime
2120
+ };
2121
+ }
2122
+ async runStep(step, context) {
2123
+ const resolvedAction = this.resolveVariables(step.action, context);
2124
+ const result = await this.adapter.execute(resolvedAction, context);
2125
+ if (step.capture) {
2126
+ for (const [varName, path] of Object.entries(step.capture)) {
2127
+ context[varName] = this.getValueByPath(result, path);
2128
+ }
2129
+ }
2130
+ if (step.assertions) {
2131
+ for (const assertion of step.assertions) {
2132
+ this.assert(result, assertion, context);
2133
+ }
2134
+ }
2135
+ return result;
2136
+ }
2137
+ resolveVariables(action, _context) {
2138
+ return action;
2139
+ }
2140
+ getValueByPath(obj, path) {
2141
+ if (!path) return obj;
2142
+ const parts = path.split(".");
2143
+ let current = obj;
2144
+ for (const part of parts) {
2145
+ if (current === null || current === void 0) return void 0;
2146
+ current = current[part];
2147
+ }
2148
+ return current;
2149
+ }
2150
+ assert(result, assertion, _context) {
2151
+ const actual = this.getValueByPath(result, assertion.field);
2152
+ const expected = assertion.expectedValue;
2153
+ switch (assertion.operator) {
2154
+ case "equals":
2155
+ if (actual !== expected) throw new Error(`Assertion failed: ${assertion.field} expected ${expected}, got ${actual}`);
2156
+ break;
2157
+ case "not_equals":
2158
+ if (actual === expected) throw new Error(`Assertion failed: ${assertion.field} expected not ${expected}, got ${actual}`);
2159
+ break;
2160
+ case "contains":
2161
+ if (Array.isArray(actual)) {
2162
+ if (!actual.includes(expected)) throw new Error(`Assertion failed: ${assertion.field} array does not contain ${expected}`);
2163
+ } else if (typeof actual === "string") {
2164
+ if (!actual.includes(String(expected))) throw new Error(`Assertion failed: ${assertion.field} string does not contain ${expected}`);
2165
+ }
2166
+ break;
2167
+ case "not_null":
2168
+ if (actual === null || actual === void 0) throw new Error(`Assertion failed: ${assertion.field} is null`);
2169
+ break;
2170
+ case "is_null":
2171
+ if (actual !== null && actual !== void 0) throw new Error(`Assertion failed: ${assertion.field} is not null`);
2172
+ break;
2173
+ // ... Add other operators
2174
+ default:
2175
+ throw new Error(`Unknown assertion operator: ${assertion.operator}`);
2176
+ }
2177
+ }
2178
+ };
2179
+
2180
+ // src/qa/http-adapter.ts
2181
+ var HttpTestAdapter = class {
2182
+ constructor(baseUrl, authToken) {
2183
+ this.baseUrl = baseUrl;
2184
+ this.authToken = authToken;
2185
+ }
2186
+ async execute(action, _context) {
2187
+ const headers = {
2188
+ "Content-Type": "application/json"
2189
+ };
2190
+ if (this.authToken) {
2191
+ headers["Authorization"] = `Bearer ${this.authToken}`;
2192
+ }
2193
+ if (action.user) {
2194
+ headers["X-Run-As"] = action.user;
2195
+ }
2196
+ switch (action.type) {
2197
+ case "create_record":
2198
+ return this.createRecord(action.target, action.payload || {}, headers);
2199
+ case "update_record":
2200
+ return this.updateRecord(action.target, action.payload || {}, headers);
2201
+ case "delete_record":
2202
+ return this.deleteRecord(action.target, action.payload || {}, headers);
2203
+ case "read_record":
2204
+ return this.readRecord(action.target, action.payload || {}, headers);
2205
+ case "query_records":
2206
+ return this.queryRecords(action.target, action.payload || {}, headers);
2207
+ case "api_call":
2208
+ return this.rawApiCall(action.target, action.payload || {}, headers);
2209
+ case "wait":
2210
+ const ms = Number(action.payload?.duration || 1e3);
2211
+ return new Promise((resolve) => setTimeout(() => resolve({ waited: ms }), ms));
2212
+ default:
2213
+ throw new Error(`Unsupported action type in HttpAdapter: ${action.type}`);
2214
+ }
2215
+ }
2216
+ async createRecord(objectName, data, headers) {
2217
+ const response = await fetch(`${this.baseUrl}/api/data/${objectName}`, {
2218
+ method: "POST",
2219
+ headers,
2220
+ body: JSON.stringify(data)
2221
+ });
2222
+ return this.handleResponse(response);
2223
+ }
2224
+ async updateRecord(objectName, data, headers) {
2225
+ const id = data._id || data.id;
2226
+ if (!id) throw new Error("Update record requires _id or id in payload");
2227
+ const response = await fetch(`${this.baseUrl}/api/data/${objectName}/${id}`, {
2228
+ method: "PUT",
2229
+ headers,
2230
+ body: JSON.stringify(data)
2231
+ });
2232
+ return this.handleResponse(response);
2233
+ }
2234
+ async deleteRecord(objectName, data, headers) {
2235
+ const id = data._id || data.id;
2236
+ if (!id) throw new Error("Delete record requires _id or id in payload");
2237
+ const response = await fetch(`${this.baseUrl}/api/data/${objectName}/${id}`, {
2238
+ method: "DELETE",
2239
+ headers
2240
+ });
2241
+ return this.handleResponse(response);
2242
+ }
2243
+ async readRecord(objectName, data, headers) {
2244
+ const id = data._id || data.id;
2245
+ if (!id) throw new Error("Read record requires _id or id in payload");
2246
+ const response = await fetch(`${this.baseUrl}/api/data/${objectName}/${id}`, {
2247
+ method: "GET",
2248
+ headers
2249
+ });
2250
+ return this.handleResponse(response);
2251
+ }
2252
+ async queryRecords(objectName, data, headers) {
2253
+ const response = await fetch(`${this.baseUrl}/api/data/${objectName}/query`, {
2254
+ method: "POST",
2255
+ headers,
2256
+ body: JSON.stringify(data)
2257
+ });
2258
+ return this.handleResponse(response);
2259
+ }
2260
+ async rawApiCall(endpoint, data, headers) {
2261
+ const method = data.method || "GET";
2262
+ const body = data.body ? JSON.stringify(data.body) : void 0;
2263
+ const url = endpoint.startsWith("http") ? endpoint : `${this.baseUrl}${endpoint}`;
2264
+ const response = await fetch(url, {
2265
+ method,
2266
+ headers,
2267
+ body
2268
+ });
2269
+ return this.handleResponse(response);
2270
+ }
2271
+ async handleResponse(response) {
2272
+ if (!response.ok) {
2273
+ const text = await response.text();
2274
+ throw new Error(`HTTP Error ${response.status}: ${text}`);
2275
+ }
2276
+ const contentType = response.headers.get("content-type");
2277
+ if (contentType && contentType.includes("application/json")) {
2278
+ return response.json();
2279
+ }
2280
+ return response.text();
2281
+ }
2282
+ };
2283
+
2284
+ // src/security/plugin-signature-verifier.ts
2285
+ var cryptoModule = null;
2286
+ var PluginSignatureVerifier = class {
2287
+ constructor(config, logger) {
2288
+ this.config = config;
2289
+ this.logger = logger;
2290
+ this.validateConfig();
2291
+ }
2292
+ /**
2293
+ * Verify plugin signature
2294
+ *
2295
+ * @param plugin - Plugin metadata with signature
2296
+ * @returns Verification result
2297
+ * @throws Error if verification fails in strict mode
2298
+ */
2299
+ async verifyPluginSignature(plugin) {
2300
+ if (!plugin.signature) {
2301
+ return this.handleUnsignedPlugin(plugin);
2302
+ }
2303
+ try {
2304
+ const publisherId = this.extractPublisherId(plugin.name);
2305
+ const publicKey = this.config.trustedPublicKeys.get(publisherId);
2306
+ if (!publicKey) {
2307
+ const error = `No trusted public key for publisher: ${publisherId}`;
2308
+ this.logger.warn(error, { plugin: plugin.name, publisherId });
2309
+ if (this.config.strictMode && !this.config.allowSelfSigned) {
2310
+ throw new Error(error);
2311
+ }
2312
+ return {
2313
+ verified: false,
2314
+ error,
2315
+ publisherId
2316
+ };
2317
+ }
2318
+ const pluginHash = this.computePluginHash(plugin);
2319
+ const isValid = await this.verifyCryptoSignature(
2320
+ pluginHash,
2321
+ plugin.signature,
2322
+ publicKey
2323
+ );
2324
+ if (!isValid) {
2325
+ const error = `Signature verification failed for plugin: ${plugin.name}`;
2326
+ this.logger.error(error, void 0, { plugin: plugin.name, publisherId });
2327
+ throw new Error(error);
2328
+ }
2329
+ this.logger.info(`\u2705 Plugin signature verified: ${plugin.name}`, {
2330
+ plugin: plugin.name,
2331
+ publisherId,
2332
+ algorithm: this.config.algorithm
2333
+ });
2334
+ return {
2335
+ verified: true,
2336
+ publisherId,
2337
+ algorithm: this.config.algorithm
2338
+ };
2339
+ } catch (error) {
2340
+ this.logger.error(`Signature verification error: ${plugin.name}`, error);
2341
+ if (this.config.strictMode) {
2342
+ throw error;
2343
+ }
2344
+ return {
2345
+ verified: false,
2346
+ error: error.message
2347
+ };
2348
+ }
2349
+ }
2350
+ /**
2351
+ * Register a trusted public key for a publisher
2352
+ */
2353
+ registerPublicKey(publisherId, publicKey) {
2354
+ this.config.trustedPublicKeys.set(publisherId, publicKey);
2355
+ this.logger.info(`Trusted public key registered for: ${publisherId}`);
2356
+ }
2357
+ /**
2358
+ * Remove a trusted public key
2359
+ */
2360
+ revokePublicKey(publisherId) {
2361
+ this.config.trustedPublicKeys.delete(publisherId);
2362
+ this.logger.warn(`Public key revoked for: ${publisherId}`);
2363
+ }
2364
+ /**
2365
+ * Get list of trusted publishers
2366
+ */
2367
+ getTrustedPublishers() {
2368
+ return Array.from(this.config.trustedPublicKeys.keys());
2369
+ }
2370
+ // Private methods
2371
+ handleUnsignedPlugin(plugin) {
2372
+ if (this.config.strictMode) {
2373
+ const error = `Plugin missing signature (strict mode): ${plugin.name}`;
2374
+ this.logger.error(error, void 0, { plugin: plugin.name });
2375
+ throw new Error(error);
2376
+ }
2377
+ this.logger.warn(`\u26A0\uFE0F Plugin not signed: ${plugin.name}`, {
2378
+ plugin: plugin.name,
2379
+ recommendation: "Consider signing plugins for production environments"
2380
+ });
2381
+ return {
2382
+ verified: false,
2383
+ error: "Plugin not signed"
2384
+ };
2385
+ }
2386
+ extractPublisherId(pluginName) {
2387
+ const parts = pluginName.split(".");
2388
+ if (parts.length < 2) {
2389
+ throw new Error(`Invalid plugin name format: ${pluginName} (expected reverse domain notation)`);
2390
+ }
2391
+ return `${parts[0]}.${parts[1]}`;
2392
+ }
2393
+ computePluginHash(plugin) {
2394
+ if (typeof globalThis.window !== "undefined") {
2395
+ return this.computePluginHashBrowser(plugin);
2396
+ }
2397
+ return this.computePluginHashNode(plugin);
2398
+ }
2399
+ computePluginHashNode(plugin) {
2400
+ if (!cryptoModule) {
2401
+ this.logger.warn("crypto module not available, using fallback hash");
2402
+ return this.computePluginHashFallback(plugin);
2403
+ }
2404
+ const pluginCode = this.serializePluginCode(plugin);
2405
+ return cryptoModule.createHash("sha256").update(pluginCode).digest("hex");
2406
+ }
2407
+ computePluginHashBrowser(plugin) {
2408
+ this.logger.debug("Using browser hash (SubtleCrypto integration pending)");
2409
+ return this.computePluginHashFallback(plugin);
2410
+ }
2411
+ computePluginHashFallback(plugin) {
2412
+ const pluginCode = this.serializePluginCode(plugin);
2413
+ let hash = 0;
2414
+ for (let i = 0; i < pluginCode.length; i++) {
2415
+ const char = pluginCode.charCodeAt(i);
2416
+ hash = (hash << 5) - hash + char;
2417
+ hash = hash & hash;
2418
+ }
2419
+ return hash.toString(16);
2420
+ }
2421
+ serializePluginCode(plugin) {
2422
+ const parts = [
2423
+ plugin.name,
2424
+ plugin.version,
2425
+ plugin.init.toString()
2426
+ ];
2427
+ if (plugin.start) {
2428
+ parts.push(plugin.start.toString());
2429
+ }
2430
+ if (plugin.destroy) {
2431
+ parts.push(plugin.destroy.toString());
2432
+ }
2433
+ return parts.join("|");
2434
+ }
2435
+ async verifyCryptoSignature(data, signature, publicKey) {
2436
+ if (typeof globalThis.window !== "undefined") {
2437
+ return this.verifyCryptoSignatureBrowser(data, signature, publicKey);
2438
+ }
2439
+ return this.verifyCryptoSignatureNode(data, signature, publicKey);
2440
+ }
2441
+ async verifyCryptoSignatureNode(data, signature, publicKey) {
2442
+ if (!cryptoModule) {
2443
+ try {
2444
+ cryptoModule = await import("crypto");
2445
+ } catch (e) {
2446
+ }
2447
+ }
2448
+ if (!cryptoModule) {
2449
+ this.logger.error("Crypto module not available for signature verification");
2450
+ return false;
2451
+ }
2452
+ try {
2453
+ if (this.config.algorithm === "ES256") {
2454
+ const verify = cryptoModule.createVerify("sha256");
2455
+ verify.update(data);
2456
+ return verify.verify(
2457
+ {
2458
+ key: publicKey,
2459
+ format: "pem",
2460
+ type: "spki"
2461
+ },
2462
+ signature,
2463
+ "base64"
2464
+ );
2465
+ } else {
2466
+ const verify = cryptoModule.createVerify("RSA-SHA256");
2467
+ verify.update(data);
2468
+ return verify.verify(publicKey, signature, "base64");
2469
+ }
2470
+ } catch (error) {
2471
+ this.logger.error("Signature verification failed", error);
2472
+ return false;
2473
+ }
2474
+ }
2475
+ async verifyCryptoSignatureBrowser(_data, _signature, _publicKey) {
2476
+ this.logger.warn("Browser signature verification not yet implemented");
2477
+ return false;
2478
+ }
2479
+ validateConfig() {
2480
+ if (!this.config.trustedPublicKeys || this.config.trustedPublicKeys.size === 0) {
2481
+ this.logger.warn("No trusted public keys configured - all signatures will fail");
2482
+ }
2483
+ if (!this.config.algorithm) {
2484
+ throw new Error("Signature algorithm must be specified");
2485
+ }
2486
+ if (!["RS256", "ES256"].includes(this.config.algorithm)) {
2487
+ throw new Error(`Unsupported algorithm: ${this.config.algorithm}`);
2488
+ }
2489
+ }
2490
+ };
2491
+
2492
+ // src/security/plugin-permission-enforcer.ts
2493
+ var PluginPermissionEnforcer = class {
2494
+ constructor(logger) {
2495
+ this.permissionRegistry = /* @__PURE__ */ new Map();
2496
+ this.capabilityRegistry = /* @__PURE__ */ new Map();
2497
+ this.logger = logger;
2498
+ }
2499
+ /**
2500
+ * Register plugin capabilities and build permission set
2501
+ *
2502
+ * @param pluginName - Plugin identifier
2503
+ * @param capabilities - Array of capability declarations
2504
+ */
2505
+ registerPluginPermissions(pluginName, capabilities) {
2506
+ this.capabilityRegistry.set(pluginName, capabilities);
2507
+ const permissions = {
2508
+ canAccessService: (service) => this.checkServiceAccess(capabilities, service),
2509
+ canTriggerHook: (hook) => this.checkHookAccess(capabilities, hook),
2510
+ canReadFile: (path) => this.checkFileRead(capabilities, path),
2511
+ canWriteFile: (path) => this.checkFileWrite(capabilities, path),
2512
+ canNetworkRequest: (url) => this.checkNetworkAccess(capabilities, url)
2513
+ };
2514
+ this.permissionRegistry.set(pluginName, permissions);
2515
+ this.logger.info(`Permissions registered for plugin: ${pluginName}`, {
2516
+ plugin: pluginName,
2517
+ capabilityCount: capabilities.length
2518
+ });
2519
+ }
2520
+ /**
2521
+ * Enforce service access permission
2522
+ *
2523
+ * @param pluginName - Plugin requesting access
2524
+ * @param serviceName - Service to access
2525
+ * @throws Error if permission denied
2526
+ */
2527
+ enforceServiceAccess(pluginName, serviceName) {
2528
+ const result = this.checkPermission(pluginName, (perms) => perms.canAccessService(serviceName));
2529
+ if (!result.allowed) {
2530
+ const error = `Permission denied: Plugin ${pluginName} cannot access service ${serviceName}`;
2531
+ this.logger.warn(error, {
2532
+ plugin: pluginName,
2533
+ service: serviceName,
2534
+ reason: result.reason
2535
+ });
2536
+ throw new Error(error);
2537
+ }
2538
+ this.logger.debug(`Service access granted: ${pluginName} -> ${serviceName}`);
2539
+ }
2540
+ /**
2541
+ * Enforce hook trigger permission
2542
+ *
2543
+ * @param pluginName - Plugin requesting access
2544
+ * @param hookName - Hook to trigger
2545
+ * @throws Error if permission denied
2546
+ */
2547
+ enforceHookTrigger(pluginName, hookName) {
2548
+ const result = this.checkPermission(pluginName, (perms) => perms.canTriggerHook(hookName));
2549
+ if (!result.allowed) {
2550
+ const error = `Permission denied: Plugin ${pluginName} cannot trigger hook ${hookName}`;
2551
+ this.logger.warn(error, {
2552
+ plugin: pluginName,
2553
+ hook: hookName,
2554
+ reason: result.reason
2555
+ });
2556
+ throw new Error(error);
2557
+ }
2558
+ this.logger.debug(`Hook trigger granted: ${pluginName} -> ${hookName}`);
2559
+ }
2560
+ /**
2561
+ * Enforce file read permission
2562
+ *
2563
+ * @param pluginName - Plugin requesting access
2564
+ * @param path - File path to read
2565
+ * @throws Error if permission denied
2566
+ */
2567
+ enforceFileRead(pluginName, path) {
2568
+ const result = this.checkPermission(pluginName, (perms) => perms.canReadFile(path));
2569
+ if (!result.allowed) {
2570
+ const error = `Permission denied: Plugin ${pluginName} cannot read file ${path}`;
2571
+ this.logger.warn(error, {
2572
+ plugin: pluginName,
2573
+ path,
2574
+ reason: result.reason
2575
+ });
2576
+ throw new Error(error);
2577
+ }
2578
+ this.logger.debug(`File read granted: ${pluginName} -> ${path}`);
2579
+ }
2580
+ /**
2581
+ * Enforce file write permission
2582
+ *
2583
+ * @param pluginName - Plugin requesting access
2584
+ * @param path - File path to write
2585
+ * @throws Error if permission denied
2586
+ */
2587
+ enforceFileWrite(pluginName, path) {
2588
+ const result = this.checkPermission(pluginName, (perms) => perms.canWriteFile(path));
2589
+ if (!result.allowed) {
2590
+ const error = `Permission denied: Plugin ${pluginName} cannot write file ${path}`;
2591
+ this.logger.warn(error, {
2592
+ plugin: pluginName,
2593
+ path,
2594
+ reason: result.reason
2595
+ });
2596
+ throw new Error(error);
2597
+ }
2598
+ this.logger.debug(`File write granted: ${pluginName} -> ${path}`);
2599
+ }
2600
+ /**
2601
+ * Enforce network request permission
2602
+ *
2603
+ * @param pluginName - Plugin requesting access
2604
+ * @param url - URL to access
2605
+ * @throws Error if permission denied
2606
+ */
2607
+ enforceNetworkRequest(pluginName, url) {
2608
+ const result = this.checkPermission(pluginName, (perms) => perms.canNetworkRequest(url));
2609
+ if (!result.allowed) {
2610
+ const error = `Permission denied: Plugin ${pluginName} cannot access URL ${url}`;
2611
+ this.logger.warn(error, {
2612
+ plugin: pluginName,
2613
+ url,
2614
+ reason: result.reason
2615
+ });
2616
+ throw new Error(error);
2617
+ }
2618
+ this.logger.debug(`Network request granted: ${pluginName} -> ${url}`);
2619
+ }
2620
+ /**
2621
+ * Get plugin capabilities
2622
+ *
2623
+ * @param pluginName - Plugin identifier
2624
+ * @returns Array of capabilities or undefined
2625
+ */
2626
+ getPluginCapabilities(pluginName) {
2627
+ return this.capabilityRegistry.get(pluginName);
2628
+ }
2629
+ /**
2630
+ * Get plugin permissions
2631
+ *
2632
+ * @param pluginName - Plugin identifier
2633
+ * @returns Permissions object or undefined
2634
+ */
2635
+ getPluginPermissions(pluginName) {
2636
+ return this.permissionRegistry.get(pluginName);
2637
+ }
2638
+ /**
2639
+ * Revoke all permissions for a plugin
2640
+ *
2641
+ * @param pluginName - Plugin identifier
2642
+ */
2643
+ revokePermissions(pluginName) {
2644
+ this.permissionRegistry.delete(pluginName);
2645
+ this.capabilityRegistry.delete(pluginName);
2646
+ this.logger.warn(`Permissions revoked for plugin: ${pluginName}`);
2647
+ }
2648
+ // Private methods
2649
+ checkPermission(pluginName, check) {
2650
+ const permissions = this.permissionRegistry.get(pluginName);
2651
+ if (!permissions) {
2652
+ return {
2653
+ allowed: false,
2654
+ reason: "Plugin permissions not registered"
2655
+ };
2656
+ }
2657
+ const allowed = check(permissions);
2658
+ return {
2659
+ allowed,
2660
+ reason: allowed ? void 0 : "No matching capability found"
2661
+ };
2662
+ }
2663
+ checkServiceAccess(capabilities, serviceName) {
2664
+ return capabilities.some((cap) => {
2665
+ const protocolId = cap.protocol.id;
2666
+ if (protocolId.includes("protocol.service.all")) {
2667
+ return true;
2668
+ }
2669
+ if (protocolId.includes(`protocol.service.${serviceName}`)) {
2670
+ return true;
2671
+ }
2672
+ const serviceCategory = serviceName.split(".")[0];
2673
+ if (protocolId.includes(`protocol.service.${serviceCategory}`)) {
2674
+ return true;
2675
+ }
2676
+ return false;
2677
+ });
2678
+ }
2679
+ checkHookAccess(capabilities, hookName) {
2680
+ return capabilities.some((cap) => {
2681
+ const protocolId = cap.protocol.id;
2682
+ if (protocolId.includes("protocol.hook.all")) {
2683
+ return true;
2684
+ }
2685
+ if (protocolId.includes(`protocol.hook.${hookName}`)) {
2686
+ return true;
2687
+ }
2688
+ const hookCategory = hookName.split(":")[0];
2689
+ if (protocolId.includes(`protocol.hook.${hookCategory}`)) {
2690
+ return true;
2691
+ }
2692
+ return false;
2693
+ });
2694
+ }
2695
+ checkFileRead(capabilities, _path) {
2696
+ return capabilities.some((cap) => {
2697
+ const protocolId = cap.protocol.id;
2698
+ if (protocolId.includes("protocol.filesystem.read")) {
2699
+ return true;
2700
+ }
2701
+ return false;
2702
+ });
2703
+ }
2704
+ checkFileWrite(capabilities, _path) {
2705
+ return capabilities.some((cap) => {
2706
+ const protocolId = cap.protocol.id;
2707
+ if (protocolId.includes("protocol.filesystem.write")) {
2708
+ return true;
2709
+ }
2710
+ return false;
2711
+ });
2712
+ }
2713
+ checkNetworkAccess(capabilities, _url) {
2714
+ return capabilities.some((cap) => {
2715
+ const protocolId = cap.protocol.id;
2716
+ if (protocolId.includes("protocol.network")) {
2717
+ return true;
2718
+ }
2719
+ return false;
2720
+ });
2721
+ }
2722
+ };
2723
+ var SecurePluginContext = class {
2724
+ constructor(pluginName, permissionEnforcer, baseContext) {
2725
+ this.pluginName = pluginName;
2726
+ this.permissionEnforcer = permissionEnforcer;
2727
+ this.baseContext = baseContext;
2728
+ }
2729
+ registerService(name, service) {
2730
+ this.baseContext.registerService(name, service);
2731
+ }
2732
+ getService(name) {
2733
+ this.permissionEnforcer.enforceServiceAccess(this.pluginName, name);
2734
+ return this.baseContext.getService(name);
2735
+ }
2736
+ getServices() {
2737
+ return this.baseContext.getServices();
2738
+ }
2739
+ hook(name, handler) {
2740
+ this.baseContext.hook(name, handler);
2741
+ }
2742
+ async trigger(name, ...args) {
2743
+ this.permissionEnforcer.enforceHookTrigger(this.pluginName, name);
2744
+ await this.baseContext.trigger(name, ...args);
2745
+ }
2746
+ get logger() {
2747
+ return this.baseContext.logger;
2748
+ }
2749
+ getKernel() {
2750
+ return this.baseContext.getKernel();
2751
+ }
2752
+ };
2753
+ function createPluginPermissionEnforcer(logger) {
2754
+ return new PluginPermissionEnforcer(logger);
2755
+ }
2756
+
2757
+ // src/security/permission-manager.ts
2758
+ var PluginPermissionManager = class {
2759
+ constructor(logger) {
2760
+ // Plugin permission definitions
2761
+ this.permissionSets = /* @__PURE__ */ new Map();
2762
+ // Granted permissions (pluginId -> Set of permission IDs)
2763
+ this.grants = /* @__PURE__ */ new Map();
2764
+ // Permission grant details
2765
+ this.grantDetails = /* @__PURE__ */ new Map();
2766
+ this.logger = logger.child({ component: "PermissionManager" });
2767
+ }
2768
+ /**
2769
+ * Register permission requirements for a plugin
2770
+ */
2771
+ registerPermissions(pluginId, permissionSet) {
2772
+ this.permissionSets.set(pluginId, permissionSet);
2773
+ this.logger.info("Permissions registered for plugin", {
2774
+ pluginId,
2775
+ permissionCount: permissionSet.permissions.length
2776
+ });
2777
+ }
2778
+ /**
2779
+ * Grant a permission to a plugin
2780
+ */
2781
+ grantPermission(pluginId, permissionId, grantedBy, expiresAt) {
2782
+ const permissionSet = this.permissionSets.get(pluginId);
2783
+ if (!permissionSet) {
2784
+ throw new Error(`No permissions registered for plugin: ${pluginId}`);
2785
+ }
2786
+ const permission = permissionSet.permissions.find((p) => p.id === permissionId);
2787
+ if (!permission) {
2788
+ throw new Error(`Permission ${permissionId} not declared by plugin ${pluginId}`);
2789
+ }
2790
+ if (!this.grants.has(pluginId)) {
2791
+ this.grants.set(pluginId, /* @__PURE__ */ new Set());
2792
+ }
2793
+ this.grants.get(pluginId).add(permissionId);
2794
+ const grantKey = `${pluginId}:${permissionId}`;
2795
+ this.grantDetails.set(grantKey, {
2796
+ permissionId,
2797
+ pluginId,
2798
+ grantedAt: /* @__PURE__ */ new Date(),
2799
+ grantedBy,
2800
+ expiresAt
2801
+ });
2802
+ this.logger.info("Permission granted", {
2803
+ pluginId,
2804
+ permissionId,
2805
+ grantedBy
2806
+ });
2807
+ }
2808
+ /**
2809
+ * Revoke a permission from a plugin
2810
+ */
2811
+ revokePermission(pluginId, permissionId) {
2812
+ const grants = this.grants.get(pluginId);
2813
+ if (grants) {
2814
+ grants.delete(permissionId);
2815
+ const grantKey = `${pluginId}:${permissionId}`;
2816
+ this.grantDetails.delete(grantKey);
2817
+ this.logger.info("Permission revoked", { pluginId, permissionId });
2818
+ }
2819
+ }
2820
+ /**
2821
+ * Grant all permissions for a plugin
2822
+ */
2823
+ grantAllPermissions(pluginId, grantedBy) {
2824
+ const permissionSet = this.permissionSets.get(pluginId);
2825
+ if (!permissionSet) {
2826
+ throw new Error(`No permissions registered for plugin: ${pluginId}`);
2827
+ }
2828
+ for (const permission of permissionSet.permissions) {
2829
+ this.grantPermission(pluginId, permission.id, grantedBy);
2830
+ }
2831
+ this.logger.info("All permissions granted", { pluginId, grantedBy });
2832
+ }
2833
+ /**
2834
+ * Check if a plugin has a specific permission
2835
+ */
2836
+ hasPermission(pluginId, permissionId) {
2837
+ const grants = this.grants.get(pluginId);
2838
+ if (!grants) {
2839
+ return false;
2840
+ }
2841
+ if (!grants.has(permissionId)) {
2842
+ return false;
2843
+ }
2844
+ const grantKey = `${pluginId}:${permissionId}`;
2845
+ const grantDetails = this.grantDetails.get(grantKey);
2846
+ if (grantDetails?.expiresAt && grantDetails.expiresAt < /* @__PURE__ */ new Date()) {
2847
+ this.revokePermission(pluginId, permissionId);
2848
+ return false;
2849
+ }
2850
+ return true;
2851
+ }
2852
+ /**
2853
+ * Check if plugin can perform an action on a resource
2854
+ */
2855
+ checkAccess(pluginId, resource, action, resourceId) {
2856
+ const permissionSet = this.permissionSets.get(pluginId);
2857
+ if (!permissionSet) {
2858
+ return {
2859
+ allowed: false,
2860
+ reason: "No permissions registered for plugin"
2861
+ };
2862
+ }
2863
+ const matchingPermissions = permissionSet.permissions.filter((p) => {
2864
+ if (p.resource !== resource) {
2865
+ return false;
2866
+ }
2867
+ if (!p.actions.includes(action)) {
2868
+ return false;
2869
+ }
2870
+ if (resourceId && p.filter?.resourceIds) {
2871
+ if (!p.filter.resourceIds.includes(resourceId)) {
2872
+ return false;
2873
+ }
2874
+ }
2875
+ return true;
2876
+ });
2877
+ if (matchingPermissions.length === 0) {
2878
+ return {
2879
+ allowed: false,
2880
+ reason: `No permission found for ${action} on ${resource}`
2881
+ };
2882
+ }
2883
+ const grantedPermissions = matchingPermissions.filter(
2884
+ (p) => this.hasPermission(pluginId, p.id)
2885
+ );
2886
+ if (grantedPermissions.length === 0) {
2887
+ return {
2888
+ allowed: false,
2889
+ reason: "Required permissions not granted",
2890
+ requiredPermission: matchingPermissions[0].id
2891
+ };
2892
+ }
2893
+ return {
2894
+ allowed: true,
2895
+ grantedPermissions: grantedPermissions.map((p) => p.id)
2896
+ };
2897
+ }
2898
+ /**
2899
+ * Get all permissions for a plugin
2900
+ */
2901
+ getPluginPermissions(pluginId) {
2902
+ const permissionSet = this.permissionSets.get(pluginId);
2903
+ return permissionSet?.permissions || [];
2904
+ }
2905
+ /**
2906
+ * Get granted permissions for a plugin
2907
+ */
2908
+ getGrantedPermissions(pluginId) {
2909
+ const grants = this.grants.get(pluginId);
2910
+ return grants ? Array.from(grants) : [];
2911
+ }
2912
+ /**
2913
+ * Get required but not granted permissions
2914
+ */
2915
+ getMissingPermissions(pluginId) {
2916
+ const permissionSet = this.permissionSets.get(pluginId);
2917
+ if (!permissionSet) {
2918
+ return [];
2919
+ }
2920
+ const granted = this.grants.get(pluginId) || /* @__PURE__ */ new Set();
2921
+ return permissionSet.permissions.filter(
2922
+ (p) => p.required && !granted.has(p.id)
2923
+ );
2924
+ }
2925
+ /**
2926
+ * Check if all required permissions are granted
2927
+ */
2928
+ hasAllRequiredPermissions(pluginId) {
2929
+ return this.getMissingPermissions(pluginId).length === 0;
2930
+ }
2931
+ /**
2932
+ * Get permission grant details
2933
+ */
2934
+ getGrantDetails(pluginId, permissionId) {
2935
+ const grantKey = `${pluginId}:${permissionId}`;
2936
+ return this.grantDetails.get(grantKey);
2937
+ }
2938
+ /**
2939
+ * Validate permission against scope constraints
2940
+ */
2941
+ validatePermissionScope(permission, context) {
2942
+ switch (permission.scope) {
2943
+ case "global":
2944
+ return true;
2945
+ case "tenant":
2946
+ return !!context.tenantId;
2947
+ case "user":
2948
+ return !!context.userId;
2949
+ case "resource":
2950
+ return !!context.resourceId;
2951
+ case "plugin":
2952
+ return true;
2953
+ default:
2954
+ return false;
2955
+ }
2956
+ }
2957
+ /**
2958
+ * Clear all permissions for a plugin
2959
+ */
2960
+ clearPluginPermissions(pluginId) {
2961
+ this.permissionSets.delete(pluginId);
2962
+ const grants = this.grants.get(pluginId);
2963
+ if (grants) {
2964
+ for (const permissionId of grants) {
2965
+ const grantKey = `${pluginId}:${permissionId}`;
2966
+ this.grantDetails.delete(grantKey);
2967
+ }
2968
+ this.grants.delete(pluginId);
2969
+ }
2970
+ this.logger.info("All permissions cleared", { pluginId });
2971
+ }
2972
+ /**
2973
+ * Shutdown permission manager
2974
+ */
2975
+ shutdown() {
2976
+ this.permissionSets.clear();
2977
+ this.grants.clear();
2978
+ this.grantDetails.clear();
2979
+ this.logger.info("Permission manager shutdown complete");
2980
+ }
2981
+ };
2982
+
2983
+ // src/security/sandbox-runtime.ts
2984
+ var PluginSandboxRuntime = class {
2985
+ constructor(logger) {
2986
+ // Active sandboxes (pluginId -> context)
2987
+ this.sandboxes = /* @__PURE__ */ new Map();
2988
+ // Resource monitoring intervals
2989
+ this.monitoringIntervals = /* @__PURE__ */ new Map();
2990
+ this.logger = logger.child({ component: "SandboxRuntime" });
2991
+ }
2992
+ /**
2993
+ * Create a sandbox for a plugin
2994
+ */
2995
+ createSandbox(pluginId, config) {
2996
+ if (this.sandboxes.has(pluginId)) {
2997
+ throw new Error(`Sandbox already exists for plugin: ${pluginId}`);
2998
+ }
2999
+ const context = {
3000
+ pluginId,
3001
+ config,
3002
+ startTime: /* @__PURE__ */ new Date(),
3003
+ resourceUsage: {
3004
+ memory: { current: 0, peak: 0, limit: config.memory?.maxHeap },
3005
+ cpu: { current: 0, average: 0, limit: config.cpu?.maxCpuPercent },
3006
+ connections: { current: 0, limit: config.network?.maxConnections }
3007
+ }
3008
+ };
3009
+ this.sandboxes.set(pluginId, context);
3010
+ this.startResourceMonitoring(pluginId);
3011
+ this.logger.info("Sandbox created", {
3012
+ pluginId,
3013
+ level: config.level,
3014
+ memoryLimit: config.memory?.maxHeap,
3015
+ cpuLimit: config.cpu?.maxCpuPercent
3016
+ });
3017
+ return context;
3018
+ }
3019
+ /**
3020
+ * Destroy a sandbox
3021
+ */
3022
+ destroySandbox(pluginId) {
3023
+ const context = this.sandboxes.get(pluginId);
3024
+ if (!context) {
3025
+ return;
3026
+ }
3027
+ this.stopResourceMonitoring(pluginId);
3028
+ this.sandboxes.delete(pluginId);
3029
+ this.logger.info("Sandbox destroyed", { pluginId });
3030
+ }
3031
+ /**
3032
+ * Check if resource access is allowed
3033
+ */
3034
+ checkResourceAccess(pluginId, resourceType, resourcePath) {
3035
+ const context = this.sandboxes.get(pluginId);
3036
+ if (!context) {
3037
+ return { allowed: false, reason: "Sandbox not found" };
3038
+ }
3039
+ const { config } = context;
3040
+ switch (resourceType) {
3041
+ case "file":
3042
+ return this.checkFileAccess(config, resourcePath);
3043
+ case "network":
3044
+ return this.checkNetworkAccess(config, resourcePath);
3045
+ case "process":
3046
+ return this.checkProcessAccess(config);
3047
+ case "env":
3048
+ return this.checkEnvAccess(config, resourcePath);
3049
+ default:
3050
+ return { allowed: false, reason: "Unknown resource type" };
3051
+ }
3052
+ }
3053
+ /**
3054
+ * Check file system access
3055
+ * WARNING: Uses simple prefix matching. For production, use proper path
3056
+ * resolution with path.resolve() and path.normalize() to prevent traversal.
3057
+ */
3058
+ checkFileAccess(config, path) {
3059
+ if (config.level === "none") {
3060
+ return { allowed: true };
3061
+ }
3062
+ if (!config.filesystem) {
3063
+ return { allowed: false, reason: "File system access not configured" };
3064
+ }
3065
+ if (!path) {
3066
+ return { allowed: config.filesystem.mode !== "none" };
3067
+ }
3068
+ const allowedPaths = config.filesystem.allowedPaths || [];
3069
+ const isAllowed = allowedPaths.some((allowed) => {
3070
+ return path.startsWith(allowed);
3071
+ });
3072
+ if (allowedPaths.length > 0 && !isAllowed) {
3073
+ return {
3074
+ allowed: false,
3075
+ reason: `Path not in allowed list: ${path}`
3076
+ };
3077
+ }
3078
+ const deniedPaths = config.filesystem.deniedPaths || [];
3079
+ const isDenied = deniedPaths.some((denied) => {
3080
+ return path.startsWith(denied);
3081
+ });
3082
+ if (isDenied) {
3083
+ return {
3084
+ allowed: false,
3085
+ reason: `Path is explicitly denied: ${path}`
3086
+ };
3087
+ }
3088
+ return { allowed: true };
3089
+ }
3090
+ /**
3091
+ * Check network access
3092
+ * WARNING: Uses simple string matching. For production, use proper URL
3093
+ * parsing with new URL() and check hostname property.
3094
+ */
3095
+ checkNetworkAccess(config, url) {
3096
+ if (config.level === "none") {
3097
+ return { allowed: true };
3098
+ }
3099
+ if (!config.network) {
3100
+ return { allowed: false, reason: "Network access not configured" };
3101
+ }
3102
+ if (config.network.mode === "none") {
3103
+ return { allowed: false, reason: "Network access disabled" };
3104
+ }
3105
+ if (!url) {
3106
+ return { allowed: config.network.mode !== "none" };
3107
+ }
3108
+ const allowedHosts = config.network.allowedHosts || [];
3109
+ if (allowedHosts.length > 0) {
3110
+ const isAllowed = allowedHosts.some((host) => {
3111
+ return url.includes(host);
3112
+ });
3113
+ if (!isAllowed) {
3114
+ return {
3115
+ allowed: false,
3116
+ reason: `Host not in allowed list: ${url}`
3117
+ };
3118
+ }
3119
+ }
3120
+ const deniedHosts = config.network.deniedHosts || [];
3121
+ const isDenied = deniedHosts.some((host) => {
3122
+ return url.includes(host);
3123
+ });
3124
+ if (isDenied) {
3125
+ return {
3126
+ allowed: false,
3127
+ reason: `Host is blocked: ${url}`
3128
+ };
3129
+ }
3130
+ return { allowed: true };
3131
+ }
3132
+ /**
3133
+ * Check process spawning access
3134
+ */
3135
+ checkProcessAccess(config) {
3136
+ if (config.level === "none") {
3137
+ return { allowed: true };
3138
+ }
3139
+ if (!config.process) {
3140
+ return { allowed: false, reason: "Process access not configured" };
3141
+ }
3142
+ if (!config.process.allowSpawn) {
3143
+ return { allowed: false, reason: "Process spawning not allowed" };
3144
+ }
3145
+ return { allowed: true };
3146
+ }
3147
+ /**
3148
+ * Check environment variable access
3149
+ */
3150
+ checkEnvAccess(config, varName) {
3151
+ if (config.level === "none") {
3152
+ return { allowed: true };
3153
+ }
3154
+ if (!config.process) {
3155
+ return { allowed: false, reason: "Environment access not configured" };
3156
+ }
3157
+ if (!varName) {
3158
+ return { allowed: true };
3159
+ }
3160
+ return { allowed: true };
3161
+ }
3162
+ /**
3163
+ * Check resource limits
3164
+ */
3165
+ checkResourceLimits(pluginId) {
3166
+ const context = this.sandboxes.get(pluginId);
3167
+ if (!context) {
3168
+ return { withinLimits: true, violations: [] };
3169
+ }
3170
+ const violations = [];
3171
+ const { resourceUsage, config } = context;
3172
+ if (config.memory?.maxHeap && resourceUsage.memory.current > config.memory.maxHeap) {
3173
+ violations.push(`Memory limit exceeded: ${resourceUsage.memory.current} > ${config.memory.maxHeap}`);
3174
+ }
3175
+ if (config.runtime?.resourceLimits?.maxCpu && resourceUsage.cpu.current > config.runtime.resourceLimits.maxCpu) {
3176
+ violations.push(`CPU limit exceeded: ${resourceUsage.cpu.current}% > ${config.runtime.resourceLimits.maxCpu}%`);
3177
+ }
3178
+ if (config.network?.maxConnections && resourceUsage.connections.current > config.network.maxConnections) {
3179
+ violations.push(`Connection limit exceeded: ${resourceUsage.connections.current} > ${config.network.maxConnections}`);
3180
+ }
3181
+ return {
3182
+ withinLimits: violations.length === 0,
3183
+ violations
3184
+ };
3185
+ }
3186
+ /**
3187
+ * Get resource usage for a plugin
3188
+ */
3189
+ getResourceUsage(pluginId) {
3190
+ const context = this.sandboxes.get(pluginId);
3191
+ return context?.resourceUsage;
3192
+ }
3193
+ /**
3194
+ * Start monitoring resource usage
3195
+ */
3196
+ startResourceMonitoring(pluginId) {
3197
+ const interval = setInterval(() => {
3198
+ this.updateResourceUsage(pluginId);
3199
+ }, 5e3);
3200
+ this.monitoringIntervals.set(pluginId, interval);
3201
+ }
3202
+ /**
3203
+ * Stop monitoring resource usage
3204
+ */
3205
+ stopResourceMonitoring(pluginId) {
3206
+ const interval = this.monitoringIntervals.get(pluginId);
3207
+ if (interval) {
3208
+ clearInterval(interval);
3209
+ this.monitoringIntervals.delete(pluginId);
3210
+ }
3211
+ }
3212
+ /**
3213
+ * Update resource usage statistics
3214
+ *
3215
+ * NOTE: Currently uses global process.memoryUsage() which tracks the entire
3216
+ * Node.js process, not individual plugins. For production, implement proper
3217
+ * per-plugin tracking using V8 heap snapshots or allocation tracking at
3218
+ * plugin boundaries.
3219
+ */
3220
+ updateResourceUsage(pluginId) {
3221
+ const context = this.sandboxes.get(pluginId);
3222
+ if (!context) {
3223
+ return;
3224
+ }
3225
+ const memoryUsage = getMemoryUsage();
3226
+ context.resourceUsage.memory.current = memoryUsage.heapUsed;
3227
+ context.resourceUsage.memory.peak = Math.max(
3228
+ context.resourceUsage.memory.peak,
3229
+ memoryUsage.heapUsed
3230
+ );
3231
+ context.resourceUsage.cpu.current = 0;
3232
+ const { withinLimits, violations } = this.checkResourceLimits(pluginId);
3233
+ if (!withinLimits) {
3234
+ this.logger.warn("Resource limit violations detected", {
3235
+ pluginId,
3236
+ violations
3237
+ });
3238
+ }
3239
+ }
3240
+ /**
3241
+ * Get all active sandboxes
3242
+ */
3243
+ getAllSandboxes() {
3244
+ return new Map(this.sandboxes);
3245
+ }
3246
+ /**
3247
+ * Shutdown sandbox runtime
3248
+ */
3249
+ shutdown() {
3250
+ for (const pluginId of this.monitoringIntervals.keys()) {
3251
+ this.stopResourceMonitoring(pluginId);
3252
+ }
3253
+ this.sandboxes.clear();
3254
+ this.logger.info("Sandbox runtime shutdown complete");
3255
+ }
3256
+ };
3257
+
3258
+ // src/security/security-scanner.ts
3259
+ var PluginSecurityScanner = class {
3260
+ constructor(logger, config) {
3261
+ // Known vulnerabilities database (CVE cache)
3262
+ this.vulnerabilityDb = /* @__PURE__ */ new Map();
3263
+ // Scan results cache
3264
+ this.scanResults = /* @__PURE__ */ new Map();
3265
+ this.passThreshold = 70;
3266
+ this.logger = logger.child({ component: "SecurityScanner" });
3267
+ if (config?.passThreshold !== void 0) {
3268
+ this.passThreshold = config.passThreshold;
3269
+ }
3270
+ }
3271
+ /**
3272
+ * Perform a comprehensive security scan on a plugin
3273
+ */
3274
+ async scan(target) {
3275
+ this.logger.info("Starting security scan", {
3276
+ pluginId: target.pluginId,
3277
+ version: target.version
3278
+ });
3279
+ const issues = [];
3280
+ try {
3281
+ const codeIssues = await this.scanCode(target);
3282
+ issues.push(...codeIssues);
3283
+ const depIssues = await this.scanDependencies(target);
3284
+ issues.push(...depIssues);
3285
+ const malwareIssues = await this.scanMalware(target);
3286
+ issues.push(...malwareIssues);
3287
+ const licenseIssues = await this.scanLicenses(target);
3288
+ issues.push(...licenseIssues);
3289
+ const configIssues = await this.scanConfiguration(target);
3290
+ issues.push(...configIssues);
3291
+ const score = this.calculateSecurityScore(issues);
3292
+ const result = {
3293
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3294
+ scanner: { name: "ObjectStack Security Scanner", version: "1.0.0" },
3295
+ status: score >= this.passThreshold ? "passed" : "failed",
3296
+ vulnerabilities: issues.map((issue) => ({
3297
+ id: issue.id,
3298
+ severity: issue.severity,
3299
+ category: issue.category,
3300
+ title: issue.title,
3301
+ description: issue.description,
3302
+ location: issue.location ? `${issue.location.file}:${issue.location.line}` : void 0,
3303
+ remediation: issue.remediation,
3304
+ affectedVersions: [],
3305
+ exploitAvailable: false,
3306
+ patchAvailable: false
3307
+ })),
3308
+ summary: {
3309
+ totalVulnerabilities: issues.length,
3310
+ criticalCount: issues.filter((i) => i.severity === "critical").length,
3311
+ highCount: issues.filter((i) => i.severity === "high").length,
3312
+ mediumCount: issues.filter((i) => i.severity === "medium").length,
3313
+ lowCount: issues.filter((i) => i.severity === "low").length,
3314
+ infoCount: issues.filter((i) => i.severity === "info").length
3315
+ }
3316
+ };
3317
+ this.scanResults.set(`${target.pluginId}:${target.version}`, result);
3318
+ this.logger.info("Security scan complete", {
3319
+ pluginId: target.pluginId,
3320
+ score,
3321
+ status: result.status,
3322
+ summary: result.summary
3323
+ });
3324
+ return result;
3325
+ } catch (error) {
3326
+ this.logger.error("Security scan failed", {
3327
+ pluginId: target.pluginId,
3328
+ error
3329
+ });
3330
+ throw error;
3331
+ }
3332
+ }
3333
+ /**
3334
+ * Scan code for vulnerabilities
3335
+ */
3336
+ async scanCode(target) {
3337
+ const issues = [];
3338
+ this.logger.debug("Code scan complete", {
3339
+ pluginId: target.pluginId,
3340
+ issuesFound: issues.length
3341
+ });
3342
+ return issues;
3343
+ }
3344
+ /**
3345
+ * Scan dependencies for known vulnerabilities
3346
+ */
3347
+ async scanDependencies(target) {
3348
+ const issues = [];
3349
+ if (!target.dependencies) {
3350
+ return issues;
3351
+ }
3352
+ for (const [depName, version] of Object.entries(target.dependencies)) {
3353
+ const vulnKey = `${depName}@${version}`;
3354
+ const vulnerability = this.vulnerabilityDb.get(vulnKey);
3355
+ if (vulnerability) {
3356
+ issues.push({
3357
+ id: `vuln-${vulnerability.cve || depName}`,
3358
+ severity: vulnerability.severity,
3359
+ category: "vulnerability",
3360
+ title: `Vulnerable dependency: ${depName}`,
3361
+ description: `${depName}@${version} has known security vulnerabilities`,
3362
+ remediation: vulnerability.fixedIn ? `Upgrade to ${vulnerability.fixedIn.join(" or ")}` : "No fix available",
3363
+ cve: vulnerability.cve
3364
+ });
3365
+ }
3366
+ }
3367
+ this.logger.debug("Dependency scan complete", {
3368
+ pluginId: target.pluginId,
3369
+ dependencies: Object.keys(target.dependencies).length,
3370
+ vulnerabilities: issues.length
3371
+ });
3372
+ return issues;
3373
+ }
3374
+ /**
3375
+ * Scan for malware patterns
3376
+ */
3377
+ async scanMalware(target) {
3378
+ const issues = [];
3379
+ this.logger.debug("Malware scan complete", {
3380
+ pluginId: target.pluginId,
3381
+ issuesFound: issues.length
3382
+ });
3383
+ return issues;
3384
+ }
3385
+ /**
3386
+ * Check license compliance
3387
+ */
3388
+ async scanLicenses(target) {
3389
+ const issues = [];
3390
+ if (!target.dependencies) {
3391
+ return issues;
3392
+ }
3393
+ this.logger.debug("License scan complete", {
3394
+ pluginId: target.pluginId,
3395
+ issuesFound: issues.length
3396
+ });
3397
+ return issues;
3398
+ }
3399
+ /**
3400
+ * Check configuration security
3401
+ */
3402
+ async scanConfiguration(target) {
3403
+ const issues = [];
3404
+ this.logger.debug("Configuration scan complete", {
3405
+ pluginId: target.pluginId,
3406
+ issuesFound: issues.length
3407
+ });
3408
+ return issues;
3409
+ }
3410
+ /**
3411
+ * Calculate security score based on issues
3412
+ */
3413
+ calculateSecurityScore(issues) {
3414
+ let score = 100;
3415
+ for (const issue of issues) {
3416
+ switch (issue.severity) {
3417
+ case "critical":
3418
+ score -= 20;
3419
+ break;
3420
+ case "high":
3421
+ score -= 10;
3422
+ break;
3423
+ case "medium":
3424
+ score -= 5;
3425
+ break;
3426
+ case "low":
3427
+ score -= 2;
3428
+ break;
3429
+ case "info":
3430
+ score -= 0;
3431
+ break;
3432
+ }
3433
+ }
3434
+ return Math.max(0, score);
3435
+ }
3436
+ /**
3437
+ * Add a vulnerability to the database
3438
+ */
3439
+ addVulnerability(packageName, version, vulnerability) {
3440
+ const key = `${packageName}@${version}`;
3441
+ this.vulnerabilityDb.set(key, vulnerability);
3442
+ this.logger.debug("Vulnerability added to database", {
3443
+ package: packageName,
3444
+ version,
3445
+ cve: vulnerability.cve
3446
+ });
3447
+ }
3448
+ /**
3449
+ * Get scan result from cache
3450
+ */
3451
+ getScanResult(pluginId, version) {
3452
+ return this.scanResults.get(`${pluginId}:${version}`);
3453
+ }
3454
+ /**
3455
+ * Clear scan results cache
3456
+ */
3457
+ clearCache() {
3458
+ this.scanResults.clear();
3459
+ this.logger.debug("Scan results cache cleared");
3460
+ }
3461
+ /**
3462
+ * Update vulnerability database from external source
3463
+ */
3464
+ async updateVulnerabilityDatabase() {
3465
+ this.logger.info("Updating vulnerability database");
3466
+ this.logger.info("Vulnerability database updated", {
3467
+ entries: this.vulnerabilityDb.size
3468
+ });
3469
+ }
3470
+ /**
3471
+ * Shutdown security scanner
3472
+ */
3473
+ shutdown() {
3474
+ this.vulnerabilityDb.clear();
3475
+ this.scanResults.clear();
3476
+ this.logger.info("Security scanner shutdown complete");
3477
+ }
3478
+ };
3479
+
3480
+ // src/health-monitor.ts
3481
+ var PluginHealthMonitor = class {
3482
+ constructor(logger) {
3483
+ this.healthChecks = /* @__PURE__ */ new Map();
3484
+ this.healthStatus = /* @__PURE__ */ new Map();
3485
+ this.healthReports = /* @__PURE__ */ new Map();
3486
+ this.checkIntervals = /* @__PURE__ */ new Map();
3487
+ this.failureCounters = /* @__PURE__ */ new Map();
3488
+ this.successCounters = /* @__PURE__ */ new Map();
3489
+ this.restartAttempts = /* @__PURE__ */ new Map();
3490
+ this.logger = logger.child({ component: "HealthMonitor" });
3491
+ }
3492
+ /**
3493
+ * Register a plugin for health monitoring
3494
+ */
3495
+ registerPlugin(pluginName, config) {
3496
+ this.healthChecks.set(pluginName, config);
3497
+ this.healthStatus.set(pluginName, "unknown");
3498
+ this.failureCounters.set(pluginName, 0);
3499
+ this.successCounters.set(pluginName, 0);
3500
+ this.restartAttempts.set(pluginName, 0);
3501
+ this.logger.info("Plugin registered for health monitoring", {
3502
+ plugin: pluginName,
3503
+ interval: config.interval
3504
+ });
3505
+ }
3506
+ /**
3507
+ * Start monitoring a plugin
3508
+ */
3509
+ startMonitoring(pluginName, plugin) {
3510
+ const config = this.healthChecks.get(pluginName);
3511
+ if (!config) {
3512
+ this.logger.warn("Cannot start monitoring - plugin not registered", { plugin: pluginName });
3513
+ return;
3514
+ }
3515
+ this.stopMonitoring(pluginName);
3516
+ const interval = setInterval(() => {
3517
+ this.performHealthCheck(pluginName, plugin, config).catch((error) => {
3518
+ this.logger.error("Health check failed with error", {
3519
+ plugin: pluginName,
3520
+ error
3521
+ });
3522
+ });
3523
+ }, config.interval);
3524
+ this.checkIntervals.set(pluginName, interval);
3525
+ this.logger.info("Health monitoring started", { plugin: pluginName });
3526
+ this.performHealthCheck(pluginName, plugin, config).catch((error) => {
3527
+ this.logger.error("Initial health check failed", {
3528
+ plugin: pluginName,
3529
+ error
3530
+ });
3531
+ });
3532
+ }
3533
+ /**
3534
+ * Stop monitoring a plugin
3535
+ */
3536
+ stopMonitoring(pluginName) {
3537
+ const interval = this.checkIntervals.get(pluginName);
3538
+ if (interval) {
3539
+ clearInterval(interval);
3540
+ this.checkIntervals.delete(pluginName);
3541
+ this.logger.info("Health monitoring stopped", { plugin: pluginName });
3542
+ }
3543
+ }
3544
+ /**
3545
+ * Perform a health check on a plugin
3546
+ */
3547
+ async performHealthCheck(pluginName, plugin, config) {
3548
+ const startTime = Date.now();
3549
+ let status = "healthy";
3550
+ let message;
3551
+ const checks = [];
3552
+ try {
3553
+ if (config.checkMethod && typeof plugin[config.checkMethod] === "function") {
3554
+ const checkResult = await Promise.race([
3555
+ plugin[config.checkMethod](),
3556
+ this.timeout(config.timeout, `Health check timeout after ${config.timeout}ms`)
3557
+ ]);
3558
+ if (checkResult === false || checkResult && checkResult.status === "unhealthy") {
3559
+ status = "unhealthy";
3560
+ message = checkResult?.message || "Custom health check failed";
3561
+ checks.push({ name: config.checkMethod, status: "failed", message });
3562
+ } else {
3563
+ checks.push({ name: config.checkMethod, status: "passed" });
3564
+ }
3565
+ } else {
3566
+ checks.push({ name: "plugin-loaded", status: "passed" });
3567
+ }
3568
+ if (status === "healthy") {
3569
+ this.successCounters.set(pluginName, (this.successCounters.get(pluginName) || 0) + 1);
3570
+ this.failureCounters.set(pluginName, 0);
3571
+ const currentStatus = this.healthStatus.get(pluginName);
3572
+ if (currentStatus === "unhealthy" || currentStatus === "degraded") {
3573
+ const successCount = this.successCounters.get(pluginName) || 0;
3574
+ if (successCount >= config.successThreshold) {
3575
+ this.healthStatus.set(pluginName, "healthy");
3576
+ this.logger.info("Plugin recovered to healthy state", { plugin: pluginName });
3577
+ } else {
3578
+ this.healthStatus.set(pluginName, "recovering");
3579
+ }
3580
+ } else {
3581
+ this.healthStatus.set(pluginName, "healthy");
3582
+ }
3583
+ } else {
3584
+ this.failureCounters.set(pluginName, (this.failureCounters.get(pluginName) || 0) + 1);
3585
+ this.successCounters.set(pluginName, 0);
3586
+ const failureCount = this.failureCounters.get(pluginName) || 0;
3587
+ if (failureCount >= config.failureThreshold) {
3588
+ this.healthStatus.set(pluginName, "unhealthy");
3589
+ this.logger.warn("Plugin marked as unhealthy", {
3590
+ plugin: pluginName,
3591
+ failures: failureCount
3592
+ });
3593
+ if (config.autoRestart) {
3594
+ await this.attemptRestart(pluginName, plugin, config);
3595
+ }
3596
+ } else {
3597
+ this.healthStatus.set(pluginName, "degraded");
3598
+ }
3599
+ }
3600
+ } catch (error) {
3601
+ status = "failed";
3602
+ message = error instanceof Error ? error.message : "Unknown error";
3603
+ this.failureCounters.set(pluginName, (this.failureCounters.get(pluginName) || 0) + 1);
3604
+ this.healthStatus.set(pluginName, "failed");
3605
+ checks.push({
3606
+ name: "health-check",
3607
+ status: "failed",
3608
+ message
3609
+ });
3610
+ this.logger.error("Health check exception", {
3611
+ plugin: pluginName,
3612
+ error
3613
+ });
3614
+ }
3615
+ const report = {
3616
+ status: this.healthStatus.get(pluginName) || "unknown",
3617
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3618
+ message,
3619
+ metrics: {
3620
+ uptime: Date.now() - startTime
3621
+ },
3622
+ checks: checks.length > 0 ? checks : void 0
3623
+ };
3624
+ this.healthReports.set(pluginName, report);
3625
+ }
3626
+ /**
3627
+ * Attempt to restart a plugin
3628
+ */
3629
+ async attemptRestart(pluginName, plugin, config) {
3630
+ const attempts = this.restartAttempts.get(pluginName) || 0;
3631
+ if (attempts >= config.maxRestartAttempts) {
3632
+ this.logger.error("Max restart attempts reached, giving up", {
3633
+ plugin: pluginName,
3634
+ attempts
3635
+ });
3636
+ this.healthStatus.set(pluginName, "failed");
3637
+ return;
3638
+ }
3639
+ this.restartAttempts.set(pluginName, attempts + 1);
3640
+ const delay = this.calculateBackoff(attempts, config.restartBackoff);
3641
+ this.logger.info("Scheduling plugin restart", {
3642
+ plugin: pluginName,
3643
+ attempt: attempts + 1,
3644
+ delay
3645
+ });
3646
+ await new Promise((resolve) => setTimeout(resolve, delay));
3647
+ try {
3648
+ if (plugin.destroy) {
3649
+ await plugin.destroy();
3650
+ }
3651
+ this.logger.info("Plugin restarted", { plugin: pluginName });
3652
+ this.failureCounters.set(pluginName, 0);
3653
+ this.successCounters.set(pluginName, 0);
3654
+ this.healthStatus.set(pluginName, "recovering");
3655
+ } catch (error) {
3656
+ this.logger.error("Plugin restart failed", {
3657
+ plugin: pluginName,
3658
+ error
3659
+ });
3660
+ this.healthStatus.set(pluginName, "failed");
3661
+ }
3662
+ }
3663
+ /**
3664
+ * Calculate backoff delay for restarts
3665
+ */
3666
+ calculateBackoff(attempt, strategy) {
3667
+ const baseDelay = 1e3;
3668
+ switch (strategy) {
3669
+ case "fixed":
3670
+ return baseDelay;
3671
+ case "linear":
3672
+ return baseDelay * (attempt + 1);
3673
+ case "exponential":
3674
+ return baseDelay * Math.pow(2, attempt);
3675
+ default:
3676
+ return baseDelay;
3677
+ }
3678
+ }
3679
+ /**
3680
+ * Get current health status of a plugin
3681
+ */
3682
+ getHealthStatus(pluginName) {
3683
+ return this.healthStatus.get(pluginName);
3684
+ }
3685
+ /**
3686
+ * Get latest health report for a plugin
3687
+ */
3688
+ getHealthReport(pluginName) {
3689
+ return this.healthReports.get(pluginName);
3690
+ }
3691
+ /**
3692
+ * Get all health statuses
3693
+ */
3694
+ getAllHealthStatuses() {
3695
+ return new Map(this.healthStatus);
3696
+ }
3697
+ /**
3698
+ * Shutdown health monitor
3699
+ */
3700
+ shutdown() {
3701
+ for (const pluginName of this.checkIntervals.keys()) {
3702
+ this.stopMonitoring(pluginName);
3703
+ }
3704
+ this.healthChecks.clear();
3705
+ this.healthStatus.clear();
3706
+ this.healthReports.clear();
3707
+ this.failureCounters.clear();
3708
+ this.successCounters.clear();
3709
+ this.restartAttempts.clear();
3710
+ this.logger.info("Health monitor shutdown complete");
3711
+ }
3712
+ /**
3713
+ * Timeout helper
3714
+ */
3715
+ timeout(ms, message) {
3716
+ return new Promise((_, reject) => {
3717
+ setTimeout(() => reject(new Error(message)), ms);
3718
+ });
3719
+ }
3720
+ };
3721
+
3722
+ // src/hot-reload.ts
3723
+ var generateUUID = () => {
3724
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
3725
+ return crypto.randomUUID();
3726
+ }
3727
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
3728
+ const r = Math.random() * 16 | 0;
3729
+ const v = c === "x" ? r : r & 3 | 8;
3730
+ return v.toString(16);
3731
+ });
3732
+ };
3733
+ var PluginStateManager = class {
3734
+ constructor(logger) {
3735
+ this.stateSnapshots = /* @__PURE__ */ new Map();
3736
+ this.memoryStore = /* @__PURE__ */ new Map();
3737
+ this.logger = logger.child({ component: "StateManager" });
3738
+ }
3739
+ /**
3740
+ * Save plugin state before reload
3741
+ */
3742
+ async saveState(pluginId, version, state, config) {
3743
+ const snapshot = {
3744
+ pluginId,
3745
+ version,
3746
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3747
+ state,
3748
+ metadata: {
3749
+ checksum: this.calculateChecksum(state),
3750
+ compressed: false
3751
+ }
3752
+ };
3753
+ const snapshotId = generateUUID();
3754
+ switch (config.stateStrategy) {
3755
+ case "memory":
3756
+ this.memoryStore.set(snapshotId, snapshot);
3757
+ this.logger.debug("State saved to memory", { pluginId, snapshotId });
3758
+ break;
3759
+ case "disk":
3760
+ this.memoryStore.set(snapshotId, snapshot);
3761
+ this.logger.debug("State saved to disk (memory fallback)", { pluginId, snapshotId });
3762
+ break;
3763
+ case "distributed":
3764
+ this.memoryStore.set(snapshotId, snapshot);
3765
+ this.logger.debug("State saved to distributed store (memory fallback)", {
3766
+ pluginId,
3767
+ snapshotId
3768
+ });
3769
+ break;
3770
+ case "none":
3771
+ this.logger.debug("State persistence disabled", { pluginId });
3772
+ break;
3773
+ }
3774
+ this.stateSnapshots.set(pluginId, snapshot);
3775
+ return snapshotId;
3776
+ }
3777
+ /**
3778
+ * Restore plugin state after reload
3779
+ */
3780
+ async restoreState(pluginId, snapshotId) {
3781
+ let snapshot;
3782
+ if (snapshotId) {
3783
+ snapshot = this.memoryStore.get(snapshotId);
3784
+ } else {
3785
+ snapshot = this.stateSnapshots.get(pluginId);
3786
+ }
3787
+ if (!snapshot) {
3788
+ this.logger.warn("No state snapshot found", { pluginId, snapshotId });
3789
+ return void 0;
3790
+ }
3791
+ if (snapshot.metadata?.checksum) {
3792
+ const currentChecksum = this.calculateChecksum(snapshot.state);
3793
+ if (currentChecksum !== snapshot.metadata.checksum) {
3794
+ this.logger.error("State checksum mismatch - data may be corrupted", {
3795
+ pluginId,
3796
+ expected: snapshot.metadata.checksum,
3797
+ actual: currentChecksum
3798
+ });
3799
+ return void 0;
3800
+ }
3801
+ }
3802
+ this.logger.debug("State restored", { pluginId, version: snapshot.version });
3803
+ return snapshot.state;
3804
+ }
3805
+ /**
3806
+ * Clear state for a plugin
3807
+ */
3808
+ clearState(pluginId) {
3809
+ this.stateSnapshots.delete(pluginId);
3810
+ this.logger.debug("State cleared", { pluginId });
3811
+ }
3812
+ /**
3813
+ * Calculate simple checksum for state verification
3814
+ * WARNING: This is a simple hash for demo purposes.
3815
+ * In production, use a cryptographic hash like SHA-256.
3816
+ */
3817
+ calculateChecksum(state) {
3818
+ const stateStr = JSON.stringify(state);
3819
+ let hash = 0;
3820
+ for (let i = 0; i < stateStr.length; i++) {
3821
+ const char = stateStr.charCodeAt(i);
3822
+ hash = (hash << 5) - hash + char;
3823
+ hash = hash & hash;
3824
+ }
3825
+ return hash.toString(16);
3826
+ }
3827
+ /**
3828
+ * Shutdown state manager
3829
+ */
3830
+ shutdown() {
3831
+ this.stateSnapshots.clear();
3832
+ this.memoryStore.clear();
3833
+ this.logger.info("State manager shutdown complete");
3834
+ }
3835
+ };
3836
+ var HotReloadManager = class {
3837
+ constructor(logger) {
3838
+ this.reloadConfigs = /* @__PURE__ */ new Map();
3839
+ this.watchHandles = /* @__PURE__ */ new Map();
3840
+ this.reloadTimers = /* @__PURE__ */ new Map();
3841
+ this.logger = logger.child({ component: "HotReload" });
3842
+ this.stateManager = new PluginStateManager(logger);
3843
+ }
3844
+ /**
3845
+ * Register a plugin for hot reload
3846
+ */
3847
+ registerPlugin(pluginName, config) {
3848
+ if (!config.enabled) {
3849
+ this.logger.debug("Hot reload disabled for plugin", { plugin: pluginName });
3850
+ return;
3851
+ }
3852
+ this.reloadConfigs.set(pluginName, config);
3853
+ this.logger.info("Plugin registered for hot reload", {
3854
+ plugin: pluginName,
3855
+ watchPatterns: config.watchPatterns,
3856
+ stateStrategy: config.stateStrategy
3857
+ });
3858
+ }
3859
+ /**
3860
+ * Start watching for changes (requires file system integration)
3861
+ */
3862
+ startWatching(pluginName) {
3863
+ const config = this.reloadConfigs.get(pluginName);
3864
+ if (!config || !config.enabled) {
3865
+ return;
3866
+ }
3867
+ this.logger.info("File watching started", {
3868
+ plugin: pluginName,
3869
+ patterns: config.watchPatterns
3870
+ });
3871
+ }
3872
+ /**
3873
+ * Stop watching for changes
3874
+ */
3875
+ stopWatching(pluginName) {
3876
+ const handle = this.watchHandles.get(pluginName);
3877
+ if (handle) {
3878
+ this.watchHandles.delete(pluginName);
3879
+ this.logger.info("File watching stopped", { plugin: pluginName });
3880
+ }
3881
+ const timer = this.reloadTimers.get(pluginName);
3882
+ if (timer) {
3883
+ clearTimeout(timer);
3884
+ this.reloadTimers.delete(pluginName);
3885
+ }
3886
+ }
3887
+ /**
3888
+ * Trigger hot reload for a plugin
3889
+ */
3890
+ async reloadPlugin(pluginName, plugin, version, getPluginState, restorePluginState) {
3891
+ const config = this.reloadConfigs.get(pluginName);
3892
+ if (!config) {
3893
+ this.logger.warn("Cannot reload - plugin not registered", { plugin: pluginName });
3894
+ return false;
3895
+ }
3896
+ this.logger.info("Starting hot reload", { plugin: pluginName });
3897
+ try {
3898
+ if (config.beforeReload) {
3899
+ this.logger.debug("Executing before reload hooks", {
3900
+ plugin: pluginName,
3901
+ hooks: config.beforeReload
3902
+ });
3903
+ }
3904
+ let snapshotId;
3905
+ if (config.preserveState && config.stateStrategy !== "none") {
3906
+ const state = getPluginState();
3907
+ snapshotId = await this.stateManager.saveState(
3908
+ pluginName,
3909
+ version,
3910
+ state,
3911
+ config
3912
+ );
3913
+ this.logger.debug("Plugin state saved", { plugin: pluginName, snapshotId });
3914
+ }
3915
+ if (plugin.destroy) {
3916
+ this.logger.debug("Destroying plugin", { plugin: pluginName });
3917
+ const shutdownPromise = plugin.destroy();
3918
+ const timeoutPromise = new Promise((_, reject) => {
3919
+ setTimeout(() => reject(new Error("Shutdown timeout")), config.shutdownTimeout);
3920
+ });
3921
+ await Promise.race([shutdownPromise, timeoutPromise]);
3922
+ this.logger.debug("Plugin destroyed successfully", { plugin: pluginName });
3923
+ }
3924
+ this.logger.debug("Plugin module would be reloaded here", { plugin: pluginName });
3925
+ if (snapshotId && config.preserveState) {
3926
+ const restoredState = await this.stateManager.restoreState(pluginName, snapshotId);
3927
+ if (restoredState) {
3928
+ restorePluginState(restoredState);
3929
+ this.logger.debug("Plugin state restored", { plugin: pluginName });
3930
+ }
3931
+ }
3932
+ if (config.afterReload) {
3933
+ this.logger.debug("Executing after reload hooks", {
3934
+ plugin: pluginName,
3935
+ hooks: config.afterReload
3936
+ });
3937
+ }
3938
+ this.logger.info("Hot reload completed successfully", { plugin: pluginName });
3939
+ return true;
3940
+ } catch (error) {
3941
+ this.logger.error("Hot reload failed", {
3942
+ plugin: pluginName,
3943
+ error
3944
+ });
3945
+ return false;
3946
+ }
3947
+ }
3948
+ /**
3949
+ * Schedule a reload with debouncing
3950
+ */
3951
+ scheduleReload(pluginName, reloadFn) {
3952
+ const config = this.reloadConfigs.get(pluginName);
3953
+ if (!config) {
3954
+ return;
3955
+ }
3956
+ const existingTimer = this.reloadTimers.get(pluginName);
3957
+ if (existingTimer) {
3958
+ clearTimeout(existingTimer);
3959
+ }
3960
+ const timer = setTimeout(() => {
3961
+ this.logger.debug("Debounce period elapsed, executing reload", {
3962
+ plugin: pluginName
3963
+ });
3964
+ reloadFn().catch((error) => {
3965
+ this.logger.error("Scheduled reload failed", {
3966
+ plugin: pluginName,
3967
+ error
3968
+ });
3969
+ });
3970
+ this.reloadTimers.delete(pluginName);
3971
+ }, config.debounceDelay);
3972
+ this.reloadTimers.set(pluginName, timer);
3973
+ this.logger.debug("Reload scheduled with debounce", {
3974
+ plugin: pluginName,
3975
+ delay: config.debounceDelay
3976
+ });
3977
+ }
3978
+ /**
3979
+ * Get state manager for direct access
3980
+ */
3981
+ getStateManager() {
3982
+ return this.stateManager;
3983
+ }
3984
+ /**
3985
+ * Shutdown hot reload manager
3986
+ */
3987
+ shutdown() {
3988
+ for (const pluginName of this.watchHandles.keys()) {
3989
+ this.stopWatching(pluginName);
3990
+ }
3991
+ for (const timer of this.reloadTimers.values()) {
3992
+ clearTimeout(timer);
3993
+ }
3994
+ this.reloadConfigs.clear();
3995
+ this.watchHandles.clear();
3996
+ this.reloadTimers.clear();
3997
+ this.stateManager.shutdown();
3998
+ this.logger.info("Hot reload manager shutdown complete");
3999
+ }
4000
+ };
4001
+
4002
+ // src/dependency-resolver.ts
4003
+ var SemanticVersionManager = class {
4004
+ /**
4005
+ * Parse a version string into semantic version components
4006
+ */
4007
+ static parse(versionStr) {
4008
+ const cleanVersion = versionStr.replace(/^v/, "");
4009
+ const match = cleanVersion.match(
4010
+ /^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9.-]+))?(?:\+([a-zA-Z0-9.-]+))?$/
4011
+ );
4012
+ if (!match) {
4013
+ throw new Error(`Invalid semantic version: ${versionStr}`);
4014
+ }
4015
+ return {
4016
+ major: parseInt(match[1], 10),
4017
+ minor: parseInt(match[2], 10),
4018
+ patch: parseInt(match[3], 10),
4019
+ preRelease: match[4],
4020
+ build: match[5]
4021
+ };
4022
+ }
4023
+ /**
4024
+ * Convert semantic version back to string
4025
+ */
4026
+ static toString(version) {
4027
+ let str = `${version.major}.${version.minor}.${version.patch}`;
4028
+ if (version.preRelease) {
4029
+ str += `-${version.preRelease}`;
4030
+ }
4031
+ if (version.build) {
4032
+ str += `+${version.build}`;
4033
+ }
4034
+ return str;
4035
+ }
4036
+ /**
4037
+ * Compare two semantic versions
4038
+ * Returns: -1 if a < b, 0 if a === b, 1 if a > b
4039
+ */
4040
+ static compare(a, b) {
4041
+ if (a.major !== b.major) return a.major - b.major;
4042
+ if (a.minor !== b.minor) return a.minor - b.minor;
4043
+ if (a.patch !== b.patch) return a.patch - b.patch;
4044
+ if (a.preRelease && !b.preRelease) return -1;
4045
+ if (!a.preRelease && b.preRelease) return 1;
4046
+ if (a.preRelease && b.preRelease) {
4047
+ return a.preRelease.localeCompare(b.preRelease);
4048
+ }
4049
+ return 0;
4050
+ }
4051
+ /**
4052
+ * Check if version satisfies constraint
4053
+ */
4054
+ static satisfies(version, constraint) {
4055
+ const constraintStr = constraint;
4056
+ if (constraintStr === "*" || constraintStr === "latest") {
4057
+ return true;
4058
+ }
4059
+ if (/^[\d.]+$/.test(constraintStr)) {
4060
+ const exact = this.parse(constraintStr);
4061
+ return this.compare(version, exact) === 0;
4062
+ }
4063
+ if (constraintStr.startsWith("^")) {
4064
+ const base = this.parse(constraintStr.slice(1));
4065
+ return version.major === base.major && this.compare(version, base) >= 0;
4066
+ }
4067
+ if (constraintStr.startsWith("~")) {
4068
+ const base = this.parse(constraintStr.slice(1));
4069
+ return version.major === base.major && version.minor === base.minor && this.compare(version, base) >= 0;
4070
+ }
4071
+ if (constraintStr.startsWith(">=")) {
4072
+ const base = this.parse(constraintStr.slice(2));
4073
+ return this.compare(version, base) >= 0;
4074
+ }
4075
+ if (constraintStr.startsWith(">")) {
4076
+ const base = this.parse(constraintStr.slice(1));
4077
+ return this.compare(version, base) > 0;
4078
+ }
4079
+ if (constraintStr.startsWith("<=")) {
4080
+ const base = this.parse(constraintStr.slice(2));
4081
+ return this.compare(version, base) <= 0;
4082
+ }
4083
+ if (constraintStr.startsWith("<")) {
4084
+ const base = this.parse(constraintStr.slice(1));
4085
+ return this.compare(version, base) < 0;
4086
+ }
4087
+ const rangeMatch = constraintStr.match(/^([\d.]+)\s*-\s*([\d.]+)$/);
4088
+ if (rangeMatch) {
4089
+ const min = this.parse(rangeMatch[1]);
4090
+ const max = this.parse(rangeMatch[2]);
4091
+ return this.compare(version, min) >= 0 && this.compare(version, max) <= 0;
4092
+ }
4093
+ return false;
4094
+ }
4095
+ /**
4096
+ * Determine compatibility level between two versions
4097
+ */
4098
+ static getCompatibilityLevel(from, to) {
4099
+ const cmp = this.compare(from, to);
4100
+ if (cmp === 0) {
4101
+ return "fully-compatible";
4102
+ }
4103
+ if (from.major !== to.major) {
4104
+ return "breaking-changes";
4105
+ }
4106
+ if (from.minor < to.minor) {
4107
+ return "backward-compatible";
4108
+ }
4109
+ if (from.patch < to.patch) {
4110
+ return "fully-compatible";
4111
+ }
4112
+ return "incompatible";
4113
+ }
4114
+ };
4115
+ var DependencyResolver = class {
4116
+ constructor(logger) {
4117
+ this.logger = logger.child({ component: "DependencyResolver" });
4118
+ }
4119
+ /**
4120
+ * Resolve dependencies using topological sort
4121
+ */
4122
+ resolve(plugins) {
4123
+ const graph = /* @__PURE__ */ new Map();
4124
+ const inDegree = /* @__PURE__ */ new Map();
4125
+ for (const [pluginName, pluginInfo] of plugins) {
4126
+ if (!graph.has(pluginName)) {
4127
+ graph.set(pluginName, []);
4128
+ inDegree.set(pluginName, 0);
4129
+ }
4130
+ const deps = pluginInfo.dependencies || [];
4131
+ for (const dep of deps) {
4132
+ if (!plugins.has(dep)) {
4133
+ throw new Error(`Missing dependency: ${pluginName} requires ${dep}`);
4134
+ }
4135
+ if (!graph.has(dep)) {
4136
+ graph.set(dep, []);
4137
+ inDegree.set(dep, 0);
4138
+ }
4139
+ graph.get(dep).push(pluginName);
4140
+ inDegree.set(pluginName, (inDegree.get(pluginName) || 0) + 1);
4141
+ }
4142
+ }
4143
+ const queue = [];
4144
+ const result = [];
4145
+ for (const [node, degree] of inDegree) {
4146
+ if (degree === 0) {
4147
+ queue.push(node);
4148
+ }
4149
+ }
4150
+ while (queue.length > 0) {
4151
+ const node = queue.shift();
4152
+ result.push(node);
4153
+ const dependents = graph.get(node) || [];
4154
+ for (const dependent of dependents) {
4155
+ const newDegree = (inDegree.get(dependent) || 0) - 1;
4156
+ inDegree.set(dependent, newDegree);
4157
+ if (newDegree === 0) {
4158
+ queue.push(dependent);
4159
+ }
4160
+ }
4161
+ }
4162
+ if (result.length !== plugins.size) {
4163
+ const remaining = Array.from(plugins.keys()).filter((p) => !result.includes(p));
4164
+ this.logger.error("Circular dependency detected", { remaining });
4165
+ throw new Error(`Circular dependency detected among: ${remaining.join(", ")}`);
4166
+ }
4167
+ this.logger.debug("Dependencies resolved", { order: result });
4168
+ return result;
4169
+ }
4170
+ /**
4171
+ * Detect dependency conflicts
4172
+ */
4173
+ detectConflicts(plugins) {
4174
+ const conflicts = [];
4175
+ const versionRequirements = /* @__PURE__ */ new Map();
4176
+ for (const [pluginName, pluginInfo] of plugins) {
4177
+ if (!pluginInfo.dependencies) continue;
4178
+ for (const [depName, constraint] of Object.entries(pluginInfo.dependencies)) {
4179
+ if (!versionRequirements.has(depName)) {
4180
+ versionRequirements.set(depName, /* @__PURE__ */ new Map());
4181
+ }
4182
+ versionRequirements.get(depName).set(pluginName, constraint);
4183
+ }
4184
+ }
4185
+ for (const [depName, requirements] of versionRequirements) {
4186
+ const depInfo = plugins.get(depName);
4187
+ if (!depInfo) continue;
4188
+ const depVersion = SemanticVersionManager.parse(depInfo.version);
4189
+ const unsatisfied = [];
4190
+ for (const [requiringPlugin, constraint] of requirements) {
4191
+ if (!SemanticVersionManager.satisfies(depVersion, constraint)) {
4192
+ unsatisfied.push({
4193
+ pluginId: requiringPlugin,
4194
+ version: constraint
4195
+ });
4196
+ }
4197
+ }
4198
+ if (unsatisfied.length > 0) {
4199
+ conflicts.push({
4200
+ type: "version-mismatch",
4201
+ severity: "error",
4202
+ description: `Version mismatch for ${depName}: detected ${unsatisfied.length} unsatisfied requirements`,
4203
+ plugins: [
4204
+ { pluginId: depName, version: depInfo.version },
4205
+ ...unsatisfied
4206
+ ],
4207
+ resolutions: [{
4208
+ strategy: "upgrade",
4209
+ description: `Upgrade ${depName} to satisfy all constraints`,
4210
+ targetPlugins: [depName],
4211
+ automatic: false
4212
+ }]
4213
+ });
4214
+ }
4215
+ }
4216
+ try {
4217
+ this.resolve(new Map(
4218
+ Array.from(plugins.entries()).map(([name, info]) => [
4219
+ name,
4220
+ { version: info.version, dependencies: info.dependencies ? Object.keys(info.dependencies) : [] }
4221
+ ])
4222
+ ));
4223
+ } catch (error) {
4224
+ if (error instanceof Error && error.message.includes("Circular dependency")) {
4225
+ conflicts.push({
4226
+ type: "circular-dependency",
4227
+ severity: "critical",
4228
+ description: error.message,
4229
+ plugins: [],
4230
+ // Would need to extract from error
4231
+ resolutions: [{
4232
+ strategy: "manual",
4233
+ description: "Remove circular dependency by restructuring plugins",
4234
+ automatic: false
4235
+ }]
4236
+ });
4237
+ }
4238
+ }
4239
+ return conflicts;
4240
+ }
4241
+ /**
4242
+ * Find best version that satisfies all constraints
4243
+ */
4244
+ findBestVersion(availableVersions, constraints) {
4245
+ const versions = availableVersions.map((v) => ({ str: v, parsed: SemanticVersionManager.parse(v) })).sort((a, b) => -SemanticVersionManager.compare(a.parsed, b.parsed));
4246
+ for (const version of versions) {
4247
+ const satisfiesAll = constraints.every(
4248
+ (constraint) => SemanticVersionManager.satisfies(version.parsed, constraint)
4249
+ );
4250
+ if (satisfiesAll) {
4251
+ return version.str;
4252
+ }
4253
+ }
4254
+ return void 0;
4255
+ }
4256
+ /**
4257
+ * Check if dependencies form a valid DAG (no cycles)
4258
+ */
4259
+ isAcyclic(dependencies) {
4260
+ try {
4261
+ const plugins = new Map(
4262
+ Array.from(dependencies.entries()).map(([name, deps]) => [
4263
+ name,
4264
+ { dependencies: deps }
4265
+ ])
4266
+ );
4267
+ this.resolve(plugins);
4268
+ return true;
4269
+ } catch {
4270
+ return false;
4271
+ }
4272
+ }
4273
+ };
4274
+ // Annotate the CommonJS export names for ESM import in node:
4275
+ 0 && (module.exports = {
4276
+ ApiRegistry,
4277
+ DependencyResolver,
4278
+ HotReloadManager,
4279
+ LiteKernel,
4280
+ ObjectKernel,
4281
+ ObjectKernelBase,
4282
+ ObjectLogger,
4283
+ PluginConfigValidator,
4284
+ PluginHealthMonitor,
4285
+ PluginLoader,
4286
+ PluginPermissionEnforcer,
4287
+ PluginPermissionManager,
4288
+ PluginSandboxRuntime,
4289
+ PluginSecurityScanner,
4290
+ PluginSignatureVerifier,
4291
+ QA,
4292
+ SecurePluginContext,
4293
+ SemanticVersionManager,
4294
+ ServiceLifecycle,
4295
+ createApiRegistryPlugin,
4296
+ createLogger,
4297
+ createPluginConfigValidator,
4298
+ createPluginPermissionEnforcer,
4299
+ getEnv,
4300
+ getMemoryUsage,
4301
+ isNode,
4302
+ safeExit
4303
+ });
4304
+ //# sourceMappingURL=index.cjs.map