Contributing
Lets work to make the Apollo experience in ReScript the best experience out there!
Core Philosopies
- Provide 1:1 mapping to Javascript then use those as building blocks for more "reasonable" ergonomics
- 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 file level, bindings to Javascript packages should mirror the directory structure in the JS package. This is critically important! It should be easy for anyone else to see exactly what is being bound and if anything is missing or incorrect. This has the side benefit of being able to navigate to any import you see in the javascript docs via filepath pattern, but it's primary purpose is consistency.
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 Js_ modules)
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.
Style Guidelines
Directory Structure and Module Naming
Each directory should have a corresponding Reason module.
should become
Breaking it down: /[1]/[2]__[3]_[4]
- ReScript files should be located in the same directory structure as the js counterpart (usually there is a
.d.tsfor every.jsfile so we can think of them interchangeably) - All module names should be prefixed with
ApolloClient__"namespace" - File names reflect the directory structure
- Files should be named the same as the js counterpart
Types
- 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, aJs_module with atype t, and atoJsorfromJsconversion function.t => Js_.torJs_.t => t - Paste the type definition from the
.d.tsfile above thetype tin theJs_module - 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
Js_modules)
Example:
SubTypes
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 t_fetchMoreOptions_updateQueryOptions.
Binding to Js Module Exports
externals go under a Js_ module and any types they reference should be Js_.t versions.
Binding to Methods
Prefer T-first with [@bs.send]. Again, externals go under a Js_ module with a type t and any types they reference should be Js_.t versions.
Binding to Objects
Use records
Binding to Enums
Prefer standard variants. jsConverter works great for ints, but otherwise use manual toJs and fromJs functions. Standard variants are just nicer to consume in other places and this keeps consistency
General
- 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
Reasoning behind Js_ modules
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.
Does 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!