Documentation

WebAssembly (WASM) Node Development

Harness the power of near-native performance by writing community nodes in Go, Rust, C/C++, Zig, Python, or AssemblyScript. WASM nodes provide ultimate computing power with an enterprise-grade isolated sandbox (Zero Memory Leaks). nLink utilizes the wazero runtime Engine to natively process WASM internally without any CGO dependency.

WASM Node Architecture & Security Restrictions

Instead of relying on the Node.js V8 execution context like standard JavaScript nodes, WASM nodes compile source code into an execution.wasm binary. This allows heavy computational workloads, robust type safety, and raw performance for high-throughput batch processing.

The internal setup inside nLink employs O(1) Batch Sandbox Initialization and safe memory bindings. This means no matter if you pass 1 record or 1 million records through a node in a workflow, the memory overhead scales perfectly linearly without crashing.

Security & Data Protection: nLink’s Wasm runtime is an Air-gapped Sandbox designed to protect sensitive server data. By default:
  • No Filesystem Access: Wasm nodes cannot read or write any files on the host server (throws fs.ErrNotExist).
  • No Environment Variables: Wasm nodes are completely blinded from the host server's process.env, preventing credentials theft.
  • No Network Sockets: HTTP requests, TCP ports, and socket listeners are stripped out. Nodes can only execute pure computing math and standard data transformations.
  • Memory Bounding: The engine automatically hard-limits Wasm execution memory to 32MB (512 Pages), neutralizing memory exhaustion attacks.
nlink-community.wasm_demo/
├── package.json      # NPM Metadata
├── definition.json   # Graphical Interface Configuration
├── main.go           # Source code logic
└── execution.wasm    # The compiled core logic binary (Replaces execution.js)

WASM Boilerplate (Golang)

If you prefer to start from scratch, or want to understand what the Auto Scaffold Generator produces, here is the boilerplate. When compiling Go to WebAssembly for nLink, we utilize wasip1 and standard c-shared build modes to construct a WASI Reactor. A Reactor maintains state between function runs without exiting, allowing nLink's execution engine to directly invoke linear memory hooks.

package main

import (
	"encoding/json"
	"reflect"
	"unsafe"
)

// Global map to keep alive memory allocations against GC and Host GC
var allocations = make(map[uint32][]byte)

// 1. Memory Hook for nLink to inject parameter data into Guest Memory
//go:wasmexport allocate
func allocate(size uint32) uint32 {
	buf := make([]byte, size)
	ptr := uint32(uintptr(unsafe.Pointer(&buf[0])))
	allocations[ptr] = buf
	return ptr
}

// 2. Memory Hook for nLink to instantly release Memory preventing OOM
//go:wasmexport deallocate
func deallocate(ptr uint32, size uint32) {
	delete(allocations, ptr)
}

// 3. Application Hook triggering business logic
//go:wasmexport execute_node
func execute_node(ptr uint32, length uint32) uint64 {
	var inputBytes []byte
	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&inputBytes))
	hdr.Data = uintptr(ptr)
	hdr.Len = int(length)
	hdr.Cap = int(length)

	// Step 1. Parse JSON Context from nLink
	var inputObj map[string]interface{}
	if err := json.Unmarshal(inputBytes, &inputObj); err != nil {
		return packError("invalid input JSON")
	}

	// Step 2. Do Pure Computing Logic here (No network / HTTP access allowed)
	text := "World"
	if params, ok := inputObj["params"].(map[string]interface{}); ok {
		if t, hasT := params["text"].(string); hasT {
			text = t
		}
	}

	// Step 3. Transform to standard array output format
	outObj := make([]interface{}, 1)
	outObj[0] = map[string]interface{}{
		"json": map[string]interface{}{
			"wasm_processed": true,
			"message":        "Hello, " + text + "! Greetings from WebAssembly Runtime! 🚀",
			"original_data":  inputObj["$json"],
		},
	}

	outBytes, _ := json.Marshal(outObj)
	return packResult(outBytes)
}

func packError(msg string) uint64 {
	b, _ := json.Marshal(map[string]interface{}{"error": msg})
	return packResult(b)
}

// 4. Memory compression to bridge 32bit architectures
func packResult(outBytes []byte) uint64 {
	ptr := uint32(uintptr(unsafe.Pointer(&outBytes[0])))
	allocations[ptr] = outBytes
	return (uint64(ptr) << 32) | uint64(len(outBytes))
}

// Required explicitly for normal execution 
func main() {}

Build & Compilation Constraints

nLink's Wasm Node Runner expects execution.wasm rather than execution.js. The WASM module must satisfy the following strict criteria:

  • Reactor Export Formats
    It MUST export the function interface:
    allocate(size i32) -> i32
    deallocate(ptr i32, size i32) (Optional, highly recommended for zero memory leak)
    execute_node(ptr i32, size i32) -> u64
  • Security Restrictions (Air-gapped)
    WASM instances process Pure Logic in deep sandboxing. Due to security considerations, Network access (Syscalls overhead HTTP) and Filesystem access are structurally stripped and isolated by nLink Wazero engine. Focus your nodes entirely on Data Transforming.
  • Compilation Command (Go 1.22+)
    Run the following script to create a cross-platform Reactor binary representing the WASI snapshot specification:
    GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o execution.wasm main.go

Workflow Fallback Strategy

The backend engine automatically attempts to read execution.wasm first upon loading standard Node architectures. However, developers may bundle an execution.js as an alternative polyfill! If nLink fails to run WebAssembly or detects it missing, it will gracefully fallback to the V8 context via execution.js allowing ultimate resilience.