Reputation: 512
I'm trying to create a .d.ts
file for a library that has a settings object that allows the dev to supply another object containing functions that will get called to perform logging.
Something like this:
type LogFunction = (message?: any, ...optionalParams: any[]) => void;
interface Logger {
warn: LogFunction;
log: LogFunction;
// several more methods here
}
interface Settings {
logger?: Logger;
}
This interface works so long as the function properties are named warn
, log
, etc. However, this settings object also lets the developer pass in other names to use for the properties/functions. For example, instead of using warn
as the name of the property/function to call, I can tell it to call one named anotherName
via code like the following.
const settings = {
logger: { anotherName: function() { .... } },
logLevelMethods: [ "anotherName" ]
}
How do I write this type of declaration?
I know TS 2.9 implemented some level of support for using Symbols as property names, but I can't find a way of associating the values in the array for key names in the logging object.
Am I overlooking it?
Thanks!
If it helps, I'm trying to write a declaration for the jquery-mockjax
jquery plugin.
Upvotes: 2
Views: 642
Reputation: 249656
You need to use generics and mapped types to achieve this. You can defined Logger
as a mapped type that takes in a type that will be a union of it's keys:
type Logger<T extends string | symbol| number > = { [P in T]: LogFunction}
And you can forward the type from Settings
to Logger
interface Settings<T extends string | symbol| number = "log" | "warn"| "error"> {
logger?: Logger<T>;
logLevelMethods: T[]
}
When declaring an instance you would normally need to specify the type parameter:
const simple: Settings<"log" | "warn"| "error"> = {
logLevelMethods: [], // while this can contain the names, it does not have to unfortunately
logger: {
error: () => {},
warn: () => {},
log: () => {},
}
}
A better solution is to create a function that helps us to create a settings object:
function createSettings<T extends string | symbol | number >(settings: Settings<T> ){
return settings;
}
const otherSymbol = Symbol("Test");
const settings = createSettings({
logLevelMethods : ["warn", "other", "new", 0, otherSymbol],
logger: {
0 : ()=>{},
// 1 : ()=>{}, // This would be an error
// log: ()=> {}, // Also an error not in the array
warn: ()=> {},
other: () => {},
new: ()=> {},
[otherSymbol]: ()=> {}
}
})
This also has the advantage of enforcing the constraint that the array and the members of logger agree (provided no explicit argument is passed to the function)
Note: I used string | symbol| number
as that is the maximum allowable domain for keys in 2.9
you can remove any of the members in this union to not support that kind of key (for example remove number
to disallow numeric keys)
Upvotes: 1