mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
chore(deps): update dependency @napi-rs/cli to v3.10.0 (#26690)
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.9.1` → `3.10.1`](https://renovatebot.com/diffs/npm/@napi-rs%2fcli/3.9.1/3.10.1) |  |  |  |  | pnpm.catalog.default | minor | | [napi](https://redirect.github.com/napi-rs/napi-rs) | `3.12.4` → `3.12.5` |  |  |  |  | workspace.dependencies | patch | | [napi-derive](https://redirect.github.com/napi-rs/napi-rs) | `3.6.5` → `3.6.6` |  |  |  |  | workspace.dependencies | patch | --- ### Release Notes <details> <summary>napi-rs/napi-rs (@​napi-rs/cli)</summary> ### [`v3.10.1`](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/cli@3.10.0...@napi-rs/cli@3.10.1) [Compare Source](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/cli@3.10.0...@napi-rs/cli@3.10.1) ### [`v3.10.0`](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/cli@3.9.1...@napi-rs/cli@3.10.0) [Compare Source](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/cli@3.9.1...@napi-rs/cli@3.10.0) </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:eyJjcmVhdGVkSW5WZXIiOiI0NC45My4wIiwidXBkYXRlZEluVmVyIjoiNDQuOTMuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==--> --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Cameron Clark <cameron.clark@hey.com>
This commit is contained in:
1 parent
f91ad92366
commit
536cea81a4
38 files changed
+2855
-96
No files matched your search
Generated
+4
-4
@@ -1401,9 +1401,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "napi"
|
||||
version = "3.12.4"
|
||||
version = "3.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d2c1d080c842ec1347ebf3cfb9347c15f8c2262f143cc30eea41ddf1c7da720"
|
||||
checksum = "f0c007d4a8ead952a81887661d41fd56b7e7a70d3d4d921f445fb37a8f6efa1e"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"ctor",
|
||||
@@ -1426,9 +1426,9 @@ checksum = "860e7c40864f95cfb83cde99f9ebadd88ef3d9bdccd7dd2cee0cc96a2dd4ffa7"
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive"
|
||||
version = "3.6.5"
|
||||
version = "3.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "350057056a30368aa76c11a0d406b0aa61321710be2c36656ab4f6efe0b785a2"
|
||||
checksum = "e8872852c2d050fc5859749864119bc5d50e3f6ad5957874d61d1a07c76771cb"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"ctor",
|
||||
|
||||
Vendored
+8
@@ -1,6 +1,14 @@
|
||||
/* auto-generated by NAPI-RS */
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* Which binding artifact the generated loader actually loaded: `'native'` for
|
||||
* a native addon, otherwise the `platformArchABI` of the WASI flavor. Every
|
||||
* flavor napi-rs can build is listed, because `NAPI_RS_NATIVE_LIBRARY_PATH`
|
||||
* can point the loader at a WASI artifact this package does not build itself.
|
||||
*/
|
||||
export declare const __napiBindingTarget: 'native' | 'wasm32-wasi' | 'wasm32-wasip1'
|
||||
|
||||
export interface Comment {
|
||||
type: 'Line' | 'Block'
|
||||
value: string
|
||||
|
||||
@@ -7,6 +7,10 @@ const require = createRequire(import.meta.url)
|
||||
|
||||
const { readFileSync } = require('fs')
|
||||
let nativeBinding = null
|
||||
// Which artifact actually loaded. The WASI fallback chain overwrites it with
|
||||
// the flavor it resolved; the late native retry below leaves it alone because
|
||||
// it only runs while no WASI candidate has been loaded.
|
||||
let __napiLoadedBindingTarget = 'native'
|
||||
const loadErrors = []
|
||||
|
||||
const isMusl = () => {
|
||||
@@ -65,7 +69,16 @@ const isMuslFromChildProcess = () => {
|
||||
function requireNative() {
|
||||
if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
|
||||
try {
|
||||
return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
const overrideBinding = require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
// The override may be a generated WASI loader, which already reports its
|
||||
// own flavor. Adopt it: `module.exports` aliases this object, so claiming
|
||||
// 'native' would both misreport the artifact and overwrite the loader's
|
||||
// marker through the alias.
|
||||
__napiLoadedBindingTarget =
|
||||
overrideBinding && typeof overrideBinding.__napiBindingTarget === 'string'
|
||||
? overrideBinding.__napiBindingTarget
|
||||
: 'native'
|
||||
return overrideBinding
|
||||
} catch (err) {
|
||||
loadErrors.push(err)
|
||||
}
|
||||
@@ -630,6 +643,7 @@ if (!nativeBinding || forceWasi) {
|
||||
if (!candidateFailed) {
|
||||
wasiBinding = require('./oxfmt.wasi.cjs')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -656,6 +670,7 @@ if (!nativeBinding || forceWasi) {
|
||||
}
|
||||
wasiBinding = require('@oxfmt/binding-wasm32-wasi')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -706,3 +721,4 @@ export { Severity }
|
||||
export { format }
|
||||
export { jsTextToDoc }
|
||||
export { runCli }
|
||||
export const __napiBindingTarget = __napiLoadedBindingTarget
|
||||
Vendored
+8
@@ -1,6 +1,14 @@
|
||||
/* auto-generated by NAPI-RS */
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* Which binding artifact the generated loader actually loaded: `'native'` for
|
||||
* a native addon, otherwise the `platformArchABI` of the WASI flavor. Every
|
||||
* flavor napi-rs can build is listed, because `NAPI_RS_NATIVE_LIBRARY_PATH`
|
||||
* can point the loader at a WASI artifact this package does not build itself.
|
||||
*/
|
||||
export declare const __napiBindingTarget: 'native' | 'wasm32-wasi' | 'wasm32-wasip1'
|
||||
|
||||
export interface Comment {
|
||||
type: 'Line' | 'Block'
|
||||
value: string
|
||||
|
||||
@@ -7,6 +7,10 @@ const require = createRequire(import.meta.url)
|
||||
|
||||
const { readFileSync } = require('fs')
|
||||
let nativeBinding = null
|
||||
// Which artifact actually loaded. The WASI fallback chain overwrites it with
|
||||
// the flavor it resolved; the late native retry below leaves it alone because
|
||||
// it only runs while no WASI candidate has been loaded.
|
||||
let __napiLoadedBindingTarget = 'native'
|
||||
const loadErrors = []
|
||||
|
||||
const isMusl = () => {
|
||||
@@ -65,7 +69,16 @@ const isMuslFromChildProcess = () => {
|
||||
function requireNative() {
|
||||
if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
|
||||
try {
|
||||
return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
const overrideBinding = require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
// The override may be a generated WASI loader, which already reports its
|
||||
// own flavor. Adopt it: `module.exports` aliases this object, so claiming
|
||||
// 'native' would both misreport the artifact and overwrite the loader's
|
||||
// marker through the alias.
|
||||
__napiLoadedBindingTarget =
|
||||
overrideBinding && typeof overrideBinding.__napiBindingTarget === 'string'
|
||||
? overrideBinding.__napiBindingTarget
|
||||
: 'native'
|
||||
return overrideBinding
|
||||
} catch (err) {
|
||||
loadErrors.push(err)
|
||||
}
|
||||
@@ -630,6 +643,7 @@ if (!nativeBinding || forceWasi) {
|
||||
if (!candidateFailed) {
|
||||
wasiBinding = require('./oxlint.wasi.cjs')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -656,6 +670,7 @@ if (!nativeBinding || forceWasi) {
|
||||
}
|
||||
wasiBinding = require('@oxlint/binding-wasm32-wasi')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -708,3 +723,4 @@ export { getBufferOffset }
|
||||
export { lint }
|
||||
export { parseRawSync }
|
||||
export { rawTransferSupported }
|
||||
export const __napiBindingTarget = __napiLoadedBindingTarget
|
||||
Vendored
+8
@@ -1,6 +1,14 @@
|
||||
/* auto-generated by NAPI-RS */
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* Which binding artifact the generated loader actually loaded: `'native'` for
|
||||
* a native addon, otherwise the `platformArchABI` of the WASI flavor. Every
|
||||
* flavor napi-rs can build is listed, because `NAPI_RS_NATIVE_LIBRARY_PATH`
|
||||
* can point the loader at a WASI artifact this package does not build itself.
|
||||
*/
|
||||
export declare const __napiBindingTarget: 'native' | 'wasm32-wasi' | 'wasm32-wasip1'
|
||||
|
||||
export interface CodegenOptions {
|
||||
/**
|
||||
* Remove whitespace.
|
||||
|
||||
+17
-1
@@ -7,6 +7,10 @@ const require = createRequire(import.meta.url)
|
||||
|
||||
const { readFileSync } = require('fs')
|
||||
let nativeBinding = null
|
||||
// Which artifact actually loaded. The WASI fallback chain overwrites it with
|
||||
// the flavor it resolved; the late native retry below leaves it alone because
|
||||
// it only runs while no WASI candidate has been loaded.
|
||||
let __napiLoadedBindingTarget = 'native'
|
||||
const loadErrors = []
|
||||
|
||||
const isMusl = () => {
|
||||
@@ -65,7 +69,16 @@ const isMuslFromChildProcess = () => {
|
||||
function requireNative() {
|
||||
if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
|
||||
try {
|
||||
return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
const overrideBinding = require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
// The override may be a generated WASI loader, which already reports its
|
||||
// own flavor. Adopt it: `module.exports` aliases this object, so claiming
|
||||
// 'native' would both misreport the artifact and overwrite the loader's
|
||||
// marker through the alias.
|
||||
__napiLoadedBindingTarget =
|
||||
overrideBinding && typeof overrideBinding.__napiBindingTarget === 'string'
|
||||
? overrideBinding.__napiBindingTarget
|
||||
: 'native'
|
||||
return overrideBinding
|
||||
} catch (err) {
|
||||
loadErrors.push(err)
|
||||
}
|
||||
@@ -630,6 +643,7 @@ if (!nativeBinding || forceWasi) {
|
||||
if (!candidateFailed) {
|
||||
wasiBinding = require('./minify.wasi.cjs')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -656,6 +670,7 @@ if (!nativeBinding || forceWasi) {
|
||||
}
|
||||
wasiBinding = require('@oxc-minify/binding-wasm32-wasi')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -714,3 +729,4 @@ export { LegalCommentsMode }
|
||||
export { minify }
|
||||
export { minifySync }
|
||||
export { Severity }
|
||||
export const __napiBindingTarget = __napiLoadedBindingTarget
|
||||
@@ -8,6 +8,55 @@ import {
|
||||
import { createContext as __emnapiCreateContext } from '@emnapi/runtime'
|
||||
|
||||
|
||||
export const __napiBindingTarget = 'wasm32-wasi'
|
||||
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',
|
||||
@@ -42,10 +91,40 @@ 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
|
||||
let __emnapiWasmEnvCleanupRan = false
|
||||
let __emnapiWasmEnvCleanupDrained = false
|
||||
let __emnapiWasmEnvCleanupDrainPromise
|
||||
@@ -119,13 +198,58 @@ function __attachCleanupErrors(error, cleanupErrors) {
|
||||
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) {
|
||||
if (__emnapiWasmEnvCleanupPrepared || __emnapiWasmEnvCleanupPreparing) {
|
||||
return
|
||||
}
|
||||
const prepare = __napiInstance?.exports?.napi_prepare_wasm_env_cleanup
|
||||
if (typeof prepare === 'function') {
|
||||
prepare()
|
||||
// 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
|
||||
}
|
||||
__emnapiWasmEnvCleanupPrepared = true
|
||||
@@ -318,13 +442,80 @@ function __destroyEmnapiContext() {
|
||||
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
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* `@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)
|
||||
@@ -354,7 +545,9 @@ function __terminateWasiWorkers() {
|
||||
)
|
||||
}
|
||||
}
|
||||
return pending.length > 0 ? Promise.all(pending).then(finish) : finish()
|
||||
return pending.length > 0
|
||||
? __keepEventLoopAliveUntil(Promise.all(pending)).then(finish)
|
||||
: finish()
|
||||
}
|
||||
|
||||
function __finishWasiDisposal() {
|
||||
@@ -555,7 +748,11 @@ let __wasiModule
|
||||
let __napiModule
|
||||
|
||||
try {
|
||||
__emnapiContext = __emnapiCreateContext({ autoDestroy: false })
|
||||
__emnapiContext = __wrapEmnapiContextDestroyForSettlement(
|
||||
__emnapiCreateContext({ autoDestroy: false }),
|
||||
__prepareWasmEnvCleanup,
|
||||
__isPreparingWasmEnvCleanup,
|
||||
)
|
||||
__emnapiContext.suppressDestroy()
|
||||
|
||||
;({
|
||||
@@ -566,7 +763,7 @@ try {
|
||||
context: __emnapiContext,
|
||||
asyncWorkPoolSize: __asyncWorkPoolSize,
|
||||
reuseWorker: false,
|
||||
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
plugins: [__captureWasiThreadManager, __emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
wasi: __wasi,
|
||||
onCreateWorker() {
|
||||
const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), {
|
||||
@@ -596,6 +793,12 @@ try {
|
||||
},
|
||||
}))
|
||||
__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)
|
||||
|
||||
+223
-5
@@ -2,6 +2,56 @@
|
||||
/* eslint-disable */
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
const __napiBindingTarget = 'wasm32-wasi'
|
||||
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 __nodeFs = require('node:fs')
|
||||
const __nodePath = require('node:path')
|
||||
const { WASI: __nodeWASI } = require('node:wasi')
|
||||
@@ -149,10 +199,40 @@ 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
|
||||
let __emnapiWasmEnvCleanupRan = false
|
||||
let __emnapiWasmEnvCleanupDrained = false
|
||||
let __emnapiWasmEnvCleanupDrainPromise
|
||||
@@ -226,13 +306,58 @@ function __attachCleanupErrors(error, cleanupErrors) {
|
||||
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) {
|
||||
if (__emnapiWasmEnvCleanupPrepared || __emnapiWasmEnvCleanupPreparing) {
|
||||
return
|
||||
}
|
||||
const prepare = __napiInstance?.exports?.napi_prepare_wasm_env_cleanup
|
||||
if (typeof prepare === 'function') {
|
||||
prepare()
|
||||
// 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
|
||||
}
|
||||
__emnapiWasmEnvCleanupPrepared = true
|
||||
@@ -425,13 +550,80 @@ function __destroyEmnapiContext() {
|
||||
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
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* `@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)
|
||||
@@ -461,7 +653,9 @@ function __terminateWasiWorkers() {
|
||||
)
|
||||
}
|
||||
}
|
||||
return pending.length > 0 ? Promise.all(pending).then(finish) : finish()
|
||||
return pending.length > 0
|
||||
? __keepEventLoopAliveUntil(Promise.all(pending)).then(finish)
|
||||
: finish()
|
||||
}
|
||||
|
||||
function __finishWasiDisposal() {
|
||||
@@ -831,7 +1025,11 @@ function __captureEmnapiAutoDestroyListener() {
|
||||
try {
|
||||
const __finishAutoDestroyCapture = __captureEmnapiAutoDestroyListener()
|
||||
try {
|
||||
__emnapiContext = __emnapiCreateContext({ autoDestroy: false })
|
||||
__emnapiContext = __wrapEmnapiContextDestroyForSettlement(
|
||||
__emnapiCreateContext({ autoDestroy: false }),
|
||||
__prepareWasmEnvCleanup,
|
||||
__isPreparingWasmEnvCleanup,
|
||||
)
|
||||
// emnapi 2.x still registers an unconditional once-listener for
|
||||
// beforeExit that auto-destroys the context, and suppressDestroy() only
|
||||
// neutralizes its callback without removing it. This loader owns cleanup
|
||||
@@ -859,7 +1057,7 @@ try {
|
||||
}
|
||||
})(),
|
||||
reuseWorker: true,
|
||||
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
plugins: [__captureWasiThreadManager, __emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
wasi: __wasi,
|
||||
onCreateWorker() {
|
||||
const worker = __createWasiWorker(__nodePath.join(__dirname, 'wasi-worker.mjs'))
|
||||
@@ -889,6 +1087,10 @@ try {
|
||||
}
|
||||
|
||||
worker.unref()
|
||||
// These stubs stay in place for the worker's whole life, disposal
|
||||
// included: `__keepEventLoopAliveUntil` is what holds the process open
|
||||
// while a termination is pending, precisely because a worker's own
|
||||
// references cannot be relied on for it.
|
||||
}
|
||||
return worker
|
||||
},
|
||||
@@ -911,6 +1113,22 @@ try {
|
||||
},
|
||||
}))
|
||||
__publishWasiDispose(__napiModule.exports)
|
||||
// The CommonJS tail below aliases `__napiModule.exports`; a named module
|
||||
// export does not travel with it, so carry the marker on the binding itself
|
||||
// too. Three things pin the stamp to exactly this spot:
|
||||
// - inside this `try`, because the guard throws on a
|
||||
// `#[napi(module_exports)]` hook that claimed the name, and only the
|
||||
// catch below tears the environment — context, workers, exit listener —
|
||||
// back down;
|
||||
// - after the async runtime host install, which hands this same object to
|
||||
// addon-provided registration functions that may put anything on it;
|
||||
// - assigning onto the loader's own `module.exports`, which is still the
|
||||
// original object here, so an addon accessor with a refusing setter is
|
||||
// never written through. `cjs-module-lexer` — Node's CJS -> ESM named
|
||||
// export detection — reads the static `module.exports.<name> =` either
|
||||
// way, and the later `module.exports = __napiModule.exports` does not
|
||||
// undo that.
|
||||
module.exports.__napiBindingTarget = __napiStampBindingTarget(__napiModule.exports, __napiBindingTarget)
|
||||
__registerWasiExitListener()
|
||||
} catch (error) {
|
||||
const rollback = {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/* auto-generated by NAPI-RS */
|
||||
/* eslint-disable */
|
||||
|
||||
/** The WASI flavor this loader instantiates. */
|
||||
export declare const __napiBindingTarget: 'wasm32-wasi'
|
||||
|
||||
export interface CodegenOptions {
|
||||
/**
|
||||
* Remove whitespace.
|
||||
|
||||
@@ -7,6 +7,10 @@ const require = createRequire(import.meta.url)
|
||||
|
||||
const { readFileSync } = require('fs')
|
||||
let nativeBinding = null
|
||||
// Which artifact actually loaded. The WASI fallback chain overwrites it with
|
||||
// the flavor it resolved; the late native retry below leaves it alone because
|
||||
// it only runs while no WASI candidate has been loaded.
|
||||
let __napiLoadedBindingTarget = 'native'
|
||||
const loadErrors = []
|
||||
|
||||
const isMusl = () => {
|
||||
@@ -65,7 +69,16 @@ const isMuslFromChildProcess = () => {
|
||||
function requireNative() {
|
||||
if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
|
||||
try {
|
||||
return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
const overrideBinding = require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
// The override may be a generated WASI loader, which already reports its
|
||||
// own flavor. Adopt it: `module.exports` aliases this object, so claiming
|
||||
// 'native' would both misreport the artifact and overwrite the loader's
|
||||
// marker through the alias.
|
||||
__napiLoadedBindingTarget =
|
||||
overrideBinding && typeof overrideBinding.__napiBindingTarget === 'string'
|
||||
? overrideBinding.__napiBindingTarget
|
||||
: 'native'
|
||||
return overrideBinding
|
||||
} catch (err) {
|
||||
loadErrors.push(err)
|
||||
}
|
||||
@@ -630,6 +643,7 @@ if (!nativeBinding || forceWasi) {
|
||||
if (!candidateFailed) {
|
||||
wasiBinding = require('./parser.wasi.cjs')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -656,6 +670,7 @@ if (!nativeBinding || forceWasi) {
|
||||
}
|
||||
wasiBinding = require('@oxc-parser/binding-wasm32-wasi')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -719,5 +734,6 @@ export { ImportNameKind }
|
||||
export { parse }
|
||||
export { parseSync }
|
||||
export { rawTransferSupported }
|
||||
export const __napiBindingTarget = __napiLoadedBindingTarget
|
||||
const { getBufferOffset, parseRaw, parseRawSync } = nativeBinding
|
||||
export { getBufferOffset, parseRaw, parseRawSync }
|
||||
Vendored
+8
@@ -15,6 +15,14 @@ export class Visitor {
|
||||
visit(program: Program): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Which binding artifact the generated loader actually loaded: `'native'` for
|
||||
* a native addon, otherwise the `platformArchABI` of the WASI flavor. Every
|
||||
* flavor napi-rs can build is listed, because `NAPI_RS_NATIVE_LIBRARY_PATH`
|
||||
* can point the loader at a WASI artifact this package does not build itself.
|
||||
*/
|
||||
export declare const __napiBindingTarget: 'native' | 'wasm32-wasi' | 'wasm32-wasip1'
|
||||
|
||||
export interface Comment {
|
||||
type: 'Line' | 'Block'
|
||||
value: string
|
||||
|
||||
@@ -8,6 +8,55 @@ import {
|
||||
import { createContext as __emnapiCreateContext } from '@emnapi/runtime'
|
||||
|
||||
|
||||
export const __napiBindingTarget = 'wasm32-wasi'
|
||||
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',
|
||||
@@ -42,10 +91,40 @@ 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
|
||||
let __emnapiWasmEnvCleanupRan = false
|
||||
let __emnapiWasmEnvCleanupDrained = false
|
||||
let __emnapiWasmEnvCleanupDrainPromise
|
||||
@@ -119,13 +198,58 @@ function __attachCleanupErrors(error, cleanupErrors) {
|
||||
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) {
|
||||
if (__emnapiWasmEnvCleanupPrepared || __emnapiWasmEnvCleanupPreparing) {
|
||||
return
|
||||
}
|
||||
const prepare = __napiInstance?.exports?.napi_prepare_wasm_env_cleanup
|
||||
if (typeof prepare === 'function') {
|
||||
prepare()
|
||||
// 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
|
||||
}
|
||||
__emnapiWasmEnvCleanupPrepared = true
|
||||
@@ -318,13 +442,80 @@ function __destroyEmnapiContext() {
|
||||
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
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* `@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)
|
||||
@@ -354,7 +545,9 @@ function __terminateWasiWorkers() {
|
||||
)
|
||||
}
|
||||
}
|
||||
return pending.length > 0 ? Promise.all(pending).then(finish) : finish()
|
||||
return pending.length > 0
|
||||
? __keepEventLoopAliveUntil(Promise.all(pending)).then(finish)
|
||||
: finish()
|
||||
}
|
||||
|
||||
function __finishWasiDisposal() {
|
||||
@@ -555,7 +748,11 @@ let __wasiModule
|
||||
let __napiModule
|
||||
|
||||
try {
|
||||
__emnapiContext = __emnapiCreateContext({ autoDestroy: false })
|
||||
__emnapiContext = __wrapEmnapiContextDestroyForSettlement(
|
||||
__emnapiCreateContext({ autoDestroy: false }),
|
||||
__prepareWasmEnvCleanup,
|
||||
__isPreparingWasmEnvCleanup,
|
||||
)
|
||||
__emnapiContext.suppressDestroy()
|
||||
|
||||
;({
|
||||
@@ -566,7 +763,7 @@ try {
|
||||
context: __emnapiContext,
|
||||
asyncWorkPoolSize: __asyncWorkPoolSize,
|
||||
reuseWorker: false,
|
||||
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
plugins: [__captureWasiThreadManager, __emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
wasi: __wasi,
|
||||
onCreateWorker() {
|
||||
const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), {
|
||||
@@ -596,6 +793,12 @@ try {
|
||||
},
|
||||
}))
|
||||
__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)
|
||||
|
||||
@@ -2,6 +2,56 @@
|
||||
/* eslint-disable */
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
const __napiBindingTarget = 'wasm32-wasi'
|
||||
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 __nodeFs = require('node:fs')
|
||||
const __nodePath = require('node:path')
|
||||
const { WASI: __nodeWASI } = require('node:wasi')
|
||||
@@ -149,10 +199,40 @@ 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
|
||||
let __emnapiWasmEnvCleanupRan = false
|
||||
let __emnapiWasmEnvCleanupDrained = false
|
||||
let __emnapiWasmEnvCleanupDrainPromise
|
||||
@@ -226,13 +306,58 @@ function __attachCleanupErrors(error, cleanupErrors) {
|
||||
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) {
|
||||
if (__emnapiWasmEnvCleanupPrepared || __emnapiWasmEnvCleanupPreparing) {
|
||||
return
|
||||
}
|
||||
const prepare = __napiInstance?.exports?.napi_prepare_wasm_env_cleanup
|
||||
if (typeof prepare === 'function') {
|
||||
prepare()
|
||||
// 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
|
||||
}
|
||||
__emnapiWasmEnvCleanupPrepared = true
|
||||
@@ -425,13 +550,80 @@ function __destroyEmnapiContext() {
|
||||
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
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* `@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)
|
||||
@@ -461,7 +653,9 @@ function __terminateWasiWorkers() {
|
||||
)
|
||||
}
|
||||
}
|
||||
return pending.length > 0 ? Promise.all(pending).then(finish) : finish()
|
||||
return pending.length > 0
|
||||
? __keepEventLoopAliveUntil(Promise.all(pending)).then(finish)
|
||||
: finish()
|
||||
}
|
||||
|
||||
function __finishWasiDisposal() {
|
||||
@@ -831,7 +1025,11 @@ function __captureEmnapiAutoDestroyListener() {
|
||||
try {
|
||||
const __finishAutoDestroyCapture = __captureEmnapiAutoDestroyListener()
|
||||
try {
|
||||
__emnapiContext = __emnapiCreateContext({ autoDestroy: false })
|
||||
__emnapiContext = __wrapEmnapiContextDestroyForSettlement(
|
||||
__emnapiCreateContext({ autoDestroy: false }),
|
||||
__prepareWasmEnvCleanup,
|
||||
__isPreparingWasmEnvCleanup,
|
||||
)
|
||||
// emnapi 2.x still registers an unconditional once-listener for
|
||||
// beforeExit that auto-destroys the context, and suppressDestroy() only
|
||||
// neutralizes its callback without removing it. This loader owns cleanup
|
||||
@@ -859,7 +1057,7 @@ try {
|
||||
}
|
||||
})(),
|
||||
reuseWorker: true,
|
||||
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
plugins: [__captureWasiThreadManager, __emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
wasi: __wasi,
|
||||
onCreateWorker() {
|
||||
const worker = __createWasiWorker(__nodePath.join(__dirname, 'wasi-worker.mjs'))
|
||||
@@ -889,6 +1087,10 @@ try {
|
||||
}
|
||||
|
||||
worker.unref()
|
||||
// These stubs stay in place for the worker's whole life, disposal
|
||||
// included: `__keepEventLoopAliveUntil` is what holds the process open
|
||||
// while a termination is pending, precisely because a worker's own
|
||||
// references cannot be relied on for it.
|
||||
}
|
||||
return worker
|
||||
},
|
||||
@@ -911,6 +1113,22 @@ try {
|
||||
},
|
||||
}))
|
||||
__publishWasiDispose(__napiModule.exports)
|
||||
// The CommonJS tail below aliases `__napiModule.exports`; a named module
|
||||
// export does not travel with it, so carry the marker on the binding itself
|
||||
// too. Three things pin the stamp to exactly this spot:
|
||||
// - inside this `try`, because the guard throws on a
|
||||
// `#[napi(module_exports)]` hook that claimed the name, and only the
|
||||
// catch below tears the environment — context, workers, exit listener —
|
||||
// back down;
|
||||
// - after the async runtime host install, which hands this same object to
|
||||
// addon-provided registration functions that may put anything on it;
|
||||
// - assigning onto the loader's own `module.exports`, which is still the
|
||||
// original object here, so an addon accessor with a refusing setter is
|
||||
// never written through. `cjs-module-lexer` — Node's CJS -> ESM named
|
||||
// export detection — reads the static `module.exports.<name> =` either
|
||||
// way, and the later `module.exports = __napiModule.exports` does not
|
||||
// undo that.
|
||||
module.exports.__napiBindingTarget = __napiStampBindingTarget(__napiModule.exports, __napiBindingTarget)
|
||||
__registerWasiExitListener()
|
||||
} catch (error) {
|
||||
const rollback = {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
|
||||
/** The WASI flavor this loader instantiates. */
|
||||
export declare const __napiBindingTarget: 'wasm32-wasi'
|
||||
|
||||
export interface Comment {
|
||||
type: 'Line' | 'Block'
|
||||
value: string
|
||||
|
||||
@@ -7,6 +7,10 @@ const require = createRequire(import.meta.url)
|
||||
|
||||
const { readFileSync } = require('fs')
|
||||
let nativeBinding = null
|
||||
// Which artifact actually loaded. The WASI fallback chain overwrites it with
|
||||
// the flavor it resolved; the late native retry below leaves it alone because
|
||||
// it only runs while no WASI candidate has been loaded.
|
||||
let __napiLoadedBindingTarget = 'native'
|
||||
const loadErrors = []
|
||||
|
||||
const isMusl = () => {
|
||||
@@ -65,7 +69,16 @@ const isMuslFromChildProcess = () => {
|
||||
function requireNative() {
|
||||
if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
|
||||
try {
|
||||
return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
const overrideBinding = require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
// The override may be a generated WASI loader, which already reports its
|
||||
// own flavor. Adopt it: `module.exports` aliases this object, so claiming
|
||||
// 'native' would both misreport the artifact and overwrite the loader's
|
||||
// marker through the alias.
|
||||
__napiLoadedBindingTarget =
|
||||
overrideBinding && typeof overrideBinding.__napiBindingTarget === 'string'
|
||||
? overrideBinding.__napiBindingTarget
|
||||
: 'native'
|
||||
return overrideBinding
|
||||
} catch (err) {
|
||||
loadErrors.push(err)
|
||||
}
|
||||
@@ -630,6 +643,7 @@ if (!nativeBinding || forceWasi) {
|
||||
if (!candidateFailed) {
|
||||
wasiBinding = require('./playground.wasi.cjs')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -656,6 +670,7 @@ if (!nativeBinding || forceWasi) {
|
||||
}
|
||||
wasiBinding = require('@oxc-playground/binding-wasm32-wasi')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -704,3 +719,4 @@ if (!nativeBinding) {
|
||||
const { Severity, Oxc } = nativeBinding
|
||||
export { Severity }
|
||||
export { Oxc }
|
||||
export const __napiBindingTarget = __napiLoadedBindingTarget
|
||||
@@ -8,6 +8,55 @@ import {
|
||||
import { createContext as __emnapiCreateContext } from '@emnapi/runtime'
|
||||
|
||||
|
||||
export const __napiBindingTarget = 'wasm32-wasi'
|
||||
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',
|
||||
@@ -42,10 +91,40 @@ 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
|
||||
let __emnapiWasmEnvCleanupRan = false
|
||||
let __emnapiWasmEnvCleanupDrained = false
|
||||
let __emnapiWasmEnvCleanupDrainPromise
|
||||
@@ -119,13 +198,58 @@ function __attachCleanupErrors(error, cleanupErrors) {
|
||||
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) {
|
||||
if (__emnapiWasmEnvCleanupPrepared || __emnapiWasmEnvCleanupPreparing) {
|
||||
return
|
||||
}
|
||||
const prepare = __napiInstance?.exports?.napi_prepare_wasm_env_cleanup
|
||||
if (typeof prepare === 'function') {
|
||||
prepare()
|
||||
// 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
|
||||
}
|
||||
__emnapiWasmEnvCleanupPrepared = true
|
||||
@@ -318,13 +442,80 @@ function __destroyEmnapiContext() {
|
||||
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
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* `@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)
|
||||
@@ -354,7 +545,9 @@ function __terminateWasiWorkers() {
|
||||
)
|
||||
}
|
||||
}
|
||||
return pending.length > 0 ? Promise.all(pending).then(finish) : finish()
|
||||
return pending.length > 0
|
||||
? __keepEventLoopAliveUntil(Promise.all(pending)).then(finish)
|
||||
: finish()
|
||||
}
|
||||
|
||||
function __finishWasiDisposal() {
|
||||
@@ -555,7 +748,11 @@ let __wasiModule
|
||||
let __napiModule
|
||||
|
||||
try {
|
||||
__emnapiContext = __emnapiCreateContext({ autoDestroy: false })
|
||||
__emnapiContext = __wrapEmnapiContextDestroyForSettlement(
|
||||
__emnapiCreateContext({ autoDestroy: false }),
|
||||
__prepareWasmEnvCleanup,
|
||||
__isPreparingWasmEnvCleanup,
|
||||
)
|
||||
__emnapiContext.suppressDestroy()
|
||||
|
||||
;({
|
||||
@@ -566,7 +763,7 @@ try {
|
||||
context: __emnapiContext,
|
||||
asyncWorkPoolSize: __asyncWorkPoolSize,
|
||||
reuseWorker: false,
|
||||
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
plugins: [__captureWasiThreadManager, __emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
wasi: __wasi,
|
||||
onCreateWorker() {
|
||||
const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), {
|
||||
@@ -596,6 +793,12 @@ try {
|
||||
},
|
||||
}))
|
||||
__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)
|
||||
|
||||
@@ -2,6 +2,56 @@
|
||||
/* eslint-disable */
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
const __napiBindingTarget = 'wasm32-wasi'
|
||||
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 __nodeFs = require('node:fs')
|
||||
const __nodePath = require('node:path')
|
||||
const { WASI: __nodeWASI } = require('node:wasi')
|
||||
@@ -149,10 +199,40 @@ 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
|
||||
let __emnapiWasmEnvCleanupRan = false
|
||||
let __emnapiWasmEnvCleanupDrained = false
|
||||
let __emnapiWasmEnvCleanupDrainPromise
|
||||
@@ -226,13 +306,58 @@ function __attachCleanupErrors(error, cleanupErrors) {
|
||||
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) {
|
||||
if (__emnapiWasmEnvCleanupPrepared || __emnapiWasmEnvCleanupPreparing) {
|
||||
return
|
||||
}
|
||||
const prepare = __napiInstance?.exports?.napi_prepare_wasm_env_cleanup
|
||||
if (typeof prepare === 'function') {
|
||||
prepare()
|
||||
// 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
|
||||
}
|
||||
__emnapiWasmEnvCleanupPrepared = true
|
||||
@@ -425,13 +550,80 @@ function __destroyEmnapiContext() {
|
||||
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
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* `@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)
|
||||
@@ -461,7 +653,9 @@ function __terminateWasiWorkers() {
|
||||
)
|
||||
}
|
||||
}
|
||||
return pending.length > 0 ? Promise.all(pending).then(finish) : finish()
|
||||
return pending.length > 0
|
||||
? __keepEventLoopAliveUntil(Promise.all(pending)).then(finish)
|
||||
: finish()
|
||||
}
|
||||
|
||||
function __finishWasiDisposal() {
|
||||
@@ -831,7 +1025,11 @@ function __captureEmnapiAutoDestroyListener() {
|
||||
try {
|
||||
const __finishAutoDestroyCapture = __captureEmnapiAutoDestroyListener()
|
||||
try {
|
||||
__emnapiContext = __emnapiCreateContext({ autoDestroy: false })
|
||||
__emnapiContext = __wrapEmnapiContextDestroyForSettlement(
|
||||
__emnapiCreateContext({ autoDestroy: false }),
|
||||
__prepareWasmEnvCleanup,
|
||||
__isPreparingWasmEnvCleanup,
|
||||
)
|
||||
// emnapi 2.x still registers an unconditional once-listener for
|
||||
// beforeExit that auto-destroys the context, and suppressDestroy() only
|
||||
// neutralizes its callback without removing it. This loader owns cleanup
|
||||
@@ -859,7 +1057,7 @@ try {
|
||||
}
|
||||
})(),
|
||||
reuseWorker: true,
|
||||
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
plugins: [__captureWasiThreadManager, __emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
wasi: __wasi,
|
||||
onCreateWorker() {
|
||||
const worker = __createWasiWorker(__nodePath.join(__dirname, 'wasi-worker.mjs'))
|
||||
@@ -889,6 +1087,10 @@ try {
|
||||
}
|
||||
|
||||
worker.unref()
|
||||
// These stubs stay in place for the worker's whole life, disposal
|
||||
// included: `__keepEventLoopAliveUntil` is what holds the process open
|
||||
// while a termination is pending, precisely because a worker's own
|
||||
// references cannot be relied on for it.
|
||||
}
|
||||
return worker
|
||||
},
|
||||
@@ -911,6 +1113,22 @@ try {
|
||||
},
|
||||
}))
|
||||
__publishWasiDispose(__napiModule.exports)
|
||||
// The CommonJS tail below aliases `__napiModule.exports`; a named module
|
||||
// export does not travel with it, so carry the marker on the binding itself
|
||||
// too. Three things pin the stamp to exactly this spot:
|
||||
// - inside this `try`, because the guard throws on a
|
||||
// `#[napi(module_exports)]` hook that claimed the name, and only the
|
||||
// catch below tears the environment — context, workers, exit listener —
|
||||
// back down;
|
||||
// - after the async runtime host install, which hands this same object to
|
||||
// addon-provided registration functions that may put anything on it;
|
||||
// - assigning onto the loader's own `module.exports`, which is still the
|
||||
// original object here, so an addon accessor with a refusing setter is
|
||||
// never written through. `cjs-module-lexer` — Node's CJS -> ESM named
|
||||
// export detection — reads the static `module.exports.<name> =` either
|
||||
// way, and the later `module.exports = __napiModule.exports` does not
|
||||
// undo that.
|
||||
module.exports.__napiBindingTarget = __napiStampBindingTarget(__napiModule.exports, __napiBindingTarget)
|
||||
__registerWasiExitListener()
|
||||
} catch (error) {
|
||||
const rollback = {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/* auto-generated by NAPI-RS */
|
||||
/* eslint-disable */
|
||||
|
||||
/** The WASI flavor this loader instantiates. */
|
||||
export declare const __napiBindingTarget: 'wasm32-wasi'
|
||||
|
||||
export interface Comment {
|
||||
type: 'Line' | 'Block'
|
||||
value: string
|
||||
|
||||
Vendored
+8
@@ -1,6 +1,14 @@
|
||||
/* auto-generated by NAPI-RS */
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* Which binding artifact the generated loader actually loaded: `'native'` for
|
||||
* a native addon, otherwise the `platformArchABI` of the WASI flavor. Every
|
||||
* flavor napi-rs can build is listed, because `NAPI_RS_NATIVE_LIBRARY_PATH`
|
||||
* can point the loader at a WASI artifact this package does not build itself.
|
||||
*/
|
||||
export declare const __napiBindingTarget: 'native' | 'wasm32-wasi' | 'wasm32-wasip1'
|
||||
|
||||
export interface Comment {
|
||||
type: 'Line' | 'Block'
|
||||
value: string
|
||||
|
||||
@@ -7,6 +7,10 @@ const require = createRequire(import.meta.url)
|
||||
|
||||
const { readFileSync } = require('fs')
|
||||
let nativeBinding = null
|
||||
// Which artifact actually loaded. The WASI fallback chain overwrites it with
|
||||
// the flavor it resolved; the late native retry below leaves it alone because
|
||||
// it only runs while no WASI candidate has been loaded.
|
||||
let __napiLoadedBindingTarget = 'native'
|
||||
const loadErrors = []
|
||||
|
||||
const isMusl = () => {
|
||||
@@ -65,7 +69,16 @@ const isMuslFromChildProcess = () => {
|
||||
function requireNative() {
|
||||
if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
|
||||
try {
|
||||
return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
const overrideBinding = require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
// The override may be a generated WASI loader, which already reports its
|
||||
// own flavor. Adopt it: `module.exports` aliases this object, so claiming
|
||||
// 'native' would both misreport the artifact and overwrite the loader's
|
||||
// marker through the alias.
|
||||
__napiLoadedBindingTarget =
|
||||
overrideBinding && typeof overrideBinding.__napiBindingTarget === 'string'
|
||||
? overrideBinding.__napiBindingTarget
|
||||
: 'native'
|
||||
return overrideBinding
|
||||
} catch (err) {
|
||||
loadErrors.push(err)
|
||||
}
|
||||
@@ -630,6 +643,7 @@ if (!nativeBinding || forceWasi) {
|
||||
if (!candidateFailed) {
|
||||
wasiBinding = require('./transform-react.wasi.cjs')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -656,6 +670,7 @@ if (!nativeBinding || forceWasi) {
|
||||
}
|
||||
wasiBinding = require('@oxc-transform-react/binding-wasm32-wasi')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -713,3 +728,4 @@ const { Severity, transform, transformSync } = nativeBinding
|
||||
export { Severity }
|
||||
export { transform }
|
||||
export { transformSync }
|
||||
export const __napiBindingTarget = __napiLoadedBindingTarget
|
||||
@@ -8,6 +8,55 @@ import {
|
||||
import { createContext as __emnapiCreateContext } from '@emnapi/runtime'
|
||||
|
||||
|
||||
export const __napiBindingTarget = 'wasm32-wasi'
|
||||
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',
|
||||
@@ -42,10 +91,40 @@ 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
|
||||
let __emnapiWasmEnvCleanupRan = false
|
||||
let __emnapiWasmEnvCleanupDrained = false
|
||||
let __emnapiWasmEnvCleanupDrainPromise
|
||||
@@ -119,13 +198,58 @@ function __attachCleanupErrors(error, cleanupErrors) {
|
||||
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) {
|
||||
if (__emnapiWasmEnvCleanupPrepared || __emnapiWasmEnvCleanupPreparing) {
|
||||
return
|
||||
}
|
||||
const prepare = __napiInstance?.exports?.napi_prepare_wasm_env_cleanup
|
||||
if (typeof prepare === 'function') {
|
||||
prepare()
|
||||
// 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
|
||||
}
|
||||
__emnapiWasmEnvCleanupPrepared = true
|
||||
@@ -318,13 +442,80 @@ function __destroyEmnapiContext() {
|
||||
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
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* `@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)
|
||||
@@ -354,7 +545,9 @@ function __terminateWasiWorkers() {
|
||||
)
|
||||
}
|
||||
}
|
||||
return pending.length > 0 ? Promise.all(pending).then(finish) : finish()
|
||||
return pending.length > 0
|
||||
? __keepEventLoopAliveUntil(Promise.all(pending)).then(finish)
|
||||
: finish()
|
||||
}
|
||||
|
||||
function __finishWasiDisposal() {
|
||||
@@ -555,7 +748,11 @@ let __wasiModule
|
||||
let __napiModule
|
||||
|
||||
try {
|
||||
__emnapiContext = __emnapiCreateContext({ autoDestroy: false })
|
||||
__emnapiContext = __wrapEmnapiContextDestroyForSettlement(
|
||||
__emnapiCreateContext({ autoDestroy: false }),
|
||||
__prepareWasmEnvCleanup,
|
||||
__isPreparingWasmEnvCleanup,
|
||||
)
|
||||
__emnapiContext.suppressDestroy()
|
||||
|
||||
;({
|
||||
@@ -566,7 +763,7 @@ try {
|
||||
context: __emnapiContext,
|
||||
asyncWorkPoolSize: __asyncWorkPoolSize,
|
||||
reuseWorker: false,
|
||||
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
plugins: [__captureWasiThreadManager, __emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
wasi: __wasi,
|
||||
onCreateWorker() {
|
||||
const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), {
|
||||
@@ -596,6 +793,12 @@ try {
|
||||
},
|
||||
}))
|
||||
__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)
|
||||
|
||||
@@ -2,6 +2,56 @@
|
||||
/* eslint-disable */
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
const __napiBindingTarget = 'wasm32-wasi'
|
||||
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 __nodeFs = require('node:fs')
|
||||
const __nodePath = require('node:path')
|
||||
const { WASI: __nodeWASI } = require('node:wasi')
|
||||
@@ -149,10 +199,40 @@ 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
|
||||
let __emnapiWasmEnvCleanupRan = false
|
||||
let __emnapiWasmEnvCleanupDrained = false
|
||||
let __emnapiWasmEnvCleanupDrainPromise
|
||||
@@ -226,13 +306,58 @@ function __attachCleanupErrors(error, cleanupErrors) {
|
||||
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) {
|
||||
if (__emnapiWasmEnvCleanupPrepared || __emnapiWasmEnvCleanupPreparing) {
|
||||
return
|
||||
}
|
||||
const prepare = __napiInstance?.exports?.napi_prepare_wasm_env_cleanup
|
||||
if (typeof prepare === 'function') {
|
||||
prepare()
|
||||
// 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
|
||||
}
|
||||
__emnapiWasmEnvCleanupPrepared = true
|
||||
@@ -425,13 +550,80 @@ function __destroyEmnapiContext() {
|
||||
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
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* `@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)
|
||||
@@ -461,7 +653,9 @@ function __terminateWasiWorkers() {
|
||||
)
|
||||
}
|
||||
}
|
||||
return pending.length > 0 ? Promise.all(pending).then(finish) : finish()
|
||||
return pending.length > 0
|
||||
? __keepEventLoopAliveUntil(Promise.all(pending)).then(finish)
|
||||
: finish()
|
||||
}
|
||||
|
||||
function __finishWasiDisposal() {
|
||||
@@ -831,7 +1025,11 @@ function __captureEmnapiAutoDestroyListener() {
|
||||
try {
|
||||
const __finishAutoDestroyCapture = __captureEmnapiAutoDestroyListener()
|
||||
try {
|
||||
__emnapiContext = __emnapiCreateContext({ autoDestroy: false })
|
||||
__emnapiContext = __wrapEmnapiContextDestroyForSettlement(
|
||||
__emnapiCreateContext({ autoDestroy: false }),
|
||||
__prepareWasmEnvCleanup,
|
||||
__isPreparingWasmEnvCleanup,
|
||||
)
|
||||
// emnapi 2.x still registers an unconditional once-listener for
|
||||
// beforeExit that auto-destroys the context, and suppressDestroy() only
|
||||
// neutralizes its callback without removing it. This loader owns cleanup
|
||||
@@ -859,7 +1057,7 @@ try {
|
||||
}
|
||||
})(),
|
||||
reuseWorker: true,
|
||||
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
plugins: [__captureWasiThreadManager, __emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
wasi: __wasi,
|
||||
onCreateWorker() {
|
||||
const worker = __createWasiWorker(__nodePath.join(__dirname, 'wasi-worker.mjs'))
|
||||
@@ -889,6 +1087,10 @@ try {
|
||||
}
|
||||
|
||||
worker.unref()
|
||||
// These stubs stay in place for the worker's whole life, disposal
|
||||
// included: `__keepEventLoopAliveUntil` is what holds the process open
|
||||
// while a termination is pending, precisely because a worker's own
|
||||
// references cannot be relied on for it.
|
||||
}
|
||||
return worker
|
||||
},
|
||||
@@ -911,6 +1113,22 @@ try {
|
||||
},
|
||||
}))
|
||||
__publishWasiDispose(__napiModule.exports)
|
||||
// The CommonJS tail below aliases `__napiModule.exports`; a named module
|
||||
// export does not travel with it, so carry the marker on the binding itself
|
||||
// too. Three things pin the stamp to exactly this spot:
|
||||
// - inside this `try`, because the guard throws on a
|
||||
// `#[napi(module_exports)]` hook that claimed the name, and only the
|
||||
// catch below tears the environment — context, workers, exit listener —
|
||||
// back down;
|
||||
// - after the async runtime host install, which hands this same object to
|
||||
// addon-provided registration functions that may put anything on it;
|
||||
// - assigning onto the loader's own `module.exports`, which is still the
|
||||
// original object here, so an addon accessor with a refusing setter is
|
||||
// never written through. `cjs-module-lexer` — Node's CJS -> ESM named
|
||||
// export detection — reads the static `module.exports.<name> =` either
|
||||
// way, and the later `module.exports = __napiModule.exports` does not
|
||||
// undo that.
|
||||
module.exports.__napiBindingTarget = __napiStampBindingTarget(__napiModule.exports, __napiBindingTarget)
|
||||
__registerWasiExitListener()
|
||||
} catch (error) {
|
||||
const rollback = {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/* auto-generated by NAPI-RS */
|
||||
/* eslint-disable */
|
||||
|
||||
/** The WASI flavor this loader instantiates. */
|
||||
export declare const __napiBindingTarget: 'wasm32-wasi'
|
||||
|
||||
export interface Comment {
|
||||
type: 'Line' | 'Block'
|
||||
value: string
|
||||
|
||||
Vendored
+8
@@ -1,6 +1,14 @@
|
||||
/* auto-generated by NAPI-RS */
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* Which binding artifact the generated loader actually loaded: `'native'` for
|
||||
* a native addon, otherwise the `platformArchABI` of the WASI flavor. Every
|
||||
* flavor napi-rs can build is listed, because `NAPI_RS_NATIVE_LIBRARY_PATH`
|
||||
* can point the loader at a WASI artifact this package does not build itself.
|
||||
*/
|
||||
export declare const __napiBindingTarget: 'native' | 'wasm32-wasi' | 'wasm32-wasip1'
|
||||
|
||||
export interface Comment {
|
||||
type: 'Line' | 'Block'
|
||||
value: string
|
||||
|
||||
@@ -7,6 +7,10 @@ const require = createRequire(import.meta.url)
|
||||
|
||||
const { readFileSync } = require('fs')
|
||||
let nativeBinding = null
|
||||
// Which artifact actually loaded. The WASI fallback chain overwrites it with
|
||||
// the flavor it resolved; the late native retry below leaves it alone because
|
||||
// it only runs while no WASI candidate has been loaded.
|
||||
let __napiLoadedBindingTarget = 'native'
|
||||
const loadErrors = []
|
||||
|
||||
const isMusl = () => {
|
||||
@@ -65,7 +69,16 @@ const isMuslFromChildProcess = () => {
|
||||
function requireNative() {
|
||||
if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
|
||||
try {
|
||||
return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
const overrideBinding = require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
// The override may be a generated WASI loader, which already reports its
|
||||
// own flavor. Adopt it: `module.exports` aliases this object, so claiming
|
||||
// 'native' would both misreport the artifact and overwrite the loader's
|
||||
// marker through the alias.
|
||||
__napiLoadedBindingTarget =
|
||||
overrideBinding && typeof overrideBinding.__napiBindingTarget === 'string'
|
||||
? overrideBinding.__napiBindingTarget
|
||||
: 'native'
|
||||
return overrideBinding
|
||||
} catch (err) {
|
||||
loadErrors.push(err)
|
||||
}
|
||||
@@ -630,6 +643,7 @@ if (!nativeBinding || forceWasi) {
|
||||
if (!candidateFailed) {
|
||||
wasiBinding = require('./transform-relay.wasi.cjs')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -656,6 +670,7 @@ if (!nativeBinding || forceWasi) {
|
||||
}
|
||||
wasiBinding = require('@oxc-transform-relay/binding-wasm32-wasi')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -713,3 +728,4 @@ const { Severity, transform, transformSync } = nativeBinding
|
||||
export { Severity }
|
||||
export { transform }
|
||||
export { transformSync }
|
||||
export const __napiBindingTarget = __napiLoadedBindingTarget
|
||||
@@ -8,6 +8,55 @@ import {
|
||||
import { createContext as __emnapiCreateContext } from '@emnapi/runtime'
|
||||
|
||||
|
||||
export const __napiBindingTarget = 'wasm32-wasi'
|
||||
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',
|
||||
@@ -42,10 +91,40 @@ 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
|
||||
let __emnapiWasmEnvCleanupRan = false
|
||||
let __emnapiWasmEnvCleanupDrained = false
|
||||
let __emnapiWasmEnvCleanupDrainPromise
|
||||
@@ -119,13 +198,58 @@ function __attachCleanupErrors(error, cleanupErrors) {
|
||||
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) {
|
||||
if (__emnapiWasmEnvCleanupPrepared || __emnapiWasmEnvCleanupPreparing) {
|
||||
return
|
||||
}
|
||||
const prepare = __napiInstance?.exports?.napi_prepare_wasm_env_cleanup
|
||||
if (typeof prepare === 'function') {
|
||||
prepare()
|
||||
// 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
|
||||
}
|
||||
__emnapiWasmEnvCleanupPrepared = true
|
||||
@@ -318,13 +442,80 @@ function __destroyEmnapiContext() {
|
||||
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
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* `@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)
|
||||
@@ -354,7 +545,9 @@ function __terminateWasiWorkers() {
|
||||
)
|
||||
}
|
||||
}
|
||||
return pending.length > 0 ? Promise.all(pending).then(finish) : finish()
|
||||
return pending.length > 0
|
||||
? __keepEventLoopAliveUntil(Promise.all(pending)).then(finish)
|
||||
: finish()
|
||||
}
|
||||
|
||||
function __finishWasiDisposal() {
|
||||
@@ -555,7 +748,11 @@ let __wasiModule
|
||||
let __napiModule
|
||||
|
||||
try {
|
||||
__emnapiContext = __emnapiCreateContext({ autoDestroy: false })
|
||||
__emnapiContext = __wrapEmnapiContextDestroyForSettlement(
|
||||
__emnapiCreateContext({ autoDestroy: false }),
|
||||
__prepareWasmEnvCleanup,
|
||||
__isPreparingWasmEnvCleanup,
|
||||
)
|
||||
__emnapiContext.suppressDestroy()
|
||||
|
||||
;({
|
||||
@@ -566,7 +763,7 @@ try {
|
||||
context: __emnapiContext,
|
||||
asyncWorkPoolSize: __asyncWorkPoolSize,
|
||||
reuseWorker: false,
|
||||
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
plugins: [__captureWasiThreadManager, __emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
wasi: __wasi,
|
||||
onCreateWorker() {
|
||||
const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), {
|
||||
@@ -596,6 +793,12 @@ try {
|
||||
},
|
||||
}))
|
||||
__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)
|
||||
|
||||
@@ -2,6 +2,56 @@
|
||||
/* eslint-disable */
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
const __napiBindingTarget = 'wasm32-wasi'
|
||||
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 __nodeFs = require('node:fs')
|
||||
const __nodePath = require('node:path')
|
||||
const { WASI: __nodeWASI } = require('node:wasi')
|
||||
@@ -149,10 +199,40 @@ 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
|
||||
let __emnapiWasmEnvCleanupRan = false
|
||||
let __emnapiWasmEnvCleanupDrained = false
|
||||
let __emnapiWasmEnvCleanupDrainPromise
|
||||
@@ -226,13 +306,58 @@ function __attachCleanupErrors(error, cleanupErrors) {
|
||||
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) {
|
||||
if (__emnapiWasmEnvCleanupPrepared || __emnapiWasmEnvCleanupPreparing) {
|
||||
return
|
||||
}
|
||||
const prepare = __napiInstance?.exports?.napi_prepare_wasm_env_cleanup
|
||||
if (typeof prepare === 'function') {
|
||||
prepare()
|
||||
// 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
|
||||
}
|
||||
__emnapiWasmEnvCleanupPrepared = true
|
||||
@@ -425,13 +550,80 @@ function __destroyEmnapiContext() {
|
||||
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
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* `@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)
|
||||
@@ -461,7 +653,9 @@ function __terminateWasiWorkers() {
|
||||
)
|
||||
}
|
||||
}
|
||||
return pending.length > 0 ? Promise.all(pending).then(finish) : finish()
|
||||
return pending.length > 0
|
||||
? __keepEventLoopAliveUntil(Promise.all(pending)).then(finish)
|
||||
: finish()
|
||||
}
|
||||
|
||||
function __finishWasiDisposal() {
|
||||
@@ -831,7 +1025,11 @@ function __captureEmnapiAutoDestroyListener() {
|
||||
try {
|
||||
const __finishAutoDestroyCapture = __captureEmnapiAutoDestroyListener()
|
||||
try {
|
||||
__emnapiContext = __emnapiCreateContext({ autoDestroy: false })
|
||||
__emnapiContext = __wrapEmnapiContextDestroyForSettlement(
|
||||
__emnapiCreateContext({ autoDestroy: false }),
|
||||
__prepareWasmEnvCleanup,
|
||||
__isPreparingWasmEnvCleanup,
|
||||
)
|
||||
// emnapi 2.x still registers an unconditional once-listener for
|
||||
// beforeExit that auto-destroys the context, and suppressDestroy() only
|
||||
// neutralizes its callback without removing it. This loader owns cleanup
|
||||
@@ -859,7 +1057,7 @@ try {
|
||||
}
|
||||
})(),
|
||||
reuseWorker: true,
|
||||
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
plugins: [__captureWasiThreadManager, __emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
wasi: __wasi,
|
||||
onCreateWorker() {
|
||||
const worker = __createWasiWorker(__nodePath.join(__dirname, 'wasi-worker.mjs'))
|
||||
@@ -889,6 +1087,10 @@ try {
|
||||
}
|
||||
|
||||
worker.unref()
|
||||
// These stubs stay in place for the worker's whole life, disposal
|
||||
// included: `__keepEventLoopAliveUntil` is what holds the process open
|
||||
// while a termination is pending, precisely because a worker's own
|
||||
// references cannot be relied on for it.
|
||||
}
|
||||
return worker
|
||||
},
|
||||
@@ -911,6 +1113,22 @@ try {
|
||||
},
|
||||
}))
|
||||
__publishWasiDispose(__napiModule.exports)
|
||||
// The CommonJS tail below aliases `__napiModule.exports`; a named module
|
||||
// export does not travel with it, so carry the marker on the binding itself
|
||||
// too. Three things pin the stamp to exactly this spot:
|
||||
// - inside this `try`, because the guard throws on a
|
||||
// `#[napi(module_exports)]` hook that claimed the name, and only the
|
||||
// catch below tears the environment — context, workers, exit listener —
|
||||
// back down;
|
||||
// - after the async runtime host install, which hands this same object to
|
||||
// addon-provided registration functions that may put anything on it;
|
||||
// - assigning onto the loader's own `module.exports`, which is still the
|
||||
// original object here, so an addon accessor with a refusing setter is
|
||||
// never written through. `cjs-module-lexer` — Node's CJS -> ESM named
|
||||
// export detection — reads the static `module.exports.<name> =` either
|
||||
// way, and the later `module.exports = __napiModule.exports` does not
|
||||
// undo that.
|
||||
module.exports.__napiBindingTarget = __napiStampBindingTarget(__napiModule.exports, __napiBindingTarget)
|
||||
__registerWasiExitListener()
|
||||
} catch (error) {
|
||||
const rollback = {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/* auto-generated by NAPI-RS */
|
||||
/* eslint-disable */
|
||||
|
||||
/** The WASI flavor this loader instantiates. */
|
||||
export declare const __napiBindingTarget: 'wasm32-wasi'
|
||||
|
||||
export interface Comment {
|
||||
type: 'Line' | 'Block'
|
||||
value: string
|
||||
|
||||
Vendored
+8
@@ -1,6 +1,14 @@
|
||||
/* auto-generated by NAPI-RS */
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* Which binding artifact the generated loader actually loaded: `'native'` for
|
||||
* a native addon, otherwise the `platformArchABI` of the WASI flavor. Every
|
||||
* flavor napi-rs can build is listed, because `NAPI_RS_NATIVE_LIBRARY_PATH`
|
||||
* can point the loader at a WASI artifact this package does not build itself.
|
||||
*/
|
||||
export declare const __napiBindingTarget: 'native' | 'wasm32-wasi' | 'wasm32-wasip1'
|
||||
|
||||
export interface Comment {
|
||||
type: 'Line' | 'Block'
|
||||
value: string
|
||||
|
||||
+17
-1
@@ -7,6 +7,10 @@ const require = createRequire(import.meta.url)
|
||||
|
||||
const { readFileSync } = require('fs')
|
||||
let nativeBinding = null
|
||||
// Which artifact actually loaded. The WASI fallback chain overwrites it with
|
||||
// the flavor it resolved; the late native retry below leaves it alone because
|
||||
// it only runs while no WASI candidate has been loaded.
|
||||
let __napiLoadedBindingTarget = 'native'
|
||||
const loadErrors = []
|
||||
|
||||
const isMusl = () => {
|
||||
@@ -65,7 +69,16 @@ const isMuslFromChildProcess = () => {
|
||||
function requireNative() {
|
||||
if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
|
||||
try {
|
||||
return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
const overrideBinding = require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
// The override may be a generated WASI loader, which already reports its
|
||||
// own flavor. Adopt it: `module.exports` aliases this object, so claiming
|
||||
// 'native' would both misreport the artifact and overwrite the loader's
|
||||
// marker through the alias.
|
||||
__napiLoadedBindingTarget =
|
||||
overrideBinding && typeof overrideBinding.__napiBindingTarget === 'string'
|
||||
? overrideBinding.__napiBindingTarget
|
||||
: 'native'
|
||||
return overrideBinding
|
||||
} catch (err) {
|
||||
loadErrors.push(err)
|
||||
}
|
||||
@@ -630,6 +643,7 @@ if (!nativeBinding || forceWasi) {
|
||||
if (!candidateFailed) {
|
||||
wasiBinding = require('./transform.wasi.cjs')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -656,6 +670,7 @@ if (!nativeBinding || forceWasi) {
|
||||
}
|
||||
wasiBinding = require('@oxc-transform/binding-wasm32-wasi')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -718,3 +733,4 @@ export { moduleRunnerTransform }
|
||||
export { moduleRunnerTransformSync }
|
||||
export { transform }
|
||||
export { transformSync }
|
||||
export const __napiBindingTarget = __napiLoadedBindingTarget
|
||||
@@ -8,6 +8,55 @@ import {
|
||||
import { createContext as __emnapiCreateContext } from '@emnapi/runtime'
|
||||
|
||||
|
||||
export const __napiBindingTarget = 'wasm32-wasi'
|
||||
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',
|
||||
@@ -42,10 +91,40 @@ 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
|
||||
let __emnapiWasmEnvCleanupRan = false
|
||||
let __emnapiWasmEnvCleanupDrained = false
|
||||
let __emnapiWasmEnvCleanupDrainPromise
|
||||
@@ -119,13 +198,58 @@ function __attachCleanupErrors(error, cleanupErrors) {
|
||||
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) {
|
||||
if (__emnapiWasmEnvCleanupPrepared || __emnapiWasmEnvCleanupPreparing) {
|
||||
return
|
||||
}
|
||||
const prepare = __napiInstance?.exports?.napi_prepare_wasm_env_cleanup
|
||||
if (typeof prepare === 'function') {
|
||||
prepare()
|
||||
// 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
|
||||
}
|
||||
__emnapiWasmEnvCleanupPrepared = true
|
||||
@@ -318,13 +442,80 @@ function __destroyEmnapiContext() {
|
||||
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
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* `@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)
|
||||
@@ -354,7 +545,9 @@ function __terminateWasiWorkers() {
|
||||
)
|
||||
}
|
||||
}
|
||||
return pending.length > 0 ? Promise.all(pending).then(finish) : finish()
|
||||
return pending.length > 0
|
||||
? __keepEventLoopAliveUntil(Promise.all(pending)).then(finish)
|
||||
: finish()
|
||||
}
|
||||
|
||||
function __finishWasiDisposal() {
|
||||
@@ -555,7 +748,11 @@ let __wasiModule
|
||||
let __napiModule
|
||||
|
||||
try {
|
||||
__emnapiContext = __emnapiCreateContext({ autoDestroy: false })
|
||||
__emnapiContext = __wrapEmnapiContextDestroyForSettlement(
|
||||
__emnapiCreateContext({ autoDestroy: false }),
|
||||
__prepareWasmEnvCleanup,
|
||||
__isPreparingWasmEnvCleanup,
|
||||
)
|
||||
__emnapiContext.suppressDestroy()
|
||||
|
||||
;({
|
||||
@@ -566,7 +763,7 @@ try {
|
||||
context: __emnapiContext,
|
||||
asyncWorkPoolSize: __asyncWorkPoolSize,
|
||||
reuseWorker: false,
|
||||
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
plugins: [__captureWasiThreadManager, __emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
wasi: __wasi,
|
||||
onCreateWorker() {
|
||||
const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), {
|
||||
@@ -596,6 +793,12 @@ try {
|
||||
},
|
||||
}))
|
||||
__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)
|
||||
|
||||
@@ -2,6 +2,56 @@
|
||||
/* eslint-disable */
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
const __napiBindingTarget = 'wasm32-wasi'
|
||||
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 __nodeFs = require('node:fs')
|
||||
const __nodePath = require('node:path')
|
||||
const { WASI: __nodeWASI } = require('node:wasi')
|
||||
@@ -149,10 +199,40 @@ 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
|
||||
let __emnapiWasmEnvCleanupRan = false
|
||||
let __emnapiWasmEnvCleanupDrained = false
|
||||
let __emnapiWasmEnvCleanupDrainPromise
|
||||
@@ -226,13 +306,58 @@ function __attachCleanupErrors(error, cleanupErrors) {
|
||||
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) {
|
||||
if (__emnapiWasmEnvCleanupPrepared || __emnapiWasmEnvCleanupPreparing) {
|
||||
return
|
||||
}
|
||||
const prepare = __napiInstance?.exports?.napi_prepare_wasm_env_cleanup
|
||||
if (typeof prepare === 'function') {
|
||||
prepare()
|
||||
// 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
|
||||
}
|
||||
__emnapiWasmEnvCleanupPrepared = true
|
||||
@@ -425,13 +550,80 @@ function __destroyEmnapiContext() {
|
||||
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
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* `@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)
|
||||
@@ -461,7 +653,9 @@ function __terminateWasiWorkers() {
|
||||
)
|
||||
}
|
||||
}
|
||||
return pending.length > 0 ? Promise.all(pending).then(finish) : finish()
|
||||
return pending.length > 0
|
||||
? __keepEventLoopAliveUntil(Promise.all(pending)).then(finish)
|
||||
: finish()
|
||||
}
|
||||
|
||||
function __finishWasiDisposal() {
|
||||
@@ -831,7 +1025,11 @@ function __captureEmnapiAutoDestroyListener() {
|
||||
try {
|
||||
const __finishAutoDestroyCapture = __captureEmnapiAutoDestroyListener()
|
||||
try {
|
||||
__emnapiContext = __emnapiCreateContext({ autoDestroy: false })
|
||||
__emnapiContext = __wrapEmnapiContextDestroyForSettlement(
|
||||
__emnapiCreateContext({ autoDestroy: false }),
|
||||
__prepareWasmEnvCleanup,
|
||||
__isPreparingWasmEnvCleanup,
|
||||
)
|
||||
// emnapi 2.x still registers an unconditional once-listener for
|
||||
// beforeExit that auto-destroys the context, and suppressDestroy() only
|
||||
// neutralizes its callback without removing it. This loader owns cleanup
|
||||
@@ -859,7 +1057,7 @@ try {
|
||||
}
|
||||
})(),
|
||||
reuseWorker: true,
|
||||
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
plugins: [__captureWasiThreadManager, __emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
|
||||
wasi: __wasi,
|
||||
onCreateWorker() {
|
||||
const worker = __createWasiWorker(__nodePath.join(__dirname, 'wasi-worker.mjs'))
|
||||
@@ -889,6 +1087,10 @@ try {
|
||||
}
|
||||
|
||||
worker.unref()
|
||||
// These stubs stay in place for the worker's whole life, disposal
|
||||
// included: `__keepEventLoopAliveUntil` is what holds the process open
|
||||
// while a termination is pending, precisely because a worker's own
|
||||
// references cannot be relied on for it.
|
||||
}
|
||||
return worker
|
||||
},
|
||||
@@ -911,6 +1113,22 @@ try {
|
||||
},
|
||||
}))
|
||||
__publishWasiDispose(__napiModule.exports)
|
||||
// The CommonJS tail below aliases `__napiModule.exports`; a named module
|
||||
// export does not travel with it, so carry the marker on the binding itself
|
||||
// too. Three things pin the stamp to exactly this spot:
|
||||
// - inside this `try`, because the guard throws on a
|
||||
// `#[napi(module_exports)]` hook that claimed the name, and only the
|
||||
// catch below tears the environment — context, workers, exit listener —
|
||||
// back down;
|
||||
// - after the async runtime host install, which hands this same object to
|
||||
// addon-provided registration functions that may put anything on it;
|
||||
// - assigning onto the loader's own `module.exports`, which is still the
|
||||
// original object here, so an addon accessor with a refusing setter is
|
||||
// never written through. `cjs-module-lexer` — Node's CJS -> ESM named
|
||||
// export detection — reads the static `module.exports.<name> =` either
|
||||
// way, and the later `module.exports = __napiModule.exports` does not
|
||||
// undo that.
|
||||
module.exports.__napiBindingTarget = __napiStampBindingTarget(__napiModule.exports, __napiBindingTarget)
|
||||
__registerWasiExitListener()
|
||||
} catch (error) {
|
||||
const rollback = {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/* auto-generated by NAPI-RS */
|
||||
/* eslint-disable */
|
||||
|
||||
/** The WASI flavor this loader instantiates. */
|
||||
export declare const __napiBindingTarget: 'wasm32-wasi'
|
||||
|
||||
export interface Comment {
|
||||
type: 'Line' | 'Block'
|
||||
value: string
|
||||
|
||||
Generated
+28
-22
@@ -117,8 +117,8 @@ catalogs:
|
||||
specifier: 2.0.0-alpha.3
|
||||
version: 2.0.0-alpha.3
|
||||
'@napi-rs/cli':
|
||||
specifier: 3.9.1
|
||||
version: 3.9.1
|
||||
specifier: 3.10.1
|
||||
version: 3.10.1
|
||||
'@napi-rs/wasm-runtime':
|
||||
specifier: 1.2.4
|
||||
version: 1.2.4
|
||||
@@ -161,7 +161,7 @@ importers:
|
||||
devDependencies:
|
||||
'@napi-rs/cli':
|
||||
specifier: 'catalog:'
|
||||
version: 3.9.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.0.2)(emnapi@2.0.0-alpha.3)
|
||||
version: 3.10.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.0.2)(emnapi@2.0.0-alpha.3)
|
||||
oxfmt:
|
||||
specifier: ^0.67.0
|
||||
version: 0.67.0(svelte@5.57.0(@typescript-eslint/types@8.69.0))(vite-plus@0.3.0(@arethetypeswrong/core@0.18.5)(@types/node@25.0.2)(@vitest/browser-playwright@4.1.11)(esbuild@0.28.2)(happy-dom@20.0.11)(publint@0.3.24)(svelte@5.57.0(@typescript-eslint/types@8.69.0))(terser@5.44.1)(tsx@4.23.13)(typescript@6.0.3)(vite@8.3.0(@types/node@25.0.2)(esbuild@0.28.2)(terser@5.44.1)(tsx@4.23.13)))
|
||||
@@ -192,7 +192,7 @@ importers:
|
||||
version: 0.18.5
|
||||
'@napi-rs/cli':
|
||||
specifier: 'catalog:'
|
||||
version: 3.9.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)
|
||||
version: 3.10.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)
|
||||
'@oxapps/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../shared
|
||||
@@ -240,7 +240,7 @@ importers:
|
||||
version: 8.0.0
|
||||
'@napi-rs/cli':
|
||||
specifier: 'catalog:'
|
||||
version: 3.9.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)
|
||||
version: 3.10.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)
|
||||
'@oxapps/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../shared
|
||||
@@ -354,7 +354,7 @@ importers:
|
||||
version: 2.0.0-alpha.3
|
||||
'@napi-rs/cli':
|
||||
specifier: 'catalog:'
|
||||
version: 3.9.1(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)
|
||||
version: 3.10.1(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)
|
||||
'@types/node':
|
||||
specifier: 'catalog:'
|
||||
version: 24.1.0
|
||||
@@ -382,7 +382,7 @@ importers:
|
||||
version: 2.0.0-alpha.3
|
||||
'@napi-rs/cli':
|
||||
specifier: 'catalog:'
|
||||
version: 3.9.1(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)
|
||||
version: 3.10.1(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)
|
||||
'@napi-rs/wasm-runtime':
|
||||
specifier: 'catalog:'
|
||||
version: 1.2.4(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)
|
||||
@@ -428,7 +428,7 @@ importers:
|
||||
version: 2.0.0-alpha.3
|
||||
'@napi-rs/cli':
|
||||
specifier: 'catalog:'
|
||||
version: 3.9.1(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@25.0.2)(emnapi@2.0.0-alpha.3)
|
||||
version: 3.10.1(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@25.0.2)(emnapi@2.0.0-alpha.3)
|
||||
publint:
|
||||
specifier: 'catalog:'
|
||||
version: 0.3.24
|
||||
@@ -443,7 +443,7 @@ importers:
|
||||
version: 2.0.0-alpha.3
|
||||
'@napi-rs/cli':
|
||||
specifier: 'catalog:'
|
||||
version: 3.9.1(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)
|
||||
version: 3.10.1(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)
|
||||
'@types/node':
|
||||
specifier: 'catalog:'
|
||||
version: 24.1.0
|
||||
@@ -464,7 +464,7 @@ importers:
|
||||
version: 2.0.0-alpha.3
|
||||
'@napi-rs/cli':
|
||||
specifier: 'catalog:'
|
||||
version: 3.9.1(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)
|
||||
version: 3.10.1(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)
|
||||
'@types/node':
|
||||
specifier: 'catalog:'
|
||||
version: 24.1.0
|
||||
@@ -485,7 +485,7 @@ importers:
|
||||
version: 2.0.0-alpha.3
|
||||
'@napi-rs/cli':
|
||||
specifier: 'catalog:'
|
||||
version: 3.9.1(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)
|
||||
version: 3.10.1(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)
|
||||
'@types/node':
|
||||
specifier: 'catalog:'
|
||||
version: 24.1.0
|
||||
@@ -553,7 +553,7 @@ importers:
|
||||
devDependencies:
|
||||
'@napi-rs/cli':
|
||||
specifier: 'catalog:'
|
||||
version: 3.9.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.0.2)(emnapi@2.0.0-alpha.3)
|
||||
version: 3.10.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.0.2)(emnapi@2.0.0-alpha.3)
|
||||
|
||||
tasks/compat_data:
|
||||
devDependencies:
|
||||
@@ -1480,8 +1480,8 @@ packages:
|
||||
resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
'@napi-rs/cli@3.9.1':
|
||||
resolution: {integrity: sha512-2DYBrM2j4n00L+O7fKAS38q19rVQK2HXz5RxSuJgFyZrd9TrdVQFmHOfqvqqsRIRBTZmqiMqBd/yzn4X1a23DQ==}
|
||||
'@napi-rs/cli@3.10.1':
|
||||
resolution: {integrity: sha512-9w66AHq2U3vIfj7G0UlVUOENbt6M7XHlHxa+rSbxkVhmCXKY2GIvLGLyYfWz8wBMjwC7dJojNInQhhUJU4hJUw==}
|
||||
engines: {node: ^20.17.0 || ^22.13.0 || >= 23.5.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -4936,6 +4936,10 @@ packages:
|
||||
resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
|
||||
obug@3.0.0:
|
||||
resolution: {integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
|
||||
on-finished@2.4.1:
|
||||
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -6722,7 +6726,7 @@ snapshots:
|
||||
|
||||
'@lukeed/csprng@1.1.0': {}
|
||||
|
||||
'@napi-rs/cli@3.9.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)':
|
||||
'@napi-rs/cli@3.10.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)':
|
||||
dependencies:
|
||||
'@inquirer/prompts': 8.5.2(@types/node@24.1.0)
|
||||
'@napi-rs/cross-toolchain': 1.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
|
||||
@@ -6732,7 +6736,7 @@ snapshots:
|
||||
colorette: 2.0.20
|
||||
es-toolkit: 1.47.0
|
||||
js-yaml: 5.4.1
|
||||
obug: 2.1.4
|
||||
obug: 3.0.0
|
||||
semver: 7.8.5
|
||||
typanion: 3.14.0
|
||||
typescript: 6.0.3
|
||||
@@ -6754,7 +6758,7 @@ snapshots:
|
||||
- '@types/node'
|
||||
- supports-color
|
||||
|
||||
'@napi-rs/cli@3.9.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.0.2)(emnapi@2.0.0-alpha.3)':
|
||||
'@napi-rs/cli@3.10.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.0.2)(emnapi@2.0.0-alpha.3)':
|
||||
dependencies:
|
||||
'@inquirer/prompts': 8.5.2(@types/node@25.0.2)
|
||||
'@napi-rs/cross-toolchain': 1.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
|
||||
@@ -6764,7 +6768,7 @@ snapshots:
|
||||
colorette: 2.0.20
|
||||
es-toolkit: 1.47.0
|
||||
js-yaml: 5.4.1
|
||||
obug: 2.1.4
|
||||
obug: 3.0.0
|
||||
semver: 7.8.5
|
||||
typanion: 3.14.0
|
||||
typescript: 6.0.3
|
||||
@@ -6786,7 +6790,7 @@ snapshots:
|
||||
- '@types/node'
|
||||
- supports-color
|
||||
|
||||
'@napi-rs/cli@3.9.1(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)':
|
||||
'@napi-rs/cli@3.10.1(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@24.1.0)(emnapi@2.0.0-alpha.3)':
|
||||
dependencies:
|
||||
'@inquirer/prompts': 8.5.2(@types/node@24.1.0)
|
||||
'@napi-rs/cross-toolchain': 1.0.3(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)
|
||||
@@ -6796,7 +6800,7 @@ snapshots:
|
||||
colorette: 2.0.20
|
||||
es-toolkit: 1.47.0
|
||||
js-yaml: 5.4.1
|
||||
obug: 2.1.4
|
||||
obug: 3.0.0
|
||||
semver: 7.8.5
|
||||
typanion: 3.14.0
|
||||
typescript: 6.0.3
|
||||
@@ -6818,7 +6822,7 @@ snapshots:
|
||||
- '@types/node'
|
||||
- supports-color
|
||||
|
||||
'@napi-rs/cli@3.9.1(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@25.0.2)(emnapi@2.0.0-alpha.3)':
|
||||
'@napi-rs/cli@3.10.1(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@25.0.2)(emnapi@2.0.0-alpha.3)':
|
||||
dependencies:
|
||||
'@inquirer/prompts': 8.5.2(@types/node@25.0.2)
|
||||
'@napi-rs/cross-toolchain': 1.0.3(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)
|
||||
@@ -6828,7 +6832,7 @@ snapshots:
|
||||
colorette: 2.0.20
|
||||
es-toolkit: 1.47.0
|
||||
js-yaml: 5.4.1
|
||||
obug: 2.1.4
|
||||
obug: 3.0.0
|
||||
semver: 7.8.5
|
||||
typanion: 3.14.0
|
||||
typescript: 6.0.3
|
||||
@@ -9876,6 +9880,8 @@ snapshots:
|
||||
|
||||
obug@2.1.4: {}
|
||||
|
||||
obug@3.0.0: {}
|
||||
|
||||
on-finished@2.4.1:
|
||||
dependencies:
|
||||
ee-first: 1.1.1
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ catalog:
|
||||
"@emnapi/runtime": 2.0.0-alpha.3
|
||||
emnapi: 2.0.0-alpha.3
|
||||
"@arethetypeswrong/core": 0.18.5
|
||||
"@napi-rs/cli": 3.9.1
|
||||
"@napi-rs/cli": 3.10.1
|
||||
"@napi-rs/wasm-runtime": 1.2.4
|
||||
"@types/node": 24.1.0
|
||||
cross-env: ^10.1.0
|
||||
|
||||
Vendored
+8
@@ -1,6 +1,14 @@
|
||||
/* auto-generated by NAPI-RS */
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* Which binding artifact the generated loader actually loaded: `'native'` for
|
||||
* a native addon, otherwise the `platformArchABI` of the WASI flavor. Every
|
||||
* flavor napi-rs can build is listed, because `NAPI_RS_NATIVE_LIBRARY_PATH`
|
||||
* can point the loader at a WASI artifact this package does not build itself.
|
||||
*/
|
||||
export declare const __napiBindingTarget: 'native' | 'wasm32-wasi' | 'wasm32-wasip1'
|
||||
|
||||
/**
|
||||
* Parse `source_text` with `oxc_parser` and print the AST with `oxc_codegen`, including a source map.
|
||||
*
|
||||
|
||||
@@ -7,6 +7,10 @@ const require = createRequire(import.meta.url)
|
||||
|
||||
const { readFileSync } = require('fs')
|
||||
let nativeBinding = null
|
||||
// Which artifact actually loaded. The WASI fallback chain overwrites it with
|
||||
// the flavor it resolved; the late native retry below leaves it alone because
|
||||
// it only runs while no WASI candidate has been loaded.
|
||||
let __napiLoadedBindingTarget = 'native'
|
||||
const loadErrors = []
|
||||
|
||||
const isMusl = () => {
|
||||
@@ -65,7 +69,16 @@ const isMuslFromChildProcess = () => {
|
||||
function requireNative() {
|
||||
if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
|
||||
try {
|
||||
return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
const overrideBinding = require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH)
|
||||
// The override may be a generated WASI loader, which already reports its
|
||||
// own flavor. Adopt it: `module.exports` aliases this object, so claiming
|
||||
// 'native' would both misreport the artifact and overwrite the loader's
|
||||
// marker through the alias.
|
||||
__napiLoadedBindingTarget =
|
||||
overrideBinding && typeof overrideBinding.__napiBindingTarget === 'string'
|
||||
? overrideBinding.__napiBindingTarget
|
||||
: 'native'
|
||||
return overrideBinding
|
||||
} catch (err) {
|
||||
loadErrors.push(err)
|
||||
}
|
||||
@@ -630,6 +643,7 @@ if (!nativeBinding || forceWasi) {
|
||||
if (!candidateFailed) {
|
||||
wasiBinding = require('./codegen_conformance.wasi.cjs')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -656,6 +670,7 @@ if (!nativeBinding || forceWasi) {
|
||||
}
|
||||
wasiBinding = require('oxc-codegen-conformance-wasm32-wasi')
|
||||
nativeBinding = wasiBinding
|
||||
__napiLoadedBindingTarget = 'wasm32-wasi'
|
||||
wasiBindingLoaded = true
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -704,3 +719,4 @@ if (!nativeBinding) {
|
||||
const { codegen, Severity } = nativeBinding
|
||||
export { codegen }
|
||||
export { Severity }
|
||||
export const __napiBindingTarget = __napiLoadedBindingTarget
|
||||
Reference in new issue
Block a user