Skip to content

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

CoucheTechnologieVersion
RuntimeNode.js22+
Package ManagerYarn4 (Berry)
Build orchestratorTurborepo2.x
Build (packages/apps)tsupLatest
Build (web UI)Vite6
APItRPCv11
ValidationZodLatest
Frontend frameworkVue3.5
UI ComponentsPrimeVue4 (Aura)
State managementPiniaLatest
Test frameworkVitestLatest
Process executionexeca9 (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 cascade

Patterns 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

Publié sous licence MIT.