Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
1
vendor/ruvector/npm/packages/raft/src/index.d.ts.map
vendored
Normal file
1
vendor/ruvector/npm/packages/raft/src/index.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAGH,OAAO,EACL,MAAM,EACN,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,eAAe,EACf,aAAa,EACb,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,SAAS,EACT,aAAa,EACb,SAAS,EACT,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAGnC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC"}
|
||||
1
vendor/ruvector/npm/packages/raft/src/index.js.map
vendored
Normal file
1
vendor/ruvector/npm/packages/raft/src/index.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;;;AAEH,QAAQ;AACR,uCAoBoB;AAhBlB,qGAAA,SAAS,OAAA;AAUT,qGAAA,SAAS,OAAA;AACT,yGAAA,aAAa,OAAA;AACb,qGAAA,SAAS,OAAA;AAMX,MAAM;AACN,mCAAmC;AAA1B,iGAAA,OAAO,OAAA;AAEhB,QAAQ;AACR,uCAAuC;AAA9B,qGAAA,SAAS,OAAA;AAElB,OAAO;AACP,qCAAkE;AAAzD,mGAAA,QAAQ,OAAA"}
|
||||
78
vendor/ruvector/npm/packages/raft/src/index.ts
vendored
Normal file
78
vendor/ruvector/npm/packages/raft/src/index.ts
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @ruvector/raft - Raft Consensus Implementation
|
||||
*
|
||||
* A TypeScript implementation of the Raft consensus algorithm for
|
||||
* distributed systems, providing leader election, log replication,
|
||||
* and fault tolerance.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { RaftNode, RaftTransport, NodeState } from '@ruvector/raft';
|
||||
*
|
||||
* // Create a Raft node
|
||||
* const node = new RaftNode({
|
||||
* nodeId: 'node-1',
|
||||
* peers: ['node-2', 'node-3'],
|
||||
* electionTimeout: [150, 300],
|
||||
* heartbeatInterval: 50,
|
||||
* maxEntriesPerRequest: 100,
|
||||
* });
|
||||
*
|
||||
* // Set up transport for RPC communication
|
||||
* node.setTransport(myTransport);
|
||||
*
|
||||
* // Set up state machine for applying commands
|
||||
* node.setStateMachine(myStateMachine);
|
||||
*
|
||||
* // Listen for events
|
||||
* node.on('stateChange', (event) => {
|
||||
* console.log(`State changed: ${event.previousState} -> ${event.newState}`);
|
||||
* });
|
||||
*
|
||||
* node.on('leaderElected', (event) => {
|
||||
* console.log(`New leader: ${event.leaderId} in term ${event.term}`);
|
||||
* });
|
||||
*
|
||||
* // Start the node
|
||||
* node.start();
|
||||
*
|
||||
* // Propose a command (only works if leader)
|
||||
* if (node.isLeader) {
|
||||
* await node.propose({ type: 'SET', key: 'foo', value: 'bar' });
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
// Types
|
||||
export {
|
||||
NodeId,
|
||||
Term,
|
||||
LogIndex,
|
||||
NodeState,
|
||||
LogEntry,
|
||||
PersistentState,
|
||||
VolatileState,
|
||||
LeaderState,
|
||||
RaftNodeConfig,
|
||||
RequestVoteRequest,
|
||||
RequestVoteResponse,
|
||||
AppendEntriesRequest,
|
||||
AppendEntriesResponse,
|
||||
RaftError,
|
||||
RaftErrorCode,
|
||||
RaftEvent,
|
||||
StateChangeEvent,
|
||||
LeaderElectedEvent,
|
||||
LogCommittedEvent,
|
||||
} from './types.js';
|
||||
|
||||
// Log
|
||||
export { RaftLog } from './log.js';
|
||||
|
||||
// State
|
||||
export { RaftState } from './state.js';
|
||||
|
||||
// Node
|
||||
export { RaftNode, RaftTransport, StateMachine } from './node.js';
|
||||
44
vendor/ruvector/npm/packages/raft/src/log.d.ts
vendored
Normal file
44
vendor/ruvector/npm/packages/raft/src/log.d.ts
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Raft Log Implementation
|
||||
* Manages the replicated log with persistence support
|
||||
*/
|
||||
import { LogEntry, LogIndex, Term } from './types.js';
|
||||
/** In-memory log storage with optional persistence callback */
|
||||
export declare class RaftLog<T = unknown> {
|
||||
private entries;
|
||||
private persistCallback?;
|
||||
constructor(options?: {
|
||||
onPersist?: (entries: LogEntry<T>[]) => Promise<void>;
|
||||
});
|
||||
/** Get the last log index */
|
||||
get lastIndex(): LogIndex;
|
||||
/** Get the last log term */
|
||||
get lastTerm(): Term;
|
||||
/** Get log length */
|
||||
get length(): number;
|
||||
/** Get entry at index */
|
||||
get(index: LogIndex): LogEntry<T> | undefined;
|
||||
/** Get term at index */
|
||||
termAt(index: LogIndex): Term | undefined;
|
||||
/** Append entries to log */
|
||||
append(entries: LogEntry<T>[]): Promise<void>;
|
||||
/** Append a single command, returning the new entry */
|
||||
appendCommand(term: Term, command: T): Promise<LogEntry<T>>;
|
||||
/** Get entries starting from index */
|
||||
getFrom(startIndex: LogIndex, maxCount?: number): LogEntry<T>[];
|
||||
/** Get entries in range [start, end] */
|
||||
getRange(startIndex: LogIndex, endIndex: LogIndex): LogEntry<T>[];
|
||||
/** Truncate log from index (remove index and all following) */
|
||||
truncateFrom(index: LogIndex): void;
|
||||
/** Check if log is at least as up-to-date as given term/index */
|
||||
isUpToDate(lastLogTerm: Term, lastLogIndex: LogIndex): boolean;
|
||||
/** Check if log contains entry at index with matching term */
|
||||
containsEntry(index: LogIndex, term: Term): boolean;
|
||||
/** Get all entries */
|
||||
getAll(): LogEntry<T>[];
|
||||
/** Clear all entries */
|
||||
clear(): void;
|
||||
/** Load entries from storage */
|
||||
load(entries: LogEntry<T>[]): void;
|
||||
}
|
||||
//# sourceMappingURL=log.d.ts.map
|
||||
1
vendor/ruvector/npm/packages/raft/src/log.d.ts.map
vendored
Normal file
1
vendor/ruvector/npm/packages/raft/src/log.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["log.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEtD,+DAA+D;AAC/D,qBAAa,OAAO,CAAC,CAAC,GAAG,OAAO;IAC9B,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,eAAe,CAAC,CAA4C;gBAExD,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE;IAI/E,6BAA6B;IAC7B,IAAI,SAAS,IAAI,QAAQ,CAExB;IAED,4BAA4B;IAC5B,IAAI,QAAQ,IAAI,IAAI,CAEnB;IAED,qBAAqB;IACrB,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,yBAAyB;IACzB,GAAG,CAAC,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS;IAI7C,wBAAwB;IACxB,MAAM,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS;IAMzC,4BAA4B;IACtB,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BnD,uDAAuD;IACjD,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAWjE,sCAAsC;IACtC,OAAO,CAAC,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE;IAW/D,wCAAwC;IACxC,QAAQ,CAAC,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE;IAIjE,+DAA+D;IAC/D,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAInC,iEAAiE;IACjE,UAAU,CAAC,WAAW,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,GAAG,OAAO;IAO9D,8DAA8D;IAC9D,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO;IAMnD,sBAAsB;IACtB,MAAM,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE;IAIvB,wBAAwB;IACxB,KAAK,IAAI,IAAI;IAIb,gCAAgC;IAChC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI;CAInC"}
|
||||
122
vendor/ruvector/npm/packages/raft/src/log.js
vendored
Normal file
122
vendor/ruvector/npm/packages/raft/src/log.js
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Raft Log Implementation
|
||||
* Manages the replicated log with persistence support
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.RaftLog = void 0;
|
||||
/** In-memory log storage with optional persistence callback */
|
||||
class RaftLog {
|
||||
constructor(options) {
|
||||
this.entries = [];
|
||||
this.persistCallback = options?.onPersist;
|
||||
}
|
||||
/** Get the last log index */
|
||||
get lastIndex() {
|
||||
return this.entries.length > 0 ? this.entries[this.entries.length - 1].index : 0;
|
||||
}
|
||||
/** Get the last log term */
|
||||
get lastTerm() {
|
||||
return this.entries.length > 0 ? this.entries[this.entries.length - 1].term : 0;
|
||||
}
|
||||
/** Get log length */
|
||||
get length() {
|
||||
return this.entries.length;
|
||||
}
|
||||
/** Get entry at index */
|
||||
get(index) {
|
||||
return this.entries.find((e) => e.index === index);
|
||||
}
|
||||
/** Get term at index */
|
||||
termAt(index) {
|
||||
if (index === 0)
|
||||
return 0;
|
||||
const entry = this.get(index);
|
||||
return entry?.term;
|
||||
}
|
||||
/** Append entries to log */
|
||||
async append(entries) {
|
||||
if (entries.length === 0)
|
||||
return;
|
||||
// Find where to start appending (handle conflicting entries)
|
||||
for (const entry of entries) {
|
||||
const existing = this.get(entry.index);
|
||||
if (existing) {
|
||||
if (existing.term !== entry.term) {
|
||||
// Conflict: delete this and all following entries
|
||||
this.truncateFrom(entry.index);
|
||||
}
|
||||
else {
|
||||
// Same entry, skip
|
||||
continue;
|
||||
}
|
||||
}
|
||||
this.entries.push(entry);
|
||||
}
|
||||
// Sort by index to maintain order
|
||||
this.entries.sort((a, b) => a.index - b.index);
|
||||
if (this.persistCallback) {
|
||||
await this.persistCallback(this.entries);
|
||||
}
|
||||
}
|
||||
/** Append a single command, returning the new entry */
|
||||
async appendCommand(term, command) {
|
||||
const entry = {
|
||||
term,
|
||||
index: this.lastIndex + 1,
|
||||
command,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
await this.append([entry]);
|
||||
return entry;
|
||||
}
|
||||
/** Get entries starting from index */
|
||||
getFrom(startIndex, maxCount) {
|
||||
const result = [];
|
||||
for (const entry of this.entries) {
|
||||
if (entry.index >= startIndex) {
|
||||
result.push(entry);
|
||||
if (maxCount && result.length >= maxCount)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/** Get entries in range [start, end] */
|
||||
getRange(startIndex, endIndex) {
|
||||
return this.entries.filter((e) => e.index >= startIndex && e.index <= endIndex);
|
||||
}
|
||||
/** Truncate log from index (remove index and all following) */
|
||||
truncateFrom(index) {
|
||||
this.entries = this.entries.filter((e) => e.index < index);
|
||||
}
|
||||
/** Check if log is at least as up-to-date as given term/index */
|
||||
isUpToDate(lastLogTerm, lastLogIndex) {
|
||||
if (this.lastTerm !== lastLogTerm) {
|
||||
return this.lastTerm > lastLogTerm;
|
||||
}
|
||||
return this.lastIndex >= lastLogIndex;
|
||||
}
|
||||
/** Check if log contains entry at index with matching term */
|
||||
containsEntry(index, term) {
|
||||
if (index === 0)
|
||||
return true;
|
||||
const entry = this.get(index);
|
||||
return entry?.term === term;
|
||||
}
|
||||
/** Get all entries */
|
||||
getAll() {
|
||||
return [...this.entries];
|
||||
}
|
||||
/** Clear all entries */
|
||||
clear() {
|
||||
this.entries = [];
|
||||
}
|
||||
/** Load entries from storage */
|
||||
load(entries) {
|
||||
this.entries = [...entries];
|
||||
this.entries.sort((a, b) => a.index - b.index);
|
||||
}
|
||||
}
|
||||
exports.RaftLog = RaftLog;
|
||||
//# sourceMappingURL=log.js.map
|
||||
1
vendor/ruvector/npm/packages/raft/src/log.js.map
vendored
Normal file
1
vendor/ruvector/npm/packages/raft/src/log.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"log.js","sourceRoot":"","sources":["log.ts"],"names":[],"mappings":"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"}
|
||||
135
vendor/ruvector/npm/packages/raft/src/log.ts
vendored
Normal file
135
vendor/ruvector/npm/packages/raft/src/log.ts
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Raft Log Implementation
|
||||
* Manages the replicated log with persistence support
|
||||
*/
|
||||
|
||||
import { LogEntry, LogIndex, Term } from './types.js';
|
||||
|
||||
/** In-memory log storage with optional persistence callback */
|
||||
export class RaftLog<T = unknown> {
|
||||
private entries: LogEntry<T>[] = [];
|
||||
private persistCallback?: (entries: LogEntry<T>[]) => Promise<void>;
|
||||
|
||||
constructor(options?: { onPersist?: (entries: LogEntry<T>[]) => Promise<void> }) {
|
||||
this.persistCallback = options?.onPersist;
|
||||
}
|
||||
|
||||
/** Get the last log index */
|
||||
get lastIndex(): LogIndex {
|
||||
return this.entries.length > 0 ? this.entries[this.entries.length - 1].index : 0;
|
||||
}
|
||||
|
||||
/** Get the last log term */
|
||||
get lastTerm(): Term {
|
||||
return this.entries.length > 0 ? this.entries[this.entries.length - 1].term : 0;
|
||||
}
|
||||
|
||||
/** Get log length */
|
||||
get length(): number {
|
||||
return this.entries.length;
|
||||
}
|
||||
|
||||
/** Get entry at index */
|
||||
get(index: LogIndex): LogEntry<T> | undefined {
|
||||
return this.entries.find((e) => e.index === index);
|
||||
}
|
||||
|
||||
/** Get term at index */
|
||||
termAt(index: LogIndex): Term | undefined {
|
||||
if (index === 0) return 0;
|
||||
const entry = this.get(index);
|
||||
return entry?.term;
|
||||
}
|
||||
|
||||
/** Append entries to log */
|
||||
async append(entries: LogEntry<T>[]): Promise<void> {
|
||||
if (entries.length === 0) return;
|
||||
|
||||
// Find where to start appending (handle conflicting entries)
|
||||
for (const entry of entries) {
|
||||
const existing = this.get(entry.index);
|
||||
if (existing) {
|
||||
if (existing.term !== entry.term) {
|
||||
// Conflict: delete this and all following entries
|
||||
this.truncateFrom(entry.index);
|
||||
} else {
|
||||
// Same entry, skip
|
||||
continue;
|
||||
}
|
||||
}
|
||||
this.entries.push(entry);
|
||||
}
|
||||
|
||||
// Sort by index to maintain order
|
||||
this.entries.sort((a, b) => a.index - b.index);
|
||||
|
||||
if (this.persistCallback) {
|
||||
await this.persistCallback(this.entries);
|
||||
}
|
||||
}
|
||||
|
||||
/** Append a single command, returning the new entry */
|
||||
async appendCommand(term: Term, command: T): Promise<LogEntry<T>> {
|
||||
const entry: LogEntry<T> = {
|
||||
term,
|
||||
index: this.lastIndex + 1,
|
||||
command,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
await this.append([entry]);
|
||||
return entry;
|
||||
}
|
||||
|
||||
/** Get entries starting from index */
|
||||
getFrom(startIndex: LogIndex, maxCount?: number): LogEntry<T>[] {
|
||||
const result: LogEntry<T>[] = [];
|
||||
for (const entry of this.entries) {
|
||||
if (entry.index >= startIndex) {
|
||||
result.push(entry);
|
||||
if (maxCount && result.length >= maxCount) break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Get entries in range [start, end] */
|
||||
getRange(startIndex: LogIndex, endIndex: LogIndex): LogEntry<T>[] {
|
||||
return this.entries.filter((e) => e.index >= startIndex && e.index <= endIndex);
|
||||
}
|
||||
|
||||
/** Truncate log from index (remove index and all following) */
|
||||
truncateFrom(index: LogIndex): void {
|
||||
this.entries = this.entries.filter((e) => e.index < index);
|
||||
}
|
||||
|
||||
/** Check if log is at least as up-to-date as given term/index */
|
||||
isUpToDate(lastLogTerm: Term, lastLogIndex: LogIndex): boolean {
|
||||
if (this.lastTerm !== lastLogTerm) {
|
||||
return this.lastTerm > lastLogTerm;
|
||||
}
|
||||
return this.lastIndex >= lastLogIndex;
|
||||
}
|
||||
|
||||
/** Check if log contains entry at index with matching term */
|
||||
containsEntry(index: LogIndex, term: Term): boolean {
|
||||
if (index === 0) return true;
|
||||
const entry = this.get(index);
|
||||
return entry?.term === term;
|
||||
}
|
||||
|
||||
/** Get all entries */
|
||||
getAll(): LogEntry<T>[] {
|
||||
return [...this.entries];
|
||||
}
|
||||
|
||||
/** Clear all entries */
|
||||
clear(): void {
|
||||
this.entries = [];
|
||||
}
|
||||
|
||||
/** Load entries from storage */
|
||||
load(entries: LogEntry<T>[]): void {
|
||||
this.entries = [...entries];
|
||||
this.entries.sort((a, b) => a.index - b.index);
|
||||
}
|
||||
}
|
||||
71
vendor/ruvector/npm/packages/raft/src/node.d.ts
vendored
Normal file
71
vendor/ruvector/npm/packages/raft/src/node.d.ts
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Raft Node Implementation
|
||||
* Core Raft consensus algorithm implementation
|
||||
*/
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { NodeId, Term, LogIndex, NodeState, RaftNodeConfig, RequestVoteRequest, RequestVoteResponse, AppendEntriesRequest, AppendEntriesResponse, LogEntry, PersistentState } from './types.js';
|
||||
/** Transport interface for sending RPCs to peers */
|
||||
export interface RaftTransport<T = unknown> {
|
||||
/** Send RequestVote RPC to a peer */
|
||||
requestVote(peerId: NodeId, request: RequestVoteRequest): Promise<RequestVoteResponse>;
|
||||
/** Send AppendEntries RPC to a peer */
|
||||
appendEntries(peerId: NodeId, request: AppendEntriesRequest<T>): Promise<AppendEntriesResponse>;
|
||||
}
|
||||
/** State machine interface for applying committed entries */
|
||||
export interface StateMachine<T = unknown, R = void> {
|
||||
/** Apply a committed command to the state machine */
|
||||
apply(command: T): Promise<R>;
|
||||
}
|
||||
/** Raft consensus node */
|
||||
export declare class RaftNode<T = unknown, R = void> extends EventEmitter {
|
||||
private readonly config;
|
||||
private readonly state;
|
||||
private nodeState;
|
||||
private leaderId;
|
||||
private transport;
|
||||
private stateMachine;
|
||||
private electionTimer;
|
||||
private heartbeatTimer;
|
||||
private running;
|
||||
constructor(config: RaftNodeConfig);
|
||||
/** Get node ID */
|
||||
get nodeId(): NodeId;
|
||||
/** Get current state */
|
||||
get currentState(): NodeState;
|
||||
/** Get current term */
|
||||
get currentTerm(): Term;
|
||||
/** Get current leader ID */
|
||||
get leader(): NodeId | null;
|
||||
/** Check if this node is the leader */
|
||||
get isLeader(): boolean;
|
||||
/** Get commit index */
|
||||
get commitIndex(): LogIndex;
|
||||
/** Set transport for RPC communication */
|
||||
setTransport(transport: RaftTransport<T>): void;
|
||||
/** Set state machine for applying commands */
|
||||
setStateMachine(stateMachine: StateMachine<T, R>): void;
|
||||
/** Start the Raft node */
|
||||
start(): void;
|
||||
/** Stop the Raft node */
|
||||
stop(): void;
|
||||
/** Propose a command to be replicated (only works if leader) */
|
||||
propose(command: T): Promise<LogEntry<T>>;
|
||||
/** Handle RequestVote RPC from a candidate */
|
||||
handleRequestVote(request: RequestVoteRequest): Promise<RequestVoteResponse>;
|
||||
/** Handle AppendEntries RPC from leader */
|
||||
handleAppendEntries(request: AppendEntriesRequest<T>): Promise<AppendEntriesResponse>;
|
||||
/** Load persistent state */
|
||||
loadState(state: PersistentState<T>): void;
|
||||
/** Get current persistent state */
|
||||
getState(): PersistentState<T>;
|
||||
private transitionTo;
|
||||
private getRandomElectionTimeout;
|
||||
private resetElectionTimer;
|
||||
private clearTimers;
|
||||
private startElection;
|
||||
private startHeartbeat;
|
||||
private replicateToFollowers;
|
||||
private replicateToPeer;
|
||||
private applyCommitted;
|
||||
}
|
||||
//# sourceMappingURL=node.d.ts.map
|
||||
1
vendor/ruvector/npm/packages/raft/src/node.d.ts.map
vendored
Normal file
1
vendor/ruvector/npm/packages/raft/src/node.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["node.ts"],"names":[],"mappings":"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"}
|
||||
352
vendor/ruvector/npm/packages/raft/src/node.js
vendored
Normal file
352
vendor/ruvector/npm/packages/raft/src/node.js
vendored
Normal file
@@ -0,0 +1,352 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Raft Node Implementation
|
||||
* Core Raft consensus algorithm implementation
|
||||
*/
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.RaftNode = void 0;
|
||||
const eventemitter3_1 = __importDefault(require("eventemitter3"));
|
||||
const types_js_1 = require("./types.js");
|
||||
const state_js_1 = require("./state.js");
|
||||
/** Default configuration values */
|
||||
const DEFAULT_CONFIG = {
|
||||
electionTimeout: [150, 300],
|
||||
heartbeatInterval: 50,
|
||||
maxEntriesPerRequest: 100,
|
||||
};
|
||||
/** Raft consensus node */
|
||||
class RaftNode extends eventemitter3_1.default {
|
||||
constructor(config) {
|
||||
super();
|
||||
this.nodeState = types_js_1.NodeState.Follower;
|
||||
this.leaderId = null;
|
||||
this.transport = null;
|
||||
this.stateMachine = null;
|
||||
this.electionTimer = null;
|
||||
this.heartbeatTimer = null;
|
||||
this.running = false;
|
||||
this.config = { ...DEFAULT_CONFIG, ...config };
|
||||
this.state = new state_js_1.RaftState(config.nodeId, config.peers);
|
||||
}
|
||||
/** Get node ID */
|
||||
get nodeId() {
|
||||
return this.config.nodeId;
|
||||
}
|
||||
/** Get current state */
|
||||
get currentState() {
|
||||
return this.nodeState;
|
||||
}
|
||||
/** Get current term */
|
||||
get currentTerm() {
|
||||
return this.state.currentTerm;
|
||||
}
|
||||
/** Get current leader ID */
|
||||
get leader() {
|
||||
return this.leaderId;
|
||||
}
|
||||
/** Check if this node is the leader */
|
||||
get isLeader() {
|
||||
return this.nodeState === types_js_1.NodeState.Leader;
|
||||
}
|
||||
/** Get commit index */
|
||||
get commitIndex() {
|
||||
return this.state.commitIndex;
|
||||
}
|
||||
/** Set transport for RPC communication */
|
||||
setTransport(transport) {
|
||||
this.transport = transport;
|
||||
}
|
||||
/** Set state machine for applying commands */
|
||||
setStateMachine(stateMachine) {
|
||||
this.stateMachine = stateMachine;
|
||||
}
|
||||
/** Start the Raft node */
|
||||
start() {
|
||||
if (this.running)
|
||||
return;
|
||||
this.running = true;
|
||||
this.resetElectionTimer();
|
||||
}
|
||||
/** Stop the Raft node */
|
||||
stop() {
|
||||
this.running = false;
|
||||
this.clearTimers();
|
||||
}
|
||||
/** Propose a command to be replicated (only works if leader) */
|
||||
async propose(command) {
|
||||
if (this.nodeState !== types_js_1.NodeState.Leader) {
|
||||
throw types_js_1.RaftError.notLeader();
|
||||
}
|
||||
const entry = await this.state.log.appendCommand(this.state.currentTerm, command);
|
||||
this.emit(types_js_1.RaftEvent.LogAppended, entry);
|
||||
// Immediately replicate to followers
|
||||
await this.replicateToFollowers();
|
||||
return entry;
|
||||
}
|
||||
/** Handle RequestVote RPC from a candidate */
|
||||
async handleRequestVote(request) {
|
||||
// If request term is higher, update term and become follower
|
||||
if (request.term > this.state.currentTerm) {
|
||||
await this.state.setTerm(request.term);
|
||||
this.transitionTo(types_js_1.NodeState.Follower);
|
||||
}
|
||||
// Deny vote if request term is less than current term
|
||||
if (request.term < this.state.currentTerm) {
|
||||
return { term: this.state.currentTerm, voteGranted: false };
|
||||
}
|
||||
// Check if we can vote for this candidate
|
||||
const canVote = (this.state.votedFor === null || this.state.votedFor === request.candidateId) &&
|
||||
this.state.log.isUpToDate(request.lastLogTerm, request.lastLogIndex);
|
||||
if (canVote) {
|
||||
await this.state.vote(request.term, request.candidateId);
|
||||
this.resetElectionTimer();
|
||||
this.emit(types_js_1.RaftEvent.VoteGranted, { candidateId: request.candidateId, term: request.term });
|
||||
return { term: this.state.currentTerm, voteGranted: true };
|
||||
}
|
||||
return { term: this.state.currentTerm, voteGranted: false };
|
||||
}
|
||||
/** Handle AppendEntries RPC from leader */
|
||||
async handleAppendEntries(request) {
|
||||
// If request term is higher, update term
|
||||
if (request.term > this.state.currentTerm) {
|
||||
await this.state.setTerm(request.term);
|
||||
this.transitionTo(types_js_1.NodeState.Follower);
|
||||
}
|
||||
// Reject if term is less than current term
|
||||
if (request.term < this.state.currentTerm) {
|
||||
return { term: this.state.currentTerm, success: false };
|
||||
}
|
||||
// Valid leader - reset election timer
|
||||
this.leaderId = request.leaderId;
|
||||
this.resetElectionTimer();
|
||||
// If not follower, become follower
|
||||
if (this.nodeState !== types_js_1.NodeState.Follower) {
|
||||
this.transitionTo(types_js_1.NodeState.Follower);
|
||||
}
|
||||
this.emit(types_js_1.RaftEvent.Heartbeat, { leaderId: request.leaderId, term: request.term });
|
||||
// Check if log contains entry at prevLogIndex with prevLogTerm
|
||||
if (request.prevLogIndex > 0 && !this.state.log.containsEntry(request.prevLogIndex, request.prevLogTerm)) {
|
||||
return { term: this.state.currentTerm, success: false };
|
||||
}
|
||||
// Append entries
|
||||
if (request.entries.length > 0) {
|
||||
await this.state.log.append(request.entries);
|
||||
}
|
||||
// Update commit index
|
||||
if (request.leaderCommit > this.state.commitIndex) {
|
||||
this.state.setCommitIndex(Math.min(request.leaderCommit, this.state.log.lastIndex));
|
||||
await this.applyCommitted();
|
||||
}
|
||||
return {
|
||||
term: this.state.currentTerm,
|
||||
success: true,
|
||||
matchIndex: this.state.log.lastIndex,
|
||||
};
|
||||
}
|
||||
/** Load persistent state */
|
||||
loadState(state) {
|
||||
this.state.loadPersistentState(state);
|
||||
}
|
||||
/** Get current persistent state */
|
||||
getState() {
|
||||
return this.state.getPersistentState();
|
||||
}
|
||||
// Private methods
|
||||
transitionTo(newState) {
|
||||
const previousState = this.nodeState;
|
||||
if (previousState === newState)
|
||||
return;
|
||||
this.nodeState = newState;
|
||||
this.clearTimers();
|
||||
if (newState === types_js_1.NodeState.Leader) {
|
||||
this.state.initLeaderState();
|
||||
this.leaderId = this.config.nodeId;
|
||||
this.startHeartbeat();
|
||||
this.emit(types_js_1.RaftEvent.LeaderElected, {
|
||||
leaderId: this.config.nodeId,
|
||||
term: this.state.currentTerm,
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.state.clearLeaderState();
|
||||
if (newState === types_js_1.NodeState.Follower) {
|
||||
this.leaderId = null;
|
||||
this.resetElectionTimer();
|
||||
}
|
||||
}
|
||||
this.emit(types_js_1.RaftEvent.StateChange, {
|
||||
previousState,
|
||||
newState,
|
||||
term: this.state.currentTerm,
|
||||
});
|
||||
}
|
||||
getRandomElectionTimeout() {
|
||||
const [min, max] = this.config.electionTimeout;
|
||||
return min + Math.random() * (max - min);
|
||||
}
|
||||
resetElectionTimer() {
|
||||
if (this.electionTimer) {
|
||||
clearTimeout(this.electionTimer);
|
||||
}
|
||||
if (!this.running)
|
||||
return;
|
||||
this.electionTimer = setTimeout(() => {
|
||||
this.startElection();
|
||||
}, this.getRandomElectionTimeout());
|
||||
}
|
||||
clearTimers() {
|
||||
if (this.electionTimer) {
|
||||
clearTimeout(this.electionTimer);
|
||||
this.electionTimer = null;
|
||||
}
|
||||
if (this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer);
|
||||
this.heartbeatTimer = null;
|
||||
}
|
||||
}
|
||||
async startElection() {
|
||||
if (!this.running)
|
||||
return;
|
||||
// Increment term and become candidate
|
||||
await this.state.setTerm(this.state.currentTerm + 1);
|
||||
await this.state.vote(this.state.currentTerm, this.config.nodeId);
|
||||
this.transitionTo(types_js_1.NodeState.Candidate);
|
||||
this.emit(types_js_1.RaftEvent.VoteRequested, {
|
||||
term: this.state.currentTerm,
|
||||
candidateId: this.config.nodeId,
|
||||
});
|
||||
// Start with 1 vote (self)
|
||||
let votesReceived = 1;
|
||||
const majority = Math.floor((this.config.peers.length + 1) / 2) + 1;
|
||||
// Request votes from all peers
|
||||
if (!this.transport) {
|
||||
this.resetElectionTimer();
|
||||
return;
|
||||
}
|
||||
const votePromises = this.config.peers.map(async (peerId) => {
|
||||
try {
|
||||
const response = await this.transport.requestVote(peerId, {
|
||||
term: this.state.currentTerm,
|
||||
candidateId: this.config.nodeId,
|
||||
lastLogIndex: this.state.log.lastIndex,
|
||||
lastLogTerm: this.state.log.lastTerm,
|
||||
});
|
||||
// If response term is higher, become follower
|
||||
if (response.term > this.state.currentTerm) {
|
||||
await this.state.setTerm(response.term);
|
||||
this.transitionTo(types_js_1.NodeState.Follower);
|
||||
return;
|
||||
}
|
||||
if (response.voteGranted && this.nodeState === types_js_1.NodeState.Candidate) {
|
||||
votesReceived++;
|
||||
if (votesReceived >= majority) {
|
||||
this.transitionTo(types_js_1.NodeState.Leader);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// Peer unavailable, continue
|
||||
}
|
||||
});
|
||||
await Promise.allSettled(votePromises);
|
||||
// If still candidate, restart election timer
|
||||
if (this.nodeState === types_js_1.NodeState.Candidate) {
|
||||
this.resetElectionTimer();
|
||||
}
|
||||
}
|
||||
startHeartbeat() {
|
||||
if (this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer);
|
||||
}
|
||||
// Send immediate heartbeat
|
||||
this.replicateToFollowers();
|
||||
// Start periodic heartbeat
|
||||
this.heartbeatTimer = setInterval(() => {
|
||||
if (this.nodeState === types_js_1.NodeState.Leader) {
|
||||
this.replicateToFollowers();
|
||||
}
|
||||
}, this.config.heartbeatInterval);
|
||||
}
|
||||
async replicateToFollowers() {
|
||||
if (!this.transport || this.nodeState !== types_js_1.NodeState.Leader)
|
||||
return;
|
||||
const replicationPromises = this.config.peers.map(async (peerId) => {
|
||||
await this.replicateToPeer(peerId);
|
||||
});
|
||||
await Promise.allSettled(replicationPromises);
|
||||
// Update commit index if majority have replicated
|
||||
if (this.state.updateCommitIndex()) {
|
||||
this.emit(types_js_1.RaftEvent.LogCommitted, {
|
||||
index: this.state.commitIndex,
|
||||
term: this.state.currentTerm,
|
||||
});
|
||||
await this.applyCommitted();
|
||||
}
|
||||
}
|
||||
async replicateToPeer(peerId) {
|
||||
if (!this.transport || this.nodeState !== types_js_1.NodeState.Leader)
|
||||
return;
|
||||
const nextIndex = this.state.getNextIndex(peerId);
|
||||
const prevLogIndex = nextIndex - 1;
|
||||
const prevLogTerm = this.state.log.termAt(prevLogIndex) ?? 0;
|
||||
const entries = this.state.log.getFrom(nextIndex, this.config.maxEntriesPerRequest);
|
||||
try {
|
||||
const response = await this.transport.appendEntries(peerId, {
|
||||
term: this.state.currentTerm,
|
||||
leaderId: this.config.nodeId,
|
||||
prevLogIndex,
|
||||
prevLogTerm,
|
||||
entries,
|
||||
leaderCommit: this.state.commitIndex,
|
||||
});
|
||||
if (response.term > this.state.currentTerm) {
|
||||
await this.state.setTerm(response.term);
|
||||
this.transitionTo(types_js_1.NodeState.Follower);
|
||||
return;
|
||||
}
|
||||
if (response.success) {
|
||||
if (response.matchIndex !== undefined) {
|
||||
this.state.setNextIndex(peerId, response.matchIndex + 1);
|
||||
this.state.setMatchIndex(peerId, response.matchIndex);
|
||||
}
|
||||
else if (entries.length > 0) {
|
||||
const lastEntry = entries[entries.length - 1];
|
||||
this.state.setNextIndex(peerId, lastEntry.index + 1);
|
||||
this.state.setMatchIndex(peerId, lastEntry.index);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Decrement nextIndex and retry
|
||||
this.state.setNextIndex(peerId, nextIndex - 1);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// Peer unavailable, will retry on next heartbeat
|
||||
}
|
||||
}
|
||||
async applyCommitted() {
|
||||
while (this.state.lastApplied < this.state.commitIndex) {
|
||||
const nextIndex = this.state.lastApplied + 1;
|
||||
const entry = this.state.log.get(nextIndex);
|
||||
if (entry && this.stateMachine) {
|
||||
try {
|
||||
await this.stateMachine.apply(entry.command);
|
||||
this.state.setLastApplied(nextIndex);
|
||||
this.emit(types_js_1.RaftEvent.LogApplied, entry);
|
||||
}
|
||||
catch (error) {
|
||||
this.emit(types_js_1.RaftEvent.Error, error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.state.setLastApplied(nextIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.RaftNode = RaftNode;
|
||||
//# sourceMappingURL=node.js.map
|
||||
1
vendor/ruvector/npm/packages/raft/src/node.js.map
vendored
Normal file
1
vendor/ruvector/npm/packages/raft/src/node.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
435
vendor/ruvector/npm/packages/raft/src/node.ts
vendored
Normal file
435
vendor/ruvector/npm/packages/raft/src/node.ts
vendored
Normal file
@@ -0,0 +1,435 @@
|
||||
/**
|
||||
* Raft Node Implementation
|
||||
* Core Raft consensus algorithm implementation
|
||||
*/
|
||||
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import {
|
||||
NodeId,
|
||||
Term,
|
||||
LogIndex,
|
||||
NodeState,
|
||||
RaftNodeConfig,
|
||||
RequestVoteRequest,
|
||||
RequestVoteResponse,
|
||||
AppendEntriesRequest,
|
||||
AppendEntriesResponse,
|
||||
LogEntry,
|
||||
RaftError,
|
||||
RaftEvent,
|
||||
StateChangeEvent,
|
||||
LeaderElectedEvent,
|
||||
LogCommittedEvent,
|
||||
PersistentState,
|
||||
} from './types.js';
|
||||
import { RaftState } from './state.js';
|
||||
|
||||
/** Transport interface for sending RPCs to peers */
|
||||
export interface RaftTransport<T = unknown> {
|
||||
/** Send RequestVote RPC to a peer */
|
||||
requestVote(peerId: NodeId, request: RequestVoteRequest): Promise<RequestVoteResponse>;
|
||||
/** Send AppendEntries RPC to a peer */
|
||||
appendEntries(peerId: NodeId, request: AppendEntriesRequest<T>): Promise<AppendEntriesResponse>;
|
||||
}
|
||||
|
||||
/** State machine interface for applying committed entries */
|
||||
export interface StateMachine<T = unknown, R = void> {
|
||||
/** Apply a committed command to the state machine */
|
||||
apply(command: T): Promise<R>;
|
||||
}
|
||||
|
||||
/** Default configuration values */
|
||||
const DEFAULT_CONFIG: Partial<RaftNodeConfig> = {
|
||||
electionTimeout: [150, 300],
|
||||
heartbeatInterval: 50,
|
||||
maxEntriesPerRequest: 100,
|
||||
};
|
||||
|
||||
/** Raft consensus node */
|
||||
export class RaftNode<T = unknown, R = void> extends EventEmitter {
|
||||
private readonly config: Required<RaftNodeConfig>;
|
||||
private readonly state: RaftState<T>;
|
||||
private nodeState: NodeState = NodeState.Follower;
|
||||
private leaderId: NodeId | null = null;
|
||||
private transport: RaftTransport<T> | null = null;
|
||||
private stateMachine: StateMachine<T, R> | null = null;
|
||||
|
||||
private electionTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
||||
private running = false;
|
||||
|
||||
constructor(config: RaftNodeConfig) {
|
||||
super();
|
||||
this.config = { ...DEFAULT_CONFIG, ...config } as Required<RaftNodeConfig>;
|
||||
this.state = new RaftState<T>(config.nodeId, config.peers);
|
||||
}
|
||||
|
||||
/** Get node ID */
|
||||
get nodeId(): NodeId {
|
||||
return this.config.nodeId;
|
||||
}
|
||||
|
||||
/** Get current state */
|
||||
get currentState(): NodeState {
|
||||
return this.nodeState;
|
||||
}
|
||||
|
||||
/** Get current term */
|
||||
get currentTerm(): Term {
|
||||
return this.state.currentTerm;
|
||||
}
|
||||
|
||||
/** Get current leader ID */
|
||||
get leader(): NodeId | null {
|
||||
return this.leaderId;
|
||||
}
|
||||
|
||||
/** Check if this node is the leader */
|
||||
get isLeader(): boolean {
|
||||
return this.nodeState === NodeState.Leader;
|
||||
}
|
||||
|
||||
/** Get commit index */
|
||||
get commitIndex(): LogIndex {
|
||||
return this.state.commitIndex;
|
||||
}
|
||||
|
||||
/** Set transport for RPC communication */
|
||||
setTransport(transport: RaftTransport<T>): void {
|
||||
this.transport = transport;
|
||||
}
|
||||
|
||||
/** Set state machine for applying commands */
|
||||
setStateMachine(stateMachine: StateMachine<T, R>): void {
|
||||
this.stateMachine = stateMachine;
|
||||
}
|
||||
|
||||
/** Start the Raft node */
|
||||
start(): void {
|
||||
if (this.running) return;
|
||||
this.running = true;
|
||||
this.resetElectionTimer();
|
||||
}
|
||||
|
||||
/** Stop the Raft node */
|
||||
stop(): void {
|
||||
this.running = false;
|
||||
this.clearTimers();
|
||||
}
|
||||
|
||||
/** Propose a command to be replicated (only works if leader) */
|
||||
async propose(command: T): Promise<LogEntry<T>> {
|
||||
if (this.nodeState !== NodeState.Leader) {
|
||||
throw RaftError.notLeader();
|
||||
}
|
||||
|
||||
const entry = await this.state.log.appendCommand(this.state.currentTerm, command);
|
||||
this.emit(RaftEvent.LogAppended, entry);
|
||||
|
||||
// Immediately replicate to followers
|
||||
await this.replicateToFollowers();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/** Handle RequestVote RPC from a candidate */
|
||||
async handleRequestVote(request: RequestVoteRequest): Promise<RequestVoteResponse> {
|
||||
// If request term is higher, update term and become follower
|
||||
if (request.term > this.state.currentTerm) {
|
||||
await this.state.setTerm(request.term);
|
||||
this.transitionTo(NodeState.Follower);
|
||||
}
|
||||
|
||||
// Deny vote if request term is less than current term
|
||||
if (request.term < this.state.currentTerm) {
|
||||
return { term: this.state.currentTerm, voteGranted: false };
|
||||
}
|
||||
|
||||
// Check if we can vote for this candidate
|
||||
const canVote =
|
||||
(this.state.votedFor === null || this.state.votedFor === request.candidateId) &&
|
||||
this.state.log.isUpToDate(request.lastLogTerm, request.lastLogIndex);
|
||||
|
||||
if (canVote) {
|
||||
await this.state.vote(request.term, request.candidateId);
|
||||
this.resetElectionTimer();
|
||||
this.emit(RaftEvent.VoteGranted, { candidateId: request.candidateId, term: request.term });
|
||||
return { term: this.state.currentTerm, voteGranted: true };
|
||||
}
|
||||
|
||||
return { term: this.state.currentTerm, voteGranted: false };
|
||||
}
|
||||
|
||||
/** Handle AppendEntries RPC from leader */
|
||||
async handleAppendEntries(request: AppendEntriesRequest<T>): Promise<AppendEntriesResponse> {
|
||||
// If request term is higher, update term
|
||||
if (request.term > this.state.currentTerm) {
|
||||
await this.state.setTerm(request.term);
|
||||
this.transitionTo(NodeState.Follower);
|
||||
}
|
||||
|
||||
// Reject if term is less than current term
|
||||
if (request.term < this.state.currentTerm) {
|
||||
return { term: this.state.currentTerm, success: false };
|
||||
}
|
||||
|
||||
// Valid leader - reset election timer
|
||||
this.leaderId = request.leaderId;
|
||||
this.resetElectionTimer();
|
||||
|
||||
// If not follower, become follower
|
||||
if (this.nodeState !== NodeState.Follower) {
|
||||
this.transitionTo(NodeState.Follower);
|
||||
}
|
||||
|
||||
this.emit(RaftEvent.Heartbeat, { leaderId: request.leaderId, term: request.term });
|
||||
|
||||
// Check if log contains entry at prevLogIndex with prevLogTerm
|
||||
if (request.prevLogIndex > 0 && !this.state.log.containsEntry(request.prevLogIndex, request.prevLogTerm)) {
|
||||
return { term: this.state.currentTerm, success: false };
|
||||
}
|
||||
|
||||
// Append entries
|
||||
if (request.entries.length > 0) {
|
||||
await this.state.log.append(request.entries);
|
||||
}
|
||||
|
||||
// Update commit index
|
||||
if (request.leaderCommit > this.state.commitIndex) {
|
||||
this.state.setCommitIndex(
|
||||
Math.min(request.leaderCommit, this.state.log.lastIndex),
|
||||
);
|
||||
await this.applyCommitted();
|
||||
}
|
||||
|
||||
return {
|
||||
term: this.state.currentTerm,
|
||||
success: true,
|
||||
matchIndex: this.state.log.lastIndex,
|
||||
};
|
||||
}
|
||||
|
||||
/** Load persistent state */
|
||||
loadState(state: PersistentState<T>): void {
|
||||
this.state.loadPersistentState(state);
|
||||
}
|
||||
|
||||
/** Get current persistent state */
|
||||
getState(): PersistentState<T> {
|
||||
return this.state.getPersistentState();
|
||||
}
|
||||
|
||||
// Private methods
|
||||
|
||||
private transitionTo(newState: NodeState): void {
|
||||
const previousState = this.nodeState;
|
||||
if (previousState === newState) return;
|
||||
|
||||
this.nodeState = newState;
|
||||
this.clearTimers();
|
||||
|
||||
if (newState === NodeState.Leader) {
|
||||
this.state.initLeaderState();
|
||||
this.leaderId = this.config.nodeId;
|
||||
this.startHeartbeat();
|
||||
this.emit(RaftEvent.LeaderElected, {
|
||||
leaderId: this.config.nodeId,
|
||||
term: this.state.currentTerm,
|
||||
} as LeaderElectedEvent);
|
||||
} else {
|
||||
this.state.clearLeaderState();
|
||||
if (newState === NodeState.Follower) {
|
||||
this.leaderId = null;
|
||||
this.resetElectionTimer();
|
||||
}
|
||||
}
|
||||
|
||||
this.emit(RaftEvent.StateChange, {
|
||||
previousState,
|
||||
newState,
|
||||
term: this.state.currentTerm,
|
||||
} as StateChangeEvent);
|
||||
}
|
||||
|
||||
private getRandomElectionTimeout(): number {
|
||||
const [min, max] = this.config.electionTimeout;
|
||||
return min + Math.random() * (max - min);
|
||||
}
|
||||
|
||||
private resetElectionTimer(): void {
|
||||
if (this.electionTimer) {
|
||||
clearTimeout(this.electionTimer);
|
||||
}
|
||||
if (!this.running) return;
|
||||
|
||||
this.electionTimer = setTimeout(() => {
|
||||
this.startElection();
|
||||
}, this.getRandomElectionTimeout());
|
||||
}
|
||||
|
||||
private clearTimers(): void {
|
||||
if (this.electionTimer) {
|
||||
clearTimeout(this.electionTimer);
|
||||
this.electionTimer = null;
|
||||
}
|
||||
if (this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer);
|
||||
this.heartbeatTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async startElection(): Promise<void> {
|
||||
if (!this.running) return;
|
||||
|
||||
// Increment term and become candidate
|
||||
await this.state.setTerm(this.state.currentTerm + 1);
|
||||
await this.state.vote(this.state.currentTerm, this.config.nodeId);
|
||||
this.transitionTo(NodeState.Candidate);
|
||||
|
||||
this.emit(RaftEvent.VoteRequested, {
|
||||
term: this.state.currentTerm,
|
||||
candidateId: this.config.nodeId,
|
||||
});
|
||||
|
||||
// Start with 1 vote (self)
|
||||
let votesReceived = 1;
|
||||
const majority = Math.floor((this.config.peers.length + 1) / 2) + 1;
|
||||
|
||||
// Request votes from all peers
|
||||
if (!this.transport) {
|
||||
this.resetElectionTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
const votePromises = this.config.peers.map(async (peerId) => {
|
||||
try {
|
||||
const response = await this.transport!.requestVote(peerId, {
|
||||
term: this.state.currentTerm,
|
||||
candidateId: this.config.nodeId,
|
||||
lastLogIndex: this.state.log.lastIndex,
|
||||
lastLogTerm: this.state.log.lastTerm,
|
||||
});
|
||||
|
||||
// If response term is higher, become follower
|
||||
if (response.term > this.state.currentTerm) {
|
||||
await this.state.setTerm(response.term);
|
||||
this.transitionTo(NodeState.Follower);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.voteGranted && this.nodeState === NodeState.Candidate) {
|
||||
votesReceived++;
|
||||
if (votesReceived >= majority) {
|
||||
this.transitionTo(NodeState.Leader);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Peer unavailable, continue
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.allSettled(votePromises);
|
||||
|
||||
// If still candidate, restart election timer
|
||||
if (this.nodeState === NodeState.Candidate) {
|
||||
this.resetElectionTimer();
|
||||
}
|
||||
}
|
||||
|
||||
private startHeartbeat(): void {
|
||||
if (this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer);
|
||||
}
|
||||
|
||||
// Send immediate heartbeat
|
||||
this.replicateToFollowers();
|
||||
|
||||
// Start periodic heartbeat
|
||||
this.heartbeatTimer = setInterval(() => {
|
||||
if (this.nodeState === NodeState.Leader) {
|
||||
this.replicateToFollowers();
|
||||
}
|
||||
}, this.config.heartbeatInterval);
|
||||
}
|
||||
|
||||
private async replicateToFollowers(): Promise<void> {
|
||||
if (!this.transport || this.nodeState !== NodeState.Leader) return;
|
||||
|
||||
const replicationPromises = this.config.peers.map(async (peerId) => {
|
||||
await this.replicateToPeer(peerId);
|
||||
});
|
||||
|
||||
await Promise.allSettled(replicationPromises);
|
||||
|
||||
// Update commit index if majority have replicated
|
||||
if (this.state.updateCommitIndex()) {
|
||||
this.emit(RaftEvent.LogCommitted, {
|
||||
index: this.state.commitIndex,
|
||||
term: this.state.currentTerm,
|
||||
} as LogCommittedEvent);
|
||||
await this.applyCommitted();
|
||||
}
|
||||
}
|
||||
|
||||
private async replicateToPeer(peerId: NodeId): Promise<void> {
|
||||
if (!this.transport || this.nodeState !== NodeState.Leader) return;
|
||||
|
||||
const nextIndex = this.state.getNextIndex(peerId);
|
||||
const prevLogIndex = nextIndex - 1;
|
||||
const prevLogTerm = this.state.log.termAt(prevLogIndex) ?? 0;
|
||||
const entries = this.state.log.getFrom(nextIndex, this.config.maxEntriesPerRequest);
|
||||
|
||||
try {
|
||||
const response = await this.transport.appendEntries(peerId, {
|
||||
term: this.state.currentTerm,
|
||||
leaderId: this.config.nodeId,
|
||||
prevLogIndex,
|
||||
prevLogTerm,
|
||||
entries,
|
||||
leaderCommit: this.state.commitIndex,
|
||||
});
|
||||
|
||||
if (response.term > this.state.currentTerm) {
|
||||
await this.state.setTerm(response.term);
|
||||
this.transitionTo(NodeState.Follower);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.success) {
|
||||
if (response.matchIndex !== undefined) {
|
||||
this.state.setNextIndex(peerId, response.matchIndex + 1);
|
||||
this.state.setMatchIndex(peerId, response.matchIndex);
|
||||
} else if (entries.length > 0) {
|
||||
const lastEntry = entries[entries.length - 1];
|
||||
this.state.setNextIndex(peerId, lastEntry.index + 1);
|
||||
this.state.setMatchIndex(peerId, lastEntry.index);
|
||||
}
|
||||
} else {
|
||||
// Decrement nextIndex and retry
|
||||
this.state.setNextIndex(peerId, nextIndex - 1);
|
||||
}
|
||||
} catch {
|
||||
// Peer unavailable, will retry on next heartbeat
|
||||
}
|
||||
}
|
||||
|
||||
private async applyCommitted(): Promise<void> {
|
||||
while (this.state.lastApplied < this.state.commitIndex) {
|
||||
const nextIndex = this.state.lastApplied + 1;
|
||||
const entry = this.state.log.get(nextIndex);
|
||||
|
||||
if (entry && this.stateMachine) {
|
||||
try {
|
||||
await this.stateMachine.apply(entry.command);
|
||||
this.state.setLastApplied(nextIndex);
|
||||
this.emit(RaftEvent.LogApplied, entry);
|
||||
} catch (error) {
|
||||
this.emit(RaftEvent.Error, error);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this.state.setLastApplied(nextIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
vendor/ruvector/npm/packages/raft/src/state.d.ts
vendored
Normal file
63
vendor/ruvector/npm/packages/raft/src/state.d.ts
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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 declare class RaftState<T = unknown> {
|
||||
private readonly nodeId;
|
||||
private readonly peers;
|
||||
private _currentTerm;
|
||||
private _votedFor;
|
||||
private _commitIndex;
|
||||
private _lastApplied;
|
||||
private _leaderState;
|
||||
readonly log: RaftLog<T>;
|
||||
constructor(nodeId: NodeId, peers: NodeId[], options?: {
|
||||
onPersist?: (state: PersistentState<T>) => Promise<void>;
|
||||
onLogPersist?: (entries: LogEntry<T>[]) => Promise<void>;
|
||||
});
|
||||
private persistCallback?;
|
||||
/** Get current term */
|
||||
get currentTerm(): Term;
|
||||
/** Get voted for */
|
||||
get votedFor(): NodeId | null;
|
||||
/** Get commit index */
|
||||
get commitIndex(): LogIndex;
|
||||
/** Get last applied */
|
||||
get lastApplied(): LogIndex;
|
||||
/** Get leader state (null if not leader) */
|
||||
get leaderState(): LeaderState | null;
|
||||
/** Update term (with persistence) */
|
||||
setTerm(term: Term): Promise<void>;
|
||||
/** Record vote (with persistence) */
|
||||
vote(term: Term, candidateId: NodeId): Promise<void>;
|
||||
/** Update commit index */
|
||||
setCommitIndex(index: LogIndex): void;
|
||||
/** Update last applied */
|
||||
setLastApplied(index: LogIndex): void;
|
||||
/** Initialize leader state */
|
||||
initLeaderState(): void;
|
||||
/** Clear leader state */
|
||||
clearLeaderState(): void;
|
||||
/** Update nextIndex for a peer */
|
||||
setNextIndex(peerId: NodeId, index: LogIndex): void;
|
||||
/** Update matchIndex for a peer */
|
||||
setMatchIndex(peerId: NodeId, index: LogIndex): void;
|
||||
/** Get nextIndex for a peer */
|
||||
getNextIndex(peerId: NodeId): LogIndex;
|
||||
/** Get matchIndex for a peer */
|
||||
getMatchIndex(peerId: NodeId): LogIndex;
|
||||
/** Update commit index based on match indices (for leader) */
|
||||
updateCommitIndex(): boolean;
|
||||
/** Get persistent state */
|
||||
getPersistentState(): PersistentState<T>;
|
||||
/** Get volatile state */
|
||||
getVolatileState(): VolatileState;
|
||||
/** Load persistent state */
|
||||
loadPersistentState(state: PersistentState<T>): void;
|
||||
/** Persist state */
|
||||
private persist;
|
||||
}
|
||||
//# sourceMappingURL=state.d.ts.map
|
||||
1
vendor/ruvector/npm/packages/raft/src/state.d.ts.map
vendored
Normal file
1
vendor/ruvector/npm/packages/raft/src/state.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["state.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,MAAM,EACN,IAAI,EACJ,QAAQ,EACR,eAAe,EACf,aAAa,EACb,WAAW,EACX,QAAQ,EACT,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,oCAAoC;AACpC,qBAAa,SAAS,CAAC,CAAC,GAAG,OAAO;IAU9B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAVxB,OAAO,CAAC,YAAY,CAAW;IAC/B,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,YAAY,CAA4B;IAEhD,SAAgB,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;gBAGb,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EAAE,EAChC,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;QACzD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KAC1D;IAMH,OAAO,CAAC,eAAe,CAAC,CAA+C;IAEvE,uBAAuB;IACvB,IAAI,WAAW,IAAI,IAAI,CAEtB;IAED,oBAAoB;IACpB,IAAI,QAAQ,IAAI,MAAM,GAAG,IAAI,CAE5B;IAED,uBAAuB;IACvB,IAAI,WAAW,IAAI,QAAQ,CAE1B;IAED,uBAAuB;IACvB,IAAI,WAAW,IAAI,QAAQ,CAE1B;IAED,4CAA4C;IAC5C,IAAI,WAAW,IAAI,WAAW,GAAG,IAAI,CAEpC;IAED,qCAAqC;IAC/B,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAQxC,qCAAqC;IAC/B,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM1D,0BAA0B;IAC1B,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAMrC,0BAA0B;IAC1B,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAMrC,8BAA8B;IAC9B,eAAe,IAAI,IAAI;IAcvB,yBAAyB;IACzB,gBAAgB,IAAI,IAAI;IAIxB,kCAAkC;IAClC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,IAAI;IAMnD,mCAAmC;IACnC,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,IAAI;IAMpD,+BAA+B;IAC/B,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ;IAItC,gCAAgC;IAChC,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ;IAIvC,8DAA8D;IAC9D,iBAAiB,IAAI,OAAO;IA6B5B,2BAA2B;IAC3B,kBAAkB,IAAI,eAAe,CAAC,CAAC,CAAC;IAQxC,yBAAyB;IACzB,gBAAgB,IAAI,aAAa;IAOjC,4BAA4B;IAC5B,mBAAmB,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,IAAI;IAMpD,oBAAoB;YACN,OAAO;CAKtB"}
|
||||
158
vendor/ruvector/npm/packages/raft/src/state.js
vendored
Normal file
158
vendor/ruvector/npm/packages/raft/src/state.js
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Raft State Management
|
||||
* Manages persistent and volatile state for Raft consensus
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.RaftState = void 0;
|
||||
const log_js_1 = require("./log.js");
|
||||
/** State manager for a Raft node */
|
||||
class RaftState {
|
||||
constructor(nodeId, peers, options) {
|
||||
this.nodeId = nodeId;
|
||||
this.peers = peers;
|
||||
this._currentTerm = 0;
|
||||
this._votedFor = null;
|
||||
this._commitIndex = 0;
|
||||
this._lastApplied = 0;
|
||||
this._leaderState = null;
|
||||
this.log = new log_js_1.RaftLog({ onPersist: options?.onLogPersist });
|
||||
this.persistCallback = options?.onPersist;
|
||||
}
|
||||
/** Get current term */
|
||||
get currentTerm() {
|
||||
return this._currentTerm;
|
||||
}
|
||||
/** Get voted for */
|
||||
get votedFor() {
|
||||
return this._votedFor;
|
||||
}
|
||||
/** Get commit index */
|
||||
get commitIndex() {
|
||||
return this._commitIndex;
|
||||
}
|
||||
/** Get last applied */
|
||||
get lastApplied() {
|
||||
return this._lastApplied;
|
||||
}
|
||||
/** Get leader state (null if not leader) */
|
||||
get leaderState() {
|
||||
return this._leaderState;
|
||||
}
|
||||
/** Update term (with persistence) */
|
||||
async setTerm(term) {
|
||||
if (term > this._currentTerm) {
|
||||
this._currentTerm = term;
|
||||
this._votedFor = null;
|
||||
await this.persist();
|
||||
}
|
||||
}
|
||||
/** Record vote (with persistence) */
|
||||
async vote(term, candidateId) {
|
||||
this._currentTerm = term;
|
||||
this._votedFor = candidateId;
|
||||
await this.persist();
|
||||
}
|
||||
/** Update commit index */
|
||||
setCommitIndex(index) {
|
||||
if (index > this._commitIndex) {
|
||||
this._commitIndex = index;
|
||||
}
|
||||
}
|
||||
/** Update last applied */
|
||||
setLastApplied(index) {
|
||||
if (index > this._lastApplied) {
|
||||
this._lastApplied = index;
|
||||
}
|
||||
}
|
||||
/** Initialize leader state */
|
||||
initLeaderState() {
|
||||
const nextIndex = new Map();
|
||||
const matchIndex = new Map();
|
||||
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() {
|
||||
this._leaderState = null;
|
||||
}
|
||||
/** Update nextIndex for a peer */
|
||||
setNextIndex(peerId, index) {
|
||||
if (this._leaderState) {
|
||||
this._leaderState.nextIndex.set(peerId, Math.max(1, index));
|
||||
}
|
||||
}
|
||||
/** Update matchIndex for a peer */
|
||||
setMatchIndex(peerId, index) {
|
||||
if (this._leaderState) {
|
||||
this._leaderState.matchIndex.set(peerId, index);
|
||||
}
|
||||
}
|
||||
/** Get nextIndex for a peer */
|
||||
getNextIndex(peerId) {
|
||||
return this._leaderState?.nextIndex.get(peerId) ?? this.log.lastIndex + 1;
|
||||
}
|
||||
/** Get matchIndex for a peer */
|
||||
getMatchIndex(peerId) {
|
||||
return this._leaderState?.matchIndex.get(peerId) ?? 0;
|
||||
}
|
||||
/** Update commit index based on match indices (for leader) */
|
||||
updateCommitIndex() {
|
||||
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() {
|
||||
return {
|
||||
currentTerm: this._currentTerm,
|
||||
votedFor: this._votedFor,
|
||||
log: this.log.getAll(),
|
||||
};
|
||||
}
|
||||
/** Get volatile state */
|
||||
getVolatileState() {
|
||||
return {
|
||||
commitIndex: this._commitIndex,
|
||||
lastApplied: this._lastApplied,
|
||||
};
|
||||
}
|
||||
/** Load persistent state */
|
||||
loadPersistentState(state) {
|
||||
this._currentTerm = state.currentTerm;
|
||||
this._votedFor = state.votedFor;
|
||||
this.log.load(state.log);
|
||||
}
|
||||
/** Persist state */
|
||||
async persist() {
|
||||
if (this.persistCallback) {
|
||||
await this.persistCallback(this.getPersistentState());
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.RaftState = RaftState;
|
||||
//# sourceMappingURL=state.js.map
|
||||
1
vendor/ruvector/npm/packages/raft/src/state.js.map
vendored
Normal file
1
vendor/ruvector/npm/packages/raft/src/state.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"state.js","sourceRoot":"","sources":["state.ts"],"names":[],"mappings":"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"}
|
||||
200
vendor/ruvector/npm/packages/raft/src/state.ts
vendored
Normal file
200
vendor/ruvector/npm/packages/raft/src/state.ts
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
154
vendor/ruvector/npm/packages/raft/src/types.d.ts
vendored
Normal file
154
vendor/ruvector/npm/packages/raft/src/types.d.ts
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Raft Consensus Types
|
||||
* Based on the Raft paper specification
|
||||
*/
|
||||
/** Unique identifier for a node in the cluster */
|
||||
export type NodeId = string;
|
||||
/** Monotonically increasing term number */
|
||||
export type Term = number;
|
||||
/** Index into the replicated log */
|
||||
export type LogIndex = number;
|
||||
/** Possible states of a Raft node */
|
||||
export declare enum NodeState {
|
||||
Follower = "follower",
|
||||
Candidate = "candidate",
|
||||
Leader = "leader"
|
||||
}
|
||||
/** Entry in the replicated log */
|
||||
export interface LogEntry<T = unknown> {
|
||||
/** Term when entry was received by leader */
|
||||
term: Term;
|
||||
/** Index in the log */
|
||||
index: LogIndex;
|
||||
/** Command to be applied to state machine */
|
||||
command: T;
|
||||
/** Timestamp when entry was created */
|
||||
timestamp: number;
|
||||
}
|
||||
/** Persistent state on all servers (updated on stable storage before responding to RPCs) */
|
||||
export interface PersistentState<T = unknown> {
|
||||
/** Latest term server has seen */
|
||||
currentTerm: Term;
|
||||
/** CandidateId that received vote in current term (or null if none) */
|
||||
votedFor: NodeId | null;
|
||||
/** Log entries */
|
||||
log: LogEntry<T>[];
|
||||
}
|
||||
/** Volatile state on all servers */
|
||||
export interface VolatileState {
|
||||
/** Index of highest log entry known to be committed */
|
||||
commitIndex: LogIndex;
|
||||
/** Index of highest log entry applied to state machine */
|
||||
lastApplied: LogIndex;
|
||||
}
|
||||
/** Volatile state on leaders (reinitialized after election) */
|
||||
export interface LeaderState {
|
||||
/** For each server, index of the next log entry to send to that server */
|
||||
nextIndex: Map<NodeId, LogIndex>;
|
||||
/** For each server, index of highest log entry known to be replicated on server */
|
||||
matchIndex: Map<NodeId, LogIndex>;
|
||||
}
|
||||
/** Configuration for a Raft node */
|
||||
export interface RaftNodeConfig {
|
||||
/** Unique identifier for this node */
|
||||
nodeId: NodeId;
|
||||
/** List of all node IDs in the cluster */
|
||||
peers: NodeId[];
|
||||
/** Election timeout range in milliseconds [min, max] */
|
||||
electionTimeout: [number, number];
|
||||
/** Heartbeat interval in milliseconds */
|
||||
heartbeatInterval: number;
|
||||
/** Maximum entries per AppendEntries RPC */
|
||||
maxEntriesPerRequest: number;
|
||||
}
|
||||
/** Request for RequestVote RPC */
|
||||
export interface RequestVoteRequest {
|
||||
/** Candidate's term */
|
||||
term: Term;
|
||||
/** Candidate requesting vote */
|
||||
candidateId: NodeId;
|
||||
/** Index of candidate's last log entry */
|
||||
lastLogIndex: LogIndex;
|
||||
/** Term of candidate's last log entry */
|
||||
lastLogTerm: Term;
|
||||
}
|
||||
/** Response for RequestVote RPC */
|
||||
export interface RequestVoteResponse {
|
||||
/** Current term, for candidate to update itself */
|
||||
term: Term;
|
||||
/** True means candidate received vote */
|
||||
voteGranted: boolean;
|
||||
}
|
||||
/** Request for AppendEntries RPC */
|
||||
export interface AppendEntriesRequest<T = unknown> {
|
||||
/** Leader's term */
|
||||
term: Term;
|
||||
/** So follower can redirect clients */
|
||||
leaderId: NodeId;
|
||||
/** Index of log entry immediately preceding new ones */
|
||||
prevLogIndex: LogIndex;
|
||||
/** Term of prevLogIndex entry */
|
||||
prevLogTerm: Term;
|
||||
/** Log entries to store (empty for heartbeat) */
|
||||
entries: LogEntry<T>[];
|
||||
/** Leader's commitIndex */
|
||||
leaderCommit: LogIndex;
|
||||
}
|
||||
/** Response for AppendEntries RPC */
|
||||
export interface AppendEntriesResponse {
|
||||
/** Current term, for leader to update itself */
|
||||
term: Term;
|
||||
/** True if follower contained entry matching prevLogIndex and prevLogTerm */
|
||||
success: boolean;
|
||||
/** Hint for next index to try (optimization) */
|
||||
matchIndex?: LogIndex;
|
||||
}
|
||||
/** Raft error types */
|
||||
export declare class RaftError extends Error {
|
||||
readonly code: RaftErrorCode;
|
||||
constructor(message: string, code: RaftErrorCode);
|
||||
static notLeader(): RaftError;
|
||||
static noLeader(): RaftError;
|
||||
static electionTimeout(): RaftError;
|
||||
static logInconsistency(): RaftError;
|
||||
}
|
||||
export declare enum RaftErrorCode {
|
||||
NotLeader = "NOT_LEADER",
|
||||
NoLeader = "NO_LEADER",
|
||||
InvalidTerm = "INVALID_TERM",
|
||||
InvalidLogIndex = "INVALID_LOG_INDEX",
|
||||
ElectionTimeout = "ELECTION_TIMEOUT",
|
||||
LogInconsistency = "LOG_INCONSISTENCY",
|
||||
SnapshotFailed = "SNAPSHOT_FAILED",
|
||||
ConfigError = "CONFIG_ERROR",
|
||||
Internal = "INTERNAL"
|
||||
}
|
||||
/** Event types emitted by RaftNode */
|
||||
export declare enum RaftEvent {
|
||||
StateChange = "stateChange",
|
||||
LeaderElected = "leaderElected",
|
||||
LogAppended = "logAppended",
|
||||
LogCommitted = "logCommitted",
|
||||
LogApplied = "logApplied",
|
||||
VoteRequested = "voteRequested",
|
||||
VoteGranted = "voteGranted",
|
||||
Heartbeat = "heartbeat",
|
||||
Error = "error"
|
||||
}
|
||||
/** State change event data */
|
||||
export interface StateChangeEvent {
|
||||
previousState: NodeState;
|
||||
newState: NodeState;
|
||||
term: Term;
|
||||
}
|
||||
/** Leader elected event data */
|
||||
export interface LeaderElectedEvent {
|
||||
leaderId: NodeId;
|
||||
term: Term;
|
||||
}
|
||||
/** Log committed event data */
|
||||
export interface LogCommittedEvent {
|
||||
index: LogIndex;
|
||||
term: Term;
|
||||
}
|
||||
//# sourceMappingURL=types.d.ts.map
|
||||
1
vendor/ruvector/npm/packages/raft/src/types.d.ts.map
vendored
Normal file
1
vendor/ruvector/npm/packages/raft/src/types.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"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"}
|
||||
61
vendor/ruvector/npm/packages/raft/src/types.js
vendored
Normal file
61
vendor/ruvector/npm/packages/raft/src/types.js
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Raft Consensus Types
|
||||
* Based on the Raft paper specification
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.RaftEvent = exports.RaftErrorCode = exports.RaftError = exports.NodeState = void 0;
|
||||
/** Possible states of a Raft node */
|
||||
var NodeState;
|
||||
(function (NodeState) {
|
||||
NodeState["Follower"] = "follower";
|
||||
NodeState["Candidate"] = "candidate";
|
||||
NodeState["Leader"] = "leader";
|
||||
})(NodeState || (exports.NodeState = NodeState = {}));
|
||||
/** Raft error types */
|
||||
class RaftError extends Error {
|
||||
constructor(message, code) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.name = 'RaftError';
|
||||
}
|
||||
static notLeader() {
|
||||
return new RaftError('Node is not the leader', RaftErrorCode.NotLeader);
|
||||
}
|
||||
static noLeader() {
|
||||
return new RaftError('No leader available', RaftErrorCode.NoLeader);
|
||||
}
|
||||
static electionTimeout() {
|
||||
return new RaftError('Election timeout', RaftErrorCode.ElectionTimeout);
|
||||
}
|
||||
static logInconsistency() {
|
||||
return new RaftError('Log inconsistency detected', RaftErrorCode.LogInconsistency);
|
||||
}
|
||||
}
|
||||
exports.RaftError = RaftError;
|
||||
var RaftErrorCode;
|
||||
(function (RaftErrorCode) {
|
||||
RaftErrorCode["NotLeader"] = "NOT_LEADER";
|
||||
RaftErrorCode["NoLeader"] = "NO_LEADER";
|
||||
RaftErrorCode["InvalidTerm"] = "INVALID_TERM";
|
||||
RaftErrorCode["InvalidLogIndex"] = "INVALID_LOG_INDEX";
|
||||
RaftErrorCode["ElectionTimeout"] = "ELECTION_TIMEOUT";
|
||||
RaftErrorCode["LogInconsistency"] = "LOG_INCONSISTENCY";
|
||||
RaftErrorCode["SnapshotFailed"] = "SNAPSHOT_FAILED";
|
||||
RaftErrorCode["ConfigError"] = "CONFIG_ERROR";
|
||||
RaftErrorCode["Internal"] = "INTERNAL";
|
||||
})(RaftErrorCode || (exports.RaftErrorCode = RaftErrorCode = {}));
|
||||
/** Event types emitted by RaftNode */
|
||||
var RaftEvent;
|
||||
(function (RaftEvent) {
|
||||
RaftEvent["StateChange"] = "stateChange";
|
||||
RaftEvent["LeaderElected"] = "leaderElected";
|
||||
RaftEvent["LogAppended"] = "logAppended";
|
||||
RaftEvent["LogCommitted"] = "logCommitted";
|
||||
RaftEvent["LogApplied"] = "logApplied";
|
||||
RaftEvent["VoteRequested"] = "voteRequested";
|
||||
RaftEvent["VoteGranted"] = "voteGranted";
|
||||
RaftEvent["Heartbeat"] = "heartbeat";
|
||||
RaftEvent["Error"] = "error";
|
||||
})(RaftEvent || (exports.RaftEvent = RaftEvent = {}));
|
||||
//# sourceMappingURL=types.js.map
|
||||
1
vendor/ruvector/npm/packages/raft/src/types.js.map
vendored
Normal file
1
vendor/ruvector/npm/packages/raft/src/types.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAWH,qCAAqC;AACrC,IAAY,SAIX;AAJD,WAAY,SAAS;IACnB,kCAAqB,CAAA;IACrB,oCAAuB,CAAA;IACvB,8BAAiB,CAAA;AACnB,CAAC,EAJW,SAAS,yBAAT,SAAS,QAIpB;AAoGD,uBAAuB;AACvB,MAAa,SAAU,SAAQ,KAAK;IAClC,YACE,OAAe,EACC,IAAmB;QAEnC,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,SAAI,GAAJ,IAAI,CAAe;QAGnC,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;IAC1B,CAAC;IAED,MAAM,CAAC,SAAS;QACd,OAAO,IAAI,SAAS,CAAC,wBAAwB,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IAC1E,CAAC;IAED,MAAM,CAAC,QAAQ;QACb,OAAO,IAAI,SAAS,CAAC,qBAAqB,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,CAAC,eAAe;QACpB,OAAO,IAAI,SAAS,CAAC,kBAAkB,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC1E,CAAC;IAED,MAAM,CAAC,gBAAgB;QACrB,OAAO,IAAI,SAAS,CAAC,4BAA4B,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IACrF,CAAC;CACF;AAxBD,8BAwBC;AAED,IAAY,aAUX;AAVD,WAAY,aAAa;IACvB,yCAAwB,CAAA;IACxB,uCAAsB,CAAA;IACtB,6CAA4B,CAAA;IAC5B,sDAAqC,CAAA;IACrC,qDAAoC,CAAA;IACpC,uDAAsC,CAAA;IACtC,mDAAkC,CAAA;IAClC,6CAA4B,CAAA;IAC5B,sCAAqB,CAAA;AACvB,CAAC,EAVW,aAAa,6BAAb,aAAa,QAUxB;AAED,sCAAsC;AACtC,IAAY,SAUX;AAVD,WAAY,SAAS;IACnB,wCAA2B,CAAA;IAC3B,4CAA+B,CAAA;IAC/B,wCAA2B,CAAA;IAC3B,0CAA6B,CAAA;IAC7B,sCAAyB,CAAA;IACzB,4CAA+B,CAAA;IAC/B,wCAA2B,CAAA;IAC3B,oCAAuB,CAAA;IACvB,4BAAe,CAAA;AACjB,CAAC,EAVW,SAAS,yBAAT,SAAS,QAUpB"}
|
||||
189
vendor/ruvector/npm/packages/raft/src/types.ts
vendored
Normal file
189
vendor/ruvector/npm/packages/raft/src/types.ts
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* Raft Consensus Types
|
||||
* Based on the Raft paper specification
|
||||
*/
|
||||
|
||||
/** Unique identifier for a node in the cluster */
|
||||
export type NodeId = string;
|
||||
|
||||
/** Monotonically increasing term number */
|
||||
export type Term = number;
|
||||
|
||||
/** Index into the replicated log */
|
||||
export type LogIndex = number;
|
||||
|
||||
/** Possible states of a Raft node */
|
||||
export enum NodeState {
|
||||
Follower = 'follower',
|
||||
Candidate = 'candidate',
|
||||
Leader = 'leader',
|
||||
}
|
||||
|
||||
/** Entry in the replicated log */
|
||||
export interface LogEntry<T = unknown> {
|
||||
/** Term when entry was received by leader */
|
||||
term: Term;
|
||||
/** Index in the log */
|
||||
index: LogIndex;
|
||||
/** Command to be applied to state machine */
|
||||
command: T;
|
||||
/** Timestamp when entry was created */
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/** Persistent state on all servers (updated on stable storage before responding to RPCs) */
|
||||
export interface PersistentState<T = unknown> {
|
||||
/** Latest term server has seen */
|
||||
currentTerm: Term;
|
||||
/** CandidateId that received vote in current term (or null if none) */
|
||||
votedFor: NodeId | null;
|
||||
/** Log entries */
|
||||
log: LogEntry<T>[];
|
||||
}
|
||||
|
||||
/** Volatile state on all servers */
|
||||
export interface VolatileState {
|
||||
/** Index of highest log entry known to be committed */
|
||||
commitIndex: LogIndex;
|
||||
/** Index of highest log entry applied to state machine */
|
||||
lastApplied: LogIndex;
|
||||
}
|
||||
|
||||
/** Volatile state on leaders (reinitialized after election) */
|
||||
export interface LeaderState {
|
||||
/** For each server, index of the next log entry to send to that server */
|
||||
nextIndex: Map<NodeId, LogIndex>;
|
||||
/** For each server, index of highest log entry known to be replicated on server */
|
||||
matchIndex: Map<NodeId, LogIndex>;
|
||||
}
|
||||
|
||||
/** Configuration for a Raft node */
|
||||
export interface RaftNodeConfig {
|
||||
/** Unique identifier for this node */
|
||||
nodeId: NodeId;
|
||||
/** List of all node IDs in the cluster */
|
||||
peers: NodeId[];
|
||||
/** Election timeout range in milliseconds [min, max] */
|
||||
electionTimeout: [number, number];
|
||||
/** Heartbeat interval in milliseconds */
|
||||
heartbeatInterval: number;
|
||||
/** Maximum entries per AppendEntries RPC */
|
||||
maxEntriesPerRequest: number;
|
||||
}
|
||||
|
||||
/** Request for RequestVote RPC */
|
||||
export interface RequestVoteRequest {
|
||||
/** Candidate's term */
|
||||
term: Term;
|
||||
/** Candidate requesting vote */
|
||||
candidateId: NodeId;
|
||||
/** Index of candidate's last log entry */
|
||||
lastLogIndex: LogIndex;
|
||||
/** Term of candidate's last log entry */
|
||||
lastLogTerm: Term;
|
||||
}
|
||||
|
||||
/** Response for RequestVote RPC */
|
||||
export interface RequestVoteResponse {
|
||||
/** Current term, for candidate to update itself */
|
||||
term: Term;
|
||||
/** True means candidate received vote */
|
||||
voteGranted: boolean;
|
||||
}
|
||||
|
||||
/** Request for AppendEntries RPC */
|
||||
export interface AppendEntriesRequest<T = unknown> {
|
||||
/** Leader's term */
|
||||
term: Term;
|
||||
/** So follower can redirect clients */
|
||||
leaderId: NodeId;
|
||||
/** Index of log entry immediately preceding new ones */
|
||||
prevLogIndex: LogIndex;
|
||||
/** Term of prevLogIndex entry */
|
||||
prevLogTerm: Term;
|
||||
/** Log entries to store (empty for heartbeat) */
|
||||
entries: LogEntry<T>[];
|
||||
/** Leader's commitIndex */
|
||||
leaderCommit: LogIndex;
|
||||
}
|
||||
|
||||
/** Response for AppendEntries RPC */
|
||||
export interface AppendEntriesResponse {
|
||||
/** Current term, for leader to update itself */
|
||||
term: Term;
|
||||
/** True if follower contained entry matching prevLogIndex and prevLogTerm */
|
||||
success: boolean;
|
||||
/** Hint for next index to try (optimization) */
|
||||
matchIndex?: LogIndex;
|
||||
}
|
||||
|
||||
/** Raft error types */
|
||||
export class RaftError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly code: RaftErrorCode,
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'RaftError';
|
||||
}
|
||||
|
||||
static notLeader(): RaftError {
|
||||
return new RaftError('Node is not the leader', RaftErrorCode.NotLeader);
|
||||
}
|
||||
|
||||
static noLeader(): RaftError {
|
||||
return new RaftError('No leader available', RaftErrorCode.NoLeader);
|
||||
}
|
||||
|
||||
static electionTimeout(): RaftError {
|
||||
return new RaftError('Election timeout', RaftErrorCode.ElectionTimeout);
|
||||
}
|
||||
|
||||
static logInconsistency(): RaftError {
|
||||
return new RaftError('Log inconsistency detected', RaftErrorCode.LogInconsistency);
|
||||
}
|
||||
}
|
||||
|
||||
export enum RaftErrorCode {
|
||||
NotLeader = 'NOT_LEADER',
|
||||
NoLeader = 'NO_LEADER',
|
||||
InvalidTerm = 'INVALID_TERM',
|
||||
InvalidLogIndex = 'INVALID_LOG_INDEX',
|
||||
ElectionTimeout = 'ELECTION_TIMEOUT',
|
||||
LogInconsistency = 'LOG_INCONSISTENCY',
|
||||
SnapshotFailed = 'SNAPSHOT_FAILED',
|
||||
ConfigError = 'CONFIG_ERROR',
|
||||
Internal = 'INTERNAL',
|
||||
}
|
||||
|
||||
/** Event types emitted by RaftNode */
|
||||
export enum RaftEvent {
|
||||
StateChange = 'stateChange',
|
||||
LeaderElected = 'leaderElected',
|
||||
LogAppended = 'logAppended',
|
||||
LogCommitted = 'logCommitted',
|
||||
LogApplied = 'logApplied',
|
||||
VoteRequested = 'voteRequested',
|
||||
VoteGranted = 'voteGranted',
|
||||
Heartbeat = 'heartbeat',
|
||||
Error = 'error',
|
||||
}
|
||||
|
||||
/** State change event data */
|
||||
export interface StateChangeEvent {
|
||||
previousState: NodeState;
|
||||
newState: NodeState;
|
||||
term: Term;
|
||||
}
|
||||
|
||||
/** Leader elected event data */
|
||||
export interface LeaderElectedEvent {
|
||||
leaderId: NodeId;
|
||||
term: Term;
|
||||
}
|
||||
|
||||
/** Log committed event data */
|
||||
export interface LogCommittedEvent {
|
||||
index: LogIndex;
|
||||
term: Term;
|
||||
}
|
||||
Reference in New Issue
Block a user