View all results

Modules

Overview

Modules add capabilities to an embedded Ferret runtime. Official, contributed, third-party, and application-defined modules all use the same registration model:

  1. Import the module as a Go package.
  2. Construct and configure it in the host application.
  3. Pass it to the engine with ferret.WithModules.
  4. Let ferret.New register it and run its initialization hooks.
  5. 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:

terminal
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:

terminal
go get github.com/MontFerret/contrib/modules/web/html

Import the module and any supporting packages your configuration needs:

example.go
read-only
import ( "github.com/MontFerret/contrib/modules/web/html" "github.com/MontFerret/contrib/modules/web/html/drivers/memory" )

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:

example.go
read-only
htmlmod := html.New( html.WithDefaultDriver(memory.New()), )

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:

example.go
read-only
package main import ( "log" "github.com/MontFerret/contrib/modules/web/html" "github.com/MontFerret/contrib/modules/web/html/drivers/memory" "github.com/MontFerret/ferret/v2" "github.com/MontFerret/ferret/v2/pkg/stdlib" ) func main() { htmlmod := html.New( html.WithDefaultDriver(memory.New()), ) engine, err := ferret.New( ferret.WithStdlib(stdlib.Safe()), ferret.WithModules(htmlmod), ) if err != nil { log.Fatal(err) } defer engine.Close() // Compile and run FQL programs with 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:

example.go
read-only
engine, err := ferret.New( ferret.WithModules( htmlmod, sqlitemod, appmod, ), )

Host applications can name the accepted contract without importing pkg/module:

example.go
read-only
modules := []ferret.Module{htmlmod, sqlitemod, appmod} engine, err := ferret.New( ferret.WithModules(modules...), )

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:

example.go
read-only
type Module interface { Name() string Register(Bootstrap) error }

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.

example.go
read-only
engine, err := ferret.New( ferret.WithEngineInitHook(func() error { log.Println("engine initialized") return nil }), ferret.WithEngineCloseHook(func() error { log.Println("engine closing") return nil }), )

Modules use the engine hook registrar:

example.go
read-only
func (m *Module) Register(boot module.Bootstrap) error { boot.Hooks().Engine().OnInit(func() error { return m.initialize() }) boot.Hooks().Engine().OnClose(func() error { return m.close() }) return nil }

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.

Next steps