Boot without the compiler

The scan reads the convention directories. What it writes from them is a fronds.ts — the list of frond() calls an author would write — and that file is what every host boots from. Writing it yourself is how an app runs with no @fougere/compiler installed.

A class says most of what it is: Repository<Post> its five operations, Present<Post> its fields. What TypeScript erases — the parameters of a method you wrote, and what it answers — is stated, and nothing is guessed:

// fronds.ts
import { frond } from '@fougere/core';
import Post from './fronds/blog/entities/Post.ts';
import User from './fronds/user/entities/User.ts';
import PostHandler from './fronds/blog/handlers/PostHandler.ts';
import CurrentUserCollector from './fronds/blog/services/CurrentUserCollector.ts';
import { slug } from './fronds/blog/vocabulary/slug.ts';

export const vocabulary = [slug];

export default [
  frond('blog', {
    entities: [Post],
    collectors: [CurrentUserCollector],
    handlers: [{
      ctor: PostHandler,
      operations: {
        publish: { params: { id: [Post, 'id'], user: { type: User, optional: true } }, output: Post },
        mine: { params: { user: { type: User, optional: true } }, output: [Post] },
      },
    }],
  }),
  frond('user', { entities: [User] }),
];

[Post, 'id'] is a field of an entity, user is what the collector answers, [Post] answers several, and whether an operation is a query or a command is told by its name: only read verbs make a query. What the file does not list is not served — the boot cannot tell a method left out from one kept private, since TypeScript erases the keyword. fougere check reads the sources and names a public method the file does not list.

What an operation refuses is yours to state too: errors: [ErrorCode.CONFLICT] beside output. The scan finds that list by following the throws, and a list you write is taken as it is, so nothing checks it against the code.

Every generated file is optional

The scan writes in .fougere/, which git ignores and you can delete at any time. No command has to run: a host writes these files when it starts, and fougere build writes them for a CI. Each one saves you some writing, and none is required:

GeneratedWithout itBy hand
fronds.generated.tsthe scan runs at boot, or the boot refuses if the compiler is not installeda fronds.ts, above
facade.generated.tsa page's @fronds/facade import does not resolveone line per handler, below
names.generated.d.tsa wrong name in the config is refused at boot, not in the editornothing to write
build/fougere serve refuses; fougere dev runs the sourcesnothing to write

A facade is the handler as a type, and an address as a value. Written in a page, it brings no server code to the browser:

import type { FacadeName } from '@fougere/core/contract';
import type PostHandler from '@fronds/blog/handlers/PostHandler.js';

const post: FacadeName<PostHandler, 'post'> = { address: 'post' };

Two things are lost. The address is yours to keep right, since a typo only fails when the call runs. And a client reads every error code, because what an operation refuses is what the scan finds by following its throws.

Built with Fougere and Nuxt, hosted on GitHub Pages.