Skip to main content

When to Use

When any CLI command or tool needs to work with the full set of units on disk — validation (import resolution), run (dependency tree), publish (import checking). The registry is the bridge between the filesystem and the graph library.

The Interface

// From @stratt/graph — synchronous by design
interface UnitRegistry {
  get(uri: string): Unit | undefined;
  all(): Unit[];
  has(uri: string): boolean;
}

FileSystemRegistry

import { FileSystemRegistry } from "@stratt/cli/lib/registry";

// Async factory — scans filesystem, parses YAML, validates, populates Map
const registry = await FileSystemRegistry.create("packages/units");

// Synchronous access — pure Map lookups
const unit = registry.get("strat://dev/task/extract-logs@1.0.0");
const allUnits = registry.all();
const exists = registry.has("strat://dev/task/extract-logs@1.0.0");

// CLI-specific extensions
const path = registry.getPath("strat://dev/task/extract-logs@1.0.0");
const collision = registry.slugExistsInDomain("dev", "extract-logs");

How It Works

  1. create(dir) — async factory:
    • Glob scans dir/**/*.yaml
    • Parses each file with pinned YAML options (schema: core, version: 1.2)
    • Runs validateUnit() on each — only valid units enter the registry
    • Stores Map<string, Unit> (URI → unit) and Map<string, string> (URI → filepath)
    • Invalid files are silently skipped (malformed YAML, schema failures)
  2. get() / all() / has() — synchronous Map lookups

Why This Pattern

The @stratt/graph library (resolveImports, buildDag, topologicalSort, computeBlastRadius) requires synchronous UnitRegistry access. Making the interface async would cascade Promises through every graph function. The eager-load pattern keeps the library clean and pushes the async boundary to the consumer.

Performance Note

Scanning 100-1000 YAML files is sub-second on any modern filesystem. For larger corpora, a cached registry with file mtime tracking could skip re-parsing unchanged files. Not needed for Phase 1-2.