1

I have the following create function, which have two definitions:

// Definition 1
export async function create<Entity>(
  entity: ObjectType<Entity>,
  data: DeepPartial<Entity>
): Promise<Entity>;

// Definition 2 
export async function create<Entity>(
  entity: ObjectType<Entity>,
  data: DeepPartial<Entity>,
  pickValues: keyof Entity
): Promise<Partial<Entity>>;

// Implementation
export async function create<Entity>(
  entity: ObjectType<Entity>,
  data: DeepPartial<Entity>,
  pickValues?: keyof Entity
): Promise<Entity | Partial<Entity>> {
  const { manager } = await connectDatabase();

  const instance = manager.create(entity, data);
  const createdEntity = await manager.save(instance);

  return typeof pickValues === 'undefined'
    ? createdEntity
    : pick(createdEntity, pickValues);
}

Is there a way to merge those two definitions in the typings of the function implementation? Maybe with a conditional return type... Something like this:

A extends B ? c : d;

1 Answer 1

2

It is indeed possible to use a conditional return type for this. What we need to do is introduce a generic type for the pickValues argument so we can use it for the return type:

export async function create<Entity, PickValues extends keyof Entity | undefined = undefined>(
  entity: ObjectType<Entity>,
  data: DeepPartial<Entity>,
  pickValues?: PickValues
): Promise<PickValues extends undefined ? Entity : Partial<Entity>> {
  const { manager } = await connectDatabase();

  const instance = manager.create(entity, data);
  const createdEntity = await manager.save(instance);

  return (typeof pickValues === 'undefined'
    ? createdEntity
    : pick(createdEntity, pickValues as keyof Entity)) as any;
}

Unfortunately due to design limitations in TypeScript it doesn't really understand that the return type should be different based on the value of pickValues (github issue). Because of this we have to cast the return value to any to make it work.

Because of the necessary casts and the added complexity I would recommend to stick with the overloads for better readability.


Note: TypeScript actually has a utility type Pick which should allow you to be even more specific. Instead of Partial<Entity> you could use Pick<Entity, Exclude<Values, undefined>> in the return type.

Sign up to request clarification or add additional context in comments.

7 Comments

Yes, I tried to use this kind of generic type, but I get this error: Type 'Partial<Entity>' is not assignable to type 'PickValues extends undefined ? Entity : Partial<Entity>'.ts(2322). See it here.
Sorry I've made a mistake in the code example. The argument pickValues should have the type PickValues. Answer is updated.
Hmm although I don't think that will fix the error... I'll try and figure out what the issue is.
Even if it works with all these casts... I would probably prefer the two overloads instead. Mainly because they are easier to read and understand.
No worries. I'm kind of bummed out myself that it didn't work out nicely. I've updated the answer with the casts and info about why they're needed.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.