feat(crdt): add @robonen/crdt package

Hand-built CRDT primitives: Lamport clock + version vectors, op-log,
LWW register/map, RGA sequence, fractional indexing, marks store, sync
encode, and doc/replica. Includes eslint flat config + composite tsconfig.
This commit is contained in:
2026-06-07 16:28:58 +07:00
parent 70a8678743
commit 008d85a8fd
35 changed files with 1152 additions and 0 deletions
@@ -0,0 +1,20 @@
import { describe, expect, it } from 'vitest';
import { VersionVector, opId } from '../../clock';
import { decodeOps, decodeStateVector, encodeOps, encodeStateVector } from '..';
describe('sync encoding', () => {
it('round-trips a version vector through bytes', () => {
const vv = new VersionVector();
vv.observe(opId('a', 3));
vv.observe(opId('b', 1));
const restored = decodeStateVector(encodeStateVector(vv));
expect(restored.get('a')).toBe(3);
expect(restored.get('b')).toBe(1);
});
it('round-trips an op batch through bytes', () => {
const ops = [{ id: opId('a', 1), kind: 'insert', value: 'x' }];
expect(decodeOps(encodeOps(ops))).toEqual(ops);
});
});
+34
View File
@@ -0,0 +1,34 @@
import { VersionVector } from '../clock';
const encoder = new TextEncoder();
const decoder = new TextDecoder();
/**
* Transport-agnostic wire encoding. v1 is JSON-over-bytes — simple and
* debuggable; a compact varint format is a later optimization with no API change.
*/
export function encodeJson(value: unknown): Uint8Array {
return encoder.encode(JSON.stringify(value));
}
export function decodeJson<T>(bytes: Uint8Array): T {
return JSON.parse(decoder.decode(bytes)) as T;
}
/** Encode a version vector for a "what do you have?" sync handshake. */
export function encodeStateVector(vv: VersionVector): Uint8Array {
return encodeJson(vv.toJSON());
}
export function decodeStateVector(bytes: Uint8Array): VersionVector {
return VersionVector.fromJSON(decodeJson(bytes));
}
/** Encode a batch of ops (the delta or a full snapshot). */
export function encodeOps<Op>(ops: readonly Op[]): Uint8Array {
return encodeJson(ops);
}
export function decodeOps<Op>(bytes: Uint8Array): Op[] {
return decodeJson<Op[]>(bytes);
}
+1
View File
@@ -0,0 +1 @@
export * from './encode';