milpa / event-store
Append-only event-log primitive for the Milpa PHP framework: durable JSONL file store and in-memory store behind a common EventStoreInterface — per-stream replay and store-wide monotonic sequencing.
Requires
- php: >=8.3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.65
- milpa/core: *
- phpstan/phpdoc-parser: ^2.3
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5
This package is auto-updated.
Last update: 2026-08-19 23:39:38 UTC
README
Milpa Event Store
A tiny append-only event log for the Milpa PHP framework, with zero package dependencies. Append events, replay a stream, project state from the fold. Two interchangeable stores — file (JSONL) and in-memory — behind one
EventStoreInterface. The persistence primitive under Milpa's event-sourced process engine.
milpa/event-store is the smallest possible seam onto an append-only log: an Event is an
immutable fact — streamId, type, payload, seq, recordedAt — and a store's only two jobs are
"append durably" and "read a stream back in order". No ORM, no serializer, no framework
coupling — construct a store with a path (or nothing at all) and call append().
recordedAt is a UTC-serialized wall-clock observation made by the process constructing a new
event. It says when that process's clock observed the record, not when the domain event objectively
happened, and it is no more trustworthy than that process and clock. Legacy records replay with
recordedAt === null; the store never invents their time from filenames, file metadata, or payloads.
Install
composer require milpa/event-store
Quick example
use Milpa\EventStore\Event; use Milpa\EventStore\FileEventStore; $store = new FileEventStore('/var/data/orders.jsonl'); // Append: nextSeq() hands out the store-wide monotonic counter, one call per event. $store->append(new Event('order-42', 'OrderPlaced', ['total' => 19.99], $store->nextSeq())); $store->append(new Event('order-42', 'OrderShipped', ['carrier' => 'DHL'], $store->nextSeq())); // Replay: every event for one stream, in ascending seq order — never other streams' events. foreach ($store->replay('order-42') as $event) { printf("#%d %s %s\n", $event->seq, $event->type, json_encode($event->payload)); } // #1 OrderPlaced {"total":19.99} // #2 OrderShipped {"carrier":"DHL"} $store->streams(); // ["order-42"] $store->nextSeq(); // 3 — one past the highest seq in the store, across every stream
Project current state by folding the replayed events yourself — the store never stores state, only the facts it was told:
$state = array_reduce( $store->replay('order-42'), static fn (array $state, Event $event): array => match ($event->type) { 'OrderPlaced' => [...$state, 'status' => 'placed', 'total' => $event->payload['total']], 'OrderShipped' => [...$state, 'status' => 'shipped', 'carrier' => $event->payload['carrier']], default => $state, }, [], ); // ['status' => 'shipped', 'total' => 19.99, 'carrier' => 'DHL']
Two stores, one interface
| Store | Durability | Use it for |
|---|---|---|
FileEventStore |
Appends one JSON line per event to a flat file, under an exclusive flock() so concurrent appenders never interleave partial lines. nextSeq() and replay() both re-derive their answer from the file itself — a fresh instance pointed at the same path, in a different process or a different request, agrees with every other instance about both "what happened" and "what comes next". |
Real persistence — the process engine's durable log. |
InMemoryEventStore |
An in-process array. Nothing is written to disk; nothing survives past the instance's lifetime. | Tests, and zero-file consumers that don't need durability. |
Both implement the same four-method EventStoreInterface (append(), replay(),
nextSeq(), streams()), verified by one shared contract test suite
(EventStoreContractTestCase) so behavior — sequencing, stream isolation, replay order —
never drifts between the two.
Requirements
- PHP ≥ 8.3
- Nothing else —
milpa/event-storehas no package dependencies, Milpa or otherwise
Documentation
Full API reference: getmilpa.github.io/event-store — generated straight from the source DocBlocks and dressed with the Milpa design system.
Contributing
Contributions are welcome — see CONTRIBUTING.md. Please report security issues via SECURITY.md, and note that this project follows a Code of Conduct.
License
Apache-2.0 © Rodrigo Vicente - TeamX Agency.
Milpa is designed, built, and maintained by Rodrigo Vicente - TeamX Agency.