Skip to content

System Overview

Three Core Principles

  1. Modular #-architecture — the system grows through bounded contexts. Current build = 25 active contexts (see Bounded Contexts). No cross-context *.service imports — integration flows through the EventBus only (single whitelisted exception: the admin orchestration module).

  2. Federation-readiness — URI-based actor identifiers (acct:[email protected], Webfinger-style), event-sourcing of critical domains. ActivityPub v1 is live: WebFinger, Actor, Outbox, Inbox with HTTP-signature signing (see the federation context).

  3. Cost trajectory $30 → $500/month at 50 k MAU — one VPS (PM2 + nginx + Docker Postgres) + Expo + Cloudflare R2 + Centrifugo. No Convex / Firebase / Clerk / Supabase without an architectural ADR.

System Diagram

mermaid
graph TB
    subgraph Mobile["apps/mobile (Expo SDK 54 + RN 0.81, runtime 1.4.0)"]
        MobileApp[React Native App]
    end

    subgraph Admin["apps/admin (Vite + React 19 + Ant Design SPA)"]
        AdminPanel[Admin Panel]
    end

    subgraph API["apps/api (NestJS + Fastify) — 25 bounded contexts"]
        direction TB
        Identity[identity]
        Interests[interests]
        InviteTree[invite-tree]
        Social[social]
        Groups[groups]
        Guilds[guilds]
        Reputation[reputation]
        Messaging[messaging]
        AI[ai-assistant]
        Notifications[notifications]
        Posts[posts]
        Media[media]
        Discovery[discovery]
        Geo[geo]
        Economy[economy]
        Contribution[contribution]
        Wiki[wiki]
        Events[events]
        Deliberation[deliberation]
        Education[education]
        Projects[projects]
        Videos[videos]
        Audio[audio]
        Federation[federation]
        AdminMod[admin]
        Shared[shared/ auth · access · embeddings · realtime · flags · health]
    end

    subgraph Data["Data Layer (same VPS)"]
        PG[(Postgres 16 + pgvector, Docker :5433)]
        R2[(Cloudflare R2)]
        Centrifugo[Centrifugo WS]
    end

    subgraph Ext["External"]
        Anthropic[Anthropic Claude Sonnet 4.6]
        OpenAI[OpenAI Embeddings]
        Fediverse[Fediverse / ActivityPub]
    end

    Mobile -->|REST + JWT| API
    Admin -->|REST + JWT| API
    API -->|Prisma| PG
    API -->|Presigned URLs| R2
    API -->|Publish| Centrifugo
    Mobile -->|Subscribe| Centrifugo
    AI -->|Vercel AI SDK| Anthropic
    Shared -->|text-embedding| OpenAI
    Federation <-->|signed HTTP| Fediverse

Tech Stack

LayerTechnologyWhy
MobileExpo SDK 54 + RN 0.81 + New ArchitectureOne codebase, EAS OTA updates (runtimeVersion 1.4.0, expo-image disk cache)
Mobile UITamagui v2.0.0-rc.0+Compiler-driven, design tokens first-class
BackendNestJS + FastifyModules = bounded contexts
DatabasePostgres 16 + pgvector (Docker on the VPS, port 5433)Same version prod & dev, no Supabase
AuthCustom JWT via NestJS + invite flowNo Supabase Auth
Real-timeCentrifugo (self-hosted, same VPS)Scalable, federation-friendly
AIVercel AI SDK + Anthropic Claude Sonnet 4.6Streaming, provider-agnostic
EmbeddingsOpenAI (pgvector storage)Semantic feed ranking + search; graceful no-op without key
StorageCloudflare R2Zero egress cost for video
Web AdminVite + React 19 + Ant DesignStatic SPA served by nginx — no Next.js / Vercel
MonorepoTurborepo + pnpmSimplicity, remote cache
StateTanStack Query + Zustand + MMKVStandard, offline-first
PushExpo Push600 notif/sec, zero infra
ObservabilityPostHog + SentryFree tiers cover Alpha
HostingOne VPS regulus (PM2 + nginx + Docker Postgres)API + admin + docs + Centrifugo + DB on one box; Cloudflare TLS; mobile via EAS

Monorepo Structure

regulus/
├── apps/
│   ├── api/          NestJS modular monolith
│   │   ├── prisma/   Schema + migrations (75 migrations)
│   │   └── src/
│   │       ├── modules/    One folder = one bounded context (25)
│   │       └── shared/     Auth, access matrix, embeddings, realtime, flags, health
│   ├── mobile/       Expo SDK 54 (iOS + Android)
│   │   └── src/
│   │       ├── screens/    Full-screen views
│   │       ├── components/ Local UI components
│   │       └── lib/        API client, auth, storage
│   ├── admin/        Vite + React 19 + Ant Design SPA
│   │   └── src/
│   │       ├── pages/      One file = one admin resource
│   │       ├── components/ Admin-specific components
│   │       └── lib/        Queries, auth, API client
│   └── docs/         VitePress documentation (this site)
├── packages/
│   ├── domain/       Shared types, Zod schemas, domain events
│   ├── ui/           Shared React Native components
│   ├── design-tokens/ Tamagui theme tokens
│   └── ai-prompts/   Anthropic prompt templates
├── scripts/
│   └── ops/          VPS ops (nightly DB backup → R2, etc.)
└── docs/
    ├── adr/          Architecture Decision Records (ADR-001 … ADR-009)
    ├── architecture/ Bounded context map, audits
    └── setup/        Human setup guides

Cross-Cutting Concerns

Reputation, Moderation, Governance, and Economy must integrate through events, not direct service calls:

typescript
// WRONG — cross-context service import
@Injectable()
class ProfileService {
  constructor(private readonly reputationService: ReputationService) {}
}

// CORRECT — event-driven integration
@Injectable()
class ProfileService {
  constructor(private readonly eventBus: EventBus) {}

  async updateProfile(userId: string, data: UpdateProfileDto) {
    await this.eventBus.publish(new ProfileUpdatedEvent({ userId, changes: data }));
  }
}

// Reputation module listens
@OnEvent('profile.updated')
async handleProfileUpdated(event: ProfileUpdatedEvent) { /* ... */ }

Two documented exceptions to the strict boundary rules:

  • admin module — a legitimate orchestration layer; the ESLint no-restricted-imports cross-context rule explicitly ignores apps/api/src/modules/admin/** so it may call services from any context for admin-only operations.
  • FK on UserADR-009 (draft, docs/adr/ADR-009-user-fk-exception.md) legalises Postgres foreign keys targeting User(id) from any context (actor reference, GDPR cascade delete). FKs between two non-identity contexts remain forbidden — UUID columns only.

Regulus — invite-only social-knowledge platform