Bindings for preferences library

interop

#1

Hey, another day and another chance to write bindings :smile:

I’m a heavy node user, and because that I know some very cool libraries that deserve bindings, and I am the guy to wrote them ! But my Reason/Ocaml knowledge is not yet good enough.

This is the library I want to write bindings to: https://www.npmjs.com/package/preferences

It makes managing encrypted configs easy. You just provide a namespace, some defaults and it takes care of the rest, the rest being:

  • Store the peferences on disk
  • Encrypt the stored file
  • Make sure you do not overwrite other preference files
  • Save again to disk when you mutate any value

It is very useful and convenient for cli interfaces.

I am on the situation where I have to decide the implementation of the bindings. It can be as simple as this:


    [@bs.new] [@bs.module] external preferences: (string, defaults, options) => Js.t({..}) = "";
    
    let read = (~key=?, ~encrypt=true, ~defaults=?, namespace) =>
      preferences(namespace, defaults, options(~key, ~encrypt));

And then the usage could be as simple as that:

let prefs = Preferences.preferences("com.my-namespace");

prefs##username
prefs##password

Of course I don’t like that because

  • It is very JS specific, and I don’t want to write javascript with a different syntax
  • I can not mutate the properties (set them)
  • I can not accept a defaults object because I don’t know the shape beforehand

So my intuition tells me that I have to use a functor. That way I should be able to accept the type I should return and take as defaults (they should be almost the same) and the end user of the module should be able to define a JS record that they can mutate by deriving from abstract. Something like this:

[@bs.deriving abstract]
type options = {
  key: option(string),
  encrypt: bool,
};

module type T = { type t; };

module Make = (T:T) => {

    type defaults = option(T.t);

    [@bs.new] [@bs.module] external preferences: (string, defaults, options) => T.t = "";
    
    let read = (~key=?, ~defaults=?, ~encrypt=true, namespace:string) =>
      preferences(namespace, defaults, options(~key, ~encrypt));
}

I can imagine an usage like this:

[@bs.deriving abstract]
type prefs = {
  mutable username: string,
  password: string
};

module Pref = Preferences.Make({ type t = prefs ;});

let prefs = Pref.read("com.puto-pene");

Js.log( prefs -> usernameGet );
Js.log(prefs);
prefs->usernameSet("Joe");
Js.log(prefs);

What do you think about the different implementations ? The first one is simpler, but I don’t know how to avoid the described problems.

Regards