What We Learned
The @stratt/graph package definesUnitRegistry as a synchronous interface: get(uri) returns Unit | undefined, not Promise<Unit | undefined>. But the CLI loads units from the filesystem, which is inherently async. The solution is an “eager-load, sync-access” pattern:
- Static async factory:
FileSystemRegistry.create(dir)scans the filesystem, parses all YAML files, and populates an in-memoryMap<string, Unit>. - Synchronous access: Once created,
get(),all(), andhas()are pure Map lookups — zero I/O, zero Promises.
Why This Pattern Matters
When designing library APIs, synchronous interfaces are simpler to compose (no async infection). But consumers often need async I/O to populate them. The bridge pattern keeps the library clean and pushes the async boundary to the consumer’s factory.The Anti-Pattern It Avoids
Making the library interface async (get(uri): Promise<Unit | undefined>) would cascade Promises through every function that consumes the registry — resolveImports, buildDag, topologicalSort, computeBlastRadius. This would require rewriting the entire @stratt/graph package for what is fundamentally a consumer concern.