@maravilla-labs/zephyr
v0.2.0
Published
Lightweight service worker library for intelligent caching strategies in web applications
Maintainers
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/zephyrCDN
<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 TTLCache-Control: s-maxage=X- Shared cache TTL (takes priority)Cache-Control: must-revalidate- Always check with serverExpires: <date>- Fallback TTL if no max-ageETag- 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
- Request Interception: Service worker intercepts fetch requests
- Rule Matching: Checks if URL matches any configured rule
- Cache Check: Looks for valid cached response in IndexedDB
- Revalidation: If stale, optionally revalidates with conditional request
- Network Fallback: Fetches from network if cache miss or expired
- Response Validation: Only caches successful responses (status 200-299)
- Storage: Stores response with TTL, ETag, and updates access time
- 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 buildTypeScript
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.jsis in the root directory - Look for errors in browser DevTools → Application → Service Workers
Cache not working
- Enable debug mode: add
?zephyrDebug=trueto 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 DevToolsQuota 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.
