I'm trying to create a method where typed builders can be created that hide properties during implementation. I came across this proxy approach and I'm close but I can't seem to get the build
method to return the correct type.
type ProxyBuilder<Type, SubType = Type> = {
[Key in keyof SubType]: (arg: SubType[Key]) => ProxyBuilder<SubType>;
} & { build(): Type }; // `Type` is ignored (see below)
function proxyBuilder<Type, SubType = Type>(
defaultValues?: Partial<Type>
): ProxyBuilder<Type, SubType> {
const built: any = { ...defaultValues };
const builder = new Proxy(
{},
{
get: function (_target, prop) {
if (prop === 'build') return () => built as Type;
return (value: any) => {
built[prop] = value;
return builder as SubType;
};
},
}
);
return builder as any;
}
type User = {
type: 'admin' | 'basic';
name: string;
age: number;
};
const basicUserBuilder = () =>
proxyBuilder<User, Omit<User, 'type'>>({ type: 'basic' });
const jane = basicUserBuilder()
.type('admin') // "hidden" so you get intellisense error
.name('Jane')
.age(42)
.build(); // Returning `Omit<User, 'type'>` but I want `User`
& { build(): Type }
Your Type
here is not being ignored, it's just being replaced by whatever you pass to the resulting proxy builder as you chain your methods:
type ProxyBuilder<Type, SubType = Type> = {
[...]: (...) => ProxyBuilder<SubType>; // <-- the problem
} & { build(): Type };
You need to carry your main type as you chain the methods:
type ProxyBuilder<Type, SubType = Type> = {
[Key in keyof SubType]: (arg: SubType[Key]) => ProxyBuilder<Type, SubType>;
} & { build(): Type };
or
type ProxyBuilder<Type, SubType = Type> = {
[Key in keyof SubType]: (arg: SubType[Key]) => ProxyBuilder<Type, Omit<SubType, Key>>;
} & { build(): Type };