Data Structures
TopGun provides two primary CRDT-based data structures: LWW-Map (Last-Write-Wins Map) and OR-Map (Observed-Remove Map). Understanding the difference between them is critical for preventing data loss in distributed applications.
LWW-Map (Last-Write-Wins)
The LWW-Map is the default map structure in TopGun. As the name suggests, it resolves conflicts by accepting the update with the highest timestamp (Last-Write-Wins).
Use when: Each item in the collection is a unique entity identified by a stable key (e.g., a user profile by UUID, a document by ID). The LWW-Map is collection-oriented — useQuery<T>(mapName) returns an array of all items, each with a _key property.
How it works
- Each key holds a single value and a timestamp.
- When two nodes update the same key, the one with the later timestamp wins.
- The collection pattern uses stable UUID keys (e.g., one profile per user) so concurrent updates to different profiles never collide.
Usage
import { useQuery, useMutation } from '@topgunbuild/react';
import { type UserProfile } from './schema';
function ProfileList() {
const { data: profiles } = useQuery<UserProfile>('profiles');
const { update } = useMutation<UserProfile>('profiles');
const setOnline = (profileId: string) =>
update(profileId, { status: 'online' });
return (
<ul>
{profiles.map(p => (
<li key={p._key} onClick={() => setOnline(p._key)}>
{p.name} — {p.status}
</li>
))}
</ul>
);
} OR-Map (Observed-Remove)
The OR-Map is designed for Sets and Collections. It allows you to add multiple items to a key without them overwriting each other, even if they have the same value (depending on implementation) or if they are added concurrently. In TopGun, the OR-Map is implemented as a map where values are “observed” and can be removed without race conditions affecting re-additions.
Use when: Collections, Lists, Sets, and scenarios where multiple users might add/remove items concurrently (e.g., Todo Lists, Chat Messages in a channel, Tags).
Why LWW is bad for Sets
Imagine two users adding a “Todo Item” to a list. If you used LWW-Map and stored the list as a JSON array under a single key todos, the user who saves last would overwrite the other user’s addition.
With OR-Map, each addition is treated as a unique operation. Even if two users add the same value, or different values, both are preserved (unless one is explicitly removed).
Usage
import { useORMap } from '@topgunbuild/react';
function ActiveGames() {
const orMap = useORMap<string, Game>('games:active');
// useORMap returns the raw ORMap<K, V> instance — call imperative methods directly
const addGame = (id: string, game: Game) => orMap.add(id, game);
const removeGame = (id: string, game: Game) => orMap.remove(id, game);
return <ul>{/* iterate orMap entries */}</ul>;
} Tombstones & Deletion
When you delete data in TopGun, the record isn’t immediately removed from storage. Instead, a tombstone is created. This is essential for proper synchronization with offline clients.
LWW-Map Tombstones
A tombstone is simply a record with value: null:
{ value: null, timestamp: {...} }OR-Map Tombstones
Removed tags are stored in a tombstone set:
tombstones: Set<tag>
Garbage Collection: Tombstones are eligible for cleanup once all replicas
have acknowledged the deletion. The LWWMap.prune() method removes tombstones
older than a given threshold, preventing “resurrection” of deleted data.
Summary
| Feature | LWW-Map | OR-Map |
|---|---|---|
| Primary Use Case | Key-Value properties (Profile, Config) | Collections, Sets, Lists (Todos, Comments) |
| Conflict Resolution | Last Write Wins (Overwrites) | Union of Adds (Merge), Observed Remove |
| Deletion | Tombstone (value: null) | Tag added to tombstone set |
| Hook (UI) | useQuery / useMutation | useORMap |