Home > Blockchain >  Custom type in TypeScript for ID that is not in the DB
Custom type in TypeScript for ID that is not in the DB

Time:02-01

I am currently building a quite big web-application with typescript as its backend. I have a function that adds a user to my database:

function doSomethingWithUser(id: ValidUserId, ...someOtherInformation: any[]): boolean {
    // doing the db stuff
    return true
}

but i want the id parameter to have its own type called NewUserId which should only be used for strings that contain user ids that are currently not in the database. I want to have this feature, because currently i always have to remember to check if the id does not already exist in the db, with the own type typescript would remind me to check for that, because there would be a type conflict.

I tried using a custom typeguard by typescript:

type ValidUserId = string & {"ValidUserId": {}}
function isValidUserId(id: string): id is ValidUserId {
    // check if there is no user with the id in the database
    return numberOfUsersWithThisId == 0
}

This would work, but the problem is, that i obviously would need an async function for the typeguard to do some db stuff, but typescript does not allow for async type guards. Is there a good replacement for this?

CodePudding user response:

As you noted, there is currently no support for asynchronous custom type guard functions returning something like Promise<id is ValidUserId>. There's an open feature request for such support at microsoft/TypeScript#37681, but it's not clear when or even if it will be implemented. For now you just have to work around it.


Probably the most direct workaround is to give up on custom type guards and just return a value which is either of the narrowed type (if the type guard function would have returned true) or undefined (if the type guard function would have returned false), like this:

declare function toValidUserId(id: string): Promise<ValidUserId | undefined>;

implemented however you need to implement it:

async function toValidUserId(id: string): Promise<ValidUserId | undefined> {
    const isValid = await Math.random() < 0.5; // <-- do a real check here
    return isValid ? (id as ValidUserId) : undefined;
}

keeping in mind that you'll need some kind of assertion to convince the compiler that id is an acceptable instance of a branded primitive type.

Anyway, now you'd use the return value from toValidUserId() instead of re-using its argument:

async function foo() {
    const id = "abcde";
    const validatedUserId = await toValidUserId(id);
    if (validatedUserId) {
        // is valid
        doSomethingWithUser(validatedUserId); // okay
    } else {
        // is not valid
        console.log(id   " isn't a valid id!");
    }
}

Playground link to code

  • Related