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

LWW-Map 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

OR-Map 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

FeatureLWW-MapOR-Map
Primary Use CaseKey-Value properties (Profile, Config)Collections, Sets, Lists (Todos, Comments)
Conflict ResolutionLast Write Wins (Overwrites)Union of Adds (Merge), Observed Remove
DeletionTombstone (value: null)Tag added to tombstone set
Hook (UI)useQuery / useMutationuseORMap