๐ค Addons
Rastrillo's core holds what every app needs and what is hard to get right twice. Some things are neither โ real for many apps, wrong for every app to carry. Those ship as addons: separate modules, versioned separately, that an app pulls in when it wants them.
An addon is not a plugin system. There is no registry, no lifecycle and no hook table. An addon is an ordinary Go module that happens to obey four rules.
What makes an addon
It depends on Rastrillo; Rastrillo never depends on it. The arrow points one way, always. Nothing in the framework knows an addon exists, which is what lets an addon ship on its own schedule.
It ships its own migrations, namespaced. A migrate.Set the app
merges into BootSchema โ never into its own Schema, or
rastrillo migration check proposes dropping tables that Models does
not know about. See Migrations.
It ships its own SKILL.md, inside the module. The framework's sits
at the repo root a scaffolded app points to; an addon's lands in the
module cache, so the directory page's job is to name the one command
that prints it. An addon that an agent cannot read is an addon that
saves the typing and none of the reading.
It does not re-implement the core. Sessions, CSRF, migrations, forms and flash are already there. An addon that brings its own is a fork wearing a smaller name.
The directory
idear โ accounts, roles, invitations
Status: released, v0.1.1.
Module: amadan.net/rastrillo/idear ยท
Source: https://amadan.net/rastrillo/idear
The vanity path is amadan.net, not github.com/carlosframework,
because an addon is a separate module in a separate repository on its
own release schedule โ do not guess github.com/carlosframework/idear,
which does not exist. Use the path above verbatim.
The roster for an instance: who is in it, at what role, and who may change that. Three strictly ordered roles โ Owner, Admin, Member, with exactly one Owner at all times โ plus invitations, member management, and the middleware that makes the membership gate the short path.
Load its authoring doc before building on it. An addon's SKILL.md
ships inside the module, so go get has already put it on disk โ
and the copy you read is pinned to the version you are building
against, which a URL would not be:
go get amadan.net/rastrillo/idear
cat "$(go list -m -f '{{.Dir}}' amadan.net/rastrillo/idear)/SKILL.md"
It sits on top of sessions and whichever identity plugin the app already chose, so passwords and magic links both keep working:
roster, err := idear.New(idear.Config{DB: d.G, OpenSignUp: false})
if err != nil {
return nil, err
}
ph, err := password.New(password.Config{
Sessions: sess,
Lookup: lookupUser(d.G),
Create: roster.Admitting(createUser(d.G)),
RenderSignin: renderSignin,
RenderSignup: renderSignup,
})
if err != nil {
return nil, err
}
Not r: that name is the chi router everywhere else, including
Passwords, and shadowing it here would cost you the
router for the rest of the function.
What it deliberately does not do. It is not an identity provider: it never mints a session, never hashes a password, never renders a sign-in form. It has no tenant field and no tenant scope โ a CARLOS app serves one team, and separating teams stays the platform's job. See Scoping.
Recording refusals is the addon's job, not the framework's. password
answers a Refuse at 403 and logs nothing: a refusal is expected
policy rather than an error, and writing a caller-supplied string and a
visitor's address to the log on every uninvited signup is noise and
personal data both. An addon that wants the audit trail keeps it
itself, at whatever fidelity its own policy calls for.
Publishing an addon
Follow the four rules above, then serve SKILL.md at a stable URL and
send a patch adding an entry here. An addon nobody can find and no agent
can read is a library, not an addon.
Read this page as markdown โ exact, unstyled, and cheap for an agent to fetch.