Files
2026-05-27 23:47:53 +01:00

5.6 KiB

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

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

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<string, SeedDefinition> 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.