Open-source feature management platform
# Agent Instructions for Unleash
This document provides AI coding assistants with context about the Unleash codebase. It is tool-agnostic and can be referenced by any AI assistant configuration.
## Project Overview
Unleash is an open-source feature flagging platform. This repository (OSS) is a monorepo containing:
- **Backend**: Node.js/TypeScript REST API (`/src`)
- **Frontend**: React/TypeScript single-page application (`/frontend`)
**Enterprise** extends OSS via a separate repository (`unleash-enterprise`) using a hook-based architecture. Enterprise does not fork OSS; it injects additional functionality through `preRouterHook`. If the user says that this is an enterprise feature, you need to ask where the enterprise repository is located and work across both this repository and the enterprise repository.
## Architecture
### Backend (`/src`)
The backend follows a **CSR (Controller, Service, Repository/Store)** pattern. We promote packages by feature, not by layer (see feature-based modules below), although the legacy components are still packed by layer:
- **Controllers** (`/src/lib/routes/`): Handle HTTP requests, validate input, delegate business logic to services, and demarcate transactions.
- **Services** (`/src/lib/services/`): Business logic layer, emit events, manage transactions
- **Stores** (`/src/lib/db/`): Data access layer using Knex query builder
**Feature-based modules** live in `/src/lib/features/` where each domain contains its own controller, service, store, and types. Examples: `feature-toggle`, `project`, `segment`, `change-request`, `release-plans`.
**Key Patterns**:
- **Audit-log**: Services emit typed events (`FeatureCreatedEvent`, etc.) for audit trails and read model updates
- **Transaction wrapper**: Use `withTransactional()` for atomic operations across services. The common pattern is initiating transactions at the controller level.
- **Fake implementations**: Every store/service has a Fake variant for testing (prefer over mocking)
- **Internal feature flags**: `flagResolver.isEnabled()` controls operational features
**Stack**: Express, PostgreSQL with Knex, TypeScript with ES modules
### Frontend (`/frontend/src`)
The frontend is a React SPA communicating with the backend via REST API.
- **Components**: `/frontend/src/component/` - React components organized by feature domain
- **Hooks**: `/frontend/src/hooks/` - 71+ custom hooks for data fetching and mutations
- **Contexts**: `AccessContext` (permissions), `UIContext` (toasts, theme)
**Key Patterns**:
- **Data fetching**: SWR-based `useApiGetter` hooks for GET requests with caching
- **Mutations**: `useApi` hook for POST/PUT/DELETE with error handling
- **Route gating**: Routes support `flag`, `enterprise`, and `configFlag` properties
- **Styling**: MUI `styled()` components with emotion, use `sx` for one-offs
**Stack**: React 18+, Vite, Material-UI (MUI), SWR for server state
## Enterprise Integration
Enterprise extends OSS through hooks without forking:
1. **Entry**: `unleash-enterprise/src/index.ts` wraps OSS `start()`/`create()`
2. **Hook**: `preRouterHook` runs after OSS init, before route binding
3. **Extension**: Adds 50+ services, 30+ stores, 50+ controllers
4. **Gating**: License middleware restricts enterprise features
**Enterprise-Only Features**: Change Requests, SSO (SAML/OIDC), Service Accounts, Signals & Actions, Insights, SCIM, Private Projects, Release Plans, Safeguards
**Combined Interfaces**: `IEnterpriseServices extends IUnleashServices`, `IUnleashEnterpriseStores extends IUnleashStores`
## Composition Root Pattern
All dependencies are wired at application startup, not scattered throughout the codebase. All services have a dedicated composition root function to stand up the service.:
**OSS Composition**:
- `/src/lib/db/index.ts` → `createStores()` instantiates all stores with Knex connection
- `/src/lib/services/index.ts` → `createServices()` instantiates all services with stores + config
- `/src/lib/server-impl.ts` → Orchestrates: DB → Stores → Services → App
**Enterprise Composition**:
- `enterprise/src/util/setup-stores.ts` → Creates enterprise stores, merges with OSS stores
- `enterprise/src/util/setup-services.ts` → Creates enterprise services with combined stores
- `enterprise/src/create-enterprise-routes.ts` → Wires everything in `preRouterHook`
**Why this matters**: Never `new` a service/store inline. Always receive dependencies through constructor injection. This enables testing with fakes and keeps the dependency graph explicit.
## Read Models vs Write Models
To avoid overloading stores with complex queries, we separate read and write concerns:
**Write Models (Stores)**: Handle CRUD operations on single entities
- Keep queries simple: insert, update, delete, getById
- Located in `/src/lib/db/` or feature directories
- Example: `FeatureToggleStore` handles basic feature CRUD
**Read Models**: Handle complex queries, aggregations, cross-domain queries and denormalized views
- Optimized for specific read use cases (dashboards, lists, reports)
- Located in feature `/read-models/` directories
- Example: `FeatureStrategiesReadModel`, `ProjectOwnersReadModel`, `FeatureSearchReadModel`
**When to use Read Models**:
- Query spans multiple tables with complex joins
- Need denormalized data for performance
- Building dashboard/overview endpoints
- Query doesn't map to a single entity's lifecycle
- You don't want to expose the entire write model and only need one value from another module
**Pattern**: Services coordinate between stores (writes) and read models (reads). Controllers call services or read models, never stores directly.
## Development Philosophy
We follow three core principles:
1. **Test code always** - We test our code and prefer automation over manual testing
2. **Write maintainable code** - Code is communication; clarity and readability are paramount
3. **Think before committing**
## Coding Standards
Detailed standards are documented as Architectural Decision Records (ADRs). They can be located:
- /contributing/ADRs/back-end/
- /contributing/ADRs/front-end/
- /contributing/ADRs/overarching/
Instead of `!!someVariable` prefer `Boolean(someVariable)`.
## Database Migrations
- Migrations live in `/src/migrations/`
- Never modify a merged migration; create a new one instead
- Each migration needs `up` and `down` methods
- Use `pnpm db-migrate create <name>` to create new migrations
## Testing
- **Backend**: Vitest + Supertest for API testing; fake stores for isolation
- **Frontend**: Vitest + Testing Library
- **E2E**: Cypress (`/frontend/cypress/`)
Run tests with:
```bash
pnpm test # All tests
pnpm test:frontend # Frontend only
pnpm test:backend # Backend only
```
## Critical Files Reference
### OSS Entry Points
| File | Purpose |
|------|---------|
| `/src/server.ts` | Main entry point |
| `/src/lib/app.ts` | Express app setup, middleware stack |
| `/src/lib/routes/index.ts` | Route registration |
| `/src/lib/services/index.ts` | Service factory |
| `/src/lib/db/index.ts` | Store factory |
| `/src/lib/types/index.js` | Importing types |
### Pattern References
| Pattern | Example Location |
|---------|-----------------|
| Controller | `/src/lib/features/feature-toggle/feature-toggle-controller.ts` |
| Service | `/src/lib/features/feature-toggle/feature-toggle-service.ts` |
| Store (write model) | `/src/lib/features/feature-toggle/feature-toggle-store.ts` |
| Read Model | `/src/lib/features/feature-search/feature-search-read-model.ts` |
| Composition Root | `/src/lib/services/index.ts` |
| API Hook (GET) | `/frontend/src/hooks/api/getters/useFeature/useFeature.ts` |
| API Hook (mutation) | `/frontend/src/hooks/api/actions/useFeatureApi.ts` |
| Fake Store | `/src/test/fixtures/fake-feature-toggle-store.ts` |