I am working on a TypeScript solution to ensure exhaustive type-checking for dynamic form fields. The goal is to make sure that all required fields defined in an interface are included in the form configuration (I’m 90% there). While the first example works as expected, the second example should throw a type error because it is missing a required field, but no error is thrown. Here is a TsPlay link or my code below
interface IUser {
fName: string;
mName: string;
lName: string;
}
interface DynamicFormGroupsOptions {}
export interface DynamicFieldDataPartial<K extends string, V = unknown> {
id: string;
name: K;
label: string;
}
type FieldFor<T> = { [K in keyof T]-?: DynamicFieldDataPartial<Extract<K, string>, T[K]> }[keyof T];
type FieldsGroupLayout = { fields: FieldFor<any>[] };
type SpecialInput<T> = {
fields: FieldFor<T>[];
layouts?: FieldsGroupLayout[];
} & Omit<FieldsGroupLayout, 'fields' | 'layouts'>;
type ExtractFieldNames<T extends readonly SpecialInput<any>[]> = {
[K in keyof T]: T[K] extends { fields: infer F }
? F extends FieldFor<any>[]
? F[number]['name']
: never
: never;
}[number];
type ExtractLayoutFieldNames<T extends readonly SpecialInput<any>[]> = {
[K in keyof T]: T[K] extends { layouts: infer L }
? L extends FieldsGroupLayout[]
? {
[M in keyof L]: L[M] extends { fields: infer F }
? F extends FieldFor<any>[]
? F[number]['name']
: never
: never;
}[number]
: never
: never;
}[number];
type CollectedFieldNames<U extends readonly SpecialInput<any>[]> = ExtractFieldNames<U> | ExtractLayoutFieldNames<U>;
type AllFieldNames<T> = { [K in keyof T]: Extract<K, string> }[keyof T];
type MissingFields<T extends object, U extends readonly SpecialInput<T>[]> = Exclude<AllFieldNames<T>, CollectedFieldNames<U>>;
type ValidateFields<T extends object, U extends readonly SpecialInput<T>[]> = MissingFields<T, U> extends never ? true : false;
function e11FormFor<T extends object>(dynamicFormOptions?: DynamicFormGroupsOptions) {
return function <U extends readonly SpecialInput<T>[]>(
props: U & (ValidateFields<T, U> extends true ? {} : { error: "Missing fields" })
): { props: U; dynamicFormOptions?: DynamicFormGroupsOptions } {
return { props, dynamicFormOptions };
};
}
const userForm = e11FormFor<IUser>()([
{
fields: [{ id: '1', name: 'fName', label: 'First Name' }],
layouts: [{ fields: [{ id: '2', name: 'mName', label: 'Middle Name' }, { id: '3', name: 'lName', label: 'Last Name' }] }],
},
]);
const invalidUserForm = e11FormFor<IUser>()([
{
fields: [{ id: '1', name: 'fName', label: 'First Name' }],
layouts: [{ fields: [{ id: '2', name: 'mName', label: 'Middle Name' }] }],
},
]);
const invalidUserFormEmptyLayouts = e11FormFor<IUser>()([
{
fields: [{ id: '1', name: 'fName', label: 'First Name' }],
},
]);
How can I adjust the type definitions and constraints to ensure that TypeScript correctly throws a type error when required fields are missing from the form configuration? This includes weird behavior where having empty fields will also pass all missing errors.