A note on Node's "Detected unsettled top-level await" warning in case anyone else runs into this and might be searching for it in the future...
Node exits when the event loop is empty and there's no additional work to be scheduled. Counterintuitively to me, using a top-level await doesn't change this. If Node is waiting on a top-level await and the promise hasn't resolved, Node will still exit when the event loop is empty and there's no work waiting to be scheduled, even though we haven't returned from the "await". Node will print "Warning: Detected unsettled top-level await".
The connection to Missionary is that I had converted a Missionary task to a JavaScript promise and was waiting on the promise in a Node script with a top-level await. At one point I was returning a dfv that I hadn't yet written the code to assign to. Since I wasn't yet assigning to the dfv, my expectation was that the script would pause and wait forever for the dfv. Instead Node appeared to crash with the warning. I was trying to imagine what weird interaction with Missionary being written in ClojureScript, compiled to JavaScript, and then being run in Node could possibly cause such a crash. I was running Node with all the trace and diagnostic flags turned on that I could think of without being able to figure it out.
It looked like calling (m/dfv) was causing Node to crash, but I instrumented the dfv implementation and was able to determine that no Missionary library code was actually running at the time of the "crash".
Nope, this is Node working as designed. It actually does the same thing with a script that consists of nothing but a top-level await like this:
await new Promise((resolve, reject) => {});
If Node exited the process based on reaching the end of script, it would wait forever at the "await" because the promise isn't ever resolved. But that's not Node's exit criteria.
A setTimeout, setInterval, listening on a socket, code such as m/sleep that uses setTimeout internally, anything that could cause a future event will keep Node running. Node exits by itself when the event loop is empty and there isn't anything left being listened to.
So, nothing to do with Missionary. Just useful to know, if I have code like (m/? (m/dfv)) and Node exits, I may be thinking, "Whoops! What's going on? Why isn't my code waiting on the dfv?" After all, I never assign to the dfv, so the m/? would never return. But Node isn't paying attention to that. If I had an event listener that assigned to the dfv, Node would keep running, not because I was waiting on the dfv, but because there was an active event listener.