mal.haza.website

Nested listeners

Another Holster release to add a new nested listener feature that I've needed for multiple projects now. In RSStream I added it outside of Holster itself, since I wasn't sure how often I would need it.

The idea is that you add a listener to a query in Holster, but that query always refers to a particular node. That node can reference other nodes, but those references don't change. So even though the API chains nodes together you need to explicitly list which ones you want to listen to changes on. The external way to do this is to create on() callbacks for each of the child nodes in the parent's on() callback. This works fine, but the code is really the same no matter what the project. So it's now included with Holster and just needs the extra parameter to the parent query to set up all listeners below that point. They're also automatically removed when the parent off() is called.

I asked Claude for a simple example and it of course gave a stand alone example that works in Node 🤖:
 
import Holster from "@mblaney/holster/src/holster.js"

const holster = Holster()
const user = holster.user()
  
user.create("alice", "password", () => {
    user.auth("alice", "password", () => {
      // Without {nested: true}, this listener only fires when a NEW key is
      // added to "team" - it won't fire again for a later, value-only
      // change to an EXISTING member, because that's a write to the
      // member's own soul (every object-valued child gets its own), not to
      // "team" itself.
      user.get("team").on(members => {
        console.log("team changed:", members)
      }, true, {nested: true})

      // A brand new key - the listener above always sees this.
      user.get("team").next("alice").put({role: "engineer"})

      // A value-only change to an ALREADY-KNOWN key - only reaches the
      // listener because of {nested: true}. Without it, this write would
      // be silently missed.
      setTimeout(() => {
        user.get("team").next("alice").put({role: "lead engineer"})
      }, 100)
    })
})

Concurrency

New Holster release focused mainly on concurrency fixes. I've been working on a new app that runs on Holster which is very front end heavy and running all sorts of async requests, and creating lots of tests for it at the same time. The tests started getting pretty complicated, matching the types of interactions involved, and these all run on top of real holster test instances. The tests started getting a bit flaky as they exhausted their memory limit and swapped out cache files.

I realized Holster has never had any specific concurrency tests, so it was interesting adding them and looking at what sort of race conditions were causing problems. I have to say that Claude is getting really good at methodically working through scenarios without getting lost in the details. I'm impressed with the way it found the issues that have been fixed in this release and the relatively simple changes that were made.