Init and API design
This section covers the fundamental patterns for initializing TypeScript SDKs that wrap WebAssembly modules and designing APIs that provide a seamless developer experience.
Initialization Patterns
When designing a TypeScript SDK that wraps a WebAssembly module, proper initialization is crucial for both performance and developer experience. The initialization phase typically involves loading the WASM module, setting up memory management, and configuring any threading or worker pools.
This example demonstrates a robust initialization pattern that handles both browser and Node.js environments, with proper error handling and state management.
/**
* Basic TypeScript SDK initialization pattern for WASM modules
*
* This example demonstrates the standard initialization flow for a TypeScript
* SDK that wraps a WebAssembly module, handling both browser and Node.js environments.
*/
// Type definitions for the WASM module
interface WasmModule {
process_data(data: Uint8Array): Promise<Uint8Array>;
free(): void;
}
// Mock init function for demonstration - in practice this would be from wasm-bindgen
declare function init(wasmPath?: string): Promise<void>;
declare const WasmModule: {
new (): WasmModule;
};
interface InitOptions {
wasmPath?: string;
memory?: WebAssembly.Memory;
enableThreads?: boolean;
}
export class MySDK {
private wasmModule: WasmModule | null = null;
private initialized: boolean = false;
private initPromise: Promise<void> | null = null;
/**
* Initialize the SDK with optional configuration
*/
async initialize(options: InitOptions = {}): Promise<void> {
// Prevent multiple simultaneous initializations
if (this.initPromise) {
return this.initPromise;
}
this.initPromise = this._doInitialize(options);
return this.initPromise;
}
private async _doInitialize(options: InitOptions): Promise<void> {
try {
// Initialize the WASM module
await init(options.wasmPath);
// Create the main module instance
this.wasmModule = new WasmModule();
// Configure threads if enabled and supported
if (options.enableThreads && this._supportsThreads()) {
await this._initializeThreads(options.memory);
}
this.initialized = true;
} catch (error) {
this.initPromise = null;
throw new Error(`Failed to initialize SDK: ${error}`);
}
}
/**
* Check if the SDK is ready for use
*/
isInitialized(): boolean {
return this.initialized && this.wasmModule !== null;
}
/**
* Ensure the SDK is initialized before use
*/
private _ensureInitialized(): void {
if (!this.isInitialized()) {
throw new Error('SDK not initialized. Call initialize() first.');
}
}
/**
* Example API method that uses the WASM module
*/
async processData(data: Uint8Array): Promise<Uint8Array> {
this._ensureInitialized();
return this.wasmModule!.process_data(data);
}
/**
* Check if SharedArrayBuffer and threads are supported
*/
private _supportsThreads(): boolean {
return (
typeof SharedArrayBuffer !== 'undefined' && typeof Worker !== 'undefined'
);
}
/**
* Initialize thread pool for multi-threaded operations
*/
private async _initializeThreads(
customMemory?: WebAssembly.Memory
): Promise<void> {
// Thread initialization logic would go here
// This is a simplified example
console.log('Initializing thread support...');
}
/**
* Clean up resources
*/
destroy(): void {
if (this.wasmModule) {
this.wasmModule.free();
this.wasmModule = null;
}
this.initialized = false;
this.initPromise = null;
}
}
// Factory function for easier instantiation
export async function createSDK(options?: InitOptions): Promise<MySDK> {
const sdk = new MySDK();
await sdk.initialize(options);
return sdk;
}
Key Design Principles
- Asynchronous Initialization - WASM modules must be loaded asynchronously, so design your API to handle this properly
- Single Initialization - Prevent multiple simultaneous initialization attempts
- Environment Detection - Handle differences between browser and Node.js environments
- Resource Cleanup - Provide clean destruction methods for proper resource management
API Design Considerations
When wrapping WASM functionality, consider these API design patterns:
1. Factory Functions vs Class Constructors
Factory functions often provide better ergonomics for async initialization:
// Preferred: Factory function
const sdk = await createSDK(options);
// vs Class constructor requiring separate init
const sdk = new SDK();
await sdk.initialize(options);
2. Method Chaining and Fluent Interfaces
Design APIs that feel natural to TypeScript developers:
const result = await sdk
.configure({ threads: 4 })
.processData(inputData)
.withOptions({ validate: true });
3. Type Safety Across the Boundary
Ensure type safety when crossing the JavaScript-WASM boundary:
interface ProcessingOptions {
algorithm: 'fast' | 'accurate' | 'balanced';
maxMemory?: number;
validate?: boolean;
}
// TypeScript will catch invalid algorithm values
await sdk.process(data, { algorithm: 'invalid' }); // ❌ Type error
Error Handling Strategy
WASM modules can fail in unique ways, so implement comprehensive error handling:
- Initialization Errors - Module loading, memory allocation failures
- Runtime Errors - Out of bounds access, invalid operations
- Resource Errors - Memory exhaustion, thread pool saturation
Next Steps
- Learn about Memory Management in WASM contexts
- Explore Error Handling patterns
- Review Packaging strategies for distribution