Files
agentic-dev/docs/architecture/dependency-flow.md

3.1 KiB

Dependency Flow

                    +-------------+   +-----------------+   +-----------+
                    | apps/web-   |   | apps/web-       |   | apps/cms  |
                    | next        |   | tanstack        |   |           |
                    +------+------+   +--------+--------+   +-----+-----+
                           |                   |                  |
        +------------------+--------------+    |                  |
        |                  |              |    |                  |
   +----v-----+      +-----v------+ +-----v----v---+      +-------v------+
   | core-api |      | core-trpc  | | feature      |      | core-cms     |
   |          |      |            | | packages     |      |              |
   +-----+----+      +-----+------+ +------+-------+      +-------+------+
         |                 |               |                      |
         |                 |               |                      |
         +--+-------+------+---------------+----+   +-------------+
            |       |                           |   |
       +----v---+ +-v---------+         +-------v---v---+
       | core-  | | core-ui   |         | core-shared    |
       | shared | |           |         |                |
       +--------+ +-----------+         +----------------+

  Boundary rules (enforced by eslint-plugin-boundaries):
    app             → app, core, feature, core-composition (any)
    feature         → core (any), but NOT other features, NOT app
    core            → core, but NOT feature, NOT app
    core-composition → core, feature subpath exports only (`/cms`, `/api`)
                      core-api  → @repo/<feature>/api
                      core-cms  → @repo/<feature>/cms

Concrete examples

Allowed:

// in apps/web-next
import { appRouter } from "@repo/core-api";
import { NextTrpcProvider } from "@repo/core-trpc/next";
import { bindProductionBlog } from "@repo/blog/di/bind-production";

// in packages/blog
import { slugifyIfMissing } from "@repo/core-shared/payload";

// in packages/core-api
import { blogRouter } from "@repo/blog/api";          // composition exception
import { router } from "@repo/core-shared/trpc/init"; // core → core fine

// in packages/core-cms
import { articles } from "@repo/blog/cms";             // composition exception

Disallowed:

// in packages/blog (cross-feature)
import { Article } from "@repo/marketing-pages";     // ❌ feature → feature

// in packages/blog (deep import past public exports)
import { articles } from "@repo/blog/src/integrations/cms/collections/articles"; // ❌ no-private

// in packages/core-shared
import { blogRouter } from "@repo/blog/api";         // ❌ core → feature

// in packages/core-trpc
import { someBlogThing } from "@repo/blog";          // ❌ core → feature (only core-api/core-cms have exception)

Three-layer enforcement

ESLint catches accidental cross-package imports at lint time. The package.json exports map blocks deep imports at module-resolution time. Workspace dependencies declarations make the package graph itself the source of truth — if you didn't declare it, you can't import it.