Leia

Leia Hot Reload

Leia hot reload is a Go embedding API for replacing script code while a host process stays alive. The loader does not start a filesystem watcher; hosts call reload methods from their watcher, admin endpoint, editor integration, or deployment hook.

Two Levels

API Use
HotLoader + ModuleHandle Atomically publish the latest successfully compiled program. The host chooses the VM used for each run.
HotInstance Own one persistent VM and automatically preserve ordinary script state while replacing code.

Use ModuleHandle when each request has its own VM. Use HotInstance when one live script instance should keep counters, tables, caches, and other runtime state across reloads.

Loader Options

NewHotLoader accepts:

Option Use
WithHotLoaderCompileOptions(...) Apply source-name and future compile options to every load or reload.
WithHotLoaderVMOptions(...) Apply VM options to VMs created for HotInstance.

Module Handles

loader := leia.NewHotLoader()
handle, err := loader.Load("logic.leia")
if err != nil {
    return err
}

_ = loader.Reload("logic.leia")
out, err := handle.Call(leia.New(leia.WithVM()), "answer")

Reload compiles first and swaps only on success. If parsing or compilation fails, the old generation remains active.

ReloadIfChanged hashes source bytes and skips unchanged files. Unchanged reloads do not advance the generation.

ModuleHandle.Call runs the latest top-level program on the supplied VM before calling the named function. Hosts that need to avoid replaying top-level side effects should call handle.Run(vm) once and then call vm.Call(...) directly.

Context-aware forms are available as LoadContext, ReloadContext, ReloadIfChangedContext, RunContext, and CallContext.

Hot Instances

loader := leia.NewHotLoader(leia.WithHotLoaderVMOptions(leia.WithVM()))
inst, err := loader.LoadInstance("logic.leia")
if err != nil {
    return err
}

_, _ = inst.Call("tick")
_ = inst.Reload()
_, _ = inst.Call("tick")

HotInstance runs the program in a persistent VM. On reload it rewrites the new program before execution so existing non-function globals are preserved by default and changed functions are replaced.

Context-aware forms are available as LoadInstanceContext, ReloadContext, ReloadIfChangedContext, and CallContext.

State Preservation

Current preservation rules:

This gives the common online-reload behavior: state stays live, new code takes effect, and unchanged closures can keep their captured state.

Rollback

Hot reload is publish-on-success:

The previous generation remains callable after a failed reload.

Concurrency

HotLoader serializes load/reload operations and publishes handles atomically. Each Program and each VM still follows the normal embedding rule: do not run the same instance concurrently unless the API explicitly owns locking.

HotInstance locks around reload and calls on its owned VM. Callers must not use inst.VM() concurrently with HotInstance methods.

Watcher Pattern

Use ReloadIfChanged from a watcher or polling loop. It returns a ReloadResult with Changed and Generation fields:

result, err := inst.ReloadIfChanged()
if err != nil {
    log.Printf("reload failed: %v", err)
}
if result.Changed {
    log.Printf("reloaded generation %d", result.Generation)
}

Limits

Hot reload is not a whole-program migration system:

For explicit migrations, expose a host API or script function and call it after a successful generation change.