FileEventStore
Append-only JSONL log of {@see Event}s: one JSON object per line, one line per event, never rewritten or truncated. Zero DB — a flat file is the entire durability story. `nextSeq()` and `replay()` both derive their answer from the file itself rather than from an in-memory counter, on purpose: a fresh `FileEventStore` pointed at the same path — a different process, a different request — must agree with every other instance about both "what happened" and "what comes next", and the file is the only thing every instance shares. An instance does remember where its last read ended and the last stream it replayed, but only to ask the file for what was appended since: every call checks that it is still the same file, no shorter, with the same last line, and reads it again from the start when it is not.
FileEventStore::__construct()
public function __construct(string $path):Parameters
| Name | Type | Description |
|---|---|---|
| $path | string | path to the JSONL log file; its directory is created on first append if missing |
FileEventStore::append()
public function append(Milpa\EventStore\Event $event): voidAppends `$event` as one JSON line, under an exclusive lock so concurrent appenders cannot interleave partial lines.
Parameters
| Name | Type | Description |
|---|---|---|
| $event | Milpa\EventStore\Event |
FileEventStore::replay()
public function replay(string $streamId): arrayAll events belonging to `$streamId`, in ascending `seq` order. The last stream asked for is kept, and every later call asks the FILE what changed since: the same file (device and inode), not shorter, and the last line read still the same bytes. If all three hold, only the lines appended since are read, and the kept stream grows by the ones that are its own; if any fails, the log is read again from the start. A long agent leg asked for its own 21 MB session 28 times and held four decoded copies of it at once, ~26 MB each (greenhouse evidence/1045): one shared read is the difference between fitting in PHP's 128 MB and dying there. The same list is handed to every caller — {@see Event} is immutable, so sharing it is safe.
Parameters
| Name | Type | Description |
|---|---|---|
| $streamId | string |
FileEventStore::first()
public function first(string $streamId, string $type): ?Milpa\EventStore\EventThe first event of `$type` appended to `$streamId`, read from the top of the log a line at a time and no further than that event — never the whole stream (greenhouse evidence/1045 §3: the check that only wanted a session's opener built all 293 MB of it and died there). The remembered read is left as it was: this read stops early, so it knows nothing about where the log ends.
Parameters
| Name | Type | Description |
|---|---|---|
| $streamId | string | |
| $type | string |
FileEventStore::nextSeq()
public function nextSeq(): intThe next `seq` to assign, one past the highest `seq` currently in the store (across every stream) — `1` for an empty or missing log.
FileEventStore::streams()
public function streams(): arrayEvery distinct stream id present in the store, in the order each first appears (ascending `seq` of its first event).
FileEventStore::replayAll()
public function replayAll(): arrayEvery stream replayed in a single pass — see {@see EventStoreInterface::replayAll()}. Reads the file ONCE and buckets by stream, where a `replay()`-per-stream loop would read it once per stream.