Another update - I've reworked gestalt-entity-system for a pipeline-based transactional framework. How this works:
There is now a class called TransactionManager. It provides the functionality to begin() a transaction, and then commit() or rollback() the transaction. Under the hood it does a little trickery so transactions are thread local, but little else. It also exposes a TransactionContext, which provides any state stored in the transaction. But overall it does nothing on its own. However, TransactionHandlers can be registered against various stages of a transaction - PRE_TRANSACTION, PRE_COMMIT, a variety of COMMIT stages, POST_COMMIT or POST_ROLLBACK, and POST_TRANSACTION.
The entity manager now registers with the TransactionManager. As stages it is interested in are reached during a transaction, the handlers that the entity manager registers are triggered. There is a handler to add entity related state into the TransactionContext, which tracks the entities accessed during the transaction. There is a handler to save changes back to the entity store. And so forth.
The event manager similarly registers with the TransactionManager to add support for transactional events - ensuring asynch events are only sent after a transaction completes successfully.
Overall this framework feels pretty good - it decouples event and entity management nicely. With a little effort it could even support having multiple entity systems using the same transactions... not that this is needed. This will help support the lifecycle events, indexes and other additions that may be desired such as tracking entity changes for autosaving.
I have skipped over some performance optimisation again. In particular, the way entity changes are tracked means every component of every entity accessed is copied - twice - so that a before and current state is available. This definitely could be improved. It does mean that the system can automatically detect when components change nicely though, and the before copies will be useful for the Lifecycle events. Which will probably be the next bit of work.