-
Notifications
You must be signed in to change notification settings - Fork 891
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FR: improve typing on CRUD methods #1496
Comments
We generally do not want to impose stricter type restrictions in our API layer than those that the SDK and the backend can validate. While your suggestion only narrows the set of input values, it doesn't actually change the input types that are accepted. It general, we have refrained from offering typed DocumentSnapshots and DocumentReferences since Firestore by design does not enforce schema. For now, I would suggest that you rely on intermediary types to achieve a similar result:
This should get you 90% of what you are asking and you even get to write a couple more lines of TypeScript code :) I am going to close this issue for now, but feel free to chime in with more feedback. |
I'm suggesting an optional, developer chosen type restriction. Thus, the word "impose" seems inappropriate. I.e.
If someone doesn't supply a type argument, their consumption of the library would not change.
I didn't suggest that it would. You seem to be suggesting that using optional type arguments in a typescript project would be confusing for developers, but they are a fundamental part of the typescript language (and again, these are optional types--if someone doesn't know how to use them, they probably will not even know they exist). It doesn't seem desirable to design a typescript library for developers who don't understand typescript (presumably, these developers would be consuming the library as javascript and this conversation is n/a to them). This being said, if the fear is that this change would be confusing for developers, that seems like a different conversation (admittedly, one that the Firebase team is in a much better position to decide).
This is my current work around. I imagine this is what most developers do, which seems like a reason to implement a proper fix (aka generics).
Lawl 😆 |
@thefliik Thanks for this suggestion and explaining all your reasoning! You make good points. You're right that this wouldn't impose additional restrictions for people that don't use it, but I do still have a couple concerns after talking this over with @schmidt-sebastian and other teammates: First, I think this actually doesn't end up being a good candidate for the
Then In general the correct typing for That said, your proposal would work better for My remaining concern here is mostly that I'm not aware of other TypeScript libraries exposing generic type parameters just to help the API consumer "double-check" themselves (make sure their arguments match up with the type they meant them to be). And so I'm worried it could generate confusion if people expect the generic type parameter to actually do something, e.g. if they expect So ultimately we think this is a reasonable idea, but we're leaning against making it right now. It's the kind of thing we could change our minds on if we get more feedback or see this becoming a common pattern in other libraries, etc. In any case, thanks again for the feedback! Keep it coming. |
Well, I'm skeptical that optimizing a typescript library for "developers who don't understand typescript" will generate the most utility, but the Firebase team is obviously in a better position to make this call so I can accept this reasoning.
While this is technically true, I'll argue that, in reality, this is a false assertion. This is because, in any given place in my application I generally know, specifically, what I'm updating right then, and the interface of that thing is almost always specific and definable (such as I'll also add that, while I'm aware of the One reason why I haven't found Anyway, I'll submit the first vote in favor of changing the the current typing strategy in the library. And again, if you ever do decide to move towards a greater use of generics, I imagine the related PR's would be ripe for community contribution. |
Points well-taken. :-) Regarding update() I'll just throw out that we get a lot of confusion from devs who accidentally do By the way, I just had a thought... Have you considered using a TypeScript cast? E.g.:
I think this might give you the same type-safety you want, but it would work in any context in TypeScript, without us modifying the Firebase SDK? |
Unfortunately this does not give the same level of type safety. Take the following example (with interface IContactListUpdateDoc {
primary?: boolean;
name?: string;
updatedAt: FirebaseFirestore.FieldValue;
}
// As desired, typescript *does not* error on this:
const updateDoc: IContactListUpdateDoc = {
primary: false,
updatedAt: timestamp(),
};
// As desired, typescript *does* error on this
const updateDoc: IContactListUpdateDoc = {
primary: false,
updatedAt: timestamp(),
BAD_PROPERTY: 'mwahahahahaha',
}
// UNDESIRED, typescript *does not* error on this
const updateDoc = {
primary: false,
updatedAt: timestamp(),
BAD_PROPERTY: 'mwahahahahaha',
} as IContactListUpdateDoc UpdateI'll note that the As @schmidt-sebastian pointed out earlier, the work-around for the time being is const foo : Footype = {...};
documentReference.set(foo); The only problem with this is that it seems needlessly verbose (though, as you pointed out, perhaps not needlessly if generic types end up causing too much trouble for the Firebase team). It's a shame though. While no-schema obviously has advantages, it also has disadvantages. Used properly, typescript interfaces can help to mitigate some of the disadvantages without a runtime performance impact. |
I'm confused. I think your UNDESIRED case is also going to be a problem for your original proposal as well as for the suggested workaround. i.e. I'd expect TypeScript to accept both of these:
|
Well that's unexpected :(. You are correct that typescript accepts doc.set<IContactListUpdateDoc>({
primary: false,
updatedAt: timestamp(),
BAD_PROPERTY: 'mwahahahahaha',
}); Thankfully, Typescript does not accept this: const updateDoc5: IContactListUpdateDoc = {
primary: false,
updatedAt: timestamp(),
BAD_PROPERTY: 'mwahahahahaha',
}; So the work-around is appropriate (more than appropriate, it is the best solution for this problem at the moment). See simple stackblitz example. This appears to be because typescript function input parameters are bivariant. The following issues are tracking the addition of stricter function parameter variance, which I think would address this issue: #10717, #18770. There is actually still an advantage to supplying input paramater types vs simply type casting, but the advantage is greatly reduced from what I thought it was. For example: interface IAddress {
street: string;
city: string
}
// expected: not allowed; actual: not allowed
doc.set<IAddress>({
street: string,
});
// expected: not allowed; actual: allowed
doc.set({
street: string,
} as IAddress); When/if support for covariance eventually lands in typescript, I think this issue would be worth revisiting. But ya, at the moment its usefulness is limited.
Regarding that point, if a user attempted the following, an error would be thrown: interface IUser {
name: string;
address: {
street: string;
city: string;
}
}
const updateDoc: Partial<IUser> = {
address: { street: '123 Street' }
}
doc.update(updateDoc) This is because |
Oops! When I did the Anyway, thanks very much for exploring all the options here and tracking down the bivariant issues, etc.! And thanks for the pointer on |
As a blanket concept, it would be very useful to update the SDK's document
create
andupdate
methods to accept a type param to better type the passed in document argument.For example, the first overload for the Firestore
DocumentReference#update()
method accepts a singlefirestore.UpdateData
argument which is lightly typed.Example usage:
It would be awesome if a user could supply an optional type param to
update
which typescript would use to check thedata.doc
interface.For example:
Typescript would ensure that
data.doc
had the interface{name: string; age: number}
.This general concept would be widely useful across the SDK and I do not think it would be a breaking change. My immediate desire is for Firestore related methods, but I believe the typing for the entire library could be improved in some places.
An implementation example for the first overload of the
DocumentReference
class:Is this something you would accept PR's for?
The text was updated successfully, but these errors were encountered: