Some typescript magic to make named arguments a reality... Sort of.
type Fn = (arg: any) => any;
// oh boy don't do this
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type LastOf<T> = UnionToIntersection<T extends any ? () => T : never> extends () => (infer R) ? R : never
// TS4.1+
type TuplifyUnion<T, L = LastOf<T>, N = [T] extends [never] ? true : false> = true extends N ? [] : [...TuplifyUnion<Exclude<T, L>>, L]
/**
* Sometimes you want to be able to take named parameters,
* and or
*
* Should put optional parameters last.
*/
type Never<T> = { [K in keyof T as T[K] extends never ? never : K extends 'prototype' ? never : K]: T[K] }
type WithRequiredKeys<T> = Never<{ [K in keyof T]-?: {} extends Pick<T, K> ? never : [K, T[K]] }>;
type WithOptionalKeys<T> = Never<{ [K in keyof T]-?: {} extends Pick<T, K> ? [K, T[K]] : never }>;
type NamedParameters_<T> = TuplifyUnion<T[keyof T]>
type NamedParameters<T> = [...NamedParameters_<WithRequiredKeys<T>>, ...NamedParameters_<WithOptionalKeys<T>>];
type FlattenName<T> = T extends [[infer Name, infer Type], ...infer Rest] ? [Name, Type, ...FlattenName<Rest>] : [];
type FlattenType<T> = T extends [[any, infer Type], ...infer Rest] ? [Type, ...FlattenType<Rest>] : [];
type Optional<T> = T extends [ infer K, infer V, ...infer Rest] ? ([K, V, ...Optional<Rest>] | Optional<Rest>) : [];
type AllArgs<T> = [T] |[...FlattenName<NamedParameters_<WithRequiredKeys<T>>>, ...Optional<FlattenName<NamedParameters_<WithOptionalKeys<T>>>>];
type F = AllArgs<Args>;
function otherIsString(all: any[]) {
const ret = {} as Record<string, unknown>;
for (let i = 0; i < all.length; i += 2) {
if (typeof all[i] !== 'string') {
return;
} else {
ret[all[i]] = all[i + 1];
}
}
return ret;
}
function args<T extends Fn>(fn: T): (...a: AllArgs<Parameters<T>[0]>) => ReturnType<T> {
return (...all: AllArgs<Parameters<T>[0]>): ReturnType<T> => {
if (all.length === 1) {
return fn.call(null, all[0]);
}
let obj = otherIsString(all)
if (!obj) {
throw new Error('not a valid call signature');
}
return fn.call(null, obj)
}
}
type Args = { hello?: string, goodbye: number, name: string }
function myFunc(arg: Args) {
return true;
}
const f2 = args(myFunc);
f2('goodbye', 1, 'name', 'joe', 'hello', 'doe')
f2({ goodbye: 1, name:'Joe' })
class Stuff {
foo = args((v:Args)=>{
console.log('helo ', v.name);
})
}
var s = new Stuff();
s.foo( 'goodbye', 1, 'name', 'John');
s.foo( 'goodbye', 1, 'name', 'John', 'hello', 'world');
s.foo({goodbye:1, name:'John', hello:'World'})