@demlik/tea 0.1.2 → 0.3.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.
- package/README.md +49 -0
- package/dist/agent/index.d.ts +16 -770
- package/dist/agent/index.js +18 -16
- package/dist/authed-call/index.d.ts +3 -3
- package/dist/authed-call/index.js +22 -36
- package/dist/authed-call/index.js.map +1 -1
- package/dist/await-terminal/index.d.ts +2 -2
- package/dist/await-terminal/index.js +2 -2
- package/dist/batch-window/index.d.ts +3 -3
- package/dist/batch-window/index.js +3 -3
- package/dist/cache/index.d.ts +2 -2
- package/dist/cache/index.js +5 -2
- package/dist/{chunk-3545BOYL.js → chunk-25LBDCBC.js} +6 -5
- package/dist/chunk-25LBDCBC.js.map +1 -0
- package/dist/{chunk-IN33Q4VJ.js → chunk-2KNLKQVN.js} +3 -3
- package/dist/{chunk-IN33Q4VJ.js.map → chunk-2KNLKQVN.js.map} +1 -1
- package/dist/{chunk-MMH7CW5K.js → chunk-BP2R7TPP.js} +216 -157
- package/dist/chunk-BP2R7TPP.js.map +1 -0
- package/dist/chunk-C6QC273X.js +71 -0
- package/dist/chunk-C6QC273X.js.map +1 -0
- package/dist/{chunk-7W4LMPUW.js → chunk-CAXMQJJ3.js} +17 -24
- package/dist/chunk-CAXMQJJ3.js.map +1 -0
- package/dist/{chunk-UV6UWGEO.js → chunk-D736QUGF.js} +6 -5
- package/dist/chunk-D736QUGF.js.map +1 -0
- package/dist/chunk-EWJP3YYP.js +21 -0
- package/dist/chunk-EWJP3YYP.js.map +1 -0
- package/dist/{chunk-TGG26XMK.js → chunk-FV3Z5MBE.js} +10 -5
- package/dist/chunk-FV3Z5MBE.js.map +1 -0
- package/dist/chunk-G5E74IJB.js +8 -0
- package/dist/chunk-G5E74IJB.js.map +1 -0
- package/dist/{chunk-27N2SPTN.js → chunk-IXBKFQH5.js} +12 -11
- package/dist/chunk-IXBKFQH5.js.map +1 -0
- package/dist/{chunk-SQTCS44W.js → chunk-JFFVBEDI.js} +3 -3
- package/dist/{chunk-SQTCS44W.js.map → chunk-JFFVBEDI.js.map} +1 -1
- package/dist/chunk-LYLDCIPH.js +365 -0
- package/dist/chunk-LYLDCIPH.js.map +1 -0
- package/dist/{chunk-HJO6VNQI.js → chunk-MU6MCIAC.js} +3 -3
- package/dist/{chunk-HJO6VNQI.js.map → chunk-MU6MCIAC.js.map} +1 -1
- package/dist/{chunk-UYWUVEP6.js → chunk-MUUROGIH.js} +2 -2
- package/dist/chunk-MUUROGIH.js.map +1 -0
- package/dist/{chunk-EHIX7NYZ.js → chunk-MVY5DAMF.js} +4 -4
- package/dist/{chunk-EHIX7NYZ.js.map → chunk-MVY5DAMF.js.map} +1 -1
- package/dist/{chunk-LP7VQBRG.js → chunk-NPERUUUE.js} +76 -95
- package/dist/chunk-NPERUUUE.js.map +1 -0
- package/dist/{chunk-QLA76GIT.js → chunk-PBDYW2TU.js} +68 -9
- package/dist/chunk-PBDYW2TU.js.map +1 -0
- package/dist/{chunk-5VNWZADP.js → chunk-Q2UM7BXU.js} +18 -7
- package/dist/chunk-Q2UM7BXU.js.map +1 -0
- package/dist/chunk-RMX4EOQK.js +26 -0
- package/dist/chunk-RMX4EOQK.js.map +1 -0
- package/dist/{chunk-2YOE5PIS.js → chunk-S2EGRPCM.js} +8 -6
- package/dist/chunk-S2EGRPCM.js.map +1 -0
- package/dist/{chunk-ON24RKSE.js → chunk-S4O26D7B.js} +7 -14
- package/dist/chunk-S4O26D7B.js.map +1 -0
- package/dist/{chunk-3XJWC7VW.js → chunk-SK5JGUY7.js} +3 -3
- package/dist/{chunk-3XJWC7VW.js.map → chunk-SK5JGUY7.js.map} +1 -1
- package/dist/{chunk-5DEQ5I3U.js → chunk-TPGASTFF.js} +8 -10
- package/dist/chunk-TPGASTFF.js.map +1 -0
- package/dist/{chunk-SC3FAFZN.js → chunk-UF4MZSLT.js} +2 -2
- package/dist/chunk-UF4MZSLT.js.map +1 -0
- package/dist/{chunk-PS23H6KQ.js → chunk-VBUCHVCV.js} +6 -5
- package/dist/chunk-VBUCHVCV.js.map +1 -0
- package/dist/{chunk-S3FQN4SL.js → chunk-WGGAMW7Y.js} +191 -178
- package/dist/chunk-WGGAMW7Y.js.map +1 -0
- package/dist/chunk-ZJ5AOYBH.js +8 -0
- package/dist/chunk-ZJ5AOYBH.js.map +1 -0
- package/dist/{chunk-LEC4U6CX.js → chunk-ZVJZ72HY.js} +3 -3
- package/dist/{chunk-LEC4U6CX.js.map → chunk-ZVJZ72HY.js.map} +1 -1
- package/dist/circuit-breaker/index.d.ts +1 -0
- package/dist/circuit-breaker/index.js +1 -1
- package/dist/{core-BoB6t7dB.d.ts → core-42kapW0n.d.ts} +105 -6
- package/dist/deadline/index.d.ts +77 -11
- package/dist/deadline/index.js +3 -3
- package/dist/devtools/index.js +8 -28
- package/dist/devtools/index.js.map +1 -1
- package/dist/do/index.d.ts +483 -339
- package/dist/do/index.js +244 -214
- package/dist/do/index.js.map +1 -1
- package/dist/{durable-effects-CO9Uh971.d.ts → durable-effects-CFRngTNA.d.ts} +3 -3
- package/dist/extension/index.d.ts +25 -7
- package/dist/extension/index.js +4 -3
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/react.js +4 -3
- package/dist/extension/react.js.map +1 -1
- package/dist/extension/subs/index.d.ts +2 -2
- package/dist/extension/subs/index.js +7 -10
- package/dist/extension/subs/index.js.map +1 -1
- package/dist/fan-out/index.d.ts +10 -10
- package/dist/fan-out/index.js +3 -3
- package/dist/idempotent-intake/index.d.ts +2 -2
- package/dist/idempotent-intake/index.js +3 -3
- package/dist/index.d.ts +50 -472
- package/dist/index.js +2 -2
- package/dist/llm-call/index.d.ts +3 -2
- package/dist/llm-call/index.js +12 -10
- package/dist/machine-B_BF4jij.d.ts +804 -0
- package/dist/machine-viz/index.d.ts +1 -1
- package/dist/machine-viz/index.js +2 -2
- package/dist/mem/index.d.ts +3 -2
- package/dist/mem/index.js.map +1 -1
- package/dist/monitored-run/index.d.ts +54 -30
- package/dist/monitored-run/index.js +7 -7
- package/dist/node/index.d.ts +3 -2
- package/dist/node/index.js +2 -3
- package/dist/node/index.js.map +1 -1
- package/dist/paginated-walk/index.d.ts +2 -2
- package/dist/paginated-walk/index.js +11 -10
- package/dist/parity/index.d.ts +6 -5
- package/dist/parity/index.js +5 -4
- package/dist/parity/index.js.map +1 -1
- package/dist/pbt/arbitraries/index.d.ts +1 -1
- package/dist/pbt/index.d.ts +1 -1
- package/dist/pbt/index.js +3 -3
- package/dist/pbt/index.js.map +1 -1
- package/dist/pbt/runners/index.d.ts +1 -1
- package/dist/pbt/runners/index.js +3 -3
- package/dist/poller/index.d.ts +59 -17
- package/dist/poller/index.js +56 -20
- package/dist/poller/index.js.map +1 -1
- package/dist/prediction/index.d.ts +1 -1
- package/dist/prediction/index.js +2 -2
- package/dist/pure/index.d.ts +1 -1
- package/dist/pure/index.js +2 -2
- package/dist/rate-limit/index.d.ts +2 -2
- package/dist/rate-limit/index.js +1 -1
- package/dist/react/index.d.ts +3 -2
- package/dist/react/index.js +2 -2
- package/dist/react/index.js.map +1 -1
- package/dist/reconciler/index.d.ts +2 -2
- package/dist/reconciler/index.js +13 -15
- package/dist/reconciler/index.js.map +1 -1
- package/dist/recorder/index.d.ts +20 -20
- package/dist/recorder/index.js +1 -1
- package/dist/resilient-call/index.d.ts +31 -6
- package/dist/resilient-call/index.js +10 -9
- package/dist/retry-backoff/index.d.ts +191 -14
- package/dist/retry-backoff/index.js +1 -1
- package/dist/retry-to-success/index.js +1 -1
- package/dist/runtime-types-gsog27b6.d.ts +337 -0
- package/dist/saga/index.d.ts +3 -2
- package/dist/saga/index.js +2 -2
- package/dist/saga/index.js.map +1 -1
- package/dist/snapshot/index.d.ts +1 -1
- package/dist/snapshot/index.js +3 -3
- package/dist/subs/index.d.ts +372 -4
- package/dist/subs/index.js +4 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/throttled-input/index.d.ts +2 -2
- package/dist/throttled-input/index.js +14 -13
- package/dist/throttled-input/index.js.map +1 -1
- package/dist/token-refresh/index.d.ts +1 -1
- package/dist/token-refresh/index.js +3 -3
- package/dist/trace-replay/index.d.ts +12 -7
- package/dist/trace-replay/index.js +4 -3
- package/dist/{types-B-8bcAp8.d.ts → types-KbL3W1mj.d.ts} +1 -1
- package/dist/with-deadline/index.d.ts +1 -1
- package/dist/with-deadline/index.js +2 -2
- package/dist/with-resilience/index.d.ts +21 -2
- package/dist/with-resilience/index.js +12 -23
- package/dist/with-resilience/index.js.map +1 -1
- package/dist/with-telemetry/index.d.ts +1 -1
- package/dist/with-telemetry/index.js +2 -2
- package/dist/work-queue/adapter.d.ts +2 -2
- package/dist/work-queue/adapter.js +2 -2
- package/dist/work-queue/index.d.ts +3 -2
- package/dist/work-queue/index.js +1 -1
- package/dist/work-queue/index.js.map +1 -1
- package/dist/work-queue/ops.d.ts +2 -2
- package/dist/work-queue/ops.js +1 -1
- package/dist/workflow/index.d.ts +51 -19
- package/dist/workflow/index.js +71 -52
- package/dist/workflow/index.js.map +1 -1
- package/package.json +4 -19
- package/dist/chunk-27N2SPTN.js.map +0 -1
- package/dist/chunk-2YOE5PIS.js.map +0 -1
- package/dist/chunk-3545BOYL.js.map +0 -1
- package/dist/chunk-5DEQ5I3U.js.map +0 -1
- package/dist/chunk-5VNWZADP.js.map +0 -1
- package/dist/chunk-6RGBKODV.js +0 -268
- package/dist/chunk-6RGBKODV.js.map +0 -1
- package/dist/chunk-7W4LMPUW.js.map +0 -1
- package/dist/chunk-DILMKOZ7.js +0 -91
- package/dist/chunk-DILMKOZ7.js.map +0 -1
- package/dist/chunk-K7Q23EQ4.js +0 -20
- package/dist/chunk-K7Q23EQ4.js.map +0 -1
- package/dist/chunk-LP7VQBRG.js.map +0 -1
- package/dist/chunk-MMH7CW5K.js.map +0 -1
- package/dist/chunk-NSDFA6M6.js +0 -198
- package/dist/chunk-NSDFA6M6.js.map +0 -1
- package/dist/chunk-ON24RKSE.js.map +0 -1
- package/dist/chunk-PS23H6KQ.js.map +0 -1
- package/dist/chunk-QLA76GIT.js.map +0 -1
- package/dist/chunk-S3FQN4SL.js.map +0 -1
- package/dist/chunk-SC3FAFZN.js.map +0 -1
- package/dist/chunk-TGG26XMK.js.map +0 -1
- package/dist/chunk-UV6UWGEO.js.map +0 -1
- package/dist/chunk-UYWUVEP6.js.map +0 -1
- package/dist/chunk-VLGARN6H.js +0 -202
- package/dist/chunk-VLGARN6H.js.map +0 -1
- package/dist/raft/demo.d.ts +0 -105
- package/dist/raft/demo.js +0 -172
- package/dist/raft/demo.js.map +0 -1
- package/dist/raft/index.d.ts +0 -548
- package/dist/raft/index.js +0 -7
- package/dist/raft/index.js.map +0 -1
- package/dist/raft/sim.d.ts +0 -222
- package/dist/raft/sim.js +0 -8
- package/dist/raft/sim.js.map +0 -1
- package/dist/raft/timeline.d.ts +0 -46
- package/dist/raft/timeline.js +0 -62
- package/dist/raft/timeline.js.map +0 -1
package/dist/raft/index.d.ts
DELETED
|
@@ -1,548 +0,0 @@
|
|
|
1
|
-
import { DeadlineSub } from '../deadline/index.js';
|
|
2
|
-
import { C as Cmd } from '../core-BoB6t7dB.js';
|
|
3
|
-
export { S as Sub, s as subId } from '../core-BoB6t7dB.js';
|
|
4
|
-
import '../types-B-8bcAp8.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @demlik/tea/raft — Raft (Figure 2) as a pure TEA reducer. Slices #119 (the
|
|
8
|
-
* election half) + #120 (the log-replication half) of the consensus epic
|
|
9
|
-
* (#117): the role state machine (`follower → candidate → leader`), election +
|
|
10
|
-
* heartbeat timers as `DeadlineSub`s, `RequestVote` request/reply handling, and
|
|
11
|
-
* — added in #120 — the real replicated log with the Figure-2 `AppendEntries`
|
|
12
|
-
* consistency check, leader `nextIndex`/`matchIndex` bookkeeping, and
|
|
13
|
-
* `commitIndex` advancement under the Figure-8 current-term safety rule.
|
|
14
|
-
* Persistence/cold-wake (#122) and the safety-property suite + multi-node
|
|
15
|
-
* harness (#121/#123) are LATER siblings — out of scope here.
|
|
16
|
-
*
|
|
17
|
-
* The Raft paper ("In Search of an Understandable Consensus Algorithm",
|
|
18
|
-
* raft.github.io) maps onto TEA almost 1:1, and this module is the proof of
|
|
19
|
-
* that mapping:
|
|
20
|
-
*
|
|
21
|
-
* - **Reducer** — the Raft transition function over `RaftState`. PURE: it
|
|
22
|
-
* reads no ambient clock and no ambient RNG. Wall time arrives as the `at`
|
|
23
|
-
* parameter every verb takes (the firing Msg carries it, same discipline
|
|
24
|
-
* as `../poller`); election-timeout jitter comes from the `rng` injected
|
|
25
|
-
* ONCE at `createRaftNode` (default `Math.random` at the effect boundary, a
|
|
26
|
-
* fixed value in tests). No verb body names the global RNG or `Date.now`,
|
|
27
|
-
* so a node driven by a recorded `(timer × message)` schedule re-decides
|
|
28
|
-
* bit-for-bit — the headline replay-testing payoff of the epic.
|
|
29
|
-
* - **Sub** — the election-timeout and heartbeat timers are `DeadlineSub`s
|
|
30
|
-
* (`../deadline`, the same shape `../poller` / the agent layer arm). A
|
|
31
|
-
* fired timer is a `Msg` the consumer routes back into a verb; the verb
|
|
32
|
-
* never owns a `setTimeout`.
|
|
33
|
-
* - **Cmd** — the RPCs (`RequestVote` request + reply, `AppendEntries`
|
|
34
|
-
* request + reply) are `Cmd`s routed through the consumer's `interpret`
|
|
35
|
-
* over an injected transport port. This module emits the Cmd literals; it
|
|
36
|
-
* performs NO networking. Tests assert against the emitted Cmds and fake
|
|
37
|
-
* the transport.
|
|
38
|
-
*
|
|
39
|
-
* Invalid states are unrepresentable: the leader-only volatile state
|
|
40
|
-
* (`nextIndex` / `matchIndex`) lives ONLY on the `leader` variant of the role
|
|
41
|
-
* union, so a follower or candidate cannot carry — or be asked for — replication
|
|
42
|
-
* bookkeeping it has no business holding (#119 acceptance criterion).
|
|
43
|
-
*
|
|
44
|
-
* **Persistence seam (#120 → #122).** The replicated log *is* the event log
|
|
45
|
-
* (epic Approach). The verbs here keep `log` a pure value computed by folding —
|
|
46
|
-
* a client command or a follower-side `AppendEntries` append/truncate is a
|
|
47
|
-
* deterministic function of `(state, msg)`, with no clock/RNG and no mutation.
|
|
48
|
-
* That is exactly the port the event-sourced store under `../do`
|
|
49
|
-
* (`doEventSourcedStore`) folds over: its `replay(machine, { loaded, msgs })`
|
|
50
|
-
* re-runs this same reducer to rebuild `log` byte-identically. #120 therefore
|
|
51
|
-
* routes the log through the pure-reducer seam and defers the *effectful* host
|
|
52
|
-
* wiring (async persist-before-respond, snapshot retention, cold-wake replay)
|
|
53
|
-
* to #122 — see {@link RaftState.log} and {@link RaftNode.onClientCommand}.
|
|
54
|
-
*
|
|
55
|
-
* Generic over the command payload `C` (`unknown` by default): a `LogEntry`
|
|
56
|
-
* carries an opaque `command` the consumer's state machine interprets on
|
|
57
|
-
* commit. Raft itself never inspects it.
|
|
58
|
-
*
|
|
59
|
-
* NOT a substrate primitive: it depends only on sibling subpaths (`../deadline`)
|
|
60
|
-
* and the core `Cmd` / `Sub` types. Consumers reach it via the
|
|
61
|
-
* `@demlik/tea/raft` subpath.
|
|
62
|
-
*/
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* A Raft node identity. Opaque string; the cluster's `peers` set lists every
|
|
66
|
-
* OTHER node (self excluded). Branded-by-convention only — kept a plain string
|
|
67
|
-
* so transport adapters can use it as an address key without unwrapping.
|
|
68
|
-
*/
|
|
69
|
-
type NodeId = string;
|
|
70
|
-
/**
|
|
71
|
-
* A Raft term — a monotonically increasing logical clock. Starts at 0 (no
|
|
72
|
-
* leader elected yet) and bumps by one each time a node starts an election.
|
|
73
|
-
*/
|
|
74
|
-
type Term = number;
|
|
75
|
-
/**
|
|
76
|
-
* The election-timeout window, in milliseconds. Raft randomizes each node's
|
|
77
|
-
* timeout uniformly in `[minMs, maxMs]` so split votes resolve quickly — the
|
|
78
|
-
* single source of liveness in leader election. The concrete duration is drawn
|
|
79
|
-
* from the injected `rng` (never `Math.random` ambiently), so a fixed `rng`
|
|
80
|
-
* pins the timeout and the run replays identically.
|
|
81
|
-
*/
|
|
82
|
-
interface ElectionTimeout {
|
|
83
|
-
/** Lower bound of the randomized election timeout, inclusive. */
|
|
84
|
-
readonly minMs: number;
|
|
85
|
-
/** Upper bound of the randomized election timeout, inclusive. */
|
|
86
|
-
readonly maxMs: number;
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Static configuration for one Raft node, injected at construction. Pure data:
|
|
90
|
-
* no node reads its own id or peer set from any ambient source.
|
|
91
|
-
*/
|
|
92
|
-
interface RaftConfig {
|
|
93
|
-
/** This node's identity. */
|
|
94
|
-
readonly self: NodeId;
|
|
95
|
-
/**
|
|
96
|
-
* Every OTHER node in the cluster (self excluded). Cluster size is
|
|
97
|
-
* `peers.length + 1`; a majority is `floor((peers.length + 1) / 2) + 1`.
|
|
98
|
-
*/
|
|
99
|
-
readonly peers: readonly NodeId[];
|
|
100
|
-
/** The randomized election-timeout window (see {@link ElectionTimeout}). */
|
|
101
|
-
readonly electionTimeout: ElectionTimeout;
|
|
102
|
-
/**
|
|
103
|
-
* The leader's heartbeat interval, in milliseconds. A leader re-arms an empty
|
|
104
|
-
* `AppendEntries` to every peer this often; it MUST be comfortably below
|
|
105
|
-
* `electionTimeout.minMs` so followers never time out under a live leader.
|
|
106
|
-
*/
|
|
107
|
-
readonly heartbeatMs: number;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* One entry in the replicated log (Figure 2). Generic over the command payload
|
|
111
|
-
* `C` (`unknown` by default): Raft itself never inspects `command` — it
|
|
112
|
-
* replicates and orders the opaque blob, and the consumer's state machine
|
|
113
|
-
* applies it once the entry commits.
|
|
114
|
-
*
|
|
115
|
-
* `index` is 1-based (Raft convention: index 0 means "before the log"; the
|
|
116
|
-
* first real entry is index 1), so an empty log has `lastLogIndex` 0. It is
|
|
117
|
-
* stored on the entry rather than derived from array position so the
|
|
118
|
-
* consistency check and the leader's per-peer bookkeeping read it directly,
|
|
119
|
-
* and so a persisted/replayed log carries its own indices.
|
|
120
|
-
*/
|
|
121
|
-
interface LogEntry<C = unknown> {
|
|
122
|
-
/** The term in which the leader created this entry. */
|
|
123
|
-
readonly term: Term;
|
|
124
|
-
/** This entry's 1-based position in the log. */
|
|
125
|
-
readonly index: number;
|
|
126
|
-
/** The opaque command this entry replicates; applied by the consumer on commit. */
|
|
127
|
-
readonly command: C;
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* The role discriminated union. `_tag` is the discriminant (engine convention).
|
|
131
|
-
*
|
|
132
|
-
* The crux of "make invalid states unrepresentable": the leader-only volatile
|
|
133
|
-
* state — `nextIndex` / `matchIndex`, the per-peer replication bookkeeping —
|
|
134
|
-
* exists ONLY on the `leader` variant. A follower or candidate value has no
|
|
135
|
-
* such field to read or mis-set; the type makes "a follower with a matchIndex
|
|
136
|
-
* map" unspellable rather than merely discouraged (#119 acceptance criterion).
|
|
137
|
-
*
|
|
138
|
-
* `candidate` carries the per-election vote tally (`votesGranted`, including
|
|
139
|
-
* self) so the reducer can decide majority without re-deriving it.
|
|
140
|
-
*/
|
|
141
|
-
type Role = {
|
|
142
|
-
readonly _tag: "follower";
|
|
143
|
-
} | {
|
|
144
|
-
readonly _tag: "candidate";
|
|
145
|
-
/**
|
|
146
|
-
* The set of nodes (self + peers) that have granted this candidate a vote
|
|
147
|
-
* THIS term. A `Set`-by-id semantic kept as a readonly array of unique
|
|
148
|
-
* ids; the reducer dedups on insert so a duplicate reply never
|
|
149
|
-
* double-counts toward the majority.
|
|
150
|
-
*/
|
|
151
|
-
readonly votesGranted: readonly NodeId[];
|
|
152
|
-
} | {
|
|
153
|
-
readonly _tag: "leader";
|
|
154
|
-
/**
|
|
155
|
-
* For each peer, the index of the next log entry to send (Figure 2,
|
|
156
|
-
* leader volatile state). Initialized to the leader's last-log index + 1
|
|
157
|
-
* on election; #120 advances it on `AppendEntries`. Keyed by `NodeId`.
|
|
158
|
-
*/
|
|
159
|
-
readonly nextIndex: Readonly<Record<NodeId, number>>;
|
|
160
|
-
/**
|
|
161
|
-
* For each peer, the highest log index known to be replicated (Figure 2,
|
|
162
|
-
* leader volatile state). Initialized to 0; #120 advances it. Keyed by
|
|
163
|
-
* `NodeId`.
|
|
164
|
-
*/
|
|
165
|
-
readonly matchIndex: Readonly<Record<NodeId, number>>;
|
|
166
|
-
};
|
|
167
|
-
/**
|
|
168
|
-
* The whole state of one Raft node, generic over the command payload `C`.
|
|
169
|
-
* Persistent state (`currentTerm`, `votedFor`, `log`) survives crashes in a
|
|
170
|
-
* real deployment (#122); volatile state (`commitIndex`, the leader's
|
|
171
|
-
* `nextIndex`/`matchIndex`) is rebuilt on restart.
|
|
172
|
-
*/
|
|
173
|
-
interface RaftState<C = unknown> {
|
|
174
|
-
/**
|
|
175
|
-
* Latest term this node has seen (Figure 2, persistent). Init 0; increases
|
|
176
|
-
* monotonically — every higher-term message observed bumps it (and steps the
|
|
177
|
-
* node down to follower).
|
|
178
|
-
*/
|
|
179
|
-
readonly currentTerm: Term;
|
|
180
|
-
/**
|
|
181
|
-
* The candidate this node voted for in `currentTerm`, or `null` if it has not
|
|
182
|
-
* voted this term (Figure 2, persistent). Reset to `null` whenever
|
|
183
|
-
* `currentTerm` advances.
|
|
184
|
-
*/
|
|
185
|
-
readonly votedFor: NodeId | null;
|
|
186
|
-
/**
|
|
187
|
-
* The replicated log (Figure 2, persistent). 1-based: `log[i]` has
|
|
188
|
-
* `index === i + 1`; `lastLogIndex` is `log.length`. The log *is* the event
|
|
189
|
-
* log (epic Approach): every mutation here — a leader appending a client
|
|
190
|
-
* command, a follower truncating-and-appending on `AppendEntries` — is a pure
|
|
191
|
-
* function of `(state, msg)`, so the event-sourced store under `../do` rebuilds
|
|
192
|
-
* it by folding this same reducer over the recorded Msgs (#122 wires the host;
|
|
193
|
-
* see the module header's persistence-seam note).
|
|
194
|
-
*/
|
|
195
|
-
readonly log: readonly LogEntry<C>[];
|
|
196
|
-
/**
|
|
197
|
-
* Highest log index known to be committed (Figure 2, volatile). Init 0. A
|
|
198
|
-
* follower advances it to `min(leaderCommit, lastNewEntryIndex)` on a matching
|
|
199
|
-
* `AppendEntries`; a leader advances it under the Figure-8 current-term
|
|
200
|
-
* majority rule (see {@link RaftNode.onAppendEntriesReply}). Entries up to and
|
|
201
|
-
* including `commitIndex` are durably agreed and safe for the consumer to apply.
|
|
202
|
-
*/
|
|
203
|
-
readonly commitIndex: number;
|
|
204
|
-
/** The current role + its role-specific state (see {@link Role}). */
|
|
205
|
-
readonly role: Role;
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Tagged wire shapes the consumer unions into its machine's Msg type and routes
|
|
209
|
-
* into the matching verb. `_tag` is the discriminant. Each carries the sender's
|
|
210
|
-
* `term` (Figure 2: every RPC carries the sender's `currentTerm`) so the
|
|
211
|
-
* "rules for all servers" higher-term step-down applies uniformly.
|
|
212
|
-
*/
|
|
213
|
-
/** Inbound `RequestVote` RPC — a candidate asking this node for its vote. */
|
|
214
|
-
interface RequestVoteRequest {
|
|
215
|
-
readonly _tag: "request_vote_request";
|
|
216
|
-
/** The candidate's term. */
|
|
217
|
-
readonly term: Term;
|
|
218
|
-
/** The candidate requesting the vote. */
|
|
219
|
-
readonly candidateId: NodeId;
|
|
220
|
-
/**
|
|
221
|
-
* Index of the candidate's last log entry (Figure 2). Read by the up-to-date
|
|
222
|
-
* check — STUBBED in #119 (see {@link logIsUpToDate}); real comparison in #120.
|
|
223
|
-
*/
|
|
224
|
-
readonly lastLogIndex: number;
|
|
225
|
-
/** Term of the candidate's last log entry (Figure 2). Stub-read in #119. */
|
|
226
|
-
readonly lastLogTerm: Term;
|
|
227
|
-
}
|
|
228
|
-
/** Inbound `RequestVote` reply — a peer answering this node's vote request. */
|
|
229
|
-
interface RequestVoteReply {
|
|
230
|
-
readonly _tag: "request_vote_reply";
|
|
231
|
-
/** The replier's `currentTerm`, for the candidate to update itself. */
|
|
232
|
-
readonly term: Term;
|
|
233
|
-
/** The peer that replied (so the candidate can dedup its tally). */
|
|
234
|
-
readonly from: NodeId;
|
|
235
|
-
/** Whether the peer granted its vote. */
|
|
236
|
-
readonly voteGranted: boolean;
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Inbound `AppendEntries` RPC (Figure 2). An empty `entries` array is a
|
|
240
|
-
* heartbeat; a non-empty one replicates log entries. The `prevLog*` pair anchors
|
|
241
|
-
* the consistency check, and `leaderCommit` carries the leader's commit index so
|
|
242
|
-
* the follower can advance its own.
|
|
243
|
-
*/
|
|
244
|
-
interface AppendEntriesRequest<C = unknown> {
|
|
245
|
-
readonly _tag: "append_entries_request";
|
|
246
|
-
/** The leader's term. */
|
|
247
|
-
readonly term: Term;
|
|
248
|
-
/** The leader sending this (so a follower can track the leader). */
|
|
249
|
-
readonly leaderId: NodeId;
|
|
250
|
-
/**
|
|
251
|
-
* Index of the log entry immediately preceding `entries` (0 if `entries`
|
|
252
|
-
* starts at the head of the log). The follower must already hold an entry at
|
|
253
|
-
* this index whose term is `prevLogTerm`, or it rejects (consistency check).
|
|
254
|
-
*/
|
|
255
|
-
readonly prevLogIndex: number;
|
|
256
|
-
/** Term of the entry at `prevLogIndex` (0 when `prevLogIndex` is 0). */
|
|
257
|
-
readonly prevLogTerm: Term;
|
|
258
|
-
/** The new entries to store (empty for a heartbeat). Already index-stamped. */
|
|
259
|
-
readonly entries: readonly LogEntry<C>[];
|
|
260
|
-
/** The leader's `commitIndex`, so the follower can advance its own. */
|
|
261
|
-
readonly leaderCommit: number;
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Inbound `AppendEntries` reply — a follower answering this leader's
|
|
265
|
-
* replication RPC (Figure 2 reply shape). `matchIndex` is the index of the last
|
|
266
|
-
* entry the follower now agrees on (0 if none), used by the leader to advance
|
|
267
|
-
* `matchIndex`/`nextIndex` on success without re-deriving it.
|
|
268
|
-
*/
|
|
269
|
-
interface AppendEntriesReply {
|
|
270
|
-
readonly _tag: "append_entries_reply";
|
|
271
|
-
/** The replier's `currentTerm`, for the leader to update itself / step down. */
|
|
272
|
-
readonly term: Term;
|
|
273
|
-
/** The follower that replied (so the leader can update its per-peer bookkeeping). */
|
|
274
|
-
readonly from: NodeId;
|
|
275
|
-
/** Whether the consistency check passed and the entries were stored. */
|
|
276
|
-
readonly success: boolean;
|
|
277
|
-
/**
|
|
278
|
-
* On success, the index of the follower's last entry covered by this RPC
|
|
279
|
-
* (`prevLogIndex + entries.length`) — the leader's new `matchIndex` for this
|
|
280
|
-
* peer. Meaningless (0) on rejection.
|
|
281
|
-
*/
|
|
282
|
-
readonly matchIndex: number;
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* A client command submitted to this node (Figure 2 §5.3: clients send commands
|
|
286
|
-
* to the leader). The leader appends it to its log and replicates; a non-leader
|
|
287
|
-
* rejects it (the consumer would redirect the client to the known leader).
|
|
288
|
-
*/
|
|
289
|
-
interface ClientCommand<C = unknown> {
|
|
290
|
-
readonly _tag: "client_command";
|
|
291
|
-
/** The opaque command to replicate (the consumer's state-machine input). */
|
|
292
|
-
readonly command: C;
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* The two fired-timer Msgs, dispatched by the `DeadlineSub`s this module arms.
|
|
296
|
-
* The consumer maps a `deadline_exceeded` whose id matches the election /
|
|
297
|
-
* heartbeat sub id onto these (`toRaftMsg` does the mapping), then routes them
|
|
298
|
-
* into `onElectionTimeout` / `onHeartbeat`. Each carries the firing `at` (the
|
|
299
|
-
* deadline's `atMs`) so the verb re-arms the next absolute target without
|
|
300
|
-
* reading the clock.
|
|
301
|
-
*/
|
|
302
|
-
interface ElectionTimeoutFired {
|
|
303
|
-
readonly _tag: "election_timeout_fired";
|
|
304
|
-
/** Absolute wall-clock ms the timer fired at (the deadline's target). */
|
|
305
|
-
readonly at: number;
|
|
306
|
-
}
|
|
307
|
-
interface HeartbeatFired {
|
|
308
|
-
readonly _tag: "heartbeat_fired";
|
|
309
|
-
/** Absolute wall-clock ms the timer fired at (the deadline's target). */
|
|
310
|
-
readonly at: number;
|
|
311
|
-
}
|
|
312
|
-
/** The full inbound-Msg union a Raft node's `update` handles. */
|
|
313
|
-
type RaftMsg<C = unknown> = RequestVoteRequest | RequestVoteReply | AppendEntriesRequest<C> | AppendEntriesReply | ClientCommand<C> | ElectionTimeoutFired | HeartbeatFired;
|
|
314
|
-
/**
|
|
315
|
-
* Outbound RPCs as `Cmd`s. The consumer's `interpret` performs each over the
|
|
316
|
-
* injected transport port (a fetch binding, an in-memory bus in tests); this
|
|
317
|
-
* module only emits the literals. `to` addresses a peer; a reply addresses the
|
|
318
|
-
* original caller.
|
|
319
|
-
*/
|
|
320
|
-
/** Send a `RequestVote` request to one peer (fanned out, one Cmd per peer). */
|
|
321
|
-
interface SendRequestVote extends Cmd<"raft:send_request_vote"> {
|
|
322
|
-
/** The peer to ask. */
|
|
323
|
-
readonly to: NodeId;
|
|
324
|
-
/** This candidate's term. */
|
|
325
|
-
readonly term: Term;
|
|
326
|
-
/** This candidate's id. */
|
|
327
|
-
readonly candidateId: NodeId;
|
|
328
|
-
/** This candidate's last-log index (stub payload until #120). */
|
|
329
|
-
readonly lastLogIndex: number;
|
|
330
|
-
/** This candidate's last-log term (stub payload until #120). */
|
|
331
|
-
readonly lastLogTerm: Term;
|
|
332
|
-
}
|
|
333
|
-
/** Reply to a `RequestVote` request the original candidate awaits. */
|
|
334
|
-
interface SendRequestVoteReply extends Cmd<"raft:send_request_vote_reply"> {
|
|
335
|
-
/** The candidate that asked. */
|
|
336
|
-
readonly to: NodeId;
|
|
337
|
-
/** This node's `currentTerm`. */
|
|
338
|
-
readonly term: Term;
|
|
339
|
-
/** Whether this node granted its vote. */
|
|
340
|
-
readonly voteGranted: boolean;
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Send an `AppendEntries` to one peer. Empty `entries` is a heartbeat; a
|
|
344
|
-
* non-empty one replicates from the leader's `nextIndex` for that peer. Carries
|
|
345
|
-
* the full Figure-2 payload so the follower can run its consistency check.
|
|
346
|
-
*/
|
|
347
|
-
interface SendAppendEntries<C = unknown> extends Cmd<"raft:send_append_entries"> {
|
|
348
|
-
/** The peer to send to. */
|
|
349
|
-
readonly to: NodeId;
|
|
350
|
-
/** The leader's term. */
|
|
351
|
-
readonly term: Term;
|
|
352
|
-
/** The leader's id. */
|
|
353
|
-
readonly leaderId: NodeId;
|
|
354
|
-
/** Index of the entry preceding `entries` (0 at the head of the log). */
|
|
355
|
-
readonly prevLogIndex: number;
|
|
356
|
-
/** Term of the entry at `prevLogIndex` (0 when `prevLogIndex` is 0). */
|
|
357
|
-
readonly prevLogTerm: Term;
|
|
358
|
-
/** The entries to replicate (empty for a heartbeat). */
|
|
359
|
-
readonly entries: readonly LogEntry<C>[];
|
|
360
|
-
/** The leader's `commitIndex`. */
|
|
361
|
-
readonly leaderCommit: number;
|
|
362
|
-
}
|
|
363
|
-
/** Reply to an `AppendEntries` the original leader awaits. */
|
|
364
|
-
interface SendAppendEntriesReply extends Cmd<"raft:send_append_entries_reply"> {
|
|
365
|
-
/** The leader that asked. */
|
|
366
|
-
readonly to: NodeId;
|
|
367
|
-
/** This node's `currentTerm`. */
|
|
368
|
-
readonly term: Term;
|
|
369
|
-
/** Whether the consistency check passed and the entries were stored. */
|
|
370
|
-
readonly success: boolean;
|
|
371
|
-
/** On success, this node's new last-agreed index (the leader's `matchIndex`). */
|
|
372
|
-
readonly matchIndex: number;
|
|
373
|
-
}
|
|
374
|
-
/** The full outbound-Cmd union a Raft node emits. */
|
|
375
|
-
type RaftCmd<C = unknown> = SendRequestVote | SendRequestVoteReply | SendAppendEntries<C> | SendAppendEntriesReply;
|
|
376
|
-
/** Stable sub id for this node's election-timeout deadline. */
|
|
377
|
-
declare function electionSubId(self: NodeId): string;
|
|
378
|
-
/** Stable sub id for this node's heartbeat deadline. */
|
|
379
|
-
declare function heartbeatSubId(self: NodeId): string;
|
|
380
|
-
/**
|
|
381
|
-
* Map a fired `deadline_exceeded` (from `../deadline`'s subscribe cell) onto the
|
|
382
|
-
* Raft timer Msg it represents, by matching the sub id. Returns `null` if the
|
|
383
|
-
* id belongs to neither Raft timer — so a consumer wiring several deadline
|
|
384
|
-
* families can route ours without a brittle string-prefix check at the call
|
|
385
|
-
* site. The deadline's `atMs` becomes the Msg's `at` (no clock read).
|
|
386
|
-
*/
|
|
387
|
-
declare function toRaftMsg(self: NodeId, fired: {
|
|
388
|
-
readonly id: string;
|
|
389
|
-
readonly atMs: number;
|
|
390
|
-
}): ElectionTimeoutFired | HeartbeatFired | null;
|
|
391
|
-
/**
|
|
392
|
-
* Result tuple every verb returns: the next state and the Cmds to emit. Matches
|
|
393
|
-
* the `(state, cmds)` shape the substrate's reducer cells return, so a consumer
|
|
394
|
-
* threads these straight through their `update`.
|
|
395
|
-
*/
|
|
396
|
-
type RaftStep<C = unknown> = readonly [
|
|
397
|
-
RaftState<C>,
|
|
398
|
-
readonly RaftCmd<C>[]
|
|
399
|
-
];
|
|
400
|
-
/**
|
|
401
|
-
* The node handle returned by {@link createRaftNode}. Spread its hooks into a
|
|
402
|
-
* machine: `init()` seeds a fresh follower, the verbs (`onElectionTimeout`,
|
|
403
|
-
* `onRequestVote`, `onRequestVoteReply`, `onAppendEntries`, `onHeartbeat`) are
|
|
404
|
-
* the reducer cells, and `subs(state, now)` returns the live `DeadlineSub`s.
|
|
405
|
-
*
|
|
406
|
-
* Every verb is PURE given the injected `rng`: no clock read (time arrives as
|
|
407
|
-
* `at`), no ambient randomness, no mutation, no throw.
|
|
408
|
-
*/
|
|
409
|
-
interface RaftNode<C = unknown> {
|
|
410
|
-
/** Seed a fresh node: term 0, no vote, empty log, follower. */
|
|
411
|
-
init(): RaftState<C>;
|
|
412
|
-
/**
|
|
413
|
-
* The election timeout fired without a heartbeat / granted vote. Convert
|
|
414
|
-
* follower OR candidate → candidate and start a NEW election: bump the term,
|
|
415
|
-
* vote for self, reset the tally to `{self}`, and emit `RequestVote` to every
|
|
416
|
-
* peer (carrying the real last-log index/term). A leader ignores its own
|
|
417
|
-
* election timeout (it has a live mandate).
|
|
418
|
-
*
|
|
419
|
-
* `at` is the firing wall-clock (carried by the Msg) — used only so the
|
|
420
|
-
* caller can re-arm the next election deadline at `at + <jittered timeout>`
|
|
421
|
-
* via `subs`; the verb itself reads no clock.
|
|
422
|
-
*
|
|
423
|
-
* A single-node cluster (`peers` empty) wins the election immediately:
|
|
424
|
-
* one vote (self) is already a majority, so the node converts straight to
|
|
425
|
-
* leader and emits the first heartbeat round (empty here — no peers).
|
|
426
|
-
*/
|
|
427
|
-
onElectionTimeout(state: RaftState<C>, at: number): RaftStep<C>;
|
|
428
|
-
/**
|
|
429
|
-
* Handle an inbound `RequestVote` request (Figure 2 receiver rule).
|
|
430
|
-
*
|
|
431
|
-
* - Rules for all servers: if `req.term > currentTerm`, adopt the term, clear
|
|
432
|
-
* `votedFor`, and step down to follower BEFORE evaluating the grant.
|
|
433
|
-
* - Reply `false` if `req.term < currentTerm` (stale candidate).
|
|
434
|
-
* - Otherwise grant iff (`votedFor` is null OR already this candidate) AND the
|
|
435
|
-
* candidate's log is at least as up-to-date ({@link logIsUpToDate} — the
|
|
436
|
-
* real last-log term/index comparison, #120). Granting sets
|
|
437
|
-
* `votedFor = candidateId`.
|
|
438
|
-
*
|
|
439
|
-
* Granting a vote (or stepping down) counts as "heard from a valid leader /
|
|
440
|
-
* candidate", so the consumer re-arms the election timer off the resulting
|
|
441
|
-
* state (`subs`).
|
|
442
|
-
*/
|
|
443
|
-
onRequestVote(state: RaftState<C>, req: RequestVoteRequest): RaftStep<C>;
|
|
444
|
-
/**
|
|
445
|
-
* Handle an inbound `RequestVote` reply (the candidate tallying votes).
|
|
446
|
-
*
|
|
447
|
-
* - Rules for all servers: a higher-term reply steps the node down to follower
|
|
448
|
-
* (it lost the election implicitly).
|
|
449
|
-
* - A stale-term reply (`reply.term < currentTerm`) is dropped.
|
|
450
|
-
* - A grant from a peer not yet counted adds to the tally; on reaching a
|
|
451
|
-
* majority the candidate converts to leader (initializing `nextIndex` /
|
|
452
|
-
* `matchIndex`) and emits the first `AppendEntries` round to every peer.
|
|
453
|
-
* - A reply that arrives when the node is no longer a candidate (already
|
|
454
|
-
* leader / stepped down) is a no-op.
|
|
455
|
-
*/
|
|
456
|
-
onRequestVoteReply(state: RaftState<C>, reply: RequestVoteReply): RaftStep<C>;
|
|
457
|
-
/**
|
|
458
|
-
* Handle an inbound `AppendEntries` — heartbeat or replication (Figure 2
|
|
459
|
-
* receiver rules). Always emits an `AppendEntries` reply Cmd unless the term
|
|
460
|
-
* is stale-and-dropped without a state change.
|
|
461
|
-
*
|
|
462
|
-
* - Reply-side rule: `req.term < currentTerm` is rejected — a stale leader;
|
|
463
|
-
* the node holds its role and replies `success: false`.
|
|
464
|
-
* - `req.term >= currentTerm`: a valid leader for this term. Adopt the term if
|
|
465
|
-
* higher and revert candidate → follower (a candidate that hears from a
|
|
466
|
-
* leader of term ≥ its own steps down). Then run the **consistency check**:
|
|
467
|
-
* if the follower's log has no entry at `prevLogIndex` with term
|
|
468
|
-
* `prevLogTerm`, reply `success: false` (the leader backs off `nextIndex`
|
|
469
|
-
* and retries). On a match, delete any conflicting suffix and append the new
|
|
470
|
-
* `entries`, then advance `commitIndex` to
|
|
471
|
-
* `min(leaderCommit, lastNewEntryIndex)`, and reply `success: true` with the
|
|
472
|
-
* follower's new last-agreed index as `matchIndex`.
|
|
473
|
-
*
|
|
474
|
-
* Hearing a valid `AppendEntries` resets the election timer (the consumer
|
|
475
|
-
* re-arms off the resulting state).
|
|
476
|
-
*/
|
|
477
|
-
onAppendEntries(state: RaftState<C>, req: AppendEntriesRequest<C>): RaftStep<C>;
|
|
478
|
-
/**
|
|
479
|
-
* Handle an inbound `AppendEntries` reply (the leader updating its per-peer
|
|
480
|
-
* bookkeeping, Figure 2 leader rules).
|
|
481
|
-
*
|
|
482
|
-
* - Rules for all servers: a higher-term reply steps the leader down to
|
|
483
|
-
* follower.
|
|
484
|
-
* - A reply received when no longer leader, or a stale-term reply, is a no-op.
|
|
485
|
-
* - On `success`: set `matchIndex[from]` to the reply's `matchIndex` and
|
|
486
|
-
* `nextIndex[from]` to `matchIndex + 1`, then advance `commitIndex` under the
|
|
487
|
-
* **Figure-8 safety rule** — to the highest N such that N is on a majority of
|
|
488
|
-
* `matchIndex`es AND `log[N].term === currentTerm` (a leader NEVER commits a
|
|
489
|
-
* prior-term entry by counting replicas; it commits it only transitively once
|
|
490
|
-
* a current-term entry above it commits).
|
|
491
|
-
* - On rejection: decrement `nextIndex[from]` (floored at 1) and re-emit an
|
|
492
|
-
* `AppendEntries` to that peer from the backed-off index (the retry).
|
|
493
|
-
*/
|
|
494
|
-
onAppendEntriesReply(state: RaftState<C>, reply: AppendEntriesReply): RaftStep<C>;
|
|
495
|
-
/**
|
|
496
|
-
* A client submitted a command. On the leader: append a new entry
|
|
497
|
-
* `{ term: currentTerm, index: lastLogIndex + 1, command }` to the log and
|
|
498
|
-
* emit an `AppendEntries` round replicating it to every peer. On a non-leader:
|
|
499
|
-
* a no-op (no append, no Cmds) — the consumer redirects the client to the
|
|
500
|
-
* leader. This is the entry point that grows the replicated/event log.
|
|
501
|
-
*/
|
|
502
|
-
onClientCommand(state: RaftState<C>, msg: ClientCommand<C>): RaftStep<C>;
|
|
503
|
-
/**
|
|
504
|
-
* The leader's heartbeat timer fired: emit an `AppendEntries` to every peer
|
|
505
|
-
* (entries beyond that peer's `nextIndex`, empty when the peer is caught up).
|
|
506
|
-
* A no-op (no Cmds) on a non-leader — a stale heartbeat fire racing a
|
|
507
|
-
* step-down the reconcile pass has not yet retired.
|
|
508
|
-
*/
|
|
509
|
-
onHeartbeat(state: RaftState<C>, at: number): RaftStep<C>;
|
|
510
|
-
/**
|
|
511
|
-
* The live `DeadlineSub`s for `state`, computed against the current clock
|
|
512
|
-
* `now` (the ONLY clock read — at subscribe time, never in a verb; same seam
|
|
513
|
-
* as `../poller`'s `subs`). The election-timeout target is `now + <jittered
|
|
514
|
-
* timeout>` drawn from the injected `rng`; the heartbeat target is
|
|
515
|
-
* `now + heartbeatMs`.
|
|
516
|
-
*
|
|
517
|
-
* - follower / candidate → arm ONLY the election timeout.
|
|
518
|
-
* - leader → arm ONLY the heartbeat (a leader does not time itself out).
|
|
519
|
-
*/
|
|
520
|
-
subs(state: RaftState<C>, now: number): readonly DeadlineSub[];
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
|
-
* The candidate "log at least as up-to-date" check (Figure 2 receiver rule,
|
|
524
|
-
* §5.4.1 election restriction). Compares the candidate's last-log
|
|
525
|
-
* (term, index) against ours: term dominates, index breaks a tie.
|
|
526
|
-
*
|
|
527
|
-
* Up-to-date iff the candidate's last-log term is GREATER than ours, OR the
|
|
528
|
-
* terms are EQUAL and the candidate's last-log index is ≥ ours. (Our own
|
|
529
|
-
* last-log term/index are read from the tail of `log`; an empty log is
|
|
530
|
-
* (term 0, index 0), so any candidate is at least as up-to-date as an empty
|
|
531
|
-
* log.) This is the property that keeps a leader's log a superset of every
|
|
532
|
-
* committed entry — a node missing a committed entry can never gather a
|
|
533
|
-
* majority, so it cannot win.
|
|
534
|
-
*/
|
|
535
|
-
declare function logIsUpToDate(log: readonly LogEntry[], candidateLastLogIndex: number, candidateLastLogTerm: Term): boolean;
|
|
536
|
-
/**
|
|
537
|
-
* Build a Raft node from `config`. Returns the hook bag to spread into a
|
|
538
|
-
* machine. Pure factory — no clock read, no timer; everything happens when the
|
|
539
|
-
* consumer calls the verbs / `subs` with an injected `at` / `now`.
|
|
540
|
-
*
|
|
541
|
-
* `rng` is injected ONCE here (default `Math.random` at the effect boundary, a
|
|
542
|
-
* fixed value in tests) and read ONLY by `subs` to jitter the election timeout.
|
|
543
|
-
* No verb body names the global RNG, so a node driven by a recorded schedule
|
|
544
|
-
* replays bit-for-bit (the epic's headline payoff).
|
|
545
|
-
*/
|
|
546
|
-
declare function createRaftNode<C = unknown>(config: RaftConfig, rng?: () => number): RaftNode<C>;
|
|
547
|
-
|
|
548
|
-
export { type AppendEntriesReply, type AppendEntriesRequest, type ClientCommand, type ElectionTimeout, type ElectionTimeoutFired, type HeartbeatFired, type LogEntry, type NodeId, type RaftCmd, type RaftConfig, type RaftMsg, type RaftNode, type RaftState, type RaftStep, type RequestVoteReply, type RequestVoteRequest, type Role, type SendAppendEntries, type SendAppendEntriesReply, type SendRequestVote, type SendRequestVoteReply, type Term, createRaftNode, electionSubId, heartbeatSubId, logIsUpToDate, toRaftMsg };
|
package/dist/raft/index.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export { createRaftNode, electionSubId, heartbeatSubId, logIsUpToDate, toRaftMsg } from '../chunk-6RGBKODV.js';
|
|
2
|
-
import '../chunk-K7Q23EQ4.js';
|
|
3
|
-
import '../chunk-2YFLOCWX.js';
|
|
4
|
-
import '../chunk-MMH7CW5K.js';
|
|
5
|
-
export { subId } from '../chunk-QLA76GIT.js';
|
|
6
|
-
//# sourceMappingURL=index.js.map
|
|
7
|
-
//# sourceMappingURL=index.js.map
|
package/dist/raft/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|