xPulse
🇩🇪 DE

@xpulse/controller – Component Spec

Status: CONCEPT · Erstellt März 2026 Übergeordnet: GLOBAL_concept-ecosystem.md, GLOBAL_concept-web.md


Übersicht

Controller-Discovery und Basis-Klasse für das xPulse MVC Stack.

Eine Aufgabe: Controller finden, Routes ableiten, Views aufrufen – nicht mehr.

Discovert src/controllers/**/*.js, leitet Routes aus Dateipfad und Methodenname ab, meldet sie bei @xpulse/router an und stellt die Controller Basis-Klasse bereit.


Dependencies

@xpulse/controller
├── @xpulse/router ← Routes anmelden
├── @xpulse/template ← this.render() delegiert hierhin
└── @xpulse/event ← auf template:render:before lauschen

API

Initialisieren

import controller from '@xpulse/controller';
await controller.init();

Discovert rekursiv src/controllers/**/*.js, leitet Routes ab und registriert sie bei @xpulse/router. Wird von @xpulse/app aufgerufen.


Filesystem-Routing

Der Dateipfad bestimmt die Basis-Route des Controllers. Der Methodenname bestimmt die Sub-Route.

Regeln

Mapping-Beispiele

src/controllers/
index.js → IndexController
index() → GET /
info.js → InfoController
index() → GET /info
team() → GET /info/team
bla() → GET /info/bla
tool/
chat.js → ChatController
index() → GET /tool/chat
guide() → GET /tool/chat/guide
contact.js → ContactController
index() → GET /contact
submit() → POST /contact

Response Object

Alle Controller-Methoden geben ein Response Object zurück – @xpulse/http ist der einzige der es tatsächlich sendet. Der Controller beschreibt die Response, nie sendet er sie direkt.

// Intern – alle Helper geben dieses Format zurück:
{
status: 200,
headers: { 'Content-Type': 'text/html' },
body: '<html>...',
}

Controller Basis-Klasse

import { Controller } from '@xpulse/controller';
// Datei: src/controllers/info.js
// Basis-Route: /info
export default class InfoController extends Controller {
define() {
this.overrideRoute('team', '/info/ueber-uns');
this.overrideMethod('submit', 'POST');
}
// → GET /info
async index() {
return this.render('info/index', {
title: 'Info',
});
}
// → GET /info/bla
async bla() {
return this.render('info/bla', {
title: 'Bla',
items: ['a', 'b', 'c'],
});
}
// → GET /info/ueber-uns (via overrideRoute)
async team() {
return this.render('info/team', {
title: 'Über uns',
});
}
}

POST + JSON Beispiel

// Datei: src/controllers/contact.js
export default class ContactController extends Controller {
define() {
this.overrideMethod('submit', 'POST');
}
// → GET /contact
async index() {
return this.render('contact/index', {
title: 'Kontakt',
});
}
// → POST /contact
async submit() {
const { name, message } = this.req.body;
if (!name || !message) {
return this.render('contact/index', {
title: 'Kontakt',
error: 'Bitte alle Felder ausfüllen.',
});
}
return this.redirect('/contact/danke');
}
// → GET /contact/api – JSON Response ohne Template
async api() {
return this.json({ status: 'ok' });
}
}

Controller Basis-Klasse API

Response Helper – geben immer ein Response Object zurück:

Methode Beschreibung
this.render(view, data) Template rendern via @xpulse/template → Response Object
this.response(html, options?) Eigenes HTML → Response Object, kein Template
this.json(data, options?) JSON → Response Object, kein Template
this.redirect(url) Redirect → Response Object (301)

Route Overrides – nur in define():

Methode Beschreibung
this.overrideRoute(method, path) Route für eine Methode überschreiben
this.overrideMethod(method, httpMethod) HTTP-Methode für einen Handler überschreiben

Request Zugriff:

Eigenschaft Beschreibung
this.req.path Aktueller URL-Pfad – /info/bla
this.req.params URL-Parameter – { page: 'bla' }
this.req.query Query-String – { lang: 'de' }
this.req.method HTTP-Methode – GET, POST
this.req.body Request Body – bei POST geparst
this.req.headers Request Headers

Automatische Route-Info in Template-Daten

@xpulse/controller lauscht auf template:render:before und injiziert _route in die Template-Daten – ohne manuellen Aufwand in jeder View-Methode, und ohne dass @xpulse/template den Controller kennen muss.

// Intern in @xpulse/controller – einmalig beim Init:
event.on('template:render:before', ({ view, data }) => {
data._route = {
path: this.req.path,
params: this.req.params,
query: this.req.query,
method: this.req.method,
};
});

Im Template abrufbar zur Laufzeit – praktisch zum Debuggen und für aktive Navigation-States:

<!-- Aktiven Nav-Link markieren -->
<a href="/info" class="{{ _route.path === '/info' ? 'active' : '' }}">Info</a>
<!-- Debug-Info -->
<p>Route: {{ _route.path }}</p>

_ Prefix signalisiert: System-Wert, kein User-Daten.


Gefeuerte Events

Event Payload Wann
controller:init { root } Discovery beginnt
controller:discovered { name, routes } Controller gefunden
controller:ready { count, routes } Alle registriert
controller:called { traceId, controller, action, method, path, name } Handler aufgerufen
controller:navigation:collected { items } Navigation-Items gesammelt
controller:navigation:ready { items } Navigation bereit

Zusammenspiel im Request Flow

@xpulse/http empfängt GET /info/bla
@xpulse/router matched → InfoController@bla
@xpulse/controller ruft InfoController.bla() auf
InfoController this.render('info/bla', { title: 'Bla' })
@xpulse/template rendert HTML
controller gibt Response Object zurück { status: 200, body: html }
@xpulse/http sendet Response

Debug Integration

Wenn @xpulse/debug als devDependency installiert ist, stellt @xpulse/controller automatisch einen DataCollector bereit. @xpulse/debug discovert ihn via node_modules/@xpulse/controller/src/datacollectors/ – keine Konfiguration nötig.

"optionalDependencies": {
"@xpulse/debug": "^1.0.0"
}

Der ControllerCollector (name: 'controller', icon: '⚙️') zeigt im Web Profiler:

Dispatch Time misst die Zeit von http:request bis controller:called – das schließt Routing und Dispatch ein, nicht die reine Handler-Execution-Zeit. Dies ist im Panel als (request start → controller called) ausgewiesen.

Außerdem schreibt der ControllerCollector einen Waterfall-Span (kind: 'controller') für die Dispatch-Zeit.

Badge in der Toolbar: InfoController@index · 3ms


Paket-Struktur

@xpulse/controller/
src/
index.js ← default export: controller-Objekt
discovery.js ← rekursives Laden aus src/controllers/
resolver.js ← Filesystem-Pfad → Route ableiten
base.js ← Controller Basis-Klasse
test/
discovery.test.js
resolver.test.js
base.test.js
docs/
index.md
api.md
_meta.json
Dockerfile
Makefile
README.md
package.json
xpulse.json
de/spec.md 2026-04-10