Take a look at this scenario:
export enum EId {
userId = 'userId',
customerId = 'customerId',
}
type EnsureOne<Obj, Keys = keyof Obj> = Keys extends keyof Obj ? Record<Keys, string> : never
// credits to https://stackoverflow.com/questions/65805600/type-union-not-checking-for-excess-properties#answer-65805753
type UnionProps<T> = T extends T ? keyof T : never;
type StrictUnionHelper<T, TAll> =
T extends any
? T & Partial<Record<Exclude<UnionProps<TAll>, keyof T>, never>> : never;
type StrictUnite<T> = StrictUnionHelper<T, T>
export type IIdentifierParamString =
& StrictUnion<EnsureOne<typeof EId>>
& {
paramString: string;
}
/**
* Yep
*/
const data: IIdentifierParamString = {
userId: '1234',
paramString: 'limit',
};
/**
* Anticipate errors
*/
const data2: IIdentifierParamString = {
paramString: 'limit',
};
const data3: IIdentifierParamString = {
userId: '1234',
customerId: 'sdf',
paramString: 'limit',
};
EnsureOne
- detailed explanation can be found here
StrictUnite
- you can find in-depth explanation here. This function operates similarly to using the concept of `never` as you have done.
Is my methodology generally suitable for solving my issue, ensuring it is either this or that?
Absolutely, your method works. However, I suggest considering discriminated unions (also known as tagged unions). For example, refer to F# discriminated unions. Each union has its own tag/marker which aids the compiler in distinguishing them.
Could this approach be implemented using an interface instead?
Regrettably - no. It must be a type since an `interface` can solely extend statically known types. Refer to the example below:
// An interface can only extend an object type or intersection of object types with statically known members.
export interface IIdentifierParamString extends StrictUnion<EnsureOne<typeof EId>> { // error
paramString: string;
}
In relation to your final query, I may need clarification.
Feel free to revise your question or consider asking separately.