Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Make argument of a function required if a generic type is provided

I am trying to create class that I can use for nested collections, hence my functions have the pathArgs and as you can see it is optional, that is because for collections in firebase that are not nested, I do not need any additional paths given.

However, I need this to actually be required if PathIds is provided.

class FirebaseService<T, PathIds = {}> {
    public findOne(id: string, pathArgs?: PathIds): T { return {} as T };
}

type SomeEntity = {
    hello: "hello"
}

So if I do:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

const firebase = new FirebaseService<SomeEntity>()

// this should be ok, actually pathArgs should NOT be possible to be given to it at all
firebase.findOne("someId"); 
// this should give me an error saying that the function does not accept a second argument
firebase.findOne("someId", { someRandomProp: "" }); 

But if I do:

const firebase = new FirebaseService<SomeEntity, { userId: string }>()

// this should now not be okay, and the function should require a second argument
// that is an object and matches { userId: string }
firebase.findOne("someId"); 
// and this should now be the only valid way to call this function
firebase.findOne("someId", { userId: "" });

The only difference, is the generics given to FirebaseService

Playground link: TS Playground

Edit:

New Playground link: TS Playground

So the solution does work, but now I am getting an error:

Argument of type '[PathArgs<P>]' is not assignable to parameter of type 'PathArgs<P>'

Everywhere where I use the functions, they work as expected now, but this became an internal error.

>Solution :

One approach is to have the second type parameter P to FirebaseService control the tuple type of a rest parameter for your methods. If you explicitly specify P it with some type, then the rest tuple should look like [pathArgs: P]; otherwise, if it’s not specified we can have it default to [the impossible never type](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-never-type
So your class would look like) and then the rest tuple should look like [], the empty tuple.

That is:

type PathArgs<T> = [T] extends [never] ? [] : [pathArgs: T];

declare class FirebaseService<T, P = never> {
    public findOne(id: string, ...p: PathArgs<P>): T;
}

(the reason we check [T] extends [never] instead of T extends never is because the latter would be a distributive conditional type and as such would behave unexpectedly).


Let’s test it out:

const firebase1 = new FirebaseService<SomeEntity>()
firebase1.findOne("someId"); // okay    
firebase1.findOne("someId", { someRandomProp: "" }); // error!
// Expected 1 arg, got 2 -> ~~~~~~~~~~~~~~~~~~~~~~

const firebase2 = new FirebaseService<SomeEntity, { userId: string }>()
firebase2.findOne("someId"); // error    
//        ~~~~~~~~~~~~~~~~~ <-- Expected 2 args, got 1
firebase2.findOne("someId", { userId: "" }); // okay

Looks good! The presence of a second type argument causes a second method argument to be required, and the absence of a second type argument causes a second method argument to be prohibited.

Playground link to code

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading