@objectstack/core 1.0.2 → 1.0.5

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