# SeedRegistry _(no submodules)_ ## Purpose `SeedRegistry` is the canonical in-memory store for `SeedDefinition` entries used by `@suaveplan/seed`. It owns insertion-order bookkeeping, duplicate-name detection, and environment-scoped query helpers, providing every other layer (builder, runner, fixtures) a single source of truth for "what seeds exist in this process". The registry is intentionally simple: no I/O, no concurrency primitives, no dependency resolution. Its sole invariants are (1) seed names are unique, (2) every read accessor returns a defensive shallow copy so caller mutations never leak into internal storage, and (3) duplicate registrations raise a structured `SeedError` with code `SEED.DUPLICATE_NAME` rather than a bare `Error`, so downstream catch sites can dispatch programmatically. ## Features - O(1) name-keyed insertion, lookup, and deletion backed by a `Map`. - Bulk registration via `registerMany` that fails fast on the first duplicate. - Environment-scoped query (`getForEnvironment`) where unrestricted seeds (undefined or empty `environments`) match every environment. - Defensive shallow copies returned from every read accessor (`get`, `getAll`, `getForEnvironment`). - `SeedError`-typed failure with stable `SEED.DUPLICATE_NAME` code and `seedName` discriminant. - Insertion-order iteration via `names` getter (Map semantics). - Implements `ISeedRegistry` so consumers can substitute alternative registry implementations in tests. ## Usage: Basic ```ts import { SeedRegistry } from "@suaveplan/seed/registry/registry"; const registry = new SeedRegistry(); registry.register({ name: "users", table: "users", data: [{ id: 1, email: "a@example.com" }], }); const seed = registry.get("users"); if (seed) { // seed.data is a defensive copy; safe to inspect without affecting storage. } console.log(registry.size); // 1 console.log(registry.names); // ["users"] ``` ## Usage: Advanced ```ts import { SeedRegistry } from "@suaveplan/seed/registry/registry"; import { SEED_CODES, SeedError, } from "@suaveplan/seed/error-codes/error-codes"; import type { SeedDefinition } from "@suaveplan/seed/types/common"; const registry = new SeedRegistry(); const defs: SeedDefinition[] = [ { name: "users", table: "users", data: [] }, { name: "fixtures", table: "fixtures", environments: ["test"], data: [], }, { name: "demo-data", table: "demo", environments: ["dev"], data: [], }, ]; try { registry.registerMany(defs); } catch (err) { if (err instanceof SeedError && err.code === SEED_CODES.DUPLICATE_NAME) { // Structured: err.seedName identifies the colliding entry. } throw err; } // Environment-scoped query: unrestricted + matching seeds only. const forTest = registry.getForEnvironment("test"); // → users (unrestricted) + fixtures (env=test) // Remove yields a boolean; missing keys are a no-op. registry.remove("demo-data"); registry.clear(); ``` ## API Reference | Member | Description | | ----------------------------------- | ----------------------------------------------------------------- | | `register(seed)` | Insert one seed; throws `SeedError(SEED.DUPLICATE_NAME)` on collision. | | `registerMany(seeds)` | Iterate and `register` each seed; aborts on first duplicate. | | `get(name)` | Defensive copy of the named seed, or `undefined`. | | `getAll()` | Defensive-copy array of every registered seed. | | `getForEnvironment(env)` | Subset matching `env` or unrestricted (`undefined`/empty). | | `has(name)` | `true` if the name is present, `false` otherwise. | | `remove(name)` | Delete; returns `true` if the entry existed. | | `clear()` | Remove every registered seed. | | `size` | Current entry count. | | `names` | Snapshot of registered names in insertion order. | ## Implementation Notes - Storage uses `Map` so insertion order is preserved by `names`, `getAll`, and `getForEnvironment`. - Defensive copies are *shallow* (`{ ...seed }`). Nested arrays (`environments`, `dependencies`, `upsertKeys`, `data`) are not cloned; they remain `readonly` on the `SeedDefinition` type, so callers cannot mutate them without an explicit cast. This matches the upstream reference behaviour and avoids deep-clone overhead during runner setup. - `getForEnvironment` filters the already-defensive `getAll()` result. The two-pass approach keeps the filter logic small and reuses the copy contract. - `register` throws *before* mutating the map, so a duplicate registration leaves the registry intact. `registerMany` therefore preserves a partial-success prefix when an entry mid-stream collides — consumers who need transactional semantics should snapshot `names` and roll back via `remove` after catching. - Error construction always includes `seedName` so downstream `instanceof SeedError` checks can dispatch without parsing the message string. - No internal concurrency control: the registry is intended for single-threaded use during process startup and test setup. Consumers requiring concurrent registration must serialise externally.