Make an exception at certain fields

Viewed 135

I have a method that saves an author with the .findOneAndUpdate

AuthorInterface looks like this:

    export interface AuthorInterface {
        name: string,
        bio: string,
        githubLink: string,
        stackoverflowLink: string,
        twitterLink: string,
        image: string,
        image_webp: string,
    }

In this case I need to make an exception here. image and image_webp are image paths and therefore I cannot just override the values. I already used Omit to remove it out of the AuthorInterface argument.

However typescript still complains at author: { ...author } that image_webp and image fields are missing. How do I tell typescript that I don't expect image and image_webp properties to be in the argument object?

    public saveAuthor(_id: mongoose.Types.ObjectId | string, author: Omit<AuthorInterface, "image" | "image_webp">): Promise<UserModelInterface | null> {
        return User.findOneAndUpdate({ _id }, { author: { ...author } }, { new: true }).exec()
    }
4 Answers

change your AuthorInterface like this

export interface AuthorInterface {
    name: string,
    bio: string,
    githubLink: string,
    stackoverflowLink: string,
    twitterLink: string,
    image?: string,
    image_webp?: string,
}

This is the beauty of typescript.

public saveAuthor(_id: mongoose.Types.ObjectId | string, author: unknown): Promise<UserModelInterface | null> {
    return User.findOneAndUpdate({ _id }, { author: author as AuthorInterface }, { new: true }).exec()
}

Here is my solution:

import mongoose from 'mongoose'

interface AuthorInterface {
        name: string,
        bio: string,
        githubLink: string,
        stackoverflowLink: string,
        twitterLink: string,
        image: string,
        image_webp: string,
}

type AuthorUpdate = Omit<AuthorInterface, "image" | "image_webp">;

/**
 * I'd willing to bet, that you can change/update/modify
 * User object/class
 * 
 * Here what you should do:
 */
interface User {
        findOneAndUpdate: <T>(arg1: any, author: AuthorUpdate extends T ? AuthorUpdate : T, arg3: any) => any
}

/**
 * User mock, just for test purpose
 */
const User: User = {
        findOneAndUpdate: (...args: any[]) => { }
}

/**
 * Now, if you pass AuthorUpdate to findOneAndUpdate it should work as expected
 */
function saveAuthor(_id: mongoose.Types.ObjectId | string, author: AuthorUpdate) {
        return User.findOneAndUpdate({ _id }, { author: { ...author } }, { new: true }).exec()
}

/**
 * If you have problems with my solution, please share the minimum reproducable example
 */

For your saveAuthor function, the function only uses author to clone and pass to User.findOneAndUpdate.

To be very literal, you could type saveAuthor's parameter as:

author: Parameters<(typeof User)['findOneAndUpdate']>[1].author

This is an ugly way of saying: "this function expects what User.findOneAndUpdate expects".

But a better way is to use the established type. Assuming findOneAndUpdate accepts AuthorInterface in the 2nd param for the author prop, just write:

author: AuthorInterface

But you're trying to remove properties.. This statement of yours sounds odd:

"image and image_webp are image paths and therefore I cannot just override the values".

But you're not modifying these properties in saveAuthor. Are you worried about User.findOneAndUpdate modifying the properties? That should not matter since you're cloning the object with the spread operator: { ...author }.

It sounds like you might be confusing types with values. Using Omit doesn't remove properties from the actual value; it only relaxes the parameter's type requirements. You can still pass in an object with image and image_webp in most cases (typescript's "excess property checks" heuristic will not kick in unless you're writing a literal object).

You seem to misunderstand the typescript error at author: { ...author }. You say "How do I tell typescript that I don't expect these properties to be in this object?" But the error isn't about what your saveAuthor function expects, it's about what User.findOneAndUpdate expects. You've passed it a deficient AuthorInterface (not a full AuthorInterface), but it requires a full AuthorInterface.

If User.findOneAndUpdate is requiring too much, then you can change it to relax its parameter types. If you're trying to remove the values, do this:

author: {
    ...author,
    image: undefined,
    image_webp: undefined,
}

If User.findOneAndUpdate's types don't accept this, then fix them by using nancy's answer, assuming the code supports this optionality.

Related