Skip to main content

What We Learned

The @stratt/graph package defines UnitRegistry 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:
  1. Static async factory: FileSystemRegistry.create(dir) scans the filesystem, parses all YAML files, and populates an in-memory Map<string, Unit>.
  2. Synchronous access: Once created, get(), all(), and has() 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.

Applicability

Use this pattern when: (a) a library defines a synchronous interface, (b) the CLI/consumer needs to populate it from async sources (filesystem, network), (c) the data set is small enough to hold in memory. For STRATT, this holds well — a prompt library of 100-1000 YAML files fits trivially in memory.