Introducing wasp-lib: A TypeScript Library for Simplified WebAssembly Memory Management

“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 tha…


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


Print Share Comment Cite Upload Translate Updates
APA

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/

MLA
" » Introducing wasp-lib: A TypeScript Library for Simplified WebAssembly Memory Management." Pt. Prashant tripathi | Sciencx - Tuesday August 19, 2025, https://www.scien.cx/2025/08/19/introducing-wasp-lib-a-typescript-library-for-simplified-webassembly-memory-management/
HARVARD
Pt. Prashant tripathi | Sciencx Tuesday August 19, 2025 » Introducing wasp-lib: A TypeScript Library for Simplified WebAssembly Memory Management., viewed ,<https://www.scien.cx/2025/08/19/introducing-wasp-lib-a-typescript-library-for-simplified-webassembly-memory-management/>
VANCOUVER
Pt. Prashant tripathi | Sciencx - » Introducing wasp-lib: A TypeScript Library for Simplified WebAssembly Memory Management. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/08/19/introducing-wasp-lib-a-typescript-library-for-simplified-webassembly-memory-management/
CHICAGO
" » Introducing wasp-lib: A TypeScript Library for Simplified WebAssembly Memory Management." Pt. Prashant tripathi | Sciencx - Accessed . https://www.scien.cx/2025/08/19/introducing-wasp-lib-a-typescript-library-for-simplified-webassembly-memory-management/
IEEE
" » Introducing wasp-lib: A TypeScript Library for Simplified WebAssembly Memory Management." Pt. Prashant tripathi | Sciencx [Online]. Available: https://www.scien.cx/2025/08/19/introducing-wasp-lib-a-typescript-library-for-simplified-webassembly-memory-management/. [Accessed: ]
rf:citation
» Introducing wasp-lib: A TypeScript Library for Simplified WebAssembly Memory Management | Pt. Prashant tripathi | Sciencx | 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.

You must be logged in to translate posts. Please log in or register.