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,368 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DandelionMesh = void 0;
4
+ const CryptoService_1 = require("../crypto/CryptoService");
5
+ const RaftNode_1 = require("../raft/RaftNode");
6
+ const InMemoryRaftLog_1 = require("../raft/log/InMemoryRaftLog");
7
+ /**
8
+ * DandelionMesh — a fault-tolerant P2P mesh network for browser applications.
9
+ *
10
+ * Combines three layers:
11
+ * - **Transport** (default: PeerJS) — handles peer-to-peer WebRTC connections.
12
+ * - **Crypto** — RSA-OAEP + AES-256-GCM hybrid encryption for private messages.
13
+ * - **Raft consensus** — leader election and totally-ordered log replication.
14
+ *
15
+ * All application messages (public broadcasts and encrypted private messages)
16
+ * flow through the Raft log, guaranteeing every peer sees the same events in
17
+ * the same order. Non-leader peers forward proposals to the current leader.
18
+ *
19
+ * ## Events
20
+ *
21
+ * | Event | Payload | When |
22
+ * |------------------|--------------------------------------|---------------------------------------------|
23
+ * | `ready` | `localPeerId: string` | Transport is open and Raft has started |
24
+ * | `message` | `MeshMessage<T>, replay: boolean` | A committed log entry is delivered |
25
+ * | `peersChanged` | `peers: string[]` | A peer connects or disconnects |
26
+ * | `leaderChanged` | `leaderId: string \| null` | Raft leader changes |
27
+ * | `error` | `Error` | Transport-level error |
28
+ *
29
+ * @typeParam T - The application-level payload type for messages.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * const transport = new PeerJSTransport({ peerId: 'alice' });
34
+ * const mesh = new DandelionMesh<GameAction>(transport, {
35
+ * bootstrapPeers: ['bob', 'charlie'],
36
+ * });
37
+ *
38
+ * mesh.on('ready', (id) => console.log('My ID:', id));
39
+ * mesh.on('message', (msg) => {
40
+ * if (msg.type === 'public') console.log(msg.sender, msg.data);
41
+ * if (msg.type === 'private') console.log('secret from', msg.sender);
42
+ * });
43
+ *
44
+ * await mesh.sendPublic({ action: 'bet', amount: 100 });
45
+ * await mesh.sendPrivate('bob', { cards: ['Ah', 'Kd'] });
46
+ * ```
47
+ *
48
+ * @example Providing a pre-generated crypto key bundle
49
+ * ```ts
50
+ * const bundle = await generateKeyBundle(2048);
51
+ * const mesh = new DandelionMesh(transport, { cryptoKeyBundle: bundle });
52
+ * ```
53
+ */
54
+ class DandelionMesh {
55
+ constructor(transport, options) {
56
+ var _a, _b, _c, _d;
57
+ this.raftNode = null;
58
+ this.cryptoBundle = null;
59
+ this.peerPublicKeys = new Map();
60
+ this.pendingKeyWaiters = new Map();
61
+ this.lastAppliedIndex = 0;
62
+ this.listeners = {
63
+ ready: new Set(),
64
+ message: new Set(),
65
+ peersChanged: new Set(),
66
+ leaderChanged: new Set(),
67
+ error: new Set(),
68
+ };
69
+ // --- Transport event handlers ---
70
+ this.onTransportOpen = (peerId) => {
71
+ this.localPeerId = peerId;
72
+ // Connect to bootstrap peers
73
+ for (const bp of this.bootstrapPeers) {
74
+ if (bp !== peerId) {
75
+ this.transport.connect(bp);
76
+ }
77
+ }
78
+ // Initialize Raft node
79
+ this.raftNode = new RaftNode_1.RaftNode(peerId, this.raftLog, this.raftOptions);
80
+ this.raftNode.sendMessage = (toPeerId, message) => {
81
+ this.transport
82
+ .send(toPeerId, this.wireMessage('raft', message))
83
+ .catch(() => {
84
+ // Peer may have disconnected; non-fatal
85
+ });
86
+ };
87
+ this.raftNode.on('committed', this.onRaftCommitted);
88
+ this.raftNode.on('leaderChanged', (leaderId) => {
89
+ this.emit('leaderChanged', leaderId);
90
+ });
91
+ // Start Raft with currently connected peers
92
+ this.raftNode.start(this.transport.connectedPeers);
93
+ this.emit('ready', peerId);
94
+ // Propose our public key through Raft so all peers (including
95
+ // those joining later) receive it via log replay.
96
+ this.proposePublicKey();
97
+ };
98
+ this.onPeerConnected = (_remotePeerId) => {
99
+ var _a;
100
+ // Update Raft cluster membership
101
+ (_a = this.raftNode) === null || _a === void 0 ? void 0 : _a.updatePeers(this.transport.connectedPeers);
102
+ this.emit('peersChanged', this.peers);
103
+ };
104
+ this.onPeerDisconnected = (_remotePeerId) => {
105
+ var _a;
106
+ (_a = this.raftNode) === null || _a === void 0 ? void 0 : _a.updatePeers(this.transport.connectedPeers);
107
+ this.emit('peersChanged', this.peers);
108
+ };
109
+ this.onTransportMessage = (fromPeerId, data) => {
110
+ var _a;
111
+ const wire = data;
112
+ if (!wire || !wire.channel)
113
+ return;
114
+ if (wire.channel === 'raft') {
115
+ (_a = this.raftNode) === null || _a === void 0 ? void 0 : _a.handleMessage(fromPeerId, wire.payload);
116
+ }
117
+ else if (wire.channel === 'control') {
118
+ this.handleControlMessage(wire.payload);
119
+ }
120
+ };
121
+ this.onTransportError = (error) => {
122
+ this.emit('error', error);
123
+ };
124
+ // --- Raft commit handler ---
125
+ this.onRaftCommitted = (entry, index) => {
126
+ const isReplay = index <= this.lastAppliedIndex;
127
+ const cmd = entry.command;
128
+ switch (cmd._meshType) {
129
+ case 'public':
130
+ this.emit('message', {
131
+ type: 'public',
132
+ sender: cmd.sender,
133
+ data: cmd.data,
134
+ }, isReplay);
135
+ break;
136
+ case 'encrypted':
137
+ this.handleEncryptedCommit(cmd, isReplay);
138
+ break;
139
+ case 'publicKey': {
140
+ if (!this.peerPublicKeys.has(cmd.peerId)) {
141
+ const keyPromise = (0, CryptoService_1.importPublicKey)(cmd.jwk);
142
+ this.peerPublicKeys.set(cmd.peerId, keyPromise);
143
+ // Notify any pending sendPrivate calls waiting for this key
144
+ const waiters = this.pendingKeyWaiters.get(cmd.peerId);
145
+ if (waiters) {
146
+ for (const resolve of waiters) {
147
+ resolve(keyPromise);
148
+ }
149
+ this.pendingKeyWaiters.delete(cmd.peerId);
150
+ }
151
+ }
152
+ break;
153
+ }
154
+ }
155
+ if (!isReplay) {
156
+ this.lastAppliedIndex = index;
157
+ }
158
+ };
159
+ this.transport = transport;
160
+ this.modulusLength = (_a = options === null || options === void 0 ? void 0 : options.modulusLength) !== null && _a !== void 0 ? _a : 4096;
161
+ this.raftOptions = (_b = options === null || options === void 0 ? void 0 : options.raft) !== null && _b !== void 0 ? _b : {};
162
+ this.raftLog =
163
+ (_c = options === null || options === void 0 ? void 0 : options.raftLog) !== null && _c !== void 0 ? _c : new InMemoryRaftLog_1.InMemoryRaftLog();
164
+ this.bootstrapPeers = (_d = options === null || options === void 0 ? void 0 : options.bootstrapPeers) !== null && _d !== void 0 ? _d : [];
165
+ this.lastAppliedIndex = this.raftLog.length();
166
+ // Use provided key bundle or generate a new one
167
+ this.cryptoReady = ((options === null || options === void 0 ? void 0 : options.cryptoKeyBundle)
168
+ ? Promise.resolve(options.cryptoKeyBundle)
169
+ : (0, CryptoService_1.generateKeyBundle)(this.modulusLength)).then((bundle) => {
170
+ this.cryptoBundle = bundle;
171
+ return bundle;
172
+ });
173
+ // Wire up transport events
174
+ this.transport.on('open', this.onTransportOpen);
175
+ this.transport.on('peerConnected', this.onPeerConnected);
176
+ this.transport.on('peerDisconnected', this.onPeerDisconnected);
177
+ this.transport.on('message', this.onTransportMessage);
178
+ this.transport.on('error', this.onTransportError);
179
+ }
180
+ // --- Public API ---
181
+ /** Send a public (broadcast) message through the Raft cluster */
182
+ async sendPublic(data) {
183
+ if (!this.raftNode || !this.localPeerId)
184
+ return false;
185
+ const entry = {
186
+ _meshType: 'public',
187
+ sender: this.localPeerId,
188
+ data,
189
+ };
190
+ if (this.raftNode.isLeader()) {
191
+ return this.raftNode.propose(entry);
192
+ }
193
+ // Forward to leader
194
+ const leaderId = this.raftNode.getLeaderId();
195
+ if (!leaderId)
196
+ return false;
197
+ const propose = { _meshType: 'propose', command: entry };
198
+ await this.transport.send(leaderId, this.wireMessage('control', propose));
199
+ return true;
200
+ }
201
+ /** Send an encrypted private message through the Raft cluster */
202
+ async sendPrivate(recipientPeerId, data) {
203
+ if (!this.raftNode || !this.localPeerId)
204
+ return false;
205
+ const recipientKey = await this.getOrAwaitPublicKey(recipientPeerId);
206
+ const plaintext = new TextEncoder().encode(JSON.stringify(data));
207
+ const payload = await (0, CryptoService_1.encrypt)(plaintext, recipientKey);
208
+ const entry = {
209
+ _meshType: 'encrypted',
210
+ sender: this.localPeerId,
211
+ recipient: recipientPeerId,
212
+ payload,
213
+ };
214
+ if (this.raftNode.isLeader()) {
215
+ return this.raftNode.propose(entry);
216
+ }
217
+ // Forward to leader
218
+ const leaderId = this.raftNode.getLeaderId();
219
+ if (!leaderId)
220
+ return false;
221
+ const propose = { _meshType: 'propose', command: entry };
222
+ await this.transport.send(leaderId, this.wireMessage('control', propose));
223
+ return true;
224
+ }
225
+ /** Get all connected peer IDs (including self) */
226
+ get peers() {
227
+ const connected = [...this.transport.connectedPeers];
228
+ if (this.localPeerId)
229
+ connected.unshift(this.localPeerId);
230
+ return connected;
231
+ }
232
+ /** Get the current Raft leader ID */
233
+ get leaderId() {
234
+ var _a, _b;
235
+ return (_b = (_a = this.raftNode) === null || _a === void 0 ? void 0 : _a.getLeaderId()) !== null && _b !== void 0 ? _b : null;
236
+ }
237
+ /** Whether this node is the Raft leader */
238
+ get isLeader() {
239
+ var _a, _b;
240
+ return (_b = (_a = this.raftNode) === null || _a === void 0 ? void 0 : _a.isLeader()) !== null && _b !== void 0 ? _b : false;
241
+ }
242
+ /** The local peer ID (available after 'ready') */
243
+ get peerId() {
244
+ return this.localPeerId;
245
+ }
246
+ /** Register an event listener */
247
+ on(event, listener) {
248
+ this.listeners[event].add(listener);
249
+ }
250
+ /** Remove an event listener */
251
+ off(event, listener) {
252
+ this.listeners[event].delete(listener);
253
+ }
254
+ /** Shut down the mesh: stop Raft, close transport */
255
+ close() {
256
+ var _a;
257
+ (_a = this.raftNode) === null || _a === void 0 ? void 0 : _a.destroy();
258
+ this.transport.off('open', this.onTransportOpen);
259
+ this.transport.off('peerConnected', this.onPeerConnected);
260
+ this.transport.off('peerDisconnected', this.onPeerDisconnected);
261
+ this.transport.off('message', this.onTransportMessage);
262
+ this.transport.off('error', this.onTransportError);
263
+ this.transport.close();
264
+ }
265
+ // --- Control message handling ---
266
+ handleControlMessage(msg) {
267
+ var _a;
268
+ switch (msg._meshType) {
269
+ case 'propose': {
270
+ // A non-leader peer is forwarding a command to us (the leader)
271
+ if ((_a = this.raftNode) === null || _a === void 0 ? void 0 : _a.isLeader()) {
272
+ this.raftNode.propose(msg.command);
273
+ }
274
+ break;
275
+ }
276
+ }
277
+ }
278
+ async handleEncryptedCommit(cmd, isReplay) {
279
+ if (!this.localPeerId)
280
+ return;
281
+ // Only the intended recipient decrypts
282
+ if (cmd.recipient !== this.localPeerId)
283
+ return;
284
+ if (!this.cryptoBundle) {
285
+ await this.cryptoReady;
286
+ }
287
+ try {
288
+ const plaintext = await (0, CryptoService_1.decrypt)(cmd.payload, this.cryptoBundle.privateKey);
289
+ const data = JSON.parse(new TextDecoder().decode(plaintext));
290
+ this.emit('message', {
291
+ type: 'private',
292
+ sender: cmd.sender,
293
+ recipient: cmd.recipient,
294
+ data,
295
+ }, isReplay);
296
+ }
297
+ catch (err) {
298
+ // Not for us or decryption failed — this is expected for other recipients
299
+ }
300
+ }
301
+ // --- Key management ---
302
+ async proposePublicKey() {
303
+ if (!this.cryptoBundle) {
304
+ await this.cryptoReady;
305
+ }
306
+ if (!this.localPeerId || !this.cryptoBundle || !this.raftNode)
307
+ return;
308
+ // Wait until a leader is available
309
+ if (!this.raftNode.isLeader() && !this.raftNode.getLeaderId()) {
310
+ await new Promise((resolve) => {
311
+ const onLeader = (leaderId) => {
312
+ if (leaderId) {
313
+ this.off('leaderChanged', onLeader);
314
+ resolve();
315
+ }
316
+ };
317
+ this.on('leaderChanged', onLeader);
318
+ });
319
+ }
320
+ if (!this.raftNode)
321
+ return;
322
+ const announcement = {
323
+ _meshType: 'publicKey',
324
+ peerId: this.localPeerId,
325
+ jwk: this.cryptoBundle.publicKeyJwk,
326
+ };
327
+ if (this.raftNode.isLeader()) {
328
+ this.raftNode.propose(announcement);
329
+ }
330
+ else {
331
+ const leaderId = this.raftNode.getLeaderId();
332
+ if (!leaderId)
333
+ return;
334
+ const propose = {
335
+ _meshType: 'propose',
336
+ command: announcement,
337
+ };
338
+ this.transport
339
+ .send(leaderId, this.wireMessage('control', propose))
340
+ .catch(() => { });
341
+ }
342
+ }
343
+ async getOrAwaitPublicKey(peerId) {
344
+ const existing = this.peerPublicKeys.get(peerId);
345
+ if (existing)
346
+ return existing;
347
+ // Wait for the key to arrive via Raft commit
348
+ return new Promise((resolve) => {
349
+ let waiters = this.pendingKeyWaiters.get(peerId);
350
+ if (!waiters) {
351
+ waiters = [];
352
+ this.pendingKeyWaiters.set(peerId, waiters);
353
+ }
354
+ waiters.push((keyPromise) => resolve(keyPromise));
355
+ });
356
+ }
357
+ // --- Helpers ---
358
+ wireMessage(channel, payload) {
359
+ return { channel, payload };
360
+ }
361
+ emit(event, ...args) {
362
+ for (const listener of this.listeners[event]) {
363
+ listener(...args);
364
+ }
365
+ }
366
+ }
367
+ exports.DandelionMesh = DandelionMesh;
368
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRGFuZGVsaW9uTWVzaC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9tZXNoL0RhbmRlbGlvbk1lc2gudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsMkRBTWlDO0FBQ2pDLCtDQUE2RDtBQUM3RCxpRUFBOEQ7QUF1QzlEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBOENHO0FBQ0gsTUFBYSxhQUFhO0lBMkJ4QixZQUFZLFNBQW9CLEVBQUUsT0FBOEI7O1FBeEJ4RCxhQUFRLEdBQXVDLElBQUksQ0FBQztRQUlwRCxpQkFBWSxHQUEyQixJQUFJLENBQUM7UUFDbkMsbUJBQWMsR0FBRyxJQUFJLEdBQUcsRUFBOEIsQ0FBQztRQUN2RCxzQkFBaUIsR0FBRyxJQUFJLEdBQUcsRUFHekMsQ0FBQztRQUtJLHFCQUFnQixHQUFHLENBQUMsQ0FBQztRQUVaLGNBQVMsR0FBbUI7WUFDM0MsS0FBSyxFQUFFLElBQUksR0FBRyxFQUFFO1lBQ2hCLE9BQU8sRUFBRSxJQUFJLEdBQUcsRUFBRTtZQUNsQixZQUFZLEVBQUUsSUFBSSxHQUFHLEVBQUU7WUFDdkIsYUFBYSxFQUFFLElBQUksR0FBRyxFQUFFO1lBQ3hCLEtBQUssRUFBRSxJQUFJLEdBQUcsRUFBRTtTQUNqQixDQUFDO1FBb0lGLG1DQUFtQztRQUUzQixvQkFBZSxHQUFHLENBQUMsTUFBYyxFQUFRLEVBQUU7WUFDakQsSUFBSSxDQUFDLFdBQVcsR0FBRyxNQUFNLENBQUM7WUFFMUIsNkJBQTZCO1lBQzdCLEtBQUssTUFBTSxFQUFFLElBQUksSUFBSSxDQUFDLGNBQWMsRUFBRTtnQkFDcEMsSUFBSSxFQUFFLEtBQUssTUFBTSxFQUFFO29CQUNqQixJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0FBQztpQkFDNUI7YUFDRjtZQUVELHVCQUF1QjtZQUN2QixJQUFJLENBQUMsUUFBUSxHQUFHLElBQUksbUJBQVEsQ0FDMUIsTUFBTSxFQUNOLElBQUksQ0FBQyxPQUFPLEVBQ1osSUFBSSxDQUFDLFdBQVcsQ0FDakIsQ0FBQztZQUNGLElBQUksQ0FBQyxRQUFRLENBQUMsV0FBVyxHQUFHLENBQUMsUUFBUSxFQUFFLE9BQU8sRUFBRSxFQUFFO2dCQUNoRCxJQUFJLENBQUMsU0FBUztxQkFDWCxJQUFJLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxXQUFXLENBQUMsTUFBTSxFQUFFLE9BQU8sQ0FBQyxDQUFDO3FCQUNqRCxLQUFLLENBQUMsR0FBRyxFQUFFO29CQUNWLHdDQUF3QztnQkFDMUMsQ0FBQyxDQUFDLENBQUM7WUFDUCxDQUFDLENBQUM7WUFDRixJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxXQUFXLEVBQUUsSUFBSSxDQUFDLGVBQWUsQ0FBQyxDQUFDO1lBQ3BELElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLGVBQWUsRUFBRSxDQUFDLFFBQVEsRUFBRSxFQUFFO2dCQUM3QyxJQUFJLENBQUMsSUFBSSxDQUFDLGVBQWUsRUFBRSxRQUFRLENBQUMsQ0FBQztZQUN2QyxDQUFDLENBQUMsQ0FBQztZQUVILDRDQUE0QztZQUM1QyxJQUFJLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLGNBQTBCLENBQUMsQ0FBQztZQUUvRCxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztZQUUzQiw4REFBOEQ7WUFDOUQsa0RBQWtEO1lBQ2xELElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO1FBQzFCLENBQUMsQ0FBQztRQUVNLG9CQUFlLEdBQUcsQ0FBQyxhQUFxQixFQUFRLEVBQUU7O1lBQ3hELGlDQUFpQztZQUNqQyxNQUFBLElBQUksQ0FBQyxRQUFRLDBDQUFFLFdBQVcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLGNBQTBCLENBQUMsQ0FBQztZQUN0RSxJQUFJLENBQUMsSUFBSSxDQUFDLGNBQWMsRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDeEMsQ0FBQyxDQUFDO1FBRU0sdUJBQWtCLEdBQUcsQ0FBQyxhQUFxQixFQUFRLEVBQUU7O1lBQzNELE1BQUEsSUFBSSxDQUFDLFFBQVEsMENBQUUsV0FBVyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsY0FBMEIsQ0FBQyxDQUFDO1lBQ3RFLElBQUksQ0FBQyxJQUFJLENBQUMsY0FBYyxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN4QyxDQUFDLENBQUM7UUFFTSx1QkFBa0IsR0FBRyxDQUFDLFVBQWtCLEVBQUUsSUFBYSxFQUFRLEVBQUU7O1lBQ3ZFLE1BQU0sSUFBSSxHQUFHLElBQW1CLENBQUM7WUFDakMsSUFBSSxDQUFDLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPO2dCQUFFLE9BQU87WUFFbkMsSUFBSSxJQUFJLENBQUMsT0FBTyxLQUFLLE1BQU0sRUFBRTtnQkFDM0IsTUFBQSxJQUFJLENBQUMsUUFBUSwwQ0FBRSxhQUFhLENBQzFCLFVBQVUsRUFDVixJQUFJLENBQUMsT0FBeUMsQ0FDL0MsQ0FBQzthQUNIO2lCQUFNLElBQUksSUFBSSxDQUFDLE9BQU8sS0FBSyxTQUFTLEVBQUU7Z0JBQ3JDLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxJQUFJLENBQUMsT0FBZ0MsQ0FBQyxDQUFDO2FBQ2xFO1FBQ0gsQ0FBQyxDQUFDO1FBRU0scUJBQWdCLEdBQUcsQ0FBQyxLQUFZLEVBQVEsRUFBRTtZQUNoRCxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsQ0FBQztRQUM1QixDQUFDLENBQUM7UUFnQkYsOEJBQThCO1FBRXRCLG9CQUFlLEdBQUcsQ0FDeEIsS0FBa0MsRUFDbEMsS0FBYSxFQUNQLEVBQUU7WUFDUixNQUFNLFFBQVEsR0FBRyxLQUFLLElBQUksSUFBSSxDQUFDLGdCQUFnQixDQUFDO1lBQ2hELE1BQU0sR0FBRyxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUM7WUFDMUIsUUFBUSxHQUFHLENBQUMsU0FBUyxFQUFFO2dCQUNyQixLQUFLLFFBQVE7b0JBQ1gsSUFBSSxDQUFDLElBQUksQ0FDUCxTQUFTLEVBQ1Q7d0JBQ0UsSUFBSSxFQUFFLFFBQVE7d0JBQ2QsTUFBTSxFQUFFLEdBQUcsQ0FBQyxNQUFNO3dCQUNsQixJQUFJLEVBQUUsR0FBRyxDQUFDLElBQUk7cUJBQ0csRUFDbkIsUUFBUSxDQUNULENBQUM7b0JBQ0YsTUFBTTtnQkFFUixLQUFLLFdBQVc7b0JBQ2QsSUFBSSxDQUFDLHFCQUFxQixDQUFDLEdBQUcsRUFBRSxRQUFRLENBQUMsQ0FBQztvQkFDMUMsTUFBTTtnQkFFUixLQUFLLFdBQVcsQ0FBQyxDQUFDO29CQUNoQixJQUFJLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxFQUFFO3dCQUN4QyxNQUFNLFVBQVUsR0FBRyxJQUFBLCtCQUFlLEVBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDO3dCQUM1QyxJQUFJLENBQUMsY0FBYyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLFVBQVUsQ0FBQyxDQUFDO3dCQUVoRCw0REFBNEQ7d0JBQzVELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDO3dCQUN2RCxJQUFJLE9BQU8sRUFBRTs0QkFDWCxLQUFLLE1BQU0sT0FBTyxJQUFJLE9BQU8sRUFBRTtnQ0FDN0IsT0FBTyxDQUFDLFVBQVUsQ0FBQyxDQUFDOzZCQUNyQjs0QkFDRCxJQUFJLENBQUMsaUJBQWlCLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQzt5QkFDM0M7cUJBQ0Y7b0JBQ0QsTUFBTTtpQkFDUDthQUNGO1lBRUQsSUFBSSxDQUFDLFFBQVEsRUFBRTtnQkFDYixJQUFJLENBQUMsZ0JBQWdCLEdBQUcsS0FBSyxDQUFDO2FBQy9CO1FBQ0gsQ0FBQyxDQUFDO1FBbFFBLElBQUksQ0FBQyxTQUFTLEdBQUcsU0FBUyxDQUFDO1FBQzNCLElBQUksQ0FBQyxhQUFhLEdBQUcsTUFBQSxPQUFPLGFBQVAsT0FBTyx1QkFBUCxPQUFPLENBQUUsYUFBYSxtQ0FBSSxJQUFJLENBQUM7UUFDcEQsSUFBSSxDQUFDLFdBQVcsR0FBRyxNQUFBLE9BQU8sYUFBUCxPQUFPLHVCQUFQLE9BQU8sQ0FBRSxJQUFJLG1DQUFJLEVBQUUsQ0FBQztRQUN2QyxJQUFJLENBQUMsT0FBTztZQUNWLE1BQUMsT0FBTyxhQUFQLE9BQU8sdUJBQVAsT0FBTyxDQUFFLE9BQXNDLG1DQUNoRCxJQUFJLGlDQUFlLEVBQXFCLENBQUM7UUFDM0MsSUFBSSxDQUFDLGNBQWMsR0FBRyxNQUFBLE9BQU8sYUFBUCxPQUFPLHVCQUFQLE9BQU8sQ0FBRSxjQUFjLG1DQUFJLEVBQUUsQ0FBQztRQUNwRCxJQUFJLENBQUMsZ0JBQWdCLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUU5QyxnREFBZ0Q7UUFDaEQsSUFBSSxDQUFDLFdBQVcsR0FBRyxDQUNqQixDQUFBLE9BQU8sYUFBUCxPQUFPLHVCQUFQLE9BQU8sQ0FBRSxlQUFlO1lBQ3RCLENBQUMsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxlQUFlLENBQUM7WUFDMUMsQ0FBQyxDQUFDLElBQUEsaUNBQWlCLEVBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxDQUMxQyxDQUFDLElBQUksQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFO1lBQ2hCLElBQUksQ0FBQyxZQUFZLEdBQUcsTUFBTSxDQUFDO1lBQzNCLE9BQU8sTUFBTSxDQUFDO1FBQ2hCLENBQUMsQ0FBQyxDQUFDO1FBRUgsMkJBQTJCO1FBQzNCLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsZUFBZSxDQUFDLENBQUM7UUFDaEQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUMsZUFBZSxFQUFFLElBQUksQ0FBQyxlQUFlLENBQUMsQ0FBQztRQUN6RCxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxrQkFBa0IsRUFBRSxJQUFJLENBQUMsa0JBQWtCLENBQUMsQ0FBQztRQUMvRCxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxTQUFTLEVBQUUsSUFBSSxDQUFDLGtCQUFrQixDQUFDLENBQUM7UUFDdEQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO0lBQ3BELENBQUM7SUFFRCxxQkFBcUI7SUFFckIsaUVBQWlFO0lBQ2pFLEtBQUssQ0FBQyxVQUFVLENBQUMsSUFBTztRQUN0QixJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsSUFBSSxDQUFDLElBQUksQ0FBQyxXQUFXO1lBQUUsT0FBTyxLQUFLLENBQUM7UUFFdEQsTUFBTSxLQUFLLEdBQTBCO1lBQ25DLFNBQVMsRUFBRSxRQUFRO1lBQ25CLE1BQU0sRUFBRSxJQUFJLENBQUMsV0FBVztZQUN4QixJQUFJO1NBQ0wsQ0FBQztRQUVGLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxRQUFRLEVBQUUsRUFBRTtZQUM1QixPQUFPLElBQUksQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO1NBQ3JDO1FBRUQsb0JBQW9CO1FBQ3BCLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDN0MsSUFBSSxDQUFDLFFBQVE7WUFBRSxPQUFPLEtBQUssQ0FBQztRQUU1QixNQUFNLE9BQU8sR0FBc0IsRUFBRSxTQUFTLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsQ0FBQztRQUM1RSxNQUFNLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxJQUFJLENBQUMsV0FBVyxDQUFDLFNBQVMsRUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDO1FBQzFFLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELGlFQUFpRTtJQUNqRSxLQUFLLENBQUMsV0FBVyxDQUFDLGVBQXVCLEVBQUUsSUFBTztRQUNoRCxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsSUFBSSxDQUFDLElBQUksQ0FBQyxXQUFXO1lBQUUsT0FBTyxLQUFLLENBQUM7UUFFdEQsTUFBTSxZQUFZLEdBQUcsTUFBTSxJQUFJLENBQUMsbUJBQW1CLENBQUMsZUFBZSxDQUFDLENBQUM7UUFDckUsTUFBTSxTQUFTLEdBQUcsSUFBSSxXQUFXLEVBQUUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1FBQ2pFLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBQSx1QkFBTyxFQUFDLFNBQVMsRUFBRSxZQUFZLENBQUMsQ0FBQztRQUV2RCxNQUFNLEtBQUssR0FBNEI7WUFDckMsU0FBUyxFQUFFLFdBQVc7WUFDdEIsTUFBTSxFQUFFLElBQUksQ0FBQyxXQUFXO1lBQ3hCLFNBQVMsRUFBRSxlQUFlO1lBQzFCLE9BQU87U0FDUixDQUFDO1FBRUYsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLFFBQVEsRUFBRSxFQUFFO1lBQzVCLE9BQU8sSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7U0FDckM7UUFFRCxvQkFBb0I7UUFDcEIsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUM3QyxJQUFJLENBQUMsUUFBUTtZQUFFLE9BQU8sS0FBSyxDQUFDO1FBRTVCLE1BQU0sT0FBTyxHQUFzQixFQUFFLFNBQVMsRUFBRSxTQUFTLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxDQUFDO1FBQzVFLE1BQU0sSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxXQUFXLENBQUMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUM7UUFDMUUsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQsa0RBQWtEO0lBQ2xELElBQUksS0FBSztRQUNQLE1BQU0sU0FBUyxHQUFHLENBQUMsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLGNBQWMsQ0FBQyxDQUFDO1FBQ3JELElBQUksSUFBSSxDQUFDLFdBQVc7WUFBRSxTQUFTLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUMxRCxPQUFPLFNBQVMsQ0FBQztJQUNuQixDQUFDO0lBRUQscUNBQXFDO0lBQ3JDLElBQUksUUFBUTs7UUFDVixPQUFPLE1BQUEsTUFBQSxJQUFJLENBQUMsUUFBUSwwQ0FBRSxXQUFXLEVBQUUsbUNBQUksSUFBSSxDQUFDO0lBQzlDLENBQUM7SUFFRCwyQ0FBMkM7SUFDM0MsSUFBSSxRQUFROztRQUNWLE9BQU8sTUFBQSxNQUFBLElBQUksQ0FBQyxRQUFRLDBDQUFFLFFBQVEsRUFBRSxtQ0FBSSxLQUFLLENBQUM7SUFDNUMsQ0FBQztJQUVELGtEQUFrRDtJQUNsRCxJQUFJLE1BQU07UUFDUixPQUFPLElBQUksQ0FBQyxXQUFXLENBQUM7SUFDMUIsQ0FBQztJQUVELGlDQUFpQztJQUNqQyxFQUFFLENBQ0EsS0FBUSxFQUNSLFFBQW1DO1FBRWxDLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFvQyxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUMxRSxDQUFDO0lBRUQsK0JBQStCO0lBQy9CLEdBQUcsQ0FDRCxLQUFRLEVBQ1IsUUFBbUM7UUFFbEMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQW9DLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQzdFLENBQUM7SUFFRCxxREFBcUQ7SUFDckQsS0FBSzs7UUFDSCxNQUFBLElBQUksQ0FBQyxRQUFRLDBDQUFFLE9BQU8sRUFBRSxDQUFDO1FBQ3pCLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsZUFBZSxDQUFDLENBQUM7UUFDakQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsZUFBZSxFQUFFLElBQUksQ0FBQyxlQUFlLENBQUMsQ0FBQztRQUMxRCxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxrQkFBa0IsRUFBRSxJQUFJLENBQUMsa0JBQWtCLENBQUMsQ0FBQztRQUNoRSxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxTQUFTLEVBQUUsSUFBSSxDQUFDLGtCQUFrQixDQUFDLENBQUM7UUFDdkQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO1FBQ25ELElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxFQUFFLENBQUM7SUFDekIsQ0FBQztJQXVFRCxtQ0FBbUM7SUFFM0Isb0JBQW9CLENBQUMsR0FBMEI7O1FBQ3JELFFBQVEsR0FBRyxDQUFDLFNBQVMsRUFBRTtZQUNyQixLQUFLLFNBQVMsQ0FBQyxDQUFDO2dCQUNkLCtEQUErRDtnQkFDL0QsSUFBSSxNQUFBLElBQUksQ0FBQyxRQUFRLDBDQUFFLFFBQVEsRUFBRSxFQUFFO29CQUM3QixJQUFJLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUM7aUJBQ3BDO2dCQUNELE1BQU07YUFDUDtTQUNGO0lBQ0gsQ0FBQztJQWtETyxLQUFLLENBQUMscUJBQXFCLENBQ2pDLEdBQTRCLEVBQzVCLFFBQWlCO1FBRWpCLElBQUksQ0FBQyxJQUFJLENBQUMsV0FBVztZQUFFLE9BQU87UUFFOUIsdUNBQXVDO1FBQ3ZDLElBQUksR0FBRyxDQUFDLFNBQVMsS0FBSyxJQUFJLENBQUMsV0FBVztZQUFFLE9BQU87UUFFL0MsSUFBSSxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUU7WUFDdEIsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFDO1NBQ3hCO1FBQ0QsSUFBSTtZQUNGLE1BQU0sU0FBUyxHQUFHLE1BQU0sSUFBQSx1QkFBTyxFQUM3QixHQUFHLENBQUMsT0FBTyxFQUNYLElBQUksQ0FBQyxZQUFhLENBQUMsVUFBVSxDQUM5QixDQUFDO1lBQ0YsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLFdBQVcsRUFBRSxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBTSxDQUFDO1lBQ2xFLElBQUksQ0FBQyxJQUFJLENBQ1AsU0FBUyxFQUNUO2dCQUNFLElBQUksRUFBRSxTQUFTO2dCQUNmLE1BQU0sRUFBRSxHQUFHLENBQUMsTUFBTTtnQkFDbEIsU0FBUyxFQUFFLEdBQUcsQ0FBQyxTQUFTO2dCQUN4QixJQUFJO2FBQ2EsRUFDbkIsUUFBUSxDQUNULENBQUM7U0FDSDtRQUFDLE9BQU8sR0FBRyxFQUFFO1lBQ1osMEVBQTBFO1NBQzNFO0lBQ0gsQ0FBQztJQUVELHlCQUF5QjtJQUVqQixLQUFLLENBQUMsZ0JBQWdCO1FBQzVCLElBQUksQ0FBQyxJQUFJLENBQUMsWUFBWSxFQUFFO1lBQ3RCLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQztTQUN4QjtRQUNELElBQUksQ0FBQyxJQUFJLENBQUMsV0FBVyxJQUFJLENBQUMsSUFBSSxDQUFDLFlBQVksSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRO1lBQUUsT0FBTztRQUV0RSxtQ0FBbUM7UUFDbkMsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLFdBQVcsRUFBRSxFQUFFO1lBQzdELE1BQU0sSUFBSSxPQUFPLENBQU8sQ0FBQyxPQUFPLEVBQUUsRUFBRTtnQkFDbEMsTUFBTSxRQUFRLEdBQUcsQ0FBQyxRQUF1QixFQUFFLEVBQUU7b0JBQzNDLElBQUksUUFBUSxFQUFFO3dCQUNaLElBQUksQ0FBQyxHQUFHLENBQUMsZUFBZSxFQUFFLFFBQVEsQ0FBQyxDQUFDO3dCQUNwQyxPQUFPLEVBQUUsQ0FBQztxQkFDWDtnQkFDSCxDQUFDLENBQUM7Z0JBQ0YsSUFBSSxDQUFDLEVBQUUsQ0FBQyxlQUFlLEVBQUUsUUFBUSxDQUFDLENBQUM7WUFDckMsQ0FBQyxDQUFDLENBQUM7U0FDSjtRQUVELElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUTtZQUFFLE9BQU87UUFFM0IsTUFBTSxZQUFZLEdBQTBCO1lBQzFDLFNBQVMsRUFBRSxXQUFXO1lBQ3RCLE1BQU0sRUFBRSxJQUFJLENBQUMsV0FBVztZQUN4QixHQUFHLEVBQUUsSUFBSSxDQUFDLFlBQVksQ0FBQyxZQUFZO1NBQ3BDLENBQUM7UUFFRixJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsUUFBUSxFQUFFLEVBQUU7WUFDNUIsSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUM7U0FDckM7YUFBTTtZQUNMLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsV0FBVyxFQUFFLENBQUM7WUFDN0MsSUFBSSxDQUFDLFFBQVE7Z0JBQUUsT0FBTztZQUN0QixNQUFNLE9BQU8sR0FBc0I7Z0JBQ2pDLFNBQVMsRUFBRSxTQUFTO2dCQUNwQixPQUFPLEVBQUUsWUFBWTthQUN0QixDQUFDO1lBQ0YsSUFBSSxDQUFDLFNBQVM7aUJBQ1gsSUFBSSxDQUFDLFFBQVEsRUFBRSxJQUFJLENBQUMsV0FBVyxDQUFDLFNBQVMsRUFBRSxPQUFPLENBQUMsQ0FBQztpQkFDcEQsS0FBSyxDQUFDLEdBQUcsRUFBRSxHQUFFLENBQUMsQ0FBQyxDQUFDO1NBQ3BCO0lBQ0gsQ0FBQztJQUVPLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQyxNQUFjO1FBQzlDLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ2pELElBQUksUUFBUTtZQUFFLE9BQU8sUUFBUSxDQUFDO1FBRTlCLDZDQUE2QztRQUM3QyxPQUFPLElBQUksT0FBTyxDQUFZLENBQUMsT0FBTyxFQUFFLEVBQUU7WUFDeEMsSUFBSSxPQUFPLEdBQUcsSUFBSSxDQUFDLGlCQUFpQixDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUNqRCxJQUFJLENBQUMsT0FBTyxFQUFFO2dCQUNaLE9BQU8sR0FBRyxFQUFFLENBQUM7Z0JBQ2IsSUFBSSxDQUFDLGlCQUFpQixDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLENBQUM7YUFDN0M7WUFDRCxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsVUFBVSxFQUFFLEVBQUUsQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQztRQUNwRCxDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRCxrQkFBa0I7SUFFVixXQUFXLENBQ2pCLE9BQTJCLEVBQzNCLE9BQWdCO1FBRWhCLE9BQU8sRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLENBQUM7SUFDOUIsQ0FBQztJQUVPLElBQUksQ0FDVixLQUFRLEVBQ1IsR0FBRyxJQUEyQztRQUU5QyxLQUFLLE1BQU0sUUFBUSxJQUFJLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLEVBQUU7WUFDM0MsUUFBa0UsQ0FDakUsR0FBRyxJQUFJLENBQ1IsQ0FBQztTQUNIO0lBQ0gsQ0FBQztDQUNGO0FBL1lELHNDQStZQyJ9
@@ -0,0 +1,58 @@
1
+ import { EncryptedPayload } from '../crypto/CryptoService';
2
+ export interface PublicMessage<T = unknown> {
3
+ type: 'public';
4
+ sender: string;
5
+ data: T;
6
+ }
7
+ export interface PrivateMessage<T = unknown> {
8
+ type: 'private';
9
+ sender: string;
10
+ recipient: string;
11
+ data: T;
12
+ }
13
+ export type MeshMessage<T = unknown> = PublicMessage<T> | PrivateMessage<T>;
14
+ /** Broadcast of a peer's RSA public key */
15
+ export interface PublicKeyAnnouncement {
16
+ _meshType: 'publicKey';
17
+ peerId: string;
18
+ jwk: JsonWebKey;
19
+ }
20
+ /** An encrypted private message (the Raft log entry payload for private messages) */
21
+ export interface EncryptedPrivateMessage {
22
+ _meshType: 'encrypted';
23
+ sender: string;
24
+ recipient: string;
25
+ payload: EncryptedPayload;
26
+ }
27
+ /** A public message log entry */
28
+ export interface PublicMessageEntry<T = unknown> {
29
+ _meshType: 'public';
30
+ sender: string;
31
+ data: T;
32
+ }
33
+ /** Union of all Raft log command types */
34
+ export type MeshLogCommand<T = unknown> = PublicMessageEntry<T> | EncryptedPrivateMessage | PublicKeyAnnouncement;
35
+ /** A proposal forwarded from a non-leader to the leader */
36
+ export interface ProposeMessage<T = unknown> {
37
+ _meshType: 'propose';
38
+ command: MeshLogCommand<T>;
39
+ }
40
+ /** Internal transport-level messages (not replicated via Raft) */
41
+ export type MeshControlMessage<T = unknown> = ProposeMessage<T>;
42
+ /** Wrapper to distinguish Raft messages from mesh control messages on the wire */
43
+ export interface WireMessage {
44
+ channel: 'raft' | 'control';
45
+ payload: unknown;
46
+ }
47
+ export interface DandelionMeshEvents<T = unknown> {
48
+ /** Local peer is ready */
49
+ ready: (localPeerId: string) => void;
50
+ /** A message was committed and delivered */
51
+ message: (message: MeshMessage<T>, replay: boolean) => void;
52
+ /** The set of connected peer IDs changed */
53
+ peersChanged: (peers: string[]) => void;
54
+ /** The cluster leader changed */
55
+ leaderChanged: (leaderId: string | null) => void;
56
+ /** An error occurred */
57
+ error: (error: Error) => void;
58
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvbWVzaC90eXBlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0=
@@ -0,0 +1,87 @@
1
+ import { RaftLog } from './log/RaftLog';
2
+ import { RaftMessage, RaftNodeEvents, RaftRole } from './types';
3
+ export interface RaftNodeOptions {
4
+ /** Minimum election timeout in ms (default 2000) */
5
+ electionTimeoutMin?: number;
6
+ /** Maximum election timeout in ms (default 4000) */
7
+ electionTimeoutMax?: number;
8
+ /** Heartbeat interval in ms (default 400) */
9
+ heartbeatInterval?: number;
10
+ }
11
+ /**
12
+ * Core Raft consensus node.
13
+ *
14
+ * This class implements the Raft algorithm for leader election and log
15
+ * replication. It is transport-agnostic — the caller must wire up
16
+ * `sendMessage` to deliver RPCs, and call `handleMessage` when RPCs arrive.
17
+ *
18
+ * Log indices are 1-based per the Raft paper.
19
+ */
20
+ export declare class RaftNode<T = unknown> {
21
+ readonly id: string;
22
+ private role;
23
+ private currentTerm;
24
+ private votedFor;
25
+ private leaderId;
26
+ private commitIndex;
27
+ private lastApplied;
28
+ private nextIndex;
29
+ private matchIndex;
30
+ private electionTimer;
31
+ private heartbeatTimer;
32
+ private readonly electionTimeoutMin;
33
+ private readonly electionTimeoutMax;
34
+ private readonly heartbeatInterval;
35
+ private peers;
36
+ private votesReceived;
37
+ private readonly log;
38
+ /** Called by the node to send an RPC to a peer — must be wired up externally */
39
+ sendMessage: (toPeerId: string, message: RaftMessage<T>) => void;
40
+ private readonly eventListeners;
41
+ private destroyed;
42
+ constructor(id: string, log: RaftLog<T>, options?: RaftNodeOptions);
43
+ /** Start the node (begin election timer as follower) */
44
+ start(peerIds: string[]): void;
45
+ /** Update the cluster membership dynamically (e.g. when peers join/leave) */
46
+ updatePeers(peerIds: string[]): void;
47
+ /** Submit a command to be replicated. Only succeeds on the leader. */
48
+ propose(command: T): boolean;
49
+ /** Handle an incoming Raft RPC message from a peer */
50
+ handleMessage(fromPeerId: string, message: RaftMessage<T>): void;
51
+ /** Get current role */
52
+ getRole(): RaftRole;
53
+ /** Get current term */
54
+ getCurrentTerm(): number;
55
+ /** Get the current leader ID (null if unknown) */
56
+ getLeaderId(): string | null;
57
+ /** Get the commit index */
58
+ getCommitIndex(): number;
59
+ /** Check if this node is the leader */
60
+ isLeader(): boolean;
61
+ /** Register an event listener */
62
+ on<E extends keyof RaftNodeEvents<T>>(event: E, listener: RaftNodeEvents<T>[E]): void;
63
+ /** Remove an event listener */
64
+ off<E extends keyof RaftNodeEvents<T>>(event: E, listener: RaftNodeEvents<T>[E]): void;
65
+ /** Stop all timers and clean up */
66
+ destroy(): void;
67
+ private becomeFollower;
68
+ private becomeCandidate;
69
+ private becomeLeader;
70
+ private handleRequestVote;
71
+ private handleRequestVoteResult;
72
+ /** Check if a candidate's log is at least as up-to-date as ours */
73
+ private isLogUpToDate;
74
+ private handleAppendEntries;
75
+ private handleAppendEntriesResult;
76
+ private sendAppendEntriesToAll;
77
+ private sendAppendEntriesTo;
78
+ /** Advance commitIndex if a majority of matchIndex values allow it */
79
+ private advanceCommitIndex;
80
+ /** Apply all committed but not-yet-applied entries */
81
+ private applyCommitted;
82
+ private resetElectionTimer;
83
+ private clearElectionTimer;
84
+ private clearHeartbeatTimer;
85
+ private persistState;
86
+ private emit;
87
+ }