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.
http – Externe Requests absetzen
Abschnitt betitelt „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
Abschnitt betitelt „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
Abschnitt betitelt „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
Abschnitt betitelt „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
Abschnitt betitelt „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
Abschnitt betitelt „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
Abschnitt betitelt „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
Abschnitt betitelt „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
Abschnitt betitelt „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
Abschnitt betitelt „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
Abschnitt betitelt „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
Abschnitt betitelt „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
Abschnitt betitelt „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
Abschnitt betitelt „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
Abschnitt betitelt „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)