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

zephyr-native-cache

v1.6.0

Published

Native caching module for Module Federation Metro bundles

Readme

Zephyr Native Cache

Zephyr Cloud | Zephyr Docs | Discord | Twitter | LinkedIn

A React Native native cache layer for Module Federation Metro bundles. It verifies bundle hashes, caches validated bundles on device, and supports background polling for updates.

Installation

# npm
npm install zephyr-native-cache

# yarn
yarn add zephyr-native-cache

# pnpm
pnpm add zephyr-native-cache

# bun
bun add zephyr-native-cache

Usage

Configure the Module Federation runtime plugin in your Metro MF setup:

runtimePlugins: [require.resolve('zephyr-native-cache/runtime-plugin')];

Register the cache once at app startup before loading remotes:

import { ZephyrNativeCache } from 'zephyr-native-cache';

ZephyrNativeCache.register({
  maxCacheSizeBytes: 50 * 1024 * 1024,
  maxAgeMs: 3 * 24 * 60 * 60 * 1000,
  enablePolling: true,
  pollIntervalMs: 10 * 60 * 1000,
});

Configuration

ZephyrNativeCache.register(config) accepts:

  • bundleDir: custom storage directory for cached bundles
  • maxCacheSizeBytes: max cache size before LRU eviction (default 20MB)
  • maxAgeMs: stale threshold for cache entries (default 7 days)
  • minCacheSizeBytes: minimum cache size to preserve during cleanup
  • enablePolling: start automatic manifest polling (default true)
  • pollIntervalMs: polling interval in ms (default 5 minutes)
  • forceCacheInDev: enable cache in development mode (production is always enabled)

Runtime APIs

ZephyrNativeCache is the recommended app-facing API:

  • register(config?)
  • getStatus()
  • subscribe(listener)
  • checkForUpdates(options?)
  • startUpdatePolling(intervalMs?)
  • stopUpdatePolling()
  • clearCache()
  • reloadApp() — reloads the React Native JS context after an update is ready
  • restart() — alias for reloadApp() for compatibility with existing app code

ZephyrNativeCache.register returns a BundleCacheLayer instance:

  • loadBundle(bundleUrl)
  • checkForUpdates()
  • startPolling(intervalMs?)
  • stopPolling()
  • clearCache()
  • getLoadedBundles()

checkForUpdates also supports policy options:

  • checkForUpdates({ policy: 'downloadOnly' })
  • checkForUpdates({ policy: 'downloadAndApply' })

downloadAndApply reloads the React Native JS context through the native reload path after a successful download. Avoid using it in flows that cannot tolerate an immediate reload, or persist any critical UI state before calling it.

The package also exposes status helpers as named exports:

  • getCacheStatus()
  • subscribeCacheStatus(listener)

And package-level control helpers for existing integrations:

  • checkForUpdates(options?)
  • startUpdatePolling(intervalMs?)
  • stopUpdatePolling()
  • clearCache()

The raw React Native native module is intentionally not exported from the root API. Use ZephyrNativeCache.reloadApp() instead of calling native module methods directly.

It also exposes legacy globals for manual control:

  • globalThis.__MFE_CHECK_UPDATES__(options?)
    • e.g. globalThis.__MFE_CHECK_UPDATES__({ policy: 'downloadOnly' })
    • default when omitted: { policy: 'downloadOnly' }
  • globalThis.__MFE_START_UPDATE_POLLING__(intervalMs?)
  • globalThis.__MFE_STOP_UPDATE_POLLING__()

Events

Use CacheEvents to observe cache lifecycle events:

  • bundle:load
  • poll:start
  • update:available
  • update:downloaded
  • poll:complete

Example:

import { ZephyrNativeCache } from 'zephyr-native-cache';

const cache = ZephyrNativeCache.register();

cache.events.on('bundle:load', (event) => {
  console.log('[cache]', event.status, event.remoteName);
});

cache.events.on('poll:complete', (event) => {
  console.log('[cache] poll complete', event.updated, '/', event.checked);
});

React Hook

For React Native UIs, use the built-in hook:

import { useCacheStatus } from 'zephyr-native-cache';

export function CacheStatusPanel() {
  const { status, latestUpdateEvent } = useCacheStatus();
  return null;
}

useCacheStatus exposes runtime state and raw update signals only. UI/notification behavior (toasts, banners, restart prompts, silent apply, etc.) is intentionally app-defined.

Show An Update Prompt

Use latestUpdateEvent as a raw signal to display your own update banner, toast, or modal. The hook does not force any restart UX.

import { Button, Text, View } from 'react-native';
import ZephyrNativeCache, { useCacheStatus } from 'zephyr-native-cache';

export function UpdateBanner() {
  const { latestUpdateEvent } = useCacheStatus();

  if (!latestUpdateEvent) return null;

  return (
    <View>
      <Text>Update ready for {latestUpdateEvent.remoteName}</Text>
      <Button title="Reload" onPress={() => ZephyrNativeCache.reloadApp()} />
    </View>
  );
}

Build Cache Controls

Use the facade for manual update checks and cache invalidation. These calls are safe to wire into dev tools, settings screens, or internal diagnostics.

import { Button, View } from 'react-native';
import ZephyrNativeCache, { useCacheStatus } from 'zephyr-native-cache';

export function CacheControls() {
  const { status } = useCacheStatus();

  return (
    <View>
      <Button
        title={status.isPolling ? 'Checking...' : 'Check for updates'}
        disabled={status.isPolling}
        onPress={() => {
          ZephyrNativeCache.checkForUpdates({ policy: 'downloadOnly' }).catch((error) =>
            console.warn('[cache] Failed to check for updates', error)
          );
        }}
      />
      <Button
        title="Clear cache"
        onPress={() => {
          ZephyrNativeCache.clearCache().catch((error) => {
            console.warn('[cache] Failed to clear cache', error);
          });
        }}
      />
    </View>
  );
}

Render Polling Telemetry

The status snapshot contains enough state to show polling progress, last-check results, and pending remotes.

import { Text, View } from 'react-native';
import { useCacheStatus } from 'zephyr-native-cache';

export function CacheDiagnostics() {
  const { status } = useCacheStatus();
  const pending = status.pendingUpdates.join(', ') || 'none';

  return (
    <View>
      <Text>Polling: {status.pollingEnabled ? 'enabled' : 'disabled'}</Text>
      <Text>Checking now: {status.isPolling ? 'yes' : 'no'}</Text>
      <Text>Poll interval: {Math.round(status.pollIntervalMs / 1000)}s</Text>
      <Text>Pending updates: {pending}</Text>
      {status.lastPollResult && (
        <Text>
          Last check: {status.lastPollResult.checked} checked, {status.lastPollResult.updated}{' '}
          updated
        </Text>
      )}
    </View>
  );
}

Label Remote Bundle Sources

You can map status.remotes into source badges so operators can see whether each remote came from disk cache or network. This mirrors the pattern used in the native cache test host app.

import { Text } from 'react-native';
import { useCacheStatus, type CacheStatusRemoteEntry } from 'zephyr-native-cache';

const SOURCE_LABELS: Record<string, string> = {
  'cache-hit': 'from cache',
  downloaded: 'from network',
  skipped: 'from network',
  pending: 'loading...',
};

function findEntry(
  remotes: Record<string, CacheStatusRemoteEntry>,
  name: string
): CacheStatusRemoteEntry | undefined {
  return (
    remotes[name] ??
    Object.values(remotes).find(
      (entry) => entry.remoteName.endsWith('/' + name) || entry.remoteName === name
    )
  );
}

export function RemoteSourceBadge({ name }: { name: string }) {
  const { status } = useCacheStatus();
  const entry = findEntry(status.remotes, name);
  const label = entry ? (SOURCE_LABELS[entry.status] ?? entry.status) : 'not loaded';

  return (
    <Text>
      {name}: {label}
    </Text>
  );
}

For a fuller reference, see the zephyr-native-cache-test host app, which combines these patterns into a dev tools panel and source overlay.

Requirements

  • React >=19.0.0
  • React Native >=0.79.0

License

Licensed under the Apache-2.0 License. See LICENSE for more information.