Skip to main content

Mono Repo Structure

Mono Repository Structure

Overview

The MOOD MNKY ecosystem uses a mono repository architecture to organize all codebase components and ensure consistency across our digital products. This approach allows us to share code, maintain unified standards, and streamline development workflows.
Our mono repo is managed with Turborepo, providing parallel execution, incremental builds, and a structured development experience.

Repository Organization

Apps

Complete applications that are deployed independently

Packages

Shared libraries used across multiple applications

Infra

Infrastructure configuration and deployment resources

Data

Data models and schema definitions

Config

Configuration files shared across the codebase

Directory Structure

MNKY-REPO/
├── apps/
   ├── web/                 # Primary MOOD MNKY website (Next.js)
   ├── dojo/                # Dojo platform (Next.js)
   ├── admin/               # Admin dashboard (Next.js)
   ├── mobile/              # Mobile app (React Native)
   └── docs/                # Documentation (Mintlify)

├── packages/
   ├── ui/                  # Shared UI components
   ├── utils/               # Utility functions
   ├── hooks/               # Shared React hooks
   ├── api/                 # API client libraries
   ├── config/              # Shared configuration
   ├── supabase-client/     # Supabase client and utilities
   ├── analytics/           # Analytics implementation
   ├── scent-engine/        # Fragrance creation logic
   ├── agent-core/          # Shared agent infrastructure
   ├── token-economy/       # Token system implementation
   └── types/               # Shared TypeScript types

├── infra/
   ├── supabase/            # Supabase infrastructure
   ├── migrations/      # Database migration scripts
   ├── functions/       # Edge functions
   └── seed-data/       # Seed data for development
   ├── deployment/          # Deployment configurations
   └── docker/              # Docker configurations

├── data/
   ├── models/              # TypeScript data models
   └── schemas/             # Database schema definitions

├── agents/                  # Agent definitions and configurations
   ├── mood-mnky/           # MOOD MNKY agent
   ├── code-mnky/           # CODE MNKY agent
   └── sage-mnky/           # SAGE MNKY agent

├── content/                 # Content libraries and assets
├── docs/                    # Documentation (Mintlify)
├── scripts/                 # Repository scripts
├── config/                  # Shared configurations
├── .github/                 # GitHub workflows
├── turbo.json               # Turborepo configuration
└── package.json             # Root package.json

Application Structure

Each application follows a consistent structure to ensure predictability across the codebase:
  • Next.js Apps
  • React Native
apps/[app-name]/
├── src/
   ├── app/             # App router components and routes
   ├── components/      # React components
   ├── ui/          # Presentational components
   └── features/    # Feature-specific components
   ├── hooks/           # Custom React hooks
   ├── lib/             # Utilities and helpers
   ├── styles/          # Global styles
   └── types/           # TypeScript types
├── public/              # Static assets
├── tests/               # Tests
├── next.config.js       # Next.js configuration
├── package.json         # App-specific dependencies
└── tsconfig.json        # TypeScript configuration

Package Structure

Shared packages follow a consistent structure to make them easy to consume:
packages/[package-name]/
├── src/
   └── index.ts            # Main entry point
├── dist/                   # Built output (generated)
├── tests/                  # Tests
├── package.json            # Package metadata and dependencies
└── tsconfig.json           # TypeScript configuration

Infrastructure and Data Organization

Our infrastructure and data follow a clear organization:
  • Supabase
  • Agents
# Infrastructure configuration
infra/supabase/
├── migrations/           # Database migration scripts
   ├── 20240101000000_   # Timestamp-prefixed migrations
   └── README.md         # Migration documentation
├── functions/            # Edge and database functions
├── config.toml           # Supabase configuration
└── seed-data/            # Development seed data

# Data definitions
data/
├── schemas/              # SQL schema definitions
   ├── users.sql         # Users table schema
   └── products.sql      # Products table schema
├── models/               # TypeScript data models
   ├── user.model.ts     # User model definition
   └── product.model.ts  # Product model definition

Dependency Management

We use a combination of tools to manage dependencies in our mono repo:
1

Package Manager

pnpm is our primary package manager, chosen for its efficient handling of dependencies in a mono repo structure.
# Installing a dependency in a specific package or app
pnpm --filter @mood-mnky/web add react-query

# Installing a dependency for all packages and apps
pnpm add -w typescript

# Installing a workspace package as a dependency
pnpm --filter @mood-mnky/dojo add @mood-mnky/ui
2

Workspace Configuration

Our workspaces are configured in pnpm-workspace.yaml:
packages:
  - 'apps/*'
  - 'packages/*'
  - 'infra/*'
  - 'data'
And the root package.json:
{
  "name": "mood-mnky",
  "private": true,
  "scripts": {
    "dev": "turbo run dev",
    "build": "turbo run build",
    "test": "turbo run test",
    "lint": "turbo run lint"
  }
}
3

Build System

Turborepo orchestrates our build process, enabling incremental builds and task caching.
// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**", "public/dist/**"]
    },
    "test": {
      "dependsOn": ["^build"],
      "outputs": []
    },
    "lint": {
      "outputs": []
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

Supabase Integration

The Supabase integration follows our structured organization:
Database schemas are defined in SQL files within the data/schemas directory:
-- data/schemas/users.sql
CREATE TABLE public.users (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  email TEXT UNIQUE NOT NULL,
  full_name TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
  scent_profile JSONB,
  preferences JSONB
);

-- Add indexes for common query patterns
CREATE INDEX users_email_idx ON public.users (email);

-- Add RLS policies
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can view their own data" ON public.users
  FOR SELECT USING (auth.uid() = id);
  
CREATE POLICY "Users can update their own data" ON public.users
  FOR UPDATE USING (auth.uid() = id);
These schema files are the source of truth for table definitions and are used to generate migrations.
TypeScript data models provide type safety for database interactions:
// data/models/user.model.ts
import { Database } from './supabase.types';

export interface User {
  id: string;
  email: string;
  fullName: string | null;
  createdAt: string;
  updatedAt: string;
  scentProfile: ScentProfile | null;
  preferences: UserPreferences | null;
}

export interface ScentProfile {
  favoriteNotes: string[];
  intensity: number;
  preferences: {
    floral: number;
    woody: number;
    citrus: number;
    oriental: number;
    fresh: number;
  };
}

export interface UserPreferences {
  theme: 'light' | 'dark' | 'system';
  notifications: boolean;
  newsletter: boolean;
}

// Type-safe helper to convert from DB model to application model
export function fromDBUser(
  dbUser: Database['public']['Tables']['users']['Row']
): User {
  return {
    id: dbUser.id,
    email: dbUser.email,
    fullName: dbUser.full_name,
    createdAt: dbUser.created_at,
    updatedAt: dbUser.updated_at,
    scentProfile: dbUser.scent_profile,
    preferences: dbUser.preferences
  };
}
Migrations in the infra/supabase/migrations directory reflect changes to the database schema:
-- infra/supabase/migrations/20240401000000_create_users_table.sql
CREATE TABLE public.users (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  email TEXT UNIQUE NOT NULL,
  full_name TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);

-- infra/supabase/migrations/20240402000000_add_scent_profile.sql
ALTER TABLE public.users ADD COLUMN scent_profile JSONB;
ALTER TABLE public.users ADD COLUMN preferences JSONB;
Each migration is a timestamped file that can be applied in sequence to build the database structure.
The packages/supabase-client package provides a consistent way to interact with Supabase:
// packages/supabase-client/src/index.ts
import { createClient } from '@supabase/supabase-js';
import type { Database } from '@repo/types';

let supabaseInstance: ReturnType<typeof createClient> | null = null;

export function getSupabaseClient() {
  if (!supabaseInstance) {
    const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
    const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
    
    if (!supabaseUrl || !supabaseKey) {
      throw new Error('Missing Supabase environment variables');
    }
    
    supabaseInstance = createClient<Database>(supabaseUrl, supabaseKey);
  }
  
  return supabaseInstance;
}

export function getSupabaseServerClient() {
  const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
  const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
  
  if (!supabaseUrl || !supabaseServiceKey) {
    throw new Error('Missing Supabase environment variables for server client');
  }
  
  return createClient<Database>(supabaseUrl, supabaseServiceKey);
}
This client is used across all applications to ensure consistent API access.

Development Workflow

Developing in our mono repo follows these principles:
  1. Task Isolation: Work on isolated features or bugfixes in dedicated branches
  2. Local Development: Use Turborepo’s filtering to run only relevant applications and packages
  3. Dependency Management: Add shared code to appropriate packages rather than duplicating across apps
  4. Testing: Write tests for shared packages to ensure reliability across applications
  5. Documentation: Document shared components and utilities to facilitate reuse

Setting Up Supabase for Development

To set up Supabase for local development:
1

Install the Supabase CLI

npm install -g supabase
2

Start Supabase Local Development

# Navigate to the infrastructure directory
cd infra/supabase

# Start Supabase local development
supabase start
This will start a local Supabase instance with PostgreSQL, Auth, Storage, and other services.
3

Apply Migrations

# Apply all migrations to the local database
supabase db reset
This will apply all migrations in the migrations directory and reset the database to a clean state.
4

Seed Data (Optional)

# Seed the database with test data
supabase db reset --seed-data
This will populate the database with seed data for development and testing.

Typical Development Commands

# Start development server for a specific app
pnpm --filter @mood-mnky/web dev

# Start development servers for multiple apps
pnpm --filter "@mood-mnky/web..." dev

# Build a specific package and its dependencies
pnpm --filter "@mood-mnky/ui..." build

# Run tests for all packages and apps
pnpm test

# Create a new migration
cd infra/supabase
supabase migration new add_new_feature

CI/CD Integration

Our continuous integration pipeline is configured to handle our mono repo structure efficiently:
1

Change Detection

The pipeline detects which packages and apps have changed to determine what needs to be built and tested.
2

Dependency Analysis

It analyzes dependencies to ensure that changes to shared packages trigger builds and tests for dependent apps.
3

Parallel Execution

Build and test jobs run in parallel for independent packages and apps to minimize pipeline time.
4

Deployment

After successful tests, affected applications are deployed to their respective environments.

Best Practices

Code Organization

  • Keep apps and packages focused on specific concerns
  • Extract shared code into dedicated packages
  • Maintain consistent file structure across apps

Dependencies

  • Minimize dependencies between packages
  • Avoid circular dependencies
  • Properly declare all dependencies in package.json

Performance

  • Use Turborepo’s caching for faster builds
  • Implement code splitting within apps
  • Optimize package sizes

Testing

  • Write tests for shared packages
  • Use mocks for complex dependencies
  • Ensure tests run in isolation

Documentation

  • Document package APIs and component usage
  • Maintain README files for all packages
  • Use TypeScript for type safety

Versioning

  • Follow SemVer for all packages
  • Document breaking changes
  • Keep package versions in sync when appropriate

Resources


For questions about our mono repo structure, please contact the MOOD MNKY development team.