Files
oxc/napi/transform-react/transform-react.wasip1-browser.js
renovate[bot]andCameron Clark 6fe829bbd8 chore(deps): update napi (#26941)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Adoption](https://docs.renovatebot.com/merge-confidence/) |
[Passing](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) | Type |
Update |
|---|---|---|---|---|---|---|---|
| [@napi-rs/cli](https://napi.rs/)
([source](https://redirect.github.com/napi-rs/napi-rs)) | [`3.10.4` →
`3.10.5`](https://renovatebot.com/diffs/npm/@napi-rs%2fcli/3.10.4/3.10.5)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@napi-rs%2fcli/3.10.5?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@napi-rs%2fcli/3.10.5?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@napi-rs%2fcli/3.10.4/3.10.5?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@napi-rs%2fcli/3.10.4/3.10.5?slim=true)
| pnpm.catalog.default | patch |
| [napi](https://redirect.github.com/napi-rs/napi-rs) | `3.12.7` →
`3.13.0` |
![age](https://developer.mend.io/api/mc/badges/age/crate/napi/3.13.0?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/crate/napi/3.13.0?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/crate/napi/3.12.7/3.13.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/crate/napi/3.12.7/3.13.0?slim=true)
| workspace.dependencies | minor |
| [napi-build](https://redirect.github.com/napi-rs/napi-rs) | `2.4.4` →
`2.5.0` |
![age](https://developer.mend.io/api/mc/badges/age/crate/napi-build/2.5.0?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/crate/napi-build/2.5.0?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/crate/napi-build/2.4.4/2.5.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/crate/napi-build/2.4.4/2.5.0?slim=true)
| workspace.dependencies | minor |
| [napi-derive](https://redirect.github.com/napi-rs/napi-rs) | `3.6.8` →
`3.6.9` |
![age](https://developer.mend.io/api/mc/badges/age/crate/napi-derive/3.6.9?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/crate/napi-derive/3.6.9?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/crate/napi-derive/3.6.8/3.6.9?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/crate/napi-derive/3.6.8/3.6.9?slim=true)
| workspace.dependencies | patch |

---

### Release Notes

<details>
<summary>napi-rs/napi-rs (@&#8203;napi-rs/cli)</summary>

###
[`v3.10.5`](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/cli@3.10.4...@napi-rs/cli@3.10.5)

[Compare
Source](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/cli@3.10.4...@napi-rs/cli@3.10.5)

</details>

---

### Configuration

📅 **Schedule**: (in timezone Asia/Shanghai)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/oxc-project/oxc).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMDMuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjEwMy4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Cameron Clark <cameron.clark@hey.com>
2026-09-23 14:48:55 +01:00

1393 lines
52 KiB
JavaScript

import {
emnapiAsyncWorkPlugin as __emnapiAsyncWorkPlugin,
emnapiTSFNPlugin as __emnapiTSFNPlugin,
instantiateNapiModuleSync as __emnapiInstantiateNapiModuleSync,
WASI as __WASI,
} from '@napi-rs/wasm-runtime'
import { createContext as __emnapiCreateContext } from '@emnapi/runtime'
export const __napiBindingTarget = 'wasm32-wasip1'
function __napiStampBindingTarget(exportsObject, target) {
if (
Object.prototype.hasOwnProperty.call(exportsObject, '__napiBindingTarget')
) {
if (exportsObject.__napiBindingTarget === target) {
// Already ours: the root entry aliases the object it loaded, so a WASI
// fallback candidate — or a `NAPI_RS_NATIVE_LIBRARY_PATH` override that
// is a generated loader — arrives already stamped with this same value.
return target
}
const error = new Error(
'`__napiBindingTarget` is reserved by the generated binding loader, but the loaded binding already exports it. Rename the export, e.g. #[napi(js_name = "...")].',
)
error.code = 'ERR_NAPI_BINDING_TARGET_CONFLICT'
throw error
}
if (!Object.isExtensible(exportsObject)) {
// A `#[napi(module_exports)]` hook may seal or freeze this object
// (`Object::seal` / `Object::freeze`). Reporting the artifact is metadata,
// never a reason to fail an otherwise successful load, so the stamp is
// skipped. What a consumer still sees then follows the entry point: the
// browser and deferred loaders declare `__napiBindingTarget` at module
// level and go on reporting it, while the CommonJS entries hand back this
// very object as `module.exports`, so there the value is absent.
return target
}
try {
// [[Define]], not [[Set]]: an ordinary assignment walks the prototype
// chain, so an inherited accessor could swallow the value or throw and
// fail an otherwise successful load. The descriptor is what a successful
// assignment would have produced.
Object.defineProperty(exportsObject, '__napiBindingTarget', {
configurable: true,
enumerable: true,
value: target,
writable: true,
})
} catch {
// Same rule as the non-extensible skip above: reporting the artifact is
// metadata, never a reason to fail an otherwise successful load. An exotic
// object (a Proxy whose defineProperty trap refuses) is skipped, not
// thrown over.
}
// The CommonJS loaders assign this return value so `cjs-module-lexer` — and
// therefore Node's CJS -> ESM named export detection — can see
// `__napiBindingTarget` statically.
return target
}
const __wasi = new __WASI({
version: 'preview1',
})
const __wasmUrl = new URL('./transform-react.wasm32-wasip1.wasm', import.meta.url).href
const __wasmResponse = await globalThis.fetch(__wasmUrl)
if (!__wasmResponse.ok) {
throw new Error(
'Failed to fetch WASI module ' +
__wasmUrl +
': ' +
__wasmResponse.status +
' ' +
(__wasmResponse.statusText || 'Unknown Status'),
)
}
const __wasmFile = await __wasmResponse.arrayBuffer()
const __wasmMemory = new WebAssembly.Memory({
initial: 4000,
maximum: 65536,
})
let __emnapiContext
const __wasiDisposeSymbol = Symbol.for('napi.rs.wasi.dispose')
const __wasiWorkers = new Set()
// The thread manager has to be reachable *before* anything that can throw
// during load or registration. Initialization can fail after the pool has
// already spawned workers, and the rollback still has to mark their
// terminations as expected — but `__napiModule` is assigned only when
// instantiation RETURNS, so on exactly that path it is still undefined. A
// plugin factory runs while the emnapi module is being created, before the
// wasm is loaded and before any registration function runs, and its context
// carries the very same manager instance.
let __wasiThreadManager
function __captureWasiThreadManager(context) {
if (context && context.PThread) {
__wasiThreadManager = context.PThread
}
return {}
}
function __getWasiThreadManager() {
const manager =
__wasiThreadManager !== undefined
? __wasiThreadManager
: __napiModule
? __napiModule.PThread
: undefined
if (manager && typeof manager.terminateWorker === 'function') {
return manager
}
return undefined
}
let __napiInstance
let __emnapiContextDestroyed = false
let __emnapiContextDestroyPromise
let __emnapiWasmEnvCleanupPrepared = false
let __emnapiWasmEnvCleanupPreparing = false
// The closer for a barrier that is parked between `…_begin` and `…_finish`,
// set only while that window is open. `__emnapiWasmEnvCleanupPreparing` cannot
// tell those two apart on its own: it is raised both for a purely synchronous
// frame — which must not be re-entered, and which nothing outside it can
// finish — and across this window, which spans real event-loop turns, so a
// caller that cannot yield can land in the middle of one. That caller can close
// this window, because `…_finish` is idempotent and joins, which is exactly
// what the single call does. See `__prepareWasmEnvCleanup`.
let __finishParkedWasmEnvCleanup
// Raised while a caller that can still yield is driving the barrier, so the
// queue it leaves behind is expected rather than lost. See
// `__reportUnreachedWasmEnvSettlements`.
let __emnapiWasmEnvCleanupYielding = false
let __emnapiWasmEnvSettlementLossReported = false
let __emnapiWasmEnvCleanupRan = false
let __emnapiWasmEnvCleanupDrained = false
let __emnapiWasmEnvCleanupDrainPromise
let __wasiDisposed = false
let __wasiAsyncWorkDrainPromise
let __wasiDisposePromise
let __completeWasiDisposal = function () {}
// Overridden by loader flavors that have a last-resort reclaim for a rollback
// that stopped short of destroying the context. See
// `__rollbackWasiInitialization`.
let __retainWasiRollbackForRetry = function () {}
function __isThenable(value) {
return (
value !== null &&
(typeof value === 'object' || typeof value === 'function') &&
typeof value.then === 'function'
)
}
function __createCleanupError(errors, message) {
if (errors.length === 1) {
return errors[0]
}
const __AggregateError = globalThis.AggregateError
if (typeof __AggregateError === 'function') {
return new __AggregateError(errors, message)
}
const error = new Error(message)
error.errors = errors
return error
}
function __attachCleanupErrors(error, cleanupErrors) {
if (cleanupErrors.length === 0) {
return error
}
const cleanupError = __createCleanupError(
cleanupErrors,
'WASI binding cleanup failed',
)
try {
if (
error &&
(typeof error === 'object' || typeof error === 'function')
) {
if (error.cause === undefined) {
error.cause = cleanupError
if (error.cause === cleanupError) {
return error
}
}
if (Array.isArray(error.cleanupErrors)) {
error.cleanupErrors.push(cleanupError)
return error
} else {
const attachedCleanupErrors = [cleanupError]
error.cleanupErrors = attachedCleanupErrors
if (error.cleanupErrors === attachedCleanupErrors) {
return error
}
}
}
} catch {}
const aggregate = __createCleanupError(
[error, cleanupError],
'WASI binding initialization and cleanup failed',
)
try {
aggregate.cause = error
} catch {}
return aggregate
}
function __wrapEmnapiContextDestroyForSettlement(
context,
prepareEnvCleanup,
isPreparingEnvCleanup,
) {
let destroy
try {
destroy = context.destroy
} catch {
return context
}
if (typeof destroy !== 'function') {
return context
}
try {
Object.defineProperty(context, 'destroy', {
configurable: true,
enumerable: false,
writable: true,
value: function () {
// Reentered from a promise hook that fired inside the barrier: the
// frame running it destroys as soon as it returns.
if (isPreparingEnvCleanup?.()) {
return
}
prepareEnvCleanup?.()
return Reflect.apply(destroy, this, arguments)
},
})
} catch {}
return context
}
function __isPreparingWasmEnvCleanup() {
return __emnapiWasmEnvCleanupPreparing
}
function __prepareWasmEnvCleanup() {
if (__emnapiWasmEnvCleanupPrepared) {
return
}
// A handshake parked between its two halves is one this frame can close, and
// must: every caller of this function is about to destroy the context, and
// the turns the poll is waiting for will not come — an 'exit' teardown is
// the last thing the process runs, and `Context.destroy()` takes the
// environment away. Closing it here runs `…_finish`, which is the call that joins, so
// this degrades to exactly the single call below. Leaving it open instead
// destroys the context with the barrier still raised, the runtime never
// joined and the workers never drained.
const finishParked = __finishParkedWasmEnvCleanup
if (finishParked !== undefined) {
finishParked()
__reportUnreachedWasmEnvSettlements()
return
}
if (__emnapiWasmEnvCleanupPreparing) {
return
}
const prepare = __napiInstance?.exports?.napi_prepare_wasm_env_cleanup
if (typeof prepare === 'function') {
// The addon settles the promises it cancels synchronously, under a
// non-reentrant lifecycle mutex: anything a promise hook calls from in
// here must not reach this export again.
__emnapiWasmEnvCleanupPreparing = true
try {
prepare()
} finally {
__emnapiWasmEnvCleanupPreparing = false
}
__emnapiWasmEnvCleanupRan = true
__reportUnreachedWasmEnvSettlements()
}
__emnapiWasmEnvCleanupPrepared = true
}
/**
* Say so when the barrier leaves settlements queued and nothing is left that
* could deliver them.
*
* Only the disposal chain yields the event-loop turns @emnapi/core needs to
* dispatch its queue. Every other caller of the barrier destroys in the same
* turn — a raw `Context.destroy()`, the 'exit' teardown — and
* `Context.destroy()` runs the threadsafe function's cleanup hook, which drains
* that queue with a null env and discards it. The promises those settlements
* were for then hang forever, silently.
*
* Loud, once, and never throwing: this runs from inside `Context.destroy()`,
* emnapi's own beforeExit destroy included, where throwing would take the whole
* teardown down with it. Destroying anyway is still the right trade — the queue
* is already unreachable by then.
*/
function __reportUnreachedWasmEnvSettlements() {
if (__emnapiWasmEnvCleanupYielding || __emnapiWasmEnvSettlementLossReported) {
return
}
const pending = __napiInstance?.exports?.napi_wasm_env_cleanup_pending
if (typeof pending !== 'function') {
return
}
let queued
try {
queued = pending()
} catch {
return
}
if (!queued) {
return
}
__emnapiWasmEnvSettlementLossReported = true
try {
const consoleHost = globalThis.console
if (consoleHost && typeof consoleHost.error === 'function') {
consoleHost.error(
"napi-rs: the wasm environment is being destroyed with " +
queued +
" queued promise settlement(s). Context.destroy() discards them, so those promises never settle. Dispose with binding[Symbol.for('napi.rs.wasi.dispose')]() instead: only it yields the event-loop turns the settlements need.",
)
}
} catch {}
}
// Mirror the primitive @emnapi/core schedules its threadsafe-function dispatch
// on, so the drain turns below interleave with that dispatch instead of racing
// ahead of it on a faster queue.
const __scheduleMacrotask = (function () {
if (typeof setImmediate === 'function') {
return function (callback) {
setImmediate(callback)
}
}
const __MessageChannel = globalThis.MessageChannel
if (typeof __MessageChannel === 'function') {
return function (callback) {
const channel = new __MessageChannel()
channel.port1.onmessage = function () {
channel.port1.onmessage = null
try {
channel.port1.close()
} catch {}
try {
channel.port2.close()
} catch {}
callback()
}
channel.port2.postMessage(null)
}
}
return function (callback) {
setTimeout(callback, 0)
}
})()
// A real, *referenced* timer, for waits that must let the whole host make
// progress between looks — the async-work drain polls the addon rather than
// interleaving with the @emnapi/core dispatch, so a zero-delay macrotask there
// would spin the loop instead of yielding it. Falls back to the macrotask
// scheduler on a host without timers.
function __scheduleTimer(callback, delay) {
const setTimer = globalThis.setTimeout
if (typeof setTimer !== 'function') {
__scheduleMacrotask(callback)
return
}
try {
setTimer(callback, delay)
} catch {
__scheduleMacrotask(callback)
}
}
// A real, referenced timer rather than a zero-delay macrotask, for the same
// reason the async-work drain uses one: this polls the addon instead of
// interleaving with the @emnapi/core dispatch, so a zero-delay turn would spin
// the loop instead of yielding it.
const __WASM_RUNTIME_WORK_POLL_INTERVAL_MS = 1
// Arrivals it takes before the poll paces on the host's timers alone. One
// proves nothing: a timer armed before the host's timers stopped still fires.
const __WASM_RUNTIME_WORK_POLL_TRUSTED_ARRIVALS = 2
// How long a parked turn's own timer must already have been due before a
// backup that runs calls it dropped. Slack, not a deadline: a timer is due
// against the event loop's clock, which is read once per iteration, while
// these are `Date.now()` readings taken part-way through one, so the two
// drift apart by however long the loop has been inside the current iteration.
const __WASM_RUNTIME_WORK_POLL_STALL_MS = 50
// How long a backup itself waits. What is left of it after the slack and one
// interval — 149 ms — has to cover the *two* poll turns that can separate a
// parked turn from the last backup armed while the host's timers still
// worked, so the ceiling on a single turn is half of it. See the invariant on
// `__armWasmRuntimePollStallBackup`.
const __WASM_RUNTIME_WORK_POLL_BACKUP_MS = 200
/**
* Pacing state for one runtime-work poll.
*
* Per poll, never per module: whether the host's timers arrive is not a
* property of the module. A host can lose its timers between two disposals,
* and in the deferred shape every instance shares this module — one healthy
* instance must not disarm the fallback for the next one.
*/
function __createWasmRuntimePollPace() {
return {
// Timers armed by *this* poll that have actually arrived.
arrivals: 0,
// The turn waiting on a timer alone *right now* — undefined whenever no
// turn is parked — and when that turn's own timer came due.
settleTurn: undefined,
turnTimerDueAt: 0,
}
}
/**
* The backup that ends a turn whose timer is never going to arrive.
*
* Once the poll paces on the timer alone it has nothing left to fall back on
* if the host's timers stop mid-poll: the turn that armed the dead timer is
* the turn that parks, and a parked poll schedules nothing that could notice.
* So every turn arms one of these before it yields, and each one compares due
* times instead of measuring how long the parked turn has been waiting.
*
* Invariant: a parked turn is ended by the newest backup that was armed while
* the host's timers still worked, and a backup ends a turn only when that
* turn's own timer was already due a whole window before the backup itself.
* Neither half turns on how far apart the arms happen to fall — what bounds
* the rescue is how far back that newest live backup is:
*
* - *Ends it.* Hosts run timers in due order, so a backup that runs while a
* turn due a whole window earlier is still parked proves that turn's timer
* was dropped rather than merely late. That same comparison is what leaves a
* healthy host alone: there the turn's timer has already run and cleared
* `settleTurn` before any backup due after it can look.
* - *Two turns back, not one.* A turn that ended does not prove its own timer
* arrived: until `…_TRUSTED_ARRIVALS` is reached every turn arms both
* primitives and the macrotask wins, so such a turn can end with its own
* timer — and the backup armed one line before it — already dead. The
* arrival that then flips the poll onto the timer alone can itself be a
* timer armed before the host's timers died. So the turn that parks can sit
* two turns past the last live arm, and the newest live backup is due
* `…_BACKUP_MS` less *two* turn lengths after that turn's own timer.
* Arming on every turn is what holds it to two, rather than however far back
* a throttle last let one through.
* - *Ceiling.* Coverage therefore holds while two consecutive poll turns fit
* inside `…_BACKUP_MS` less the slack and one interval: 149 ms, so 74 ms
* per turn (measured: a 74 ms turn is still rescued, a 75 ms one parks).
* Past that the turn stays parked and the disposal promise never settles.
* The bound is deliberate: reaching it takes a host that drops timers
* mid-poll *and* keeps every poll turn busy for more than 74 ms, and neither
* Node nor WebContainer — the hosts that run the threaded artifact — does
* the second.
*
* The poll then goes back to arming both primitives until two fresh arrivals
* prove the timers again. A host that stops running the timers it has
* *already* accepted leaves nothing to fire, and the disposal promise stays
* pending rather than wedging the thread — the same outcome as a blocking
* closure that never returns. Unreferenced wherever the host allows it: the
* poll's own turn timers are what keep the loop alive, never these.
*/
function __armWasmRuntimePollStallBackup(pace) {
const setTimer = globalThis.setTimeout
if (typeof setTimer !== 'function') {
// Nothing to back up: `__scheduleTimer` is on the macrotask channel
// already, and that one cannot park.
return
}
// Read before arming, so this never claims to be due earlier than the timer
// actually is: a backup ends a turn only when it is provably due after it.
const dueAt = Date.now() + __WASM_RUNTIME_WORK_POLL_BACKUP_MS
let handle
try {
handle = setTimer(() => {
const settleTurn = pace.settleTurn
if (
!settleTurn ||
pace.turnTimerDueAt > dueAt - __WASM_RUNTIME_WORK_POLL_STALL_MS
) {
// No turn is parked, or the parked one's timer came due too close to
// this backup to call it dropped — it may still arrive, and the turn
// that armed it armed a backup due a whole window after *that*.
return
}
pace.arrivals = 0
pace.settleTurn = undefined
settleTurn()
}, __WASM_RUNTIME_WORK_POLL_BACKUP_MS)
} catch {
return
}
if (handle && typeof handle.unref === 'function') {
try {
handle.unref()
} catch {}
}
}
/**
* One turn of the runtime-work poll.
*
* `__scheduleTimer` falls back to the macrotask scheduler when `setTimeout` is
* missing or throws, but not when it is present, returns a handle and never
* fires — fake timers in a test suite that disposes from an `afterEach`, or a
* host whose timers belong to an IO context that is already gone. That host
* would park this poll forever, and the poll is unbounded, so nothing would
* ever call `…_finish`.
*
* Arm both primitives until timers armed by this poll have arrived twice, and
* let whichever lands first end the turn; the loser resolves nothing. A host
* with working timers therefore pays the double arming for the first turn or
* two — the macrotask wins the race, but the timers behind it still arrive and
* are counted — and paces on the timer alone from then on, instead of spinning
* the loop on a zero-delay queue. A host whose timers never arrive keeps both,
* and the macrotask is what keeps the poll moving. A host whose timers stop
* after proving themselves is caught by `__armWasmRuntimePollStallBackup`,
* which ends the parked turn and puts this poll back on both.
*/
function __yieldWasmRuntimePollTurn(pace) {
// Armed before the turn yields, and by every turn: what rescues a parked
// turn has to have been armed while the host's timers still worked, and the
// turn that parks is the one whose own timer is already dead.
__armWasmRuntimePollStallBackup(pace)
return new Promise((resolve) => {
let settled = false
const settle = () => {
if (settled) {
return
}
settled = true
if (pace.settleTurn === settle) {
// Nothing is parked any more: a backup running later must not read a
// due time this turn has already answered.
pace.settleTurn = undefined
}
resolve()
}
__scheduleTimer(() => {
pace.arrivals++
settle()
}, __WASM_RUNTIME_WORK_POLL_INTERVAL_MS)
// Read next to the arming it describes; see
// `__armWasmRuntimePollStallBackup` for what the two due times mean.
const turnTimerDueAt = Date.now() + __WASM_RUNTIME_WORK_POLL_INTERVAL_MS
if (pace.arrivals < __WASM_RUNTIME_WORK_POLL_TRUSTED_ARRIVALS) {
__scheduleMacrotask(settle)
return
}
// Paced by the timer alone from here; the backup is what ends this turn if
// the timer never arrives.
pace.settleTurn = settle
pace.turnTimerDueAt = turnTimerDueAt
})
}
/**
* The barrier for callers that can yield: `__prepareWasmEnvCleanup` with real
* event-loop turns in the middle.
*
* `napi_prepare_wasm_env_cleanup` waits — it returns only once the addon's
* async runtime has quiesced, and on `wasm32-wasip1-threads` the thread it
* waits on is this one, the only thread that can give a running blocking
* closure the JavaScript turn *it* is waiting for. A single call there can wait
* for work that can never finish. The addon's two-phase form splits that:
* `…_begin` stops the runtime without joining and reports whether anything is
* still live, `napi_wasm_runtime_work_pending` answers that question again
* without blocking, and `…_finish` joins. The turns yielded in between are the
* entire point.
*
* The poll has no deadline, for the same reason the async-work drain below has
* none: giving up means calling `…_finish`, which joins on this thread, and the
* work it would join is the work that is waiting for a turn from this thread —
* so a bound does not end the wait, it only moves it somewhere the JavaScript
* thread can no longer be reached. A blocking closure that never returns keeps
* the disposal promise pending instead, exactly as a task whose `execute` never
* returns already keeps an *undisposed* process alive. The host contract is in
* `crates/async-runtime/README.md`: a blocking closure must never wait on a
* JavaScript turn. The process-exit path still blocks in `…_finish`, because it
* has no turns left to give (see `__prepareWasmEnvCleanup`).
*
* Feature-detected like every other export in this teardown, so an addon built
* against a napi crate that predates the split keeps the single blocking call.
* Returns nothing whenever the handshake finished without yielding, which keeps
* an idle disposal synchronous.
*/
function __prepareWasmEnvCleanupWithTurns() {
if (__emnapiWasmEnvCleanupPrepared || __emnapiWasmEnvCleanupPreparing) {
return
}
const exports = __napiInstance?.exports
const begin = exports?.napi_prepare_wasm_env_cleanup_begin
const finish = exports?.napi_prepare_wasm_env_cleanup_finish
if (typeof begin !== 'function' || typeof finish !== 'function') {
// No split to use. The settlement drain still follows this, so the queue
// the single call leaves behind is expected rather than lost.
__emnapiWasmEnvCleanupYielding = true
try {
__prepareWasmEnvCleanup()
} finally {
__emnapiWasmEnvCleanupYielding = false
}
return
}
const workPending = exports?.napi_wasm_runtime_work_pending
// The in-flight flag stays raised across the turns below, so a `destroy()`
// from one of the JavaScript handlers they run is the same no-op it is inside
// the single call: the barrier is up and the runtime is mid-teardown, and
// destroying between the halves would strand exactly what this delivers.
__emnapiWasmEnvCleanupPreparing = true
let live
try {
live = begin()
} catch (error) {
__emnapiWasmEnvCleanupPreparing = false
throw error
}
__emnapiWasmEnvCleanupRan = true
const finishCleanup = () => {
if (__emnapiWasmEnvCleanupPrepared) {
// Already closed by a caller that could not yield — the 'exit' teardown
// reached `__prepareWasmEnvCleanup` while this poll was parked. `…_finish`
// is idempotent, but the flags it lowers are not: running it again here
// would clear a `preparing` some later barrier had raised.
return
}
__finishParkedWasmEnvCleanup = undefined
try {
finish()
} finally {
__emnapiWasmEnvCleanupPreparing = false
}
__emnapiWasmEnvCleanupPrepared = true
}
if (!live || typeof workPending !== 'function') {
finishCleanup()
return
}
// Publish the closer before yielding: from here until `finishCleanup` runs,
// a caller that cannot yield is entitled to end this handshake itself.
__finishParkedWasmEnvCleanup = finishCleanup
return (async () => {
// Unbounded, exactly like the async-work drain below. The wait ends when
// the addon reports its runtime work finished; the turns spent here are
// what let that happen at all.
const pace = __createWasmRuntimePollPace()
for (;;) {
await __yieldWasmRuntimePollTurn(pace)
try {
if (!workPending()) {
return
}
} catch {
// A trap is the only way this fails, and a trapped instance has no
// reachable work left. Stop polling and finish.
return
}
}
})().then(finishCleanup, finishCleanup)
}
// Turns to wait for while the addon still reports queued settlements. Reaching
// zero is the only success. A counter still nonzero at this bound rejects the
// disposal as retryable (`ERR_NAPI_WASI_CLEANUP_PENDING`) rather than
// destroying the context over a still-queued settlement — the wait stays
// bounded either way.
const __WASM_ENV_CLEANUP_DRAIN_TURNS = 128
// Without `napi_wasm_env_cleanup_pending` the queue is not observable. Fall
// back to the number of turns @emnapi/core needs to coalesce and dispatch a
// call made on this thread (two), plus a margin.
const __WASM_ENV_CLEANUP_BLIND_DRAIN_TURNS = 4
/**
* `napi_prepare_wasm_env_cleanup` only *queues* the promise settlements of the
* tasks it cancelled: `napi_call_threadsafe_function` appends to the
* threadsafe-function queue, and @emnapi/core dispatches that queue from a
* macrotask — two coalescing turns later, even for a call made on this very
* thread. `Context.destroy()` then runs the threadsafe function's cleanup hook,
* which drains the queue with a null env and *discards* whatever is still in it.
*
* So destroying without yielding first strands exactly the promises the barrier
* exists to settle. Yield real event-loop turns until the addon reports the
* queue empty; microtask checkpoints cannot help, no number of them lets a
* macrotask run.
*
* Returns nothing when there is nothing to wait for, which keeps disposal
* synchronous in the common case.
*
* The "already drained" flag is set only once a wait has actually finished.
* Scheduling a macrotask can fail — a host-provided or patched `setImmediate`
* that throws is enough — and a disposal that rejects stays retryable, so
* marking the drain complete up front would make the retry skip it and destroy
* the context with the barrier's settlements still queued.
*
* A wait that runs out of turns with the counter still nonzero rejects with
* `ERR_NAPI_WASI_CLEANUP_PENDING` for the same reason: at that point
* "finished" is indistinguishable from the stranding above, and destroying
* would discard the very settlement the wait was for. The rejection leaves the
* flag unset and disposal retryable.
*/
function __drainWasmEnvCleanup() {
if (__emnapiWasmEnvCleanupDrained || !__emnapiWasmEnvCleanupRan) {
return
}
if (__emnapiWasmEnvCleanupDrainPromise) {
return __emnapiWasmEnvCleanupDrainPromise
}
const pending = __napiInstance?.exports?.napi_wasm_env_cleanup_pending
const observable = typeof pending === 'function'
if (observable) {
let queued
try {
queued = pending()
} catch {
__emnapiWasmEnvCleanupDrained = true
return
}
if (!queued) {
__emnapiWasmEnvCleanupDrained = true
return
}
}
const limit = observable
? __WASM_ENV_CLEANUP_DRAIN_TURNS
: __WASM_ENV_CLEANUP_BLIND_DRAIN_TURNS
const drainPromise = (async () => {
let queued = 0
for (let turn = 0; turn < limit; turn++) {
await new Promise((resolve) => {
__scheduleMacrotask(resolve)
})
if (!observable) {
continue
}
try {
queued = pending()
} catch {
return
}
if (!queued) {
return
}
}
if (!observable) {
// Blind wait: without `napi_wasm_env_cleanup_pending` the bound IS the
// contract — there is nothing to consult, so finishing the turns is
// finishing the drain.
return
}
// The counter is still nonzero after every turn the bound allows. The wait
// stays bounded — but claiming success here would be indistinguishable from
// the stranding this drain exists to prevent: disposal would go on to
// destroy the context, whose cleanup hook discards the still-queued
// settlement with a null env, and the promise it was for hangs forever.
// Reject instead, as a retryable cleanup failure: the drained flag stays
// unset, dispose() (and the rollback) decline to destroy, and a later
// dispose() runs the drain again — by which time the queue has usually been
// delivered. A counter that is somehow stuck nonzero therefore costs each
// attempt at most another bounded wait and a rejection, never a stranded
// promise; the process-exit teardown still reclaims the context.
const drainError = new Error(
'the wasm environment still reports ' +
queued +
' queued settlement(s) after ' +
limit +
' event-loop turns; the context was not destroyed - retry dispose() to wait for the queue again',
)
drainError.code = 'ERR_NAPI_WASI_CLEANUP_PENDING'
throw drainError
})().then(
(value) => {
// Set only when the wait actually finished AND the queue was seen empty
// (or is unobservable): a drain that timed out with settlements still
// queued rejects above and must stay repeatable.
__emnapiWasmEnvCleanupDrained = true
__emnapiWasmEnvCleanupDrainPromise = undefined
return value
},
(error) => {
__emnapiWasmEnvCleanupDrainPromise = undefined
throw error
},
)
__emnapiWasmEnvCleanupDrainPromise = drainPromise
return drainPromise
}
function __destroyEmnapiContext() {
if (__emnapiContextDestroyed || __emnapiContext === undefined) {
__emnapiContextDestroyed = true
return
}
if (__emnapiContextDestroyPromise) {
return __emnapiContextDestroyPromise
}
__prepareWasmEnvCleanup()
if (__isPreparingWasmEnvCleanup()) {
// Reached from inside the synchronous barrier — a promise hook one of the
// settlements above ran, which is the reentrancy the destroy wrapper
// exists for. `Context.destroy()` below would hit that wrapper's in-flight
// no-op and answer `undefined`, and recording that as a completed destroy
// is what makes the frame that *did* start the barrier skip the real one
// afterwards, leaving the context retained with its cleanup hooks unrun.
// Refuse instead: nothing is flagged, and that frame destroys for real the
// moment it returns. The deferred loader carries the same backstop. A
// parked handshake cannot get here — `__prepareWasmEnvCleanup` closes one
// rather than skipping it.
return
}
const result = __emnapiContext.destroy()
if (!__isThenable(result)) {
__emnapiContextDestroyed = true
return
}
const destroyPromise = Promise.resolve(result).then(
(value) => {
__emnapiContextDestroyed = true
return value
},
(error) => {
__emnapiContextDestroyPromise = undefined
throw error
},
)
__emnapiContextDestroyPromise = destroyPromise
return destroyPromise
}
/**
* Holds the event loop open until `work` settles.
*
* Nothing else can: the pool workers are deliberately unreferenced so an idle
* binding cannot keep a process alive, and referencing them again for the
* termination does not hold either — emnapi unreferences a worker the moment it
* reports `async-thread-ready`, which for a worker that was still starting
* lands *after* the termination began. Without a handle of its own, an
* `await dispose()` with nothing else pending exits the process with its
* promise unsettled, and everything after the `await` is skipped.
*
* The timer is cleared as soon as the work settles, so this never outlives the
* disposal that asked for it.
*/
function __keepEventLoopAliveUntil(work) {
const setTimer = globalThis.setInterval
const clearTimer = globalThis.clearInterval
if (typeof setTimer !== 'function' || typeof clearTimer !== 'function') {
return work
}
let timer
try {
timer = setTimer(function () {}, 50)
} catch {
return work
}
const release = function () {
try {
clearTimer(timer)
} catch {}
}
return work.then(
(value) => {
release()
return value
},
(error) => {
release()
throw error
},
)
}
// How often to re-read `napi_wasm_async_work_pending` while waiting. The wait
// ends when the addon reports zero, so this only decides how promptly disposal
// notices — not how long it waits.
const __WASI_ASYNC_WORK_POLL_INTERVAL_MS = 1
/**
* Settles this addon's outstanding `napi_async_work` before the teardown that
* would strand it.
*
* `napi_prepare_wasm_env_cleanup` does not cover async work, and nothing about
* it is observable from JavaScript: the threadless archive resolves
* `napi_*_async_work` through the `@emnapi/core` plugins, but the threaded one
* links the C `async_work.c` on the uv threadpool, so there the wasm neither
* imports nor exports those symbols and the only brackets a loader could watch
* (`_emnapi_ctx_*_waiting_request_counter`) are shared with threadsafe
* functions. The addon is the one place both flavors go through, so it answers
* for both, through the same kind of handshake the settlement drain uses:
*
* - `napi_wasm_cancel_pending_async_work()` cancels what no thread has
* started. Those completion callbacks run with `napi_cancelled`, which
* napi-rs turns into a promise rejected with an `AbortError`.
* - `napi_wasm_async_work_pending()` counts what is still owed a completion
* callback. Work already executing refuses cancellation and stays counted
* until it finishes normally — which it can, because this runs before the
* barrier, before `Context.destroy()` and before anything is terminated.
*
* Both exports are optional: an addon built against a napi crate that predates
* them drains nothing and keeps the previous behavior, exactly as the
* `napi_wasm_env_cleanup_pending` handshake degrades.
*
* Returns nothing when there is nothing outstanding, which keeps disposal
* synchronous in the common case. The promise it returns otherwise never
* rejects.
*
* The wait has no deadline, and that is the point: giving up would destroy the
* environment with a completion callback still owed, which is the stranding
* this exists to prevent. A task whose `execute` never returns already keeps an
* *undisposed* process alive in exactly the same way, so disposal inherits that
* rather than inventing a bound it cannot honor.
*
* Safe to call from inside a completion callback, which is reachable: settling
* a task runs addon code that can re-enter JavaScript — a setter on the value
* being handed back, a threadsafe-function callback — and that JavaScript can
* call `dispose()`. Two things make it terminate rather than wait on itself:
*
* - The addon keeps a work registered until its completion callback
* *finishes*, so the count read here is at least one and this takes the
* polling path instead of declaring the environment drained and tearing it
* down from inside the frame that is still settling a promise.
* - The poll is a timer, so it cannot run until the callback has returned to
* the host — by which time that work has left the registry. The count the
* next poll reads is the one taken after the callback finished.
*
* `__disposeWasiBinding` hands every caller the same in-flight promise, so the
* nested call joins this disposal rather than starting a second one.
*/
function __drainWasiAsyncWork() {
if (__wasiAsyncWorkDrainPromise !== undefined) {
return __wasiAsyncWorkDrainPromise
}
const exports = __napiInstance?.exports
const pending = exports?.napi_wasm_async_work_pending
const cancelPending = exports?.napi_wasm_cancel_pending_async_work
if (typeof pending !== 'function' || typeof cancelPending !== 'function') {
return
}
const readPending = () => {
try {
return pending()
} catch (error) {
// A trap is the only way this call fails: it reads a counter and cannot
// allocate or call back into JavaScript. A trapped instance can no longer
// run anything, so its outstanding work is unreachable by definition —
// there is nothing left to wait for, and refusing to dispose would only
// keep a dead instance and its stuck counter alive. Best-effort here is
// the honest answer, and it is what disposal did before this drain
// existed.
//
// Only a trap. Anything else means the export is not what this loader
// thinks it is, which is a defect worth surfacing rather than disposing
// over.
if (error instanceof globalThis.WebAssembly.RuntimeError) {
return 0
}
throw error
}
}
if (!readPending()) {
return
}
try {
cancelPending()
} catch {
// Cancellation is an optimization: it bounds the wait by the work already
// executing. Failing it only means waiting for the whole queue instead.
}
if (!readPending()) {
return
}
const drainPromise = __keepEventLoopAliveUntil(
(async () => {
while (readPending()) {
await new Promise((resolve) => {
__scheduleTimer(resolve, __WASI_ASYNC_WORK_POLL_INTERVAL_MS)
})
}
})(),
).then(
() => {
__wasiAsyncWorkDrainPromise = undefined
},
(error) => {
// A wait that could not run is not a wait that finished. The only way
// here is a host whose timers and macrotask primitives all refuse, and
// the work is still outstanding — reporting success would destroy the
// environment over it, which is the stranding this exists to prevent.
// Reject instead: disposal stays retryable, and the context is not
// destroyed. Clearing the memo first is what makes the retry re-run this.
__wasiAsyncWorkDrainPromise = undefined
throw error
},
)
__wasiAsyncWorkDrainPromise = drainPromise
return drainPromise
}
/**
* `@emnapi/wasi-threads` counts a worker exit as expected only when its own
* thread manager performed the termination. A bare `worker.terminate()` reaches
* the manager's `exit` listener instead, which reports
* `worker (tid = N) sent an error! ... stopped with exit code 1` and rethrows
* inside the emit — aborting the `once('exit')` that backs the terminate
* promise, so disposal never settles and the process dies with an uncaught
* exception. Mark the termination through the manager first.
*
* The manager comes from `__getWasiThreadManager`, not from `__napiModule`:
* the initialization rollback runs on the one path where instantiation never
* returned, so `__napiModule` is still undefined there while the workers it
* spawned are already registered and loaded.
*
* Not `terminateAllThreads()`: that one recreates the pool it just shut down.
*/
function __terminateWasiWorkers() {
const cleanupErrors = []
const pending = []
const threadManager = __getWasiThreadManager()
for (const worker of __wasiWorkers) {
let result
try {
if (threadManager) {
threadManager.terminateWorker(worker)
// `terminateWorker` leaves behind a reporter that logs every message
// still queued on the port, which Node flushes on exit. Nothing is
// listening for those any more.
worker.onmessage = undefined
}
result = worker.terminate()
} catch (error) {
cleanupErrors.push(error)
continue
}
if (__isThenable(result)) {
pending.push(
Promise.resolve(result).then(
() => {
__wasiWorkers.delete(worker)
},
(error) => {
cleanupErrors.push(error)
},
),
)
} else {
__wasiWorkers.delete(worker)
}
}
const finish = () => {
if (cleanupErrors.length > 0) {
throw __createCleanupError(
cleanupErrors,
'Failed to terminate WASI workers',
)
}
}
return pending.length > 0
? __keepEventLoopAliveUntil(Promise.all(pending)).then(finish)
: finish()
}
function __finishWasiDisposal() {
const workerResult = __terminateWasiWorkers()
if (__isThenable(workerResult)) {
return Promise.resolve(workerResult).then(__completeWasiDisposal)
}
return __completeWasiDisposal()
}
function __continueWasiDisposal() {
const destroyResult = __destroyEmnapiContext()
if (__isThenable(destroyResult)) {
return Promise.resolve(destroyResult).then(__finishWasiDisposal)
}
return __finishWasiDisposal()
}
function __drainWasmEnvForWasiDisposal() {
const drainResult = __drainWasmEnvCleanup()
if (__isThenable(drainResult)) {
return Promise.resolve(drainResult).then(__continueWasiDisposal)
}
return __continueWasiDisposal()
}
function __cleanUpWasmEnvForWasiDisposal() {
// Run the pre-teardown barrier — yielding the turns its two-phase form asks
// for, when the addon has one — then let the settlements it queued actually
// reach JavaScript, and only then destroy the environment. Doing any two of
// these back to back is what strands them.
const prepareResult = __prepareWasmEnvCleanupWithTurns()
if (__isThenable(prepareResult)) {
return Promise.resolve(prepareResult).then(__drainWasmEnvForWasiDisposal)
}
return __drainWasmEnvForWasiDisposal()
}
function __startWasiDisposal() {
// Outstanding `napi_async_work` goes first, while the environment is still
// completely live: the completion callbacks run addon code, and everything
// after this point takes that away from them — the barrier shuts the async
// runtime down, `Context.destroy()` stops JavaScript calls, and terminating
// the pool threads removes what would have reported the work finished.
const asyncWorkResult = __drainWasiAsyncWork()
if (__isThenable(asyncWorkResult)) {
return Promise.resolve(asyncWorkResult).then(
__cleanUpWasmEnvForWasiDisposal,
)
}
return __cleanUpWasmEnvForWasiDisposal()
}
/**
* Disposes this generated WASI binding.
*
* Access this function with:
* binding[Symbol.for('napi.rs.wasi.dispose')]()
*/
function __disposeWasiBinding() {
if (__wasiDisposePromise) {
return __wasiDisposePromise
}
if (__wasiDisposed) {
return Promise.resolve()
}
let resolveDispose
let rejectDispose
const disposePromise = new Promise((resolve, reject) => {
resolveDispose = resolve
rejectDispose = reject
})
__wasiDisposePromise = disposePromise
let result
try {
result = __startWasiDisposal()
} catch (error) {
__wasiDisposePromise = undefined
rejectDispose(error)
return disposePromise
}
Promise.resolve(result).then(
(value) => {
__wasiDisposed = true
resolveDispose(value)
},
(error) => {
__wasiDisposePromise = undefined
rejectDispose(error)
},
)
return disposePromise
}
function __publishWasiDispose(exports) {
Object.defineProperty(exports, __wasiDisposeSymbol, {
configurable: false,
enumerable: false,
value: __disposeWasiBinding,
writable: false,
})
}
function __finishWasiInitializationRollback(cleanupErrors) {
let workerResult
try {
workerResult = __terminateWasiWorkers()
} catch (cleanupError) {
cleanupErrors.push(cleanupError)
return cleanupErrors
}
if (__isThenable(workerResult)) {
return Promise.resolve(workerResult)
.catch((cleanupError) => {
cleanupErrors.push(cleanupError)
})
.then(() => cleanupErrors)
}
return cleanupErrors
}
function __destroyContextForWasiRollback(cleanupErrors) {
let destroyResult
try {
destroyResult = __destroyEmnapiContext()
} catch (cleanupError) {
cleanupErrors.push(cleanupError)
return __finishWasiInitializationRollback(cleanupErrors)
}
if (__isThenable(destroyResult)) {
return Promise.resolve(destroyResult)
.catch((cleanupError) => {
cleanupErrors.push(cleanupError)
})
.then(() => __finishWasiInitializationRollback(cleanupErrors))
}
return __finishWasiInitializationRollback(cleanupErrors)
}
/**
* Leaves a rollback that could not reach the queued settlements undestroyed, and
* hands it to whatever this flavor has that can still reclaim it.
*/
function __retainFailedWasiRollback(cleanupErrors) {
try {
__retainWasiRollbackForRetry()
} catch (cleanupError) {
cleanupErrors.push(cleanupError)
}
return cleanupErrors
}
/**
* Initialization can fail *after* registration has already run, and registration
* runs with a live environment: a module-init hook can start async work and then
* return an error, and the promise it created may already have escaped into
* JavaScript. The barrier cancels that work and *queues* the settlement, so this
* path needs the same drain the ordinary disposal does — destroying without
* yielding discards the queue with a null env and strands the promise.
*
* Stays synchronous when nothing is queued, which covers every failure before
* `beforeInit`: there is no instance to run the barrier on, so nothing to drain.
*
* A barrier or drain that did *not* finish stops the rollback short of
* destroying, which is what `dispose()` already does — a rejected drain there
* never reaches `__continueWasiDisposal`. Destroying anyway is the worse of the
* two trades, and not because of what it saves:
*
* - It cannot deliver the settlements. `Context.destroy()` runs the
* threadsafe function's cleanup hook, which drains the queue with a null env
* and discards it, so a promise that already escaped into JavaScript hangs
* forever with nothing left that could ever settle it.
* - It saves less than it looks. `Context.destroy()` stops JavaScript calls
* and runs cleanup hooks; it does not free the wasm instance or its Memory,
* which this module's scope holds either way. What stopping short retains is
* the emnapi context's bookkeeping and its un-run cleanup hooks.
* - Retry is not theoretical. A rollback that records a cleanup error is
* already kept in the process-wide registry above, so re-`require()`ing this
* file replays it instead of re-instantiating — and the `6e15de6f` flag fix
* means the replay drains again rather than skipping it. Destroying first is
* what makes that retained record useless.
*
* The residual cost is honest: the CJS flavor hands the context to its
* `process.on('exit')` teardown, so a process that never retries still reclaims
* it on the way out. The ESM browser flavor has no equivalent — a module that
* throws while evaluating is permanently errored, so re-importing rethrows
* without re-running this file — and there the context stays until the realm
* goes away. That is the deliberate choice: a hung promise is a silent liveness
* bug with no upper bound, while the retained bookkeeping is bounded by the page.
*/
function __rollbackWasiInitialization() {
// The environment teardown this rollback performs, kept nested so it cannot
// be reached without the async-work drain below running first.
function __rollbackWasmEnvForWasiInitialization() {
const cleanupErrors = []
let prepareResult
try {
prepareResult = __prepareWasmEnvCleanupWithTurns()
} catch (cleanupError) {
cleanupErrors.push(cleanupError)
return __retainFailedWasiRollback(cleanupErrors)
}
if (__isThenable(prepareResult)) {
return Promise.resolve(prepareResult).then(
() => __drainWasmEnvForWasiRollback(cleanupErrors),
(cleanupError) => {
cleanupErrors.push(cleanupError)
return __retainFailedWasiRollback(cleanupErrors)
},
)
}
return __drainWasmEnvForWasiRollback(cleanupErrors)
}
// The settlement drain of the rollback above, reached either straight away or
// after the barrier's two-phase form has yielded its turns. A barrier that
// did not finish never gets here: it retains instead, exactly as a drain that
// did not finish does.
function __drainWasmEnvForWasiRollback(cleanupErrors) {
let drainResult
try {
drainResult = __drainWasmEnvCleanup()
} catch (cleanupError) {
cleanupErrors.push(cleanupError)
return __retainFailedWasiRollback(cleanupErrors)
}
if (__isThenable(drainResult)) {
return Promise.resolve(drainResult).then(
() => __destroyContextForWasiRollback(cleanupErrors),
(cleanupError) => {
cleanupErrors.push(cleanupError)
return __retainFailedWasiRollback(cleanupErrors)
},
)
}
return __destroyContextForWasiRollback(cleanupErrors)
}
// Same reason as `__startWasiDisposal`: a module-init hook can start async
// work before the load goes on to fail, and this rollback tears down exactly
// what those completions need. Settle them while everything is still live,
// before the barrier and the teardown above take that away.
//
// A drain that could not finish leaves async work possibly outstanding, and
// destroying the context over it would strand exactly what this rollback is
// there to settle. Stop short and retain instead — the same trade
// `__rollbackWasmEnvForWasiInitialization` makes for the settlement drain, so
// the context stays reclaimable by a retry or by this flavor's own
// last-resort teardown.
const __retainAfterAsyncWorkDrainFailure = (cleanupError) =>
__retainFailedWasiRollback([cleanupError])
let asyncWorkResult
try {
asyncWorkResult = __drainWasiAsyncWork()
} catch (cleanupError) {
return __retainAfterAsyncWorkDrainFailure(cleanupError)
}
if (__isThenable(asyncWorkResult)) {
return Promise.resolve(asyncWorkResult).then(
__rollbackWasmEnvForWasiInitialization,
__retainAfterAsyncWorkDrainFailure,
)
}
return __rollbackWasmEnvForWasiInitialization()
}
let __wasiModule
let __napiModule
try {
__emnapiContext = __wrapEmnapiContextDestroyForSettlement(
__emnapiCreateContext({ autoDestroy: false }),
__prepareWasmEnvCleanup,
__isPreparingWasmEnvCleanup,
)
__emnapiContext.suppressDestroy()
;({
instance: __napiInstance,
module: __wasiModule,
napiModule: __napiModule,
} = __emnapiInstantiateNapiModuleSync(__wasmFile, {
context: __emnapiContext,
asyncWorkPoolSize: 0,
plugins: [
__captureWasiThreadManager,
__emnapiAsyncWorkPlugin,
__emnapiTSFNPlugin,
],
wasi: __wasi,
overwriteImports(importObject) {
importObject.env = {
...importObject.env,
...importObject.napi,
...importObject.emnapi,
memory: __wasmMemory,
}
return importObject
},
beforeInit({ instance }) {
__napiInstance = instance
for (const name of Object.keys(instance.exports)) {
if (name.startsWith('__napi_register__')) {
instance.exports[name]()
}
}
},
}))
__publishWasiDispose(__napiModule.exports)
// The default export hands out this object; a named module export does not
// travel with it, so carry the marker on the binding itself too. After the
// host install, which hands the same object to addon-provided registration
// functions that may put anything on it, and inside this `try`, so a claimed
// name fails the load through the rollback below rather than past it.
__napiStampBindingTarget(__napiModule.exports, __napiBindingTarget)
} catch (error) {
const cleanupErrors = await __rollbackWasiInitialization()
throw __attachCleanupErrors(error, cleanupErrors)
}
export default __napiModule.exports
export const Severity = __napiModule.exports.Severity
export const transform = __napiModule.exports.transform
export const transformSync = __napiModule.exports.transformSync