The boot, step by step
A boot rises in steps, and a release goes down them in reverse. An extension states what it does to an app and what it undoes:
const app = await createApp({
root,
createContainer,
extensions: [observability()],
});
up runs before createApp returns, in declaration order; down runs inside dispose(),
in reverse. It is also the one point in the boot that may await, which is where a
provider that has to open something belongs — a constructor cannot.
Two members are the framework's own, and they are ordinary members:
| name | what it does |
|---|---|
schema | reads the database against the entities and refuses the boot when it is behind, naming each missing table and column. It writes nothing: that is fougere migrate --apply. A process whose database is born with it replaces the member with migrating(storage.migrate), which writes instead |
seeds | plants what seeds/ declares, in dependency order |
A name already declared is replaced, not refused. That is how a host says the seeding, but mine:
// what @fougere/nuxt generates: a bundler needs its seed modules as static imports
extensions: [
{ name: 'seeds', up: (app) => runSeeds(app, [/* imported above */]) },
]
// a test, whose `:memory:` database is born with it and has nobody else to migrate it
extensions: [migrating(storage.migrate)]
The ascent and the descent refuse in opposite ways, on purpose:
upstops at the first refusal. A seed must not run against a database the schema check just refused, and a half-started app must not be handed out.downreleases every member even when one throws, and the refusals leave together in anAggregateError— a release that gives up on the first failure leaks everything after it.
An extension is not a frond and cannot become one: a frond has entities and may move behind
fronds:, while an extension belongs to the process that hosts them. observability()
is the case that proves it — moved, it would report the observer instead of the observed.