Auth-aware nodes
Model the session as a service node, and make every user-scoped node depend on it. The session gates readiness — nothing user-scoped readies without it — and evicting it on logout cascades to the whole user subgraph.
The session node
type SessionResult = { userId: string; token: string };
type SessionSpec = Frond.NodeSpec<{
args: Frond.Args.None;
key: Frond.Key.Singleton;
result: SessionResult;
actions: {
signOut: Frond.ActionContract<void, void>;
};
}>;
export class SessionNode extends Frond.NodeBase<SessionSpec> {
static readonly spec = Frond.serviceSpec<SessionSpec>({
tag: Frond.tag("services/session"),
key: () => Frond.Key.singleton(),
driver: Frond.Driver.Async<SessionSpec>({
acquire: Frond.Driver.Acquire(() => loadSession()),
actions: {
signOut: Frond.Driver.Action(() => clearSession()),
},
}),
});
}
Depend on it
A user-scoped node declares the session as a dependency and reads it through ctx.deps.
dependencies: Frond.dependencies(() => ({
session: Frond.dep(SessionNode, Frond.Args.none),
})),
driver: Frond.Driver.Async<ProfileSpec>({
acquire: Frond.Driver.Acquire((ctx) =>
fetchProfile(ctx.deps.session.result.userId)
),
}),
The profile cannot become ready until the session is ready, and it reads the user id straight from the session node — no prop drilling, no duplicated auth state. See Dependencies.
Tear down on logout
Evicting the session evicts its reverse dependency closure: every user-scoped node that depends on it, directly or transitively.
await runtime.client.node(SessionNode, Frond.Args.none).evict();
// session + profile + every other user-scoped node are removed
The next render requests fresh nodes against a new session. One eviction clears the user’s entire graph — no manual cache invalidation per resource. See Eviction and release.
Next: Effect escape hatches.