10 — Domain Packages
Section titled “10 — Domain Packages”This document defines what belongs in domain packages in this repository and records the first extraction pass from app/feature layers.
Related docs:
02-package-categories.md03-dependency-rules.md08-feature-internals.md
Purpose
Section titled “Purpose”Domain packages own reusable business logic that should be framework-light, runtime-light, and easy to test in isolation.
They are used by apps, server functions, and feature packages, but do not own UI composition, transport wiring, or Cloudflare runtime assembly.
What a domain package may own
Section titled “What a domain package may own”- domain policies and permission rules
- domain-level normalizers and mappers with business meaning
- value objects and domain-specific primitive transformations
- domain service contracts (interfaces) when runtime adapters are needed
- pure or mostly pure functions suitable for unit testing
What a domain package must not own
Section titled “What a domain package must not own”- React components, hooks, route files, or page composition
- TanStack Query
queryOptions,useQuery,useMutation - Cloudflare
Env, D1/KV binding assembly, worker entrypoints - request/response transport parsing and HTTP status wiring
Domain packages added in this pass
Section titled “Domain packages added in this pass”@platform/domain-auth
Section titled “@platform/domain-auth”Path: packages/domain-auth/
Owns reusable workspace access policies:
canUseAdminWorkspace(isSuperadmin)hasWorkspaceMembership(workspaceMemberships, requiredWorkspaces?)
Current consumers:
apps/dashboard/src/app/layouts/WorkspaceSwitcher.tsx(direct import, 2026-04-23)apps/dashboard/src/app/layouts/DashboardLayout.tsx(direct import, 2026-04-23)apps/dashboard/src/lib/routing/middleware.ts
Note: src/app/workspace/access.ts re-export shim deleted 2026-04-23 — consumers now import directly.
@platform/domain-people
Section titled “@platform/domain-people”Path: packages/domain-people/
Owns reusable people business rules:
- affiliation status normalization (
toAffiliationViewStatus) - affiliation policy helpers (
isExternalAffiliation,affiliationStatusLabel) - membership-filter mapping (
affiliationMembershipStatusesForFilter) - category-id normalization (
normalizeCategoryIds)
Current consumers:
apps/dashboard/src/server/functions/people/index.tsapps/dashboard/src/server/functions/people/row-mappers.tsapps/dashboard/src/features/people/pages/PersonDetailPage.tsxapps/dashboard/src/features/people/components/PeopleTable.tsxapps/dashboard/src/features/people/components/person-modal/PersonModalForm.tsxapps/dashboard/src/features/people/components/person-modal/form-utils.ts
Boundary model after extraction
Section titled “Boundary model after extraction”- feature/app layers compose UI and call APIs/queries
- server functions handle transport, persistence orchestration, and runtime concerns
- domain packages own reusable business rules and pure normalization
In other words: orchestration stays local; policy/normalization moves to domain.
Temporary exceptions left in place
Section titled “Temporary exceptions left in place”These remain intentionally outside domain packages in this pass:
-
People SQL projection and query-shape composition in
apps/dashboard/src/server/functions/people/index.ts. Reason: tightly coupled to D1 query structure and pagination. -
Person row DB hydration helpers with direct D1 access in
apps/dashboard/src/server/functions/people/row-mappers.ts(ensurePersonCategoriesExistOnShard, shard sync, assignment cleanup). Reason: repository/runtime concerns, not pure domain. -
Route-level auth redirects and request-context handling in
apps/dashboard/src/lib/routing/middleware.ts. Reason: transport/routing orchestration; only reusable policy decisions were extracted.
Migration rules for future work
Section titled “Migration rules for future work”When adding new business logic:
- If logic is reusable and mostly pure, place it in a domain package.
- If logic is route/UI/transport/runtime orchestration, keep it local.
- If logic needs D1/KV bindings directly, keep implementation in server/worker runtime, but define reusable policy/contract pieces in a domain package.
- Prefer incremental extraction by responsibility (policy, normalization, lifecycle transitions), not large all-at-once moves.