I am writing bindings for Firefox WebExtension API using gen_js_api
.
One of the APIs browser.storage.local.get()
returns an object literal with no definite shape: StorageArea.get() - Mozilla | MDN
So far, I have come up with:
(* Aside: the code uses Promise module from promise_jsoo *)
module Storage : sig
module Local : sig
type t = private Ojs.t
val get_all : t -> unit -> Ojs.t Promise.t [@@js.call "get"]
val get_one : t -> string -> Ojs.t Promise.t [@@js.call "get"]
val get_some : t -> string list -> Ojs.t Promise.t [@@js.call "get"]
val get_some_with_defaults : t -> Ojs.t -> Ojs.t Promise.t [@@js.call "get"]
val set : t -> Ojs.t -> unit Promise.t [@@js.call "set"]
end
type t = private Ojs.t
val local : t -> Local.t [@@js.get]
end
module Browser : sig
type t = private Ojs.t
val storage : t -> Storage.t [@@js.get]
end
val browser : Browser.t [@@js.global]
While the type of the returned object literal can be anything, the user of the API usually expects a certain type depending on the context. In my case, it is
type app_state = {
long_click_toggle: bool;
long_click_toggle_time: int
}
So far, the code like this works:
val cast_to_app_state : Ojs.t -> app_state [@@js.cast]
module Query : sig
val make :
long_click_toggle:(bool[@js "longClickToggle"])
-> long_click_toggle_time:(int[@js "longClickToggleTime"])
-> Ojs.t
[@@js.builder]
end
let promised_app_state =
let local_storage = browser |> Browser.storage |> Storage.local in
Storage.Local.get_all local_storage ()
>>| fun result -> result |> Lib.cast_to_app_state
The above code is an abridged version to cut out unnecessary context. So it may not be 100% correct.
While this works, I am wary of the @@js.cast
application. Is there a type-safe way to do this? Perhaps, constraining the binding itself with a type variable (I don’t know how though)?