Resolver Package Implementation
Relevant source files
The following files were used as context for generating this wiki page:
Introduction
The resolver package is a critical component of the cheMD pipeline, responsible for transforming a raw parsed Abstract Syntax Tree (AST) into a fully resolved semantic model. Its primary purpose is to handle object indexing, reference resolution (such as @id.field or @meta.key), and template expansion. By decoupling syntax parsing from semantic resolution, the system ensures that document logic remains consistent regardless of the final output format.
In the cheMD architecture, the resolver sits between the parser and the various renderers. It consumes the output of parseChemd and produces a ResolvedChemdDocument which contains resolution outcomes for every reference token and expanded AST fragments for template usage. This stage also performs semantic validation, identifying issues like duplicate IDs or circular template dependencies.
Sources: chemd-v0.1-architecture.md:99-108, chemd-v0.1-spec.md:289-301
Core Resolution Logic
The resolver operates through a series of stages to ensure all semantic connections within a document are validated and linked.
Object and Template Indexing
Before any references can be resolved, the resolver builds a global index of all identifiable entities in the document. This includes:
- Object Index: A mapping of unique
#idstrings to their corresponding semantic blocks (e.g.,molecule,reaction,result). - Template Index: A mapping of template names to their definitions.
If the resolver encounters duplicate IDs or duplicate template names during this phase, it emits diagnostics such as E_DUPLICATE_ID or E_DUPLICATE_TEMPLATE.
Sources: packages/resolver/tests/resolver.test.ts:11-26, chemd-v0.1-architecture.md:237-241
Reference Resolution Workflow
The resolver iterates through markdown nodes and block fields to process reference tokens. References are resolved based on a specific priority order, especially when dealing with implicit aliases.
The diagram shows the logic flow for resolving different types of cheMD reference tokens.
Sources: chemd-v0.1-spec.md:468-485, packages/resolver/tests/resolver.test.ts:258-278
Alias Resolution Rules
For reserved implicit aliases like @reaction.* or @result.*, the resolver follows a strict hierarchy:
- Explicit Override: Values provided directly in a
useblock. - Template Bind: The source defined in the template's
bindfield. - Frontmatter Primary: Fallback to keys like
primary_reactionin the YAML metadata. - First Object Fallback: Optionally using the first object of that type found in the document.
Sources: chemd-v0.1-spec.md:470-480, packages/resolver/tests/resolver.test.ts:153-176
Template Expansion System
The resolver implements a "weak" template engine that expands use blocks into standard AST fragments before rendering occurs.
Expansion Process
When the resolver encounters a :::use <name> block, it:
- Retrieves the template definition by name.
- Classifies the
useblock's fields into alias overrides (matching template binds) or free parameters. - Deep-copies the template body.
- Substitutes references within the body using the provided parameters and resolved aliases.
- Replaces the
usenode in the document children list with the expanded fragments.
Sources: chemd-v0.1-spec.md:447-460, packages/resolver/tests/resolver.test.ts:74-106
Safety and Constraints
To prevent system abuse or infinite loops, the resolver enforces several constraints during expansion:
- Cycle Detection: It tracks the expansion stack and throws
E_TEMPLATE_CYCLEif a template refers to itself. - Expansion Limit: A maximum depth (e.g., 40 levels) is enforced to avoid Denial of Service (DoS) via deep nesting.
- Scope Isolation: Objects defined inside a template body are not validated as top-level document objects until they are expanded.
Sources: packages/resolver/tests/resolver.test.ts:215-231, packages/resolver/tests/resolver.test.ts:251-275
Diagnostic Reporting
The resolver is the primary source of semantic diagnostics. It attaches these to the document model for UI display or build-time warnings.
| Diagnostic Code | Severity | Description |
|---|---|---|
E_DUPLICATE_ID | Error | An ID is defined more than once in the document. |
E_MISSING_REQUIRED_FIELD | Error | A block (e.g., reaction) is missing a mandatory field like reactants. |
W_UNRESOLVED_REFERENCE | Warning | A reference like @id.field could not be found in the index. |
E_TEMPLATE_CYCLE | Error | Circular dependency detected between templates. |
E_UNKNOWN_TEMPLATE | Error | A use block references a template name that does not exist. |
E_INVALID_PRIMARY_REFERENCE | Error | A primary_* key in frontmatter points to a non-existent ID. |
Sources: packages/resolver/tests/resolver.test.ts:31-48, chemd-v0.1-architecture.md:387-402
Technical Implementation Details
API Signature
The package exposes a primary entry point for resolution:
// packages/resolver/src/index.ts
export function resolveChemd(
doc: ParsedChemdDocument,
ctx?: ResolveContext
): ResolvedChemdDocument;
Sources: chemd-v0.1-architecture.md:424-426
Data Structures
The resolver relies on the ChemdNode and ReferenceToken structures defined in @chemd/core. A resolved reference is updated with a resolution object:
{
"type": "reference",
"kind": "object_field",
"source": "res-main",
"field": "yield",
"raw": "@res-main.yield",
"resolution": {
"status": "resolved",
"value": "63%"
}
}
Sources: chemd-v0.1-spec.md:580-588, packages/core/tests/core.test.ts:32-55
Summary
The resolver package is essential for turning a static cheMD document into a dynamic, interconnected dataset. By managing complex tasks like template expansion and reference lookup, it allows authors to use high-level abstractions without complicating the underlying Markdown syntax. Its rigorous validation and diagnostic system ensure that semantic errors are caught early in the pipeline, maintaining the integrity of the chemical electronic lab notebook.