ADR-0005: Package Boundaries and Dependency Graph β
Last Updated: 2026-01-08 Status: Active Context: Decksmith
Context β
The monorepo structure (ADR-0002) allows code sharing, but without clear boundaries, packages become tightly coupled and the architecture degrades into a monolith.
Critical architectural rules from CLAUDE.md:
- Prisma models never exposed outside API
- All boundaries use DTOs from
packages/schema - Domain logic only in
packages/domain apps/*orchestrate, don't implement business rules- No circular dependencies
This ADR defines the dependency graph and enforcement strategy to maintain these boundaries.
Current Decision β
We enforce unidirectional dependency flow with explicit package boundaries:
Package Dependency Rules β
Core Contracts (zero dependencies):
packages/schema: Zod DTOs only. No dependencies (only Zod as peer dependency)packages/tokens: Design primitives (colors, spacing, fonts). No dependenciespackages/config: Shared tooling configs. DevDependencies only
Domain Logic (depends on contracts):
packages/domain: Pure functions. Depends only onschema- No I/O, no HTTP, no database, no React
Infrastructure Packages (depends on contracts):
packages/db: Prisma schema and client. Depends only onschema- Prisma models are never exported outside this package
- Only consumed by
apps/apiandapps/worker
packages/scryfall: External API client. Depends only onschemapackages/pdf: PDF generation. Depends ondomainandschema- Used only by
apps/worker
- Used only by
Client Packages (depends on contracts and clients):
packages/api-client: Typed HTTP client. Depends onschemapackages/query: TanStack Query hooks. Depends onapi-clientandschema
UI Packages (depends on design tokens):
packages/web-ui: React components. Depends ontokenspackages/native-ui: React Native components. Depends ontokens
Application Dependency Rules β
apps/web: Web SPA- Can use:
schema,api-client,query,web-ui,tokens - Cannot use:
db,domain,scryfall,pdf,native-ui
- Can use:
apps/mobile: Mobile app (future)- Can use:
schema,api-client,query,native-ui,tokens - Cannot use:
db,domain,scryfall,pdf,web-ui
- Can use:
apps/api: HTTP API- Can use:
schema,domain,db,scryfall - Cannot use:
query,api-client,pdf,*-ui,tokens - Critical: Prisma models never leak outside this app
- Can use:
apps/worker: Background jobs (PDF generation)- Can use:
schema,domain,pdf,scryfall,db - Cannot use:
query,api-client,*-ui,tokens
- Can use:
Dependency Graph Visualization β
Layer 1: Contracts (zero dependencies)
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β schema β β tokens β β config β
ββββββββ¬βββββββ ββββββββ¬βββββββ βββββββββββββββ
β β
β β
Layer 2: Domain & Infrastructure
β β
ββββββ΄βββββ¬βββββββββββ¬β΄ββββββββββ¬βββββββββββ
β β β β β
βββββΌββββ ββββΌβββ ββββββΌβββββ βββββΌβββββ ββββΌβββββββ
βdomain β β db β βscryfall β βapi- β βweb-ui/ β
β β β β β β βclient β βnative-uiβ
βββββ¬ββββ βββββββ βββββββββββ ββββββ¬ββββ βββββββββββ
β β
β β
Layer 3: Derived Services
β β
βββββΌββββ βββββΌββββ
β pdf β β query β
βββββ¬ββββ βββββ¬ββββ
β β
β β
Layer 4: Applications
β β
βββββββββββββββββββββββββββββββββΌβββββββββββ
β β β
βββββΌββββ βββββΌββββ βββββΌβββββ
β workerβ β web β β mobile β
βββββββββ βββββββββ ββββββββββ
β
β (also uses db)
β
βββββΌββββ
β api β
βββββββββEnforcement Strategy β
Immediate (this PR):
- Use pnpm
workspace:*protocol in all internal dependencies - Document rules in this ADR (living document)
Near-term (future PR):
- Add
@manypkg/clito validate dependency graph - Add custom script to check for circular dependencies
Long-term (as needed):
- ESLint plugin to enforce import rules (e.g.,
@typescript-eslint/no-restricted-imports) - Automated PR checks for boundary violations
Rationale β
Why These Boundaries β
packages/schemahas zero dependencies- DTOs are contracts, not implementations
- Must be stable, minimal, focused
- Both frontend and backend depend on it β it cannot depend on either
packages/domainonly depends onschema- Pure domain logic (deck validation, print layout, card parsing)
- No I/O β fully testable without mocks
- Aligns with "Deterministic behavior" and "Separation of concerns"
Prisma models never leave
packages/db- Prisma models contain DB-specific types (
Decimal,Json, relations) - Exposing them couples consumers to Prisma implementation
- Instead,
apps/apiconverts Prisma models βschemaDTOs at the boundary - Aligns with "All boundaries use DTOs"
- Prisma models contain DB-specific types (
apps/apiandapps/workercan usedb, butapps/webcannot- Frontend cannot directly access database (security, architecture)
apps/webusesapi-clientto talk toapps/api- Enforces client-server boundary
packages/pdfonly used byapps/worker- PDF generation is CPU-intensive, belongs in background jobs
- Not needed in
apps/web(web calls worker via API) - Aligns with "PDF generation only in worker"
UI packages (
web-ui,native-ui) depend only ontokens- UI components should not know about business logic
- Only depend on design primitives (colors, spacing)
- Makes components reusable and testable
Why Unidirectional Flow β
Data flows one direction: contracts β domain β apps
schema β domain β api β web
β β β
βββββ pdf ββββ workerBenefits:
- No circular dependencies: Impossible by design
- Predictable changes: Changing
schemaaffects downstream, never upstream - Easy testing: Pure packages (
schema,domain) have no dependencies, easy to test - Clear mental model: Dependencies always point toward apps, never back
This aligns with "Minimal coupling" and "Clarity over cleverness".
Trade-offs β
Benefits:
- Clear architecture: Dependency graph makes system structure obvious
- Prevents coupling: Rules catch violations before they become tech debt
- Easier refactoring: Changes are localized by boundaries
- Testability: Pure packages (
domain,schema) are trivial to test - Enforces architectural rules: CLAUDE.md principles are checked, not just documented
Costs:
- Requires discipline: Developers must understand and respect boundaries
- May feel restrictive: "Why can't I just import this directly?"
- Indirection overhead: DTOs at boundaries add conversion code
- Initial friction: Setting up boundaries takes time upfront
Risks:
- Workarounds: Developers may bypass boundaries if rules feel too strict
- Mitigation: Document why each rule exists in this ADR
- Boundary erosion: Over time, boundaries may weaken without enforcement
- Mitigation: Add automated checks (
@manypkg/cli, ESLint plugins)
- Mitigation: Add automated checks (
- Over-abstraction: DTOs at every boundary can add boilerplate
- Mitigation: Only use DTOs at system boundaries (API, DB), not internal package boundaries
Evolution History β
2026-01-08: Initial decision β
- Defined dependency graph for all 11 packages and 4 apps
- Established unidirectional flow (contracts β domain β apps)
- Documented enforcement strategy (pnpm workspace protocol, future tooling)
References β
- CLAUDE.md - Architectural rules (non-negotiable)
- Hexagonal Architecture - Influence for domain/infrastructure separation
- @manypkg/cli - Monorepo validation tool
- Related ADR: ADR-0002 (Monorepo structure establishes workspace protocol)