@napi-rs/cli
Version:
Cli tools for napi-rs
1,696 lines (1,605 loc) • 51.8 kB
text/typescript
const WASI_DISPOSE_SYMBOL = 'napi.rs.wasi.dispose'
const WASI_ROLLBACK_REGISTRY_SYMBOL = 'napi.rs.wasi.rollback.registry.v1'
const emnapiContextLifecycle = `
const __wasiDisposeSymbol = Symbol.for('${WASI_DISPOSE_SYMBOL}')
const __wasiWorkers = new Set()
let __napiInstance
let __emnapiContextDestroyed = false
let __emnapiContextDestroyPromise
let __emnapiWasmEnvCleanupPrepared = false
let __wasiDisposed = false
let __wasiDisposePromise
let __completeWasiDisposal = function() {}
function __isThenable(value) {
return (
value !== null &&
(typeof value === 'object' || typeof value === 'function') &&
typeof value.then === 'function'
)
}
function __createCleanupError(errors, message) {
if (errors.length === 1) {
return errors[0]
}
const __AggregateError = globalThis.AggregateError
if (typeof __AggregateError === 'function') {
return new __AggregateError(errors, message)
}
const error = new Error(message)
error.errors = errors
return error
}
function __attachCleanupErrors(error, cleanupErrors) {
if (cleanupErrors.length === 0) {
return error
}
const cleanupError = __createCleanupError(
cleanupErrors,
'WASI binding cleanup failed',
)
try {
if (
error &&
(typeof error === 'object' || typeof error === 'function')
) {
if (error.cause === undefined) {
error.cause = cleanupError
if (error.cause === cleanupError) {
return error
}
}
if (Array.isArray(error.cleanupErrors)) {
error.cleanupErrors.push(cleanupError)
return error
} else {
const attachedCleanupErrors = [cleanupError]
error.cleanupErrors = attachedCleanupErrors
if (error.cleanupErrors === attachedCleanupErrors) {
return error
}
}
}
} catch {}
const aggregate = __createCleanupError(
[error, cleanupError],
'WASI binding initialization and cleanup failed',
)
try {
aggregate.cause = error
} catch {}
return aggregate
}
function __prepareWasmEnvCleanup() {
if (__emnapiWasmEnvCleanupPrepared) {
return
}
const prepare = __napiInstance?.exports?.napi_prepare_wasm_env_cleanup
if (typeof prepare === 'function') {
prepare()
}
__emnapiWasmEnvCleanupPrepared = true
}
function __destroyEmnapiContext() {
if (__emnapiContextDestroyed || __emnapiContext === undefined) {
__emnapiContextDestroyed = true
return
}
if (__emnapiContextDestroyPromise) {
return __emnapiContextDestroyPromise
}
__prepareWasmEnvCleanup()
const result = __emnapiContext.destroy()
if (!__isThenable(result)) {
__emnapiContextDestroyed = true
return
}
const destroyPromise = Promise.resolve(result).then(
(value) => {
__emnapiContextDestroyed = true
return value
},
(error) => {
__emnapiContextDestroyPromise = undefined
throw error
},
)
__emnapiContextDestroyPromise = destroyPromise
return destroyPromise
}
function __terminateWasiWorkers() {
const cleanupErrors = []
const pending = []
for (const worker of __wasiWorkers) {
let result
try {
result = worker.terminate()
} catch (error) {
cleanupErrors.push(error)
continue
}
if (__isThenable(result)) {
pending.push(
Promise.resolve(result).then(
() => {
__wasiWorkers.delete(worker)
},
(error) => {
cleanupErrors.push(error)
},
),
)
} else {
__wasiWorkers.delete(worker)
}
}
const finish = () => {
if (cleanupErrors.length > 0) {
throw __createCleanupError(
cleanupErrors,
'Failed to terminate WASI workers',
)
}
}
return pending.length > 0 ? Promise.all(pending).then(finish) : finish()
}
function __finishWasiDisposal() {
const workerResult = __terminateWasiWorkers()
if (__isThenable(workerResult)) {
return Promise.resolve(workerResult).then(__completeWasiDisposal)
}
return __completeWasiDisposal()
}
function __startWasiDisposal() {
const destroyResult = __destroyEmnapiContext()
if (__isThenable(destroyResult)) {
return Promise.resolve(destroyResult).then(__finishWasiDisposal)
}
return __finishWasiDisposal()
}
/**
* Disposes this generated WASI binding.
*
* Access this function with:
* binding[Symbol.for('${WASI_DISPOSE_SYMBOL}')]()
*/
function __disposeWasiBinding() {
if (__wasiDisposePromise) {
return __wasiDisposePromise
}
if (__wasiDisposed) {
return Promise.resolve()
}
let resolveDispose
let rejectDispose
const disposePromise = new Promise((resolve, reject) => {
resolveDispose = resolve
rejectDispose = reject
})
__wasiDisposePromise = disposePromise
let result
try {
result = __startWasiDisposal()
} catch (error) {
__wasiDisposePromise = undefined
rejectDispose(error)
return disposePromise
}
Promise.resolve(result).then(
(value) => {
__wasiDisposed = true
resolveDispose(value)
},
(error) => {
__wasiDisposePromise = undefined
rejectDispose(error)
},
)
return disposePromise
}
function __publishWasiDispose(exports) {
Object.defineProperty(exports, __wasiDisposeSymbol, {
configurable: false,
enumerable: false,
value: __disposeWasiBinding,
writable: false,
})
}
function __finishWasiInitializationRollback(cleanupErrors) {
let workerResult
try {
workerResult = __terminateWasiWorkers()
} catch (cleanupError) {
cleanupErrors.push(cleanupError)
return cleanupErrors
}
if (__isThenable(workerResult)) {
return Promise.resolve(workerResult)
.catch((cleanupError) => {
cleanupErrors.push(cleanupError)
})
.then(() => cleanupErrors)
}
return cleanupErrors
}
function __rollbackWasiInitialization() {
const cleanupErrors = []
let destroyResult
try {
destroyResult = __destroyEmnapiContext()
} catch (cleanupError) {
cleanupErrors.push(cleanupError)
return __finishWasiInitializationRollback(cleanupErrors)
}
if (__isThenable(destroyResult)) {
return Promise.resolve(destroyResult)
.catch((cleanupError) => {
cleanupErrors.push(cleanupError)
})
.then(() => __finishWasiInitializationRollback(cleanupErrors))
}
return __finishWasiInitializationRollback(cleanupErrors)
}
`
export const createWasiBrowserBinding = (
wasiFilename: string,
initialMemory = 4000,
maximumMemory = 65536,
fs = false,
asyncInit = false,
buffer = false,
errorEvent = false,
threads = true,
) => {
const fsImport = fs
? buffer
? `import { memfs, Buffer } from '@napi-rs/wasm-runtime/fs'`
: `import { memfs } from '@napi-rs/wasm-runtime/fs'`
: ''
const bufferImport = buffer && !fs ? `import { Buffer } from 'buffer'` : ''
const wasiCreation = fs
? `
export const { fs: __fs, vol: __volume } = memfs()
const __wasi = new __WASI({
version: 'preview1',
fs: __fs,
preopens: {
'/': '/',
},
})`
: `
const __wasi = new __WASI({
version: 'preview1',
})`
const workerFsHandler = fs
? ` worker.addEventListener('message', __wasmCreateOnMessageForFsProxy(__fs))\n`
: ''
const workerErrorHandler = errorEvent
? ` worker.addEventListener('message', (event) => {
if (event.data && typeof event.data === 'object' && event.data.type === 'error') {
const __CustomEvent = globalThis.CustomEvent
if (
typeof globalThis.dispatchEvent === 'function' &&
typeof __CustomEvent === 'function'
) {
globalThis.dispatchEvent(
new __CustomEvent('napi-rs-worker-error', { detail: event.data }),
)
}
}
})
`
: ''
const emnapiInjectBuffer = buffer
? ' __emnapiContext.features.Buffer = Buffer\n'
: ''
const emnapiInstantiateImport = asyncInit
? `instantiateNapiModule as __emnapiInstantiateNapiModule`
: `instantiateNapiModuleSync as __emnapiInstantiateNapiModuleSync`
const emnapiInstantiateCall = asyncInit
? `await __emnapiInstantiateNapiModule`
: `__emnapiInstantiateNapiModuleSync`
const workerRuntimeImport = threads
? ` createOnMessage as __wasmCreateOnMessageForFsProxy,\n`
: ''
const memoryName = threads ? '__sharedMemory' : '__wasmMemory'
const asyncWorkPoolOption = ` asyncWorkPoolSize: ${threads ? 4 : 0},
`
// Every build links a "basic" emnapi archive without the C async-work and
// threadsafe-function implementations (the `emnapi-napi-rs(-mt)` archives shipped by the emnapi package), so the
// JavaScript implementations must be provided through the emnapi plugins in
// both threading modes. Without threads the C code would be unconditional
// `napi_generic_failure` stubs; with threads it would shadow the
// `@emnapi/core` threaded TSFN/async-work protocol the plugins implement.
const emnapiPluginImport = ` emnapiAsyncWorkPlugin as __emnapiAsyncWorkPlugin,\n emnapiTSFNPlugin as __emnapiTSFNPlugin,\n`
const emnapiPluginOption = ` plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],\n`
const workerOption = threads
? ` onCreateWorker() {
const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), {
type: 'module',
})
__wasiWorkers.add(worker)
${workerFsHandler}
${workerErrorHandler}
return worker
},
`
: ''
return `import {
${emnapiPluginImport}\
${workerRuntimeImport}\
${emnapiInstantiateImport},
WASI as __WASI,
} from '@napi-rs/wasm-runtime'
import { createContext as __emnapiCreateContext } from '@emnapi/runtime'
${fsImport}
${bufferImport}
${wasiCreation}
const __wasmUrl = new URL('./${wasiFilename}.wasm', import.meta.url).href
const __wasmResponse = await globalThis.fetch(__wasmUrl)
if (!__wasmResponse.ok) {
throw new Error(
'Failed to fetch WASI module ' +
__wasmUrl +
': ' +
__wasmResponse.status +
' ' +
(__wasmResponse.statusText || 'Unknown Status'),
)
}
const __wasmFile = await __wasmResponse.arrayBuffer()
const ${memoryName} = new WebAssembly.Memory({
initial: ${initialMemory},
maximum: ${maximumMemory},
${threads ? ' shared: true,\n' : ''}\
})
let __emnapiContext
${emnapiContextLifecycle}
let __wasiModule
let __napiModule
try {
__emnapiContext = __emnapiCreateContext({ autoDestroy: false })
__emnapiContext.suppressDestroy()
${emnapiInjectBuffer}
;({
instance: __napiInstance,
module: __wasiModule,
napiModule: __napiModule,
} = ${emnapiInstantiateCall}(__wasmFile, {
context: __emnapiContext,
${asyncWorkPoolOption}\
${emnapiPluginOption}\
wasi: __wasi,
${workerOption}\
overwriteImports(importObject) {
importObject.env = {
...importObject.env,
...importObject.napi,
...importObject.emnapi,
memory: ${memoryName},
}
return importObject
},
beforeInit({ instance }) {
__napiInstance = instance
for (const name of Object.keys(instance.exports)) {
if (name.startsWith('__napi_register__')) {
instance.exports[name]()
}
}
},
}))
__publishWasiDispose(__napiModule.exports)
} catch (error) {
const cleanupErrors = await __rollbackWasiInitialization()
throw __attachCleanupErrors(error, cleanupErrors)
}
`
}
export const createWasiDeferredBrowserBinding = (
wasiFilename: string,
// 64 MiB leaves headroom for JS/runtime state under workerd's 128 MiB
// isolate limit. The regular Node/browser loaders retain their historical
// 4,000-page default.
initialMemory = 1024,
maximumMemory = 65536,
buffer = false,
) => {
const bufferImport = buffer ? `import { Buffer } from 'buffer'` : ''
const emnapiInjectBuffer = buffer
? ' __emnapiContext.features.Buffer = Buffer\n'
: ''
return `import {
emnapiAsyncWorkPlugin as __emnapiAsyncWorkPlugin,
emnapiTSFNPlugin as __emnapiTSFNPlugin,
instantiateNapiModule as __emnapiInstantiateNapiModule,
WASI as __WASI,
} from '@napi-rs/wasm-runtime'
import { createContext as __emnapiCreateContext } from '@emnapi/runtime'
${bufferImport}
/**
* Deferred, workerd-safe instantiation: no top-level I/O, no compile-from-bytes.
* Accepts ONLY a precompiled WebAssembly.Module, or a Promise resolving to one
* (e.g. \`import mod from './${wasiFilename}.wasm'\` under a CompiledWasm
* module rule / wrangler module import). Byte buffers, URLs and Response
* objects are rejected: they require dynamic Wasm compilation, which
* Cloudflare Workers disallows.
*/
async function __resolveModule(__wasmInput) {
const __module = await __wasmInput
// Brand check, not \`instanceof\`: \`WebAssembly.Module.imports\` throws unless
// its argument is a genuine WebAssembly.Module, so prototype-spoofed byte
// buffers are rejected while cross-realm Module instances are accepted.
try {
WebAssembly.Module.imports(__module)
} catch {
throw new TypeError(
"instantiate() and createInstance() expect a precompiled WebAssembly.Module (or a Promise resolving to one), " +
"e.g. import mod from './${wasiFilename}.wasm' under a CompiledWasm module rule / wrangler module import. " +
"Byte buffers, URLs and Response objects require dynamic Wasm compilation, which Cloudflare Workers disallows.",
)
}
return __module
}
let __normalizedModules
function __rememberNormalizedModule(__module, __normalizedModule) {
if (!__normalizedModules) {
__normalizedModules = new WeakMap()
}
__normalizedModules.set(__module, __normalizedModule)
return __normalizedModule
}
async function __normalizeModuleForEmnapi(__module) {
if (__module instanceof WebAssembly.Module) {
return __module
}
if (__normalizedModules) {
const __normalizedModule = __normalizedModules.get(__module)
if (__normalizedModule) {
return __normalizedModule
}
}
// @emnapi/core currently performs realm-local \`instanceof\` checks after
// accepting the module. Structured cloning preserves compiled code without
// compiling bytes and produces a Module owned by the current realm.
if (typeof structuredClone === 'function') {
try {
const __normalizedModule = structuredClone(__module)
if (__normalizedModule instanceof WebAssembly.Module) {
return __rememberNormalizedModule(__module, __normalizedModule)
}
} catch {}
}
// MessageChannel uses the same structured-clone semantics and covers older
// browser/Node hosts that expose it but not the structuredClone function.
if (typeof MessageChannel === 'function') {
let __channel
try {
__channel = new MessageChannel()
const __normalizedModule = await new Promise((resolve, reject) => {
__channel.port1.onmessage = (event) => resolve(event.data)
__channel.port1.onmessageerror = () =>
reject(new TypeError('Failed to clone WebAssembly.Module'))
try {
__channel.port2.postMessage(__module)
} catch (error) {
reject(error)
}
})
if (__normalizedModule instanceof WebAssembly.Module) {
return __rememberNormalizedModule(__module, __normalizedModule)
}
} catch {
} finally {
try {
__channel?.port1.close()
} catch {}
try {
__channel?.port2.close()
} catch {}
}
}
// Last-resort compatibility for genuine, extensible foreign Modules.
try {
Object.setPrototypeOf(__module, WebAssembly.Module.prototype)
} catch {}
if (__module instanceof WebAssembly.Module) {
return __module
}
throw new TypeError(
'This host cannot normalize a cross-realm WebAssembly.Module; ' +
'provide structuredClone or MessageChannel support.',
)
}
function __captureEmnapiAutoDestroyListener(__process) {
if (
!__process ||
typeof __process.prependListener !== 'function' ||
typeof __process.removeListener !== 'function'
) {
return
}
let __autoDestroyListener
const __captureListener = (__event, __listener) => {
if (__event === 'beforeExit' && __autoDestroyListener === undefined) {
__autoDestroyListener = __listener
}
}
try {
// Run before existing newListener hooks so a hook that registers its own
// beforeExit listener cannot be mistaken for emnapi's registration.
__process.prependListener('newListener', __captureListener)
} catch {
return
}
return () => {
try {
__process.removeListener('newListener', __captureListener)
} catch {}
if (__autoDestroyListener !== undefined) {
try {
__process.removeListener('beforeExit', __autoDestroyListener)
} catch {}
}
}
}
function __attachCleanupError(__error, __cleanupError) {
try {
if (
__error &&
(typeof __error === 'object' || typeof __error === 'function') &&
__error.cause === undefined
) {
__error.cause = __cleanupError
}
} catch {}
}
function __createLifecycleReentryError(__operation) {
const __error = new Error(
__operation +
'() cannot run while an emnapi Context.destroy() call is still active; await the original cleanup promise instead.',
)
__error.code = 'ERR_NAPI_WASI_LIFECYCLE_REENTRY'
return __error
}
const __managedEmnapiContextDestroyers = new Set()
let __managedCleanupProcess
let __managedBeforeExitListener
let __managedDestroyPromise
let __managedDestroyersInFlight
let __managedBeforeExitRegistrationRetryCount = 0
let __managedBeforeExitRegistrationRetryScheduled = false
let __moduleLifecycleDestroyDepth = 0
function __removeManagedEmnapiCleanupListeners() {
const __process = __managedCleanupProcess
const __beforeExitListener = __managedBeforeExitListener
__managedCleanupProcess = undefined
__managedBeforeExitListener = undefined
__managedBeforeExitRegistrationRetryCount = 0
if (__process && __beforeExitListener) {
try {
__process.removeListener('beforeExit', __beforeExitListener)
} catch {}
}
}
function __scheduleManagedBeforeExitListenerRegistration() {
if (
!__managedCleanupProcess ||
__managedBeforeExitListener ||
__managedEmnapiContextDestroyers.size === 0 ||
__managedBeforeExitRegistrationRetryScheduled ||
__managedBeforeExitRegistrationRetryCount >= 3
) {
return
}
__managedBeforeExitRegistrationRetryScheduled = true
__managedBeforeExitRegistrationRetryCount++
queueMicrotask(() => {
__managedBeforeExitRegistrationRetryScheduled = false
if (
!__managedCleanupProcess ||
__managedBeforeExitListener ||
__managedEmnapiContextDestroyers.size === 0
) {
return
}
try {
__registerManagedBeforeExitListener()
} catch {}
})
}
function __registerManagedBeforeExitListener() {
if (!__managedCleanupProcess || __managedBeforeExitListener) {
return
}
try {
__managedCleanupProcess.once(
'beforeExit',
__destroyManagedEmnapiContextsBeforeExit,
)
} catch (error) {
__scheduleManagedBeforeExitListenerRegistration()
throw error
}
__managedBeforeExitListener = __destroyManagedEmnapiContextsBeforeExit
__managedBeforeExitRegistrationRetryCount = 0
}
function __settleManagedEmnapiContextDestroy(__promise) {
if (__managedDestroyPromise === __promise) {
__managedDestroyPromise = undefined
__managedDestroyersInFlight = undefined
}
if (__managedEmnapiContextDestroyers.size === 0) {
__removeManagedEmnapiCleanupListeners()
return
}
try {
__registerManagedBeforeExitListener()
} catch {}
}
function __destroyManagedEmnapiContexts(__excludedDestroyers) {
if (__managedDestroyPromise) {
return __managedDestroyPromise
}
const __destroyers = Array.from(__managedEmnapiContextDestroyers).filter(
(__destroy) => !__excludedDestroyers?.has(__destroy),
)
if (__destroyers.length === 0) {
return Promise.resolve()
}
let __resolveDestroy
let __rejectDestroy
const __promise = new Promise((resolve, reject) => {
__resolveDestroy = resolve
__rejectDestroy = reject
})
__managedDestroyPromise = __promise
__managedDestroyersInFlight = new Set(__destroyers)
void Promise.all(
__destroyers.map((__destroy) => {
try {
return Promise.resolve(__destroy()).then(
() => ({ failed: false }),
(error) => ({ failed: true, error }),
)
} catch (error) {
return { failed: true, error }
}
}),
).then((__results) => {
let __primaryError
let __failed = false
for (const __result of __results) {
if (!__result.failed) {
continue
}
if (!__failed) {
__failed = true
__primaryError = __result.error
} else {
__attachCleanupError(__primaryError, __result.error)
}
}
if (__failed) {
__rejectDestroy(__primaryError)
} else {
__resolveDestroy()
}
}, __rejectDestroy)
void __promise.then(
() => {
__settleManagedEmnapiContextDestroy(__promise)
},
() => {
__settleManagedEmnapiContextDestroy(__promise)
},
)
return __promise
}
async function __drainManagedEmnapiContexts(__excludedDestroyers) {
const __attemptedDestroyers = new Set(__excludedDestroyers)
let __primaryError
let __failed = false
while (true) {
let __promise = __managedDestroyPromise
let __destroyers = __managedDestroyersInFlight
if (!__promise) {
__promise = __destroyManagedEmnapiContexts(__attemptedDestroyers)
__destroyers = __managedDestroyersInFlight
if (!__destroyers) {
break
}
}
for (const __destroy of __destroyers) {
__attemptedDestroyers.add(__destroy)
}
try {
await __promise
} catch (error) {
if (!__failed) {
__failed = true
__primaryError = error
} else {
__attachCleanupError(__primaryError, error)
}
}
}
if (__failed) {
throw __primaryError
}
}
function __destroyManagedEmnapiContextsBeforeExit() {
// A once listener is consumed before Node invokes it, including when another
// cleanup batch is still pending.
__managedBeforeExitListener = undefined
if (__managedDestroyPromise) {
return
}
void __destroyManagedEmnapiContexts().catch((error) => {
queueMicrotask(() => {
throw error
})
})
}
function __registerManagedEmnapiContext(__process, __destroy) {
__managedEmnapiContextDestroyers.add(__destroy)
if (
!__managedCleanupProcess &&
__process &&
typeof __process.once === 'function' &&
typeof __process.removeListener === 'function'
) {
__managedCleanupProcess = __process
}
let __registered = true
return () => {
if (!__registered) {
return
}
__registered = false
__managedEmnapiContextDestroyers.delete(__destroy)
if (__managedEmnapiContextDestroyers.size === 0) {
__removeManagedEmnapiCleanupListeners()
}
}
}
async function __createManagedEmnapiContext(__prepareEnvCleanup) {
const __process =
typeof process === 'object' && process !== null ? process : undefined
const __finishAutoDestroyCapture =
__captureEmnapiAutoDestroyListener(__process)
let __emnapiContext
let __contextInitializationError
let __contextInitializationFailed = false
try {
__emnapiContext = __emnapiCreateContext({ autoDestroy: false })
// emnapi 2.x still registers an unconditional process.once('beforeExit')
// auto-destroy listener on Node hosts, and suppressDestroy() only
// neutralizes its callback without removing it. This loader must stay
// side-effect free per instance, so the listener is captured and removed;
// suppressDestroy() remains the safety net when removal is unavailable.
__emnapiContext.suppressDestroy()
} catch (error) {
__contextInitializationError = error
__contextInitializationFailed = true
} finally {
// Remove only the exact emnapi callback captured above.
__finishAutoDestroyCapture?.()
}
if (__emnapiContext === undefined) {
throw __contextInitializationError
}
let __disposed = false
let __destroying = false
let __destroyPromise
let __cleanupRegistered = false
let __unregisterCleanup
const __destroy = (__blocksModuleLifecycle = false) => {
if (__disposed) {
return
}
if (__destroying) {
throw __createLifecycleReentryError('dispose')
}
if (__destroyPromise) {
return __destroyPromise
}
__destroying = true
let __result
const __finishDestroyInvocation = () => {
__destroying = false
}
const __finishModuleLifecycleDestroy = () => {
if (__blocksModuleLifecycle) {
__blocksModuleLifecycle = false
__moduleLifecycleDestroyDepth--
}
}
if (__blocksModuleLifecycle) {
__moduleLifecycleDestroyDepth++
}
try {
// Context.destroy() disables JS before cleanup hooks run, so settle
// runtime-owned promises while this environment can still call JS.
__prepareEnvCleanup?.()
__result = __emnapiContext.destroy()
} catch (error) {
__finishDestroyInvocation()
__finishModuleLifecycleDestroy()
throw error
}
let __then
try {
if (
__result !== null &&
(typeof __result === 'object' || typeof __result === 'function')
) {
__then = __result.then
}
} catch (error) {
__finishDestroyInvocation()
__finishModuleLifecycleDestroy()
throw error
}
if (typeof __then === 'function') {
let __resolveResult
let __rejectResult
const __resultPromise = new Promise((resolve, reject) => {
__resolveResult = resolve
__rejectResult = reject
})
const __promise = __resultPromise.then(
(value) => {
__finishDestroyInvocation()
__finishModuleLifecycleDestroy()
__disposed = true
__destroyPromise = undefined
__unregisterCleanup?.()
return value
},
(error) => {
__finishDestroyInvocation()
__finishModuleLifecycleDestroy()
__destroyPromise = undefined
throw error
},
)
__destroyPromise = __promise
try {
Reflect.apply(__then, __result, [__resolveResult, __rejectResult])
} catch (error) {
__rejectResult(error)
}
return __promise
}
__finishDestroyInvocation()
__finishModuleLifecycleDestroy()
__disposed = true
__unregisterCleanup?.()
}
const __destroyForModuleLifecycle = () => __destroy(true)
const __registerCleanup = (
__beforeExitDestroy = __destroyForModuleLifecycle,
) => {
if (__cleanupRegistered || __disposed) {
return
}
__unregisterCleanup = __registerManagedEmnapiContext(
__process,
__beforeExitDestroy,
)
__cleanupRegistered = true
__registerManagedBeforeExitListener()
}
if (__contextInitializationFailed) {
let __registrationError
let __registrationFailed = false
try {
__registerCleanup()
} catch (error) {
__attachCleanupError(__contextInitializationError, error)
__registrationError = error
__registrationFailed = true
}
try {
await __destroyForModuleLifecycle()
} catch (error) {
__attachCleanupError(
__registrationFailed
? __registrationError
: __contextInitializationError,
error,
)
try {
__registerManagedBeforeExitListener()
} catch {}
}
throw __contextInitializationError
}
return {
context: __emnapiContext,
destroy: __destroy,
destroyForModuleLifecycle: __destroyForModuleLifecycle,
registerCleanup: __registerCleanup,
}
}
async function __createInstance(
__wasmInput,
__beforeExitDestroy,
__onManagedDestroyer,
) {
const __module = await __resolveModule(__wasmInput)
const __emnapiModule = await __normalizeModuleForEmnapi(__module)
const __wasi = new __WASI({
version: 'preview1',
})
// The wasm module is linked with \`--import-memory\`, so a Memory must be
// provided. It is allocated here in function scope (workerd bans global
// scope allocation) and is not shared (no threads, no SharedArrayBuffer).
// Allocate it before the emnapi context so a host memory-limit failure cannot
// leak a context that never reaches instantiation.
const __wasmMemory = new WebAssembly.Memory({
initial: ${initialMemory},
maximum: ${maximumMemory},
})
let __lifecycleState = 'pending'
let __destroyEmnapiContext
let __destroyOwnedContext
let __destroyManagedOwnedContext
let __napiInstance
const __destroyBeforeExit = __beforeExitDestroy
? async () => {
if (__lifecycleState === 'failed') {
await __destroyManagedOwnedContext()
return
}
__lifecycleState = 'disposal'
try {
await __beforeExitDestroy()
} catch (error) {
if (__lifecycleState !== 'failed') {
throw error
}
// The singleton's initialization rejection is already observable
// through instantiate() and dispose(). Managed beforeExit cleanup
// owns only context destruction, including retrying a failed rollback.
await __destroyManagedOwnedContext()
}
}
: undefined
const {
context: __emnapiContext,
destroy,
destroyForModuleLifecycle,
registerCleanup: __registerCleanup,
} = await __createManagedEmnapiContext(() => {
const __prepareWasmEnvCleanup =
__napiInstance?.exports.napi_prepare_wasm_env_cleanup
if (typeof __prepareWasmEnvCleanup === 'function') {
__prepareWasmEnvCleanup()
}
})
__destroyEmnapiContext = destroy
__destroyOwnedContext = () => __destroyEmnapiContext()
__destroyManagedOwnedContext = destroyForModuleLifecycle
try {
if (__destroyBeforeExit) {
__onManagedDestroyer(__destroyBeforeExit)
await __registerCleanup(__destroyBeforeExit)
}
${emnapiInjectBuffer}\
let __napiModule
;({
instance: __napiInstance,
napiModule: __napiModule,
} = await __emnapiInstantiateNapiModule(__emnapiModule, {
context: __emnapiContext,
asyncWorkPoolSize: 0,
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
wasi: __wasi,
overwriteImports(importObject) {
importObject.env = {
...importObject.env,
...importObject.napi,
...importObject.emnapi,
memory: __wasmMemory,
}
return importObject
},
beforeInit({ instance }) {
__napiInstance = instance
for (const name of Object.keys(instance.exports)) {
if (name.startsWith('__napi_register__')) {
instance.exports[name]()
}
}
},
}))
if (__lifecycleState === 'pending') {
__lifecycleState = 'succeeded'
}
return {
exports: __napiModule.exports,
async dispose() {
if (__lifecycleState !== 'failed') {
__lifecycleState = 'disposal'
}
return __beforeExitDestroy
? __destroyManagedOwnedContext()
: __destroyOwnedContext()
},
}
} catch (error) {
__lifecycleState = 'failed'
let __registrationError
let __registrationFailed = false
if (!__beforeExitDestroy) {
try {
// Independent instances are caller-owned while pending and after
// success. Register only failed rollback so cleanup remains retryable.
await __registerCleanup()
} catch (registrationError) {
__attachCleanupError(error, registrationError)
__registrationError = registrationError
__registrationFailed = true
}
}
try {
await __destroyManagedOwnedContext()
} catch (disposeError) {
// Initialization is the primary failure. Preserve it even if cleanup
// also fails, while retaining the cleanup error when the value is
// extensible and has no existing cause.
__attachCleanupError(
__registrationFailed ? __registrationError : error,
disposeError,
)
try {
__registerManagedBeforeExitListener()
} catch {}
}
throw error
}
}
/**
* Create an independent instance. Call dispose() when the instance is no
* longer needed so emnapi cleanup hooks run deterministically.
*/
export async function createInstance(__wasmInput) {
return __createInstance(__wasmInput)
}
let __defaultModulePromise
let __defaultInstancePromise
let __defaultDisposePromise
let __defaultDisposalStarted = false
const __defaultManagedDestroyers = new WeakMap()
let __moduleDisposePromise
/**
* Instantiate a module-local singleton. Concurrent and repeated calls
* with the same module share one instance and one Memory allocation.
*/
export function instantiate(__wasmInput) {
const __modulePromise = __resolveModule(__wasmInput)
if (__moduleLifecycleDestroyDepth !== 0) {
void __modulePromise.catch(() => {})
return Promise.reject(__createLifecycleReentryError('instantiate'))
}
if (__moduleDisposePromise) {
void __modulePromise.catch(() => {})
return __moduleDisposePromise.then(() => instantiate(__modulePromise))
}
if (__defaultDisposalStarted) {
// Observe rejected input immediately, but preserve lifecycle ordering and
// error precedence by instantiating only after disposal succeeds. A failed
// disposal retains the old instance only so its cleanup can be retried.
void __modulePromise.catch(() => {})
const __disposePromise = __defaultDisposePromise ?? dispose()
return __disposePromise.then(() => instantiate(__modulePromise))
}
if (!__defaultInstancePromise) {
__defaultModulePromise = __modulePromise
const __instancePromise = __modulePromise.then((__module) =>
__createInstance(
__module,
__disposeDefaultInstance,
(__managedDestroyer) => {
__defaultManagedDestroyers.set(
__instancePromise,
__managedDestroyer,
)
},
),
)
__defaultInstancePromise = __instancePromise
void __instancePromise.catch(() => {
if (__defaultInstancePromise === __instancePromise) {
__defaultInstancePromise = undefined
__defaultModulePromise = undefined
}
})
return __instancePromise.then((__instance) => __instance.exports)
}
const __defaultModulePromiseForCall = __defaultModulePromise
const __defaultInstancePromiseForCall = __defaultInstancePromise
return Promise.all([__defaultModulePromiseForCall, __modulePromise]).then(
async ([__defaultModule, __module]) => {
if (__defaultModule !== __module) {
throw new Error(
'instantiate() already owns a different WebAssembly.Module; call dispose() first or use createInstance() for independent instances.',
)
}
return (await __defaultInstancePromiseForCall).exports
},
)
}
async function __disposeDefaultInstance(__onDestroy) {
if (__defaultDisposePromise) {
return __defaultDisposePromise
}
const __instancePromise = __defaultInstancePromise
if (!__instancePromise) {
__defaultDisposalStarted = false
return
}
__defaultDisposalStarted = true
const __disposePromise = (async () => {
let __instance
try {
__instance = await __instancePromise
} catch (error) {
const __managedDestroyer =
__defaultManagedDestroyers.get(__instancePromise)
if (__managedDestroyer) {
__onDestroy?.(__managedDestroyer)
}
__defaultManagedDestroyers.delete(__instancePromise)
throw error
}
const __managedDestroyer =
__defaultManagedDestroyers.get(__instancePromise)
if (__managedDestroyer) {
__onDestroy?.(__managedDestroyer)
}
await __instance.dispose()
if (__defaultInstancePromise === __instancePromise) {
__defaultInstancePromise = undefined
__defaultModulePromise = undefined
__defaultDisposalStarted = false
}
__defaultManagedDestroyers.delete(__instancePromise)
})()
__defaultDisposePromise = __disposePromise
try {
await __disposePromise
} finally {
if (__defaultDisposePromise === __disposePromise) {
__defaultDisposePromise = undefined
}
}
}
async function __dispose() {
let __defaultDisposeError
let __defaultDisposeFailed = false
let __attemptedDefaultDestroyer
try {
await __disposeDefaultInstance((__destroyer) => {
__attemptedDefaultDestroyer = __destroyer
})
} catch (error) {
__defaultDisposeError = error
__defaultDisposeFailed = true
}
const __excludedDestroyers = new Set()
if (__defaultDisposeFailed && __attemptedDefaultDestroyer) {
__excludedDestroyers.add(__attemptedDefaultDestroyer)
}
try {
await __drainManagedEmnapiContexts(__excludedDestroyers)
} catch (error) {
if (!__defaultDisposeFailed) {
throw error
}
if (error !== __defaultDisposeError) {
__attachCleanupError(__defaultDisposeError, error)
}
}
if (__defaultDisposeFailed) {
throw __defaultDisposeError
}
}
/**
* Dispose the singleton created by instantiate(). A later call may create a
* fresh instance, including from a different module. This also retries cleanup
* retained after a failed initialization rollback.
*/
export function dispose() {
if (__moduleLifecycleDestroyDepth !== 0) {
return Promise.reject(__createLifecycleReentryError('dispose'))
}
if (__moduleDisposePromise) {
return __moduleDisposePromise
}
let __resolveDispose
let __rejectDispose
const __promise = new Promise((resolve, reject) => {
__resolveDispose = resolve
__rejectDispose = reject
})
__moduleDisposePromise = __promise
void __dispose().then(__resolveDispose, __rejectDispose)
void __promise.then(
() => {
if (__moduleDisposePromise === __promise) {
__moduleDisposePromise = undefined
}
},
() => {
if (__moduleDisposePromise === __promise) {
__moduleDisposePromise = undefined
}
},
)
return __promise
}
`
}
export const createWasiDeferredBrowserBindingTypeDef = (
packageName: string,
) => `export type WasiBinding = typeof import('${packageName}')
export type WasiModuleInput =
| WebAssembly.Module
| PromiseLike<WebAssembly.Module>
export interface WasiInstance {
readonly exports: WasiBinding
dispose(): Promise<void>
}
export function instantiate(wasmInput: WasiModuleInput): Promise<WasiBinding>
export function createInstance(wasmInput: WasiModuleInput): Promise<WasiInstance>
/** Dispose the singleton and retry retained failed-initialization cleanup. */
export function dispose(): Promise<void>
`
export const createWasiBinding = (
wasmFileName: string,
packageName: string,
initialMemory = 4000,
maximumMemory = 65536,
threads = true,
// `platformArchABI` of the flavor this loader belongs to; the fallback
// package (`<packageName>-<platformArchABI>`) must ship the same flavor's
// wasm artifact.
platformArchABI = 'wasm32-wasi',
packageWasmFileName = wasmFileName,
) => {
const workerImports = threads
? `const { Worker } = require('node:worker_threads')
`
: ''
const workerExecArgv = threads
? `
function __getWasiWorkerExecArgv() {
const __workerExecArgv = []
for (let __index = 0; __index < process.execArgv.length; __index += 1) {
const __arg = process.execArgv[__index]
if (
__arg === '--input-type' ||
__arg === '--eval' ||
__arg === '-e' ||
__arg === '--print' ||
__arg === '-p'
) {
__index += 1
continue
}
if (
__arg.startsWith('--input-type=') ||
__arg.startsWith('--eval=') ||
__arg.startsWith('--print=')
) {
continue
}
__workerExecArgv.push(__arg)
}
return __workerExecArgv
}
function __isInvalidWasiWorkerExecArgv(errorMessage, argument) {
const __equalsIndex = argument.indexOf('=')
const __argumentName =
__equalsIndex === -1 ? argument : argument.slice(0, __equalsIndex)
return (
errorMessage.includes(': ' + __argumentName + ',') ||
errorMessage.includes(': ' + __argumentName + '=') ||
errorMessage.endsWith(': ' + __argumentName) ||
errorMessage.includes(', ' + __argumentName + ',') ||
errorMessage.includes(', ' + __argumentName + '=') ||
errorMessage.endsWith(', ' + __argumentName)
)
}
function __removeInvalidWasiWorkerExecArgv(execArgv, error) {
if (typeof error.message !== 'string') {
return
}
const __workerExecArgv = []
let __removed = false
for (let __index = 0; __index < execArgv.length; __index += 1) {
const __arg = execArgv[__index]
if (
__arg.startsWith('-') &&
__isInvalidWasiWorkerExecArgv(error.message, __arg)
) {
__removed = true
if (
!__arg.includes('=') &&
__index + 1 < execArgv.length &&
!execArgv[__index + 1].startsWith('-')
) {
__index += 1
}
continue
}
__workerExecArgv.push(__arg)
}
return __removed ? __workerExecArgv : undefined
}
function __createWasiWorker(filename) {
let __workerExecArgv = __getWasiWorkerExecArgv()
while (true) {
try {
return new Worker(filename, {
env: process.env,
execArgv: __workerExecArgv,
})
} catch (error) {
if (!error || error.code !== 'ERR_WORKER_INVALID_EXEC_ARGV') {
throw error
}
const __nextWorkerExecArgv =
__removeInvalidWasiWorkerExecArgv(__workerExecArgv, error)
if (!__nextWorkerExecArgv) {
throw error
}
__workerExecArgv = __nextWorkerExecArgv
}
}
}
`
: ''
const workerRuntimeImport = threads
? ` createOnMessage: __wasmCreateOnMessageForFsProxy,\n`
: ''
const memoryName = threads ? '__sharedMemory' : '__wasmMemory'
const asyncWorkOptions = threads
? ` asyncWorkPoolSize: (function() {
const threadsSizeFromEnv = Number(process.env.NAPI_RS_ASYNC_WORK_POOL_SIZE ?? process.env.UV_THREADPOOL_SIZE)
// NaN > 0 is false
if (threadsSizeFromEnv > 0) {
return threadsSizeFromEnv
} else {
return 4
}
})(),
reuseWorker: true,
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
`
: ` asyncWorkPoolSize: 0,
plugins: [__emnapiAsyncWorkPlugin, __emnapiTSFNPlugin],
`
// Every build links a "basic" emnapi archive without the C async-work and
// threadsafe-function implementations (the `emnapi-napi-rs(-mt)` archives shipped by the emnapi package), so the
// JavaScript implementations must be provided through the emnapi plugins in
// both threading modes. Without threads the C code would be unconditional
// `napi_generic_failure` stubs; with threads it would shadow the
// `@emnapi/core` threaded TSFN/async-work protocol the plugins implement.
const emnapiPluginRequire = ` emnapiAsyncWorkPlugin: __emnapiAsyncWorkPlugin,\n emnapiTSFNPlugin: __emnapiTSFNPlugin,\n`
const workerOption = threads
? ` onCreateWorker() {
const worker = __createWasiWorker(__nodePath.join(__dirname, 'wasi-worker.mjs'))
__wasiWorkers.add(worker)
worker.onmessage = ({ data }) => {
__wasmCreateOnMessageForFsProxy(__nodeFs)(data)
}
// The main thread of Node.js waits for all the active handles before exiting.
// But Rust threads are never waited without \`thread::join\`.
// So here we hack the code of Node.js to prevent the workers from being referenced (active).
// According to https://github.com/nodejs/node/blob/19e0d472728c79d418b74bddff588bea70a403d0/lib/internal/worker.js#L415,
// a worker is consist of two handles: kPublicPort and kHandle.
{
const kPublicPort = Object.getOwnPropertySymbols(worker).find(s =>
s.toString().includes("kPublicPort")
);
if (kPublicPort) {
worker[kPublicPort].ref = () => {};
}
const kHandle = Object.getOwnPropertySymbols(worker).find(s =>
s.toString().includes("kHandle")
);
if (kHandle) {
worker[kHandle].ref = () => {};
}
worker.unref();
}
return worker
},
`
: ''
return `/* eslint-disable */
/* prettier-ignore */
/* auto-generated by NAPI-RS */
const __nodeFs = require('node:fs')
const __nodePath = require('node:path')
const { WASI: __nodeWASI } = require('node:wasi')
${workerImports}\
const {
${emnapiPluginRequire}\
${workerRuntimeImport}\
instantiateNapiModuleSync: __emnapiInstantiateNapiModuleSync,
} = require('@napi-rs/wasm-runtime')
const { createContext: __emnapiCreateContext } = require('@emnapi/runtime')
${workerExecArgv}\
const __rootDir = __nodePath.parse(process.cwd()).root
const __wasi = new __nodeWASI({
version: 'preview1',
env: process.env,
preopens: {
[__rootDir]: __rootDir,
}
})
const ${memoryName} = new WebAssembly.Memory({
initial: ${initialMemory},
maximum: ${maximumMemory},
${threads ? ' shared: true,\n' : ''}\
})
let __wasmFilePath = __nodePath.join(__dirname, '${wasmFileName}.wasm')
const __wasmDebugFilePath = __nodePath.join(__dirname, '${wasmFileName}.debug.wasm')
if (__nodeFs.existsSync(__wasmDebugFilePath)) {
__wasmFilePath = __wasmDebugFilePath
} else if (!__nodeFs.existsSync(__wasmFilePath)) {
const __wasiPackageEntry = require.resolve('${packageName}-${platformArchABI}')
const __packagedWasmFilePath = __nodePath.join(
__nodePath.dirname(__wasiPackageEntry),
'${packageWasmFileName}.wasm',
)
if (!__nodeFs.existsSync(__packagedWasmFilePath)) {
throw new Error(
'${packageName}-${platformArchABI} is installed but is missing ${packageWasmFileName}.wasm.',
)
}
__wasmFilePath = __packagedWasmFilePath
}
const __wasmFile = __nodeFs.readFileSync(__wasmFilePath)
let __emnapiContext
${emnapiContextLifecycle}
const __wasiRollbackRegistrySymbol = Symbol.for('${WASI_ROLLBACK_REGISTRY_SYMBOL}')
const __wasiRollbackRegistryKey =
typeof __filename === 'string' ? __filename : __wasmFilePath
function __getWasiRollbackRegistry() {
const existing = process[__wasiRollbackRegistrySymbol]
if (existing !== undefined) {
if (!(existing instanceof Map)) {
throw new TypeError(
'The process-wide NAPI-RS WASI rollback registry is invalid',
)
}
return existing
}
const registry = new Map()
Object.defineProperty(process, __wasiRollbackRegistrySymbol, {
configurable: false,
enumerable: false,
value: registry,
writable: false,
})
return registry
}
const __wasiRollbackRegistry = __getWasiRollbackRegistry()
function __completeWasiInitializationRollback(record, cleanupErrors) {
try {
if (cleanupErrors.length === 0) {
if (
__wasiRollbackRegistry.get(__wasiRollbackRegistryKey) === record
) {
__wasiRollbackRegistry.delete(__wasiRollbackRegistryKey)
}
return
}
record.error = __attachCleanupErrors(record.error, cleanupErrors)
} catch (cleanupError) {
try {
record.error = __createCleanupError(
[record.error, cleanupError],
'WASI binding initialization and cleanup failed',
)
} catch {}
} finally {
record.active = false
record.promise = undefined
}
}
function __runWasiInitializationRollback(record) {
if (record.active) {
return
}
record.active = true
let rollbackResult
try {
rollbackResult = record.rollback()
} catch (cleanupError) {
__completeWasiInitializationRollback(record, [cleanupError])
return
}
if (!__isThenable(rollbackResult)) {
__completeWasiInitializationRollback(record, rollbackResult)
return
}
record.promise = Promise.resolve(rollbackResult).then(
(cleanupErrors) => {
__completeWasiInitializationRollback(record, cleanupErrors)
},
(cleanupError) => {
__completeWasiInitializationRollback(record, [cleanupError])
},
)
}
const __pendingWasiRollback = __wasiRollbackRegistry.get(
__wasiRollbackRegistryKey,
)
if (__pendingWasiRollback !== undefined) {
__runWasiInitializationRollback(__pendingWasiRollback)
throw __pendingWasiRollback.error
}
let __wasiModule
let __napiModule
let __wasiExitListenerRegistered = false
function __removeWasiExitListener() {
if (
__wasiExitListenerRegistered &&
typeof process.removeListener === 'function'
) {
process.removeListener('exit', __disposeWasiBindingAtExit)
}
__wasiExitListenerRegistered = false
}
function __disposeWasiBindingAtExit() {
__wasiExitListenerRegistered = false
try {
const result = __disposeWasiBinding()
if (__isThenable(result)) {
void Promise.resolve(result).catch(() => {})
}
} catch {}
}
function __registerWasiExitListener() {
if (
!__wasiExitListenerRegistered &&
typeof process.once === 'function'
) {
process.once('exit', __disposeWasiBindingAtExit)
__wasiExitListenerRegistered = true
}
}
__completeWasiDisposal = __removeWasiExitListener
function __captureEmnapiAutoDestroyListener() {
if (
typeof process.prependListener !== 'function' ||
typeof process.removeListener !== 'function'
) {
return
}
let __autoDestroyListener
const __captureListener = (__event, __listener) => {
if (__event === 'beforeExit' && __autoDestroyListener === undefined) {
__autoDestroyListener = __listener
}
}
try {
// Run before existing newListener hooks so a hook that registers its own
// beforeExit listener cannot be mistaken for emnapi's registration.
process.prependListener('newListener', __captureListener)
} catch {
return
}
return () => {
try {
process.removeListener('newListener', __captureListener)
} catch {}
if (__autoDestroyListener !== undefined) {
try {
process.removeListener('beforeExit', __autoDestroyListener)