@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.
Files changed (212) hide show
  1. package/README.md +49 -0
  2. package/dist/agent/index.d.ts +16 -770
  3. package/dist/agent/index.js +18 -16
  4. package/dist/authed-call/index.d.ts +3 -3
  5. package/dist/authed-call/index.js +22 -36
  6. package/dist/authed-call/index.js.map +1 -1
  7. package/dist/await-terminal/index.d.ts +2 -2
  8. package/dist/await-terminal/index.js +2 -2
  9. package/dist/batch-window/index.d.ts +3 -3
  10. package/dist/batch-window/index.js +3 -3
  11. package/dist/cache/index.d.ts +2 -2
  12. package/dist/cache/index.js +5 -2
  13. package/dist/{chunk-3545BOYL.js → chunk-25LBDCBC.js} +6 -5
  14. package/dist/chunk-25LBDCBC.js.map +1 -0
  15. package/dist/{chunk-IN33Q4VJ.js → chunk-2KNLKQVN.js} +3 -3
  16. package/dist/{chunk-IN33Q4VJ.js.map → chunk-2KNLKQVN.js.map} +1 -1
  17. package/dist/{chunk-MMH7CW5K.js → chunk-BP2R7TPP.js} +216 -157
  18. package/dist/chunk-BP2R7TPP.js.map +1 -0
  19. package/dist/chunk-C6QC273X.js +71 -0
  20. package/dist/chunk-C6QC273X.js.map +1 -0
  21. package/dist/{chunk-7W4LMPUW.js → chunk-CAXMQJJ3.js} +17 -24
  22. package/dist/chunk-CAXMQJJ3.js.map +1 -0
  23. package/dist/{chunk-UV6UWGEO.js → chunk-D736QUGF.js} +6 -5
  24. package/dist/chunk-D736QUGF.js.map +1 -0
  25. package/dist/chunk-EWJP3YYP.js +21 -0
  26. package/dist/chunk-EWJP3YYP.js.map +1 -0
  27. package/dist/{chunk-TGG26XMK.js → chunk-FV3Z5MBE.js} +10 -5
  28. package/dist/chunk-FV3Z5MBE.js.map +1 -0
  29. package/dist/chunk-G5E74IJB.js +8 -0
  30. package/dist/chunk-G5E74IJB.js.map +1 -0
  31. package/dist/{chunk-27N2SPTN.js → chunk-IXBKFQH5.js} +12 -11
  32. package/dist/chunk-IXBKFQH5.js.map +1 -0
  33. package/dist/{chunk-SQTCS44W.js → chunk-JFFVBEDI.js} +3 -3
  34. package/dist/{chunk-SQTCS44W.js.map → chunk-JFFVBEDI.js.map} +1 -1
  35. package/dist/chunk-LYLDCIPH.js +365 -0
  36. package/dist/chunk-LYLDCIPH.js.map +1 -0
  37. package/dist/{chunk-HJO6VNQI.js → chunk-MU6MCIAC.js} +3 -3
  38. package/dist/{chunk-HJO6VNQI.js.map → chunk-MU6MCIAC.js.map} +1 -1
  39. package/dist/{chunk-UYWUVEP6.js → chunk-MUUROGIH.js} +2 -2
  40. package/dist/chunk-MUUROGIH.js.map +1 -0
  41. package/dist/{chunk-EHIX7NYZ.js → chunk-MVY5DAMF.js} +4 -4
  42. package/dist/{chunk-EHIX7NYZ.js.map → chunk-MVY5DAMF.js.map} +1 -1
  43. package/dist/{chunk-LP7VQBRG.js → chunk-NPERUUUE.js} +76 -95
  44. package/dist/chunk-NPERUUUE.js.map +1 -0
  45. package/dist/{chunk-QLA76GIT.js → chunk-PBDYW2TU.js} +68 -9
  46. package/dist/chunk-PBDYW2TU.js.map +1 -0
  47. package/dist/{chunk-5VNWZADP.js → chunk-Q2UM7BXU.js} +18 -7
  48. package/dist/chunk-Q2UM7BXU.js.map +1 -0
  49. package/dist/chunk-RMX4EOQK.js +26 -0
  50. package/dist/chunk-RMX4EOQK.js.map +1 -0
  51. package/dist/{chunk-2YOE5PIS.js → chunk-S2EGRPCM.js} +8 -6
  52. package/dist/chunk-S2EGRPCM.js.map +1 -0
  53. package/dist/{chunk-ON24RKSE.js → chunk-S4O26D7B.js} +7 -14
  54. package/dist/chunk-S4O26D7B.js.map +1 -0
  55. package/dist/{chunk-3XJWC7VW.js → chunk-SK5JGUY7.js} +3 -3
  56. package/dist/{chunk-3XJWC7VW.js.map → chunk-SK5JGUY7.js.map} +1 -1
  57. package/dist/{chunk-5DEQ5I3U.js → chunk-TPGASTFF.js} +8 -10
  58. package/dist/chunk-TPGASTFF.js.map +1 -0
  59. package/dist/{chunk-SC3FAFZN.js → chunk-UF4MZSLT.js} +2 -2
  60. package/dist/chunk-UF4MZSLT.js.map +1 -0
  61. package/dist/{chunk-PS23H6KQ.js → chunk-VBUCHVCV.js} +6 -5
  62. package/dist/chunk-VBUCHVCV.js.map +1 -0
  63. package/dist/{chunk-S3FQN4SL.js → chunk-WGGAMW7Y.js} +191 -178
  64. package/dist/chunk-WGGAMW7Y.js.map +1 -0
  65. package/dist/chunk-ZJ5AOYBH.js +8 -0
  66. package/dist/chunk-ZJ5AOYBH.js.map +1 -0
  67. package/dist/{chunk-LEC4U6CX.js → chunk-ZVJZ72HY.js} +3 -3
  68. package/dist/{chunk-LEC4U6CX.js.map → chunk-ZVJZ72HY.js.map} +1 -1
  69. package/dist/circuit-breaker/index.d.ts +1 -0
  70. package/dist/circuit-breaker/index.js +1 -1
  71. package/dist/{core-BoB6t7dB.d.ts → core-42kapW0n.d.ts} +105 -6
  72. package/dist/deadline/index.d.ts +77 -11
  73. package/dist/deadline/index.js +3 -3
  74. package/dist/devtools/index.js +8 -28
  75. package/dist/devtools/index.js.map +1 -1
  76. package/dist/do/index.d.ts +483 -339
  77. package/dist/do/index.js +244 -214
  78. package/dist/do/index.js.map +1 -1
  79. package/dist/{durable-effects-CO9Uh971.d.ts → durable-effects-CFRngTNA.d.ts} +3 -3
  80. package/dist/extension/index.d.ts +25 -7
  81. package/dist/extension/index.js +4 -3
  82. package/dist/extension/index.js.map +1 -1
  83. package/dist/extension/react.js +4 -3
  84. package/dist/extension/react.js.map +1 -1
  85. package/dist/extension/subs/index.d.ts +2 -2
  86. package/dist/extension/subs/index.js +7 -10
  87. package/dist/extension/subs/index.js.map +1 -1
  88. package/dist/fan-out/index.d.ts +10 -10
  89. package/dist/fan-out/index.js +3 -3
  90. package/dist/idempotent-intake/index.d.ts +2 -2
  91. package/dist/idempotent-intake/index.js +3 -3
  92. package/dist/index.d.ts +50 -472
  93. package/dist/index.js +2 -2
  94. package/dist/llm-call/index.d.ts +3 -2
  95. package/dist/llm-call/index.js +12 -10
  96. package/dist/machine-B_BF4jij.d.ts +804 -0
  97. package/dist/machine-viz/index.d.ts +1 -1
  98. package/dist/machine-viz/index.js +2 -2
  99. package/dist/mem/index.d.ts +3 -2
  100. package/dist/mem/index.js.map +1 -1
  101. package/dist/monitored-run/index.d.ts +54 -30
  102. package/dist/monitored-run/index.js +7 -7
  103. package/dist/node/index.d.ts +3 -2
  104. package/dist/node/index.js +2 -3
  105. package/dist/node/index.js.map +1 -1
  106. package/dist/paginated-walk/index.d.ts +2 -2
  107. package/dist/paginated-walk/index.js +11 -10
  108. package/dist/parity/index.d.ts +6 -5
  109. package/dist/parity/index.js +5 -4
  110. package/dist/parity/index.js.map +1 -1
  111. package/dist/pbt/arbitraries/index.d.ts +1 -1
  112. package/dist/pbt/index.d.ts +1 -1
  113. package/dist/pbt/index.js +3 -3
  114. package/dist/pbt/index.js.map +1 -1
  115. package/dist/pbt/runners/index.d.ts +1 -1
  116. package/dist/pbt/runners/index.js +3 -3
  117. package/dist/poller/index.d.ts +59 -17
  118. package/dist/poller/index.js +56 -20
  119. package/dist/poller/index.js.map +1 -1
  120. package/dist/prediction/index.d.ts +1 -1
  121. package/dist/prediction/index.js +2 -2
  122. package/dist/pure/index.d.ts +1 -1
  123. package/dist/pure/index.js +2 -2
  124. package/dist/rate-limit/index.d.ts +2 -2
  125. package/dist/rate-limit/index.js +1 -1
  126. package/dist/react/index.d.ts +3 -2
  127. package/dist/react/index.js +2 -2
  128. package/dist/react/index.js.map +1 -1
  129. package/dist/reconciler/index.d.ts +2 -2
  130. package/dist/reconciler/index.js +13 -15
  131. package/dist/reconciler/index.js.map +1 -1
  132. package/dist/recorder/index.d.ts +20 -20
  133. package/dist/recorder/index.js +1 -1
  134. package/dist/resilient-call/index.d.ts +31 -6
  135. package/dist/resilient-call/index.js +10 -9
  136. package/dist/retry-backoff/index.d.ts +191 -14
  137. package/dist/retry-backoff/index.js +1 -1
  138. package/dist/retry-to-success/index.js +1 -1
  139. package/dist/runtime-types-gsog27b6.d.ts +337 -0
  140. package/dist/saga/index.d.ts +3 -2
  141. package/dist/saga/index.js +2 -2
  142. package/dist/saga/index.js.map +1 -1
  143. package/dist/snapshot/index.d.ts +1 -1
  144. package/dist/snapshot/index.js +3 -3
  145. package/dist/subs/index.d.ts +372 -4
  146. package/dist/subs/index.js +4 -1
  147. package/dist/testing/index.d.ts +2 -2
  148. package/dist/testing/index.js +2 -2
  149. package/dist/throttled-input/index.d.ts +2 -2
  150. package/dist/throttled-input/index.js +14 -13
  151. package/dist/throttled-input/index.js.map +1 -1
  152. package/dist/token-refresh/index.d.ts +1 -1
  153. package/dist/token-refresh/index.js +3 -3
  154. package/dist/trace-replay/index.d.ts +12 -7
  155. package/dist/trace-replay/index.js +4 -3
  156. package/dist/{types-B-8bcAp8.d.ts → types-KbL3W1mj.d.ts} +1 -1
  157. package/dist/with-deadline/index.d.ts +1 -1
  158. package/dist/with-deadline/index.js +2 -2
  159. package/dist/with-resilience/index.d.ts +21 -2
  160. package/dist/with-resilience/index.js +12 -23
  161. package/dist/with-resilience/index.js.map +1 -1
  162. package/dist/with-telemetry/index.d.ts +1 -1
  163. package/dist/with-telemetry/index.js +2 -2
  164. package/dist/work-queue/adapter.d.ts +2 -2
  165. package/dist/work-queue/adapter.js +2 -2
  166. package/dist/work-queue/index.d.ts +3 -2
  167. package/dist/work-queue/index.js +1 -1
  168. package/dist/work-queue/index.js.map +1 -1
  169. package/dist/work-queue/ops.d.ts +2 -2
  170. package/dist/work-queue/ops.js +1 -1
  171. package/dist/workflow/index.d.ts +51 -19
  172. package/dist/workflow/index.js +71 -52
  173. package/dist/workflow/index.js.map +1 -1
  174. package/package.json +4 -19
  175. package/dist/chunk-27N2SPTN.js.map +0 -1
  176. package/dist/chunk-2YOE5PIS.js.map +0 -1
  177. package/dist/chunk-3545BOYL.js.map +0 -1
  178. package/dist/chunk-5DEQ5I3U.js.map +0 -1
  179. package/dist/chunk-5VNWZADP.js.map +0 -1
  180. package/dist/chunk-6RGBKODV.js +0 -268
  181. package/dist/chunk-6RGBKODV.js.map +0 -1
  182. package/dist/chunk-7W4LMPUW.js.map +0 -1
  183. package/dist/chunk-DILMKOZ7.js +0 -91
  184. package/dist/chunk-DILMKOZ7.js.map +0 -1
  185. package/dist/chunk-K7Q23EQ4.js +0 -20
  186. package/dist/chunk-K7Q23EQ4.js.map +0 -1
  187. package/dist/chunk-LP7VQBRG.js.map +0 -1
  188. package/dist/chunk-MMH7CW5K.js.map +0 -1
  189. package/dist/chunk-NSDFA6M6.js +0 -198
  190. package/dist/chunk-NSDFA6M6.js.map +0 -1
  191. package/dist/chunk-ON24RKSE.js.map +0 -1
  192. package/dist/chunk-PS23H6KQ.js.map +0 -1
  193. package/dist/chunk-QLA76GIT.js.map +0 -1
  194. package/dist/chunk-S3FQN4SL.js.map +0 -1
  195. package/dist/chunk-SC3FAFZN.js.map +0 -1
  196. package/dist/chunk-TGG26XMK.js.map +0 -1
  197. package/dist/chunk-UV6UWGEO.js.map +0 -1
  198. package/dist/chunk-UYWUVEP6.js.map +0 -1
  199. package/dist/chunk-VLGARN6H.js +0 -202
  200. package/dist/chunk-VLGARN6H.js.map +0 -1
  201. package/dist/raft/demo.d.ts +0 -105
  202. package/dist/raft/demo.js +0 -172
  203. package/dist/raft/demo.js.map +0 -1
  204. package/dist/raft/index.d.ts +0 -548
  205. package/dist/raft/index.js +0 -7
  206. package/dist/raft/index.js.map +0 -1
  207. package/dist/raft/sim.d.ts +0 -222
  208. package/dist/raft/sim.js +0 -8
  209. package/dist/raft/sim.js.map +0 -1
  210. package/dist/raft/timeline.d.ts +0 -46
  211. package/dist/raft/timeline.js +0 -62
  212. package/dist/raft/timeline.js.map +0 -1
@@ -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 };
@@ -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
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}