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
registerManythat fails fast on the first duplicate. - Environment-scoped query (
getForEnvironment) where unrestricted seeds (undefined or emptyenvironments) match every environment. - Defensive shallow copies returned from every read accessor (
get,getAll,getForEnvironment). SeedError-typed failure with stableSEED.DUPLICATE_NAMEcode andseedNamediscriminant.- Insertion-order iteration via
namesgetter (Map semantics). - Implements
ISeedRegistryso 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 bynames,getAll, andgetForEnvironment. - Defensive copies are shallow (
{ ...seed }). Nested arrays (environments,dependencies,upsertKeys,data) are not cloned; they remainreadonlyon theSeedDefinitiontype, so callers cannot mutate them without an explicit cast. This matches the upstream reference behaviour and avoids deep-clone overhead during runner setup. getForEnvironmentfilters the already-defensivegetAll()result. The two-pass approach keeps the filter logic small and reuses the copy contract.registerthrows before mutating the map, so a duplicate registration leaves the registry intact.registerManytherefore preserves a partial-success prefix when an entry mid-stream collides — consumers who need transactional semantics should snapshotnamesand roll back viaremoveafter catching.- Error construction always includes
seedNameso downstreaminstanceof SeedErrorchecks 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.