Lets work to make the Apollo experience in ReScript the best experience out there!
- Follow a consistent pattern for bindings
- Avoid partial types or bindings if possible
- Encourage incremental contribution from the community rather than biting off more than one person can chew
There's nothing fancy about this library. It tends to view the problem of bindings as a people problem rather than a programming problem (all the hard work has already been done for us by Graphql-ppx) As such, a lot of detail is paid to consistency and clarity with the hope of maximizing human ability to both add and verify that bindings are correct. The long-term success or failure of this library is will likely be dependent on active community contribution rather than the efforts of a few individuals.
Following a Consistent Pattern
At the code level, all JS bindings should go in a
Js_ module of some sort. At first it seems ridiculous, but it pays off at scale. (See Reasoning Behind
Avoiding Partial Types
Please type something as completely as possible when you come across it or leave it for someone else (if nothing else, you can use an abstract type so things will still flow through everywhere and people can cast it when in a hurry). This way no one has to go back and duplicate that work of tracing through the same code you were just in and we can trust that if a binding exists, it's probably complete and we can just reuse.
Directory Structure and Module Naming
Each directory should have a corresponding Reason module.
Breaking it down:
- ReScript files should be located in the same directory structure as the js counterpart (usually there is a
.jsfile so we can think of them interchangeably)
- All module names should be prefixed with
- File names reflect the directory structure
- Files should be named the same as the js counterpart
- Please use the same naming as typescript where possible
- Every type goes in its own module with a
type t(exception: see SubStypes)
- Every type module should contain a
type t, a
Js_module with a
type t, and a
t => Js_.tor
Js_.t => t
- Paste the type definition from the
.d.tsfile above the
type tin the
- If data requires parsing/serializing create a new record even if the shape is the same. This ensures you don't forget to parse somewhere.
- Prefer single types when the more complicated type in a union can express both
shouldResubscribe?: boolean | ((options: BaseSubscriptionOptions<TData, TVariables>) => boolean);
- Do all of this even if it doesn't need it (See Reasoning Behind
Sometimes multiple types were required to represent a single type in TypeScript. In order to help make it clear what is a binding to an actual type and what is just needed by ReScript, we take a similar naming approach to the modules (prefixing with the parent). For instance,
Apollo_Client__React_Types.QueryResult.Raw has a
type t that uses
t_fetchMoreOptions which in turn uses
Binding to Js Module Exports
externals go under a
Js_ module and any types they reference should be
Binding to Methods
Prefer T-first with
externals go under a
Js_ module with a
type t and any types they reference should be
Binding to Objects
Binding to Enums
Prefer standard variants.
jsConverter works great for ints, but otherwise use manual
fromJs functions. Standard variants are just nicer to consume in other places and this keeps consistency
- Prefer T-first because that's the Reason community default
- Hooks are T-last because that makes sense given their usage
- ApolloClient methods are a Frankenstein T-first and T-last because they want to maintain similarity with hooks api, but are also T-first due to [@bs.send] and T-first preference
Here's a typical example:
Not much point yet, but bear with me, it pays off in the big picture. What if we need to parse/serialize some data which happens a lot in this library?
Nice, now when wherever we say we want a
Typename.t, we can never forget to parse because records are nominally typed.
What if you need to construct a class or object with a bunch of optional properties?
It's nice to have all this conversion stuff wrapped in one module and have consistent naming. All of it together really begins to pay off when we have types that reference many other types.
reusedType need some conversion or parsing or serializing? If we've done things right, all we need to confirm is that any
Js_ modules reference the
Js_.t versions of types, the compiler will do the rest! In this case, it turns out it needs also needs parse!