3.1 KiB
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.