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 🙏

© 2026 – Pkg Stats / Ryan Hefner

pulse-updates

v1.0.9

Published

OTA updates for React Native - lightweight alternative to expo-updates

Readme

Pulse Updates

Lightweight OTA (Over-The-Air) updates for React Native apps. A simpler alternative to expo-updates with full support for React Native's New Architecture (Fabric/Bridgeless).

Features

  • Full support for React Native New Architecture (Bridgeless mode)
  • Automatic asset resolution with embedded fallback
  • Hermes bytecode compilation for faster startup
  • Incremental updates (only changed assets are downloaded)
  • Rollback protection with health checks
  • Channel-based deployments (production, staging, etc.)
  • Compatible with expo-asset resolution

Installation

npm install pulse-updates
# or
yarn add pulse-updates

iOS Setup

Add to your Podfile:

pod 'pulse-updates', :path => '../node_modules/pulse-updates'

Then run:

cd ios && pod install

Add these keys to your Info.plist:

<key>PulseUpdatesEnabled</key>
<true/>
<key>PulseUpdatesURL</key>
<string>https://your-update-server.com</string>
<key>PulseUpdatesRuntimeVersion</key>
<string>$(MARKETING_VERSION)</string>
<key>PulseUpdatesCheckOnLaunch</key>
<string>ALWAYS</string>

Android Setup

Add to your android/app/build.gradle:

dependencies {
    implementation project(':pulse-updates')
}

Add metadata to AndroidManifest.xml inside <application>:

<meta-data android:name="PulseUpdatesEnabled" android:value="true" />
<meta-data android:name="PulseUpdatesURL" android:value="https://your-update-server.com" />
<meta-data android:name="PulseUpdatesRuntimeVersion" android:value="@string/app_version" />
<meta-data android:name="PulseUpdatesCheckOnLaunch" android:value="ALWAYS" />

New Architecture (Bridgeless) Setup

For React Native 0.76+ with New Architecture enabled, update your MainApplication.kt:

import app.pulse.updates.PulseUpdatesModule
import app.pulse.updates.PulseReactHostFactory

class MainApplication : Application(), ReactApplication {

    // Extract packages into a reusable method
    private fun buildPackages(): List<ReactPackage> {
        val packages = PackageList(this).packages.toMutableList()
        // Add your custom packages here
        return packages
    }

    override val reactNativeHost: ReactNativeHost =
        object : DefaultReactNativeHost(this) {
            override fun getPackages(): List<ReactPackage> = buildPackages()
            override fun getJSMainModuleName(): String = "index"

            // Use Pulse Updates bundle
            override fun getJSBundleFile(): String? {
                return PulseUpdatesModule.getBundleFile(applicationContext)?.absolutePath
                    ?: super.getJSBundleFile()
            }

            override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
            override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
        }

    // For New Architecture: use PulseReactHostFactory
    override val reactHost: ReactHost
        get() = PulseReactHostFactory.createReactHost(
            applicationContext,
            packages = buildPackages(),
            jsMainModuleName = "index",
            useDevSupport = BuildConfig.DEBUG
        )
}

Configuration Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | PulseUpdatesEnabled | boolean | true | Enable/disable OTA updates | | PulseUpdatesURL | string | required | Your update server URL | | PulseUpdatesRuntimeVersion | string | app version | Version for update compatibility | | PulseUpdatesCheckOnLaunch | string | ALWAYS | When to check: ALWAYS, WIFI_ONLY, NEVER | | PulseUpdatesChannel | string | production | Update channel | | PulseUpdatesLaunchWaitMs | number | 0 | Wait time for update check on launch |

JavaScript API

Basic Usage

import * as PulseUpdates from 'pulse-updates';
import { initializeAssetResolver } from 'pulse-updates';

// Initialize at app startup
async function initUpdates() {
  // Refresh state from native module
  await PulseUpdates.refreshStateAsync();

  // Initialize asset resolver for images
  initializeAssetResolver(PulseUpdates.localAssets);

  // Check current state
  console.log('Update ID:', PulseUpdates.updateId);
  console.log('Is embedded:', PulseUpdates.isEmbeddedLaunch);
}

// Check for updates
async function checkUpdates() {
  const result = await PulseUpdates.checkForUpdateAsync();

  if (result.isAvailable) {
    console.log('Update available:', result.manifest?.updateId);

    // Download the update
    const fetchResult = await PulseUpdates.fetchUpdateAsync();

    if (fetchResult.isNew) {
      // Reload to apply
      await PulseUpdates.reloadAsync();
    }
  }
}

React Hook

import { usePulseUpdates } from 'pulse-updates';

function UpdateBanner() {
  const {
    isChecking,
    isDownloading,
    availableUpdate,
    downloadedUpdate,
    checkForUpdate,
    downloadUpdate,
    reload
  } = usePulseUpdates();

  if (downloadedUpdate) {
    return (
      <View>
        <Text>Update ready!</Text>
        <Button title="Restart" onPress={reload} />
      </View>
    );
  }

  if (availableUpdate) {
    return (
      <View>
        <Text>Update available</Text>
        <Button
          title={isDownloading ? "Downloading..." : "Download"}
          onPress={downloadUpdate}
          disabled={isDownloading}
        />
      </View>
    );
  }

  return null;
}

API Reference

State Properties

PulseUpdates.isEnabled        // boolean - Updates enabled
PulseUpdates.updateId         // string | null - Current update ID
PulseUpdates.runtimeVersion   // string | null - Runtime version
PulseUpdates.channel          // string | null - Update channel
PulseUpdates.isEmbeddedLaunch // boolean - Running embedded bundle
PulseUpdates.manifest         // PulseManifest | null - Current manifest
PulseUpdates.localAssets      // Record<string, string> | null - Local asset map

Methods

// Refresh state from native
await PulseUpdates.refreshStateAsync()

// Check for available updates
const result = await PulseUpdates.checkForUpdateAsync()
// Returns: { isAvailable: boolean, manifest?: PulseManifest }

// Download available update
const result = await PulseUpdates.fetchUpdateAsync()
// Returns: { isNew: boolean, manifest?: PulseManifest }

// Reload app with new update
await PulseUpdates.reloadAsync()

// Mark app as successfully launched (for rollback protection)
await PulseUpdates.markAppReady()

// Report launch failure (triggers rollback)
await PulseUpdates.reportLaunchFailure(reason: string)

CLI Commands

Publishing Updates

# Publish an update
npx pulse-updates publish --platform <ios|android> --build <number>

# With all options
npx pulse-updates publish \
  --platform ios \
  --build 42 \
  --channel production \
  --api-key your-api-key \
  --api-url https://your-server.com \
  --runtime-version 1.0.0 \
  --message "Bug fixes and improvements"

CLI Options

| Option | Description | |--------|-------------| | --platform | Target platform: ios or android (required) | | --build | Build number for this update (required) | | --channel | Update channel (default: production) | | --api-key | Server API key (or set in pulse.config.json) | | --api-url | Server URL (auto-detected from native config) | | --runtime-version | Runtime version (auto-detected from native config) | | --message | Release notes | | --skip-bundle | Skip bundle creation (use existing) |

Configuration File

Create pulse.config.json in your project root:

{
  "apiKey": "your-api-key",
  "apiUrl": "https://your-server.com",
  "channel": "production"
}

Server Requirements

Pulse Updates requires a compatible server. The server must implement:

  • POST /api/releases - Create new release
  • POST /api/assets/check - Check which assets exist
  • POST /api/assets/upload - Upload assets
  • POST /api/releases/:id/finalize - Finalize release
  • GET /api/manifest/:appId - Get latest manifest

Embedded Manifest

For offline-first support, generate an embedded manifest during your build:

node node_modules/pulse-updates/scripts/generate-embedded-manifest.mjs \
  --bundle path/to/index.bundle \
  --assets path/to/assets \
  --out path/to/output \
  --platform ios \
  --runtime-version 1.0.0

Troubleshooting

Images not loading after update

Ensure you initialize the asset resolver at app startup:

import { initializeAssetResolver } from 'pulse-updates';
import * as PulseUpdates from 'pulse-updates';

await PulseUpdates.refreshStateAsync();
initializeAssetResolver(PulseUpdates.localAssets);

Update not applying on reload

For New Architecture apps, ensure you're using PulseReactHostFactory in your MainApplication.kt.

Debug Logging

Set PULSE_DEBUG=true environment variable to enable verbose logging in native code.

License

MIT