dandelion-mesh 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +192 -0
  3. package/build/main/crypto/CryptoService.d.ts +31 -0
  4. package/build/main/crypto/CryptoService.js +78 -0
  5. package/build/main/index.d.ts +11 -0
  6. package/build/main/index.js +24 -0
  7. package/build/main/mesh/DandelionMesh.d.ts +115 -0
  8. package/build/main/mesh/DandelionMesh.js +368 -0
  9. package/build/main/mesh/types.d.ts +58 -0
  10. package/build/main/mesh/types.js +3 -0
  11. package/build/main/raft/RaftNode.d.ts +87 -0
  12. package/build/main/raft/RaftNode.js +443 -0
  13. package/build/main/raft/log/InMemoryRaftLog.d.ts +20 -0
  14. package/build/main/raft/log/InMemoryRaftLog.js +55 -0
  15. package/build/main/raft/log/LocalStorageRaftLog.d.ts +9 -0
  16. package/build/main/raft/log/LocalStorageRaftLog.js +16 -0
  17. package/build/main/raft/log/RaftLog.d.ts +30 -0
  18. package/build/main/raft/log/RaftLog.js +3 -0
  19. package/build/main/raft/log/RaftLog.test-util.d.ts +3 -0
  20. package/build/main/raft/log/RaftLog.test-util.js +82 -0
  21. package/build/main/raft/log/SessionStorageRaftLog.d.ts +9 -0
  22. package/build/main/raft/log/SessionStorageRaftLog.js +38 -0
  23. package/build/main/raft/log/StorageRaftLog.d.ts +28 -0
  24. package/build/main/raft/log/StorageRaftLog.js +117 -0
  25. package/build/main/raft/log/StorageRaftLog.test-util.d.ts +3 -0
  26. package/build/main/raft/log/StorageRaftLog.test-util.js +54 -0
  27. package/build/main/raft/types.d.ts +53 -0
  28. package/build/main/raft/types.js +3 -0
  29. package/build/main/transport/PeerJSTransport.d.ts +46 -0
  30. package/build/main/transport/PeerJSTransport.js +139 -0
  31. package/build/main/transport/Transport.d.ts +38 -0
  32. package/build/main/transport/Transport.js +8 -0
  33. package/build/module/crypto/CryptoService.d.ts +31 -0
  34. package/build/module/crypto/CryptoService.js +69 -0
  35. package/build/module/index.d.ts +11 -0
  36. package/build/module/index.js +11 -0
  37. package/build/module/mesh/DandelionMesh.d.ts +115 -0
  38. package/build/module/mesh/DandelionMesh.js +364 -0
  39. package/build/module/mesh/types.d.ts +58 -0
  40. package/build/module/mesh/types.js +2 -0
  41. package/build/module/raft/RaftNode.d.ts +87 -0
  42. package/build/module/raft/RaftNode.js +440 -0
  43. package/build/module/raft/log/InMemoryRaftLog.d.ts +20 -0
  44. package/build/module/raft/log/InMemoryRaftLog.js +48 -0
  45. package/build/module/raft/log/LocalStorageRaftLog.d.ts +9 -0
  46. package/build/module/raft/log/LocalStorageRaftLog.js +12 -0
  47. package/build/module/raft/log/RaftLog.d.ts +30 -0
  48. package/build/module/raft/log/RaftLog.js +2 -0
  49. package/build/module/raft/log/RaftLog.test-util.d.ts +3 -0
  50. package/build/module/raft/log/RaftLog.test-util.js +78 -0
  51. package/build/module/raft/log/SessionStorageRaftLog.d.ts +9 -0
  52. package/build/module/raft/log/SessionStorageRaftLog.js +34 -0
  53. package/build/module/raft/log/StorageRaftLog.d.ts +28 -0
  54. package/build/module/raft/log/StorageRaftLog.js +114 -0
  55. package/build/module/raft/log/StorageRaftLog.test-util.d.ts +3 -0
  56. package/build/module/raft/log/StorageRaftLog.test-util.js +50 -0
  57. package/build/module/raft/types.d.ts +53 -0
  58. package/build/module/raft/types.js +2 -0
  59. package/build/module/transport/PeerJSTransport.d.ts +46 -0
  60. package/build/module/transport/PeerJSTransport.js +134 -0
  61. package/build/module/transport/Transport.d.ts +38 -0
  62. package/build/module/transport/Transport.js +7 -0
  63. package/package.json +119 -0
@@ -0,0 +1,443 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RaftNode = void 0;
4
+ /**
5
+ * Core Raft consensus node.
6
+ *
7
+ * This class implements the Raft algorithm for leader election and log
8
+ * replication. It is transport-agnostic — the caller must wire up
9
+ * `sendMessage` to deliver RPCs, and call `handleMessage` when RPCs arrive.
10
+ *
11
+ * Log indices are 1-based per the Raft paper.
12
+ */
13
+ class RaftNode {
14
+ constructor(id, log, options) {
15
+ var _a, _b, _c;
16
+ this.role = 'follower';
17
+ this.currentTerm = 0;
18
+ this.votedFor = null;
19
+ this.leaderId = null;
20
+ // Volatile state (all servers)
21
+ this.commitIndex = 0;
22
+ this.lastApplied = 0;
23
+ // Volatile state (leader only)
24
+ this.nextIndex = new Map();
25
+ this.matchIndex = new Map();
26
+ // Election / heartbeat timers
27
+ this.electionTimer = null;
28
+ this.heartbeatTimer = null;
29
+ // Cluster membership (peer IDs excluding self)
30
+ this.peers = [];
31
+ // Votes received in current election
32
+ this.votesReceived = new Set();
33
+ /** Called by the node to send an RPC to a peer — must be wired up externally */
34
+ this.sendMessage = () => { };
35
+ this.eventListeners = {
36
+ leaderChanged: new Set(),
37
+ committed: new Set(),
38
+ roleChanged: new Set(),
39
+ };
40
+ this.destroyed = false;
41
+ this.id = id;
42
+ this.log = log;
43
+ this.electionTimeoutMin = (_a = options === null || options === void 0 ? void 0 : options.electionTimeoutMin) !== null && _a !== void 0 ? _a : 2000;
44
+ this.electionTimeoutMax = (_b = options === null || options === void 0 ? void 0 : options.electionTimeoutMax) !== null && _b !== void 0 ? _b : 4000;
45
+ this.heartbeatInterval = (_c = options === null || options === void 0 ? void 0 : options.heartbeatInterval) !== null && _c !== void 0 ? _c : 400;
46
+ // Restore persistent state
47
+ const state = this.log.loadState();
48
+ this.currentTerm = state.currentTerm;
49
+ this.votedFor = state.votedFor;
50
+ }
51
+ // --- Public API ---
52
+ /** Start the node (begin election timer as follower) */
53
+ start(peerIds) {
54
+ this.peers = peerIds.filter((p) => p !== this.id);
55
+ this.resetElectionTimer();
56
+ }
57
+ /** Update the cluster membership dynamically (e.g. when peers join/leave) */
58
+ updatePeers(peerIds) {
59
+ this.peers = peerIds.filter((p) => p !== this.id);
60
+ // Reinitialize leader volatile state for new peers
61
+ if (this.role === 'leader') {
62
+ for (const peer of this.peers) {
63
+ if (!this.nextIndex.has(peer)) {
64
+ this.nextIndex.set(peer, this.log.lastIndex() + 1);
65
+ this.matchIndex.set(peer, 0);
66
+ }
67
+ }
68
+ }
69
+ }
70
+ /** Submit a command to be replicated. Only succeeds on the leader. */
71
+ propose(command) {
72
+ if (this.role !== 'leader') {
73
+ return false;
74
+ }
75
+ const entry = { term: this.currentTerm, command };
76
+ this.log.append([entry]);
77
+ this.persistState();
78
+ // Immediately replicate to followers
79
+ this.sendAppendEntriesToAll();
80
+ // Check if we can commit (single-node cluster)
81
+ this.advanceCommitIndex();
82
+ return true;
83
+ }
84
+ /** Handle an incoming Raft RPC message from a peer */
85
+ handleMessage(fromPeerId, message) {
86
+ if (this.destroyed)
87
+ return;
88
+ // If we see a higher term, step down
89
+ const msgTerm = 'term' in message ? message.term : 0;
90
+ if (msgTerm > this.currentTerm) {
91
+ this.currentTerm = msgTerm;
92
+ this.votedFor = null;
93
+ this.persistState();
94
+ this.becomeFollower();
95
+ }
96
+ switch (message.type) {
97
+ case 'RequestVote':
98
+ this.handleRequestVote(fromPeerId, message);
99
+ break;
100
+ case 'RequestVoteResult':
101
+ this.handleRequestVoteResult(fromPeerId, message);
102
+ break;
103
+ case 'AppendEntries':
104
+ this.handleAppendEntries(fromPeerId, message);
105
+ break;
106
+ case 'AppendEntriesResult':
107
+ this.handleAppendEntriesResult(message);
108
+ break;
109
+ }
110
+ }
111
+ /** Get current role */
112
+ getRole() {
113
+ return this.role;
114
+ }
115
+ /** Get current term */
116
+ getCurrentTerm() {
117
+ return this.currentTerm;
118
+ }
119
+ /** Get the current leader ID (null if unknown) */
120
+ getLeaderId() {
121
+ return this.leaderId;
122
+ }
123
+ /** Get the commit index */
124
+ getCommitIndex() {
125
+ return this.commitIndex;
126
+ }
127
+ /** Check if this node is the leader */
128
+ isLeader() {
129
+ return this.role === 'leader';
130
+ }
131
+ /** Register an event listener */
132
+ on(event, listener) {
133
+ this.eventListeners[event].add(listener);
134
+ }
135
+ /** Remove an event listener */
136
+ off(event, listener) {
137
+ this.eventListeners[event].delete(listener);
138
+ }
139
+ /** Stop all timers and clean up */
140
+ destroy() {
141
+ this.destroyed = true;
142
+ this.clearElectionTimer();
143
+ this.clearHeartbeatTimer();
144
+ }
145
+ // --- Election ---
146
+ becomeFollower() {
147
+ const wasLeader = this.role === 'leader';
148
+ this.role = 'follower';
149
+ this.clearHeartbeatTimer();
150
+ this.resetElectionTimer();
151
+ this.emit('roleChanged', 'follower');
152
+ if (wasLeader) {
153
+ // Leader identity is unknown until we hear from the new leader
154
+ }
155
+ }
156
+ becomeCandidate() {
157
+ this.role = 'candidate';
158
+ this.currentTerm++;
159
+ this.votedFor = this.id;
160
+ this.persistState();
161
+ this.votesReceived.clear();
162
+ this.votesReceived.add(this.id); // vote for self
163
+ this.leaderId = null;
164
+ this.emit('roleChanged', 'candidate');
165
+ this.emit('leaderChanged', null);
166
+ this.resetElectionTimer();
167
+ // Single-node cluster: become leader immediately
168
+ if (this.peers.length === 0) {
169
+ this.becomeLeader();
170
+ return;
171
+ }
172
+ // Send RequestVote to all peers
173
+ const request = {
174
+ type: 'RequestVote',
175
+ term: this.currentTerm,
176
+ candidateId: this.id,
177
+ lastLogIndex: this.log.lastIndex(),
178
+ lastLogTerm: this.log.lastTerm(),
179
+ };
180
+ for (const peer of this.peers) {
181
+ this.sendMessage(peer, request);
182
+ }
183
+ }
184
+ becomeLeader() {
185
+ this.role = 'leader';
186
+ this.leaderId = this.id;
187
+ this.clearElectionTimer();
188
+ this.emit('roleChanged', 'leader');
189
+ this.emit('leaderChanged', this.id);
190
+ // Initialize nextIndex and matchIndex for all peers
191
+ const lastLogIndex = this.log.lastIndex();
192
+ this.nextIndex.clear();
193
+ this.matchIndex.clear();
194
+ for (const peer of this.peers) {
195
+ this.nextIndex.set(peer, lastLogIndex + 1);
196
+ this.matchIndex.set(peer, 0);
197
+ }
198
+ // Send initial heartbeats
199
+ this.sendAppendEntriesToAll();
200
+ // Start heartbeat timer
201
+ this.clearHeartbeatTimer();
202
+ this.heartbeatTimer = setInterval(() => {
203
+ if (this.role === 'leader' && !this.destroyed) {
204
+ this.sendAppendEntriesToAll();
205
+ }
206
+ }, this.heartbeatInterval);
207
+ }
208
+ // --- RequestVote handling ---
209
+ handleRequestVote(_fromPeerId, args) {
210
+ const reply = {
211
+ type: 'RequestVoteResult',
212
+ term: this.currentTerm,
213
+ voteGranted: false,
214
+ };
215
+ // Reject if candidate's term is behind
216
+ if (args.term < this.currentTerm) {
217
+ this.sendMessage(args.candidateId, reply);
218
+ return;
219
+ }
220
+ // Grant vote if we haven't voted for someone else, and candidate's log is up-to-date
221
+ const canVote = this.votedFor === null || this.votedFor === args.candidateId;
222
+ const candidateLogUpToDate = this.isLogUpToDate(args.lastLogTerm, args.lastLogIndex);
223
+ if (canVote && candidateLogUpToDate) {
224
+ this.votedFor = args.candidateId;
225
+ this.persistState();
226
+ reply.voteGranted = true;
227
+ this.resetElectionTimer(); // reset timer on granting vote
228
+ }
229
+ this.sendMessage(args.candidateId, reply);
230
+ }
231
+ handleRequestVoteResult(fromPeerId, result) {
232
+ if (this.role !== 'candidate')
233
+ return;
234
+ if (result.term !== this.currentTerm)
235
+ return;
236
+ if (result.voteGranted) {
237
+ this.votesReceived.add(fromPeerId);
238
+ // Check majority (votesReceived includes self)
239
+ const totalCluster = this.peers.length + 1;
240
+ if (this.votesReceived.size >= Math.floor(totalCluster / 2) + 1) {
241
+ this.becomeLeader();
242
+ }
243
+ }
244
+ }
245
+ /** Check if a candidate's log is at least as up-to-date as ours */
246
+ isLogUpToDate(candidateLastTerm, candidateLastIndex) {
247
+ const myLastTerm = this.log.lastTerm();
248
+ if (candidateLastTerm !== myLastTerm) {
249
+ return candidateLastTerm > myLastTerm;
250
+ }
251
+ return candidateLastIndex >= this.log.lastIndex();
252
+ }
253
+ // --- AppendEntries handling ---
254
+ handleAppendEntries(_fromPeerId, args) {
255
+ const reply = {
256
+ type: 'AppendEntriesResult',
257
+ term: this.currentTerm,
258
+ success: false,
259
+ responderId: this.id,
260
+ matchIndex: 0,
261
+ };
262
+ // Reject if leader's term is behind
263
+ if (args.term < this.currentTerm) {
264
+ this.sendMessage(args.leaderId, reply);
265
+ return;
266
+ }
267
+ // Valid leader — become follower if not already
268
+ if (this.role !== 'follower') {
269
+ this.becomeFollower();
270
+ }
271
+ else {
272
+ this.resetElectionTimer();
273
+ }
274
+ if (this.leaderId !== args.leaderId) {
275
+ this.leaderId = args.leaderId;
276
+ this.emit('leaderChanged', args.leaderId);
277
+ }
278
+ // Check log consistency
279
+ if (args.prevLogIndex > 0) {
280
+ const prevTerm = this.log.getTerm(args.prevLogIndex);
281
+ if (prevTerm === 0 && args.prevLogIndex > this.log.lastIndex()) {
282
+ // We don't have an entry at prevLogIndex
283
+ this.resetElectionTimer();
284
+ this.sendMessage(args.leaderId, reply);
285
+ return;
286
+ }
287
+ if (prevTerm !== args.prevLogTerm) {
288
+ // Conflict: delete this entry and everything after
289
+ this.log.truncateFrom(args.prevLogIndex);
290
+ this.resetElectionTimer();
291
+ this.sendMessage(args.leaderId, reply);
292
+ return;
293
+ }
294
+ }
295
+ // Append new entries (handle conflicts and skip duplicates)
296
+ if (args.entries.length > 0) {
297
+ for (let i = 0; i < args.entries.length; i++) {
298
+ const entryIndex = args.prevLogIndex + 1 + i;
299
+ const existing = this.log.getEntry(entryIndex);
300
+ if (existing) {
301
+ if (existing.term !== args.entries[i].term) {
302
+ // Conflict: truncate from here and append the rest
303
+ this.log.truncateFrom(entryIndex);
304
+ this.log.append(args.entries.slice(i));
305
+ break;
306
+ }
307
+ // Same term — entry already present, skip
308
+ }
309
+ else {
310
+ // No existing entry — append remaining
311
+ this.log.append(args.entries.slice(i));
312
+ break;
313
+ }
314
+ }
315
+ }
316
+ // Update commit index
317
+ if (args.leaderCommit > this.commitIndex) {
318
+ this.commitIndex = Math.min(args.leaderCommit, this.log.lastIndex());
319
+ this.applyCommitted();
320
+ }
321
+ reply.success = true;
322
+ reply.matchIndex = args.prevLogIndex + args.entries.length;
323
+ this.resetElectionTimer();
324
+ this.sendMessage(args.leaderId, reply);
325
+ }
326
+ handleAppendEntriesResult(result) {
327
+ var _a;
328
+ if (this.role !== 'leader')
329
+ return;
330
+ if (result.term !== this.currentTerm)
331
+ return;
332
+ const peer = result.responderId;
333
+ if (result.success) {
334
+ // Update nextIndex and matchIndex
335
+ if (result.matchIndex > 0) {
336
+ this.nextIndex.set(peer, result.matchIndex + 1);
337
+ this.matchIndex.set(peer, result.matchIndex);
338
+ }
339
+ this.advanceCommitIndex();
340
+ }
341
+ else {
342
+ // Decrement nextIndex and retry
343
+ const ni = (_a = this.nextIndex.get(peer)) !== null && _a !== void 0 ? _a : 1;
344
+ this.nextIndex.set(peer, Math.max(1, ni - 1));
345
+ this.sendAppendEntriesTo(peer);
346
+ }
347
+ }
348
+ // --- Log replication ---
349
+ sendAppendEntriesToAll() {
350
+ for (const peer of this.peers) {
351
+ this.sendAppendEntriesTo(peer);
352
+ }
353
+ }
354
+ sendAppendEntriesTo(peer) {
355
+ var _a;
356
+ const ni = (_a = this.nextIndex.get(peer)) !== null && _a !== void 0 ? _a : this.log.lastIndex() + 1;
357
+ const prevLogIndex = ni - 1;
358
+ const prevLogTerm = this.log.getTerm(prevLogIndex);
359
+ const lastIndex = this.log.lastIndex();
360
+ const entries = ni <= lastIndex ? this.log.getEntries(ni, lastIndex) : [];
361
+ const args = {
362
+ type: 'AppendEntries',
363
+ term: this.currentTerm,
364
+ leaderId: this.id,
365
+ prevLogIndex,
366
+ prevLogTerm,
367
+ entries,
368
+ leaderCommit: this.commitIndex,
369
+ };
370
+ this.sendMessage(peer, args);
371
+ }
372
+ /** Advance commitIndex if a majority of matchIndex values allow it */
373
+ advanceCommitIndex() {
374
+ var _a;
375
+ const lastIndex = this.log.lastIndex();
376
+ for (let n = lastIndex; n > this.commitIndex; n--) {
377
+ // Only commit entries from the current term
378
+ if (this.log.getTerm(n) !== this.currentTerm)
379
+ continue;
380
+ // Count replicas (including self)
381
+ let replicaCount = 1; // self
382
+ for (const peer of this.peers) {
383
+ if (((_a = this.matchIndex.get(peer)) !== null && _a !== void 0 ? _a : 0) >= n) {
384
+ replicaCount++;
385
+ }
386
+ }
387
+ const majority = Math.floor((this.peers.length + 1) / 2) + 1;
388
+ if (replicaCount >= majority) {
389
+ this.commitIndex = n;
390
+ this.applyCommitted();
391
+ break;
392
+ }
393
+ }
394
+ }
395
+ /** Apply all committed but not-yet-applied entries */
396
+ applyCommitted() {
397
+ while (this.lastApplied < this.commitIndex) {
398
+ this.lastApplied++;
399
+ const entry = this.log.getEntry(this.lastApplied);
400
+ if (entry) {
401
+ this.emit('committed', entry, this.lastApplied);
402
+ }
403
+ }
404
+ }
405
+ // --- Timer management ---
406
+ resetElectionTimer() {
407
+ this.clearElectionTimer();
408
+ const timeout = this.electionTimeoutMin +
409
+ Math.random() * (this.electionTimeoutMax - this.electionTimeoutMin);
410
+ this.electionTimer = setTimeout(() => {
411
+ if (!this.destroyed) {
412
+ this.becomeCandidate();
413
+ }
414
+ }, timeout);
415
+ }
416
+ clearElectionTimer() {
417
+ if (this.electionTimer !== null) {
418
+ clearTimeout(this.electionTimer);
419
+ this.electionTimer = null;
420
+ }
421
+ }
422
+ clearHeartbeatTimer() {
423
+ if (this.heartbeatTimer !== null) {
424
+ clearInterval(this.heartbeatTimer);
425
+ this.heartbeatTimer = null;
426
+ }
427
+ }
428
+ // --- Persistence ---
429
+ persistState() {
430
+ this.log.saveState({
431
+ currentTerm: this.currentTerm,
432
+ votedFor: this.votedFor,
433
+ });
434
+ }
435
+ // --- Event emission ---
436
+ emit(event, ...args) {
437
+ for (const listener of this.eventListeners[event]) {
438
+ listener(...args);
439
+ }
440
+ }
441
+ }
442
+ exports.RaftNode = RaftNode;
443
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUmFmdE5vZGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvcmFmdC9SYWZ0Tm9kZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUF5QkE7Ozs7Ozs7O0dBUUc7QUFDSCxNQUFhLFFBQVE7SUE0Q25CLFlBQVksRUFBVSxFQUFFLEdBQWUsRUFBRSxPQUF5Qjs7UUF6QzFELFNBQUksR0FBYSxVQUFVLENBQUM7UUFDNUIsZ0JBQVcsR0FBRyxDQUFDLENBQUM7UUFDaEIsYUFBUSxHQUFrQixJQUFJLENBQUM7UUFDL0IsYUFBUSxHQUFrQixJQUFJLENBQUM7UUFFdkMsK0JBQStCO1FBQ3ZCLGdCQUFXLEdBQUcsQ0FBQyxDQUFDO1FBQ2hCLGdCQUFXLEdBQUcsQ0FBQyxDQUFDO1FBRXhCLCtCQUErQjtRQUN2QixjQUFTLEdBQUcsSUFBSSxHQUFHLEVBQWtCLENBQUM7UUFDdEMsZUFBVSxHQUFHLElBQUksR0FBRyxFQUFrQixDQUFDO1FBRS9DLDhCQUE4QjtRQUN0QixrQkFBYSxHQUF5QyxJQUFJLENBQUM7UUFDM0QsbUJBQWMsR0FBMEMsSUFBSSxDQUFDO1FBTXJFLCtDQUErQztRQUN2QyxVQUFLLEdBQWEsRUFBRSxDQUFDO1FBRTdCLHFDQUFxQztRQUM3QixrQkFBYSxHQUFHLElBQUksR0FBRyxFQUFVLENBQUM7UUFJMUMsZ0ZBQWdGO1FBQ3pFLGdCQUFXLEdBQ2hCLEdBQUcsRUFBRSxHQUFFLENBQUMsQ0FBQztRQUVNLG1CQUFjLEdBQXdCO1lBQ3JELGFBQWEsRUFBRSxJQUFJLEdBQUcsRUFBRTtZQUN4QixTQUFTLEVBQUUsSUFBSSxHQUFHLEVBQUU7WUFDcEIsV0FBVyxFQUFFLElBQUksR0FBRyxFQUFFO1NBQ3ZCLENBQUM7UUFFTSxjQUFTLEdBQUcsS0FBSyxDQUFDO1FBR3hCLElBQUksQ0FBQyxFQUFFLEdBQUcsRUFBRSxDQUFDO1FBQ2IsSUFBSSxDQUFDLEdBQUcsR0FBRyxHQUFHLENBQUM7UUFFZixJQUFJLENBQUMs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
@@ -0,0 +1,20 @@
1
+ import { LogEntry, RaftPersistentState } from '../types';
2
+ import { RaftLog } from './RaftLog';
3
+ /**
4
+ * In-memory Raft log implementation.
5
+ * All state is lost on page refresh — suitable for ephemeral sessions.
6
+ */
7
+ export declare class InMemoryRaftLog<T = unknown> implements RaftLog<T> {
8
+ private entries;
9
+ private state;
10
+ loadState(): RaftPersistentState;
11
+ saveState(state: RaftPersistentState): void;
12
+ length(): number;
13
+ getEntry(index: number): LogEntry<T> | undefined;
14
+ getTerm(index: number): number;
15
+ append(entries: LogEntry<T>[]): void;
16
+ truncateFrom(index: number): void;
17
+ getEntries(startIndex: number, endIndex: number): LogEntry<T>[];
18
+ lastIndex(): number;
19
+ lastTerm(): number;
20
+ }
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InMemoryRaftLog = void 0;
4
+ /**
5
+ * In-memory Raft log implementation.
6
+ * All state is lost on page refresh — suitable for ephemeral sessions.
7
+ */
8
+ class InMemoryRaftLog {
9
+ constructor() {
10
+ this.entries = [];
11
+ this.state = { currentTerm: 0, votedFor: null };
12
+ }
13
+ loadState() {
14
+ return Object.assign({}, this.state);
15
+ }
16
+ saveState(state) {
17
+ this.state = Object.assign({}, state);
18
+ }
19
+ length() {
20
+ return this.entries.length;
21
+ }
22
+ getEntry(index) {
23
+ if (index < 1 || index > this.entries.length) {
24
+ return undefined;
25
+ }
26
+ return this.entries[index - 1];
27
+ }
28
+ getTerm(index) {
29
+ var _a, _b;
30
+ return (_b = (_a = this.getEntry(index)) === null || _a === void 0 ? void 0 : _a.term) !== null && _b !== void 0 ? _b : 0;
31
+ }
32
+ append(entries) {
33
+ this.entries.push(...entries);
34
+ }
35
+ truncateFrom(index) {
36
+ if (index < 1)
37
+ return;
38
+ this.entries.length = index - 1;
39
+ }
40
+ getEntries(startIndex, endIndex) {
41
+ if (startIndex > endIndex || startIndex > this.entries.length)
42
+ return [];
43
+ return this.entries.slice(startIndex - 1, endIndex);
44
+ }
45
+ lastIndex() {
46
+ return this.entries.length;
47
+ }
48
+ lastTerm() {
49
+ if (this.entries.length === 0)
50
+ return 0;
51
+ return this.entries[this.entries.length - 1].term;
52
+ }
53
+ }
54
+ exports.InMemoryRaftLog = InMemoryRaftLog;
55
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiSW5NZW1vcnlSYWZ0TG9nLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3JhZnQvbG9nL0luTWVtb3J5UmFmdExvZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFJQTs7O0dBR0c7QUFDSCxNQUFhLGVBQWU7SUFBNUI7UUFDVSxZQUFPLEdBQWtCLEVBQUUsQ0FBQztRQUM1QixVQUFLLEdBQXdCLEVBQUUsV0FBVyxFQUFFLENBQUMsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLENBQUM7SUErQzFFLENBQUM7SUE3Q0MsU0FBUztRQUNQLHlCQUFZLElBQUksQ0FBQyxLQUFLLEVBQUc7SUFDM0IsQ0FBQztJQUVELFNBQVMsQ0FBQyxLQUEwQjtRQUNsQyxJQUFJLENBQUMsS0FBSyxxQkFBUSxLQUFLLENBQUUsQ0FBQztJQUM1QixDQUFDO0lBRUQsTUFBTTtRQUNKLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUM7SUFDN0IsQ0FBQztJQUVELFFBQVEsQ0FBQyxLQUFhO1FBQ3BCLElBQUksS0FBSyxHQUFHLENBQUMsSUFBSSxLQUFLLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUU7WUFDNUMsT0FBTyxTQUFTLENBQUM7U0FDbEI7UUFDRCxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQ2pDLENBQUM7SUFFRCxPQUFPLENBQUMsS0FBYTs7UUFDbkIsT0FBTyxNQUFBLE1BQUEsSUFBSSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsMENBQUUsSUFBSSxtQ0FBSSxDQUFDLENBQUM7SUFDekMsQ0FBQztJQUVELE1BQU0sQ0FBQyxPQUFzQjtRQUMzQixJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxHQUFHLE9BQU8sQ0FBQyxDQUFDO0lBQ2hDLENBQUM7SUFFRCxZQUFZLENBQUMsS0FBYTtRQUN4QixJQUFJLEtBQUssR0FBRyxDQUFDO1lBQUUsT0FBTztRQUN0QixJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sR0FBRyxLQUFLLEdBQUcsQ0FBQyxDQUFDO0lBQ2xDLENBQUM7SUFFRCxVQUFVLENBQUMsVUFBa0IsRUFBRSxRQUFnQjtRQUM3QyxJQUFJLFVBQVUsR0FBRyxRQUFRLElBQUksVUFBVSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTTtZQUFFLE9BQU8sRUFBRSxDQUFDO1FBQ3pFLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsVUFBVSxHQUFHLENBQUMsRUFBRSxRQUFRLENBQUMsQ0FBQztJQUN0RCxDQUFDO0lBRUQsU0FBUztRQUNQLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUM7SUFDN0IsQ0FBQztJQUVELFFBQVE7UUFDTixJQUFJLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxLQUFLLENBQUM7WUFBRSxPQUFPLENBQUMsQ0FBQztRQUN4QyxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDO0lBQ3BELENBQUM7Q0FDRjtBQWpERCwwQ0FpREMifQ==
@@ -0,0 +1,9 @@
1
+ import { StorageRaftLog } from './StorageRaftLog';
2
+ /**
3
+ * localStorage-backed Raft log implementation.
4
+ * Survives page refreshes — suitable for durable sessions where peers
5
+ * may rejoin after a restart.
6
+ */
7
+ export declare class LocalStorageRaftLog<T = unknown> extends StorageRaftLog<T> {
8
+ constructor(namespace: string);
9
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalStorageRaftLog = void 0;
4
+ const StorageRaftLog_1 = require("./StorageRaftLog");
5
+ /**
6
+ * localStorage-backed Raft log implementation.
7
+ * Survives page refreshes — suitable for durable sessions where peers
8
+ * may rejoin after a restart.
9
+ */
10
+ class LocalStorageRaftLog extends StorageRaftLog_1.StorageRaftLog {
11
+ constructor(namespace) {
12
+ super(localStorage, namespace);
13
+ }
14
+ }
15
+ exports.LocalStorageRaftLog = LocalStorageRaftLog;
16
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTG9jYWxTdG9yYWdlUmFmdExvZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9yYWZ0L2xvZy9Mb2NhbFN0b3JhZ2VSYWZ0TG9nLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHFEQUFrRDtBQUVsRDs7OztHQUlHO0FBQ0gsTUFBYSxtQkFBaUMsU0FBUSwrQkFBaUI7SUFDckUsWUFBWSxTQUFpQjtRQUMzQixLQUFLLENBQUMsWUFBWSxFQUFFLFNBQVMsQ0FBQyxDQUFDO0lBQ2pDLENBQUM7Q0FDRjtBQUpELGtEQUlDIn0=
@@ -0,0 +1,30 @@
1
+ import { LogEntry, RaftPersistentState } from '../types';
2
+ /**
3
+ * Abstract log persistence interface for the Raft consensus algorithm.
4
+ * Raft log indices are 1-based.
5
+ */
6
+ export interface RaftLog<T = unknown> {
7
+ /** Load persistent state (currentTerm, votedFor). Returns defaults if not yet stored. */
8
+ loadState(): RaftPersistentState;
9
+ /** Persist the current term and votedFor */
10
+ saveState(state: RaftPersistentState): void;
11
+ /** Number of entries in the log */
12
+ length(): number;
13
+ /** Get the entry at the given 1-based index, or undefined if out of range */
14
+ getEntry(index: number): LogEntry<T> | undefined;
15
+ /** Get the term of the entry at the given 1-based index, or 0 if out of range */
16
+ getTerm(index: number): number;
17
+ /** Append one or more entries to the end of the log */
18
+ append(entries: LogEntry<T>[]): void;
19
+ /**
20
+ * Delete all entries from the given 1-based index onward (inclusive).
21
+ * Used when resolving log conflicts.
22
+ */
23
+ truncateFrom(index: number): void;
24
+ /** Get all entries from startIndex to endIndex (both inclusive, 1-based) */
25
+ getEntries(startIndex: number, endIndex: number): LogEntry<T>[];
26
+ /** Get the index of the last entry (0 if log is empty) */
27
+ lastIndex(): number;
28
+ /** Get the term of the last entry (0 if log is empty) */
29
+ lastTerm(): number;
30
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUmFmdExvZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9yYWZ0L2xvZy9SYWZ0TG9nLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiIifQ==
@@ -0,0 +1,3 @@
1
+ import { ExecutionContext } from 'ava';
2
+ import { RaftLog } from './RaftLog';
3
+ export declare function runCommonRaftLogTests<T>(t: ExecutionContext, createLog: () => RaftLog<T>): void;