export class AgentEndpointResolver {
static readonly make: Effect.Effect<AgentEndpointResolver> = Effect.map(
Ref.make<ResolverState>(emptyState),
(state) => new AgentEndpointResolver(state),
);
private constructor(private readonly state: Ref.Ref<ResolverState>) {}
/**
* Atomically associate `(agentId, connectionId)` and the reverse
* `(connectionId → agentId)` entry.
*
* Idempotent on the forward set: re-adding the same connection to the
* same agent leaves the set unchanged ({@link HashSet.add} is set-union
* semantics).
*
* Cross-agent ownership conflict: if `connectionId` is already in the
* reverse index for a *different* agent, the new add takes ownership —
* the connection is removed from
* the prior agent's forward set inside the same `Ref.update` so the
* forward and reverse views stay invariant. Practically unreachable
* but the detection is cheap and the alternative is a silent
* forward-map leak.
*/
add(agentId: AgentId, connId: ConnectionId): Effect.Effect<void> {
return Ref.update(this.state, (s) => {
const prior = HashMap.get(s.byConnection, connId);
let byAgent = s.byAgent;
if (Option.isSome(prior) && prior.value !== agentId) {
byAgent = HashMap.modifyAt(byAgent, prior.value, (existing) =>
Option.flatMap(existing, (set) => {
const next = HashSet.remove(set, connId);
return HashSet.size(next) === 0 ? Option.none() : Option.some(next);
}),
);
}
return {
byAgent: HashMap.modifyAt(byAgent, agentId, (existing) =>
Option.some(
Option.match(existing, {
onNone: () => HashSet.make(connId),
onSome: (set) => HashSet.add(set, connId),
}),
),
),
byConnection: HashMap.set(s.byConnection, connId, agentId),
};
});
}
/**
* Atomically drop `(agentId, connectionId)` from the forward multimap
* and, if the pair was actually present in the agent's set, drop
* `connectionId` from the reverse index too.
*
* Idempotent. Calling `remove` for a `(agentId, connectionId)` pair
* that was never added is a no-op — the disconnect path can fire it
* unconditionally when the connection authed. For never-authed
* connections, the disconnect path simply skips the call (no agentId
* to address it with) and the resolver state is unchanged.
*
* Tearing the invariant matters when `connectionId` is genuinely owned
* by a *different* agent than the caller asserts. The reverse index
* is only cleared when `byAgent[agentId]` actually held `connectionId`;
* a stray `remove(WRONG_AGENT, conn)` therefore cannot evict
* `byConnection[conn]` from under the rightful owner. This guarantees
* the two maps stay consistent under any sequence of mis-targeted
* removes (programmer error or a re-issued lifecycle hook).
*
* If removing `connectionId` empties the agent's set, the agent key
* itself is dropped from the forward map so {@link resolveAll} returns
* the empty set rather than hitting an empty bucket.
*/
remove(agentId: AgentId, connId: ConnectionId): Effect.Effect<void> {
return Ref.update(this.state, (s) => {
const existed = Option.match(HashMap.get(s.byAgent, agentId), {
onNone: () => false,
onSome: (set) => HashSet.has(set, connId),
});
if (!existed) return s;
return {
byAgent: HashMap.modifyAt(s.byAgent, agentId, (existing) =>
Option.flatMap(existing, (set) => {
const next = HashSet.remove(set, connId);
return HashSet.size(next) === 0 ? Option.none() : Option.some(next);
}),
),
byConnection: HashMap.remove(s.byConnection, connId),
};
});
}
/**
* Hot-path fan-out lookup. Returns every connection id currently
* associated with `agentId`. Read-only snapshot — the `HashSet` is
* immutable and the caller cannot mutate the resolver through it.
*/
resolveAll(agentId: AgentId): Effect.Effect<HashSet.HashSet<ConnectionId>> {
return Effect.map(Ref.get(this.state), (s) =>
Option.getOrElse(HashMap.get(s.byAgent, agentId), () =>
HashSet.empty<ConnectionId>(),
),
);
}
}