Retour à la documentation
Guide 02

Architecture

Simmetric Chat est un monorepo TypeScript modulaire avec 5 packages plus une application desktop Tauri. Le principe directeur est la séparation des frontières : chaque package a des responsabilités distinctes et ne communique avec les autres que par des …

5 min de lecture

Simmetric Chat est un monorepo TypeScript modulaire avec 5 packages plus une application desktop Tauri. Le principe directeur est la séparation des frontières : chaque package a des responsabilités distinctes et ne communique avec les autres que par des interfaces bien définies (HTTP ou le package shared).

Vue d'ensemble

text
┌──────────────┐   ┌──────────────┐   ┌──────────────┐
│   frontend   │   │    widget    │   │  src-tauri   │
│  React 19    │   │  Preact IIFE │   │  Desktop app │
│  Vite :5173  │   │  Express:3211│   │              │
└──────┬───────┘   └──────┬───────┘   └──────┬───────┘
    │  /api (JWT)     │  /api/internal/widget (X-Api-Key) │
    │                 │                                  │
    ▼                 ▼                                  │
┌────────────────────────────────────┐                      │
│              server                │◄──── Tauri usa il ────┘
│  Express :3000  (RBAC, agent, RAG) │     server via HTTP
└──────┬──────────────────┬──────────┘
    │ HTTP (COLLECTOR_SECRET)     │ HTTP (status callback)
    ▼                             ▼
┌──────────────┐             ┌──────────────┐
│   collector  │             │  PostgreSQL  │
│  Express:3210│             │  (Prisma)    │
│  parse/embed │             └──────────────┘
└──────────────┘
    │
    ▼
┌──────────────────────────┐
│  Vector DB (LanceDB/     │
│  Qdrant) + LLM (Ollama/  │
│  OpenAI/Anthropic)       │
└──────────────────────────┘

Les 5 packages

shared - le leaf node

Types, schema Zod et constantes. Zéro business logic, unique dépendance runtime zod. Il est importé par server, collector, frontend. Jamais importer depuis d'autres packages : la direction est toujours * → shared. Cela garantit l'ordre de build Turborepo et évite les cycles.

  • src/types/ - interfaces d'entités (User, Workspace, Chat, Document, Provider, Widget, BackupDestination, Archive, OcrJob, SynthesisRun, ...).
  • src/schemas/ - Zod (auth, chat, document, widget, mcpConnection, license, backup, archive, wiki, ocr, synthesis, provider). Un schema par domaine, inférence de type z.infer.
  • src/constants/ - permissions.ts (31 permissions RBAC), license.ts (11 feature flags enterprise ; les 8 flags commodity sont toujours actifs en Community).

server - le cœur (Express :3000)

API REST + RBAC + orchestrateur agent. Voir packages/server/CLAUDE.md.

  • src/routes/ - un fichier par domaine (auth, users, roles, projects, workspaces, documents, agent, chat, providers, apiKeys, eventLogs, analytics, backups, backupJobs, backupDestinations, webhooks, push, license, sso, templates, settings, mcpConnections, marketplace, widgets, internalWidget, health, ocr, archives, wiki, synthesis).
  • src/middleware/ - auth.ts (JWT + API key), rbac.ts (permissions + prévention IDOR), rateLimit.ts (200/min général, 10/min auth, 10/min probe - aucun limiter chat dédié : l'agent impose concurrence/budget/timeout en interne), license.ts (feature flag gating), widgetCors.ts (CORS dynamique par-origin).
  • src/services/ - authService, providerService (résolution du modèle + capability), hybridSearchService (RRF), ftsService (tsvector), encryptionService (AES-256-GCM), backupSchedulerService (Bree), eventLogService (+ dispatch webhook), dlpFilter, etc.
  • src/agent/ - orchestrator.ts (boucle ReAct), builtinSkills.ts (rag_search, workspace_memory, document_temp_process), llmStreaming.ts (Ollama/OpenAI/Anthropic), mcpClient.ts (connexions MCP outbound), mcpServer.ts (RAG comme tool MCP pour IDE).
  • src/config/env.ts - schema Zod pour toutes les env var ; process.exit(1) si invalides.

collector - microservice d'ingestion (Express :3210)

Il parle au serveur uniquement via HTTP (secret COLLECTOR_SECRET). Il n'importe pas Prisma ni les services du serveur : frontière critique pour le déploiement air-gap sur des nœuds séparés.

  • src/routes/ingest.ts - upload (multer), query, youtube, delete.
  • src/services/ - parser (PDF/DOCX/PPTX/XLSX/TXT/CSV/YouTube), chunker (RecursiveCharacterTextSplitter), embeddings (Xenova local / OpenAI), vectorStore (LanceDB / Qdrant).
  • Flux : server → collector POST /api/ingest/upload → parse → chunk → embed → store → callback collector PUT /api/documents/:id/status → le serveur met à jour la DB.

widget - chat embarquable (Express :3211)

Express CommonJS + bundle Preact IIFE. Authentifié auprès du serveur via X-Api-Key.

  • src/index.ts - factory createApp() ; static /widget/app.js + loader routes + API.
  • src/routes/ - chat (proxy SSE), session, config, loader, lead.
  • src/widget/ - composants Preact (12 composants) dans un iframe sandboxed.
  • Proxy SSE transparent : relais de bytes bruts server→iframe, zéro parsing.

frontend - SPA (Vite + React 19 :5173)

Pas de Next.js. Proxy /api → server.

  • State management 3-tier : TanStack Query (REST/CRUD), React Context (état UI), fetchEventSource + useState/useRef (SSE, PAS TanStack Query).
  • src/queries/ (22 hooks), src/contexts/, src/hooks/useChat.ts (streaming).
  • i18n : 8 langues (en, it, ru, de, fr, es, zh, pt).

src-tauri - application desktop

Wrapper Tauri qui empaquette le frontend comme application desktop ; il communique avec le serveur via HTTP (même API). Pensé pour le déploiement air-gap sur des machines sans navigateur.

Data flow principaux

Chat streaming (utilisateur interne)

text
Frontend useChat → POST /api/workspaces/:id/chat/stream (SSE)
→ requireWorkspaceAccess (RBAC/IDOR)
→ agent orchestrator (ReAct loop)
  ├─ resolveSkillsForChat (builtin + MCP pinned)
  ├─ rag_search (vector + tsvector → RRF merge) se serve
  ├─ llmStreaming (provider risolto: per-chat → workspace → global → ENV)
  └─ tool execution (sandboxed)
→ SSE: token | status | citations | done(modelUsed,providerUsed,mcpSources) | error

Résolution du modèle : override per-chat → workspace default → global default → ENV. Fallback graceful à 3 niveaux si le modèle n'est pas disponible.

Ingestion de documents

text
Frontend dropzone → POST /api/documents (server, multer)
→ server salva Document(status=pending) + forwarda file a collector
→ collector: parse → chunk → embed → store(vector) → ritorna chunks con text
→ collector callback PUT /api/documents/:id/status { status, chunkCount }
→ server scrive document_chunks + popola searchVector (tsvector) lato DB
→ frontend polla stato fino a completed

Widget chat (visiteur anonyme)

text
Pagina host → <script src="/widget/:id.js"> (loader)
→ iframe sandboxed → Preact app
→ POST /api/sessions (widget) → server crea WidgetSession (256-bit token, 24h)
→ GET /api/config/:widgetId (branding, trigger, lead)
→ user messaggio → POST /api/chat/:widgetId/stream (widget, x-session-token)
  → sessionMiddleware valida → pre-search RAG (whitelist workspaces, IDOR-safe)
  → proxy a server /api/workspaces/:id/chat/stream (X-Api-Key)
  → relay SSE byte verbatim → Preact parse token/citations/done/error

Frontières et règles architecturales

  1. `shared` est leaf - aucun import vers d'autres packages, même import type.
  2. Server ↔ collector = HTTP only - aucun import runtime cross-package ; le collector n'a pas d'accès direct à la DB. Cela permet de scaler le collector sur des nœuds séparés.
  3. Widget = service séparé - il ne partage pas de code avec le frontend React ; il utilise Preact pour un bundle réduit dans un iframe sandboxed.
  4. Prisma singleton - importe toujours depuis src/utils/prisma, jamais new PrismaClient().
  5. Soft delete universel - les entités supprimables utilisent deletedAt ; toutes les requêtes filtrent where: { deletedAt: null }.
  6. Config resolution - ALWAYS_READONLY (JWT_SECRET, DATABASE_URL, ports, URL) : ENV > Default. Toutes les autres clés : DB > ENV > Default (modifiables depuis l'UI).

Étendre l'architecture

  • Nouvelle entité/endpoint → ajouter le schema Zod dans shared, le modèle Prisma, la route dans server/src/routes/, le hook TanStack dans frontend/src/queries/.
  • Nouvelle skill agent → l'enregistrer dans server/src/agent/builtinSkills.ts (sandboxed, aucune exécution arbitraire de code).
  • Nouvelle langue → ajouter frontend/src/i18n/<lang>/translation.json + mettre à jour i18n-check.cjs (voir 10 - Development).
  • Nouveau provider LLM → étendre providerTypeSchema dans shared + handler dans server/src/agent/llmStreaming.ts + providerService.