Files
wifi-densepose/vendor/ruvector/npm/packages/raft/src/state.ts

201 lines
5.1 KiB
TypeScript

/**
* Raft State Management
* Manages persistent and volatile state for Raft consensus
*/
import type {
NodeId,
Term,
LogIndex,
PersistentState,
VolatileState,
LeaderState,
LogEntry,
} from './types.js';
import { RaftLog } from './log.js';
/** State manager for a Raft node */
export class RaftState<T = unknown> {
private _currentTerm: Term = 0;
private _votedFor: NodeId | null = null;
private _commitIndex: LogIndex = 0;
private _lastApplied: LogIndex = 0;
private _leaderState: LeaderState | null = null;
public readonly log: RaftLog<T>;
constructor(
private readonly nodeId: NodeId,
private readonly peers: NodeId[],
options?: {
onPersist?: (state: PersistentState<T>) => Promise<void>;
onLogPersist?: (entries: LogEntry<T>[]) => Promise<void>;
},
) {
this.log = new RaftLog({ onPersist: options?.onLogPersist });
this.persistCallback = options?.onPersist;
}
private persistCallback?: (state: PersistentState<T>) => Promise<void>;
/** Get current term */
get currentTerm(): Term {
return this._currentTerm;
}
/** Get voted for */
get votedFor(): NodeId | null {
return this._votedFor;
}
/** Get commit index */
get commitIndex(): LogIndex {
return this._commitIndex;
}
/** Get last applied */
get lastApplied(): LogIndex {
return this._lastApplied;
}
/** Get leader state (null if not leader) */
get leaderState(): LeaderState | null {
return this._leaderState;
}
/** Update term (with persistence) */
async setTerm(term: Term): Promise<void> {
if (term > this._currentTerm) {
this._currentTerm = term;
this._votedFor = null;
await this.persist();
}
}
/** Record vote (with persistence) */
async vote(term: Term, candidateId: NodeId): Promise<void> {
this._currentTerm = term;
this._votedFor = candidateId;
await this.persist();
}
/** Update commit index */
setCommitIndex(index: LogIndex): void {
if (index > this._commitIndex) {
this._commitIndex = index;
}
}
/** Update last applied */
setLastApplied(index: LogIndex): void {
if (index > this._lastApplied) {
this._lastApplied = index;
}
}
/** Initialize leader state */
initLeaderState(): void {
const nextIndex = new Map<NodeId, LogIndex>();
const matchIndex = new Map<NodeId, LogIndex>();
for (const peer of this.peers) {
// Initialize nextIndex to leader's last log index + 1
nextIndex.set(peer, this.log.lastIndex + 1);
// Initialize matchIndex to 0
matchIndex.set(peer, 0);
}
this._leaderState = { nextIndex, matchIndex };
}
/** Clear leader state */
clearLeaderState(): void {
this._leaderState = null;
}
/** Update nextIndex for a peer */
setNextIndex(peerId: NodeId, index: LogIndex): void {
if (this._leaderState) {
this._leaderState.nextIndex.set(peerId, Math.max(1, index));
}
}
/** Update matchIndex for a peer */
setMatchIndex(peerId: NodeId, index: LogIndex): void {
if (this._leaderState) {
this._leaderState.matchIndex.set(peerId, index);
}
}
/** Get nextIndex for a peer */
getNextIndex(peerId: NodeId): LogIndex {
return this._leaderState?.nextIndex.get(peerId) ?? this.log.lastIndex + 1;
}
/** Get matchIndex for a peer */
getMatchIndex(peerId: NodeId): LogIndex {
return this._leaderState?.matchIndex.get(peerId) ?? 0;
}
/** Update commit index based on match indices (for leader) */
updateCommitIndex(): boolean {
if (!this._leaderState) return false;
// Find the highest index N such that a majority have matchIndex >= N
// and log[N].term == currentTerm
const matchIndices = Array.from(this._leaderState.matchIndex.values());
matchIndices.push(this.log.lastIndex); // Include self
matchIndices.sort((a, b) => b - a); // Sort descending
const majority = Math.floor((this.peers.length + 1) / 2) + 1;
for (const index of matchIndices) {
if (index <= this._commitIndex) break;
const term = this.log.termAt(index);
if (term === this._currentTerm) {
// Count how many have this index or higher
const count =
matchIndices.filter((m) => m >= index).length + 1; // +1 for self
if (count >= majority) {
this._commitIndex = index;
return true;
}
}
}
return false;
}
/** Get persistent state */
getPersistentState(): PersistentState<T> {
return {
currentTerm: this._currentTerm,
votedFor: this._votedFor,
log: this.log.getAll(),
};
}
/** Get volatile state */
getVolatileState(): VolatileState {
return {
commitIndex: this._commitIndex,
lastApplied: this._lastApplied,
};
}
/** Load persistent state */
loadPersistentState(state: PersistentState<T>): void {
this._currentTerm = state.currentTerm;
this._votedFor = state.votedFor;
this.log.load(state.log);
}
/** Persist state */
private async persist(): Promise<void> {
if (this.persistCallback) {
await this.persistCallback(this.getPersistentState());
}
}
}