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

@azlib/sync

v0.2.6

Published

Standardized multi-entity offline-first database persistence, optimistic UI updates (0ms latency), background mutation queues, and server synchronization handlers for browser and Node.js applications.

Readme

@azlib/sync

Standardized multi-entity offline-first database persistence, optimistic UI updates (0ms latency), background mutation queues, and server synchronization handlers for browser and Node.js applications.

Capabilities

  • Multi-Entity Repository Pattern: Manage database stores for arbitrary entities (mind_maps, nodes, user_settings, documents, forms) under a single SyncDatabase container.
  • Optimistic UI Execution (0ms Latency): Local reads and writes resolve instantly to keep the UI lag-free, while background workers queue and push mutations to the server API.
  • Offline First & Network Resiliency: Listens for network connection changes (online/offline). Mutations performed offline are buffered in a persistent queue and flushed automatically upon reconnection with exponential backoff retries.
  • Pluggable Storage Drivers:
    • IndexedDbDriver: Primary browser driver for large, indexed JSON object stores.
    • LocalStorageDriver: Lightweight key-value driver for settings or extension contexts (chrome.storage.local).
    • MemoryDriver: Zero-overhead in-memory fallback for Next.js SSR and Vitest / Node test runners.
  • Conflict Resolution Policies: Configurable policies (last-write-wins, server-wins, client-wins, or custom merger function (localRecord, serverRecord) => MergedRecord).
  • Server Integration Module: Includes @azlib/sync/server exporting Express/Node batch sync helpers (createSyncServerHandler, processBatchSync).

AI Agent Quick Reference

Core Exports

| Subpath | Export | Description | | -------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------- | | @azlib/sync | createSyncDatabase(config) | Creates a central database container instance (SyncDatabase). | | @azlib/sync | SyncDatabase | Database container class managing storage drivers, mutation queues, and repository registrations. | | @azlib/sync | SyncRepository<T, K> | Entity repository providing find, list, save, delete, bulkSave, sync, and subscribe. | | @azlib/sync | SyncQueue | Background offline mutation queue managing retry backoff and network reconnection listeners. | | @azlib/sync | resolveConflict(local, server, strategy) | Conflict resolution helper. | | @azlib/sync/server | createSyncServerHandler(config) | Express/Node HTTP middleware for multi-entity batch sync endpoints. | | @azlib/sync/server | processBatchSync(payload, config) | Core server function processing batch sync payloads. |

Core Types & Signatures

type SyncState =
  | "synced"
  | "pending_create"
  | "pending_update"
  | "pending_delete"
  | "conflict"
  | "error";

type SyncMeta = {
  entityName: string;
  syncState: SyncState;
  lastSyncedAt?: string;
  updatedAt: string;
  clientVersion: number;
};

type SyncRecord<T> = T & { _syncMeta: SyncMeta };

type ConflictStrategy =
  | "last-write-wins"
  | "server-wins"
  | "client-wins"
  | ((localRecord: unknown, serverRecord: unknown) => unknown);

Basic Client Usage

import { createSyncDatabase } from "@azlib/sync";
import { httpClient } from "@azlib/http-client";

export interface MindMapSnapshot {
  id: string;
  title: string;
  updatedAt: string;
}

// 1. Initialize Database Container
export const clientDb = createSyncDatabase({
  name: "app_client_db",
  version: 1,
  driver: "indexeddb", // Falls back to memory driver automatically in SSR
});

// 2. Register Repositories
export const mindMapRepo = clientDb.registerRepository<MindMapSnapshot, string>(
  {
    name: "mind_maps",
    keyPath: "id",
    api: {
      fetch: async (id) => (await httpClient.get(`/api/mind-maps/${id}`)).data,
      push: async (record, op) =>
        (
          await httpClient.post(`/api/mind-maps/${record.id}/sync`, {
            record,
            op,
          })
        ).data,
      list: async () => (await httpClient.get("/api/mind-maps")).data,
    },
    conflictStrategy: "last-write-wins",
  },
);

// 3. Optimistic CRUD Operations
await mindMapRepo.save({
  id: "map_1",
  title: "Architecture Blueprint",
  updatedAt: new Date().toISOString(),
}); // Resolves instantly (0ms UI delay) & queues background API push

const map = await mindMapRepo.find("map_1");
const allMaps = await mindMapRepo.list();
await mindMapRepo.delete("map_1");

React Component Integration

import React, { useEffect, useState } from "react";
import { mindMapRepo, clientDb } from "./db";
import type { MindMapSnapshot, SyncStatus } from "@azlib/sync";

export function MindMapEditor({ mapId }: { mapId: string }) {
  const [map, setMap] = useState<MindMapSnapshot | null>(null);
  const [syncStatus, setSyncStatus] = useState<SyncStatus>({
    state: "idle",
    pendingMutationsCount: 0,
  });

  useEffect(() => {
    // Read from IndexedDB instantly
    mindMapRepo.find(mapId).then(setMap);

    // Subscribe to entity updates
    const unsubscribeData = mindMapRepo.subscribe(mapId, setMap);

    // Subscribe to sync queue status changes
    const unsubscribeSync = clientDb.onSyncStatusChange(setSyncStatus);

    return () => {
      unsubscribeData();
      unsubscribeSync();
    };
  }, [mapId]);

  const handleUpdateTitle = async (newTitle: string) => {
    if (!map) return;
    // Writes locally instantly (0ms delay) -> UI updates via subscription
    await mindMapRepo.save({
      ...map,
      title: newTitle,
      updatedAt: new Date().toISOString(),
    });
  };

  return (
    <div>
      <div className="status-bar">
        Status: {syncStatus.state} ({syncStatus.pendingMutationsCount} pending)
      </div>
      <input
        value={map?.title ?? ""}
        onChange={(e) => handleUpdateTitle(e.target.value)}
      />
    </div>
  );
}

Server Integration (@azlib/sync/server)

// In apps/api (Express Server)
import { Router } from "express";
import { createSyncServerHandler } from "@azlib/sync/server";
import * as MindMapService from "./features/mind-map/mind-map.service";

const router = Router();

router.post(
  "/api/sync",
  createSyncServerHandler({
    repositories: {
      mind_maps: {
        save: async (userId, data) =>
          MindMapService.updateMindMap(userId, data.id, data),
        delete: async (userId, id) => MindMapService.deleteMindMap(userId, id),
      },
    },
  }),
);

Behavioral Gotchas & Best Practices

  1. SSR Hydration Safety: The database automatically falls back to MemoryDriver in SSR environments (Next.js server rendering) where window.indexedDB or window.localStorage is unavailable.
  2. Key Path Requirement: Every entity passed to repo.save() must contain the designated keyPath property defined in registerRepository({ keyPath: 'id' }).
  3. Queue Idempotency: When offline mutations are flushed upon reconnection, push(record, operation) callbacks should ideally handle idempotency or timestamp checks on the server side.