Collectors
A collector resolves a handler parameter by its type from the invocation context. Operations declaring that parameter receive the value automatically.
import { Collector } from '@fougere/core';
import type { InvocationContext } from '@fougere/core';
import User from '../../user/entities/User.js';
/**
* The auth middleware puts the session user on ctx.state.user —
* this surfaces it to any handler that declares `user?: User`.
*/
export default class CurrentUserCollector extends Collector(User) {
async collect(ctx: InvocationContext) {
return ctx.state.user as User | undefined;
}
}
From then on, in any handler of the Frond:
async mine(user?: User): Promise<Post[]> { … }
async publish(id: string, user?: User): Promise<Post> { … }
Resolution rules
- The match is by checked type:
Collector(User)resolvesuser?: User,user: User | undefined, and aliases with the same meaning. The class is registered asUserCollectorby convention. - The target needs no schema. Nothing reads its fields, so an ordinary class works —
Collector(Ability)for a value you build from state at every call. - Absence is
undefined. Prefer the conciseuser?: User. The type checker also resolves an alias such astype CurrentUser = User | undefined. - Collectors are DI-injectable classes — declare constructor dependencies as usual.
State and the gradient
ctx.state is the request state built by the consuming application, for example from the
session. During a remote call, this state is sent with the invocation, so the collector
receives the same ctx.state.user as it would locally.
A collector does not cross a Frond boundary
Keep the collector in each Frond that consumes it. What happens otherwise deserves to be said precisely, because it is neither "later" nor "nothing":
- it is not at the split. The binding is decided at boot, in a single process, from the collectors of that Frond itself;
- the parameter is not empty. A type the Frond cannot resolve falls into the fourth
binding rule,
body. So the handler receives the request body where it expects aUser.
Put plainly: user?: User declared in a Frond that has no UserCollector receives
whatever the client sent. A handler judging on user.role judges a caller-supplied value.
An earlier version of this page said the collector was "lost after a process split". That was wrong twice, and the truth is less comfortable.
Next: Errors — typed errors across layers.