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

@multiplatform.one/frappe

v6.1.0

Published

frappe client for multiplatform.one ecosystem

Readme

Frappe Live Sync SDK

Real-time sync for Frappe with TanStack DB integration, virtual pagination, active tab priority leadership, and enterprise features.

Documentation

docs/complete-reference.md

Comprehensive technical reference containing architecture, implementation details, server specification, and production deployment.

Core Features

Virtual Pagination

  • Progressive cache with page islands for efficient large datasets
  • Cursor-based pagination via frappe_live extension
  • Opt-in via progressive: true on subscription queries
  • Automatic consistency across pages

Data Integrity

  • Version checking with 409/412 errors (OCC via If-Match header)
  • Three-way merge conflict resolution with field-level strategies
  • Idempotency via Idempotency-Key headers for safe retries
  • CRDT-like field merging (sum, max, min, union-set, custom)

Offline Support

  • IndexedDB / KV persistence via persistKey config
  • Mutation journal with configurable TTL (default: 24hr)
  • Automatic replay on reconnection
  • Cross-tab coordination via BroadcastChannel + leader election

Real-time Sync

  • WebSocket via Socket.IO with exponential backoff
  • CDC with checkpoints for incremental sync after disconnects
  • Tombstone support for tracking deletions
  • Optional periodic CDC polling via cdcInterval for safety net
  • Intelligent rehydration after mutations and remote changes

Advanced Features

  • Active tab priority leadership with graceful handoffs
  • Mutation batching (opt-in via enableBatching)
  • Progressive pagination for infinite scroll with full sync
  • Normalized cache with optional LRU eviction
  • Devtools panel for inspecting subscriptions, cache, and journal

Installation

npm install @multiplatform.one/frappe

React Hooks

When to Use What

| Hook | Use Case | Updates | | ----------------------- | -------------------------------------------------------------- | ------------ | | useFrappeCollection | Creates a TanStack DB collection backed by a Frappe doctype | Auto-updates | | useLiveQuery | Reactive TanStack DB query with .where(), .orderBy(), etc. | Auto-updates | | useFrappeInfiniteList | Infinite scroll with full sync (realtime, offline, CDC) | Auto-updates |

useFrappeCollection + useLiveQuery

import { useFrappeCollection, useLiveQuery, eq } from "@multiplatform.one/frappe";

// List with ordering
const collection = useFrappeCollection<Pokemon>(config, "Pokemon", { fields });
const { data } = useLiveQuery(
  (q) =>
    collection
      ? q.from({ pokemon: collection }).orderBy(({ pokemon }) => pokemon.poke_id, "asc")
      : null,
  [collection],
);

// Single document with filtering
const { data } = useLiveQuery(
  (q) =>
    collection
      ? q.from({ pokemon: collection }).where(({ pokemon }) => eq(pokemon.name, id))
      : null,
  [collection, id],
);

useFrappeInfiniteList

import { useFrappeInfiniteList } from "@multiplatform.one/frappe";

function PokemonList() {
  const { items, hasMore, isLoading, sentinelRef } = useFrappeInfiniteList<Pokemon>({
    config: { baseURL: "https://your-instance.frappe.cloud", auth: { token: "..." } },
    doctype: "Pokemon",
    fields: ["name", "pokemon_name", "poke_id"],
    orderBy: { field: "poke_id", order: "asc" },
    pageSize: 20,
  });

  if (isLoading && items.length === 0) return <Spinner />;

  return (
    <div>
      {items.map((pokemon) => (
        <PokemonCard key={pokemon.name} {...pokemon} />
      ))}
      <div ref={sentinelRef}>{hasMore ? <Spinner /> : "No more items"}</div>
    </div>
  );
}

Quick Start

import { FrappeApp } from "@multiplatform.one/frappe";

const app = new FrappeApp("https://your-instance.frappe.cloud", {
  token: "your-token",
});

// Use the sync module for advanced features
const sync = app.sync();

// Subscribe to documents
const { subscriptionId } = await sync.subscribe({
  doctype: "Task",
  filters: { status: "Open" },
  limit: 20,
});

// Mutate with automatic conflict resolution
await sync.mutate({
  mutationId: crypto.randomUUID(),
  operation: "update",
  doc: { doctype: "Task", name: "TASK-001", status: "Complete" },
  version: task.modified,
});

Architecture

SyncModule (orchestrator)
├── SubscriptionManager
│   ├── Subscribe / unsubscribe lifecycle
│   ├── CDC backfill (backfillSub)
│   ├── Client-side filter evaluation
│   └── VirtualPageManager (opt-in progressive pagination)
│       ├── Page islands (contiguous + non-contiguous cache)
│       ├── Cursor + offset fetching
│       └── Remote change handlers
├── MutationEngine
│   ├── Optimistic create / update / delete with rollback
│   ├── Version checking (OCC via If-Match)
│   ├── Three-way merge with field-level strategies
│   ├── Cross-page coordination (global optimistic doc registry)
│   └── Optional batch queue (enableBatching)
├── MutationJournal
│   ├── Pending / confirmed / failed tracking
│   ├── TTL-based auto-cleanup
│   └── Rate-limited replay on reconnect
├── NormalizedCache
│   ├── Per-doctype document store
│   └── Optional LRU eviction (maxCacheSize)
├── RealtimeHandler
│   ├── Socket.IO doc_update / list_update events
│   ├── Authoritative REST fetch on event
│   └── Connection state management
├── RehydrationManager
│   ├── FetchItemOnly / FullRefresh strategies
│   ├── Priority-based task queue
│   └── Debounced processing
└── PersistenceManager
    ├── IndexedDB / KV snapshots (persistKey)
    ├── Multi-tab leader election (localStorage)
    ├── BroadcastChannel cross-tab sync
    └── Active tab priority handoff

Conflict Resolution

const sync = new SyncModule(db, realtime, {
  fieldMergeConfig: {
    Task: {
      tags: "union-set",
      progress: "max",
      time_spent: "sum",
      description: "server-wins",
      custom_field: (base, client, server) => {
        // Custom resolution logic
        return client ?? server;
      },
    },
  },
});

Server Requirements

The SDK works with standard Frappe REST API for reads and uses the frappe_live extension app for enhanced write operations. The extension provides:

  • Idempotent create/delete via Idempotency-Key header
  • OCC update via If-Match header (409/412 on version mismatch)
  • Cursor-based pagination via get_list_with_cursor
  • Batch mutations via batch_mutations
  • CDC endpoints via list_since and backfill
  • Tombstone tracking via Deleted Document doctype

Production Configuration

const sync = new SyncModule(db, realtime, {
  persistKey: "my-app", // Enable IndexedDB persistence
  cdcInterval: 30000, // Periodic CDC every 30s (safety net)
  maxCacheSize: 5000, // LRU eviction threshold
  mutationTTL: 24 * 60 * 60 * 1000, // 24hr mutation journal TTL
  enableBatching: false, // Opt-in mutation batching
  fieldMergeConfig: {
    /* ... */
  }, // Per-doctype merge strategies
  onConflict: (info) => console.warn("Conflict:", info),
  onMutationError: ({ mutation, err }) => reportError(err),
});

Testing

npm test

License

Apache 2.0