v0 · Developer Preview Frond is under active development. APIs may change between releases.

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.