Fibel

9 · Ausliefern

Es gibt drei Wege, und welcher der richtige ist, entscheidet die Zeile daten in deinem Programm.

datenfibel baue erzeugtWofür
hier (Vorgabe)statische HTML-SeitenSeiten, die sich selten ändern
lokaleine App, die im Browser rechnetWerkzeuge ohne Server
von "…"eine App, die ihre Daten holtApp im Gerät, Daten auf dem Server
—(nichts; fibel starte läuft weiter)alles Geteilte: Konten, Bestellungen, Beiträge

Während der Arbeit

bash
fibel starte meine-app
fibel starte meine-app --port 4000

Der Server liest die .fibel-Datei bei jeder Anfrage neu. Datei speichern, Seite aktualisiert sich — kein Bauen dazwischen. Wer gerade in ein Feld tippt oder eine Unterschrift begonnen hat, wird nicht unterbrochen; die Aktualisierung wartet.

Das ist der Weg, den man beim Entwickeln immer nimmt, und auch der, auf dem eine echte App mit Konten und geteilten Daten dauerhaft läuft.

Statische Seiten

bash
fibel baue meine-app --out site
  /          ->  index.html
  /ueber     ->  ueber.html
  /kontakt   ->  kontakt.html

  3 Seite(n) in .../site

Fertiges HTML, das auf jeden Webspace passt. Kein Node, kein Prozess, nichts zu warten.

Der Haken: Es ist eine Momentaufnahme. Formulare, Anmeldung und alles, was Daten ändert, funktioniert darin nicht — es steht das da, was beim Bauen in der Datendatei lag. Gut für eine Vereinsseite, ein Portfolio, eine Doku. Nicht für einen Shop.

App, die im Browser rechnet

fibel
daten lokal
bash
fibel baue meine-app --out app

Herauskommt ein Ordner, in dem der ganze Interpreter liegt. Der Browser liest die .fibel-Datei selbst, rechnet selbst und legt die Daten im Browserspeicher ab. Es gibt keinen Server, der etwas wüsste.

Gut für: eine Einkaufsliste, einen Rechner, ein Formularwerkzeug, eine Schulungsanwendung — alles, was einer Person allein gehört.

Nicht geeignet, sobald zwei Menschen dasselbe sehen sollen. Die Daten liegen im Browser dieses einen Geräts.

Was in dieser Fassung fehlt

Ehrlichkeitshalber: Bild-Uploads und Unterschriftenfelder gehen in der Browser-Fassung noch nicht — die dafür nötigen Skripte werden nur in der Server-Fassung mitgegeben. Steht in OFFEN.md.


Auf einen Server

Fibel bringt seinen eigenen Server mit; davor gehört ein Webserver, der TLS macht und weiterreicht.

Ein Projekt ist eine Subdomain. Wer mehrere Projekte betreibt, startet mehrere Prozesse auf verschiedenen Ports.

bash
fibel starte /pfad/zu/meine-app.fibel --port 4711

In einem Unterordner

Nicht jede App bekommt eine eigene Domain. Auf einem geteilten Webspace, hinter einem vorhandenen Webserver oder im Intranet liegt sie oft in einem Unterordner — beispiel.de/anmeldung/:

bash
fibel starte meine-app --basis /anmeldung

Auch als Umgebungsvariable: FIBEL_BASIS=/anmeldung.

Fibel setzt den Basis-Pfad dann vor jede Adresse, die es herausgibt — Verweise, Formulare, Bilder, Weiterleitungen —, und die Kekse gelten nur für diesen Unterordner. Ob der Webserver davor den Pfad weiterreicht oder schon abnimmt, ist gleich; beides wird verstanden.

Im Programm ändert sich nichts: Dort steht weiter seite / und -> /neu. Der Basis-Pfad steht nicht in der .fibel-Datei, denn ein Programm beschreibt, was es tut, nicht, wo es liegt. Gespeicherte Adressen — ein hochgeladenes Bild etwa — stehen ohne ihn in den Daten; zieht die App um, stimmen sie weiter.

Eine Einschränkung: Apps in Unterordnern derselben Domain teilen sich eine Herkunft. Die Kekse sind getrennt, aber Seiten derselben Herkunft können sich im Browser gegenseitig lesen. Fibel-Apps führen kein fremdes JavaScript aus; wer die strengere Trennung will, nimmt Subdomains.

Die Datei, die den Dienst beschreibt

Der Befehl darüber läuft, solange das Fenster offen ist. Damit die App wirklich steht, braucht es jemanden, der sie startet und nach einem Absturz wieder hochbringt. Auf einem Uberspace ist das supervisord — und die Datei dafür schreibt Fibel:

bash
fibel dienst meine-app.fibel

Auf dem Server aufgerufen weiß der Befehl, wo die App liegt. Von woanders muss man es ihm sagen:

bash
fibel dienst meine-app.fibel --pfad /home/dein-name/meine-app

Dazu wahlweise --port, --name und --fibel (wo bin/fibel.js auf dem Server liegt). Danach steht dort, was noch zu tun ist — supervisorctl reread && supervisorctl update, der Port beim Webserver, und welche Dateien mitmüssen.

Der Befehl liest dabei das Programm, statt eine Vorlage auszugeben:

  • Bei daten lokal gibt es keinen Dienst. Dann sagt er das und verweist auf fibel baue — ein Ordner zum Hochladen, mehr braucht es nicht.
  • Die environment=-Zeile für FIBEL_POST kommt nur hinein, wenn das Programm überhaupt etwas verschickt (ein post-Block oder eine Adresse am Nutzer-Model).
  • Welche Dateien mitmüssen, hängt am Programm: ohne Nutzer-Model gibt es keine Zugangsdatei, ohne Datei-Felder keinen Datei-Ordner.

Das Passwort schreibt Fibel nicht hinein. Die Post-Zeile wird angelegt und bleibt auskommentiert; eintragen muss sie ein Mensch. Eine erzeugte Datei landet früher oder später in einem Repository — ein Werkzeug, das Zugangsdaten hineinschreibt, hat das Problem nur verschoben. Aus demselben Grund entsteht die Datei mit chmod 600, und im Kopf steht, dass sie dort auch bleiben soll.

Was daneben zu tun ist:

  • Neustart nach einem Neustart erledigt der Dienst oben. Auf einem Server ohne supervisord (systemd und anderes) muss man die Datei bisher selbst schreiben — fibel dienst kennt nur diese eine Art.
  • Sicherung. meine-app.db (bei älteren Apps meine-app.data.json), meine-app.zugang.json und meine-app.dateien/ sind die App. Alles andere lässt sich neu bauen, das hier nicht.
  • Erstes Konto. fibel zugang meine-app <name> setzt ein Passwort für ein vorhandenes Konto.
  • Pflichtseiten. Siehe Pflichtseiten — in Deutschland ist das keine Kür.

Die Bremse

Ändern und Hochladen sind gebremst, damit ein Skript nicht eine ganze Maschine beschäftigt: 120 Änderungen je Minute und 60 Uploads je Stunde, gezählt je Sitzung — und wo es keine gibt, je Adresse. Zahlen, die ein Mensch nie erreicht und ein Skript sofort.

bash
FIBEL_BREMSE=schreiben:300      # großzügiger
FIBEL_BREMSE=aus                # gar nicht (nur beim Entwickeln sinnvoll)

Wer zu schnell ist, bekommt eine 429 mit Retry-After — das versteht ein Programm, und ein Mensch bekommt einen Satz, der sagt, wie lange.

Was die Bremse nicht ist: Sie liegt im Arbeitsspeicher genau eines Prozesses. Nach einem Neustart ist sie leer, zwei Prozesse haben zwei getrennte Bremsen, und gegen einen verteilten Ansturm hilft sie nicht. Dafür ist der Webserver davor zuständig.

Welcher Speicher

bash
fibel starte meine-app                      # Vorgabe: SQLite (meine-app.db)
fibel starte meine-app --speicher datei     # ausdrücklich die JSON-Datei

Auch als Umgebungsvariable: FIBEL_SPEICHER=datei.

Die Vorgabe ist SQLite — mit einer Ausnahme: Liegt neben der .fibel-Datei schon eine .data.json und noch keine .db, bleibt es bei der JSON-Datei, und beim Start steht, wie man umzieht. Dieselbe Regel gilt für jeden Befehl, der Daten anfasst: starte, baue, zugang, aufräumen, apk.

Die JSON-Datei wird ganz gelesen und ganz geschrieben. Für ein paar Dutzend Nutzer und ein paar tausend Datensätze reicht das; darüber wird es zäh, und zwei gleichzeitige Schreibvorgänge können sich gegenseitig überschreiben.

SQLite hat Indizes und Transaktionen. Vor allem aber kann es mit der Frage etwas anfangen, die eine Liste stellt: Aus wo, sortiert und höchstens wird ein WHERE, ein ORDER BY und ein LIMIT, statt alles zu laden und danach zu sieben. Was sich nicht übersetzen lässt — oder, ein Vergleich zwischen zwei Feldern —, läuft weiterhin in JavaScript; das Ergebnis ist dasselbe, nur der Weg ist länger.

Der Umstieg ist einmalig und verschluckt nichts: Liegt noch keine Datenbank da, aber eine .data.json, wird die übernommen. Die alte Datei bleibt liegen.

Zwei Dinge dazu, die man wissen sollte:

  • SQLite steckt in Node selbst — kein Paket, nichts zu übersetzen. Fibel braucht dafür Node 24 oder neuer. Node nennt es noch experimentell; diese eine Warnung blendet Fibel beim Start aus, jede andere bleibt sichtbar.
  • Umgeschaltet wird nie von selbst. Eine App mit vorhandener .data.json bleibt dort, bis jemand einmal mit --speicher sqlite startet. Sonst stünde sie nach einem Update leer da und die alten Daten unbemerkt daneben. Ein Speicherwechsel ist eine Entscheidung, keine Nebenwirkung.
  • Eine laufende SQLite-Datei nicht kopieren. Was die App zuletzt geschrieben hat, liegt noch in der .db-wal daneben. Zum Sichern oder Umziehen die App vorher anhalten.

Für ein Projekt gilt eine Datei — umziehen heißt kopieren, sichern heißt kopieren.

PostgreSQL, wenn SQLite nicht mehr reicht:

bash
npm install pg
FIBEL_PG=postgres://nutzer:geheim@rechner:5432/datenbank   fibel starte meine-app --speicher postgres

Was das kann und SQLite nicht: mehrere Schreiber gleichzeitig, und die Datenbank darf auf einer anderen Maschine stehen als das Programm. Was es kostet: einen Dienst, Zugangsdaten und eine Sicherung.

Zwei Punkte dazu:

  • pg bringt jeder selbst mit. Es steht als freiwillige Beigabe in der package.json; wer die JSON-Datei oder SQLite benutzt, installiert nichts, und npm install fibel lädt keinen fremden Code.
  • Die Adresse kommt aus der Umgebung, nie aus der .fibel-Datei. Ein Programm beschreibt, was es tut, nicht, wo seine Datenbank steht — und ein Passwort in einer Datei, die man weitergibt und einer KI zu lesen gibt, ist ein Passwort, das nicht mehr geheim ist.

Einen eigenen Speicher schreiben

datei und sqlite sind eingebaut, weil sie ohne fremden Code auskommen. Alles andere ist eine Erweiterung und geht durch dieselbe Tür — auch PostgreSQL, das Fibel mitliefert. Im Werkzeug gibt es dafür keinen Sonderfall: Was mitgeliefert wird, geht denselben Weg wie das, was du selbst schreibst.

bash
fibel starte meine-app --speicher ./mein-speicher.js   # eine Datei
fibel starte meine-app --speicher fibel-mysql          # ein Paket aus npm

Eine Speicher-Erweiterung stellt zwei Dinge bereit:

js
export async function pruefe() {
  // null, wenn alles da ist. Sonst ein Text, der sagt, was fehlt und wie man
  // es nachholt — er wird dem Menschen gezeigt.
  return null;
}

export async function oeffne({ models, quellPfad, melden }) {
  return new MeinSpeicher(models);
}

Was ein Speicher können muss, steht in src/store.js — und ob deiner es wirklich kann, findest du am schnellsten heraus, indem du ihn gegen den eingebauten stellst: Die Testreihe schickt allen Speichern dieselbe Folge von Handgriffen und vergleicht die Antworten.

Auch hier wird eine vorhandene .data.json beim ersten Start übernommen, und sie bleibt liegen.

Sind die drei wirklich gleich?

Ja, und das ist keine Behauptung: Die Testreihe schickt allen dreien dieselbe Folge von Handgriffen — anlegen, ändern, löschen, filtern, sortieren, begrenzen — und vergleicht die Antworten Zeichen für Zeichen. Wo pg und node:sqlite fehlen, entfällt der Vergleich und sagt es.

Ein Programm läuft deshalb unverändert auf jedem Unterbau. Genau das macht den Speicher zu einer Betriebsfrage statt zu einem Umbau.


Was neben der Datei mit muss

Auf den Server gehört nicht nur die .fibel-Datei:

Neben karat.fibelWas es istMitnehmen?
karat.bilder/Bilder, die zum Programm gehören (Logo, Bühne)ja, immer
karat.muster.jsonMusterdatenwenn mit --muster gestartet wird
woerter.jsoneigene Wörterja, wenn vorhanden
karat.db, karat.data.jsonder Datenbestandnur beim Umzug
karat.dateien/hochgeladene Dateiennur beim Umzug
karat.zugang.jsonKonten und Sitzungennur beim Umzug

Musterdaten

Ein Beispiel, das leer dasteht, zeigt nichts. Neben zwitscher.fibel liegt deshalb zwitscher.muster.json:

json
{
  "Nutzer":  [ { "_schluessel": "lena", "name": "Lena" } ],
  "Beitrag": [ { "inhalt": "Hallo", "autor": "@lena", "am": "jetzt - 2 std" } ]
}
  • Die Models kommen in der Reihenfolge der Datei an. _schluessel gibt einem Eintrag einen Namen, "@lena" verweist auf ihn.
  • Zeitfelder dürfen "jetzt" heißen, auch "jetzt - 3 tage", "jetzt - 20 std" oder "jetzt - 15 min" (englisch "now - 3 days"). So wirken die Muster an jedem Tag frisch.
  • Was fehlt, nimmt die Vorgabe aus dem Programm. = ich bleibt leer — beim Einspielen ist niemand angemeldet.
  • Eingespielt wird nur in einen leeren Bestand. Hat eines der Models aus der Datei schon Einträge, bleibt alles, wie es ist.
  • Das Nutzer-Model zählt dabei nicht mit: Konten entstehen durch Registrieren, und wer sich schon angemeldet hat, soll die Muster nicht verhindern. Einen Muster-Nutzer, dessen Name schon vergeben ist, legt Fibel nicht ein zweites Mal an — Verweise auf ihn zeigen auf den vorhandenen.
  • Erst wird die ganze Datei geprüft, dann geschrieben: Ein Fehler lässt nichts halb gefüllt zurück.
bash
fibel muster zwitscher.fibel            # einmal einspielen
fibel starte zwitscher.fibel --muster   # bei jedem Start, solange leer

Mit --muster am Dienst füllt sich ein Beispiel nach dem Zurücksetzen von selbst wieder. Bei daten lokal gibt es auf dem Server keinen Bestand: Der Browser bekommt die Musterdaten mit und spielt sie beim allerersten Besuch ein — danach nie wieder, auch nicht, wenn man alles löscht.

Muster-Nutzer haben kein Passwort. Anmelden kann sich unter ihrem Namen niemand, und neu registrieren lässt sich ein vergebener Name auch nicht.

Was der Server ausliefert

AdresseWas
/…deine Seiten
/__datei/<name>hochgeladene Bilder und Dateien
/__fibel/<datei>.jsder Interpreter, für Apps mit daten lokal oder daten von
/__livedie leise Verbindung für Aktualisierungen
/__do/…Formulare und Aktionen
/__datender Bestand für eine App mit daten von (Kapitel 13)

Die __-Adressen gehören Fibel. Eine Seite /​__meins anzulegen ist keine gute Idee.


Rechnen im Gerät, Daten vom Server

fibel
daten von "https://lieferung.deine-domain.de"

Die App rechnet weiterhin selbst, holt den Bestand aber von einem Fibel-Server und schickt Änderungen dorthin zurück — dieselbe .fibel-Datei, nur diese eine Zeile anders. Der Fall, für den es gebaut wurde: Im Büro ein Web-Backend, auf dem Telefon dieselbe App.

Eigenes Kapitel: App mit Server.

Und als App aufs Telefon

fibel apk baut aus derselben .fibel-Datei eine native Android-App — mit echten Android-Bedienelementen, nicht mit einer eingerahmten Webansicht. Dabei wandern Bilder und der aktuelle Datenbestand mit ins Paket, sodass die App ohne Netz läuft.

Eigenes Kapitel: Native App.

Wer eine Oberfläche für etwas anderes bauen will — iOS, Desktop, was auch immer —, findet das Nötige unter Ansichtsbaum.


Weiter: Pflichtseiten