Modules
Overview
Modules add capabilities to an embedded Ferret runtime. Official, contributed, third-party, and application-defined modules all use the same registration model:
- Import the module as a Go package.
- Construct and configure it in the host application.
- Pass it to the engine with
ferret.WithModules. - Let
ferret.Newregister it and run its initialization hooks. - Close module-owned resources through the engine lifecycle.
The host application chooses which modules are available. Importing a package alone does not add its functions or runtime capabilities to Ferret.
Installing a module
For a module published in the Ferret Registry, run ferret mod install from the host application’s Go module. The CLI resolves a compatible release, adds its Go package, and registers its zero-argument constructor in the application’s ferret.New(...) composition:
ferret mod install montferret/archive
See Install a module for project prerequisites, exact versions, non-interactive setup, and validation behavior.
You can also add a Go package manually when it is not registered or when its constructor needs host-specific setup. For example, the HTML module is published from the Ferret contrib repository:
go get github.com/MontFerret/contrib/modules/web/html
Import the module and any supporting packages your configuration needs:
Application-defined modules can live in the same Go module as the host application and do not need to be published separately.
Creating and configuring a module
Construct modules before creating the engine. Constructors and functional options belong to the module package, so their configuration differs by module.
The HTML module requires a driver. This configuration uses the in-process memory driver for static HTML:
The HTML constructor returns a module value and applies its options during registration. Check the error from ferret.New, which reports invalid HTML options together with other registration failures. If another module constructor returns an error directly, handle it before building the engine.
Registering modules
Pass constructed modules to ferret.WithModules when creating the engine:
ferret.New calls the module’s Register method while assembling the engine. The engine is returned only after every module has registered, the host registries have been built, and all engine initialization hooks have completed.
Registering multiple modules
ferret.WithModules accepts any number of modules:
Host applications can name the accepted contract without importing pkg/module:
Modules register sequentially in the order passed. Multiple WithModules options append to the same ordered list.
Modules may add different functions to the same namespace. A shared namespace is not itself an error. If two modules register the same fully qualified function name, however, engine construction fails instead of choosing one implementation or overriding the earlier registration.
Registration errors
Module construction and engine registration report errors at different points:
| Failure | Result |
|---|---|
| A module constructor returns an error | Handle the error before calling ferret.New |
A nil module is passed to WithModules |
Engine option validation fails |
A module’s Register method returns an error |
Registration stops; later modules are not registered |
| Host registries cannot be built, such as after a duplicate function registration | Engine construction fails after module registration |
| An engine initialization hook returns an error | Initialization stops and engine construction fails |
In each engine-construction failure that occurs after bootstrap begins, Ferret runs the engine close hooks registered so far in reverse registration order. Close-hook errors are aggregated with the construction error, so cleanup failures are not discarded.
Always check the error returned by ferret.New. When construction fails, it returns no usable engine.
Module lifecycle
A module participates in the engine lifecycle through registration and hooks:
| Stage | Behavior |
|---|---|
| Construction | The host application creates and configures the module |
| Registration | ferret.New calls each module’s Register method in order |
| Host build | Ferret finalizes functions, parameters, codecs, and host services |
| Initialization | Engine initialization hooks run in registration order and stop on the first error |
| Shutdown | engine.Close() runs engine close hooks in reverse registration order and aggregates their errors |
ferret.Module is an alias of module.Module, so host and module-authoring code use the same contract without conversion. The interface does not define a Close method. A module that owns engine-scoped resources registers an engine close hook and releases them there. Plan- and session-scoped resources belong in their corresponding close hooks and are released when the host application closes those plans and sessions.
Writing a custom module
Module authors implement the contract in pkg/module to expose functions, namespaces, host values, codecs, parameters, and lifecycle behavior:
Register receives a module.Bootstrap with access to host registries and the engine, plan, and session hook registrars. Keep registration focused on wiring the module into the host; construct and validate module-specific configuration before registration when practical.
For a complete implementation walkthrough, see Develop a Ferret module.
Lifecycle hooks
Lifecycle hooks let modules and host applications react to engine, compilation, execution, and cleanup events. Host applications can register them through ferret option functions. Modules register the same hooks through module.Bootstrap.
Engine hooks
| Hook option | Root hook type | Signature | When it runs |
|---|---|---|---|
WithEngineInitHook |
ferret.EngineInitHook |
func() error |
During ferret.New, after registration and host construction |
WithEngineCloseHook |
ferret.EngineCloseHook |
func() error |
When construction fails or engine.Close() is called |
Initialization hooks run in registration order (FIFO) and stop on the first error. Close hooks run in reverse registration order (LIFO), continue after errors, and aggregate those errors.
Modules use the engine hook registrar:
Compilation hooks
| Hook option | Root hook type | Signature | When it runs |
|---|---|---|---|
WithBeforeCompileHook |
ferret.BeforeCompileHook |
func(context.Context) error |
Before compilation begins |
WithAfterCompileHook |
ferret.AfterCompileHook |
func(context.Context, error) error |
After a compilation attempt |
Before-compile hooks run FIFO and stop on the first error. Once compilation is attempted, after-compile hooks run LIFO and receive the compilation error, if any. They continue after hook errors and aggregate them.
Modules register these hooks with boot.Hooks().Plan().BeforeCompile(...) and boot.Hooks().Plan().AfterCompile(...).
Execution hooks
| Hook option | Root hook type | Signature | When it runs |
|---|---|---|---|
WithBeforeRunHook |
ferret.BeforeRunHook |
func(context.Context) (context.Context, error) |
Before session.Run begins |
WithAfterRunHook |
ferret.AfterRunHook |
func(context.Context, error) error |
After a run attempt |
Before-run hooks run FIFO and can return a derived context for subsequent hooks and VM execution. They stop on the first error. Once all before-run hooks succeed, after-run hooks run exactly once in LIFO order, even if cancellation or an invalid context prevents execution. They receive the returned hook context, or the original caller context when it is nil, and the same run or validation error. Their failures are joined with that error. Pre-canceled admission and failed before-run hooks do not trigger after-run hooks.
Normal and debug sessions follow the same pairing rules. If a debugger’s Start aborts after successful before-run hooks but before execution, it settles those after-run hooks immediately and remains available for another Start. Closing the session does not repeat the aborted attempt’s hooks.
An after-run failure does not discard successful encoded output. Normal sessions run after-run hooks before encoding and result cleanup; hook, encoding, and cleanup failures remain inspectable in the returned error. Check for available output even when a run returns an error.
Modules register these hooks with boot.Hooks().Session().BeforeRun(...) and boot.Hooks().Session().AfterRun(...).
Cleanup hooks
| Hook option | Root hook type | Signature | When it runs | Module registrar |
|---|---|---|---|---|
WithPlanCloseHook |
ferret.PlanCloseHook |
func() error |
When plan.Close() is called |
boot.Hooks().Plan().OnClose(...) |
WithSessionCloseHook |
ferret.SessionCloseHook |
func() error |
When session.Close() is called |
boot.Hooks().Session().OnClose(...) |
Plan and session close hooks run in LIFO order, continue after errors, and aggregate those errors.
Hook execution order
| Hook type | Order | Stops on error |
|---|---|---|
| Init, Before | FIFO | Yes |
| After, Close | LIFO | No; errors are aggregated |
Hooks registered through engine options and modules join the same hook chains. Engine-option hooks are registered while options are processed; module hooks are added later as modules register in the order passed to WithModules.