ADR-004: Monorepo with Bun Workspaces
Why Owlat uses a monorepo with Bun workspaces and Turborepo for fast installs, atomic cross-package changes, and cached CI.
- Status: Accepted
- Date: 2024-06-15
Context
Owlat consists of multiple interconnected pieces: a Nuxt frontend, a Convex backend, a documentation site, an email builder component, an email renderer library, shared types, and publishable SDKs. These packages share types and utilities extensively — for example, both the email builder and the email renderer depend on @owlat/shared for block type definitions.
The main options:
- Separate repositories — each package in its own repo with published npm packages for shared code. Clean boundaries but slow iteration: changing a shared type requires publishing, bumping versions, and updating consumers.
- Monorepo with npm/pnpm workspaces — colocated code with symlinked dependencies. Well-established but slower installs and more complex tooling.
- Monorepo with Bun workspaces — same workspace model but with Bun's fast install, native TypeScript execution, and Turbo for task orchestration.
Decision
Use a monorepo with Bun workspaces and Turborepo for task orchestration. All apps and packages live in a single repository under apps/ and packages/.
Structure:
apps/web — @owlat/web (Nuxt frontend)
apps/api — @owlat/api (Convex backend)
apps/docs — @owlat/docs (VitePress)
packages/shared — @owlat/shared (types, utilities)
packages/email-builder — @owlat/email-builder
packages/email-renderer — @owlat/email-renderer
packages/sdk-js — @owlat/sdk-js
packages/ui — @owlat/ui
Consequences
Enables:
- Atomic cross-package changes — a type change in
sharedand its consumers ship in one PR - Fast installs — Bun's linker is significantly faster than npm/pnpm for workspace resolution
- Turbo caching —
bun run ci:testonly re-runs tests for packages affected by changes - Single CI pipeline —
test.ymlruns all workspace tests in one workflow - Shared tooling — one
oxlintrc.json, oneoxfmtrc.json, one Turbo config - Changesets for SDK publishing —
@changesets/climanages versioning for publishable packages
Trade-offs:
- All contributors need Bun installed (not just Node)
- Large
bun.lockfile that can cause merge conflicts on dependency changes - Turbo adds a build orchestration layer that new contributors need to understand
- CI runs all workspace checks even for single-package changes (mitigated by Turbo caching)