@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.
- package/CHANGELOG.md +7 -0
- package/ENHANCED_FEATURES.md +380 -0
- package/README.md +299 -12
- package/dist/contracts/data-engine.d.ts +39 -22
- package/dist/contracts/data-engine.d.ts.map +1 -1
- package/dist/contracts/logger.d.ts +63 -0
- package/dist/contracts/logger.d.ts.map +1 -0
- package/dist/contracts/logger.js +1 -0
- package/dist/enhanced-kernel.d.ts +103 -0
- package/dist/enhanced-kernel.d.ts.map +1 -0
- package/dist/enhanced-kernel.js +403 -0
- package/dist/enhanced-kernel.test.d.ts +2 -0
- package/dist/enhanced-kernel.test.d.ts.map +1 -0
- package/dist/enhanced-kernel.test.js +412 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/dist/kernel-base.d.ts +84 -0
- package/dist/kernel-base.d.ts.map +1 -0
- package/dist/kernel-base.js +219 -0
- package/dist/kernel.d.ts +11 -18
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +43 -114
- package/dist/kernel.test.d.ts +2 -0
- package/dist/kernel.test.d.ts.map +1 -0
- package/dist/kernel.test.js +161 -0
- package/dist/logger.d.ts +70 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +268 -0
- package/dist/logger.test.d.ts +2 -0
- package/dist/logger.test.d.ts.map +1 -0
- package/dist/logger.test.js +92 -0
- package/dist/plugin-loader.d.ts +148 -0
- package/dist/plugin-loader.d.ts.map +1 -0
- package/dist/plugin-loader.js +287 -0
- package/dist/plugin-loader.test.d.ts +2 -0
- package/dist/plugin-loader.test.d.ts.map +1 -0
- package/dist/plugin-loader.test.js +339 -0
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/examples/enhanced-kernel-example.ts +309 -0
- package/package.json +19 -4
- package/src/contracts/data-engine.ts +46 -24
- package/src/contracts/logger.ts +70 -0
- package/src/enhanced-kernel.test.ts +535 -0
- package/src/enhanced-kernel.ts +496 -0
- package/src/index.ts +23 -2
- package/src/kernel-base.ts +256 -0
- package/src/kernel.test.ts +200 -0
- package/src/kernel.ts +55 -129
- package/src/logger.test.ts +116 -0
- package/src/logger.ts +306 -0
- package/src/plugin-loader.test.ts +412 -0
- package/src/plugin-loader.ts +435 -0
- package/src/types.ts +2 -1
- package/vitest.config.ts +8 -0
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { LoggerConfig } from '@objectstack/spec/system';
|
|
2
|
+
import type { Logger } from '@objectstack/spec/contracts';
|
|
3
|
+
/**
|
|
4
|
+
* Universal Logger Implementation
|
|
5
|
+
*
|
|
6
|
+
* A configurable logger that works in both browser and Node.js environments.
|
|
7
|
+
* - Node.js: Uses Pino for high-performance structured logging
|
|
8
|
+
* - Browser: Simple console-based implementation
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Structured logging with multiple formats (json, text, pretty)
|
|
12
|
+
* - Log level filtering
|
|
13
|
+
* - Sensitive data redaction
|
|
14
|
+
* - File logging with rotation (Node.js only via Pino)
|
|
15
|
+
* - Browser console integration
|
|
16
|
+
* - Distributed tracing support (traceId, spanId)
|
|
17
|
+
*/
|
|
18
|
+
export declare class ObjectLogger implements Logger {
|
|
19
|
+
private config;
|
|
20
|
+
private isNode;
|
|
21
|
+
private pinoLogger?;
|
|
22
|
+
private pinoInstance?;
|
|
23
|
+
constructor(config?: Partial<LoggerConfig>);
|
|
24
|
+
/**
|
|
25
|
+
* Initialize Pino logger for Node.js
|
|
26
|
+
*/
|
|
27
|
+
private initPinoLogger;
|
|
28
|
+
/**
|
|
29
|
+
* Redact sensitive keys from context object (for browser)
|
|
30
|
+
*/
|
|
31
|
+
private redactSensitive;
|
|
32
|
+
/**
|
|
33
|
+
* Format log entry for browser
|
|
34
|
+
*/
|
|
35
|
+
private formatBrowserLog;
|
|
36
|
+
/**
|
|
37
|
+
* Log using browser console
|
|
38
|
+
*/
|
|
39
|
+
private logBrowser;
|
|
40
|
+
/**
|
|
41
|
+
* Public logging methods
|
|
42
|
+
*/
|
|
43
|
+
debug(message: string, meta?: Record<string, any>): void;
|
|
44
|
+
info(message: string, meta?: Record<string, any>): void;
|
|
45
|
+
warn(message: string, meta?: Record<string, any>): void;
|
|
46
|
+
error(message: string, error?: Error, meta?: Record<string, any>): void;
|
|
47
|
+
fatal(message: string, error?: Error, meta?: Record<string, any>): void;
|
|
48
|
+
/**
|
|
49
|
+
* Create a child logger with additional context
|
|
50
|
+
* Note: Child loggers share the parent's Pino instance
|
|
51
|
+
*/
|
|
52
|
+
child(context: Record<string, any>): ObjectLogger;
|
|
53
|
+
/**
|
|
54
|
+
* Set trace context for distributed tracing
|
|
55
|
+
*/
|
|
56
|
+
withTrace(traceId: string, spanId?: string): ObjectLogger;
|
|
57
|
+
/**
|
|
58
|
+
* Cleanup resources
|
|
59
|
+
*/
|
|
60
|
+
destroy(): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Compatibility method for console.log usage
|
|
63
|
+
*/
|
|
64
|
+
log(message: string, ...args: any[]): void;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create a logger instance
|
|
68
|
+
*/
|
|
69
|
+
export declare function createLogger(config?: Partial<LoggerConfig>): ObjectLogger;
|
|
70
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAY,MAAM,0BAA0B,CAAC;AACvE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AAE1D;;;;;;;;;;;;;;GAcG;AACH,qBAAa,YAAa,YAAW,MAAM;IACvC,OAAO,CAAC,MAAM,CAAkJ;IAChK,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,UAAU,CAAC,CAAM;IACzB,OAAO,CAAC,YAAY,CAAC,CAAM;gBAEf,MAAM,GAAE,OAAO,CAAC,YAAY,CAAM;IAwB9C;;OAEG;IACH,OAAO,CAAC,cAAc;IA+EtB;;OAEG;IACH,OAAO,CAAC,eAAe;IAqBvB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAsCxB;;OAEG;IACH,OAAO,CAAC,UAAU;IAelB;;OAEG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAQxD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAQvD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAQvD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IASvE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IASvE;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,YAAY;IAYjD;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,YAAY;IAIzD;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ9B;;OAEG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;CAG7C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAEzE"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal Logger Implementation
|
|
3
|
+
*
|
|
4
|
+
* A configurable logger that works in both browser and Node.js environments.
|
|
5
|
+
* - Node.js: Uses Pino for high-performance structured logging
|
|
6
|
+
* - Browser: Simple console-based implementation
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Structured logging with multiple formats (json, text, pretty)
|
|
10
|
+
* - Log level filtering
|
|
11
|
+
* - Sensitive data redaction
|
|
12
|
+
* - File logging with rotation (Node.js only via Pino)
|
|
13
|
+
* - Browser console integration
|
|
14
|
+
* - Distributed tracing support (traceId, spanId)
|
|
15
|
+
*/
|
|
16
|
+
export class ObjectLogger {
|
|
17
|
+
constructor(config = {}) {
|
|
18
|
+
// Detect runtime environment
|
|
19
|
+
this.isNode = typeof process !== 'undefined' && process.versions?.node !== undefined;
|
|
20
|
+
// Set defaults
|
|
21
|
+
this.config = {
|
|
22
|
+
name: config.name,
|
|
23
|
+
level: config.level ?? 'info',
|
|
24
|
+
format: config.format ?? (this.isNode ? 'json' : 'pretty'),
|
|
25
|
+
redact: config.redact ?? ['password', 'token', 'secret', 'key'],
|
|
26
|
+
sourceLocation: config.sourceLocation ?? false,
|
|
27
|
+
file: config.file,
|
|
28
|
+
rotation: config.rotation ?? {
|
|
29
|
+
maxSize: '10m',
|
|
30
|
+
maxFiles: 5
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
// Initialize Pino logger for Node.js
|
|
34
|
+
if (this.isNode) {
|
|
35
|
+
this.initPinoLogger();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Initialize Pino logger for Node.js
|
|
40
|
+
*/
|
|
41
|
+
initPinoLogger() {
|
|
42
|
+
if (!this.isNode)
|
|
43
|
+
return;
|
|
44
|
+
try {
|
|
45
|
+
// Dynamic import for Pino (Node.js only)
|
|
46
|
+
const pino = require('pino');
|
|
47
|
+
// Build Pino options
|
|
48
|
+
const pinoOptions = {
|
|
49
|
+
level: this.config.level,
|
|
50
|
+
redact: {
|
|
51
|
+
paths: this.config.redact,
|
|
52
|
+
censor: '***REDACTED***'
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
// Add name if provided
|
|
56
|
+
if (this.config.name) {
|
|
57
|
+
pinoOptions.name = this.config.name;
|
|
58
|
+
}
|
|
59
|
+
// Transport configuration for pretty printing or file output
|
|
60
|
+
const targets = [];
|
|
61
|
+
// Console transport
|
|
62
|
+
if (this.config.format === 'pretty') {
|
|
63
|
+
targets.push({
|
|
64
|
+
target: 'pino-pretty',
|
|
65
|
+
options: {
|
|
66
|
+
colorize: true,
|
|
67
|
+
translateTime: 'SYS:standard',
|
|
68
|
+
ignore: 'pid,hostname'
|
|
69
|
+
},
|
|
70
|
+
level: this.config.level
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else if (this.config.format === 'json') {
|
|
74
|
+
// JSON to stdout
|
|
75
|
+
targets.push({
|
|
76
|
+
target: 'pino/file',
|
|
77
|
+
options: { destination: 1 }, // stdout
|
|
78
|
+
level: this.config.level
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// text format (simple)
|
|
83
|
+
targets.push({
|
|
84
|
+
target: 'pino/file',
|
|
85
|
+
options: { destination: 1 },
|
|
86
|
+
level: this.config.level
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// File transport (if configured)
|
|
90
|
+
if (this.config.file) {
|
|
91
|
+
targets.push({
|
|
92
|
+
target: 'pino/file',
|
|
93
|
+
options: {
|
|
94
|
+
destination: this.config.file,
|
|
95
|
+
mkdir: true
|
|
96
|
+
},
|
|
97
|
+
level: this.config.level
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
// Create transport
|
|
101
|
+
if (targets.length > 0) {
|
|
102
|
+
pinoOptions.transport = targets.length === 1 ? targets[0] : { targets };
|
|
103
|
+
}
|
|
104
|
+
// Create Pino logger
|
|
105
|
+
this.pinoInstance = pino(pinoOptions);
|
|
106
|
+
this.pinoLogger = this.pinoInstance;
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
// Fallback to console if Pino is not available
|
|
110
|
+
console.warn('[Logger] Pino not available, falling back to console:', error);
|
|
111
|
+
this.pinoLogger = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Redact sensitive keys from context object (for browser)
|
|
116
|
+
*/
|
|
117
|
+
redactSensitive(obj) {
|
|
118
|
+
if (!obj || typeof obj !== 'object')
|
|
119
|
+
return obj;
|
|
120
|
+
const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
|
|
121
|
+
for (const key in redacted) {
|
|
122
|
+
const lowerKey = key.toLowerCase();
|
|
123
|
+
const shouldRedact = this.config.redact.some((pattern) => lowerKey.includes(pattern.toLowerCase()));
|
|
124
|
+
if (shouldRedact) {
|
|
125
|
+
redacted[key] = '***REDACTED***';
|
|
126
|
+
}
|
|
127
|
+
else if (typeof redacted[key] === 'object' && redacted[key] !== null) {
|
|
128
|
+
redacted[key] = this.redactSensitive(redacted[key]);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return redacted;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Format log entry for browser
|
|
135
|
+
*/
|
|
136
|
+
formatBrowserLog(level, message, context) {
|
|
137
|
+
if (this.config.format === 'json') {
|
|
138
|
+
return JSON.stringify({
|
|
139
|
+
timestamp: new Date().toISOString(),
|
|
140
|
+
level,
|
|
141
|
+
message,
|
|
142
|
+
...context
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (this.config.format === 'text') {
|
|
146
|
+
const parts = [new Date().toISOString(), level.toUpperCase(), message];
|
|
147
|
+
if (context && Object.keys(context).length > 0) {
|
|
148
|
+
parts.push(JSON.stringify(context));
|
|
149
|
+
}
|
|
150
|
+
return parts.join(' | ');
|
|
151
|
+
}
|
|
152
|
+
// Pretty format
|
|
153
|
+
const levelColors = {
|
|
154
|
+
debug: '\x1b[36m', // Cyan
|
|
155
|
+
info: '\x1b[32m', // Green
|
|
156
|
+
warn: '\x1b[33m', // Yellow
|
|
157
|
+
error: '\x1b[31m', // Red
|
|
158
|
+
fatal: '\x1b[35m' // Magenta
|
|
159
|
+
};
|
|
160
|
+
const reset = '\x1b[0m';
|
|
161
|
+
const color = levelColors[level] || '';
|
|
162
|
+
let output = `${color}[${level.toUpperCase()}]${reset} ${message}`;
|
|
163
|
+
if (context && Object.keys(context).length > 0) {
|
|
164
|
+
output += ` ${JSON.stringify(context, null, 2)}`;
|
|
165
|
+
}
|
|
166
|
+
return output;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Log using browser console
|
|
170
|
+
*/
|
|
171
|
+
logBrowser(level, message, context, error) {
|
|
172
|
+
const redactedContext = context ? this.redactSensitive(context) : undefined;
|
|
173
|
+
const mergedContext = error ? { ...redactedContext, error: { message: error.message, stack: error.stack } } : redactedContext;
|
|
174
|
+
const formatted = this.formatBrowserLog(level, message, mergedContext);
|
|
175
|
+
const consoleMethod = level === 'debug' ? 'debug' :
|
|
176
|
+
level === 'info' ? 'log' :
|
|
177
|
+
level === 'warn' ? 'warn' :
|
|
178
|
+
level === 'error' || level === 'fatal' ? 'error' :
|
|
179
|
+
'log';
|
|
180
|
+
console[consoleMethod](formatted);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Public logging methods
|
|
184
|
+
*/
|
|
185
|
+
debug(message, meta) {
|
|
186
|
+
if (this.isNode && this.pinoLogger) {
|
|
187
|
+
this.pinoLogger.debug(meta || {}, message);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
this.logBrowser('debug', message, meta);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
info(message, meta) {
|
|
194
|
+
if (this.isNode && this.pinoLogger) {
|
|
195
|
+
this.pinoLogger.info(meta || {}, message);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
this.logBrowser('info', message, meta);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
warn(message, meta) {
|
|
202
|
+
if (this.isNode && this.pinoLogger) {
|
|
203
|
+
this.pinoLogger.warn(meta || {}, message);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
this.logBrowser('warn', message, meta);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
error(message, error, meta) {
|
|
210
|
+
if (this.isNode && this.pinoLogger) {
|
|
211
|
+
const errorContext = error ? { err: error, ...meta } : meta || {};
|
|
212
|
+
this.pinoLogger.error(errorContext, message);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
this.logBrowser('error', message, meta, error);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
fatal(message, error, meta) {
|
|
219
|
+
if (this.isNode && this.pinoLogger) {
|
|
220
|
+
const errorContext = error ? { err: error, ...meta } : meta || {};
|
|
221
|
+
this.pinoLogger.fatal(errorContext, message);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
this.logBrowser('fatal', message, meta, error);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Create a child logger with additional context
|
|
229
|
+
* Note: Child loggers share the parent's Pino instance
|
|
230
|
+
*/
|
|
231
|
+
child(context) {
|
|
232
|
+
const childLogger = new ObjectLogger(this.config);
|
|
233
|
+
// For Node.js with Pino, create a Pino child logger
|
|
234
|
+
if (this.isNode && this.pinoInstance) {
|
|
235
|
+
childLogger.pinoLogger = this.pinoInstance.child(context);
|
|
236
|
+
childLogger.pinoInstance = this.pinoInstance;
|
|
237
|
+
}
|
|
238
|
+
return childLogger;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Set trace context for distributed tracing
|
|
242
|
+
*/
|
|
243
|
+
withTrace(traceId, spanId) {
|
|
244
|
+
return this.child({ traceId, spanId });
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Cleanup resources
|
|
248
|
+
*/
|
|
249
|
+
async destroy() {
|
|
250
|
+
if (this.pinoLogger && this.pinoLogger.flush) {
|
|
251
|
+
await new Promise((resolve) => {
|
|
252
|
+
this.pinoLogger.flush(() => resolve());
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Compatibility method for console.log usage
|
|
258
|
+
*/
|
|
259
|
+
log(message, ...args) {
|
|
260
|
+
this.info(message, args.length > 0 ? { args } : undefined);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Create a logger instance
|
|
265
|
+
*/
|
|
266
|
+
export function createLogger(config) {
|
|
267
|
+
return new ObjectLogger(config);
|
|
268
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.test.d.ts","sourceRoot":"","sources":["../src/logger.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { createLogger } from './logger';
|
|
3
|
+
describe('ObjectLogger', () => {
|
|
4
|
+
let logger;
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
logger = createLogger();
|
|
7
|
+
});
|
|
8
|
+
afterEach(async () => {
|
|
9
|
+
await logger.destroy();
|
|
10
|
+
});
|
|
11
|
+
describe('Basic Logging', () => {
|
|
12
|
+
it('should create a logger with default config', () => {
|
|
13
|
+
expect(logger).toBeDefined();
|
|
14
|
+
expect(logger.info).toBeDefined();
|
|
15
|
+
expect(logger.debug).toBeDefined();
|
|
16
|
+
expect(logger.warn).toBeDefined();
|
|
17
|
+
expect(logger.error).toBeDefined();
|
|
18
|
+
});
|
|
19
|
+
it('should log info messages', () => {
|
|
20
|
+
expect(() => logger.info('Test message')).not.toThrow();
|
|
21
|
+
});
|
|
22
|
+
it('should log debug messages', () => {
|
|
23
|
+
expect(() => logger.debug('Debug message')).not.toThrow();
|
|
24
|
+
});
|
|
25
|
+
it('should log warn messages', () => {
|
|
26
|
+
expect(() => logger.warn('Warning message')).not.toThrow();
|
|
27
|
+
});
|
|
28
|
+
it('should log error messages', () => {
|
|
29
|
+
const error = new Error('Test error');
|
|
30
|
+
expect(() => logger.error('Error occurred', error)).not.toThrow();
|
|
31
|
+
});
|
|
32
|
+
it('should log with metadata', () => {
|
|
33
|
+
expect(() => logger.info('Message with metadata', { userId: '123', action: 'login' })).not.toThrow();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
describe('Configuration', () => {
|
|
37
|
+
it('should respect log level configuration', async () => {
|
|
38
|
+
const warnLogger = createLogger({ level: 'warn' });
|
|
39
|
+
// These should not throw but might not output anything
|
|
40
|
+
expect(() => warnLogger.debug('Debug message')).not.toThrow();
|
|
41
|
+
expect(() => warnLogger.info('Info message')).not.toThrow();
|
|
42
|
+
expect(() => warnLogger.warn('Warning message')).not.toThrow();
|
|
43
|
+
await warnLogger.destroy();
|
|
44
|
+
});
|
|
45
|
+
it('should support different formats', async () => {
|
|
46
|
+
const jsonLogger = createLogger({ format: 'json' });
|
|
47
|
+
const textLogger = createLogger({ format: 'text' });
|
|
48
|
+
const prettyLogger = createLogger({ format: 'pretty' });
|
|
49
|
+
expect(() => jsonLogger.info('JSON format')).not.toThrow();
|
|
50
|
+
expect(() => textLogger.info('Text format')).not.toThrow();
|
|
51
|
+
expect(() => prettyLogger.info('Pretty format')).not.toThrow();
|
|
52
|
+
await jsonLogger.destroy();
|
|
53
|
+
await textLogger.destroy();
|
|
54
|
+
await prettyLogger.destroy();
|
|
55
|
+
});
|
|
56
|
+
it('should redact sensitive keys', async () => {
|
|
57
|
+
const logger = createLogger({ redact: ['password', 'apiKey'] });
|
|
58
|
+
// This should work without exposing the password
|
|
59
|
+
expect(() => logger.info('User login', {
|
|
60
|
+
username: 'john',
|
|
61
|
+
password: 'secret123',
|
|
62
|
+
apiKey: 'key-12345'
|
|
63
|
+
})).not.toThrow();
|
|
64
|
+
await logger.destroy();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('Child Loggers', () => {
|
|
68
|
+
it('should create child logger with context', () => {
|
|
69
|
+
const childLogger = logger.child({ service: 'api', requestId: '123' });
|
|
70
|
+
expect(childLogger).toBeDefined();
|
|
71
|
+
expect(() => childLogger.info('Child log message')).not.toThrow();
|
|
72
|
+
});
|
|
73
|
+
it('should support trace context', () => {
|
|
74
|
+
const tracedLogger = logger.withTrace('trace-123', 'span-456');
|
|
75
|
+
expect(tracedLogger).toBeDefined();
|
|
76
|
+
expect(() => tracedLogger.info('Traced message')).not.toThrow();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe('Environment Detection', () => {
|
|
80
|
+
it('should detect Node.js environment', async () => {
|
|
81
|
+
// This test runs in Node.js, so logger should detect it
|
|
82
|
+
const nodeLogger = createLogger({ format: 'json' });
|
|
83
|
+
expect(() => nodeLogger.info('Node environment')).not.toThrow();
|
|
84
|
+
await nodeLogger.destroy();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe('Compatibility', () => {
|
|
88
|
+
it('should support console.log compatibility', () => {
|
|
89
|
+
expect(() => logger.log('Compatible log')).not.toThrow();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Plugin, PluginContext } from './types.js';
|
|
2
|
+
import type { Logger } from '@objectstack/spec/contracts';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
/**
|
|
5
|
+
* Service Lifecycle Types
|
|
6
|
+
* Defines how services are instantiated and managed
|
|
7
|
+
*/
|
|
8
|
+
export declare enum ServiceLifecycle {
|
|
9
|
+
/** Single instance shared across all requests */
|
|
10
|
+
SINGLETON = "singleton",
|
|
11
|
+
/** New instance created for each request */
|
|
12
|
+
TRANSIENT = "transient",
|
|
13
|
+
/** New instance per scope (e.g., per HTTP request) */
|
|
14
|
+
SCOPED = "scoped"
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Service Factory
|
|
18
|
+
* Function that creates a service instance
|
|
19
|
+
*/
|
|
20
|
+
export type ServiceFactory<T = any> = (ctx: PluginContext) => T | Promise<T>;
|
|
21
|
+
/**
|
|
22
|
+
* Service Registration Options
|
|
23
|
+
*/
|
|
24
|
+
export interface ServiceRegistration {
|
|
25
|
+
name: string;
|
|
26
|
+
factory: ServiceFactory;
|
|
27
|
+
lifecycle: ServiceLifecycle;
|
|
28
|
+
dependencies?: string[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Plugin Configuration Validator
|
|
32
|
+
* Uses Zod for runtime validation of plugin configurations
|
|
33
|
+
*/
|
|
34
|
+
export interface PluginConfigValidator {
|
|
35
|
+
schema: z.ZodSchema;
|
|
36
|
+
validate(config: any): any;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Plugin Metadata with Enhanced Features
|
|
40
|
+
*/
|
|
41
|
+
export interface PluginMetadata extends Plugin {
|
|
42
|
+
/** Semantic version (e.g., "1.0.0") */
|
|
43
|
+
version: string;
|
|
44
|
+
/** Configuration schema for validation */
|
|
45
|
+
configSchema?: z.ZodSchema;
|
|
46
|
+
/** Plugin signature for security verification */
|
|
47
|
+
signature?: string;
|
|
48
|
+
/** Plugin health check function */
|
|
49
|
+
healthCheck?(): Promise<PluginHealthStatus>;
|
|
50
|
+
/** Startup timeout in milliseconds (default: 30000) */
|
|
51
|
+
startupTimeout?: number;
|
|
52
|
+
/** Whether plugin supports hot reload */
|
|
53
|
+
hotReloadable?: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Plugin Health Status
|
|
57
|
+
*/
|
|
58
|
+
export interface PluginHealthStatus {
|
|
59
|
+
healthy: boolean;
|
|
60
|
+
message?: string;
|
|
61
|
+
details?: Record<string, any>;
|
|
62
|
+
lastCheck?: Date;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Plugin Load Result
|
|
66
|
+
*/
|
|
67
|
+
export interface PluginLoadResult {
|
|
68
|
+
success: boolean;
|
|
69
|
+
plugin?: PluginMetadata;
|
|
70
|
+
error?: Error;
|
|
71
|
+
loadTime?: number;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Plugin Startup Result
|
|
75
|
+
*/
|
|
76
|
+
export interface PluginStartupResult {
|
|
77
|
+
success: boolean;
|
|
78
|
+
pluginName: string;
|
|
79
|
+
startTime?: number;
|
|
80
|
+
error?: Error;
|
|
81
|
+
timedOut?: boolean;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Version Compatibility Result
|
|
85
|
+
*/
|
|
86
|
+
export interface VersionCompatibility {
|
|
87
|
+
compatible: boolean;
|
|
88
|
+
pluginVersion: string;
|
|
89
|
+
requiredVersion?: string;
|
|
90
|
+
message?: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Enhanced Plugin Loader
|
|
94
|
+
* Provides advanced plugin loading capabilities with validation, security, and lifecycle management
|
|
95
|
+
*/
|
|
96
|
+
export declare class PluginLoader {
|
|
97
|
+
private logger;
|
|
98
|
+
private loadedPlugins;
|
|
99
|
+
private serviceFactories;
|
|
100
|
+
private serviceInstances;
|
|
101
|
+
private scopedServices;
|
|
102
|
+
constructor(logger: Logger);
|
|
103
|
+
/**
|
|
104
|
+
* Load a plugin asynchronously with validation
|
|
105
|
+
*/
|
|
106
|
+
loadPlugin(plugin: Plugin): Promise<PluginLoadResult>;
|
|
107
|
+
/**
|
|
108
|
+
* Register a service with factory function
|
|
109
|
+
*/
|
|
110
|
+
registerServiceFactory(registration: ServiceRegistration): void;
|
|
111
|
+
/**
|
|
112
|
+
* Get or create a service instance based on lifecycle type
|
|
113
|
+
*/
|
|
114
|
+
getService<T>(name: string, scopeId?: string): Promise<T>;
|
|
115
|
+
/**
|
|
116
|
+
* Register a static service instance (legacy support)
|
|
117
|
+
*/
|
|
118
|
+
registerService(name: string, service: any): void;
|
|
119
|
+
/**
|
|
120
|
+
* Detect circular dependencies in service factories
|
|
121
|
+
* Note: This only detects cycles in service dependencies, not plugin dependencies.
|
|
122
|
+
* Plugin dependency cycles are detected in the kernel's resolveDependencies method.
|
|
123
|
+
*/
|
|
124
|
+
detectCircularDependencies(): string[];
|
|
125
|
+
/**
|
|
126
|
+
* Check plugin health
|
|
127
|
+
*/
|
|
128
|
+
checkPluginHealth(pluginName: string): Promise<PluginHealthStatus>;
|
|
129
|
+
/**
|
|
130
|
+
* Clear scoped services for a scope
|
|
131
|
+
*/
|
|
132
|
+
clearScope(scopeId: string): void;
|
|
133
|
+
/**
|
|
134
|
+
* Get all loaded plugins
|
|
135
|
+
*/
|
|
136
|
+
getLoadedPlugins(): Map<string, PluginMetadata>;
|
|
137
|
+
private toPluginMetadata;
|
|
138
|
+
private validatePluginStructure;
|
|
139
|
+
private checkVersionCompatibility;
|
|
140
|
+
private isValidSemanticVersion;
|
|
141
|
+
private validatePluginConfig;
|
|
142
|
+
private verifyPluginSignature;
|
|
143
|
+
private getSingletonService;
|
|
144
|
+
private createTransientService;
|
|
145
|
+
private getScopedService;
|
|
146
|
+
private createServiceInstance;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=plugin-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-loader.d.ts","sourceRoot":"","sources":["../src/plugin-loader.ts"],"names":[],"mappings":"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"}
|