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 deliver ultimate computing power within an enterprise-grade isolated sandbox (Zero Memory Leaks). nLink utilizes the wazero runtime engine to 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 enables heavy computational workloads, robust type safety, and raw performance for high-throughput batch processing.

nLink's internal architecture employs O(1) Batch Sandbox Initialization and safe memory bindings. Whether processing 1 record or 1 million records through a node, the memory overhead scales linearly without risking crashes.

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 entirely isolated from the host server's process.env, preventing credential theft.
  • No Network Sockets: HTTP requests, TCP ports, and socket listeners are strictly prohibited. Nodes can only execute pure computational 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 the output of the Auto Scaffold Generator, review the boilerplate below. 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 invocations 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
    The module MUST export the following function interfaces:
    allocate(size i32) -> i32
    deallocate(ptr i32, size i32) (Optional, but highly recommended to ensure zero memory leaks)
    execute_node(ptr i32, size i32) -> u64
  • Security Restrictions (Air-gapped)
    WASM instances process Pure Logic within deep sandboxes. For security considerations, network access (Syscalls overhead HTTP) and filesystem access are structurally stripped and isolated by the nLink Wazero engine. Focus your nodes entirely on data transformations.
  • Compilation Command (Go 1.22+)
    Execute the following command to create a cross-platform Reactor binary adhering to 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 when loading standard Node architectures. However, developers may bundle an execution.js file as an alternative polyfill. If nLink fails to run WebAssembly or detects its absence, it will gracefully fall back to the V8 context via execution.js, ensuring maximum resilience.