Architecture
Vue d'ensemble
Runeya est un monorepo TypeScript/JavaScript organisé en trois couches applicatives.
┌──────────────────────────────────────────────────────┐
│ Web UI (Vue 3) │
│ Vite 6 · PrimeVue 4 · Pinia · tRPC Client v11 │
└────────────────────────┬─────────────────────────────┘
│ tRPC (HTTP + WebSocket)
┌────────────────────────▼─────────────────────────────┐
│ Local Server (Express) │
│ tRPC v11 · JSON persistence │
└────────────────────────┬─────────────────────────────┘
│ HTTP Bearer + WebSocket
┌────────────────────────▼─────────────────────────────┐
│ Agent (Express) │
│ tRPC v11 · execa · stateless · ring buffer logs │
└────────────────────────┬─────────────────────────────┘
│ execa (child_process)
┌────────────────────────▼─────────────────────────────┐
│ Vos processus / vos applications │
└──────────────────────────────────────────────────────┘Stack technique
| Couche | Technologie | Version |
|---|---|---|
| Runtime | Node.js | 22+ |
| Package Manager | Yarn | 4 (Berry) |
| Build orchestrator | Turborepo | 2.x |
| Build (packages/apps) | tsup | Latest |
| Build (web UI) | Vite | 6 |
| API | tRPC | v11 |
| Validation | Zod | Latest |
| Frontend framework | Vue | 3.5 |
| UI Components | PrimeVue | 4 (Aura) |
| State management | Pinia | Latest |
| Test framework | Vitest | Latest |
| Process execution | execa | 9 (ESM) |
Packages du monorepo
runeya/
├── apps/
│ ├── agent/ # @runeya/apps-agent — exécution des processus
│ ├── cli/ # @runeya/apps-cli — binaire runeya (entrée unique)
│ ├── docs/ # @runeya/apps-docs — ce site de documentation
│ ├── server/ # @runeya/apps-server — serveur local
│ └── web/ # @runeya/apps-web — interface Vue 3
├── packages/
│ ├── shared/ # @runeya/packages-shared — schémas Zod partagés
│ ├── mcp-server/ # @runeya/packages-mcp-server — serveur MCP (Model Context Protocol)
│ ├── runner-native/ # @runeya/packages-runner-native — exécution de processus via execa
│ └── runner-docker/ # @runeya/packages-runner-docker — exécution de processus via Docker
└── scripts/
├── check-monorepo/ # Validation des conventions monorepo
├── generate-ci/ # Génération des workflows GitHub Actions
├── tsup-config/ # Config tsup partagée
└── retrigger-all-build/ # Déclenchement du rebuild en cascadePatterns architecturaux clés
tRPC v11 — Subscriptions async generator
typescript
// ✅ Pattern correct (tRPC v11)
mySubscription: protectedProcedure.subscription(async function* () {
for await (const event of eventEmitter) {
yield event
}
})
// ❌ Pattern obsolète (tRPC v10)
mySubscription: protectedProcedure.subscription(() => observable(...))Singletons serveur
Les services d'état sont des singletons module-level :
typescript
// apps/server/src/services/service-store.ts
export const serviceStore = new ServiceStore()
// apps/agent/src/process-manager.ts
export const processManager = new ProcessManager()Écriture atomique JSON
typescript
// 1. Écriture dans .tmp
await fs.writeFile(path + '.tmp', JSON.stringify(data))
// 2. Rename atomique
await fs.rename(path + '.tmp', path)Déploiement lazy (server → agent)
L'agent ne reçoit une configuration de service que lorsque ce service est démarré pour la première fois — jamais au boot du serveur.
typescript
// Dans process.start (server)
await agentClient.process.deploy(resolvedConfig) // idempotent
await agentClient.process.start({ id: serviceId })Communication server ↔ agent
HTTP (requêtes/commandes)
POST /api/trpc/process.deploy Authorization: Bearer <passphrase>
POST /api/trpc/process.start Authorization: Bearer <passphrase>
POST /api/trpc/process.stop Authorization: Bearer <passphrase>WebSocket (subscriptions temps réel)
ws://localhost:9546 — tRPC subscriptions (logs, status, metrics)
passphrase dans Authorization header (jamais query params)
ws://localhost:9546/health — health check dédiéAuthentification server → agent
Agent startup:
passphrase = crypto.randomBytes(32).toString('hex')
Server registration:
agents.json ← encrypt(passphrase, AES-256-GCM)
Each request:
Authorization: Bearer <passphrase>
Agent validation:
crypto.timingSafeEqual(expected, received)Schémas Zod — Source unique de vérité
Tous les types d'API sont définis dans packages/shared via Zod et inférés automatiquement par tRPC :
typescript
// packages/shared/src/schemas/service.schema.ts
export const ServiceConfigSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(128),
command: z.string().max(2048),
// ...
})
export type ServiceConfig = z.infer<typeof ServiceConfigSchema>
// → type inféré automatiquement, pas de duplication