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

@maravilla-labs/zephyr

v0.2.0

Published

Lightweight service worker library for intelligent caching strategies in web applications

Readme


Zephyr provides intelligent caching strategies for web applications using Service Workers and IndexedDB. It enables offline access, reduces network requests, and improves performance with minimal configuration.

Features

  • Simple Configuration - Define caching rules with regex patterns
  • Multiple HTTP Methods - Cache GET and POST requests (POST uses payload hashing)
  • TTL-based Expiration - Configure cache lifetime per rule
  • LRU Eviction - Automatic cache size management with configurable limits
  • Cache Invalidation - Multiple strategies for enterprise CMS integration
  • HTTP Header Support - Respects Cache-Control, ETag, Last-Modified
  • Quota Monitoring - Track storage usage with configurable limits
  • Configurable Fallback - stale-while-revalidate, stale-if-error, network-only
  • Cache Management API - Clear cache, invalidate patterns, get statistics
  • TypeScript Support - Full type definitions included
  • Lightweight - No dependencies, ~8KB minified

Installation

npm

npm install @maravilla-labs/zephyr

CDN

<script type="module" src="https://unpkg.com/@maravilla-labs/[email protected]/lib/zephrInstall.js"></script>

Quick Start

1. Create Configuration File

Create zephyrConfig.js in your project root:

// Import the worker (CDN or local path)
importScripts('https://unpkg.com/@maravilla-labs/[email protected]/lib/zephyrWorker.js');

const config = {
  rules: [
    // Cache images for 1 hour
    {
      test: '.*\\.(png|jpg|jpeg|gif|webp|svg)$',
      method: 'GET',
      cache: 60,
      maxEntries: 100
    },
    // Cache API responses for 24 hours
    {
      test: '.*\\/api\\/products',
      method: 'POST',
      cache: 1440,
      maxEntries: 50
    }
  ]
};

initZephyr(config);

2. Register the Service Worker

Add to your HTML:

<script type="module" src="https://unpkg.com/@maravilla-labs/[email protected]/lib/zephrInstall.js"></script>

Or register manually:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('./zephyrConfig.js', { scope: '/' });
}

That's it! Zephyr will now cache matching requests.

Configuration

Rule Options

| Property | Type | Required | Description | |----------|------|----------|-------------| | test | string | ✓ | Regex pattern to match request URLs | | method | string | | HTTP method (GET, POST, etc.). Matches all if omitted | | cache | number | ✓ | Cache TTL in minutes | | maxEntries | number | | Max cached entries for this rule (default: 100) | | timeout | number | | Request timeout in ms (default: 10000) | | fallback | object | | Fallback strategy configuration |

Pattern Examples

// Match any .jpg or .png file
'.*\\.(jpg|png)$'

// Match specific API endpoint
'.*\\/api\\/v1\\/products$'

// Match any request to a domain
'^https://api\\.example\\.com/.*'

// Match JSON files
'.*\\.json$'

Complete Example

importScripts('https://unpkg.com/@maravilla-labs/[email protected]/lib/zephyrWorker.js');

const config = {
  rules: [
    // Static assets - long cache with stale fallback
    {
      test: '.*\\.(png|jpg|jpeg|gif|webp|svg|ico)$',
      method: 'GET',
      cache: 1440,      // 24 hours
      maxEntries: 200,
      fallback: {
        strategy: 'stale-if-error',
        maxStaleAge: 2880  // Serve stale up to 48h on error
      }
    },
    // API with background refresh
    {
      test: '.*\\/api\\/.*',
      method: 'GET',
      cache: 5,         // 5 minutes
      maxEntries: 100,
      fallback: {
        strategy: 'stale-while-revalidate'
      }
    },
    // Critical endpoints - never stale
    {
      test: '.*\\/api\\/checkout',
      method: 'POST',
      cache: 1,
      fallback: {
        strategy: 'network-only'
      }
    }
  ],

  // Quota monitoring
  quota: {
    maxSize: 50 * 1024 * 1024,  // 50MB
    warningThreshold: 0.8,
    onQuotaExceeded: 'evict-lru'
  }
};

initZephyr(config);

Invalidation

Zephyr supports multiple cache invalidation strategies for enterprise use cases.

HTTP Standard Headers (Default)

By default, Zephyr respects standard HTTP cache headers:

const config = {
  invalidation: {
    type: 'http',
    respectHttpHeaders: true  // Default
  },
  rules: [...]
};

Supported headers:

  • Cache-Control: max-age=X - Override rule TTL
  • Cache-Control: s-maxage=X - Shared cache TTL (takes priority)
  • Cache-Control: must-revalidate - Always check with server
  • Expires: <date> - Fallback TTL if no max-age
  • ETag - Used for conditional requests (If-None-Match)
  • Last-Modified - Used for conditional requests (If-Modified-Since)

Manifest-Based Polling

For CMS integration, use manifest polling:

const config = {
  invalidation: {
    type: 'manifest',
    url: '/api/cache-manifest.json',
    interval: 60000  // Poll every 60 seconds
  },
  rules: [...]
};

Expected manifest format:

{
  "version": "2025-01-11T22:00:00Z",
  "patterns": {
    ".*\\/api\\/products": "2025-01-11T21:30:00Z",
    ".*\\.(jpg|png)": "2025-01-10T10:00:00Z"
  }
}

When version changes, all cache is invalidated. Pattern-specific timestamps invalidate matching entries cached before that time.

Custom Header Invalidation

For version-based invalidation via response headers:

const config = {
  invalidation: {
    type: 'header',
    header: 'X-Cache-Version',
    compare: (cached, current) => cached !== current
  },
  rules: [...]
};

Manual Invalidation API

Invalidate cache programmatically from your page:

// Invalidate all entries matching pattern
await zephyr.invalidate('.*\\/api\\/products');

// Invalidate specific URL
await zephyr.invalidateUrl('https://example.com/api/user/123');

// Clear all cache
await zephyr.clear();

Quota Monitoring

Track and manage cache storage usage:

const config = {
  quota: {
    maxSize: 50 * 1024 * 1024,  // 50MB limit
    warningThreshold: 0.8,      // Warn at 80%
    onQuotaExceeded: 'evict-lru'  // or 'stop-caching' or 'clear-all'
  },
  rules: [...]
};

Quota Events

Listen for quota warnings in your page:

zephyr.onQuotaWarning((event) => {
  console.warn(`Cache at ${(event.percentage * 100).toFixed(1)}%`);
  console.log(`Used: ${event.used} / ${event.max} bytes`);
});

Check Usage

const usage = await zephyr.quota();
// {
//   used: 25165824,
//   max: 52428800,
//   percentage: "48.0%",
//   available: 27262976
// }

Fallback Strategies

Configure per-rule behavior when cache is stale or network fails:

stale-if-error (Default)

Return stale cache only when network request fails:

{
  test: '.*\\/api\\/.*',
  cache: 5,
  fallback: {
    strategy: 'stale-if-error',
    maxStaleAge: 1440  // Max 24 hours stale
  }
}

stale-while-revalidate

Return cache immediately, refresh in background:

{
  test: '.*\\/api\\/products',
  cache: 5,
  fallback: {
    strategy: 'stale-while-revalidate',
    maxStaleAge: 60  // Max 1 hour stale
  }
}

network-only

Never use stale cache - always require fresh data:

{
  test: '.*\\/api\\/checkout',
  cache: 1,
  fallback: {
    strategy: 'network-only'
  }
}

API

Zephyr exposes a client-side API via window.zephyr:

Cache Management

// Clear all cached entries
await zephyr.clear();

// Clear entries matching a pattern
await zephyr.clearPattern('.*\\.jpg$');

// Invalidate by pattern (alias for clearPattern)
await zephyr.invalidate('.*\\/api\\/.*');

// Invalidate specific URL
await zephyr.invalidateUrl('https://example.com/api/data');

Statistics

const stats = await zephyr.stats();
// {
//   hits: 150,
//   misses: 23,
//   errors: 0,
//   evictions: 5,
//   revalidations: 12,
//   entries: 47,
//   storageUsed: 5242880,
//   storageUsedMB: "5.00 MB",
//   hitRate: "86.7%"
// }

Quota Usage

const quota = await zephyr.quota();
// {
//   used: 5242880,
//   max: 52428800,
//   percentage: "10.0%",
//   available: 47185920
// }

Quota Events

zephyr.onQuotaWarning((event) => {
  console.warn('Cache usage at', event.percentage * 100, '%');
});

Debug Mode

// Toggle debug mode
await zephyr.debug();

Or add ?zephyrDebug=true to any URL.

Ready State

await zephyr.ready();
console.log('Service worker is active');

Real-time Invalidation

For immediate cache invalidation (without polling), use WebSockets or SSE:

// In your page - connect to CMS dispatcher
const eventSource = new EventSource('/api/cache-events');

eventSource.addEventListener('publish', (event) => {
  const data = JSON.parse(event.data);
  zephyr.invalidate(data.pattern);
});

eventSource.addEventListener('unpublish', (event) => {
  const data = JSON.parse(event.data);
  zephyr.invalidateUrl(data.url);
});

How It Works

  1. Request Interception: Service worker intercepts fetch requests
  2. Rule Matching: Checks if URL matches any configured rule
  3. Cache Check: Looks for valid cached response in IndexedDB
  4. Revalidation: If stale, optionally revalidates with conditional request
  5. Network Fallback: Fetches from network if cache miss or expired
  6. Response Validation: Only caches successful responses (status 200-299)
  7. Storage: Stores response with TTL, ETag, and updates access time
  8. Eviction: Removes oldest entries when limits are reached

Security Features

Zephyr automatically:

  • Skips error responses (4xx, 5xx status codes)
  • Respects Cache-Control headers (no-store, no-cache)
  • Ignores Set-Cookie responses (user-specific data)
  • Applies request timeouts (configurable, default 10s)
  • Uses conditional requests (ETag, If-None-Match)

Browser Support

| Browser | Support | |---------|---------| | Chrome | 60+ | | Firefox | 44+ | | Safari | 11.1+ | | Edge | 17+ | | Opera | 47+ |

Requirements:

  • HTTPS or localhost (Service Workers require secure context)
  • IndexedDB support

Development

# Install dependencies
pnpm install

# Run dev server
pnpm dev

# Run tests
pnpm test

# Build for production
pnpm build

TypeScript

Type definitions are included. Import types:

import type {
  ZephyrConfig,
  ZephyrRule,
  ZephyrStats,
  QuotaConfig,
  FallbackConfig,
  InvalidationConfig
} from '@maravilla-labs/zephyr';

const config: ZephyrConfig = {
  rules: [
    {
      test: '.*\\.json$',
      cache: 60,
      maxEntries: 50,
      fallback: {
        strategy: 'stale-while-revalidate',
        maxStaleAge: 120
      }
    }
  ],
  quota: {
    maxSize: 50 * 1024 * 1024,
    warningThreshold: 0.8,
    onQuotaExceeded: 'evict-lru'
  }
};

Troubleshooting

Service worker not registering

  • Ensure you're on HTTPS or localhost
  • Check that zephyrConfig.js is in the root directory
  • Look for errors in browser DevTools → Application → Service Workers

Cache not working

  • Enable debug mode: add ?zephyrDebug=true to URL
  • Check console for cache hit/miss logs
  • Verify your regex patterns match the URLs

Clear stuck cache

// In browser console
await zephyr.clear();
// Or unregister service worker in DevTools

Quota issues

// Check current usage
const quota = await zephyr.quota();
console.log(quota.percentage);

// Clear if needed
await zephyr.clear();

Contributing

Contributions welcome! Please read our contributing guidelines and submit PRs to the GitHub repository.

License

Apache License 2.0 - see LICENSE for details.