Open braaar opened 2 years ago
Hey, thank you so much for the detailed suggestion. I love the idea, even though I never encountered the need to do that.
There's a problem that requires a detailed explanation, so sorry for the long response.
I'm currently working on a new version of Typesaurus, and one of my goals for me was to ensure schema consistency. The problem with partial updates is that they might easily spoil the data. For example, for the given interface:
interface Organization {
counters?: {
drafts: number
scheduled: number
published: number
}
}
...such a patch will cause data inconsistency because counters
might be undefined:
patch({
counters: {
published: 123
}
})
I managed to write types that ensure that every single field on the page is "safe to update":
db.organizations.update(
orgId,
($) => $.field('counters', 'published').set($.increment(1))
// ^^^^^^^^^^^
// Argument of type 'string' is not assignable to parameter of type 'never'.
)
It would force you to either write types, so every field is optional:
interface Organization {
counters?: {
drafts?: number
scheduled?: number
published?: number
}
}
...or get the document first and update it with data:
org.update({
counters: {
...org.data.counters,
published: 123
}
})
As you said, it's laborious to write such updates when there's deep nesting, but it ensures data consistency.
Said that, I need to see if it's possible to make patch operation type-safe and if so, I'll be happy to incorporate it. Please let me know if you have ideas on how to approach it or want to contribute.
My implementation generally relies on the assumption that you are working with a document whose fields are all optional, or that you have verified that your input contains any required fields before you run the patch function. So my example was inaccurate. I should have added ?
to each field to signify that they were all optional.
But I don't think it needs to be like that. If the model has some required fields and you try to pass an input object of a type that is missing some of the required fields, you would get a type error, no?
Should we have some way to tell if a field is required during runtime (when we iterate through Object.entries) to prevent the user from applying a bad patch?
For a project that uses Typesaurus, I wrote a
patch
function that, unlike theupdate
function, only updates data on the keys that you provide in your input. This is useful for writing PATCH functionality that let you apply a partial patch to your document.Say my document in firestore is like this:
If I wanted to only update
myField
I would provide this as the input to thepatch
function, expecting onlymyField
to be updated:If I were to use
update
, I would overwrite my document andsomeOtherField
would be lost.Under the hood my
patch
function usesfield
to make specific update instructions. It.flatMap
s throughObject.entries
recursively to createfield
s for every key-value pair. What's convenient about my proposed function is that you don't have to create thesefield
s yourself. For this specific example it would be trivial (field(['nest1', 'nest2', 'nest3', 'nest4', 'myField'], 'Hello world!')
), but for bigger objects this can be quite overwhelming, and maintaining such code can be somewhat laborious.Is this an interesting feature to have in typesaurus?
For my use case I didn't need deeper nesting than 5 levels, so this would have to be rewritten to allow for the maximum nesting that typesaurus supports. There may very well be a better performing way to write this, and a better typed way to write this. I just took a stab at it and was happy to make it work with types when you use it.
Here is the code for my
patch
function. It usescreateFields
to create thefield
instructions, and then uses them in anupdate
call.Here is the
isPlainObject
helper function: