@objectstack/core 0.6.1 → 0.7.1

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 (56) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/ENHANCED_FEATURES.md +380 -0
  3. package/README.md +299 -12
  4. package/dist/contracts/data-engine.d.ts +39 -22
  5. package/dist/contracts/data-engine.d.ts.map +1 -1
  6. package/dist/contracts/logger.d.ts +63 -0
  7. package/dist/contracts/logger.d.ts.map +1 -0
  8. package/dist/contracts/logger.js +1 -0
  9. package/dist/enhanced-kernel.d.ts +103 -0
  10. package/dist/enhanced-kernel.d.ts.map +1 -0
  11. package/dist/enhanced-kernel.js +403 -0
  12. package/dist/enhanced-kernel.test.d.ts +2 -0
  13. package/dist/enhanced-kernel.test.d.ts.map +1 -0
  14. package/dist/enhanced-kernel.test.js +412 -0
  15. package/dist/index.d.ts +11 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +10 -2
  18. package/dist/kernel-base.d.ts +84 -0
  19. package/dist/kernel-base.d.ts.map +1 -0
  20. package/dist/kernel-base.js +219 -0
  21. package/dist/kernel.d.ts +11 -18
  22. package/dist/kernel.d.ts.map +1 -1
  23. package/dist/kernel.js +43 -114
  24. package/dist/kernel.test.d.ts +2 -0
  25. package/dist/kernel.test.d.ts.map +1 -0
  26. package/dist/kernel.test.js +161 -0
  27. package/dist/logger.d.ts +70 -0
  28. package/dist/logger.d.ts.map +1 -0
  29. package/dist/logger.js +268 -0
  30. package/dist/logger.test.d.ts +2 -0
  31. package/dist/logger.test.d.ts.map +1 -0
  32. package/dist/logger.test.js +92 -0
  33. package/dist/plugin-loader.d.ts +148 -0
  34. package/dist/plugin-loader.d.ts.map +1 -0
  35. package/dist/plugin-loader.js +287 -0
  36. package/dist/plugin-loader.test.d.ts +2 -0
  37. package/dist/plugin-loader.test.d.ts.map +1 -0
  38. package/dist/plugin-loader.test.js +339 -0
  39. package/dist/types.d.ts +2 -1
  40. package/dist/types.d.ts.map +1 -1
  41. package/examples/enhanced-kernel-example.ts +309 -0
  42. package/package.json +19 -4
  43. package/src/contracts/data-engine.ts +46 -24
  44. package/src/contracts/logger.ts +70 -0
  45. package/src/enhanced-kernel.test.ts +535 -0
  46. package/src/enhanced-kernel.ts +496 -0
  47. package/src/index.ts +23 -2
  48. package/src/kernel-base.ts +256 -0
  49. package/src/kernel.test.ts +200 -0
  50. package/src/kernel.ts +55 -129
  51. package/src/logger.test.ts +116 -0
  52. package/src/logger.ts +306 -0
  53. package/src/plugin-loader.test.ts +412 -0
  54. package/src/plugin-loader.ts +435 -0
  55. package/src/types.ts +2 -1
  56. package/vitest.config.ts +8 -0
package/src/kernel.ts CHANGED
@@ -1,4 +1,7 @@
1
- import { Plugin, PluginContext } from './types.js';
1
+ import { Plugin } from './types.js';
2
+ import { createLogger, ObjectLogger } from './logger.js';
3
+ import type { LoggerConfig } from '@objectstack/spec/system';
4
+ import { ObjectKernelBase } from './kernel-base.js';
2
5
 
3
6
  /**
4
7
  * ObjectKernel - MiniKernel Architecture
@@ -8,63 +11,28 @@ import { Plugin, PluginContext } from './types.js';
8
11
  * - Provides dependency injection via service registry
9
12
  * - Implements event/hook system for inter-plugin communication
10
13
  * - Handles dependency resolution (topological sort)
14
+ * - Provides configurable logging for server and browser
11
15
  *
12
16
  * Core philosophy:
13
17
  * - Business logic is completely separated into plugins
14
18
  * - Kernel only manages lifecycle, DI, and hooks
15
19
  * - Plugins are loaded as equal building blocks
16
20
  */
17
- export class ObjectKernel {
18
- private plugins: Map<string, Plugin> = new Map();
19
- private services: Map<string, any> = new Map();
20
- private hooks: Map<string, Array<(...args: any[]) => void | Promise<void>>> = new Map();
21
- private state: 'idle' | 'initializing' | 'running' | 'stopped' = 'idle';
22
-
23
- /**
24
- * Plugin context - shared across all plugins
25
- */
26
- private context: PluginContext = {
27
- registerService: (name, service) => {
28
- if (this.services.has(name)) {
29
- throw new Error(`[Kernel] Service '${name}' already registered`);
30
- }
31
- this.services.set(name, service);
32
- this.context.logger.log(`[Kernel] Service '${name}' registered`);
33
- },
34
- getService: <T>(name: string) => {
35
- const service = this.services.get(name);
36
- if (!service) {
37
- throw new Error(`[Kernel] Service '${name}' not found`);
38
- }
39
- return service as T;
40
- },
41
- hook: (name, handler) => {
42
- if (!this.hooks.has(name)) {
43
- this.hooks.set(name, []);
44
- }
45
- this.hooks.get(name)!.push(handler);
46
- },
47
- trigger: async (name, ...args) => {
48
- const handlers = this.hooks.get(name) || [];
49
- for (const handler of handlers) {
50
- await handler(...args);
51
- }
52
- },
53
- getServices: () => {
54
- return new Map(this.services);
55
- },
56
- logger: console,
57
- getKernel: () => this,
58
- };
21
+ export class ObjectKernel extends ObjectKernelBase {
22
+ constructor(config?: { logger?: Partial<LoggerConfig> }) {
23
+ const logger = createLogger(config?.logger);
24
+ super(logger);
25
+
26
+ // Initialize context after logger is created
27
+ this.context = this.createContext();
28
+ }
59
29
 
60
30
  /**
61
31
  * Register a plugin
62
32
  * @param plugin - Plugin instance
63
33
  */
64
- use(plugin: Plugin) {
65
- if (this.state !== 'idle') {
66
- throw new Error('[Kernel] Cannot register plugins after bootstrap has started');
67
- }
34
+ use(plugin: Plugin): this {
35
+ this.validateIdle();
68
36
 
69
37
  const pluginName = plugin.name;
70
38
  if (this.plugins.has(pluginName)) {
@@ -75,51 +43,6 @@ export class ObjectKernel {
75
43
  return this;
76
44
  }
77
45
 
78
- /**
79
- * Resolve plugin dependencies using topological sort
80
- * @returns Ordered list of plugins
81
- */
82
- private resolveDependencies(): Plugin[] {
83
- const resolved: Plugin[] = [];
84
- const visited = new Set<string>();
85
- const visiting = new Set<string>();
86
-
87
- const visit = (pluginName: string) => {
88
- if (visited.has(pluginName)) return;
89
-
90
- if (visiting.has(pluginName)) {
91
- throw new Error(`[Kernel] Circular dependency detected: ${pluginName}`);
92
- }
93
-
94
- const plugin = this.plugins.get(pluginName);
95
- if (!plugin) {
96
- throw new Error(`[Kernel] Plugin '${pluginName}' not found`);
97
- }
98
-
99
- visiting.add(pluginName);
100
-
101
- // Visit dependencies first
102
- const deps = plugin.dependencies || [];
103
- for (const dep of deps) {
104
- if (!this.plugins.has(dep)) {
105
- throw new Error(`[Kernel] Dependency '${dep}' not found for plugin '${pluginName}'`);
106
- }
107
- visit(dep);
108
- }
109
-
110
- visiting.delete(pluginName);
111
- visited.add(pluginName);
112
- resolved.push(plugin);
113
- };
114
-
115
- // Visit all plugins
116
- for (const pluginName of this.plugins.keys()) {
117
- visit(pluginName);
118
- }
119
-
120
- return resolved;
121
- }
122
-
123
46
  /**
124
47
  * Bootstrap the kernel
125
48
  * 1. Resolve dependencies (topological sort)
@@ -127,62 +50,72 @@ export class ObjectKernel {
127
50
  * 3. Start phase - plugins execute business logic
128
51
  * 4. Trigger 'kernel:ready' hook
129
52
  */
130
- async bootstrap() {
131
- if (this.state !== 'idle') {
132
- throw new Error('[Kernel] Kernel already bootstrapped');
133
- }
53
+ async bootstrap(): Promise<void> {
54
+ this.validateState('idle');
134
55
 
135
56
  this.state = 'initializing';
136
- this.context.logger.log('[Kernel] Bootstrap started...');
57
+ this.logger.info('Bootstrap started');
137
58
 
138
59
  // Resolve dependencies
139
60
  const orderedPlugins = this.resolveDependencies();
140
61
 
141
62
  // Phase 1: Init - Plugins register services
142
- this.context.logger.log('[Kernel] Phase 1: Init plugins...');
63
+ this.logger.info('Phase 1: Init plugins');
143
64
  for (const plugin of orderedPlugins) {
144
- this.context.logger.log(`[Kernel] Init: ${plugin.name}`);
145
- await plugin.init(this.context);
65
+ await this.runPluginInit(plugin);
146
66
  }
147
67
 
148
68
  // Phase 2: Start - Plugins execute business logic
149
- this.context.logger.log('[Kernel] Phase 2: Start plugins...');
69
+ this.logger.info('Phase 2: Start plugins');
150
70
  this.state = 'running';
71
+
151
72
  for (const plugin of orderedPlugins) {
152
- if (plugin.start) {
153
- this.context.logger.log(`[Kernel] Start: ${plugin.name}`);
154
- await plugin.start(this.context);
155
- }
73
+ await this.runPluginStart(plugin);
156
74
  }
157
75
 
158
- // Phase 3: Trigger kernel:ready hook
159
- this.context.logger.log('[Kernel] Triggering kernel:ready hook...');
160
- await this.context.trigger('kernel:ready');
161
-
162
- this.context.logger.log('[Kernel] ✅ Bootstrap complete');
76
+ // Trigger ready hook
77
+ await this.triggerHook('kernel:ready');
78
+ this.logger.info('✅ Bootstrap complete', {
79
+ pluginCount: this.plugins.size
80
+ });
163
81
  }
164
82
 
165
83
  /**
166
84
  * Shutdown the kernel
167
85
  * Calls destroy on all plugins in reverse order
168
86
  */
169
- async shutdown() {
170
- if (this.state !== 'running') {
171
- throw new Error('[Kernel] Kernel not running');
87
+ async shutdown(): Promise<void> {
88
+ await this.destroy();
89
+ }
90
+
91
+ /**
92
+ * Graceful shutdown - destroy all plugins in reverse order
93
+ */
94
+ async destroy(): Promise<void> {
95
+ if (this.state === 'stopped') {
96
+ this.logger.warn('Kernel already stopped');
97
+ return;
172
98
  }
173
99
 
174
- this.context.logger.log('[Kernel] Shutdown started...');
175
- this.state = 'stopped';
100
+ this.state = 'stopping';
101
+ this.logger.info('Shutdown started');
176
102
 
177
- const orderedPlugins = Array.from(this.plugins.values()).reverse();
178
- for (const plugin of orderedPlugins) {
179
- if (plugin.destroy) {
180
- this.context.logger.log(`[Kernel] Destroy: ${plugin.name}`);
181
- await plugin.destroy();
182
- }
103
+ // Trigger shutdown hook
104
+ await this.triggerHook('kernel:shutdown');
105
+
106
+ // Destroy plugins in reverse order
107
+ const orderedPlugins = this.resolveDependencies();
108
+ for (const plugin of orderedPlugins.reverse()) {
109
+ await this.runPluginDestroy(plugin);
183
110
  }
184
111
 
185
- this.context.logger.log('[Kernel] Shutdown complete');
112
+ this.state = 'stopped';
113
+ this.logger.info('✅ Shutdown complete');
114
+
115
+ // Cleanup logger resources
116
+ if (this.logger && typeof (this.logger as ObjectLogger).destroy === 'function') {
117
+ await (this.logger as ObjectLogger).destroy();
118
+ }
186
119
  }
187
120
 
188
121
  /**
@@ -199,11 +132,4 @@ export class ObjectKernel {
199
132
  isRunning(): boolean {
200
133
  return this.state === 'running';
201
134
  }
202
-
203
- /**
204
- * Get kernel state
205
- */
206
- getState(): string {
207
- return this.state;
208
- }
209
135
  }
@@ -0,0 +1,116 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { createLogger, ObjectLogger } from './logger';
3
+
4
+ describe('ObjectLogger', () => {
5
+ let logger: ObjectLogger;
6
+
7
+ beforeEach(() => {
8
+ logger = createLogger();
9
+ });
10
+
11
+ afterEach(async () => {
12
+ await logger.destroy();
13
+ });
14
+
15
+ describe('Basic Logging', () => {
16
+ it('should create a logger with default config', () => {
17
+ expect(logger).toBeDefined();
18
+ expect(logger.info).toBeDefined();
19
+ expect(logger.debug).toBeDefined();
20
+ expect(logger.warn).toBeDefined();
21
+ expect(logger.error).toBeDefined();
22
+ });
23
+
24
+ it('should log info messages', () => {
25
+ expect(() => logger.info('Test message')).not.toThrow();
26
+ });
27
+
28
+ it('should log debug messages', () => {
29
+ expect(() => logger.debug('Debug message')).not.toThrow();
30
+ });
31
+
32
+ it('should log warn messages', () => {
33
+ expect(() => logger.warn('Warning message')).not.toThrow();
34
+ });
35
+
36
+ it('should log error messages', () => {
37
+ const error = new Error('Test error');
38
+ expect(() => logger.error('Error occurred', error)).not.toThrow();
39
+ });
40
+
41
+ it('should log with metadata', () => {
42
+ expect(() => logger.info('Message with metadata', { userId: '123', action: 'login' })).not.toThrow();
43
+ });
44
+ });
45
+
46
+ describe('Configuration', () => {
47
+ it('should respect log level configuration', async () => {
48
+ const warnLogger = createLogger({ level: 'warn' });
49
+
50
+ // These should not throw but might not output anything
51
+ expect(() => warnLogger.debug('Debug message')).not.toThrow();
52
+ expect(() => warnLogger.info('Info message')).not.toThrow();
53
+ expect(() => warnLogger.warn('Warning message')).not.toThrow();
54
+
55
+ await warnLogger.destroy();
56
+ });
57
+
58
+ it('should support different formats', async () => {
59
+ const jsonLogger = createLogger({ format: 'json' });
60
+ const textLogger = createLogger({ format: 'text' });
61
+ const prettyLogger = createLogger({ format: 'pretty' });
62
+
63
+ expect(() => jsonLogger.info('JSON format')).not.toThrow();
64
+ expect(() => textLogger.info('Text format')).not.toThrow();
65
+ expect(() => prettyLogger.info('Pretty format')).not.toThrow();
66
+
67
+ await jsonLogger.destroy();
68
+ await textLogger.destroy();
69
+ await prettyLogger.destroy();
70
+ });
71
+
72
+ it('should redact sensitive keys', async () => {
73
+ const logger = createLogger({ redact: ['password', 'apiKey'] });
74
+
75
+ // This should work without exposing the password
76
+ expect(() => logger.info('User login', {
77
+ username: 'john',
78
+ password: 'secret123',
79
+ apiKey: 'key-12345'
80
+ })).not.toThrow();
81
+
82
+ await logger.destroy();
83
+ });
84
+ });
85
+
86
+ describe('Child Loggers', () => {
87
+ it('should create child logger with context', () => {
88
+ const childLogger = logger.child({ service: 'api', requestId: '123' });
89
+
90
+ expect(childLogger).toBeDefined();
91
+ expect(() => childLogger.info('Child log message')).not.toThrow();
92
+ });
93
+
94
+ it('should support trace context', () => {
95
+ const tracedLogger = logger.withTrace('trace-123', 'span-456');
96
+
97
+ expect(tracedLogger).toBeDefined();
98
+ expect(() => tracedLogger.info('Traced message')).not.toThrow();
99
+ });
100
+ });
101
+
102
+ describe('Environment Detection', () => {
103
+ it('should detect Node.js environment', async () => {
104
+ // This test runs in Node.js, so logger should detect it
105
+ const nodeLogger = createLogger({ format: 'json' });
106
+ expect(() => nodeLogger.info('Node environment')).not.toThrow();
107
+ await nodeLogger.destroy();
108
+ });
109
+ });
110
+
111
+ describe('Compatibility', () => {
112
+ it('should support console.log compatibility', () => {
113
+ expect(() => logger.log('Compatible log')).not.toThrow();
114
+ });
115
+ });
116
+ });
package/src/logger.ts ADDED
@@ -0,0 +1,306 @@
1
+ import type { LoggerConfig, LogLevel } from '@objectstack/spec/system';
2
+ import type { Logger } from '@objectstack/spec/contracts';
3
+
4
+ /**
5
+ * Universal Logger Implementation
6
+ *
7
+ * A configurable logger that works in both browser and Node.js environments.
8
+ * - Node.js: Uses Pino for high-performance structured logging
9
+ * - Browser: Simple console-based implementation
10
+ *
11
+ * Features:
12
+ * - Structured logging with multiple formats (json, text, pretty)
13
+ * - Log level filtering
14
+ * - Sensitive data redaction
15
+ * - File logging with rotation (Node.js only via Pino)
16
+ * - Browser console integration
17
+ * - Distributed tracing support (traceId, spanId)
18
+ */
19
+ export class ObjectLogger implements Logger {
20
+ private config: Required<Omit<LoggerConfig, 'file' | 'rotation' | 'name'>> & { file?: string; rotation?: { maxSize: string; maxFiles: number }; name?: string };
21
+ private isNode: boolean;
22
+ private pinoLogger?: any; // Pino logger instance for Node.js
23
+ private pinoInstance?: any; // Base Pino instance for creating child loggers
24
+
25
+ constructor(config: Partial<LoggerConfig> = {}) {
26
+ // Detect runtime environment
27
+ this.isNode = typeof process !== 'undefined' && process.versions?.node !== undefined;
28
+
29
+ // Set defaults
30
+ this.config = {
31
+ name: config.name,
32
+ level: config.level ?? 'info',
33
+ format: config.format ?? (this.isNode ? 'json' : 'pretty'),
34
+ redact: config.redact ?? ['password', 'token', 'secret', 'key'],
35
+ sourceLocation: config.sourceLocation ?? false,
36
+ file: config.file,
37
+ rotation: config.rotation ?? {
38
+ maxSize: '10m',
39
+ maxFiles: 5
40
+ }
41
+ };
42
+
43
+ // Initialize Pino logger for Node.js
44
+ if (this.isNode) {
45
+ this.initPinoLogger();
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Initialize Pino logger for Node.js
51
+ */
52
+ private initPinoLogger() {
53
+ if (!this.isNode) return;
54
+
55
+ try {
56
+ // Dynamic import for Pino (Node.js only)
57
+ const pino = require('pino');
58
+
59
+ // Build Pino options
60
+ const pinoOptions: any = {
61
+ level: this.config.level,
62
+ redact: {
63
+ paths: this.config.redact,
64
+ censor: '***REDACTED***'
65
+ }
66
+ };
67
+
68
+ // Add name if provided
69
+ if (this.config.name) {
70
+ pinoOptions.name = this.config.name;
71
+ }
72
+
73
+ // Transport configuration for pretty printing or file output
74
+ const targets: any[] = [];
75
+
76
+ // Console transport
77
+ if (this.config.format === 'pretty') {
78
+ targets.push({
79
+ target: 'pino-pretty',
80
+ options: {
81
+ colorize: true,
82
+ translateTime: 'SYS:standard',
83
+ ignore: 'pid,hostname'
84
+ },
85
+ level: this.config.level
86
+ });
87
+ } else if (this.config.format === 'json') {
88
+ // JSON to stdout
89
+ targets.push({
90
+ target: 'pino/file',
91
+ options: { destination: 1 }, // stdout
92
+ level: this.config.level
93
+ });
94
+ } else {
95
+ // text format (simple)
96
+ targets.push({
97
+ target: 'pino/file',
98
+ options: { destination: 1 },
99
+ level: this.config.level
100
+ });
101
+ }
102
+
103
+ // File transport (if configured)
104
+ if (this.config.file) {
105
+ targets.push({
106
+ target: 'pino/file',
107
+ options: {
108
+ destination: this.config.file,
109
+ mkdir: true
110
+ },
111
+ level: this.config.level
112
+ });
113
+ }
114
+
115
+ // Create transport
116
+ if (targets.length > 0) {
117
+ pinoOptions.transport = targets.length === 1 ? targets[0] : { targets };
118
+ }
119
+
120
+ // Create Pino logger
121
+ this.pinoInstance = pino(pinoOptions);
122
+ this.pinoLogger = this.pinoInstance;
123
+
124
+ } catch (error) {
125
+ // Fallback to console if Pino is not available
126
+ console.warn('[Logger] Pino not available, falling back to console:', error);
127
+ this.pinoLogger = null;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Redact sensitive keys from context object (for browser)
133
+ */
134
+ private redactSensitive(obj: any): any {
135
+ if (!obj || typeof obj !== 'object') return obj;
136
+
137
+ const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
138
+
139
+ for (const key in redacted) {
140
+ const lowerKey = key.toLowerCase();
141
+ const shouldRedact = this.config.redact.some((pattern: string) =>
142
+ lowerKey.includes(pattern.toLowerCase())
143
+ );
144
+
145
+ if (shouldRedact) {
146
+ redacted[key] = '***REDACTED***';
147
+ } else if (typeof redacted[key] === 'object' && redacted[key] !== null) {
148
+ redacted[key] = this.redactSensitive(redacted[key]);
149
+ }
150
+ }
151
+
152
+ return redacted;
153
+ }
154
+
155
+ /**
156
+ * Format log entry for browser
157
+ */
158
+ private formatBrowserLog(level: LogLevel, message: string, context?: Record<string, any>): string {
159
+ if (this.config.format === 'json') {
160
+ return JSON.stringify({
161
+ timestamp: new Date().toISOString(),
162
+ level,
163
+ message,
164
+ ...context
165
+ });
166
+ }
167
+
168
+ if (this.config.format === 'text') {
169
+ const parts = [new Date().toISOString(), level.toUpperCase(), message];
170
+ if (context && Object.keys(context).length > 0) {
171
+ parts.push(JSON.stringify(context));
172
+ }
173
+ return parts.join(' | ');
174
+ }
175
+
176
+ // Pretty format
177
+ const levelColors: Record<LogLevel, string> = {
178
+ debug: '\x1b[36m', // Cyan
179
+ info: '\x1b[32m', // Green
180
+ warn: '\x1b[33m', // Yellow
181
+ error: '\x1b[31m', // Red
182
+ fatal: '\x1b[35m' // Magenta
183
+ };
184
+ const reset = '\x1b[0m';
185
+ const color = levelColors[level] || '';
186
+
187
+ let output = `${color}[${level.toUpperCase()}]${reset} ${message}`;
188
+
189
+ if (context && Object.keys(context).length > 0) {
190
+ output += ` ${JSON.stringify(context, null, 2)}`;
191
+ }
192
+
193
+ return output;
194
+ }
195
+
196
+ /**
197
+ * Log using browser console
198
+ */
199
+ private logBrowser(level: LogLevel, message: string, context?: Record<string, any>, error?: Error) {
200
+ const redactedContext = context ? this.redactSensitive(context) : undefined;
201
+ const mergedContext = error ? { ...redactedContext, error: { message: error.message, stack: error.stack } } : redactedContext;
202
+
203
+ const formatted = this.formatBrowserLog(level, message, mergedContext);
204
+
205
+ const consoleMethod = level === 'debug' ? 'debug' :
206
+ level === 'info' ? 'log' :
207
+ level === 'warn' ? 'warn' :
208
+ level === 'error' || level === 'fatal' ? 'error' :
209
+ 'log';
210
+
211
+ console[consoleMethod](formatted);
212
+ }
213
+
214
+ /**
215
+ * Public logging methods
216
+ */
217
+ debug(message: string, meta?: Record<string, any>): void {
218
+ if (this.isNode && this.pinoLogger) {
219
+ this.pinoLogger.debug(meta || {}, message);
220
+ } else {
221
+ this.logBrowser('debug', message, meta);
222
+ }
223
+ }
224
+
225
+ info(message: string, meta?: Record<string, any>): void {
226
+ if (this.isNode && this.pinoLogger) {
227
+ this.pinoLogger.info(meta || {}, message);
228
+ } else {
229
+ this.logBrowser('info', message, meta);
230
+ }
231
+ }
232
+
233
+ warn(message: string, meta?: Record<string, any>): void {
234
+ if (this.isNode && this.pinoLogger) {
235
+ this.pinoLogger.warn(meta || {}, message);
236
+ } else {
237
+ this.logBrowser('warn', message, meta);
238
+ }
239
+ }
240
+
241
+ error(message: string, error?: Error, meta?: Record<string, any>): void {
242
+ if (this.isNode && this.pinoLogger) {
243
+ const errorContext = error ? { err: error, ...meta } : meta || {};
244
+ this.pinoLogger.error(errorContext, message);
245
+ } else {
246
+ this.logBrowser('error', message, meta, error);
247
+ }
248
+ }
249
+
250
+ fatal(message: string, error?: Error, meta?: Record<string, any>): void {
251
+ if (this.isNode && this.pinoLogger) {
252
+ const errorContext = error ? { err: error, ...meta } : meta || {};
253
+ this.pinoLogger.fatal(errorContext, message);
254
+ } else {
255
+ this.logBrowser('fatal', message, meta, error);
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Create a child logger with additional context
261
+ * Note: Child loggers share the parent's Pino instance
262
+ */
263
+ child(context: Record<string, any>): ObjectLogger {
264
+ const childLogger = new ObjectLogger(this.config);
265
+
266
+ // For Node.js with Pino, create a Pino child logger
267
+ if (this.isNode && this.pinoInstance) {
268
+ childLogger.pinoLogger = this.pinoInstance.child(context);
269
+ childLogger.pinoInstance = this.pinoInstance;
270
+ }
271
+
272
+ return childLogger;
273
+ }
274
+
275
+ /**
276
+ * Set trace context for distributed tracing
277
+ */
278
+ withTrace(traceId: string, spanId?: string): ObjectLogger {
279
+ return this.child({ traceId, spanId });
280
+ }
281
+
282
+ /**
283
+ * Cleanup resources
284
+ */
285
+ async destroy(): Promise<void> {
286
+ if (this.pinoLogger && this.pinoLogger.flush) {
287
+ await new Promise<void>((resolve) => {
288
+ this.pinoLogger.flush(() => resolve());
289
+ });
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Compatibility method for console.log usage
295
+ */
296
+ log(message: string, ...args: any[]): void {
297
+ this.info(message, args.length > 0 ? { args } : undefined);
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Create a logger instance
303
+ */
304
+ export function createLogger(config?: Partial<LoggerConfig>): ObjectLogger {
305
+ return new ObjectLogger(config);
306
+ }