Events
Each User instance can have a UserEventsListener attached to this.events. The listener is opt-in and dispatches through @mongez/events.
Enabling
class AppUser extends BaseUser { protected cacheDriver = myDriver; protected enableEvents = true; // off by default protected eventsBaseName = "auth"; // defaults to cacheKey}
const user = new AppUser();user.boot();
user.events!.onLogin((userData, u) => { /* … */ });Without enableEvents = true, user.events is undefined and the trigger methods are skipped internally.
The five events
| Event | Listener method | Args | Fires |
|---|---|---|---|
boot | onBoot(cb) | (initData, user) | At the end of boot(). |
login | onLogin(cb) | (userData, user) | At the START of login(), before the data is committed. |
logout | onLogout(cb) | (user) | At the end of logout(). |
change | onChange(cb) | (newData, oldData, user) | At the end of update(). |
keyChange | onKeyChange(cb) | (key, newValue, oldValue, user) | At the end of set() and once per key inside update(). |
All listener methods return an EventSubscription from @mongez/events. Call .unsubscribe() to remove the listener.
Bus topics
Events dispatch on the global @mongez/events bus under ${eventsBaseName || cacheKey}.${eventType}. That means you can subscribe from anywhere without holding the user reference:
import events from "@mongez/events";
events.subscribe("auth.login", (userData, user) => { console.log("Welcome", userData.name);});
events.subscribe("auth.logout", (user) => { // Clear cached queries, reset atoms, redirect, …});The eventsBaseName matters: pick a stable namespace early. Different subclasses can use different namespaces if you have multiple user types in one app.
onLogin fires BEFORE the data is committed
This is the trigger order inside login(userData):
public login(userData: UserInfo): UserInterface { if (this.events) { this.events.triggerLogin(userData, this); // ← fires first } this.update(userData); // ← commits + fires keyChange + change return this;}So inside an onLogin callback, user.get(...) still returns the PREVIOUS values. The callback’s first argument (userData) is the new payload. If you need post-commit values, listen to change instead.
Multiple listeners + unsubscribe
const sub1 = user.events!.onLogin(cbA);const sub2 = user.events!.onLogin(cbB);
// Both run on login. Unsubscribe individually:sub1.unsubscribe();Subscribing without enableEvents
You can subscribe to the topic directly via the events bus even if the user instance doesn’t have events set — but then nobody is dispatching, so the listener will never fire. enableEvents = true is needed for any event flow.
Patterns
Cross-module reaction
class AppUser extends BaseUser { protected cacheDriver = myDriver; protected enableEvents = true; protected eventsBaseName = "auth";}
// elsewhere/queries.tsimport events from "@mongez/events";
events.subscribe("auth.logout", () => { queryCache.invalidateAll();});React without a hook
You can subscribe in useEffect and call setState on the value:
function useUserName(user: AppUser) { const [name, setName] = useState(user.get("name", "")); useEffect(() => { if (!user.events) return; const sub = user.events.onKeyChange((key, next) => { if (key === "name") setName(next); }); return () => sub.unsubscribe(); }, [user]); return name;}The same pattern works for any framework with effect hooks.
Caveats
UserEventsListeneralways dispatches through the singletoneventsbus imported from@mongez/events. There’s no per-instance bus and no way to swap it — everyUserin the process shares the same topic space. Pick distincteventsBaseNamevalues when you have multiple user types so their topics don’t collide (seemongez-user-recipesfor the admin/customer split).