Let say I want to create a "feature-config", which has the following type:
type FeatureConfig = {
isActivated: boolean;
childs?: {
[key: string]: FeatureConfig;
}
}
const config : FeatureConfig = {
isActivated: true,
childs: {
bar: {
isActivated: true,
childs: {
foo: {
isActivated: true
}
}
},
foo: {
isActivated: false,
childs: {
bar: {
isActivated: true
}
}
}
}
}
All fine and typed-checked this far. Now I want to create a "build"-function which takes a Config
and returns a "feature-tree" which I can check if a certain feature is activated. My goal with the function is to have the following syntax/functionality:
/// How should the types of this function look like??
const createFeatureChecker = (config: FeatureConfig) => {
//...implementation
}
const featureChecker = createFeatureChecker(config);
featureChecker.bar.foo.isActivated // true
featureChecker.foo.isActivated // false
featureChecker.foo.bar.isActivated // false (since featureChecker.foo.isActivated is false)
I have no problems implementing the function logic in plain old javascript, but I'm struggling to type createFeatureChecker
in a way so that I can use the featureChecker
in a type-safe way.
Any ideas on how to solve my issue?
CodePudding user response:
You will need to use as const
declaring config
or pass it as an object literal.
const config = {
isActivated: true,
childs: {
bar: {
isActivated: true,
childs: {
foo: {
isActivated: true
}
}
},
foo: {
isActivated: false,
childs: {
bar: {
isActivated: true
}
}
}
}
} as const
A typed createFeatureFunction
would look like this:
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never
type CreateFeatureChecker<T extends FeatureConfig, Activated extends boolean = true> = {
isActivated: Activated extends true ? T["isActivated"] : false
} & {
-readonly [K in keyof (T["childs"])]:
(T["isActivated"] extends false ? false : Activated extends false ? false : true) extends infer A extends boolean
? T["childs"][K] extends { isActivated: infer B extends boolean }
? T["childs"][K] extends { childs: infer C extends {
[key: string]: FeatureConfig;
}}
? Expand<CreateFeatureChecker<{ isActivated: B, childs: C }, A>>
: Expand<CreateFeatureChecker<{ isActivated: B }, A>>
: never
: never
}
const createFeatureChecker = <T extends FeatureConfig>(config: T): Expand<CreateFeatureChecker<T>> => {
return {} as any
}
Some tests so see if it's working:
const featureChecker = createFeatureChecker(config);
const a = featureChecker.bar.foo.isActivated // true
const b = featureChecker.foo.isActivated // false
const c = featureChecker.foo.bar.isActivated // false