Conflict-free counter for Convex that appends writes to a log instead of updating a shared row, so concurrent mutations never contend or retry.
npm install convex-conflict-free-counterConflict Free Counter provides an eventually-consistent, contention-free counter built on a conflict-free log architecture. Every call to `add` appends a row to an internal log rather than updating a shared document, eliminating write contention entirely. A scheduled compaction pipeline periodically folds the log into a snapshot, and reads return `snapshot + visible log entries` along with a `fullyConsistent` flag indicating whether the returned count reflects every committed write.
The convex-conflict-free-counter component appends each increment to an internal log rather than updating a shared document, so concurrent mutations never conflict. Call counter.add(ctx, 'my-key') from any mutation or action and writes are durably committed without retries. A scheduled compaction pipeline folds the log into a snapshot periodically to keep read costs low.
convex-conflict-free-counter is designed for metrics, event counts, and tallies where many writers update the same key. Reads return both a count and a fullyConsistent boolean: true means the count reflects every write committed before the read, false means the log backlog exceeded the scan budget and the count may lag by up to the compaction delay (default 15 seconds). Every write is durable and the count always converges.
Because convex-conflict-free-counter appends to a log rather than mutating a single row, there is no OCC (optimistic concurrency control) contention between writers. Throughput scales with write volume automatically, unlike sharded counters where you must pre-select a shard count. Use counter.addMany to batch multiple key updates into a single component call.
Pass logScanLimit: 0 to counter.count() to read only the compacted snapshot, skipping the live log. This means the query only re-runs once per compaction cycle rather than on every increment, which reduces reactive subscription overhead in high-write scenarios.
convex-conflict-free-counter does not require you to choose a shard count upfront or rebalance shards as load changes. Throughput scales automatically because every write appends a new log row rather than contending on a fixed set of shards. The trade-off is that reads can lag by up to the compaction delay (default 15 seconds) during high write bursts, while sharded-counter trades read contention against shard count instead.
No. Every call to counter.add() is committed in the calling mutation's transaction before the function returns. The compaction pipeline only consolidates existing log entries into a snapshot; it does not gate durability. The count always converges to the true total regardless of compaction timing.
fullyConsistent: true means the returned count includes every write committed before the query started, because the snapshot plus the scanned log tail covered all entries. fullyConsistent: false means more log entries existed than the read scanned, so the count may temporarily be missing recent increments. The lag is bounded by the compactionDelay setting, which defaults to 15 seconds.
Run npm install convex-conflict-free-counter, then add app.use(conflictFreeCounter) in your convex/convex.config.ts file after importing from convex-conflict-free-counter/convex.config. Instantiate the client with new ConflictFreeCounter(components.conflictFreeCounter) in a shared module and import that instance in your mutation and query files.
Yes. The delta argument to counter.add() accepts any Convex number, including negative values. For example, counter.add(ctx, 'credits:remaining', -3) subtracts 3 from that key. Deltas are IEEE 754 doubles, so integer counts remain exact up to 2^53.