blockwatcher-embed
blockwatcher-embed is the in-process composition façade: it folds every
compiled-in module family into one
ModuleCatalog, and it re-exports the engine types a host needs to boot
and stop without linking the blockwatcher binary. The binary itself calls
build_catalog here rather than keeping a second registrar
(crates/blockwatcher/src/run.rs).
It sits in the glue ring, the same ring as
the binary: it may depend on blockwatcher-core, and scripts/check-dep-graph.sh
allowlists it that way. It does not depend on blockwatcher-api, axum, the
metrics HTTP listener, CLI parsing, or process signals.
Its production dependencies are exactly the ALLOW_BLOCKWATCHER_EMBED entry
scripts/check-dep-graph.sh lists (crates/blockwatcher-embed/Cargo.toml):
blockwatcher-core:Engine,EngineDeps,Engine::start,ControlHandle,ModuleCatalogblockwatcher-storage: always folded into the catalog (memory,sqlite); not behind a featureblockwatcher-gates: always folded into the catalog (threshold,max_once); not behind a feature — there is nogatesflagblockwatcher-expr,blockwatcher-evm,blockwatcher-sinks: optional (dep:in[features]), one per feature flag, mirroring the binary
The family exemption list (FAMILY_EXEMPT_BLOCKWATCHER_EMBED) covers the module
crates this façade links (alloy, reqwest, hyper, tower-http,
rusqlite) and not axum: embed never serves HTTP.
Embedding blockwatcher in a host process
already covers this crate from a host’s side: the boot recipe, an
in-process Sink, and how shutdown differs from the binary. This page does
not restate that recipe; it covers what the crate actually exports, which
feature flag removes which registration, and where it sits in the
dependency graph.
Key takeaways
blockwatcher-embedis glue, not a module: it may depend onblockwatcher-core, and it is the one place module families are registered.- Default features (
evm,expr,sinks) mirror the binary; storage and gate modules (threshold,max_once) are always present. There is nogatesfeature: turning a gate module off is not a compile-time switch; an unknowngate.moduleat write is422listing catalog names. - It re-exports
build_catalog,DEFAULT_MATCHER(whenexpris on),Engine,EngineDeps,EngineConfig,EngineError, andControlHandle. It does not wrapEngine::start. - The binary is a client of this crate for catalog construction. Nothing in the workspace depends on embed except the binary.
Responsibilities
- Registers every compiled-in module family into one
ModuleCatalog, feature-gated so what a build can select is exactly what it linked (catalog.rs). - Re-exports the small set of engine types a host needs for the happy path
(
lib.rs). - Forwards the binary’s
evm/expr/sinksfeature flags onto the same optional module crates, so a binary built withoutevmalso builds embed withoutevm(crates/blockwatcher/Cargo.toml).
Not this crate’s job: parsing CLI or instance config, seeding a
directory, binding listeners, installing SIGTERM/SIGINT, or serving HTTP or
Prometheus (blockwatcher binary, blockwatcher-api, blockwatcher-metrics); running a
pipeline or deciding when a checkpoint advances (blockwatcher-core);
implementing any module a catalog can select (each lives in its own module
crate).
Key types and functions
| Name | Kind | Role |
|---|---|---|
build_catalog | fn | Folds every compiled-in family’s get_all() into one ModuleCatalog, feature-gated per family (catalog.rs) |
DEFAULT_MATCHER | const | "expr", behind feature expr. The name build_catalog registers for the matcher family; the binary’s config fallback uses it so an unset [engine].matcher selects the same module (catalog.rs) |
Engine, EngineDeps, EngineConfig, EngineError | re-export | The same types blockwatcher-core publishes; Engine::start takes EngineDeps by value |
ControlHandle | re-export | The one path a running engine’s resources change through |
Feature-flag wiring
Cargo.toml’s optional features each gate one dep: entry, all on by
default:
[features]
default = ["evm", "expr", "sinks"]
evm = ["dep:blockwatcher-evm"]
expr = ["dep:blockwatcher-expr"]
sinks = ["dep:blockwatcher-sinks"]
build_catalog (catalog.rs) is where the effect of each flag is entirely
mechanical: a #[cfg(feature = "...")] block around one fold, family by
family:
| Feature | Off, build_catalog no longer registers |
|---|---|
evm | blockwatcher_evm::sources::get_all() (evm-rpc, evm-mempool) and blockwatcher_evm::decoders::get_all() (evm) |
expr | blockwatcher_expr::matchers::get_all() (expr) |
sinks | blockwatcher_sinks::registry::sinks::get_all() (webhook, script, log) |
Storage’s own registration, blockwatcher_storage::registry::storages::get_all()
(memory, sqlite), is not behind any feature: blockwatcher-storage is a
plain, non-optional dependency. Gate modules,
blockwatcher_gates::registry::gates::get_all() (threshold, max_once),
are the same: always registered.
A build missing a feature does not merely fail to offer the module by
name; the factory function itself is absent, since the whole fold is
compiled out. A config naming a module a build didn’t link refuses at
catalog lookup time with the alternatives that build actually carries
(catalog.rs’s own tests,
an_unknown_storage_module_is_refused_naming_what_this_build_carries).
Neighbours
blockwatcher-embed depends on, in production:
blockwatcher-coreblockwatcher-storageblockwatcher-gatesblockwatcher-expr(featureexpr, on by default)blockwatcher-evm(featureevm, on by default)blockwatcher-sinks(featuresinks, on by default)
and, in [dev-dependencies] only:
tokio(macros/rtfeatures): the catalog construction tests
The following crate depends on it directly (per the dependency table):
blockwatcher(binary): callsbuild_catalogat boot, seed validation, and checkpoint prune; forwards its own feature flags onto this crate’s matching features.
Reading the source
lib.rs: the crate doc comment (why hosts depend here rather than assembling a catalog themselves) and the re-export surface.catalog.rs:build_catalog, and read it beside Feature-flag wiring above.