The best I can think of would be to use a module function, whose argument provides the underlying type. For example:
module MakeScene = (Specification: {type scenes}) => {
type t
external make: unit => t = "aFunction"
@send
external scene: (t, Specification.scenes, 'a => unit) => unit = "scene";
@send
external go: (t, Specification.scenes, ~data: 'a=?) => unit = "go";
}
module Spec1 = {
type scenes = | One | Two
}
module Scene1 = MakeScene(Spec1)
let scene1 = Scene1.make()
Scene1.scene(scene1, Spec1.One, val => ())
Scene1.go(scene1, Spec1.One)
module Spec2 = {
type scenes = | A | B
}
module Scene2 = MakeScene(Spec2)
let scene2 = Scene2.make()
Scene2.scene(scene2, Spec2.A, val => ())
Scene2.go(scene2, Spec2.A)
Overuse of module functions can over-complicate code, so do be judicious here.