Initial Commit
This commit is contained in:
113
packages/persistence/database/seed/src/registry/registry.md
Normal file
113
packages/persistence/database/seed/src/registry/registry.md
Normal 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.
|
||||
Reference in New Issue
Block a user