My TypeScript code includes a Logger
class that has an optional options
parameter in its constructor. The options
parameter includes a class
generic
C
which represents custom levels. These custom levels are used within the class for methods like log(level: Level<C>)
. Now, I need to implement an updateConfig
method that will return a new instance of the Logger
class with the C
parameter being a union of the original custom levels and those specified in the new options object.
Here's the code snippet:
type DefaultLevel = 'info' | 'error'; // Default log levels
type Level<C extends string> = DefaultLevel | C
type LevelWithSilence<C extends string> = Level<C> | 'silence'
interface LoggerOptions<C extends string> {
customLevels?: Record<C, number>
level: LevelWithSilence<NoInfer<C>>
}
interface UpdatedLoggerOptions<C extends string, L extends string> {
customLevels?: Record<L, number>
level: LevelWithSilence<NoInfer<C | L >>
}
class Logger<C extends string = ''> {
options?: LoggerOptions<C>
constructor(options?: LoggerOptions<C>) {
this.options = options
}
log(level: Level<C>, message: string) {
// Log message
}
updateConfig<L extends string = never>(options?: UpdatedLoggerOptions<C, L>) {
if(!this.options) return new Logger (options as LoggerOptions<C>)
if(!options) return this
return new Logger<C | L>({
customLevels: {
...this.options.customLevels as Record<C, number>,
...options.customLevels as Record<L, number>
},
level: options.level
} as LoggerOptions<C | L>)
}
}
// Usage
const logger = new Logger({
customLevels: {
debug: 1,
trace: 2,
},
level: 'error'
});
logger.log('debug', '')
const newLogger = logger.updateConfig({
customLevels: {
test: 3,
},
level: 'test'
})
newLogger.log('debug', '') // Works
newLogger.log('test', '') // Should work
Although everything seems to be typesafe, there is an issue with the log
method. While newLogger.log('debug', '')
works fine and extracts the father's custom levels correctly, attempting newLogger.log('test', '')
results in the error:
Argument of type '"test"' is not assignable to parameter of type 'Level<"debug" | "trace">'.
It seems to have trouble extrapolating the custom levels from the options object.
Moreover, the type for the logger
object is shown as:
const logger: Logger<"debug" | "trace">
, while for newLogger
it is: const newLogger: Logger<"debug" | "trace"> | Logger<"debug" | "trace" | "test">
.
How can I achieve the desired behavior?