npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@weave-apps/sdk

v0.1.18

Published

SDK for building Weave Micro Apps

Readme

Weave App SDK

Official SDK for building third-party applications for the Weave Platform.

Features

  • TypeScript Support - Write apps in TypeScript with full type safety
  • Clean JavaScript Output - Transpiles to readable, unobfuscated JavaScript
  • Security Review Ready - Generated code is easy to audit
  • Project Scaffolding - Quick start with weave-init
  • DOM API - Secure parent page DOM manipulation
  • Shadow DOM Isolation - Scoped styles and encapsulation
  • Background Services - Run code without user interaction
  • State Persistence - Survive page reloads with auto-persist
  • Scheduled Tasks - Cron jobs with standard cron syntax
  • Multi-Page Flows - Pending operations across navigations
  • Form Integration - Auto-fill forms with extracted data
  • AI Integration - Leverage AI for smart form filling
  • Data Persistence - Store app data with the Weave API

Table of Contents generated with DocToc

Quick Start

1. Initialize a New App

npx weave-init my-custom-app
cd my-custom-app
npm install
npm run build

2. Develop Your App

Edit src/app.ts:

import { WeaveBaseApp } from '@weave-apps/sdk';

class MyCustomApp extends WeaveBaseApp {
  constructor() {
    super({
      id: 'my-custom-app',
      name: 'My Custom App',
      version: '1.0.0',
      category: 'utility',
      description: 'My awesome app',
      author: 'Your Name',
      tags: ['custom']
    });

    this.state = {
      count: 0
    };
  }

  protected render(): void {
    this.renderHTML(`
      <style>
        .container { 
          padding: 20px; 
          font-family: system-ui, sans-serif;
        }
        button { 
          padding: 10px 20px; 
          background: #3b82f6;
          color: white;
          border: none;
          border-radius: 6px;
          cursor: pointer;
        }
        button:hover {
          background: #2563eb;
        }
      </style>
      <div class="container">
        <h1>Counter App</h1>
        <p>Count: <span id="count">${this.state.count}</span></p>
        <button id="incrementBtn">Increment</button>
      </div>
    `);
  }

  protected setupEventListeners(): void {
    this.query('#incrementBtn')?.addEventListener('click', () => {
      this.setState({ count: this.state.count + 1 });
      this.render();
    });
  }
}

customElements.define('my-custom-app', MyCustomApp);

3. Build Your App

npm run build

This runs:

  1. tsc - Compiles TypeScript to JavaScript
  2. weave-compile - Transpiles to clean, platform-ready JavaScript

Output: dist/my-custom-app.js - ready for upload to Weave Platform.

4. Upload to Weave

Upload the generated dist/my-custom-app.js file to the Weave Platform via the Enterprise Management Console.

API Reference

WeaveBaseApp

Base class for all Weave apps. Handles state management, rendering, and lifecycle hooks.

Constructor

constructor(appInfo: WeaveAppInfo)

App Info:

  • id (string) - Unique identifier (kebab-case)
  • name (string) - Display name
  • version (string) - Semantic version (x.y.z)
  • category (string) - App category
  • description (string) - Detailed description
  • author (string) - Author name
  • tags (string[])` - Optional tags
  • persistState (boolean) - Enable auto-persist across page reloads (default: false)
  • persistDebounce (number) - Debounce delay in ms for state saves (default: 100)

Settings & State

Define typed settings and state:

interface MyAppSettings {
  apiEndpoint?: string;
  debugMode?: boolean;
}

interface MyAppState {
  isLoading: boolean;
  data: any;
}

class MyApp extends WeaveBaseApp<MyAppSettings, MyAppState> {
  constructor() {
    super({ /* ... */ });
    
    this.state = {
      isLoading: false,
      data: null
    };

    // Access settings (injected from Enterprise Console)
    console.log(this.appSettings?.apiEndpoint);
  }
}

Methods to Implement

render(): void | Promise<void>

Render your app's UI. Use this.renderHTML() to inject HTML.

protected async render(): Promise<void> {
  this.renderHTML(`
    <div>Content here</div>
  `);
}
setupEventListeners(): void (Optional)

Setup event listeners for your app's elements.

protected setupEventListeners(): void {
  this.query('#myBtn')?.addEventListener('click', () => {
    // Handle click
  });
}
onBackgroundService(): void (Optional)

Background service that runs when sidebar loads, even if app drawer isn't open.

async onBackgroundService(): Promise<void> {
  const url = await window.weaveDOM.getPageUrl();
  if (url.includes('mypage.com')) {
    // Do something on background
  }
}
onUrlChange(newUrl: string): void (Optional)

Called when the page URL changes (SPA navigation).

async onUrlChange(newUrl: string): Promise<void> {
  if (newUrl.includes('form')) {
    await this.checkAndInjectButton();
  }
}
cleanup(): void (Optional)

Cleanup when app is removed from DOM. Clear intervals, listeners, watchers.

protected cleanup(): void {
  if (this.myInterval) {
    clearInterval(this.myInterval);
  }
}

Helper Methods

renderHTML(html: string): void

Renders HTML into the shadow root.

this.renderHTML('<h1>Hello</h1>');
setState(updates: object): void

Update app state (shallow merge). If persistState: true, state is automatically saved to background service.

this.setState({ isLoading: true, count: 5 });
this.background (Property)

Access the background API for state persistence and pending operations.

// Save state manually
await this.background?.saveState({ count: 5 });

// Load state manually
const state = await this.background?.loadState();

// Set pending operation before navigation
await this.background?.setPendingOperation({
  type: 'fill-form',
  data: { content: '...' }
});

// Get pending operation after navigation
const pending = await this.background?.getPendingOperation();

// Clear pending operation
await this.background?.clearPendingOperation();
query<T>(selector: string): T | null

Query a single element in shadow root.

const btn = this.query<HTMLButtonElement>('#myBtn');
queryAll<T>(selector: string): NodeListOf<T>

Query all matching elements in shadow root.

const buttons = this.queryAll<HTMLButtonElement>('button');

WeaveDOMAPI

API for securely interacting with the parent page DOM. Available globally as window.weaveDOM.

URL & Navigation

// Get current page URL
const url = await window.weaveDOM.getPageUrl();

// Open new tab
window.open(url, '_blank');

Read Operations

// Query element
const element = await window.weaveDOM.query('h1');
// Returns: { exists: boolean, value?: string, outerHTML?: string }

// Get text content
const text = await window.weaveDOM.getText('h1');

// Get form data
const formData = await window.weaveDOM.getFormData('form');
// Returns: { formId, formName, fields: [...] }

Write Operations

// Set text
await window.weaveDOM.setText('h1', 'New Title');

// Set form field value
await window.weaveDOM.setFormFieldValue('#email', '[email protected]', true);

// Click element
await window.weaveDOM.clickElement('.submit-btn');

// Remove element
await window.weaveDOM.removeElement('.old-element');

DOM Injection

// Inject HTML element
const elementId = await window.weaveDOM.injectElement(
  '.target-container',
  'beforeend',
  '<button id="my-btn">Click Me</button>',
  {
    onClick: async () => {
      console.log('Button clicked!');
    }
  }
);

// Remove injected element
await window.weaveDOM.removeInjectedElement(elementId);

Event Listeners

// Start form click listener
await window.weaveDOM.startFormClickListener(async (data) => {
  console.log('Form detected:', data.formData);
  await window.weaveDOM.stopFormClickListener();
});

// Watch element for changes
const watcherId = await window.weaveDOM.watchElement(
  '.target',
  (data) => {
    console.log('Element changed:', data.changeType);
  },
  { watchChildren: true, watchAttributes: true }
);

// Stop watching
await window.weaveDOM.unwatchElement(watcherId);

WeaveAPIClient

API for accessing backend services. Available as this.weaveAPI or window.weaveAPI.

App Data Management

// Create app data
await this.weaveAPI.appData.create({
  dataKey: 'my-key',
  data: { content: '...', timestamp: new Date() }
});

// Get all app data
const response = await this.weaveAPI.appData.getAll();
const allData = response.data || [];

// Update existing data
await this.weaveAPI.appData.update(dataId, {
  dataKey: 'my-key',
  data: { content: '...', timestamp: new Date() }
});

AI Integration

// Call AI for text analysis
const response = await this.weaveAPI.ai.chat({
  prompt: 'Extract patient name from this text: ...',
  context: 'Medical form filling',
  disableJsonExtraction: false
});

console.log(response.response);

Utilities

// Convert HTML to Markdown
const markdown = window.weaveAPI.utils.htmlToMarkdown(htmlString);

// Convert Markdown to HTML
const html = window.weaveAPI.utils.markdownToHtml(markdownString);

Advanced Topics

State Persistence (Survive Page Reloads)

Apps can persist state across page reloads using the background state service. There are two approaches:

Auto-Persist (Recommended)

Simply set persistState: true in your app config. State is automatically saved on every setState() call and restored when the app initializes after a page reload.

class MyApp extends WeaveBaseApp<Settings, State> {
  constructor() {
    super({
      id: 'my-app',
      name: 'My App',
      version: '1.0.0',
      category: 'utility',
      description: 'App with persistent state',
      author: 'Your Name',
      persistState: true,      // Enable auto-persist
      persistDebounce: 100,    // Optional: debounce delay (default 100ms)
    });
    
    this.state = { count: 0, items: [] };
  }
  
  async onBackgroundService() {
    // State is already restored automatically!
    console.log('Restored count:', this.state.count);
  }
  
  handleIncrement() {
    // Automatically saved to background service
    this.setState({ count: this.state.count + 1 });
  }
}

Multi-Page Flows (Pending Operations)

For operations that span multiple pages (e.g., extract data on page A, fill form on page B), use pending operations:

class DataTransferApp extends WeaveBaseApp {
  async handleTransferClick() {
    // Save pending operation BEFORE navigation
    await this.background?.setPendingOperation({
      type: 'fill-form',
      data: {
        content: this.state.extractedContent,
        targetForm: 'patient-notes'
      },
      targetUrl: 'https://target-system.com/form'
    });
    
    // Navigate to target page
    window.open('https://target-system.com/form', '_self');
  }
  
  async onBackgroundService() {
    // Check for pending operation after page loads
    const pending = await this.background?.getPendingOperation();
    
    if (pending?.type === 'fill-form') {
      const pageUrl = await window.weaveDOM.getPageUrl();
      
      // Verify we're on the right page
      if (pageUrl.includes('target-system.com/form')) {
        await this.fillForm(pending.data);
        await this.background?.clearPendingOperation();
      }
    }
  }
}

State TTL

Persisted state expires after 5 minutes by default. This prevents stale state from accumulating.

Scheduled Tasks (Cron Jobs)

Apps can register scheduled tasks using standard cron syntax. The browser extension acts as the master clock.

class PollingApp extends WeaveBaseApp {
  constructor() {
    super({
      id: 'polling-app',
      name: 'Polling App',
      version: '1.0.0',
      category: 'utility',
      description: 'App with scheduled polling',
      author: 'Your Name',
      persistState: true, // Important for cron!
    });
    
    this.state = { 
      cronEnabled: false,  // Track cron state
      tickCount: 0 
    };
  }
  
  async onBackgroundService() {
    // Re-register cron if it was enabled before page reload
    if (this.state.cronEnabled) {
      await this.startPolling();
    }
  }
  
  async startPolling() {
    // Register cron job - every 5 seconds
    const jobId = await this.cronTab('*/5 * * * * *', this.handleTick, 'poller');
    if (jobId) {
      this.setState({ cronEnabled: true });
    }
  }
  
  async stopPolling() {
    await this.cronUnregisterAll();
    this.setState({ cronEnabled: false });
  }
  
  handleTick() {
    this.setState({ tickCount: this.state.tickCount + 1 });
    console.log('Tick!', this.state.tickCount);
    
    // Update UI if app is open
    if (this.isConnected) {
      this.render();
      this.setupEventListeners();
    }
  }
}

🔮 Secret Hack: Persistent Cron

Cron jobs don't survive page reloads by default. The secret hack is to combine persistState: true with a cronEnabled state flag:

  1. Track cron state - Add cronEnabled: boolean to your state
  2. Set flag when starting - this.setState({ cronEnabled: true })
  3. Re-register on reload - Check this.state.cronEnabled in onBackgroundService()

This way, when the page reloads, your state is restored (including cronEnabled: true), and onBackgroundService() automatically re-registers the cron job!

Cron Syntax

┌───────────── second (0-59) - optional
│ ┌───────────── minute (0-59)
│ │ ┌───────────── hour (0-23)
│ │ │ ┌───────────── day of month (1-31)
│ │ │ │ ┌───────────── month (1-12)
│ │ │ │ │ ┌───────────── day of week (0-6)
│ │ │ │ │ │
* * * * * *

Common patterns:

  • * * * * * * - Every second
  • */5 * * * * * - Every 5 seconds
  • */30 * * * * * - Every 30 seconds
  • 0 * * * * * - Every minute
  • 0 */5 * * * * - Every 5 minutes

Settings & Configuration

Settings are injected from the Enterprise Console and displayed as auto-extracted form fields:

interface MyAppSettings {
  /**
   * @description API endpoint for the service
   * @default "https://api.example.com"
   */
  apiEndpoint?: string;

  /**
   * @description Enable debug logging
   * @default false
   */
  debugMode?: boolean;
}

class MyApp extends WeaveBaseApp<MyAppSettings, MyAppState> {
  constructor() {
    super({ /* ... */ });
    
    const endpoint = this.appSettings?.apiEndpoint || 'https://api.example.com';
    const debug = this.appSettings?.debugMode || false;
  }
}

Error Handling

try {
  const data = await this.weaveAPI.appData.getAll();
} catch (error) {
  console.error('Failed to fetch data:', error);
  this.setState({ error: error.message });
  this.render();
}

Performance Tips

  • Use async/await for API calls to avoid blocking UI
  • Debounce rapid state changes with timers
  • Clear intervals and listeners in cleanup()
  • Use isCheckingContainer flags to prevent overlapping async callbacks
  • Minimize re-renders by updating state only when needed

Build Process

The build pipeline has two steps:

  1. TypeScript Compilation (tsc)

    • Compiles TypeScript to ES2020 JavaScript
    • Outputs to dist/
  2. SDK Build (weave-compile)

    • Removes SDK imports (SDK is global)
    • Replaces SDK references with window.*
    • Generates final output ready for deployment

You can also run manually:

tsc
weave-compile

Security

  • Selector Validation - Prevents dangerous selectors
  • HTML Sanitization - Removes scripts and dangerous content
  • Attribute Whitelist - Blocks dangerous attributes
  • Shadow DOM Isolation - Scoped styles and encapsulation
  • Secure Bridge - All DOM operations go through security validation

Project Structure

my-app/
├── src/
│   └── app.ts                    # Your TypeScript app
├── dist/
│   └── my-app.js                 # Compiled output (upload this)
├── package.json
├── tsconfig.json
├── SIDEKICK_SPEC.md              # AI assistant guide
└── README.md

Troubleshooting

"TypeScript not found in SDK"

Make sure to run npm install in your app directory after initialization.

Form not filling

Check that selectors match the actual form structure. Use browser DevTools to inspect the form.

Button not injecting

Verify the target selector exists before injection attempt. Check browser console for errors.

State not persisting across page reloads

Enable persistState: true in your app config. State will automatically save on setState() and restore on page reload.

State not persisting long-term

Use this.weaveAPI.appData.* methods to persist data to backend. Background state only lasts 5 minutes.