8.5 KiB
OpenSpec Change: enterprise-crdt
- Change ID: enterprise-crdt
- Package:
@suaveplan/crdt - Category: universal
- Target path:
universal/data/crdt - Status: PROPOSED
- Date: 2026-04-24
- Reference spec:
/mnt/s/web/suaveplan/enterprise/crdt.md
Summary
Deliver @suaveplan/crdt on the genesis branch from scratch at enterprise quality. The package provides conflict-free replicated data type primitives: G-Counter, PN-Counter, OR-Set, LWW-Register, MV-Register, 2P-Set, and RGA (Replicated Growable Array). All CRDTs implement a common CRDT interface with merge, value, and toJSON/fromJSON for serialization. Genesis adds structured errors via @suaveplan/error, event-bus integration via @suaveplan/events for state-change propagation, diff/patch integration via @suaveplan/diff for efficient delta exchange, injectable clock and ID generator, OTel instrumentation, and property-based correctness proofs.
Current-state audit (genesis)
The package does not exist on genesis yet — fresh build. No prior source, no test results, no coverage data.
Available workspace deps on genesis
| Package | Role |
|---|---|
@suaveplan/error (workspace:^) |
Base class for CrdtError; defineCodes for CRDT.* codes |
@suaveplan/types (workspace:^) |
Clock, IdGenerator, EventBusLike injection interfaces |
@suaveplan/events (workspace:^) |
Emit typed state-change events on merge |
@suaveplan/diff (workspace:^) |
Delta encoding and patch application for efficient state sync |
zod (catalog:forms) |
Zod schemas for all public option and serialization types |
@opentelemetry/api (catalog:observability) |
Spans and counters on merge, update, and sync operations |
tslib (catalog:build) |
Runtime helpers |
Dev deps: vitest, @vitest/coverage-istanbul, fast-check, mitata (all catalog:testing).
Public API surface
Common CRDT interface
interface CRDT<TValue, TState = unknown> {
readonly id: string
value(): TValue
merge(remote: TState): void
state(): TState
toJSON(): string
fromJSON(json: string): void
clone(): this
}
interface CrdtOptions {
id?: string
clock?: Clock
idGenerator?: IdGenerator
}
G-Counter
class GCounter implements CRDT<number, GCounterState> {
increment(nodeId: string, delta?: number): void
get(nodeId: string): number
total(): number
}
interface GCounterState {
type: "g-counter"
counters: Record<string, number>
}
PN-Counter
class PNCounter implements CRDT<number, PNCounterState> {
increment(nodeId: string, delta?: number): void
decrement(nodeId: string, delta?: number): void
total(): number
}
interface PNCounterState {
type: "pn-counter"
increments: Record<string, number>
decrements: Record<string, number>
}
OR-Set
class ORSet<T> implements CRDT<Set<T>, ORSetState<T>> {
add(element: T, nodeId: string): void
remove(element: T): void
has(element: T): boolean
size(): number
}
interface ORSetState<T> {
type: "or-set"
entries: Array<{ element: T; tag: string; tombstone: boolean }>
}
LWW-Register
class LWWRegister<T> implements CRDT<T | undefined, LWWRegisterState<T>> {
set(value: T, nodeId: string, timestamp?: number): void
get(): T | undefined
}
interface LWWRegisterState<T> {
type: "lww-register"
value: T | undefined
timestamp: number
nodeId: string
}
MV-Register
class MVRegister<T> implements CRDT<T[], MVRegisterState<T>> {
set(value: T, nodeId: string): void
get(): T[]
conflict(): boolean
}
interface MVRegisterState<T> {
type: "mv-register"
entries: Array<{ value: T; vectorClock: Record<string, number> }>
}
2P-Set
class TwoPSet<T> implements CRDT<Set<T>, TwoPSetState<T>> {
add(element: T): void
remove(element: T): void
has(element: T): boolean
}
interface TwoPSetState<T> {
type: "2p-set"
added: T[]
removed: T[]
}
RGA (Replicated Growable Array)
class RGA<T> implements CRDT<T[], RGAState<T>> {
insert(index: number, element: T, nodeId: string): void
delete(index: number): void
get(index: number): T
length(): number
}
interface RGAState<T> {
type: "rga"
elements: Array<{ id: string; value: T | null; tombstone: boolean }>
}
Error handling
class CrdtError extends SuaveplanError<CrdtErrorCode>
type CrdtErrorCode =
| "CRDT_MERGE_CONFLICT"
| "CRDT_INVALID_STATE"
| "CRDT_DESERIALIZE_FAILED"
| "CRDT_OPERATION_INVALID"
function registerCrdtErrorCodes(): void
Zod schemas
const GCounterStateSchema: ZodSchema
const PNCounterStateSchema: ZodSchema
const ORSetStateSchema: ZodSchema
const LWWRegisterStateSchema: ZodSchema
const MVRegisterStateSchema: ZodSchema
const TwoPSetStateSchema: ZodSchema
const RGAStateSchema: ZodSchema
New additions for genesis
1. Event-bus integration
Every CRDT constructor accepts an optional bus?: EventBusLike. When provided, merge operations emit typed events:
crdt:merged—{ id, type, previousState, nextState }crdt:updated—{ id, type, operation, args }
2. Delta-based sync via @suaveplan/diff
CrdtSyncManager wraps one or more CRDTs and uses @suaveplan/diff JSON Patch to compute minimal delta payloads between sync points. Callers exchange deltas rather than full state snapshots.
class CrdtSyncManager {
register<T extends CRDT<unknown>>(crdt: T): void
delta(since?: string): SyncDelta
apply(delta: SyncDelta): void
}
3. Vector clock utilities
class VectorClock {
increment(nodeId: string): void
merge(remote: VectorClockState): void
compare(other: VectorClockState): "before" | "after" | "concurrent" | "equal"
state(): VectorClockState
}
type VectorClockState = Record<string, number>
4. OTel instrumentation
- Per-operation spans:
crdt.<type>.merge,crdt.<type>.update,crdt.sync.delta,crdt.sync.apply - Counters:
crdt.merges_total,crdt.updates_total,crdt.conflicts_total - Attributes:
crdt.type,crdt.id,crdt.node_id
5. Property-based tests (fast-check)
- Commutativity:
merge(a, b) ≡ merge(b, a)for all CRDT types - Associativity:
merge(merge(a, b), c) ≡ merge(a, merge(b, c)) - Idempotency:
merge(a, a) ≡ a - Monotonicity:
value(merge(a, b)) ≥ value(a)for counter types - RGA convergence: concurrent inserts on two replicas converge after mutual merge
6. Benchmark suite (src/__benchmarks__/)
- GCounter with 100 nodes, 10k increments
- ORSet with 1k add/remove operations
- RGA with 1k concurrent inserts on two replicas
File layout
universal/data/crdt/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
constants/
index.ts
types/
common.ts
common.md
core/
crdt-interface/
crdt-interface.ts
crdt-interface.md
crdt-interface.types.ts
index.ts
vector-clock/
vector-clock.ts
vector-clock.md
vector-clock.types.ts
vector-clock.test.ts
index.ts
sync-manager/
sync-manager.ts
sync-manager.md
sync-manager.types.ts
sync-manager.test.ts
index.ts
crdts/
g-counter/
g-counter.ts
g-counter.md
g-counter.types.ts
g-counter.test.ts
index.ts
pn-counter/
pn-counter.ts
pn-counter.md
pn-counter.types.ts
pn-counter.test.ts
index.ts
or-set/
or-set.ts
or-set.md
or-set.types.ts
or-set.test.ts
index.ts
lww-register/
lww-register.ts
lww-register.md
lww-register.types.ts
lww-register.test.ts
index.ts
mv-register/
mv-register.ts
mv-register.md
mv-register.types.ts
mv-register.test.ts
index.ts
two-p-set/
two-p-set.ts
two-p-set.md
two-p-set.types.ts
two-p-set.test.ts
index.ts
rga/
rga.ts
rga.md
rga.types.ts
rga.test.ts
index.ts
__benchmarks__/
crdt.bench.ts
tests/
integration.test.ts
property.test.ts
Impact
Downstream packages: collaborative editing, offline-first sync, and multi-peer state reconciliation features. No existing genesis package depends on it yet.
Workspace dep prerequisites: @suaveplan/error, @suaveplan/types, @suaveplan/events, @suaveplan/diff must be published to Verdaccio before this package can be built.
Breaking changes: none (new package).