Baseleg Docs
Reference · Architecture / Package boundaries · Baseleg Docs

Package boundaries

Baseleg is a modular monolith with an explicit dependency direction.

Allowed dependencies

  • apps/*packages/application/*, packages/ui, and selected packages/domain/* (types/value objects only)
  • packages/application/*packages/domain/*, packages/shared/*
  • packages/infrastructure/*packages/domain/*, packages/shared/*, and packages/application/* type-only, to implement that layer’s repository/port interfaces (e.g. PersonRepositoryD1 implements PersonRepository) — this is the standard ports-and-adapters direction: the port is defined where it’s consumed (application), the adapter implements it. Never a runtime/value import — only import type.
  • packages/domain/*packages/shared/*
  • packages/uipackages/shared/*

Forbidden dependencies

  • Domain must not import infrastructure.
  • UI must not import infrastructure, outside the composition-root exceptions below.
  • Application must not import infrastructure implementations directly (use ports + wiring).
  • Astro pages/routes must not import Drizzle schema or repository implementations, outside the composition-root exceptions below.
  • Shared must not depend on domain/application/infrastructure/ui.
  • Infrastructure must not import runtime values from application — type-only port imports are the one allowed exception (see above).

Composition-root exceptions

A small, fixed set of files legitimately wire infrastructure into the app — this is where dependency injection actually happens, so it’s the one place apps/web is expected to know about concrete infrastructure. Enforced today via the exemption list in eslint.config.mjs; listed here so the two stay in sync:

  • apps/web/src/middleware.ts — constructs the DB connection and repository implementations, puts them on Astro.locals.
  • apps/web/src/lib/auth.ts — wires the auth infrastructure adapter.
  • apps/web/src/pages/api/logout.ts — auth session wiring for logout.
  • apps/web/src/env.d.ts — types App.Locals against the concrete types middleware.ts actually puts there; this is a type-only reflection of the composition root, not a new one.

Nothing outside this list may import infrastructure from apps/web. If you find yourself adding a new file to this list, that’s worth a second look — most pages should only ever need Astro.locals.repos/Astro.locals.db as already-wired values, or an application-layer use case, never a fresh infrastructure import of their own.

Good imports

// apps/web: call a use case
import { createPerson } from '@baseleg/application-people';
// application: use domain + shared
import type { PersonId } from '@baseleg/domain-people';
import { Result } from '@baseleg/shared-result';
// infrastructure: implement a repository using domain types
import type { PersonId } from '@baseleg/domain-people';
// infrastructure: implement an application-layer port (type-only)
import type { PersonRepository, ListPeopleFilter } from '@baseleg/application-people';

export class PersonRepositoryD1 implements PersonRepository { /* ... */ }

Bad imports

// domain must not import infrastructure
import { db } from '@baseleg/infrastructure-db';
// infrastructure must not import a runtime value from application — type-only only
import { registerPerson } from '@baseleg/application-people';
// UI/routes must not talk directly to DB/schema
import { peopleTable } from '../../../db/schema';
// UI must not import repository implementations
import { PeopleRepositoryD1 } from '@baseleg/infrastructure-repositories';