M5: KVEventStorage with dense commit keys, setIfAbsent and mutex modes, head cache, probe-based read
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qxbkp4tA7G5SCWjzWuxmE
This commit is contained in:
@@ -459,6 +459,31 @@ Decisions taken where the brief was silent. Each is the simplest option consiste
|
||||
- **`set` of an indexed child with duplicate keys in one `setMany`**: last write wins, and index
|
||||
diffs are computed once per key.
|
||||
|
||||
### M5 — Events
|
||||
|
||||
- **`EventStorage` is an interface (type), not a class.** The brief's sample says `new EventStorage(kv)`
|
||||
but also says it is the abstract interface; the concrete class is `KVEventStorage(kv, opts?)`, which
|
||||
accepts a `KVStorage` or any `KVNode` as root. Options: `headCacheSize` (default 10 000),
|
||||
`maxRetries` (default 5) and `now` (clock, default `Date.now`).
|
||||
- **Commit value**: `{ firstVersion, at, events }`, with `at` in ms since epoch. Read results are
|
||||
`{ version, commit, at, data }`.
|
||||
- **The root node is not a stream** (consistent with the root having no own KV value). Non-root
|
||||
nodes are both a stream and a container of child streams; `keys()` lists child streams, and a
|
||||
node's `~e` subtree is reserved, so streams and children never mix.
|
||||
- **`version()` always reads storage** (one reverse scan with `limit: 1`) and refreshes the head cache.
|
||||
Only `append` trusts the cache. When a cached head disagrees with a numeric `expectedVersion`,
|
||||
the head is re-read before throwing, so a stale cache never produces a false `ConcurrencyError`.
|
||||
- **`append([])`** writes nothing; it still checks `expectedVersion` against a fresh head and returns it.
|
||||
- **Read arity**: `read()` and `read(opts)` are the self form (the first argument is a non-null
|
||||
object). Any other first argument is a key, so `read(undefined)` throws like every other
|
||||
passed-but-undefined key.
|
||||
- **Locating `from`/`to`**: commit `n` has `firstVersion >= n`, so the commit containing version `v` has
|
||||
`seq <= v`. `read` probes `seq = v` first; with single-event commits that is the answer, in one
|
||||
`getMany`. Otherwise it runs an 8-ary search, one `getMany` of up to 8 probes per round, which is
|
||||
O(log₈ commits) round trips. The head is only read when the guess is past the end.
|
||||
- **Mutex mode** (driver without `setIfAbsent`): a per-stream in-process promise chain serialises
|
||||
appends. It is safe only with a single writer process.
|
||||
|
||||
<!-- decisions appended per milestone -->
|
||||
|
||||
## 13. Benchmark baselines
|
||||
|
||||
Reference in New Issue
Block a user