I’m trying to get my head around gentype and using it to get some type-safety around interop with TypeScript. I don’t know if I’ll use ReScript from TypeScript or vice-versa; I’m using gentype to generate compiler warnings if my bindings aren’t correct. My main question is whether it is safe/ok/recommended to use look-alike-subset ReScript types or whether I should always be using opaque types.
Here is the opaque-type way. There is no type-safety on the @get
and @send
methods - bad. If I change the binding name from “forEach” to “zebra” no compile errors are generated but at runtime it will fail. If the schedule
function expects an int
rather than a Js.Date.t
, I’ll get a compile error which is good.
@genType.import("firebase/firestore") @genType.as("QuerySnapshot")
type t<'a>
@get external getSize: t<'a> => int = "size"
@get external getEmpty: t<'a> => bool = "empty"
@get external getDocs: t<'a> => array<queryDocSnapshot<'a>> = "docs"
@send external forEach: (t<'a>, queryDocSnapshot<'a> => unit) => unit = "forEach"
@genType.import("firebase/firestore")
external schedule: (t<'a>, Js.Date.t) => unit = "schedule"
Here is a simpler and easier way to do it. I define a type that looks like the type I’d receive from firebase. I can access the fields of this type directly without accessors. If the getSnapshot
function returns something that looks different than the type I’ve defined I’ll get a compiler error. The real querySnapshot
type has many more fields and methods than I’ve listed. I only define the ones I care about and use. The real one is a class not a simple object.
type querySnapshot<'a> = {
size: string,
empty: bool,
docs: array<queryDocSnapshot<'a>>,
forEach: (queryDocSnapshot<'a> => unit) => unit,
}
@genType.import("firebase/firestore")
external runQuery: unit => querySnapshot<'a> = "getSnapshot"
Assuming I never construct one of these types by myself - it is always generated by Firebase - is it ok to go with my simpler solution and define a look-alike type? I get nervous saying the type is x
when in reality it is a lot more than just x
and I’m only defining the small part I care about.
If I want to work with Firebase functions that receive a querySnapshot
I assume my simpler solution here will fail because these functions will expect the full definition of the type (the opaque type), not just my small subset.
Is there a way to get type safety with accessors using an opaque type? There is no method I can @gentype.import
to get the size of a querySnapshot
for example.
I suppose I could define a small TypeScript file with accessor functions, and then use the opaque type and import those simple functions I’ve defined. That seems to cover all the bases and might actually be less error prone than the first option mentioned above.