typescripttypestype-coercion

How can I coerce the Typescript type of a method in a proxyHandler


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.

Stackblitz example

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`

enter image description here


Solution

  • & { 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 };