To enhance your interface, consider extending a mapped conditional type that depends on the interface itself. This is a recursive type definition known as F-bounded quantification, which allows for complex type constraints. Here's an example:
type Restrict<T, U> = { [K in keyof U]: K extends keyof T ? T[K] : never };
type RestrictClient<U> = Restrict<Client, U>;
// Works as desired
interface Okay extends RestrictClient<Okay> {
firstName: string;
lastName: string;
cellNumberFull: string;
}
// Error occurs intentionally
interface Extra extends RestrictClient<Extra> {
// ~~~~~
// Types of property 'foo' are incompatible.
// Type 'string' is not assignable to type 'never'.
firstName: string;
lastName: string;
cellNumberFull: string;
foo: string;
}
By defining your new interface as
I extends RestrictClient<I>
, it will only work if
I
can be assigned to
RestrictClient<I></code. Essentially, this means each key <code>K
in
I
must exist in
Client
with the same or narrower type.
This approach leads to the following outcomes:
// Properties can be narrowed
interface Narrowed extends RestrictClient<Narrowed> {
firstName: "specificString";
}
// Properties cannot be widened
interface Widened extends RestrictClient<Widened> {
// ~~~~~~~ <-- number not assignable to string
firstName: string | number;
}
// Changing properties to unrelated types results in error
interface Unrelated extends RestrictClient<Unrelated> {
// ~~~~~~~~~ <-- number not assignable to string
firstName: number;
}
If this solution doesn't align perfectly with your requirements, you might need to adjust the definition of Restrict
for a closer match. Nevertheless, I hope this provides some valuable insights for your project. Good luck!
Link to code