A small runtime for storing garbage-collection acknowledgement frontiers for Sovereignbase CRDT replicas.
garbageCollect(frontiers).CRSet, CRMap, CRList, CRText, and CRStruct acknowledgement types.npm install @sovereignbase/frontier-store
# or
pnpm add @sovereignbase/frontier-store
# or
yarn add @sovereignbase/frontier-store
# or
bun add @sovereignbase/frontier-store
# or
deno add jsr:@sovereignbase/frontier-store
# or
vlt install jsr:@sovereignbase/frontier-store
import { CRSet } from '@sovereignbase/convergent-replicated-set'
import { FrontierStore } from '@sovereignbase/frontier-store'
const alice = new CRSet<string>()
const bob = new CRSet<string>()
const frontiers = new FrontierStore()
alice.addEventListener('delta', (event) => {
bob.merge(event.detail)
})
bob.addEventListener('delta', (event) => {
alice.merge(event.detail)
})
alice.addEventListener('ack', (event) => {
frontiers.setFrontier('set', 'shopping-list', 'alice', event.detail)
})
bob.addEventListener('ack', (event) => {
frontiers.setFrontier('set', 'shopping-list', 'bob', event.detail)
})
alice.add('milk')
alice.delete('milk')
bob.add('coffee')
alice.acknowledge()
bob.acknowledge()
const acknowledged = frontiers.getFrontiers('set', 'shopping-list')
alice.garbageCollect(acknowledged)
bob.garbageCollect(acknowledged)
import { FrontierStore } from '@sovereignbase/frontier-store'
const store = new FrontierStore(
JSON.parse(localStorage.getItem('frontiers') ?? '{}')
)
store.setFrontier(
'text',
'article:homepage',
'device:laptop',
'0198f0a8-1357-7c00-8000-000000000001'
)
localStorage.setItem('frontiers', JSON.stringify(store))
Snapshots are plain structured-clone-compatible objects:
type FrontierStoreSnapshot = {
set?: Record<string, Record<string, CRSetAck>>
map?: Record<string, Record<string, CRMapAck>>
list?: Record<string, Record<string, CRListAck>>
text?: Record<string, Record<string, CRTextAck>>
struct?: Record<string, Record<string, CRStructAck<Record<string, unknown>>>>
}
import { FrontierStore } from '@sovereignbase/frontier-store'
const store = new FrontierStore()
store.setFrontier('map', 'profile:alice', 'phone', mapAck)
store.setFrontier('text', 'profile:alice/name', 'phone', textAck)
store.setFrontier('struct', 'profile:alice/settings', 'phone', structAck)
profileMap.garbageCollect(store.getFrontiers('map', 'profile:alice'))
nameText.garbageCollect(store.getFrontiers('text', 'profile:alice/name'))
settingsStruct.garbageCollect(
store.getFrontiers('struct', 'profile:alice/settings')
)
The kind and targetId are application-level routing keys. Keep them stable
for the lifetime of the CRDT object whose acknowledgements they describe.
import { FrontierStore } from '@sovereignbase/frontier-store'
const store = new FrontierStore(snapshot)
store.deleteFrontier('set', 'shopping-list', 'old-phone')
store.deleteFrontier('text', 'article:homepage')
Passing an entityId removes one acknowledging entity. Omitting it removes all
frontiers for the target. Empty target and kind buckets are cleaned up.
setFrontier() stores the provided acknowledgement value.getFrontiers() returns the currently stored acknowledgement values.toJSON(), JSON.stringify(), inspection symbols, and iteration expose the current snapshot model.deleteFrontier() is idempotent for missing kinds, targets, and entities.FrontierStore does not decide when a CRDT may compact history.(kind, targetId, entityId).garbageCollect().npm run test
The test suite covers:
FrontierStore storage, replacement, deletion, cleanup, iteration, inspection, and serialization behavior.getFrontiers(), toJSON(), and iteration.CRSet, CRMap, CRList, CRText, and CRStruct: collect ack events, store them by target/entity, retrieve complete frontier arrays, run garbageCollect(), settle replicas, and hydrate snapshots.dist/**/*.js with 100% statements, branches, functions, and lines via c8.npm run bench
The benchmark suite measures FrontierStore orchestration paths:
setFrontier() for string and struct acknowledgementsgetFrontiers()toJSON()Apache-2.0