Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,113 @@
# SeedRegistry
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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<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.