# Embedding API Decodal core can be embedded without giving the core crate access to a filesystem. The host supplies imported sources through `SourceLoader` and may also provide global bindings through the host prelude API. ## Host prelude `Engine` owns a prelude environment. Bindings in this environment are visible from every module loaded by the engine. ```text prelude env ↓ module root env ↓ let / function env ``` Module top-level bindings shadow prelude bindings. Primitive type names such as `String`, `Int`, `Float`, `Bool`, and `Array` are handled before environment lookup, so they are reserved and cannot be shadowed by host bindings. ## Global bindings The host can bind values before adding or evaluating user sources. ```rust use decodal::{EmptyLoader, Engine, HostValue}; let mut engine = Engine::new(EmptyLoader); engine.bind_global( "Service", HostValue::object([ ("name", HostValue::string_type()), ("port", HostValue::int_type().gt(443).default_int(8443)?), ("enabled", HostValue::bool_type().default_bool(true)?), ]), )?; ``` A user source can then refer to `Service` without importing it. ```dcdl Service & { name = "api"; port = 9443; } ``` ## HostValue `HostValue` is the public builder-facing value representation for embedding. It keeps host code from constructing internal `ThunkId` or `ObjectValue` values directly. ```text HostValue = String Int Float Bool Array(Vec) Object(Vec) Abstract { constraints, default } ``` When a host value is bound, the engine internalizes it into `RuntimeValue` and allocates value thunks for object fields, array items, and defaults. ## Abstract host objects A host-provided schema object is represented as a concrete object structure whose fields may contain abstract values. ```rust HostValue::object([ ("name", HostValue::string_type()), ("port", HostValue::int_type().gt(443).default_int(8443)?), ]) ``` Conceptually this becomes: ```text Concrete(Object { name -> Thunk(Abstract { constraints: [String], default: none }) port -> Thunk(Abstract { constraints: [Int, > 443], default: 8443 }) }) ``` This matches the runtime model used for Decodal source-defined schema objects. ## Typed Rust integration Hosts can use `decodal-derive` to keep a Rust struct, the Decodal schema, and the decoded result in sync. The derive implements two traits from the `decodal` crate: - `DecodalSchema`: builds a `HostValue` schema that can be passed to `Engine::bind_global`. - `DecodalDecode`: decodes materialized `Data` back into the Rust type. ```rust use decodal::{DecodalDecode, DecodalSchema, EmptyLoader, Engine}; use decodal_derive::Decodal; #[derive(Decodal)] struct Service { name: String, #[decodal(gt = 443, default = 8443)] port: i64, #[decodal(rename = "feature.enable", default = true)] feature_enabled: bool, } let mut engine = Engine::new(EmptyLoader); engine.bind_global("Service", Service::decodal_schema())?; let value = engine.eval_module(module)?; let data = engine.materialize(&value)?; let service = Service::decodal_decode(&data)?; ``` Supported field attributes are intentionally small: - `rename = "path.to.field"` - `default` - `default = value` - numeric constraints: `gt`, `gte`, `lt`, `lte` The derive does not add host callbacks or reflection. It only generates schema construction and typed decoding code. ## SourceLoader and prelude together `SourceLoader` and host prelude bindings are independent mechanisms. - Use `SourceLoader` when user sources should explicitly import host-provided modules. - Use prelude bindings when host-provided schemas or constants should be globally available. Both mechanisms share the same runtime evaluator, thunk model, and materialization rules.