Back when Node was the new kid on the block, the single-threaded async model was justified by pointing out that the major challenge with multithreading was shared memory. It's one of the strong points of the language / environment, because you never have to worry that some callback will run on another thread.
Javascript shines when it's handling multiple concurrent IO operations, and concurrent operations can become very thread-like with async/await syntax. Multithreaded code in this context only helps with CPU-bound operations; but if I was doing something CPU-bound, I'd probably choose a different language.
One thing I wonder, does Bun (or Node) have a way to call into native code on another thread, but still keep single-threaded once back in JavaScript?
yes, all of node.js is built on such operations. what do you think is happening when you `await fs.promises.readFile(name, 'utf8')`? it dispatches a libuv thread to read the file you want, and resolve the promise when the background thread has completed the operation.
That really varies significantly depending on the underlying OS. On Windows, you can do threaded (IE, your thread blocks) or async (you have a callback) IO. There isn't a requirement to spin up a background thread to perform non-blocking IO.
Except there is often no background thread but async IO.