⚡ VBWD
A sales platform for the digital world — SaaS subscriptions, CMS, shop, booking and a token economy on one self-hosted backend, two Vue front-ends and one plugin contract.
Plugins don't import each other. They listen.
The event bus is the platform's spinal cord. Every meaningful state change emits a typed domain event; plugins subscribe to what they care about. Email confirmations, analytics, Mailchimp sync, dunning, GHRM access grants — none are wired into the services that triggered them.
Named events shipped
created · activated · cancelled · expired · dunning
authorized · captured · failed · refunded · refund.reversed
checkout.initiated · completed
password_reset.request · execute · login.failed
plugin.registered · initialized · enabled · disabled
chat.tokens.consumed, booking.created, order.fulfilled, lead.captured.
Why the bus is the extension point
- Decoupling — services emit; subscribers proliferate. No service grows tentacles into other plugins.
- Pluggable side-effects — add an audit listener, a Slack notifier, a metrics exporter without touching the emitter.
- Replayability — events are recorded; "what should have happened after this capture" is a query, not archaeology.
- Testability — assert "event X emitted with payload Y" instead of mocking N collaborators.
How a plugin plugs into the bus
A plugin registers handlers via
register_event_handlers(bus) and emits its own
events for others to consume — cross-plugin coordination with
zero direct imports. The same handler signature keeps working
when the bus moves to a durable queue.
Async path. Synchronous today (in-process dispatch); async [planned] via a durable queue — a wiring change, not a plugin rewrite.