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

@sync-vault-js/core

v1.0.0

Published

The Universal Sync Layer - Offline-first store-and-forward engine for any JavaScript environment

Readme

SyncVault

The Universal Sync Layer

Write your offline logic once. Runs on Web, Mobile, and Desktop.

npm version License: MIT TypeScript


What is SyncVault?

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.

Key Features

  • 🔄 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

Installation

npm install @sync-vault-js/core
# or
yarn add @sync-vault-js/core
# or
pnpm add @sync-vault-js/core

🎮 Try the Demo

Want to see SyncVault in action? We've included a fully interactive demo application!

# Clone the repository
git clone https://github.com/syncvault/sync-vault-js.git
cd sync-vault-js

# Install dependencies
npm install

# Run the demo
npm run demo

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:

cd demo
python3 -m http.server 8000
# Then open http://localhost:8000

See demo/README.md for more details.


Quick Start

Basic Usage (Vanilla JavaScript/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: "[email protected]",
});

// 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

React / Next.js

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 (
    <div>
      <StatusBar
        online={isOnline}
        syncing={isProcessing}
        pending={queueLength}
      />
      <TodoForm onSubmit={addTodo} />
    </div>
  );
}

Additional React Hooks

import {
  useSyncVault,
  useOnlineStatus,
  useQueueStatus,
  useSyncRequest,
  useSyncVaultEvent,
} from "@sync-vault-js/core/react";

// Simple online status
function OnlineIndicator() {
  const isOnline = useOnlineStatus();
  return <span>{isOnline ? "🟢" : "🔴"}</span>;
}

// Queue status with auto-refresh
function QueueStatus() {
  const { length, isProcessing } = useQueueStatus();
  return (
    <span>
      {length} pending, {isProcessing ? "syncing..." : "idle"}
    </span>
  );
}

// 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 <Spinner />;
  if (error) return <Error message={error.message} />;
  if (queued) return <Badge>Queued for sync</Badge>;

  return <Form onSubmit={handleSubmit} />;
}

// 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;
}

Vue 3 / Nuxt

<script setup lang="ts">
import { useSyncVault } from "@sync-vault-js/core/vue";

const { isOnline, isProcessing, queueLength, post } = useSyncVault();

const addTodo = async (title: string) => {
  const response = await post("/api/todos", { title });

  if (response.fromQueue) {
    showNotification("Saved offline - will sync when online");
  }
};
</script>

<template>
  <div>
    <StatusBar
      :online="isOnline"
      :syncing="isProcessing"
      :pending="queueLength"
    />
    <TodoForm @submit="addTodo" />
  </div>
</template>

Additional Vue Composables

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);

Angular

// 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: `
    <div class="status">
      <span [class.online]="state.isOnline">
        {{ state.isOnline ? "Online" : "Offline" }}
      </span>
      <span *ngIf="state.queueLength > 0">
        {{ state.queueLength }} pending
      </span>
    </div>
    <button (click)="addTodo()">Add Todo</button>
  `,
})
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");
    }
  }
}

Svelte / SvelteKit

<script lang="ts">
  import { useSyncVault } from '@sync-vault-js/core/svelte';

  const {
    isOnline,
    isProcessing,
    queueLength,
    post,
  } = useSyncVault();

  async function addTodo(title: string) {
    const response = await post('/api/todos', { title });

    if (response.fromQueue) {
      showNotification('Saved offline');
    }
  }
</script>

<div>
  <StatusBar
    online={$isOnline}
    syncing={$isProcessing}
    pending={$queueLength}
  />
  <TodoForm on:submit={(e) => addTodo(e.detail.title)} />
</div>

Additional Svelte Stores

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

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 | - |

// 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

// 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:

// 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:

import type { StorageAdapter, QueuedJob } from "@sync-vault-js/core";

class CustomStorageAdapter implements StorageAdapter {
  async init(): Promise<void> {
    /* ... */
  }
  async add(job: QueuedJob): Promise<void> {
    /* ... */
  }
  async getAll(): Promise<QueuedJob[]> {
    /* ... */
  }
  async get(id: string): Promise<QueuedJob | undefined> {
    /* ... */
  }
  async update(id: string, updates: Partial<QueuedJob>): Promise<void> {
    /* ... */
  }
  async remove(id: string): Promise<void> {
    /* ... */
  }
  async count(): Promise<number> {
    /* ... */
  }
  async clear(): Promise<void> {
    /* ... */
  }
  async moveToDLQ(job: QueuedJob): Promise<void> {
    /* ... */
  }
  async getDLQ(): Promise<QueuedJob[]> {
    /* ... */
  }
  async clearDLQ(): Promise<void> {
    /* ... */
  }
  async close(): Promise<void> {
    /* ... */
  }
}

const vault = createSyncVault({
  storage: new CustomStorageAdapter(),
});

React Native Integration

For React Native, use the custom network checker with @react-native-community/netinfo:

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:

interface User {
  id: string;
  name: string;
  email: string;
}

interface CreateUserPayload {
  name: string;
  email: string;
}

// Fully typed request and response
const response = await vault.post<User, CreateUserPayload>("/api/users", {
  name: "John",
  email: "[email protected]",
});

// 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)  │           │     │
│  │  └──────────────┘         └──────────────┘           │     │
│  └───────────────────────────────────────────────────────┘     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Contributing

Contributions are welcome! Please read our contributing guidelines before submitting a PR.


Authors

Nabhodipta Garai

Swayam Debata (Contributor)


License

MIT © SyncVault


Built for the offline-first future 🚀