Files
oxc/napi/transform-react/transform-react.wasip1-deferred.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

1866 lines
67 KiB
JavaScript

import {
emnapiAsyncWorkPlugin as __emnapiAsyncWorkPlugin,
emnapiTSFNPlugin as __emnapiTSFNPlugin,
instantiateNapiModule as __emnapiInstantiateNapiModule,
WASI as __WASI,
} from '@napi-rs/wasm-runtime'
import { createContext as __emnapiCreateContext } from '@emnapi/runtime'
export const WASM_MEMORY = Object.freeze({
initialPages: 1024,
maximumPages: 65536,
pageBytes: 65536,
initialBytes: 1024 * 65536,
maximumBytes: 65536 * 65536,
})
let __createdInstances = 0
let __liveInstances = 0
/**
* Counters for instances created by THIS module evaluation, not process-wide:
* a second bundled copy of this loader keeps its own. Only successfully
* created instances are counted, and `liveInstances` drops when an instance's
* `dispose()` resolves.
*
* `declaredInitialMemoryBytes` is declared address space, not a host's
* committed-memory metric; pair it with host telemetry rather than treating it
* as a quota.
*/
export function getDeferredRuntimeStats() {
return Object.freeze({
createdInstances: __createdInstances,
liveInstances: __liveInstances,
declaredInitialMemoryBytes: WASM_MEMORY.initialBytes,
})
}
const __arrayBufferByteLengthGetter = Object.getOwnPropertyDescriptor(
ArrayBuffer.prototype,
'byteLength',
).get
const __memoryBufferGetter = Object.getOwnPropertyDescriptor(
WebAssembly.Memory.prototype,
'buffer',
).get
// One managed initialization per Memory, success or failure: an attempt that
// throws may already have written into linear memory, so the bytes are not a
// clean slate for a second instance. Module-local, like the counters above.
const __claimedMemories = new WeakSet()
function __resolveInstanceMemory(__options) {
const __provided = __options == null ? undefined : __options.memory
if (__provided === undefined || __provided === null) {
// Page counts are handed to the engine unvalidated: it already rejects a
// negative, over-4GiB or below-maximum value with a precise message, and a
// second set of bounds here would only drift from it.
const __allocated = new WebAssembly.Memory({
initial:
__options != null && __options.initialMemoryPages !== undefined
? __options.initialMemoryPages
: WASM_MEMORY.initialPages,
maximum:
__options != null && __options.maximumMemoryPages !== undefined
? __options.maximumMemoryPages
: WASM_MEMORY.maximumPages,
})
// Claimed like a caller-provided one. The handle publishes it as
// `instance.memory`, so handing it back to `createInstance()` is as easy
// as passing your own twice, and it would put two live instances on one
// linear memory: each initialization rewrites the emnapi/WASI state the
// other is still running on.
__claimedMemories.add(__allocated)
return __allocated
}
if (
__options.initialMemoryPages !== undefined ||
__options.maximumMemoryPages !== undefined
) {
throw new TypeError(
'Pass either memory or initialMemoryPages/maximumMemoryPages, not both',
)
}
let __buffer
try {
// Brand check: the getter throws for anything that is not a genuine
// WebAssembly.Memory, including a cross-realm look-alike object.
__buffer = Reflect.apply(__memoryBufferGetter, __provided, [])
} catch {
throw new TypeError('memory must be an unshared WebAssembly.Memory')
}
try {
// Throws for a SharedArrayBuffer. This loader has no threads, and shared
// growth does not detach: external views handed to the addon would
// silently outlive the bytes they describe.
Reflect.apply(__arrayBufferByteLengthGetter, __buffer, [])
} catch {
throw new TypeError(
'The deferred loader requires an unshared WebAssembly.Memory',
)
}
// The intrinsic getters above accept a genuine Memory from ANY realm, but
// the loader's dependencies do not: `WASI.setMemory` in
// `@napi-rs/wasm-runtime` and emnapi identify a Memory with a realm-local
// `instanceof`. A Memory built in another realm (a `node:vm` context, a
// same-origin iframe) would pass every check here and only fail deep inside
// initialization. Reject it up front, and before the claim below, so the
// caller keeps it usable in the realm that made it.
if (!(__provided instanceof WebAssembly.Memory)) {
throw new TypeError(
'memory must be a WebAssembly.Memory created in the same realm as this loader',
)
}
if (__claimedMemories.has(__provided)) {
throw new TypeError(
'This WebAssembly.Memory has already been used for a deferred initialization attempt and cannot be reused, including after a failed initialization or a disposal',
)
}
// Last step, after every check: a rejected option bag must leave the Memory
// unclaimed, or a caller could not fix the call and retry with it.
__claimedMemories.add(__provided)
return __provided
}
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
}
/**
* Deferred, workerd-safe instantiation: no top-level I/O, no compile-from-bytes.
* Accepts ONLY a precompiled WebAssembly.Module, or a Promise resolving to one
* (e.g. `import mod from './transform-react.wasm32-wasip1.wasm'` under a CompiledWasm
* module rule / wrangler module import). Byte buffers, URLs and Response
* objects are rejected: they require dynamic Wasm compilation, which
* Cloudflare Workers disallows.
*/
async function __resolveModule(__wasmInput) {
const __module = await __wasmInput
// Brand check, not `instanceof`: `WebAssembly.Module.imports` throws unless
// its argument is a genuine WebAssembly.Module, so prototype-spoofed byte
// buffers are rejected while cross-realm Module instances are accepted.
try {
WebAssembly.Module.imports(__module)
} catch {
throw new TypeError(
"instantiate() and createInstance() expect a precompiled WebAssembly.Module (or a Promise resolving to one), " +
"e.g. import mod from './transform-react.wasm32-wasip1.wasm' under a CompiledWasm module rule / wrangler module import. " +
"Byte buffers, URLs and Response objects require dynamic Wasm compilation, which Cloudflare Workers disallows.",
)
}
return __module
}
let __normalizedModules
function __rememberNormalizedModule(__module, __normalizedModule) {
if (!__normalizedModules) {
__normalizedModules = new WeakMap()
}
__normalizedModules.set(__module, __normalizedModule)
return __normalizedModule
}
async function __normalizeModuleForEmnapi(__module) {
if (__module instanceof WebAssembly.Module) {
return __module
}
if (__normalizedModules) {
const __normalizedModule = __normalizedModules.get(__module)
if (__normalizedModule) {
return __normalizedModule
}
}
// @emnapi/core currently performs realm-local `instanceof` checks after
// accepting the module. Structured cloning preserves compiled code without
// compiling bytes and produces a Module owned by the current realm.
if (typeof structuredClone === 'function') {
try {
const __normalizedModule = structuredClone(__module)
if (__normalizedModule instanceof WebAssembly.Module) {
return __rememberNormalizedModule(__module, __normalizedModule)
}
} catch {}
}
// MessageChannel uses the same structured-clone semantics and covers older
// browser/Node hosts that expose it but not the structuredClone function.
if (typeof MessageChannel === 'function') {
let __channel
try {
__channel = new MessageChannel()
const __normalizedModule = await new Promise((resolve, reject) => {
__channel.port1.onmessage = (event) => resolve(event.data)
__channel.port1.onmessageerror = () =>
reject(new TypeError('Failed to clone WebAssembly.Module'))
try {
__channel.port2.postMessage(__module)
} catch (error) {
reject(error)
}
})
if (__normalizedModule instanceof WebAssembly.Module) {
return __rememberNormalizedModule(__module, __normalizedModule)
}
} catch {
} finally {
try {
__channel?.port1.close()
} catch {}
try {
__channel?.port2.close()
} catch {}
}
}
// Last-resort compatibility for genuine, extensible foreign Modules.
try {
Object.setPrototypeOf(__module, WebAssembly.Module.prototype)
} catch {}
if (__module instanceof WebAssembly.Module) {
return __module
}
throw new TypeError(
'This host cannot normalize a cross-realm WebAssembly.Module; ' +
'provide structuredClone or MessageChannel support.',
)
}
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 __captureEmnapiAutoDestroyListener(__process) {
if (
!__process ||
typeof __process.prependListener !== 'function' ||
typeof __process.removeListener !== 'function'
) {
return
}
let __autoDestroyListener
const __captureListener = (__event, __listener) => {
if (__event === 'beforeExit' && __autoDestroyListener === undefined) {
__autoDestroyListener = __listener
}
}
try {
// Run before existing newListener hooks so a hook that registers its own
// beforeExit listener cannot be mistaken for emnapi's registration.
__process.prependListener('newListener', __captureListener)
} catch {
return
}
return () => {
try {
__process.removeListener('newListener', __captureListener)
} catch {}
if (__autoDestroyListener !== undefined) {
try {
__process.removeListener('beforeExit', __autoDestroyListener)
} catch {}
}
}
}
function __attachCleanupError(__error, __cleanupError) {
try {
if (
__error &&
(typeof __error === 'object' || typeof __error === 'function') &&
__error.cause === undefined
) {
__error.cause = __cleanupError
}
} 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)
}
})()
// 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.
*/
function __drainWasmEnvCleanup(__instance) {
const __pending = __instance?.exports.napi_wasm_env_cleanup_pending
const __observable = typeof __pending === 'function'
if (__observable) {
let __queued
try {
__queued = __pending()
} catch {
return
}
if (!__queued) {
return
}
}
const __limit = __observable
? __WASM_ENV_CLEANUP_DRAIN_TURNS
: __WASM_ENV_CLEANUP_BLIND_DRAIN_TURNS
return (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: `__prepareForDisposal`
// leaves its drained flag unset, dispose() (and the instantiation-failure
// path) 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 managed
// beforeExit destroyer 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
})()
}
// A real, *referenced* timer for the async-work wait below, which polls the
// addon rather than interleaving with the @emnapi/core dispatch: 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 wait 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
})
}
/**
* Yield event-loop turns until the addon reports its runtime work finished.
*
* The window between `napi_prepare_wasm_env_cleanup_begin` and
* `…_finish` — the turns are the entire point of splitting the barrier, because
* on a threaded artifact the work `…_finish` joins can itself be waiting for a
* JavaScript turn from this very thread.
*
* Unbounded, for the same reason the async-work wait above is: giving up means
* calling `…_finish`, which joins on this thread, and the work it would join is
* the work 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. The host contract is in `crates/async-runtime/README.md`: a
* blocking closure must never wait on a JavaScript turn.
*/
async function __pollWasmRuntimeWork(__workPending) {
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
}
}
}
// 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 instance's outstanding `napi_async_work` before the teardown
* that would strand it.
*
* The same hole the eager loaders had, and the same fix: the environment
* cleanup barrier covers promise settlements queued on the threadsafe-function
* queue and says nothing about `napi_async_work`, so destroying the context
* with a work still outstanding leaves its completion callback with nowhere to
* run and its promise unsettled forever.
*
* This flavor is threadless, so `compute` runs on the JavaScript thread inside
* the macrotask that dequeued it: while this is running, any outstanding work
* is queued rather than executing, and `napi_wasm_cancel_pending_async_work`
* can take all of it. The poll is still what decides when the drain is done —
* cancellation delivers those completions from a later macrotask, not
* synchronously.
*
* Per instance, from that instance's own exports: two instances of this loader
* have separate registries and must not wait on each other.
*
* Both exports are optional, so an addon built against a napi crate that
* predates them keeps the previous behavior.
*
* Returns nothing when there is nothing outstanding, which keeps disposal
* synchronous in the common case. No deadline: giving up would destroy the
* environment with a completion callback still owed.
*/
function __drainInstanceAsyncWork(__instance) {
const __exports = __instance?.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 and
// best-effort is the honest answer. Anything else means the export is not
// what this loader thinks it is — a defect worth surfacing.
if (__error instanceof globalThis.WebAssembly.RuntimeError) {
return 0
}
throw __error
}
}
if (!__readPending()) {
return
}
try {
__cancelPending()
} catch {
// Cancellation only bounds the wait. Failing it means waiting for the queue
// to run instead, which the poll below already does.
}
if (!__readPending()) {
return
}
return (async () => {
while (__readPending()) {
await new Promise((__resolve) => {
__scheduleTimer(__resolve, __WASI_ASYNC_WORK_POLL_INTERVAL_MS)
})
}
})()
}
function __createLifecycleReentryError(__operation) {
const __error = new Error(
__operation +
'() cannot run while an emnapi Context.destroy() call is still active; await the original cleanup promise instead.',
)
__error.code = 'ERR_NAPI_WASI_LIFECYCLE_REENTRY'
return __error
}
const __managedEmnapiContextDestroyers = new Set()
let __managedCleanupProcess
let __managedBeforeExitListener
let __managedDestroyPromise
let __managedDestroyersInFlight
let __managedBeforeExitRegistrationRetryCount = 0
let __managedBeforeExitRegistrationRetryScheduled = false
let __moduleLifecycleDestroyDepth = 0
function __removeManagedEmnapiCleanupListeners() {
const __process = __managedCleanupProcess
const __beforeExitListener = __managedBeforeExitListener
__managedCleanupProcess = undefined
__managedBeforeExitListener = undefined
__managedBeforeExitRegistrationRetryCount = 0
if (__process && __beforeExitListener) {
try {
__process.removeListener('beforeExit', __beforeExitListener)
} catch {}
}
}
function __scheduleManagedBeforeExitListenerRegistration() {
if (
!__managedCleanupProcess ||
__managedBeforeExitListener ||
__managedEmnapiContextDestroyers.size === 0 ||
__managedBeforeExitRegistrationRetryScheduled ||
__managedBeforeExitRegistrationRetryCount >= 3
) {
return
}
__managedBeforeExitRegistrationRetryScheduled = true
__managedBeforeExitRegistrationRetryCount++
queueMicrotask(() => {
__managedBeforeExitRegistrationRetryScheduled = false
if (
!__managedCleanupProcess ||
__managedBeforeExitListener ||
__managedEmnapiContextDestroyers.size === 0
) {
return
}
try {
__registerManagedBeforeExitListener()
} catch {}
})
}
function __registerManagedBeforeExitListener() {
if (!__managedCleanupProcess || __managedBeforeExitListener) {
return
}
try {
__managedCleanupProcess.once(
'beforeExit',
__destroyManagedEmnapiContextsBeforeExit,
)
} catch (error) {
__scheduleManagedBeforeExitListenerRegistration()
throw error
}
__managedBeforeExitListener = __destroyManagedEmnapiContextsBeforeExit
__managedBeforeExitRegistrationRetryCount = 0
}
function __settleManagedEmnapiContextDestroy(__promise) {
if (__managedDestroyPromise === __promise) {
__managedDestroyPromise = undefined
__managedDestroyersInFlight = undefined
}
if (__managedEmnapiContextDestroyers.size === 0) {
__removeManagedEmnapiCleanupListeners()
return
}
try {
__registerManagedBeforeExitListener()
} catch {}
}
function __destroyManagedEmnapiContexts(__excludedDestroyers) {
if (__managedDestroyPromise) {
return __managedDestroyPromise
}
const __destroyers = Array.from(__managedEmnapiContextDestroyers).filter(
(__destroy) => !__excludedDestroyers?.has(__destroy),
)
if (__destroyers.length === 0) {
return Promise.resolve()
}
let __resolveDestroy
let __rejectDestroy
const __promise = new Promise((resolve, reject) => {
__resolveDestroy = resolve
__rejectDestroy = reject
})
__managedDestroyPromise = __promise
__managedDestroyersInFlight = new Set(__destroyers)
void Promise.all(
__destroyers.map((__destroy) => {
try {
return Promise.resolve(__destroy()).then(
() => ({ failed: false }),
(error) => ({ failed: true, error }),
)
} catch (error) {
return { failed: true, error }
}
}),
).then((__results) => {
let __primaryError
let __failed = false
for (const __result of __results) {
if (!__result.failed) {
continue
}
if (!__failed) {
__failed = true
__primaryError = __result.error
} else {
__attachCleanupError(__primaryError, __result.error)
}
}
if (__failed) {
__rejectDestroy(__primaryError)
} else {
__resolveDestroy()
}
}, __rejectDestroy)
void __promise.then(
() => {
__settleManagedEmnapiContextDestroy(__promise)
},
() => {
__settleManagedEmnapiContextDestroy(__promise)
},
)
return __promise
}
async function __drainManagedEmnapiContexts(__excludedDestroyers) {
const __attemptedDestroyers = new Set(__excludedDestroyers)
let __primaryError
let __failed = false
while (true) {
let __promise = __managedDestroyPromise
let __destroyers = __managedDestroyersInFlight
if (!__promise) {
__promise = __destroyManagedEmnapiContexts(__attemptedDestroyers)
__destroyers = __managedDestroyersInFlight
if (!__destroyers) {
break
}
}
for (const __destroy of __destroyers) {
__attemptedDestroyers.add(__destroy)
}
try {
await __promise
} catch (error) {
if (!__failed) {
__failed = true
__primaryError = error
} else {
__attachCleanupError(__primaryError, error)
}
}
}
if (__failed) {
throw __primaryError
}
}
function __destroyManagedEmnapiContextsBeforeExit() {
// A once listener is consumed before Node invokes it, including when another
// cleanup batch is still pending.
__managedBeforeExitListener = undefined
if (__managedDestroyPromise) {
return
}
void __destroyManagedEmnapiContexts().catch((error) => {
queueMicrotask(() => {
throw error
})
})
}
function __registerManagedEmnapiContext(__process, __destroy) {
__managedEmnapiContextDestroyers.add(__destroy)
if (
!__managedCleanupProcess &&
__process &&
typeof __process.once === 'function' &&
typeof __process.removeListener === 'function'
) {
__managedCleanupProcess = __process
}
let __registered = true
return () => {
if (!__registered) {
return
}
__registered = false
__managedEmnapiContextDestroyers.delete(__destroy)
if (__managedEmnapiContextDestroyers.size === 0) {
__removeManagedEmnapiCleanupListeners()
}
}
}
async function __createManagedEmnapiContext(
__prepareEnvCleanup,
__isPreparingEnvCleanup,
) {
const __process =
typeof process === 'object' && process !== null ? process : undefined
const __finishAutoDestroyCapture =
__captureEmnapiAutoDestroyListener(__process)
let __emnapiContext
let __contextInitializationError
let __contextInitializationFailed = false
try {
__emnapiContext = __wrapEmnapiContextDestroyForSettlement(
__emnapiCreateContext({ autoDestroy: false }),
__prepareEnvCleanup,
__isPreparingEnvCleanup,
)
// emnapi 2.x still registers an unconditional process.once('beforeExit')
// auto-destroy listener on Node hosts, and suppressDestroy() only
// neutralizes its callback without removing it. This loader must stay
// side-effect free per instance, so the listener is captured and removed;
// suppressDestroy() remains the safety net when removal is unavailable.
__emnapiContext.suppressDestroy()
} catch (error) {
__contextInitializationError = error
__contextInitializationFailed = true
} finally {
// Remove only the exact emnapi callback captured above.
__finishAutoDestroyCapture?.()
}
if (__emnapiContext === undefined) {
throw __contextInitializationError
}
let __disposed = false
let __destroying = false
let __destroyPromise
let __cleanupRegistered = false
let __unregisterCleanup
const __destroy = (__blocksModuleLifecycle = false) => {
if (__disposed) {
return
}
if (__destroying) {
throw __createLifecycleReentryError('dispose')
}
if (__destroyPromise) {
return __destroyPromise
}
__destroying = true
let __result
const __finishDestroyInvocation = () => {
__destroying = false
}
const __finishModuleLifecycleDestroy = () => {
if (__blocksModuleLifecycle) {
__blocksModuleLifecycle = false
__moduleLifecycleDestroyDepth--
}
}
if (__blocksModuleLifecycle) {
__moduleLifecycleDestroyDepth++
}
try {
// Context.destroy() disables JS before cleanup hooks run, so settle
// runtime-owned promises while this environment can still call JS.
__prepareEnvCleanup?.()
if (__isPreparingEnvCleanup?.()) {
// Reached from inside the barrier, so `Context.destroy()` below would
// hit the wrapper's in-flight no-op. 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, the context stays
// registered for managed beforeExit cleanup, and a later destroy still
// works. dispose() coalesces reentrancy before it can get here, so this
// is the backstop for any other caller that manages to. A handshake
// parked between the two halves of the barrier does not reach here —
// `__prepareEnvCleanup` closes one rather than skipping it.
throw __createLifecycleReentryError('dispose')
}
__result = __emnapiContext.destroy()
} catch (error) {
__finishDestroyInvocation()
__finishModuleLifecycleDestroy()
throw error
}
let __then
try {
if (
__result !== null &&
(typeof __result === 'object' || typeof __result === 'function')
) {
__then = __result.then
}
} catch (error) {
__finishDestroyInvocation()
__finishModuleLifecycleDestroy()
throw error
}
if (typeof __then === 'function') {
let __resolveResult
let __rejectResult
const __resultPromise = new Promise((resolve, reject) => {
__resolveResult = resolve
__rejectResult = reject
})
const __promise = __resultPromise.then(
(value) => {
__finishDestroyInvocation()
__finishModuleLifecycleDestroy()
__disposed = true
__destroyPromise = undefined
__unregisterCleanup?.()
return value
},
(error) => {
__finishDestroyInvocation()
__finishModuleLifecycleDestroy()
__destroyPromise = undefined
throw error
},
)
__destroyPromise = __promise
try {
Reflect.apply(__then, __result, [__resolveResult, __rejectResult])
} catch (error) {
__rejectResult(error)
}
return __promise
}
__finishDestroyInvocation()
__finishModuleLifecycleDestroy()
__disposed = true
__unregisterCleanup?.()
}
const __destroyForModuleLifecycle = () => __destroy(true)
const __registerCleanup = (
__beforeExitDestroy = __destroyForModuleLifecycle,
) => {
if (__cleanupRegistered || __disposed) {
return
}
__unregisterCleanup = __registerManagedEmnapiContext(
__process,
__beforeExitDestroy,
)
__cleanupRegistered = true
__registerManagedBeforeExitListener()
}
if (__contextInitializationFailed) {
let __registrationError
let __registrationFailed = false
try {
__registerCleanup()
} catch (error) {
__attachCleanupError(__contextInitializationError, error)
__registrationError = error
__registrationFailed = true
}
try {
await __destroyForModuleLifecycle()
} catch (error) {
__attachCleanupError(
__registrationFailed
? __registrationError
: __contextInitializationError,
error,
)
try {
__registerManagedBeforeExitListener()
} catch {}
}
throw __contextInitializationError
}
return {
context: __emnapiContext,
destroy: __destroy,
destroyForModuleLifecycle: __destroyForModuleLifecycle,
registerCleanup: __registerCleanup,
}
}
async function __createInstance(
__wasmInput,
__options,
__beforeExitDestroy,
__onManagedDestroyer,
) {
const __module = await __resolveModule(__wasmInput)
const __emnapiModule = await __normalizeModuleForEmnapi(__module)
const __wasi = new __WASI({
version: 'preview1',
})
// The wasm module is linked with `--import-memory`, so a Memory must be
// provided. It is resolved here in function scope (workerd bans global scope
// allocation) and is never shared (no threads, no SharedArrayBuffer).
// Resolve it before the emnapi context so a rejected option bag or a host
// memory-limit failure cannot leak a context that never reaches
// instantiation.
const __wasmMemory = __resolveInstanceMemory(__options)
let __lifecycleState = 'pending'
let __destroyEmnapiContext
let __destroyOwnedContext
let __destroyManagedOwnedContext
let __napiInstance
let __wasmEnvCleanupRan = false
let __wasmEnvCleanupPrepared = false
let __wasmEnvCleanupPreparing = false
// The closer for a barrier parked between `…_begin` and `…_finish`, set only
// while that window is open. `__wasmEnvCleanupPreparing` cannot tell that
// apart from a purely synchronous frame, which must not be re-entered and
// which nothing outside it can finish; this window spans real event-loop
// turns, so a caller that cannot yield — the managed beforeExit teardown —
// can land inside it, and can close it. See `__prepareEnvCleanup`.
let __finishParkedEnvCleanup
// Raised while a caller that can still yield is driving the barrier, so the
// queue it leaves behind is expected rather than lost.
let __wasmEnvCleanupYielding = false
let __wasmEnvSettlementLossReported = false
let __wasmEnvCleanupDrained = false
let __wasmEnvCleanupDrainPromise
const __isPreparingEnvCleanup = () => __wasmEnvCleanupPreparing
/**
* Say so when the barrier leaves settlements queued and nothing is left that
* could deliver them.
*
* Only `dispose()` and the initialization rollback yield 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 managed
* beforeExit 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()`,
* where throwing would take the whole teardown down with it. Destroying
* anyway is still the right trade — the queue is already unreachable by then.
*/
const __reportUnreachedSettlements = () => {
if (__wasmEnvCleanupYielding || __wasmEnvSettlementLossReported) {
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
}
__wasmEnvSettlementLossReported = 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 the instance instead: only dispose() yields the event-loop turns the settlements need.',
)
}
} catch {}
}
const __prepareEnvCleanup = () => {
if (__wasmEnvCleanupPrepared) {
return
}
// A handshake parked between its two halves is one this frame can close,
// and must: every caller of this is about to destroy the context, and the
// turns the poll is waiting for will not come. Closing it runs `…_finish`,
// which is the call that joins, so this degrades to exactly the single
// call below. Leaving it open destroys the context with the barrier still
// raised and the runtime never joined.
const __finishParked = __finishParkedEnvCleanup
if (__finishParked !== undefined) {
__finishParked()
__reportUnreachedSettlements()
return
}
if (__wasmEnvCleanupPreparing) {
return
}
const __prepareWasmEnvCleanup =
__napiInstance?.exports.napi_prepare_wasm_env_cleanup
if (typeof __prepareWasmEnvCleanup === '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.
__wasmEnvCleanupPreparing = true
try {
__prepareWasmEnvCleanup()
} finally {
__wasmEnvCleanupPreparing = false
}
__wasmEnvCleanupRan = true
__reportUnreachedSettlements()
}
__wasmEnvCleanupPrepared = true
}
/**
* The barrier for the callers that can yield: `__prepareEnvCleanup` 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 the work it waits for can itself be waiting
* for a JavaScript turn from this thread. 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 again
* without blocking, and `…_finish` joins.
*
* The poll is unbounded — see `__pollWasmRuntimeWork` — but a caller that
* cannot yield closes the handshake itself rather than waiting for it, so
* `…_finish` still runs on every teardown path. Feature-detected like every
* other export here, and returns nothing whenever the handshake finished
* without yielding.
*/
const __prepareEnvCleanupWithTurns = () => {
if (__wasmEnvCleanupPrepared || __wasmEnvCleanupPreparing) {
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.
__wasmEnvCleanupYielding = true
try {
__prepareEnvCleanup()
} finally {
__wasmEnvCleanupYielding = 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.
__wasmEnvCleanupPreparing = true
let __live
try {
__live = __begin()
} catch (__error) {
__wasmEnvCleanupPreparing = false
throw __error
}
__wasmEnvCleanupRan = true
const __finishEnvCleanup = () => {
if (__wasmEnvCleanupPrepared) {
// Already closed by a caller that could not yield. `…_finish` is
// idempotent, but the flags it lowers are not.
return
}
__finishParkedEnvCleanup = undefined
try {
__finish()
} finally {
__wasmEnvCleanupPreparing = false
}
__wasmEnvCleanupPrepared = true
}
if (!__live || typeof __workPending !== 'function') {
__finishEnvCleanup()
return
}
// Publish the closer before yielding: from here until `__finishEnvCleanup`
// runs, a caller that cannot yield is entitled to end this handshake.
__finishParkedEnvCleanup = __finishEnvCleanup
return __pollWasmRuntimeWork(__workPending).then(
__finishEnvCleanup,
__finishEnvCleanup,
)
}
// The barrier + settlement drain, hoisted out of the context destroyer so the
// drain can yield without widening the destroyer's reentry window. Both
// yielding paths run it — dispose() and the initialization-failure rollback.
// The destroyer still runs the barrier itself (idempotently) for the one path
// that cannot yield: managed beforeExit cleanup of an instance whose rollback
// is being retried.
//
// "Drained" is recorded only once a wait has actually finished. Scheduling a
// macrotask can fail — a host-provided or patched `setImmediate` that throws
// is enough — and dispose() stays retryable after it rejects, so marking the
// drain complete up front would make the retry skip it and destroy the context
// with the barrier's settlements still queued.
const __drainAfterEnvCleanup = () => {
if (!__wasmEnvCleanupRan) {
return
}
const __drained = __drainWasmEnvCleanup(__napiInstance)
if (!__drained || typeof __drained.then !== 'function') {
__wasmEnvCleanupDrained = true
return
}
return __drained.then((__value) => {
__wasmEnvCleanupDrained = true
return __value
})
}
const __prepareForDisposal = () => {
if (__wasmEnvCleanupDrained) {
return
}
if (__wasmEnvCleanupDrainPromise) {
return __wasmEnvCleanupDrainPromise
}
// The barrier itself can yield now, so the memo below has to cover it too:
// a reentrant caller must join this handshake rather than start a second
// one while the first is parked between the two halves.
const __prepared = __prepareEnvCleanupWithTurns()
const __settled =
__prepared && typeof __prepared.then === 'function'
? __prepared.then(__drainAfterEnvCleanup)
: __drainAfterEnvCleanup()
if (!__settled || typeof __settled.then !== 'function') {
return
}
const __tracked = __settled.then(
(__value) => {
__wasmEnvCleanupDrainPromise = undefined
return __value
},
(__error) => {
__wasmEnvCleanupDrainPromise = undefined
throw __error
},
)
__wasmEnvCleanupDrainPromise = __tracked
return __tracked
}
let __disposed = false
const __runInstanceDisposal = async () => {
if (__lifecycleState !== 'failed') {
__lifecycleState = 'disposal'
}
// Outstanding async work first, while the environment is still completely
// live: its completion callbacks run addon code, and the barrier and
// `Context.destroy()` below each take that away. Undefined unless work is
// outstanding.
const __asyncWorkDrained = __drainInstanceAsyncWork(__napiInstance)
if (__asyncWorkDrained) {
await __asyncWorkDrained
}
// Then settle what the barrier cancelled, before the environment stops
// accepting JavaScript calls. Undefined unless something is queued, so
// an idle disposal is not delayed by a single turn.
const __drained = __prepareForDisposal()
if (__drained) {
await __drained
}
const __result = await (__beforeExitDestroy
? __destroyManagedOwnedContext()
: __destroyOwnedContext())
// Only a completed destroy retires the instance; a throw above leaves
// the counter untouched so a retried dispose() cannot double-decrement.
if (!__disposed) {
__disposed = true
__liveInstances -= 1
}
return __result
}
let __instanceDisposePromise
/**
* The disposal frame runs the barrier, and the barrier settles the promises it
* cancels synchronously — so a promise hook firing inside it can call this
* same instance's dispose() again while the first call is still in its drain.
* That nested call finds the barrier flagged in flight, prepares nothing,
* drains nothing, and falls straight through to the context destroyer, whose
* `Context.destroy()` hits the wrapper's in-flight no-op. It would record a
* destruction that never happened, and the outer frame would then skip the
* real one: both disposals resolve, no cleanup hook runs, the context stays
* retained.
*
* Memoize before any of that starts, exactly like the eager loaders'
* `__disposeWasiBinding`, so there is only ever one disposal frame per
* instance and a reentrant caller awaits it instead of racing it. Cleared on
* rejection: a drain that failed has to stay retryable.
*/
const __disposeInstance = () => {
if (__instanceDisposePromise) {
return __instanceDisposePromise
}
let __resolveDispose
let __rejectDispose
const __disposePromise = new Promise((__resolve, __reject) => {
__resolveDispose = __resolve
__rejectDispose = __reject
})
__instanceDisposePromise = __disposePromise
__runInstanceDisposal().then(__resolveDispose, (__error) => {
__instanceDisposePromise = undefined
__rejectDispose(__error)
})
return __disposePromise
}
const __destroyBeforeExit = __beforeExitDestroy
? async () => {
if (__lifecycleState === 'failed') {
await __destroyManagedOwnedContext()
return
}
__lifecycleState = 'disposal'
try {
await __beforeExitDestroy()
} catch (error) {
if (__lifecycleState !== 'failed') {
throw error
}
// The singleton's initialization rejection is already observable
// through instantiate() and dispose(). Managed beforeExit cleanup
// owns only context destruction, including retrying a failed rollback.
await __destroyManagedOwnedContext()
}
}
: undefined
const {
context: __emnapiContext,
destroy,
destroyForModuleLifecycle,
registerCleanup: __registerCleanup,
} = await __createManagedEmnapiContext(
__prepareEnvCleanup,
__isPreparingEnvCleanup,
)
__destroyEmnapiContext = destroy
__destroyOwnedContext = () => __destroyEmnapiContext()
__destroyManagedOwnedContext = destroyForModuleLifecycle
try {
if (__destroyBeforeExit) {
__onManagedDestroyer(__destroyBeforeExit)
await __registerCleanup(__destroyBeforeExit)
}
let __napiModule
;({
instance: __napiInstance,
napiModule: __napiModule,
} = await __emnapiInstantiateNapiModule(__emnapiModule, {
context: __emnapiContext,
asyncWorkPoolSize: 0,
plugins: [__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]()
}
}
},
}))
// `instantiate()` and `createInstance().exports` hand out this object; a
// named module export does not travel with it. After the instance 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 flips `__lifecycleState` to 'failed' and tears the instance
// down rather than escaping a half-built one.
__napiStampBindingTarget(__napiModule.exports, __napiBindingTarget)
if (__lifecycleState === 'pending') {
__lifecycleState = 'succeeded'
}
__createdInstances += 1
__liveInstances += 1
return {
exports: __napiModule.exports,
get memory() {
return __wasmMemory
},
get memoryBytes() {
// The Memory outlives the environment, so this stays readable after a
// FAILED dispose() (which leaves the instance undisposed and
// retryable). It reports 0 only once disposal has actually completed.
return __disposed ? 0 : __wasmMemory.buffer.byteLength
},
get disposed() {
return __disposed
},
dispose: __disposeInstance,
}
} catch (error) {
__lifecycleState = 'failed'
// Instantiation can fail *after* registration has 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. Settle what the barrier cancels before the context is
// destroyed, exactly like dispose() does; destroying without yielding
// discards the queue with a null env. Undefined unless something is queued,
// so a failure before beforeInit costs no extra turn.
let __settlementsUnreached = false
try {
// Registration can start async work too, and this path destroys the same
// environment its completions need. A drain that cannot finish leaves the
// work outstanding, so it counts as settlements unreached and stops the
// rollback short of destroying, exactly like a failed barrier drain.
const __asyncWorkDrained = __drainInstanceAsyncWork(__napiInstance)
if (__asyncWorkDrained) {
await __asyncWorkDrained
}
const __drained = __prepareForDisposal()
if (__drained) {
await __drained
}
} catch (drainError) {
__attachCleanupError(error, drainError)
__settlementsUnreached = true
}
let __registrationError
let __registrationFailed = false
if (!__beforeExitDestroy) {
try {
// Independent instances are caller-owned while pending and after
// success. Register only failed rollback so cleanup remains retryable.
await __registerCleanup()
} catch (registrationError) {
__attachCleanupError(error, registrationError)
__registrationError = registrationError
__registrationFailed = true
}
}
if (__settlementsUnreached) {
// The barrier or the drain did not finish, so the settlements it queued
// are still in the threadsafe-function queue. Destroying now runs the
// cleanup hook that drains that queue with a null env and discards it,
// stranding a promise that already escaped into JavaScript — with nothing
// left that could ever settle it. dispose() refuses to destroy for exactly
// this reason (a rejected drain there never reaches the destroy), so this
// path refuses too.
//
// Nothing leaks. The registration just above — and, for the singleton, the
// one made before instantiation — leaves this context in
// `__managedEmnapiContextDestroyers`, so beforeExit destroys it and
// dispose() can retry it. Only the destruction is deferred, and the turns
// that pass in the meantime are exactly what the queue needed.
try {
__registerManagedBeforeExitListener()
} catch {}
throw error
}
try {
await __destroyManagedOwnedContext()
} catch (disposeError) {
// Initialization is the primary failure. Preserve it even if cleanup
// also fails, while retaining the cleanup error when the value is
// extensible and has no existing cause.
__attachCleanupError(
__registrationFailed ? __registrationError : error,
disposeError,
)
try {
__registerManagedBeforeExitListener()
} catch {}
}
throw error
}
}
/**
* Create an independent instance. Call and await dispose() when the instance
* is no longer needed so emnapi cleanup hooks run deterministically.
*
* The optional second argument selects this instance's linear memory: either
* `memory` (an unshared, single-use WebAssembly.Memory you allocated) or
* `initialMemoryPages` / `maximumMemoryPages`, never both. Omitted, the
* loader allocates WASM_MEMORY.initialPages..WASM_MEMORY.maximumPages.
*
* A provided Memory must come from this loader's own realm: the WASI and
* emnapi layers underneath identify one with a realm-local `instanceof`, so a
* Memory built in a `node:vm` context or another frame is rejected. Every
* Memory an instance runs on is single-use, the loader-allocated one included:
* `instance.memory` cannot be recycled into a second `createInstance()`.
*/
export async function createInstance(__wasmInput, __options) {
return __createInstance(__wasmInput, __options)
}
let __defaultModulePromise
let __defaultInstancePromise
let __defaultDisposePromise
let __defaultDisposalStarted = false
const __defaultManagedDestroyers = new WeakMap()
let __moduleDisposePromise
/**
* Instantiate a module-local singleton. Concurrent and repeated calls
* with the same module share one instance and one Memory allocation.
*/
export function instantiate(__wasmInput) {
const __modulePromise = __resolveModule(__wasmInput)
if (__moduleLifecycleDestroyDepth !== 0) {
void __modulePromise.catch(() => {})
return Promise.reject(__createLifecycleReentryError('instantiate'))
}
if (__moduleDisposePromise) {
void __modulePromise.catch(() => {})
return __moduleDisposePromise.then(() => instantiate(__modulePromise))
}
if (__defaultDisposalStarted) {
// Observe rejected input immediately, but preserve lifecycle ordering and
// error precedence by instantiating only after disposal succeeds. A failed
// disposal retains the old instance only so its cleanup can be retried.
void __modulePromise.catch(() => {})
const __disposePromise = __defaultDisposePromise ?? dispose()
return __disposePromise.then(() => instantiate(__modulePromise))
}
if (!__defaultInstancePromise) {
__defaultModulePromise = __modulePromise
const __instancePromise = __modulePromise.then((__module) =>
__createInstance(
__module,
undefined,
__disposeDefaultInstance,
(__managedDestroyer) => {
__defaultManagedDestroyers.set(
__instancePromise,
__managedDestroyer,
)
},
),
)
__defaultInstancePromise = __instancePromise
void __instancePromise.catch(() => {
if (__defaultInstancePromise === __instancePromise) {
__defaultInstancePromise = undefined
__defaultModulePromise = undefined
}
})
return __instancePromise.then((__instance) => __instance.exports)
}
const __defaultModulePromiseForCall = __defaultModulePromise
const __defaultInstancePromiseForCall = __defaultInstancePromise
return Promise.all([__defaultModulePromiseForCall, __modulePromise]).then(
async ([__defaultModule, __module]) => {
if (__defaultModule !== __module) {
throw new Error(
'instantiate() already owns a different WebAssembly.Module; call dispose() first or use createInstance() for independent instances.',
)
}
return (await __defaultInstancePromiseForCall).exports
},
)
}
async function __disposeDefaultInstance(__onDestroy) {
if (__defaultDisposePromise) {
return __defaultDisposePromise
}
const __instancePromise = __defaultInstancePromise
if (!__instancePromise) {
__defaultDisposalStarted = false
return
}
__defaultDisposalStarted = true
const __disposePromise = (async () => {
let __instance
try {
__instance = await __instancePromise
} catch (error) {
const __managedDestroyer =
__defaultManagedDestroyers.get(__instancePromise)
if (__managedDestroyer) {
__onDestroy?.(__managedDestroyer)
}
__defaultManagedDestroyers.delete(__instancePromise)
throw error
}
const __managedDestroyer =
__defaultManagedDestroyers.get(__instancePromise)
if (__managedDestroyer) {
__onDestroy?.(__managedDestroyer)
}
await __instance.dispose()
if (__defaultInstancePromise === __instancePromise) {
__defaultInstancePromise = undefined
__defaultModulePromise = undefined
__defaultDisposalStarted = false
}
__defaultManagedDestroyers.delete(__instancePromise)
})()
__defaultDisposePromise = __disposePromise
try {
await __disposePromise
} finally {
if (__defaultDisposePromise === __disposePromise) {
__defaultDisposePromise = undefined
}
}
}
async function __dispose() {
let __defaultDisposeError
let __defaultDisposeFailed = false
let __attemptedDefaultDestroyer
try {
await __disposeDefaultInstance((__destroyer) => {
__attemptedDefaultDestroyer = __destroyer
})
} catch (error) {
__defaultDisposeError = error
__defaultDisposeFailed = true
}
const __excludedDestroyers = new Set()
if (__defaultDisposeFailed && __attemptedDefaultDestroyer) {
__excludedDestroyers.add(__attemptedDefaultDestroyer)
}
try {
await __drainManagedEmnapiContexts(__excludedDestroyers)
} catch (error) {
if (!__defaultDisposeFailed) {
throw error
}
if (error !== __defaultDisposeError) {
__attachCleanupError(__defaultDisposeError, error)
}
}
if (__defaultDisposeFailed) {
throw __defaultDisposeError
}
}
/**
* Dispose the singleton created by instantiate(). A later call may create a
* fresh instance, including from a different module. This also retries cleanup
* retained after a failed initialization rollback.
*/
export function dispose() {
if (__moduleLifecycleDestroyDepth !== 0) {
return Promise.reject(__createLifecycleReentryError('dispose'))
}
if (__moduleDisposePromise) {
return __moduleDisposePromise
}
let __resolveDispose
let __rejectDispose
const __promise = new Promise((resolve, reject) => {
__resolveDispose = resolve
__rejectDispose = reject
})
__moduleDisposePromise = __promise
void __dispose().then(__resolveDispose, __rejectDispose)
void __promise.then(
() => {
if (__moduleDisposePromise === __promise) {
__moduleDisposePromise = undefined
}
},
() => {
if (__moduleDisposePromise === __promise) {
__moduleDisposePromise = undefined
}
},
)
return __promise
}