The module graph
Every part of a module — functions, globals, memories, tables, imports, exports, types, data,
elements, locals, tags, custom sections, producers — is exposed as a live handle. Reading a
property reads through to the module; writing one writes back. Your edits persist when you call
emitWasm().
Requires @napi-rs/wasm-tools ≥ 1.0.2 (the published 1.0.1 predates this API). See Getting Started for the version note.
Collections vs. handles
A WasmModule exposes one collection per item kind — mod.functions, mod.globals,
mod.memories, mod.tables, mod.types, mod.imports, mod.exports, mod.data,
mod.elements, mod.tags, and mod.locals. Each collection
caches nothing: every accessor materializes a fresh item handle (e.g. WasmFunction,
WasmGlobal) that carries the item's id plus a strong reference to the owning module.
Collections share a shape:
.length— how many items..items()— every item as a live handle (forfor … of)..getByIndex(i)— the item whose stable.indexequalsi, ornull.- kind-specific lookups:
exports.byName(name),imports.find(module, name),globals.byName(name).
mod.producers and mod.customs are section façades, not collections — they do not
share .length / .items() / .getByIndex(). Each has its own API (mod.producers:
addLanguage / addProcessedBy / addSdk, fields(), clear(); mod.customs: addRaw,
removeRaw, list()).
The inspect loop
import { readFile } from 'node:fs/promises'
import { WasmModule } from '@napi-rs/wasm-tools'
const mod = WasmModule.fromBuffer(await readFile('./module.wasm'))
console.log(
`functions=${mod.functions.length} globals=${mod.globals.length} ` +
`memories=${mod.memories.length} imports=${mod.imports.length} ` +
`exports=${mod.exports.length}`,
)
for (const exp of mod.exports.items()) {
switch (exp.kind) {
case 'Function':
console.log(`export fn ${exp.name}`)
break
case 'Global':
console.log(`export glob ${exp.name} : ${exp.global()!.ty.type}`)
break
case 'Memory':
console.log(`export mem ${exp.name} : ${exp.memory()!.initial} page(s)`)
break
}
}
for (const g of mod.globals.items()) {
console.log(`global #${g.index} ${g.ty.type} mutable=${g.mutable} (${g.kind})`)
}
// Direct lookups, too:
mod.exports.byName('run') // -> WasmExport | null
mod.imports.find('env', 'log') // -> WasmImport | null
An export's kind ('Function' | 'Table' | 'Memory' | 'Global' | 'Tag') tells you which typed
accessor to call — exp.func(), exp.global(), exp.memory(), etc. — each returning the handle
for its variant and null for the others.
Edit through live handles, then emit
Mutations on a handle write straight back to the owning module. Rename an export, flip a global's
mutability, grow the initial memory, set the module name — then emitWasm() and re-parse to prove
every edit persisted.
const mod = WasmModule.fromBuffer(await readFile('./module.wasm'))
mod.name = 'patched-module'
mod.exports.byName('run')!.name = 'main' // rename an export
mod.exports.byName('counter')!.global()!.mutable = true // flip a global's mutability
mod.mainMemory!.initial = 4n // grow initial memory (page counts are bigint)
const bytes = mod.emitWasm(false)
const out = WasmModule.fromBuffer(bytes)
console.log(out.name) // 'patched-module'
console.log(out.exports.byName('run')) // null
console.log(out.exports.byName('main')!.kind) // 'Function'
console.log(out.mainMemory!.initial) // 4n
Page and entry counts are bigint throughout (memories/tables can be 64-bit). A handle stays valid
as long as you hold it, even across edits.