@gomani/boundaries
v0.14.0
Published
A build-time module-boundary gate: enforce that modules talk only through their public interface, so a modular monolith stays carve-able into services later.
Maintainers
Readme
@gomani/boundaries
A build-time module-boundary gate. A modular monolith gives you most of the
independent-evolution benefit of microservices with none of the network tax — and it stays carve-able
into separate services along its seams, if and when one genuinely needs independent scaling. But only if
the seams are real. This enforces them the way @gomani/budget enforces size: a violation fails the build.
The convention
Modules live under app/modules/<name>/. Each has a public entry (index.ts) — the only legal way in.
Everything else in the module is private. Then:
deep-import— importing another module's internals (anything but its public entry) fails.forbidden-dependency— a module depending on another that isn't in its allow-list fails (when you declare one ingomani.boundaries.json).cycle— a dependency cycle between modules fails.
Imports within a module, imports of a module's public entry, and package imports are all fine.
gomani boundaries check # standalone (CI); exits non-zero on any violationThe gate also runs inside gomani build (like the budget + a11y gates). A project with no app/modules/
trivially passes — boundaries only bind once you declare modules.
Config (optional)
gomani.boundaries.json declares an allow-list per module; without it, any public cross-module import is
allowed (only deep imports and cycles are rejected):
{
"modules": {
"checkout": { "mayUse": ["catalog", "shared"] },
"catalog": { "mayUse": ["shared"] },
"shared": { "mayUse": [] },
},
"forbidCycles": true,
}Programmatic API
Zero dependencies; usable on any TS/JS project. The analyzer is pure (feed it files) so it is exhaustively
testable; checkBoundaries is the thin filesystem wrapper.
import { checkBoundaries, analyzeBoundaries, formatBoundaryReport } from '@gomani/boundaries';
const report = checkBoundaries(projectRoot); // scans app/**, loads gomani.boundaries.json
if (!report.ok) console.error(formatBoundaryReport(report)); // located, one line per violation
// Or purely, from in-memory files (no disk):
analyzeBoundaries({ files: [{ path: 'app/modules/a/index.ts', source }], config });Imports are extracted with a comment- and string-safe scanner (a commented-out or string-embedded
import '…' is never mistaken for a real one), and each violation is located to file:line:column.
