@codependix/boundaries

中文文档
0.0.75发布于 2026/11/10MITTypeScriptES Module构建来源已验证近 30 天下载 9606

Boundary rule validation, forbid/allow rule enforcement, and cycle detection for Codependix graphs

原始使用文档
文档目录6 节
  1. Why not an ESLint rule
  2. The rule model
  3. Selectors
  4. The two halves, and the one seam between them
  5. Test
  6. 👔 Conformetry

🚧 Codependix Boundaries

npm

Evaluates declared rules against a graph codependix already built, and reports the edges and cycles that break them.

Codependix draws graphs at three levels and says nothing about whether their shape is allowed. This turns it from a documentation tool into a gate, at all three levels — Nx project edges, NestJS module edges, and file-level import edges, judged separately per language even though TypeScript and Python are exported together as one fileImports graph type.

codependix map --check boundaries --config configuration/codependix.config.ts

Why not an ESLint rule

@nx/enforce-module-boundaries reads import statements, one file at a time. Three facts it structurally cannot see:

  1. Implicit edges. An Nx implicitDependencies entry creates a project-graph edge with no import statement to flag. NeighborhoodEdge already carries implicit, so codependix holds a fact the lint rule has no way to reach.
  2. A NestJS module edge is a container fact, not a source fact. NestjsProjectService.exploreProject reports what the container resolved, so a dynamic module, a forRootAsync, or a conditionally composed import produces an edge no import statement expresses.
  3. A rule can be about the graph rather than the edge. A cycle, or a project's dependents, is a statement about a shape, and a rule that sees one file at a time cannot make one.

What this deliberately does not replace: the layered depConstraints graph in configuration/eslint.config.ts, which reports at the import site with a line number, and dependency-cruiser's no-circular, which is already this repository's file-cycle gate.

The two were checked against each other rather than assumed equivalent. All 32 of this repository's depConstraints translate mechanically — onlyDependOnLibsWithTags is an allow rule, notDependOnLibsWithTags is a forbid rule, and an empty onlyDependOnLibsWithTags is a forbid reaching everything — and with edges: { implicit: false } all 32 pass, exactly as ESLint reports them. Without that narrowing one more edge is reported: conformetry-examples → conformetry-cli, declared by an implicitDependencies entry with no import statement behind it. Its own depConstraint comment says that dependency should not exist, and ESLint has nothing to flag. Whether that is a finding or a false positive is the question edges exists to let a rule answer.

The rule model

Rules are declared in codependix.config.ts, keyed by the graph level that judges them — nestjsModules, nxProjects, and fileImports, which nests by language as { typescript, python } since no edge ever crosses the two. A level declaring no rule is never built at all, which is what keeps the gate affordable: judging the NestJS level means booting every container in preview mode.

Two shapes and three kinds — an access rule, written as from/to with the verdict running one way or the other, and an acyclic rule scoped by nodes:

KindReports
forbidAn edge whose source matches from and whose target matches to
allowAn edge leaving from for anywhere to does not claim
acyclicA cycle among the nodes nodes selects, defaulting to every node

An access rule may also narrow which edges it judges, rather than which nodes it selects:

edgesJudges
unsetevery edge — the stricter reading, and the right default for a rule about what a project may depend on
{ implicit: false }only edges backed by an import statement — exactly what an @nx/enforce-module-boundaries depConstraint sees
{ implicit: true }only edges an implicitDependencies entry declares and no import backs

Only the Nx level draws an implicit edge; every other level's edges are read as explicit.

Each rule carries a name and, optionally, a message saying why it exists. The message is appended to the generated sentence rather than replacing it, so no wording a configuration chooses can cost a report the rule that fired and both ends of what it fired on.

Selectors

One node shape covers three vocabularies, and every field is a list of globs matched with path.matchesGlob:

FieldMatchesAvailable at
idThe node's identifier — a project name, a file path, or a module class nameevery level
pathA workspace-relative project root, or a project-relative file pathnxProjects, fileImports.typescript, fileImports.python
projectThe Nx project a node belongs tonxProjects, fileImports.typescript, fileImports.python
tagsThe node's Nx tags; one tag matching is enoughnxProjects

Every field a selector states must match — the fields narrow each other. Within one field, one glob matching is enough. A selector naming a field its level does not carry matches nothing rather than everything: a path rule evaluated against a NestJS module graph, which carries no file paths, selects no module instead of silently selecting all of them. A selector stating no field at all is refused by the configuration schema, since it reads exactly like a typo.

The two halves, and the one seam between them

src/modules/boundaries/ evaluates rules and knows nothing about workspaces: a BoundaryGraph and a list of rules go in, and violations come out. src/modules/boundary-check/ is what turns a real workspace into those graphs — one adapter per level, plus the orchestration around them.

BoundaryGraph is the seam, and it is deliberately not any of the four real graph types. Each adapter flattens a Neighborhood, a NestjsModuleGraph, or an import graph into it, so rule evaluation never sees @nx/devkit, @nestjs/core, or typescript and could be lifted out again without touching a rule.

BoundaryCheckService.run walks the four levels in BOUNDARY_LEVEL_ORDER — cheapest first — and skips any level with no declared rules before building anything. That is what keeps the gate affordable: judging the NestJS level means booting every container in preview mode, and judging the TypeScript level means building a ts.Program per project. A workspace declaring only Nx rules pays for neither. Each level also isolates one project's failure to that project, so a container that will not boot is collected as a BoundaryCheckFailure while every other project is still judged.

Nothing here depends on @codependix/cli — the host calls in, never the reverse — and a rule in configuration/codependix.config.ts says so, so the two cannot close a cycle.

Test

nx run codependix-boundaries:vitest

👔 Conformetry

This project was generated from the nestjs-service-project conformetry template.

版本与发布记录

@codependix/boundaries 共发布 73 个版本,首次发布于 2026/10/05;过去 12 个月发布了 73 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.0.75latest2026/11/10今天
0.0.742026/11/091 天前
0.0.732026/11/091 天前
0.0.722026/11/091 天前
0.0.712026/11/082 天前
0.0.702026/11/073 天前

关于 @codependix/boundaries 的常见问题

@codependix/boundaries 是什么?

Boundary rule validation, forbid/allow rule enforcement, and cycle detection for Codependix graphs

如何安装 @codependix/boundaries?

在项目目录运行 npm install @codependix/boundaries,或使用 pnpm add / yarn add / bun add。

@codependix/boundaries 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@codependix/boundaries 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@codependix/boundaries 可以商用吗?

@codependix/boundaries 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@codependix/boundaries 的下载量有多少?

根据 npm 官方下载统计,@codependix/boundaries 在 2026/10/10 – 2026/11/08 这 30 天内被下载 9,606 次,日均约 320 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@codependix/boundaries 还在维护吗?

最新版本 0.0.75 发布于 2026/11/10(今天);至今共发布 73 个版本,其中过去 12 个月发布了 73 个;首次发布于 2026/10/05。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。