# 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 + Turborepo boundaries): app → app, core, core-composition, feature, tooling feature → core, tooling core → core, core-composition, tooling core-composition → core, core-composition, feature, tooling tooling → tooling Composition exceptions: core-api → @repo//api (subpath only) core-cms → @repo//cms (subpath only) App-side feature subpaths (Plan 9): @repo/ — contracts (types, errors, schemas, IUseCase aliases, router type, constants) @repo//ui — UI artifacts (query builders, components) ``` ## Concrete examples Allowed: ```ts // 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"; import { signInInputSchema, type SignInInput } from "@repo/auth"; // contracts (Plan 9) import { articleBySlugQuery } from "@repo/blog/ui"; // queries (Plan 9) // 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: ```ts // 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 import { ArticleNotFoundError } from "@repo/blog"; // ❌ core → feature // (defineErrorMiddleware takes Error // constructors as args from features — // core-shared never imports them) // in packages/core-trpc import { someBlogThing } from "@repo/blog"; // ❌ core → feature (only core-api/core-cms have exception) // in apps (using the wrong subpath) import { articleBySlugQuery } from "@repo/blog"; // ❌ queries live on ./ui import { Article } from "@repo/blog/ui"; // ❌ types live on the root subpath ``` ## Enforcement strategy Three layers work in tandem: 1. **`package.json` dependencies** — if you didn't declare it, you can't import it 2. **`exports` map** — blocks deep imports; only public subpaths are accessible 3. **Two parallel automated checks** (both enforcing the same five-tag model): - **ESLint `eslint-plugin-boundaries`** runs at lint time, catching direct-import violations - **Turborepo `boundaries`** runs at build time, validating the entire workspace graph including transitive dependencies The two enforcement layers are independent but complementary. ESLint is stricter on per-import context (e.g., file-specific exemptions via `// @boundaries-ignore`), while Turborepo catches transitive issues that lint-time checking misses. Run `pnpm lint` and `pnpm turbo boundaries` in CI to catch all violations.