Monorepo Structure

Monorepo Structure — Turborepo

All Foundation projects are organized as a Turborepo monorepo. This provides unified build orchestration, dependency deduplication, and a single repository for all application code, infrastructure, and configuration.

Package manager: always use pnpm. Never use npm or yarn for JavaScript/TypeScript dependencies. All workspace scripts, CI commands, and documentation assume pnpm. The root package.json declares "packageManager": "pnpm@latest" to enforce this.


Directory Layout

/
├── ui/
│   ├── web-app/     # Authenticated web application — Vite + TanStack (Router + Query, TypeScript)
│   ├── marketing/   # Marketing site — Next.js 14 (App Router, TypeScript)
│   ├── ios/         # Mobile app — Expo (React Native, TypeScript)
│   ├── types/       # Shared TypeScript type definitions
│   ├── components/  # Shared React component library
│   ├── common/      # Shared TypeScript "business logic" libraries
├── backend/
│   ├── api/         # FastAPI HTTP service (Python)
│   ├── worker/      # Restate durable-execution handlers — durable / agent workflows (Python)
├── schema/          # Drizzle Kit — schema, migrations, RLS policies (single migration history; TypeScript)
├── policies/        # Cerbos authorization policies — app-level PDP that pairs with Neon RLS (YAML)
├── infra/           # Pulumi infrastructure-as-code (TypeScript) — includes restate.ts (Restate Cloud wiring)
├── dbt/             # dbt project — BigQuery adapter (Neon → BigQuery)
├── .github/
│   ├── copilot-instructions.md   # AI assistant context
│   ├── skills/                   # Repository copilot skills
│   └── workflows/                # GitHub Actions CI/CD
├── turbo.json
├── package.json         # Root workspace manifest
├── pnpm-workspace.yaml  # pnpm workspace packages
├── biome.json           # BiomeJS config (JS/TS linting + formatting)
├── lefthook.yml         # Git hooks
└── README.md

turbo.json

Defines pipelines for all monorepo tasks:

PipelinePurposeKey Config
buildCompile all apps and packagesoutputs: [".next/**", "dist/**"]
testRun test suites across all workspacesNo outputs (side-effect only)
lintRun Biome (JS/TS) checksNo outputs
formatAuto-format all codeNo outputs
type-checkTypeScript strict type checkingNo outputs

Remote caching should be configured when available (left as a commented-out TODO in fresh scaffolds).

📥 Download turbo.json template


Root package.json

The root package.json serves as the workspace manifest:

{
  "private": true,
  "packageManager": "pnpm@latest",
  "scripts": {
    "dev": "turbo run dev",
    "build": "turbo run build",
    "test": "turbo run test",
    "lint": "turbo run lint",
    "format": "turbo run format",
    "type-check": "turbo run type-check"
  },
  "devDependencies": {
    "turbo": "latest",
    "@biomejs/biome": "latest",
    "lefthook": "latest"
  }
}

📥 Download root package.json template

Workspaces are declared in pnpm-workspace.yaml (pnpm does not use the workspaces field in package.json):

packages:
  - "ui/*"
  - "backend/*"
  - "schema"

Workspace Conventions

  • ui/ — Frontend applications and shared UI libraries. Contains deployable apps (web-app/ — Vite + TanStack applications frontend on Cloud Run; marketing/ — Next.js 14 marketing site on Cloud Run; ios/ — Expo React Native) and shared packages (types/, components/, common/). Each has its own package.json, build command, and deployment target.
  • backend/ — Backend services. Contains the FastAPI HTTP service (api/) and the Restate durable-execution handlers (worker/) for async / agent workflows. Each has its own pyproject.toml, build command, and deployment target.
  • schema/ — Drizzle Kit package: the single migration history, schema, and RLS policies (TypeScript, its own tsconfig.json). The Python backend reflects this schema — no Alembic.
  • policies/ — Cerbos authorization policies (YAML) — the app-level policy decision point that pairs with Neon RLS. Not part of the JS or Python workspace graph.
  • infra/ — Pulumi program (includes restate.ts, which wires the managed Restate Cloud environment — no self-hosted node). Has its own package.json and is not part of the Turborepo workspace graph (deployed separately).
  • dbt/ — dbt project (BigQuery adapter). Pure SQL + YAML, managed by Python tooling (not part of the JS workspace graph).

TypeScript Configuration

Foundation follows a one tsconfig.json per logical unit rule. A "logical unit" maps to each workspace package (deployable app or shared library package) — not to arbitrary subdirectories inside a package.

Logical UnitConfig FileCovers
Web appui/web-app/tsconfig.jsonVite + TanStack app + all source under ui/web-app/
Marketing siteui/marketing/tsconfig.jsonNext.js 14 app + all source under ui/marketing/
Shared packageui/<package-name>/tsconfig.jsonThat package's source (e.g., ui/components, ui/types, ui/common)
Mobile / Expo appui/ios/tsconfig.jsonExpo React Native app + all source under ui/ios/
Schemaschema/tsconfig.jsonDrizzle schema + migrations under schema/

Do not create additional tsconfig.json files in nested subdirectories within a workspace package. Each package gets a single tsconfig.json; extra nested configs fragment the type-checking graph, break IDE go-to-definition across package boundaries, and make it harder for tools like Turborepo and Biome to reason about the project.

Typical settings

Each tsconfig should extend a shared base (e.g., @tsconfig/strictest or a root-level tsconfig.base.json):

// ui/marketing/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "plugins": [{ "name": "next" }],
    "paths": { "@/*": ["./src/*"] }
  },
  "include": ["src", "next-env.d.ts", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}
// ui/ios/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "jsx": "react-native",
    "paths": { "@/*": ["./src/*"] }
  },
  "include": ["src", "app", "*.ts", "*.tsx"],
  "exclude": ["node_modules"]
}
// ui/components/tsconfig.json  (repeated pattern for every shared package)
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "dist",
    "declaration": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

If present, a root-level tsconfig.base.json at the repo root holds the common strict settings (strict: true, noUncheckedIndexedAccess, exactOptionalPropertyTypes, etc.) so each unit opts in by extending it rather than duplicating options.


Why Turborepo?

  1. Incremental builds. Only rebuilds what changed. Dramatically speeds up CI.
  2. Parallel execution. Tasks across workspaces run concurrently by default.
  3. Dependency graph awareness. Automatically determines build order based on workspace dependencies.
  4. Remote caching. (When enabled) Shares build artifacts across the team and CI.
  5. Simple configuration. Single turbo.json file, no complex plugin system.