DocsReferenceData Structures API

Data Structures API

TopGun provides two CRDT-based data structures for storing and syncing data. This page documents all available methods for performing CRUD operations.

Which one to use? Use LWWMap for key-value data where each key has one value (profiles, settings). Use ORMap for collections where a key can have multiple values (todo lists, tags, comments). See Concepts: Data Structures for detailed comparison.


Timestamp Structure

All records include a Hybrid Logical Clock (HLC) timestamp for ordering and conflict resolution.

Timestamp & Record Types
// Timestamp structure (Hybrid Logical Clock)
interface Timestamp {
  millis: number;   // Physical time in milliseconds
  counter: number;  // Logical counter for same-millisecond ordering
  nodeId: string;   // Node identifier for tie-breaking
}

// LWWRecord structure
interface LWWRecord<V> {
  value: V | null;        // null indicates tombstone (deleted)
  timestamp: Timestamp;
  ttlMs?: number;         // Optional TTL in milliseconds
}

// ORMapRecord structure
interface ORMapRecord<V> {
  value: V;
  timestamp: Timestamp;
  tag: string;            // Unique identifier for this entry
  ttlMs?: number;
}

LWWMap (Last-Write-Wins Map)

The LWWMap is a key-value store where conflicts are resolved by accepting the update with the latest timestamp. Access it via client.getMap<K, V>(name).

Reading Data

Retrieve values by key or iterate over entries.
LWWMap<K, V>

Parameters

  • get(key: K)
    V | undefined. Returns the value for the given key, or undefined if not found, deleted, or expired.
  • getRecord(key: K)
    LWWRecord<V> | undefined. Returns the full record including timestamp and TTL. Useful for sync operations.
  • entries()
    IterableIterator<[K, V]>. Returns an iterator over all non-deleted, non-expired entries as [key, value] pairs.
  • allKeys()
    IterableIterator<K>. Returns an iterator over all keys including tombstones. Use getRecord() to check if deleted.
  • size
    number. The total number of entries in the map (including tombstones).
Reading from LWWMap
const users = client.getMap<string, User>('users');

// Read a single value by key
const user = users.get('user-123');
// Returns: User | undefined

// Get the full record with timestamp
const record = users.getRecord('user-123');
// Returns: LWWRecord<User> | undefined
// { value: User, timestamp: { millis, counter, nodeId }, ttlMs? }

Creating & Updating Data

Use set() for both create and update operations.
LWWMap<K, V>

Parameters

  • set(key: K, value: V, ttlMs?: number)
    LWWRecord<V>. Sets the value for the given key. Returns the created record with timestamp. Optional TTL in milliseconds for auto-expiration.
Creating & Updating in LWWMap
const users = client.getMap<string, User>('users');

// Create a new entry - returns the created record
const id = crypto.randomUUID();
const record = users.set(id, {
  id,
  name: 'Alice',
  email: 'alice@example.com',
  createdAt: Date.now()
});
// Returns: LWWRecord<User>
// { value: {...}, timestamp: { millis, counter, nodeId } }

// Set with TTL (auto-expires after 1 hour)
const tempRecord = users.set('session-123', sessionData, 3600000);

Note: LWWMap performs full replacement on set(). To update a single field, read the current value first, merge your changes, then set the entire object.

Deleting Data

Remove entries by key. Creates a tombstone for sync.
LWWMap<K, V>

Parameters

  • remove(key: K)
    LWWRecord<V>. Removes the entry for the given key. Returns a tombstone record (value: null) for sync.
  • clear()
    void. Clears all data and tombstones. Resets the internal Merkle tree.
Deleting from LWWMap
const users = client.getMap<string, User>('users');

// Delete by key - returns the tombstone record
const tombstone = users.remove('user-123');
// Returns: LWWRecord<User>
// { value: null, timestamp: { millis, counter, nodeId } }

Iterating Over Data

Access entries using iterators.
Iterating LWWMap
const users = client.getMap<string, User>('users');

// Iterate over all non-deleted entries
for (const [key, value] of users.entries()) {
  console.log(key, value);
}

// Get all keys (including tombstones)
for (const key of users.allKeys()) {
  const record = users.getRecord(key);
  if (record?.value !== null) {
    console.log('Active:', key);
  }
}

// Get the size (total entries including tombstones)
console.log('Total entries:', users.size);

Observing Changes

React to changes in real-time.
LWWMap<K, V>

Parameters

  • onChange(callback: () => void)
    () => void. Registers a callback that fires whenever the map changes. Returns an unsubscribe function.
Observing LWWMap Changes
const users = client.getMap<string, User>('users');

// Subscribe to any changes in this map
const unsubscribe = users.onChange(() => {
  console.log('Map changed!');
  // Re-read data as needed
});

// Later: stop listening
unsubscribe();

Synchronization Methods

Low-level methods for CRDT synchronization.
LWWMap<K, V> - Sync Methods

Parameters

  • merge(key: K, remoteRecord: LWWRecord<V>)
    boolean. Merges a record from a remote source. Returns true if local state was updated (remote wins).
  • prune(olderThan: Timestamp)
    K[]. Garbage collection: removes tombstones older than the specified timestamp. Returns array of pruned keys.
  • getMerkleTree()
    MerkleTree. Returns the internal Merkle tree for efficient sync protocol.
Synchronization
// For synchronization - merge a remote record
const remoteRecord: LWWRecord<User> = {
  value: { id: 'u1', name: 'Bob' },
  timestamp: { millis: 1678900000, counter: 0, nodeId: 'node-2' }
};

// Returns true if local state was updated
const changed = users.merge('u1', remoteRecord);

ORMap (Observed-Remove Map)

The ORMap is designed for collections where a key can have multiple values. Concurrent additions are preserved, and removals only affect observed items. Access it via client.getORMap<K, V>(name).

Adding Data

Add items to a collection. Multiple items can share the same key.
ORMap<K, V>

Parameters

  • add(key: K, value: V, ttlMs?: number)
    ORMapRecord<V>. Adds a value under the given key. Returns an ORMapRecord with unique tag and timestamp. Multiple adds with same key create separate entries.
Adding to ORMap
const todos = client.getORMap<string, Todo>('todos');

// Add an item to a collection
// Each add() creates a unique entry, even with same key
const record = todos.add('active', { title: 'Write docs', priority: 1 });
// Returns: ORMapRecord
// { value: {...}, timestamp: {...}, tag: "unique-id" }

// Add with TTL (auto-expires)
const tempRecord = todos.add('active', { title: 'Temp task' }, 3600000);

// Add another item with same key (both are preserved)
todos.add('active', { title: 'Fix bugs', priority: 2 });

Reading Data

Retrieve all values for a key.
ORMap<K, V>

Parameters

  • get(key: K)
    V[]. Returns an array of all active (non-tombstoned, non-expired) values for the key.
  • getRecords(key: K)
    ORMapRecord<V>[]. Returns full records with metadata for the key. Useful for sync operations.
  • size
    number. The number of unique keys that have at least one value.
  • totalRecords
    number. The total count of all active records across all keys.
Reading from ORMap
const todos = client.getORMap<string, Todo>('todos');

// Get all values for a key (returns array)
const activeTodos = todos.get('active');
// Returns: Todo[] (all items added with key 'active')

// Get full records with metadata
const records = todos.getRecords('active');
// Returns: ORMapRecord<Todo>[]
// [{ value: {...}, timestamp: {...}, tag: "..." }, ...]

// Get count of unique keys
console.log('Keys:', todos.size);

// Get total record count across all keys
console.log('Total records:', todos.totalRecords);

Removing Data

Remove specific values from a key.
ORMap<K, V>

Parameters

  • remove(key: K, value: V)
    string[]. Removes a specific value from the key using strict equality (===). Returns array of removed tags for sync.
  • clear()
    void. Clears all data and tombstones.
Removing from ORMap
const todos = client.getORMap<string, Todo>('todos');

// Remove specific value from a key
// Returns array of removed tags (for sync)
const removedTags = todos.remove('active', todoToRemove);
// Returns: string[] - tags that were removed

// Note: Uses strict equality (===) for matching
// For objects, you need the exact same reference

Note: remove() uses strict equality (===). For objects, you need the exact same reference that was added. Consider storing a unique ID in your values for easier removal.

Observing Changes

React to additions and removals in real-time.
ORMap<K, V>

Parameters

  • onChange(callback: () => void)
    () => void. Registers a callback that fires whenever the map changes. Returns an unsubscribe function.
Observing ORMap Changes
const todos = client.getORMap<string, Todo>('todos');

// Subscribe to changes
const unsubscribe = todos.onChange(() => {
  console.log('ORMap changed!');
  // Re-read data as needed
});

// Later: stop listening
unsubscribe();

Synchronization Methods

Low-level methods for CRDT synchronization.
ORMap<K, V> - Sync Methods

Parameters

  • apply(key: K, record: ORMapRecord<V>)
    void. Applies a record from a remote source. Ignored if the tag is already tombstoned.
  • applyTombstone(tag: string)
    void. Applies a tombstone (deletion) from a remote source. Removes the record with matching tag.
  • getTombstones()
    string[]. Returns all tombstone tags. Used for sync protocol.
  • merge(other: ORMap<K, V>)
    void. Merges state from another ORMap instance. Unions items and tombstones.
  • prune(olderThan: Timestamp)
    string[]. Garbage collection: removes tombstones older than the timestamp. Returns pruned tags.
Synchronization
// For synchronization - apply a remote record
const remoteRecord: ORMapRecord<Todo> = {
  value: { title: 'Remote task' },
  timestamp: { millis: 1678900000, counter: 0, nodeId: 'node-2' },
  tag: 'unique-tag-from-remote'
};

// Apply the record (idempotent - ignores if tag is tombstoned)
todos.apply('active', remoteRecord);

// Apply a tombstone (deletion from remote)
todos.applyTombstone('unique-tag-from-remote');

// Get all tombstones (for sync protocol)
const tombstones = todos.getTombstones();
// Returns: string[]

Querying Data

For more advanced data retrieval with filtering, sorting, and live updates, use client.query().

client.query<T>(mapName, options?)

Parameters

  • mapName
    string. The name of the map to query.
  • options.where?
    Partial<T>. Simple equality filter. Returns entries where all specified fields match.
  • options.predicate?
    Predicate. Complex filter using Predicates (and, or, gt, lt, contains, etc.).
  • options.sort?
    { [field]: 'asc' | 'desc' }. Sort order for results.

Basic Query

Basic Query
// Query with filters
const query = client.query<Todo>('todos', {
  where: { completed: false },
  sort: { createdAt: 'desc' }
});

// Subscribe to live results
const unsubscribe = query.subscribe((results) => {
  console.log('Matching todos:', results);
});

// Get current snapshot (one-time read)
const snapshot = await query.get();

Complex Predicates

Query with Predicates
import { Predicates } from '@topgunbuild/client';

// Complex filtering with predicates
const query = client.query<Product>('products', {
  predicate: Predicates.and(
    Predicates.gt('price', 100),
    Predicates.lt('price', 500),
    Predicates.contains('tags', 'electronics')
  )
});

Available Predicates

PredicateDescriptionExample
eq(field, value)Equal toPredicates.eq(‘status’, ‘active’)
neq(field, value)Not equal toPredicates.neq(‘status’, ‘deleted’)
gt(field, value)Greater thanPredicates.gt(‘price’, 100)
gte(field, value)Greater than or equalPredicates.gte(‘age’, 18)
lt(field, value)Less thanPredicates.lt(‘stock’, 10)
lte(field, value)Less than or equalPredicates.lte(‘priority’, 5)
contains(field, value)Array contains valuePredicates.contains(‘tags’, ‘featured’)
and(…predicates)All conditions must matchPredicates.and(p1, p2, p3)
or(…predicates)Any condition must matchPredicates.or(p1, p2)
not(predicate)Negates a conditionPredicates.not(Predicates.eq(‘hidden’, true))

CRUD Summary

OperationLWWMapORMap
Createmap.set(key, value)map.add(key, value)
Read (single)map.get(key)map.get(key)
Read (with metadata)map.getRecord(key)map.getRecords(key)
Read (all)map.entries()
Read (filtered)client.query(name, { where, predicate })
Updatemap.set(key, newValue)map.remove() + map.add()
Deletemap.remove(key)map.remove(key, value)
Subscribemap.onChange(callback)
Sync (merge)map.merge(key, record)map.apply() / map.merge()
Garbage collectmap.prune(olderThan)