Maps (LWWMap, ORMap, GCounterMap)
Three map CRDTs, picked by what the values are:
| Type | Value type | When |
|---|---|---|
LWWMap<K, V> | Plain values (LWW per key) | Per-key config — flags, settings. |
ORMap<K, C> | Other CRDTs | Per-key counters, sets, registers. |
GCounterMap<K> | Implicit GCounter values | Per-key counts (clicks per page, requests per route). |
GCounterMap<K> is sugar for ORMap<K, GCounter> — the common
case so common it gets its own type.
LWWMap
Section titled “LWWMap”import { LWWMap } from 'actor-ts';
let m = LWWMap.empty<string, string>();m = m.put('node-a', 'theme', 'dark', 1000);m = m.put('node-b', 'lang', 'en', 2000);m.get('theme'); // → 'dark'm.get('lang'); // → 'en'Per-key LWW semantics — same as LWWRegister, but indexed. Concurrent writes to the same key resolve by timestamp; writes to different keys don’t conflict.
m = m.remove('theme'); // also LWW — most-recent action winsremove is also tracked as an LWW operation — a recent remove
beats an older put, even from a different replica.
Use for per-key single-value config: user preferences, feature flag overrides per-tenant, version per-resource.
import { ORMap, GCounter } from 'actor-ts';
let m = ORMap.empty<string, GCounter>();m = m.update('node-a', 'clicks', GCounter.empty, (c) => c.increment('node-a', 1),);m = m.update('node-b', 'views', GCounter.empty, (c) => c.increment('node-b', 1),);m.get('clicks')?.value(); // → 1The values are themselves CRDTs. Each key’s value merges
using the value-CRDT’s own merge — GCounter keys merge
per-replica-max, ORSet keys add-wins, etc.
m = m.update('a', 'tags', () => ORSet.empty<string>(), (s) => s.add('a', 'urgent'),);Heterogeneous value types are allowed but discouraged — a single ORMap typically holds one CRDT kind per map. If different keys hold different kinds, the framework can’t infer types and you fight the compiler.
Use for per-key replicated state that needs CRDT semantics on each key — per-user shopping carts, per-feature counter + last-changed register.
Remove
Section titled “Remove”m = m.remove('clicks');Removing a key from ORMap uses the OR-semantics — tagged
operation that concurrent re-adds can override. Same “add wins”
semantics as ORSet.
GCounterMap
Section titled “GCounterMap”import { GCounterMap } from 'actor-ts';
let m = GCounterMap.empty<string>();m = m.increment('node-a', 'page-home', 1);m = m.increment('node-a', 'page-about', 1);m = m.increment('node-b', 'page-home', 1);
m.get('page-home'); // → 2 (sum across replicas)m.get('page-about'); // → 1A GCounterMap<K> is functionally ORMap<K, GCounter> — a map
where each value is a counter. It exists because per-key counts
are common enough (page views per URL, clicks per button,
requests per route) to warrant a typed shortcut.
The API is flatter:
m.increment(replicaId, key, delta); // bump one key's counterm.get(key); // → number (sum across replicas)m.entries(); // → IterableIterator<[K, number]>Use anywhere you’d type Map<K, number> in regular code and need
concurrent-write convergence.
Picking between them
Section titled “Picking between them”Per-key value type?├── A plain value with last-writer-wins resolution → LWWMap├── A CRDT (counter, set, register, another map) → ORMap└── A counter (very common case) → GCounterMap (sugar over ORMap<K, GCounter>)Common shapes:
- Per-user preferences —
LWWMap<UserId, UserPrefs>(user pref edits are LWW; conflicts are rare). - Per-tenant feature flags —
LWWMap<TenantId, FlagSet>. - Per-resource view counts —
GCounterMap<ResourceId>. - Per-room online members —
ORMap<RoomId, ORSet<UserId>>. - Per-shard rebalance counts —
GCounterMap<ShardId>.
Composition
Section titled “Composition”// Per-room subscription set + per-room message countlet rooms = ORMap.empty<string, ORSet<string>>();let counts = GCounterMap.empty<string>();
rooms = rooms.update('a', 'general', () => ORSet.empty<string>(), (s) => s.add('a', 'user-1'),);counts = counts.increment('a', 'general', 1);ORMap of ORSet is the common shape for map of sets.
ORMap of LWWRegister<T> is a typed version of LWWMap<K, T>
with all the same semantics (use LWWMap directly when that’s
what you mean).
Where to next
Section titled “Where to next”- Counters — for non-keyed counters.
- Registers — for non-keyed single values.
- Sets — for non-keyed collections.
- Designing data — picking between flat and map CRDTs.
The LWWMap,
ORMap, and
GCounterMap API references
cover the full surface.