Voltar para a documentação
Guia 02

Arquitetura

Simmetric Chat é um monorepo TypeScript modular com 5 packages mais um app desktop Tauri. O princípio norteador é a separação de fronteiras: cada package tem responsabilidades distintas e se comunica com os outros apenas por interfaces bem definidas (HTT…

5 min de leitura

Simmetric Chat é um monorepo TypeScript modular com 5 packages mais um app desktop Tauri. O princípio norteador é a separação de fronteiras: cada package tem responsabilidades distintas e se comunica com os outros apenas por interfaces bem definidas (HTTP ou o package shared).

Visão geral

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

Os 5 packages

shared - o leaf node

Tipos, schema Zod e constantes. Zero business logic, única dependência runtime zod. É importado por server, collector, frontend. Nunca importe de outros packages: a direção é sempre * → shared. Isso garante a ordem de build do Turborepo e evita ciclos.

  • src/types/ - interfaces de entidades (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). Um schema por domínio, inferência de tipo z.infer.
  • src/constants/ - permissions.ts (31 permissões RBAC), license.ts (11 feature flags enterprise; os 8 flags commodity estão sempre ativos no Community).

server - o coração (Express :3000)

API REST + RBAC + orquestrador de agente. Veja packages/server/CLAUDE.md.

  • src/routes/ - um arquivo por domínio (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 (permissões + prevenção de IDOR), rateLimit.ts (200/min geral, 10/min auth, 10/min probe - nenhum limiter de chat dedicado: o agente impõe concorrência/budget/timeout internamente), license.ts (feature flag gating), widgetCors.ts (CORS dinâmico por origem).
  • src/services/ - authService, providerService (resolução de modelo + capability), hybridSearchService (RRF), ftsService (tsvector), encryptionService (AES-256-GCM), backupSchedulerService (Bree), eventLogService (+ dispatch de webhook), dlpFilter etc.
  • src/agent/ - orchestrator.ts (loop ReAct), builtinSkills.ts (rag_search, workspace_memory, document_temp_process), llmStreaming.ts (Ollama/OpenAI/Anthropic), mcpClient.ts (conexões MCP outbound), mcpServer.ts (RAG como tool MCP para IDE).
  • src/config/env.ts - schema Zod para todas as env var; process.exit(1) se inválidas.

collector - microserviço de ingestão (Express :3210)

Fala com o servidor somente via HTTP (segredo COLLECTOR_SECRET). Não importa Prisma nem serviços do servidor: fronteira crítica para o deploy air-gap em nós separados.

  • 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).
  • Fluxo: server → collector POST /api/ingest/upload → parse → chunk → embed → store → callback do collector PUT /api/documents/:id/status → o servidor atualiza o DB.

widget - chat embutível (Express :3211)

Express CommonJS + bundle Preact IIFE. Autenticado no servidor 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/ - componentes Preact (12 componentes) em iframe sandboxed.
  • Proxy SSE transparente: relay de bytes crus server→iframe, zero parsing.

frontend - SPA (Vite + React 19 :5173)

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

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

src-tauri - app desktop

Wrapper Tauri que empacota o frontend como app desktop; comunica com o servidor via HTTP (mesma API). Pensado para deploy air-gap em máquinas sem navegador.

Data flows principais

Chat streaming (usuário interno)

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

Resolução de modelo: override per-chat → workspace default → global default → ENV. Fallback graceful em 3 níveis se o modelo não estiver disponível.

Ingestão de documentos

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 (visitante anônimo)

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

Fronteiras e regras arquiteturais

  1. `shared` é leaf - nenhum import para outros packages, nem import type.
  2. Server ↔ collector = apenas HTTP - nenhum import runtime cross-package; o collector não tem acesso direto ao DB. Permite escalar o collector em nós separados.
  3. Widget = serviço separado - não compartilha código com o frontend React; usa Preact para bundle pequeno em iframe sandboxed.
  4. Prisma singleton - importe sempre de src/utils/prisma, nunca new PrismaClient().
  5. Soft delete universal - entidades removíveis usam deletedAt; todas as queries filtram where: { deletedAt: null }.
  6. Config resolution - ALWAYS_READONLY (JWT_SECRET, DATABASE_URL, portas, URL): ENV > Default. Todas as outras chaves: DB > ENV > Default (modificáveis pela UI).

Estender a arquitetura

  • Nova entidade/endpoint → adicione o schema Zod em shared, o modelo Prisma, a rota em server/src/routes/, o hook TanStack em frontend/src/queries/.
  • Nova skill de agente → registre em server/src/agent/builtinSkills.ts (sandboxed, sem execução arbitrária de código).
  • Novo idioma → adicione frontend/src/i18n/<lang>/translation.json + atualize i18n-check.cjs (veja 10 - Development).
  • Novo provider LLM → estenda providerTypeSchema em shared + handler em server/src/agent/llmStreaming.ts + providerService.