By submitting a Pull Request or making any contribution to this project, you automatically agree to and accept all terms outlined in our Contributor License Agreement. This includes all future contributions you may make to the project.
- Installation
- Project Structure Overview
- Generated Indexer Runtime Architecture
- Update CLI Generated Docs
- Create templates
- Configure the files according to your project
- Generate code according to configuration
- Run the indexer
- View the database
Install prerequisite tools:
-
Node.js v22+ (v24 recommended) https://nodejs.org/en (Recommended to use a node manager like fnm or nvm)
-
pnpm
npm install --global pnpm
-
Cargo https://doc.rust-lang.org/cargo/getting-started/installation.html
curl https://sh.rustup.rs -sSf | sh -
Docker Desktop https://www.docker.com/products/docker-desktop/
If you want to test the latest changes in the
envioCLI
cargo install --path packages/cli --locked --debugCommand to see available CLI commands
envio --helpAlternatively you can add an alias in your shell config. This will allow you to test Envio locally without manual recompiling.
Go to your shell config file and add the following line:
alias lenvio="node <absolute repository path>/hyperindex/packages/envio/bin.mjs"
lenviois likelocal envio😁
Envio is split into a Rust CLI and the generated indexer runtime.
Top-level folders:
packages/cli– Rust source of the Envio CLI (Cargo.tomllives here).src/commands.rs– dispatches sub-commands using Clap.src/executor/– implementation details for each command.src/config_parsing/– configuration loading pipeline:human_config.rs→ reads userconfig.yaml.system_config.rs→ converts user config into the internal representation.
src/hbs_templating/codegen_templates.rs– prepares data for Handlebars and writes thegenerated/directory.templates/– code scaffold used during generation (dynamic/for Handlebars,static/for raw files).
Main CLI commands:
init– interactive project scaffolding (src/executor/init.rs,src/cli_args/interactive_init/).codegen– parses config, builds the internal model, then calls templates to create thegenerated/indexer runtime.start– runs the already generated runtime code.dev– detects changes; if anything changed it runscodegenand thenstart, otherwise juststart.
Generated indexer runtime locations:
- Library-ified code:
packages/envio(ReScript/TypeScript). Usepnpm rescript-wfor live recompilation; nopnpm codegenneeded. - Static scaffold:
packages/cli/templates/static/codegenand dynamic templates inpackages/cli/templates/dynamic/codegen(requirespnpm codegenafter edits). - Scenario & regression tests:
scenarios/(e.g.scenarios/test_codegen). Runpnpm codegenthenpnpm test. (You don't need to runpnpm codegenwhen changing librariefied code). - Quick-iteration trick when working with static code a lot: open
scenarios/test_codegen/generated, runpnpm rescript -w, adjust files, then copy changes back into templates.
Navigation cheat-sheet (useful for code search / AI):
- CLI entry point:
packages/cli/src/lib.rs - Command definitions:
packages/cli/src/commands.rs - Arg parsing:
packages/cli/src/cli_args/ - EVM helpers:
packages/cli/src/evm/ - Fuel helpers:
packages/cli/src/fuel/
All code below is generated into your project’s generated/ folder or located in the reusable library components in packages/envio.
Entry point:
Bin.res(inpackages/envio) – launched byenvio(thebin.mjsCLI). Responsibilities:- Calls the Rust CLI via NAPI (
Core.runCli) and decodes the single taggedCommandit returns (start/migrate/drop-schema, ornullfor Rust-only work likecodegen). - For
start: primes the config JSON (Config.prime), setscwd+ env vars, then callsMain.start(~migrate?). - For
migrate/drop-schema: primes config and callsMain.migrate/Main.dropSchema.
- Calls the Rust CLI via NAPI (
Main.start(inpackages/envio) is the indexer entry proper. Responsibilities:- Parses CLI flags (
--tui-off, etc.). - Loads runtime configuration (
Config.res). - Starts an Express server that serves
/metrics,/health, and the Development Console endpoints. - Initializes the Persistence layer (Postgres + Hasura) — a single
init()call that also handles~reset+upsertPersistedStatewhen~migrateis provided. - Loads user handler modules via
HandlerLoader.registerAllHandlers. - Loads initial state (to resume from a previous run).
- Spawns the
GlobalStateManager.reswhich orchestrates fetch & process loops.
- Parses CLI flags (
Configuration layer:
Config.res– strongly typed runtime config. Parsed from the JSON the Rust CLI embeds in theCommandpayload (primed viaConfig.primeinBin.res), or, when called outside the CLI (worker threads, test harnesses), lazy-loaded via thegetConfigJsonNAPI call.- Handler registrations (
indexer.onEvent(...)) land inHandlerRegister.resat module load time and are merged intoConfig.ton the nextConfig.load()(seebuildContractEvents). - Environment variables (
.env) feedEnv.res, consumed byPgStorage/ClickHousefor connection settings.
Persistence layer:
PgStorage.res– low-level Postgres adapter.Hasura.res– Hasura metadata integration.Persistence.res– high-level persistence façade.IO.res– commits batched entity changes using the persistence layer. (should be refactored to usePgStorage.resandPersistence.res)
Data sourcing (fetch side):
ChainManager.res– picks the next chain / block range to fetch. (manages multiple chain buffers)ChainFetcher.res– per-chain data source progress. (should be refactored in favorFetchState.resandSourceManager.res)FetchState.res– in-memory buffer and query bookkeeping. (per-chain)SourceManager.res– selects data source & handles fallbacks. (per-chain)
Event processing:
GlobalStateManager.res– top-level scheduler:NextQuery– fetch more events.ProcessEventBatch– execute handlers - usually 5000-event batches.
EventProcessing.res– runs handlers & builds in-memory entity updates.IO.res– flushes the batch to Postgres in a single transaction.
Monitoring & health:
Prometheus.res– exports metrics.- Health endpoints served by
Main.start.
Quick dev tips:
- Library code under
packages/envio→ hot-recompile withpnpm rescript -w. - Changes inside generated require rerunning
pnpm codegen(unless you are in the quick-iteration workflow described above).
Need to expose a startBlock setting for every contract address in config.yaml. The same pattern applies to most new config features.
- CLI (Rust) side
- Extend
config.yamlby changing the user-facing structs inhuman_config.rs(addstart_blockinside theaddressobject). - Run
cargo test– unit tests intest/configs/*should cover happy & failure cases. - Regenerate JSON-schemas for docs & validation:
make update-schemas. - Mirror the new field in
system_config.rs; convert it to an internal type that is easier for templates (e.g.,IndexingContract { address, start_block, abi, events }). - Pass the enriched contract structs into
hbs_templating/codegen_templates.rs. - Update the Handlebars context used by
templates/dynamic/codegen/src/RegisterHandlers.res.hbsso the generatedRegisterHandlers.resforwardsstartBlock. - Add the field to
Config.res. - Compress the two previous arrays passed to
FetchState.resmakefunction (static vs dynamic contracts) into a singlearray<IndexingContract>that already containsstartBlock. - Inside
FetchState.resinmakefunction create the initial block-partitions from thestartBlockof each contract. - Compile changes in
packages/envioby runningpnpm rescriptorpnpm rescript -wif you want to see changes live. - Test changes in
scenarios/test_codegenby runningpnpm codegenandpnpm test.
Navigate to the cli directory
cd packages/cli
To update all generated docs run
make update-generated-docs
To updated just the config json schemas
make update-schemas
Or to update just the cli help md file
make update-help
cd into folder of your choice and run
envio initThen choose a template out of the possible options
? Which template would you like to use?
> "Gravatar"
[↑↓ to move, enter to select, type to filter]
Then choose a language from JavaScript, TypeScript or ReScript to write the event handlers file.
? Which language would you like to use?
> "JavaScript"
"TypeScript"
"ReScript"
[↑↓ to move, enter to select, type to filter
This will generate the config, schema and event handlers files according to the template and language chosen.
Our greeter template config.yaml and schema.graphql is an example of how to layout a configuration file for indexing.
Please refer to the documentation website for a thorough guide on all Envio indexer features
Once you have configured the above files and deployed the contracts, the following can be used generate all the code that is required for indexing your project:
pnpm codegenOnce all the configuration files and auto-generated files are in place, you are ready to run the indexer for your project:
pnpm startTo view the data in the database open http://localhost:8080/console.
Admin-secret for local Hasura is testing.
Commands:
cd scenarios/test_codegen
pnpm codegen # Generate indexer code (only needed after config/schema changes)
pnpm test # Run all tests
pnpm mocha --grep "test name pattern" # Run specific testsDevelopment workflow:
- Changes to library code (
packages/envio): Runpnpm rescript -wfor live compilation - Changes to templates or config: Run
pnpm codegenthenpnpm testinscenarios/test_codegen