Every CLSDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/elfrask/cls/llms.txt
Use this file to discover all available pages before exploring further.
import statement goes through a ModuleResolver. The resolver is the single, pluggable component that decides where modules come from — whether from the built-in standard library, from native Rust values injected by the host application, from .clsx source files on disk, or from any other custom source. The cls-runtime crate is intentionally agnostic about module origins: it only defines the resolver contract. It is the node (the clx desktop CLI, the clxr lightweight runtime, or your own embedding code) that configures the resolver with the right internals and the right external hook.
Resolution Order
When the interpreter encountersimport "name", ModuleResolver::resolve is called. The lookup follows a strict chain:
Cache
If
"name" was already resolved in this interpreter session, the cached Value is returned immediately. This means each module is executed at most once.Internals
The resolver checks its internal map (
HashMap<String, Value>) for an entry keyed exactly by "name". Internal modules are registered with add_internal or with_core_stdlib.External hook
If no internal matches, the resolver calls the closure registered with
set_external, passing the path string and a mutable reference to the current Environment. The hook returns Ok(Some(module)) if found, Ok(None) if not found, or Err for an error.Core API
| Method | Description |
|---|---|
ModuleResolver::new() | Creates an empty resolver with no modules and no hook |
.with_core_stdlib() | Registers math, json, and async as internal modules (builder pattern — consumes self) |
.add_internal(name: &str, value: Value) | Registers a named module as a Value (typically a Value::Record of functions) |
.set_external(fn) | Sets the closure-based hook for resolving modules not found in internals |
Writing an External Hook
The external hook is a closure (or function pointer) with the signature:- Return
Ok(Some(value))— module found;valueis stored in the cache and returned to the importer. - Return
Ok(None)— module not found at this hook; fall through to the error step. - Return
Err(e)— a load error; wrapped in aRuntimeErrorand propagated to the script.
.clsx modules from the filesystem. The correct approach is to compile and execute the source file using a nested Interpreter and return the collected exports via load_module_source:
Interpreter::load_module_source handles the full compile → execute → collect-exports pipeline internally:
- Tokenizes and parses the source string.
- Executes the resulting
Modulein an isolated environment. - Collects every name that was declared with
exportvisibility. - Returns a
Value::Recordmapping exported names to their values.
Injecting Native Modules with add_internal
add_internal is ideal when you want to expose parts of your Rust application — its data model, services, or APIs — to CLS scripts as a module. Build a Value::Record from FunValue::new_native entries and register it:
ClsLibResolver — Compiled .clslib Libraries
Pre-compiled CLS libraries (.clslib files) use a separate resolver: ClsLibResolver (in cls-runtime/src/clslib.rs). It is configured by the node and is used internally by Lib.load(path) calls inside CLS scripts.
Search order for a .clslib:
- Current working directory
- Each path listed in the
$CLS_LIB_PATHenvironment variable - Node-configured search paths (set by the
clxorclxrnode at startup)
.clslib is identified by its SHA-256 content hash after loading, which prevents the same library from being loaded twice. The resolver returns the raw bytes of the library file, which the runtime then indexes and executes.
ClsLibResolver is equivalent to a dynamic library loader (.dll/.so) for CLS — it provides binary modules that were compiled offline with clx build.
Design Rules
The following rules govern how nodes and embedders should use the resolver:The node decides; the runtime executes
cls-core and cls-runtime know nothing about where modules come from. All module sourcing logic lives in the node or embedding code, registered via add_internal and set_external.load_module_source is the compile+run entry point
When your external hook needs to compile a
.clsx source file, always delegate to Interpreter::load_module_source. Never manually invoke Lexer, Parser, and execute in the hook — load_module_source correctly isolates the module environment and collects only exported symbols.Node internals are not in the runtime
The
fs, http, and Lib modules are node-level concerns. They are built and injected by clx. The cls-runtime crate has no knowledge of them. Embedders that need those capabilities must build equivalent native modules and register them with add_internal.Packaging uses the resolver
When
clx build packages a project into a .clsapp, it uses the resolver to discover all transitive module dependencies. Each discovered .clsx module is compiled to a .clbin AST bundle and stored inside the .clsapp archive.