Zum Inhalt springen

Hook-Referenz: Netze und Nachrichten

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.

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).

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.

http.fetch – Beispiel
;(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).

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.

http.fetchStream – Beispiel
;(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).

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.

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.

smtp.sendMail – Beispiel
;(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”).

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.

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).

channel.subscribe – Beispiel
;(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).

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.

channel.send – Beispiel
;(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).

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.

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

ratelimit.backoffCheck – Beispiel
;(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).

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.

ratelimit.backoffRecord – Beispiel
;(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).

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.

ratelimit.backoffReset – Beispiel
;(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).

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.

ratelimit.windowCheck – Beispiel
;(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).

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.

ratelimit.windowReset – Beispiel
;(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).

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.

ratelimit.windowCount – Beispiel
;(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)