@enspirit/emb
Version:
A replacement for our Makefile-for-monorepos
88 lines (87 loc) • 2.97 kB
JavaScript
import { AmbiguousReferenceError, ItemCollisionsError, UnkownReferenceError, } from '../../errors.js';
export class EMBCollection {
items;
idField;
depField;
//
byId;
byName;
constructor(items, cfg) {
this.items = [];
this.idField = cfg.idField;
this.depField = cfg.depField;
this.byId = new Map();
this.byName = new Map();
// single-pass validation state
const seenIds = new Set();
const seenNames = new Set();
const dupIdReports = [];
const collisions = [];
for (const t of items) {
const id = t[this.idField];
const { name } = t;
// duplicate id?
if (seenIds.has(id)) {
const firstOwner = this.byId.get(id); // first occurrence already stored
dupIdReports.push(`id \`${id}\` used by \`${firstOwner.name}\` and \`${name}\``);
// keep the first owner in byId; do not overwrite
}
else {
this.byId.set(id, t);
seenIds.add(id);
}
// byName index
const list = this.byName.get(name);
if (list) {
list.push(t);
}
else {
this.byName.set(name, [t]);
}
// keep item list (stable order)
this.items.push(t);
// record name after checks so current name won’t collide with itself
seenNames.add(name);
}
if (dupIdReports.length > 0 || collisions.length > 0) {
const parts = [];
if (dupIdReports.length > 0) {
parts.push(`Duplicate ${String(this.idField)} values (${dupIdReports.length}):\n` +
dupIdReports.join('\n'));
}
if (collisions.length > 0) {
parts.push(`id↔name collisions (${collisions.length}):\n` +
collisions.join('\n'));
}
throw new ItemCollisionsError('Collision between items', parts);
}
}
/** All items (stable array iteration) */
get all() {
return this.items;
}
idOf(t) {
return t[this.idField];
}
depsOf(t) {
return (t[this.depField] ?? []);
}
matches(ref, opts) {
const idHit = this.byId.get(ref);
if (idHit) {
return opts?.multiple ? [idHit] : idHit;
}
const nameHits = this.byName.get(ref) ?? [];
if (nameHits.length === 0) {
throw new UnkownReferenceError(`Unknown reference \`${ref}\``, ref);
}
if (opts?.multiple) {
return nameHits;
}
if (nameHits.length > 1) {
const names = nameHits.map((t) => this.idOf(t));
throw new AmbiguousReferenceError(`Ambiguous reference \`${ref}\` matches multiple names`, ref, names);
}
return nameHits[0];
}
}