RJR
Convex Community3y ago
19 replies
RJ

Declarative schema validations and branding

I'm sure this is a big topic that you all have thought a lot about, but I've found myself more and more wishing that I could declare constraints for specific fields in my schema. It would be really neat (I think) if Convex supported something like this:

items: defineTable({
  quantity: s.number().validate((n) => n >= 0)
})


Such that whenever using db.insert, db.patch or db.replace, any validations would be run on new or modified fields, and an error would be raised if any fail (or, better yet, the validation failure is [even optionally] reflected in the type signature).

I imagine there are much fancier alternatives to this, but this seems to have some nice advantages nonetheless:

1. It's effectively the same as what a user would have to do within each of their individual mutation functions if they're trying to enforce this invariant in a more ad-hoc way (the only option at the moment). By "effectively the same" I mean it is computationally identical (JS/TS code being executed at runtime), but a much nicer API.
2. It's what many ORMs do and have done for a long time (e.g. Ruby's Rails and Elixir's Phoenix, at least)—which implies a) that it's generally a useful pattern and also b) that it's familiar to people.
3. It doesn't require changing the database layer to implement (obviously database-layer guarantees are much nicer, but are, I imagine, also much harder—or at least more work).

In a similar vein, I've often found myself wanting to be able to more easily declare that e.g. a s.string() is an ISO 8601 date. It would be cool to extend the above with type branding, such that you could have something like:

export default defineSchema({
  items: defineTable({
    quantity: positiveInteger
  })
});

const positiveInteger = s.number()
  .validate((n) => n >= 0)
  .brand("PositiveInteger");

export type PositiveInteger = Infer<typeof positiveInteger> // number & Brand<"PositiveInteger">


These would both be huge DX upgrades for me!
Was this page helpful?