npm package: @sync-vault-js/core
Description: The Universal Sync Layer - Offline-first store-and-forward engine for any JavaScript environment
Author: Nabhodipta Garai
Latest version: 1.0.0

# SyncVault

<div align="center">

**The Universal Sync Layer**

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

[![npm version](https://img.shields.io/npm/v/@sync-vault-js/core.svg)](https://www.npmjs.com/package/@sync-vault-js/core)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue.svg)](https://www.typescriptlang.org/)

</div>

---

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

```bash
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!

```bash
# 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:**

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

See [`demo/README.md`](./demo/README.md) for more details.

---

## Quick Start

### Basic Usage (Vanilla JavaScript/TypeScript)

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

### React / Next.js

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

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

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

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

---

### Angular

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

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

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

```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<User, CreateUserPayload>("/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)  │           │     │
│  │  └──────────────┘         └──────────────┘           │     │
│  └───────────────────────────────────────────────────────┘     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

---

## Contributing

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

---

## Authors

**Nabhodipta Garai**

- GitHub: [@brownboycodes](https://github.com/brownboycodes)
- LinkedIn: [Nabhodipta Garai](https://www.linkedin.com/in/brownboycodes)
- [![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/brownboycodes)

**Swayam Debata** (Contributor)

- GitHub: [@SwayamDebata](https://github.com/SwayamDebata)
- LinkedIn: [Swayam Debata](https://www.linkedin.com/in/swayam-debata/)
- [![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/swayam_dev)

---

## License

MIT © SyncVault

---

<div align="center">

**Built for the offline-first future** 🚀

</div>