Files
tibi-docs/src/content/docs/reference/hooks-netze.md
T
apairon 1928922e07 Audit + Umbau: Faktenfehler behoben, Server-/Admin-Lücken geschlossen, 49 Screenshots, Schema-Matrix generiert
- Audit: plans/doku-audit.md (Befunde, Phasen P1-P9, Ausführungsprotokoll) + plans/audit/ (8 Detailreports) + plans/offene-issues.md
- Werkzeuge: scripts/audit-docs.mjs (Links/Anker/Bilder/Tabellen), scripts/gen-schema-matrix.mjs (npm run gen:schemas)
- Neue Seiten: projekt/feld-rechte, reference/rest-system, reference/schema-properties, projekt/tests, 5 Anwender-Seiten
- Fehler behoben: EMBEDDING_BGE_M3_*-Env, allowSnapshots, MCP-Scopes (.read/.create/.update), meta.views, loadAll-Signatur, OpenAPI-Endpunkt, MongoDB 8, Befehlspalette
- Screenshots: 9 Admin + 10 Anwender aus Mock-Projekt corporate (t1 entfernt), 49 Bilder gesamt
- AGENTS.md: Sync-Behauptung richtiggestellt, Belegpflicht/UI-Namen/Screenshot-Regeln, Werkzeug-Abschnitt
- Build: 71 Seiten grün, 0 tote Links, 0 kaputte Tabellen
2026-10-17 01:49:35 +00:00

11 KiB
Raw Blame History

title, description
title description
Hook-Referenz: Netze und Nachrichten http, smtp, channel und ratelimit im Hook-JS: Signaturen aus internal/hook, je ein Beispiel und je ein belegter Fehlerfall.

In Hooks sprichst du diese vier Pakete über context an, z. B. für einen Fetch-Request oder den Mail-Versand. Die Signaturen stammen aus den // args:- bzw. // JS signature:-Kommentaren in internal/hook/context_*.go im tibi-server; alle vier Pakete sind dort lückenlos kommentiert, keine Signatur musste abgeleitet werden. Jede Funktion zeigt genau einen Fehlerfall mit dem originalen Error-Text aus Code oder Tests.

http – Externe Requests absetzen

Quelle: context_http.go, registriert per RegisterPackageFunctions("http", ...) mit fetch, fetchStream. Beide teilen sich die Argumentprüfung in parseHTTPArgs; fetch liest den Body vollständig ein (Timeout-Default 10000 ms), fetchStream liefert einen zeilenweisen Reader für SSE und Streaming (Timeout-Default 0, also kein Timeout).

http.fetch

Signatur: http.fetch(url: string, options?: { method?: string, headers?: object, body?: string, timeout?: number }): object — aus // args: url:string, options:map[string]interface{} (context_http.go:124). Antwortet mit { headers, status, statusText, trailer, url, body: { text(), json() } }; body.json() wirft bei ungültigem JSON den Parser-Error als JS-Exception.

;(function () {
    const res = context.http.fetch("https://api.example.com/content", {
        method: "GET",
        headers: { Accept: "application/json" },
        timeout: 5000
    })
    throw { status: 200, json: { status: res.status, data: res.body.json() } }
})()

Fehlerfall (Error): Ohne URL brichst du mit missing url in http.fetch function ab (context_http.go:37); eine URL vom falschen Typ liefert url must be string in http.fetch function (context_http.go:45), belegt durch die Test-Assertions auf beide Texte (context_http_test.go:432, context_http_test.go:442).

http.fetchStream

Signatur: http.fetchStream(url: string, options?: { method?: string, headers?: object, body?: string, timeout?: number }): object — aus // args: url:string, options:map[string]interface{} (context_http.go:186). Antwortet mit { headers, status, statusText, body: { read(), close() } }; read() liefert die nächste Zeile als String oder null bei EOF und schließt dann automatisch.

;(function () {
    const res = context.http.fetchStream("https://api.example.com/events", {
        headers: { Accept: "text/event-stream" }
    })
    const first = res.body.read()
    res.body.close()
    throw { status: 200, json: { status: res.status, first: first } }
})()

Fehlerfall (Error): Ohne URL brichst du mit missing url in http.fetchStream function ab (context_http.go:37); options als String statt Objekt scheitert mit options must be object in http.fetchStream function (context_http.go:52), belegt durch die Test-Assertion auf diesen Text (context_http_test.go:452).

smtp – Mails versenden

Quelle: context_smtp.go, registriert per RegisterPackageFunctions("smtp", ...) mit sendMail. Der Versand nutzt den Server-Mail-Host aus der Config oder einen per host, username und password übergebenen Host. Beachte: Die Error-Texte nennen wörtlich mail.send, obwohl das Paket in JS smtp heißt — Altlast-Namensrest im Server-Code, kein Alias.

smtp.sendMail

Signatur: smtp.sendMail(options: { to: string | Array, cc?: string | Array, bcc?: string | Array, from: string, fromName?: string, replyTo?: string, subject?: string, plain?: string, html?: string, attach?: string | Array, host?: string, username?: string, password?: string }): void — aus // args: options:map[string]interface{} (context_smtp.go:23). Gibt bei Erfolg null zurück; Anhänge sind Pfade im Config-Verzeichnis.

;(function () {
    context.smtp.sendMail({
        to: "redaktion@example.com",
        from: "noreply@example.com",
        fromName: "Tibi CMS",
        subject: "Neuer Beitrag",
        plain: "Ein neuer Beitrag wartet auf Freigabe."
    })
    throw { status: 200, json: { sent: true } }
})()

Fehlerfall (Error): Ohne options scheitert der Aufruf mit missing options in mail.send function (context_smtp.go:37); ohne Empfänger meldet der Code missing or invalid to in options of mail.send function (context_smtp.go:76), ohne Absender missing or invalid from in options of mail.send function (context_smtp.go:117) — beides belegt in context_smtp_test.go:77 („error - missing options") und context_smtp_test.go:88 („error - missing from").

channel – Realtime-Nachrichten

Quelle: context_channel.go, registriert per RegisterPackageFunctions("channel", ...) mit subscribe, send. subscribe blockiert per receive(), bis eine Nachricht eintrifft, der Client trennt (null) oder die Subscription geschlossen wird (Exception mit Code channel_closed); der Hook-Timeout wird dafür deaktiviert.

channel.subscribe

Signatur: channel.subscribe(name: string, options?: { bufferSize?: number, onFull?: string, messageTTL?: number, lastN?: number, maxAge?: number }): { receive(), close() } — aus // JS signature: channel.subscribe(name: string, options?: object): Subscription (context_channel.go:21).

;(function () {
    const sub = context.channel.subscribe("news", { lastN: 10 })
    const msg = sub.receive()
    sub.close()
    throw { status: 200, json: { message: msg } }
})()

Fehlerfall (Error): Außerhalb von Hooks (in Jobs) scheitert der Aufruf mit channel.subscribe is only available in hooks, not in jobs (context_channel.go:29); ohne Kanalnamen meldet der Code channel.subscribe requires a channel name (context_channel.go:38).

channel.send

Signatur: channel.send(name: string, data: any): void — aus // JS signature: channel.send(name: string, data: any): void (context_channel.go:109). Publiziert an alle Subscriber des Kanals.

;(function () {
    context.channel.send("news", { title: "Eilmeldung", active: true })
    throw { status: 200, json: { sent: true } }
})()

Fehlerfall (Error): Ohne Kanalnamen und Daten scheitert der Aufruf mit channel.send requires a channel name and data (context_channel.go:117); ein leerer Kanalname liefert channel.send: channel name must be a non-empty string (context_channel.go:121).

ratelimit – Backoff und Sliding Window

Quelle: context_ratelimit.go, registriert per RegisterPackageFunctions("ratelimit", ...) mit backoffCheck, backoffRecord, backoffReset, windowCheck, windowReset, windowCount. Alle sechs Funktionen brauchen denselben key (nicht-leerer String) und einen verfügbaren Limiter.

ratelimit.backoffCheck

Signatur: ratelimit.backoffCheck(key: string): { blocked: boolean, retryAfterMs: number } — aus // JS signature: ratelimit.backoffCheck(key: string): { blocked: boolean, retryAfterMs: number } (context_ratelimit.go:41).

;(function () {
    const state = context.ratelimit.backoffCheck("login:mailer@example.com")
    if (state.blocked) {
        throw { status: 429, json: { retryAfterMs: state.retryAfterMs } }
    }
    throw { status: 200, json: { blocked: false } }
})()

Fehlerfall (Error): Ohne Schlüssel scheitert der Aufruf mit ratelimit: key argument required (context_ratelimit.go:30); ohne verfügbaren Limiter meldet der Code ratelimit: limiter not available (context_ratelimit.go:23).

ratelimit.backoffRecord

Signatur: ratelimit.backoffRecord(key: string): void — aus // JS signature: ratelimit.backoffRecord(key: string): void (context_ratelimit.go:59). Zeichnet einen Fehlversuch auf und verschärft das exponentielle Backoff.

;(function () {
    context.ratelimit.backoffRecord("login:mailer@example.com")
    throw { status: 200, json: { recorded: true } }
})()

Fehlerfall (Error): Ein leerer Schlüssel scheitert mit ratelimit: key must be a non-empty string (context_ratelimit.go:34).

ratelimit.backoffReset

Signatur: ratelimit.backoffReset(key: string): void — aus // JS signature: ratelimit.backoffReset(key: string): void (context_ratelimit.go:74). Setzt den Backoff-Zähler zurück, z. B. nach erfolgreichem Login.

;(function () {
    context.ratelimit.backoffReset("login:mailer@example.com")
    throw { status: 200, json: { reset: true } }
})()

Fehlerfall (Error): Ohne Schlüssel scheitert der Aufruf mit ratelimit: key argument required (context_ratelimit.go:30).

ratelimit.windowCheck

Signatur: ratelimit.windowCheck(key: string): { allowed: boolean, retryAfterMs: number } — aus // JS signature: ratelimit.windowCheck(key: string): { allowed: boolean, retryAfterMs: number } (context_ratelimit.go:89). Zählt den Aufruf als Hit und prüft das Sliding Window in einem Schritt.

;(function () {
    const check = context.ratelimit.windowCheck("api:mailer@example.com")
    if (!check.allowed) {
        throw { status: 429, json: { retryAfterMs: check.retryAfterMs } }
    }
    throw { status: 200, json: { allowed: true } }
})()

Fehlerfall (Error): Ohne Schlüssel scheitert der Aufruf mit ratelimit: key argument required (context_ratelimit.go:30); ohne verfügbaren Limiter meldet der Code ratelimit: limiter not available (context_ratelimit.go:23).

ratelimit.windowReset

Signatur: ratelimit.windowReset(key: string): void — aus // JS signature: ratelimit.windowReset(key: string): void (context_ratelimit.go:107). Setzt das Sliding Window für den Schlüssel zurück.

;(function () {
    context.ratelimit.windowReset("api:mailer@example.com")
    throw { status: 200, json: { reset: true } }
})()

Fehlerfall (Error): Ohne Schlüssel scheitert der Aufruf mit ratelimit: key argument required (context_ratelimit.go:30).

ratelimit.windowCount

Signatur: ratelimit.windowCount(key: string): number — aus // JS signature: ratelimit.windowCount(key: string): number (context_ratelimit.go:122). Liefert die aktuelle Trefferzahl im Fenster, ohne zu zählen.

;(function () {
    const n = context.ratelimit.windowCount("api:mailer@example.com")
    throw { status: 200, json: { hits: n } }
})()

Fehlerfall (Error): Ohne Schlüssel scheitert der Aufruf mit ratelimit: key argument required (context_ratelimit.go:30).

Weiter: Hook-Kontexte als Paket-Index, Hooks schreiben für Lifecycle und Starter-Muster.

Verifiziert am 68b4278 2026-10-16 (tibi-server)