CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-finos--perspective-viewer

The `<perspective-viewer>` Custom Element, frontend for Perspective.js, providing interactive analytics and data visualization.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

wasm-init.mddocs/

WebAssembly Initialization

Manual WebAssembly initialization for advanced use cases and custom deployments.

Capabilities

Manual Initialization

Initialize the perspective-viewer WebAssembly module manually when automatic initialization is not suitable.

/**
 * Initialize the perspective-viewer WebAssembly client
 * @param wasm_binary - WebAssembly binary data in various formats
 */
function init_client(
  wasm_binary: Promise<Response | ArrayBuffer | Uint8Array> 
    | Response 
    | ArrayBuffer 
    | Uint8Array
): Promise<void>;

/**
 * Default export providing init_client function
 */
export default { init_client };

Initialization Examples

Basic Manual Initialization

import { init_client } from "@finos/perspective-viewer";

// Initialize with WebAssembly binary URL
const wasmResponse = fetch("/path/to/perspective-viewer.wasm");
await init_client(wasmResponse);

// Now the custom elements are available
const viewer = document.createElement("perspective-viewer");

Initialize with ArrayBuffer

import { init_client } from "@finos/perspective-viewer";

// Load WASM binary as ArrayBuffer
const wasmBinary = await fetch("/path/to/perspective-viewer.wasm")
  .then(response => response.arrayBuffer());

await init_client(wasmBinary);

// Custom elements are now registered
const viewer = document.createElement("perspective-viewer");

Initialize with Uint8Array

import { init_client } from "@finos/perspective-viewer";

// Example: Load from base64 or other encoded format
const base64Wasm = "AGFzbQEAAAABhQEYY..."; // Base64 encoded WASM
const wasmBytes = Uint8Array.from(atob(base64Wasm), c => c.charCodeAt(0));

await init_client(wasmBytes);

Using Default Export

import perspectiveViewer from "@finos/perspective-viewer";

// Alternative import pattern using default export
const wasmResponse = fetch("/path/to/perspective-viewer.wasm");
await perspectiveViewer.init_client(wasmResponse);

// Custom elements are now registered
const viewer = document.createElement("perspective-viewer");

Advanced Initialization Patterns

Conditional Initialization

import { init_client } from "@finos/perspective-viewer";

class PerspectiveManager {
  private initialized = false;
  
  async ensureInitialized() {
    if (!this.initialized) {
      await this.initializePerspective();
      this.initialized = true;
    }
  }
  
  private async initializePerspective() {
    try {
      // Try CDN first
      const cdnWasm = fetch("https://cdn.jsdelivr.net/npm/@finos/perspective-viewer/dist/cdn/perspective-viewer.wasm");
      await init_client(cdnWasm);
    } catch (error) {
      console.warn("CDN initialization failed, trying local:", error);
      
      // Fallback to local WASM
      const localWasm = fetch("/assets/perspective-viewer.wasm");
      await init_client(localWasm);
    }
  }
  
  async createViewer(): Promise<HTMLPerspectiveViewerElement> {
    await this.ensureInitialized();
    return document.createElement("perspective-viewer");
  }
}

// Usage
const manager = new PerspectiveManager();
const viewer = await manager.createViewer();

Lazy Loading with Error Handling

import { init_client } from "@finos/perspective-viewer";

class LazyPerspectiveLoader {
  private initPromise: Promise<void> | null = null;
  
  async loadPerspective(): Promise<void> {
    if (!this.initPromise) {
      this.initPromise = this.performInitialization();
    }
    return this.initPromise;
  }
  
  private async performInitialization(): Promise<void> {
    const startTime = performance.now();
    
    try {
      // Show loading indicator
      this.showLoadingIndicator();
      
      // Load WASM with timeout
      const wasmPromise = fetch("/wasm/perspective-viewer.wasm");
      const timeoutPromise = new Promise<never>((_, reject) => {
        setTimeout(() => reject(new Error("WASM load timeout")), 10000);
      });
      
      const wasmResponse = await Promise.race([wasmPromise, timeoutPromise]);
      
      if (!wasmResponse.ok) {
        throw new Error(`Failed to load WASM: ${wasmResponse.status}`);
      }
      
      await init_client(wasmResponse);
      
      const loadTime = performance.now() - startTime;
      console.log(`Perspective initialized in ${loadTime.toFixed(2)}ms`);
      
    } catch (error) {
      console.error("Failed to initialize Perspective:", error);
      this.showErrorState(error);
      throw error;
    } finally {
      this.hideLoadingIndicator();
    }
  }
  
  private showLoadingIndicator() {
    const indicator = document.createElement("div");
    indicator.id = "perspective-loading";
    indicator.textContent = "Loading Perspective Viewer...";
    indicator.style.cssText = `
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      padding: 20px;
      background: rgba(0,0,0,0.8);
      color: white;
      border-radius: 5px;
      z-index: 10000;
    `;
    document.body.appendChild(indicator);
  }
  
  private hideLoadingIndicator() {
    const indicator = document.getElementById("perspective-loading");
    if (indicator) {
      indicator.remove();
    }
  }
  
  private showErrorState(error: Error) {
    const errorDiv = document.createElement("div");
    errorDiv.textContent = `Failed to load Perspective: ${error.message}`;
    errorDiv.style.cssText = `
      color: red;
      padding: 10px;
      border: 1px solid red;
      background: #ffe6e6;
      margin: 10px;
    `;
    document.body.appendChild(errorDiv);
    
    // Provide retry button
    const retryBtn = document.createElement("button");
    retryBtn.textContent = "Retry";
    retryBtn.onclick = () => {
      errorDiv.remove();
      this.initPromise = null; // Reset for retry
      this.loadPerspective();
    };
    errorDiv.appendChild(retryBtn);
  }
}

// Usage
const loader = new LazyPerspectiveLoader();

document.getElementById("load-perspective")?.addEventListener("click", async () => {
  try {
    await loader.loadPerspective();
    
    // Create viewer after successful initialization
    const viewer = document.createElement("perspective-viewer");
    document.body.appendChild(viewer);
  } catch (error) {
    console.error("Could not load Perspective:", error);
  }
});

Progressive Web App (PWA) Integration

import { init_client } from "@finos/perspective-viewer";

class PWAPerspectiveLoader {
  private readonly CACHE_NAME = "perspective-wasm-v1";
  private readonly WASM_URL = "/wasm/perspective-viewer.wasm";
  
  async initializeWithCaching(): Promise<void> {
    try {
      // Try to get from cache first
      const cachedWasm = await this.getCachedWasm();
      if (cachedWasm) {
        console.log("Using cached WASM");
        await init_client(cachedWasm);
        return;
      }
      
      // Download and cache
      console.log("Downloading WASM");
      const wasmResponse = await fetch(this.WASM_URL);
      
      if (!wasmResponse.ok) {
        throw new Error(`HTTP ${wasmResponse.status}: ${wasmResponse.statusText}`);
      }
      
      // Cache for future use
      await this.cacheWasm(wasmResponse.clone());
      
      // Initialize
      await init_client(wasmResponse);
      
    } catch (error) {
      console.error("WASM initialization failed:", error);
      throw error;
    }
  }
  
  private async getCachedWasm(): Promise<Response | null> {
    if (!("caches" in window)) {
      return null;
    }
    
    const cache = await caches.open(this.CACHE_NAME);
    return await cache.match(this.WASM_URL);
  }
  
  private async cacheWasm(response: Response): Promise<void> {
    if (!("caches" in window)) {
      return;
    }
    
    const cache = await caches.open(this.CACHE_NAME);
    await cache.put(this.WASM_URL, response);
  }
  
  async clearCache(): Promise<void> {
    if ("caches" in window) {
      await caches.delete(this.CACHE_NAME);
    }
  }
}

// Usage in PWA
const pwaLoader = new PWAPerspectiveLoader();

// Initialize on app startup
pwaLoader.initializeWithCaching()
  .then(() => {
    console.log("Perspective ready");
    // App is ready to create viewers
  })
  .catch(error => {
    console.error("Perspective initialization failed:", error);
    // Show fallback UI
  });

Worker Thread Initialization

// main.ts
import { init_client } from "@finos/perspective-viewer";

class WorkerPerspectiveLoader {
  async initializeWithWorker(): Promise<void> {
    return new Promise((resolve, reject) => {
      const worker = new Worker("/js/wasm-loader-worker.js");
      
      worker.postMessage({ action: "loadWasm", url: "/wasm/perspective-viewer.wasm" });
      
      worker.onmessage = async (event) => {
        const { action, data, error } = event.data;
        
        if (action === "wasmLoaded") {
          try {
            await init_client(data);
            resolve();
          } catch (err) {
            reject(err);
          } finally {
            worker.terminate();
          }
        } else if (action === "error") {
          reject(new Error(error));
          worker.terminate();
        }
      };
      
      worker.onerror = (error) => {
        reject(error);
        worker.terminate();
      };
      
      // Timeout after 30 seconds
      setTimeout(() => {
        reject(new Error("Worker timeout"));
        worker.terminate();
      }, 30000);
    });
  }
}
// wasm-loader-worker.js (Web Worker)
self.onmessage = async function(event) {
  const { action, url } = event.data;
  
  if (action === "loadWasm") {
    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      const arrayBuffer = await response.arrayBuffer();
      
      self.postMessage({
        action: "wasmLoaded",
        data: arrayBuffer
      });
    } catch (error) {
      self.postMessage({
        action: "error",
        error: error.message
      });
    }
  }
};

Initialization Options

Bundle Size Optimization

// Use specific entry points to reduce bundle size
import { init_client } from "@finos/perspective-viewer/dist/esm/bootstrap";

// Initialize without side effects
await init_client(wasmBinary);

// Only then import the components you need
const { PerspectiveViewerElement } = await import("@finos/perspective-viewer/dist/esm/perspective-viewer");

Development vs Production

import { init_client } from "@finos/perspective-viewer";

async function initializePerspective() {
  const isDevelopment = process.env.NODE_ENV === "development";
  
  if (isDevelopment) {
    // Development: Use local file with source maps
    const wasmResponse = fetch("/dev/perspective-viewer.wasm");
    await init_client(wasmResponse);
  } else {
    // Production: Use CDN with compression
    const wasmResponse = fetch("https://cdn.jsdelivr.net/npm/@finos/perspective-viewer/dist/cdn/perspective-viewer.wasm");
    await init_client(wasmResponse);
  }
}

Error Handling

import { init_client } from "@finos/perspective-viewer";

async function robustInitialization() {
  const wasmSources = [
    "https://cdn.jsdelivr.net/npm/@finos/perspective-viewer/dist/cdn/perspective-viewer.wasm",
    "https://unpkg.com/@finos/perspective-viewer/dist/cdn/perspective-viewer.wasm",
    "/local/perspective-viewer.wasm"
  ];
  
  for (const source of wasmSources) {
    try {
      console.log(`Attempting to load WASM from: ${source}`);
      const response = await fetch(source);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      await init_client(response);
      console.log(`Successfully initialized from: ${source}`);
      return;
      
    } catch (error) {
      console.warn(`Failed to load from ${source}:`, error);
      continue;
    }
  }
  
  throw new Error("All WASM sources failed to load");
}

Initialization States

enum InitializationState {
  NotStarted = "not-started",
  Loading = "loading", 
  Ready = "ready",
  Failed = "failed"
}

class PerspectiveInitializer {
  private state = InitializationState.NotStarted;
  private initPromise: Promise<void> | null = null;
  private error: Error | null = null;
  
  getState(): InitializationState {
    return this.state;
  }
  
  getError(): Error | null {
    return this.error;
  }
  
  async initialize(): Promise<void> {
    if (this.state === InitializationState.Ready) {
      return;
    }
    
    if (this.state === InitializationState.Loading) {
      return this.initPromise!;
    }
    
    this.state = InitializationState.Loading;
    this.error = null;
    
    this.initPromise = this.performInitialization();
    
    try {
      await this.initPromise;
      this.state = InitializationState.Ready;
    } catch (error) {
      this.state = InitializationState.Failed;
      this.error = error as Error;
      throw error;
    }
  }
  
  private async performInitialization(): Promise<void> {
    const wasmResponse = await fetch("/wasm/perspective-viewer.wasm");
    await init_client(wasmResponse);
  }
}

// Usage with state management
const initializer = new PerspectiveInitializer();

// Check state before using
if (initializer.getState() !== InitializationState.Ready) {
  await initializer.initialize();
}

const viewer = document.createElement("perspective-viewer");

docs

configuration.md

core-element.md

events.md

index.md

plugin-development.md

wasm-init.md

tile.json