[@bs.deriving abstract] with type parameters and optional keys


#1

I’m loving the [@bs.deriving abstract] API, but running into some difficulties understanding how to work with both [@bs.optional] annotations and type parameters. Here’s my use case.

JS API
I am trying to call a JS API that takes a JS object as a config. This object has one required field (url) and one optional field (fetchOptions). The optional field, fetchOptions can be either an object or a function that returns an object. For example:

const client = new Client({
   url: "http://localhost:3001"
})
const client = new Client({
   url: "http://localhost:3001",
   fetchOptions: {
      id: 1234
   }
});
const client = new Client({
   url: "http://localhost:3001",
   fetchOptions: () => {
      id: 1234
   }
});

are all valid use cases for the API. My first attempt at binding this looks like this:

[@bs.deriving abstract]
type clientConfig('fetchOptions) = {
  url: string,
  [@bs.optional] fetchOptions: 'fetchOptions
};

[@bs.new] [@bs.module "myModule"]
external client : clientConfig('fetchOptions) => client = "Client";

I’m using a type parameter to say, “Allow the user to pass any sort of value in here.” My first question is: How can I restrict fetchOptions to be either a JS object or a function returning an arbitrary JS object? Would a polymorphic variant be appropriate here?

Part 2

The above more or less works as long as I actually pass fetchOptions explictly. For example:

let config = clientConfig(
  ~url="http://localhost:3001",
  ~fetchOptions={"me": "you"},
  ()
);

But it fails if I pass just url. For example:

let config = clientConfig(
  ~url="http://localhost:3001",
  ()
);

results in: This expression's type contains type variables that can't be generalized. This makes sense because I require a type parameter fetchOptions when calling clientConfig. However, I still want API users to get the benefit of the [@bs.optional] annotation. So my question here is: How can I provide the correct type for fetchOptions (object or function returning object) without passing a type parameter (and therefore maintaining the optional use case)? Is there a better way to do this altogether and I’m just barking up the wrong tree?

Thanks in advance for any help. Bindings make my head spin a bit :tired_face: