Alright, buckle up because this is going to be a long one...
Reorganization
To begin with, I reorganized some of the types in a more streamlined manner without altering their functionality significantly. This makes it easier to understand and manage intricate types.
The current approach to computing the PluginOptionsMap
seems a bit convoluted. Essentially, it aims to fill all empty keys with undefined
.
type PluginOptionsMap = Omit<
{ [key in ServicePlugin]: undefined },
keyof PluginOptions
> &
PluginOptions;
A cleaner alternative would involve iterating over the keys of the ServicePlugin
enum. For each key, check if it exists within PluginOptions
. If it does, return the corresponding options; otherwise, return undefined.
type PluginOptionsMap = {
[key in ServicePlugin]: key extends keyof PluginOptions
? PluginOptions[key] : undefined
};
Side note - While using 'key' as a generic type works, utilizing single capital letters like 'T' for generics can improve readability, especially in complex TypeScript codebases. Refer to this link for more insights.
Ultimately, the choice is yours - just a helpful tip :)
Tackling Issues
During troubleshooting sessions where I encounter type errors, assigning the troublesome type to a variable and inspecting its properties via hover helps pinpoint the root cause effectively.
Throughout the process, I introduce intermediate types for clarity and validation purposes. Feel free to remove them once your types are refined.
Resolution
Upon analyzing the resulting type of PluginConfig\<ServicePlugin\>[]
, the structure unveiled...
const testParams: {
plugin: ServicePlugin;
options: {
option1: string;
option2?: undefined;
} | {
option1: number;
option2: number;
} | undefined;
}[]
An issue arises whereby TypeScript assigns the union at the Params.options
level instead of the topmost level. Essentially, any plugin value from ServicePlugin
and any compatible options
value is acceptable.
To enforce the creation of unions at the highest level, we design a mapping strategy similar to that used for PluginOptionsMap
but with tailored structuring.
type PluginConfigMap = {
[T in keyof PluginOptionsMap]: PluginOptionsMap[T] extends undefined ? { plugin: T } : {
plugin: T;
options: PluginOptionsMap[T];
}
};
This resulting type takes shape as follows...
type PluginConfigMap = {
0: {
plugin: ServicePlugin.Plugin1;
options: {
option1: string;
};
};
1: {
plugin: ServicePlugin.Plugin2;
options: {
option1: number;
option2: number;
};
};
2: {
plugin: ServicePlugin.Plugin3;
};
}
Subsequently, we extract the values from PluginConfigMap
.
import { ValuesType } from 'utility-types';
type PluginConfigs = ValuesType<PluginConfigMap>
Here, I leverage the ValuesType
helper type from utility-types
. It's a recommended resource for understanding and simplifying type definitions.
With the new PluginConfigs
type in play, the anticipated outcome materializes...
https://i.stack.imgur.com/X9IZh.png
For an in-depth exploration of the topic, refer to the complete solution playground https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgNQIYBsCuBTAzgFQE8xs4BfOAMyghDgHJMZh1gZCBadk3egbgCwAKGHYAdpjoBlbFABuwAMbYAClgDmwMYmFw4azJrEBGADS79GrQCZzQvQaMBmO2WHCtMWZVTLLhrQB5MGYIMVwdezgAbRl5JVUrMQA6Ry...undai\
Further Insights
- At times, dealing with unions of objects may pose challenges in differentiation. Addressing this involves representing each property across all objects within the union. Explore related articles for detailed guidance.