Listening to updates

I finally finished adding event listeners to the Holster API, and decided to stick with GunDB's idea of on and off functions.

It took me longer than I expected because on callbacks are long-lived, so the context chain I was using needed to stick around somehow. I also realised that I couldn't do much else once there was an event listener because there was only one context. Or if there was another request the on callback would lose what it was referring to when called.

This meant that the API need to store each context while it was being used, and only remove them when a callback had finished. This took a few attempts, as holster.get().get() previously made sense, but having another request start a new holster.get() meant the two requests couldn't be distinguished from each other and would overwrite the single context available. The solution ended up being that each holster.get() starts a new context, and to differentiate new requests I've added then() to provide the chaining API.

Now Holster can do queries such as:
holster.get("hello").then("world!").on(console.log)

// Which will get called by this update.
holster.get("hello").then("world!").put("update", console.log)
I figured that was enough API changes to add a new page to the Github Wiki, so I've also added a new API page with more details.

Returning resolved data

Another step on the roadmap has been completed and removed! Holster will now resolve all data in and out. It previously just returned an object containing rels, now it resolves the ids and replaces the rel in the returned data with the full object. Luckily the code to do this was quite similar to the way ids are resolved when putting nodes, so both holster.get() and holster.put() should work with any arbitrarily nested data.

This change makes the underlying data completely opaque when using the Holster API, so data out is the same as data in:
holster.get("hello", console.log)

{
  "world!": {
    "things": "I want to add",
    "which": {
      "can": "also have nested objects"
    }
  }
}

Another interesting thing you can now do with Holster is see all your data. This is possible because of the above change, but you also need to know the top level keys to get. You can do that using the wire API to get the root node:
holster.wire.get({"#": "root"}, msg => console.log(Object.keys(msg.put.root)))

// Or use the above to display everything on disk
holster.wire.get({"#": "root"}, async msg => {
  // wire API returns meta data.
  delete msg.put.root._
  for (const key of Object.keys(msg.put.root)) {
    const value = await new Promise(resolve => holster.get(key, resolve))
    console.log(key, value)
  }
})
A few caveats, the wire API can be used to create top level nodes too, so this method will only show everything created with the Holster API. The Promise is required above because holster.get() controls the context until the callback is called, so this forces the loop to wait until the previous get has finished.