Skip to content

Commit 3a21cb5

Browse files
committed
final part of adding documentation
1 parent f109369 commit 3a21cb5

35 files changed

Lines changed: 623 additions & 0 deletions

packages/raft-core/src/config/ClusterConfig.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
import { NodeId } from "../core/Config";
22

3+
/**
4+
* Cluster member identity and transport address.
5+
*/
36
export interface ClusterMember {
47
id: NodeId;
58
address: string;
69
}
710

11+
/**
12+
* Cluster membership split into voters and learners.
13+
*/
814
export interface ClusterConfig {
915
voters: ClusterMember[];
1016
learners: ClusterMember[];
1117
}
1218

19+
/**
20+
* Compares two configurations by voter and learner membership ids.
21+
*/
1322
export function clusterConfigsEqual(a: ClusterConfig, b: ClusterConfig): boolean {
1423
if (a.voters.length !== b.voters.length) return false;
1524
if (a.learners.length !== b.learners.length) return false;
@@ -30,18 +39,22 @@ export function clusterConfigsEqual(a: ClusterConfig, b: ClusterConfig): boolean
3039
return true;
3140
}
3241

42+
/** Returns true when node is a voter in config. */
3343
export function isVoter(config: ClusterConfig, nodeId: NodeId): boolean {
3444
return config.voters.some(m => m.id === nodeId);
3545
}
3646

47+
/** Returns true when node is a learner in config. */
3748
export function isLearner(config: ClusterConfig, nodeId: NodeId): boolean {
3849
return config.learners.some(m => m.id === nodeId);
3950
}
4051

52+
/** Returns true when node is either voter or learner in config. */
4153
export function isNodeInCluster(config: ClusterConfig, nodeId: NodeId): boolean {
4254
return isVoter(config, nodeId) || isLearner(config, nodeId);
4355
}
4456

57+
/** Returns majority quorum size for current voter set. */
4558
export function getQuorumSize(config: ClusterConfig): number {
4659
return Math.floor(config.voters.length / 2) + 1;
4760
}

packages/raft-core/src/core/Config.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,34 @@
11
import { ClusterMember } from "../config/ClusterConfig";
22

3+
/** Logical node identifier used across raft-core. */
34
export type NodeId = string;
45

6+
/**
7+
* Static node bootstrap/runtime configuration.
8+
*/
59
export interface RaftConfig {
10+
/** Local node id. */
611
nodeId: NodeId;
12+
/** Local transport address. */
713
address: string;
14+
/** Other cluster members (excluding self). */
815
peers: ClusterMember[];
16+
/** Minimum election timeout in milliseconds. */
917
electionTimeoutMinMs: number;
18+
/** Maximum election timeout in milliseconds. */
1019
electionTimeoutMaxMs: number;
20+
/** Heartbeat interval in milliseconds. */
1121
heartbeatIntervalMs: number;
22+
/** Optional committed-entry threshold for triggering snapshots. */
1223
snapshotThreshold?: number;
1324
}
1425

26+
/**
27+
* Validates Raft node configuration invariants.
28+
*
29+
* @param config Configuration object to validate.
30+
* @throws Error When any required field is invalid.
31+
*/
1532
export function validateConfig(config: RaftConfig): void {
1633
if(!config.nodeId || typeof config.nodeId !== 'string') {
1734
throw new Error(`Invalid nodeId: ${config.nodeId}. nodeId must be a non-empty string.`);
@@ -58,6 +75,12 @@ export function validateConfig(config: RaftConfig): void {
5875
}
5976
}
6077

78+
/**
79+
* Creates and validates a Raft configuration object.
80+
*
81+
* @returns Validated RaftConfig instance.
82+
* @throws Error When provided values violate config invariants.
83+
*/
6184
export function createConfig(nodeId: NodeId, address: string, peers: ClusterMember[], electionTimeoutMinMs: number, electionTimeoutMaxMs: number, heartbeatIntervalMs: number, snapshotThreshold?: number): RaftConfig {
6285
const config: RaftConfig = {
6386
nodeId,

packages/raft-core/src/events/EventBus.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import { RaftEvent, RaftEventBus } from "./RaftEvents";
22

3+
/**
4+
* In-process event bus implementation with in-memory subscriber list.
5+
*/
36
export class LocalEventBus implements RaftEventBus {
47
private handlers: Array<(event: RaftEvent) => void> = [];
58

9+
/** Publishes an event to all current subscribers. */
610
emit(event: RaftEvent): void {
711
for (const handler of this.handlers) {
812
try {
@@ -13,6 +17,12 @@ export class LocalEventBus implements RaftEventBus {
1317
}
1418
}
1519

20+
/**
21+
* Registers a subscriber callback.
22+
*
23+
* @param handler Subscriber function.
24+
* @returns Unsubscribe callback.
25+
*/
1626
subscribe(handler: (event: RaftEvent) => void): () => void {
1727
this.handlers.push(handler);
1828
return () => {
@@ -21,10 +31,15 @@ export class LocalEventBus implements RaftEventBus {
2131
}
2232
}
2333

34+
/**
35+
* Event bus implementation that drops all events and subscriptions.
36+
*/
2437
export class NoOpEventBus implements RaftEventBus {
38+
/** Ignores published events. */
2539
emit(_event: RaftEvent): void {
2640
// no-op
2741
}
42+
/** Returns a no-op unsubscribe callback. */
2843
subscribe(handler: (event: RaftEvent) => void): () => void {
2944
return () => {
3045
// no-op

packages/raft-core/src/events/EventStore.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,31 @@
11
import { RaftEvent, RaftEventBus } from "./RaftEvents";
22

3+
/**
4+
* Configuration for in-memory event retention.
5+
*/
36
export interface EventStoreOptions {
7+
/** Maximum number of retained historical events. */
48
maxEvents: number;
59
}
610

11+
/**
12+
* In-memory event store that records bus events and broadcasts live updates.
13+
*/
714
export class EventStore {
815
private events: RaftEvent[] = [];
916
private liveSubscribers: Set<(event: RaftEvent) => void> = new Set();
1017

18+
/**
19+
* Subscribes to an event bus and starts buffering incoming events.
20+
*
21+
* @param bus Source event bus.
22+
* @param options Retention configuration.
23+
*/
1124
constructor(bus: RaftEventBus, private options: EventStoreOptions) {
1225
bus.subscribe((event) => this.append(event));
1326
}
1427

28+
/** Appends an event to retention buffer and notifies live subscribers. */
1529
private append(event: RaftEvent): void {
1630
this.events.push(event);
1731

@@ -24,15 +38,23 @@ export class EventStore {
2438
}
2539
}
2640

41+
/** Returns a copy of currently retained events. */
2742
getAllEvents(): RaftEvent[] {
2843
return [...this.events];
2944
}
3045

46+
/**
47+
* Subscribes to live appended events.
48+
*
49+
* @param subscriber Live event callback.
50+
* @returns Unsubscribe callback.
51+
*/
3152
onLiveEvent(subscriber: (event: RaftEvent) => void): () => void {
3253
this.liveSubscribers.add(subscriber);
3354
return () => this.liveSubscribers.delete(subscriber);
3455
}
3556

57+
/** Returns current number of retained events. */
3658
getSize(): number {
3759
return this.events.length;
3860
}

packages/raft-core/src/events/RaftEvents.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import {
1111
import { RaftState } from "../core/StateMachine";
1212
import { ClusterConfig, ClusterMember } from "../config/ClusterConfig";
1313

14+
/**
15+
* Common fields present in node-scoped Raft runtime events.
16+
*/
1417
export interface BaseEvent {
1518
eventId: string;
1619
timestamp: number;
@@ -109,6 +112,9 @@ export interface NextIndexDecrementedEvent extends BaseEvent {
109112
term: number;
110113
}
111114

115+
/**
116+
* Supported wire-level message event categories used by transport instrumentation.
117+
*/
112118
export type MessageType =
113119
| "RequestVote"
114120
| "RequestVoteResponse"
@@ -163,7 +169,9 @@ export interface InstallSnapshotResponseEvent extends BaseMessageEvent {
163169
latencyMs: number;
164170
}
165171

172+
/** Message-sent event variants. */
166173
export type MessageSentEvent = RequestVoteSentEvent | AppendEntriesSentEvent | InstallSnapshotRequestEvent;
174+
/** Message-received event variants. */
167175
export type MessageReceivedEvent = RequestVoteReceivedEvent | AppendEntriesReceivedEvent | InstallSnapshotResponseEvent;
168176

169177
export interface MessageDroppedEvent extends BaseMessageEvent {
@@ -265,6 +273,9 @@ export interface FatalErrorEvent extends BaseEvent {
265273
error: string;
266274
}
267275

276+
/**
277+
* Union of all observability events emitted by raft-core.
278+
*/
268279
export type RaftEvent =
269280
| NodeStateChangedEvent
270281
| TermChangedEvent
@@ -296,6 +307,9 @@ export type RaftEvent =
296307
| ConfigChangeRejectedEvent
297308
| FatalErrorEvent;
298309

310+
/**
311+
* Event bus abstraction for publishing and subscribing to Raft runtime events.
312+
*/
299313
export interface RaftEventBus {
300314
emit(event: RaftEvent): void;
301315
subscribe(handler: (event: RaftEvent) => void): () => void;

packages/raft-core/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/**
2+
* Public raft-core package exports.
3+
*/
14
export { RaftNode } from "./core/RaftNode";
25
export type {
36
RaftNodeOptions,

packages/raft-core/src/lock/AsyncLock.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
/**
2+
* Minimal FIFO async mutual exclusion lock.
3+
*/
14
export class AsyncLock {
25
private locked: boolean = false
36
private queue: Array<() => void> = []
47

8+
/**
9+
* Acquires lock when available or waits until current holder releases.
10+
*/
511
async acquire(): Promise<void> {
612

713
if (!this.locked) {
@@ -12,6 +18,11 @@ export class AsyncLock {
1218
await new Promise<void>(resolve => this.queue.push(resolve));
1319
}
1420

21+
/**
22+
* Releases lock and wakes next queued waiter if present.
23+
*
24+
* @throws Error When lock is not currently held.
25+
*/
1526
release(): void {
1627
if (!this.locked) {
1728
throw new Error('Cannot release an unlocked lock');
@@ -25,6 +36,12 @@ export class AsyncLock {
2536
}
2637
}
2738

39+
/**
40+
* Executes callback while holding lock and always releases afterwards.
41+
*
42+
* @param callback Async critical section body.
43+
* @returns Callback result.
44+
*/
2845
async runExclusive<T>(callback: () => Promise<T>): Promise<T> {
2946
await this.acquire();
3047
try {
@@ -34,10 +51,12 @@ export class AsyncLock {
3451
}
3552
}
3653

54+
/** Returns true when lock is currently held. */
3755
isLocked(): boolean {
3856
return this.locked;
3957
}
4058

59+
/** Returns number of waiters queued for lock acquisition. */
4160
getQueueLength(): number {
4261
return this.queue.length;
4362
}

packages/raft-core/src/log/LogEntry.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,46 @@
11
import { ClusterConfig } from "../config/ClusterConfig";
22

3+
/**
4+
* Application command payload replicated through Raft log.
5+
*/
36
export interface Command {
7+
/** Command discriminator used by application state machine. */
48
type: string;
9+
/** Command payload consumed by application state machine. */
510
payload: any;
611
}
712

13+
/**
14+
* Supported replicated log entry categories.
15+
*/
816
export enum LogEntryType {
917
COMMAND = 'COMMAND',
1018
CONFIG = 'CONFIG',
1119
NOOP = 'NOOP'
1220
}
1321

22+
/**
23+
* Replicated Raft log record.
24+
*/
1425
export interface LogEntry {
26+
/** Raft term when this entry was created. */
1527
term: number;
28+
/** Monotonic log index. */
1629
index: number;
30+
/** Entry semantic type. */
1731
type: LogEntryType;
32+
/** Command payload for COMMAND entries. */
1833
command?: Command;
34+
/** Cluster config payload for CONFIG entries. */
1935
config?: ClusterConfig
2036
}
2137

38+
/**
39+
* Validates a single log entry shape and type-specific payload fields.
40+
*
41+
* @param entry Entry to validate.
42+
* @throws Error When entry shape is invalid.
43+
*/
2244
export function validateLogEntry(entry: LogEntry): void {
2345
if (!Number.isInteger(entry.term) || entry.term < 0) {
2446
throw new Error(`Invalid term: ${entry.term}. Term must be a non-negative integer.`);
@@ -51,6 +73,12 @@ export function validateLogEntry(entry: LogEntry): void {
5173
}
5274
}
5375

76+
/**
77+
* Validates a complete log sequence for per-entry validity and monotonic ordering.
78+
*
79+
* @param log Log entries in index order.
80+
* @throws Error When sequence invariants are violated.
81+
*/
5482
export function validateLogSequence(log: LogEntry[]): void {
5583
if (log.length === 0) {
5684
return;
@@ -74,6 +102,9 @@ export function validateLogSequence(log: LogEntry[]): void {
74102
}
75103
}
76104

105+
/**
106+
* Deep-compares two command payloads for deterministic test assertions.
107+
*/
77108
export function commandsEqual(cmd1: Command, cmd2: Command): boolean {
78109
if (cmd1.type !== cmd2.type) {
79110
return false;
@@ -84,6 +115,9 @@ export function commandsEqual(cmd1: Command, cmd2: Command): boolean {
84115
return payload1 === payload2;
85116
}
86117

118+
/**
119+
* Compares two log entries by metadata and type-specific payload.
120+
*/
87121
export function entriesEqual(entry1: LogEntry, entry2: LogEntry): boolean {
88122
if (entry1.term !== entry2.term || entry1.index !== entry2.index) {
89123
return false;
@@ -106,6 +140,9 @@ export function entriesEqual(entry1: LogEntry, entry2: LogEntry): boolean {
106140
return commandsEqual(entry1.command!, entry2.command!);
107141
}
108142

143+
/**
144+
* Compares two logs entry-by-entry.
145+
*/
109146
export function logsEqual(log1: LogEntry[], log2: LogEntry[]): boolean {
110147
if (log1.length !== log2.length) {
111148
return false;

0 commit comments

Comments
 (0)