Zurück zur Dokumentation
Guide 02

Architektur

Simmetric Chat ist ein modulares TypeScript-Monorepo mit 5 Packages plus einer Tauri-Desktop-App. Das Leitprinzip ist die Trennung der Grenzen: Jedes Package hat eigene Zuständigkeiten und kommuniziert mit den anderen nur über wohldefinierte Schnittstell…

5 Min. Lesezeit

Simmetric Chat ist ein modulares TypeScript-Monorepo mit 5 Packages plus einer Tauri-Desktop-App. Das Leitprinzip ist die Trennung der Grenzen: Jedes Package hat eigene Zuständigkeiten und kommuniziert mit den anderen nur über wohldefinierte Schnittstellen (HTTP oder das shared-Package).

Überblick

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 nutzt den ─┘
│  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)       │
└──────────────────────────┘

Die 5 Packages

shared - der Leaf-Node

Typen, Zod-Schemas und Konstanten. Null Business Logic, einzige Runtime-Abhängigkeit zod. Es wird von server, collector und frontend importiert. Nie aus anderen Packages importieren: Die Richtung ist immer * → shared. Das garantiert die Turborepo-Build-Reihenfolge und verhindert Zyklen.

  • src/types/ - Entitäts-Interfaces (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). Ein Schema pro Domäne, Typinferenz über z.infer.
  • src/constants/ - permissions.ts (31 RBAC-Berechtigungen), license.ts (11 Enterprise-Feature-Flags; die 8 Commodity-Flags sind in Community immer aktiv).

server - das Herz (Express :3000)

REST-API + RBAC + Agent-Orchestrator. Siehe packages/server/CLAUDE.md.

  • src/routes/ - eine Datei pro Domäne (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 (Berechtigungen + IDOR-Prävention), rateLimit.ts (200/min allgemein, 10/min auth, 10/min probe - kein dedizierter Chat-Limiter: Der Agent erzwingt Nebenläufigkeit/Budget/Timeout intern), license.ts (Feature-Flag-Gating), widgetCors.ts (dynamisches CORS pro Origin).
  • src/services/ - authService, providerService (Modell-Auflösung + Capability), hybridSearchService (RRF), ftsService (tsvector), encryptionService (AES-256-GCM), backupSchedulerService (Bree), eventLogService (+ Webhook-Dispatch), dlpFilter usw.
  • src/agent/ - orchestrator.ts (ReAct-Loop), builtinSkills.ts (rag_search, workspace_memory, document_temp_process), llmStreaming.ts (Ollama/OpenAI/Anthropic), mcpClient.ts (ausgehende MCP-Verbindungen), mcpServer.ts (RAG als MCP-Tool für IDEs).
  • src/config/env.ts - Zod-Schema für alle Env-Variablen; process.exit(1) bei ungültigen Werten.

collector - Ingestion-Microservice (Express :3210)

Er spricht mit dem Server nur über HTTP (Secret COLLECTOR_SECRET). Er importiert kein Prisma und keine Server-Services: kritische Grenze für das Air-Gap-Deployment auf getrennten Knoten.

  • src/routes/ingest.ts - Upload (multer), query, youtube, delete.
  • src/services/ - parser (PDF/DOCX/PPTX/XLSX/TXT/CSV/YouTube), chunker (RecursiveCharacterTextSplitter), embeddings (Xenova lokal / OpenAI), vectorStore (LanceDB / Qdrant).
  • Ablauf: server → collector POST /api/ingest/upload → parse → chunk → embed → store → Collector-Callback PUT /api/documents/:id/status → der Server aktualisiert die DB.

widget - einbettbarer Chat (Express :3211)

Express CommonJS + Preact-IIFE-Bundle. Authentifiziert sich am Server über X-Api-Key.

  • src/index.ts - Factory createApp(); statisches /widget/app.js + Loader-Routes + API.
  • src/routes/ - chat (SSE-Proxy), session, config, loader, lead.
  • src/widget/ - Preact-Komponenten (12 Komponenten) in einem sandboxed iframe.
  • Transparenter SSE-Proxy: Relay roher Bytes server→iframe, null Parsing.

frontend - SPA (Vite + React 19 :5173)

Kein Next.js. Proxy /api → Server.

  • 3-Tier-State-Management: TanStack Query (REST/CRUD), React Context (UI-State), fetchEventSource + useState/useRef (SSE, NICHT TanStack Query).
  • src/queries/ (22 Hooks), src/contexts/, src/hooks/useChat.ts (Streaming).
  • i18n: 8 Sprachen (en, it, ru, de, fr, es, zh, pt).

src-tauri - Desktop-App

Tauri-Wrapper, der das Frontend als Desktop-App paketiert; kommuniziert per HTTP mit dem Server (dieselbe API). Gedacht für Air-Gap-Deployment auf Maschinen ohne Browser.

Zentrale Data Flows

Chat-Streaming (interner Nutzer)

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

Modell-Auflösung: Per-Chat-Override → Workspace-Default → Global Default → ENV. Graceful Fallback auf 3 Ebenen, wenn das Modell nicht verfügbar ist.

Dokument-Ingestion

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 (anonymer Besucher)

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

Grenzen und Architekturregeln

  1. `shared` ist Leaf - keine Importe in andere Packages, auch kein import type.
  2. Server ↔ Collector = nur HTTP - kein Runtime-Import über Packages hinweg; der Collector hat keinen direkten DB-Zugriff. Das erlaubt es, den Collector auf getrennten Knoten zu skalieren.
  3. Widget = separater Service - teilt keinen Code mit dem React-Frontend; nutzt Preact für ein kleines Bundle im sandboxed iframe.
  4. Prisma-Singleton - immer aus src/utils/prisma importieren, nie new PrismaClient().
  5. Universelles Soft Delete - löschbare Entitäten verwenden deletedAt; alle Queries filtern where: { deletedAt: null }.
  6. Config Resolution - ALWAYS_READONLY (JWT_SECRET, DATABASE_URL, Ports, URLs): ENV > Default. Alle anderen Keys: DB > ENV > Default (über die UI änderbar).

Die Architektur erweitern

  • Neue Entität/Endpoint → Zod-Schema in shared ergänzen, Prisma-Modell, Route in server/src/routes/, TanStack-Hook in frontend/src/queries/.
  • Neue Agent-Skill → in server/src/agent/builtinSkills.ts registrieren (sandboxed, keine beliebige Code-Ausführung).
  • Neue Sprache → frontend/src/i18n/<lang>/translation.json hinzufügen + i18n-check.cjs aktualisieren (siehe 10 - Development).
  • Neuer LLM-Provider → providerTypeSchema in shared erweitern + Handler in server/src/agent/llmStreaming.ts + providerService.