@mks2508/better-logger 2.1.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +8 -1
- package/CHANGELOG.json +168 -1
- package/CHANGELOG.md +127 -0
- package/CLAUDE.md +14 -1
- package/README.md +111 -20
- package/dist/Logger.d.ts +217 -70
- package/dist/Logger.d.ts.map +1 -1
- package/dist/ScopedLogger.d.ts +23 -276
- package/dist/ScopedLogger.d.ts.map +1 -1
- package/dist/chunks/Logger-BV0v1AyC.js +2 -0
- package/dist/chunks/Logger-BV0v1AyC.js.map +1 -0
- package/dist/chunks/{Logger-B7L-ujY4.js → Logger-C086w3EM.js} +868 -102
- package/dist/chunks/Logger-C086w3EM.js.map +1 -0
- package/dist/chunks/environment-C_8J-zQ_.js +4 -0
- package/dist/chunks/environment-C_8J-zQ_.js.map +1 -0
- package/dist/chunks/{environment-TI2ByCPT.js → environment-DXHouGD4.js} +468 -100
- package/dist/chunks/environment-DXHouGD4.js.map +1 -0
- package/dist/chunks/{formatting-CuNUqGks.js → formatting-BNnVUPcw.js} +2 -2
- package/dist/chunks/{formatting-CuNUqGks.js.map → formatting-BNnVUPcw.js.map} +1 -1
- package/dist/chunks/{formatting-Blwy-f0W.js → formatting-Cg5YhB9Y.js} +2 -2
- package/dist/chunks/{formatting-Blwy-f0W.js.map → formatting-Cg5YhB9Y.js.map} +1 -1
- package/dist/core.cjs +1 -1
- package/dist/core.js +2 -2
- package/dist/exports-module.d.ts +1 -1
- package/dist/exports-module.d.ts.map +1 -1
- package/dist/exports.cjs +1 -1
- package/dist/exports.js +2 -2
- package/dist/hooks/HookManager.d.ts +21 -0
- package/dist/hooks/HookManager.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +30 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +433 -20
- package/dist/index.js.map +1 -1
- package/dist/serializers/SerializerRegistry.d.ts +16 -0
- package/dist/serializers/SerializerRegistry.d.ts.map +1 -0
- package/dist/serializers/index.d.ts +2 -0
- package/dist/serializers/index.d.ts.map +1 -0
- package/dist/styling/StyleCache.d.ts +42 -0
- package/dist/styling/StyleCache.d.ts.map +1 -0
- package/dist/styling-module.d.ts +1 -1
- package/dist/styling-module.d.ts.map +1 -1
- package/dist/styling.cjs +1 -1
- package/dist/styling.js +3 -3
- package/dist/terminal/color-converter.d.ts +73 -0
- package/dist/terminal/color-converter.d.ts.map +1 -0
- package/dist/terminal/formatter.d.ts +20 -0
- package/dist/terminal/formatter.d.ts.map +1 -0
- package/dist/terminal/terminal-renderer.d.ts +20 -6
- package/dist/terminal/terminal-renderer.d.ts.map +1 -1
- package/dist/transports/ConsoleTransport.d.ts +9 -0
- package/dist/transports/ConsoleTransport.d.ts.map +1 -0
- package/dist/transports/FileTransport.d.ts +15 -0
- package/dist/transports/FileTransport.d.ts.map +1 -0
- package/dist/transports/HttpTransport.d.ts +16 -0
- package/dist/transports/HttpTransport.d.ts.map +1 -0
- package/dist/transports/TransportManager.d.ts +14 -0
- package/dist/transports/TransportManager.d.ts.map +1 -0
- package/dist/transports/index.d.ts +5 -0
- package/dist/transports/index.d.ts.map +1 -0
- package/dist/types/core.d.ts +55 -0
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/hooks.d.ts +36 -0
- package/dist/types/hooks.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/serializers.d.ts +25 -0
- package/dist/types/serializers.d.ts.map +1 -0
- package/dist/types/transports.d.ts +47 -0
- package/dist/types/transports.d.ts.map +1 -0
- package/dist/utils/environment-detector.d.ts +10 -0
- package/dist/utils/environment-detector.d.ts.map +1 -1
- package/docs/API.md +227 -0
- package/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/packages/styling/package.json +2 -2
- package/src/Logger.ts +403 -114
- package/src/ScopedLogger.ts +78 -318
- package/src/hooks/HookManager.ts +177 -0
- package/src/hooks/index.ts +1 -0
- package/src/index.ts +133 -3
- package/src/serializers/SerializerRegistry.ts +173 -0
- package/src/serializers/index.ts +1 -0
- package/src/styling/StyleCache.ts +131 -0
- package/src/terminal/color-converter.ts +315 -0
- package/src/terminal/formatter.ts +236 -0
- package/src/terminal/terminal-renderer.ts +74 -79
- package/src/transports/ConsoleTransport.ts +28 -0
- package/src/transports/FileTransport.ts +53 -0
- package/src/transports/HttpTransport.ts +56 -0
- package/src/transports/TransportManager.ts +130 -0
- package/src/transports/index.ts +4 -0
- package/src/types/core.ts +68 -0
- package/src/types/hooks.ts +45 -0
- package/src/types/index.ts +44 -1
- package/src/types/serializers.ts +30 -0
- package/src/types/transports.ts +52 -0
- package/src/utils/environment-detector.ts +23 -1
- package/dist/chunks/Logger-B7L-ujY4.js.map +0 -1
- package/dist/chunks/Logger-DzU_c5sX.js +0 -2
- package/dist/chunks/Logger-DzU_c5sX.js.map +0 -1
- package/dist/chunks/ScopedLogger-BY3-E8Ov.js +0 -2
- package/dist/chunks/ScopedLogger-BY3-E8Ov.js.map +0 -1
- package/dist/chunks/ScopedLogger-D-RbiFZn.js +0 -361
- package/dist/chunks/ScopedLogger-D-RbiFZn.js.map +0 -1
- package/dist/chunks/environment-Ba5kShbx.js +0 -4
- package/dist/chunks/environment-Ba5kShbx.js.map +0 -1
- package/dist/chunks/environment-TI2ByCPT.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -98,7 +98,12 @@ export const groupEnd = () => getLogger().groupEnd();
|
|
|
98
98
|
export const time = (label: string) => getLogger().time(label);
|
|
99
99
|
export const timeEnd = (label: string) => getLogger().timeEnd(label);
|
|
100
100
|
export const setGlobalPrefix = (prefix: string) => getLogger().setGlobalPrefix(prefix);
|
|
101
|
-
export const scope = (
|
|
101
|
+
export const scope = (name: string) => getLogger().scope(name);
|
|
102
|
+
export const component = (name: string) => getLogger().component(name);
|
|
103
|
+
export const api = (name: string) => getLogger().api(name);
|
|
104
|
+
export const badges = (badgeList: string[]) => getLogger().badges(badgeList);
|
|
105
|
+
export const badge = (badgeName: string) => getLogger().badge(badgeName);
|
|
106
|
+
export const clearBadges = () => getLogger().clearBadges();
|
|
102
107
|
export const setVerbosity = (level: Verbosity) => getLogger().setVerbosity(level);
|
|
103
108
|
export const addHandler = (handler: ILogHandler) => getLogger().addHandler(handler);
|
|
104
109
|
export const setTheme = (theme: ThemeVariant) => getLogger().setTheme(theme);
|
|
@@ -122,7 +127,31 @@ export type {
|
|
|
122
127
|
LogMetadata,
|
|
123
128
|
StackInfo,
|
|
124
129
|
TimerEntry,
|
|
125
|
-
|
|
130
|
+
TimerResult,
|
|
131
|
+
OutputFormat,
|
|
132
|
+
IScopedLogger,
|
|
133
|
+
IAPILogger,
|
|
134
|
+
IComponentLogger,
|
|
135
|
+
Bindings,
|
|
136
|
+
SerializerFn,
|
|
137
|
+
SerializerContext,
|
|
138
|
+
SerializerConfig,
|
|
139
|
+
ISerializerRegistry,
|
|
140
|
+
HookLogEntry,
|
|
141
|
+
HookEvent,
|
|
142
|
+
HookCallback,
|
|
143
|
+
MiddlewareFn,
|
|
144
|
+
IHookManager,
|
|
145
|
+
TransportRecord,
|
|
146
|
+
TransportOptions,
|
|
147
|
+
TransportTarget,
|
|
148
|
+
ITransport,
|
|
149
|
+
StyleCacheConfig,
|
|
150
|
+
BadgeStyle,
|
|
151
|
+
TimestampFormat,
|
|
152
|
+
ColumnAlign,
|
|
153
|
+
ColumnConfig,
|
|
154
|
+
LogOptions,
|
|
126
155
|
} from './types/index.js';
|
|
127
156
|
|
|
128
157
|
// Styling utilities
|
|
@@ -141,6 +170,57 @@ export {
|
|
|
141
170
|
AnalyticsLogHandler
|
|
142
171
|
} from './handlers/index.js';
|
|
143
172
|
|
|
173
|
+
// Serializers
|
|
174
|
+
export { SerializerRegistry } from './serializers/index.js';
|
|
175
|
+
|
|
176
|
+
// Hooks & Middleware
|
|
177
|
+
export { HookManager } from './hooks/index.js';
|
|
178
|
+
|
|
179
|
+
// Transports
|
|
180
|
+
export {
|
|
181
|
+
TransportManager,
|
|
182
|
+
ConsoleTransport,
|
|
183
|
+
FileTransport,
|
|
184
|
+
HttpTransport
|
|
185
|
+
} from './transports/index.js';
|
|
186
|
+
|
|
187
|
+
// Style Cache
|
|
188
|
+
export { StyleCache, getStyleCache } from './styling/StyleCache.js';
|
|
189
|
+
|
|
190
|
+
// Enterprise feature function exports
|
|
191
|
+
import type { SerializerFn, HookEvent, HookCallback, MiddlewareFn, TransportTarget } from './types/index.js';
|
|
192
|
+
export const addSerializer = <T>(
|
|
193
|
+
type: new (...args: any[]) => T,
|
|
194
|
+
serializer: SerializerFn<T>,
|
|
195
|
+
priority?: number
|
|
196
|
+
) => getLogger().addSerializer(type, serializer, priority);
|
|
197
|
+
export const removeSerializer = <T>(type: new (...args: any[]) => T) =>
|
|
198
|
+
getLogger().removeSerializer(type);
|
|
199
|
+
export const on = (event: HookEvent, callback: HookCallback, priority?: number) =>
|
|
200
|
+
getLogger().on(event, callback, priority);
|
|
201
|
+
export const once = (event: HookEvent, callback: HookCallback, priority?: number) =>
|
|
202
|
+
getLogger().once(event, callback, priority);
|
|
203
|
+
export const off = (event: HookEvent, callback: HookCallback) =>
|
|
204
|
+
getLogger().off(event, callback);
|
|
205
|
+
export const use = (middleware: MiddlewareFn, priority?: number) =>
|
|
206
|
+
getLogger().use(middleware, priority);
|
|
207
|
+
export const addTransport = (target: TransportTarget) =>
|
|
208
|
+
getLogger().addTransport(target);
|
|
209
|
+
export const removeTransport = (id: string) =>
|
|
210
|
+
getLogger().removeTransport(id);
|
|
211
|
+
export const flushTransports = () =>
|
|
212
|
+
getLogger().flushTransports();
|
|
213
|
+
export const closeTransports = () =>
|
|
214
|
+
getLogger().closeTransports();
|
|
215
|
+
|
|
216
|
+
// Scoped loggers
|
|
217
|
+
export {
|
|
218
|
+
ScopedLogger,
|
|
219
|
+
APILogger,
|
|
220
|
+
ComponentLogger,
|
|
221
|
+
ContextLogger
|
|
222
|
+
} from './ScopedLogger.js';
|
|
223
|
+
|
|
144
224
|
// Constants
|
|
145
225
|
export { DEFAULT_CONFIG } from './constants.js';
|
|
146
226
|
|
|
@@ -241,4 +321,54 @@ export const stylePresets = {
|
|
|
241
321
|
warning: StylePresets.warning().build(),
|
|
242
322
|
info: StylePresets.info().build(),
|
|
243
323
|
accent: StylePresets.accent().build(),
|
|
244
|
-
};
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// Terminal formatting utilities
|
|
327
|
+
export {
|
|
328
|
+
formatWithRightAlign,
|
|
329
|
+
formatColumns,
|
|
330
|
+
formatKeyValue,
|
|
331
|
+
formatBadge,
|
|
332
|
+
formatTimestamp as formatTimestampExt,
|
|
333
|
+
formatRelativeTime,
|
|
334
|
+
formatElapsedTime,
|
|
335
|
+
resetStartTime,
|
|
336
|
+
isKeyValueObject,
|
|
337
|
+
stripAnsi,
|
|
338
|
+
getVisibleLength,
|
|
339
|
+
padToWidth,
|
|
340
|
+
applyLogOptions,
|
|
341
|
+
} from './terminal/formatter.js';
|
|
342
|
+
|
|
343
|
+
// Color converter utilities
|
|
344
|
+
export {
|
|
345
|
+
hexToRgb,
|
|
346
|
+
rgbToHex,
|
|
347
|
+
hexTo256,
|
|
348
|
+
colorToHex,
|
|
349
|
+
getANSIForeground,
|
|
350
|
+
getANSIBackground,
|
|
351
|
+
getANSI256Foreground,
|
|
352
|
+
getANSI256Background,
|
|
353
|
+
cssColorToANSI,
|
|
354
|
+
ANSI,
|
|
355
|
+
} from './terminal/color-converter.js';
|
|
356
|
+
|
|
357
|
+
export type { RGB, ColorCapability } from './terminal/color-converter.js';
|
|
358
|
+
|
|
359
|
+
// Terminal renderer
|
|
360
|
+
export { TerminalRenderer } from './terminal/terminal-renderer.js';
|
|
361
|
+
export type { ANSIStyle, ChalkLikeInterface } from './terminal/terminal-renderer.js';
|
|
362
|
+
|
|
363
|
+
// Environment detection
|
|
364
|
+
export {
|
|
365
|
+
getEnvironment,
|
|
366
|
+
isRunningInTerminal,
|
|
367
|
+
supportsANSI,
|
|
368
|
+
getColorCapability,
|
|
369
|
+
getTerminalWidth,
|
|
370
|
+
getTerminalHeight,
|
|
371
|
+
getEnvironmentInfo,
|
|
372
|
+
} from './utils/environment-detector.js';
|
|
373
|
+
|
|
374
|
+
export type { Environment } from './utils/environment-detector.js';
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SerializerFn,
|
|
3
|
+
SerializerContext,
|
|
4
|
+
SerializerEntry,
|
|
5
|
+
SerializerConfig,
|
|
6
|
+
ISerializerRegistry
|
|
7
|
+
} from '../types/index.js';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_CONFIG: Required<SerializerConfig> = {
|
|
10
|
+
maxDepth: 5,
|
|
11
|
+
circular: 'placeholder',
|
|
12
|
+
preserveUndefined: false
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export class SerializerRegistry implements ISerializerRegistry {
|
|
16
|
+
private serializers: Map<Function, SerializerEntry> = new Map();
|
|
17
|
+
private config: Required<SerializerConfig>;
|
|
18
|
+
|
|
19
|
+
constructor(config: SerializerConfig = {}) {
|
|
20
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
21
|
+
this.registerDefaults();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private registerDefaults(): void {
|
|
25
|
+
this.add(Error, (err: Error) => ({
|
|
26
|
+
name: err.name,
|
|
27
|
+
message: err.message,
|
|
28
|
+
stack: err.stack?.split('\n').slice(0, 10),
|
|
29
|
+
...(err.cause ? { cause: this.serialize(err.cause) } : {})
|
|
30
|
+
}), 100);
|
|
31
|
+
|
|
32
|
+
this.add(Date, (date: Date) => ({
|
|
33
|
+
iso: date.toISOString(),
|
|
34
|
+
timestamp: date.getTime()
|
|
35
|
+
}), 90);
|
|
36
|
+
|
|
37
|
+
this.add(RegExp, (regex: RegExp) => ({
|
|
38
|
+
pattern: regex.source,
|
|
39
|
+
flags: regex.flags
|
|
40
|
+
}), 90);
|
|
41
|
+
|
|
42
|
+
this.add(Map, (map: Map<any, any>, ctx) => {
|
|
43
|
+
const obj: Record<string, any> = {};
|
|
44
|
+
map.forEach((value, key) => {
|
|
45
|
+
const keyStr = typeof key === 'object' ? JSON.stringify(key) : String(key);
|
|
46
|
+
obj[keyStr] = this.serializeInternal(value, {
|
|
47
|
+
...ctx,
|
|
48
|
+
depth: ctx.depth + 1,
|
|
49
|
+
path: [...ctx.path, keyStr]
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
return { __type: 'Map', entries: obj };
|
|
53
|
+
}, 80);
|
|
54
|
+
|
|
55
|
+
this.add(Set, (set: Set<any>, ctx) => ({
|
|
56
|
+
__type: 'Set',
|
|
57
|
+
values: Array.from(set).map((v, i) =>
|
|
58
|
+
this.serializeInternal(v, {
|
|
59
|
+
...ctx,
|
|
60
|
+
depth: ctx.depth + 1,
|
|
61
|
+
path: [...ctx.path, `[${i}]`]
|
|
62
|
+
})
|
|
63
|
+
)
|
|
64
|
+
}), 80);
|
|
65
|
+
|
|
66
|
+
if (typeof Buffer !== 'undefined') {
|
|
67
|
+
this.add(Buffer as any, (buf: Buffer) => ({
|
|
68
|
+
__type: 'Buffer',
|
|
69
|
+
length: buf.length,
|
|
70
|
+
preview: buf.slice(0, 50).toString('hex')
|
|
71
|
+
}), 70);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
add<T>(
|
|
76
|
+
type: new (...args: any[]) => T,
|
|
77
|
+
serializer: SerializerFn<T>,
|
|
78
|
+
priority: number = 50
|
|
79
|
+
): void {
|
|
80
|
+
this.serializers.set(type, { type, serializer, priority });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
remove<T>(type: new (...args: any[]) => T): boolean {
|
|
84
|
+
return this.serializers.delete(type);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
has<T>(type: new (...args: any[]) => T): boolean {
|
|
88
|
+
return this.serializers.has(type);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
getAll(): SerializerEntry[] {
|
|
92
|
+
return Array.from(this.serializers.values())
|
|
93
|
+
.sort((a, b) => (b.priority ?? 50) - (a.priority ?? 50));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private findSerializer(value: any): SerializerEntry | null {
|
|
97
|
+
const sorted = this.getAll();
|
|
98
|
+
for (const entry of sorted) {
|
|
99
|
+
if (value instanceof entry.type) {
|
|
100
|
+
return entry;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private serializeInternal(value: any, context: SerializerContext): any {
|
|
107
|
+
if (value === null) return null;
|
|
108
|
+
if (value === undefined) return this.config.preserveUndefined ? undefined : '[undefined]';
|
|
109
|
+
if (typeof value === 'function') return `[Function: ${value.name || 'anonymous'}]`;
|
|
110
|
+
if (typeof value !== 'object') return value;
|
|
111
|
+
|
|
112
|
+
if (context.depth >= this.config.maxDepth) {
|
|
113
|
+
return '[Max Depth]';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (context.seen.has(value)) {
|
|
117
|
+
switch (this.config.circular) {
|
|
118
|
+
case 'error':
|
|
119
|
+
throw new Error(`Circular reference at ${context.path.join('.')}`);
|
|
120
|
+
case 'skip':
|
|
121
|
+
return undefined;
|
|
122
|
+
case 'placeholder':
|
|
123
|
+
default:
|
|
124
|
+
return '[Circular]';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
context.seen.add(value);
|
|
128
|
+
|
|
129
|
+
const serializer = this.findSerializer(value);
|
|
130
|
+
if (serializer) {
|
|
131
|
+
return serializer.serializer(value, context);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (Array.isArray(value)) {
|
|
135
|
+
return value.map((item, i) =>
|
|
136
|
+
this.serializeInternal(item, {
|
|
137
|
+
...context,
|
|
138
|
+
depth: context.depth + 1,
|
|
139
|
+
path: [...context.path, `[${i}]`]
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const result: Record<string, any> = {};
|
|
145
|
+
for (const [key, val] of Object.entries(value)) {
|
|
146
|
+
result[key] = this.serializeInternal(val, {
|
|
147
|
+
...context,
|
|
148
|
+
depth: context.depth + 1,
|
|
149
|
+
path: [...context.path, key]
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
serialize(value: any, config?: SerializerConfig): any {
|
|
156
|
+
const mergedConfig = { ...this.config, ...config };
|
|
157
|
+
return this.serializeInternal(value, {
|
|
158
|
+
depth: 0,
|
|
159
|
+
maxDepth: mergedConfig.maxDepth,
|
|
160
|
+
path: [],
|
|
161
|
+
seen: new WeakSet()
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let _defaultRegistry: SerializerRegistry | null = null;
|
|
167
|
+
|
|
168
|
+
export function getDefaultSerializerRegistry(): SerializerRegistry {
|
|
169
|
+
if (!_defaultRegistry) {
|
|
170
|
+
_defaultRegistry = new SerializerRegistry();
|
|
171
|
+
}
|
|
172
|
+
return _defaultRegistry;
|
|
173
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SerializerRegistry, getDefaultSerializerRegistry } from './SerializerRegistry.js';
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { LogLevel, DevToolsTheme } from '../types/core.js';
|
|
2
|
+
|
|
3
|
+
interface CacheKey {
|
|
4
|
+
level: LogLevel | 'success';
|
|
5
|
+
theme: DevToolsTheme;
|
|
6
|
+
presetName?: string;
|
|
7
|
+
hasPrefix: boolean;
|
|
8
|
+
hasLocation: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface CacheEntry {
|
|
12
|
+
styles: string[];
|
|
13
|
+
format: string;
|
|
14
|
+
createdAt: number;
|
|
15
|
+
hits: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface StyleCacheConfig {
|
|
19
|
+
maxEntries?: number;
|
|
20
|
+
ttl?: number;
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const DEFAULT_CONFIG: Required<StyleCacheConfig> = {
|
|
25
|
+
maxEntries: 100,
|
|
26
|
+
ttl: 5 * 60 * 1000,
|
|
27
|
+
enabled: true
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export class StyleCache {
|
|
31
|
+
private cache: Map<string, CacheEntry> = new Map();
|
|
32
|
+
private config: Required<StyleCacheConfig>;
|
|
33
|
+
private hitCount = 0;
|
|
34
|
+
private missCount = 0;
|
|
35
|
+
|
|
36
|
+
constructor(config: StyleCacheConfig = {}) {
|
|
37
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private generateKey(key: CacheKey): string {
|
|
41
|
+
return `${key.level}:${key.theme}:${key.presetName || 'default'}:${key.hasPrefix}:${key.hasLocation}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get(key: CacheKey): CacheEntry | null {
|
|
45
|
+
if (!this.config.enabled) return null;
|
|
46
|
+
|
|
47
|
+
const cacheKey = this.generateKey(key);
|
|
48
|
+
const entry = this.cache.get(cacheKey);
|
|
49
|
+
|
|
50
|
+
if (!entry) {
|
|
51
|
+
this.missCount++;
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (Date.now() - entry.createdAt > this.config.ttl) {
|
|
56
|
+
this.cache.delete(cacheKey);
|
|
57
|
+
this.missCount++;
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
entry.hits++;
|
|
62
|
+
this.cache.delete(cacheKey);
|
|
63
|
+
this.cache.set(cacheKey, entry);
|
|
64
|
+
this.hitCount++;
|
|
65
|
+
|
|
66
|
+
return entry;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
set(key: CacheKey, format: string, styles: string[]): void {
|
|
70
|
+
if (!this.config.enabled) return;
|
|
71
|
+
|
|
72
|
+
const cacheKey = this.generateKey(key);
|
|
73
|
+
|
|
74
|
+
if (this.cache.size >= this.config.maxEntries) {
|
|
75
|
+
const firstKey = this.cache.keys().next().value;
|
|
76
|
+
if (firstKey) {
|
|
77
|
+
this.cache.delete(firstKey);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.cache.set(cacheKey, {
|
|
82
|
+
format,
|
|
83
|
+
styles: [...styles],
|
|
84
|
+
createdAt: Date.now(),
|
|
85
|
+
hits: 0
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
clear(): void {
|
|
90
|
+
this.cache.clear();
|
|
91
|
+
this.hitCount = 0;
|
|
92
|
+
this.missCount = 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
getStats(): {
|
|
96
|
+
size: number;
|
|
97
|
+
maxSize: number;
|
|
98
|
+
hitRate: number;
|
|
99
|
+
hits: number;
|
|
100
|
+
misses: number;
|
|
101
|
+
} {
|
|
102
|
+
const total = this.hitCount + this.missCount;
|
|
103
|
+
return {
|
|
104
|
+
size: this.cache.size,
|
|
105
|
+
maxSize: this.config.maxEntries,
|
|
106
|
+
hitRate: total > 0 ? this.hitCount / total : 0,
|
|
107
|
+
hits: this.hitCount,
|
|
108
|
+
misses: this.missCount
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
setEnabled(enabled: boolean): void {
|
|
113
|
+
this.config.enabled = enabled;
|
|
114
|
+
if (!enabled) {
|
|
115
|
+
this.clear();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
isEnabled(): boolean {
|
|
120
|
+
return this.config.enabled;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let _styleCache: StyleCache | null = null;
|
|
125
|
+
|
|
126
|
+
export function getStyleCache(): StyleCache {
|
|
127
|
+
if (!_styleCache) {
|
|
128
|
+
_styleCache = new StyleCache();
|
|
129
|
+
}
|
|
130
|
+
return _styleCache;
|
|
131
|
+
}
|