@mircdj/rest-sync-lite
v1.3.2
Published
A Zero-Dependency library for offline-first REST synchronization using IndexedDB.
Maintainers
Readme
🚀 rest-sync-lite
Zero-Dependency, Offline-First REST API Synchronization Engine.
rest-sync-lite is a lightweight TypeScript library designed to make your web applications resilient to network issues. It intercepts API calls when the user is offline, persists them securely using IndexedDB, and automatically synchronizes them when the connection is restored.
Perfect for Mobile Web Apps, PWAs, Field Service Apps, and Enterprise Dashboards that cannot afford data loss.
✨ Key Features
- 📦 Zero Runtime Dependencies: Written in pure native TypeScript. No bloat (no
axios,lodash,idb). - 💾 IndexedDB Powered: Uses the browser's native database to store large queues that survive page refreshes or device restarts.
- 🔄 FIFO Queue & Sequential Processing: Guarantees that requests are executed strictly in the order they were created.
- 🛡️ Smart Retry Policy: Exponential backoff to handle server overloads (5xx) and intelligent handling of fatal errors (4xx).
- 🔐 Auth-Aware: Supports automatic session refreshing if a token expires (401) during synchronization.
- ⚛️ React Ready: Built-in hooks for real-time connection and sync status monitoring.
- 📂 File Support: Native support for persistent
Blob,File, andFormDatauploads. - ⚡️ Priority Queue: Prioritize critical requests (
high) over background tasks (low). - 🚫 Cancellable: Cancel queued requests on demand.
- 📡 PWA & Background Sync: Fully integrated with the Web Background Sync API to finish uploads even after the browser is closed.
- 🔔 Event-Driven UI: Seamlessly integrates with toast notifications and UI feedback via a unified event system.
- 🚀 Production Ready Demo: A professional playground to test complex offline scenarios and binary uploads.
- 🌐 Framework Agnostic: Works seamlessly with Angular, Vue, Svelte, or Vanilla JS.
📦 Installation
npm install rest-sync-lite
# or
yarn add rest-sync-lite🚀 Quick Start
1. Initialization
Create a singleton instance of the library.
import { RestSyncLite } from 'rest-sync-lite';
export const apiSync = new RestSyncLite({
dbName: 'MyAppSyncDB',
maxRetries: 3,
// Optional: Function to refresh token if a 401 occurs during sync
refreshToken: async () => {
await myAuthService.refreshSession();
}
});2. Usage in your App
Replace your native fetch calls with apiSync.fetch. The signature is identical to window.fetch.
🎮 Interactive Playground
We've built a "Killer Demo" to showcase what rest-sync-lite can do. It's a full PWA that demonstrates:
- Network Simulation: Toggle "Simulate Offline" to queue requests without disconnecting your Wi-Fi.
- Real-time Monitoring: Watch the queue grow and drain in real-time.
- Background Sync: Queue a request, close the tab, and watch it sync when you reopen (Service Worker magic).
👉 Try the Live Demo or run it locally:
cd playground
npm install
npm run dev// Example 1: Saving a form (Simple JSON)
async function saveOrder(orderData: any) {
try {
const response = await apiSync.fetch('https://api.example.com/v1/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(orderData),
priority: 'high' // ⚡️ Urgent request (v1.2.0+)
});
// ...
} catch (error) {
console.error('Request failed:', error);
}
}
// Example 2: File Uploads (Blob / FormData) (v1.2.0+)
async function uploadAvatar(file: File) {
const formData = new FormData();
formData.append('avatar', file);
// Files are automatically stored in IndexedDB and retried properly!
await apiSync.fetch('https://api.example.com/v1/profile/avatar', {
method: 'POST',
body: formData,
// No Content-Type header needed for FormData, let browser handle boundary
});
}
// Example 3: Cancellable Requests (v1.2.0+)
async function syncDataWithCancel() {
const customId = 'my-sync-job-123';
// Pass a custom ID (optional)
await apiSync.fetch('https://api.example.com/v1/sync', {
method: 'POST',
id: customId,
priority: 'low'
});
// ... later if user cancels ...
await apiSync.cancelRequest(customId);
}Advanced Usage
Priority Queue ⚡️
You can specify the priority of a request using the priority option. Requests are processed in this order:
highnormal(default)low
apiSync.fetch('/api/logs', { method: 'POST', body: logs, priority: 'low' });
apiSync.fetch('/api/alert', { method: 'POST', body: alert, priority: 'high' });Cancellable Requests & Custom IDs 🚫
You can assign a custom ID to a request to track or cancel it later.
// 1. Enqueue with ID
const response = await apiSync.fetch('/api/job', { id: 'job-123' });
// 2. Cancel it (removes from Queue / DB)
const cancelled = await apiSync.cancelRequest('job-123');
if (cancelled) console.log('Request cancelled!');3. Usage with React
rest-sync-lite provides a built-in hook to make your UI reactive to network capability and sync status.
import { RestSyncLite } from '@mircdj/rest-sync-lite';
import { useRestSync } from '@mircdj/rest-sync-lite/react';
// Recommended: Initialize singleton in a separate file (e.g., api.ts)
const apiSync = new RestSyncLite({ /* config */ });
function StatusBadge() {
// ⚡️ Automatically re-renders when online/offline or syncing changes
const { isOnline, isSyncing, queueSize } = useRestSync(apiSync);
return (
<div className={`badge ${isOnline ? 'online' : 'offline'}`}>
{isOnline ? 'ONLINE' : 'OFFLINE'}
{isSyncing && <span> (Syncing...)</span>}
{queueSize > 0 && <span className="counter">{queueSize} Pending</span>}
</div>
);
}4. Background Synchronization (PWA Support) 📡
rest-sync-lite supports the Web Background Sync API. This allows your app to synchronize data even if the user closes the tab or the browser, as soon as the connectivity is restored.
Step 1: Register the Service Worker helper
In your service-worker.js (or sw.ts), import and initialize the background sync listener:
// In your service-worker.js
import { initRestSyncServiceWorker } from 'rest-sync-lite/sw';
initRestSyncServiceWorker({
maxRetries: 10, // You can use a different config for background sync
// Important: refreshToken logic in SW must not use DOM APIs (window/document)
refreshToken: async () => {
/* background-safe auth logic */
}
});Step 2: How it works
The library automatically detects if the browser supports BackgroundSync.
- Chrome/Edge/Android: If the user is offline, it registers a sync tag. The browser will "wake up" your Service Worker to send the data even if the site is closed.
- Safari/Firefox: Since they don't support the Background Sync API yet, the library gracefully falls back to standard synchronization as soon as the app is reopened.
5. Events
You can force a synchronization manually if needed (e.g., when the user clicks a "Sync Now" button), although the library listens for online events automatically.
await apiSync.syncNow();📖 API Reference
Core Methods
| Method | Returns | Description |
| :--- | :--- | :--- |
| fetch(input, init?) | Promise<Response> | Drop-in replacement for native fetch. Queues if offline. |
| syncNow() | Promise<void> | Manually triggers synchronization of all pending requests. |
| cancelRequest(id) | Promise<boolean> | Cancels a pending request by its ID. Returns true if found and removed. |
| getQueueItems() | Promise<RequestItem[]> | Returns all pending requests in the queue. Useful for UI visualization. |
| setOfflineMode(enabled) | void | Manually overrides network state (true=offline). Useful for testing or "Pause Sync". |
Properties (via React Hook or direct access)
| Property | Type | Description |
| :--- | :--- | :--- |
| isOnline | boolean | Current network status. |
| isSyncing | boolean | Whether the sync engine is currently processing the queue. |
| queueSize | number | Number of pending requests in the queue. |
Events
Listen to events using apiSync.events.on('eventName', callback).
| Event | Payload Type | Description |
| :--- | :--- | :--- |
| network:change | boolean | Triggered when network status changes (true=online). |
| queue:update | void | Fired when queue size changes (enqueue/dequeue). |
| sync:start | void | Synchronization process started. |
| sync:end | void | Synchronization process finished. |
| request-success | { id, response, item } | A queued request was sent successfully. item contains original request data. |
| request-error | { id, error, permanent, item } | A queued request failed. permanent is true if max retries exceeded. |
| request:cancelled | string (id) | A request was manually cancelled. |
Example: Queue Visualization
// Display all pending requests in your UI
const pendingRequests = await apiSync.getQueueItems();
pendingRequests.forEach(req => {
console.log(`[${req.priority}] ${req.method} ${req.url} - Retries: ${req.retryCount}`);
});⚙️ Configuration
The RestSyncLite constructor accepts a configuration object:
| Option | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| dbName | string | 'rest-sync-lite' | The name of the IndexedDB database. |
| maxRetries | number | 5 | Max recursion attempts for 5xx/Network errors before giving up. |
| refreshToken | () => Promise<void> | undefined | Async callback to execute if the sync engine encounters a 401 error. |
🛠️ Architecture
rest-sync-lite acts as a proxy between your app and the network:
- Intercept: The request is analyzed.
- Check: If ONLINE, it attempts a direct send.
- Fallback: If OFFLINE or request fails (network error), it serializes the request.
- Register (PWA): If supported, registers a Background Sync task for the Service Worker.
- Persist: The serialized request is stored in IndexedDB (Object Store).
- Sync (Main Thread): A
NetworkWatcherdetects connection restoration and triggers theSyncEngine. - Sync (Background): The Browser wakes up the Service Worker to process the queue via
processQueueInBackground.
graph TD
A[App Request] --> B{Online?}
B -- Yes --> C[Direct Fetch]
B -- No --> D[Serialize & Store in IDB]
D --> E{BG Sync Supported?}
E -- Yes --> F[Register Sync Tag]
E -- No --> G[Wait for Online Event]
F --> H((Browser BG Process))
H --> I[Wake Service Worker]
I --> J[Process Queue from IDB]
G --> K[Main Thread Sync]
K --> J🤝 Contributing
Pull Requests are welcome!
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
📄 License
Distributed under the MIT License.
🗺️ Roadmap
We are constantly improving rest-sync-lite. Here is what's coming next:
- [ ] Vue.js & Angular Wrappers: First-class support for other popular frameworks.
- [ ] SQLite / OPFS Adapters: Alternative storage engines for extremely large datasets.
- [ ] Compression: Gzip/Brotli compression for synchronized payloads.
