fix(ext/napi): run async work execute callback on a worker thread (#32560)
## Summary
- **Fixes `napi_queue_async_work`** to run the `execute` callback on a
worker
thread (via `std::thread::spawn`) instead of the V8 main thread
- **Dispatches `complete`** back to the main thread via
`async_work_sender.spawn()` where it can safely access V8
- **Uses `ExternalOpsTracker`** (`ref_op`/`unref_op`) to keep the event
loop
alive while the worker thread is running
- **Fixes `uv_async_send`** to dispatch directly to the main thread
instead of
going through `napi_queue_async_work` (since uv_async callbacks need V8
access)
- **Adds a test** that calls a threadsafe function from the `execute`
callback —
the exact pattern that caused the deadlock
### Background
Per the NAPI spec, `napi_queue_async_work`'s `execute` callback must run
on a
worker thread, while `complete` runs on the main thread. Previously both
ran on
the main thread via `env.add_async_work()` →
`async_work_sender.spawn()`. This
caused deadlocks when native addons (like lmdb-js) called
`napi_call_threadsafe_function` from `execute` — the threadsafe function
queues
work to the main thread and waits, but the main thread is blocked
running
`execute`.
Closes #23268