splitwise / src /lib /server /ledger /fold.ts
assafvayner's picture
assafvayner HF Staff
feat(ledger): add event schema, jsonl encoding and fold
a896df9
Raw History Blame Contribute Delete
1.92 kB
import type { Expense, Settlement } from '../../domain/types';
import type { LedgerEvent } from './events';
export interface FoldedState {
expenses: Map<string, Expense>;
settlements: Map<string, Settlement>;
/** Winning event per entity id (may be a tombstone). */
latest: Map<string, LedgerEvent>;
/** All events per entity id, sorted by version. */
history: Map<string, LedgerEvent[]>;
eventIds: Set<string>;
}
export function newFoldedState(): FoldedState {
return { expenses: new Map(), settlements: new Map(), latest: new Map(), history: new Map(), eventIds: new Set() };
}
/** >0 if a should win over b. Higher version wins; ties by ts, then id. */
export function compareEvents(a: LedgerEvent, b: LedgerEvent): number {
if (a.version !== b.version) return a.version - b.version;
if (a.ts !== b.ts) return a.ts < b.ts ? -1 : 1;
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
}
/** Applies one event. Returns false if the event id was already applied. */
export function applyEvent(state: FoldedState, event: LedgerEvent): boolean {
if (state.eventIds.has(event.id)) return false;
state.eventIds.add(event.id);
const hist = state.history.get(event.entityId) ?? [];
hist.push(event);
hist.sort(compareEvents);
state.history.set(event.entityId, hist);
const current = state.latest.get(event.entityId);
if (current && compareEvents(event, current) <= 0) return true;
state.latest.set(event.entityId, event);
if (event.kind === 'expense') {
if (event.op === 'upsert') state.expenses.set(event.entityId, event.data);
else state.expenses.delete(event.entityId);
} else {
if (event.op === 'upsert') state.settlements.set(event.entityId, event.data);
else state.settlements.delete(event.entityId);
}
return true;
}
export function foldEvents(events: Iterable<LedgerEvent>): FoldedState {
const state = newFoldedState();
for (const e of events) applyEvent(state, e);
return state;
}