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 | null`.
*/
export default class CurrentUserCollector extends Collector(User) {
async collect(ctx: InvocationContext) {
return (ctx.state.user ?? null) as User | null;
}
}
From then on, in any handler of the Frond:
async mine(user: User | null): Promise<Post[]> { … }
async publish(id: string, user: User | null): Promise<Post> { … }
Resolution rules
- The match is by entity type name:
Collector(User)resolves parameters typedUser | null(orUser). The class is registered asUserCollectorby convention. - Spell the union out. The signature parser is AST-only:
user: User | nullmatches, a type alias (user: CurrentUser) is invisible and binds nothing. - 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 | null 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.