The Universal Sync Layer - Offline-first store-and-forward engine for any JavaScript environment
npm install @sync-vault-js/coreThe Universal Sync Layer
_Write your offline logic once. Runs on Web, Mobile, and Desktop._



---
SyncVault is an offline-first "Store-and-Forward" engine for JavaScript/TypeScript applications. It seamlessly handles network failures by automatically queuing requests when offline and syncing them with exponential backoff when connectivity returns.
- ๐ Automatic Request Interception - Queues requests when offline
- ๐พ Persistent Storage - IndexedDB for browsers, memory adapter for SSR/Node
- โก Exponential Backoff - Smart retry logic with jitter
- ๐ก Real-time Events - Subscribe to sync status, job completion, and errors
- ๐ฏ Framework Agnostic - Works with React, Vue, Angular, Svelte, or vanilla JS
- ๐ฑ Universal - Browser, Node.js, React Native ready
- ๐ Type Safe - Full TypeScript support with strict typing
---
``bash`
npm install @sync-vault-js/coreor
yarn add @sync-vault-js/coreor
pnpm add @sync-vault-js/core
---
Want to see SyncVault in action? We've included a fully interactive demo application!
`bashClone the repository
git clone https://github.com/syncvault/sync-vault-js.git
cd sync-vault-js
The demo will open in your browser at
http://localhost:8000 and showcases:- โ
Real-time request queuing
- โ
Online/offline detection
- โ
Queue management UI
- โ
Event logging
- โ
Processing controls
- โ
Dead Letter Queue visualization
Or use a simple HTTP server:
`bash
cd demo
python3 -m http.server 8000
Then open http://localhost:8000
`demo/README.md for more details.---
Quick Start
$3
`typescript
import { createSyncVault } from "@sync-vault-js/core";// Create a SyncVault instance
const vault = createSyncVault({
debug: true,
retry: {
maxRetries: 3,
initialDelay: 1000,
},
});
// Make requests - automatically queued when offline
const response = await vault.post("/api/users", {
name: "John Doe",
email: "john@example.com",
});
// Check if request was queued
if (response.fromQueue) {
console.log("Request queued for later sync");
}
// Listen to events
vault.on("job_success", (event) => {
console.log("Job completed:", event.data.job.id);
});
vault.on("network_offline", () => {
console.log("Gone offline - requests will be queued");
});
vault.on("network_online", () => {
console.log("Back online - processing queue");
});
`---
Framework Integrations
$3
`tsx
import { useSyncVault } from "@sync-vault-js/core/react";function TodoApp() {
const { isOnline, isProcessing, queueLength, post } = useSyncVault();
const addTodo = async (title: string) => {
const response = await post("/api/todos", { title });
if (response.fromQueue) {
// Show optimistic UI
toast.info("Saved offline - will sync when online");
}
};
return (
online={isOnline}
syncing={isProcessing}
pending={queueLength}
/>
);
}
`#### Additional React Hooks
`tsx
import {
useSyncVault,
useOnlineStatus,
useQueueStatus,
useSyncRequest,
useSyncVaultEvent,
} from "@sync-vault-js/core/react";// Simple online status
function OnlineIndicator() {
const isOnline = useOnlineStatus();
return {isOnline ? "๐ข" : "๐ด"};
}
// Queue status with auto-refresh
function QueueStatus() {
const { length, isProcessing } = useQueueStatus();
return (
{length} pending, {isProcessing ? "syncing..." : "idle"}
);
}
// Request with loading/error state
function CreateUser() {
const { execute, loading, error, queued } = useSyncRequest();
const handleSubmit = async (data) => {
await execute({
url: "/api/users",
method: "POST",
data,
});
};
if (loading) return ;
if (error) return ;
if (queued) return Queued for sync ;
return
;
}// Event subscription
function SyncNotifications() {
useSyncVaultEvent("job_success", (event) => {
toast.success(
Synced: ${event.data.job.url});
}); useSyncVaultEvent("job_failed", (event) => {
if (!event.data.willRetry) {
toast.error(
Failed: ${event.data.error.message});
}
}); return null;
}
`---
$3
`vue
:online="isOnline"
:syncing="isProcessing"
:pending="queueLength"
/>
`#### Additional Vue Composables
`typescript
import {
useSyncVault,
useOnlineStatus,
useQueueStatus,
useSyncRequest,
useSyncVaultEvent,
useJobWatcher,
} from "@sync-vault-js/core/vue";// Online status
const isOnline = useOnlineStatus();
// Queue status
const { length, isProcessing, refresh } = useQueueStatus();
// Request with state
const { execute, data, loading, error, queued, reset } = useSyncRequest();
// Watch a specific job
const jobId = ref(null);
const { completed, success, error } = useJobWatcher(jobId);
`---
$3
`typescript
// sync-vault.service.ts
import { Injectable } from "@angular/core";
import { SyncVaultService } from "@sync-vault-js/core/angular";@Injectable({ providedIn: "root" })
export class AppSyncVaultService extends SyncVaultService {
constructor() {
super({ debug: true });
}
}
// todo.component.ts
import { Component, OnInit, OnDestroy } from "@angular/core";
import { Subscription } from "rxjs";
import { AppSyncVaultService } from "./sync-vault.service";
@Component({
selector: "app-todo",
template:
,
})
export class TodoComponent implements OnInit, OnDestroy {
state = { isOnline: true, queueLength: 0, isProcessing: false };
private subscription = new Subscription(); constructor(private syncVault: AppSyncVaultService) {}
ngOnInit() {
this.subscription.add(
this.syncVault.state$.subscribe((state) => {
this.state = state;
})
);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
async addTodo() {
const response = await this.syncVault.post("/api/todos", {
title: "New Todo",
});
if (response.fromQueue) {
alert("Saved offline - will sync when online");
}
}
}
`---
$3
`svelte
online={$isOnline}
syncing={$isProcessing}
pending={$queueLength}
/>
addTodo(e.detail.title)} />
`#### Additional Svelte Stores
`typescript
import {
createSyncVaultStores,
createSyncVaultActions,
createEventStore,
createJobStore,
createRequestStore,
} from "@sync-vault-js/core/svelte";// Create all stores
const { state, isOnline, isProcessing, queueLength, destroy } =
createSyncVaultStores();
// Create actions
const { post, get, clearQueue } = createSyncVaultActions();
// Event store
const successEvents = createEventStore("job_success");
// Track specific job
const jobStatus = createJobStore("job-123");
// Request with state management
const { data, loading, error, queued, execute, reset } = createRequestStore();
`---
Configuration
`typescript
import { createSyncVault } from "@sync-vault-js/core";const vault = createSyncVault({
// Storage configuration
dbName: "my-app-sync", // IndexedDB database name
// Queue configuration
queue: {
concurrency: 1, // Process one job at a time
processingDelay: 100, // Delay between jobs (ms)
maxSize: 0, // Max queue size (0 = unlimited)
},
// Retry configuration
retry: {
maxRetries: 3, // Max retry attempts
initialDelay: 1000, // Initial backoff delay (ms)
maxDelay: 30000, // Maximum delay cap (ms)
multiplier: 2, // Exponential multiplier
jitter: true, // Add randomness to prevent thundering herd
},
// Behavior
debug: false, // Enable debug logging
autoStart: true, // Auto-start processing when online
// Custom adapters (advanced)
storage: customStorageAdapter,
networkChecker: customNetworkChecker,
httpClient: customHttpClient,
});
`---
Events
SyncVault emits various events throughout its lifecycle:
| Event | Description | Data |
| ----------------- | ------------------------------ | ------------------------------------------- |
|
sync_started | Queue processing started | - |
| sync_completed | Queue processing finished | - |
| sync_paused | Processing paused | - |
| job_queued | New job added to queue | { job, queueLength } |
| job_started | Job processing started | { job } |
| job_success | Job completed successfully | { job, response, duration } |
| job_failed | Job failed | { job, error, willRetry } |
| job_retry | Job will be retried | { job, attempt, maxRetries, nextRetryIn } |
| job_dead | Job moved to Dead Letter Queue | { job, error, movedToDLQ } |
| network_online | Device came online | - |
| network_offline | Device went offline | - |
| queue_empty | Queue is now empty | - |
| queue_cleared | Queue was manually cleared | - |`typescript
// Subscribe to events
const unsubscribe = vault.on("job_success", (event) => {
console.log("Job completed:", event.data);
});// One-time subscription
vault.once("sync_completed", (event) => {
console.log("Initial sync done");
});
// Unsubscribe
unsubscribe();
`---
Queue Management
`typescript
// Get queue status
const length = await vault.getQueueLength();
const jobs = await vault.getQueue();// Manual control
vault.startProcessing();
vault.stopProcessing();
const syncing = vault.isProcessing();
// Clear queue
await vault.clearQueue();
// Manage specific jobs
await vault.retryJob("job-id");
await vault.removeJob("job-id");
`---
Dead Letter Queue (DLQ)
Jobs that fail after max retries are moved to the DLQ:
`typescript
// Get failed jobs
const deadJobs = await vault.getDLQ();// Retry all dead jobs
await vault.retryDLQ();
// Clear DLQ
await vault.clearDLQ();
`---
Custom Storage Adapters
Implement the
StorageAdapter interface for custom storage:`typescript
import type { StorageAdapter, QueuedJob } from "@sync-vault-js/core";class CustomStorageAdapter implements StorageAdapter {
async init(): Promise {
/ ... /
}
async add(job: QueuedJob): Promise {
/ ... /
}
async getAll(): Promise {
/ ... /
}
async get(id: string): Promise {
/ ... /
}
async update(id: string, updates: Partial): Promise {
/ ... /
}
async remove(id: string): Promise {
/ ... /
}
async count(): Promise {
/ ... /
}
async clear(): Promise {
/ ... /
}
async moveToDLQ(job: QueuedJob): Promise {
/ ... /
}
async getDLQ(): Promise {
/ ... /
}
async clearDLQ(): Promise {
/ ... /
}
async close(): Promise {
/ ... /
}
}
const vault = createSyncVault({
storage: new CustomStorageAdapter(),
});
`---
React Native Integration
For React Native, use the custom network checker with
@react-native-community/netinfo:`typescript
import NetInfo from "@react-native-community/netinfo";
import {
createSyncVault,
createReactNativeNetworkChecker,
} from "@sync-vault-js/core";const vault = createSyncVault({
networkChecker: createReactNativeNetworkChecker(NetInfo),
});
`---
TypeScript Support
SyncVault is written in strict TypeScript with full type inference:
`typescript
interface User {
id: string;
name: string;
email: string;
}interface CreateUserPayload {
name: string;
email: string;
}
// Fully typed request and response
const response = await vault.post("/api/users", {
name: "John",
email: "john@example.com",
});
// response.data is typed as User
console.log(response.data.id);
`---
Architecture
`
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ SyncVault Client โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ HTTP Client โ โ Event Emitterโ โNetwork Checkerโ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Queue Processor โ โ
โ โ โโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ FIFO โ โ Exp Backoff โ โ Dead Letter Q โ โ โ
โ โ โ Queue โ โ Retry โ โ โ โ โ
โ โ โโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Storage Adapter (Interface) โ โ
โ โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ โ
โ โ โ IndexedDB โ โ Memory โ โ โ
โ โ โ (Browser) โ โ (SSR/Node) โ โ โ
โ โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
``---
Contributions are welcome! Please read our contributing guidelines before submitting a PR.
---
Nabhodipta Garai
- GitHub: @brownboycodes
- LinkedIn: Nabhodipta Garai
- 
Swayam Debata (Contributor)
- GitHub: @SwayamDebata
- LinkedIn: Swayam Debata
- 
---
MIT ยฉ SyncVault
---
Built for the offline-first future ๐