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 typed User | null (or User). The class is registered as UserCollector by convention.
  • Spell the union out. The signature parser is AST-only: user: User | null matches, 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 a User.

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.

Built with Fougere — this site runs on the framework it documents.