DOM-native XML state management with multi-master sync
npm install flarpDOM-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.
``html
`
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
---
`html
Count:
`
---
Every node has two special attributes:
- uuid — Stable identity (never changes)
- rev — Version string: {number}-{hash}
`xml`
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).
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
`javascriptConflict on ${uuid}: ${winner} won
// Listen for conflicts
store.onConflict(({ uuid, localRev, remoteRev, winner }) => {
console.log();`
});
---
When you set key="...", Flarp automatically:
1. Persists to localStorage
2. Syncs across browser tabs via BroadcastChannel
3. Requests catch-up sync on connect
`html`
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: "
4. On startup, tabs request missed changes from peers
`javascript
// Access the sync instance
store.sync.onChange(change => {
console.log('Remote change:', change);
});
store.sync.onConflict(info => {
console.log('Conflict:', info);
});
`
---
Apply updates from WebSocket, HTTP push, or postMessage:
`javascriptApplied: ${applied}, Winner: ${winner}
// From WebSocket
ws.onmessage = e => {
const { applied, conflict, winner } = store.applyRemote(e.data);
console.log();
};
// From postMessage
window.onmessage = e => {
if (e.data.type === 'node-update') {
store.applyRemote(e.data.xml);
}
};
`
The XML must include uuid and rev:
`xml`
---
`html`
autosave="500"
sync="true"
>
`html`
`html`
`html
Active!
Free shipping!
`
`html`
`html`
---
`javascript
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', '
// Remove node
store.remove('users.user[0]');
// Serialize
const xml = store.serialize();
// Manual save
store.save();
// Apply external update
store.applyRemote('
});
// Event handlers
store.onChange(xml => console.log('Changed'));
store.onConflict(info => console.log('Conflict:', info));
`
---
``
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
---
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
`bashStart a local server
npm run dev
---
Saving & Persistence
$3
`html
`$3
`javascript
store.save(); // Save now
store.clear(); // Clear stored state
`$3
Flarp automatically saves on
beforeunload (browser close/refresh).$3
Data is stored in localStorage under
flarp-data:{key}.---
Tag Naming
⚠️ Use lowercase tags! The DOM lowercases all tag names, so
becomes . For clarity, always write lowercase:`html
Alice
Alice
``---
- Modern browsers with BroadcastChannel (Chrome, Firefox, Safari, Edge)
- Falls back to localStorage events for older browsers
- Requires ES modules support
---
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.
---
MIT