UNPKG

@enspirit/emb

Version:

A replacement for our Makefile-for-monorepos

88 lines (87 loc) 2.97 kB
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]; } }