First, you need to get rid of the index signature as index signature contributes to the shape of the type:
keyof { [x:string]: any }; // string | number
Credit for a generic utility type goes to Mihail, I only adapted it to TS 4.1+. The gist of it is that if string
is a subtype of keyof T
, it means that the type has a string
index signature (check against number
gets us numeric signature):
type RemoveIndex<T> = {
[ P in keyof T as string extends P ? never : number extends P ? never : P ] : T[P]
};
Next, you need to make the compiler infer the type of the argument passed to the parameter to process. Therefore you need a generic function signature, in your case with a single parameter constrained to FunctionMap
.
Finally, you have to ensure the inferred type passes the "no empty objects" constraint. This can be achieved by applying an observation that keyof {}
is never
(see this Q&A for details). Therefore a conditional keyof RemoveIndex<T> extends never ? never : T
ensures that such empty object types are not assignable (see this Q&A for a similar example):
function needsARecordOfFunctions<T extends FunctionMap>(functions: keyof RemoveIndex<T> extends never ? never : T) { /* ... */ }
Bringing all of this together:
needsARecordOfFunctions({ myFunc: 'foobar' }); // Type 'string' is not assignable to type 'never'
needsARecordOfFunctions(); // Expected 1 arguments, but got 0.
needsARecordOfFunctions({ myFunc: () => {}, func2: () => 42 }); // ✅
needsARecordOfFunctions({}); // error, expected
Playground