Doku-Vollausbau: 62 Seiten (Projekt/Admin/Anwender/Referenz), Hook-Referenz mit Signaturen, Corporate-Screenshots, Coverage-Inventar
Deploy tibi-docs / build-and-deploy (push) Failing after 38s
Deploy tibi-docs / build-and-deploy (push) Failing after 38s
- Einstieg: Schnellstart + Neues Projekt aus Starter-Skill, Was-ist-Tibi mit Backend/Website-Trennung - Projekt: Sammlungen, Felder, Abfragen, Rechte, Hooks, Dateien, i18n, Suche, SSR, Pagebuilder, Jobs, Audit, Deploy, LLM, MCP - Hook-Referenz: 66 Symbole mit Signatur + Beispiel + Fehlerfall (5 Kapitel) - Admin: Ansichten, Editor, Medien, Exporte, Aktionen, Rechte, KI, Setup, Customizing - Anwender: 11 Anleitungen im professionellen Du-Ton, 30 Corporate-Screenshots - Referenz: Schemas, Token-Köpfe, Fehlerbilder, Shortcuts, OpenAPI-Platzhalter - ADR-0001 bis 0003, Coverage-Skript (282 Items) + Map, Plan-Dateien
This commit is contained in:
1 parent
acb1d32355
commit
1897bb50d9
101 files changed
+8980
-68
No files matched your search
@@ -0,0 +1,191 @@
|
||||
---
|
||||
title: 'Hook-Referenz: Netze und Nachrichten'
|
||||
description: '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.
|
||||
|
||||
```js title="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`).
|
||||
|
||||
### 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.
|
||||
|
||||
```js title="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`).
|
||||
|
||||
## 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.
|
||||
|
||||
```js title="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").
|
||||
|
||||
## 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`).
|
||||
|
||||
```js title="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`).
|
||||
|
||||
### 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.
|
||||
|
||||
```js title="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`).
|
||||
|
||||
## 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`).
|
||||
|
||||
```js title="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`).
|
||||
|
||||
### 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.
|
||||
|
||||
```js title="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`).
|
||||
|
||||
### 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.
|
||||
|
||||
```js title="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`).
|
||||
|
||||
### 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.
|
||||
|
||||
```js title="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`).
|
||||
|
||||
### 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.
|
||||
|
||||
```js title="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`).
|
||||
|
||||
### 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.
|
||||
|
||||
```js title="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](/reference/hook-kontexte/) als Paket-Index, [Hooks schreiben](/projekt/hooks-schreiben/) für Lifecycle und Starter-Muster.
|
||||
|
||||
Verifiziert am 68b42785 2026-10-16 (tibi-server)
|
||||
Reference in new issue
Block a user