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

flarp

v2.5.1

Published

DOM-native XML state management with multi-master sync

Readme

Flarp

DOM-native XML State Management with Multi-Master Sync

Flarp treats XML as state, the DOM as the runtime, and all browser tabs as equal peers. State survives refresh, syncs across tabs, and conflicts are resolved automatically.

<f-store key="myapp" autosave="500">
  <user>
    <n>Alice</n>
    <role>admin</role>
  </user>
</f-store>

<f-text path="user.name"></f-text>
<f-field path="user.role"></f-field>

Core Principles

  1. XML is State — Human-readable, self-describing, works with AI
  2. DOM is Runtime — No virtual DOM, no compile step, just the browser
  3. Multi-Master Sync — All tabs are equal peers, no single source of truth
  4. Eventually Consistent — Conflicts resolved deterministically, all tabs agree

Quick Start

<!DOCTYPE html>
<html>
<head>
  <script type="module" src="./src/index.js"></script>
</head>
<body>
  <f-store key="demo" autosave="500">
    <counter>
      <value>0</value>
    </counter>
  </f-store>

  <p>Count: <f-text path="counter.value"></f-text></p>
  <button id="inc">+1</button>

  <script type="module">
    const store = document.querySelector('f-store');

    store.state.when('ready', () => {
      document.getElementById('inc').onclick = () => {
        const val = store.at('counter.value');
        val.value = String(Number(val.value) + 1);
      };
    });
  </script>
</body>
</html>

Revision System (CouchDB-style)

Every node has two special attributes:

  • uuid — Stable identity (never changes)
  • rev — Version string: {number}-{hash}
<user uuid="abc123" rev="3-f7a8b9c0">
  <n>Alice</n>
</user>

Why This Format?

The revision number enables quick comparison (higher wins). The hash enables conflict detection (same number, different hash = conflict) and deterministic tie-breaking (alphabetically first hash wins).

Conflict Resolution

When two tabs write simultaneously:

Tab A writes: rev="3-aaa111"
Tab B writes: rev="3-bbb222"
  1. Both writes succeed (no data loss!)
  2. Conflict detected (same number: 3)
  3. Tab A wins (aaa < bbb alphabetically)
  4. All tabs independently agree on Tab A
// Listen for conflicts
store.onConflict(({ uuid, localRev, remoteRev, winner }) => {
  console.log(`Conflict on ${uuid}: ${winner} won`);
});

Cross-Tab Synchronization

When you set key="...", Flarp automatically:

  1. Persists to localStorage
  2. Syncs across browser tabs via BroadcastChannel
  3. Requests catch-up sync on connect
<f-store key="myapp" autosave="500">
  <!-- Shared across all tabs with key="myapp" -->
</f-store>

How It Works

  1. Each tab broadcasts changes to a shared channel
  2. Other tabs receive and apply changes (if revision wins)
  3. Changes include: { uuid, rev, data: "<xml>..." }
  4. On startup, tabs request missed changes from peers
// Access the sync instance
store.sync.onChange(change => {
  console.log('Remote change:', change);
});

store.sync.onConflict(info => {
  console.log('Conflict:', info);
});

External Updates (Server Push)

Apply updates from WebSocket, HTTP push, or postMessage:

// From WebSocket
ws.onmessage = e => {
  const { applied, conflict, winner } = store.applyRemote(e.data);
  console.log(`Applied: ${applied}, Winner: ${winner}`);
};

// From postMessage
window.onmessage = e => {
  if (e.data.type === 'node-update') {
    store.applyRemote(e.data.xml);
  }
};

The XML must include uuid and rev:

<n uuid="abc123" rev="5-xyz789">New Value</n>

Components

<f-store> — State Container

<f-store
  key="myapp"           <!-- Storage/sync key -->
  autosave="500"        <!-- Debounced save (ms) -->
  sync="true"           <!-- Enable cross-tab sync -->
>
  <!-- Your XML state here -->
</f-store>

<f-text> — Display Value

<f-text path="user.name"></f-text>

<f-field> — Two-Way Binding

<f-field path="user.name"></f-field>
<f-field path="user.role" type="select">
  <option value="admin">Admin</option>
  <option value="user">User</option>
</f-field>

<f-when> — Conditional

<f-when test="user.active == 'true'">
  <span>Active!</span>
</f-when>

<f-when test="cart.total > 100">
  <span>Free shipping!</span>
</f-when>

<f-match> — Switch

<f-match test="user.role">
  <f-case value="admin">Admin Panel</f-case>
  <f-case value="user">Dashboard</f-case>
  <f-else>Please log in</f-else>
</f-match>

<f-each> — Iteration

<f-each path="users.user">
  <template>
    <div class="user">
      <f-text path="name"></f-text>
    </div>
  </template>
</f-each>

JavaScript API

const store = document.querySelector('f-store');

// Wait for ready
store.state.when('ready', () => {

  // Get reactive node
  const name = store.at('user.name');

  // Read/write
  console.log(name.value);
  name.value = 'Bob';

  // Subscribe to changes
  name.subscribe(v => console.log('Name changed:', v));

  // Query multiple
  const users = store.query('users.user');

  // Add node
  store.add('users', '<user><n>New User</n></user>');

  // Remove node
  store.remove('users.user[0]');

  // Serialize
  const xml = store.serialize();

  // Manual save
  store.save();

  // Apply external update
  store.applyRemote('<n uuid="..." rev="5-abc">Value</n>');
});

// Event handlers
store.onChange(xml => console.log('Changed'));
store.onConflict(info => console.log('Conflict:', info));

Architecture

flarp/
├── src/
│   ├── core/           # Reactive primitives
│   │   ├── Signal.js   # Synchronous reactive value
│   │   └── State.js    # Named states (ready, synced, etc.)
│   │
│   ├── xml/            # XML utilities
│   │   ├── Node.js     # Reactive node wrapper
│   │   ├── Path.js     # Path resolution
│   │   └── Tree.js     # Tree management
│   │
│   ├── sync/           # Persistence & sync
│   │   ├── Sync.js     # Cross-tab sync with changes feed
│   │   ├── Store.js    # FStore component
│   │   ├── Persist.js  # Storage adapters
│   │   └── Channel.js  # BroadcastChannel wrapper
│   │
│   ├── dom/            # DOM utilities
│   │   └── find.js     # Store discovery
│   │
│   ├── components/     # Web Components
│   │   ├── FText.js
│   │   ├── FField.js
│   │   ├── FWhen.js
│   │   ├── FEach.js
│   │   └── FBind.js
│   │
│   └── index.js        # Main exports
│
├── index.html          # Demo
├── multiuser.html      # Multi-tab sync demo
└── README.md

Multi-User Demo

Open multiuser.html in multiple browser tabs to see sync in action:

  1. Each tab gets a unique user ID
  2. Changes sync instantly across all tabs
  3. Conflicts are detected and resolved automatically
  4. Enable "auto-write" to stress test
# Start a local server
npm run dev

# Open multiple tabs to:
http://localhost:8080/multiuser.html

Saving & Persistence

Automatic

<f-store key="myapp" autosave="500">
  <!-- Saves 500ms after last change -->
</f-store>

Manual

store.save();  // Save now
store.clear(); // Clear stored state

Emergency Save

Flarp automatically saves on beforeunload (browser close/refresh).

Storage Location

Data is stored in localStorage under flarp-data:{key}.


Tag Naming

⚠️ Use lowercase tags! The DOM lowercases all tag names, so <User> becomes <user>. For clarity, always write lowercase:

<!-- Good -->
<f-store>
  <user><n>Alice</n></user>
</f-store>

<!-- Works but confusing -->
<f-store>
  <User><n>Alice</n></User>
</f-store>

Browser Support

  • Modern browsers with BroadcastChannel (Chrome, Firefox, Safari, Edge)
  • Falls back to localStorage events for older browsers
  • Requires ES modules support

Name

flarp (from Jargon File)

/flarp/ [Rutgers University] Yet another metasyntactic variable (see foo). Among those who use it, it is associated with a legend that any program not containing the word "flarp" somewhere will not work. The legend is discreetly silent on the reliability of programs which do contain the magic word.


License

MIT