Registries and extension points
Fougere is extended in two ways, and every point of extension is one or the other.
- By name — a registry. A package registers a word, and config or an entity names it:
sources: { main: { type: 'sqlite' } },protocols: { rest: {} },format: 'slug'. - By type — a port. A class extends a port, or stands in front of it, and what asks for the port
receives it:
class StripePayment extends Payment,class Audit implements Partial<Dispatcher>.
Registries
| Registry | The word it answers | Registered by |
|---|---|---|
Sources | type: of a sources: entry | adapter-sql, adapter-file, adapter-memory, adapter-env |
Protocols | a key of protocols: | adapter-rest, adapter-graphql |
States | a way to keep a name of state: | state-cookie, state-session |
StringFormats, Generators, Verbs | a word of an entity | your vocabulary |
Axes | a fifth axis of a field | a package |
A word has one answer per process: the same answer registered twice is harmless, a different one
is refused, naming the registry. A package that registers when it is imported says so in its
package.json (sideEffects), or a bundler drops the import. Each word also has a type, added to an
empty interface of core with declare module — that is what lets the editor refuse a misspelled
word: see Interfaces to extend.
Ports
Every port Fougere opens — Dispatcher, Rest, Collect<T>, Present<T>, Env<T>, Storage,
Logger — is extended the way your own are: see Port
and the reference.
Storage is the one whose realization is handed in rather than written: the source builds a storage
per entity, and no class declares it. So a class extending Storage can only stand in front of it —
in front of the rows — and it covers every entity of its
own frond. No key widens that reach: a link sits beside the code it changes and goes where that code
goes, so moving a frond to another process cannot quietly leave a link behind. What must be in every
process is a frond every process holds, which an extension brings.
The check stays outside the chain: a row the entity refuses never reaches a link, and what a link receives is the value the schema already parsed.
Seeing what answers
fougere ports
prints every point of this app — each registry with the words it answers, each port with its links in
order and the realization they reach — booting the app with no schema check and no seeds. In code,
app.extensionPoints() answers the same list.
A transport, and HTTP
Neither is a host port, and neither leaves you without a way in.
A transport is named by the scheme of the address fronds: gives — http://,
https://, binding:// for a Cloudflare service binding — and answered by the package
that registers it, the way a source type is:
Transports.register('tcp', { open: (address) => mySocketTransport(Number(address.port)) });
// fougere.config.ts
fronds: { sensors: { remote: 'tcp://127.0.0.1:4700' } },
@fougere/transport-http registers http, https and binding. A scheme nothing
registered refuses the boot, naming the entry and the schemes this process knows. A host
may still swap the transport whole, and then no scheme is read:
boot({ root, createContainer, remoteTransport: (url) => mySocketTransport(url) });
And an outgoing call to a frond behind fronds: already runs through the app's
middlewares before it reaches the wire, so retrying, timing or logging one is a
host middleware (app.use), not a transport:
app.use(async (ctx, next) => { /* ctx.address, ctx.operation */ return next(); });
What that does not reach is the envelope itself — the encoding, the headers, the connection. For those you are the transport, and you swap it.
HTTP is the engine's from start to finish: the app mounts on it with answer(app, request),
and the engine's own middleware stands around it. Leaving a route out is a middleware answering
before answer does:
hono.delete('/api/*', (c) => c.text('Method Not Allowed', 405));
hono.all('/api/*', async (c) => (await answer(app, c.req.raw)) ?? c.notFound());
Where an op answers is the frond's to say, in its frond.config.ts — see
surfaces for the three parts of a URL and who sets each one.