This content originally appeared on DEV Community and was authored by Pt. Prashant tripathi
“Hexagon speed, wasp-lib precision, powered by WASM.”
A zero-dependency TypeScript library for seamless, type-safe interaction with Emscripten-generated WebAssembly memory.
🎯 What is wasp-lib?
wasp-lib is a powerful TypeScript library that bridges the gap between
JavaScript and WebAssembly memory management. It transforms complex, error-prone
manual memory operations into simple, type-safe method calls, making WebAssembly
integration as easy as working with native JavaScript objects.
The Problem
Working with WebAssembly memory directly is challenging:
-
Memory Leaks: Forgetting to call
_free()
leads to memory leaks - Type Safety: No compile-time guarantees about data types
- Boilerplate Code: Repetitive allocation/deallocation patterns
- Error Prone: Manual pointer arithmetic and buffer management
The Solution
wasp-lib provides intuitive wrapper classes that:
- ✅ Automatically manage memory allocation and deallocation
- ✅ Ensure type safety with TypeScript generics
- ✅ Eliminate boilerplate with simple, chainable APIs
- ✅ Prevent memory leaks with built-in cleanup mechanisms
Before vs After
Before wasp-lib 😰
// Manual memory management - error-prone and verbose!
function processData(wasm: any, numbers: number[]) {
// Allocate memory manually
const arraySize = numbers.length * 4; // 4 bytes per i32
const arrayPtr = wasm._malloc(arraySize);
// Copy data byte by byte
for (let i = 0; i < numbers.length; i++) {
wasm.setValue(arrayPtr + i * 4, numbers[i], "i32");
}
// Call WASM function
const sum = wasm._sum_array(arrayPtr, numbers.length);
// Read result and manually free memory
wasm._free(arrayPtr); // Easy to forget!
return sum;
}
After wasp-lib 🎉
// Clean, type-safe, automatic cleanup!
function processData(wasm: WASMModule, numbers: number[]) {
const arrayPtr = ArrayPointer.from(wasm, "i32", numbers.length, numbers);
const sum = wasm._sum_array(arrayPtr.ptr, numbers.length);
arrayPtr.free(); // Or use readAndFree() for automatic cleanup
return sum;
}
🌟 Key Features
- 🔒 Type-Safe Memory Operations: Full TypeScript support with generic types
- 🧹 Automatic Memory Management: Built-in allocation, deallocation, and cleanup
- 🎯 Intuitive Pointer Abstractions: High-level classes for all data types
- 📦 Zero Dependencies: Lightweight with no external dependencies
- ⚡ Emscripten-Optimized: Designed specifically for Emscripten-generated modules
- 🧪 Battle-Tested: Comprehensive test suite with 100% coverage
- 📚 Rich Documentation: Auto-generated API docs with examples
- 🛡️ Memory Safety: Built-in bounds checking and validation
🚀 Installation
# npm
npm install wasp-lib
# yarn
yarn add wasp-lib
# pnpm
pnpm add wasp-lib
# bun
bun add wasp-lib
🎨 Use Cases
1. Image Processing
// Process image pixel data in WebAssembly
const pixels = new Uint8Array(width * height * 4);
const pixelPtr = ArrayPointer.from(wasm, "i8", pixels.length, [...pixels]);
wasm._apply_filter(pixelPtr.ptr, width, height);
const processedPixels = pixelPtr.readAndFree();
2. Mathematical Computations
// High-performance matrix operations
const matrix = [
[1, 2],
[3, 4],
[5, 6],
];
const flatMatrix = matrix.flat();
const matrixPtr = ArrayPointer.from(
wasm,
"double",
flatMatrix.length,
flatMatrix
);
const determinant = wasm._calculate_determinant(matrixPtr.ptr, 3, 2);
matrixPtr.free();
3. String Processing
// Natural language processing
const text = "Hello, WebAssembly world!";
const textPtr = StringPointer.from(wasm, text.length + 100, text);
wasm._analyze_sentiment(textPtr.ptr);
const analysis = textPtr.readAndFree();
4. Game Development
// Game entity positions
const positions = [
{ x: 10.5, y: 20.3, z: 0.0 },
{ x: 15.2, y: 18.7, z: 5.5 },
];
const flatPositions = positions.flatMap(p => [p.x, p.y, p.z]);
const posPtr = ArrayPointer.from(
wasm,
"float",
flatPositions.length,
flatPositions
);
wasm._update_physics(posPtr.ptr, positions.length);
const updatedPositions = posPtr.readAndFree();
5. Scientific Computing
// Signal processing
const signal = new Array(1024).fill(0).map((_, i) => Math.sin(i * 0.1));
const signalPtr = ArrayPointer.from(wasm, "double", signal.length, signal);
wasm._fft_transform(signalPtr.ptr, signal.length);
const spectrum = signalPtr.readAndFree();
📖 Quick Start Guide
Step 1: Import the Library
import {
StringPointer,
ArrayPointer,
NumberPointer,
CharPointer,
BoolPointer,
TypeConverter,
} from "wasp-lib";
import type { WASMModule } from "wasp-lib";
Step 2: Initialize Your WASM Module
// Assuming you have a WASM module generated by Emscripten
import Module from "./your-wasm-module.js";
async function initWasm() {
const wasm: WASMModule = await Module();
return wasm;
}
Step 3: Use Pointer Classes
async function example() {
const wasm = await initWasm();
// String operations
const greeting = StringPointer.from(wasm, 50, "Hello");
wasm._process_string(greeting.ptr);
console.log(greeting.readAndFree()); // "Hello World!" (modified by WASM)
// Array operations
const numbers = [1, 2, 3, 4, 5];
const arrayPtr = ArrayPointer.from(wasm, "i32", numbers.length, numbers);
const sum = wasm._sum_array(arrayPtr.ptr, numbers.length);
arrayPtr.free();
console.log(sum); // 15
// Number operations
const valuePtr = NumberPointer.from(wasm, "double", 3.14159);
wasm._square_value(valuePtr.ptr);
console.log(valuePtr.readAndFree()); // 9.869...
}
🔧 Complete API Reference
Core Classes
StringPointer
Manages C-style null-terminated strings in WASM memory.
class StringPointer extends BasePointer<string> {
// Static factory methods
static from(
wasm: WASMModule,
length: number,
input?: string
): StringPointer;
static alloc(wasm: WASMModule, length: number): StringPointer;
// Instance methods
write(input: string): void;
read(): string;
readAndFree(): string;
free(): void;
// Properties
readonly ptr: number;
readonly length: number;
readonly isValid: boolean;
}
Methods:
-
from(wasm, length, input?)
- Create from JavaScript string with specified buffer size -
alloc(wasm, length)
- Allocate empty buffer of specified length -
write(input)
- Write new string content (must fit in allocated buffer) -
read()
- Read string as JavaScript string -
readAndFree()
- Read then immediately free memory
Example:
// Create with initial content
const strPtr = StringPointer.from(wasm, 100, "Initial text");
// Modify content
strPtr.write("New content");
// Read current content
const content = strPtr.read(); // "New content"
// Clean up
strPtr.free();
// One-shot operation
const result = StringPointer.from(wasm, 50, "temp").readAndFree();
NumberPointer<T>
Type-safe wrapper for single numeric values.
class NumberPointer<T extends C_NumberType> extends BasePointer<
number | bigint
> {
// Static factory methods
static from<T>(
wasm: WASMModule,
type: T,
input: TypedValue<T>
): NumberPointer<T>;
static alloc<T>(wasm: WASMModule, type: T): NumberPointer<T>;
// Instance methods
write(value: TypedValue<T>): void;
read(): TypedValue<T>;
readAndFree(): TypedValue<T>;
// Properties
readonly type: T;
}
Supported Types:
-
'i8'
- 8-bit signed integer (-128 to 127) -
'i16'
- 16-bit signed integer (-32,768 to 32,767) -
'i32'
- 32-bit signed integer (-2,147,483,648 to 2,147,483,647) -
'i64'
- 64-bit signed integer (uses BigInt) -
'float'
- 32-bit floating point -
'double'
- 64-bit floating point
Example:
// Integer types
const intPtr = NumberPointer.from(wasm, "i32", 42);
const bigIntPtr = NumberPointer.from(wasm, "i64", 9007199254740991n);
// Floating point types
const floatPtr = NumberPointer.from(wasm, "float", 3.14);
const doublePtr = NumberPointer.from(wasm, "double", 2.718281828);
// Modify values
intPtr.write(84);
floatPtr.write(2.71);
// Read values (correct TypeScript types)
const intValue: number = intPtr.read(); // 84
const bigIntValue: bigint = bigIntPtr.read(); // 9007199254740991n
const floatValue: number = floatPtr.read(); // 2.71
// Clean up
[intPtr, bigIntPtr, floatPtr, doublePtr].forEach(ptr => ptr.free());
ArrayPointer<T, N>
Type-safe wrapper for numeric arrays with fixed-length support.
class ArrayPointer<
T extends C_NumberType,
N extends number = number,
> extends BasePointer<FixedLengthArray<TypedValue<T>, N>> {
// Static factory methods
static from<T, N>(
wasm: WASMModule,
type: T,
length: N,
input?: TypedValue<T>[]
): ArrayPointer<T, N>;
static alloc<T, N>(
wasm: WASMModule,
type: T,
length: N
): ArrayPointer<T, N>;
// Instance methods
write(values: TypedValue<T>[]): void;
add(index: number, value: TypedValue<T>): void;
read(): FixedLengthArray<TypedValue<T>, N>;
readAndFree(): FixedLengthArray<TypedValue<T>, N>;
// Properties
readonly type: T;
readonly length: N;
}
Example:
// Create from existing array
const numbers = [1.1, 2.2, 3.3, 4.4, 5.5];
const arrayPtr = ArrayPointer.from(wasm, "double", numbers.length, numbers);
// Allocate empty array
const emptyPtr = ArrayPointer.alloc(wasm, "i32", 10);
// Modify individual elements
arrayPtr.add(0, 10.5); // Set first element to 10.5
arrayPtr.add(4, 99.9); // Set last element to 99.9
// Write entire array
emptyPtr.write([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
// Read array (returns fixed-length array type)
const result = arrayPtr.read(); // FixedLengthArray<number, 5>
console.log(result); // [10.5, 2.2, 3.3, 4.4, 99.9]
// Bounds checking
try {
arrayPtr.add(10, 42); // Throws: out of bounds
} catch (error) {
console.error(error.message); // "Out-of-bounds access: tried to write at index 10 in array of length 5"
}
arrayPtr.free();
emptyPtr.free();
CharPointer
Wrapper for single character values.
class CharPointer extends BasePointer<string> {
// Static factory methods
static from(wasm: WASMModule, input: string): CharPointer;
static alloc(wasm: WASMModule): CharPointer;
// Instance methods
write(input: string): void;
read(): string;
readAndFree(): string;
}
Example:
// Create from character
const charPtr = CharPointer.from(wasm, "A");
// Modify character
charPtr.write("Z");
// Read character
const char = charPtr.read(); // 'Z'
// Validation
try {
CharPointer.from(wasm, "AB"); // Throws: must be exactly one character
} catch (error) {
console.error(error.message);
}
charPtr.free();
BoolPointer
Wrapper for boolean values (stored as C integers: 0 or 1).
class BoolPointer extends BasePointer<boolean> {
// Static factory methods
static from(wasm: WASMModule, value: boolean): BoolPointer;
static alloc(wasm: WASMModule): BoolPointer;
// Instance methods
write(value: boolean): void;
read(): boolean;
readAndFree(): boolean;
}
Example:
// Create from boolean
const boolPtr = BoolPointer.from(wasm, true);
// Toggle value
boolPtr.write(false);
// Read value
const isTrue = boolPtr.read(); // false
// Allocate with default false
const defaultPtr = BoolPointer.alloc(wasm);
console.log(defaultPtr.read()); // false
[boolPtr, defaultPtr].forEach(ptr => ptr.free());
Utility Classes
TypeConverter
Static utility class for type conversions between JavaScript and C types.
class TypeConverter {
// Boolean conversions
static boolToC(value: boolean): C_BoolType; // JS boolean → C boolean (0|1)
static boolFromC(value: C_BoolType): boolean; // C boolean → JS boolean
// Character conversions
static charToC(char: string): C_CharType; // Single char → ASCII code
static charFromC(code: C_CharType): string; // ASCII code → Single char
// Type validation
static validateNumberType(type: string): boolean; // Validate supported type
static getTypeSize(type: C_NumberType): number; // Get type size in bytes
}
Example:
// Boolean conversions
const cTrue = TypeConverter.boolToC(true); // 1
const cFalse = TypeConverter.boolToC(false); // 0
const jsTrue = TypeConverter.boolFromC(1); // true
const jsFalse = TypeConverter.boolFromC(0); // false
// Character conversions
const asciiA = TypeConverter.charToC("A"); // 65
const charFromAscii = TypeConverter.charFromC(65); // 'A'
// Type validation
const isValid = TypeConverter.validateNumberType("i32"); // true
const size = TypeConverter.getTypeSize("double"); // 8
// Error handling
try {
TypeConverter.charToC("AB"); // Throws: must be exactly one character
TypeConverter.charFromC(300); // Throws: invalid ASCII code
TypeConverter.getTypeSize("invalid"); // Throws: unsupported type
} catch (error) {
console.error(error.message);
}
Type Definitions
Core Types
// Supported C numeric types
type C_NumberType = "i8" | "i16" | "i32" | "i64" | "float" | "double";
// C boolean type (0 or 1)
type C_BoolType = 0 | 1;
// C character type (ASCII code 0-255)
type C_CharType = number;
// Type constants
const C_TRUE: C_BoolType = 1;
const C_FALSE: C_BoolType = 0;
Size Constants
// Size in bytes for each numeric type
const C_TYPE_SIZES: Record<C_NumberType, number> = {
i8: 1, // 8-bit integer
i16: 2, // 16-bit integer
i32: 4, // 32-bit integer
i64: 8, // 64-bit integer
float: 4, // 32-bit float
double: 8, // 64-bit double
};
WASM Module Interface
interface WASMModule extends EmscriptenModule {
// Memory management
_malloc(size: number): number;
_free(ptr: number): void;
// Value operations
setValue(ptr: number, value: number | bigint, type: string): void;
getValue(ptr: number, type: string): number | bigint;
// String operations
stringToUTF8(str: string, outPtr: number, maxBytesToWrite: number): void;
UTF8ToString(ptr: number): string;
lengthBytesUTF8(str: string): number;
// Function wrapping
cwrap(ident: string, returnType: string, argTypes: string[]): Function;
// File system (if enabled)
FS: typeof FS;
}
🛡️ Error Handling
wasp-lib provides comprehensive error handling with descriptive messages:
Memory Safety
// Automatic validation
const ptr = StringPointer.from(wasm, 10, "test");
ptr.free();
try {
ptr.read(); // Throws: "Cannot operate on freed or invalid pointer"
} catch (error) {
console.error(error.message);
}
Bounds Checking
// Array bounds validation
const arrayPtr = ArrayPointer.alloc(wasm, "i32", 5);
try {
arrayPtr.add(10, 42); // Throws: "Out-of-bounds access: tried to write at index 10 in array of length 5"
} catch (error) {
console.error(error.message);
}
Type Validation
// Input validation
try {
CharPointer.from(wasm, "AB"); // Throws: "Input must be exactly one character"
TypeConverter.charFromC(300); // Throws: "Invalid ASCII code: 300. Must be 0-255"
// @ts-expect-error
TypeConverter.getTypeSize("invalid"); // Throws: "Unsupported number type: invalid"
} catch (error) {
console.error(error.message);
}
Buffer Overflow Protection
// String length validation
const strPtr = StringPointer.alloc(wasm, 10);
try {
strPtr.write("This string is way too long for the buffer");
// Throws: "String length exceeds buffer size"
} catch (error) {
console.error(error.message);
}
🚀 Performance Tips
1. Reuse Pointers When Possible
// Good: Reuse pointer for multiple operations
const arrayPtr = ArrayPointer.alloc(wasm, "double", 1000);
for (let i = 0; i < iterations; i++) {
// Modify array data
arrayPtr.write(newData);
wasm._process_array(arrayPtr.ptr, 1000);
}
arrayPtr.free();
// Avoid: Creating new pointers in loops
for (let i = 0; i < iterations; i++) {
const arrayPtr = ArrayPointer.from(wasm, "double", 1000, newData); // Expensive
wasm._process_array(arrayPtr.ptr, 1000);
arrayPtr.free();
}
2. Use Appropriate Buffer Sizes
// Good: Right-sized buffer
const strPtr = StringPointer.from(wasm, input.length + 10, input); // Small overhead
// Avoid: Oversized buffers
const strPtr = StringPointer.from(wasm, 10000, input); // Wastes memory
3. Batch Operations
// Good: Process arrays in batches
const batchSize = 1000;
const arrayPtr = ArrayPointer.alloc(wasm, "float", batchSize);
for (let i = 0; i < data.length; i += batchSize) {
const batch = data.slice(i, i + batchSize);
arrayPtr.write(batch);
wasm._process_batch(arrayPtr.ptr, batch.length);
}
arrayPtr.free();
📚 Advanced Examples
Image Processing Pipeline
async function processImage(imageData: ImageData, wasm: WASMModule) {
// Convert ImageData to array
const pixels = Array.from(imageData.data);
// Create WASM array pointer
const pixelPtr = ArrayPointer.from(wasm, "i8", pixels.length, pixels);
// Apply multiple filters
wasm._blur_filter(pixelPtr.ptr, imageData.width, imageData.height, 3);
wasm._sharpen_filter(pixelPtr.ptr, imageData.width, imageData.height);
wasm._color_correction(
pixelPtr.ptr,
imageData.width,
imageData.height,
1.2
);
// Get processed data
const processedPixels = pixelPtr.readAndFree();
// Convert back to ImageData
const processedImageData = new ImageData(
new Uint8ClampedArray(processedPixels),
imageData.width,
imageData.height
);
return processedImageData;
}
Scientific Computing
class Matrix {
private data: ArrayPointer<"double", number>;
constructor(
private wasm: WASMModule,
private rows: number,
private cols: number,
initialData?: number[]
) {
this.data = ArrayPointer.from(
wasm,
"double",
rows * cols,
initialData || new Array(rows * cols).fill(0)
);
}
multiply(other: Matrix): Matrix {
if (this.cols !== other.rows) {
throw new Error(
"Matrix dimensions incompatible for multiplication"
);
}
const result = new Matrix(this.wasm, this.rows, other.cols);
this.wasm._matrix_multiply(
this.data.ptr,
other.data.ptr,
result.data.ptr,
this.rows,
this.cols,
other.cols
);
return result;
}
transpose(): Matrix {
const result = new Matrix(this.wasm, this.cols, this.rows);
this.wasm._matrix_transpose(
this.data.ptr,
result.data.ptr,
this.rows,
this.cols
);
return result;
}
toArray(): number[] {
return [...this.data.read()];
}
free(): void {
this.data.free();
}
}
Audio Processing
class AudioProcessor {
private wasm: WASMModule;
private bufferSize: number;
private leftChannel: ArrayPointer<"float", number>;
private rightChannel: ArrayPointer<"float", number>;
constructor(wasm: WASMModule, bufferSize: number) {
this.wasm = wasm;
this.bufferSize = bufferSize;
this.leftChannel = ArrayPointer.alloc(wasm, "float", bufferSize);
this.rightChannel = ArrayPointer.alloc(wasm, "float", bufferSize);
}
processAudio(
leftSamples: Float32Array,
rightSamples: Float32Array
): { left: Float32Array; right: Float32Array } {
// Copy samples to WASM memory
this.leftChannel.write([...leftSamples]);
this.rightChannel.write([...rightSamples]);
// Apply effects
this.wasm._apply_reverb(
this.leftChannel.ptr,
this.rightChannel.ptr,
this.bufferSize
);
this.wasm._apply_eq(
this.leftChannel.ptr,
this.rightChannel.ptr,
this.bufferSize
);
this.wasm._apply_compressor(
this.leftChannel.ptr,
this.rightChannel.ptr,
this.bufferSize
);
// Get processed samples
const processedLeft = new Float32Array(this.leftChannel.read());
const processedRight = new Float32Array(this.rightChannel.read());
return { left: processedLeft, right: processedRight };
}
free(): void {
this.leftChannel.free();
this.rightChannel.free();
}
}
🤝 Contributing
We welcome contributions! Please see our Contributing Guide
for details.
Development Setup
# Clone repository
git clone https://github.com/ptprashanttripathi/wasp-lib.git
cd wasp-lib
# Install dependencies
npm install
# Build TypeScript
npm run build
# Build WASM test module
npm run build:wasm
# Run tests
npm test
# Generate documentation
npm run build:docs
📄 License
This project is MIT licensed.
🙏 Acknowledgments
- Emscripten Team - For making WebAssembly accessible
- TypeScript Team - For excellent type system support
- WebAssembly Community - For pushing the boundaries of web performance
Made with ❤️ by Pt. Prashant Tripathi
⭐ Star this repo if you find it helpful!
This content originally appeared on DEV Community and was authored by Pt. Prashant tripathi

Pt. Prashant tripathi | Sciencx (2025-08-19T22:45:09+00:00) Introducing wasp-lib: A TypeScript Library for Simplified WebAssembly Memory Management. Retrieved from https://www.scien.cx/2025/08/19/introducing-wasp-lib-a-typescript-library-for-simplified-webassembly-memory-management/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.