The base

The base of a Fougere application is not what the framework ships. It is the entity you wrote.

Everything Fougere hands back is indexed by that declaration, and it comes in three forms. Knowing the three is enough to predict where the schema goes in an API you have never seen: between the parentheses of a class you extend, in the type of a value you receive, or as the first argument of a call you write.

Three forms, one parameter

FormWhat you writeExamples
a class you extendextends Crud(Post)entity, Crud, Presenter, Collector, Repository, Mirror
a value you receiveconstructor(posts: Repository<Post>)Repository<E>, Emit<F>, Fact<F>, Facade<H>
a call you writeuseQuery(post, 'list')facade, useQuery, useCommand, useFormFor

The parameter is always something you declared yourself — usually an entity, sometimes a fact or a handler. Nothing in that column is a string, a name, or a key registered elsewhere: rename the class and the compiler follows every reader of it.

A class you extend

Six of them. What varies is not their nature but how much your declaration determines — Collector(User) can derive almost nothing from "I resolve a User", while Crud(Post) derives five whole operations from Post's table.

The parentheses holdWhat you inheritWhat you write
entity({…})the fieldsthe type, the validator, the metadatathe field map
Crud(Post)an entityfive operationsnothing, or the ones you redefine
Presenter(Post)an entitythe target the scan readsone method per computed field
Collector(User)any classthe target the scan readscollect(ctx)
Repository(Reading)one entity, or the several it ownsevery gesture of the guarded port — or none, from two onthe queries you name
Mirror(BookCard)a shapethe loop and the writepull()

Only the first is mandatory. A handler that extends nothing is an ordinary handler; a repository you never write still resolves. Where extending is the declaration — presenters, collectors — the base carries a target and no behaviour at all.

Each of them is documented where it is used, and the links above go there. One has no page of its own, because it belongs to no single step: it is the base itself.

Mirror — a local copy of rows you cannot query

A real database is queried where it is. Mirror(Shape) is the answer for a source with no algebra: an HTTP API, a partner's catalog, a Frond behind a wire. The subclass supplies one thing, the pull:

// services/PartnerCatalog.ts
export default class PartnerCatalog extends Mirror(BookCard) {
  async *pull(since?: Date) {
    for (let page = 0; page !== null; ) {
      const body = await fetch(`${api}?page=${page}&since=${since?.toISOString() ?? ''}`);
      const { items, next } = await body.json();
      yield items.map(toCard);
      page = next;
    }
  }
}

Everything around it is the same for every mirror and lives in the base: refresh(since?) pulls from the mark it is handed and writes each page in one upsertAll. The mark is the caller's: it read one off its own rows, and it moves it only once a pass has returned — a pass that threw halfway must not advance it past what the source changed in the gap.

A page is judged where every other row is, by the storage guard, strictly: a partner renaming label would write nulls in silence otherwise. A refused page stops the pass; what earlier pages wrote stays, because an upsert is idempotent and the next run resumes from the mark.

It does not schedule itself. A boot hook, a cron, an operation behind a facade are all legitimate and none of them is this class's business.

Mirror has no directory of its own in the Frond vocabulary yet — unlike presenters/ and collectors/, a mirrors/ folder is not scanned. Place it where providers are read (services/ or repositories/). As a storage holder, it is handed the Storage its prefab was built on; handlers, presenters and collectors do not have that exception.

A value you receive

The second form is a constructor parameter, resolved by type — never by parameter name, never by a string key:

export default class PostHandler {
  constructor(
    private posts: Repository<Post>,
    private announce: Emit<PostPublished>,
  ) {}
}

Repository<E> is the default repository shape: it forwards the guarded storage port and resolves even when no repository file exists. Injecting Storage<E> directly into a handler, presenter or collector is refused at boot; name storage access through a repository. Emit<F> announces a fact, and accepting a Fact<F> is the subscription — no topic, no register call. Facade<H> is the one whose parameter is not an entity: it names another Frond's handler, which is a declaration of yours all the same.

A call you write

The third form is the client side, and it is Facade<H> again — a page names the handler that answers, exactly as a handler names another Frond's:

import { post } from '@fronds/facade';

const { items } = await useQuery(post, 'list');
const publish = useCommand(post, 'publish');
const form = useFormFor(Post, { op: 'update', params: { id } });

Same designation as the server, so the local validator is the remote validator and a form contract is not a second declaration of the entity. The handler type is imported for its TYPE only — erased at build — and it is what gives the page the operations that exist and what each answers, so no row shape is written by hand.

useFormFor keeps the entity, and that is the line: a form is a set of fields, while a call is a facade. The two are different subjects, so they take different arguments.

One exception worth stating: useCurrentUser() takes no argument. It reads its entity from the session rather than from a call site.

Why a class, and never a decorator

A decorator would have to modify the runtime to attach the storage, the target, or the five operations. extends states the same thing in the language: the type follows without a second declaration, getFields() is a plain static, and nothing is registered behind your back.

It also survives where a scan cannot reach. Crud declares its operations at runtime, on the class, so an installed Frond whose source no parser can open still publishes its contract. Decorator metadata would need a compiler flag and a reflection library to say as much, and would say it only where both were configured.

Next: Entities.

Built with Fougere and Nuxt, hosted on GitHub Pages.