UNPKG

@napi-rs/cli

Version:

Cli tools for napi-rs

1,696 lines (1,605 loc) 51.8 kB
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)