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 🙏

© 2024 – Pkg Stats / Ryan Hefner

offlinesync

v1.0.0

Published

Why OfflineSync.js? In today's connected world, users expect apps to work seamlessly, even without an internet connection. OfflineSync.js empowers developers to create offline-first applications, ensuring that data is queued and synced reliably when conne

Downloads

54

Readme

OfflineSync

OfflineSync is a lightweight, easy-to-use library for managing offline data synchronization in web and mobile applications. It allows actions to be queued while offline and automatically syncs them once the device is online. This ensures uninterrupted workflows for users even in the absence of network connectivity.

Features

  • Automatic Queuing: Queue actions when offline, ensuring no data is lost.
  • Online Sync: Automatically sync queued actions with the server once the device goes online.
  • Conflict Resolution: Choose a strategy for resolving conflicts (e.g., latest timestamp or custom resolution).
  • Offline Persistence: Persist queued actions even if the app is closed or restarted.
  • Queue Management: Easily retrieve, add, remove, and clear actions from the queue.
  • Customizable Sync Behavior: Define custom behaviors for syncing different action types.

Installation

To install OfflineSync, use npm or yarn:

# Using npm
npm install offlinesync

# Using yarn
yarn add offlinesync

Usage

  • Initialize OfflineSync: Start by initializing the OfflineSync instance with desired configuration options.
import OfflineSync from 'offlinesync';

const offlineSync = new OfflineSync({
  maxQueueSize: 10, // Optional: Max number of actions in the queue
  conflictStrategy: 'latest', // Optional: Conflict resolution strategy
  persist: true, // Optional: Persist queue across sessions
});

Configuration Options

  • maxQueueSize: (Optional) Set a maximum queue size. If exceeded, older actions will be removed.
  • conflictStrategy: (Optional) Defines how conflicts are handled. Options: 'latest', 'merge', or a custom function.
  • persist: (Optional) If set to true, queued actions will be stored persistently (e.g., using localStorage).

Adding Actions to the Queue

Queue actions using the addAction method. Each action requires a type, data, id, and timestamp.

offlineSync.addAction({
  type: 'ADD_TASK', // Action type
  data: { id: 1, description: 'Complete the task' }, // Action data
  id: 'task-1', // Unique action ID
  timestamp: Date.now(), // Timestamp of when the action was queued
});

Syncing Queued Actions

To sync queued actions, simply use the sync() method:

offlineSync.startSync();

This will sync the queued actions once the device is online, and successfully synced actions will be removed from the queue.

Retrieving the Queue

You can retrieve the current actions in the queue:

const queue = offlineSync.getQueuedActions()
console.log(queue); // Output: Array of actions in the queue

Removing Actions from the Queue

You can remove an individual action from the queue by its ID:

offlineSync.removeActionFromQueue('id'); // Remove specific action

To clear all actions from the queue:

offlineSync.clearQueuedAction() // Clear entire queue

Sync Strategies

OfflineSync offers several strategies to resolve conflicts when the same action is queued multiple times.

Available Conflict Strategies

  • 'latest': Keeps the most recent action (based on timestamp).
  • 'merge': Merges conflicting actions (requires custom logic).
  • Custom: You can define your own conflict resolution strategy.

Example of a custom conflict strategy:

const offlineSync = new OfflineSync({
  conflictStrategy: (action1, action2) => {
    return action1.timestamp > action2.timestamp ? action1 : action2;
  },
});

Real-World Example: Shopping Cart

Goal: Enable users to add items to their shopping cart offline, queue the actions, and synchronize them with a server when back online.

Shopping Cart Code Walkthrough

1. Initialization OfflineSync is initialized with optional configurations, including queue size and conflict resolution strategies.

const syncInstance = new OfflineSync({
  maxQueueSize: 10,
  conflictStrategy: 'latest',
});

2. Adding Items to Cart When a user adds an item to the cart:

If online, the item is immediately sent to the server.

If offline, the item is added to the queue for syncing later.

const addItemToCart = async (name: string, price: number) => {
  const cartItem = {
    id: Date.now(),
    name,
    quantity: 1,
    price,
    status: 'pending',
  };

  setCartItems((prev) => [...prev, cartItem]);

  if (offlineSync?.onlineStatus) {
    await processItem(cartItem);
  } else {
    queueItem(cartItem);
  }
};

3. Queuing Offline Actions Queued actions are added to OfflineSync’s action queue. When the app detects network connectivity, these actions are automatically synced.

const queueItem = (cartItem: CartItemType) => {
  offlineSync?.addAction({
    type: 'ADD_ITEM',
    data: cartItem,
    id: String(cartItem.id),
    timestamp: Date.now(),
  });
  updateQueue();
};

4. Syncing with the Server When the app comes online, queued items are sent to the server, and the queue is cleared for synced actions.

  const sendQueuedItems = async () => {
    if (!offlineSync) return;

    const queuedActions = offlineSync.getQueuedActions()
    for (const action of queuedActions) {
      if (isCartItem(action.data)) {
        await processItem(action.data);
        offlineSync.removeActionFromQueue(action.id);
      }
    }
    updateQueue();
  };

5. Conflict Resolution OfflineSync resolves conflicts using predefined or custom strategies. For instance, keeping the latest version of conflicting actions:

const syncInstance = new OfflineSync({
  conflictStrategy: 'latest',
});

Complete Example

ShoppingCart.tsx

// shoppingCart.tsx
'use client';
import React, { useState, useEffect } from 'react';
import CartItem from './component/shopping/CartItems';
import QueueList from './component/shopping/QueueList';
import AddToCartButton from './component/shopping/AddToCartButton';
import OfflineSync from 'offlinesync';


type CartItemType = {
  id: number;
  name: string;
  quantity: number;
  price: number;
  status: 'pending' | 'sent';
};

const ShoppingCart = () => {
  const [offlineSync, setOfflineSync] = useState<OfflineSync | null>(null);
  const [status, setStatus] = useState<string>('Offline');
  const [cartItems, setCartItems] = useState<CartItemType[]>([]);
  const [queuedItems, setQueuedItems] = useState<CartItemType[]>([]);

  useEffect(() => {
    const syncInstance = new OfflineSync({ maxQueueSize: 10, conflictStrategy: 'latest' });
    setOfflineSync(syncInstance);

    const interval = setInterval(() => {
      if (syncInstance) {
        setStatus(syncInstance.onlineStatus ? 'Online' : 'Offline');
      }
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  const updateQueue = () => {
    if (offlineSync) {
      const queuedActions = offlineSync.getQueuedActions()
      const queuedCartItems = queuedActions
        .map((action) => action?.data)
        .filter((data) => isCartItem(data)); 
      setQueuedItems(queuedCartItems); 
    }
  };

  const isCartItem = (data: any): data is CartItemType => {
    return data && typeof data.id === 'number' && typeof data.name === 'string';
  };

  const addItemToCart = async (name: string, price: number) => {
    const cartItem: CartItemType = { id: Date.now(), name, quantity: 1, price, status: 'pending' };
    setCartItems((prev) => [...prev, cartItem]);

    if (offlineSync?.onlineStatus) {
      await processItem(cartItem);
    } else {
      queueItem(cartItem);
    }
  };

  const queueItem = (cartItem: CartItemType) => {
    if (!offlineSync) return;
    offlineSync?.addAction({
      type: 'ADD_ITEM',
      data: cartItem,
      id: String(cartItem.id),
      timestamp: Date.now(),
    });
    updateQueue();
  };

  const processItem = async (cartItem: CartItemType) => {
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(cartItem),
      });
      const data = await response.json();
      console.log('Item processed:', data);
    } catch (error) {
      console.error('Error processing item:', error);
    }
  };

  const sendQueuedItems = async () => {
    if (!offlineSync) return;

    const queuedActions = offlineSync.getQueuedActions()
    for (const action of queuedActions) {
      if (isCartItem(action.data)) {
        await processItem(action.data);
        offlineSync.removeActionFromQueue(action.id);
      }
    }
    updateQueue();
  };

  const clearQueue = () => {
    if (offlineSync) {
      offlineSync.clearQueuedAction()
      setQueuedItems([]); 
    }
  };

  useEffect(() => {
    if (status === 'Online') {
      sendQueuedItems();
    }
  }, [status, offlineSync]);

  useEffect(() => {
    updateQueue();
  }, [offlineSync]);

  return (
    <div>
      <h1>Shopping Cart</h1>
      <p>Status: {status}</p>

      <AddToCartButton addItemToCart={addItemToCart} />

      <div>
        <h2>Your Cart</h2>
        {cartItems.length > 0 ? (
          cartItems.map((item) => (
            <CartItem key={item.id} item={item} />
          ))
        ) : (
          <p>Your cart is empty</p>
        )}
      </div>

        
      <QueueList queuedItems={queuedItems} />

      <div>
        <button onClick={clearQueue}>Clear Queue</button>
      </div>
    </div>
  );
};

export default ShoppingCart;

Additional Components

AddToCartButton.tsx: Button for adding items to the cart. CartItem.tsx: Displays individual cart items. QueueList.tsx: Displays queued items waiting to be synced

AddToCartButton.tsx

// AddToCartButton.tsx
import React, { useState } from 'react';

type AddToCartButtonProps = {
  addItemToCart: (name: string, price: number) => void;
};

const AddToCartButton = ({ addItemToCart }: AddToCartButtonProps) => {
  const [itemName, setItemName] = useState<string>('');
  const [itemPrice, setItemPrice] = useState<number>(0);

  const handleAddItem = () => {
    if (itemName.trim() && itemPrice > 0) {
      addItemToCart(itemName, itemPrice);
      setItemName('');
      setItemPrice(0);
    }
  };

  return (
    <div>
      <input
        type="text"
        value={itemName}
        onChange={(e) => setItemName(e.target.value)}
        placeholder="Enter item name"
      />
      <input
        type="number"
        value={itemPrice}
        onChange={(e) => setItemPrice(Number(e.target.value))}
        placeholder="Enter item price"
      />
      <button onClick={handleAddItem}>Add Item to Cart</button>
    </div>
  );
};

export default AddToCartButton;

CartItems.tsx

// CartItem.tsx
import React from 'react';

type CartItemProps = {
  item: {
    id: number;
    name: string;
    quantity: number;
    price: number;
    status: 'pending' | 'sent';
  };
};

const CartItem = ({ item }: CartItemProps) => {
  return (
    <div>
      <h3>{item.name}</h3>
      <p>Quantity: {item.quantity}</p>
      <p>Price: ${item.price}</p>
      <p>Status: {item.status}</p>
    </div>
  );
};

export default CartItem;

QueueList.tsx

// QueueList.tsx
import React from 'react';

type QueueListProps = {
  queuedItems: {
    id: number;
    name: string;
    quantity: number;
    price: number;
    status: 'pending' | 'sent';
  }[];
};

const QueueList = ({ queuedItems }: QueueListProps) => {
  return (
    <div>
      <h2 className="my-20 text-2xl">Queued Items</h2>
      {queuedItems.length > 0 ? (
        <ul>
          {queuedItems.map((item) => (
            <li key={item.id}>
              {item.name} - {item.quantity} - ${item.price} - {item.status}
            </li>
          ))}
        </ul>
      ) : (
        <p>No items in queue</p>
      )}
    </div>
  );
};

export default QueueList;

Advanced Features

1. Queue Management

Retrieve actions: offlineSync.queue.get()

Remove action: offlineSync.queue.remove(id)

Clear queue: offlineSync.queue.clear()

2. Conflict Strategies

Use 'latest' or custom strategies for resolving conflicts.

With OfflineSync, managing offline shopping cart operations becomes seamless, enhancing user experience even with intermittent network connectivity.

Key Sections of the README:

  • Overview: Describes what the library does.
  • Features: Highlights the key capabilities of the library.
  • Installation: Instructions for installing the library.
  • Usage: Shows how to initialize, add actions, sync, retrieve the queue, and manage actions.
  • Sync Strategies: Details conflict resolution strategies.
  • Example Usage: Provides an example of a real-world use case.
  • Queue Management: Covers advanced operations on the queue.
  • Troubleshooting: Offers solutions to common problems.

Feel free to copy and paste this markdown directly into your project's README.md file!