Skip to content
docsv0.4.0

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

Parameters of __construct()
NameTypeDescription
$pathstringpath to the JSONL log file; its directory is created on first append if missing

FileEventStore::append()

public function append(Milpa\EventStore\Event $event): void

Appends `$event` as one JSON line, under an exclusive lock so concurrent appenders cannot interleave partial lines.

Parameters

Parameters of append()
NameTypeDescription
$eventMilpa\EventStore\Event

FileEventStore::replay()

public function replay(string $streamId): array

All 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

Parameters of replay()
NameTypeDescription
$streamIdstring

FileEventStore::first()

public function first(string $streamId, string $type): ?Milpa\EventStore\Event

The 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

Parameters of first()
NameTypeDescription
$streamIdstring
$typestring

FileEventStore::nextSeq()

public function nextSeq(): int

The 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(): array

Every distinct stream id present in the store, in the order each first appears (ascending `seq` of its first event).

FileEventStore::replayAll()

public function replayAll(): array

Every 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.