ZELYRA
ZELYRA 1.2.41 · DEUTSCHE AUSGABE

Das
Handbuch.

Von der ersten Funktion bis zur datenbankgestützten Webanwendung. Die vollständige Anleitung zum Denken, Bauen und Prüfen mit Zelyra.

Das Zelyra-Handbuch

Von den Grundlagen bis zur datenbankgestützten Webanwendung. Absicht beschreiben. Korrektheit beweisen.

Willkommen beim vollständigen Zelyra-Handbuch. Es umfasst sowohl das didaktische Lehrbuch »Zelyra lernen – Verständlich programmieren von den Grundlagen bis zur eigenen Anwendung« (Teil I bis X, Kapitel 1 bis 43) als auch das technische Referenzhandbuch (Abschnitte 1 bis 40) sowie ausführliche Anhänge (A bis K).

Zelyra 1.2.39: aktueller Funktionsumfang

Dieses Handbuch beschreibt den Stand des zuletzt veröffentlichten stabilen Releases Zelyra 1.2.39 (Stand: 21. Oktober 2026). Änderungen nach diesem Release werden erst dann als verfügbar beschrieben, wenn sie veröffentlicht sind. Zelyra 1.0.0 war die erste stabile Hauptveröffentlichung; die Sprachkompatibilitätslinie bleibt 0.1. Der Umfang einzelner Compiler-, Datenbank-, Betriebs- und IDE-Funktionen ist in den jeweiligen Abschnitten mit ihren Grenzen beschrieben. Das frühere Werkzeug zelyra migrate laravel wurde in 1.1.2 entfernt. Der davon getrennte, spätere zelyra import laravel-Workflow analysiert ausgewählte Projektstrukturen und erzeugt prüfbare Zelyra-Grundlagen; er portiert keine vollständige Laravel-Webanwendung.

  • Sprache und Typsystem: typisierte Funktionen, unveränderliche Bindings, benutzerdefinierte generische Records und Enums, payloadtragende Enums, benannte Funktionsreferenzen (&name), exhaustive match-Prüfung, exakte 9-stellige Dezimalrechnung (Decimal), typisierte Kalender- und Datums-/Zeitstrukturen (Date, Time, DateTime, Instant), Contracts (requires, ensures) und deklarierte Capabilities.
  • Spracherweiterungen und Laufzeit: typisierte wertliefernde if-Ausdrücke, funktionale Record-Updates (Customer { ..old, active: true }), typisierte lexikalische Lambdas in Pipelines (map, filter, find, reduce), boolesche Match-Guards und verschachtelte Record-Muster, First-Class-Tupel als Rückgabewerte und Zuweisungsziele, kontextuelle Feldnamen (version, type), nominale Id-Aliase (type OrderId = Id), konfigurierbare globale Schrittbegrenzung (--max-steps N), serverseitiges lokales Text-PDF-Rendering (render_pdf) und transaktionale PDF-Mail-Anhänge über die MariaDB-Outbox.
  • Datenbanken und relationales CRUD: Multi-Backend-Unterstützung für geprüfte Abläufe mit Servermatrix für MariaDB 11.4.13, MySQL 8.4.11 und PostgreSQL 16.15. Typisiertes parametrisiertes SQL, Transaktionen mit dynamischen Deadlines (innodb_lock_wait_timeout), Fremdschlüssel-Constraints mit Waisenprüfung (error[E-DB-005]), relationale Joins, mandantenisoliertes CRUD, geordneter restriktiver Löschschutz (RESTRICT) und Schema-Vorschau mit kontrolliertem Anwenden (zelyra db plan/apply/history).
  • Mandantensicherheit und zusammengesetzte Fremdschlüssel (v1.2.17 – v1.2.19): table ... scoped by tenant_id: TenantId erzwingt die Mandantenzugehörigkeit auf Datenbankebene. Zelyra ergänzt uq_{table}_tenant_id UNIQUE (tenant_id, id) und generiert zusammengesetzte Foreign Keys FOREIGN KEY (tenant_id, fk_id) REFERENCES target (tenant_id, id) ON DELETE RESTRICT. unique within tenant deklariert mandantenspezifische Eindeutigkeit (customer_number: String(50) required unique within tenant), und optionale Relationen (Customer?) erzeugen typsicher BIGINT NULL.
  • Tabelleninvarianten und typisierte Validierung (v1.2.20): table ... { validate { due_date >= invoice_date: "..." } } verankert typgeprüfte, feldübergreifende Invarianten direkt am Datenmodell. Formulare, CRUD und schreibende APIs (validates) erzwingen diese Regeln vor dem Schreibvorgang und liefern standardisierte HTTP-422-Verträge.
  • Lifecycle-Guards und Zeilenpolicies (v1.2.21): Deklarative Sperren für edit und delete (guard edit when locked == true { error: "..." }) blockieren illegale Mutationen direkt in der Mutationstransaktion. tableview-Aktionsslots und Detailseiten blenden Bearbeiten- und Löschen-Aktionen zustandsabhängig aus.
  • Transaktionale Master-Detail-CRUD-Formulare und mehrere Kind-Sammlungen (v1.2.22 – v1.2.24): has_many items: InvoiceItem { ... } und zusätzliche Sammlungen (has_many attachments: InvoiceAttachment { ... }) ermöglichen die serverseitig gerenderte Erfassung von Kopfdaten und mehreren unabhängigen 1:N-Kinddatensammlungen in einem einzigen Formular mit atomarer Transaktion und automatischer Summenaktualisierung (aggregates).
  • Geprüfte Mandanten-APIs (v1.2.23): Mandantengebundene APIs (scoped by tenant_id) validieren den Mandantenkontext verbindlich aus geprüften Benutzersitzungen.
  • Kanonische Projektstruktur (v1.2.13 – v1.2.14): Mit zelyra new --layout standard wird ein architekturgeprüftes Standardlayout angelegt (routes/, domain/, schema/, public/, assets/). Der Linter strict_architecture = true erzwingt die Trennung von HTTP-Gatekeepern und reiner Fachlogik (E-ARCH-001); zelyra migrate-layout --check liefert eine schreibgeschützte AST-Migrationsvorschau.
  • Erweiterbare CRUD- und TableView-Seiten (v1.2.15): Automatisch generierte Create/Edit-Seiten (crud.view.create, crud.view.edit) und tableview-Listen unterstützen compilergeprüfte Inhaltsbereiche, Feldhilfen und Zeilenaktionen, während Validierung, CSRF-Schutz, Berechtigungen und Persistenz zentral abgesichert bleiben.
  • Dienste, Scheduler und Hintergrund-Jobs (v1.2.5 – v1.2.9): zelyra service install --port <port> installiert Linux-systemd-Dienste mit festem Port und Statusüberwachung. Deklarierte Zeitpläne laufen im residenten Scheduler; der Hintergrund-Worker wickelt Jobs mit erneuerbaren Leases transaktional über die MariaDB-Outbox ab (zelyra schedule jobs).
  • Paket- und Modulökosystem (v1.1.5 – v1.2.12): Wiederverwendbare Zelyra-Pakete (pkg:), Offline-Lockfile (zelyra.lock), Ed25519-signierte Registry-Pakete mit zyklusfreier Auflösung, transaktionale Wiederherstellung (zelyra package recover) und eine authentifizierte Web-Einstellungsseite (/__zelyra/module-settings).
  • Native Datenbank-CLI und direkte Abfragen (v1.2.25): zelyra db console startet den nativen MariaDB-/MySQL- oder PostgreSQL-Client mit automatischer Passwortübergabe. zelyra db query führt einzelne SQL-Befehle standardmäßig in schreibgeschützten Transaktionen aus, unterstützt --allow-mutation, Tabellen-, JSON- und CSV-Formate sowie Mandantenbindung (:tenant_id).
  • Interaktive AST-Sprachkonsole (v1.2.26): zelyra console bietet eine interaktive REPL mit persistenten typisierten Bindungen, Tab-Vervollständigung, Verlauf in ~/.zelyra_history und echter Transaktions-Sandbox (--sandbox) mit sicherem Rollback auf MariaDB und MySQL.
  • Laravel-Importanalyse und Migrationsgrundlage (v1.2.27): zelyra import laravel analyze und generate analysieren bestehende Laravel-Projekte quellbelegt und schreibgeschützt (Read-Only First). Generierung nativer Zelyra-Tabellen, Invarianten, Typen und Auth-Module ohne Ausführung fremden PHP-Codes.
  • Typisierte transaktionale CRUD-Actions (v1.2.28): action <name>(...) bindet typisierte Parameter, den exklusiv gesperrten Datensatz (row) und verifizierte Sitzungsdaten (session). Reine Guards werden unter Row-Lock evaluiert; Fachmutationen, Audit-Log und PDF-Outbox teilen sich eine gemeinsame atomare Transaktion.
  • Sichere Multipart-Datei-Uploads und Mandanten-Dateispeicher (v1.2.29): Typisierte File-Formularfelder mit Größenlimit und Signaturprüfung (Magic Bytes für PDF, PNG, JPEG). Speicherung außerhalb des Web-Roots unter opaken FileRef-Tokens, berechtigungsgeprüfte Downloads, Bereinigung beim Ersetzen/Löschen und prüfsummenvalidiertes Backup/Restore (zelyra files cleanup|backup|restore).
  • Mandantensichere Schema-Referenzen (v1.2.30): Skalare ID-Spalten erhalten mit references <table> explizite Datenbank-Fremdschlüssel. Zwischen mandantengebundenen Tabellen werden automatisch zusammengesetzte Fremdschlüssel FOREIGN KEY (tenant_id, fk_id) REFERENCES target (tenant_id, id) erzeugt.
  • Mandantenbezogene Nummernkreise (v1.2.31): auto_sequence("KD-{0001}") und auto_sequence("AG-{year}-{0001}") vergeben fortlaufende, manipulationssichere Beleg- und Kundennummern transaktional pro Mandant (und UTC-Kalenderjahr).
  • Typisierte berechnete Geschäftsfelder und FileRef-Anhänge (v1.2.32): compute(expr) berechnet abgeleitete Werte (z. B. Zeilensummen) deterministisch und serverseitig erzwungen für CRUD und APIs. FileRef? attachment bindet den privaten Dateispeicher mit Größen- und Signaturgrenzen direkt an generierte Formulare an.
  • Multi-Backend-Parität, Transaktions-Rollback und PDF-Outbox (v1.2.33): Quellgenaue Schrittbudget-Abstimmung (--max-steps) über AST, MIR und Bytecode-VM; transaktionale PDF-Mail-Outbox (render_pdf + mail_queue) mit atomarem Rollback bei Transaktionsfehlern.
  • Prozessintegration und Laravel-Schemaanalyse (v1.2.34): Begrenztes run_process_piped mit Capability-Allowlist, Größenlimit und Timeout, gezieltere Abbildung ausgewählter Textkapazitäten und eng begrenzte statische Erkennung bedingter Laravel-Migrationen. Dies ist keine Sandbox für beliebigen Code und kein vollständiger Webseitenimport.
  • Laravel-Integerbreiten (v1.2.35): Unterstützte Breiten vorzeichenloser Integer bleiben bei der Schemaanalyse gezielter erhalten; Backend-Unterschiede und nicht unterstützte Fälle werden ausgewiesen.
  • CRUD-Aktionslaufzeit (v1.2.36): Ein erfolgreicher Guard und sein typisierter Handler teilen den AST-Runtime-Kontext; abgewiesene Guards überspringen weiterhin den Handler.
  • CSV-Importer und Backend-Korrekturen (v1.2.37–v1.2.39): Das wiederverwendbare CSV-Beispielpaket unterstützt ausdrücklich gewählte Text-, Integer-, Bool-, Decimal-, Date- und Time-Konvertierungen. Werte werden strikt und ohne geratenes Locale geparst; zusätzlich wurden qualifizierte Enum-Varianten für MIR und Bytecode korrigiert. Der Importer leitet kein Schema ab und schreibt nicht selbst in die Datenbank.
  • Zelyra UI & Designsystem: lokal gebündeltes, compilergeprüftes Designsystem ohne npm oder Webpack (zelyra-ui.v1.css). Typisierte UI-Komponenten, Themes (light, dark, system), harmonisiertes AppShell-Design, Modal-Dialoge und responsives Drucklayout.
  • Zelyra Studio (IDE): lokaler Editor mit Sitzungsschutz, Speichern, Live-Diagnosen, Kontexthilfe, Aktionsfinder, Code-Vervollständigung, Navigation, strukturiertem Log-Fenster, Theme-Editor und CRUD-Vorschau.
  • Rechnungstutorial: Kunden, Artikel, Rechnungen und Positionen im einheitlichen Zelyra-UI-Theme, mit interaktivem „Quelltext erklärt“-Dialog, sauber ausgerichteten Belegkarten und browserbasiertem Druck.
  • E-Mail und Sicherheit: transaktionale Outbox (mail_queue), SMTP-Worker mit TLS und CA-Validierung, Header-Injection-Schutz, persistente Passwort-Reset-Quoten (password_reset_limits) und integrierte TOTP-Zwei-Faktor-Authentifizierung (RFC 6238, AES-256-GCM, Argon2id Recovery-Codes).
  • Benachrichtigungen: mandanten- und empfängerisoliertes In-App-Zentrum (recipient: user_id), dynamischer Zähler ungelesener Nachrichten (unread: read_at), persönliche CSRF-geschützte Lesebestätigung mit Audit-Protokollierung.
  • AST-, MIR- und Bytecode-Ausführung: Der AST-Interpreter bleibt der Standardpfad. MIR und Bytecode unterstützen ausgewählte, geprüfte Abläufe; vollständige Web-/Runtime-Parität und die noch offenen 1.3.0-Gates sind nicht abgeschlossen. Es wird kein Leistungsgewinn gegenüber dem AST-Pfad behauptet.

Änderungen seit Version 1.0.0

  • 1.0.1: Compose v2 wird unterstützt; Python Compose v1 wird mit einem Update-Hinweis abgewiesen. Der Ausführen-Knopf in Studio startet den Projekteinstieg.
  • 1.0.2: Studio kann beim lokalen Rechnungstutorial MariaDB starten, das Schema anwenden und Beispieldaten laden. for-Bereiche unterstützen Schrittweiten; range(start, end, step) liefert die entsprechende Folge.
  • 1.0.3: Die Standardbibliothek wurde für Mathematik, Text, Arrays, Sets, Maps, Datum, Parsing, Encodings und Pfade erweitert. Hinzu kommen typgeprüfte |>-Pipelines, flache else if-Kaskaden, Schemaanzeige bei nicht erreichbarer Datenbank, automatische Tutorialeinrichtung, freie Ports, wiederholbare Beispieldaten und begrenzte Rekursion. Die vollständigen Signaturen stehen in Anhang D.
  • 1.0.4: Rechnungstutorials erhalten einen eigenen Compose-Projektnamen. Bei älteren Volumes kann der Start auf eine isolierte Datenbank ausweichen, ohne das bisherige Volume zu löschen oder zu verändern. Die Compose-Erkennung akzeptiert Kurzversionen wie 2.40.
  • 1.0.5: Studio ergänzt lokale strukturierte Logs; zelyra uninstall entfernt die aktive benutzereigene Programmdatei nach Bestätigung. Das Rechnungstutorial erhält Datumseingabe ohne Uhrzeit, serverseitig vergebene Rechnungsnummern, eine Ablauf-Startseite, eine benannte Rechnungsübersicht und ausführliche deutsche und englische Quelltexterklärungen.
  • 1.0.6: Zelyra UI stellt compilergeprüfte Komponenten und lokal gebündelte Stile für erzeugtes CRUD, Projektvorlagen, Studio-Eigenschaften und Rechnungstutorial bereit. Dazu kommen Themes, responsive Layouts, Diagrammkomponenten, Browserdruck/PDF und vorausgefüllte E-Mail-Entwürfe.
  • 1.0.7: Wertliefernde if-Ausdrücke, funktionale Record-Updates (Customer { ..old, active: true }), typisierte lexikalische Lambdas für Pipelines (map, filter, find, reduce), verschachtelte Record-Muster und boolesche Match-Guards, First-Class-Tupel als Rückgabewerte und Zuweisungsziele, kontextuelle Feldnamen (version, type), konfigurierbare Schrittbegrenzung (--max-steps), nominale Id-Aliase (type OrderId = Id), serverseitiges lokales Text-PDF-Rendering (render_pdf) und transaktionale PDF-Mail-Anhänge über die MariaDB-Outbox.
  • 1.1.0: Studio-Verbesserungen mit Typed-Hole-Vorschlägen, Datenkarte für Schemata, statischer Seitenvorschau ohne Codeausführung und Live-Theme-Editor für zelyra.theme.css.
  • 1.1.1: CLI- und Editor-Stabilitätsverbesserungen bei JSON-RPC-Diagnosen und Pfadauflösungen.
  • 1.1.2: Vollständige Entfernung des experimentellen Laravel-Analysators und Gerüstbefehls (zelyra migrate laravel). Der Migrator und zugehörige Berichte wurden aus Zelyra entfernt. Zelyra setzt konsequent auf native, compilergeprüfte Verträge und eigene Schemaplanung statt Code-Portierungen.
  • 1.1.5: Lokale wiederverwendbare Pakete (pkg:), Offline-Lockfile (zelyra.lock), Paketmanifeste mit deklarierten Effekten, zelyra module init/list/plan/bundle, compilergeprüfte Paketeinstellungen und CSV-Beispielmodule.
  • 1.2.0: Betriebs- und Deployment-Grundlagen, deklarative Commands und Zeitpläne, statischer Ordner public/ mit MIME-Auslieferung in serve, Apache-/Nginx-Reverse-Proxy-Konfigurationen und Systemdienst-Installation mit festem Port.
  • 1.2.5: Feste Linux-systemd-Dienste (zelyra service install/status/restart/change/remove), residenter Scheduler, Ed25519-signierte Quellpakete mit Registryauflösung und idempotente Outbox-Einrichtung.
  • 1.2.6: Wiederherstellbare Paketänderungen: serialisierte Mutationen, versioniertes Transaktionsjournal und Quarantäne-Rollback via zelyra package recover.
  • 1.2.7: Kryptografisch signierte transitive Registry-Pakete mit Trustprüfung der gesamten Closure, zyklusfreier Auflösung und deterministischen Updateplänen.
  • 1.2.8: Typisierte SQL-Query-Pläne von HIR nach MIR mit Re-Verifikation (E-MIR-VERIFY-004) und strikte Absicherung von Formular-Redirects auf lokale absolute Pfade (E-FORM-007).
  • 1.2.9: Dauerhafte Scheduler-Jobs in der MariaDB-Outbox; Ausführung über serielle Lease-Worker; Job-Statusinspektion über zelyra schedule jobs.
  • 1.2.10: Revisionsgesicherte Speicherung von Paketeinstellungen in Zelyra Studio ohne Exposition von Geheimniswerten; HostServices Versionierungsvertrag.
  • 1.2.11: Durchgängige Zelyra-UI-Harmonisierung für das Rechnungstutorial, CLI-SIGPIPE-Stabilität und verbesserte Compose-v2-Paketerkennung.
  • 1.2.12: Authentifizierte Weboberfläche zur Verwaltung von Moduleinstellungen (/__zelyra/module-settings), automatische Standardwerte für Zelyra-UI-Dialoge und direkter Theme-Zugriff in Studio.
  • 1.2.13: Kanonische Projektstruktur (zelyra new --layout standard) mit verbindlichen Wurzeln routes/, domain/, schema/, public/ und assets/ sowie sauberer Namensraumauflösung (import "domain::sales::actions").
  • 1.2.14: Deterministische Migrationsvorschau zelyra migrate-layout --check, strict_architecture = true Architekturprüfung (E-ARCH-001), gestaltete Beispielrechnung und Quelltextdialog im Rechnungstutorial.
  • 1.2.15: Typgeprüfte Erweiterungspunkte für generierte CRUD-Create-/Edit-Seiten (crud.view.create, crud.view.edit), Feldhilfen und Zeilenaktionen in tableview.
  • 1.2.16: Typisierte TableView-Zellinhalte und formatierte Zeilenaktionsslots mit HTML-Vorlagen und Escaping.
  • 1.2.17: Erzwungene Mandantenrelationen (scoped by tenant_id: TenantId), automatische zusammengesetzte Foreign Keys FOREIGN KEY (tenant_id, fk_id) REFERENCES target (tenant_id, id) ON DELETE RESTRICT, typsichere optionale Relationen (Customer?) und exakte kaufmännische Dezimalskalierung (Decimal(p, s)).
  • 1.2.18 / 1.2.19: Mandantenbezogene Eindeutigkeit unique within tenant mit automatischem zusammengesetztem MariaDB-Unique-Index UNIQUE (tenant_id, column).
  • 1.2.20: Typgeprüfte feldübergreifende Tabelleninvarianten (validate { condition: "error" }) in Formularen und CRUD sowie typisierte API-Eingabeprüfung mit validates.
  • 1.2.21: Datensatzabhängige Lifecycle-Guards (guard edit when ..., guard delete when ...) und Policy-Auswertung in Detailansichten und TableView-Slots.
  • 1.2.22: Generierte transaktionale Master-Detail-CRUD-Formulare (has_many) mit atomarer Speicherung von Kopf- und Positionsdaten, Kaskadierung und serverseitigen Aggregaten (aggregates).
  • 1.2.23: Validierung mandantengebundener APIs (scoped by tenant_id) aus geprüften Benutzersitzungen; zuverlässige Mandantentrennung auf API-Ebene.
  • 1.2.24: Unterstützung mehrerer direkter has_many-Kinddatensammlungen pro CRUD-Ressource (z. B. Positionen und Anhänge) mit atomarer Speicherung, unabhängigen Zeilenlimits und serverseitigen Aggregaten.
  • 1.2.25: Native Datenbankkonsole zelyra db console für MariaDB/MySQL/PostgreSQL und Direktausführung einzelner SQL-Anweisungen mit zelyra db query (standardmäßig schreibgeschützt, Tabellen-, JSON- und CSV-Ausgabe, --tenant-Parameter).
  • 1.2.26: Interaktive AST-Sprachkonsole zelyra console mit persistenten typisierten Bindungen, Multi-Line-Eingabe, Tab-Vervollständigung und transaktionaler MariaDB-/MySQL-Sandbox mit automatischem Rollback (:commit, :rollback, :tables, :schema).
  • 1.2.27: Read-Only Laravel-Importanalysator (zelyra import laravel analyze|generate) zur quellbelegten Rekonstruktion von Schemata, Model-Invarianten und wiederverwendbaren nativen Authentifizierungsmodulen.
  • 1.2.28: Typisierte, transaktionale CRUD-Actions (action) mit compilergeprüften row- und session-Kontexten, reinen Guards unter Datensatzsperre (Row-Lock) und gemeinsamer Transaktion für Fachänderungen, Audit-Log und PDF-Outbox. Absicherung der REPL-Sandbox gegen implizite DDL-Commits.
  • 1.2.29: Sichere Multipart-Datei-Uploads mit typisiertem File-Formularfeld, Größenbegrenzung und Signaturvalidierung (PDF/PNG/JPEG). Privater, mandantengebundener Speicher mit opaken FileRef-Tokens, autorisierte CRUD-Downloads, automatische Waisenbereinigung und prüfsummenvalidiertes Backup/Restore (zelyra files cleanup/backup/restore).
  • 1.2.30: Skalare Fremdschlüssel-Ziele (references <table>), zusammengesetzte Mandanten-Constraints, erweiterte Laravel-Importanalyse für Pivot-Tabellen und Mandanten-Routentoken.
  • 1.2.31: Mandantenbezogene Geschäftsnummernkreise (auto_sequence), automatische UTC-Jahrestrennung ({year}), verwaltete Tabelle _zelyra_number_sequences, MariaDB-Constraint-Korrektur und SQLite-Ablehnung (E-DB-001).
  • 1.2.32: Typisierte berechnete Geschäftsfelder (compute) mit serverseitiger Neuberechnung und Schutz vor Client-Overrides, FileRef-Anhänge im generierten CRUD mit geschützten Downloads, lokalisierte has_many-Legenden und Account-Approval-Guard (login_enabled).
  • 1.2.33: Quellgenaue Schrittbudget-Abstimmung (--max-steps) über AST, MIR und Bytecode-VM, transaktionale PDF-Mail-Outbox (render_pdf + mail_queue), einheitliche Transaktionsfehler-Diagnosen.
  • Roadmap bis 1.3.0: Offene Compiler-, Laufzeit-, Paket- und Projektstrukturarbeiten stehen in der Roadmap. Geplante Funktionen wie vollständige Backend-Parität sind nicht als bereits veröffentlicht zu verstehen.

Release-Details: Die vollständige Liste der veröffentlichten Versionen steht in den Zelyra-Releases auf GitHub. Die Studio-Anleitung, Logs-Anleitung, Deinstallationsleitfaden, Mail-Anleitung und Datenbankmatrix beschreiben weitere Funktionen und Grenzen.

Entwicklungsstand und vereinbarte Architektur

Zelyra befindet sich im stabilen Release 1.2.39 mit aktiver Weiterentwicklung. Den laufenden Stand und offene Ziele hält die Zelyra-Roadmap fest.

  • Residente Zeitpläne und integrierter Scheduler (✅ Implementiert seit 1.2.5/1.2.9): Commands und fünfteilige numerische Zeitpläne (schedule <name> { cron: "..."; timezone: "..."; command: ... }) werden direkt im Zelyra-Quelltext deklariert. Eine wöchentliche Ausführung samstags um 12:00 Uhr verwendet beispielsweise cron: "0 12 * * 6" und timezone: "Europe/Berlin". Der integrierte residente Scheduler läuft automatisch innerhalb des aktiven Zelyra-Dienstes (zelyra service bzw. zelyra serve). An jeder Minutengrenze wertet er fällige Zeitpläne aus und reiht Jobs transaktional in die MariaDB-Outbox ein. Ein serieller Lease-Worker (zelyra schedule jobs) verarbeitet Aufgaben zuverlässig mit automatischer Verlängerung und begrenzten Wiederholungen. Eine manuelle crontab -e-Konfiguration auf dem Host ist für den regulären Zelyra-Betrieb nicht erforderlich. Der Befehl zelyra schedule export dient lediglich als optionale Vorschau oder Export für externe Drittsysteme; Zelyra überschreibt niemals ungefragt fremde Crontabs.
  • Betriebssystemdienste (✅ Implementiert): zelyra service install --port <port> richtet einen systemd-Benutzerdienst auf einem festen Port ein. zelyra service status, restart, change --port <port> und remove verwalten den Lebenszyklus mit atomarem Rollback bei Fehlern.
  • Kanonische Projektstruktur und Vertical Slices (✅ Implementiert seit 1.2.13/1.2.14): --layout standard erzwingt eine getrennte Verzeichnisstruktur (schema/, domain/, routes/, statisches public/). strict_architecture = true prüft Architekturverletzungen (E-ARCH-001), und zelyra migrate-layout --check bietet eine deterministische Migrationsvorschau.
  • Erweiterbare CRUD- und TableView-Seiten (✅ Implementiert seit 1.2.15/1.2.16): Generierte Create- und Edit-Seiten stellen typgeprüfte Erweiterungspunkte bereit (crud.view.create, crud.view.edit), und Tabellenansichten unterstützen Feldhilfen und formatierte Zeilenaktionsslots.
  • Mandantensicherheit und zusammengesetzte Fremdschlüssel (✅ Implementiert seit 1.2.17/1.2.19): table ... scoped by tenant_id: TenantId erzwingt die Mandantentrennung auf Datenbankebene über zusammengesetzte Fremdschlüssel. unique within tenant verhindert Kollisionen bei mandanteneigenen Nummernfolgen.
  • Tabelleninvarianten und Validierungsregeln (✅ Implementiert seit 1.2.20): Typgeprüfte Invarianten auf Tabellenebene (validate) werden in Formularen und CRUD vor jedem Schreibvorgang evaluiert.
  • Lifecycle-Guards und Master-Detail-CRUD mit mehreren Kind-Sammlungen (✅ Implementiert seit 1.2.21 – 1.2.24): GoBD-Sperren für edit und delete (guard), transaktionale Erfassung mehrerer 1:N-Kinddatensammlungen (has_many) mit serverseitigen Aggregaten und API-Mandantenvalidierung aus geprüften Sitzungen.
  • Direkte Datenbank-CLI und interaktive Sprachkonsole (✅ Implementiert seit 1.2.25/1.2.26): zelyra db console, zelyra db query und zelyra console mit Transaktions-Sandbox und Rollback.
  • Laravel-Importgrundlage (✅ Implementiert seit 1.2.27): Schreibgeschützte Strukturanalyse und selektive Generierung nativer Zelyra-Module.
  • Typisierte CRUD-Actions (✅ Implementiert seit 1.2.28): Fachaktionen mit Datensatzsperren, reinen Guards und atomarer Transaktionsbündelung mit Audit und Outbox.
  • Sichere Datei-Uploads und Mandantenspeicher (✅ Implementiert seit 1.2.29): Bounded Multipart-Uploads, Signaturprüfung, privater Speicher mit FileRef und Dateispeicher-Backup/Restore.
  • Generierte .env: Bei Projektvorlagen enthält .env neben aktiven Einstellungen auch auskommentierte Konfigurationsbeispiele. Kommentarzeilen dienen der Dokumentation: Sie werden weder als Shell-Skripte noch von Compose als Befehle interpretiert. Zugangsdaten gehören in die geschützte lokale .env, niemals in Git oder Quellcode.
  • Datenbankentwicklung: Zelyra-Projekte beschreiben Schemata deklarativ über Zelyra-Tabellen. zelyra db plan, apply und history sind die maßgeblichen Werkzeuge; Laravel-Migrationsdateien sind keine Laufzeitvoraussetzung. Der frühere experimentelle Laravel-Migrator wurde aus dem Zelyra-Produkt entfernt.
  • Roadmap bis 1.3.0: Offene Compiler-, Laufzeit-, Paket- und Projektstrukturarbeiten stehen in der Roadmap. Geplante Funktionen wie vollständige Backend-Parität sind nicht als bereits veröffentlicht zu verstehen.

Zeitpläne und Outbox im Betrieb

In Zelyra-Projekten richtet das System bei der Bereitstellung alle erforderlichen Outbox- und Scheduler-Tabellen idempotent ein. Der residente Scheduler wird automatisch gestartet, sobald das Projekt über den Dienst (zelyra service) oder zelyra serve betrieben wird.

Folgende Befehle stehen zur Inspektion und Verwaltung bereit:

zelyra check main.zyl
zelyra task list main.zyl
zelyra schedule list main.zyl
zelyra schedule jobs main.zyl
zelyra schedule status main.zyl

Ein manueller Cron-Eintrag auf dem Host (crontab -e) ist für den regulären Betrieb nicht erforderlich. Für den Fall, dass ein bestehendes externes Überwachungssystem oder ein externer Server-Scheduler die Zelyra-Befehle ansteuern soll, kann mit:

zelyra schedule export main.zyl --format cron --output deploy/zelyra.cron

eine exportierbare Cron-Vorschau erzeugt werden. zelyra schedule export überschreibt keine vorhandene Datei und greift nicht in die System-Crontab ein. Die unterstützten Umgebungsvariablen stehen in der Environment-Referenz; die detaillierte Scheduler- und Task-Architektur wird in Kapitel 28 behandelt.

Statuszeichen

  • ✅ Implementiert und geprüft: im dokumentierten Funktionsumfang vorhanden und durch die angegebenen Quellen belegt.
  • 🧪 Experimentell: vorhanden, aber noch jung oder eingeschränkt.
  • 🗺️ Geplant: Teil der Sprachvision, noch nicht zuverlässig verfügbar.
  • ❌ Derzeit nicht verfügbar: im aktuellen CLI nicht vorhanden.

Inhaltsverzeichnis

Zelyra lernen – Das Lehrbuch

Technisches Referenzhandbuch

Anhänge

TEIL I – ZELYRA UND PROGRAMMIERUNG VERSTEHEN


Kapitel 1: Willkommen bei Zelyra

1. Was lerne ich in diesem Kapitel?

In diesem Einführungskapitel erfährst du:

  • Was eine Programmiersprache im Kern ist und welche Aufgabe sie erfüllt.
  • Was Zelyra besonders macht und warum es als eigenständige Sprache entwickelt wurde.
  • Welche praktischen Ziele Zelyra verfolgt und für welche Aufgaben es sich besonders eignet.
  • Wie Zelyra Lesbarkeit, Zuverlässigkeit und Sicherheit von vornherein garantiert.
  • Warum Zelyra sowohl für menschliche Entwickler als auch für KI-Systeme entworfen wurde.
  • Wie dieses Lehrbuch aufgebaut ist und wie du am besten damit arbeitest.

2. Warum ist das Thema wichtig?

Bevor du die erste Zeile Code schreibst, solltest du verstehen, welches Problem Zelyra löst. Kennst du das Gefühl, wenn ein winziger Buchstabendreher in Zeile 412 erst am Freitagabend um 17:03 Uhr im Produktivbetrieb bemerkt wird – just in dem Moment, in dem du die Jacke anziehen wolltest? In der modernen Softwareentwicklung herrscht oft ein heilloses Durcheinander: Man modelliert eine Tabelle in SQL, schreibt dieselben Regeln noch einmal in einem Backend-Framework (wie Laravel, Express oder Django), validiert dieselben Daten ein drittes Mal im Frontend (HTML/JavaScript) und hofft inständig, dass alle Schichten synchron bleiben. Zelyra bricht mit dieser Fragmentierung: Du beschreibst dein Datenmodell, deine Regeln und deine Schnittstellen an einer einzigen Stelle – und Zelyra leitet daraus geprüfte, sichere Bausteine ab. Keine doppelten Definitionen, keine Synchronisationskatastrophen und vor allem: entspanntere Freitagnachmittage.

3. Verständliche Erklärung

Eine Programmiersprache ist wie ein präzises Regelwerk. Sie erlaubt es dir, einem Computer eindeutige Anweisungen zu geben. Computer sind unfassbar schnelle Rechenknechte, glänzen aber durch die vollständige Abwesenheit von gesundem Menschenverstand: Wenn du einem Computer sagst: »Lauf zum Bäcker und kauf ein Brot, und wenn sie Eier haben, bring zehn mit«, kommt er im Zweifel mit zehn Broten zurück. In Zelyra nimmt der Compiler dich und den Rechner an die Hand, damit genau solche Missverständnisse gar nicht erst kompiliert werden.

Zelyra ist eine statisch typisierte Sprache. Das bedeutet: Der Compiler prüft Typen beim Übersetzen und kann bestimmte Typkonflikte vor der Ausführung melden. Welche weiteren Regeln er prüft, hängt vom implementierten Prüfumfang ab; eine Fehlerfreiheit des Programms garantiert das nicht.

Gleichzeitig ist Zelyra datenbank- und webzentriert:

  • Ein Tabellenschema (table) ist keine isolierte SQL-Datei, sondern integraler Bestandteil der Sprache.
  • Variablen sind standardmäßig unveränderlich (immutable). Dadurch kann sich ein Wert nicht plötzlich im Hintergrund ändern.
  • Keine Überraschungen durch null: Fehlende Werte müssen ausdrücklich als Option deklariert werden.

4. Kleine, aufeinander aufbauende Beispiele

Schauen wir uns ein erstes winziges Zelyra-Programm an:

# Unser erstes Zelyra-Programm: Eine Begrüßung
fn main() {
    print("Willkommen bei Zelyra!")
}

Wenn du diesem Programm mehr Struktur geben möchtest, zerlegst du die Aufgabe in eine Funktion:

fn begruessen(name: String) -> String {
    return "Hallo, " + name + "! Willkommen in der Zelyra-Welt."
}

fn main() {
    nachricht = begruessen("Entwickler")
    print(nachricht)
}

Kommentare: # und ##

# beginnt einen Zeilenkommentar. Er kann am Zeilenanfang oder hinter einer Anweisung stehen und reicht bis zum Zeilenende. Ein Blockkommentar wird durch zwei ##-Begrenzer eingeschlossen. Die Begrenzer stehen jeweils allein auf einer Zeile; der Inhalt darf mehrere Zeilen umfassen. Blockkommentare sind nicht verschachtelt. # innerhalb einer Zeichenkette gehört zum Text.

# Ein Zeilenkommentar
##
Dieser Text ist ein Blockkommentar.
Er kann mehrere Zeilen umfassen.
##
fn main() {
    print("Hallo") # Kommentar hinter einer Anweisung
    print("# bleibt innerhalb der Zeichenkette sichtbar")
}

Die bestehende Schreibweise // bleibt kompatibel. Verwende in neuen Beispielen #. Die kompakte Schreibweise ## Text ## auf derselben Zeile ist im dokumentierten Lexer kein Blockkommentar über mehrere Zeilen; verwende für einen Block die oben gezeigten alleinstehenden Begrenzer.

5. Typische Fehler und deren Ursachen

  • Fehler: Ein Semikolon am Zeilenende setzen (wie in Java, C++ oder PHP). Ursache: In Zelyra trennen Zeilenumbrüche Anweisungen sauber ab. Überflüssige Satzzeichen stören das Schriftbild.
  • Fehler: Zelyra für ein Framework oder eine Konfigurationssprache halten. Ursache: Zelyra hat eine eigene Syntax und ein eigenes Typsystem. Der Compiler übersetzt und prüft Zelyra-Programme; Laufzeit und Projektwerkzeuge ergänzen die Sprache für unterstützte Anwendungsfälle.

6. Merksätze

  1. Zelyra verbindet Datenmodell, Logik und Oberfläche in einer einzigen klaren Sprache.
  2. Was du meinst, steht im Code: Keine versteckte Magie, keine impliziten Null-Werte.
  3. Der Compiler meldet erkannte Syntax- und Typfehler vor der Ausführung. Laufzeitfehler und nicht abgedeckte Prüffälle bleiben möglich.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Ändere das Begrüßungsprogramm so, dass es deinen eigenen Vornamen und Wohnort ausgibt.
  • Stufe 2 (Mittel): Schreibe eine zweite Funktion verabschieden(name: String) -> String, die einen Abschiedsgruß formuliert, und rufe beide Funktionen in main() auf.
  • Stufe 3 (Anspruchsvoll): Überlege dir drei typische Probleme, die in anderen Sprachen durch Tippfehler entstehen (z. B. eine Zahl statt Text übergeben), und begründe, wie ein Compiler davor schützt.

8. Praxisaufgabe: Der Grundstein der Aufgabenverwaltung

In diesem Buch bauen wir Schritt für Schritt eine echte, praxistaugliche Aufgabenverwaltung (Task Management). Wir beginnen mit dem einfachsten Schritt: Erstelle eine Datei aufgaben_start.zyl, die den Namen unseres Systems und die Versionsnummer sauber auf dem Bildschirm ausgibt:

fn main() {
    system_name = "Zelyra TaskManager"
    app_version = "0.9.9"
    print(system_name + " (Version " + app_version + ") gestartet.")
}

9. Zusammenfassung

  • Zelyra ist eine statisch typisierte, sichere und lesbare Sprache für Geschäftslogik, Datenbanken und das Web.
  • Zelyra eliminiert Redundanzen zwischen Datenbank-Definitionen, Validierung und API.
  • Variablen sind standardmäßig unveränderlich; der Compiler garantiert Stabilität und Klarheit.

10. Kontrollfragen zur Selbstprüfung

  1. Was unterscheidet eine statisch typisierte Sprache von einer dynamischen Sprache?
  2. Warum ist es ein Vorteil, wenn Tabellenschemata direkt in der Programmiersprache definiert werden?
  3. Warum sind unveränderliche Werte standardmäßig sicherer als veränderliche Variablen?

Kapitel 2: Wie ein Programm funktioniert

1. Was lerne ich in diesem Kapitel?

  • Wie dein geschriebener Quelltext Schritt für Schritt in ein ausgeführtes Programm verwandelt wird.
  • Welche Rollen Lexer, Parser, Type Checker, Verifier und Interpreter in Zelyra spielen.
  • Was genau beim Start eines Zelyra-Programms geschieht.
  • Was der Unterschied zwischen Syntax (Form) und Semantik (Bedeutung) ist.
  • Was ein Algorithmus ist und wie das EVA-Prinzip (Eingabe, Verarbeitung, Ausgabe) funktioniert.

2. Warum ist das Thema wichtig?

Programmierfehler zu beheben ist kinderleicht, wenn man versteht, an welcher Station des Compilers der Fehler gemeldet wird. Ein Computer ist im Grunde ein extrem fleißiger, aber erschreckend wörtlich nehmender Assistent: Er tut haargenau das, was du ihm aufschreibst – niemals das, was du dir insgeheim dabei gedacht hast. Ein Syntaxfehler bedeutet, dass der Text unlesbar ist (wie ein Rezept, in dem »Salz« mit drei Konsonanten geschrieben wurde); ein Typfehler bedeutet, dass die Logik widersprüchlich ist (wie der Versuch, Milch zu braten); ein Laufzeitfehler bedeutet, dass während der Ausführung eine unvorhergesehene Bedingung eintrat. Wenn du diese Kette verstehst, verlierst du jegliche Scheu vor Fehlermeldungen: Der Compiler ist kein schlecht gelaunter Türsteher, sondern dein treuer Pair-Programming-Partner.

3. Verständliche Erklärung

Ein Computerprozessor versteht nur Nullen und Einsen (Maschinencode). Wenn wir Menschen eine Textdatei mit Zelyra-Code schreiben (z. B. programm.zyl), durchläuft dieser Text mehrere Stationen:

Quelltext (.zyl)
   │
   ▼
[1. Lexer]: Zerlegt den Text in Wörter/Symbole (Tokens)
   │
   ▼
[2. Parser]: Baut einen logischen Strukturbaum (AST = Abstract Syntax Tree)
   │
   ▼
[3. Type Checker]: Prüft alle Typen, Namen und Berechtigungen
   │
   ▼
[4. Verifier]: Beweist mathematisch Schleifen und Verträge (Contracts)
   │
   ▼
[5. Runtime / Interpreter]: Führt die geprüften Anweisungen aus
  • Syntax ist die Grammatik: Setzt du Klammern richtig? Schreibst du Schlüsselwörter korrekt?
  • Semantik ist der Sinn: Wenn du schreibst alter = "fünfundzwanzig", ist das syntaktisch Text, aber wenn du damit rechnen willst, ergibt es semantisch keinen Sinn.
  • Algorithmus: Eine präzise, endliche Schritt-für-Schritt-Anleitung zur Lösung eines Problems.

4. Kleine, aufeinander aufbauende Beispiele

Ein einfacher Algorithmus zur Berechnung der verbleibenden Tage bis zu einer Frist:

fn tage_bis_ziel(ziel_tag: Int, aktueller_tag: Int) -> Int {
    verbleibend = ziel_tag - aktueller_tag
    return verbleibend
}

fn main() {
    heute = 10
    abgabe = 24
    tage = tage_bis_ziel(abgabe, heute)
    print(tage)
}

5. Typische Fehler und deren Ursachen

  • Fehler: Ein geschweifte Klammer { öffnen, aber nicht schließen }. Ursache: Dies ist ein reiner Syntaxfehler. Der Parser bricht sofort ab (E-PARSE-001), weil der Baum unvollständig ist.
  • Fehler: Einer Zahl eine Zeichenkette zuweisen: alter: Int = "20". Ursache: Das ist ein Typfehler (E-TYPE-001). Der Parser versteht die Form, aber der Type Checker stoppt die Ausführung.

6. Merksätze

  1. Der Lexer liest Zeichen, der Parser versteht Strukturen, der Type Checker prüft den Sinn.
  2. Je früher ein Fehler abgefangen wird (beim Prüfen statt beim Kunden), desto günstiger und sicherer ist die Software.
  3. Jedes Programm folgt dem Grundmuster: Eingabe empfangen, nach klaren Regeln verarbeiten, Ergebnis ausgeben.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Zeichne auf einem Blatt Papier den Ablauf von main() im obigen Fristen-Beispiel als Pfeildiagramm auf.
  • Stufe 2 (Mittel): Erweitere die Funktion tage_bis_ziel um eine Prüfung mit if: Wenn aktueller_tag > ziel_tag ist, soll 0 zurückgegeben werden.
  • Stufe 3 (Anspruchsvoll): Erkläre in eigenen Worten, warum Zelyra vor der Ausführung prüft (zelyra check), anstatt den Code Zeile für Zeile blind auszuführen.

8. Praxisaufgabe: Aufgaben-Priorität berechnen

In unserer Aufgabenverwaltung müssen wir die Dringlichkeit einer Aufgabe einstufen. Wenn weniger als 3 Tage verbleiben, ist die Aufgabe dringend:

fn ist_dringend(verbleibende_tage: Int) -> Bool {
    return verbleibende_tage <= 3
}

fn main() {
    frist_in_tagen = 2
    dringend = ist_dringend(frist_in_tagen)
    if dringend {
        print("Achtung: Aufgabe hat hohe Prioritaet!")
    } else {
        print("Aufgabe liegt im normalen Zeitplan.")
    }
}

9. Zusammenfassung

  • Programme durchlaufen eine feste Kette: Lexing, Parsing, Typprüfung, Verifikation und Ausführung.
  • Zelyra stellt sicher, dass Syntax und Typen stimmen, bevor ein Programm gestartet wird.
  • Ein Algorithmus verwandelt Eingaben durch logische Einzelschritte in verlässliche Ausgaben.

10. Kontrollfragen zur Selbstprüfung

  1. An welcher Stelle der Toolchain wird bemerkt, dass ein Anführungszeichen fehlt?
  2. Was bedeutet das EVA-Prinzip?
  3. Warum bricht ein statisch typisiertes Programm ab, wenn man Text und Zahl fehlerhaft verbindet?

Kapitel 3: Zelyra installieren und einrichten

1. Was lerne ich in diesem Kapitel?

  • Die Systemvoraussetzungen für die Zelyra-Entwicklungsumgebung auf Linux, macOS und Windows.
  • Plattformspezifische Docker-Installation und Verifikation mit docker compose version.
  • Wie du Zelyra über den Quellcode (./install.sh / install.ps1) oder das Release-Archiv (--release v1.2.39) installierst.
  • Vollständige Versionsabfrage mit zelyra --version und Systemdiagnose mit zelyra doctor.
  • Den integrierten, token-geschützten Web-Setup-Assistenten (zelyra setup --web).
  • Typische Berechtigungs- und Port-Konflikte (z. B. Docker-Socket-Rechte, automatische Port-Wahl).

2. Warum ist das Thema wichtig?

Eine reibungslos funktionierende Werkzeugkette ist die Grundlage jeder erfolgreichen Entwicklungsarbeit. Wenn Befehle nicht gefunden werden oder Umgebungsvariablen fehlen, verliert man wertvolle Zeit. Zelyra bringt einen schlanken, benutzerlokalen Installer mit, der ohne fremde Paketmanager oder Root-Rechte auskommt. Zudem führt der neue Web-Setup-Assistent Einsteiger visuell durch die Initialisierung von Datenbank und Containern.

3. Verständliche Erklärung

Zelyra benötigt für einfache Programme weder Apache noch fremde Laufzeiten. Das Zelyra-CLI (zelyra) ist ein einzelnes, hochoptimiertes Binärprogramm.

Je nach Betriebssystem wählst du den passenden Weg:

  1. Linux / macOS: Hier lädst du das offizielle Repository herunter und führst ./install.sh aus (oder lädst ein Release-Archiv). Zelyra wird benutzerlokal in ~/.local/bin installiert. Für Docker nutzt man unter Linux die offizielle Docker-Engine-Anleitung und unter macOS Docker Desktop für Mac.
  2. Windows: Unter Windows stehen install.ps1 für PowerShell sowie install.cmd zur Verfügung. Für vollständige MariaDB-Projekte wird Docker Desktop für Windows mit aktivierter Compose-Unterstützung empfohlen.
  3. Plattform-Docker-Prüfung: Zelyra installiert Docker bewusst nicht eigenmächtig und fordert keine Root-Rechte an. Vor dem Start von Compose-Diensten prüfst du deine Umgebung einfach mit docker compose version. Fehlt Compose oder fehlen Berechtigungen auf den Docker-Socket, gibt Zelyra gezielte Plattform-Hilfestellungen aus.

4. Kleine, aufeinander aufbauende Beispiele

Schritt 1: Zelyra installieren Aus dem Quelltext (Linux / macOS):

git clone https://github.com/sf1976/zelyra.git
cd zelyra
./install.sh

Oder direkt als vorkompiliertes Release ohne Rust-Toolchain:

./install.sh --release v1.2.39

Unter Windows (PowerShell):

git clone https://github.com/sf1976/zelyra.git
Set-Location zelyra
.\install.ps1 -Release v1.2.39

Schritt 2: Vollständige Version und Hilfe prüfen

zelyra --version
zelyra --help

zelyra --version gibt den vollständigen Compiler- und Paketversionsstand aus (z. B. zelyra 1.2.39). Die Sprachkompatibilitätslinie bleibt 0.1.

Schritt 3: Docker Compose prüfen (für MariaDB-Projekte)

docker compose version

Schritt 4: Systemdiagnose ausführen

zelyra doctor

Dieser Befehl analysiert Umgebung, Pfade, Ports und Werkzeuge. Mit zelyra doctor --json erhältst du strukturierte Maschinendaten für IDEs.

Schritt 5: Geführter Web-Setup-Assistent (Optional) In jedem MariaDB-Projektverzeichnis kannst du den grafischen Assistenten starten:

zelyra setup --web

Zelyra öffnet einen lokalen HTTP-Server auf 127.0.0.1:3030 mit einem zufälligen, einmaligen Sicherheitstoken. Dort kannst du mit einem Klick die .env-Konfiguration erzeugen, den MariaDB-Container starten und das Datenbankschema anwenden.

zelyra setup richtet die MariaDB-Umgebung ein. Ein Minimalprojekt, das mit zelyra init ohne --mariadb angelegt wurde, läuft direkt mit zelyra run main.zyl und benötigt diesen Setup-Schritt nicht. Pfade werden wörtlich interpretiert: Nach mkdir test_a zeigt ./test_a auf diesen Ordner; /test_a bezeichnet unter Linux dagegen einen Ordner direkt unter dem Dateisystemstamm. Existiert nur der relative Ordner, schlägt die CLI ihn als Pfadkorrektur vor.

5. Typische Fehler und deren Ursachen

  • Fehler: zelyra: command not found Ursache: Der Ordner ~/.local/bin ist noch nicht in deiner $PATH-Variable. Führe export PATH="$HOME/.local/bin:$PATH" aus oder starte dein Terminal neu.
  • Fehler: permission denied while trying to connect to the Docker daemon socket Ursache: Auf Linux-Systemen hat dein Benutzer noch keine Rechte auf den Docker-Socket. Führe sudo usermod -aG docker $USER aus und melde dich neu an. Zelyra fängt diesen Fehler ab und gibt einen klaren Hinweis.
  • Fehler: Standard-Port 3000 oder 3306 ist belegt. Ursache: Ein anderer lokaler Dienst belegt den Port. Zelyra wählt bei zelyra setup und zelyra new automatisch den nächsten freien Host-Port, sodass kein Konflikt entsteht.

6. Merksätze

  1. Das Zelyra-CLI bündelt Compiler, Runner, Formularprüfer, Webserver und Setup-Assistenten in einem einzigen Werkzeug.
  2. Mit docker compose version und zelyra doctor überprüfst du jederzeit den Zustand deiner Toolchain.
  3. Das Release kann mit --release v1.2.39 direkt ohne Rust-Compiler installiert werden.
  4. zelyra setup --web bietet eine intuitive, browserbasierte Ersteinrichtung mit sicherem Einmal-Token.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Führe zelyra doctor in deinem Terminal aus und notiere dir die Versionsnummer.
  • Stufe 2 (Mittel): Erkunde die Hilfeseite mit zelyra check --help und schau dir die Option --format json an.
  • Stufe 3 (Anspruchsvoll): Richte in deinem bevorzugten Editor (z. B. VS Code) eine Dateizuordnung ein, sodass .zyl-Dateien automatisch als Zelyra-Dateien erkannt werden.

8. Praxisaufgabe: Die Arbeitsumgebung für den TaskManager vorbereiten

Lege auf deinem Rechner einen neuen Ordner für unser Projekt an und teste, ob die Zelyra-Toolchain dort ordnungsgemäß funktioniert:

mkdir mein-taskmanager
cd mein-taskmanager
echo 'fn main() { print("TaskManager-Umgebung bereit.") }' > test.zyl
zelyra check test.zyl
zelyra run test.zyl

Wenn die Ausgabe TaskManager-Umgebung bereit. erscheint, ist dein System perfekt vorbereitet!

9. Zusammenfassung

  • Zelyra wird über ein einfaches Skript (./install.sh) oder Docker eingerichtet.
  • Das Kommandozeilenwerkzeug zelyra enthält alle notwendigen Funktionen.
  • zelyra doctor stellt sicher, dass alles einwandfrei konfiguriert ist.

10. Kontrollfragen zur Selbstprüfung

  1. Welcher Befehl zeigt alle verfügbaren CLI-Optionen an?
  2. Warum benötigt Zelyra für Konsolen- und Web-Programme keinen externen Webserver wie Apache?
  3. Was prüft der Befehl zelyra doctor?

Kapitel 4: Das erste Zelyra-Projekt

1. Was lerne ich in diesem Kapitel?

  • Wie man ein Zelyra-Projekt mit zelyra new oder zelyra init anlegt (inkl. --mariadb).
  • Wie ein Standard-Projektordner strukturiert ist (main.zyl, zelyra.toml, .env).
  • Automatische Portvergabe (ZELYRA_HOST_PORT und ZELYRA_DB_HOST_PORT) bei belegten Ports.
  • Wie zelyra setup --all und zelyra setup --web den Erststart automatisieren.
  • Optionale Feature-Schalter ([features] in zelyra.toml oder .env) und Prüfung mit zelyra config.
  • Wann ein Programm eine main()-Funktion benötigt und wie man Programme prüft, ausführt und formatiert.

2. Warum ist das Thema wichtig?

Sobald Programme mehr als zehn Zeilen umfassen, gehören sie in eine saubere Projektstruktur. Eine gut organisierte Ordnerstruktur stellt sicher, dass Konfigurationen, Datenbankmodelle, Web-Routen und Geschäftslogik ihren festen Platz haben. Wer von Beginn an Projekte standardisiert anlegt, spart sich später aufwändige Aufräumarbeiten.

3. Verständliche Erklärung

Mit dem Befehl zelyra new <projektname> erzeugst du ein schlüsselfertiges Projekt:

  • main.zyl: Die Hauptdatei deines Programms. Hier definierst du entweder den Einstiegspunkt fn main() oder deklarierst deine Tabellen, Webseiten und APIs.
  • zelyra.toml: Die dauerhafte Projektkonfiguration (Name, Version, Capabilities und optionale Feature-Schalter wie web, api, crud, auth, audit).
  • .env: Lokale, nicht in Git eingecheckte Geheimnisse und Ports (DATABASE_URL, ZELYRA_HOST_PORT, ZELYRA_DB_HOST_PORT).
  • Docker & MariaDB: Mit --mariadb legt Zelyra zusätzlich Dockerfile, docker-compose.mariadb.yml und .env.example an.

Automatische Portvergabe: Sind die Standardports 3000 (Web) oder 3306 (MariaDB) auf deinem Entwicklungsrechner bereits belegt, scannt Zelyra automatisch und vergibt freie Ports in der neu erzeugten .env.

Optionale Feature-Schalter: Du kannst in zelyra.toml oder .env Teilbereiche aktivieren oder deaktivieren:

[features]
web = true
api = true
crud = true
auth = true
audit = true

Wird ein deaktivierter Bereich im Code genutzt, meldet der Compiler verlässlich E-FEATURE-001. Die wirksame Konfiguration kannst du jederzeit mit zelyra config main.zyl (oder --format=json) geheimnisfrei inspizieren.

4. Kleine, aufeinander aufbauende Beispiele

Projekt erstellen (Minimal oder MariaDB):

# Minimales Skriptprojekt:
zelyra new taskmanager --template minimal
cd taskmanager

# Oder vollständiges MariaDB-Webprojekt:
zelyra new taskmanager-web --mariadb
cd taskmanager-web

Ersteinrichtung mit einem einzigen Befehl:

zelyra setup --all

Dieser Befehl legt eine geschützte .env an, startet die MariaDB-Container-Umgebung und wendet das Schema an.

Projektkonfiguration prüfen:

zelyra config main.zyl

Projekt prüfen und starten:

zelyra check main.zyl
zelyra run main.zyl

Projekt automatisch formatieren:

zelyra fmt main.zyl

zelyra fmt sorgt dafür, dass aller Zelyra-Code im gesamten Team exakt denselben, sauberen Gestaltungsregeln folgt.

Weitere dokumentierte CLI-Befehle für den Projektalltag:

zelyra verify main.zyl
zelyra db plan main.zyl
zelyra editor

zelyra verify führt die unterstützten Vertrags- und Invariantenprüfungen aus; Kapitel 19 erklärt ihre Reichweite. zelyra db plan zeigt geplante Schemaänderungen. Prüfe diesen Plan, bevor du Änderungen mit zelyra db apply übernimmst; Kapitel 24 beschreibt den Ablauf. zelyra editor startet Zelyra Studio.

Die aktuelle CLI-Referenz führt weder einen Unterbefehl zelyra test noch separate Programme zelyrac build oder zelyra lsp auf. Assertions beschreibst du mit assert(...) in einer ausführbaren .zyl-Datei und startest sie mit zelyra run; einen eigenen Test-Runner dokumentiert die Referenz nicht.

5. Typische Fehler und deren Ursachen

  • Fehler: Dateien ohne die Dateiendung .zyl anlegen. Ursache: Der Compiler erwartet ausdrücklich Zelyra-Quelldateien mit der Endung .zyl.
  • Fehler: Ein CLI-Programm ohne fn main() starten. Ursache: Wenn Zelyra per zelyra run gestartet wird, sucht es nach fn main(). Fehlt diese Funktion, bricht die Ausführung ab.

6. Merksätze

  1. zelyra new erstellt eine saubere, standardisierte Projektstruktur.
  2. In zelyra.toml werden Name, Version und benötigte Berechtigungen (Capabilities) verwaltet.
  3. zelyra fmt garantiert einen einheitlichen, gut lesbaren Programmierstil.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Erstelle ein neues Projekt mein_erstes_projekt mit zelyra new und führe es aus.
  • Stufe 2 (Mittel): Ändere in zelyra.toml die Version auf 0.3.0 und gib in main() die neue Versionsnummer aus.
  • Stufe 3 (Anspruchsvoll): Verändere die Einrückungen in main.zyl absichtlich unordentlich und beobachte, wie zelyra fmt main.zyl den Quelltext wieder perfekt ausrichtet.

8. Praxisaufgabe: Die Aufgabenverwaltung als echtes Projekt initialisieren

Erstelle das Projekt, das uns durch das gesamte Buch begleiten wird:

zelyra new zelyra-tasks --template minimal
cd zelyra-tasks

Schreibe in main.zyl ein erstes Menü:

fn zeige_menue() {
    print("=================================")
    print("   ZELYRA AUFGABENVERWALTUNG     ")
    print("=================================")
    print("1: Alle Aufgaben anzeigen")
    print("2: Neue Aufgabe anlegen")
    print("3: Programm beenden")
}

fn main() {
    zeige_menue()
}

Prüfe das Projekt mit zelyra check main.zyl und führe es mit zelyra run main.zyl aus.

9. Zusammenfassung

  • Zelyra-Projekte besitzen eine klare Struktur aus Quelltext (.zyl) und Konfiguration (zelyra.toml).
  • zelyra check prüft die Gültigkeit, zelyra run führt das Programm aus.
  • zelyra fmt formatiert den Code automatisch nach einheitlichen Standards.

10. Kontrollfragen zur Selbstprüfung

  1. Wozu dient die Datei zelyra.toml?
  2. Warum ist zelyra fmt in Teams so wertvoll?
  3. Wann benötigt ein Zelyra-Programm eine main()-Funktion?

TEIL II – DIE GRUNDLAGEN DER SPRACHE


Kapitel 5: Werte und Datentypen

1. Was lerne ich in diesem Kapitel?

  • Was Werte und Datentypen sind und warum sie das Rückgrat sicherer Programme bilden.
  • Die grundlegenden Zahlentypen: Int, UInt, Float und Decimal.
  • Text- und Zeichentypen: String und Char.
  • Wahrheitswerte (Bool) und der leere Typ (Unit).
  • Zeit- und Datumstypen: Timestamp, Date, Time, Duration.
  • Der Unterschied zwischen automatischer Typableitung und ausdrücklicher Typangabe.

2. Warum ist das Thema wichtig?

Im echten Leben kann man Äpfel nicht mit Birnen addieren. Ein Computer würde ohne Typen jedoch genau das tun: Er würde versuchen, eine Postleitzahl mit einem Stundensatz zu multiplizieren oder einen Buchstabensalat als Datum zu interpretieren. Wer schon einmal erlebt hat, wie ein System Geldbeträge als Fließkommazahl (Float) speicherte und 0.10 € + 0.20 € plötzlich zu 0.30000000000000004 € wurden, weiß: Buchhalter und Finanzämter haben für solche quantenmechanischen Rundungsüberraschungen erstaunlich wenig Sinn für Humor. In Zelyra verhindert das Typsystem solche Absurditäten von vornherein.

3. Verständliche Erklärung

Jeder Wert in Zelyra besitzt einen Typ. Du kannst den Typ entweder explizit hinschreiben oder Zelyra ihn automatisch aus dem zugewiesenen Wert ableiten lassen:

fn main() {
    # Explizite Typangabe: Name gefolgt von Doppelpunkt und Typ
    anzahl: Int = 10

    # Automatische Typableitung: Zelyra erkennt sofort, dass dies ein String ist
    titel = "Wichtige Besprechung"

    print(titel)
}

Die wichtigsten Datentypen in Zelyra:

  • Int: Ganze Zahlen mit Vorzeichen (64-Bit), z. B. -5, 0, 42.
  • UInt: Ganze Zahlen ohne Vorzeichen (nur >= 0), z. B. IDs oder Zähler.
  • Float: Fließkommazahlen für wissenschaftliche Berechnungen, z. B. 3.1415.
  • Decimal: Festkommawerte aus decimal("0.10") mit neun Nachkommastellen. Basis-10-Rechenoperationen sind unterstützt; Werte mit mehr Nachkommastellen, Überlauf und Division durch null werden abgewiesen. Lege fachliche Rundungsregeln für Geld ausdrücklich fest.
  • Bool: Wahrheitswerte. Es gibt exakt zwei Zustände: true (wahr) oder false (falsch).
  • String: Zeichenketten (Text) in doppelten Anführungszeichen: "Hallo Welt".
  • Char: Einzelne Zeichen in einfachen Anführungszeichen: 'A', 'z', '✓'.
  • Unit: Steht für „kein Wert“, ähnlich wie void in anderen Sprachen. Wenn eine Funktion nur etwas ausgibt und nichts zurückliefert, ist ihr Typ Unit.

4. Kleine, aufeinander aufbauende Beispiele

fn main() {
    aufgabe_id: Int = 101
    aufgabe_name: String = "Server aktualisieren"
    ist_erledigt: Bool = false
    geschaetzte_stunden: Float = 2.5
    stundensatz: Float = 85.50

    print(aufgabe_name)
    print(ist_erledigt)
}

Zelyra schützt vor unpassenden Typen: Wenn du versuchst, aufgabe_id = "einhundert" zu schreiben, verweigert der Compiler sofort den Dienst mit E-TYPE-001.

5. Typische Fehler und deren Ursachen

  • Fehler: Geldbeträge mit Float berechnen. Ursache: Fließkommazahlen nach IEEE-754 können krumme Rundungen wie 0.1 + 0.2 = 0.30000000000000004 erzeugen. In Zelyra nutzt du für Finanzen immer Decimal.
  • Fehler: Ein einzelnes Zeichen in doppelte Anführungszeichen setzen, wenn ein Char erwartet wird. Ursache: "A" ist ein String, während 'A' ein Char ist.

6. Merksätze

  1. Datentypen schützen davor, unpassende Informationen miteinander zu verknüpfen.
  2. Für Geldbeträge gilt: Immer Decimal, niemals Float.
  3. Zelyra kann Typen intelligent ableiten, aber explizite Typen dokumentieren deine Absicht.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Deklariere drei Variablen für deinen Lieblingsfilm: Titel (String), Erscheinungsjahr (Int) und ob du ihn im Kino gesehen hast (Bool).
  • Stufe 2 (Mittel): Berechne die Gesamtkosten einer Aufgabe aus geschaetzte_stunden und stundensatz und gib das Ergebnis aus.
  • Stufe 3 (Anspruchsvoll): Erkläre, warum ein ID-Feld oft besser als Int oder als nominaler Typ type TaskId = Id deklariert wird, anstatt als beliebiger Text.

8. Praxisaufgabe: Aufgaben-Attribute definieren

Erweitere unser Aufgabenprojekt in main.zyl. Definiere die typisierten Grunddaten einer Aufgabe:

fn main() {
    task_id: Int = 1
    task_name: String = "Datenbankschema pruefen"
    is_done: Bool = false
    priority: Int = 1 # 1 = hoch, 2 = mittel, 3 = niedrig

    print("Aufgabe #" + "1" + ": " + task_name)
    if is_done {
        print("Status: Erledigt")
    } else {
        print("Status: Offen (Prioritaet: hohe Dringlichkeit)")
    }
}

9. Zusammenfassung

  • Zelyra stellt eine reichhaltige Palette primitiver Datentypen für Zahlen, Text und Logik bereit.
  • Typen werden entweder explizit angegeben oder vom Compiler automatisch abgeleitet.
  • Strenge Typprüfung verhindert logische Fehler zur Entwurfszeit.

10. Kontrollfragen zur Selbstprüfung

  1. Warum sollte man für Geldwerte Decimal statt Float verwenden?
  2. Was ist der Unterschied zwischen "Z" und 'Z'?
  3. Welche beiden Werte kann ein Bool annehmen?

Kapitel 6: Variablen und Unveränderlichkeit

1. Was lerne ich in diesem Kapitel?

  • Was eine Variable im Speicher eines Computers bedeutet.
  • Warum Variablen in Zelyra standardmäßig unveränderlich (immutable) sind.
  • Wie man veränderbare Variablen ausdrücklich mit mutable kennzeichnet.
  • Gültigkeitsbereiche (Scopes) und Lebensdauer von Variablen.
  • Warum Unveränderlichkeit Software dramatisch stabiler und fehlerfreier macht.

2. Warum ist das Thema wichtig?

Einer der häufigsten Gründe für schwer auffindbare Softwarefehler in Sprachen wie JavaScript, Python oder C++ ist unkontrollierte Veränderbarkeit: Eine Funktion verändert heimlich eine globale Variable, und an ganz anderer Stelle stürzt das Programm ab – der gefürchtete »Gestern ging es noch und niemand hat was angefasst«-Effekt. Veränderlicher Zustand hat in Entwicklerteams schon mehr Freundschaften ruiniert als die endlose Debatte über Tabs versus Leerzeichen. Zelyra folgt einem radikal klaren Prinzip: Feste Werte sind der Normalfall. Ein Wert, der sich niemals hinter deinem Rücken ändern kann, stiftet auch keine heimliche Verwirrung. Wenn sich ein Wert während der Programmlaufzeit ändern darf, musst du das ganz bewusst mit dem Schlüsselwort mutable ankündigen.

3. Verständliche Erklärung

Stell dir eine Variable wie eine beschriftete Schachtel im Arbeitsspeicher vor:

  • Unveränderliche Bindung (Standard):

    fn main() {
        titel = "Steuererklaerung"
        print(titel)
    }
    

    Du legst den Text "Steuererklärung" in die Schachtel mit der Aufschrift titel und versiegelst sie. Niemand darf den Inhalt der Schachtel austauschen. Jeder, der die Schachtel liest, kann sich darauf verlassen, dass immer dasselbe darin liegt.

  • Veränderbare Variable (mutable):

    fn main() {
        mutable zaehler = 0
        zaehler = zaehler + 1
        print(zaehler)
    }
    

    Hier bleibt die Schachtel offen. Du darfst den alten Wert herausnehmen und durch einen neuen ersetzen.

Gültigkeitsbereich (Scope): Variablen leben immer nur innerhalb des Blocks { ... }, in dem sie deklariert wurden. Wird der Block verlassen, vergisst Zelyra die Variable automatisch. Das schont den Speicher und verhindert Namenskonflikte.

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Unveränderlichkeit schützt vor versehentlichem Überschreiben

fn main() {
    projekt = "Zelyra Kern"
    # projekt = "Neues Projekt" // FEHLER: Compiler blockiert Zuweisung an unveränderliche Variable!
    print(projekt)
}

Beispiel 2: Wann mutable sinnvoll ist (z. B. Zähler und Schleifen)

fn main() {
    mutable offene_aufgaben = 5
    print(offene_aufgaben)

    # Eine Aufgabe wurde erledigt:
    offene_aufgaben = offene_aufgaben - 1
    print(offene_aufgaben)
}

Beispiel 3: Gültigkeitsbereiche (Scopes)

fn main() {
    bereich = "Global im main"
    if true {
        lokal = "Nur im if sichtbar"
        print(lokal)
        print(bereich)
    }
    # print(lokal) // FEHLER: `lokal` existiert außerhalb des Blocks nicht mehr!
}

5. Typische Fehler und deren Ursachen

  • Fehler: Versuchen, einer Variablen ohne mutable einen neuen Wert zuzuweisen. Ursache: Zelyra meldet cannot assign to immutable variable. Wenn sich ein Wert ändern können muss, schreibe mutable name = ....
  • Fehler: Jede Variable aus Bequemlichkeit als mutable deklarieren. Ursache: Schlechter Stil. Verwende mutable nur dort, wo ein Wert sich tatsächlich im Ablauf ändern muss (z. B. in Schleifen oder Zwischenakkumulatoren).

6. Merksätze

  1. In Zelyra sind Werte standardmäßig unveränderlich.
  2. Wenn sich ein Wert ändern darf, steht mutable sichtbar davor.
  3. Variablen existieren nur innerhalb ihres deklarierten Blocks { ... }.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Erstelle eine unveränderliche Variable für deinen Benutzernamen und gib sie aus.
  • Stufe 2 (Mittel): Erstelle einen mutable punktestand = 100, ziehe 15 Punkte ab, füge 30 Punkte hinzu und gib die Zwischenstände aus.
  • Stufe 3 (Anspruchsvoll): Begründe, warum unveränderliche Variablen besonders bei Programmen helfen, die mehrere Aufgaben gleichzeitig (nebenläufig) ausführen.

8. Praxisaufgabe: Zähler für unsere Aufgabenverwaltung

In unserer Aufgabenverwaltung wollen wir zählen, wie viele Aufgaben noch zu erledigen sind:

fn main() {
    mutable anzahl_offen = 3
    print("Start: Aufgaben zu erledigen: ")
    print(anzahl_offen)

    # Erste Aufgabe erledigt:
    anzahl_offen = anzahl_offen - 1
    print("Zwischenstand: Noch offen:")
    print(anzahl_offen)

    # Zweite Aufgabe erledigt:
    anzahl_offen = anzahl_offen - 1
    print("Endstand: Noch offen:")
    print(anzahl_offen)
}

9. Zusammenfassung

  • Unveränderlichkeit ist Zelyras Standard und verhindert unerwünschte Nebeneffekte.
  • Veränderliche Variablen werden explizit mit mutable deklariert.
  • Geschweifte Klammern begrenzen die Sichtbarkeit und Lebensdauer von Variablen.

10. Kontrollfragen zur Selbstprüfung

  1. Was passiert, wenn du einer Variablen ohne mutable einen neuen Wert zuweist?
  2. Warum ist Unveränderlichkeit ein Sicherheitsmerkmal?
  3. Kann eine Variable aus einem inneren Block außerhalb dieses Blocks gelesen werden?

Kapitel 7: Operatoren und Ausdrücke

1. Was lerne ich in diesem Kapitel?

  • Was ein Operator und was ein Ausdruck (Expression) ist.
  • Rechenoperatoren für Zahlen: +, -, *, /, %.
  • Vergleichsoperatoren: ==, !=, <, <=, >, >=.
  • Logische Operatoren: && (UND), || (ODER), ! (NICHT).
  • Operatorrangfolge und verständliche Klammersetzung.

2. Warum ist das Thema wichtig?

Programme bestehen nicht nur aus festen Werten, sondern berechnen Ergebnisse, treffen Vergleiche und kombinieren logische Aussagen. Ein Operator ist das Bindeglied, das aus einzelnen Werten neue Erkenntnisse formt. Wer Operatoren beherrscht, kann komplexe Geschäftsregeln präzise in einfache Ausdrücke übersetzen – und verhindert peinliche Rechenpannen, bei denen durch eine vergessene Klammer plötzlich jeder Kunde 100% Rabatt erhält, nur weil heute Dienstag ist.

3. Verständliche Erklärung

  • Ein Ausdruck ist jedes Stück Quelltext, das zu einem Wert ausgewertet werden kann. Zum Beispiel ist 5 + 3 ein Ausdruck, der zum Wert 8 wird.
  • Ein Operator ist das Symbol, das die Operation beschreibt (z. B. + oder ==).

Rechenoperatoren:

  • +: Addition (auch für String- und Array-Verkettung)
  • -: Subtraktion (oder Negation: -x)
  • *: Multiplikation
  • /: Division
  • %: Modulo (Rest einer ganzzahligen Division, z. B. 7 % 3 == 1)

Vergleichsoperatoren (liefern immer Bool):

  • ==: Ist gleich?
  • !=: Ist ungleich?
  • < / <=: Kleiner / Kleiner oder gleich?
  • > / >=: Größer / Größer oder gleich?

Logische Operatoren:

  • && (UND): Nur wahr, wenn beide Seiten wahr sind (true && true == true).
  • || (ODER): Wahr, wenn mindestens eine Seite wahr ist.
  • ! (NICHT): Kehrt einen Wahrheitswert um (!true == false).

4. Kleine, aufeinander aufbauende Beispiele

fn main() {
    # Rechnen
    grundzeit = 60
    puffer = 15
    gesamtzeit = grundzeit + puffer
    print(gesamtzeit)

    # Vergleichen
    ist_lang = gesamtzeit > 60
    print(ist_lang)

    # Logische Verknüpfung
    hat_puffer = puffer > 0
    ist_kritisch = gesamtzeit > 120 && !hat_puffer
    print(ist_kritisch)
}

5. Typische Fehler und deren Ursachen

  • Fehler: Ein einfaches Gleichheitszeichen = im Vergleich verwenden: if x = 5. Ursache: = ist die Zuweisung! Für Vergleiche verlangt Zelyra strikt das doppelte ==.
  • Fehler: Division durch Null (x / 0). Ursache: Führt zur Laufzeit zu einem Abbruch. Vor einer Division muss der Teiler geprüft werden.

6. Merksätze

  1. Zuweisen mit =, Vergleichen mit ==.
  2. Rechnungen werten Punkt vor Strich aus; setze im Zweifel Klammern für maximale Klarheit.
  3. && verlangt beidseitige Wahrheit, || gibt sich mit einer zufriedenen Bedingung zufrieden.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Prüfe mit ==, ob 10 % 2 gleich 0 ist (Gerade-Zahl-Prüfung).
  • Stufe 2 (Mittel): Schreibe einen Ausdruck, der prüft, ob eine Zahl alter zwischen 18 und 65 (inklusive) liegt.
  • Stufe 3 (Anspruchsvoll): Schreibe einen Ausdruck für eine Rabattregel: Ein Kunde erhält Rabatt, wenn er VIP ist (is_vip == true) ODER wenn sein Bestellwert über 100 liegt UND er kein Neukunde ist.

8. Praxisaufgabe: Fristen- und Status-Prüfung im TaskManager

In unserem TaskManager müssen wir prüfen, ob eine Aufgabe überfällig ist und sofortige Aufmerksamkeit verlangt:

fn main() {
    tage_verbleibend = -2
    ist_erledigt = false
    ist_blockiert = false

    # Eine Aufgabe ist ueberfaellig, wenn Tage < 0 und sie noch nicht erledigt ist
    ist_ueberfaellig = tage_verbleibend < 0 && !ist_erledigt

    # Hohe Dringlichkeit: Ueberfaellig und nicht durch andere blockiert
    braucht_eingriff = ist_ueberfaellig && !ist_blockiert

    print("Aufgabe ueberfaellig?")
    print(ist_ueberfaellig)
    print("Braucht sofortigen Eingriff?")
    print(braucht_eingriff)
}

9. Zusammenfassung

  • Operatoren verknüpfen Werte zu aussagekräftigen Ausdrücken.
  • Vergleichsoperatoren erzeugen Bool-Werte.
  • Logische Operatoren (&&, ||, !) erlauben komplexe Entscheidungsregeln.

10. Kontrollfragen zur Selbstprüfung

  1. Was ist der Unterschied zwischen = und ==?
  2. Welches Ergebnis liefert der Ausdruck 5 > 3 && 2 > 10?
  3. Wofür steht der Operator %?

Kapitel 8: Ein- und Ausgaben

1. Was lerne ich in diesem Kapitel?

  • Wie man Informationen mit print() zuverlässig auf der Konsole ausgibt.
  • Wie read_console() eine Zeile interaktiv vom Terminal einliest.
  • Wie Text und Variablen durch Verkettung formatiert werden.
  • Wie Zelyra Eingaben über Parameter, Dateien, Umgebungsvariablen, Terminal und Web-Routen empfängt.
  • Warum Zelyra für sensible Außeninteraktionen ausdrückliche Berechtigungen (Capabilities) verlangt.

2. Warum ist das Thema wichtig?

Ein Programm, das weder Daten empfangen noch Ergebnisse mitteilen kann, ist für den Anwender nutzlos. Ein- und Ausgaben (I/O) verbinden die Logik deines Codes mit der Außenwelt. Weil Zugriffe auf Tastatur, Festplatte oder Netzwerk aber auch Sicherheitsrisiken darstellen, regelt Zelyra diese Zugriffe viel kontrollierter als ältere Sprachen.

3. Verständliche Erklärung

  • Ausgabe: Der eingebaute Befehl print(wert) nimmt Zahlen, Wahrheitswerte, Zeichenketten oder zusammengesetzte Objekte entgegen und gibt sie auf dem Standard-Ausgabekanal (stdout) aus.
  • Formatierung: Mehrere Texte und Werte verbindest du mit dem Plus-Operator +.
  • Eingabe in Zelyra: Programme empfangen Eingaben über Parameter, Umgebungsvariablen, Dateien, Web-Anfragen oder interaktiv im Terminal:
    1. Funktionsparameter: Daten werden beim Aufruf übergeben.
    2. Umgebungsvariablen: env("MEIN_KEY") liest Konfigurationswerte aus dem System.
    3. Dateien: read_text("eingabe.txt") liest gespeicherte Daten ein.
    4. Web-Anfragen: Formulare (form) und URLs (page "/user/{id}") empfangen Benutzereingaben im Browser.
    5. Terminal: read_console("Prompt: ") zeigt eine Eingabeaufforderung und liest eine Zeile. Das Ergebnis ist String?: None bedeutet Dateiende, eine leere Zeile ist Some("").

Terminalzugriff ist eine Capability. Die aufrufende Funktion muss uses Console deklarieren. In Projekten mit einer [capabilities]-Sektion muss zusätzlich console = true gesetzt werden; neue Projektvorlagen lassen die Freigabe standardmäßig aus. Konsoleneingabe ist für zelyra run gedacht. Webanwendungen verwenden stattdessen typisierte Requests und Formulare. Die Eingabe wird nicht verborgen; nutze read_console() daher nicht für Passwörter oder andere Geheimnisse.

fn main() uses Console {
    datum = read_console("Datum: ")
    match datum {
        Some(wert) => {
            print("Eingegeben: " + wert)
        }
        None => {
            print("Keine Eingabe.")
        }
    }
}
[capabilities]
console = true

4. Kleine, aufeinander aufbauende Beispiele

Ausgabe formatieren:

fn main() {
    titel = "Release 1.0"
    prozent = 80
    print("Fortschritt fuer " + titel + ":")
    print(prozent)
}

Eingabe über Funktionsparameter und Umgebungsvariablen:

fn verarbeite_aufgabe(titel: String, prioritaet: Int) {
    print("Bearbeite: " + titel)
    print("Prioritaetsstufe:")
    print(prioritaet)
}

fn main() uses Environment {
    modus = env("APP_MODUS")
    print("Aktueller Modus:")
    print(modus)
    verarbeite_aufgabe("Backup erstellen", 1)
}

5. Typische Fehler und deren Ursachen

  • Fehler: Versuchen, eine Zahl direkt mit + an einen Text zu hängen: "Zahl: " + 5. Ursache: Zelyra verlangt Typkompatibilität. 5 ist ein Int, kein String. Gib die Zahl entweder separat mit print(5) aus oder nutze Hilfsfunktionen.
  • Fehler: env() ohne uses Environment aufrufen. Ursache: Zelyras Sicherheitsmodell (Capabilities) verlangt, dass Funktionen, die auf das Betriebssystem zugreifen, dies ausdrücklich deklarieren.

6. Merksätze

  1. print() gibt Werte verlässlich auf der Konsole aus.
  2. Eingaben fließen über Parameter, Umgebungsvariablen, Dateien, Terminal oder Web-Routen.
  3. Systemzugriffe benötigen die passende Capability (z. B. uses Environment).

7. Übungsaufgaben

  • Stufe 1 (Leicht): Gib eine formatierte Visitenkarte (Name, Beruf, E-Mail) mit mehreren print()-Befehlen aus.
  • Stufe 2 (Mittel): Schreibe eine Funktion drucke_aufgabe(id: Int, name: String, erledigt: Bool), die alle Details sauber untereinander darstellt.
  • Stufe 3 (Anspruchsvoll): Schreibe ein CLI-Programm mit read_console() und erkläre, wann Terminaleingabe sinnvoll ist und wann strukturierte Web-Requests besser passen.

8. Praxisaufgabe: Ausgabeformatierung für den TaskManager

Erstelle eine Ausgabefunktion für unsere Aufgabenverwaltung:

fn drucke_kopfzeile(bereich: String) {
    print("----------------------------------------")
    print("BEREICH: " + bereich)
    print("----------------------------------------")
}

fn drucke_aufgabe_eintrag(nr: Int, titel: String, erledigt: Bool) {
    print("Aufgabe Nr: ")
    print(nr)
    print("Titel: " + titel)
    if erledigt {
        print("Status: [X] ERLEDIGT")
    } else {
        print("Status: [ ] OFFEN")
    }
    print("----------------------------------------")
}

fn main() {
    drucke_kopfzeile("HEUTIGE AUFGABEN")
    drucke_aufgabe_eintrag(1, "Handbuch durcharbeiten", true)
    drucke_aufgabe_eintrag(2, "Zelyra-Beispiele ueben", false)
}

9. Zusammenfassung

  • Ausgaben erfolgen klar und unmissverständlich über print().
  • Externe Zugriffe sind durch Capabilities geschützt.
  • Eingaben werden über Parameter, Dateien, Umgebungsvariablen, read_console() oder Web-Anfragen entgegengenommen.

10. Kontrollfragen zur Selbstprüfung

  1. Welche Funktion nutzt man in Zelyra für Textausgaben?
  2. Warum verlangt der Zugriff auf env() die Angabe uses Environment?
  3. Welche Eingabewege sind in serverbasierten Zelyra-Anwendungen typisch?

Kapitel 9: Entscheidungen mit Bedingungen

1. Was lerne ich in diesem Kapitel?

  • Wie Programme mit if und else eigenständige Entscheidungen treffen.
  • Wie man alternative Pfade mit else if formuliert.
  • Wie man mehrere Bedingungen logisch kombiniert.
  • Wie man mit match elegante und lückenlose Fallunterscheidungen schreibt.
  • Typische Denkfehler bei Verschachtelungen und wie man sie vermeidet.

2. Warum ist das Thema wichtig?

Ohne Bedingungen wäre jedes Programm starr wie eine Spieluhr: Es würde stur immer dieselbe Melodie dudeln. Wirklich nützlich wird Software erst, wenn sie auf unterschiedliche Situationen reagiert: Ist der Nutzer eingeloggt? Ist die Frist abgelaufen? Reicht das Guthaben oder versucht der Kunde gerade, minus fünf Pizzen in den Warenkorb zu legen? Mit if und match verleihst du deinem Code echtes Urteilsvermögen – und mit Zelyras exhaustivem match passt der Compiler auf, dass du keinen einzigen Sonderfall unter den Tisch fallen lässt.

3. Verständliche Erklärung

  • if / else: Prüft eine Bedingung. Wenn sie true ergibt, wird der erste Block ausgeführt; andernfalls der else-Block:

    fn pruefe_ergebnis(punkte: Int) {
        if punkte >= 50 {
            print("Bestanden!")
        } else {
            print("Leider nicht bestanden.")
        }
    }
    
    fn main() {
        pruefe_ergebnis(75)
    }
    
  • match: Wenn du einen Wert gegen viele feste Möglichkeiten prüfen willst, ist match viel lesbarer als endlose if / else if-Ketten. In Zelyra stellt der Compiler sicher, dass alle möglichen Fälle abgedeckt sind (Exhaustiveness):

    fn zeige_status(status_code: Int) {
        match status_code {
            1 => { print("Neu") }
            2 => { print("In Bearbeitung") }
            3 => { print("Erledigt") }
            _ => { print("Unbekannter Status") }
        }
    }
    
    fn main() {
        zeige_status(2)
    }
    

    Der Unterstrich _ ist das sogenannte Wildcard-Muster: Er greift für alle anderen, nicht ausdrücklich genannten Werte.

4. Kleine, aufeinander aufbauende Beispiele

Einfache Verzweigung:

fn main() {
    offen = 0
    if offen == 0 {
        print("Super! Alle Aufgaben sind erledigt.")
    } else {
        print("Es gibt noch offene Aufgaben.")
    }
}

Mehrstufige Entscheidung (else if):

fn bewerte_prioritaet(stufe: Int) -> String {
    match stufe {
        1 => { return "SEHR DRINGEND" }
        2 => { return "NORMAL" }
        3 => { return "NIEDRIG" }
        _ => { return "UNBEKANNT" }
    }
}

fn main() {
    print(bewerte_prioritaet(1))
    print(bewerte_prioritaet(2))
}

Mustervergleich mit match:

fn status_text(code: Int) -> String {
    match code {
        0 => { return "Entwurf" }
        1 => { return "Aktiv" }
        2 => { return "Archiviert" }
        _ => { return "Ungueltig" }
    }
}

fn main() {
    print(status_text(1))
    print(status_text(99))
}

5. Typische Fehler und deren Ursachen

  • Fehler: Den _-Fall in einem match auf Zahlen weglassen. Ursache: Eine Zahl kann unendlich viele Werte annehmen. Wenn du nur 1 und 2 abdeckst, meldet der Compiler non-exhaustive match.
  • Fehler: Zu tiefe Verschachtelung (Pfeil-Anti-Pattern: if { if { if { ... } } }). Ursache: Schwer lesbar. Löse tiefe Verschachtelungen durch frühes Zurückkehren (early return) oder match auf.

6. Merksätze

  1. if entscheidet anhand eines Wahrheitswertes (Bool).
  2. match prüft Werte gegen Muster und garantiert Lückenlosigkeit.
  3. Der Wildcard _ fängt alle übrigen Fälle sicher ab.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Schreibe eine Funktion ist_volljaehrig(alter: Int) -> Bool, die prüft, ob alter >= 18 ist.
  • Stufe 2 (Mittel): Schreibe mit match eine Funktion monats_tage(monat: Int) -> Int, die für die Monate 1 bis 12 die Tage zurückgibt (Februar pauschal 28).
  • Stufe 3 (Anspruchsvoll): Baue eine Validierungsfunktion, die prüft, ob ein Passwort mindestens 8 Zeichen lang ist (über einen String-Vergleich) und nicht "12345678" lautet.

8. Praxisaufgabe: Status-Logik für den TaskManager

Erstelle für unsere Aufgabenverwaltung die automatische Ampel-Einstufung:

fn berechne_ampel(verbleibende_tage: Int, ist_fertig: Bool) -> String {
    if ist_fertig {
        return "GRUEN: Aufgabe ist abgeschlossen"
    } else {
        if verbleibende_tage < 0 {
            return "ROT: Frist ist abgelaufen!"
        } else {
            if verbleibende_tage <= 2 {
                return "GELB: Bald faellig, bitte bearbeiten"
            } else {
                return "BLAU: Im Zeitplan"
            }
        }
    }
}

fn main() {
    print(berechne_ampel(5, false))
    print(berechne_ampel(1, false))
    print(berechne_ampel(-1, false))
    print(berechne_ampel(-1, true))
}

9. Zusammenfassung

  • if und else steuern den Programmfluss anhand logischer Bedingungen.
  • match ermöglicht saubere, compilergeprüfte Fallunterscheidungen.
  • Klar gegliederte Verzweigungen machen Geschäftslogik verständlich und wartbar.

10. Kontrollfragen zur Selbstprüfung

  1. Wann ist ein match einem if / else if vorzuziehen?
  2. Welche Aufgabe erfüllt das _-Muster im match?
  3. Warum verlangt Zelyra, dass bei match alle möglichen Fälle abgedeckt sind?

Kapitel 10: Wiederholungen und Schleifen

1. Was lerne ich in diesem Kapitel?

  • Warum Wiederholungen (Loops) in der Datenverarbeitung unentbehrlich sind.
  • Schleifen mit Zählern: while bedingung { ... }.
  • Iteration über Listen und Arrays: for element in array { ... }.
  • Endlosschleifen: loop { ... }.
  • Schleifen vorzeitig steuern mit break (Abbrechen) und continue (Überspringen).
  • Schleifeninvarianten (invariant), mit denen Zelyra Korrektheit formal beweisen kann.

2. Warum ist das Thema wichtig?

Computer wurden erfunden, um monotone, wiederkehrende Aufgaben fehlerfrei und in Sekundenschnelle zu erledigen – und weil menschliche Entwickler nach der zwanzigsten manuellen Wiederholung unweigerlich Fehler machen oder verzweifeln. Wenn du 1.000 Aufgaben aus einer Datenbank laden, prüfen und anzeigen willst, schreibst du den Code nicht tausendmal, sondern einmal innerhalb einer Schleife. Eine Endlosschleife ist dabei wie ein Hamsterrad mit Turbomotor: Viel Lärm, glühende Lüfter, aber null Fortschritt. Zelyra bietet dafür sichere Konstrukte und erlaubt es sogar, mathematische Garantien über Schleifendurchläufe abzugeben.

3. Verständliche Erklärung

Zelyra kennt drei Arten von Schleifen:

  1. for ... in: Der einfachste und sicherste Weg, um eine Liste von Elementen abzuarbeiten. Die Schleife läuft automatisch über jedes Element und stoppt von selbst:

    fn main() {
        for zahl in [1, 2, 3] {
            print(zahl)
        }
    }
    
  2. while bedingung: Wiederholt den Block so lange, wie die Bedingung wahr (true) bleibt. Ideal, wenn du vorher nicht weißt, wie viele Durchläufe nötig sind.

  3. loop: Eine Dauerschleife. Sie läuft endlos weiter, bis sie im Inneren durch den Befehl break gestoppt wird.

Steuerbefehle:

  • break: Beendet die Schleife sofort. Das Programm springt hinter die Schleife.
  • continue: Bricht den aktuellen Durchlauf ab und springt sofort zum nächsten Element.

Schleifeninvariante (invariant): Eine Invariante ist eine Bedingung, die vor, während und nach jedem Schleifendurchlauf wahr sein muss (z. B. invariant { zaehler >= 0 }). Der Zelyra-Verifier (zelyra verify) nutzt Invarianten, um mathematisch zu beweisen, dass die Schleife niemals in unzulässige Zustände gerät!

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: for ... in über ein Array

fn main() {
    aufgaben = ["Planen", "Programmieren", "Testen", "Ausliefern"]
    for a in aufgaben {
        print("Schritt: " + a)
    }
}

Beispiel 2: while mit Zähler und invariant

fn main() {
    mutable zaehler = 1
    while zaehler <= 3
        invariant { zaehler >= 1 }
    {
        print(zaehler)
        zaehler = zaehler + 1
    }
}

Beispiel 3: break und continue gezielt einsetzen

fn main() {
    for zahl in [1, 2, 3, 4, 5] {
        if zahl == 2 {
            # Die 2 wollen wir auslassen:
            continue
        }
        if zahl == 4 {
            # Bei 4 brechen wir komplett ab:
            break
        }
        print(zahl)
    }
}

Ausgabe: Gibt 1 und 3 aus!

5. Typische Fehler und deren Ursachen

  • Fehler: In einer while-Schleife vergessen, den Zähler zu erhöhen (zaehler = zaehler + 1). Ursache: Die Bedingung bleibt ewig wahr – eine Endlosschleife entsteht und das Programm friert ein.
  • Fehler: Falsche Indexgrenzen bei manuellen Zählern. Ursache: Nutze wann immer möglich for item in array, um Grenzfehler (Off-by-one) komplett auszuschließen.

6. Merksätze

  1. Nutze for ... in für Sammlungen und Arrays.
  2. break beendet die Schleife sofort; continue springt zur nächsten Runde.
  3. Invarianten dokumentieren und beweisen die Sicherheit deiner Schleife.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Gib mit einer while-Schleife die Zahlen von 10 rückwärts bis 1 aus.
  • Stufe 2 (Mittel): Berechne mit einer for-Schleife die Summe aller Zahlen im Array [10, 20, 30, 40].
  • Stufe 3 (Anspruchsvoll): Durchsuche ein Array von Zahlen nach der Zahl 42. Sobald sie gefunden wird, gib "Gefunden!" aus und beende die Schleife mit break. Wenn sie nicht vorkommt, gib am Ende "Nicht gefunden" aus.

8. Praxisaufgabe: Aufgaben filtern und zählen

Wir wenden Schleifen auf unsere Aufgabenverwaltung an. Wir zählen die erledigten Aufgaben und geben offene Aufgaben aus:

fn main() {
    aufgaben = ["Konzept schreiben", "Datenbank aufsetzen", "Tests schreiben"]
    mutable erledigt_zaehler = 0

    print("Aufgabenliste durchgehen:")
    for a in aufgaben {
        if a == "Konzept schreiben" {
            print("[X] " + a)
            erledigt_zaehler = erledigt_zaehler + 1
        } else {
            print("[ ] " + a)
        }
    }

    print("Erledigte Aufgaben insgesamt:")
    print(erledigt_zaehler)
}

9. Zusammenfassung

  • Schleifen automatisieren monotone Wiederholungen.
  • for ... in iteriert sicher über Arrays, while wiederholt nach Bedingungen.
  • break und continue ermöglichen präzise Flusssteuerung.
  • invariant ermöglicht formale Korrektheitsbeweise mit zelyra verify.

10. Kontrollfragen zur Selbstprüfung

  1. Was ist der Unterschied zwischen break und continue?
  2. Warum ist for ... in bei Arrays sicherer als eine manuelle while-Schleife?
  3. Welche Rolle spielt eine invariant bei der Programmprüfung?

TEIL III – PROGRAMME STRUKTURIEREN


Kapitel 11: Funktionen und Prozeduren

1. Was lerne ich in diesem Kapitel?

In diesem Kapitel lernst du:

  • Was Funktionen sind und wie sie Programme übersichtlich und wiederverwendbar machen.
  • Wie man Parameter übergibt und Rückgabetypen mit -> Typ deklariert.
  • Was reine Funktionen (pure functions) ohne Seiteneffekte sind und warum sie Gold wert sind.
  • Was Prozeduren sind (Funktionen ohne Rückgabewert bzw. Typ Unit), die Aktionen ausführen.
  • Wie Zelyra Namenskonventionen und sauberen Stil fördert.

2. Warum ist das Thema wichtig?

Wenn du jede Berechnung und jeden Bildschirmausdruck zehnmal an verschiedenen Stellen deines Codes wiederholst, entsteht das gefürchtete „Spaghetti-Phänomen“. Ändert sich eine Geschäftsregel (z. B. wie die Frist einer Aufgabe berechnet wird), müsstest du alle zehn Stellen suchen und anpassen – Fehler sind dabei unausweichlich. Funktionen fassen eine logische Aufgabe unter einem klaren Namen zusammen: Du schreibst sie einmal, testest sie gründlich und verwendest sie beliebig oft.

3. Verständliche Erklärung

Stell dir eine Funktion wie ein Küchengerät vor:

  • Du gibst Zutaten hinein (Parameter).
  • Das Gerät verarbeitet die Zutaten nach einem festen Rezept (Funktionskörper).
  • Am Ende kommt ein fertiges Gericht heraus (Rückgabewert).

In Zelyra definierst du Funktionen mit dem Schlüsselwort fn:

fn addiere(a: Int, b: Int) -> Int {
    return a + b
}

fn main() {
    print(addiere(2, 3))
}

Besitzt eine Funktion keinen Rückgabewert, weil sie z. B. nur eine Zeile Text ausgibt, ist ihr Rückgabetyp Unit (oder kann weggelassen werden):

fn drucke_trennlinie() {
    print("----------------------------------------")
}

fn main() {
    drucke_trennlinie()
}

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Berechnung mit Rückgabewert

fn berechne_tage_bis_frist(heute_tag: Int, frist_tag: Int) -> Int {
    return frist_tag - heute_tag
}

fn main() {
    tage = berechne_tage_bis_frist(10, 18)
    print(tage)
}

Beispiel 2: Textformatierung in einer Hilfsfunktion

fn format_aufgabe(id_text: String, text: String, fertig: Bool) -> String {
    mutable status_zeichen = "[ ]"
    if fertig {
        status_zeichen = "[X]"
    }
    return status_zeichen + " #" + id_text + ": " + text
}

fn main() {
    ausgabe = format_aufgabe("1", "Dokumentation lesen", true)
    print(ausgabe)
}

Beispiel 3: Prozedur zur Menüausgabe

fn zeige_kopfzeile(benutzer: String) {
    print("Angemeldet als: " + benutzer)
    print("========================================")
}

fn main() {
    zeige_kopfzeile("Sabine")
}

5. Typische Fehler und deren Ursachen

  • Fehler: Ein return vergessen, obwohl ein Rückgabetyp angegeben wurde. Ursache: Zelyra verlangt bei deklariertem Rückgabetyp zwingend ein passendes Ergebnis auf allen Ausführungspfaden.
  • Fehler: Falsche Argumentreihenfolge beim Aufruf. Ursache: Zelyra prüft die Parametertypen strikt. Wenn der erste Parameter ein Int ist, darf kein String übergeben werden.
  • Fehler: Versuchen, String und Int direkt mit + zu verketten. Ursache: Der Operator + verknüpft in Zelyra entweder zwei Strings oder zwei Zahlen gleichen Typs. Verwende Strings oder gebe Werte separat über print() aus.

6. Merksätze

  1. Eine Funktion sollte genau eine einzige, klar benannte Aufgabe erfüllen.
  2. Reine Funktionen erzeugen bei gleichen Eingaben immer die gleichen Ausgaben und haben keine Seiteneffekte.
  3. Namen von Funktionen sollten aussagekräftige Verben sein (z. B. berechne_differenz, format_aufgabe).

7. Übungsaufgaben

  • Stufe 1 (Leicht): Schreibe eine Funktion verdopple(zahl: Int) -> Int, die das Doppelte einer Zahl zurückgibt.
  • Stufe 2 (Mittel): Schreibe eine Funktion ist_dringend(tage: Int) -> Bool, die true liefert, wenn weniger als 3 Tage verbleiben.
  • Stufe 3 (Anspruchsvoll): Schreibe eine Funktion status_symbol(erledigt: Bool) -> String, die "[OK]" oder "[OFFEN]" liefert.

8. Praxisaufgabe: Aufgabenanzeige modularisieren

Schreibe ein Zelyra-Programm, das drei Aufgaben mit ID-Text, Name und Erledigungsstatus über eine wiederverwendbare Formatierungsfunktion aufbereitet und ausgibt:

fn format_eintrag(id_text: String, name: String, erledigt: Bool) -> String {
    mutable symbol = "[OFFEN]"
    if erledigt {
        symbol = "[OK]"
    }
    return symbol + " Aufgabe " + id_text + ": " + name
}

fn main() {
    print(format_eintrag("1", "Post abholen", true))
    print(format_eintrag("2", "Rechnung bezahlen", false))
    print(format_eintrag("3", "Backup erstellen", false))
}

9. Zusammenfassung

  • Funktionen gliedern Programme in logische, handhabbare Bausteine.
  • Parameter und Rückgabetypen sind in Zelyra exakt typisiert.
  • Reine Funktionen machen den Code wartungsfreundlich und fehlerresistent.

10. Kontrollfragen zur Selbstprüfung

  1. Welchen Typ besitzt eine Funktion, die keinen Wert zurückgibt?
  2. Warum sind reine Funktionen einfacher zu testen als Funktionen mit globalen Seiteneffekten?
  3. Was prüft der Zelyra-Compiler bei jedem Funktionsaufruf?

Kapitel 12: Verträge und Vorbedingungen (Design by Contract)

1. Was lerne ich in diesem Kapitel?

  • Was das Konzept Design by Contract (Entwurf durch Vertrag) bedeutet.
  • Wie du Vorbedingungen mit requires definierst.
  • Wie du Nachbedingungen mit ensures und dem Schlüsselwort result formulierst.
  • Wie Schleifeninvarianten mit invariant sichergestellt werden.
  • Warum Verträge defensiven if-Kaskaden überlegen sind.

2. Warum ist das Thema wichtig?

Häufig entstehen Fehler, weil Entwickler Annahmen treffen, die nirgendwo festgeschrieben sind: »Diese Funktion darf niemals mit einer negativen Zahl aufgerufen werden!« Wenn jemand die Funktion ein halbes Jahr später doch mit -1 aufruft, kracht es unerwartet im Gebälk. In herkömmlichen Sprachen schreibt man seitenlange if-Prüfungen oder hofft auf das Prinzip Hoffnung. Verträge (requires und ensures) machen Schluss mit dem Hoffen. Sie sind wie ein notarieller Vertrag an der Funktionsgrenze: »Lieferst du mir gültige Daten (requires), garantiere ich dir ein korrektes Ergebnis (ensures). Wenn nicht, weise ich den Aufruf sofort ab!« Wer einmal erlebt hat, wie Buchhalter nachts rotieren, weil fehlerhafte Buchungsbeträge das Hauptbuch zerschossen haben, liebt vertragsbasierte Programmierung.

3. Verständliche Erklärung

Ein Vertrag in Zelyra funktioniert wie ein notarieller Vertrag zwischen dem Aufrufer und der Funktion:

  • requires (Vorbedingung): Der Aufrufer verpflichtet sich, der Funktion nur Daten zu übergeben, die die Bedingung erfüllen (z. B. wert > 0).
  • ensures (Nachbedingung): Im Gegenzug garantiert die Funktion, dass ihr Ergebnis (result) bestimmte Eigenschaften aufweist (z. B. result >= 0).

Wird ein Vertrag verletzt, stoppt Zelyra sofort mit einem präzisen Fehler (E-CONTRACT-*) und zeigt genau, wer den Vertrag gebrochen hat.

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Vorbedingung mit requires

fn dividiere(zaehler: Int, nenner: Int) -> Int
    requires { nenner != 0 }
{
    return zaehler / nenner
}

fn main() {
    ergebnis = dividiere(100, 4)
    print(ergebnis)
}

Beispiel 2: Vor- und Nachbedingung kombiniert

fn prioritaet_anpassen(aktuelle_stufe: Int, delta: Int) -> Int
    requires { aktuelle_stufe >= 1 && delta >= 0 }
    ensures { result >= 1 }
{
    neue_stufe = aktuelle_stufe + delta
    return neue_stufe
}

fn main() {
    p = prioritaet_anpassen(2, 1)
    print(p)
}

Beispiel 3: Schleifeninvariante

fn zaehle_bis(grenze: Int) -> Int
    requires { grenze >= 0 }
    ensures { result == grenze }
{
    mutable i = 0
    while i < grenze
        invariant { i >= 0 }
    {
        i = i + 1
    }
    return i
}

fn main() {
    print(zaehle_bis(5))
}

5. Typische Fehler und deren Ursachen

  • Fehler: Benutzereingaben über requires abfangen wollen. Ursache: Verträge sind Schutzschilde gegen Programmierfehler im Code, nicht zur Validierung von unsicheren Benutzereingaben gedacht. Für Benutzereingaben nutzt man Validierungsregeln (form oder Result).
  • Fehler: Ein ensures formulieren, das die Funktion logisch nicht einhalten kann. Ursache: Wenn die Funktion z. B. -5 zurückgibt, das ensures aber { result >= 0 } verlangt, schlägt die Nachbedingung fehl.

6. Merksätze

  1. requires schützt die Funktion vor unzulässigen Eingaben des Aufrufers.
  2. ensures garantiert dem Aufrufer ein korrektes Ergebnis über result.
  3. Verträge machen implizite Annahmen zu überprüfbaren, lebendigen Spezifikationen.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Schreibe eine Funktion quadratwurzel_naiv(x: Int) -> Int mit der Vorbedingung requires { x >= 0 }.
  • Stufe 2 (Mittel): Schreibe eine Funktion begrenze(wert: Int, min_wert: Int, max_wert: Int) -> Int mit Vorbedingungen und passender Nachbedingung.
  • Stufe 3 (Anspruchsvoll): Sichere eine Funktion prozentsatz(teil: Int, gesamt: Int) -> Int so ab, dass niemals durch 0 geteilt wird und das Ergebnis stets zwischen 0 und 100 liegt.

8. Praxisaufgabe: Fortschrittsrechner für Aufgaben

Schreibe eine abgesicherte Funktion für die Aufgabenverwaltung:

fn berechne_fortschritt(erledigt: Int, gesamt: Int) -> Int
    requires { gesamt > 0 && erledigt >= 0 && erledigt <= gesamt }
    ensures { result >= 0 && result <= 100 }
{
    return (erledigt * 100) / gesamt
}

fn main() {
    quote = berechne_fortschritt(3, 4)
    print(quote)
}

9. Zusammenfassung

  • Verträge (requires, ensures) dokumentieren und erzwingen Programmierannahmen zur Laufzeit.
  • result verweist in ensures auf das Berechnungsergebnis der Funktion.
  • Schleifeninvarianten sichern den inneren Zustand von Schleifendurchläufen ab.

10. Kontrollfragen zur Selbstprüfung

  1. Wann wird eine requires-Bedingung geprüft: vor oder nach der Ausführung?
  2. Worauf bezieht sich das Wort result in einem Vertrag?
  3. Warum ersetzt ein Vertrag kein HTML-Formularvalidierungs-Muster?

Kapitel 13: Sammlungen, Listen und Wörterbücher (Arrays & Maps)

1. Was lerne ich in diesem Kapitel?

  • Wie du mehrere gleichartige Werte in einem Array (Typ[]) speicherst.
  • Die wichtigsten Listen-Funktionen: len, append, contains, first, last.
  • Wie du Listen mit for .. in durchläufst.
  • Wie assoziative Schlüssel-Wert-Sammlungen mit Map<Schlüssel, Wert> deklariert und genutzt werden.
  • Die wichtigsten Wörterbuch-Funktionen: get, put, contains, keys, values.
  • Warum Zelyras get() immer eine sichere Option liefert und Abstürze verhindert.

2. Warum ist das Thema wichtig?

Eine Anwendung, die nur einzelne Werte speichern kann, wäre nutzlos. In der Praxis arbeiten wir fast immer mit Sammlungen: Listen von Aufgaben, E-Mail-Adressen oder Tabellenzeilen aus einer Datenbank. Manchmal suchen wir Elemente nach ihrer Reihenfolge (Listen bzw. Arrays). Sehr oft wollen wir Daten jedoch direkt über einen eindeutigen Schlüssel nachschlagen – zum Beispiel die Benutzereinstellungen zu einer User-ID, ein Wörterbuch von Übersetzungstexten oder Ländervorwahlen. Dafür bietet Zelyra typisierte Wörterbücher (Map).

3. Verständliche Erklärung

Teil A: Arrays – Die geordnete Liste

Ein Array ist wie ein Setzkasten: Jedes Fach hat eine feste Nummer (Index) und enthält genau ein Element. In Zelyra müssen alle Elemente im selben Kasten denselben Typ haben (Int[], String[] etc.):

fn main() {
    zahlen: Int[] = [10, 20, 30, 40]
    print(len(zahlen))
}

Zelyra stellt mächtige Hilfswerkzeuge für Arrays bereit:

  • len(liste): Gibt die Anzahl der Elemente zurück.
  • append(liste, wert): Liefert ein neues Array mit dem angehängten Element.
  • first(liste): Liefert das erste Element als Option.
  • last(liste): Liefert das letzte Element als Option.
  • contains(liste, wert): Prüft, ob ein Element vorhanden ist (Bool).

Teil B: Maps – Assoziative Schlüssel-Wert-Wörterbücher

Eine Map ordnet jedem eindeutigen Schlüssel (Key) genau einen Wert (Value) zu.

  • Typnotation: Map<KeyTyp, WertTyp>, zum Beispiel Map<String, Int> oder Map<String, String>.
  • Literalschreibweise: Map { "schluessel": wert }
  • Schlüsseltypen: Int, UInt, String, Bool, Char und Timestamp sind dokumentiert. Für JSON müssen Map-Schlüssel String sein.
fn main() {
    vorwahlen: Map<String, Int> = Map {
        "de": 49
        "at": 43
        "ch": 41
    }
}

Die wichtigsten Operationen auf Maps:

  • get(map, key): Schlägt einen Schlüssel nach. Weil ein Schlüssel fehlen könnte, liefert get() immer eine sichere Option<Wert> zurück (Some(wert) oder None) – niemals einen Null-Pointer-Absturz!
  • put(map, key, wert): Fügt ein Paar hinzu oder überschreibt den bestehenden Wert. Da Zelyra Unveränderlichkeit schätzt, liefert put() eine neue, aktualisierte Map zurück.
  • contains(map, key): Gibt true zurück, wenn der Schlüssel existiert.
  • keys(map): Gibt alle vorhandenen Schlüssel als typisiertes Array zurück (KeyTyp[]).
  • values(map): Gibt alle Werte als typisiertes Array zurück (WertTyp[]).

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Array anlegen und mit for-Schleife durchlaufen

fn main() {
    aufgabe_ids: Int[] = [101, 102, 103, 104]
    for id in aufgabe_ids {
        print(id)
    }
}

Beispiel 2: Elemente an ein Array anfügen

fn main() {
    mutable liste: Int[] = [1, 2, 3]
    liste = append(liste, 4)
    print(len(liste))
}

Beispiel 3: Wörterbuch (Map) anlegen, aktualisieren und abfragen

fn main() {
    preise: Map<String, Int> = Map {
        "Kaffee": 3
        "Tee": 2
    }
    
    # Neues Element hinzufügen
    mutable aktion = put(preise, "Kuchen", 4)
    # Vorhandenen Preis anpassen
    aktion = put(aktion, "Kaffee", 4)
    
    # Nullsicher mit match abfragen
    match get(aktion, "Kaffee") {
        Some(preis) => {
            print("Kaffeepreis in Euro:")
            print(preis)
        }
        None => {
            print("Artikel nicht gefunden.")
        }
    }
    
    # Alle Schlüssel und Werte ausgeben
    print(keys(aktion))
}

Beispiel 4: Prüfen der Existenz in einer Map

fn main() {
    einstellungen: Map<String, Bool> = Map {
        "dunkelmodus": true
        "benachrichtigungen": false
    }
    
    if contains(einstellungen, "dunkelmodus") {
        print("Dunkelmodus-Einstellung ist konfiguriert.")
    }
}

5. Typische Fehler und deren Ursachen

  • Fehler: Versuchen, verschiedene Typen in einem Array oder einer Map zu mischen (z. B. [1, "Hallo"]). Ursache: Zelyra ist strikt typrein. Alle Elemente eines Arrays müssen denselben Typ haben, und alle Schlüssel bzw. Werte einer Map müssen ihren deklarierten Typen entsprechen.
  • Fehler: map.get("key") direkt als Wert behandeln wollen, ohne die Option auszupacken. Ursache: Zelyra garantiert Compile-Time-Sicherheit. Da ein Schlüssel in der Map fehlen könnte, zwingt der Compiler dich, mit match oder Standardwerten auf Some und None zu reagieren.
  • Fehler: Nicht-skalare Typen (wie Arrays) als Map-Schlüssel verwenden. Ursache: Map-Schlüssel müssen einen unterstützten vergleichbaren Typ besitzen (Int, UInt, String, Bool, Char, Timestamp). JSON unterstützt nur String-Schlüssel.

6. Merksätze

  1. Arrays in Zelyra sind typrein: Int[], String[], Bool[].
  2. Das Durchlaufen von Arrays erfolgt elegant und sicher mit for element in sammlung { ... }.
  3. Map<Key, Value> speichert eindeutige Schlüssel-Wert-Paare.
  4. get(map, key) liefert immer eine Option (Some oder None) und schützt vor Laufzeitabstürzen.
  5. put(map, key, value) liefert funktional ein neues, aktualisiertes Wörterbuch.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Erstelle ein Array mit drei Zeichenketten und gib jedes Element mit einer for-Schleife aus.
  • Stufe 2 (Mittel): Erstelle eine Map<String, Int> mit drei Produktnamen und ihren Preisen. Frage einen vorhandenen und einen nicht vorhandenen Artikel mit get() und match ab.
  • Stufe 3 (Anspruchsvoll): Schreibe eine Funktion zaehle_woerter(liste: String[]) -> Map<String, Int>, die zählt, wie oft jedes Wort in einer Liste vorkommt, und das Ergebnis als Map zurückgibt.

8. Praxisaufgabe: Aufgaben-Prioritäten nachschlagen

Erstelle eine Verwaltung von Prioritätsstufen für Aufgaben mit einer Map:

fn prioritaet_anzeigen(prioritaeten: Map<String, Int>, aufgabe: String) {
    match get(prioritaeten, aufgabe) {
        Some(stufe) => {
            print("Prioritaet fuer " + aufgabe + ":")
            print(stufe)
        }
        None => {
            print("Keine Prioritaet hinterlegt fuer: " + aufgabe)
        }
    }
}

fn main() {
    prio_map: Map<String, Int> = Map {
        "Datenbankmigration": 1
        "CSS anpassen": 3
        "Dokumentation": 2
    }
    
    prioritaet_anzeigen(prio_map, "Datenbankmigration")
    prioritaet_anzeigen(prio_map, "Kaffeepause")
}

9. Zusammenfassung

  • Arrays (Typ[]) speichern geordnete Folgen von Werten desselben Typs.
  • Maps (Map<Key, Value>) speichern Schlüssel-Wert-Zuordnungen mit sicherem get(), put(), contains(), keys() und values().
  • Beide Sammlungen sind vollständig typsicher und arbeiten nahtlos mit Zelyras Option-System zusammen.

10. Kontrollfragen zur Selbstprüfung

  1. Welchen Typ hat der Ausdruck ["A", "B", "C"]?
  2. Warum schlägt append([1, 2], "Drei") beim Kompilieren fehl?
  3. Wie prüfst du effizient, ob ein Wert in einer Liste existiert?

Kapitel 14: Eigene Datentypen erstellen (Records & Tables)

1. Was lerne ich in diesem Kapitel?

  • Wie du eigene domänenspezifische Datentypen erstellst.
  • Was nominale Typen (type TaskId = Id) sind und wie sie Verwechslungen verhindern.
  • Was Aufzählungs- und Summentypen bedeuten und welche Varianten Zelyra eingebaut bereitstellt.
  • Wie Datensätze in Zelyra als table mit Attributen definiert werden (id: Id primary auto).
  • Warum starke Typisierung die Softwarequalität revolutioniert.

2. Warum ist das Thema wichtig?

In schlechter Software wird fast alles als einfache Zahl oder Zeichenkette behandelt („Primitive Obsession“). Wenn eine Funktion pruefe(benutzer_id: Int, aufgabe_id: Int) erwartet, du aber versehentlich die IDs vertauschst, merkt der Computer bei einfachen Int-Typen gar nichts – die Software bucht womöglich fatale Daten falsch zu. Mit eigenen Typen unterscheidet Zelyra schon beim Kompilieren zwischen einer UserId und einer TaskId.

3. Verständliche Erklärung

Eigene Datentypen erlauben es dir, der realen Welt einen Namen zu geben:

  1. Fachliche Typ-Aliase:

    type TaskId = Id
    
    fn main() {
        print("Typalias TaskId aktiv")
    }
    

    Der Alias gibt der ID einen fachlichen Namen. Er besitzt keine eigene Runtimeidentität; verwechslungssichere Geschäftsregeln müssen im konkreten Prüfumfang abgesichert werden.

  2. Aufzählungs- und Summentypen:

    Ein Aufzählungstyp beschreibt einen Wert, der genau eine Variante aus einer festen Menge benannter Varianten annimmt. Eine Variante kann zusätzlich einen Wert tragen; deshalb heißen solche Typen auch Summentypen. Das allgemeine Konzept lässt sich als enum State { Draft, Active, Archived } skizzieren. Diese Schreibweise ist jedoch nur eine Konzeptdarstellung und keine unterstützte Zelyra-Syntax. Benutzerdefinierte enum-Deklarationen gehören nicht zum derzeit dokumentierten Sprachumfang. Eingebaute Summentypen sind Option<T> und Result<T, E>; ihre Varianten werden mit match behandelt.

  3. Tabellen als strukturierte Datentypen:

    table aufgaben {
        id: Id primary auto
        beschreibung: String required
        fertig: Bool
    }
    
    fn main() {
        print("Tabellenschema definiert")
    }
    

    Dieses Schema definiert nicht nur eine Datenbanktabelle, sondern stellt in Zelyra automatisch den Datentyp für eine Aufgabe bereit!

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Eigene Typnamen für IDs

type TaskId = Id

fn main() {
    print("Typalias erfolgreich definiert")
}

Beispiel 2: Ein Datenmodell als Tabelle definieren

table aufgaben {
    id: Id primary auto
    beschreibung: String required
    erledigt: Bool
}

fn main() {
    print("Tabellenschema fuer Aufgaben definiert")
}

Beispiel 3: Typisierte Attribute in Funktionen ansprechen

table projekte {
    id: Id primary auto
    name: String required
    aktiv: Bool
}

fn zeige_projekt_status(p_name: String, p_aktiv: Bool) {
    mutable status = "Pausiert"
    if p_aktiv {
        status = "Aktiv"
    }
    print("Projekt: " + p_name + " [" + status + "]")
}

fn main() {
    zeige_projekt_status("Web-Portal", true)
}

5. Typische Fehler und deren Ursachen

  • Fehler: Ein Tabellenfeld mit einem reservierten Schlüsselwort wie title oder list benennen. Ursache: In Zelyra sind diese Wörter für Abfragen und Ansichten reserviert. Nutze stattdessen aussagekräftige Namen wie name, text oder beschreibung.
  • Fehler: Den Primärschlüssel mit primary key statt primary angeben. Ursache: In Zelyra lautet die Spaltenspezifikation id: Id primary auto.

6. Merksätze

  1. Eigene Datentypen spiegeln die Geschäftswelt wider und verhindern fatale Parameterverwechslungen.
  2. table-Definitionen sind in Zelyra gleichzeitig Datenbankschema und Sprach-Datentyp.
  3. Reservierte Wörter (title, list, etc.) dürfen nicht als Spalten- oder Variablennamen genutzt werden.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Erstelle einen Typalias type UserId = Id.
  • Stufe 2 (Mittel): Modelliere eine Tabelle kategorien mit id: Id primary auto und kategorie_name: String required.
  • Stufe 3 (Anspruchsvoll): Modelliere eine Tabelle notizen, die über ein Feld aufgabe_id: Id mit einer Aufgabe verknüpft werden kann.

8. Praxisaufgabe: Das Kernschema der Aufgabenverwaltung

Definiere das vollständige Zelyra-Schema für unsere Aufgabenverwaltung:

table aufgaben {
    id: Id primary auto
    name: String required
    beschreibung: String
    prioritaet: Int
    ist_erledigt: Bool
}

fn main() {
    print("Kernschema der Aufgabenverwaltung aktiv.")
}

9. Zusammenfassung

  • Eigene Typen geben Daten eine klare, unmissverständliche Bedeutung.
  • table vereint Schema-Deklaration und statische Typdefinition nahtlos in der Sprache.
  • Statische Typen fangen Logikfehler bereits beim Schreiben des Codes ab.

10. Kontrollfragen zur Selbstprüfung

  1. Welchen Vorteil bietet type TaskId = Id gegenüber einem einfachen Int?
  2. Warum müssen Tabellenfelder in Zelyra stets einen festen Typ besitzen?
  3. Welche Namen sollten als Feldbezeichnungen vermieden werden?

Kapitel 15: Module und Code-Organisation

1. Was lerne ich in diesem Kapitel?

  • Wie ein typisches Zelyra-Projekt strukturiert ist.
  • Die Rolle der Projektkonfigurationsdatei zelyra.toml.
  • Wie Code in logische Bestandteile (Schema, Logik, Ansichten) gegliedert wird.
  • Wie Zelyras CLI zusammenhängende Projektdateien prüft und baut.
  • Den aktuellen Entwicklungsstand und die Roadmap von Modulen und Imports.

2. Warum ist das Thema wichtig?

Zu Beginn schreibt man gerne alles in eine einzige Datei. Doch wenn deine Aufgabenverwaltung wächst – mit Datenbanktabellen, 20 Funktionen, Webformularen und Validierungen –, verliert man in einer 2000-Zeilen-Datei schnell den Überblick. Gute Softwareentwicklung bedeutet, Code so zu organisieren, dass man Neuerungen sofort an der richtigen Stelle findet.

3. Verständliche Erklärung

Ein professionelles Zelyra-Projekt folgt einer klaren Ablagestruktur:

  • zelyra.toml: Die Geburtsurkunde des Projekts. Hier stehen Name, Version und erforderliche Berechtigungen.
  • src/schema.zyl: Enthält alle Tabellendefinitionen (table) und Domänentypen.
  • src/main.zyl: Enthält die Hauptlogik und den Einstiegspunkt (fn main()).
[package]
name = "aufgaben_planer"
version = "0.1.0"
authors = ["Entwickler <dev@example.com>"]

[capabilities]
filesystem = false
database = true

Hinweis zur Sprachkompatibilität: Zelyra unterstützt ausdrückliche projektrelative Importe wie import "src/models.zyl" as models. Diese Importe verbinden Quelldateien innerhalb eines Projekts; sie bieten weder eine Paketregistrierung noch dynamische Laufzeitimporte. Modulgraph und Deployment-Abschluss bleiben experimentell und können unvollständig sein. Die Sprachkompatibilitätslinie bleibt 0.1.

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Eine saubere Hauptdatei

fn main() {
    print("Zelyra Aufgaben-System bereit.")
}

Beispiel 2: Trennung von Logikfunktionen

fn format_system_status(status: String) -> String {
    return "[STATUS] " + status
}

fn main() {
    print(format_system_status("Datenbank verbunden"))
}

Beispiel 3: Deklaration von Capabilities in der Projektdatei In zelyra.toml legst du fest, welche Systemzugriffe das Projekt überhaupt anfordern darf. Greift dein Code auf die Datenbank zu, muss database = true aktiviert sein.

5. Typische Fehler und deren Ursachen

  • Fehler: Python- oder JavaScript-Importsyntax übernehmen. Ursache: Zelyra erwartet projektrelative Importe wie import "src/models.zyl" as models. Verwende für unterstützte öffentliche Deklarationen den Modulnamen; eine Paketregistrierung gibt es nicht.
  • Fehler: zelyra.toml löschen oder im falschen Verzeichnis ausführen. Ursache: zelyra run sucht im aktuellen Verzeichnis nach der Konfiguration.

6. Merksätze

  1. zelyra.toml steuert Metadaten und Sicherheitsrichtlinien des Projekts.
  2. Trenne Datenmodell (table), Geschäftslogik (fn) und Darstellung sauber voneinander.
  3. Ordnung im Projektverzeichnis schützt vor Flüchtigkeitsfehlern im Team.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Erstelle mit zelyra new aufgaben_app eine neue Projektstruktur und untersuche die erzeugten Dateien.
  • Stufe 2 (Mittel): Konfiguriere in zelyra.toml eine Beschreibung und die Versionsnummer 0.3.0.
  • Stufe 3 (Anspruchsvoll): Schreibe ein Programm mit drei separaten Funktionen für Initialisierung, Verarbeitung und Ausgabe.

8. Praxisaufgabe: Projektstruktur für die Aufgabenverwaltung

Lege die Struktur fest mit folgendem Inhalt in main.zyl:

table aufgaben {
    id: Id primary auto
    name: String required
    erledigt: Bool
}

fn starte_system() {
    print("========================================")
    print("   AUFGABEN-MANAGER ERFOLGREICH GESTARTET")
    print("========================================")
}

fn main() {
    starte_system()
}

9. Zusammenfassung

  • Projekte werden über zelyra.toml gesteuert und konfiguriert.
  • Zelyra prüft Projektdateien als ganzheitliche Einheit.
  • Eine modulare Denkweise erleichtert Erweiterungen und Teamarbeit.

10. Kontrollfragen zur Selbstprüfung

  1. Welche Datei enthält die Metadaten eines Zelyra-Projekts?
  2. Warum ist die Trennung von Datenmodell und Ausführungslogik sinnvoll?
  3. Wie prüft die Zelyra-CLI das gesamte Projekt auf einmal?

TEIL IV – SICHERHEIT UND FEHLERBEHANDLUNG


Kapitel 16: Fehlerarten und ihre Ursachen

1. Was lerne ich in diesem Kapitel?

In diesem Kapitel lernst du:

  • Welche vier Hauptarten von Fehlern es beim Programmieren gibt.
  • Wie sich Syntaxfehler, Typfehler, Logikfehler und Laufzeitfehler unterscheiden.
  • Wie Zelyras Compiler dich durch präzise Fehlermeldungen mit Code-Ort und Hinweisen leitet.
  • Warum frühe Fehlererkennung beim Kompilieren bares Geld spart.

2. Warum ist das Thema wichtig?

Fehler sind beim Programmieren völlig normal. Selbst erfahrene Programmierer machen dutzende Fehler pro Tag – der einzige Unterschied ist, dass Profis dabei etwas entspannter Kaffee trinken. Der Unterschied zwischen frustrierenden und erfolgreichen Entwicklern liegt nicht darin, keine Fehler zu machen, sondern darin, Fehlermeldungen lesen und verstehen zu können. Zelyra wurde so entworfen, dass möglichst viele Fehler bereits beim Kompilieren (zelyra check) abgefangen werden, bevor die Anwendung den ersten Nutzer erreicht und dein Bereitschaftshandy am Wochenende klingelt.

3. Verständliche Erklärung

Wir unterscheiden vier Kategorien:

  1. Syntaxfehler (E-LEX-*, E-PARSE-*): Du hast die Grammatik der Sprache verletzt – wie ein Rechtschreibfehler in einem Diktat (z. B. eine geschlossene Klammer vergessen).
  2. Typfehler (E-TYPE-*): Die Grammatik stimmt, aber die Bedeutung passt nicht zusammen (z. B. Text zu einer Zahl addieren).
  3. Vertrags- und Berechtigungsfehler (E-CONTRACT-*, E-CAP-*): Eine vereinbarte Vorbedingung wurde verletzt oder eine Funktion versucht, ohne Erlaubnis auf das Dateisystem zuzugreifen.
  4. Logikfehler: Das Programm läuft fehlerfrei durch, tut aber nicht das, was du beabsichtigt hast (z. B. Rabatt addiert statt abgezogen).

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Typischer Syntaxfehler (und wie man ihn liest) Wenn du in Zelyra eine Klammer vergisst:

# Syntaktisch korrekt:
fn korrekte_klammern() {
    print("Alle Klammern sind geschlossen.")
}

fn main() {
    korrekte_klammern()
}

Beispiel 2: Berechtigungsfehler (Capability-Prüfung) Versucht eine Funktion ohne deklarierte Rechte auf Systemressourcen zuzugreifen, stoppt Zelyra sofort:

fn lese_datei(pfad: String) -> String
    uses FileSystem
{
    return read_text(pfad)
}

fn main() {
    print("Dateizugriff sauber deklariert.")
}

Beispiel 3: Logikfehler durch Verträge entlarven

fn berechne_rabattpreis(original: Int, rabatt: Int) -> Int
    requires { original >= 0 && rabatt >= 0 && rabatt <= original }
    ensures { result <= original }
{
    return original - rabatt
}

fn main() {
    preis = berechne_rabattpreis(100, 20)
    print(preis)
}

5. Typische Fehler und deren Ursachen

  • Fehler: Fehlermeldungen wegklicken, ohne die Zeilennummer zu beachten. Ursache: Zelyra gibt immer die exakte Zeile und Spalte des Fehlers an.
  • Fehler: Ein + zwischen Text und Zahl verwenden. Ursache: Zelyra erzwingt Typsicherheit. Formatiere Werte als String oder gib sie separat aus.

6. Merksätze

  1. Ein Compilerfehler ist kein Scheitern, sondern ein wertvoller Hinweis.
  2. Je früher ein Fehler gefunden wird (Compile-Zeit statt Laufzeit), desto sicherer ist die Software.
  3. Verträge (requires, ensures) verwandeln tückische Logikfehler in sofort sichtbare Vertragsbrüche.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Provoziere absichtlich einen Syntaxfehler (z. B. Semikolon oder Klammer weglassen) und beobachte die Meldung von zelyra check.
  • Stufe 2 (Mittel): Erstelle eine Funktion mit einem Typfehler und korrigiere sie nach dem Compiler-Hinweis.
  • Stufe 3 (Anspruchsvoll): Schreibe eine Funktion zur Altersfreigabe mit Verträgen, die ungültige Alterseingaben (z. B. negative Werte) sofort abfangen.

8. Praxisaufgabe: Fehlertolerante Aufgabendauer-Berechnung

Schreibe eine Funktion für die Aufgabenverwaltung, die verhindert, dass negative Stunden erfasst werden:

fn erfasse_stunden(bisherige_stunden: Int, neue_stunden: Int) -> Int
    requires { bisherige_stunden >= 0 && neue_stunden >= 0 }
    ensures { result >= bisherige_stunden }
{
    return bisherige_stunden + neue_stunden
}

fn main() {
    gesamt = erfasse_stunden(5, 3)
    print(gesamt)
}

9. Zusammenfassung

  • Zelyra unterscheidet strikt zwischen Syntax-, Typ-, Berechtigungs- und Logikfehlern.
  • Durch statische Prüfung und Verträge werden die meisten Fehler vor dem Einsatz aufgedeckt.

10. Kontrollfragen zur Selbstprüfung

  1. Welche Fehlermeldung (E-...) erzeugt ein vergessenes Schlüsselwort?
  2. Warum kann ein Programm trotz fehlerfreier Kompilierung falsch rechnen?
  3. Wie helfen Verträge beim Aufspüren logischer Denkfehler?

Kapitel 17: Fehler als Werte – Das Result-Muster

1. Was lerne ich in diesem Kapitel?

  • Was das Result-Muster ist und warum Zelyra keine unkontrollierten Exceptions (Ausnahmen) nutzt.
  • Die beiden Zustände: Ok(wert) für Erfolg und Err(meldung) für Fehler.
  • Wie du Ergebnisse mit match sicher zerlegst.
  • Warum Fehler als Werte deinen Code transparent und absturzsicher machen.

2. Warum ist das Thema wichtig?

In vielen älteren Sprachen (wie Java oder Python) wirft eine Funktion im Fehlerfall eine »Exception«. Exceptions sind wie Feueralarme: Oft laut, panisch, und niemand weiß genau, wer den Notausgang blockiert hat. Wenn irgendwo im Code eine solche Ausnahme vergessen wird, stürzt die gesamte Webanwendung mit einem Serverfehler ab. In Zelyra gibt es keine unkontrollierten Abstürze: Wenn eine Operation fehlschlagen kann (z. B. Datei nicht gefunden oder ungültige ID), gibt sie zwingend ein Result<T, E> zurück. Der Compiler zwingt dich, beide Fälle zu behandeln – Gelassenheit statt Panik.

3. Verständliche Erklärung

Stelle dir ein Postpaket vor:

  • Wenn der Zusteller das Paket erfolgreich abgibt, öffnest du es und findest den gewünschten Inhalt: Ok(inhalt).
  • Wenn die Adresse nicht existiert, kommt ein Rücksendebeleg mit Begründung: Err("Adresse unbekannt").

Ein Paket kann niemals „explodieren“ – du musst es einfach nur annehmen und nachsehen:

fn dividiere_sicher(a: Int, b: Int) -> Result<Int, String> {
    if b == 0 {
        return Err("Division durch 0 nicht erlaubt")
    }
    return Ok(a / b)
}

fn main() {
    print("Sichere Division definiert.")
}

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Eine Funktion mit Result definieren

fn pruefe_prioritaet(stufe: Int) -> Result<Int, String> {
    if stufe < 1 {
        return Err("Prioritaet zu niedrig (mindestens 1)")
    }
    if stufe > 3 {
        return Err("Prioritaet zu hoch (maximal 3)")
    }
    return Ok(stufe)
}

fn main() {
    res = pruefe_prioritaet(2)
    match res {
        Ok(stufe) => {
            print("Gueltige Prioritaet:")
            print(stufe)
        }
        Err(fehler) => {
            print(fehler)
        }
    }
}

Beispiel 2: Fehlerfall behandeln

fn hole_kontostand(pin: Int) -> Result<Int, String> {
    if pin != 1234 {
        return Err("Falsche PIN!")
    }
    return Ok(500)
}

fn main() {
    versuch = hole_kontostand(9999)
    match versuch {
        Ok(betrag) => {
            print(betrag)
        }
        Err(meldung) => {
            print("Abgewiesen: " + meldung)
        }
    }
}

Beispiel 3: Sichere Werteumwandlung

fn pruefe_titel_laenge(titel: String) -> Result<String, String> {
    if titel == "" {
        return Err("Aufgabentitel darf nicht leer sein.")
    }
    return Ok(titel)
}

fn main() {
    ergebnis = pruefe_titel_laenge("Projektbericht")
    match ergebnis {
        Ok(t) => {
            print("Gueltiger Titel: " + t)
        }
        Err(e) => {
            print("Fehler: " + e)
        }
    }
}

5. Typische Fehler und deren Ursachen

  • Fehler: Versuchen, direkt auf den inneren Wert zuzugreifen, ohne match zu verwenden. Ursache: Ein Result<T, E> ist eine Hülle. Du musst sie mit match öffnen.
  • Fehler: Einen der beiden Zweige (Ok oder Err) im match vergessen. Ursache: Zelyra verlangt vollständige Musterabdeckung.

6. Merksätze

  1. Result<T, E> macht Fehler zu regulären Rückgabewerten.
  2. Ok(v) repräsentiert Erfolg, Err(e) den begründeten Fehlschlag.
  3. Mit match müssen immer beide Ausgänge behandelt werden – das schützt vor Abstürzen.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Schreibe eine Funktion pruefe_gerade(zahl: Int) -> Result<Int, String>, die Ok(zahl) liefert, wenn sie durch 2 teilbar ist, sonst Err("Ungerade").
  • Stufe 2 (Mittel): Schreibe eine Funktion validiere_benutzername(name: String) -> Result<String, String>, die leere Namen oder "admin" ablehnt.
  • Stufe 3 (Anspruchsvoll): Implementiere eine Rechenfunktion mit Fehlerprüfung, die zwei Zahlen dividiert und das Ergebnis bei Erfolg verdoppelt zurückgibt.

8. Praxisaufgabe: Validierung beim Anlegen einer neuen Aufgabe

Erstelle eine sichere Validierungsfunktion für neue Aufgaben:

fn erstelle_aufgabe_geprueft(name: String, prioritaet: Int) -> Result<String, String> {
    if name == "" {
        return Err("Name darf nicht leer sein!")
    }
    if prioritaet < 1 {
        return Err("Prioritaet muss mindestens 1 sein!")
    }
    return Ok("Aufgabe [" + name + "] erfolgreich angelegt.")
}

fn main() {
    treffer1 = erstelle_aufgabe_geprueft("Dokumentation fertigstellen", 1)
    match treffer1 {
        Ok(msg) => {
            print(msg)
        }
        Err(err) => {
            print("Fehler: " + err)
        }
    }

    treffer2 = erstelle_aufgabe_geprueft("", 0)
    match treffer2 {
        Ok(msg) => {
            print(msg)
        }
        Err(err) => {
            print("Fehler: " + err)
        }
    }
}

9. Zusammenfassung

  • Das Result-Muster ersetzt unkontrollierte Ausnahmen durch typisierte Werte.
  • Zelyra garantiert, dass kein Fehler unbehandelt im Programm übersehen wird.

10. Kontrollfragen zur Selbstprüfung

  1. Wofür steht T und wofür steht E im Typ Result<T, E>?
  2. Warum führt ein unbehandelter Fehler in Zelyra nicht zum plötzlichen Programmabsturz?
  3. Wie verarbeitet man den Inhalt eines Result sicher?

Kapitel 18: Das Nichts existiert nicht – Der sichere Umgang mit Option

1. Was lerne ich in diesem Kapitel?

  • Warum der Wert null in der Informatik als „Milliarden-Dollar-Fehler“ bezeichnet wird.
  • Wie Zelyra null vollständig eliminiert und durch den sicheren Typ Option<T> (Kurzform T?) ersetzt.
  • Wie man Werte mit Some(wert) verpackt und das Nichtvorhandensein mit None signalisiert.
  • Wie Standard-Listenoperationen wie first und last den Typ Option nutzen.

2. Warum ist das Thema wichtig?

In Sprachen wie JavaScript, Java, PHP oder C existiert null. Wenn ein Programm versucht, eine Methode auf einem null-Wert aufzurufen, stürzt es augenblicklich mit einer gefürchteten NullPointerException oder Cannot read properties of null ab. Sir Tony Hoare nannte seine Erfindung von null rückblickend selbst seinen »Milliarden-Dollar-Fehler«. Zelyra besitzt schlicht kein null. Wir haben null gar nicht erst zur Party eingeladen! Jeder Wert ist garantiert vorhanden. Wenn etwas fehlen kann, muss es ausdrücklich als Option deklariert werden – und der Compiler passt auf, dass du keinen None-Fall übersiehst.

3. Verständliche Erklärung

Stell dir eine Schachtel vor:

  • Die Schachtel ist entweder mit einem Geschenk gefüllt: Some("Smartphone").
  • Oder die Schachtel ist leer: None.

Du kannst nicht versehentlich in ein „Nichts“ greifen, weil du die Schachtel erst mit match öffnen musst:

fn finde_aufgabe_nach_id(id: Int) -> Option<String> {
    if id == 42 {
        return Some("Server aufsetzen")
    }
    return None
}

fn main() {
    print("Aufgabensuche definiert.")
}

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Option erzeugen und auswerten

fn main() {
    treffer: Option<String> = Some("Zelyra 0.1 Handbuch")
    match treffer {
        Some(titel) => {
            print("Gefunden: " + titel)
        }
        None => {
            print("Kein Treffer vorhanden")
        }
    }
}

Beispiel 2: first() und last() auf Listen Greifst du auf eine leere Liste zu, stürzt Zelyra nicht ab – es liefert None:

fn main() {
    meine_liste: Int[] = [100, 200, 300]
    erstes_element = first(meine_liste)
    match erstes_element {
        Some(wert) => {
            print("Erster Wert:")
            print(wert)
        }
        None => {
            print("Die Liste ist leer!")
        }
    }
}

Beispiel 3: Standardwert bereitstellen mit Option

fn aufgabe_titel_oder_standard(opt_titel: Option<String>) -> String {
    match opt_titel {
        Some(t) => {
            return t
        }
        None => {
            return "Ohne Titel"
        }
    }
}

fn main() {
    print(aufgabe_titel_oder_standard(Some("Wichtiges Meeting")))
    print(aufgabe_titel_oder_standard(None))
}

5. Typische Fehler und deren Ursachen

  • Fehler: Denken, ein Option<String> sei direkt ein String. Ursache: Eine Schachtel ist nicht das Geschenk. Erst mit match entpackst du den Inhalt.
  • Fehler: Versuchen, None einer normalen String-Variable zuzuweisen. Ursache: Reguläre Variablen sind garantiert niemals leer.

6. Merksätze

  1. In Zelyra gibt es kein null und keinen NullPointerException-Absturz.
  2. Wenn ein Wert fehlen kann, heißt der Typ Option<T> oder T?.
  3. Some(x) verpackt den Wert, None signalisiert das Fehlen.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Schreibe eine Funktion finde_partner(name: String) -> Option<String>, die bei "Romeo" Some("Julia") liefert, sonst None.
  • Stufe 2 (Mittel): Untersuche das letzte Element eines Zahlen-Arrays mit last() und gib seinen Wert oder eine Warnung aus.
  • Stufe 3 (Anspruchsvoll): Schreibe eine Suchfunktion, die eine Liste von Aufgaben-IDs durchsucht und die Position (Index) als Option<Int> zurückgibt.

8. Praxisaufgabe: Aufgabendetails sicher abfragen

Implementiere die Nachschlagefunktion für unsere Aufgabenverwaltung:

fn suche_aufgabe_beschreibung(id: Int) -> Option<String> {
    if id == 1 {
        return Some("Datenbankschema fuer Zelyra anlegen")
    }
    if id == 2 {
        return Some("Weboberflaeche gestalten")
    }
    return None
}

fn main() {
    suche1 = suche_aufgabe_beschreibung(1)
    match suche1 {
        Some(text) => {
            print("Aufgabe 1: " + text)
        }
        None => {
            print("Aufgabe 1 nicht gefunden!")
        }
    }

    suche99 = suche_aufgabe_beschreibung(99)
    match suche99 {
        Some(text) => {
            print("Aufgabe 99: " + text)
        }
        None => {
            print("Aufgabe 99 nicht gefunden!")
        }
    }
}

9. Zusammenfassung

  • Option<T> schützt deine Anwendung vor den verheerenden Folgen unvorhergesehener Leerwerte.
  • Zelyra garantiert zur Compile-Zeit, dass jeder mögliche None-Fall behandelt wird.

10. Kontrollfragen zur Selbstprüfung

  1. Warum gibt es in Zelyra kein null?
  2. Was ist der Unterschied zwischen String und Option<String>?
  3. Welche zwei Muster werden in einem match über eine Option immer abgefragt?

Kapitel 19: Tests und Qualitätssicherung

1. Was lerne ich in diesem Kapitel?

  • Warum automatisierte Prüfungen das Rückgrat moderner, langlebiger Software sind.
  • Wie Zelyras Verifikations-Befehl zelyra verify Verträge formal analysiert.
  • Wie man eigene Test- und Prüffunktionen strukturiert.
  • Was die Philosophie von Test-Driven Development (TDD) bedeutet.
  • Wie Assertions und projektbezogene Test-Runner funktionieren und welche Testbefehle die aktuelle CLI bereitstellt.

2. Warum ist das Thema wichtig?

Manuelle Tests (Klicken im Browser oder wiederholtes manuelles Aufrufen) sind mühsam, fehleranfällig und unvollständig. Sobald eine Software komplexer wird, führt jede kleine Änderung an einer Stelle unweigerlich zu neuen Fehlern an einer anderen Stelle („Regressionen“). Automatisierte Tests stellen sicher, dass alle bereits gebauten Funktionen auch nach Wochen und Monaten noch exakt wie vereinbart arbeiten.

3. Verständliche Erklärung

Testen in Zelyra stützt sich auf zwei kraftvolle Säulen:

  1. Formale Vertragsverifikation mit zelyra verify: Der Compiler prüft, ob die Vor- und Nachbedingungen (requires, ensures) deiner Funktionen mathematisch und logisch haltbar sind.
  2. Prüffunktionen mit Erwartungsabgleich: Testfunktionen übergeben Geschäftslogik-Beispielen Eingaben und vergleichen tatsächliche mit erwarteten Ergebnissen. Die eingebaute Assertion assert(bedingung, meldung) meldet einen Fehler, wenn die Bedingung falsch ist.

Die aktuelle CLI-Referenz führt keinen Unterbefehl zelyra test auf. Lege projektbezogene Testroutinen in einer ausführbaren .zyl-Datei ab und starte sie mit zelyra run <file.zyl>. Prüfe den Quelltext vor der Ausführung mit zelyra check; zelyra verify führt die unterstützten formalen Vertragsprüfungen aus.

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Eine direkte Assertion

fn main() {
    ergebnis = 6 * 7
    assert(ergebnis == 42, "Die Multiplikation muss 42 ergeben")
}

Beispiel 2: Eine einfache Prüffunktion

fn pruefe(test_name: String, bedingung: Bool) {
    if bedingung {
        print("[PASS] " + test_name)
    } else {
        print("[FAIL] " + test_name)
    }
}

fn verdopple(x: Int) -> Int {
    return x * 2
}

fn main() {
    pruefe("Verdopple 5 ergibt 10", verdopple(5) == 10)
    pruefe("Verdopple 0 ergibt 0", verdopple(0) == 0)
}

Beispiel 3: Testen von Option-Rückgaben

fn ist_volljaehrig(alter: Int) -> Option<Bool> {
    if alter < 0 {
        return None
    }
    return Some(alter >= 18)
}

fn main() {
    test1 = ist_volljaehrig(20)
    match test1 {
        Some(ok) => {
            if ok {
                print("[PASS] 20 Jahre ist volljaehrig")
            } else {
                print("[FAIL] Unerwarteter Zustand")
            }
        }
        None => {
            print("[FAIL] Alter ungueltig")
        }
    }
}

Beispiel 4: Verifikation mit Verträgen absichern

fn berechne_ueberstunden(stunden: Int, regelarbeitszeit: Int) -> Int
    requires { stunden >= 0 && regelarbeitszeit >= 0 }
    ensures { result >= 0 }
{
    if stunden > regelarbeitszeit {
        return stunden - regelarbeitszeit
    }
    return 0
}

fn main() {
    print(berechne_ueberstunden(45, 40))
}

5. Typische Fehler und deren Ursachen

  • Fehler: Nur den „Gut-Fall“ testen und Randfälle (Grenzwerte, 0, leere Listen) ignorieren. Ursache: Die meisten Fehler treten an den Grenzen des Definitionsbereichs auf.
  • Fehler: Tests nach einer Code-Änderung nicht erneut ausführen. Ursache: Gewöhne dir an, zelyra check und deinen Test-Runner nach jeder Anpassung zu starten.

6. Merksätze

  1. Ungesteter Code ist kaputter Code, von dem du es nur noch nicht weißt.
  2. zelyra verify prüft Funktionsverträge direkt auf Sprachebene.
  3. Schreibe Tests, die Randbedingungen und Fehlerpfade gezielt herausfordern.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Schreibe drei Testfälle für eine Funktion addiere(a: Int, b: Int) -> Int.
  • Stufe 2 (Mittel): Schreibe Testfälle für die Option-Suchfunktion aus Kapitel 18.
  • Stufe 3 (Anspruchsvoll): Implementiere eine vollständige Test-Suite für eine Funktion, die prüft, ob ein Aufgabentitel den Qualitätsregeln entspricht (nicht leer, keine Sonderzeichen).

8. Praxisaufgabe: Test-Runner für die Aufgaben-Geschäftslogik

Baue eine kleine Test-Suite für die Kernlogik der Aufgabenverwaltung:

fn test_fall(beschreibung: String, ok: Bool) {
    if ok {
        print("OK: " + beschreibung)
    } else {
        print("FEHLER: " + beschreibung)
    }
}

fn filter_prioritaet(p: Int) -> Bool {
    return p == 1
}

fn main() {
    print("Starte Test-Suite: Aufgabenlogik")
    test_fall("Prioritaet 1 wird gefiltert", filter_prioritaet(1) == true)
    test_fall("Prioritaet 2 wird ignoriert", filter_prioritaet(2) == false)
    print("Test-Suite abgeschlossen.")
}

9. Zusammenfassung

  • Automatisierte Prüfungen sichern langfristige Softwarequalität.
  • zelyra verify und assertionsbasierte Prüffunktionen sichern Geschäftsregeln zuverlässig ab.

10. Kontrollfragen zur Selbstprüfung

  1. Was versteht man unter einer „Regression“ in der Softwareentwicklung?
  2. Welche Aufgabe übernimmt der CLI-Befehl zelyra verify?
  3. Warum sind Grenzwerte (z. B. 0 oder maximale Kapazität) besonders testrelevant?

TEIL V – PRAKTISCHE DATENVERARBEITUNG


Kapitel 20: Arbeiten mit Dateien

1. Was lerne ich in diesem Kapitel?

In diesem Kapitel lernst du:

  • Wie du mit Zelyra Textdateien erstellst, liest, auflistest und löschst.
  • Warum Dateizugriffe in Zelyra zwingend die Fähigkeit (Capability) uses FileSystem verlangen.
  • Die wichtigsten Bibliotheksfunktionen: read_text, write_text, delete_file, list_dir.
  • Wie du Aufgabenlisten persistent als Datei auf der Festplatte speicherst.

2. Warum ist das Thema wichtig?

Variablen im Arbeitsspeicher gehen verloren, sobald ein Programm beendet wird oder der Rechner neu startet. Um Daten dauerhaft zu sichern – z. B. Exporte, Konfigurationsdateien oder Protokolle –, müssen sie auf die Festplatte geschrieben werden. Gleichzeitig stellen Dateizugriffe ein Sicherheitsrisiko dar. Zelyra schützt das System, indem Funktionen ihre Zugriffsrechte explizit deklarieren müssen.

3. Verständliche Erklärung

Stell dir das Dateisystem wie ein Archiv vor:

  • Wenn du eine Akte ablegen willst, schreibst du Text hinein (write_text).
  • Wenn du nachsehen willst, liest du die Akte (read_text).
  • Du darfst das Archiv aber nur betreten, wenn du den Archivschlüssel besitzt: uses FileSystem.
fn speichere_notiz(pfad: String, inhalt: String)
    uses FileSystem
{
    write_text(pfad, inhalt)
}

fn main() uses FileSystem {
    speichere_notiz("notiz.txt", "Einkaufsliste: Milch, Brot")
    print("Notiz gespeichert.")
}

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Text schreiben und lesen

fn datei_workflow() uses FileSystem {
    pfad = "aufgaben_export.txt"
    write_text(pfad, "Aufgabe 1: Zelyra lernen")
    text = read_text(pfad)
    print("Gelesener Inhalt: " + text)
}

fn main() uses FileSystem {
    datei_workflow()
}

Beispiel 2: Datei aufräumen mit delete_file

fn aufraeumen(pfad: String) uses FileSystem {
    delete_file(pfad)
    print("Datei geloescht.")
}

fn main() uses FileSystem {
    write_text("temp.txt", "Kurzlebig")
    aufraeumen("temp.txt")
}

Beispiel 3: Verzeichnisinhalte auflisten

fn zeige_dateien(ordner: String) uses FileSystem {
    dateien = list_dir(ordner)
    for datei in dateien {
        print("Gefundene Datei: " + datei)
    }
}

fn main() uses FileSystem {
    zeige_dateien(".")
}

5. Typische Fehler und deren Ursachen

  • Fehler: Aufruf von read_text oder write_text ohne Deklaration von uses FileSystem. Ursache: Zelyras Sicherheitssystem (E-CAP-001) verhindert jeden unberechtigten Zugriff auf die Festplatte.
  • Fehler: Vergessen, dass auch main() die Berechtigung deklarieren muss, wenn sie aufrufende Funktionen ausführt. Ursache: Berechtigungen vererben sich entlang der Aufruf-Kette nach oben.

6. Merksätze

  1. Jede Funktion, die Dateien berührt, muss uses FileSystem deklarieren.
  2. write_text legt Dateien an oder überschreibt sie vollständig.
  3. read_text liefert den gesamten Dateiinhalt als String.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Schreibe ein Programm, das eine Begrüßungsnachricht in hallo.txt schreibt.
  • Stufe 2 (Mittel): Schreibe eine Funktion, die prüft, ob eine exportierte Datei existiert und deren Inhalt ausgibt.
  • Stufe 3 (Anspruchsvoll): Implementiere eine einfache Log-Funktion, die Statusmeldungen zeilenweise aneinanderhängt und sichert.

8. Praxisaufgabe: Aufgabenliste in eine Textdatei exportieren

Speichere die offenen Aufgaben unserer Anwendung als Datei:

fn exportiere_aufgaben(pfad: String) uses FileSystem {
    inhalt = "[ ] Dokumentation fertigstellen\n[OK] Zelyra-Compiler installieren\n[ ] Backup konfigurieren"
    write_text(pfad, inhalt)
    print("Aufgaben erfolgreich nach " + pfad + " exportiert.")
}

fn main() uses FileSystem {
    exportiere_aufgaben("aufgaben_heute.txt")
}

9. Zusammenfassung

  • Zelyra bietet schlanke, sichere Funktionen für Datei-Ein-/Ausgabe.
  • Das Capability-System verhindert verdeckte Spionage oder Datenmanipulation.

10. Kontrollfragen zur Selbstprüfung

  1. Welche Fähigkeit muss eine Funktion anfordern, um write_text aufzurufen?
  2. Warum verlangt Zelyra uses FileSystem auch für main()?
  3. Welche Funktion liefert eine Liste aller Dateinamen in einem Ordner?

Kapitel 21: Datum, Uhrzeit, Zufall und strukturierte Daten

1. Was lerne ich in diesem Kapitel?

  • Wie du mit now() den aktuellen Zeitstempel abfragst (uses Clock).
  • Wie Zufallswerte mit random_int(min, max) erzeugt werden (uses Random).
  • Wie Datenstrukturen mit json_encode in universelles JSON umgewandelt werden.
  • Wie JSON-Text mit json_decode<T> wieder typisiert zurückgewandelt wird.

2. Warum ist das Thema wichtig?

Praktisch jede reale Software benötigt Zeitangaben: Wann wurde eine Aufgabe erstellt? Wann ist die Frist abgelaufen? Zeitzonen, Sommerzeit-Umstellungen und Schaltsekunden haben allerdings schon gestandene Mathematiker zum Weinen gebracht. Auch strukturierte Datenformate wie JSON sind im Internet Standard – von REST-APIs bis hin zu Speicherständen. Zelyra integriert Zeit (uses Clock), Zufall (uses Random) und JSON nahtlos, typgeprüft und deterministisch testbar.

3. Verständliche Erklärung

  • Zeitstempel: now() liefert die exakte aktuelle Systemzeit als Timestamp. Dafür benötigt deine Funktion die Erlaubnis uses Clock.
  • Zufallszahlen: random_int(1, 10) liefert eine unvorhersehbare Zahl zwischen 1 und 10 (uses Random).
  • JSON: JSON ist ein einfaches Textformat, das Menschen und Computer gleichermaßen lesen können. Mit json_encode machst du aus einem Zelyra-Array einen Textstring, den du übers Netzwerk verschicken kannst.
fn zeige_zeit() uses Clock {
    jetzt = now()
    print("Aktuelle Systemzeit erfasst")
}

fn main() uses Clock {
    zeige_zeit()
}

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Fristen und Zeitmessung mit Clock

fn protokolliere_erstellung(aufgabe: String) uses Clock {
    erstellt_um = now()
    print("Aufgabe angelegt: " + aufgabe)
}

fn main() uses Clock {
    protokolliere_erstellung("Server patchen")
}

Beispiel 2: Zufällige Ticket-Nummern erzeugen

fn generiere_ticket_nummer() -> Int uses Random {
    return random_int(1000, 9999)
}

fn main() uses Random {
    ticket = generiere_ticket_nummer()
    print("Dein Ticket-Code:")
    print(ticket)
}

Beispiel 3: Daten als JSON exportieren

fn exportiere_ids_als_json(ids: Int[]) -> String {
    return json_encode(ids)
}

fn main() {
    ids: Int[] = [101, 102, 103]
    json_text = exportiere_ids_als_json(ids)
    print("JSON-Ausgabe: " + json_text)
}

5. Typische Fehler und deren Ursachen

  • Fehler: now() aufrufen, ohne uses Clock im Funktionskopf zu deklarieren. Ursache: Zeitabfragen sind nicht-deterministisch und erfordern in Zelyra eine Berechtigung.
  • Fehler: Ungültigen JSON-Text in json_decode übergeben. Ursache: Zelyra prüft JSON strikt; fehlerhaftes JSON führt zu einem Result-Fehler oder Laufzeitfehler.

6. Merksätze

  1. now() liefert die aktuelle Zeit und erfordert uses Clock.
  2. random_int erzeugt Zufallszahlen und verlangt uses Random.
  3. json_encode wandelt Zelyra-Datenstrukturen in standardisierten JSON-Text um.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Erzeuge eine Zufallszahl zwischen 1 und 6 (Würfel) und gib sie aus.
  • Stufe 2 (Mittel): Schreibe eine Funktion, die ein Array von Status-Codes in JSON umwandelt.
  • Stufe 3 (Anspruchsvoll): Kombiniere uses Clock und uses FileSystem, um einen Zeitstempel in eine Datei log.txt zu schreiben.

8. Praxisaufgabe: Aufgaben-Snapshot als JSON sichern

Erstelle einen JSON-Snapshot der Aufgaben-IDs und sichere ihn als Datei:

fn sichere_snapshot(dateiname: String, ids: Int[])
    uses Clock, FileSystem
{
    json_daten = json_encode(ids)
    write_text(dateiname, json_daten)
    print("Snapshot erfolgreich gesichert.")
}

fn main() uses Clock, FileSystem {
    aktuelle_ids: Int[] = [1, 2, 5, 8]
    sichere_snapshot("aufgaben_snapshot.json", aktuelle_ids)
}

9. Zusammenfassung

  • Zelyra bietet integrierte, typsichere Unterstützung für Zeitstempel, Zufall und JSON.
  • Berechtigungen (Clock, Random) sorgen für vollständige Nachvollziehbarkeit.

10. Kontrollfragen zur Selbstprüfung

  1. Welche Capability benötigt eine Funktion, die now() aufruft?
  2. Warum verlangt Zelyra für Zufallszahlen die Berechtigung uses Random?
  3. In welches Format wandelt json_encode Zelyra-Objekte um?

Kapitel 22: Nebenläufigkeit und Hintergrundaufgaben

1. Was lerne ich in diesem Kapitel?

  • Was Nebenläufigkeit bedeutet und wann Aufgaben parallel ausgeführt werden sollten.
  • Wie Zelyras parallel-Block funktioniert: parallel { a = await ...; b = await ... }.
  • Warum Zelyra unkontrollierte Threads oder „Callback-Hölle“ vermeidet.
  • Wie deterministische Nebenläufigkeit deine Anwendung schnell und sicher hält.

2. Warum ist das Thema wichtig?

Moderne Computer und Server besitzen viele Rechenkerne. Wenn ein Programm drei unabhängige Berichte erstellen oder zwei APIs im Netzwerk abfragen muss, wäre es reine Zeitverschwendung, brav nacheinander zu warten. Führt man die Abfragen gleichzeitig aus, ist das Programm doppelt oder dreifach so schnell. In vielen Sprachen führt Nebenläufigkeit jedoch zu gefürchteten Fehlern („Race Conditions“). Zelyra verhindert diese Risiken durch einen strukturierten, sicheren Ansatz.

3. Verständliche Erklärung

Stell dir ein Restaurant vor:

  • Wenn der Koch erst das Steak brät, danach die Pommes frittiert und danach den Salat wäscht, wird das Essen kalt.
  • Ein guter Küchenchef startet alle drei Schritte parallel und wartet, bis alle drei Schüsseln bereitstehen.

In Zelyra nutzt du dafür den parallel-Block mit await:

fn berechne_teil_1() -> Int {
    return 40
}

fn berechne_teil_2() -> Int {
    return 60
}

fn main() {
    parallel {
        ergebnis_1 = await berechne_teil_1()
        ergebnis_2 = await berechne_teil_2()
    }
    print("Beide Teilaufgaben parallel abgeschlossen.")
}

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Parallele Teilberechnungen

fn berechne_statistiken() -> String {
    return "Statistiken berechnet"
}

fn lade_archiv() -> String {
    return "Archiv geladen"
}

fn main() {
    parallel {
        stats = await berechne_statistiken()
        archiv = await lade_archiv()
    }
    print("Paralleles Laden erfolgreich.")
}

Beispiel 2: Unabhängige Datenbeschaffung

fn summe_a() -> Int {
    return 100
}

fn summe_b() -> Int {
    return 250
}

fn main() {
    parallel {
        wert_a = await summe_a()
        wert_b = await summe_b()
    }
    print("Summen ermittelt.")
}

5. Typische Fehler und deren Ursachen

  • Fehler: Versuchen, await außerhalb eines parallel-Blocks zu nutzen. Ursache: Zelyra erlaubt await ausschließlich innerhalb von parallel { ... }.
  • Fehler: Beliebige Anweisungen im parallel-Block platzieren. Ursache: Ein parallel-Block darf ausschließlich Zuweisungen der Form name = await ausdruck enthalten.

6. Merksätze

  1. parallel { ... } führt unabhängige Operationen gleichzeitig aus.
  2. Jede Zeile im parallel-Block folgt dem Muster variable = await aufruf().
  3. Strukturierte Nebenläufigkeit verhindert Deadlocks und unkontrollierte Hintergrundprozesse.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Definiere zwei einfache Berechnungsfunktionen und führe sie parallel aus.
  • Stufe 2 (Mittel): Erstelle zwei Funktionen, die jeweils eine Zeichenkette erzeugen, und führe beide im parallel-Block aus.
  • Stufe 3 (Anspruchsvoll): Simuliere das parallele Prüfen von zwei Bedingungen vor dem Start eines Projekts.

8. Praxisaufgabe: Paralleles Laden von Aufgaben und Benutzerdaten

Beschleunige den Systemstart unserer Aufgabenverwaltung:

fn lade_benutzerprofil() -> String {
    return "Profil: Entwickler"
}

fn lade_aufgabenliste() -> String {
    return "5 Aufgaben geladen"
}

fn main() {
    print("Starte parallelen Abruf...")
    parallel {
        profil = await lade_benutzerprofil()
        aufgaben = await lade_aufgabenliste()
    }
    print("Dashboard bereit.")
}

9. Zusammenfassung

  • Nebenläufigkeit in Zelyra ist strukturiert, deterministisch und sicher vor Race Conditions.
  • Der parallel-Block bündelt asynchrone Berechnungen sauber an einer Stelle.

10. Kontrollfragen zur Selbstprüfung

  1. Wo darf das Schlüsselwort await in Zelyra verwendet werden?
  2. Welchem Format müssen die Anweisungen innerhalb eines parallel-Blocks folgen?
  3. Welcher Vorteil ergibt sich aus parallelen Abrufen gegenüber sequenzieller Abarbeitung?

TEIL VI – DATENBANKEN MIT ZELYRA


Kapitel 23: Warum Zelyra die Datenbank direkt versteht

1. Was lerne ich in diesem Kapitel?

In diesem Kapitel lernst du:

  • Warum die Verbindung zwischen Programmiersprache und relationaler Datenbank traditionell oft fehleranfällig ist.
  • Was das Problem der „ORM-Kluft“ (Object-Relational Impedance Mismatch) ist.
  • Wie Zelyra Datenbanken als Bürger erster Klasse (First-Class Citizen) in die Sprache integriert.
  • Wie Zelyra SQL-Befehle bereits zur Compile-Zeit auf syntaktische und typbezogene Korrektheit prüft.

2. Warum ist das Thema wichtig?

In fast allen gängigen Web-Frameworks (PHP/Laravel, Python/Django, Node/TypeORM) existiert eine unsaubere Trennung: Entwickler schreiben SQL-Strings oder nutzen komplexe Abstraktionsschichten (ORMs). Das Ergebnis kennt jeder: Ein harmloser Dreizeiler feuert im Hintergrund 300 Queries ab (das gefürchtete N+1-Problem), oder Little Bobby Tables (Robert'); DROP TABLE Students;--) nutzt eine unmaskierte Lücke. Tippfehler in Spaltennamen wie user.emaiil werden oft erst bemerkt, wenn ein Nutzer im laufenden Betrieb einen Fehler 500 erhält. Zelyra beendet dieses Risiko: Wenn eine SQL-Abfrage nicht zum deklarierten Tabellenschema passt, verweigert der Compiler den Build sofort vor der Ausführung.

3. Verständliche Erklärung

In Zelyra definierst du deine Datenbank-Konfiguration direkt im Quelltext mit dem Schlüsselwort database:

database main {
    engine: mariadb
    database: "aufgaben_db"
}

fn main() {
    print("Datenbank-Konfiguration initialisiert.")
}

Wenn du Daten aus der Datenbank abfragst, schreibst du echtes SQL – aber der Compiler weiß genau, welche Spalten existieren:

  • Schreibt man SELECT id, beschreibung FROM tasks, ist das gültig.
  • Schreibt man SELECT gibts_nicht FROM tasks, meldet Zelyra schon beim Prüfen: unknown column gibts_nicht.

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Datenbank und Tabelle deklarieren

database main {
    engine: mariadb
    database: "test_db"
}

table tasks {
    id: Id primary auto
    beschreibung: String(255) required
}

fn main() {
    print("Datenbank und Tabelle geprueft.")
}

Beispiel 2: SQL-Prüfung zur Compile-Zeit

database main {
    engine: mariadb
    database: "test_db"
}

table tasks {
    id: Id primary auto
    beschreibung: String(255) required
}

fn zeige_aufgaben() uses Database {
    daten = sql<Task[]> {
        SELECT id, beschreibung
        FROM tasks
    }
    print("SQL typgeprueft.")
}

fn main() uses Database {
    zeige_aufgaben()
}

5. Typische Fehler und deren Ursachen

  • Fehler: SQL-Abfragen ohne Deklaration von uses Database in der Funktion ausführen. Ursache: Zelyras Capability-System schützt vor unerlaubten Datenbankzugriffen.
  • Fehler: Den Datenbankblock database main vergessen. Ursache: Ohne Ziel-Engine kann Zelyra das SQL-Schema nicht verifizieren.

6. Merksätze

  1. Zelyra schließt die Kluft zwischen Code und Datenbank.
  2. SQL-Abfragen werden zur Compile-Zeit typgeprüft.
  3. Datenbankzugriffe erfordern zwingend uses Database.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Erstelle einen database-Block für SQLite oder MariaDB.
  • Stufe 2 (Mittel): Modelliere eine Tabelle benutzer und schreibe eine SQL-Abfrage, die alle Benutzer selektiert.
  • Stufe 3 (Anspruchsvoll): Provoziere absichtlich einen Tippfehler in einem SQL-Spaltennamen und beobachte, wie Zelyra den Fehler exakt meldet.

8. Praxisaufgabe: Die Datenbank der Aufgabenverwaltung anbinden

Erstelle das Fundament unserer Aufgabenverwaltung:

database main {
    engine: mariadb
    database: "zelyra_tasks"
}

table tasks {
    id: Id primary auto
    name: String(100) required
    erledigt: Bool
}

fn status_bericht() uses Database {
    liste = sql<Task[]> {
        SELECT id, name, erledigt
        FROM tasks
    }
    print("Datenbank fuer Aufgabenverwaltung einsatzbereit.")
}

fn main() uses Database {
    status_bericht()
}

9. Zusammenfassung

  • Datenbanken und Schemas sind in Zelyra integraler Bestandteil der Sprache.
  • Tippfehler in SQL werden bereits zur Entwicklungszeit verhindert.

10. Kontrollfragen zur Selbstprüfung

  1. Welches Problem lösen Zelyras typgeprüfte SQL-Blöcke gegenüber gewöhnlichen SQL-Strings?
  2. Welche Fähigkeit muss eine Funktion deklarieren, die sql ausführt?
  3. Wie leitet Zelyra den Typ Aufgabe aus der Tabelle aufgaben ab?

Kapitel 24: Tabellen definieren und Daten modellieren

1. Was lerne ich in diesem Kapitel?

  • Wie relationale Tabellen mit table deklariert werden.
  • Die Syntax für Primärschlüssel: id: Id primary auto.
  • Wie Spalteneigenschaften definiert werden: required, Längenbegrenzung String(100), Standardwerte.
  • Wie Beziehungen zwischen Tabellen modelliert werden.

2. Warum ist das Thema wichtig?

Das Datenmodell ist das Fundament jeder Anwendung. Wenn das Schema unsauber entworfen ist, schleppt man Datenmüll und Performanceprobleme über Jahre mit sich herum. Zelyra erzwingt von Anfang an klare Pflichtfelder, Typen und Integrität.

3. Verständliche Erklärung

Eine Tabelle (table) ist wie ein Aktenordner für gleichartige Datenblätter:

  • Jedes Datenblatt hat eine eindeutige laufende Nummer: id: Id primary auto.
  • Bestimmte Angaben dürfen niemals fehlen: required.
  • Für Texte kannst du Längenbegrenzungen angeben: String(100).
table kategorien {
    id: Id primary auto
    bezeichnung: String(50) required
}

Aus der Tabellendefinition table kategorien generiert Zelyra automatisch den Datentyp Kategorie mit den exakten Feldern.

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Einfache Tabelle mit Pflichtfeldern

database main {
    engine: mariadb
    database: "app_db"
}

table projekte {
    id: Id primary auto
    name: String(80) required
    aktiv: Bool
}

fn main() {
    print("Tabelle projekte deklariert.")
}

Beispiel 2: Tabelle mit Datums- und Zahlenfeldern

database main {
    engine: mariadb
    database: "app_db"
}

table zeiterfassungen {
    id: Id primary auto
    stunden: Float
    erfasst_am: Timestamp
}

fn main() {
    print("Tabelle zeiterfassungen deklariert.")
}

Beispiel 3: Verknüpfung zweier Tabellen über IDs

database main {
    engine: mariadb
    database: "app_db"
}

table tasks {
    id: Id primary auto
    beschreibung: String(200) required
    projekt_id: Id
}

fn main() {
    print("Beziehung aufgaben -> projekt_id angelegt.")
}

5. Typische Fehler und deren Ursachen

  • Fehler: Ein Pflichtfeld beim Anlegen weglassen. Ursache: Felder mit required müssen in jedem Datensatz gültige Werte besitzen.
  • Fehler: Reservierte Wörter wie action, field, title als Spaltennamen wählen. Ursache: Diese Bezeichner sind für Zelyra-Sprachkonstrukte reserviert.

6. Merksätze

  1. Jede Tabelle benötigt einen Primärschlüssel id: Id primary auto.
  2. Das Attribut required verbietet leere Einträge auf Datenbank- und Sprachebene.
  3. Der Singularname der Tabelle (z. B. Aufgabe für aufgaben) wird zum automatischen Datentyp.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Definiere eine Tabelle etiketten mit einem Pflichtfeld name: String(30) required.
  • Stufe 2 (Mittel): Ergänze eine Tabelle kunden um email: Email und telefon: String(30).
  • Stufe 3 (Anspruchsvoll): Modelliere eine Tabelle kommentare, die per aufgabe_id: Id mit einer Aufgabe verknüpft ist und einen erstellt_am: Timestamp besitzt.

8. Praxisaufgabe: Vollständiges Schema für die Aufgabenverwaltung

Erstelle das produktive Datenmodell unserer Aufgabenverwaltung:

database main {
    engine: mariadb
    database: "zelyra_tasks"
}

table tasks {
    id: Id primary auto
    name: String(120) required
    beschreibung: String(500)
    prioritaet: Int
    ist_erledigt: Bool
}

fn main() {
    print("Vollstaendiges Aufgaben-Schema aktiv.")
}

9. Zusammenfassung

  • table definiert Struktur, Typen und Beschränkungen der Daten.
  • Zelyra sorgt dafür, dass Datenbank-Struktur und Code-Typen immer synchron bleiben.

10. Kontrollfragen zur Selbstprüfung

  1. Wozu dient die Kennzeichnung auto beim Primärschlüssel?
  2. Was bewirkt das Schlüsselwort required an einer Tabellenspalte?
  3. Welcher Typname entsteht automatisch aus der Tabelle projekte?

Kapitel 25: Daten abfragen und verändern

1. Was lerne ich in diesem Kapitel?

  • Wie du mit sql<T[]> Datensätze sicher aus der Datenbank liest.
  • Wie SQL-Injection durch parametrisierte Abfragen (:param) unmöglich gemacht wird.
  • Wie man Datensätze mit INSERT, UPDATE und DELETE verändert.
  • Warum Änderungen in transaction { ... }-Blöcken zusammengefasst werden.
  • Die CLI-Migrationswerkzeuge (zelyra db setup, zelyra db apply).

2. Warum ist das Thema wichtig?

SQL-Injection gehört seit über 20 Jahren zu den gefährlichsten Sicherheitslücken im Web: Ein Angreifer gibt in ein Suchfeld manipulierten Text ein und liest fremde Passwörter aus oder löscht Tabellen. Zelyra schützt deine Software konstruktiv: Parameter in SQL-Blöcken werden mit Doppelpunkt (:name) gebunden und von der Engine immer sicher escaped. Gleichzeitig sichern Transaktionen ab, dass bei Fehlern keine halben Buchungen stehenbleiben.

3. Verständliche Erklärung

  • Lesen mit sql<T[]>:

    database main {
        engine: mariadb
        database: "tasks_db"
    }
    table tasks {
        id: Id primary auto
        name: String required
        ist_erledigt: Bool
    }
    fn lade(filter_wert: Bool) uses Database {
        meine_tasks = sql<Task[]> {
            SELECT id, name, ist_erledigt
            FROM tasks
            WHERE ist_erledigt = :filter_wert
        }
        print("Tasks geladen")
    }
    fn main() uses Database { lade(false) }
    
  • Schreiben in einer Transaktion:

    database main {
        engine: mariadb
        database: "tasks_db"
    }
    table tasks {
        id: Id primary auto
        name: String required
        ist_erledigt: Bool
    }
    fn anlegen(neuer_name: String) uses Database {
        fertig_flag = false
        transaction {
            sql {
                INSERT INTO tasks (name, ist_erledigt)
                VALUES (:neuer_name, :fertig_flag)
            }
        }
    }
    fn main() uses Database { anlegen("Test") }
    

    Sollte während der Transaktion etwas schiefgehen, macht die Datenbank alle Änderungen ungeschehen (Rollback).

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Einen neuen Datensatz einfügen

database main {
    engine: mariadb
    database: "tasks_db"
}

table tasks {
    id: Id primary auto
    name: String(100) required
    fertig: Bool
}

fn fuege_aufgabe_ein(text: String) uses Database {
    fertig_status = false
    transaction {
        sql {
            INSERT INTO tasks (name, fertig)
            VALUES (:text, :fertig_status)
        }
    }
    print("Aufgabe gespeichert.")
}

fn main() uses Database {
    fuege_aufgabe_ein("E-Mail beantworten")
}

Beispiel 2: Datensätze typisiert abfragen

database main {
    engine: mariadb
    database: "tasks_db"
}

table tasks {
    id: Id primary auto
    name: String(100) required
    fertig: Bool
}

fn lade_alle() uses Database {
    liste = sql<Task[]> {
        SELECT id, name, fertig
        FROM tasks
    }
    print("Aufgabenliste geladen.")
}

fn main() uses Database {
    lade_alle()
}

Beispiel 3: Datensatz aktualisieren

database main {
    engine: mariadb
    database: "tasks_db"
}

table tasks {
    id: Id primary auto
    name: String(100) required
    fertig: Bool
}

fn markiere_als_fertig(aufgabe_id: Int) uses Database {
    transaction {
        sql {
            UPDATE tasks
            SET fertig = true
            WHERE id = :aufgabe_id
        }
    }
    print("Status aktualisiert.")
}

fn main() uses Database {
    markiere_als_fertig(1)
}

5. Typische Fehler und deren Ursachen

  • Fehler: Werte mit String-Verkettung in SQL einfügen wollen ("WHERE id = " + id). Ursache: In Zelyra gibt es keine String-Zusammenstückelung in SQL. Nutze immer Parameter mit Doppelpunkt (:id).
  • Fehler: Vergessen, dass Änderungen in transaction { ... } gekapselt sein müssen. Ursache: Zelyra verlangt für Schreiboperationen klare Transaktionsgrenzen.

6. Merksätze

  1. Binde Eingabewerte in SQL stets mit :parameter – das schützt vor SQL-Injection.
  2. Schreibende Operationen gehören in transaction { ... }.
  3. Mit zelyra db apply wird das definierte Schema auf die Datenbank übertragen.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Schreibe eine SQL-Abfrage, die alle unfertigen Aufgaben (fertig = false) selektiert.
  • Stufe 2 (Mittel): Schreibe eine Funktion zum Löschen einer Aufgabe anhand ihrer ID (DELETE FROM tasks WHERE id = :id).
  • Stufe 3 (Anspruchsvoll): Implementiere eine Funktion, die innerhalb einer einzigen Transaktion eine alte Aufgabe archiviert und eine neue Nachfolge-Aufgabe anlegt.

8. Praxisaufgabe: Vollständige Datenbank-Operationen der Aufgabenverwaltung

Schreibe den Datenzugriff für unsere Aufgabenverwaltung:

database main {
    engine: mariadb
    database: "zelyra_tasks"
}

table tasks {
    id: Id primary auto
    name: String(100) required
    ist_erledigt: Bool
}

fn aufgabe_anlegen(aufgabe_name: String) uses Database {
    status_initial = false
    transaction {
        sql {
            INSERT INTO tasks (name, ist_erledigt)
            VALUES (:aufgabe_name, :status_initial)
        }
    }
    print("Aufgabe angelegt.")
}

fn aufgabe_abschliessen(ziel_id: Int) uses Database {
    transaction {
        sql {
            UPDATE tasks
            SET ist_erledigt = true
            WHERE id = :ziel_id
        }
    }
    print("Aufgabe abgeschlossen.")
}

fn main() uses Database {
    aufgabe_anlegen("Erstes Zelyra Projekt starten")
    aufgabe_abschliessen(1)
}

9. Zusammenfassung

  • SQL in Zelyra ist nativ, typsicher und automatisch vor Angriffen geschützt.
  • transaction schützt die Datenbankkonsistenz bei allen Änderungen.

10. Kontrollfragen zur Selbstprüfung

  1. Wie schützt Zelyra vor bösartigen SQL-Injection-Angriffen?
  2. Warum müssen schreibende SQL-Befehle in einem transaction-Block stehen?
  3. Welcher CLI-Befehl richtet die Datenbanktabellen anhand des Codes ein?

TEIL VII – WEBANWENDUNGEN UND FORMULARE


Kapitel 26: Webseiten ausgeben

1. Was lerne ich in diesem Kapitel?

In diesem Kapitel lernst du:

  • Wie du mit page blitzschnell Webseiten und Routen erstellst.
  • Wie URL-Parameter (z. B. /tasks/{id}) dynamisch übergeben werden.
  • Wie HTML-Vorlagen direkt im Zelyra-Code definiert werden (html { ... }).
  • Wie Zelyra Cross-Site Scripting (XSS) durch automatisches HTML-Escaping verhindert.
  • Wie das integrierte Designsystem Zelyra UI (zelyra-ui.v1.css) offlinefähig und ohne npm moderne Komponenten, Dark Mode und Dichte bereitstellt.

2. Warum ist das Thema wichtig?

Im klassischen Web-Development muss man oft drei verschiedene Welten verbinden: Einen Webserver (wie Nginx oder Apache), einen Router, eine Template-Engine (Blade, Jinja, Twig) und den Anwendungscode. Wenn man an einer Stelle vergisst, HTML-Sonderzeichen zu maskieren, können Angreifer bösartiges JavaScript einschleusen (XSS). In Zelyra ist der Webserver direkt integriert (zelyra serve), und das HTML-Escaping geschieht automatisch und unumgänglich.

3. Verständliche Erklärung

Mit dem Schlüsselwort page definierst du eine Webroute und das dazugehörige HTML:

page "/willkommen" {
    html {
        <h1>Willkommen bei Zelyra</h1>
        <p>Deine moderne Webanwendung laeuft!</p>
    }
}

Wenn du dynamische Werte anzeigen möchtest, setzt du sie einfach in geschweifte Klammern: {name}. Zelyra ersetzt den Platzhalter sicher durch den echten Text.

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Einfache statische Begrüßungsseite

page "/hallo" {
    html {
        <html>
            <body>
                <h1>Hallo Zelyra-Welt!</h1>
            </body>
        </html>
    }
}

Beispiel 2: Dynamische Route mit URL-Parameter

page "/benutzer/{name}" {
    html {
        <html>
            <body>
                <h1>Profil von {name}</h1>
                <p>Willkommen zurueck im Dashboard.</p>
            </body>
        </html>
    }
}

Beispiel 3: Sicheres Escaping gegen XSS-Angriffe Übergibt ein Nutzer als Namen <script>alert('hack')</script>, gibt Zelyra dies im Browser als harmlosen Text aus – das Skript wird niemals ausgeführt:

page "/sicher/{eingabe}" {
    html {
        <div>Eingabe: {eingabe}</div>
    }
}

Beispiel 4: Wiederverwendbare View-Layouts und Slots (ab Zelyra 0.1.41) Statt auf jeder Seite <html>, <head>, Header und Footer neu zu schreiben, definierst du mit view ein Layout. Ein View besitzt genau einen Hauptslot <slot /> sowie optionale benannte Slots mit sicherem Standardinhalt:

view AppShell {
    html {
        <html lang="de">
            <head><title>Zelyra Anwendung</title></head>
            <body>
                <header>
                    <slot name="header"><h1>Zelyra Portal</h1></slot>
                </header>
                <main>
                    <slot />
                </main>
                <footer>
                    <slot name="footer"><p>Erstellt mit Zelyra</p></slot>
                </footer>
            </body>
        </html>
    }
}

page "/dashboard" {
    view: AppShell
    html {
        <slot name="header"><h1>Mein Dashboard</h1></slot>
        <p>Der eigentliche Inhalt wird im Haupt-Slot der AppShell platziert.</p>
    }
}

Beispiel 5: Deklarative Suche, Filterung und Pagination (ab Zelyra 0.1.40) Für datengetriebene Seiten erzeugt Zelyra semantische Steuerungen für Suche, Sortierung und Seitenaufteilung automatisch, inklusive URL-Zustandserhalt:

database main {
    engine: mariadb
}

table tasks {
    id: Id primary auto
    title: String(100) required
    done: Bool default false
}

page "/tasks" {
    search { title }
    filter { done }
    sort { title }
    paginated 25

    load tasks = sql<Task[]> {
        SELECT id, title, done
        FROM tasks
        ORDER BY title
    }

    html {
        <h1>Aufgaben ({total} gesamt, Seite {page} von {pages})</h1>
        <ul>
            for task in tasks {
                <li>{task.title}</li>
            }
        </ul>
    }
}

Zelyra führt im Hintergrund automatisch die optimierte Zählabfrage (COUNT(*)) aus, bindet total und pages als sichere UInt-Variablen und rendert semantische Filter-Fieldsets.

Beispiel 6: Das integrierte Designsystem Zelyra UI (ab Version 1.0.6) Webanwendungen benötigen oft aufwendige Frontend-Build-Pipelines mit Node.js, Webpack, Vite und hunderten Megabytes an node_modules. Zelyra 1.0.6 bricht mit diesem Overhead: Das gesamte Designsystem zelyra-ui.v1.css wird lokal im Zelyra-Binary ausgeliefert.

  • Kein Build-Schritt, kein CDN: Vollständig offlinefähig, CSP-konform und ohne externe Webfonts.
  • Modulare CSS-Layers: @layer reset, base, theme, layout, components, utilities, overrides; stellt sicher, dass projekteigene Styles in zelyra.theme.css die Barrierefreiheit nicht zerstören.
  • Dark Mode & System-Erkennung: Über data-z-theme="light|dark|system" (reagiert automatisch auf @media (prefers-color-scheme)).
  • Kompakt-Dichte für Business-Software: Über data-z-density="compact" werden Tabellen und Formulare platzsparend für datenintensive Fachanwendungen skaliert.
  • 55 typisierte UI-Komponenten (zelyra/ui): Buttons, Cards, Badges, Tabs, Alerts und Dialoge mit compilergeprüften Darstellungsattributen (appearance: "default" | "success" | "warning" | "danger").
page "/geschaefts-cockpit" {
    html {
        <div class="z-app-shell" data-z-ui data-z-theme="system" data-z-density="compact">
            <header class="z-header">
                <h2>Unternehmensportal</h2>
            </header>
            <main class="z-card">
                <div class="z-card__header">
                    <h3 class="z-card__title">Systemstatus</h3>
                </div>
                <div class="z-card__body">
                    <p>Alle Dienste laufen stabil und compilergeprueft.</p>
                    <a href="/invoices" class="z-button">Zu den Rechnungen</a>
                </div>
            </main>
        </div>
    }
}

5. Typische Fehler und deren Ursachen

  • Fehler: HTML-Tags nicht ordnungsgemäß schließen (z. B. <h1> ohne </h1>). Ursache: Zelyra prüft den HTML-Baum syntaktisch auf Wohlgeformtheit.
  • Fehler: URL-Parameter in geschweiften Klammern falsch benennen. Ursache: Der Parameter in der Route (z. B. {id}) muss mit der Variablen im HTML übereinstimmen.
  • Fehler E-VIEW-010 bis E-VIEW-015: Unbekannte Variablen oder falsche Typen in der View-Interpolation. Ursache: Der Zelyra-Compiler prüft View-Bindungen und Component-Properties bereits zur Compile-Zeit strikt gegen deklarierte Routen, Typen und SQL-Loads.
  • Fehler: Unbekannte oder doppelte Slots in page angeben. Ursache: Eine Seite darf nur benannte Slots befüllen, die der ausgewählte view auch tatsächlich deklariert.
  • Fehler E-FORM-005: Ungültiger Wert für appearance (z. B. appearance: "dangeer"). Ursache: Zelyra prüft Darstellungsattribute statisch gegen die zulässigen semantischen Varianten (default, success, warning, danger).

6. Merksätze

  1. page "/pfad" definiert eine Route und liefert geprüften HTML-Code aus.
  2. Variablen im HTML werden mit {variable} sicher interpoliert und automatisch escaped.
  3. view Name { ... } definiert wiederverwendbare Master-Layouts mit <slot /> und benannten Slots (<slot name="...">).
  4. search, filter und paginated erzeugen vollautomatische, semantische Query-Steuerungen mit Zustandsbewahrung in der URL.
  5. Mit zelyra serve startest du den integrierten HTTP-Server ohne externe Webserver-Konfiguration.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Erstelle eine page "/ueber-uns", die eine Firmenbeschreibung anzeigt.
  • Stufe 2 (Mittel): Erstelle eine dynamische Route /produkt/{nummer}, die eine Produkt-Detailansicht darstellt.
  • Stufe 3 (Anspruchsvoll): Gestalte eine Übersichtsseite mit Überschrift, Navigation und Aufzählungsliste im HTML-Block.

8. Praxisaufgabe: Startseite für die Aufgabenverwaltung

Erstelle die Web-Startseite unserer Aufgabenverwaltung:

page "/tasks" {
    html {
        <html>
            <head>
                <title>Zelyra Aufgabenverwaltung</title>
            </head>
            <body>
                <h1>Meine Aufgaben</h1>
                <p>Willkommen in deiner persoenlichen Aufgabenverwaltung.</p>
                <a href="/tasks/neu">Neue Aufgabe erstellen</a>
            </body>
        </html>
    }
}

Starte den Server mit zelyra serve main.zyl und öffne http://localhost:8080/tasks im Browser!

9. Zusammenfassung

  • Webseiten werden mit page und html direkt deklariert.
  • Automatisches Escaping schützt deine Nutzer vor Sicherheitsrisiken.

10. Kontrollfragen zur Selbstprüfung

  1. Welches Schlüsselwort leitet eine Webseiten-Definition ein?
  2. Wie bindet man dynamische Werte in den HTML-Quelltext ein?
  3. Warum ist XSS bei der HTML-Ausgabe in Zelyra standardmäßig ausgeschlossen?

Kapitel 27: Formulare und Benutzereingaben

1. Was lerne ich in diesem Kapitel?

  • Wie du mit form sichere Eingabemasken für deine Datenbanktabellen definierst.
  • Wie automatische CSRF-Schutzmechanismen funktionieren.
  • Wie Zelyra Eingabedaten typisiert validiert (z. B. Email, Mindestlängen).
  • Wie du Formulare mit dem CLI-Befehl zelyra form validate vorab testen kannst.

2. Warum ist das Thema wichtig?

Eingaben von Nutzern sind die Hauptursache für Sicherheitslücken im Web: Angreifer übermitteln leere Pflichtfelder, manipulierte IDs oder nutzen fremde Browser-Sitzungen aus (CSRF-Attacken). In anderen Frameworks muss man Formulare mühsam von Hand mit Validierungsregeln, Fehleranzeigen und CSRF-Tokens zusammenbauen. Zelyras form-Konstrukt leitet die Eingabemaske direkt aus der Datenbanktabelle ab und sichert alles automatisch ab.

3. Verständliche Erklärung

Ein Formular verknüpft eine Eingabemaske mit einer Zieltabelle:

database main {
    engine: mariadb
}

table tasks {
    id: Id primary auto
    name: String(100) required
    beschreibung: String(500)
}

form TaskCreate -> tasks {
    fields {
        name
        beschreibung
    }
}

Zelyra generiert daraus:

  • Die HTML-Eingabefelder mit passenden Typen (<input type="text">, etc.).
  • Ein unsichtbares, kryptografisches CSRF-Token, das Angriffe verhindert.
  • Server-seitige Validierungsprüfungen (z. B. name darf maximal 100 Zeichen haben und nicht fehlen).

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Basis-Formular für Kundendaten

database main {
    engine: mariadb
}

table customers {
    id: Id primary auto
    name: String(80) required
    email: Email?
}

form CustomerForm -> customers {
    fields {
        name
        email
    }
}

Beispiel 2: Formular mit benutzerdefinierten Aktionen

database main {
    engine: mariadb
}

table tasks {
    id: Id primary auto
    name: String(100) required
}

form NewTaskForm -> tasks {
    fields {
        name
    }
}

5. Typische Fehler und deren Ursachen

  • Fehler: Ein Feld im Formular aufführen, das in der Zieltabelle gar nicht existiert. Ursache: Zelyra prüft fields strikt gegen die Spalten der Tabelle.
  • Fehler: CSRF-Schutz manuell deaktivieren wollen. Ursache: In Zelyra ist der CSRF-Schutz unverzichtbarer Sicherheitsstandard.

6. Merksätze

  1. form Name -> zieltabelle generiert eine sichere Eingabemaske.
  2. Alle Validierungsregeln der Tabelle (Länge, Pflichtfeld, Typ) gelten automatisch.
  3. CSRF- und XSS-Schutz sind integral eingebaut.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Definiere ein Formular KategorieErstellen für eine Tabelle kategorien.
  • Stufe 2 (Mittel): Teste das Formular auf der Kommandozeile mit zelyra form validate.
  • Stufe 3 (Anspruchsvoll): Ergänze das Aufgaben-Formular um ein Prioritätsfeld und validiere fehlerhafte Eingaben.

8. Praxisaufgabe: Das Erstellungsformular für Aufgaben

Definiere das Eingabeformular für neue Aufgaben:

database main {
    engine: mariadb
}

table tasks {
    id: Id primary auto
    name: String(100) required
    prioritaet: Int
}

form TaskCreate -> tasks {
    fields {
        name
        prioritaet
    }
}

fn main() {
    print("Aufgaben-Formular bereit.")
}

9. Zusammenfassung

  • Formulare verknüpfen Tabellen mit sicheren Web-Eingabemasken.
  • Zelyra erledigt Validierung, CSRF-Schutz und Fehlerbehandlung automatisch.

10. Kontrollfragen zur Selbstprüfung

  1. Wofür steht der Pfeil -> in form TaskCreate -> tasks?
  2. Warum müssen Entwickler in Zelyra keine manuellen CSRF-Tokens im HTML einfügen?
  3. Welche Spaltenprüfungen werden automatisch auf das Formular angewendet?

Kapitel 28: Das vollständige CRUD-Muster

1. Was lerne ich in diesem Kapitel?

  • Was CRUD (Create, Read, Update, Delete) bedeutet und warum es das Herzstück von Business-Web-Apps ist.
  • Wie Zelyra ein vollständiges Verwaltungsinterface mit nur einem crud-Block erzeugt.
  • Wie du Listen-, Detail-, Formular- und Löschansichten konfigurierst.
  • Wie benutzerdefinierte Aktionen mit action hinzugefügt werden.

2. Warum ist das Thema wichtig?

Über 80 % der Arbeit an Webanwendungen besteht aus dem immer gleichen Muster: Eine Tabelle anzeigen, Datensätze anlegen, bearbeiten und löschen. Entwickler verbringen Wochen damit, Controller, Routen, Formulare und Bestätigungsdialoge zu schreiben. In Zelyra erledigst du das in wenigen Zeilen deklarativem Code – absolut fehlerfrei, sicher und konsistent.

3. Verständliche Erklärung

Das Schlüsselwort crud fasst alle Operationen für eine Entität zusammen:

  • Create: Neue Datensätze anlegen.
  • Read: Liste durchsuchen und Details ansehen.
  • Update: Vorhandene Daten ändern.
  • Delete: Datensätze mit Sicherheitsabfrage entfernen.
database main {
    engine: mariadb
}

table tasks {
    id: Id primary auto
    name: String(100) required
    erledigt: Bool default false
}

crud Task -> tasks {
    title: "Aufgabenverwaltung"
    view {
        fields {
            name
            erledigt
        }
        list {
            mode: cards
            empty: "Keine Aufgaben vorhanden."
        }
    }
}

Automatische Detail-Verlinkung bei ausgeblendeter ID : Wenn die technische id-Spalte im Block fields nicht aufgeführt ist (wie hier, wo nur name und erledigt sichtbar sind), verlinkt Zelyra automatisch das erste angezeigte Feld (name) mit der Detailseite des Datensatzes. Dies gilt sowohl für Tabellen- (table) als auch für Kachel-Layouts (cards).

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Ein vollständiges CRUD-Modul

database main {
    engine: mariadb
}

table tasks {
    id: Id primary auto
    name: String(100) required
    aktiv: Bool default true
}

crud Task -> tasks {
    title: "Aufgaben"

    view {
        fields {
            name
            aktiv
        }

        list {
            mode: cards
            empty: "Keine Aufgaben vorhanden."
        }

        detail {
            mode: cards
            title: "Aufgabendetails"
        }

        form {
            mode: cards
            title: "Aufgabe bearbeiten"
            submit: "Speichern"
        }

        delete {
            title: "Aufgabe loeschen"
            message: "Diese Aktion kann nicht rueckgaengig gemacht werden."
            submit: "Jetzt loeschen"
        }
    }
}

Beispiel 2: Benutzerdefinierte Aktionen im CRUD-Interface Du kannst individuelle Schaltflächen hinzufügen, z. B. um eine Aufgabe sofort als erledigt zu markieren:

database main {
    engine: mariadb
}

table tasks {
    id: Id primary auto
    name: String(100) required
    erledigt: Bool default false
}

crud Task -> tasks {
    title: "Aufgaben"
    view {
        fields {
            name
            erledigt
        }
    }

    action erledigen {
        label: "Als erledigt markieren"
        confirm: "Moechtest du diese Aufgabe abschliessen?"

        sql {
            UPDATE tasks
            SET erledigt = true
            WHERE id = :id
        }

        success "Aufgabe erfolgreich abgeschlossen."
        redirect "/tasks"
    }
}

Transaktionales Verhalten und Grenzen von CRUD-Aktionen:

  • Mehrere SQL-Anweisungen in einer Transaktion: Eine Action kann innerhalb des Blocks sql { ... } mehrere SQL-Anweisungen hintereinander ausführen (z. B. einen Datensatz aktualisieren und gleichzeitig eine Protokollzeile einfügen). Zelyra bündelt alle Anweisungen in einer einzigen MariaDB-Transaktion. Schlägt ein Statement fehl, wird die gesamte Transaktion atomar zurückgerollt.
  • Automatische Parameterbindung: Der Parameter :id wird automatisch aus dem URL-Pfad der gewählten Zeile gebunden. Mit field name: Type deklarierte Eingabefelder werden dem Benutzer im Dialog angezeigt, vorab validiert und als :name an die SQL-Statements übergeben.
  • Aktueller Ausführungsumfang: Seit 1.2.28 unterstützen CRUD-Actions typisierte Handler, Zeilen- und Sitzungsdaten sowie geprüfte Guards; Aktionen laufen im vorhandenen serverseitigen Transaktionskontext. Die konkreten erlaubten Ausdrücke und Effekte richten sich nach der dokumentierten Action-Syntax, nicht nach beliebigem Zelyra-Code.

Beispiel 3: CRUD-Ressource mit wiederverwendbarem View-Layout (ab Zelyra 0.1.43) Mit layout: ViewName bettest du alle generierten CRUD-Ansichten (Listen-, Detail-, Formular- und Löschdialoge) automatisch in ein zuvor deklariertes Seitenlayout ein:

view AppShell {
    html {
        <html lang="de">
            <body>
                <nav><a href="/">Start</a> | <a href="/tasks">Aufgaben</a></nav>
                <main>
                    <slot />
                </main>
            </body>
        </html>
    }
}

crud Task -> tasks {
    title: "Aufgabenverwaltung"
    layout: AppShell

    view {
        fields {
            name
            erledigt
        }
    }
}

Die generierte CRUD-Ressource übernimmt das Navigationsgerüst von AppShell, während alle Sicherheitsprüfungen, Rollen und CSRF-Tokens voll aktiv bleiben.

5. Typische Fehler und deren Ursachen

  • Fehler: Den view-Block oder die fields-Deklaration im CRUD weglassen. Ursache: Zelyra muss wissen, welche Spalten in den generierten Ansichten angezeigt werden sollen.
  • Fehler: Ein UPDATE in einer action ohne WHERE id = :id ausführen. Ursache: Aktionen beziehen sich immer auf den aktuell ausgewählten Datensatz mit :id.

6. Merksätze

  1. crud Name -> tabelle generiert eine vollständige, sichere Verwaltungsoberfläche.
  2. Ansichten (list, detail, form, delete) lassen sich flexibel anpassen.
  3. Benutzerdefinierte Aktionen (action) erweitern den Standard um individuelle Geschäftsregeln.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Erstelle ein CRUD-Interface für eine Tabelle kategorien.
  • Stufe 2 (Mittel): Passe die Hinweistexte und Titel der Ansichten an.
  • Stufe 3 (Anspruchsvoll): Implementiere eine benutzerdefinierte Aktion duplizieren, die eine Kopie der ausgewählten Aufgabe anlegt.

8. Praxisaufgabe: Das produktive CRUD-Interface unserer Aufgabenverwaltung

Kombiniere Datenbank, Tabelle und CRUD zu einem vollständigen System:

database main {
    engine: mariadb
    database: "tasks_app"
}

table tasks {
    id: Id primary auto
    name: String(120) required
    erledigt: Bool default false
}

crud Task -> tasks {
    title: "Meine Aufgabenverwaltung"

    view {
        fields {
            name
            erledigt
        }

        list {
            mode: cards
            empty: "Grossartig! Alle Aufgaben sind erledigt."
        }

        detail {
            mode: cards
            title: "Aufgabe ansehen"
        }

        form {
            mode: cards
            title: "Aufgabe erfassen oder bearbeiten"
            submit: "Aufgabe sichern"
        }

        delete {
            title: "Aufgabe loeschen"
            message: "Soll diese Aufgabe unwiderruflich entfernt werden?"
            submit: "Loeschen"
        }
    }
}

9. Zusammenfassung

  • Das CRUD-Muster beschleunigt die Entwicklung von Datenbank-Web-Apps dramatisch.
  • Zelyra generiert fehlerfreie Routen, HTML-Masken und Datenbankaufrufe vollautomatisch.

10. Kontrollfragen zur Selbstprüfung

  1. Welche vier Grundfunktionen umfasst die Abkürzung CRUD?
  2. Wie reagiert Zelyra, wenn in fields ein ungültiger Spaltenname steht?
  3. Welche Aufgabe erfüllt das Schlüsselwort action in einem CRUD-Block?

Kapitel 29: Benutzer, Passwörter und Sitzungen

1. Was lerne ich in diesem Kapitel?

  • Wie Zelyra Benutzerauthentifizierung mit dem auth-Block nativ bereitstellt.
  • Wie Passwörter mit dem modernen Argon2-Algorithmus gehasht werden.
  • Wie Sitzungen (Sessions) und Rollenrechte abgesichert werden.
  • Wie Seiten und Aktionen mit requires auth und permits geschützt werden.

2. Warum ist das Thema wichtig?

Sicherheit ist kein nachträgliches Add-on, das man am Tag vor dem Go-Live noch schnell drüberpinselt. Passwörter im Klartext oder mit antiken Algorithmen (wie MD5 oder SHA1) zu speichern, ist wie der Hausschlüssel unter der Fußmatte mit einem beleuchteten Pfeil darauf. In Zelyra ist Authentifizierung fest in der Spracharchitektur verankert: Passwörter werden standardmäßig mit speicherhartem Argon2 gehasht, Sitzungs-Tokens werden kryptografisch geschützt und Routenrechte werden deklarativ geprüft.

3. Verständliche Erklärung

Ein Authentifizierungsblock verbindet Benutzer, Sitzungen und Berechtigungen:

auth users {
    table: users
    sessions: auth_sessions
    permissions: user_permissions
}

table users {
    id: Id primary auto
    email: Email required unique
    password_hash: String(255) required
}

table auth_sessions {
    id: Id primary auto
    user: User required
    token_hash: String(64) required unique
    expires_at: Timestamp required
}

table user_permissions {
    id: Id primary auto
    user: User required
    permission: String(100) required
}

Um eine Webroute nur für angemeldete Nutzer freizugeben, schreibst du einfach:

auth users {
    table: users
    sessions: auth_sessions
    permissions: user_permissions
}

table users {
    id: Id primary auto
    email: Email required unique
    password_hash: String(255) required
}

table auth_sessions {
    id: Id primary auto
    user: User required
    token_hash: String(64) required unique
    expires_at: Timestamp required
}

table user_permissions {
    id: Id primary auto
    user: User required
    permission: String(100) required
}

page "/geheim" {
    requires auth
    html {
        <h1>Nur fuer angemeldete Nutzer sichtbar!</h1>
    }
}

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Die Standardtabellen für Authentifizierung

auth users {
    table: users
    sessions: auth_sessions
    permissions: user_permissions
}

table users {
    id: Id primary auto
    email: Email required unique
    password_hash: String(255) required
    active: Bool default true
}

table auth_sessions {
    id: Id primary auto
    user: User required
    token_hash: String(64) required unique
    expires_at: Timestamp required
}

table user_permissions {
    id: Id primary auto
    user: User required
    permission: String(100) required
}

Beispiel 2: Geschützte Seite mit Rechteprüfung

auth users {
    table: users
    sessions: auth_sessions
    permissions: user_permissions
}

table users {
    id: Id primary auto
    email: Email required unique
    password_hash: String(255) required
    active: Bool default true
}

table auth_sessions {
    id: Id primary auto
    user: User required
    token_hash: String(64) required unique
    expires_at: Timestamp required
}

table user_permissions {
    id: Id primary auto
    user: User required
    permission: String(100) required
}

page "/dashboard" {
    requires auth
    permits "tasks.view"

    html {
        <h1>Aufgaben-Dashboard</h1>
        <p>Du bist autorisiert.</p>
    }
}

5. Typische Fehler und deren Ursachen

  • Fehler: Passwörter im Klartext in der Tabelle ablegen. Ursache: Zelyra verlangt ein Spaltenfeld password_hash und stellt mit zelyra auth hash-password ein Hashing-Tool bereit.
  • Fehler: Die Sitzungstabelle auth_sessions vergessen. Ursache: Zelyra benötigt eine dedizierte Tabelle zur sicheren Token-Verwaltung.

6. Merksätze

  1. auth deklariert Benutzer, Sitzungen und Berechtigungen an einer zentralen Stelle.
  2. Geschützte Seiten erfordern requires auth und optional permits "recht".
  3. Passwörter werden ausschließlich als sichere Argon2-Hashes gespeichert.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Erzeuge ein Passwort-Hash über den CLI-Befehl zelyra auth hash-password.
  • Stufe 2 (Mittel): Schütze eine Seite /einstellungen mit requires auth.
  • Stufe 3 (Anspruchsvoll): Vergib einer Rolle ein Berechtigungsrecht über zelyra auth role-permission.

8. Praxisaufgabe: Geschützte Aufgabenverwaltung

Schütze die Aufgabenverwaltung vor unbefugtem Zugriff:

auth users {
    table: users
    sessions: auth_sessions
    permissions: user_permissions
}

table users {
    id: Id primary auto
    email: Email required unique
    password_hash: String(255) required
    active: Bool default true
}

table auth_sessions {
    id: Id primary auto
    user: User required
    token_hash: String(64) required unique
    expires_at: Timestamp required
}

table user_permissions {
    id: Id primary auto
    user: User required
    permission: String(100) required
}

page "/meine-aufgaben" {
    requires auth

    html {
        <h1>Geschuetzter Aufgabenbereich</h1>
        <p>Nur fuer authentifizierte Benutzer zugaenglich.</p>
    }
}

9. Zusammenfassung

  • Authentifizierung und Autorisierung sind vollwertige Bestandteile von Zelyra.
  • Moderne Sicherheitsstandards (Argon2, Session-Tokens, RBAC) sind ohne Drittanbieter-Bibliotheken einsatzbereit.

10. Kontrollfragen zur Selbstprüfung

  1. Welcher moderne Hash-Algorithmus wird von Zelyra für Passwörter verwendet?
  2. Mit welchem Befehl wird eine Webroute für unbefugte Besucher gesperrt?
  3. Wozu dient die Tabelle auth_sessions?

Kapitel 30: APIs und Datenaustausch

1. Was lerne ich in diesem Kapitel?

  • Wie du typisierte REST-Schnittstellen mit dem Schlüsselwort api definierst.
  • Wie HTTP-Methoden (GET, POST, PUT, DELETE) und Eingabe/Ausgabe typisiert werden.
  • Wie automatische Fehler-Codes (404 NotFound, 400 ValidationError) deklariert werden.
  • Wie Zelyra vollständige OpenAPI/Swagger-Dokumentationen auf Knopfdruck generiert (zelyra doc --openapi).

2. Warum ist das Thema wichtig?

Moderne Software lebt nicht isoliert: Mobile Apps (iOS/Android), Frontend-Frameworks (Vue, React) oder Partnersysteme müssen mit deinem Backend kommunizieren. Bei herkömmlichen APIs veraltet die Dokumentation oft schon am Tag nach dem Release. In Zelyra ist die API-Definition der Code selbst: Jede Route, jedes Eingabefeld und jeder Fehlercode ist exakt typisiert – und die OpenAPI-Spezifikation wird daraus vollautomatisch abgeleitet.

3. Verständliche Erklärung

Mit dem Schlüsselwort api deklarierst du einen Web-Endpunkt:

  • Methode und Pfad: z. B. GET "/tasks/{id}"
  • Input: Welche Parameter erwartet die API?
  • Output: Welcher Typ wird als JSON zurückgegeben?
  • Errors: Welche HTTP-Statuscodes können im Fehlerfall auftreten?
type TaskId = Id

table tasks {
    id: TaskId primary auto
    name: String required
}

api GET "/api/tasks/{id}" {
    input {
        id: TaskId
    }
    output Task
    errors {
        404 NotFound
    }
}

fn main() {
    print("API-Endpunkt definiert.")
}

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Ein GET-Endpunkt mit Rückgabetyp

type TaskId = Id

table tasks {
    id: TaskId primary auto
    name: String required
}

api GET "/tasks/{id}" {
    input {
        id: TaskId
    }
    output Task
    errors {
        404 NotFound
    }
}

fn main() {
    print("GET API geprueft.")
}

Beispiel 2: Ein POST-Endpunkt zum Anlegen von Daten

type TaskId = Id

table tasks {
    id: TaskId primary auto
    name: String(100) required
}

api POST "/tasks" {
    input {
        name: String
    }
    output Task
    errors {
        400 ValidationError
    }
}

fn main() {
    print("POST API geprueft.")
}

Beispiel 3: OpenAPI-Dokumentation erzeugen Mit dem Befehl:

zelyra doc main.zyl --openapi

generiert Zelyra eine normgerechte openapi.json, die du direkt in Swagger-UI oder Postman importieren kannst.

5. Typische Fehler und deren Ursachen

  • Fehler: Den Ausgabetyp output mit einem unbekannten Typen belegen. Ursache: Zelyra prüft, ob der Datentyp (z. B. Task) als Tabelle oder Record existiert.
  • Fehler: Nicht deklarierte HTTP-Fehlercodes zurücksenden wollen. Ursache: Deklarierte Schnittstellen fordern vollständige Spezifikation aller Statuscodes.

6. Merksätze

  1. api METHOD "/pfad" deklariert eine typsichere REST-Schnittstelle.
  2. input, output und errors beschreiben den Datenvertrag der Schnittstelle lückenlos.
  3. zelyra doc --openapi erzeugt OpenAPI-Spezifikationen direkt aus dem Quelltext.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Definiere einen Endpunkt GET "/api/version", der einen Versions-String liefert.
  • Stufe 2 (Mittel): Erstelle einen Endpunkt DELETE "/api/tasks/{id}" mit Fehlercode 404 NotFound.
  • Stufe 3 (Anspruchsvoll): Generiere mit zelyra doc --openapi eine API-Dokumentation und untersuche die JSON-Ausgabe.

8. Praxisaufgabe: Die REST-API für unsere Aufgabenverwaltung

Definiere die öffentliche Programmierschnittstelle unserer Aufgabenverwaltung:

type TaskId = Id

table tasks {
    id: TaskId primary auto
    name: String(120) required
    erledigt: Bool
}

api GET "/api/tasks/{id}" {
    input {
        id: TaskId
    }
    output Task
    errors {
        404 NotFound
    }
}

api POST "/api/tasks" {
    input {
        name: String
    }
    output Task
    errors {
        400 ValidationError
    }
}

fn main() {
    print("Aufgaben REST-API einsatzbereit.")
}

9. Zusammenfassung

  • APIs in Zelyra sind typsicher, selbstdokumentierend und standardkonform.
  • Mit minimalem Aufwand entstehen robuste Endpunkte für Web- und Mobilanwendungen.

10. Kontrollfragen zur Selbstprüfung

  1. Welche vier Abschnitte umfasst eine vollständige api-Deklaration?
  2. Warum ist die automatische OpenAPI-Generierung gegenüber manuellen Dokumentationen überlegen?
  3. Was geschieht, wenn ein Client ungültige Daten an einen input-Block sendet?

TEIL VIII – DIE BESONDERHEITEN VON ZELYRA


Kapitel 31: Lesbarkeit als oberstes Gebot

1. Was lerne ich in diesem Kapitel?

In diesem Kapitel lernst du:

  • Warum Code in der Realität bis zu zehnmal öfter gelesen als geschrieben wird.
  • Welche Designentscheidungen Zelyra bewusst getroffen hat, um maximale Klarheit zu schaffen.
  • Warum Zelyra auf unlesbare Syntaxakrobatik und kryptische Symbole verzichtet.
  • Wie der integrierte Code-Formatierer zelyra fmt für einen einheitlichen Standard sorgt.

2. Warum ist das Thema wichtig?

Viele Programmiersprachen erlauben es, denselben Sachverhalt auf zehn verschiedene Arten auszudrücken – oft in ultrakompakten Einzeilern mit Sonderzeichen, die nach drei Monaten niemand mehr versteht. Schreibe deinen Code immer so, als wäre die Person, die ihn in sechs Monaten warten muss, ein ungeduldiger Kollege, der die Kaffeemaschine kontrolliert und weiß, wo du sitzt. In großen Teams und langfristigen Projekten führt kryptischer Code zu enormen Wartungskosten. Zelyra folgt dem Grundsatz: Es gibt genau einen offensichtlichen, lesbaren Weg, eine Aufgabe zu lösen.

3. Verständliche Erklärung

Lesbarkeit in Zelyra bedeutet:

  • Ausdrückliche Namen statt Abkürzungen: Funktionen und Variablen sprechen Klartext (prioritaet statt prio_lvl_fn()).
  • Klare Blöcke: Jede Bedingung, Schleife und Funktion besitzt eindeutige geschweifte Klammern.
  • Automatische Formatierung: Niemand im Team muss über Einrückungen oder Leerzeichen diskutieren. Der Befehl zelyra fmt rückt jede Zeile exakt nach dem einheitlichen Zelyra-Standard ein.
fn berechne_gesamtzeit(aufgaben_dauern: Int[]) -> Int {
    mutable summe = 0
    for dauer in aufgaben_dauern {
        summe = summe + dauer
    }
    return summe
}

fn main() {
    zeiten: Int[] = [15, 30, 45]
    print(berechne_gesamtzeit(zeiten))
}

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Selbsterklärende Funktionssignaturen

fn ist_aufgabe_ueberfaellig(frist_tage: Int) -> Bool {
    return frist_tage < 0
}

fn main() {
    print(ist_aufgabe_ueberfaellig(-2))
}

Beispiel 2: Verständliche Kontrollstrukturen

fn status_anzeige(status_code: Int) -> String {
    match status_code {
        1 => {
            return "Neu"
        }
        2 => {
            return "In Bearbeitung"
        }
        3 => {
            return "Erledigt"
        }
        _ => {
            return "Unbekannt"
        }
    }
}

fn main() {
    print(status_anzeige(2))
}

5. Typische Fehler und deren Ursachen

  • Fehler: Variablen mit einzelnen Buchstaben (a, x, tmp) benennen, deren Bedeutung unklar ist. Ursache: Zelyra-Code soll wie verständliche Prosa gelesen werden können.
  • Fehler: Uneinheitliche Einrückungen manuell korrigieren. Ursache: Führe einfach zelyra fmt main.zyl aus – der Compiler formatiert alles automatisch.

6. Merksätze

  1. Schreibe Code für den Menschen, der ihn in sechs Monaten warten muss.
  2. zelyra fmt garantiert einen einheitlichen, lesbaren Programmierstil im gesamten Projekt.
  3. Klare, sprechende Bezeichner sind die beste Dokumentation.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Formatiere eine unordentliche Quellcodedatei mit zelyra fmt.
  • Stufe 2 (Mittel): Refaktoriere eine Funktion mit unklaren Variablennamen zu sprechendem Zelyra-Code.
  • Stufe 3 (Anspruchsvoll): Schreibe eine komplexe Berechnungsfunktion so sauber gegliedert, dass keine einzige Zeile mehr als 80 Zeichen benötigt.

8. Praxisaufgabe: Lesbare Status- und Filterlogik

Gestalte die Filterlogik unserer Aufgabenverwaltung maximal verständlich:

fn ist_dringend_und_offen(prioritaet: Int, erledigt: Bool) -> Bool {
    ist_prioritaet_eins = prioritaet == 1
    ist_noch_nicht_erledigt = !erledigt
    return ist_prioritaet_eins && ist_noch_nicht_erledigt
}

fn main() {
    print(ist_dringend_und_offen(1, false))
    print(ist_dringend_und_offen(2, false))
}

9. Zusammenfassung

  • Lesbarkeit ist der wichtigste Schutzfaktor gegen schleichende Software-Fäulnis.
  • Zelyra erzwingt Klarheit durch Sprachdesign und Werkzeuge.

10. Kontrollfragen zur Selbstprüfung

  1. Warum spart gut lesbarer Code auf lange Sicht Zeit und Geld?
  2. Welcher Zelyra-CLI-Befehl formatiert Quelldateien automatisch?
  3. Warum verzichtet Zelyra auf übermäßig viele alternative Schreibweisen für dieselbe Logik?

Kapitel 32: KI-Nativität – Warum Zelyra perfekt für KI-Assistenten ist

1. Was lerne ich in diesem Kapitel?

  • Was eine „KI-native Programmiersprache“ bedeutet.
  • Warum moderne LLMs (wie Claude, GPT, Gemini) bei Zelyra weniger halluzinieren.
  • Wie typisierte Löcher (Typed Holes _) Entwicklern und KIs bei der Codegenerierung helfen.
  • Wie CLI-Befehle mit --format json strukturierte Schnittstellen für Werkzeuge bieten.
  • Wie zelyra impact und zelyra edit automatisierte Code-Änderungen absichern.

2. Warum ist das Thema wichtig?

Die meisten Programmiersprachen wurden vor Jahrzehnten entwickelt – ausschließlich für menschliche Tastatureingaben. Wenn moderne KI-Assistenten Code in Python oder JavaScript schreiben, wirken sie manchmal wie geniale Praktikanten nach fünf Dosen Energydrink: Sie tippen rasend schnell, erfinden aber gelegentlich Methoden, verwechseln Typen oder übersehen Seiteneffekte. Zelyra wurde von Grund auf so entworfen, dass menschliche Entwickler und KI-Assistenten optimal zusammenarbeiten können: Der Compiler liefert strukturierte JSON-Diagnosen und mit Typed Holes _ exakte Lückentexte.

3. Verständliche Erklärung

Zelyra unterstützt KI-Entwicklung durch vier Schlüsselmerkmale:

  1. Eindeutige, kontextfreie Grammatik: Die Sprache hat keine Mehrdeutigkeiten.
  2. Maschinenlesbare JSON-Ausgabe: Fast alle Befehle bieten --format json (z. B. zelyra check --format json), sodass KIs Fehlermeldungen direkt als strukturierte Daten erfassen.
  3. Typed Holes (_): Wenn du oder die KI nicht genau wissen, wie ein Wert berechnet wird, setzt man einen Unterstrich _ ein. Der Compiler meldet sofort exakt: Welcher Typ wird erwartet? Welche Variablen sind verfügbar? Welche Verträge gelten?
  4. Auswirkungsanalyse (zelyra impact): Zelyra berechnet vorab genau, welche Programmteile von einer Änderung betroffen sind.

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Typisierte Verträge leiten die KI fehlerfrei Durch Verträge weiß die KI ohne Raten, welche Randbedingungen gelten:

fn normalisiere_skala(wert: Int) -> Int
    requires { wert >= 0 && wert <= 100 }
    ensures { result >= 0 && result <= 10 }
{
    return wert / 10
}

fn main() {
    print(normalisiere_skala(85))
}

Beispiel 2: Maschinenlesbare Fehleranalyse Führt ein KI-Tool den Befehl:

zelyra check main.zyl --format json

aus, erhält es präzise JSON-Objekte mit exaktem Fehlercode, Zeile, Spalte und Lösungshinweis.

5. Typische Fehler und deren Ursachen

  • Fehler: Typed Holes (_) in Produktionscode belassen. Ursache: Typed Holes sind Entwicklungshilfen. Vor dem finalen Kompilieren müssen alle _ durch gültigen Code ersetzt werden.
  • Fehler: KI-Code ohne zelyra check ungeprüft übernehmen. Ursache: Nutze immer den Zelyra-Compiler als unbestechlichen Richter.

6. Merksätze

  1. Zelyra ist die erste KI-native Sprache: Eindeutig, strukturiert und werkzeugfreundlich.
  2. Typed Holes _ dienen als präzise Arbeitsaufträge an den Compiler und KI-Assistenten.
  3. --format json ermöglicht nahtlose Integration in moderne KI-Agenten und IDEs.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Führe zelyra check mit der Option --format json aus und studiere die Ausgabe.
  • Stufe 2 (Mittel): Analysiere eine Datei mit zelyra impact.
  • Stufe 3 (Anspruchsvoll): Setze in einer Funktion ein Typed Hole _ ein und beobachte die detaillierten Kontextinformationen des Compilers.

8. Praxisaufgabe: KI-unterstützte Erweiterung der Aufgabenverwaltung

Schreibe eine wohlstrukturierte Funktionssignatur mit Vertrag, die sich perfekt von einer KI vervollständigen lässt:

fn berechne_restzeit(ziel_stunde: Int, aktuelle_stunde: Int) -> Int
    requires { ziel_stunde >= aktuelle_stunde }
    ensures { result >= 0 }
{
    return ziel_stunde - aktuelle_stunde
}

fn main() {
    rest = berechne_restzeit(18, 14)
    print(rest)
}

9. Zusammenfassung

  • Zelyra beseitigt Sprach-Mehrdeutigkeiten, die KI-Systeme traditionell verwirren.
  • Typed Holes und strukturierte JSON-Ausgaben machen Pair-Programming mit KIs extrem zuverlässig.

10. Kontrollfragen zur Selbstprüfung

  1. Wofür steht das Konzept der „Typed Holes“ in Zelyra?
  2. Warum profitieren KI-Systeme von der --format json-Option des Compilers?
  3. Wie helfen Verträge (requires, ensures) einer KI beim Erzeugen von korrektem Code?

Kapitel 33: Sicherheit durch Fähigkeiten (Capabilities)

1. Was lerne ich in diesem Kapitel?

  • Was das Capability-Sicherheitsmodell ist und warum es herkömmlichen Berechtigungskonzepten überlegen ist.
  • System-Capabilities wie Console, Database, Network, FileSystem, Process, Environment, Clock und Random.
  • Wie Capabilities deklariert, vererbt und in zelyra.toml beschränkt werden.
  • Warum Zelyra gegen Supply-Chain-Angriffe (bösartige Pakete) immun ist.

2. Warum ist das Thema wichtig?

In heutigen Ökosystemen bindet man oft hunderte Bibliotheken von Drittanbietern ein. Wenn ein winziges Paket manipuliert wird, kann es unbemerkt Passwörter auslesen, Festplatten verschlüsseln oder Daten ins Internet funken (Supply-Chain-Angriffe). Capabilities sind wie ein elektronischer Dienstausweis: Wer nur einen Chip für den Aufenthaltsraum besitzt, kann nicht nachts heimlich den Serverraum betreten. In Zelyra kann eine Funktion ohne ausdrückliche Deklaration von uses Network oder uses FileSystem technisch kein einziges Byte nach außen senden – der Compiler verweigert den Dienst!

3. Verständliche Erklärung

Stell dir Zelyras Capability-System wie ein Sicherheitsschloss vor:

  • Wenn eine Funktion auf die Festplatte schreiben will, muss sie den Schlüssel uses FileSystem am Revers tragen.
  • Hat sie diesen Schlüssel nicht, kann sie keine Dateien anrühren – selbst wenn sie es versucht.
  • Und das Beste: Wenn Funktion A die Funktion B aufruft, muss auch A die Berechtigung deklarieren. So siehst du auf den ersten Blick in main(), was das gesamte Programm überhaupt darf!
fn sichere_operation() {
    # Diese Funktion hat KEINE Capabilities.
    # Sie kann unmoeglich Schaden auf der Festplatte oder im Netzwerk anrichten!
    print("Garantiert seiteneffektfrei.")
}

fn main() {
    sichere_operation()
}

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Saubere Deklaration von Umgebungsvariablen

fn lese_konfiguration(schluessel: String) -> Option<String>
    uses Environment
{
    return env(schluessel)
}

fn main() uses Environment {
    print("Konfigurationszugriff erlaubt.")
}

Beispiel 2: Datenbankzugriff mit Capability

database main {
    engine: mariadb
    database: "tasks_db"
}

table tasks {
    id: Id primary auto
    name: String required
}

fn lade_daten() uses Database {
    daten = sql<Task[]> {
        SELECT id, name
        FROM tasks
    }
    print("Datenbankzugriff gewaehrt.")
}

fn main() uses Database {
    lade_daten()
}

5. Typische Fehler und deren Ursachen

  • Fehler: Vergessen, Capabilities an den Aufrufer weiterzureichen. Ursache: Wenn Funktion a() die Funktion b() uses FileSystem aufruft, muss auch a() mit uses FileSystem versehen sein.
  • Fehler: Fehlende Freigabe in zelyra.toml. Ursache: Das Projekt manifestiert seine maximalen Rechte in der Konfiguration.

6. Merksätze

  1. Keine Funktion kann heimlich auf Dateien, Datenbanken oder das Netzwerk zugreifen.
  2. Alle Seiteneffekte sind transparent in der Funktionssignatur dokumentiert.
  3. Reine Funktionen ohne Capabilities sind garantiert manipulationssicher.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Identifiziere in einem bestehenden Code alle Funktionen mit Capabilities.
  • Stufe 2 (Mittel): Schreibe eine Funktion, die uses Clock und uses Environment kombiniert.
  • Stufe 3 (Anspruchsvoll): Entwirf ein Programm so, dass alle Geschäftsrechenlogik in Funktionen völlig ohne Capabilities ausgelagert wird.

8. Praxisaufgabe: Berechtigungs-Architektur der Aufgabenverwaltung

Isoliere die Berechtigungen in unserer Aufgabenverwaltung:

# 1. Reine Logik: KEINE Berechtigungen noetig
fn ist_bereit_fuer_export(anzahl_aufgaben: Int) -> Bool {
    return anzahl_aufgaben > 0
}

# 2. I/O-Logik: Explizite Dateisystem-Berechtigung
fn fuehre_export_durch(datei: String, inhalt: String) uses FileSystem {
    write_text(datei, inhalt)
    print("Export vollzogen.")
}

fn main() uses FileSystem {
    if ist_bereit_fuer_export(5) {
        fuehre_export_durch("aufgaben.txt", "Aufgabe 1")
    }
}

9. Zusammenfassung

  • Das Capability-Modell von Zelyra schützt vor bösartigen Bibliotheken und unkontrollierten Seiteneffekten.
  • Software wird durch explizite Rechtevergabe von Grund auf sicher (Secure by Design).

10. Kontrollfragen zur Selbstprüfung

  1. Nenne drei System-Capabilities von Zelyra.
  2. Warum müssen auch übergeordnete Aufrufer-Funktionen Capabilities deklarieren?
  3. Wie schützt Zelyra vor schadhaftem Fremdcode aus Paketmanagern?

Kapitel 34: Zelyra im Vergleich

1. Was lerne ich in diesem Kapitel?

  • Wie sich Zelyra im direkten Vergleich zu Python, PHP/Laravel, Rust und TypeScript schlägt.
  • Welche Stärken die jeweiligen Sprachen haben und warum Zelyra für moderne Web- & KI-Systeme maßgeschneidert wurde.
  • Warum Zelyra die Typsicherheit von Rust mit der Entwicklungsgeschwindigkeit von Python verbindet.

2. Warum ist das Thema wichtig?

Keine Programmiersprache ist für jeden Zweck perfekt: C und Rust sind unschlagbar für Betriebssystemkerne, Python dominiert die Datenwissenschaft, JavaScript das Frontend. Wer aber moderne, datenbankgestützte Geschäftsanwendungen und Web-Backends bauen will, kämpft in diesen Sprachen oft mit historischem Ballast. Zelyra vereint das Beste aus diesen Welten.

3. Verständliche Erklärung: Der Sprachvergleich

Merkmal Python PHP / Laravel TypeScript / Node Rust Zelyra
Typisierung Dynamisch Dynamisch/Optional Statisch (wird zu JS) Statisch (sehr streng) Statisch & Eindeutig
Null-Sicherheit None-Crashes möglich null-Crashes möglich undefined-Crashes Absolut (Option) Absolut (Option)
SQL-Integration ORM (Strings) ORM (Eloquent) ORM (Prisma/TypeORM) Diesel/SQLx Nativ & Typgeprüft
Verträge (Contracts) Nein (nur assert) Nein Nein Dritt-Bibliotheken Eingebaut (requires)
Sicherheits-Capabilities Nein (Vollzugriff) Nein (Vollzugriff) Nein (Vollzugriff) Nein Eingebaut (uses ...)
KI-Werkzeugunterstützung Mittel (Mehrdeutig) Mittel Mittel Schwer für KIs Nativ (JSON, Typed Holes)

4. Kleine, aufeinander aufbauende Beispiele

Vergleich: Wie Zelyra Fehler verhindert, die in anderen Sprachen passieren

In Python/JS (potenzieller Laufzeitabsturz bei null): Ein unbemerkter fehlender Wert führt zum Servercrash: AttributeError: 'NoneType' object has no attribute 'title'.

In Zelyra (garantiert abgefangen zur Compilezeit):

fn zeige_titel(opt_titel: Option<String>) {
    match opt_titel {
        Some(t) => {
            print("Titel: " + t)
        }
        None => {
            print("Kein Titel vorhanden.")
        }
    }
}

fn main() {
    zeige_titel(Some("Projekt X"))
    zeige_titel(None)
}

5. Typische Fehler und deren Ursachen

  • Fehler: Zelyra wie Python schreiben wollen (Einrückung statt Klammern, dynamische Typänderung). Ursache: Zelyra nutzt geschweifte Klammern und verlangt statische Typstabilität.
  • Fehler: Zelyra mit Low-Level-Rust verwechseln (komplexe Lifetime-Annotationen suchen). Ursache: Zelyra nimmt Entwicklern die Speicherverwaltung vollautomatisch ab.

6. Merksätze

  1. Zelyra verbindet einen zugänglichen Entwicklungsablauf mit einem eigenen statischen Typsystem und Compilerprüfungen. Welche Probleme erkannt werden, hängt vom aktuellen Prüfumfang ab.
  2. Datenbanken, Webformulare und Schnittstellen sind native Sprachbausteine statt externer Bibliotheken.
  3. Sicherheit und Korrektheit werden nicht nachträglich hineingetestet, sondern von Anfang an erzwungen.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Übersetze eine einfache Python-Berechnungsfunktion in sauberen Zelyra-Code.
  • Stufe 2 (Mittel): Vergleiche eine Datenbankabfrage in PHP/Laravel Eloquent mit Zelyras sql<T[]>.
  • Stufe 3 (Anspruchsvoll): Diskutiere anhand eines Praxisbeispiels, warum Zelyras Capabilities Supply-Chain-Angriffe verhindern.

8. Praxisaufgabe: Die Aufgabenverwaltung als Zelyra-Vorzeigeprojekt

Führe alle Kernstärken in einem prägnanten Ausschnitt zusammen:

database main {
    engine: mariadb
    database: "tasks_demo"
}

table tasks {
    id: Id primary auto
    name: String(100) required
    erledigt: Bool default false
}

fn zaehle_offene() -> Int uses Database {
    offene = sql<Task[]> {
        SELECT id, name, erledigt
        FROM tasks
        WHERE erledigt = false
    }
    return len(offene)
}

fn main() uses Database {
    anzahl = zaehle_offene()
    print("Offene Aufgaben ermittelt.")
}

9. Zusammenfassung

  • Zelyra ist eine eigenständige kompilierte Sprache. Vergleiche mit Skript- oder Systemsprachen sollten konkrete Eigenschaften und Einsatzbereiche benennen.
  • Für moderne Web-, Daten- und KI-Anwendungen bietet Zelyra eine unübertroffen robuste Plattform.

10. Kontrollfragen zur Selbstprüfung

  1. Welchen Vorteil bietet Zelyras Option-Typ gegenüber Pythons None oder JavaScripts null?
  2. Warum ist die native Datenbankintegration in Zelyra sicherer als traditionelle ORM-Bibliotheken?
  3. Welche Rolle spielen Capabilities beim Schutz vor bösartigen Fremdpaketen?

TEIL IX – VOM ENTWURF ZUR FERTIGEN ANWENDUNG


Kapitel 35: Software planen – Von der Idee zum Entwurf

1. Was lerne ich in diesem Kapitel?

In diesem Kapitel lernst du:

  • Wie du von einer vagen Idee zu einem präzisen, umsetzbaren Software-Entwurf gelangst.
  • Wie man Entitäten und deren Beziehungen auf Papier oder im Editor skizziert.
  • Warum die frühe Definition des Schemas in Zelyra den gesamten weiteren Entwicklungsverlauf vereinfacht.
  • Wie man Anforderungen in kleine, testbare Meilensteine zerlegt.

2. Warum ist das Thema wichtig?

Der größte Fehler von Anfängern (und unvorsichtigen Profis) ist es, sofort loszutippen, ohne den Datenfluss zu planen. Wer während des Programmierens merkt, dass ein zentrales Tabellenfeld oder eine Beziehung fehlt, muss oft Tage damit verbringen, bestehenden Code mühsam umzuschreiben. Zelyra belohnt gründliche Planung: Sobald dein table-Schema steht, leiten sich Formulare, Validierungen und APIs fast wie von selbst ab.

3. Verständliche Erklärung

Jedes gute Softwareprojekt durchläuft vier Planungsphasen:

  1. Zweck und Zielgruppe klären: Wer nutzt das System? Welche Kernaufgabe muss gelöst werden? (z. B. „Ein Teamleiter möchte Aufgaben anlegen, zuweisen und als erledigt markieren“).
  2. Datenmodell entwerfen: Welche Objekte gibt es? Welche Felder sind Pflicht? (z. B. tasks mit name, prioritaet, ist_erledigt).
  3. Sicherheits- und Zugriffsregeln: Wer darf was tun? Benötigen wir Authentifizierung?
  4. Schrittweise Implementierung: Erst das Schema (table), dann die Logik (fn), dann die Webansichten (crud/page).

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Der erste Meilenstein – Das Datenmodell

database main {
    engine: mariadb
    database: "aufgaben_planer"
}

table tasks {
    id: Id primary auto
    name: String(100) required
    prioritaet: Int default 2
    erledigt: Bool default false
}

fn main() {
    print("Planungsschritt 1: Schema steht.")
}

Beispiel 2: Planung der Geschäftslogik als reine Funktionen

fn validiere_frist(tage: Int) -> Bool {
    return tage >= 0
}

fn main() {
    print(validiere_frist(3))
}

5. Typische Fehler und deren Ursachen

  • Fehler: Alles auf einmal programmieren wollen, bevor die Grundlagen getestet sind. Ursache: Baue Software schrittweise: Teste jede Funktion sofort mit zelyra check.
  • Fehler: Unklare Pflichtfelder im Datenmodell. Ursache: Lege von Anfang an fest, welche Felder required sind und welche leer sein dürfen (Option).

6. Merksätze

  1. Wer die Planung vernachlässigt, plant das Scheitern.
  2. Ein klares Datenmodell ist das Rückgrat jeder erfolgreichen Anwendung.
  3. Zerlege große Probleme in kleine, unabhängig überprüfbare Funktionen.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Skizziere den Funktionsumfang einer einfachen Notiz-App in Stichpunkten.
  • Stufe 2 (Mittel): Entwirf ein Tabellenschema für Benutzer, Aufgaben und Kategorien mit passenden Datentypen.
  • Stufe 3 (Anspruchsvoll): Formuliere für alle Kernfunktionen deiner geplanten App Vor- und Nachbedingungen (requires, ensures).

8. Praxisaufgabe: Der vollständige Architekturplan unserer Aufgabenverwaltung

Führe alle Planungselemente der Aufgabenverwaltung zusammen:

database main {
    engine: mariadb
    database: "tasks_pro"
}

table tasks {
    id: Id primary auto
    name: String(120) required
    prioritaet: Int default 1
    erledigt: Bool default false
}

fn berechne_dringlichkeit(prioritaet: Int, verbleibende_tage: Int) -> String {
    if prioritaet == 1 {
        return "HOECHSTE PRIORITAET"
    }
    if verbleibende_tage <= 1 {
        return "DRINGEND WEGEN FRIST"
    }
    return "NORMAL"
}

fn main() {
    status = berechne_dringlichkeit(1, 5)
    print("Architektur-Plan verifiziert: " + status)
}

9. Zusammenfassung

  • Strukturierte Planung spart Entwicklungszeit und verhindert Architekturfehler.
  • Zelyras deklarative Sprachstruktur passt sich nahtlos an agile Planungsphasen an.

10. Kontrollfragen zur Selbstprüfung

  1. Welche vier Phasen durchläuft ein professioneller Software-Entwurf?
  2. Warum sollte das Datenmodell vor der Benutzeroberfläche entworfen werden?
  3. Wie helfen Vorbedingungen bei der präzisen Anforderungsdefinition?

Kapitel 36: Architektur und saubere Codestruktur

1. Was lerne ich in diesem Kapitel?

  • Wie du deinen Zelyra-Code nach dem bewährten Schichtenmodell strukturierst.
  • Die klare Trennung von Persistenz (table), Geschäftslogik (fn) und Darstellung (page, crud).
  • Wie du Kopplungen vermeidest und Module wartungsfreundlich hältst.
  • Warum saubere Architektur vor bösen Überraschungen bei späteren Erweiterungen schützt.

2. Warum ist das Thema wichtig?

Wenn Datenzugriff, Geschäftsregeln und HTML-Ausgabe wild durcheinandergewürfelt werden, entsteht unwartbarer Code. Wenn sich später das Datenbanklayout ändert, zerbricht plötzlich die Weboberfläche. Eine saubere Architektur zieht klare Trennlinien: Jede Schicht hat eine einzige Verantwortlichkeit.

3. Verständliche Erklärung

Eine saubere Zelyra-Anwendung gliedert sich in drei klare Schichten:

  1. Daten- und Persistenzschicht: Tabellendefinitionen (table) und typisierte SQL-Abfragen.
  2. Geschäftslogikschicht: Reine Rechen- und Validierungsfunktionen mit Verträgen (requires, ensures).
  3. Präsentations- und Schnittstellenschicht: Weboberflächen (crud, page) und REST-Endpunkte (api).
# 1. Datenmodell
table tasks {
    id: Id primary auto
    name: String required
}

# 2. Geschäftslogik
fn formatiere_name(rohtext: String) -> String {
    return "[AUFGABE] " + rohtext
}

# 3. Einstieg / Ablauf
fn main() {
    print(formatiere_name("Server pruefen"))
}

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Trennung von Logik und I/O

# Reine Rechenfunktion: Keine Capabilities noetig
fn berechne_prozent(wert: Int, max_wert: Int) -> Int
    requires { max_wert > 0 && wert >= 0 }
{
    return (wert * 100) / max_wert
}

# I/O-Funktion: Nutzt die Logik und gibt sie aus
fn main() {
    prozent = berechne_prozent(45, 50)
    print(prozent)
}

Beispiel 2: Strukturierung durch aussagekräftige Namen

table settings {
    id: Id primary auto
    app_name: String(60) required
}

fn zeige_systeminfo(name: String) {
    print("System laeuft: " + name)
}

fn main() {
    zeige_systeminfo("Zelyra Task Suite")
}

5. Typische Fehler und deren Ursachen

  • Fehler: Geschäftslogik direkt in SQL-Strings oder HTML-Blöcke stopfen. Ursache: Trenne Berechnungen in eigene Hilfsfunktionen aus, damit sie unabhängig testbar bleiben.
  • Fehler: Zirkuläre Abhängigkeiten erzeugen. Ursache: Der Datenfluss sollte immer von oben nach unten verlaufen (Präsentation -> Logik -> Daten).

6. Merksätze

  1. Trenne Datenmodell, Geschäftsregeln und Darstellung strikt voneinander.
  2. Geschäftslogik sollte möglichst frei von Seiteneffekten und Capabilities sein.
  3. Saubere Schichten machen Anwendungen zukunftssicher und einfach erweiterbar.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Identifiziere in einem bestehenden Codebeispiel die drei Schichten.
  • Stufe 2 (Mittel): Lagere alle Berechnungen aus einer Web-Route in separate reine Funktionen aus.
  • Stufe 3 (Anspruchsvoll): Entwirf ein Schichtenmodell für ein Zeiterfassungssystem.

8. Praxisaufgabe: Schichtenarchitektur für die Aufgabenverwaltung

Implementiere das Schichtenmodell für unsere Aufgabenverwaltung:

database main {
    engine: mariadb
    database: "tasks_architecture"
}

# Schicht 1: Persistenz
table tasks {
    id: Id primary auto
    name: String required
    erledigt: Bool default false
}

# Schicht 2: Geschaeftslogik
fn ist_aufgabe_wichtig(name: String, dringend: Bool) -> Bool {
    return dringend
}

# Schicht 3: Anwendung / Ausfuehrung
fn main() uses Database {
    wichtig = ist_aufgabe_wichtig("Steuern einreichen", true)
    print("Aufgaben-Architektur geprueft.")
}

9. Zusammenfassung

  • Das Drei-Schichten-Modell garantiert Übersichtlichkeit und langfristige Wartbarkeit.
  • Zelyra unterstützt diese Struktur auf natürliche Weise durch sein klares Typsystem.

10. Kontrollfragen zur Selbstprüfung

  1. Welche drei Schichten bilden das Fundament einer sauberen Zelyra-Anwendung?
  2. Warum sollte Kern-Geschäftslogik möglichst ohne Capabilities auskommen?
  3. Welche Vorteile bietet die Trennung von Präsentation und Datenzugriff bei Re-Designs?

Kapitel 37: Konfiguration und Umgebungsvariablen

1. Was lerne ich in diesem Kapitel?

  • Wie Konfigurationswerte sicher über Umgebungsvariablen (.env) verwaltet werden.
  • Die Bibliotheksfunktion env(schluessel) und die benötigte Fähigkeit uses Environment.
  • Wie du mit dem Option<String>-Rückgabewert von env() sicher umgehst.
  • Warum Passwörter und API-Schlüssel niemals im Quelltext stehen dürfen.

2. Warum ist das Thema wichtig?

Einer der schnellsten Wege, um unfreiwillig auf der Startseite von Sicherheitsblogs zu landen, ist das versehentliche Einchecken von Datenbank-Passwörtern oder API-Schlüsseln in öffentliche Git-Repositories. Zudem muss sich eine Anwendung in Entwicklung, Test und Produktion unterschiedlich verhalten (z. B. andere Datenbank-Hosts). Umgebungsvariablen (uses Environment) trennen Code und Geheimnisse sauber voneinander – damit dein Code quelloffen sein kann, deine Passwörter aber geheim bleiben.

3. Verständliche Erklärung

In Zelyra greifst du über die Funktion env() auf Umgebungsvariablen zu. Weil eine Variable in der Umgebung existieren kann oder fehlen kann, liefert env() immer ein Option<String> zurück:

fn lese_port() -> String uses Environment {
    opt_port = env("APP_PORT")
    match opt_port {
        Some(p) => {
            return p
        }
        None => {
            return "8080"
        }
    }
}

fn main() uses Environment {
    port = lese_port()
    print("Server lauscht auf Port: " + port)
}

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Datenbank-Host konfigurieren

fn hole_db_host() -> String uses Environment {
    match env("DB_HOST") {
        Some(host) => {
            return host
        }
        None => {
            return "127.0.0.1"
        }
    }
}

fn main() uses Environment {
    print("Verbinde mit: " + hole_db_host())
}

Beispiel 2: Debug-Modus dynamisch abfragen

fn ist_debug_aktiv() -> Bool uses Environment {
    match env("APP_DEBUG") {
        Some(wert) => {
            return wert == "true"
        }
        None => {
            return false
        }
    }
}

fn main() uses Environment {
    if ist_debug_aktiv() {
        print("Debug-Modus ist AN")
    } else {
        print("Debug-Modus ist AUS")
    }
}

5. Typische Fehler und deren Ursachen

  • Fehler: Geheime Passwörter fest in .zyl-Dateien hineinschreiben. Ursache: Nutze immer eine .env-Datei und lese sensible Werte mit env().
  • Fehler: env() ohne uses Environment aufrufen. Ursache: Zelyras Sicherheitssystem schützt Systemumgebungen vor unbefugtem Zugriff.

6. Merksätze

  1. Sensible Zugangsdaten gehören niemals in den Versionskontroll-Quelltext.
  2. env(name) liefert ein Option<String> und erfordert uses Environment.
  3. Stelle für fehlende Umgebungsvariablen immer sichere Standardwerte bereit.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Lese eine Umgebungsvariable USER_NAME aus und gib eine persönliche Begrüßung aus.
  • Stufe 2 (Mittel): Schreibe eine Hilfsfunktion hole_env_oder_standard(schluessel: String, standard: String) -> String.
  • Stufe 3 (Anspruchsvoll): Konfiguriere ein Programm so, dass es zwischen Entwicklungs- und Produktions-Modus umschaltet.

8. Praxisaufgabe: Konfigurationszentrale für die Aufgabenverwaltung

Schreibe den Konfigurations-Loader für unsere Aufgabenverwaltung:

fn lade_app_titel() -> String uses Environment {
    match env("APP_TITLE") {
        Some(titel) => {
            return titel
        }
        None => {
            return "Zelyra Aufgabenverwaltung 0.1"
        }
    }
}

fn main() uses Environment {
    print("System gestartet: " + lade_app_titel())
}

9. Zusammenfassung

  • Umgebungsvariablen ermöglichen flexible, sichere Konfiguration für verschiedene Serverumgebungen.
  • Zelyras Option-Typ zwingt dich, das Fehlen von Einstellungen elegant zu behandeln.

10. Kontrollfragen zur Selbstprüfung

  1. Warum dürfen geheime API-Schlüssel niemals im Quelltext fest hinterlegt sein?
  2. Welchen Datentyp liefert die Funktion env() zurück?
  3. Welche Capability wird für den Zugriff auf Umgebungsvariablen benötigt?

Kapitel 38: Fehlersuche und Optimierung

1. Was lerne ich in diesem Kapitel?

  • Wie du mit dem CLI-Befehl zelyra doctor dein Projekt auf Herz und Nieren prüfst.
  • Wie man Compilerhinweise und Diagnosen effektiv nutzt.
  • Strategien zum systematischen Aufspüren von Fehlern (Debugging).
  • Wie du Performance-Engpässe erkennst und eliminierst.

2. Warum ist das Thema wichtig?

Selbst bei sorgfältigster Programmierung läuft nicht immer alles auf Anhieb glatt. Vielleicht ist der Datenbankport blockiert, eine Konfiguration unvollständig oder eine Schleife berechnet unnötige Schritte. Wer planlos herumprobiert, verliert Stunden. Systematisches Debugging mit den passenden Zelyra-Werkzeugen führt dagegen in wenigen Minuten zur Lösung.

3. Verständliche Erklärung

Zelyra gibt dir ein Schweizer Taschenmesser für die Fehlerdiagnose an die Hand:

  • zelyra check: Prüft Syntax, Typen, Berechtigungen und Verträge.
  • zelyra doctor: Prüft die Systemumgebung, Datenbankverbindungen, Ports und Richtlinien.
  • zelyra impact: Zeigt an, welche Funktionen durch eine geplante Änderung beeinflusst werden.
zelyra doctor main.zyl

Wenn die MariaDB-Verbindung nicht erreichbar ist, meldet doctor sofort die Ursache, anstatt dich im Unklaren zu lassen.

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Systematische Kontrollausgaben

fn berechne_summe(zahlen: Int[]) -> Int {
    mutable summe = 0
    for z in zahlen {
        summe = summe + z
    }
    return summe
}

fn main() {
    werte: Int[] = [10, 20, 30]
    ergebnis = berechne_summe(werte)
    print("Berechnetes Ergebnis:")
    print(ergebnis)
}

Beispiel 2: Absicherung vor Endlosschleifen durch Invarianten

fn sichere_zaehlung(grenze: Int) -> Int
    requires { grenze > 0 }
{
    mutable i = 0
    while i < grenze
        invariant { i >= 0 }
    {
        i = i + 1
    }
    return i
}

fn main() {
    print(sichere_zaehlung(10))
}

5. Typische Fehler und deren Ursachen

  • Fehler: Bei unerwartetem Verhalten wahllos Zeilen im Code ändern. Ursache: Lokalisiere das Problem erst exakt mit zelyra check und Ausgaben.
  • Fehler: Datenbankfehler vermuten, wenn lediglich Berechtigungen in zelyra.toml fehlen. Ursache: Führe zelyra doctor aus, um Umgebungsfehler sofort zu erkennen.

6. Merksätze

  1. zelyra doctor ist der erste Schritt bei Verbindungsproblemen und Umgebungsfehlern.
  2. Invarianten und Verträge verhindern logische Fehlberechnungen.
  3. Systematische Fehlersuche ist schneller und sicherer als blindes Ausprobieren.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Führe zelyra doctor für dein Aufgaben-Projekt aus.
  • Stufe 2 (Mittel): Isoliere eine bewusst fehlerhafte Berechnung in einer Testfunktion.
  • Stufe 3 (Anspruchsvoll): Schreibe ein Programm mit detaillierten Statusmeldungen für jeden Einzelschritt.

8. Praxisaufgabe: Selbstdiagnose-Routine der Aufgabenverwaltung

Baue einen internen Gesundheits-Check für die Aufgabenverwaltung:

fn fuehre_selbsttest_durch() -> Bool {
    test_ok = 1 + 1 == 2
    return test_ok
}

fn main() {
    print("Starte System-Selbsttest...")
    if fuehre_selbsttest_durch() {
        print("[OK] System arbeitet einwandfrei.")
    } else {
        print("[FEHLER] Interner Systemfehler.")
    }
}

9. Zusammenfassung

  • Zelyras Werkzeugkette (doctor, check, impact) liefert schnelle Klarheit bei Fehlern.
  • Verträge und Invarianten fangen Probleme ab, bevor sie zu schwer auffindbaren Bugs werden.

10. Kontrollfragen zur Selbstprüfung

  1. Welche Aspekte überprüft der CLI-Befehl zelyra doctor?
  2. Wie grenzt man einen Fehler in einer längeren Berechnung am besten ein?
  3. Welche Rolle spielen Schleifeninvarianten bei der Fehlersuche?

Kapitel 39: Bereitstellung und Betrieb

1. Was lerne ich in diesem Kapitel?

  • Wie Zelyra-Anwendungen für den Produktivbetrieb bereitgestellt (deployed) werden.
  • Wie du deine Webanwendung im Docker-Container betreibst.
  • Wie Zelyra mit einer Produktions-MariaDB verbunden wird.
  • Wie du den Server-Prozess mit zelyra serve zuverlässig am Laufen hältst.

2. Warum ist das Thema wichtig?

Eine Software nützt niemandem, wenn sie nur auf dem Entwickler-Laptop läuft. Sie muss auf einem Server oder in der Cloud rund um die Uhr stabil erreichbar sein. In anderen Umgebungen erfordert das Deployment oft komplizierte Anleitungen mit PHP-FPM, Webserver-Vhosts und Prozessmanagern. Zelyra vereinfacht den Betrieb radikal: Eine einzelne Konfiguration und ein schlanker Container genügen.

3. Verständliche Erklärung

Zelyra bringt seinen eigenen Hochleistungs-Webserver direkt mit:

zelyra serve src/main.zyl 0.0.0.0:8080

Für den professionellen Betrieb verpackst du dein Projekt in einen Docker-Container:

  • Der Container enthält das Zelyra-Binary, deine Quelldateien und zelyra.toml.
  • Beim Start führt der Container automatisch zelyra db apply aus und startet den Webserver.

4. Kleine, aufeinander aufbauende Beispiele

Beispiel 1: Produktionsreife Projektstruktur

# zelyra.toml
[package]
name = "aufgaben_produktion"
version = "1.0.0"

[capabilities]
database = true
filesystem = false
network = true

Beispiel 2: Saubere Hauptdatei für den Webbetrieb

database main {
    engine: mariadb
    database: "tasks_prod"
}

table tasks {
    id: Id primary auto
    name: String(120) required
    fertig: Bool default false
}

page "/" {
    html {
        <h1>Zelyra Aufgabenverwaltung live</h1>
        <p>Produktivsystem aktiv und sicher.</p>
    }
}

Beispiel 3: Startbefehl für den Server Auf dem Produktionsserver genügt:

zelyra db apply src/main.zyl
zelyra serve src/main.zyl 0.0.0.0:80

5. Typische Fehler und deren Ursachen

  • Fehler: Vergessen, die Datenbankmigrationen (zelyra db apply) vor dem Serverstart auszuführen. Ursache: Neue Tabellen und Spalten müssen in der Datenbank existieren, bevor Anfragen eingehen.
  • Fehler: In Docker den Port 8080 nicht nach außen freigeben. Ursache: Nutze im docker run-Befehl das Port-Mapping -p 8080:8080.

6. Merksätze

  1. zelyra serve startet den integrierten HTTP-Server ohne externe Webserver-Abhängigkeiten.
  2. zelyra db apply bringt das Produktivschema sicher auf den neuesten Stand.
  3. Klare Capability-Beschränkungen in zelyra.toml sichern den Server vor Angriffen ab.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Starte deine Webanwendung lokal auf Port 3000 mit zelyra serve main.zyl 127.0.0.1:3000.
  • Stufe 2 (Mittel): Erstelle eine docker-compose.yml, die MariaDB und deine Zelyra-App verbindet.
  • Stufe 3 (Anspruchsvoll): Simuliere ein Update mit einer Schema-Änderung und führe zelyra db plan und zelyra db apply aus.

8. Praxisaufgabe: Das produktionsfertige Aufgaben-Paket

Führe alle Einstellungen für das finale Deployment zusammen:

database main {
    engine: mariadb
    database: "tasks_production"
}

table tasks {
    id: Id primary auto
    name: String(100) required
    erledigt: Bool default false
}

page "/" {
    html {
        <html>
            <body>
                <h1>Aufgabenverwaltung - Produktivsystem</h1>
                <p>System bereit fuer Benutzeranfragen.</p>
            </body>
        </html>
    }
}

9. Zusammenfassung

  • Zelyra-Anwendungen lassen sich ohne komplizierte Server-Stacks direkt und performant betreiben.
  • Datenbank-Migrationen und Webbetrieb greifen nahtlos ineinander.

10. Kontrollfragen zur Selbstprüfung

  1. Welcher Befehl startet die Webanwendung auf einem Server?
  2. Warum sollte zelyra db apply vor dem Starten des Webservers ausgeführt werden?
  3. Welche Vorteile bietet der integrierte HTTP-Server gegenüber externen Server-Setups?

TEIL X – ABSCHLUSSPROJEKT UND WEITERFÜHRUNG


Kapitel 40: Das große Abschlussprojekt: Vollständige Aufgabenverwaltung

1. Was lerne ich in diesem Kapitel?

In diesem großen Finale lernst du:

  • Wie alle gelernten Bausteine zu einer vollständigen, produktionsreifen Anwendung verschmelzen.
  • Wie Datenbank, Authentifizierung, Rollen, CRUD-Interface, REST-API und Geschäftslogik ineinandergreifen.
  • Wie du den kompletten Code liest, verstehst und auf deinem Server in Betrieb nimmst.

2. Warum ist das Thema wichtig?

Einzelne Code-Snippets zu verstehen ist eine Sache – eine echte, zusammenhängende Anwendung aus einem Guss zu bauen, ist die eigentliche Kunst der Softwareentwicklung. Dieses Abschlussprojekt beweist die Eleganz von Zelyra: In einer einzigen, übersichtlichen Datei entsteht eine datenbankgestützte, authentifizierte Webanwendung mit UI und REST-API, für die man in herkömmlichen Frameworks dutzende Dateien anlegen müsste.

3. Verständliche Erklärung des Gesamtprojekts

Unsere Aufgabenverwaltung umfasst:

  1. Datenbank & Schemas: Ziel-Engine MariaDB mit Tabellen für Aufgaben (tasks), Benutzer (users), Sitzungen (auth_sessions) und Berechtigungen (user_permissions).
  2. Authentifizierung: auth users mit geschützten Routen und Argon2-Passwortschutz.
  3. Geschäftslogik: Reine Funktionen mit Verträgen (requires, ensures) zur Prioritätsprüfung und Fortschrittsberechnung.
  4. CRUD-Interface: Das administrative Web-Dashboard mit Listen, Formularen und Löschdialogen.
  5. REST-API: JSON-Endpunkte für den programmatischen Zugriff.

4. Das vollständige Projekt: Der finale Quelltext

database main {
    engine: mariadb
    database: "zelyra_tasks_app"
}

# ==========================================
# 1. AUTHENTIFIZIERUNG & BENUTZERVERWALTUNG
# ==========================================

auth users {
    table: users
    sessions: auth_sessions
    permissions: user_permissions
}

table users {
    id: Id primary auto
    email: Email required unique
    password_hash: String(255) required
    active: Bool default true
}

table auth_sessions {
    id: Id primary auto
    user: User required
    token_hash: String(64) required unique
    expires_at: Timestamp required
}

table user_permissions {
    id: Id primary auto
    user: User required
    permission: String(100) required
}

# ==========================================
# 2. AUFGABEN-DATENMODELL
# ==========================================

type TaskId = Id

table tasks {
    id: TaskId primary auto
    name: String(120) required
    beschreibung: String(500)
    prioritaet: Int default 2
    erledigt: Bool default false
}

# ==========================================
# 3. GESCHÄFTSLOGIK MIT VERTRÄGEN
# ==========================================

fn berechne_erfolgsquote(erledigte: Int, gesamt: Int) -> Int
    requires { gesamt > 0 && erledigte >= 0 && erledigte <= gesamt }
    ensures { result >= 0 && result <= 100 }
{
    return (erledigte * 100) / gesamt
}

fn prioritaet_label(stufe: Int) -> String {
    match stufe {
        1 => {
            return "HOCH"
        }
        2 => {
            return "MITTEL"
        }
        3 => {
            return "NIEDRIG"
        }
        _ => {
            return "NORMAL"
        }
    }
}

# ==========================================
# 4. WEBOBERFLÄCHE & CRUD-SCHNITTSTELLE
# ==========================================

crud Task -> tasks {
    title: "Zelyra Aufgabenverwaltung"

    view {
        fields {
            name
            prioritaet
            erledigt
        }

        list {
            mode: cards
            empty: "Keine Aufgaben vorhanden. Erstelle deine erste Aufgabe!"
        }

        detail {
            mode: cards
            title: "Aufgabendetails"
        }

        form {
            mode: cards
            title: "Aufgabe bearbeiten"
            submit: "Aufgabe sichern"
        }

        delete {
            title: "Aufgabe entfernen"
            message: "Moechtest du diese Aufgabe wirklich loeschen?"
            submit: "Jetzt loeschen"
        }
    }

    action abschliessen {
        label: "Als erledigt markieren"
        confirm: "Aufgabe abschliessen?"

        sql {
            UPDATE tasks
            SET erledigt = true
            WHERE id = :id
        }

        success "Aufgabe erfolgreich abgeschlossen."
        redirect "/tasks"
    }
}

# ==========================================
# 5. REST-API ENDPUNKTE
# ==========================================

api GET "/api/tasks/{id}" {
    input {
        id: TaskId
    }
    output Task
    errors {
        404 NotFound
    }
}

api POST "/api/tasks" {
    input {
        name: String
        prioritaet: Int
    }
    output Task
    errors {
        400 ValidationError
    }
}

# ==========================================
# 6. STARTSEITE
# ==========================================

page "/" {
    html {
        <html>
            <head>
                <title>Zelyra Aufgaben-System</title>
            </head>
            <body>
                <h1>Zelyra Aufgabenverwaltung</h1>
                <p>Das vollstaendige Abschlussprojekt ist einsatzbereit.</p>
                <a href="/tasks">Zur Aufgabenuebersicht</a>
            </body>
        </html>
    }
}

# ==========================================
# 7. EINSTIEGSPUNKT
# ==========================================

fn main() {
    print("Zelyra Aufgabenverwaltung vollstaendig initialisiert.")
}

5. Typische Fehler und deren Ursachen

  • Fehler: Den Code starten, ohne vorher zelyra db apply ausgeführt zu haben. Ursache: Die Tabellen in MariaDB müssen angelegt sein, bevor der Webserver Anfragen verarbeitet.
  • Fehler: Fehlende MariaDB-Zugangsdaten in .env. Ursache: Hinterlege DB_HOST, DB_USER und DB_PASSWORD in deiner Umgebungsdatei.

6. Merksätze

  1. In Zelyra entsteht eine vollständige, sichere Web-App in einer einzigen, harmonischen Datei.
  2. Typsicherheit, Verträge, Authentifizierung und APIs greifen lückenlos ineinander.
  3. Dieser Code ist sofort mit zelyra check prüfbar und mit zelyra serve startbar.

7. Übungsaufgaben

  • Stufe 1 (Leicht): Kompiliere das Gesamtprojekt mit zelyra check und überprüfe, dass 0 Fehler auftreten.
  • Stufe 2 (Mittel): Ergänze die Tabelle tasks um ein Feld faellig_am: Date und passe Formular und Ansichten an.
  • Stufe 3 (Anspruchsvoll): Richte MariaDB lokal ein, spiele das Schema mit zelyra db apply ein und lege die ersten echten Aufgaben über den Webbrowser an.

8. Praxisaufgabe: Dein eigener produktiver Server-Start

Initialisiere das Projekt und starte es:

zelyra new task_manager --template minimal
cd task_manager
# Quellcode in src/main.zyl einfuegen
zelyra check src/main.zyl
zelyra serve src/main.zyl 0.0.0.0:8080

Öffne http://localhost:8080 – deine eigene Zelyra-Anwendung ist live!

9. Zusammenfassung

  • Das Abschlussprojekt vereint alle 9 vorangegangenen Teile dieses Lehrbuchs.
  • Du hast gelernt, wie man eine moderne, fehlertolerante Webanwendung von Grund auf baut.

10. Kontrollfragen zur Selbstprüfung

  1. Welche Komponenten wurden im Abschlussprojekt kombiniert?
  2. Warum genügen in Zelyra so wenige Zeilen für ein vollwertiges CRUD-System?
  3. Welche Schritte sind erforderlich, um das Projekt auf einem neuen Server zu deployen?

Kapitel 41: Funktionsumfang und sicherer Betrieb

1. Was lerne ich in diesem Kapitel?

  • Welche Änderungen Zelyra 1.0.1 bis 1.0.7 gebracht haben.
  • Wie Compiler-Version, Sprachlinie und Datenbankunterstützung zusammenhängen.
  • Wie du lokale Änderungen sicher prüfst und das Rechnungstutorial in Studio bearbeitest.

2. Warum ist das Thema wichtig?

Ein erfolgreich kompiliertes Programm kann fachliche Fehler enthalten. Ein erfolgreiches Schema-Update ersetzt kein Backup. Prüfe deshalb sowohl die konkrete Anwendung als auch ihren Datenbankpfad, bevor du einen neuen Stand übernimmst.

3. Aktueller Stand

Zelyra 1.2.39 ist das aktuelle stabile Release; die Sprachkompatibilitätslinie bleibt 0.1. MariaDB ist der breiteste geprüfte Laufzeitpfad. PostgreSQL und MySQL unterstützen ausgewählte Abläufe, aber nicht automatisch dieselben CRUD-, Authentifizierungs-, Mail- oder Wiederherstellungsfunktionen. Die Datenbankmatrix beschreibt die geprüften Pfade.

Seit 1.0.0 kamen hinzu: Compose-v2-Erkennung und ein reparierter Studio-Run-Einstieg (1.0.1); automatische lokale Tutorialdatenbank, Schema- und Beispieldateneinrichtung sowie for ... step und range (1.0.2); die erweiterte Standardbibliothek, |>-Pipelines, else if, freie Ports und wiederholbare Beispieldaten (1.0.3); eigene Compose-Projektnamen und isolierte Datenbankausweichpfade ohne Löschen alter Volumes (1.0.4); lokale Studio-Logs, sichere Deinstallation, reines Rechnungsdatum, serverseitige Rechnungsnummern, eine am Ablauf orientierte Tutorialstartseite und zweisprachige ausführliche Quellcodeanleitungen (1.0.5); das integrierte lokale UI- und Designsystem Zelyra UI mit compilergeprüften UI-Komponenten, Themes, Varianten, responsivem Layout, Charts und Browser-Druckansichten (1.0.6); typisierte wertliefernde if-Ausdrücke, funktionale Record-Updates (Customer { ..old, active: true }), lexikalische Lambdas in Pipelines (map, filter, find, reduce), verschachtelte Record-Muster und Match-Guards, First-Class-Tupel, Ausführungsbudgets (--max-steps), nominale Id-Aliase (type OrderId = Id), serverseitiges Text-PDF (render_pdf) und transaktionale PDF-Mail-Anhänge über die Outbox (1.0.7). Der Leitfaden erklärt, wie crud Invoice -> invoices die Ressource deklariert, aus der Zelyra Routen wie /invoices/new erzeugt. Vollständige Listen stehen in Anhang D und den Release-Notizen.

4. Rechnungstutorial in Studio

Erzeuge das Tutorial ohne separaten CLI-Start und öffne es im Editor:

zelyra --tutorial invoice --no-start
zelyra editor ./invoice-tutorial

Wähle main.zyl und klicke auf Run. Studio startet bei der lokalen .env-Konfiguration MariaDB über Docker Compose, richtet das Schema und die wiederholbaren Beispieldaten ein und startet die Webvorschau. MariaDB läuft im Container; die Webanwendung ist ein lokaler Zelyra-Prozess. Docker Compose v2 oder neuer ist erforderlich, Python Compose v1 wird nicht unterstützt. Mit docker compose version prüfst du das Plugin.

Bearbeite src/customers.zyl, src/items.zyl, src/invoices.zyl, src/dashboard.zyl oder src/shell.zyl im Projektbaum. zelyra.theme.css enthält lokale Gestaltungsregeln. Neue Geschäftsdaten gibst du über die Formulare ein; der Seed enthält nur wiederholbare Beispieldaten. Die Seite / führt durch den Rechnungsablauf; /views/invoicedashboard ist die Rechnungsübersicht. src/dashboard.zyl deklariert diese Übersicht. crud Invoice -> invoices erzeugt die Standardrouten einschließlich /invoices/new. Die zweisprachigen Dateien TUTORIAL.de.md und TUTORIAL.en.md erklären jeden Einrichtungsbefehl und seine Optionen, jede Quelldatei und deren einzelne Zeilen. Rechnungsdaten werden als Datum ohne Uhrzeit mit Format-Hinweis erfasst; die Rechnungsnummer vergibt der Server.

5. Installation und sichere Änderungen

Prüfe die installierte Version mit zelyra --version und die Umgebung mit zelyra doctor. Sichere Quellcode, Konfiguration und Datenbank vor Änderungen. Lies zelyra db plan main.zyl, bevor du db apply ausführst. Eine Rückwärtsmigration kann nachträglich geschriebene Daten entfernen; abgebrochenes DDL kann einen teilweise geänderten Zustand hinterlassen.

6. Release 1.0.7 und Details zum Tutorial

Die Release-Notizen 1.0.7 und die Roadmap unterscheiden veröffentlichte Funktionen von zukünftigen Vorhaben. Das Tutorial erklärt jeden Einrichtungsbefehl samt Optionen, beschreibt jede Quelldatei und erläutert ihre Quelltextzeilen. Insbesondere deklariert crud Invoice -> invoices in src/invoices.zyl eine Rechnungsressource. Zelyra leitet daraus und aus den Standardaktionen CRUD-Routen wie /invoices/new ab. Siehe die generierten Projektanleitungen TUTORIAL.de.md und TUTORIAL.en.md oder die Rechnungstutorial-Anleitung. Studio-Logs zeigen lokale Run- und Vorschauausgaben sowie Einrichtungsschritte. zelyra uninstall entfernt nach Bestätigung die aktive benutzereigene Programmdatei, lässt Projektdaten aber bestehen.

7. Typische Fehler

  • Compose wird nicht erkannt: Prüfe docker compose version. Zelyra benötigt das Compose-v2-Plugin; Python Compose v1 reicht nicht.
  • Studio zeigt keine Tutorialdaten: Prüfe, ob Run erfolgreich war und ob die lokale MariaDB erreichbar ist. Externe oder überschriebene Datenbankadressen startet Studio nicht automatisch.
  • Eine Prüfung wird als allgemeiner Korrektheitsbeweis verstanden: Compiler- und Integrationsprüfungen decken bestimmte Abläufe ab; sie beweisen nicht die Korrektheit beliebiger Anwendungen.

8. Praxisaufgabe: Änderungen vorbereiten

zelyra --version
zelyra doctor
zelyra check main.zyl
zelyra db plan main.zyl

Prüfe die Ausgabe. Erprobe Backup und Wiederherstellung in einer getrennten Testdatenbank, bevor du Schemaänderungen übernimmst.

9. Übungsaufgaben

  • Stufe 1 (Leicht): Führe zelyra --version und zelyra doctor in deinem Terminal aus. Prüfe, ob Docker Compose v2 erkannt wird und welche Voraussetzungen deine Umgebung für das installierte Release erfüllt.
  • Stufe 2 (Mittel): Erzeuge das Rechnungstutorial mit zelyra --tutorial invoice --no-start. Führe im erzeugten Verzeichnis zelyra db plan main.zyl aus und analysiere die geplanten DDL-Statements für Kunden, Artikel und Rechnungen.
  • Stufe 3 (Anspruchsvoll): Simuliere ein risikofreies Datenbank-Upgrade: Starte eine getrennte Test-MariaDB-Instanz, wende das Schema mit zelyra db apply an und teste die Sicherung mit zelyra db backup, um die Wiederherstellbarkeit ohne Beeinträchtigung der Produktivdaten zu verifizieren.

10. Zusammenfassung

Zelyra 1.2.39 ist das aktuelle stabile Release. Das Rechnungstutorial startet MariaDB in Docker; die Webvorschau läuft als lokaler Zelyra-Prozess. Zelyra UI stellt lokal ausgelieferte, compilergeprüfte Komponenten bereit. Weitere Funktionen und ihre Grenzen sind oben, in den Release-Notizen und in der Datenbankmatrix beschrieben. Das CSV-Beispielpaket bietet explizite typisierte Konvertierungen, schreibt aber keine Datenbankdaten.

11. Kontrollfragen

  1. Was unterscheidet Compiler-Version und Sprachkompatibilitätslinie?
  2. Welche wichtigen Sprach- und Tutorialverbesserungen kamen in 1.0.3, 1.0.6 und 1.0.7 hinzu?
  3. Welche Tutorialkomponenten laufen in Docker, und welche bearbeitest du im Studio?
  4. Wie unterscheiden sich Release-Notizen und Roadmap?

Kapitel 42: Dein Weg als Zelyra-Entwickler

1. Was lerne ich in diesem Kapitel?

  • Wie du dein erworbenes Wissen vertiefst und in eigenen Projekten anwendest.
  • Die wichtigsten Prinzipien für professionelle, langlebige Softwarearchitektur.
  • Wie du Teil der Zelyra-Community wirst und zum Open-Source-Ökosystem beiträgst.

2. Warum ist das Thema wichtig?

Programmieren lernt man nicht durch bloßes Lesen, sondern durch Machen. Dieses Buch hat dir das Fundament vermittelt – jetzt beginnt deine persönliche Reise als Entwickler. Mit Zelyra besitzt du eine moderne, sichere und zukunftsfähige Sprache, die dich bei jedem Schritt unterstützt.

3. Die fünf goldenen Regeln für Zelyra-Entwickler

  1. Model First: Beginne jedes Projekt mit dem table-Schema. Ein klares Datenmodell löst die Hälfte aller späteren Probleme.
  2. Definiere Verträge: Sichere wichtige Funktionen mit requires und ensures ab. Sie sind Dokumentation und Test in einem.
  3. Rechte bewusst vergeben: Halte den Großteil deines Codes rein (ohne Capabilities) und deklariere Seiteneffekte gezielt.
  4. Fehler als Werte behandeln: Nutze Result und Option. Verbanne Ausreden für unkontrollierte Abstürze.
  5. Gemeinsam mit KI arbeiten: Nutze Zelyras --format json und Typed Holes _, um KI-Assistenten als produktive Co-Piloten einzusetzen.

4. Dein nächstes Projekt: Ideen zum Weiterprogrammieren

  • Persönliches Haushaltsbuch: Einnahmen, Ausgaben, Kategorien und Monatsberichte mit Zelyra CRUD.
  • Support-Ticket-System: Kundenanfragen, Prioritäten, Zuweisungen und E-Mail-Benachrichtigungen.
  • Kunden- und Projektzeiterfassung: Stundenerfassung mit uses Clock und Rechnungs-Export als JSON.

5. Übungsaufgaben

  • Stufe 1 (Leicht): Wähle eines der drei vorgeschlagenen Projekte (Haushaltsbuch, Ticket-System oder Zeiterfassung) und skizziere die Kernentitäten nach dem »Model First«-Prinzip als Zelyra-Tabellen mit Primärschlüsseln und Pflichtfeldern.
  • Stufe 2 (Mittel): Formuliere für mindestens zwei Kernfunktionen deines neuen Projekts vertragliche Vor- und Nachbedingungen (requires, ensures), z. B. dass Buchungsbeträge nicht negativ sein dürfen.
  • Stufe 3 (Anspruchsvoll): Implementiere ein lauffähiges Minimalmodul mit sauberer Dreischichtentrennung: Reine Berechnungsfunktionen ohne Capabilities und isolierte Persistenzfunktionen mit deklarierten Capabilities.

6. Zusammenfassung des Lehrbuchs

Herzlichen Glückwunsch! Du hast alle 10 Teile und 42 Kapitel von Zelyra lernen erfolgreich gemeistert. Du beherrschst die Grundlagen, das Typsystem, Fehlerbehandlung, Datenbanken, Webanwendungen, APIs und sichere Softwarearchitektur. Du bist jetzt bereit, eigene, robuste Anwendungen mit Zelyra zu erschaffen!

7. Kontrollfragen zum Abschluss

  1. Welche Zelyra-Besonderheit schätzt du nach diesem Lehrgang am meisten?
  2. Warum ist das Zusammenspiel von Sprache und Datenbank in Zelyra so revolutionär?
  3. Welches Projekt wirst du als Nächstes mit Zelyra umsetzen?

Kapitel 43: Mit Zelyra Studio arbeiten

1. Was lerne ich in diesem Kapitel?

  • Wie du den lokalen Zelyra-Editor startest und deine Quelldateien darin bearbeitest.
  • Wie du das Datenbankschema ansiehst, ohne Datensätze zu verändern.
  • Wie die geführte CRUD-Vorschau Quelltext erzeugt und vor dem Speichern prüft.

2. Was ist Zelyra Studio?

Zelyra Studio ist der Webeditor, der mit dem Zelyra-CLI ausgeliefert wird. Er hilft dir, ein kleines Zelyra-Projekt an einem Ort zu öffnen, Quelltext zu bearbeiten, Compilerdiagnosen zu lesen und passende Datenbankinformationen nachzuschlagen. Du brauchst dafür keinen zusätzlichen Cloud-Editor und keinen externen KI-Dienst.

Studio ist ein Entwicklungswerkzeug für dein lokales Projekt. Es ist noch keine vollständige IDE und nimmt dir keine fachlichen Entscheidungen ab. Der erzeugte Quelltext bleibt normale Zelyra-Datei, die du auch außerhalb des Editors lesen, prüfen und versionieren kannst.

3. Studio starten

Wechsle in das Verzeichnis deines Projekts und starte den Editor:

cd maschinenverwaltung
zelyra editor

Für ein anderes Projekt kannst du den Ordner direkt angeben:

zelyra editor ./maschinenverwaltung

Das CLI öffnet den Standardbrowser und zeigt die lokale Adresse an. Auf SSH- oder Headless-Systemen startest du mit zelyra editor . --no-open und öffnest die angezeigte Adresse selbst. Zum Bearbeiten benötigt Studio keine laufende Datenbank. Studio bindet sich an 127.0.0.1; die Startadresse enthält ein zufälliges Sitzungstoken. Teile diese Adresse nicht öffentlich. Schreibende Browser-Anfragen benötigen das Token und müssen von derselben Browser-Origin kommen.

4. Dateien bearbeiten und prüfen

Der Projektbaum zeigt die Zelyra-Quelldateien. Öffne eine Datei in einem Tab, suche nach Text oder Symbolen, nutze die angezeigte Vervollständigung und formatiere den Quelltext bei Bedarf. Beim Speichern prüft Studio, ob die Datei seit dem Laden von einem anderen Programm verändert wurde. Bei einem Konflikt wird der fremde Stand nicht überschrieben; lade die Datei neu und prüfe die Änderungen.

Compilerdiagnosen erscheinen im Problems-Bereich und helfen dir, Fehler direkt im Editor zu finden. Der Aktionsfinder öffnet häufige Aktionen; die Kontexthilfe erklärt unterstützte Begriffe, Optionen und Beispiele. Strg+Leertaste bietet Vervollständigungen aus dem Compiler-Projektmodell und lokalen Bindungen, wenn der aktuelle Code geparst werden kann. F12 öffnet eine eindeutige Definition in der aktuellen oder direkt importierten Datei. Umschalt+F12 sucht lexikalisch gleiche Bezeichner in gespeicherten .zyl-Dateien, löst aber keine Gültigkeitsbereiche auf.

Die Projektkarte verbindet erkannte Module, Importe, Tabellen, TableViews, Views, CRUD-Ressourcen, Seiten, APIs und Funktionen mit Quellpositionen. Seiten- und CRUD-Layouts werden im eigenen Modul oder über ausdrücklich importierte öffentliche Views aufgelöst. Unklare und fehlende Beziehungen erhalten stabile Diagnosecodes. Mehrdeutige und dynamische Beziehungen werden nicht geraten; Größenbegrenzungen können die Karte unvollständig machen. Strg+S speichert den aktuellen Inhalt. Formatierung und Starten sind ausdrückliche Aktionen. Starten führt main.zyl höchstens 15 Sekunden aus und begrenzt jeden Ausgabekanal auf 64 KiB; erkannte Geheimnisse aus Umgebung und .env werden redigiert. Interaktive stdin-Eingabe ist nicht verfügbar. Speichern und Ausführen ändern keine weiteren Projektdateien stillschweigend.

Barrierefreiheit und Schriftgrößenskalierung

Für ermüdungsfreies Arbeiten und unterschiedliche Display-Anforderungen bietet Zelyra Studio eine integrierte dreistufige Schriftgrößenskalierung (Standard 100 %, Mittel +15 % und Groß +30 %):

  • Topbar-Schaltfläche: Über das Icon #font-size-button (aA, aA⁺, aA⁺⁺) in der oberen Leiste kannst du die Schriftgröße zyklisch umschalten.
  • Tastenkürzel: Mit Strg++ (bzw. Strg+=) vergrößerst du die Schrift, mit Strg+- verkleinerst du sie, und Strg+0 setzt sie auf den Standard zurück (unter macOS jeweils mit Cmd).
  • Befehlspalette: Im Aktionsfinder (F1 / Strg+Umschalt+P) stehen die Aktionen „Editor: Schriftgröße vergrößern“, „Editor: Schriftgröße verkleinern“ und „Editor: Schriftgröße zurücksetzen (Standard)“ zur Verfügung.
  • Flackerfreie Persistenz: Die gewählte Stufe wird im Browser (localStorage-Schlüssel zelyra-editor-font-size) gesichert und bereits vor dem DOM-Rendering ausgewertet, sodass beim Neuladen kein Flackern (FOUC) entsteht. Editorfenster (CodeMirror) und Studio-UI passen sich dynamisch an.

5. Datenbankschema ansehen

Wähle im Studio Datenbank und lade das Schema. Dafür muss DATABASE_URL in der Umgebung oder in der lokalen Projektdatei .env verfügbar sein. Die Ansicht liest Tabellen- und Spaltenmetadaten sowie Beziehungen. Sie liest keine Geschäftsdatensätze und führt keine Schemaänderungen aus.

Die Karte zeigt Tabellen und Fremdschlüsselbeziehungen. Für den geprüften Umfang vergleicht sie deklarierte Tabellen und Spalten mit der Live-Datenbank und markiert Abweichungen. Eine Gruppierung nach Quelldatei hilft dir bei der Orientierung; sie vergibt keine Berechtigungen.

Die visuelle Schemaansicht ist für die geprüften MariaDB- und PostgreSQL-Pfade verfügbar. MySQL besitzt in dieser Version einen begrenzten parametrisierten Abfragepfad, aber keine Studio-Live-Introspektion. Die genaue Matrix steht in der Datenbankdokumentation.

Datenbrowser und Quellspalten bearbeiten

Der Datenbrowser lädt Datensätze erst auf ausdrücklichen Klick und zeigt höchstens 25 Zeilen je Seite. Er ist schreibgeschützt. Die Schemaansicht selbst liest nur Metadaten; ihr Öffnen lädt keine Geschäftsdatensätze.

Der Spaltenassistent ergänzt neue Quellfelder und bearbeitet einfache bestehende Felder. Schlüssel, eindeutige Felder und Felder mit Defaultwert sind von der visuellen Änderung ausgeschlossen. Studio zeigt Diff und Compilerdiagnosen und prüft vor der Übernahme erneut den Datei-Hash. Die Übernahme schreibt ausschließlich Quelltext. Prüfe danach zelyra db plan main.zyl und führe eine Datenbankänderung gesondert aus.

5a. Rechnungstutorial in Studio starten

Für den IDE-Arbeitsablauf erzeugst du zuerst die Dateien, ohne den separaten CLI-Start auszuführen:

zelyra --tutorial invoice --no-start
zelyra editor ./invoice-tutorial

Wähle im Projektbaum main.zyl und klicke auf Run. Bei der lokalen Tutorialkonfiguration in .env startet Studio MariaDB mit Docker Compose, wartet auf die Datenbank, wendet das deklarierte Schema an und lädt die wiederholbaren Beispieldaten. Anschließend startet Studio die Webvorschau und zeigt ihre Adresse an. Stop preview beendet die Vorschau.

Docker Engine und Docker Compose v2 oder neuer müssen installiert und für deinen Benutzer erreichbar sein. Python Compose v1 wird nicht unterstützt. Prüfe Compose mit docker compose version; Zelyra installiert Docker oder Compose nicht automatisch. Bei fehlendem Compose-Plugin findest du die Installationsanleitung unter Docker Engine für Linux.

In diesem Ablauf läuft MariaDB im Docker-Container. Die Webanwendung wird als lokaler Zelyra-Prozess gestartet; sie läuft nicht selbst in einem Anwendungscontainer. Externe oder per Prozessumgebung überschriebene Datenbankverbindungen startet Studio nicht und verändert sie nicht automatisch.

Das Tutorial enthält zwei Kunden, drei Artikel und den Rechnungsentwurf RE-2026-001 mit zwei Positionen. Wiederholte Starts duplizieren die markierten Beispieldaten nicht. Neue Kundinnen und Kunden, Artikel und Rechnungen legst du danach über die Formulare der Anwendung an.

5b. Rechnungstutorial bearbeiten

Bearbeite die Dateien im Projektbaum von Studio und speichere deine Änderungen:

  • src/customers.zyl: Kundenfelder, Formular und Kundenliste.
  • src/items.zyl: Artikel, Beschreibungen und Nettopreise.
  • src/invoices.zyl: Rechnungsfelder, Positionen und Druckansicht.
  • src/dashboard.zyl: die tabellarische Rechnungsübersicht.
  • src/shell.zyl: gemeinsamer Seitenrahmen und Navigation.
  • zelyra.theme.css: zusätzliche lokale Gestaltung.

Studio zeigt Compilerdiagnosen beim Bearbeiten. Wähle danach main.zyl und klicke erneut auf Run, um die lokale Vorschau mit der Änderung zu starten. Geschäftsdaten gibst du über die Oberfläche ein: /customers, /items und /invoices; die Rechnungsübersicht ist aktuell unter /views/invoicedashboard erreichbar.

/invoices/new wird nicht als eigene page-Route deklariert. Die Deklaration crud Invoice -> invoices in src/invoices.zyl erzeugt automatisch die CRUD-Routen für den Tabellenpfad invoices, darunter GET /invoices/new für das Formular und POST /invoices/new zum Speichern. Der Name rechts von -> bestimmt also den URL-Basispfad. Die Druckroute page "/invoices/{invoice_number}/print" ist dagegen ausdrücklich im Quelltext eingetragen. Mit zelyra routes main.zyl lässt sich die Herkunft prüfen: Die Ausgabe nennt Methode, Pfad, Routentyp sowie Quelldatei und Zeile; /invoices/new verweist so auf die CRUD-Deklaration.

Die Route /views/invoicedashboard ist eine durchsuchbare und filterbare Tabellenansicht von Rechnungen mit berechneten Netto-, Steuer- und Bruttosummen. Sie ist keine zweite Dashboard-Startseite. Die Startseite / zeigt derzeit Kennzahlen und führt durch den Tutorialablauf. Die Trennung und Gestaltung dieser Ansichten ist zur Überarbeitung für 1.0.5 vorgemerkt.

Wenn du zelyra update ausführst, während Studio geöffnet ist, beende die Sitzung mit Ctrl+C und starte Studio danach neu. Bereits laufende IDE-Sitzungen laden eine aktualisierte Programmdatei nicht automatisch nach.

Wenn beim Laden oder Speichern eines Kunden „The requested data could not be loaded“ erscheint, prüfe zuerst, ob die lokale Datenbank gestartet wurde und das Schema-Setup erfolgreich war. zelyra db create main.zyl gibt SQL zur Prüfung aus, erstellt aber selbst weder die Datenbank noch Tabellen.

6. CRUD-Quelltext geführt erzeugen

Wenn eine Tabelle im Zelyra-Projekt deklariert ist, auch live vorhanden ist und noch keine CRUD-Ressource hat, kannst du den Assistenten verwenden. Wähle die Quelldatei, einen Ressourcennamen, einen Titel und die sichtbaren Felder. Studio zeigt die betroffenen Datei-, Tabellen- und Beziehungsinformationen sowie die erzeugten Routen an.

Der Ablauf hat drei Schritte:

  1. Auswahl: Bestimme die deklarierte Tabelle, die Quelldatei und die sichtbaren Formularfelder.
  2. Code prüfen und Vorschau zeigen: Studio lässt den normalen Projektcompiler den Kandidaten prüfen und zeigt den erzeugten Quelltext, den Diff und mögliche Diagnosen.
  3. Änderung übernehmen: Erst nach deiner ausdrücklichen Bestätigung wird die geprüfte Datei geschrieben. Direkt davor prüft Studio erneut den Datei-Hash. Hat sich der Quelltext geändert, verlangt es eine neue Vorschau.

So kannst du sehen, welche Änderung der Assistent vorschlägt, bevor sie in deinem Projekt landet. Das Anwenden erzeugt Quelltext. Es migriert weder die Datenbank noch legt es Datensätze an oder löscht sie.

7. Ein einfacher Arbeitsablauf

Für eine kleine Kundenverwaltung kannst du so vorgehen:

  1. Lege ein Projekt mit einer deklarierten Tabelle an oder öffne dein bestehendes Projekt.
  2. Starte Studio und prüfe, ob die Datenbankverbindung erreichbar ist.
  3. Sieh dir Tabelle und Fremdschlüssel in der Datenbankkarte an.
  4. Erzeuge die CRUD-Ressource in der Vorschau und lies den Diff.
  5. Übernimm die Änderung erst, wenn Datei, Felder und Routen deinen Erwartungen entsprechen.
  6. Prüfe das Projekt erneut mit zelyra check main.zyl und starte es nach Bedarf mit zelyra run main.zyl.

Für Kunden, Artikel und Rechnungen kannst du stattdessen das geführte CLI-Tutorial mit zelyra --tutorial invoice starten. Es führt dich Schritt für Schritt durch diese zusammenhängende Anwendung.

Rechnungstutorial verwenden

Das Tutorial enthält mehrere Quelldateien und führt von Kunden und Artikeln zu Rechnungen und verknüpften Positionen. Das Dashboard zählt gespeicherte Kunden und Rechnungen. Netto, 19% Umsatzsteuer und Brutto ergeben sich aus Positionen; die Summen enthalten auch Entwürfe und sind keine gebuchte Umsatzstatistik. Die Druckansicht kann über den Browser als PDF gespeichert werden. Tabler Core 1.6.1 wird lokal als CSS ausgeliefert, ohne CDN oder externes JavaScript. Die Layout-Stile in zelyra.css sind für responsive Ansichten optimiert, sodass das Hauptinhalts-Grid auch auf schmaleren Bildschirmen die volle Arbeitsbreite nutzt und nicht in einer schmalen Randspalte zusammenfällt. Das Beispiel ist keine fachlich oder rechtlich abgenommene Rechnungsanwendung und enthält keine native PDF-Engine.

8. Grenzen und Sicherheit

  • Studio läuft lokal und ist nicht als öffentlich erreichbarer Mehrbenutzer-Editor gedacht.
  • Die Datenbankkarte ist schreibgeschützt. Sie ist kein Drag-and-drop-Datenbankdesigner.
  • Der CRUD-Assistent arbeitet mit bereits deklarierten und live vorhandenen Tabellen; er entwirft kein beliebiges Datenmodell.
  • Datenbankunterstützung ist backend- und funktionsabhängig. Lies die Supportmatrix, bevor du einen Pfad einplanst.
  • Vorschau und Compilerprüfung helfen beim sicheren Ändern, beweisen aber nicht die Korrektheit der gesamten Anwendung.
  • Zelyra 1.0.4 ist ein stabiles Release. Prüfe für den konkreten Einsatz die dokumentierten Funktions-, Datenbank- und Sicherheitsgrenzen.

9. Übungsaufgaben

  • Stufe 1: Starte zelyra editor in einem Lernprojekt und öffne main.zyl.
  • Stufe 2: Öffne die Datenbankkarte und notiere eine deklarierte Beziehung. Ändere dabei keine Datenbankobjekte.
  • Stufe 3: Erzeuge eine CRUD-Vorschau, prüfe den Diff und vergleiche die dort aufgeführten Routen mit zelyra routes main.zyl.

10. Zusammenfassung

Studio bündelt Quelltextbearbeitung, Compilerfeedback, Schemaübersicht und eine geführte CRUD-Quelltextvorschau. Du behältst die Kontrolle über das ausdrückliche Anwenden; die Datenbankkarte bleibt lesend und der erzeugte CRUD-Code bleibt normaler Zelyra-Quelltext.


TECHNISCHES REFERENZHANDBUCH

1. Was Zelyra anders macht

In einer typischen Businessanwendung wird dieselbe Information mehrfach beschrieben: einmal in der Datenbank, noch einmal im Backend, erneut im Formular und schließlich in der API. Zelyra versucht, daraus eine einzige nachvollziehbare Kette zu machen:

Tabelle → Typen → SQL → Formulare → CRUD → Webseite → API/OpenAPI

Eine Spalte wie diese:

email: Email? 

sagt bereits:

  • Der Wert ist eine E-Mail-Adresse.
  • Der Wert darf fehlen.
  • SQL-Ergebnisse müssen diese Nullfähigkeit beachten.
  • Formulare können das passende Eingabefeld erzeugen.
  • Views müssen mit dem optionalen Wert vernünftig umgehen.

SQL bleibt dabei echtes SQL. Zelyra zwingt niemanden, einen anspruchsvollen JOIN in eine 38 Glieder lange Methodenkette zu verwandeln. SQL hat schon genug erlebt.

2. Installation

Voraussetzungen

Für Sprachbeispiele genügen:

  • Linux, macOS oder Windows (PowerShell/WSL);
  • curl;
  • eine funktionierende Shell.

MariaDB wird erst für Datenbank-, Formularaktions-, Auth- und CRUD-Beispiele benötigt.

Aus dem Repository installieren

git clone https://github.com/sf1976/zelyra.git
cd zelyra
./install.sh

Der Installer ist wiederholbar und benutzerlokal. Optionen zur Kontrolle:

./install.sh --help
./install.sh --dry-run --root "$HOME/.local"
./install.sh --check
./install.sh --uninstall

Mit --no-rustup wird die automatische Rust-Installation deaktiviert, --no-path unterdrückt PATH-Hinweise. Mit --root PATH oder ZELYRA_INSTALL_ROOT lässt sich ein anderes benutzerbezogenes Ziel wählen. Veraltete cargo-PATH-Einträge werden erkannt und nicht blind ausgeführt.

Veröffentlichte Releases für Linux x86_64 und Windows x86_64 können ohne Rust oder Cargo installiert werden. Das gewählte Archiv wird über HTTPS geladen und per SHA-256 geprüft:

./install.sh --release v1.0.5

Unter Windows steht install.ps1 für PowerShell und install.cmd für die Eingabeaufforderung bereit:

git clone https://github.com/sf1976/zelyra.git
Set-Location zelyra
.\install.ps1
zelyra --version

Das Release-Archiv unter Windows:

.\install.ps1 -Release v1.0.5

Danach:

zelyra --version
zelyra --help

Wenn die Shell zelyra nicht findet:

export PATH="$HOME/.local/bin:$PATH"

Docker und Container-Umgebung

Zelyra installiert Docker selbst nicht, verändert keine Betriebssystempakete und fordert keine Root-Rechte an. Für Container- und MariaDB-Workflows wird Docker mit Compose-Unterstützung benötigt:

Überprüfe die Docker-Umgebung vor dem ersten Start:

docker compose version

Ist Docker installiert, aber der Zugriff auf seinen Socket verweigert, meldet Zelyra einen sicheren Hinweis zur Linux-Gruppenmitgliedschaft (sudo usermod -aG docker $USER) statt der rohen Docker-Ausgabe. Auch Portkonflikte werden ohne Preisgabe von Zugangsdaten gemeldet.

Zelyra aktualisieren

Ein Update besteht aus zwei getrennten Teilen: Du aktualisierst den Zelyra- Compiler im Compiler-Repository und prüfst danach dein eigenes Anwendungsprojekt. Deine .zyl-Dateien, zelyra.toml und .env liegen in deinem Anwendungsprojekt und werden durch install.sh nicht überschrieben.

Installation aus dem Git-Repository

Arbeite zuerst im Compiler-Repository. Prüfe lokale Änderungen, bevor du sie aktualisierst:

cd /pfad/zu/zelyra
git status --short
git pull --ff-only origin main
cargo check --workspace
./install.sh
zelyra doctor /pfad/zu/deinem-projekt/main.zyl --json

git pull --ff-only bricht ab, wenn du lokale Änderungen oder eine eigene Historie hast. Sichere oder committe diese Änderungen zuerst und führe das Update danach erneut aus. ./install.sh baut die aktuelle CLI-Version und installiert sie mit cargo install --path cli --force erneut in deinem Benutzerverzeichnis.

Die installierte Versionsnummer kannst du über zelyra --version oder den JSON-Bericht von doctor ablesen:

zelyra --version
zelyra doctor /pfad/zu/deinem-projekt/main.zyl --json

3. Das erste Programm

Datei hello.zyl:

fn main() {
    print("Hallo von Zelyra")
}

Ausführen:

zelyra run hello.zyl

Ausgabe:

Hallo von Zelyra

Ein etwas ehrgeizigeres Beispiel:

fn fibonacci(n: Int) -> Int {
    if n <= 1 {
        return n
    }

    return fibonacci(n - 1) + fibonacci(n - 2)
}

fn main() {
    print(fibonacci(10))
}

Ergebnis: 55. Der Rechner hat es geschafft. Wir dürfen fortfahren.

4. Neues Projekt anlegen und CLI

Compiler-Repository und Anwendungsprojekt

Das GitHub-Repository sf1976/zelyra ist das Compiler-Repository. Es enthält Lexer, Parser, Runtime, Datenbank- und Webmodule sowie das CLI. Eine eigene Anwendung ist ein davon getrenntes Verzeichnis. Du musst also nicht in der Zelyra-Quelle arbeiten und solltest dort auch keine Zugangsdaten ablegen.

Der Projektstamm ist das Verzeichnis, in dem die Zelyra-Quelldatei und – wenn vorhanden – zelyra.toml liegen. Der Speicherort ist frei wählbar, zum Beispiel ~/projekte/adressverwaltung oder C:\\Users\\Du\\Projekte\\adressverwaltung.

✅ Projekt mit dem vorhandenen CLI anlegen

Voraussetzungen für die CLI-Installation sind Rust/Cargo oder ein Release-Archiv; für relationale Datenbankbefehle der externe mariadb-Client oder Docker. Das CLI bietet aktuell diese Projektbefehle:

zelyra new adressverwaltung
cd adressverwaltung
zelyra run main.zyl

Ein vorhandenes Verzeichnis initialisieren:

mkdir adressverwaltung
cd adressverwaltung
zelyra init

Für eine lokale MariaDB- und Webserver-Vorlage zelyra new maschinenverwaltung --mariadb verwenden:

zelyra new maschinenverwaltung --mariadb
cd maschinenverwaltung

Dadurch entstehen main.zyl, .env.example, Dockerfile und docker-compose.mariadb.yml sowie direkt eine geschützte .env mit sicheren Zufallspasswörtern.

Automatische Portvergabe bei Konflikten: Sind die Standardports 3000 (Web) oder 3306 (MariaDB) auf dem Rechner belegt, ermitteln zelyra new, zelyra init und zelyra setup automatisch den nächsten freien Host-Port und tragen ihn in die neue .env ein. Mit den optionalen Flags --web-port <p>, --host-port <p> und --db-host-port <p> können Ports verbindlich vorgegeben werden.

✅ Zelyra-Setup-Assistent (Konsole und Browser)

zelyra setup bietet konsistente Einrichtungsaktionen sowohl über die Befehlszeile als auch über eine benutzerfreundliche Weboberfläche:

Setup auf der Konsole

Aus einem MariaDB-Projektverzeichnis:

zelyra setup
zelyra setup --database
zelyra setup --schema
zelyra setup --all
zelyra setup --host-port 18080 --db-host-port 3308
  • zelyra setup: Legt eine geschützte .env an, wenn sie fehlt. Vorhandene .env-Dateien werden niemals überschrieben.
  • zelyra setup --database: Startet die Docker-Compose-Dienste (MariaDB und Zelyra-App). Erkennt automatisch docker compose oder den Legacy-Befehl docker-compose.
  • zelyra setup --schema: Startet die Umgebung und wendet das Schema aus main.zyl sicher an.
  • zelyra setup --all: Führt alle Schritte vollautomatisch in einem Zug aus.

Wähle den Projekttyp vor dem Setup:

  • Ein minimales Projekt ohne Datenbank braucht kein setup:

    zelyra init ./test_a
    cd ./test_a
    zelyra check main.zyl
    zelyra run main.zyl
    
  • Für ein neues MariaDB-Projekt erzeugt zelyra new test_a --mariadb die benötigten Compose- und Konfigurationsdateien. Danach kannst du mit cd test_a && zelyra setup --all Datenbank und Schema einrichten. Alternativ unterstützt zelyra init ./test_a --mariadb die MariaDB-Option beim Init.

Projektpfade sind relativ oder absolut, nicht austauschbar:

zelyra setup ./test_a
zelyra setup /test_a

Der erste Aufruf sucht ein Unterverzeichnis des aktuellen Ordners, der zweite das Verzeichnis /test_a im Wurzelverzeichnis. Existiert der angegebene Pfad nicht, meldet Zelyra E-SETUP-001; wenn ein gleichnamiges relatives Verzeichnis existiert, schlägt die Meldung den passenden relativen Aufruf vor.

Setup im lokalen Browser (zelyra setup --web)

zelyra setup --web

Der Assistent bindet standardmäßig ausschließlich an 127.0.0.1:3030 und erzeugt eine sichere, einmalige URL mit einem Zufallstoken:

Zelyra setup web is running on http://127.0.0.1:3030/
open: http://127.0.0.1:3030/?token=<local-token>
  • Der Browser bietet dieselben Aktionen wie die Konsole: Konfiguration vorbereiten, MariaDB und Anwendung starten, Schema anwenden oder alles auf Knopfdruck ausführen.
  • Der Server ist aus Sicherheitsgründen nur lokal erreichbar und darf nicht öffentlich exponiert werden.
  • Ist Port 3030 belegt, wählt der Assistent automatisch den nächsten freien Port (oder wird mit --port <port> fest vorgegeben).
  • Nach Abschluss beendet Ctrl+C den Assistenten.

Nach dem Start prüft zelyra doctor den Status ohne destruktive Datenbankänderungen:

zelyra doctor main.zyl --env-file .env

Für ein vollständiges CRUD-Starterprojekt mit Geschäftslogik:

zelyra new maschinenverwaltung --template mariadb-crud
cd maschinenverwaltung
zelyra setup --all

Prüfen, ausführen, formatieren und verifizieren

Das dokumentierte, benutzerseitige Kommandozeilenprogramm heißt zelyra. Mit zelyra check <file.zyl> prüfst du Syntax, Typen, Verträge und Capabilities, bevor du ein Programm mit zelyra run <file.zyl> ausführst. zelyra fmt <file.zyl> formatiert den Quelltext; --check prüft die Formatierung, ohne die Datei zu ändern. zelyra verify <file.zyl> führt die unterstützten Vertrags- und Invariantenprüfungen aus.

Die aktuelle CLI-Referenz führt keinen Unterbefehl zelyra test auf. Projektbezogene Testroutinen kannst du in einer ausführbaren Quelldatei ablegen und mit zelyra run starten; assertionsbasierte Prüfungen beschreibt Kapitel 19. Ebenso dokumentiert die Referenz kein separates Programm zelyrac build: Der Compiler wird über zelyra check und zelyra run aufgerufen.

LSP steht für Language Server Protocol und bezeichnet den Standard für die Kommunikation zwischen Editor und Sprachserver. Der dokumentierte Einstieg in den Editor ist zelyra editor, das Studio lokal startet. Einen separaten Unterbefehl zelyra lsp führt die aktuelle CLI-Referenz nicht auf.

5. Variablen, Typen und Funktionen

Werte sind standardmäßig unveränderlich:

machine_name = "Presse 7"
capacity: Int = 120
active = true

Veränderung muss sichtbar sein:

mutable completed = 0
completed = completed + 1

Das verhindert versehentliche Änderungen. Der Compiler ist dabei nicht misstrauisch; er hat nur schon Dinge gesehen.

Wichtige Typen:

Int UInt Float Decimal Bool String Char Bytes
Timestamp Date Time Duration Email Url Uuid Money

Funktionen:

fn available_capacity(total: Int, reserved: Int) -> Int {
    return total - reserved
}

Typnamen dokumentieren die fachliche Bedeutung von IDs:

type MachineId = Id
type OrderId = Id

Diese Aliase besitzen keine eigene Runtimeidentität. Prüfe ID-Zuordnungen und Mandantengrenzen in der Geschäftslogik; die Bezeichnung allein beweist keinen Zugriffsschutz.

6. Option, Result und Pattern Matching

Normale Typen sind nicht null. Ein möglicher fehlender Wert wird markiert:

email: Email?

Behandlung:

match email {
    Some(value) => print(value)
    None => print("Keine E-Mail hinterlegt")
}

Pattern Matching muss vollständig sein. Sonst erinnert dich der Compiler an den Fall, den der Freitagabend-Deploy vermutlich gefunden hätte.

Fehler werden als Teil der Funktionssignatur sichtbar. Der aktuelle Sprachkern verwendet dafür Result<T, E> mit Ok oder Err:

fn load_number(found: Bool) -> Result<Int, String> {
    if found {
        return Ok(42)
    }
    return Err("nicht gefunden")
}

7. MariaDB und Tabellen

✅ MariaDB ist das Standardbackend und die primäre Runtime-Referenz. Die generierte Compose-Vorlage verwendet mariadb:11; der Compiler erzwingt aber keine konkrete MariaDB-Serverversion. Die Datenbankbefehle rufen den externen mariadb-Client auf.

database main {
    engine: mariadb
}

table departments {
    id: Id primary auto
    name: String(100) required unique
}

table machines {
    id: Id primary auto
    number: String(30) required unique
    name: String(100) required
    department: Department required
    active: Bool default true
}

Zelyra erkennt die Beziehung zwischen Maschine und Abteilung. Daraus können Foreign Keys, Formulare und Auswahlfelder entstehen.

Typische Abbildung:

Zelyra MariaDB
Id primary auto automatisch vergebene Primär-ID
String(100) VARCHAR(100)
String TEXT
Email E-Mail-kompatible Textspalte
Bool boolescher Datenbankwert
Timestamp Zeitstempelwert

MariaDB sicher vorbereiten

Lege für die Anwendung eine eigene Datenbank und einen eigenen Benutzer an. Verwende nicht den MariaDB-Account root für den laufenden Zelyra-Webserver. Die folgenden Befehle werden als administrativer MariaDB-Benutzer ausgeführt; das Passwort wird interaktiv abgefragt:

mariadb --host=127.0.0.1 --port=3307 --user=root --password
CREATE DATABASE `adressverwaltung`
    CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

CREATE USER 'zelyra'@'127.0.0.1'
    IDENTIFIED BY 'HIER_LOKALES_PASSWORT_EINTRAGEN';

GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, REFERENCES
    ON `adressverwaltung`.* TO 'zelyra'@'127.0.0.1';

SHOW GRANTS FOR 'zelyra'@'127.0.0.1';

CREATE DATABASE kann bei einer bereits vorhandenen Datenbank mit IF NOT EXISTS wiederholbar gemacht werden. Die Rechte sind bewusst auf diese Datenbank begrenzt; GRANT ALL ON *.* gehört nicht in einen Anwendungs- Schnellstart. InnoDB ist die für Transaktionen und Fremdschlüssel erwartete Storage-Engine. Die aktuelle Zelyra-SQL-Ausgabe setzt ENGINE=InnoDB jedoch nicht selbst; kontrolliere und ergänze das SQL vor dem Anwenden, wenn deine Servervorgaben es verlangen.

Für reine Lese- und normale CRUD-Operationen reichen die aufgeführten Rechte. db apply --allow-destructive kann zusätzlich DROP-Rechte benötigen; erteile sie nur bewusst und möglichst zeitlich begrenzt. db setup ist ein Administratorvorgang, weil dabei die Datenbank selbst angelegt wird.

Lokal kann MariaDB bereits auf Port 3306 laufen. Die generierte Compose-Datei veröffentlicht standardmäßig 127.0.0.1:3306. Wenn dieser Port belegt ist, ändere die Zuordnung auf 127.0.0.1:3307:3306: außen ist dann 3307, im Container bleibt MariaDB auf 3306. Von einem anderen Compose-Service ist der Host der Servicename mariadb und der Port weiterhin 3306; vom Host ist es 127.0.0.1 plus der veröffentlichte Port.

Das Adressschema kontrollieren

Für das Beispiel erzeugt zelyra db create src/main.zyl derzeit sinngemäß:

CREATE TABLE IF NOT EXISTS `addresses` (
    `id` BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT,
    `first_name` VARCHAR(100) NOT NULL,
    `last_name` VARCHAR(100) NOT NULL,
    `street` VARCHAR(150) NOT NULL,
    `postal_code` VARCHAR(10) NOT NULL,
    `city` VARCHAR(100) NOT NULL,
    `email` VARCHAR(255)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Für MariaDB enthalten die erzeugten Tabellenaussagen ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci. Damit sind sie für die dokumentierte MariaDB-Basis vollständig; sie bleiben dennoch ein zu prüfender Schema-Vorschlag. Vor Änderungen an einer bestehenden Datenbank weiterhin db plan lesen und das Risiko ausdrücklich bewerten.

Weitere Datenbank-Backends

Neben MariaDB kann das Schema-CLI derzeit auch SQLite und PostgreSQL verarbeiten. Das Backend wird in der .zyl-Datei angegeben:

database main { engine: sqlite database: "adressverwaltung.sqlite3" }
database main { engine: postgres database: "adressverwaltung" }

Für SQLite verwendet DATABASE_URL das Schema sqlite:// oder sqlite:; für MariaDB sind mariadb:// und der kompatible Name mysql:// gültig. Die Schemaoperationen create, inspect, plan und apply berücksichtigen Tabellen, Spalten, Foreign Keys und Indizes. db setup und db bootstrap unterstützen für das automatische Anlegen derzeit MariaDB und SQLite; für PostgreSQL verwendest du das gewünschte Schema mit db create, db inspect, db plan und anschließend db apply.

export DATABASE_URL='sqlite:///tmp/adressverwaltung.sqlite3'
zelyra db bootstrap src/main.zyl
zelyra db inspect src/main.zyl

Die native SQL-Runtime wird weiterhin hauptsächlich mit MariaDB eingesetzt. Ein vorhandenes Schema-Backend ist daher nicht automatisch ein Beleg dafür, dass jede Runtime-Abfrage mit jedem Backend gleich funktioniert.

8. Schema prüfen und anwenden

Zelyra verwendet keine Migrationsklassen. Die Quelldatei ist das gewünschte Schema; das CLI vergleicht dieses mit dem Ist-Zustand. Der aktuelle Ablauf ist:

  1. Quelldatei prüfen: zelyra check src/main.zyl.
  2. SQL nur anzeigen: zelyra db create src/main.zyl.
  3. Ist-Schema lesen: zelyra db inspect src/main.zyl.
  4. Unterschied planen: zelyra db plan src/main.zyl.
  5. Plan lesen und Risiko bewerten.
  6. Nach der Kontrolle anwenden: zelyra db apply src/main.zyl.

Die Verbindung kommt ausschließlich aus der Prozessumgebung:

export DATABASE_URL='mariadb://zelyra:HIER_LOKALES_PASSWORT_EINTRAGEN@127.0.0.1:3307/adressverwaltung'

⚠️ Zelyra lädt .env derzeit nicht automatisch. Eine .env ist eine sichere lokale Ablage, aber die Variablen müssen vor dem CLI-Aufruf in die Prozessumgebung gelangen. Siehe Abschnitt 16.

Schema-SQL anzeigen:

zelyra db create src/main.zyl > sql/addresses.generated.sql

Wenn du diese Datei nach der Kontrolle manuell um ENGINE=InnoDB und die Zeichensatzklauseln ergänzt hast, wendest du genau diese Datei mit dem MariaDB-Client an:

mariadb --host=127.0.0.1 --port=3307 --user=zelyra --password \
    adressverwaltung < sql/addresses.generated.sql

zelyra db apply liest keine von dir bearbeitete SQL-Datei ein; es erzeugt seinen Plan erneut aus der .zyl-Quelle. Verwende deshalb entweder den unveränderten Zelyra-Plan oder den manuellen Clientweg – nicht beides blind hintereinander.

Bedingt durch die aktuelle Implementierung kann db plan auch ohne DATABASE_URL gegen ein leeres Schema planen. Für eine echte Bestandsaufnahme und für db apply ist die Variable Pflicht:

zelyra db inspect src/main.zyl
zelyra db plan src/main.zyl
zelyra db apply src/main.zyl

Destruktive Änderungen werden abgelehnt, bis sie ausdrücklich freigegeben werden:

zelyra db apply src/main.zyl --allow-destructive

Dieses Flag bedeutet nicht „wird schon gutgehen“. Es bedeutet „ich habe den Plan gelesen, ein Backup und einen vernünftigen Puls“.

db setup und sein Alias db bootstrap versuchen bei MariaDB zuerst die Datenbank anzulegen und wenden anschließend das generierte Schema an. Dafür braucht DATABASE_URL administrative Rechte. Ein gewöhnlicher Anwendungsbenutzer mit begrenzten Rechten sollte stattdessen db create verwenden und die SQL-Schritte durch einen Administrator ausführen lassen.

❌ Nicht vorhanden sind zelyra db check sowie zelyra schema inspect, zelyra schema plan und zelyra schema apply. Der tatsächlich vorhandene Bereitschaftstest ist zelyra doctor src/main.zyl; er prüft statische Regeln, Cargo, DATABASE_URL (wenn gesetzt) und den lokalen Webport.

In 10 Minuten zur ersten Zelyra-Anwendung

Die folgenden Schritte verwenden überall dieselben Werte: das Verzeichnis adressverwaltung, die Datei src/main.zyl, die Datenbank adressverwaltung, den Benutzer zelyra und den Host-Port 3307.

  1. Projektverzeichnis und Vorlage anlegen:

    zelyra new adressverwaltung --mariadb
    cd adressverwaltung
    cp .env.example .env
    
  2. In .env die Platzhalter setzen. Wichtig: Compose liest die MARIADB_*-Werte beim Containerstart; Zelyra selbst liest nur DATABASE_URL. Ändere für den Host-Port in der Compose-Datei die Bindung auf 127.0.0.1:3307:3306 und verwende lokal 3307.

  3. MariaDB starten:

    docker compose -f docker-compose.mariadb.yml up -d mariadb
    docker compose -f docker-compose.mariadb.yml ps
    
  4. Als MariaDB-Administrator Datenbank und Benutzer vorbereiten; verwende das SQL aus Abschnitt 7 und nur ein lokales Platzhalterpasswort.

  5. Die gültige Adressverwaltung als src/main.zyl speichern und prüfen:

    zelyra check src/main.zyl
    
  6. Die URL in die Prozessumgebung laden und die Verbindung testen. .env wird von Zelyra nicht automatisch geladen:

    set -a
    . ./.env
    set +a
    zelyra doctor src/main.zyl --json
    
  7. Das gewünschte SQL erzeugen und vor dem Einsatz lesen:

    zelyra db create src/main.zyl > sql/addresses.generated.sql
    sed -n '1,160p' sql/addresses.generated.sql
    
  8. Ist-Schema und Plan ansehen:

    zelyra db inspect src/main.zyl
    zelyra db plan src/main.zyl
    
  9. Nach manueller Prüfung anwenden:

    zelyra db apply src/main.zyl
    
  10. Eine Webanwendung starten. Für eine CRUD-Route braucht die Quelldatei zusätzlich eine vom Server akzeptierte Webdefinition; crud allein ist noch kein statischer Export:

    zelyra serve src/main.zyl 127.0.0.1:3000
    

Unter Windows ersetze cp durch Copy-Item und lade Variablen beispielsweise so in PowerShell:

Copy-Item .env.example .env
$env:DATABASE_URL = 'mariadb://zelyra:HIER_LOKALES_PASSWORT_EINTRAGEN@127.0.0.1:3307/adressverwaltung'
zelyra doctor src/main.zyl --json

Der Generator bindet in der ausgelieferten Compose-Vorlage standardmäßig Port 3306; 3307 ist dieses Handbuchs ein bewusst gewählter, kollisionsarmer Host-Port. Passe die Compose-Portzeile an, bevor du den Schnellstart kopierst.

9. Natives SQL

✅ SQL ist ein Sprachelement:

fn load_active_machines() -> Machine[]
    uses Database
{
    return sql<Machine[]> {
        SELECT id, number, name, department_id, active
        FROM machines
        WHERE active = true
        ORDER BY number
    }
}

Parameter werden benannt und sicher gebunden:

fn load_machine(id: MachineId) -> Machine?
    uses Database
{
    return sql<Machine?> {
        SELECT id, number, name, department_id, active
        FROM machines
        WHERE id = :id
    }
}

Zelyra prüft, soweit das Schema bekannt ist:

  • Tabellen und Spalten;
  • Aliase;
  • Parameter;
  • Nullfähigkeit;
  • Ergebniszuordnung;
  • erforderliche Database-Capability.

Schreibzugriff in einer Transaktion:

transaction {
    sql {
        UPDATE machines
        SET active = false
        WHERE id = :id
    }
}

10. Webseiten

✅ Eine vollständige, sichere Webschicht mit typisierten Datenbindungen, Query-Steuerungen und Komponenten ist implementiert.

page "/machines/{name}" {
    html {
        <html>
            <body>
                <h1>Maschine {name}</h1>
                <p>Sie läuft. Hoffentlich nicht weg.</p>
            </body>
        </html>
    }
}

Start:

zelyra serve app.zyl

Dann beispielsweise:

http://127.0.0.1:3000/machines/Presse-7

Pfadwerte werden standardmäßig HTML-escaped. Der derzeitige Web Core umfasst GET-Routen, Pfadparameter, Query-String-Behandlung, HTTP-Parsing und HTML-Antworten.

Typisierte Datenbindung und Collections in Views

View-Interpolationen werden vor dem Serverstart statisch validiert. Eine Seite kann Routenparameter nutzen, geladene Datensätze über typisiertes SQL abrufen und auf deren Felder geprüft zugreifen:

page "/customers/{name}" {
    load customer = sql<Customer> {
        SELECT id, name FROM customers WHERE name = :name
    }
    html { <h1>{customer.name}</h1> }
}

SQL wird gegen das Schema geprüft, Parameter werden sicher gebunden und Capabilities sowie Berechtigungen vorab erzwungen. Collections können mit einer typisierten serverseitigen Schleife gerendert werden:

page "/customers" {
    load customers = sql<Customer[]> { SELECT id, name FROM customers }
    html { <ul>for customer in customers { <li>{customer.name}</li> }</ul> }
}

Deklarative Query-Steuerungen: Suche, Sortierung, Pagination und Filter

Seiten können typisierte Query-Eingaben für ausdrückliches SQL deklarieren:

page "/customers" {
    input { search: String? }

    load customers = sql<Customer[]> {
        SELECT id, name FROM customers
        WHERE (:search IS NULL OR name LIKE CONCAT('%', :search, '%'))
        ORDER BY name
    }

    html { <p>Suche: {search}</p> }
}

Für Page-Collections, die Suche, Filterung, Sortierung oder Pagination deklarieren, erzeugt Zelyra automatisch semantische Formulare und bewahrt den URL-Zustand:

page "/customers" {
    search { name email }
    sort { name }
    paginated 25
    filter { name quantity }
    load customers = sql<Customer[]> { SELECT id, name, quantity FROM customers }
    html {
        <p>Seite: {page} von {pages} (Gesamt: {total})</p>
        <p>Sortierung: {sort} ({order})</p>
    }
}
  • search { name email }: Der Compiler prüft die Whitelist der durchsuchbaren Felder. Suchbegriffe werden sicher parametrisiert als LIKE-Bedingungen gebunden.
  • sort { name }: Akzeptiert nur deklarierte Ergebnisfelder, order nur asc oder desc (z. B. /customers?sort=name&order=desc).
  • paginated 25: Validiert page als positive Ganzzahl, nutzt parametrisiertes LIMIT/OFFSET und stellt page, pages und total als UInt-Werte bereit.
  • filter { ... }: Unterstützt typisierte Operatoren: Textfelder unterstützen eq, contains, starts_with, ends_with und Null-Checks; numerische Felder zusätzlich gt, gte, lt, lte. Beispiele: /customers?filter_name__contains=Acme oder /customers?filter_quantity__gte=10. Unbekannte Felder und ungültige Werte werden mit kontrolliertem HTTP 400 beantwortet.

Benannte Views und Komponenten

Ein benannter View bietet ein wiederverwendbares Seiten-Layout. Er deklariert Slots, in die Seiteninhalte eingefügt werden:

view SiteShell {
    html {
        <html><body><main><slot /></main></body></html>
    }
}

page "/customers" {
    view: SiteShell
    html { <h1>Customers</h1> }
}

Typisierte Komponenten deklarieren Eigenschaften mit props:

component Badge {
    props { text: String }
    html { <span class="badge">{text}</span> }
}

page "/status" {
    html { <Badge text="Ready" /> }
}

Komponenten unterstützen Default-Slots und benannte Slots mit Fallback-Inhalten:

component Panel {
    html {
        <section class="panel">
            <header><slot name="header">Standard-Kopfzeile</slot></header>
            <div class="body"><slot /></div>
        </section>
    }
}

page "/dashboard" {
    html {
        <Panel>
            <slot name="header"><h1>Mein Dashboard</h1></slot>
            <p>Hauptinhalt des Panels.</p>
        </Panel>
    }
}

Hinweis: Verschachtelte Komponenten-Slots innerhalb einer Komponentenverwendung benötigen nicht fälschlich ein übergeordnetes Seiten-view:-Layout.

11. Formulare

✅ Formulare können Regeln aus Tabellen übernehmen:

form MachineCreate -> machines {
    fields {
        number
        name
        department
        active
    }
}

Explizite Definition:

form ContactForm {
    field email: Email {
        label: "E-Mail"
        required
        max: 255
        widget: email
    }
}

Werte ohne Server prüfen:

zelyra form validate examples/customer_form.zyl CustomerCreate \
    name="Muster GmbH" email=info@example.test

Mit zelyra serve stellt Zelyra das Formular unter /forms/FormName bereit. GET rendert das Formular samt CSRF-Token; POST prüft Token und Werte.

Eine Aktion:

form CustomerCreate -> customers {
    fields { name email }

    action save {
        requires auth
        permits "customers.save"
        sql {
            INSERT INTO customers (name, email)
            VALUES (:name, :email)
        }

        redirect "/customers"
    }
}

Formularaktionen können eine eigene Autorisierung deklarieren. Die Berechtigung wird beim Anzeigen des Formulars und erneut vor dem Absenden geprüft.

12. CRUD

✅ Der kurze Fall ist erfreulich kurz:

crud Machine -> machines

Konfiguriert:

crud Machine -> machines {
    title: "Maschinen"
    list { number name department active }
    search { number name }
    filter { department active }
}

Zelyra stellt Listen, Details, Create/Edit-Formulare, Suche, Filter, Sortierung, Pagination und eine CSRF-geschützte Löschaktion bereit. Spalten werden gegen das Schema geprüft.

Beispiele für URLs:

/machines
/machines?search=Presse
/machines?filter_active=true
/machines?sort=number&order=asc
/machines/new
/machines/42/edit

CRUD-Ressourcen mit wiederverwendbaren View-Layouts (layout: ViewName)

CRUD-Definitionen können über das Attribut layout: ViewName in das globale Seitenlayout eingebunden werden:

view AppLayout {
    html {
        <html>
            <head><title><slot name="title">Verwaltung</slot></title></head>
            <body>
                <nav><slot name="nav">Standard-Navigation</slot></nav>
                <main><slot name="content" /></main>
                <aside><slot name="actions" /></aside>
            </body>
        </html>
    }
}

crud Machine {
    table machines
    layout: AppLayout
}

Das System befüllt die benannten Slots title, nav, content und actions automatisch mit den generierten CRUD-Ansichten.

Erzeugte CRUD- und Tableview-Steuerungen verwenden semantische Fieldsets und getrennte Beschriftungen für Operator und Wert jedes Filters. Filterverarbeitung und bewahrte Pagination-URLs verwenden eine deterministische Reihenfolge.

🗺️ Vollständig eigene typisierte Komponenten und feingranulare View-Overrides sind Teil der weiteren View-Roadmap.

CRUD-Ansichten, Aktionen und Soft Delete

🧪 Die aktuelle CRUD-Schicht lässt sich innerhalb der sicheren Standardpfade gezielt anpassen. Eine Listenansicht kann beispielsweise als Kartenansicht erscheinen, ohne Suche, Filter, Sortierung, Pagination, Escaping oder Berechtigungsprüfung zu verlieren:

crud Customer -> customers {
    view {
        list {
            mode: cards
            empty: "Keine Kunden gefunden."
        }
        detail {
            mode: cards
            title: "Kundendetails"
        }
        form {
            mode: cards
            title: "Kundenformular"
            submit: "Kunden speichern"
        }
        delete {
            title: "Kunden löschen"
            message: "Dieser Vorgang kann nicht rückgängig gemacht werden."
            submit: "Jetzt löschen"
        }
        loading { message: "Kunden werden geladen ..." }
        error {
            title: "Kunden nicht verfügbar"
            message: "Bitte später erneut versuchen."
        }
    }
}

Für den häufigen Fall kann ein gemeinsames Feldprofil die erzeugte Liste, Detailansicht sowie Create-/Edit-Formulare einheitlich steuern:

crud Customer -> customers {
    view {
        fields { name email active }
    }
}

Ein explizites list { ... } bleibt eine Überschreibung für Liste/Detail. Primärschlüssel und automatisch erzeugte Felder bleiben in Formularen automatisch ausgeschlossen; unbekannte Profilfelder weist der Compiler zurück. Wenn die technische id-Spalte in fields ausgeblendet ist (wie im obigen Beispiel mit name email active), verlinkt Zelyra in generierten CRUD-Listen automatisch das erste angezeigte Feld (name) mit der Detailseite des Datensatzes. Das gilt sowohl für Tabellen- als auch für Kachel-Layouts.

Eigene fachliche Aktionen bleiben POST-only, parametrisiert und geschützt. Eine Action kann mehrere SQL-Anweisungen bündeln, die in einer gemeinsamen MariaDB-Transaktion atomar ausgeführt werden:

crud Invoice -> invoices {
    action lock {
        label: "Festschreiben (GoBD)"
        confirm: "Möchten Sie diese Rechnung unwiderruflich festschreiben?"
        permits "invoices.lock"
        field reason: String required
        sql {
            UPDATE invoices SET is_locked = true WHERE id = :id
            INSERT INTO invoice_audit_logs (invoice_id, action, message) VALUES (:id, "locked", :reason)
        }
        success "Rechnung wurde festgeschrieben."
        redirect "/invoices"
    }
}

Ausführungsmodell und Transaktionsverhalten von Actions

  • Atomare Datenbanktransaktion: Alle SQL-Anweisungen innerhalb von sql { ... } laufen gebündelt in einer einzigen MariaDB-Transaktion (with_decision_transaction). Schlägt eine Anweisung fehl (z. B. durch einen Constraint-Fehler), wird die gesamte Transaktion atomar zurückgerollt.
  • Parameterbindung: Parameter wie :id werden automatisch aus dem URL-Pfad der CRUD-Ressource gebunden. Über field <name>: <Typ> deklarierte Zusatzfelder werden im Bestätigungsdialog als typisierte Eingabefelder gerendert, vorab geprüft und als Parameter :name an die SQL-Statements übergeben.
  • Aktueller Ausführungsumfang: Seit 1.2.28 unterstützen CRUD-Actions typisierte Handler, Datensatz- und Sitzungsdaten sowie geprüfte Guards im vorhandenen serverseitigen Transaktionskontext. Zulässige Ausdrücke und Effekte folgen der dokumentierten Action-Syntax; Actions sind keine beliebigen Zelyra-Programme.

Die Laufzeit erzwingt für solche Aktionen Datenbank-Capability, CSRF-Schutz, Authentifizierung und die deklarierte Berechtigung. Aktionen können zusätzlich typisierte Felder, eine Bestätigungsseite sowie eigene Erfolgs- und Fehlerseiten erhalten. Das ist eine Erweiterung der vorhandenen CRUD-Runtime, kein frei programmierbarer Frontend-Generator.

Für reversible Löschungen gibt es eine Soft-Delete-Konfiguration:

table customers {
    id: Id primary auto
    name: String(100) required
    deleted_at: Timestamp?
}

crud Customer -> customers {
    soft_delete { column: deleted_at }
}

Normale Listen und Details zeigen nur Zeilen mit NULL; archivierte Datensätze sind über ?archived=true erreichbar und können über eine CSRF-geschützte Restore-Aktion wiederhergestellt werden. Endgültiges Löschen, Aufbewahrungsregeln und Massenarchivierung sind weiterhin geplant.

Wenn eine Auth-Definition eine Audit-Tabelle angibt, schreiben CRUD-Erstellen, Ändern, Löschen, Archivieren, Wiederherstellen und eigene Aktionen ihre Ereignisse in derselben MariaDB-Transaktion. Passwörter, Tokens, Geheimnisse und Hashes werden aus Änderungsdetails entfernt.

13. Authentifizierung und Berechtigungen

🧪 Zelyra unterstützt Argon2-Login, persistente MariaDB-Sessions, Logout, Routenschutz und datenbankgestützte Berechtigungsprüfungen. Fünf Fehlversuche für dieselbe normalisierte E-Mail-Adresse innerhalb von 15 Minuten lösen eine 60-sekündige HTTP-429-Sperre aus. Ein erfolgreicher Login rotiert das vorherige Session-Token dieses Browsers und entwertet es.

Jedes schreibende Browserformular benötigt sein CSRF-Token sowie einen gleichursprünglichen Origin- oder Referer-Header, der zu Host und dem effektiven Schema der Anfrage passt. Fehlende, fehlerhafte oder fremde Angaben werden abgelehnt. Ein aus einem anderen Browser kopiertes Token reicht somit nicht für eine Website-übergreifende Formularanfrage. Schreibende API-Anfragen mit Browser-Origin-Angaben durchlaufen dieselbe Prüfung; Website-übergreifender API-Zugriff ist nur für einen exakt in der CORS-Richtlinie freigegebenen Origin möglich. API-Anfragen mit Browser-Origin-Angaben oder Browser-Session-Cookie durchlaufen diese Prüfung ebenfalls. Das gilt auch für GET, weil Handler noch nicht statisch auf schreibgeschütztes Verhalten beschränkt sind. Website-übergreifende API-Aufrufe sind nur für einen exakt in der CORS-Richtlinie freigegebenen Origin möglich; mit Session-Cookie muss CORS zusätzlich Credentials erlauben. Das aktuelle CSRF-Token gilt pro Prozess und wird noch nicht einzeln pro Session gespeichert; deshalb sind diese Origin-Prüfungen ein notwendiger Bestandteil des Schutzes.

Der Zelyra-Server spricht derzeit ausschließlich unverschlüsseltes HTTP. Schalte ihm einen vertrauenswürdigen TLS-terminierenden Proxy vor, bevor die Anwendung außerhalb eines lokalen Entwicklungsrechners erreichbar ist. Der Proxy muss den öffentlichen Host erhalten, X-Forwarded-Proto mit dem tatsächlichen externen Schema überschreiben und direkten öffentlichen Zugriff auf den Anwendungsport verhindern. Zelyra verwendet den Header für die Prüfung des effektiven Origins und setzt bei HTTPS das Cookie-Attribut Secure. Vertraue an einer öffentlich erreichbaren Proxy-Grenze niemals ungeprüften, vom Client gelieferten Forwarded-Headern.

Zusätzlich prüft der Server jeden vorhandenen Host-Header gegen ZELYRA_ALLOWED_HOSTS. Der Standard erlaubt nur localhost, 127.0.0.1 und [::1]; das verhindert unter anderem DNS-Rebinding über frei gewählte Hosts. Mehrfach vorhandene sicherheitsrelevante Request-Header wie Host, Origin, Referer, Cookie und Authorization werden abgelehnt, damit keine mehrdeutige Auswertung entsteht. Die Antwort-Policy Referrer-Policy: same-origin ermöglicht gleichursprünglichen API-GETs diesen Nachweis, sendet aber keine Referrer-Informationen an andere Origins. Für eine eigene Domain oder einen LAN-Host muss der tatsächliche Hostname explizit in der kommagetrennten .env-Einstellung ergänzt werden. Es werden keine Schemes, Ports oder Wildcards akzeptiert. Prozessumgebung hat Vorrang vor Projekt-.env und Standardwert. Die Allowlist ersetzt weder TLS noch die Origin-/CSRF-Prüfung.

Einen Wert für die erforderliche Spalte password_hash mit der CLI erzeugen. Der interaktive Befehl schaltet die Passwortanzeige aus und verlangt eine Bestätigung:

zelyra auth hash-password

Für bewusste Automatisierung eine Passwortzeile mit --stdin übergeben. Echte Passwörter nicht als Kommandoargument verwenden und erzeugte Hashes nicht in die Versionsverwaltung übernehmen:

printf '%s\n' 'dieses-passwort-aendern' | zelyra auth hash-password --stdin
auth users {
    table: users
    permissions: user_permissions
    roles: user_roles
    role_permissions: role_permissions
}

page "/admin" {
    requires auth
    permits "machines.manage"

    html {
        <h1>Maschinenverwaltung</h1>
    }
}

Die optionale Tabelle permissions enthält user_id und permission für direkte Vergaben. Rollengruppen werden mit roles und role_permissions aktiviert: Die erste Tabelle enthält user_id und role, die zweite role und permission. Effektive Berechtigungen sind die Vereinigung direkter Vergaben und aller Berechtigungen aus den Rollen des Benutzers. In der integrierten Administrationsansicht übermittelt auch das Formular zum Entziehen von Berechtigungen das korrekte CSRF-Token und entfernt Berechtigungen zuverlässig.

Dieselben Schutzregeln sichern typisierte API-Handler:

api GET "/api/machines/{id}" {
    handler get_machine
    requires auth
    permits "machines.view"
    input { id: MachineId }
    output Machine
    errors { 404 NotFound }
}

Fehler bei geschützten APIs verwenden JSON mit code und message. Ein Handler kann Err("NotFound") zurückgeben, um den passenden Status aus dem deklarierten errors-Block zu wählen; nicht deklarierte Fehler führen zu 500. JSON-Arrays können an typisierte Felder wie Int[] oder MachineId[] gebunden werden. Verschachteltes JSON wird über deklarierte Records modelliert:

struct Address { city: String }
struct CustomerInput { name: String address: Address }

api POST "/customers" {
    handler echo_customer
    input { customer: CustomerInput }
    output CustomerInput
}

Unbekannte Record-Felder und fehlende Pflichtfelder werden abgelehnt. Im Sprachkern unterstützen Arrays Literale, Indexzugriff, len, append, contains, first, last und Verkettung mit +. Record-Literale und geprüfter Feldzugriff stehen für verschachtelte Werte zur Verfügung:

customer = CustomerInput {
    name: "Anna"
    address: Address { city: "Berlin" }
}

print(customer.address.city)

Die Array-Iteration verwendet for ... in; die Schleifenvariable ist unveränderlich und nur im Schleifenkörper sichtbar. break und continue werden unterstützt.

Einen mit Browsern und Node kompatiblen TypeScript-Client aus denselben API-Deklarationen erzeugen:

zelyra doc examples/api_records.zyl --typescript > customer-client.ts

Der erzeugte Client verwendet die standardmäßige fetch-API, enthält deklarierte Records und Tabellen als TypeScript-Typen und behandelt Pfad-/Query-Parameter, JSON-Bodies, Bearer-Tokens, Response-Typen und HTTP-Fehler. Deklarierte API-Fehlernamen sind über ZelyraApiErrorCode verfügbar; ZelyraApiError.fromResponse liest Status, Code und Servermeldung aus und bewahrt den unveränderten Response-Body auf.

API-Fehler können zusätzlich einen geprüften Payload enthalten. Der Payload- Typ wird nach einem Doppelpunkt angegeben und muss dem Fehlertyp im Result des Handlers entsprechen:

struct ValidationProblem {
    field: String
    message: String
}

api POST "/customers/validate" {
    handler validate_customer
    output Result<String, ValidationProblem>
    errors { 422 ValidationError: ValidationProblem }
}

Die Antwort behält error.code und error.message und ergänzt den serialisierten Payload als error.details. OpenAPI enthält das Details-Schema; der erzeugte Client stellt es über ZelyraApiErrorPayloads und das generische Feld ZelyraApiError.details bereit. Bestehende ungetypte API-Fehler bleiben kompatibel.

Browserzugriff ist standardmäßig deaktiviert. Wenn ein separates Frontend eine API aufrufen soll, werden exakte Origins in der Projektkonfiguration freigegeben:

[web]
allowed_origins = ["http://localhost:5173"]
allow_credentials = false

Zelyra beantwortet API-OPTIONS-Preflight-Anfragen automatisch und fügt CORS-Header nur bei deklarierten API-Routen hinzu. Wildcard-Origins werden abgelehnt; CORS umgeht weder Authentifizierung noch Berechtigungen. Aktiviere Credentials nur für benötigte Browser-Session-Cookies; der Client muss dann zusätzlich credentials: "include" verwenden.

Eine Origin muss mit http:// oder https:// beginnen. Pfade, Query-Strings, Fragmente, Wildcards und ein abschließender Slash sind nicht erlaubt. Erlaubte Antworten erhalten Access-Control-Allow-Origin und Vary: Origin; bei aktivierten Zugangsdaten kommt Access-Control-Allow-Credentials: true hinzu. Eine Preflight-Antwort liefert HTTP 204 mit den erlaubten Methoden und angeforderten Headern. Verbotene Origins oder Methoden werden als strukturierte JSON-Fehler beantwortet; bei einem Methodenfehler enthält die Antwort den Allow-Header.

Ein ausgeblendeter Button ist keine Sicherheitsgrenze. Berechtigungen müssen serverseitig an der Aktion geprüft werden. Der Browser ist kreativ, besonders wenn man ihm vertraut.

API-Eingaben und sichere Antwort-Defaults

API-Bodies für Methoden außer GET und DELETE dürfen aktuell application/json oder application/x-www-form-urlencoded verwenden. Nicht unterstützte Medientypen liefern HTTP 415 als strukturierten JSON-Fehler. JSON-Bodies müssen ein Objekt sein; anschließend wird jedes deklarierte Feld in den Zelyra-Typ umgewandelt und geprüft.

Der HTTP-Parser prüft Content-Length, liest vollständige Bodies auch über mehrere Netzwerk-Reads ein und begrenzt Request-Bodies auf 1 MiB. Header sind auf 64 KiB begrenzt. Ein zu großer Body wird vor dem Handler mit HTTP 413 abgelehnt.

Alle HTML-, JSON-, Redirect-, Fehler- und Preflight-Antworten erhalten diese sicheren Standard-Header:

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: same-origin

Diese Defaults ersetzen weder TLS noch Authentifizierung, Autorisierung, CSRF-Schutz oder eine geeignete Content-Security-Policy.

Rollenverwaltung und manipulationssichtbares Audit

🧪 Rollen und Rollenberechtigungen können mit den vorhandenen CLI-Befehlen gepflegt werden, wenn die auth-Definition die Tabellen dafür konfiguriert:

auth users {
    table: users
    sessions: auth_sessions
    permissions: user_permissions
    roles: user_roles
    role_permissions: role_permissions
    audit: auth_audit_log
    admin_path: "/admin/access"
    admin_permission: "auth.manage"
    admin_role: admin
}
DATABASE_URL='mariadb://user:passwort@127.0.0.1:3306/app' \
  zelyra auth role grant app.zyl 42 manager
DATABASE_URL='mariadb://user:passwort@127.0.0.1:3306/app' \
  zelyra auth role-permission grant app.zyl manager customers.edit

revoke entfernt die jeweilige Zuordnung wieder. Die Befehle binden Werte als SQL-Parameter und prüfen zunächst das Projektschema. Verwende für echte Passwörter niemals den Platzhalter direkt aus diesem Beispiel.

Mit audit: auth_audit_log lassen sich Authentifizierungs-, Rollen- und CRUD-Ereignisse untersuchen oder exportieren:

DATABASE_URL='mariadb://user:passwort@127.0.0.1:3306/app' \
  zelyra audit inspect app.zyl --limit 100
DATABASE_URL='mariadb://user:passwort@127.0.0.1:3306/app' \
  zelyra audit export app.zyl --format json > audit.json
zelyra audit verify app.zyl

Für eine sichtbare Manipulationserkennung kann die Verkettung aktiviert werden:

auth users {
    table: users
    audit: auth_audit_log
    audit_chain: true
}

Die Audit-Tabelle benötigt dann id, previous_hash und entry_hash, üblicherweise String(64). Zelyra verwendet kleingeschriebene SHA-256- Hexwerte. Der Hash bezieht sich auf die kanonische, mit | getrennte Folge previous_hash|actor_user_id|event|target_user_id|details|created_at. zelyra audit verify prüft Verknüpfungen und Hashes. Das Bereinigen ist für verkettete Protokolle absichtlich deaktiviert, weil das Löschen eines Eintrags die Kette brechen würde. Nicht verkettete alte Einträge können dagegen mit zelyra audit prune ... --before ... --confirm kontrolliert entfernt werden.

Eine optionale Browser-Administrationsseite wird durch admin_path, admin_permission und admin_role aktiviert. Sie kann Benutzer, Passwörter, Aktivierung, Rollen und Rollenberechtigungen verwalten; die Formulare sind CSRF-geschützt. Der Schutz des letzten aktiven Administrators bleibt aktiv.

14. Capabilities

✅ Externe Fähigkeiten werden sichtbar deklariert:

fn load_machines() -> Machine[]
    uses Database
{
    return sql<Machine[]> {
        SELECT id, number, name FROM machines
    }
}

Bekannte Capabilities:

Database Network FileSystem Environment Process Clock Random Console Email

Aufrufende Funktionen müssen benötigte Capabilities weiterführen. Projekte können sie in zelyra.toml freigeben:

[capabilities]
database = true
network = false

Statische Prüfung und Runtime-Durchsetzung an Funktions-, nativen SQL-, Formular-, CRUD- und Authentifizierungs-Datenbankgrenzen sind bei vorhandenen Projektfreigaben implementiert. Eine vollständige Betriebssystem-Sandbox für alle Capabilities ist noch nicht vorhanden.

Zwei sichere Host-APIs sind implementiert:

fn runtime_timestamp() -> Timestamp uses Clock {
    return now()
}

fn configured_mode() -> String? uses Environment {
    return env("ZELYRA_MODE")
}

now() benötigt Clock und liefert Unix-Epoch-Millisekunden. env(name) benötigt Environment und liefert String?; eine fehlende Variable wird zu None. Werte werden nicht automatisch protokolliert oder veröffentlicht. Die Netzwerk-, Datei-, Prozess- und Zufalls-APIs sind implementiert, benötigen aber jeweils eigene Ressourcenfreigaben und bleiben in ihrer ersten Fassung bewusst eingeschränkt.

Die Random-Capability erzeugt sichere Ganzzahlen:

fn dice_roll() -> Int uses Random {
    return random_int(1, 6)
}

Der Bereich ist auf beiden Seiten inklusiv. Ungültige Bereiche führen zu einem Runtime-Fehler; Zufallswerte werden nicht implizit ausgegeben. Prozessausführung ist nur über die folgende, ausdrücklich begrenzte API verfügbar.

Die erste Network-Host-API ist http_get:

fn load_status(url: String) -> String uses Network {
    return http_get(url)
}

Projekte verwenden eine exakte Host-Allowlist und begrenzte Ressourcen:

[network]
allowed_hosts = ["127.0.0.1:8080", "api.example.com"]
timeout_ms = 5000
max_response_bytes = 1048576

Ohne [network] sind in einem Projekt keine Hosts erlaubt. Der Transport unterstützt http:// und https://; die Zertifikatsprüfung über Rustls ist standardmäßig aktiviert. Es werden keine Redirects verfolgt und nur erfolgreiche UTF-8-GET-Response-Bodies innerhalb der konfigurierten Grenzen geliefert. Der Helper http_get bleibt die einfache GET-Komfort-API; für Request-Header, Request-Bodies oder den Response-Status wird http_request verwendet.

Typisierte Anfragen verwenden http_request:

fn create_customer(url: String) -> HttpResponse uses Network {
    return http_request(
        "POST",
        url,
        ["Content-Type: application/json"],
        Some("{\"name\":\"Anna\"}")
    )
}

Die Methode akzeptiert GET, POST, PUT, PATCH, DELETE und HEAD. Header sind Strings im Format Name: value, der Body ist String?. Das typisierte Ergebnis enthält status: Int, headers: String[] und body: String. GET- und HEAD-Anfragen dürfen keinen Body enthalten.

JSON-Werte können in geprüfte Zelyra-Werte umgewandelt werden und umgekehrt. Records und verschachtelte Felder werden gegen das deklarierte Schema geprüft:

struct Customer { name: String tags: String[] nickname: String? }

fn decode_customer(body: String) -> Customer {
    return json_decode<Customer>(body)
}

fn encode_customer(customer: Customer) -> String {
    return json_encode(customer)
}

json_decode<Typ>(text) benötigt genau ein Zieltypargument und unterstützt Records, verschachtelte Records, Arrays, Optionen und Skalarwerte. json_encode serialisiert dieselben Werte. Ungültiges JSON, Typfehler, unbekannte Record-Felder und fehlende Pflichtfelder werden als ausdrückliche Laufzeitfehler gemeldet.

Für einen vollständigen typisierten JSON-Request-/Response-Ablauf gibt es http_json mit getrennten Request- und Response-Typargumenten:

struct CustomerCreate { name: String }
struct Customer { id: Int name: String }

fn create_customer(url: String, payload: CustomerCreate) -> Customer uses Network {
    return http_json<CustomerCreate, Customer>("POST", url, [], Some(payload))
}

Der Request-Record wird automatisch serialisiert und der Response-Body in den Response-Record dekodiert. Wenn kein Content-Type angegeben ist, wird application/json ergänzt. Nicht-2xx-Antworten sind ausdrückliche Laufzeitfehler; der Helper liefert den dekodierten Wert und nicht die Response-Header zurück.

Wenn die Response-Metadaten erhalten bleiben müssen, wird http_result verwendet:

fn submit(url: String, payload: CustomerCreate) -> HttpResult<Customer> uses Network {
    return http_result<CustomerCreate, Customer>("POST", url, [], Some(payload))
}

HttpResult<Response> enthält status: Int, headers: String[], body: String, data: Response? und error: HttpError?. Erfolgreiche 2xx-Antworten setzen data; Nicht-2xx-Antworten setzen error mit Status, Headern, Body und Meldung. Transportfehler und ungültiges Erfolgs-JSON bleiben Laufzeitfehler.

Die Process-Capability stellt eine Befehls-API ohne Shell bereit:

fn render_report(input: String) -> String uses Process {
    return run_process("/usr/bin/printf", ["%s", input])
}

Für Projekte ist eine exakte Befehls-Allowlist erforderlich:

[process]
allowed_commands = ["/usr/bin/printf"]
timeout_ms = 5000
max_output_bytes = 1048576

Ohne [process] darf kein Befehl laufen. Die Umgebung des Kindprozesses wird geleert, stdin geschlossen, Prozesse werden nach dem Timeout beendet und stdout/stderr begrenzt. Shell-Ausführung, Umgebungsweitergabe, Arbeitsverzeichnisse und Pipelines folgen später.

Die FileSystem-Host-APIs sind:

fn read_source(path: String) -> String uses FileSystem {
    return read_text(path)
}

fn write_note(path: String, content: String) uses FileSystem {
    write_text(path, content)
}

fn entries(path: String) -> String[] uses FileSystem {
    return list_dir(path)
}

fn remove_note(path: String) uses FileSystem {
    delete_file(path)
}

Alle vier APIs benötigen FileSystem. Lesen und Verzeichnislisten verwenden read_roots; Schreiben und Löschen verwenden write_roots. Relative Pfade werden ausgehend vom Projektverzeichnis aufgelöst, vorhandene Symlink-Ziele vor dem Zugriff kanonisiert. Ohne filesystem-Abschnitt sind Projektlesezugriffe auf das Projektverzeichnis begrenzt; Schreiben und Löschen sind gesperrt:

[filesystem]
read_roots = ["."]
write_roots = ["data"]

Die konfigurierten Verzeichnisse müssen bereits existieren. Ein neues Schreibziel benötigt ein bereits existierendes Elternverzeichnis.

Strukturierte Nebenläufigkeit

Für einen ersten eingeschränkten Nebenläufigkeitsablauf gibt es parallel und await:

parallel {
    customer = await load_customer()
    orders = await load_orders()
}

Jeder Zweig bindet sein Ergebnis mit await. Die Zweige erhalten eine unveränderliche Momentaufnahme der umgebenden Werte und werden vor der Fortsetzung in Quelltextreihenfolge zusammengeführt. Ein Fehler in einem Zweig lässt den gesamten Block fehlschlagen, nachdem die gestarteten Zweige beendet wurden. await außerhalb eines parallel-Blocks weist der Type Checker ab. Die aktuelle Runtime verwendet einen Worker-Thread pro Zweig; Abbruch und die Verwendung eines Datenbank-Connection-Pools sind noch nicht umgesetzt.

15. Contracts und Verify

🧪 Vor- und Nachbedingungen:

fn reserve(stock: Int, amount: Int) -> Int
    requires {
        amount > 0
        stock >= amount
    }
    ensures {
        result >= 0
        result == stock - amount
    }
{
    return stock - amount
}

requires wird vor dem Funktionskörper, ensures danach geprüft. In ensures bezeichnet result den Rückgabewert.

zelyra verify examples/contracts.zyl

Mögliche Statuswerte:

PROVEN
RUNTIME_CHECK
UNPROVEN
FAILED

Nur PROVEN bedeutet bewiesen. RUNTIME_CHECK trägt keinen falschen Schnurrbart und behauptet nicht, Mathematik zu sein.

Der Verifier fasst außerdem Funktionsaufrufe mit begrenzter Tiefe zusammen. Eine Callee mit mehreren Rückgabepfaden, etwa eine Absolutwertfunktion, liefert ihre Pfadbedingungen an den aufrufenden Contract. requires-Bedingungen der Callee werden nach Argumentsubstitution geprüft; requires des Aufrufers sind Annahmen beim Beweis seiner ensures. Komplexe, rekursive oder nicht auflösbare Fälle bleiben RUNTIME_CHECK.

Lokaler Zustandsfluss wird in diesen Zusammenfassungen berücksichtigt. Sowohl next: Int = value + 1 als auch die Kurzform next = value + 1 mit anschließendem return next werden wie eine direkte Rückgabe analysiert. Einfache lineare Mutable-Zuweisungen wie next = next + 1 werden ebenfalls verfolgt. Statisch begrenzte Schleifen mit linearem Zähler werden entfaltet; break beendet die aktuelle Schleife und continue startet ihren nächsten Durchlauf als eigene symbolische Pfade. Nichtlineare Zuweisungen und unbeschränkte Schleifen ohne bewiesene Invariante bleiben konservativ.

Schleifeninvarianten

Eine while- oder unbedingte loop-Schleife kann eine oder mehrere explizite Invarianten deklarieren:

while current > 0
    invariant { current >= 0 }
{
    current = current - 1
}

Der Verifier prüft die Invariante beim Eintritt und nach unterstützten Körperpfaden. Eine bewiesene Invariante kann eine ansonsten unbeschränkte lineare while-Schleife zusammenfassen; eine unbedingte loop-Schleife kann sie mit einem modellierten break-Austritt verwenden. Die Runtime prüft sie vor und nach jedem Durchlauf. Nicht unterstützte oder nicht beweisbare Invarianten bleiben konservativ und erzeugen kein PROVEN-Ergebnis.

zelyra verify meldet jede deklarierte Invariante separat, nach den ensures-Ergebnissen einer Funktion. Die Indizes der Invarianten beginnen bei null:

PROVEN [V-001]: reduce.ensures[0] (src/reduce.zyl:3:5-3:21)
PROVEN [V-001]: reduce.invariant[0] (src/reduce.zyl:7:21-7:33)
FAILED [V-004]: reduce.invariant[1] (src/reduce.zyl:8:21-8:34)

Jedes Ergebnis enthält einen stabilen Code und einen Quellbereich als (datei.zyl:startzeile:startspalte-endzeile:endspalte). Die Codes sind V-001 (PROVEN), V-002 (RUNTIME_CHECK), V-003 (UNPROVEN) und V-004 (FAILED). Für IDEs und CI kann zelyra verify app.zyl --json verwendet werden; die JSON-Ausgabe enthält dieselben Ergebnisdaten, eine verständliche message, ein optionales counterexample-Objekt und ein strukturiertes location-Objekt. Ein Gegenbeispiel wird nur ausgegeben, wenn eine begrenzte Suche einen kleinen linearen Integerzeugen sicher bestätigt. Die aktuelle Suche umfasst bis zu drei lineare Variablen im Bereich -32..=32, auch bei fehlgeschlagenen Schleifeninvarianten; sonst ist der Wert null. Die Textausgabe zeigt außerdem für jedes Ergebnis eine Erklärung und einen Quellzeilenausschnitt mit Caret-Marker.

FAILED bedeutet, dass die Invariante auf einem möglichen analysierten Pfad falsch ist oder vom Schleifenkörper nicht erhalten bleibt. RUNTIME_CHECK bedeutet, dass eine Laufzeitprüfung erforderlich ist, weil der symbolische Verifier den Beweis nicht vollständig führen kann. Nur PROVEN ist ein mathematischer Beweis.

16. Konfiguration und Geheimnisse

Projektkonfiguration gehört in zelyra.toml, Geheimnisse nicht:

[project]
name = "maschinenverwaltung"
version = "0.9.9"
zelyra = "0.1"

[capabilities]
database = true
network = false

Verbindungen und Passwörter werden über geschützte Umgebungsvariablen bereitgestellt:

export DATABASE_URL='mariadb://user:password@127.0.0.1:3306/zelyra_demo'

Regeln:

  • .env niemals in Versionskontrolle committen;
  • Produktionszugänge nie in Codebeispiele schreiben;
  • Geheimnisse nicht loggen;
  • getrennte Datenbanken für Entwicklung, Tests und Produktion verwenden;
  • destruktive Tests niemals gegen Produktion ausführen.

Einfacher Einstieg, optionale Feature-Schalter

Für den einfachen Einstieg ist keine zusätzliche Feature-Konfiguration erforderlich. Erweiterte Projektbereiche können in zelyra.toml ausgewählt werden; umgebungsabhängige, nicht geheime Überschreibungen gehören in .env oder die Prozessumgebung:

[features]
web = true
api = true
crud = true
auth = true
audit = true
Schalter .env / Prozessvariable Standard Bedeutung
web ZELYRA_FEATURE_WEB true Seiten, Formulare und Web-Ressourcen
api ZELYRA_FEATURE_API true api-Deklarationen und API-Oberfläche
crud ZELYRA_FEATURE_CRUD true crud-Deklarationen und generierte CRUD-Oberfläche
auth ZELYRA_FEATURE_AUTH true auth-Deklarationen und Authentifizierungsoberfläche
audit ZELYRA_FEATURE_AUDIT true Audit-Konfiguration innerhalb der Authentifizierung

Auswertungsreihenfolge für Konfigurationswerte:

Prozessumgebung → .env → zelyra.toml → sichere Standardwerte

Wenn der Quellcode einen deaktivierten Bereich verwendet, meldet der Compiler E-FEATURE-001. Feature-Schalter können niemals Typprüfung, SQL-Prüfung, Capabilities, Contracts, CSRF-Schutz oder Sicherheitsregeln abschalten.

Die wirksame Konfiguration kann jederzeit geheimnisfrei geprüft werden:

zelyra config main.zyl
zelyra config main.zyl --format=json

.env-Werte des generierten Projekts

Variable Standard im generierten Projekt Verwendung Geheim
ZELYRA_WEB_PORT 3000 Port des internen Webservers im Container nein
ZELYRA_HOST_PORT 3000 (oder autom. freier Port) lokal veröffentlichter Webport nein
ZELYRA_DB_HOST_PORT 3306 (oder autom. freier Port) lokal veröffentlichter MariaDB-Port nein
DATABASE_URL projektabhängig MariaDB-Verbindungs-URI (mariadb://user:pass@host:port/db) ja
MARIADB_DATABASE zelyra_app Compose: Datenbankname nein
MARIADB_USER zelyra Compose: Anwendungsbenutzer nein
MARIADB_PASSWORD zufällig erzeugt Compose: Passwort des Anwendungsbenutzers ja
MARIADB_ROOT_PASSWORD zufällig erzeugt Compose: MariaDB-Root-Passwort ja
ZELYRA_AUTH_TOKEN keiner optionaler lokaler Bearer-Token für geschützte Anfragen ja
ZELYRA_AUTH_PERMISSIONS leere Liste kommagetrennte lokale Berechtigungs-Allowlist nein

Test- und Entwicklungsvariablen

Die Testvariablen mit ZELYRA_INSTALL_ROOT, ZELYRA_BIN, *_E2E_* und GENERATED_* dienen internen CI- und lokalen Integrationstests (z. B. tests/generated-project-docker-e2e.sh, tests/sqlite-e2e.sh). Sie sind keine Anwendungskonfiguration und dürfen nie Produktionszugänge enthalten.

Umgebungszugriff innerhalb der Sprache

Über die Built-in-Funktion env(name) kann Zelyra-Code Werte aus der Umgebung lesen, sofern uses Environment und [capabilities] environment = true deklariert sind:

fn configured_mode() -> String? uses Environment {
    return env("ZELYRA_MODE")
}

DATABASE_URL und sensible Schlüssel dürfen niemals per env(...) in ungesichertem Code ausgelesen werden.

17. Diagnosen und Fehlersuche

Zelyra möchte Fehler so erklären, dass man nicht erst eine archäologische Ausgrabung im Stacktrace beginnen muss.

Verbindung unabhängig testen

Teste zuerst MariaDB ohne Zelyra. Das Passwort wird interaktiv abgefragt und landet nicht in der Shell-History:

mariadb \
    --host=127.0.0.1 \
    --port=3307 \
    --user=zelyra \
    --password \
    adressverwaltung

Danach ist zelyra doctor src/main.zyl --json (optional mit --env-file .env und --port 18080) der vorhandene Zelyra-Test. Es gibt aktuell keinen zelyra db check-Befehl. doctor prüft Quellcode, Schema, DB-Verbindung, Docker Compose und Host-Ports. Ohne DATABASE_URL meldet er nur eine Warnung, bei einer gesetzten, aber nicht erreichbaren Verbindung einen Fehler. Ein laufender DB-Container allein beweist noch nicht, dass Host, Port, Benutzer und Datenbank zusammenpassen.

Diagnosebefehle ohne Geheimnisse

pwd
ls -la
docker compose ps
docker compose logs mariadb
ss -ltn
mariadb --version

Unter Windows in PowerShell sind Get-Location, Get-ChildItem, docker compose ps und mariadb --version die entsprechenden ersten Schritte. Gib niemals DATABASE_URL oder ein Passwort in eine Diagnoseausgabe aus.

Typische Fehler

Fehlermeldung Wahrscheinliche Ursache Lösung
Permission denied fehlende Dateirechte, falscher Besitzer oder kein Zugriff auf den Client ls -la, chmod 600 .env und Installationspfad prüfen
Access denied for user Passwort stimmt nicht oder Benutzer ist für einen anderen Host angelegt SHOW GRANTS FOR 'zelyra'@'127.0.0.1'; prüfen; Passwort rotieren
Connection refused auf Host/Port lauscht kein Dienst docker compose ps, ss -ltn und den veröffentlichten Port prüfen
Can't connect to server falscher Host, falscher Port oder Container noch nicht bereit docker compose logs mariadb; vom Host 127.0.0.1:3307, im Compose-Netz mariadb:3306 verwenden
Unknown database Datenbankname in URL und MariaDB unterscheiden sich SHOW DATABASES; ausführen und DATABASE_URL korrigieren
falscher Port außen 3307 mit innen 3306 verwechselt Host nutzt 3307, ein Compose-Service nutzt 3306
MariaDB-Container nicht gestartet Compose-Fehler, belegter Port oder ungesundes Volume docker compose ps und docker compose logs mariadb prüfen
Benutzer nur für anderen Host freigegeben 'zelyra'@'localhost' ist nicht immer 'zelyra'@'127.0.0.1' Benutzer exakt für den verwendeten Host anlegen und Grants kontrollieren
fehlende Umgebungsvariable DATABASE_URL wurde nicht exportiert .env laden oder Variable für den Prozess setzen; Zelyra lädt sie nicht selbst
.env wird nicht gefunden falsches Arbeitsverzeichnis oder Annahme eines automatischen Loaders pwd, ls -la; im Projektstamm arbeiten und Variable explizit exportieren
ungültiger Zahlenwert beim Port Portteil der URI ist kein gültiger MariaDB-Port Ziffern verwenden, zum Beispiel 3307; die URL wird ansonsten abgelehnt
falscher Zeichensatz Datenbank mit anderem Charset/Kollation angelegt Datenbankdefinition prüfen; db setup nutzt utf8mb4/utf8mb4_unicode_ci
TLS-Fehler TLS-Parameter wurden an die URL angehängt, werden aber nicht unterstützt aktuelle CLI-URL ohne TLS-Query-Option nutzen; TLS-Konfiguration ist geplant
Testdatenbank wird aus Sicherheitsgründen abgelehnt Schutzmechanismus wird erwartet, ist aber nicht implementiert Zelyra verhindert Produktionszugriff in Tests nicht automatisch; Variablen manuell prüfen
PostgreSQL-SQL gegen MariaDB falsches Backend oder nicht passende DDL Backend in .zyl prüfen und zelyra db create-Ausgabe vor Anwendung lesen

Wenn mariadb gar nicht gestartet werden kann, nennt Zelyra den Startfehler des externen Programms. Der CLI-Prozess enthält keinen eigenen MariaDB-Treiber.

Quellcode prüfen:

zelyra check app.zyl

Typische Fehlerklassen:

  • unbekannter Name oder Typ;
  • Zuweisung an unveränderlichen Wert;
  • unvollständiges Pattern Matching;
  • unbekannte Tabelle oder Spalte;
  • fehlender SQL-Parameter;
  • falsche Ergebnisstruktur;
  • fehlende Capability;
  • ungültiges Formularfeld;
  • nicht erfüllter Contract;
  • unvollständige typisierte Lücke (_).

Beim Entwickeln kannst du _ als Platzhalter für einen unfertigen Ausdruck einsetzen (Typed Hole). zelyra check lehnt unfertigen Code für den Bau zwar ab, liefert aber kontextbezogene Diagnosen: erwarteter Typ, sichtbare Variablen und Funktionen, aktive Capabilities, Contract-Pflichten und Quelltextposition.

Wenn DATABASE_URL fehlt, funktionieren reine Sprachprüfungen weiterhin. Datenbankoperationen melden den fehlenden Zugriff kontrolliert. Das run- Kommando verwendet ohne Variable die reine Runtime; serve startet zwar die Routen, datenbankabhängige Seiten antworten aber mit einem kontrollierten Fehler.

18. Testen und Mitentwickeln

Vor jedem Commit:

cargo fmt --all
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings

Ein Sprachfeature ist erst fertig, wenn es besitzt:

  • dokumentierte Syntax;
  • AST/HIR-Unterstützung;
  • statische Prüfung;
  • verständliche Diagnosen;
  • positive Tests;
  • negative Tests;
  • ein ausführbares Beispiel;
  • aktualisierte deutsche und englische Dokumentation.

Tests, die nur deshalb grün sind, weil sie nie liefen, sind Dekoration.

19. Was als Nächstes kommt

Die wichtigsten geplanten Bereiche:

  • typisierte, vollständig anpassbare View-Komponenten;
  • sichere benannte E-Mail-Vorlagen und Anhänge;
  • persönliche Lesebestätigungen und Empfängerautorisierung im Benachrichtigungszentrum;
  • allgemeine Hintergrundaufgaben über die vorhandene begrenzte MariaDB-Outbox hinaus;
  • Activity-, Audit- und technische Logs;
  • typisierte Connections und Secret Provider;
  • ODBC und externe Read-only-Datenbanken;
  • umfangreichere fachliche Fehlerwerte über typisierte API-Payloads hinaus und weitergehende Request-/Response-Verarbeitung zur Laufzeit;
  • Abbruch und Datenbank-Pool-Integration für strukturierte Nebenläufigkeit;
  • weitergehende formale Verifikation;
  • Optimierungsmodelle für reale Planungsprobleme.

Zelyra soll den Standardfall kurz halten und beim Sonderfall nicht plötzlich die Tür abschließen:

Automatisch, wenn möglich. Anpassbar, wenn nötig. Überall geprüft.

Und jetzt: eine Tabelle bauen, SQL lesen, Backup prüfen. In dieser Reihenfolge.

20. Zelyra im Vergleich zu Rust

Die wichtigste Aussage zuerst

Zelyra wird mit Rust entwickelt. Der Zelyra-Compiler, die Sprachmodule und Teile der Laufzeit liegen als Rust-Crates im Compiler-Repository. Das bedeutet nicht, dass Zelyra Rust verändert oder dass Zelyra-Anwendungsprogramme Rust- Programme sind.

Zelyra ist nicht verändertes Rust. Zelyra ist eine eigenständige Sprache, deren Compiler und Laufzeit in Rust entwickelt werden.

Der Rust-Compiler wird für Zelyra nicht geforkt und nicht um Zelyra-Schlüssel- wörter erweitert. Zelyra ist auch keine Rust-Bibliothek und kein Präprozessor, der gewöhnlichen Rust-Code in etwas anderes umschreibt. Eine .zyl-Datei wird vom eigenen Zelyra-Lexer und -Parser gelesen, in eigene AST-/HIR-Strukturen überführt, typgeprüft und anschließend von der Zelyra-Runtime verarbeitet.

Die Ähnlichkeit bei fn, geschweiften Klammern, if, match oder statischer Typisierung ist eine Designentscheidung, aber kein Abstammungsnachweis. Entschei- dend sind Grammatik, Semantik und Programmiermodell. Zelyra ist derzeit noch ein experimenteller Prototyp; die Eigenständigkeit wächst mit der Umsetzung des eigenen Typsystems, der SQL-Prüfung, der Runtime und der Zelyra-spezifischen Konstrukte.

Die Aussagen in diesem Kapitel wurden gegen den aktuellen Quellcode geprüft: Lexer und TokenKind definieren die Zelyra-Tokens, der Parser erzeugt eigene AST-Strukturen, und die nachfolgenden Module übernehmen Auflösung, Typprüfung, Contract-Prüfung, SQL-Analyse, Webverarbeitung und Runtime. Die CLI- und Datenbankmodule wurden ebenfalls berücksichtigt. Wo ein Merkmal nur als Token, AST-Knoten oder Zielbild vorhanden ist, wird es nicht als vollständig ausführbare Spracheigenschaft ausgegeben.

Allgemeiner Vergleich

Bereich Rust Zelyra Wesentlicher Unterschied Zelyra-Status
Sprachkategorie universelle System- und Anwendungssprache eigenständige deklarative Sprache für Business- und Webanwendungen andere Grammatik und Semantik 🧪
Haupteinsatzgebiet Systeme, Services, CLI, Embedded, WebAssembly datenbankgestützte Business- und Webanwendungen Zelyra bündelt Fachanwendungsebenen 🧪
Compiler rustc, Cargo-Ökosystem eigenes Rust-Programm im Zelyra-Repository Rust kompiliert den Compiler; rustc kompiliert nicht .zyl ✅
Laufzeit Rust-Code läuft nativ oder über gewählte Runtime eigene Zelyra-Runtime, in Rust implementiert Zelyra führt eigene Werte und Regeln aus 🧪
Speicherverwaltung Ownership, Borrowing, Lifetimes für Zelyra-Code weitgehend automatisch verborgen kein Rust-Borrow-Checker im .zyl-Programm 🧪
Ownership zentrale Rust-Semantik kein entsprechendes .zyl-Konstrukt Speicherregeln sind nicht dieselben 🗺️
Borrowing Referenzen und Borrow-Checker kein entsprechendes .zyl-Konstrukt keine Rust-Referenzsyntax 🗺️
Lifetimes explizite oder inferierte Lebensdauern keine Lifetime-Syntax Zelyra legt diese Ebene derzeit nicht offen 🗺️
statische Typisierung sehr ausgereift, generisch und trait-basiert eigener statischer Typechecker mit Int, String, Option, Records usw. Zelyra-Typen sind nicht Rust-Typen ✅
Nullfähigkeit Option<T> T?, etwa Email? Zelyra kann daraus Schema-/Formregeln ableiten ✅
Fehlerbehandlung Result<T, E>, Option<T>, ?-Operator Result<T, E>, Some/None, Laufzeitdiagnosen kein Rust-?-Operator als Zelyra-Syntax ✅
Datenbankintegration externe Crates wie SQLx, Diesel oder SeaORM vorgesehener Bestandteil von Sprache, CLI und Runtime anderes Integrationsmodell; aktuell externer Client 🧪
SQL Bibliotheken, Makros oder Strings natives sql<T> { ... } mit Schema-/Parameterprüfung Zelyra kennt SQL als AST-Ausdruck ✅
MariaDB-Schema nicht Aufgabe der Rust-Sprache Tabellen werden in Zelyra beschrieben Generator erzeugt sichtbares SQL 🧪
Formulare Framework, Templates und Validierung selbst verbinden form-Konstrukt und Tabellenregeln Standardpfad ist Teil des Sprachmodells 🧪
CRUD muss programmiert oder über Frameworks erzeugt werden deklaratives crud Name -> table mit gemeinsamem View-Feldprofil aktuelle Runtime stellt CRUD-Routen bereit 🧪
Views externe Bibliotheken oder Frameworks page/html, benannte view-Layouts, typisierte component-Bausteine und benannte Slots mit Fallbacks sind vorhanden kein Rust-Äquivalent; freie Styling-Komponenten fehlen noch 🧪
Quellformatierung cargo fmt, rustfmt zelyra fmt <file.zyl> [--check] deterministischer Zelyra-Formatter, schützt SQL und HTML ✅
Refactoring/Wirkungsanalyse rust-analyzer, Compiler-APIs zelyra impact, zelyra edit versionierte, atomare JSON-Maschinenschnittstellen 🧪
Authentifizierung externe Web-/Auth-Crates Auth-Definition, Sessions und Berechtigungsprüfungen im Webmodul Zelyra bündelt den Standardfall 🧪
Berechtigungen selbst entworfene Typen und Middleware requires auth, permits und CRUD-Aktionsrechte deklarative Regeln werden serverseitig geprüft 🧪
Contracts manuell oder über Bibliotheken native requires {} und ensures {} plus verify Contract-Syntax gehört zu Zelyra ✅
Capabilities APIs und Bibliotheken regeln Effekte uses Database, uses Network usw. sichtbare Effektdeklaration ist Sprachbestandteil ✅
E-Mails externe SMTP-/Mail-Crates mail_queue, mail_deliver, uses Email MariaDB-Outbox, TLS, Hostfreigabe und ausdrücklich gestarteter Worker 🧪
Jobs externe Job-/Queue-Systeme kein Hintergrundjob-Konstrukt keine stabile Job-Syntax ❌
Audit externe Logs oder Audit-Crates Audit-Tabelle, CLI-Auswertung und optionale Hash-Kette an Auth/CRUD gebunden und noch experimentell 🧪
Deployment Cargo, Container, CI und Infrastruktur frei wählbar generierte Docker-/Compose-Vorlage vorhanden Vorlage ist Entwicklungsstart, keine Produktionsplattform 🧪
Produktionsreife Rust ist breit produktiv eingesetzt Zelyra ist experimentell Reife und Ökosystem sind nicht vergleichbar 🧪
Ökosystem sehr groß: Crates, Tools, Frameworks kleines eigenes Repository und wenige Integrationen Zelyra kann Rust-Crates nicht direkt importieren 🧪

Rust ist also das technische Fundament, nicht die Anwendungssprache hinter Zelyra. Eine Rust-Struktur Customer und eine Zelyra-Tabelle customers können ähnliche Daten beschreiben, erzeugen aber nicht dasselbe Verhalten.

Ausführlicher Syntaxvergleich

Die Statusangabe ✅ bedeutet in diesem Kapitel: im aktuellen Quellcode vorhanden und mit der installierten Rust-Toolchain beziehungsweise dem Zelyra- CLI geprüft. 🧪, 🗺️ und ❌ kennzeichnen weiterhin eingeschränkte, geplante oder derzeit nicht verfügbare Sprachmerkmale.

Sprachmerkmal Zelyra-Syntax Rust-Syntax Semantischer Unterschied Zelyra-Status
Dateiendung app.zyl main.rs eigener Lexer und eigener Dateityp ✅
Programmeinstieg fn main() { ... } fn main() { ... } gleiche Schreibweise, andere Sprache ✅
Funktionsdefinition fn add(a: Int) -> Int { ... } fn add(a: i64) -> i64 { ... } Zelyra-Typen sind eigene AST-Typen ✅
Parameter name: String name: String ähnliche Position, andere Typsemantik ✅
Rückgabetyp -> Int -> i64 Zelyra abstrahiert den Geschäftstyp ✅
Rückgabewert return value value oder return value; letzter Rust-Ausdruck ist Rückgabewert ✅
unveränderliche Variable value = 1 let value = 1; Zelyra bindet ohne mutable unveränderlich ✅
veränderliche Variable mutable value = 1 let mut value = 1; Veränderbarkeit wird anders markiert ✅
Ganzzahl Int oder UInt i32, i64, u32, u64 Rust verlangt konkrete Breite; Zelyra abstrahiert derzeit ✅
Dezimalzahl Float oder Decimal f64 oder f32 Größe und genaue Überlaufregeln von Zelyra sind noch nicht vollständig spezifiziert 🧪
Boolean true, false, Bool true, false, bool eigenes Zelyra-Basistypmodell ✅
Zeichenkette String und Zeichenliterale String, &str, Zeichenliterale Rust unterscheidet Besitz und Borrowing ✅
optionale Werte Email? oder Option<String> Option<String> ? ist Zelyras Kurzform für Option ✅
fehlender Wert None None gleiche Bezeichnung in verschiedenem Enum-Modell ✅
Listen/Arrays Int[], [1, 2, 3] Vec<i64>, vec![1, 2, 3] Zelyra bietet keine Rust-Makrosyntax ✅
benannte Datentypen type CustomerId = Id, struct Customer { ... } type CustomerId = u64, struct Customer { ... } Zelyra-Records und Rust-Structs sind nicht austauschbar ✅
Bedingungen if ok { ... } if ok { ... } Blocksemantik und Ausdrucksregeln unterscheiden sich ✅
else else { ... } else { ... } ähnliche Kontrollflussform ✅
match match value { Some(x) => ... None => ... } match value { Some(x) => ..., None => ... } Zelyra verlangt ebenfalls vollständige Fälle ✅
Schleifen for item in items, while, loop for item in items, while, loop Zelyra unterstützt keine Rust-Iteratortraits ✅
Funktionsaufrufe add(1, 2) add(1, 2) gleiche Oberfläche, andere Auflösung ✅
Ausgabe print(value) println!("{}", value); Rust verwendet ein Makro mit ! ✅
Kommentare # Kommentar, alleinstehendes ## als Blockgrenze // Kommentar, /* ... */ # ist Standard; // bleibt kompatibel; ##-Blöcke sind nicht verschachtelt ✅
Zeilenumbrüche meist Trennzeichen; nach Operatoren fortsetzbar meist Whitespace Parserregeln sind eigenständig ✅
Semikolons werden als Statementtrenner akzeptiert, aber nicht benötigt häufig Statementtrenner Zelyra ist nicht semikolonpflichtig ✅
Blockstruktur { ... } { ... } Klammern bestimmen in beiden die Blöcke ✅
Einrückung Lesbarkeit, keine Blocksemantik Lesbarkeit, keine Blocksemantik Leerzeichen/Tabs werden nicht zu Python-Blöcken ✅
Fehlerbehandlung Result<T, E>, Some/None Result<T, E>, ?, panic! Zelyra hat keinen Rust-Operator ? ✅
Stringinterpolation HTML kann {name} in html-Bodies verwenden format!("{name}") oder println!("{}", name) keine allgemeine Zelyra-Stringinterpolation dokumentieren 🧪
Module Noch nicht festgelegt mod name {}, Dateien und Module keine mod-Syntax im Zelyra-Parser ❌
Imports Noch nicht festgelegt use crate::module::Item; keine Importsyntax ❌
Generics Option<T>, Result<T, E> und begrenzte Built-in-Typargumente allgemeine Generics und Traits keine benutzerdefinierten Zelyra-Generics 🧪
asynchrone Funktionen async fn nicht vorhanden; await/parallel nur eingeschränkt async fn, .await, Futures kein stabiles Zelyra-Async-Modell 🧪
Tabellen table customers { ... } kein Sprachkonstrukt Zelyra verbindet Tabelle und Schema ✅
Datenbanktypen Id, String(100), Email, Bool Rust-Typen und externe Mapping-Crates Zelyra erzeugt SQL-Typen aus der Tabelle ✅
Beziehungen department: Department required Feld plus eigene Query-/Mappinglogik Zelyra leitet Fremdschlüssel ab ✅
SQL-Abfragen sql<Customer[]> { SELECT ... } String/Makro einer DB-Crate Zelyra prüft Schema, Parameter und Ergebnis ✅
Formulare form CustomerCreate -> customers { ... } kein natives Formular Webframework und Validierung nötig ✅
CRUD crud Customer -> customers kein natives CRUD Zelyra-Runtime stellt Standardrouten bereit 🧪
Views page, view SiteShell und component Badge kein natives View-Konstrukt benannte Views/Komponenten sind vorhanden; input/render/?? bleiben Zielsyntax 🧪
Vorbedingungen requires { amount > 0 } kein eingebautes Äquivalent Contract ist Teil der Zelyra-Funktion ✅
Nachbedingungen ensures { result >= 0 } kein eingebautes Äquivalent Verifier und Runtime kennen Zelyra-Contracts ✅
Zugriff auf alte Werte Noch nicht festgelegt; old(...) nicht geparst ebenfalls kein allgemeiner eingebauter Contractstandard keine old-Syntax vortäuschen ❌
Capabilities uses Database kein identisches Sprachkonstrukt Effekte werden in Zelyra sichtbar deklariert ✅
E-Mails mail_queue und mail_deliver externe Crate/API Email-Capability; Einreihen in transaction mit Database(write) 🧪
Hintergrundjobs Noch nicht festgelegt externe Queue-/Runtime-Crate keine Job-Syntax ❌
Audit auth users { audit: auth_audit_log } externe Logging-/Audit-Crate Zelyra bindet Audit an Auth- und CRUD-Ereignisse 🧪
API-Definitionen api GET "/customers" { ... } Router, Handler und Typen separat Zelyra bündelt Vertrag und Route ✅

Funktionen: derselbe Gedanke, andere Sprache

Beide folgenden Beispiele sind in ihrer jeweiligen Sprache typische kleine Funktionen. Das Zelyra-Beispiel entspricht der vom Parser verarbeiteten Funktionssyntax und wurde mit dem aktuellen CLI geprüft.

fn add(a: Int, b: Int) -> Int {
    return a + b
}
fn add(a: i64, b: i64) -> i64 {
    a + b
}

Rust verwendet konkrete Ganzzahltypen wie i32, i64, u32 oder u64. Zelyra bietet für typische Businesslogik den verständlichen Typ Int; die verbindliche Größe, Überlaufbehandlung und jede Datenbankabbildung müssen noch vollständig spezifiziert werden.

Fibonacci

fn fibonacci(n: Int) -> Int {
    if n <= 1 {
        return n
    }

    return fibonacci(n - 1) + fibonacci(n - 2)
}

fn main() {
    print(fibonacci(10))
}
fn fibonacci(n: u64) -> u64 {
    if n <= 1 {
        return n;
    }

    fibonacci(n - 1) + fibonacci(n - 2)
}

fn main() {
    println!("{}", fibonacci(10));
}

✅ Die Zelyra-Fassung nutzt Int, print und explizites return; Rust nutzt u64, das println!-Makro und den letzten Ausdruck als Rückgabewert. Rust- Makros tragen das !. In Zelyra sind Semikolons nicht erforderlich. Klammern bestimmen in beiden Beispielen die Blockstruktur, Einrückung dient nur der Lesbarkeit. Ein Zeilenumbruch nach + setzt den Ausdruck fort; der Parser überspringt an dieser Stelle Zeilenumbrüche. Rekursion funktioniert in Zelyra nur, weil Funktionsauflösung und Runtime sie tatsächlich unterstützen. Der Code wurde in diesem Arbeitslauf nicht ausgeführt.

Optionale Werte

email: Email?
email: Option<String>

Email? ist fachlich kürzer und drückt neben der Optionalität den E-Mail- Datentyp aus. Rust verwendet den allgemeinen generischen Typ Option<T>. Zelyra kann Email? in der Tabellen-, Formular- und SQL-Prüfung berücksichtigen; eine allgemeine automatische View- oder Validierungsableitung ist jedoch nicht für jede Oberfläche vorhanden.

Tabellen

table customers {
    id: Id primary auto
    name: String(100) required
    email: Email?
    active: Bool default true
}
struct Customer {
    id: u64,
    name: String,
    email: Option<String>,
    active: bool,
}

Die Rust-Struktur erzeugt keine Tabelle, keine SQL-Spalten, keine Validierung, kein Formular und keine CRUD-Oberfläche. Dafür braucht Rust zusätzliche Crates, Makros, Queries, Handler und Templates. Die Zelyra-Tabelle wird dagegen in die Schemaableitung und – wo die Webfunktion vorhanden ist – in Formulare und CRUD einbezogen.

CRUD

crud Customer -> customers

Rust besitzt dafür kein natives Äquivalent. Typischerweise kommen in Rust ein Webframework, Routing, eine Datenbank-Crate, ein Datenmodell, Abfragen, Request-Typen, Validierung, Handler, Templates oder ein Frontend, Fehlerbehandlung und Berechtigungsprüfung zusammen. Zelyra parst diese deklarative Definition und die aktuelle Runtime stellt daraus CRUD-Routen, Formulare, Suche, Filter und CSRF-geschützte Aktionen bereit. Das ist experimentell; es ist kein statischer Frontend-Generator.

SQL

Die Zielsyntax aus dem Auftrag enthielt with { ... }. Das ist im aktuellen Parser nicht vorhanden. Parameter kommen derzeit als Funktionsparameter in die SQL-Prüfung:

struct Customer { id: Int name: String email: Email? active: Bool }

fn active_customers(active: Bool) -> Customer[]
    uses Database
{
    return sql<Customer[]> {
        SELECT id, name, email, active
        FROM customers
        WHERE active = :active
        ORDER BY name
    }
}
let customers = sqlx::query_as!(
    Customer,
    r#"
        SELECT id, name, email, active
        FROM customers
        WHERE active = ?
        ORDER BY name
    "#,
    true
)
.fetch_all(&pool)
.await?;

Rust hat SQL nicht als Spracheigenschaft; SQLx oder andere Crates können zusätzliche Compile-Time-Prüfungen anbieten. Zelyras aktueller SQL-Checker prüft – wenn Schema und Typen bekannt sind – Tabellen, Spalten, Aliase, Parameter, Nullfähigkeit, Ergebniszuordnung und Database-Capability. Das macht Zelyra nicht automatisch besser als SQLx. Der aktuelle MariaDB-Generator und seine fehlenden Tabellenoptionen sind in Abschnitt 7 offen dokumentiert.

Views: aktuelle Syntax statt Zielbild

Die gezeigte Zielsyntax mit view, input, render, Komponenten und ?? ist heute nicht Parser-Syntax. Der aktuelle Webkern verwendet stattdessen:

page "/customers/{name}" {
    html {
        <h1>Kunde {name}</h1>
    }
}

Rust besitzt keine eingebaute HTML- oder Komponenten-Syntax; dort kommen Templates und Webframeworks hinzu. Zelyras page/html-Form sowie benannte Views und typisierte Komponenten sind vorhanden. Mehrere Slots, verschachtelte Komponenten und die Zielsyntax mit input/render sind noch nicht stabil; ?? ist keine implementierte Zelyra-Operation.

Verträge

Die Zielsyntax require amount > 0 und old(...) ist nicht der aktuelle Stand. Der Parser akzeptiert requires {} und ensures {}:

fn reserve(stock: Int, amount: Int) -> Int
    requires {
        amount > 0
        stock >= amount
    }
    ensures {
        result >= 0
        result == stock - amount
    }
{
    return stock - amount
}

Rust besitzt hierfür kein direkt eingebautes Äquivalent. requires beschreibt Vorbedingungen, ensures Nachbedingungen. Runtime-Prüfung und formale Verifikation sind verschieden: zelyra verify kann PROVEN, RUNTIME_CHECK, UNPROVEN oder FAILED melden. Ein Zugriff auf den alten Wert über old(...) ist nicht implementiert.

Capabilities

fn load_customers() -> Customer[] uses Database {
    return sql<Customer[]> {
        SELECT id, name, email, active FROM customers
    }
}

uses macht erlaubte Seiteneffekte in der Signatur sichtbar. Database, Network, FileSystem, Environment, Process, Clock, Random und Console sind im aktuellen Runtime-Code bekannte Capabilities. Rust besitzt kein identisches eingebautes Capability-System; dort werden Zugriffe typischerweise über Typen, Werte und Bibliotheks-APIs organisiert. Email ist die Capability für Anwendungs-Mail. mail_queue benötigt zusätzlich Database(write) und eine Transaktion; die SMTP-Zustellung prüft den erlaubten Host.

Was Rust-Kenner in Zelyra nicht suchen sollten

Zelyra soll für typische Businessanwendungen nicht verlangen, im gewöhnlichen Anwendungscode explizite Lifetimes zu schreiben, Borrowing für einfache Formulare zu debuggen, zwischen vielen Ganzzahlbreiten zu wählen oder ein Webframework aus zahlreichen Crates zusammenzustellen. Das ist eine Abstraktion, keine Behauptung, dass Speicher- und Laufzeitfragen verschwinden.

Von Rust inspiriert sind statische Typisierung, verständliche Diagnosen, sichere Standardeinstellungen, explizite Veränderbarkeit, Pattern Matching, Records/Enums, klare Grenzen, reproduzierbares Tooling und formale Prüfungen.

Was Zelyra eigenständig macht

Zelyra-Merkmal Nutzen Status
Eine fachliche Definition weniger widersprüchliche Mehrfachdefinitionen 🧪
natives geprüftes SQL Datenbankfehler möglichst vor Ausführung erkennen ✅
deklaratives CRUD Standardverwaltungen mit wenig Code 🧪
page/html-Webkern einfache typisierte Pfadwerte und HTML-Antworten 🧪
sichtbares MariaDB-SQL nachvollziehbare Schemaänderungen 🧪
requires und ensures Geschäftsregeln ausdrücklich festlegen ✅
Capabilities erlaubte Seiteneffekte sichtbar machen ✅
integriertes Audit Änderungen nachvollziehen 🧪

Ehrliches Fazit

Zelyra sieht an einigen Stellen ähnlich aus wie Rust, weil beide moderne, statisch typisierte Sprachen mit geschweiften Klammern und klaren Funktionssignaturen sind. Zelyra verfolgt jedoch ein anderes Programmiermodell: Datenbank, SQL, Formulare, CRUD, Views und Geschäftsregeln sollen Bestandteile eines gemeinsamen Sprachsystems sein. Rust ist das technische Fundament des Compilers – nicht die Sprache, die Zelyra-Anwendungsentwickler schreiben.

Status: Zelyra ist derzeit ein experimenteller Sprachprototyp. Einige gezeigte Sprachmerkmale beschreiben das verbindliche Zielbild und sind noch nicht vollständig implementiert. Der Status an jedem Beispiel zeigt, was heute im Code tatsächlich vorhanden und geprüft ist.

Weiterführend: Einführung, Sprachgrundlagen, MariaDB, SQL, Formulare, Views/Webseiten, CRUD, Contracts, Capabilities, Implementierungsstatus und Roadmap. Der laufende Repository-Stand steht zusätzlich auf der Statusseite.

21. Positionierung und aktueller Entwicklungsstand

Die aktuelle Positionierung in docs/positioning.de.md beschreibt Zelyra als eigenständige Sprache für datenbankgestützte Businessanwendungen. Sie ersetzt nicht die technische Prüfung im Compiler; sie erklärt, wofür die Bausteine zusammen gedacht sind.

Was Zelyra unterscheidet

  1. Eine Quelle der Wahrheit: Schema, Typen, SQL, Formulare, CRUD, Views und APIs sollen aus miteinander prüfbaren Definitionen entstehen.
  2. SQL bleibt First-Class: SQL wird nicht hinter einer ORM-Abstraktion versteckt, sondern als Bestandteil des Programms mit Tabellen, Parametern und Ergebnisformen geprüft.
  3. Businessfunktionen sind Sprachbausteine: Tabellen, Formulare, CRUD, Seiten, Authentifizierung, Berechtigungen und Contracts gehören zum selben Modell.
  4. Sichere Defaults sind sichtbar: HTML-Escaping, parametrisierte SQL- Werte, CSRF-Schutz, Null-Sicherheit und serverseitige Berechtigungen sind keine bloßen Empfehlungen.
  5. Beweise werden ehrlich bezeichnet: PROVEN, RUNTIME_CHECK, UNPROVEN und FAILED unterscheiden echte statische Beweise von Laufzeitprüfungen und offenen Fällen.
  6. Kurzer Einstieg, vollständige Sprache: Der deklarative Standardfall ist kurz; eigene Funktionen und native SQL bleiben für komplexe Fachlogik verfügbar.
  7. Wenig Infrastruktur für den Start: Der eingebaute Server und die CLI sollen den Lern- und Entwicklungsweg ohne Apache, PHP oder ein verpflichtendes Framework-Bündel ermöglichen.

Der aktuelle Stand ist trotzdem ein experimenteller Prototyp. Die Roadmap und die einzelnen Statuszeichen sind deshalb wichtiger als eine allgemeine Produktbehauptung.

22. Unterstützung und technische Nachweise

Wähle den Backendpfad anhand der Datenbankmatrix. MariaDB 11.4.13 (sowie 10.11.19, 11.8.9 und 12.3.3), MySQL Server 8.4.11 und PostgreSQL 16.15 bilden die verifizierte Multi-Backend-Servermatrix. Relationales CRUD mit echten Fremdschlüsseln (fk_...), Preflight-Waisenabweisung (error[E-DB-005]) unter Datenerhalt, relationalen Joins und restriktivem Löschschutz (RESTRICT-Delete-Policy) ist für alle drei Engines dokumentiert und getestet. Auf MySQL sind Schreibsperren über innodb_lock_wait_timeout dynamisch an Transaktions-Deadlines gebunden. Native Entscheidungstransaktionen (with_decision_transaction) sichern Rollback, Unwind und Deadlines auf einer geliehenen Verbindung ab. SQLite ergänzt lokale Schema- und grundlegende CRUD-Abläufe.

Diese Nachweise gelten für die genannten Abläufe, nicht für beliebige Datenbankfunktionen. Erweiterte Authentifizierungsmerkmale, Mail-Outbox und Benachrichtigungsbeispiele verwenden vorrangig MariaDB und MySQL.

Installation, Upgrade, Rollback, Migration und Wiederherstellung müssen zur eingesetzten Plattform und Serverversion passen. Prüfsummen, SBOMs und Attestierungen helfen, das Release-Artefakt zuzuordnen. Sie bestätigen nicht die fachliche Korrektheit einer Anwendung. Die Betriebs- und Prüfziele verlangen reproduzierbare Proben; ein Ziel allein ist kein bestandener Nachweis.

Für ein Lernprojekt empfiehlt sich: zuerst check und run, dann Schema-Vorschau und Plan, danach Seiten/Formulare/CRUD und schließlich Authentifizierung, Mail und Integrationen.

23. KI-native Entwicklung

Die aktuellen Architektur- und Spezifikationsdokumente ergänzen ein wichtiges Prinzip:

Die KI schreibt. Zelyra prüft.

Zelyra soll für Menschen und KI-Systeme gleichermaßen nutzbar sein, bleibt aber vollständig KI-unabhängig. Der Compiler und die Tests sind die Vertrauensgrenze; eine plausible Erklärung eines Modells ist kein Korrektheitsnachweis. Für menschlichen und generierten Code gelten dieselben Prüfungen für Lexer, Parser, Namen, Typen, SQL, Capabilities, Contracts, Tests und Laufzeit.

Heute verfügbare Maschinenschnittstellen

🧪 Die JSON-Ausgaben für Werkzeuge verwenden das gemeinsame Format mit schema_version: "1". Menschliche Ausgabe bleibt Standard; JSON wird nur mit --format=json angefordert:

{
    "schema_version": "1",
    "command": "check",
    "success": false,
    "diagnostics": []
}

Die Ausgabe ist deterministisch. schema_version ist verpflichtend; neue optionale Felder dürfen innerhalb einer Version ergänzt werden, inkompatible Änderungen benötigen eine neue Version. JSON gehört ausschließlich auf stdout, technische Meldungen auf stderr. Source-Spans verwenden nullbasierte UTF-8-Byte-Offsets, einsbasierte Zeilen-/Byte-Spalten und ein halb-offenes Intervall. Secrets, Zeitstempel, Zufalls-IDs, absolute maschinenabhängige Pfade und Live-Datenbankinhalte gehören nicht in diese Ausgaben.

Kanonische Quellformatierung

✅ zelyra fmt <file.zyl> erzeugt nach erfolgreichem Lexen und Parsen eine deterministische Quellformatierung. zelyra fmt <file.zyl> --check schreibt keine Dateien und liefert einen Fehlercode, wenn eine Änderung nötig wäre; damit kann CI kanonischen Quellcode erzwingen.

zelyra fmt examples/fibonacci.zyl
zelyra fmt examples/fibonacci.zyl --check

Der Formatter bewahrt Zeilenkommentare und behandelt SQL- und HTML-Blöcke als opaken Quelltext. Er ist idempotent: Ein bereits formatiertes Dokument erzeugt byte-identisch dieselbe Ausgabe.

Typisierte Lücken (Typed Holes)

✅ Ausdrucks-Typed-Holes mit _ sind als erste sichere Stufe verfügbar. Der Compiler meldet Kontexttyp, sichtbare Werte und Funktionen, aktive Capabilities, Contract-Pflichten und Source-Span:

fn double(x: Int) -> Int {
    return _
}

zelyra check meldet die Diagnose E-HOLE-001 mit dem erwarteten Typ Int und den sichtbaren Bezeichnern. Baubare Befehle (build, run, serve) lehnen unvollständigen Code vor Lowering und Ausführung ab. Lücken in Deklarationskontexten bleiben geplant.

Strukturierte Projektübersicht

✅ context ist schreibgeschützt und verbindet sich nicht mit MariaDB, nutzt kein Netzwerk, führt keine E-Mail oder Jobs aus und gibt keine Geheimnisse aus:

zelyra context examples/auth_crud_api.zyl --format=json

Sie meldet deklarierte Funktionen, Tabellen, SQL-Abfragen, CRUD-Ressourcen, Formulare, APIs und Source-Spans.

Deterministische Wirkungsanalyse

🧪 Quelltextabhängigkeiten eines Programms lassen sich deterministisch prüfen:

zelyra impact examples/auth_crud_api.zyl --format=json
zelyra impact examples/auth_crud_api.zyl --symbol table:customers --format=json

Die Wirkungsantwort meldet quelltextbasierte Tabellen, SQL, Formulare, CRUD- Ressourcen, Views, APIs, Berechtigungen, Contracts und eine deterministische references-Kantenliste für bekannte Beziehungen. Jede bekannte Kante enthält Quelle, Ziel, Art und Quelltextspanne. E-Mail-, Job-, Test- und Live-Schemaauswirkungen bleiben ausdrücklich leer oder nicht verfügbar; der Befehl verbindet sich nie mit MariaDB.

Mit --symbol <kind:name> kann die Ausgabe auf einen bekannten Knoten wie table:customers fokussiert werden. Die fokussierte Antwort enthält nur direkt verbundene Referenzen und zugehörige Knoten-IDs. Unbekannte Knoten liefern E-IMPACT-001 und einen Exit-Code ungleich null.

Atomare semantische Änderungen

🧪 Eine validierte Symbol-Umbenennung kann ohne Änderung des Quelltexts als Vorschau berechnet werden:

{
  "schema_version": "1",
  "entry": "examples/fibonacci.zyl",
  "expected_source_fingerprint": "fnv1a64:18f35ecb3e2f99c4",
  "operations": [
    {"kind": "rename", "symbol": "function", "from": "fibonacci", "to": "fib"}
  ]
}

Als change.json speichern und ausführen:

zelyra edit --format=json change.json

Die Anfrage ist versioniert und darf nur auf eine existierende .zyl-Datei innerhalb der aufgelösten Zelyra-Projektwurzel zeigen. Quelltext vor und nach der Änderung muss die Compilerprüfungen bestehen. Das Ergebnis meldet die genauen Token-Spans und einen deterministischen Quelltext-Fingerprint.

Für --apply muss die Anfrage den Fingerprint aus der Vorschau enthalten; so wird eine zwischenzeitlich geänderte Datei nicht überschrieben (Stale-Source- Schutz). Ohne den ausdrücklichen --apply-Schalter bleibt es eine reine Vorschau:

zelyra edit --format=json --apply change.json

Vor dem atomaren Ersetzen wird der Quelltext erneut geparst und vollständig geprüft; ein ungültiger oder semantisch unsicherer Vorschlag kann daher nicht geschrieben werden.

Umbenennungen von Funktionen, Typen und Records sind AST-basiert: Deklarationen und bekannte Referenzen werden umbenannt, während lokale Bindungen mit demselben Namen unverändert bleiben. Tabellen-, View-, Form- und CRUD-Deklarationen sowie ihre strukturierten Referenzen werden ebenfalls unterstützt. Tabellenumbenennungen aktualisieren geprüfte SQL-Tabellenpositionen (FROM, JOIN, INTO, UPDATE), lassen aber Literale, Kommentare, Parameter und HTML unverändert. Komponenten-Umbenennungen aktualisieren die Deklaration sowie bekannte öffnende und schließende Komponententags in HTML-Bodies.

Sicherheitsgrenze und Benchmark

KI-Werkzeuge dürfen nicht unbemerkt Capabilities hinzufügen, Berechtigungen erweitern, destruktives SQL ausführen, Diagnosen abschwächen, Tests deaktivieren oder Geheimnisse ausgeben. Destruktive Schemaänderungen und sicherheitsrelevante Änderungen brauchen eine sichtbare menschliche Freigabe. Zelyra sendet keinen Quelltext automatisch an externe KI-Dienste; geplante Integrationen sollen offen, lokal nutzbar, herstellerneutral und versioniert sein.

Der neue KI-Autorenschaftsbenchmark ist eine Spezifikation in docs/benchmarks/ai-authoring.de.md. Er soll mit versionierten Fixtures und identischen Aufgaben unter anderem Erstversuchskompilierung, Korrekturschleifen, Zeit bis zu bestandenen Tests, Tokens, Sicherheitsfehler, übersehene Abhängigkeiten, unsichere Schemaänderungen und menschlichen Prüfaufwand messen. Es gibt noch keine veröffentlichten Vergleichsergebnisse. Ein Secret-Leak oder eine nicht freigegebene destruktive Änderung bleibt ein Sicherheitsfehler und wird nicht durch vermeintliche Produktivität aufgewogen.


24. Verbindliche Quellen und Compiler-Prüfung (Source Authority)

Grundsatz: Zelyra ist eine eigenständige Sprache. Parser und geprüfte Tests entscheiden, was existiert.

Bei Widersprüchen gilt immer folgende verbindliche Reihenfolge:

  1. Formale Sprachspezifikation und Phasendokumente
  2. Compiler-Code: Lexer-, AST-, Parser-, Namensauflösungs-, Typprüfungs- und semantischer Code in den Crates lexer, parser, ast, hir, cli, runtime, database, web und forms
  3. Offizielle automatisierte Sprach- und Integrationstests: Workspace-Tests (cargo test --workspace), Machine-Interface-Tests und E2E-Shellskripte in tests/
  4. Die offizielle Standardbibliothek: (sobald eigenständig strukturiert)
  5. Offizielle Zelyra-Beispiele: .zyl-Dateien in examples/, die mit dem aktuellen Compiler erfolgreich verifiziert wurden
  6. Dokumentation und Handbuch

Wichtige Invarianten für Entwickler und KI-Assistenten

  • Roadmap ist Planung, keine Syntax: Zukünftige Phasenvorschläge dürfen erst nach Implementierung in Lexer/Parser als verfügbare Syntax dargestellt werden.
  • Compiler-Implementierungssprache Rust ist kein Zelyra: Zelyra wird in Rust entwickelt, aber Rust-Syntax in einer .zyl-Datei ist ungültig, es sei denn, die Zelyra-Grammatik definiert sie ausdrücklich.
  • Keine erfundenen Befehle: Alle CLI-Befehle müssen in cli/src/main.rs existieren.
  • Prüfzyklus: Jede Erweiterung durchläuft Formatierung (cargo fmt), Typ- und Crate-Prüfung (cargo check), Linter (cargo clippy) und Tests (cargo test).

25. Anwendungs-Mail und SMTP

Anwendungs-Mail wird in derselben MariaDB-Transaktion wie eine Geschäftsänderung eingereiht. Der Webrequest wartet nicht auf SMTP. Ein separater Worker stellt fällige Nachrichten zu.

Projekt und Transport konfigurieren

[capabilities]
email = true
database = true

[network]
allowed_hosts = ["smtp.example.test"]
ZELYRA_SMTP_HOST=smtp.example.test
ZELYRA_SMTP_SECURITY=starttls
ZELYRA_SMTP_PORT=587
ZELYRA_SMTP_FROM="Zelyra <noreply@example.test>"
ZELYRA_SMTP_USERNAME=app-user
ZELYRA_SMTP_PASSWORD=durch-ein-secret-ersetzen

implicit_tls ist der Standard und verwendet Port 465; starttls verwendet Port 587. Zertifikats- und Hostnamenprüfung bleiben aktiv. Optional ergänzt ZELYRA_SMTP_CA_CERT ein PEM-Wurzelzertifikat aus einer regulären Datei ohne Symlink bis 128 KiB. local_plaintext ist nur für Loopback ohne Zugangsdaten zulässig. Der genaue SMTP-Host muss in der Allowlist stehen. Speichere Zugangsdaten in der lokalen .env oder Prozessumgebung; übernimm sie nicht in die Versionsverwaltung.

Nachrichten einreihen

Speichere folgendes Beispiel als main.zyl in einem entsprechend konfigurierten MariaDB-Projekt:

pub fn deliver_mail(event_id: String, event_type: String, payload_json: String) -> Bool uses Email {
  if event_type == "zelyra.mail.send" {
    match mail_deliver(event_id, payload_json) {
      Ok(_) => { return true }
      Err(_) => { return false }
    }
  }
  return false
}

fn main() uses Email, Database(write) {
  transaction {
    mail_queue(
      "customer@example.test",
      "Your Zelyra order",
      "Your order has been recorded.",
      "<p>Your order has been recorded.</p>"
    )
  }
}

mail_queue(to, subject, text, html?) ist eine Anweisung direkt innerhalb von transaction. Die Funktion benötigt Email und Database(write). Die API prüft Header-Injection und Größen; Argumente müssen Werte oder nebenwirkungsfreie String-Ausdrücke sein. Bei einer zurückgerollten Transaktion bleibt keine zugehörige Mail eingereiht. HTML-Inhalte musst du vor dem Einreihen passend escapen, wenn sie nicht vertrauenswürdig sind.

Outbox und Worker

zelyra outbox setup main.zyl
zelyra run main.zyl
zelyra outbox list main.zyl --limit 25
zelyra outbox run main.zyl --handler zelyra.mail.send=deliver_mail --once

Richte die Outbox vor dem ersten Programmlauf ein. --once verarbeitet einen fälligen Eintrag; ohne diese Option verarbeitet der Worker zugeordnete fällige Ereignisse, bis keines mehr ansteht. mail_deliver(event_id, payload_json) liefert einen Result; der Handler bestätigt Erfolg mit true und erlaubt Wiederholung mit false. Der SMTP-Transport hat ein Zeitlimit von fünf Sekunden und gibt redigierte Fehler aus.

Zustellung erfolgt mindestens einmal. Nach SMTP-Annahme und vor Outbox-Quittierung kann ein Worker ausfallen; dadurch sind Duplikate möglich. Die Ereignis-ID wird als stabile Nachrichten-ID verwendet, garantiert aber keine einmalige Zustellung. Die dokumentierte API unterstützt Text-/HTML-Alternativen über die MariaDB-Outbox. Benannte Templates, Anhänge und PostgreSQL-Mail-Outbox sind nicht als verfügbare API belegt. Siehe die Mail-Referenz.

Persistente Passwort-Reset-Quoten

Zur Härtung gegen Brute-Force- und Denial-of-Service-Angriffe speichert Zelyra Passwort-Reset-Anfragen neustartsicher in der Tabelle password_reset_limits. Die Drosselung bleibt auch bei Prozessneustarts aktiv. Abgelaufene Zeitfenster werden in Batch-Zyklen automatisch aufgeräumt, während Diagnoseausgaben für Operatoren strikt sanitisiert bleiben, um keine E-Mail-Adressen oder Tokens im Protokoll preiszugeben.

26. Mandantengebundene Benachrichtigungen

Das In-App-Zentrum funktioniert ohne SMTP. Es verwendet authentifizierte Mandantenzugehörigkeit, eine gemeinsame Benachrichtigungstabelle und eine mandantengebundene CRUD-Ressource. Der aktive Mandant begrenzt Listen, direkte Zugriffe und den ungelesenen Zähler. Abgelaufene und archivierte Einträge werden ausgeblendet.

Vollständiges MariaDB-Beispiel

Speichere das Beispiel in einem MariaDB-Projekt mit aktivierten Datenbank-, Web-, CRUD- und Auth-Funktionen. Richte Schema, Benutzer, Passwort-Hash, Mandantenzugehörigkeit und Berechtigungen ein, bevor du die Anwendung im Browser verwendest:

# Tenant-scoped notification inbox starter for MariaDB projects.
# Every generated notification route verifies the authenticated membership.

auth users {
    table: users
    sessions: auth_sessions
    permissions: user_permissions
    memberships: tenant_memberships
}

table users {
    id: Id primary auto
    email: Email required unique
    password_hash: String(255) required
    active: Bool default true
}

table auth_sessions {
    id: Id primary auto
    user: User required
    token_hash: String(64) required unique
    expires_at: Timestamp required
}

table user_permissions {
    id: Id primary auto
    user_id: Id required
    permission: String(100) required
}

table tenant_memberships {
    id: Id primary auto
    user_id: Id required
    tenant_id: Id required
    active: Bool required
}

table notifications {
    id: Id primary auto
    tenant_id: Id required
    title: String(160) required
    message: String(4000) required
    category: String(40) required
    read_at: Timestamp?
    archived_at: Timestamp?
    expires_at: Timestamp?
    created_at: Timestamp default now
}

crud Notification -> notifications {
    title: "Notifications"
    tenant: tenant_id
    unread: read_at
    expires: expires_at
    requires auth
    permits "notifications.view"
    permits create "notifications.manage"
    permits edit "notifications.manage"
    permits delete "notifications.manage"

    soft_delete {
        column: archived_at
    }

    list {
        title
        category
        created_at
        read_at
    }

    search {
        title
        message
    }

    filter {
        category
        read_at
    }

    view {
        fields {
            title
            message
            category
            read_at
            expires_at
        }
    }
}

table notification_preferences {
    id: Id primary auto
    tenant_id: Id required
    email_enabled: Bool default false
    retention_days: Int default 90
    updated_at: Timestamp default now
}

crud NotificationPreference -> notification_preferences {
    title: "Notification preferences"
    tenant: tenant_id
    requires auth
    permits "notifications.manage"
    list {
        email_enabled
        retention_days
        updated_at
    }
    view {
        fields {
            email_enabled
            retention_days
        }
    }
}

fn main() {
}

tenant: tenant_id bindet die Ressource an den aktiven Mandanten und seine Mitgliedschaft. unread: read_at stellt den ungelesenen Zähler bereit; expires: expires_at blendet abgelaufene Einträge aus. soft_delete archiviert über archived_at. notifications.view erlaubt Lesen, notifications.manage schützt Verwaltung und Änderungen. Ein Tenant-Feld allein ohne die Auth-/Mitgliedschaftskonfiguration ist keine Mandantenisolierung.

Lesestatus, Aufbewahrung und Mail

read_at ist ein Zeitstempel am Benachrichtigungseintrag. Über die Empfängerbindung (recipient: user_id) wird sichergestellt, dass Benachrichtigungen nur für den berechtigten Benutzer sichtbar sind. Persönliche Lesebestätigungen werden mit CSRF-Schutz und atomarem Audit-Logging quittiert. Die Einstellungstabelle speichert email_enabled und retention_days. Ablauf wird beim Lesen berücksichtigt; Archivierung ist keine physische Löschung. Das Beispiel zeigt den vorhandenen Umfang.

Lokales TOTP-MFA

Für mehrstufige Authentifizierung unterstützt Zelyra zeitbasierte Einmalkennwörter (TOTP nach RFC 6238). Die geheimen Schlüssel werden mit AES-256-GCM verschlüsselt in der Datenbank abgelegt. Einmalige Wiederherstellungscodes werden mit Argon2id sicher gehasht. Das System wehrt Token-Wiederholungsangriffe (Replay Defense) sowie Rennbedingungen ab und sperrt Konten nach fünf aufeinanderfolgenden Fehlversuchen automatisch temporär (HTTP 429).

27. Sprache, Laufzeit und PDF-Mail

Dieses Kapitel dokumentiert die Spracherweiterungen aus Zelyra 1.0.7. Die Version erweitert Sprache und Runtime für gut prüfbare Businesslogik und ergänzt eine lokale, serverseitige Text-PDF-Pipeline für Views und transaktionale E-Mail-Anhänge.

Wertliefernde Ausdrücke und Record-Updates

Ein if-Ausdruck liefert in beiden Zweigen denselben typgeprüften Wert. Records lassen sich mit ..old funktional kopieren und selektiv aktualisieren:

fn invoice_label(paid: Bool) -> String {
    return if paid { "Bezahlt" } else { "Offen" }
}

struct Customer {
    id: Int
    name: String
    active: Bool
    version: Int
}

fn activate_customer(c: Customer) -> Customer {
    return Customer { ..c, active: true, version: c.version + 1 }
}

Typisierte Lambdas und Pipelines

Lambdas sind lexikalische Closures. Der Compiler prüft Callback-Eingaben und Ergebnisse statisch; Pipelines bleiben synchron und deterministisch:

fn calculate_tax_totals(amounts: Int[]) -> Int[] {
    return amounts
        |> filter(|a| a > 0)
        |> map(|a| a + (a * 19 / 100))
}

Verschachtelte Record-Muster, Match-Guards und Tupel

match unterstützt verschachtelte Record-Muster und boolesche Wächter (Guards). Tupel können als reguläre Rückgabewerte dienen und mehrfach destrukturiert werden:

struct Order { status: String total: Int }

fn inspect_order(order: Order) -> Tuple<String, Bool> {
    return match order {
        Order { status: "paid", total } if total >= 1000 => ("VIP-Grossauftrag", true)
        Order { status: "paid", total }                  => ("Standardauftrag", true)
        _                                                => ("Ausstehend", false)
    }
}

Begrenzte Ausführung mit Step-Budget

zelyra run datei.zyl --max-steps N begrenzt Anweisungen und Ausdrucksauswertungen über alle parallel-Zweige hinweg und meldet Überschreitungen als E-RUNTIME-001 an der Quellstelle:

zelyra run main.zyl --max-steps 5000

Serverseitiges Text-PDF und transaktionale Outbox-E-Mail

render_pdf("InvoicePrint", invoice) rendert eine deklarierte View lokal in Bytes. Innerhalb derselben Datenbanktransaktion kann mail_queue einen PDF-Anhang in der MariaDB-Outbox einreihen:

fn send_invoice(recipient: String, invoice: Invoice) uses Email, Database(write) {
    transaction {
        mail_queue(
            recipient,
            "Rechnung " + invoice.number,
            "Ihre Rechnung ist beigefügt.",
            "invoice-" + invoice.number + ".pdf",
            render_pdf("InvoicePrint", invoice)
        )
    }
}

28. Systemd-Dienste, Scheduler und Jobs

Zelyra integriert Betriebssystemdienste, permanente Hintergrund-Zeitpläne und asynchrone Jobs direkt in die Toolchain, ohne externe Cron-Dämonen oder unzuverlässige Skriptschleifen.

Linux-systemd-Dienstverwaltung

Mit zelyra service wird für ein Projekt ein dedizierter Linux-systemd-Benutzerdienst eingerichtet. Der Service verwendet einen fest konfigurierten Port:

# Dienst auf festem Port 8080 installieren
zelyra service install --port 8080

# Status des Dienstes und HTTP-Erreichbarkeit prüfen
zelyra service status

# Dienst neu starten (wartet auf lokale Socket-Erreichbarkeit)
zelyra service restart

# Port unterbrechungsfrei und mit atomarem Rollback ändern
zelyra service change --port 8081

# Dienst stoppen und Unit-Datei entfernen
zelyra service remove

Ist der angegebene Port belegt, schlägt die Installation fehl (fail-closed). Zelyra weicht niemals stillschweigend auf einen anderen Port aus. Bei service change wird die alte Unit erst dann überschrieben und der neue Dienst gestartet, wenn der Zielport frei ist; bei Fehlern wird der vorherige Zustand automatisch wiederhergestellt.

Residente Zeitpläne und MariaDB-Outbox-Jobs

Zeitpläne werden deklarativ im Zelyra-Quellcode definiert:

schedule weekly_report {
    cron: "0 8 * * 1"
    timezone: "Europe/Berlin"
    command: generate_reports
}

Der residente Scheduler läuft ausschließlich innerhalb des aktiven Zelyra-Projektdienstes. An jeder Minutengrenze prüft er anstehende Ausführungen und reiht geplante Jobs transaktional in die MariaDB-Outbox ein. Ein serieller Worker übernimmt anstehende Aufgaben mit erneuerbaren Leases und begrenzten Wiederholungen.

# Alle anstehenden und ausgeführten Jobs des Projekts einsehen
zelyra schedule jobs main.zyl

# Status des residenten Schedulers abfragen
zelyra schedule status main.zyl

# Zeitpläne als crontab-kompatible Vorschau exportieren (z. B. für externe Systeme)
zelyra schedule export main.zyl --format cron

29. Kanonische Projektstruktur und Vertical Slices

Ab Version 1.2.13 unterstützt Zelyra ein verbindliches, feature-orientiertes Standard-Projektlayout (Vertical Slice Architecture), das in Version 1.3.0 zum Standard avanciert. Es trennt Routing strikt von Geschäfts- und Datenmodell.

Das Standardlayout anlegen

zelyra new mein-erp --layout standard

Ein Projekt mit Standard-Layout besitzt folgende verbindliche Struktur:

mein-erp/
├── zelyra.toml                 # Projekt-Manifest (layout = "standard")
├── schema/                     # Single Source of Schema
│   └── tables.zyl              # Tabellen, Indizes, Relationen
├── domain/                     # Fachdomänen (Vertical Feature Slices)
│   ├── invoices/               # Feature-Modul Rechnungen
│   │   ├── actions.zyl         # Geschäftsvorfälle & Rechenregeln
│   │   └── views.zyl           # UI-Komponenten & Ansichten
│   └── customers/              # Feature-Modul Kunden
│       ├── actions.zyl
│       └── views.zyl
├── routes/                     # Schlanke HTTP-Gatekeeper
│   ├── web.zyl                 # Web-Routen & Formular-Handling
│   └── api.zyl                 # REST-/JSON-Endpunkte
├── public/                     # Statische Dateien (vom Server direkt ausgeliefert)
└── assets/                     # Web-Assets (fließen in das Binary ein)
    ├── css/
    └── js/

Namespace-Importe und strikte Architekturprüfung

Im Standardlayout löst Zelyra Importe direkt über die Namensraumwurzeln auf:

use schema::tables::*
use domain::invoices::actions as invoice_ops

Wird in der zelyra.toml der Schalter strict_architecture = true aktiviert, erzwingt der Zelyra-Linter folgende Kardinalregel:

Keine direkten Datenbankmutationen in Routen-Dateien (routes/*.zyl).

Schreibende Operationen (insert, update, delete) innerhalb von Routen führen zum Abbruch mit dem Compilerfehler E-ARCH-001. Fachlogik muss in domain/<feature>/actions.zyl implementiert und getestet werden.

# Migrationsvorschau auf das Standardlayout prüfen (schreibgeschützt, verändert keine Dateien)
zelyra migrate-layout --check main.zyl

30. Erweiterbare CRUD- und TableView-Seiten

Zelyra 1.2.15 führt typgeprüfte Erweiterungspunkte für automatisch generierte CRUD-Seiten und Tabellenansichten ein. Entwickler können Standard-Masken flexibel anpassen, ohne das sichere Fundament aus Validierung, Berechtigungen und CSRF-Schutz aufgeben zu müssen.

Create- und Edit-Seiten anpassen

Über crud.view.create und crud.view.edit lassen sich Seitentitel, Schaltflächentexte und ergänzende Inhaltsblöcke vor oder nach dem Formular einbinden:

crud KundenVerwaltung for Customer {
    view create {
        title: "Neuen Kunden registrieren"
        submit_label: "Kundenkonto anlegen"
        help: "Bitte prüfen Sie die USt-IdNr. vor dem Speichern."
    }
    view edit {
        title: "Kundendaten aktualisieren"
        submit_label: "Änderungen übernehmen"
    }
}

Zusätzliche Inhalte verwenden denselben sicheren Template-Kontext und unterliegen voller XSS-Escaping- und Typprüfung. Scheitert eine Formularvalidierung, bleiben die ergänzten Inhalte und Hinweistexte zusammen mit den Fehlermeldungen erhalten.

Erweiterte TableView-Listen und Zeilenaktionen

Tabellenansichten (tableview) können mit Kopf- und Fußbereichen sowie individuellen Zeilenaktionen versehen werden:

tableview RechnungsListe for Invoice {
    columns: [number, customer_name, total_gross, status]
    row_actions: [
        action "Details" -> "/invoices/show/{id}",
        action "PDF" -> "/invoices/{id}/pdf"
    ]
}

Die Zeilenaktionen erzeugen saubere, auditierbare URLs. Sicherheitsprüfungen und Mandanten-Filter bleiben vollständig wirksam.

31. AST-, MIR- und Bytecode-Ausführung

Stand des stabilen Releases 1.2.39: Der AST-Interpreter bleibt der Standard-Ausführungspfad. MIR und Bytecode unterstützen ausgewählte Abläufe; vollständige Web-/Laufzeitparität bleibt offene Arbeit auf dem Weg zu 1.3.0. Dieser Abschnitt beschreibt Architektur und Grenzen. Er behauptet keinen allgemeinen Leistungsvorteil oder abgeschlossene Enterprise-Garantien.

Quellcode (.zyl)
      │
      ▼
Lexer & Parser
      │
      ▼
AST mit Source-Spans
      │
Gemeinsame statische Prüfungen (Types, Contracts, Scopes)
      │
      ├───────────────────────────────┐
      ▼                               ▼
[1. AST-Interpreter]            [2. MIR und Bytecode]
- Standard-Ausführungspfad      - Für unterstützte Abläufe verfügbar
- Quellbezogene Diagnostik      - Geprüfte Zwischenrepräsentationen
- Vergleichsreferenz            - Paritätsprüfungen bleiben offen
  1. AST-Interpreter: Standardpfad für unterstützte Programmausführung und Referenz beim Vergleich von Backends.
  2. Typisierte MIR: Trägt geprüfte Programmdaten für unterstützte Konstrukte; der Verifier weist widersprüchliche SQL-Metadaten mit E-MIR-VERIFY-004 ab.
  3. Bytecode-VM: Führt den unterstützten Bytecode-Umfang aus. Parität wird dort getestet, wo sie implementiert ist; die Tests garantieren sie nicht für jede Sprachfunktion oder jeden Webablauf.

32. Mandantensicherheit und zusammengesetzte Fremdschlüssel

In mandantenfähigen B2B-Anwendungen darf kein Datensatz ohne Tenant-Zugehörigkeit existieren, und Fremdschlüssel dürfen niemals mandantenübergreifend referenzieren. Zelyra verankert Mandantensicherheit direkt im Tabellenschema (scoped by tenant_id: TenantId).

Deklaration von Mandantentabellen

type TenantId = Id

table customers scoped by tenant_id: TenantId {
    id: Id primary auto
    tenant_id: TenantId required
    customer_number: String(50) required unique within tenant
    name: String required
}

table invoices scoped by tenant_id: TenantId {
    id: Id primary auto
    tenant_id: TenantId required
    invoice_number: String(50) required unique within tenant
    customer: Customer?
    total_net: Decimal(12, 2) required default 0
    total_gross: Decimal(12, 2) required default 0
}

Automatische Integritätsgarantien auf Datenbankebene

  1. Zusammengesetzter Primärschlüssel-Schutz: Zelyra erzeugt automatisch uq_{table}_tenant_id UNIQUE (tenant_id, id).
  2. Mandantenisolierte Fremdschlüssel: Verknüpfungen zwischen mandantenisolierten Tabellen (customer: Customer?) generieren automatisch zusammengesetzte Fremdschlüssel:
    FOREIGN KEY (tenant_id, customer_id) REFERENCES customers (tenant_id, id) ON DELETE RESTRICT
    
    Diese Constraints weisen mandantenübergreifende Fremdschlüsselzuweisungen auf den unterstützten Schema-Pfaden auf Datenbankebene zurück.
  3. Mandantenbezogene Eindeutigkeit: Mit unique within tenant deklarierte Felder erzeugen UNIQUE KEY (tenant_id, customer_number). Unterschiedliche Mandanten können dieselbe Belegnummer führen, innerhalb eines Mandanten sind Duplikate ausgeschlossen.
  4. Optionale Relationen und kaufmännische Dezimalzahlen: Optionale Verknüpfungen (Customer?) erzeugen typsicher BIGINT NULL. Decimal(p, s) (z. B. Decimal(12, 2)) garantiert exakte kaufmännische Rundung ohne Gleitkomma-Ungenauigkeiten.

33. Tabelleninvarianten und Validierungsregeln

Geschäftsregeln gehören direkt an die Datenstruktur. Statt Validierungsregeln in Controllern, Requests und Formularen redundant zu wiederholen, deklariert Zelyra typgeprüfte Invarianten im table-Block.

Deklaration von Tabelleninvarianten

table invoices scoped by tenant_id: TenantId {
    id: Id primary auto
    tenant_id: TenantId required
    invoice_date: Date required
    due_date: Date required
    discount_days: Int required default 0

    validate {
        due_date >= invoice_date: "Das Fälligkeitsdatum muss am oder nach dem Rechnungsdatum liegen."
        discount_days >= 0: "Skontofrist darf nicht negativ sein."
    }
}

Durchsetzung in Formularen und APIs

  • Deklaratives CRUD und Formulare: Vor jedem Schreibvorgang (Create/Edit) evaluiert Zelyra die Invarianten atomar. Werden Invarianten verletzt, wird der Schreibvorgang verhindert und das Formular liefert die deklarierte Fehlermeldung mit HTTP-Status 422.
  • Typisierte API-Eingabeprüfung: APIs können Request-Bodies an Tabellen binden (validates). Die Validierung liefert standardisierte HTTP-422-Fehlerverträge, bevor der Handler ausgeführt wird.

34. Lifecycle-Guards, Policies und Master-Detail-CRUD

Revisionssicherheit (z. B. nach GoBD) verlangt, dass festgeschriebene Belege nicht mehr nachträglich verändert oder gelöscht werden dürfen. Gleichzeitig müssen komplexe Belege wie Rechnungen oder Aufträge Kopf- und Positionsdaten atomar erfassen.

Datensatzabhängige Lifecycle-Guards

table invoices scoped by tenant_id: TenantId {
    id: Id primary auto
    tenant_id: TenantId required
    invoice_date: Date required
    is_locked: Bool required default false
}

crud Invoice -> invoices {
    title: "Rechnungen"

    guard edit when is_locked == true {
        error: "Festgeschriebene Rechnungen können nicht mehr bearbeitet werden (GoBD-Schutz)."
    }

    guard delete when is_locked == true {
        error: "Festgeschriebene Rechnungen dürfen nicht gelöscht werden."
    }
}
  • Guard-Semantik als Abweisungsbedingung: Ein Guard weist eine Aktion ab, wenn seine Bedingung wahr (true) ist. Für den Schutz vor unbefugter Bearbeitung oder Festschreibung prüft man daher guard edit when is_locked == true. Eine Bedingung wie is_locked == false würde fälschlicherweise noch offene Entwürfe abweisen.
  • Transaktionale Prüfung: Die Sperrbedingung wird nach Erwerb der Zeilensperre innerhalb der Schreibtransaktion evaluiert. Direkte POST-Requests können den Schutz nicht umgehen.
  • UI-Integration: In tableview-Aktionsslots und Detailansichten werden Bearbeiten- und Löschen-Aktionen abhängig vom Datensatzstatus automatisch deaktiviert oder ausgeblendet.

Zusammenspiel mit GoBD, Prüfsummen und Capabilities

Reale Geschäftsanwendungen erfordern neben Lifecycle-Sperren weitere Bausteine, die Zelyra über die Standardbibliothek und Capabilities bereitstellt:

  1. Kryptografische Prüfsummen (hash_sha256): Die Funktion hash_sha256(text: String) -> String aus der Standardbibliothek berechnet den SHA-256-Hash eines Strings. Sie dient dazu, den Inhalt eines Belegs bei der Festschreibung kryptografisch zu fixieren:
    fn beleg_hash(beleg_nr: String, brutto: Decimal) -> String {
        return hash_sha256(beleg_nr + "#" + brutto)
    }
    
  2. Revisionssichere Zeitstempel (now() und uses Clock): Funktionen, die die aktuelle Zeit ermitteln, müssen die Capability Clock deklarieren (fn festschreiben(...) uses Clock). Dadurch bleibt Zeitverhalten in Unittests deterministisch überprüfbar und reproduzierbar.
  3. Beleg-PDFs und Outbox-Mail (render_pdf und mail_queue): Über render_pdf("RechnungsDruck", invoice) wird eine View serverseitig in PDF-Bytes umgewandelt. Innerhalb einer MariaDB-transaction reiht mail_queue(to, subject, text, filename, pdf_bytes) den Beleg atomar in die Outbox ein.
  4. CRUD-Actions: Seit 1.2.28 unterstützen CRUD-Actions typisierte Handler, Datensatz- und Sitzungsdaten sowie geprüfte Guards im vorhandenen serverseitigen Transaktionskontext. Zulässige Ausdrücke und Effekte folgen der dokumentierten Action-Syntax; Actions sind keine beliebigen Zelyra-Programme.

Transaktionale Master-Detail-CRUD-Formulare (has_many)

Mit has_many deklarieren CRUD-Ressourcen direkte 1:N-Kinddatensammlungen:

table invoice_items scoped by tenant_id: TenantId {
    id: Id primary auto
    tenant_id: TenantId required
    invoice: Invoice required on_delete cascade
    position: Int required
    description: String(160) required
    total_net: Decimal(12, 2) required
    total_gross: Decimal(12, 2) required

    validate {
        total_gross >= total_net: "Bruttobetrag darf nicht kleiner als der Nettobetrag sein."
    }
}

crud Invoice -> invoices {
    title: "Rechnungen"

    view {
        fields { invoice_date }
        form {
            title: "Rechnung"
            submit: "Rechnung speichern"
        }
    }

    has_many items: InvoiceItem {
        foreign_key: invoice_id
        order_by: position asc
        cascade: delete
        aggregates {
            net_amount: sum(total_net)
            tax_amount: sum(total_gross - total_net)
            gross_amount: sum(total_gross)
        }
    }
}
  1. Serverseitiges Rendering: Kindzeilen werden serverseitig in HTML generiert. Zeilen können ohne Client-JavaScript hinzugefügt oder entfernt werden.
  2. Atomare Transaktion: Kopfdaten, Kindpositionen und berechnete Aggregate (aggregates) werden in einer einzigen Datenbanktransaktion gespeichert. Ein Fehler in einer Position rollt den gesamten Vorgang zurück.
  3. Automatische Aggregation: Summenfelder wie net_amount oder gross_amount werden serverseitig aus den Kindzeilen berechnet und atomar im Kopfsatz aktualisiert.

Mehrere Kind-Sammlungen pro CRUD-Ressource (ab Zelyra 1.2.24)

Seit Zelyra 1.2.24 können generierte CRUD-Anlegen- und Bearbeiten-Formulare mehrere direkte has_many-Sammlungen desselben Elternsatzes verwalten (z. B. Positionen und Dateianhänge einer Rechnung in einem gemeinsamen Formular):

table invoices {
    id: Id primary auto
    reference: String(100) required
    invoice_date: Date required
    net_amount: Decimal(12, 2) required default 0
    gross_amount: Decimal(12, 2) required default 0
}

table invoice_items {
    id: Id primary auto
    invoice: Invoice required on_delete cascade
    position: Int required
    description: String(160) required
    total_net: Decimal(12, 2) required
    total_gross: Decimal(12, 2) required

    validate {
        total_gross >= total_net: "Line gross amount cannot be below its net amount."
    }
}

table invoice_attachments {
    id: Id primary auto
    invoice: Invoice required on_delete cascade
    file_name: String(255) required
    size_bytes: Int required

    validate {
        size_bytes > 0: "Attachment size must be positive."
    }
}

crud Invoice -> invoices {
    title: "Rechnungen mit Positionen und Anhängen"

    view {
        fields { reference invoice_date }
        form {
            title: "Rechnung"
            submit: "Rechnung speichern"
        }
    }

    has_many items: InvoiceItem {
        foreign_key: invoice_id
        order_by: position asc
        cascade: delete
        aggregates {
            net_amount: sum(total_net)
            gross_amount: sum(total_gross)
        }
    }

    has_many attachments: InvoiceAttachment {
        foreign_key: invoice_id
        order_by: file_name asc
        cascade: delete
    }
}
  • Unabhängige Abschnitte und Aktionen: Jede Sammlung erscheint in einem eigenen Formularabschnitt mit eigener Zeilenanzahl und unabhängigen Zeilenaktionsnamen (add_item, remove_item, add_attachment, etc.).
  • Unabhängiges Zeilenlimit: Das serverseitige Limit von 50 Zeilen gilt pro deklarierter Sammlung separat.
  • Atomare Validierung und Speicherung: Zelyra validiert alle Sammlungen vor dem Schreiben. Elterndaten, alle Kindzeilen beider Sammlungen und deklarierte Aggregate werden in einer einzigen Datenbanktransaktion gespeichert. Ein Fehler in einer beliebigen Sammlung rollt den gesamten Vorgang atomar zurück.
  • Eindeutigkeit von Kindtabellen: Jede Kindtabelle darf nur einmal an eine CRUD-Ressource gebunden werden; mehrere Sammlungen dürfen dasselbe Aggregatziel nicht mehrdeutig beschreiben.

35. Das Zelyra-Ökosystem: Pakete, Module und Einstellungen

Das Zelyra-Modul- und Paketökosystem ermöglicht die saubere Modularisierung von Anwendungen in wiederverwendbare, unabhängig testbare und kryptografisch prüfbare Komponenten. Es verbindet Offline-First-Prinzipien mit strikter Typisierung und Compiler-Garantien.

Grundlagen und Philosophie des Zelyra-Ökosystems

  1. Offline-First und Deterministisch: Ein Zelyra-Projekt baut und prüft vollständig offline. Es gibt keine impliziten Downloads beim Kompilieren (zelyra check / zelyra run), keine unkontrollierten Netzwerkzugriffe und keine Ausführung beliebiger Build-Skripte von Drittanbietern.
  2. Integrität und Lockfile: Jede Abhängigkeit wird mit einer kryptografischen SHA-256-Prüfsumme in zelyra.lock fixiert. Änderungen an Quellcode oder Paketmanifest werden vom Compiler sofort erkannt und erfordern eine explizite Aktualisierung (zelyra package lock --update).
  3. Kryptografisches Vertrauensmodell (Ed25519): Für die Weitergabe und Registrierung nutzt Zelyra signierte .zpkg-Quellarchive. Ein Archiv wird mit dem privaten Ed25519-Schlüssel eines Herausgebers signiert. Empfänger hinterlegen autorisierte öffentliche Schlüssel in zelyra.trust.toml. Ohne explizites Vertrauen verweigert Zelyra die Installation.
  4. Compiler-Effektobergrenzen (effects): Ein Paket kann im Manifest deklarieren, welche Capabilities seine Funktionen maximal beanspruchen dürfen (z. B. effects = ["Database(read)"] oder effects = [] für rein rechnerische Bibliotheken). Überschreitet eine Funktion im Paket diese Obergrenze, meldet der Compiler einen Fehler.

Ein neues Modul erstellen: zelyra module init

Ein neues Modulpaket wird mit dem CLI-Befehl zelyra module init initialisiert:

zelyra module init packages/finanz_tools --name finanz_tools --version 0.1.0

Der Befehl erstellt die Standardstruktur ohne Netzwerkverbindung und ohne bestehende Dateien zu überschreiben:

packages/finanz_tools/
├── zelyra.toml
├── README.md
└── src/
    └── lib.zyl

Das Paketmanifest: zelyra.toml

Das Manifest zelyra.toml deklariert die Identität, Versionskompatibilität und Effektobergrenzen des Pakets:

[package]
name = "finanz_tools"
version = "0.1.0"
entry = "src/lib.zyl"
zelyra = ">=1.1.0, <2.0.0"
effects = ["Database(read)"]

[settings.api_timeout]
type = "Int"
required = false
default = 15
description = "Zeitlimit für externe Abfragen in Sekunden"

[settings.api_token]
type = "String"
required = true
secret = true
description = "Geheimer API-Schlüssel für Finanzdaten"
  • name: Paketname in Kleinbuchstaben mit Ziffern, _ und -.
  • version: Exakte SemVer-Version (z. B. 0.1.0).
  • entry: Einstiegsdatei, standardmäßig src/lib.zyl für Bibliotheken oder main.zyl für eigenständige Anwendungen.
  • zelyra: SemVer-Vorgabe für die Zelyra-CLI. Liegt die laufende Compiler-Version außerhalb dieses Bereichs, verweigert Zelyra die Übersetzung.
  • effects: Optionale Liste erlaubter Capability-Namen. [] erzwingt seiteneffektfreie reine Funktionen. Fehlt der Eintrag, gilt keine zusätzliche Paketobergrenze.

Modulcode schreiben und exportieren

Funktionen, Records und Enums, die von außen erreichbar sein sollen, müssen mit dem Schlüsselwort pub exportiert werden:

pub record TaxResult {
    net: Decimal,
    tax: Decimal,
    gross: Decimal,
}

pub fn calculate_tax(net: Decimal, rate: Decimal) -> TaxResult
    requires { net >= 0 && rate >= 0 }
    ensures { result.gross >= result.net }
{
    let tax = (net * rate) / 100
    return TaxResult {
        net: net,
        tax: tax,
        gross: net + tax,
    }
}

Nicht als pub deklarierte Deklarationen bleiben paketintern geschützt. Verträge (requires, ensures) werden beim Import vollständig vom Compiler geprüft.

Typisierte Moduleinstellungen (Settings)

Zelyra trennt Paket-Konfigurationsschemata strikt von Anwendungsdaten und Geheimnissen:

  1. Schemadeklaration: Das Schema wird im Paketmanifest unter [settings.<feld>] beschrieben. Unterstützt werden String (bis 4 KiB), Bool, Int und Float. Geheimnisse deklarieren secret = true und dürfen keinen Standardwert besitzen.
  2. Anwendungs-Konfiguration in zelyra.settings.toml: Werte liegen im Workspace der Hauptanwendung, getrennt von zelyra.toml, damit sich Paketprüfsummen nicht verändern:
    [package_settings."packages/finanz_tools"]
    api_timeout = 30
    api_token = { env = "FINANZ_API_TOKEN" }
    
    Geheimnisse werden ausschließlich über { env = "ENV_VAR" } referenziert. Die Zelyra-Laufzeit liest den Wert aus der Prozessumgebung und verhindert die Protokollierung in Fehlermeldungen oder Logs.
  3. Abruf im Zelyra-Code: Paketfunktionen lesen konfigurierte Werte typisiert über die interne generische Funktion module_setting<T>:
    fn get_timeout_seconds() -> Int {
        let timeout = module_setting<Int>("api_timeout")
        return timeout
    }
    
  4. Verwaltung über Web und Studio:
    • CLI-Prüfung: zelyra module settings check main.zyl prüft Typen, Pflichtfelder und die Verfügbarkeit von Umgebungsvariablen.
    • Zelyra Studio: Unter Moduleinstellungen bietet Studio einen revisionsgesicherten Einstellungseditor mit .zelyra.settings.lock.
    • Laufzeit-Webseite: Ist in einer Anwendung Authentifizierung aktiv und admin_permission definiert, steht unter /__zelyra/module-settings eine CSRF-geschützte Webverwaltung für Administratoren bereit.

Einbinden in eine Zelyra-Anwendung

Um ein Modul in einer Hauptanwendung zu verwenden, wird es im Anwendungsmanifest zelyra.toml unter [dependencies.<alias>] eingetragen:

[package]
name = "meine_anwendung"
version = "1.0.0"
entry = "main.zyl"
zelyra = ">=1.2.0"

[dependencies.finanz]
path = "packages/finanz_tools"
version = "^0.1"

Im Quellcode erfolgt der Import über den Namensraum-Präfix pkg:<alias>:

import "pkg:finanz" as finanz

fn main() {
    let result = finanz::calculate_tax(200.0, 19.0)
    print(result.gross)
}

Nach dem Hinzufügen der Abhängigkeit wird das Lockfile synchronisiert und geprüft:

zelyra package lock .
zelyra check main.zyl

Modul-Inspektion, Planung und Bundling

Zelyra stellt spezialisierte Befehle zur Analyse und Distribution von Modulen bereit:

  1. Modul-Katalog anzeigen (module list):
    zelyra module list main.zyl
    zelyra module list main.zyl --format json
    
    Listet alle Quellmodule, Importaliase, öffentlichen Exporte, Prüfsummen, Effektobergrenzen und Einstellungs-Schemas deterministisch auf.
  2. Abhängigkeitsgraph analysieren (module plan):
    zelyra module plan main.zyl pkg:finanz
    
    Berechnet den statischen Quellabschluss für das angegebene Modul und meldet fehlende oder zirkuläre Referenzen vor dem Bauen.
  3. Modul bündeln (module bundle):
    zelyra module bundle main.zyl pkg:finanz --output ./dist/finanz-bundle
    
    Erstellt ein eigenständiges Quellbundle mit isolierten Paketabhängigkeiten und neu berechnetem Lockfile, das offline und portabel deployt werden kann.

Signierte Pakete, Registry und Transaktionale Wiederherstellung

Für teams- und organisationsübergreifende Verteilungen stellt Zelyra kryptografische Paketwerkzeuge bereit:

  • Schlüsselpaar und Signatur:
    zelyra package keygen --publisher finanzteam --output finanzteam.key
    zelyra package sign packages/finanz_tools --publisher finanzteam --key finanzteam.key --output finanz_tools-0.1.0.zpkg
    
  • Herausgebervertrauen einrichten:
    zelyra package trust add finanzteam <oeffentlicher-schluessel-hex>
    zelyra package verify finanz_tools-0.1.0.zpkg
    
  • Statische Registry aufbauen und durchsuchen:
    zelyra package registry build ./veroeffentlichte-pakete
    zelyra package search https://pakete.example.test/index.json finanz
    
  • Verifizierte Installation:
    zelyra package add https://pakete.example.test/index.json finanz_tools 0.1.0 --as finanz
    zelyra package update https://pakete.example.test/index.json finanz_tools 0.2.0 --as finanz
    zelyra package remove finanz
    
  • Transaktionale Wiederherstellung (package recover): Alle mutierenden Paketbefehle schreiben ein geschütztes Transaktionsjournal. Bricht ein Installations- oder Update-Vorgang (z. B. durch Stromausfall oder Verbindungsabbruch) ab, blockieren Folgeoperationen, bis der Zustand geprüft ist:
    zelyra package recover
    zelyra package recover --apply
    
    zelyra package recover --apply stellt Manifest und zelyra.lock atomar auf den letzten fehlerfreien Stand zurück und bereinigt unvollständige Cache-Pfade.

36. CSS-Designsystem und Oberflächengestaltung für CRUD und TableViews

Zelyra verfolgt beim Frontend denselben Grundsatz wie bei Sprache und Datenbank: Konstruierte Sicherheit ohne Ballast. Statt schwergewichtige JavaScript-Build-Pipelines, Node.js-Abhängigkeiten oder npm-Pakete vorauszusetzen, liefert Zelyra ein integriertes, performantes CSS-Designsystem (zelyra-ui.v1.css), das direkt vom integrierten Webserver ausgeliefert wird.

Dieses Kapitel beschreibt die Architektur des Zelyra-UI-Systems, alle verfügbaren Design-Tokens und Klassen sowie konkrete, produktionsreife CSS-Rezepte zur individuellen Gestaltung von CRUD-Seiten, TableViews, Master-Detail-Formularen und Filtern.


1. Architektur des Zelyra-UI-Designsystems

Das Zelyra-Designsystem basiert auf modernen Webstandards und modernen CSS-Kaskadierungs-Ebenen (CSS Cascade Layers @layer).

┌────────────────────────────────────────────────────────────────────────┐
│                        CSS Cascade Layers                              │
│                                                                        │
│  @layer reset         Browser-Normalisierung & Box-Sizing              │
│       ↓                                                                │
│  @layer base          Root-Variablen, Typografie, Standardelemente     │
│       ↓                                                                │
│  @layer theme         Farbschemata (Light, Dark, System), Dichte       │
│       ↓                                                                │
│  @layer layout        App-Shell, Grids, Container, Scrollbereiche      │
│       ↓                                                                │
│  @layer components    Karten, Buttons, Badges, Tabellen, Formulare     │
│       ↓                                                                │
│  @layer utilities     Hilfsklassen (Abstände, Textausrichtung)         │
│       ↓                                                                │
│  @layer overrides     Zelyra-Standardseiten (CRUD, TableViews)         │
│       ↓                                                                │
│  [Projekt-CSS]        Eigene zelyra.theme.css (höchste Priorität)      │
└────────────────────────────────────────────────────────────────────────┘

Der Root-Selektor und Spezifität

Alle Standardstile von Zelyra UI sind mit dem CSS-Pseudoselektor :where(html[data-z-ui]) gekapselt:

:where(html[data-z-ui]) .z-table {
  width: 100%;
  border-collapse: collapse;
}

Die :where()-Pseudoklasse verleiht dem Selektor eine Spezifität von 0. Dadurch können Entwickler beliebige Zelyra-Stile in ihrer eigenen zelyra.theme.css mit gewöhnlichen Klassenselektoren (z. B. .z-table) überschreiben, ohne zu !important oder unleserlichen Selektorketten greifen zu müssen.

Einbindung von Projekt-Stylesheets

Ein Zelyra-Projekt kann projektspezifisches CSS auf zwei Wegen einbinden:

  1. Automatische Erkennung: Liegt im Wurzelverzeichnis des Projekts eine Datei namens zelyra.theme.css oder assets/css/theme.css, wird diese vom integrierten Server automatisch nach dem Zelyra-Basissystem in den <head> aller generierten Seiten injiziert.
  2. Umgebungsvariable / Konfiguration: Über die Umgebungsvariable ZELYRA_UI_STYLESHEET=/pfad/zu/style.css oder den CLI-Parameter --stylesheet kann ein beliebiger alternativer Pfad deklariert werden.

Da ungelayertes CSS laut CSS-Spezifikation Vorrang vor allen @layer-Regeln hat, überschreiben Regeln in zelyra.theme.css automatisch die Framework-Vorgaben:

/* In zelyra.theme.css: Überschreibt Standardstile sauber und konfliktfrei */
.z-table th {
  background: #1a2234;
  color: #ffffff;
}

Alternativ können eigene Regeln explizit im @layer overrides platziert werden:

@layer overrides {
  .z-table th {
    background: #1a2234;
    color: #ffffff;
  }
}

2. Design-Tokens und CSS Custom Properties

Zelyra UI definiert ein durchgängiges System typisierter CSS-Variablen auf dem :root- bzw. html[data-z-ui]-Element.

Farbtokens (Color Tokens)

Token Standardwert (Light) Zweck / Verwendung
--z-color-canvas #f4f6fa Hintergrundfarbe des gesamten Dokuments
--z-color-surface #ffffff Hintergrund von Karten, Formularen und Tabellen
--z-color-surface-raised #ffffff Leicht erhabene Bereiche (z. B. Tabellenköpfe, Dropdowns)
--z-color-text #182230 Primäre Schriftfarbe für Fließtext und Titel
--z-color-text-muted #526174 Abgeschwächte Schrift für Hinweise, Labels, Metadaten
--z-color-border #d8e0e9 Standard-Rahmenfarbe für Tabellen, Inputs und Karten
--z-color-primary #3157a4 Primärfarbe für Buttons, aktive Tabs, Fokusrahmen
--z-color-on-primary #ffffff Kontrast-Textfarbe auf Primärflächen
--z-color-primary-hover #274889 Hover-Zustand für Primärschaltflächen
--z-color-primary-soft #eaf0fb Zarter Hintergrund für Zeilen-Hover und aktive Elemente
--z-color-success #16734c Erfolgsmeldungen, Freigabestatus, bezahlte Rechnungen
--z-color-success-soft #e9f6ef Weicher Hintergrund für Erfolgs-Badges und Notizen
--z-color-warning #795000 Warnungen, Entwürfe, Mahnungen
--z-color-warning-soft #fff4d8 Weicher Hintergrund für Warn-Badges
--z-color-danger #a12e39 Fehler, Stornos, Löschaktionen, Invarianten-Fehler
--z-color-danger-soft #fff0f0 Weicher Hintergrund für Fehlermeldungen
--z-color-focus #275fc5 Farbe des barrierefreien Fokus-Rings (:focus-visible)
--z-color-link #244f9a Textlinks und Zeilenverweise

Maße, Abstände und Radien

:where(html[data-z-ui]) {
  /* Typografie */
  --z-font-sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --z-font-mono: ui-monospace, "Cascadia Code", "Segoe UI Mono", monospace;
  --z-font-size: 1rem;
  --z-line-height: 1.5;

  /* Höhen und Inhaltsbreite */
  --z-control-height: 2.75rem; /* Standard-Eingabehöhe */
  --z-content-width: 76rem;    /* Maximale Inhaltsbreite */

  /* Abstände */
  --z-space-1: 0.25rem;  /* 4px */
  --z-space-2: 0.5rem;   /* 8px */
  --z-space-3: 0.75rem;  /* 12px */
  --z-space-4: 1rem;     /* 16px */
  --z-space-5: 1.25rem;  /* 20px */
  --z-space-6: 1.5rem;   /* 24px */
  --z-space-8: 2rem;     /* 32px */

  /* Radien */
  --z-radius-sm: 0.35rem;  /* Inputs, Buttons */
  --z-radius-md: 0.65rem;  /* Karten, Dialoge, Badges */
  --z-radius-lg: 1rem;     /* AppShell-Hauptcontainer */
  --z-radius-pill: 999px;  /* Runde Pill-Badges, Schalter */

  /* Z-Index Ebenen */
  --z-layer-sticky: 10;
  --z-layer-dropdown: 20;
  --z-layer-dialog: 100;
  --z-layer-toast: 120;
}

Kompakte Datendichte (compact)

In administrativen Dashboards, Buchhaltungsprogrammen oder ERP-Modulen wird häufig eine hohe Informationsdichte gewünscht. Zelyra UI unterstützt dies nativ über das HTML-Attribut data-z-density="compact":

<html data-z-ui data-z-density="compact">

Im Compact-Modus reduziert Zelyra UI die Eingabehöhe automatisch von 2.75rem auf 2.25rem und strafft die Innenabstände aller Tabellenzeilen und Formularfelder. Entwickler können die Dichte auch gezielt für einzelne Sektionen (z. B. nur für eine bestimmte Datentabelle) aktivieren:

<div class="z-table-scroll" data-z-density="compact">
  <table class="z-table">...</table>
</div>

3. TableViews formatieren und optimieren

Tabellenansichten (tableview) gehören zu den zentralen Oberflächenelementen von Zelyra. Sie stellen relationale Daten dar, bieten Such-, Filter- und Sortierfunktionen und enthalten Zeilenaktionen.

Die Standard-HTML-Struktur

Ein Zelyra-TableView erzeugt typischerweise folgende HTML-Hierarchie:

<div class="z-generated-page">
  <header class="z-page-header">
    <div class="z-page-header__copy">
      <h1 class="z-page-header__title">Rechnungen</h1>
      <p class="z-page-header__description">Alle erfassten Rechnungen des aktuellen Mandanten.</p>
    </div>
    <div class="z-page-header__actions">
      <a class="z-button z-button--primary" href="/invoices/new">+ Neue Rechnung</a>
    </div>
  </header>

  <form class="zelyra-query-controls" method="get">
    <fieldset>
      <legend>Filter & Suche</legend>
      <div class="z-query-controls__fields">
        <!-- Filterfelder -->
      </div>
    </fieldset>
  </form>

  <div class="z-table-scroll">
    <table class="z-table">
      <thead>
        <tr>
          <th>Nummer</th>
          <th>Kunde</th>
          <th>Datum</th>
          <th>Status</th>
          <th style="text-align: right;">Bruttobetrag</th>
          <th class="z-table__actions-head">Aktionen</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><strong>RE-2026-0042</strong></td>
          <td>Musterfirma GmbH</td>
          <td>19.10.2026</td>
          <td><span class="z-badge z-badge--success">Bezahlt</span></td>
          <td style="text-align: right;">1.428,00 €</td>
          <td class="z-table__actions">
            <a class="z-button z-button--sm" href="/invoices/42">Anzeigen</a>
            <a class="z-button z-button--sm" href="/invoices/42/edit">Bearbeiten</a>
          </td>
        </tr>
      </tbody>
    </table>
  </div>

  <nav class="z-pagination" aria-label="Seitennavigation">
    <div class="z-pagination__summary">Zeige 1 bis 25 von 148 Einträgen</div>
    <div class="z-pagination__controls">...</div>
  </nav>
</div>

Sticky Header (Fixierter Tabellenkopf)

Wenn Tabellen hunderte Einträge umfassen, scrollt der Tabellenkopf oft aus dem sichtbaren Bereich. Mit Sticky Headers bleibt die Kopfzeile dauerhaft sichtbar:

/* In zelyra.theme.css: Fixierter Tabellenkopf */
.z-table-scroll {
  max-height: 70vh; /* Scrollbarer Bereich mit fester Maximalhöhe */
  overflow-y: auto;
  position: relative;
}

.z-table thead th {
  position: sticky;
  top: 0;
  z-index: var(--z-layer-sticky);
  background: var(--z-color-surface-raised);
  border-bottom: 2px solid var(--z-color-border);
  box-shadow: 0 2px 4px rgb(0 0 0 / 6%);
}

Zebra-Streifen und interaktive Zeilenhervorhebung

Zebra-Streifen verbessern das zeilenweise Erfassen komplexer Zahlenkolonnen dramatisch:

/* Zebra-Streifen für gerade Zeilen */
.z-table tbody tr:nth-child(even) {
  background-color: rgb(0 0 0 / 2%);
}

[data-z-theme="dark"] .z-table tbody tr:nth-child(even) {
  background-color: rgb(255 255 255 / 2.5%);
}

/* Intensiverer Zeilen-Hover */
.z-table tbody tr:hover {
  background-color: var(--z-color-primary-soft);
  transition: background-color var(--z-motion-fast) ease-in-out;
}

Rechtsbündige Zahlen und Währungen (Tabular Nums)

In Tabellen dürfen Zahlen und Beträge niemals linksbündig oder zentriert gesetzt werden, da das menschliche Auge Dezimalstellen untereinander vergleichen muss. Zudem verhindert die Schriftart-Eigenschaft tabular-nums, dass Ziffern wie die schmale 1 das Schriftbild verschieben:

/* Exakte numerische Ausrichtung für Betrags- und Mengenspalten */
.z-table td.col-number,
.z-table td.col-currency,
.z-table th.col-number,
.z-table th.col-currency {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* Währungsbeträge dezent hervorheben */
.z-table td.col-currency {
  font-weight: 600;
  letter-spacing: -0.01em;
}

Formatierung der Aktionsspalte (.z-table__actions)

Die Zeilenaktionsleiste bündelt Bearbeiten-, Anzeigen- und Löschschaltflächen:

/* Kompakte, sauber angeordnete Zeilenaktionsknöpfe */
.z-table td.z-table__actions {
  white-space: nowrap;
  text-align: right;
  vertical-align: middle;
}

.z-table td.z-table__actions .z-button,
.z-table td.z-table__actions a.z-button {
  min-height: 1.85rem;
  padding: 0.2rem 0.6rem;
  font-size: 0.82rem;
  font-weight: 500;
  border-radius: var(--z-radius-sm);
}

.z-table td.z-table__actions .z-button:hover {
  transform: translateY(-1px);
}

4. Such-, Filter- und Abfragewerkzeuge

Die Filterleiste über einer TableView wird in Zelyra über .zelyra-query-controls und .z-query-controls__fields gerendert.

/* Modernes, aufgeräumtes Suchleisten-Design */
.zelyra-query-controls {
  border: 1px solid var(--z-color-border);
  border-radius: var(--z-radius-md);
  background: var(--z-color-surface-raised);
  padding: 1rem 1.25rem;
  margin-bottom: 1.5rem;
}

.zelyra-query-controls legend {
  font-size: 0.88rem;
  font-weight: 700;
  color: var(--z-color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0 0.4rem;
}

.z-query-controls__fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)) auto;
  align-items: end;
  gap: 1rem;
}

/* Eingabefelder in der Filterleiste */
.zelyra-query-controls .z-input,
.zelyra-query-controls .z-select {
  background: var(--z-color-surface);
  border-color: var(--z-color-border);
  box-shadow: inset 0 1px 2px rgb(0 0 0 / 4%);
}

.zelyra-query-controls .z-input:focus,
.zelyra-query-controls .z-select:focus {
  border-color: var(--z-color-primary);
  outline: 3px solid var(--z-color-primary-soft);
}

5. Paginierung und leere Tabellenzustände

Paginierung (.z-pagination)

.z-pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--z-color-border);
}

.z-pagination__summary {
  font-size: 0.88rem;
  color: var(--z-color-text-muted);
}

.z-pagination__controls {
  display: inline-flex;
  gap: 0.35rem;
}

.z-pagination__controls a,
.z-pagination__controls span {
  display: inline-flex;
  min-width: 2.25rem;
  height: 2.25rem;
  align-items: center;
  justify-content: center;
  padding: 0 0.5rem;
  border: 1px solid var(--z-color-border);
  border-radius: var(--z-radius-sm);
  background: var(--z-color-surface);
  color: var(--z-color-text);
  font-size: 0.88rem;
  text-decoration: none;
}

.z-pagination__controls a:hover {
  background: var(--z-color-primary-soft);
  border-color: var(--z-color-primary);
}

.z-pagination__controls [aria-current="page"] {
  background: var(--z-color-primary);
  color: var(--z-color-on-primary);
  border-color: var(--z-color-primary);
  font-weight: 700;
}

Leere Zustände (.z-empty-state)

Liefert eine Abfrage keine Ergebnisse, zeigt Zelyra den Zustand .z-empty-state:

.z-empty-state {
  text-align: center;
  padding: 3.5rem 1.5rem;
  background: var(--z-color-surface);
  border: 1px dashed var(--z-color-border);
  border-radius: var(--z-radius-md);
  margin-block: 1rem;
}

.z-empty-state h3 {
  margin: 0 0 0.5rem;
  font-size: 1.2rem;
  color: var(--z-color-text);
}

.z-empty-state p {
  max-width: 30rem;
  margin: 0 auto 1.5rem;
  color: var(--z-color-text-muted);
  font-size: 0.95rem;
  line-height: 1.5;
}

.z-empty-state__actions {
  display: flex;
  justify-content: center;
  gap: 0.75rem;
}

6. CRUD- und Master-Detail-Formulare gestalten

Automatisch generierte Formulare (crud.view.create, crud.view.edit) werden im Container .zelyra-crud-form-card eingebettet.

Zwei-Spalten-Formularlayout mit responsivem Umbruch

/* Card-Hülle des Formulars */
.zelyra-crud-form-card {
  max-width: 68rem;
  margin: 1.5rem auto 3rem;
  padding: 2rem;
  background: var(--z-color-surface);
  border: 1px solid var(--z-color-border);
  border-radius: var(--z-radius-lg);
  box-shadow: var(--z-shadow-sm);
}

/* Zweispaltiges Formular-Grid */
.zelyra-crud-form-card form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.25rem 1.5rem;
}

/* Vollbreite Felder (z. B. Bemerkungen, Textareas) */
.zelyra-crud-form-card .z-field--full,
.zelyra-crud-form-card .z-fieldset,
.zelyra-crud-form-card .z-form-actions {
  grid-column: 1 / -1;
}

/* Auf mobilen Displays einspaltig umbrechen */
@media (max-width: 768px) {
  .zelyra-crud-form-card form {
    grid-template-columns: 1fr;
  }
}

Formularfelder, Labels und Hilfetexte

.z-field {
  display: grid;
  gap: 0.4rem;
  min-width: 0;
}

.z-field__label {
  font-size: 0.92rem;
  font-weight: 650;
  color: var(--z-color-text);
}

/* Pflichtfeld-Sternchen hervorheben */
.z-field__label[data-required="true"]::after {
  content: " *";
  color: var(--z-color-danger);
}

.z-field__hint {
  font-size: 0.82rem;
  color: var(--z-color-text-muted);
}

.z-input,
.z-select,
.z-textarea {
  min-height: var(--z-control-height);
  padding: 0.55rem 0.8rem;
  border: 1px solid var(--z-color-border);
  border-radius: var(--z-radius-sm);
  background: var(--z-color-surface);
  color: var(--z-color-text);
  transition: border-color var(--z-motion-fast), box-shadow var(--z-motion-fast);
}

.z-input:focus,
.z-select:focus,
.z-textarea:focus {
  border-color: var(--z-color-focus);
  box-shadow: 0 0 0 3px var(--z-color-primary-soft);
  outline: none;
}

Validierungsfehler und Invarianten-Feedback ([aria-invalid="true"])

Meldet die Zelyra-Validierung einen Fehler (z. B. eine verletzte Tabelleninvariante wie total_gross >= total_net), setzt Zelyra das Attribut aria-invalid="true":

/* Roter Signalfokus für fehlerhafte Felder */
.z-input[aria-invalid="true"],
.z-select[aria-invalid="true"],
.z-textarea[aria-invalid="true"] {
  border: 2px solid var(--z-color-danger);
  background-color: var(--z-color-danger-soft);
}

.z-field__error {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--z-color-danger);
}

/* Gesamtübersicht der Validierungsfehler oben am Formular */
.z-error-summary {
  margin-bottom: 1.5rem;
  padding: 1.25rem;
  border: 1px solid var(--z-color-danger);
  border-left-width: 6px;
  border-radius: var(--z-radius-md);
  background: var(--z-color-danger-soft);
  color: var(--z-color-danger);
}

Master-Detail-Kindtabellen (has_many) und Summenboxen

In Transaktionsformularen mit has_many-Relationen (wie Rechnungspositionen) wird die Kindtabelle nahtlos in das Formular integriert:

/* Kinddatensatz-Tabelle im Formular */
.z-fieldset.z-master-detail-items {
  border: 1px solid var(--z-color-border);
  border-radius: var(--z-radius-md);
  padding: 1.25rem;
  background: var(--z-color-surface-raised);
}

.z-master-detail-items table {
  width: 100%;
  border-collapse: collapse;
}

.z-master-detail-items th,
.z-master-detail-items td {
  padding: 0.6rem 0.5rem;
  border-bottom: 1px solid var(--z-color-border);
}

/* Kompakte Inputs in der Positionstabelle */
.z-master-detail-items input.z-input {
  min-height: 2.15rem;
  padding: 0.25rem 0.5rem;
  font-size: 0.9rem;
}

/* Summenbox für automatische Aggregat-Werte (Netto, MwSt, Brutto) */
.z-form-summary-card {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-width: 22rem;
  margin-left: auto;
  margin-top: 1rem;
  padding: 1.25rem;
  border: 1px solid var(--z-color-border);
  border-radius: var(--z-radius-md);
  background: var(--z-color-surface);
}

.z-form-summary-card__row {
  display: flex;
  justify-content: space-between;
  font-size: 0.95rem;
}

.z-form-summary-card__row--total {
  border-top: 2px solid var(--z-color-border);
  padding-top: 0.65rem;
  margin-top: 0.25rem;
  font-size: 1.15rem;
  font-weight: 750;
  color: var(--z-color-primary);
}

7. Modaldialoge und Status-Badges

Modaldialoge (.z-dialog)

Zelyra verwendet standardkonforme HTML5-<dialog>-Elemente mit nativem Backdrop:

.z-dialog {
  width: min(38rem, calc(100vw - 2rem));
  max-height: min(90vh, 48rem);
  padding: 0;
  border: 1px solid var(--z-color-border);
  border-radius: var(--z-radius-lg);
  background: var(--z-color-surface);
  color: var(--z-color-text);
  box-shadow: var(--z-shadow-md);
  overflow: hidden;
}

/* Dunkel abgedunkelter Hintergrund mit Unschärfe */
.z-dialog::backdrop {
  background: rgb(10 20 35 / 65%);
  backdrop-filter: blur(3px);
}

.z-dialog__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.1rem 1.5rem;
  border-bottom: 1px solid var(--z-color-border);
  background: var(--z-color-surface-raised);
}

.z-dialog__body {
  padding: 1.5rem;
  overflow-y: auto;
}

.z-dialog__footer {
  display: flex;
  justify-content: flex-end;
  gap: 0.75rem;
  padding: 1rem 1.5rem;
  border-top: 1px solid var(--z-color-border);
  background: var(--z-color-surface-raised);
}

Status-Badges (.z-badge)

.z-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.25rem 0.65rem;
  font-size: 0.8rem;
  font-weight: 650;
  line-height: 1;
  border-radius: var(--z-radius-pill);
  border: 1px solid transparent;
  white-space: nowrap;
}

/* Farbvarianten für Beleg- und Buchungszustände */
.z-badge--success {
  background: var(--z-color-success-soft);
  color: var(--z-color-success);
  border-color: rgb(22 115 76 / 20%);
}

.z-badge--warning {
  background: var(--z-color-warning-soft);
  color: var(--z-color-warning);
  border-color: rgb(121 80 0 / 20%);
}

.z-badge--danger {
  background: var(--z-color-danger-soft);
  color: var(--z-color-danger);
  border-color: rgb(161 46 57 / 20%);
}

.z-badge--neutral {
  background: var(--z-color-canvas);
  color: var(--z-color-text-muted);
  border-color: var(--z-color-border);
}

8. Vollständige Praxisrezepte für zelyra.theme.css

Die folgenden Rezepte können direkt in die Datei zelyra.theme.css im Projektverzeichnis übernommen werden.

Rezept 1: Modernes Dark Mode & Accent Theme

Ein sattes, augenschonendes dunkles Theme mit Indigo- und Cyan-Akzenten für Entwicklungs- und Leitstandanwendungen:

/* ==========================================================================
   Rezept 1: Modern Dark Theme für zelyra.theme.css
   ========================================================================== */
:where(html[data-z-ui][data-z-theme="dark"]),
:where(html[data-z-ui]:not([data-z-theme])) {
  /* Tiefes Schieferblau als Basisfarben */
  --z-color-canvas: #0b0f17;
  --z-color-surface: #121824;
  --z-color-surface-raised: #1a2233;
  --z-color-border: #28354b;

  /* Typografie */
  --z-color-text: #f1f5f9;
  --z-color-text-muted: #94a3b8;

  /* Strahlendes Cyan/Indigo als Primärakzent */
  --z-color-primary: #38bdf8;
  --z-color-on-primary: #082f49;
  --z-color-primary-hover: #7dd3fc;
  --z-color-primary-soft: #0c2b45;

  /* Statusfarben */
  --z-color-success: #4ade80;
  --z-color-success-soft: #0e3720;
  --z-color-warning: #facc15;
  --z-color-warning-soft: #3d330c;
  --z-color-danger: #f87171;
  --z-color-danger-soft: #3d1417;

  /* Schatten mit sanftem Schein */
  --z-shadow-sm: 0 1px 3px rgb(0 0 0 / 50%);
  --z-shadow-md: 0 12px 32px rgb(0 0 0 / 70%);
}

/* Akzentuierter Tabellenkopf im Dark Mode */
.z-table thead th {
  background: #172030;
  color: #38bdf8;
  border-bottom: 2px solid #28354b;
}

/* Sanfter Zeilen-Hover */
.z-table tbody tr:hover {
  background-color: #17243b;
}

Rezept 2: Kompakte Finanz- und Buchhaltungstabelle

Optimiert für Buchhaltungslisten, Kassenbücher und Belegübersichten mit fixiertem Tabellenkopf, Währungsausrichtung und schnellem visuellen Scannen:

/* ==========================================================================
   Rezept 2: Finanz- und Buchhaltungstabelle mit Sticky Header
   ========================================================================== */
.z-table-scroll {
  max-height: 72vh;
  overflow: auto;
  border: 1px solid var(--z-color-border);
  border-radius: var(--z-radius-md);
  box-shadow: 0 4px 12px rgb(0 0 0 / 4%);
}

.z-table {
  font-size: 0.9rem;
  border-collapse: separate;
  border-spacing: 0;
}

/* Fixierter Tabellenkopf */
.z-table thead th {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--z-color-surface-raised);
  border-bottom: 2px solid var(--z-color-primary);
  padding: 0.65rem 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--z-color-text);
}

/* Schmale, platzsparende Zeilen */
.z-table td {
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--z-color-border);
  vertical-align: middle;
}

/* Subtile Streifung */
.z-table tbody tr:nth-child(even) td {
  background-color: rgb(0 0 0 / 1.5%);
}

/* Numerische Spalten rechtsbündig mit fester Ziffernbreite */
.z-table th:nth-child(5),
.z-table td:nth-child(5),
.z-table th:nth-child(6),
.z-table td:nth-child(6) {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.z-table td:nth-child(5) {
  font-weight: 700;
  color: var(--z-color-text);
}

/* Zeilenaktionen rechtsbündig und kompakt */
.z-table td:last-child {
  text-align: right;
  white-space: nowrap;
}

.z-table td:last-child .z-button {
  min-height: 1.85rem;
  padding: 0.15rem 0.55rem;
  font-size: 0.8rem;
}

Rezept 3: Master-Detail-Rechnungsformular mit Card-Stil

Dieses Rezept gestaltet Belegformulare mit geteilten Kopfdaten, strukturierter Positionstabelle und hervorgehobener Rechnungssummenkarte:

/* ==========================================================================
   Rezept 3: Master-Detail Rechnungsformular mit Card-Design
   ========================================================================== */
.zelyra-crud-form-card {
  max-width: 72rem;
  border: 1px solid var(--z-color-border);
  border-radius: var(--z-radius-lg);
  padding: 2.25rem;
  background: var(--z-color-surface);
  box-shadow: 0 10px 30px rgb(0 0 0 / 5%);
}

/* Headerbereich des Formulars */
.zelyra-crud-form-card > header {
  margin-bottom: 2rem;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid var(--z-color-border);
}

.zelyra-crud-form-card > header h1 {
  font-size: 1.6rem;
  margin: 0 0 0.4rem;
  color: var(--z-color-text);
}

/* Gruppierung der Stammdaten in einer erhabenen Karte */
.z-form-master-section {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.25rem;
  padding: 1.5rem;
  background: var(--z-color-surface-raised);
  border: 1px solid var(--z-color-border);
  border-radius: var(--z-radius-md);
  margin-bottom: 2rem;
}

@media (max-width: 900px) {
  .z-form-master-section {
    grid-template-columns: 1fr;
  }
}

/* Positionstabelle (Kind-Sammlung) */
.z-form-items-table {
  width: 100%;
  border-collapse: collapse;
  margin-block: 1rem 1.5rem;
}

.z-form-items-table th {
  padding: 0.65rem;
  border-bottom: 2px solid var(--z-color-border);
  font-weight: 700;
  font-size: 0.85rem;
  color: var(--z-color-text-muted);
}

.z-form-items-table td {
  padding: 0.5rem;
  border-bottom: 1px solid var(--z-color-border);
}

/* Hervorgehobene Summen-Box rechts unten */
.z-invoice-totals-box {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.6rem 2rem;
  width: min(100%, 24rem);
  margin-left: auto;
  padding: 1.25rem 1.5rem;
  background: var(--z-color-surface-raised);
  border: 1px solid var(--z-color-border);
  border-radius: var(--z-radius-md);
}

.z-invoice-totals-box dt {
  color: var(--z-color-text-muted);
  font-size: 0.95rem;
}

.z-invoice-totals-box dd {
  margin: 0;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.z-invoice-totals-box dt.total-gross,
.z-invoice-totals-box dd.total-gross {
  border-top: 2px solid var(--z-color-border);
  padding-top: 0.75rem;
  margin-top: 0.25rem;
  font-size: 1.25rem;
  font-weight: 800;
  color: var(--z-color-primary);
}

/* Formular-Aktionsleiste */
.z-form-actions {
  display: flex;
  justify-content: flex-end;
  gap: 1rem;
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--z-color-border);
}

.z-form-actions .z-button--primary {
  min-width: 11rem;
  font-size: 1rem;
}

37. Native Datenbank-CLI und interaktive Sprachkonsole

Mit Zelyra 1.2.25 und 1.2.26 wurde die Entwickler- und Administrationserfahrung um eine direkte Datenbank-CLI sowie eine interaktive AST-Sprachkonsole erweitert.

Direkte Datenbankkonsole (zelyra db console)

Der Befehl zelyra db console [main.zyl] liest die Konfiguration der Ziel-Datenbank aus Umgebungsvariablen (ZELYRA_DATABASE_<NAME>_URL bzw. DATABASE_URL) sowie der Projekt-.env aus und startet direkt den passenden nativen Client (MariaDB/MySQL mysql oder PostgreSQL psql). Passwörter werden sicher über Umgebungsvariablen (MYSQL_PWD bzw. PGPASSWORD) übergeben, sodass sie weder in der Prozessliste noch in Logs sichtbar sind:

# Startet den nativen Datenbank-Client mit der konfigurierten Projekt-Verbindung
zelyra db console main.zyl

Direkte Abfragen über die CLI (zelyra db query)

Der Befehl zelyra db query führt eine einzelne SQL-Anweisung transaktional aus. Zur Absicherung gegen versehentliche Modifikationen laufen Abfragen standardmäßig in einer von der Datenbank erzwungenen schreibgeschützten Transaktion:

# Standardmäßig schreibgeschützte Abfrage mit tabellarischer Formatierung
zelyra db query main.zyl "SELECT id, email, created_at FROM customers" --format table

# JSON- oder CSV-Format für Skripte und Pipeline-Verarbeitung
zelyra db query main.zyl "SELECT id, email FROM customers" --format json
zelyra db query main.zyl "SELECT id, email FROM customers" --format csv

# Schreibende DML-Operationen erfordern das explizite Flag --allow-mutation
zelyra db query main.zyl "UPDATE customers SET active = true WHERE id = 1" --allow-mutation

# Begrenzung und Mandantenbindung über Platzhalter :tenant_id
zelyra db query main.zyl "SELECT * FROM invoices WHERE tenant_id = :tenant_id" --tenant 42 --limit 50

[!NOTE] DDL-Anweisungen (ALTER, DROP, CREATE) sind in zelyra db query bewusst blockiert, da viele Datenbank-Engines DDL implizit committen und damit Transaktionssicherheitsnetze aushebeln.

Interaktive Sprachkonsole (zelyra console)

Die mit Zelyra 1.2.26 eingeführte Sprachkonsole startet eine interaktive Read-Eval-Print-Loop (REPL) auf Basis des AST-Interpreters:

zelyra console main.zyl

Merkmale der Sprachkonsole:

  • Persistente Bindungen: Mit let deklarierte Variablen, Funktionen und Typen bleiben über mehrere Zeilen hinweg erhalten.
  • Transaktions-Sandbox: Auf MariaDB/MySQL läuft die Konsole standardmäßig in einer echten Transaktion mit automatischem Rollback beim Verlassen (--sandbox). Modifikationen können mit :commit explizit festgeschrieben werden. Der Modus --persist deaktiviert die Sandbox.
  • Terminal-Komfort: Vollständige Verlaufshistorie (~/.zelyra_history), Tab-Vervollständigung für deklarierte Funktionen, Tabellen, Records und Felder sowie automatische Fortsetzungsprompts für mehrzeilige Blöcke.
  • Metabefehle:
    • :help – Zeigt eine Übersicht aller verfügbaren Befehle
    • :tables – Listet alle im Projekt deklarierten Tabellen auf
    • :schema <name> – Zeigt Spaltentypen, Fremdschlüssel und Tabelleninvarianten mit Zeilennummern an
    • :tenant <id> – Setzt den aktuellen Mandantenkontext (tenant_id)
    • :commit / :rollback – Bestätigt oder verwirft den aktuellen Sandbox-Transaktionsblock
    • :clear – Leert den Konsolenbildschirm
    • :quit oder :exit – Beendet die Konsole

38. Laravel-Importanalyse und Migrationsgrundlage

Zelyra 1.2.27 stellt ein spezialisiertes CLI-Werkzeug bereit, um vorhandene Laravel-Projekte quellbelegt zu analysieren und als native, typgeprüfte Zelyra-Module zu rekonstruieren.

Read-Only-Analyse

Der Importer arbeitet nach dem Prinzip Read-Only First. Er führt zu keinem Zeitpunkt unvertrauenswürdigen PHP-Code aus, sondern analysiert syntaktisch und semantisch:

  1. Migrationen und Schema-Definitionen
  2. Eloquent-Models, Beziehungen und Casts
  3. Validierungsregeln aus Request-Klassen und Controllern
  4. Blade-Templates und Asset-Referenzen
# Reine Strukturanalyse ohne Schreibvorgänge
zelyra import laravel analyze /pfad/zum/laravel-projekt

Selektive Codegenerierung

Die Generierung nativer Zelyra-Artefakte erfolgt gezielt und isoliert:

# Generiert native Zelyra-Tabellen und -Records für eine bestimmte Tabelle
zelyra import laravel generate /pfad/zum/laravel-projekt --table customers

# Generiert alle unterstützten Kernmodule
zelyra import laravel generate /pfad/zum/laravel-projekt --all-supported

Dabei werden folgende Transformationen vorgenommen:

  • Mandanten-Isolation: Laravel-Mandantenstrukturen (z. B. tenant_id Spalten, Pivot-Tabellen) werden in native Zelyra-Konstrukte (table ... scoped by tenant_id: TenantId) übersetzt.
  • Invarianten: Deklarierte Validierungsregeln (required, numeric, min, max, Datumsvergleiche) werden in Zelyra-Tabelleninvarianten (validate { ... }) überführt.
  • Authentifizierungsmodul: Standard-Auth-Tabellen (users, sessions, password_resets) werden in ein wiederverwendbares, compilergeprüftes Zelyra-Auth-Paket transformiert.

39. Typisierte transaktionale CRUD-Actions

Mit Version 1.2.28 führt Zelyra typisierte CRUD-Actions ein. Sie erlauben es, fachliche Aktionen direkt an Tabellen- und Datensatzkontexte zu binden, ohne CRUD-Sicherheitsgrenzen aufzugeben.

Deklaration von Actions

Eine Action bindet typisierte Parameter, den aktuellen gesperrten Datensatz (row) sowie vertrauenswürdige Sitzungsinformationen (session):

table Invoices scoped by tenant_id: TenantId {
  id: Id required,
  tenant_id: TenantId required,
  number: String(50) required unique within tenant,
  status: String(20) required default "draft",
  total_gross: Decimal(12, 2) required default 0.00,
  locked: Bool required default false,

  action approve(comment: String(200)?) {
    guard row.status == "draft" {
      error: "Nur Entwürfe können genehmigt werden."
    }
    guard row.locked == false {
      error: "Gesperrte Belege können nicht genehmigt werden."
    }

    requires {
      comment != ""
    }

    execute {
      UPDATE Invoices
      SET status = "approved",
          approved_by = session.user_id,
          approved_at = now()
      WHERE id = row.id AND tenant_id = session.tenant_id;

      // Automatisches Einreihen in Audit-Log und PDF-Outbox in derselben Transaktion
      enqueue_audit_log("invoice.approved", row.id);
    }
  }
}

Transaktionale Garantien von Actions

  • Exklusive Datensatzsperre (Row Lock): Vor Ausführung der Guards und des execute-Blocks wird der betroffene Datensatz mit SELECT ... FOR UPDATE gesperrt. Konkurrierende Anfragen müssen warten; Race Conditions bei Zustandsübergängen sind ausgeschlossen.
  • Reine Guards: Die guard-Bedingungen werden direkt auf dem aktuellsten, gesperrten Zustand der Tabellenzeile geprüft.
  • Gemeinsame Transaktion: Fachdaten-Mutationen, Audit-Protokollierung und das Einreihen von asynchronen Aufgaben (wie PDF-Generierung in die Outbox) teilen sich dieselbe Datenbanktransaktion. Scheitert ein Teilschritt, wird die gesamte Aktion atomar zurückgerollt.

40. Sichere Datei-Uploads und Mandanten-Dateispeicher

Zelyra 1.2.29 führt sichere, begrenzte Multipart-Datei-Uploads ein. Sie verbinden compilergeprüfte Formularfelder mit privatem, mandantenisoliertem Dateispeicher.

Deklaration von Datei-Feldern

Upload-Felder werden mit strengen Typ-, Größen- und Formatgrenzen deklariert:

table Documents scoped by tenant_id: TenantId {
  id: Id required,
  tenant_id: TenantId required,
  title: String(100) required,
  attachment: File(
    max_size: 10_000_000, // 10 MB Limit
    allowed_types: ["application/pdf", "image/png", "image/jpeg"]
  ) required,
  uploaded_at: DateTime required default now()
}

Sicherheitsarchitektur

  1. Signaturprüfung: Dateiinhalte werden nicht allein anhand der Dateiendung oder des vom Client übergebenen MIME-Typs beurteilt, sondern durch serverseitige Magic-Byte-Signaturprüfung (PDF-, PNG- und JPEG-Signaturen).
  2. Privater Speicher außerhalb des Web-Roots: Hochgeladene Dateien werden unter opaken SHA-256-Hash-Token (FileRef) in einem geschützten, mandanteneigenen Verzeichnis abgelegt. Ein direkter Zugriff über den Webserver ohne Authentifizierung ist technisch unmöglich.
  3. Geschützte Downloads: Der Zugriff auf Dateien erfolgt über autorisierte CRUD-Routen, die vor Auslieferung den Mandantenkontext und die Benutzerberechtigungen prüfen.
  4. Lebenszyklus und Waisenbereinigung:
    • Wird eine Datei in einem Datensatz ersetzt, wird die alte Datei erst nach erfolgreichem Commit der Datenbanktransaktion gelöscht.
    • Beim Löschen eines Datensatzes wird die physische Datei nur gelöscht, wenn keine andere FileRef-Referenz mehr darauf verweist.
    • zelyra files cleanup spürt verwaiste Uploads auf. Der Befehl arbeitet standardmäßig als vorschauende Trockenübung und erfordert zur Ausführung --apply --maintenance-window.
  5. Prüfsummen-Backup:
    # Erstellt ein prüfsummenvalidiertes Backup des Dateispeichers
    zelyra files backup /var/backups/zelyra-files.tar.gz
    
    # Stellt den Dateispeicher wieder her und validiert alle Prüfsummen
    zelyra files restore /var/backups/zelyra-files.tar.gz
    

41. Mandanten-Nummernkreise und Sequenzen (auto_sequence)

Mit Zelyra 1.2.31 wurden deklarierte, mandantensichere Nummernkreise für String-Felder in Tabellen und generierten CRUD-Ressourcen eingeführt.

Deklaration von Nummernkreisen

Ein Nummernkreis wird mit auto_sequence("FORMAT") direkt an einer Spaltendeklaration definiert:

table customers scoped by tenant_id: TenantId {
    id: Id primary auto
    tenant_id: TenantId required
    customer_number: String(32) required unique within tenant auto_sequence("KD-{0001}")
    name: String(100) required
}

table quotes scoped by tenant_id: TenantId {
    id: Id primary auto
    tenant_id: TenantId required
    quote_number: String(40) required unique within tenant auto_sequence("AG-{year}-{0001}")
    total: Decimal(12, 2) required default 0.00
}

Formatbausteine:

  • {0001}: Formatierungsmaske für den aufsteigenden Zähler mit Mindestbreite (hier 4 Stellen mit führenden Nullen: 0001, 0002, ...).
  • {year}: UTC-Kalenderjahr (z. B. 2026). Beginnt jedes Jahr mit einem eigenen Zählerstand.

Interne Verwaltung und Datenbankgarantien

  1. Verwaltete Sequenztabelle: Der Schema-Planer erzeugt automatisch die interne Tabelle _zelyra_number_sequences, die den Zählerstand speichert.
  2. Atomare Transaktion: Die Zählererhöhung und das Einfügen des neuen Datensatzes erfolgen in derselben Datenbanktransaktion.
  3. Schutz vor Manipulation: In generierten CRUD-Formularen und APIs werden vom Client übermittelte Nummernwerte ignoriert. In Bearbeitungsformularen wird die generierte Nummer schreibgeschützt dargestellt.
  4. Keine Lückenlosigkeit: Zelyra garantiert strikte Eindeutigkeit und Monotonie pro Mandant, verspricht jedoch bewusst keine lückenlose Zählung. Bei Transaktions-Rollbacks wird eine vergebene Nummer nicht erneut vergeben, um Nachvollziehbarkeit und GoBD-Konformität zu wahren.
  5. Backend-Unterstützung: MariaDB und PostgreSQL werden vollständig unterstützt. SQLite wird bei der Schemaprüfung mit error[E-DB-001] abgewiesen.

42. Typisierte berechnete Geschäftsfelder (compute) und FileRef-Anhänge im CRUD

Zelyra 1.2.32 führt zwei wesentliche Funktionen für geschäftliche Kernabläufe ein: typisierte, serverseitig berechnete Felder und schemagebundene FileRef-Anhänge in generierten CRUD-Ansichten.

Typisierte berechnete Felder (compute)

Felder mit compute(expr) berechnen ihren Wert deterministisch aus anderen Spalten desselben Datensatzes:

table invoice_items {
    id: Id primary auto
    invoice_id: Id required references invoices
    quantity: Decimal(10, 2) required
    unit_price: Decimal(12, 2) required
    total: Decimal(12, 2) required compute(quantity * unit_price)
}

Eigenschaften und Compiler-Prüfungen:

  • Reine, deterministische Ausdrücke: Der Compiler prüft Feldabhängigkeiten, Datentypen und Zyklen zur Compile-Zeit.
  • Serverseitige Neuberechnung: Weder in CRUD-Formularen noch bei API-Mutationen (POST, PUT, PATCH) kann der Client den berechneten Wert manipulieren. Übermittelte Clientwerte werden verworfen.
  • Partielle Updates (PATCH): Bei partiellen API-Mutationen lädt die Laufzeit die nicht übermittelten Ausgangswerte aus dem bestehenden Datensatz nach, bevor die Berechnung ausgeführt wird.
  • Exakte Dezimalrechnung: Berechnungen mit Decimal runden halbe Werte kaufmännisch von Null weg und weisen Überläufe ab.

FileRef-Anhänge im generierten CRUD

Spalten vom Typ FileRef können über einen deklarierten attachment-Block direkt an generierte CRUD-Formulare angebunden werden:

table contracts scoped by tenant_id: TenantId {
    id: Id primary auto
    tenant_id: TenantId required
    title: String(200) required
    signed_pdf: FileRef? attachment {
        max_size: "20MB"
        allowed_types: ["application/pdf"]
    }
}

crud Contract -> contracts {}
  • Natives Dateifeld: Generierte Anlegen- und Bearbeiten-Seiten stellen ein sicheres HTML-Dateieingabefeld bereit.
  • Erhalten bestehender Dateien: Wird beim Bearbeiten keine neue Datei hochgeladen, bleibt der vorhandene Verweis unverändert bestehen.
  • Mandantenisolierte Downloads: Downloads laufen über geschützte Routen wie /contracts/{id}/files/signed_pdf und validieren Sitzung, Mandant und Benutzerrechte.
  • Unterstützte MIME-Typen: PDF, PNG und JPEG mit Magic-Byte-Signaturprüfung.

43. Multi-Backend-Parität, Transaktions-Rollback und PDF-Outbox

Zelyra 1.2.33 konzentriert sich auf die semantische Ausrichtung zwischen dem AST-Referenzinterpreter, der Typed-MIR-Pipeline und der Bytecode-VM (v24).

Ausrichtung des Schrittbudgets (--max-steps)

Das konfigurierbare Ausführungsbudget --max-steps N wird über AST, MIR und Bytecode-VM hinweg auf Basis derselben Quellspannen und Ausdrucksknoten abgerechnet. Endlosschleifen oder rechenintensive Operationen werden in allen Backends nach exakt derselben Schrittanzahl deterministisch abgebrochen.

Transaktionale PDF-Mail-Outbox

Das Zusammenspiel von serverseitigem PDF-Rendering (render_pdf) und E-Mail-Einreihung (mail_queue) ist vollständig transaktional abgesichert:

fn send_invoice(recipient: String, invoice: Invoice) uses Email, Database(write) {
    transaction {
        mail_queue(
            recipient,
            "Rechnung " + invoice.number,
            "Ihre Rechnung " + invoice.number + " ist beigefügt.",
            "invoice-" + invoice.number + ".pdf",
            render_pdf("InvoicePrint", invoice)
        )
    }
}
  1. Atomarität: Schlägt die umgebende Transaktion fehl (z. B. durch einen Datenbankfehler oder Invariantenverletzung), werden weder die PDF-Datei noch der E-Mail-Versandauftrag in der Outbox persistiert.
  2. Backend-Gleichheit: Differentialtests stellen sicher, dass AST, MIR und Bytecode-VM bei Transaktionsfehlern dieselben Fehlerdiagnosen liefern und den Rollback zuverlässig durchführen.

ANHÄNGE

Anhang A: Schnelleinstieg / Spickzettel (Syntax-Cheat-Sheet)

Grundlegende Syntax

# Kommentar gilt bis zum Zeilenende. Für einen Block stehen öffnendes und schließendes ## jeweils allein auf einer Zeile; dazwischen steht der Kommentartext.

# Funktionen mit Vertraegen
fn summe(a: Int, b: Int) -> Int
    requires { a >= 0 && b >= 0 }
    ensures { result >= 0 }
{
    return a + b
}

# Einstiegspunkt und Variablen
fn main() {
    x = 10                  # Typableitung (unveraenderlich)
    mutable zaehler = 0     # Veraenderlich
    name: String = "Zelyra" # Expliziter Typ

    print(summe(3, 7))
}

Typen

  • Zahlen: Int (64-Bit vorzeichenbehaftet), UInt (vorzeichenlos), Float, Decimal (Festkomma)
  • Text & Zeichen: String, Char
  • Wahrheitswerte: Bool (true, false)
  • Sammlungen: Int[], String[]
  • Abwesenheit: Option<T> (Some(x), None), Kurzform T?
  • Fehler: Result<T, E> (Ok(x), Err(e))
  • System & Zeit: Timestamp, Date, Time, Duration

Kontrollstrukturen

fn kontrolle(x: Int) {
    if x > 10 {
        print("Gross")
    } else {
        print("Klein")
    }

    match x {
        1 => { print("Eins") }
        2 => { print("Zwei") }
        _ => { print("Andere") }
    }

    mutable i = 0
    while i < 3 invariant { i >= 0 } {
        i = i + 1
    }

    for n in [1, 2, 3] {
        print(n)
    }
}

fn main() {
    kontrolle(1)
}

Datenbank & Web

database main {
    engine: mariadb
    database: "app"
}

table items {
    id: Id primary auto
    bezeichnung: String required
}

page "/items" {
    html {
        <h1>Artikelliste</h1>
    }
}

Anhang B: Alle Fehlermeldungen von Zelyra auf einen Blick

Fehlercode Kategorie Beschreibung Typische Behebung
E-LEX-001 Lexer Unerwartetes Zeichen / Lexikalischer Fehler Tippfehler oder unzulässiges Sonderzeichen entfernen
E-PARSE-001 Parser Syntaxfehler (z. B. fehlende Klammer, falsches Token) Syntax gemäß Zelyra-Grammatik korrigieren
E-NAME-001 Auflösung Unbekannter Name / Variable nicht gefunden Deklaration prüfen oder Tippfehler korrigieren
E-TYPE-001 Typprüfung Typkonflikt (z. B. String zugewiesen an Int) Typen anpassen oder Konvertierung vornehmen
E-FEATURE-001 Feature-Schalter Zugriff auf eine deaktivierte Sprachoberfläche (web, api, crud, auth, audit) Feature in zelyra.toml oder .env aktivieren
E-CAP-001 / E-CAP-002 Capabilities Fehlende Capability-Berechtigung (z. B. database, network) In zelyra.toml unter [capabilities] freigeben
E-POLICY-001 / 002 Richtlinien Verstoß gegen Sicherheits- oder Audit-Richtlinien Sicherheitsdeklaration prüfen
E-DB-001 - E-DB-005 Datenbank Datenbankverbindungs- oder Treiberfehler DATABASE_URL prüfen, MariaDB-Dienst starten
E-SQL-001 - E-SQL-004 SQL Ungültiges SQL / Schema-Misfit / Spalte nicht existent SQL-Anweisung gegen Tabellendefinition prüfen
E-VIEW-001 - E-VIEW-009 Views & Pages Fehler in View-Interpolation, Slots oder Datenbindung Slot-Namen und Datentypen der Page-Bindung prüfen
E-VIEW-010 - E-VIEW-015 Query-Controls Ungültige Such-, Sortier-, Paginierungs- oder Filterfelder Deklarierte Whitelist (search, sort, filter) prüfen
E-FORM-001 - E-FORM-004 Formulare Validierungsfehler oder ungültige Feldtypen Formular-Deklaration und Eingabedaten anpassen
E-CRUD-001 - E-CRUD-006 CRUD Ungültige CRUD-Ressource, Schema-Konflikt oder Layout-Fehler Tabellenverknüpfung und Layout-Slots prüfen
E-AUTH-001 - E-AUTH-028 Authentifizierung Session-, Passwort- oder Berechtigungskonflikt Rollen (permits), requires auth und Hashes prüfen
E-AUDIT-001 - E-AUDIT-010 Audit-Trail Fehler in der kryptografischen Hash-Kette des Audit-Logs Prüfsummen und Audit-Tabelle validieren
E-SETUP-001 - E-SETUP-006 Setup-Flow Portkonflikt, Socket-Fehler oder Compose-Problem Freie Ports wählen, Docker-Berechtigungen prüfen
E-SETUP-WEB-001 Web-Setup Ungültiges oder abgelaufenes Setup-Token Setup-Assistenten neu starten und Token-URL nutzen
E-IMPACT-001 Impact-Analyse Zyklische oder ungültige Abhängigkeiten Quellcode-Abhängigkeiten entflechten
E-RUNTIME-001 Laufzeit Unbehandelter Laufzeitfehler Verträge (requires, ensures) oder Fehlerwerte prüfen

Anhang C: Zelyra-CLI-Referenz

Befehl Option / Flag Beschreibung
zelyra --version Gibt den vollständigen Compiler- und Paketversionsstand aus
zelyra new <dir> --template minimal|mariadb-crud|... Erstellt ein neues Zelyra-Projekt mit Vorlage
--mariadb Erzeugt MariaDB-Projekt mit Compose, Dockerfile und .env
--web-port <p> --host-port <p> --db-host-port <p> Konfiguriert Container- und Host-Ports
zelyra init [--mariadb] Initialisiert das aktuelle Verzeichnis als Zelyra-Projekt
zelyra check <file.zyl> [--format json] Prüft Syntax, Typen, Verträge und Capabilities statisch
zelyra run <file.zyl> Kompiliert und führt ein Zelyra-Programm aus
zelyra serve <file.zyl> [host:port] Startet den integrierten HTTP-Webserver
zelyra routes <entry.zyl> [--format human|json] Listet erkannte Routen mit HTTP-Methode und Quellfundstelle; Kollisionen derselben Methode melden E-ROUTE-001
zelyra setup [--database] Startet Docker Compose
[--schema] Startet Umgebung und wendet Datenbankschema an
[--all] Führt Konfiguration, Start und Migration in einem Schritt aus
[--host-port <p>] [--db-host-port <p>] Setzt verbindliche Host-Ports für die neue .env
zelyra setup --web [--port <p>] Startet den lokalen, token-geschützten Browser-Setup-Assistenten
zelyra config <file.zyl> [--format json] Zeigt die wirksame Konfiguration und Feature-Schalter geheimnisfrei an
zelyra doctor <file.zyl> [--port <p>] [--json] Prüft Toolchain, MariaDB, Docker und Ports ohne DB-Änderung
[--env-file <file>] Liest gezielt DATABASE_URL aus der angegebenen Datei
zelyra fmt <file.zyl> [--check] Formatiert Quellcode nach dem offiziellen Standard
zelyra verify <file.zyl> Führt formale Vertragsverifikation durch
zelyra doc <file.zyl> --openapi Generiert OpenAPI-3.0-Spezifikationen
--typescript Generiert typisierten, abhängigkeitsfreien TypeScript-Client
zelyra db create <file.zyl> Erzeugt SQL für die Datenbankeinrichtung
zelyra db setup <file.zyl> Richtet die MariaDB-Datenbank initial ein
zelyra db map <file.zyl> [--format=text|json] Vergleicht schreibgeschützt deklarierte Tabellen mit Live-Tabellen, Spalten und Fremdschlüsseln
zelyra db plan <file.zyl> Zeigt die geplanten Schemaänderungen; prüfe den Plan vor der Migration
zelyra db apply <file.zyl> Wendet Schema-Migrationen sicher an
zelyra auth hash-password [--stdin] Erzeugt sichere Argon2-Passworthashes
zelyra form validate <file> <Form> Prüft Formulare mit Testwerten auf der Konsole
zelyra context <file.zyl> [--format json] Gibt den semantischen Quellcode-Kontext für Tools aus
zelyra editor [directory] [--port <p>] [--no-open] Startet Studio
zelyra --tutorial invoice [directory] [--no-start] Erzeugt das Rechnungstutorial
zelyra outbox setup <file.zyl> Richtet die MariaDB-Outbox ein
zelyra outbox list <file.zyl> [--limit <1..200>] [--format text|json] Zeigt Einträge
zelyra outbox run <file.zyl> --handler <event.type=function> [--once] Verarbeitet fällige Ereignisse
zelyra outbox requeue <event-id> <file.zyl> Reiht erneut ein
`zelyra task <list run> <file.zyl>` [command] [options]
`zelyra service <install status restart
`zelyra schedule <list export> <file.zyl>` [--format cron|json]
zelyra module init <dir> --name <n> [--version <semver>] Erstellt ein offline nutzbares lokales Zelyra-Modulpaket
zelyra module list <file.zyl> [--format human|json] Zeigt Quellmodule, Exporte, Prüfsummen und Einstellungs-Schemas
zelyra module settings check <file.zyl> [--format json] Validiert typisierte Moduleinstellungen und Geheimnisse
zelyra module plan <entry.zyl> <target> Ermittelt den statischen Modulabschluss und Abhängigkeitsgraphen
zelyra module bundle <entry> <target> --output <dir> [--docker] Schnürt ein portables Quellbundle mit isolierten Paketabhängigkeiten
zelyra package lock [file.zyl] [--check|--update] Prüft oder aktualisiert die SHA-256-Integrität in zelyra.lock
zelyra package keygen|sign|verify [options] Erstellt Ed25519-Schlüssel und signiert/prüft Quellarchive (.zpkg)
zelyra package trust <add|list|remove> [publisher] [key-hex] Verwaltet autorisierte Herausgeberschlüssel in zelyra.trust.toml
zelyra package registry build <dir> Erzeugt einen statischen Index (index.json) aus .zpkg-Dateien
zelyra package search <url> [filter] Durchsucht einen statischen Paketindex nach Namen
zelyra package add <url> <pkg> <ver> --as <alias> Installiert eine exakte, vertrauensgeprüfte Paketversion
zelyra package update <url> <pkg> <ver> --as <alias> [--plan <file>] Aktualisiert Paketabhängigkeiten deterministisch
zelyra package remove <alias> Entfernt eine Paketabhängigkeit und aktualisiert das Lockfile
zelyra package cache clean [--apply] Prüft und bereinigt unreferenzierte Cache-Versionen sicher
zelyra package recover [--apply] Stellt nach abgebrochenen Transaktionen Manifest und Lock wieder her
zelyra reflect <entry.zyl> [--format=json|human] Exportiert Symbole, Schemata, Routen und Contracts als JSON
zelyra audit <inspect|export|verify|prune> <file.zyl> [options] Prüft, verifiziert oder bereinigt Audit-Logs der Anwendung
zelyra migrate-layout --check [entry.zyl] [--format human|json] Prüft die Konformität mit dem kanonischen Standard-Layout
zelyra db history <file.zyl> Zeigt die angewendete Schema-Migrationshistorie
zelyra update [--check] Prüft oder installiert das stabile Release

Anhang D: Die Standardbibliothek im Überblick (Zelyra 1.0.3 bis 1.0.7)

Die eingebaute Zelyra-Standardbibliothek umfasst Funktionen für Mathematik, Textverarbeitung, Sammlungsoperationen, Mengen, Maps, Datumsrechnung, Fehlerkontrolle, Web-Sicherheit, Encodings, Pfade sowie PDF-Rendering. Pipeline-Stufen werden typgeprüft und unterstützen seit 1.0.7 lexikalische Lambdas. Funktionen, die auf externe Systemressourcen zugreifen (z. B. Zufall, Dateisystem, Uhrzeit, E-Mail), erfordern wie gewohnt die entsprechende Capability.

1. Mathematik & Numerik (ohne Capabilities)

  • abs(x: Int | Float | Decimal) -> Int | Float | Decimal: Liefert den Betrag; Überlauf beim kleinsten Int oder Decimal sowie nicht-endliche Float-Werte ergeben einen Laufzeitfehler.
  • min(a, b) und max(a, b): Wählen den kleineren oder größeren Wert aus zwei gleich typisierten Zahlen (Int, UInt, Float, Decimal) oder Strings. Nicht-endliche Float-Werte ergeben einen Laufzeitfehler.
  • clamp(val, min_val, max_val): Begrenzt eine gleich typisierte Zahl (Int, UInt, Float, Decimal) auf [min_val, max_val]; nicht-endliche Float-Werte oder min_val > max_val ergeben einen Laufzeitfehler.
  • sqrt(x: Float) -> Float: Berechnet die Quadratwurzel einer endlichen, nicht-negativen Fließkommazahl; negative und nicht-endliche Werte ergeben einen Laufzeitfehler.
  • pow(base: Float, exp: Float) -> Float: Berechnet base ^ exp; undefinierte oder nicht-endliche Ergebnisse ergeben einen Laufzeitfehler.
  • floor(x: Float) -> Float und ceil(x: Float) -> Float: Runden eine endliche Fließkommazahl ab beziehungsweise auf.
  • round(x: Float, decimals: Int) -> Float: Rundet Halbwerte weg von null; decimals muss zwischen -15 und 15 liegen. Ein nicht-endliches Ergebnis ergibt einen Laufzeitfehler.

2. Strings & Textverarbeitung

  • len(text: String) -> Int: Liefert die Anzahl der Unicode-Codepoints im String.
  • text_to_lower(text: String) -> String: Konvertiert den gesamten Text in Kleinbuchstaben.
  • text_to_upper(text: String) -> String: Konvertiert den gesamten Text in Großbuchstaben.
  • text_starts_with(text: String, prefix: String) -> Bool: Prüft, ob der Text mit prefix beginnt.
  • text_ends_with(text: String, suffix: String) -> Bool: Prüft, ob der Text mit suffix endet.
  • text_replace(text: String, search: String, replacement: String) -> String: Ersetzt alle Vorkommen von search durch replacement.
  • text_split(text: String, delimiter: String) -> String[] (Alias: split): Zerlegt den Text am Trennzeichen in ein Array.
  • text_pad_left(text: String, target_len: Int, pad: String) -> String und text_pad_right(...): Füllen auf die Ziellänge in Unicode-Codepoints auf; ein mehrstelliges pad wird codepointweise wiederholt. Leere Fülltexte verwenden Leerzeichen. Negative Längen und Ausgaben über 16 MiB ergeben Laufzeitfehler.
  • text_index_of(text: String, sub: String) -> Option<Int>: Liefert den nullbasierten Unicode-Codepoint-Index des ersten Vorkommens oder None.
  • text_repeat(text: String, count: Int) -> String: Wiederholt den Text count-mal; negative Wiederholungen oder Ausgaben über 16 MiB ergeben Laufzeitfehler.
  • text_is_empty(text: String) -> Bool: Prüft, ob der Text exakt die Länge 0 hat.
  • text_is_blank(text: String) -> Bool: Prüft, ob der Text nach Entfernung von Whitespace leer ist.
  • text_capitalize(text: String) -> String: Wandelt das erste Zeichen in einen Großbuchstaben um.
  • text_substring(text: String, start: Int, length: Int) -> String: Schneidet einen Teilstring ab Position start mit Maximallänge length aus.
  • text_trim(text: String) -> String: Entfernt führende und nachgestellte Whitespaces.
  • text_trim_start(text: String) -> String: Entfernt führende Whitespaces.
  • text_trim_end(text: String) -> String: Entfernt nachgestellte Whitespaces.
  • text_contains(text: String, sub: String) -> Bool: Prüft, ob sub im Text enthalten ist.
  • text_chars(text: String) -> String[]: Zerlegt den Text in einzelne Zeichen als Array.

3. Arrays & Sammlungen

  • len(array: T[]) -> Int: Liefert die Anzahl der Elemente im Array.
  • append(array: T[], element: T) -> T[]: Liefert ein neues Array mit angehängtem Element.
  • concat(a: T[], b: T[]) -> T[]: Verbindet zwei Arrays desselben Typs.
  • contains(array: T[], element: T) -> Bool: Prüft, ob das Element im Array vorhanden ist.
  • first(array: T[]) -> Option<T>: Liefert das erste Element oder None bei leerem Array.
  • last(array: T[]) -> Option<T>: Liefert das letzte Element oder None bei leerem Array.
  • sort(array: T[]) -> T[]: Sortiert die Elemente aufsteigend.
  • sort_desc(array: T[]) -> T[]: Sortiert die Elemente absteigend.
  • reverse(array: T[]) -> T[]: Kehrt die Reihenfolge der Elemente um.
  • slice(array: T[], start: Int, length: Int) -> T[]: Extrahiert einen Teilbereich des Arrays.
  • join(array: T[], delimiter: String) -> String: Verbindet alle Elemente als String mit dem Trennzeichen.
  • unique(array: T[]) -> T[] (Alias: dedup): Liefert ein neues Array ohne Duplikate unter Beibehaltung der ersten Vorkommen.
  • array_sum(array: T[]) -> T: Berechnet die Summe von Int, Float oder Decimal; Ganzzahl- und Dezimalüberläufe sowie nicht-endliche Fließkommawerte ergeben einen Laufzeitfehler. Für ein leeres Array ist das Ergebnis 0.
  • avg(array: T[]) -> Float | Decimal: Berechnet den Mittelwert von Int- und Float-Arrays als Float, bei Decimal-Arrays als Decimal. Der Dezimalmittelwert wird mit der festen Decimal-Skala abgeschnitten; ein leeres Array und Überläufe ergeben einen Laufzeitfehler.
  • min_of(array: T[]) -> Option<T> und max_of(array: T[]) -> Option<T>: Wählen das Minimum beziehungsweise Maximum aus Zahlen oder Strings; ein leeres Array ergibt None, nicht-endliche Float-Werte ergeben einen Laufzeitfehler.
  • chunk(array: T[], size: Int) -> T[][]: Teilt das Array in Teil-Arrays der angegebenen Höchstgröße auf; size muss größer als null sein.
  • flatten(array: T[][]) -> T[]: Ebnet ein verschachteltes zweidimensionales Array in ein flaches Array ein.
  • zip(a: T[], b: U[]) -> Tuple<T, U>[]: Kombiniert Arrays elementweise bis zur Länge des kürzeren Arrays.
  • map(array: T[], transform: Function<(T) -> U>) -> U[]: Transformiert jedes Element eines Arrays (unterstützt seit 1.0.7 lexikalische Lambdas wie |x| x * 2).
  • filter(array: T[], predicate: Function<(T) -> Bool>) -> T[]: Filtert Elemente eines Arrays nach einer Prädikatsfunktion oder einem Lambda (|x| x > 0).
  • find(array: T[], predicate: Function<(T) -> Bool>) -> Option<T>: Findet das erste Element, das die Bedingung erfüllt, oder liefert None.
  • reduce(array: T[], initial: U, accumulator: Function<(U, T) -> U>) -> U: Faltet das Array mit einem Anfangswert und einer Akkumulatorfunktion zusammen.

4. Mengen (Sets)

  • set_union(a: Set<T>, b: Set<T>) -> Set<T>: Berechnet die Vereinigungsmenge A ∪ B.
  • set_intersection(a: Set<T>, b: Set<T>) -> Set<T>: Berechnet die Schnittmenge A ∩ B.
  • set_difference(a: Set<T>, b: Set<T>) -> Set<T>: Berechnet die Differenzmenge A \ B.

5. Maps & Wörterbücher

  • get(map: Map<K, V>, key: K) -> Option<V>: Schlägt den Schlüssel nach und liefert Some(V) oder None.
  • get_or(map: Map<K, V>, key: K, default: V) -> V (Alias: get_or_default): Liefert den Wert zum Schlüssel oder den Standardwert default.
  • has_key(map: Map<K, V>, key: K) -> Bool: Prüft, ob der Schlüssel in der Map existiert.
  • put(map: Map<K, V>, key: K, value: V) -> Map<K, V>: Fügt ein Paar hinzu oder aktualisiert es funktional.
  • map_remove(map: Map<K, V>, key: K) -> Map<K, V>: Liefert eine neue Map ohne den angegebenen Schlüssel.
  • map_merge(a: Map<K, V>, b: Map<K, V>) -> Map<K, V>: Verschmilzt zwei Maps; bei Kollisionen überschreiben Werte aus b.
  • map_increment(map: Map<K, Int>, key: K) -> Result<Map<K, Int>, String>: Erhöht einen typgeprüften Zähler um eins oder fügt ihn mit Startwert 1 ein. Der Eingabewert bleibt unverändert; ein Int-Überlauf wird als Err zurückgegeben.
  • keys(map: Map<K, V>) -> K[]: Liefert alle Schlüssel der Map als Array.
  • values(map: Map<K, V>) -> V[]: Liefert alle Werte der Map als Array.
  • entries(map: Map<K, V>) -> Tuple<K, V>[]: Liefert Schlüssel-Wert-Paare als Tupel-Array, das sich mit for (schluessel, wert) in entries(map) typgeprüft zerlegen lässt.

6. Datums- & Kalenderberechnung

  • date_add_days(d: Date, days: Int) -> Date: Addiert oder subtrahiert Kalendertage von einem Date (YYYY-MM-DD); Ergebnisse müssen zwischen den Jahren 0001 und 9999 liegen.
  • date_diff_days(start: Date, end: Date) -> Int: Berechnet start - end in Kalendertagen. Für verbleibende Tage daher date_diff_days(frist, heute) aufrufen.
  • is_before(a: Date, b: Date) -> Bool: Prüft, ob Datum a chronologisch vor b liegt.
  • is_after(a: Date, b: Date) -> Bool: Prüft, ob Datum a chronologisch nach b liegt.

7. Ablauf-, Option- und Result-Kontrolle

  • is_ok(res: Result<T, E>) -> Bool: Prüft, ob das Resultat ein Erfolg Ok(...) ist.
  • is_err(res: Result<T, E>) -> Bool: Prüft, ob das Resultat ein Fehler Err(...) ist.
  • is_some(opt: Option<T>) -> Bool: Prüft, ob die Option einen Wert Some(...) enthält.
  • is_none(opt: Option<T>) -> Bool: Prüft, ob die Option None ist.
  • unwrap_or(opt: Option<T>, default: T) -> T: Entpackt den Wert oder liefert den Fallback default.
  • panic(msg: String) -> Never: Bricht die Programmausführung mit einer kontrollierten Laufzeitfehlermeldung ab.
  • else if: Flache Verzweigungskaskaden ohne erzwungene tiefe Blockschachtelung.
  • |> (Pipeline-Operator): Übergibt den linken Ausdruck als erstes Argument an den rechten Funktionsaufruf: daten |> sort() |> join(", ").

8. Typkonvertierung & Parsing

  • to_string(value: Any) -> String: Wandelt jeden beliebigen Zelyra-Wert in seine String-Darstellung um.
  • to_int(value: Int | UInt | Float | Bool | String) -> Int: Konvertiert zu Int; Fließkommazahlen werden gegen null abgeschnitten. Nicht endliche oder außerhalb des Int-Bereichs liegende Zahlen und ungültige Strings ergeben einen Laufzeitfehler.
  • to_float(value: Float | Int | UInt | String) -> Float: Konvertiert zu einer endlichen Fließkommazahl; ungültige oder nicht-endliche Werte ergeben einen Laufzeitfehler.
  • parse_int(text: String) -> Result<Int, String>: Parst einen String sicher als Int oder liefert Err(String).
  • parse_float(text: String) -> Result<Float, String>: Parst einen String als endliche Float-Zahl oder liefert Err(String).
  • parse_bool(text: String) -> Result<Bool, String>: Parst "true", "false", "1", "0" in einen Bool; Groß-/Kleinschreibung der Wörter ist egal, ungültige Werte liefern Err(String).

9. Sicherheit, Hashing & Web-Encodings

  • hash_sha256(text: String) -> String: Berechnet den hexadezimalen SHA-256-Hash des Strings.
  • base64_encode(text: String) -> String: Kodiert den UTF-8-String in Base64.
  • base64_decode(encoded: String) -> Result<String, String>: Dekodiert einen Base64-String sicher.
  • url_encode(text: String) -> String: Prozent-kodiert Sonderzeichen für URL-Parameter nach RFC 3986.
  • url_decode(encoded: String) -> Result<String, String>: Dekodiert Prozentkodierungen sicher; + wird als Leerzeichen behandelt.
  • uuid_v4() -> String (uses Random): Erzeugt eine kryptografisch sichere Version-4-UUID nach RFC 4122.

10. Pfade & Dateisystem

  • path_join(a: String, b: String) -> String: Verbindet zwei Pfadsegmente nach den Pfadkonventionen des Hostbetriebssystems (ohne Capability).
  • path_filename(path: String) -> String: Extrahiert den Dateinamen aus einem Pfad (ohne Capability).
  • path_extension(path: String) -> String: Extrahiert die Dateiendung ohne führenden Punkt (ohne Capability).
  • path_dir(path: String) -> String: Extrahiert das Elternverzeichnis eines Pfades (ohne Capability).
  • file_exists(path: String) -> Bool (uses FileSystem): Prüft innerhalb der erlaubten Lesepfade, ob eine Datei existiert.
  • dir_exists(path: String) -> Bool (uses FileSystem): Prüft innerhalb der erlaubten Lesepfade, ob ein Verzeichnis existiert.
  • file_size(path: String) -> Result<Int, String> (uses FileSystem): Liefert die Dateigröße in Bytes.

11. Capability-geschützte System-APIs

  • uses Console:
    • read_console(prompt: String) -> String?: Gibt den Prompt auf stdout aus und liest eine Zeile von stdin; None signalisiert EOF.
  • uses Clock:
    • now() -> Timestamp: Liefert den aktuellen Systemzeitstempel in Millisekunden seit der Unix-Epoche.
  • uses Random:
    • random_int(min: Int, max: Int) -> Int: Erzeugt eine ganzzahlige Pseudozufallszahl im geschlossenen Bereich [min, max].
    • uuid_v4() -> String: Erzeugt eine kryptografisch sichere Version-4-UUID.
  • uses Environment:
    • env(name: String) -> Option<String>: Liest den Wert einer Systemumgebungsvariablen aus.
  • uses FileSystem:
    • read_text(path: String) -> String: Liest den gesamten Inhalt einer UTF-8-Textdatei.
    • write_text(path: String, content: String): Schreibt Text in eine Datei und überschreibt vorhandenen Inhalt innerhalb der freigegebenen Schreibpfade.
    • delete_file(path: String): Löscht eine Datei innerhalb der Projektfreigaben.
    • list_dir(dir: String) -> String[]: Listet die Dateinamen eines Verzeichnisses auf.
    • file_exists(path: String) -> Bool: Prüft die Existenz einer Datei.
    • dir_exists(path: String) -> Bool: Prüft die Existenz eines Ordners.
    • file_size(path: String) -> Result<Int, String>: Liest die Größe einer Datei in Bytes.
  • uses Network:
    • http_get(url: String) -> String: Führt einen synchronen HTTP-GET-Request gegen erlaubte Hosts aus.
    • http_request(method: String, url: String, headers: String[], body: String?) -> HttpResponse: Führt eine HTTP-Anfrage mit Methode, URL, Headern und optionalem Text-Body aus.
    • http_json<Req, Res>(method: String, url: String, headers: String[], body: Req?) -> Res: Sendet eine HTTP-Anfrage und dekodiert eine erfolgreiche JSON-Antwort.
    • http_result<Req, Res>(method: String, url: String, headers: String[], body: Req?) -> HttpResult<Res>: Liefert typisierte HTTP-Ergebnisse einschließlich Statuscode und Headern.
  • uses Process:
    • run_process(command: String, args: String[]) -> ProcessResult: Startet einen explizit freigegebenen Prozess ohne Shell.
  • uses Database:
    • sql<T[]> { SELECT ... }: Führt typisierte relationale Abfragen mit parametrisierten Platzhaltern aus.
    • transaction { ... }: Fasst mehrere Schreib- und Leseabfragen in einer atomaren Datenbanktransaktion zusammen.
  • uses Email:
    • render_pdf(view_name: String, data: Record) -> Bytes (neu in 1.0.7): Rendert eine deklarierte View mit Record-Daten lokal in ein paginiertes Text-PDF.
    • mail_queue(to, subject, text, filename, pdf_bytes) (neu in 1.0.7): Reiht innerhalb einer Datenbank-Transaktion eine E-Mail mit transaktionalem PDF-Anhang in die Outbox ein.

Anhang E: SQL-Spickzettel für Zelyra-Entwickler

In Zelyra eingebettetes SQL wird mit sql<T[]> oder sql ausgeführt:

database main {
    engine: mariadb
    database: "app"
}

table tasks {
    id: Id primary auto
    name: String required
    erledigt: Bool default false
}

fn sql_beispiele() uses Database {
    # 1. SELECT mit typisiertem Rueckgabetyp und sicherem Parameter
    status = false
    gefiltert = sql<Task[]> {
        SELECT id, name, erledigt
        FROM tasks
        WHERE erledigt = :status
    }

    # 2. INSERT in einer Transaktion
    text = "Neue Aufgabe"
    transaction {
        sql {
            INSERT INTO tasks (name, erledigt)
            VALUES (:text, false)
        }
    }

    # 3. UPDATE
    ziel_id = 1
    transaction {
        sql {
            UPDATE tasks
            SET erledigt = true
            WHERE id = :ziel_id
        }
    }
}

fn main() uses Database {
    print("SQL Spickzettel validiert.")
}

Anhang F: HTML- und Web-Referenz in Zelyra

Web-Strukturen und Deklarationen

Element Deklaration Zweck
Page page "/pfad/{param}" { ... } Definiert eine HTTP-GET-Route mit Pfadparametern und HTML-Antwort
View-Layout view LayoutName { html { ... <slot /> ... } } Wiederverwendbares Layout mit Standard- und benannten Slots
Component component Name { props { ... } html { ... } } Wiederverwendbare HTML-Komponente mit typisierten Eigenschaften
Named Slot <slot name="header">Fallback</slot> Platzhalter im Layout/Komponente mit optionalem Standardinhalt
Slot Injection <slot name="header">Inhalt</slot> Übergabe von Kindinhalten an den passenden Slot
Data Loading load item = sql<Item> { SELECT ... } Typisiertes Laden eines Einzeldatensatzes mit Feldzugriff {item.field}
Collection Loop for item in items { <li>{item.name}</li> } Typisierte serverseitige Iteration über geladene Datensätze
Search Control search { col1 col2 } Whitelist-geprüfte URL-Suche mit parametrisierter LIKE-Abfrage
Sort Control sort { col1 col2 } Typisierte Sortierung über ?sort=col&order=asc|desc
Pagination paginated 25 Paginierung mit LIMIT/OFFSET, page, pages und total
Filter Control filter { col1 col2 } Typisierte Filteroperatoren (eq, contains, starts_with, gt, lte etc.)
CRUD Layout crud Res { table tbl layout: LayoutName } Bindet generierte CRUD-Ansichten in Slots title, nav, content, actions ein

Zelyra UI: Designsystem & CSS-Architektur (ab Version 1.0.6)

Ab Zelyra 1.0.6 enthält die Plattform ein vollwertiges, compilergeprüftes UI- und CSS-Framework für datenbankgestützte Geschäftsanwendungen. Es ersetzt unübersichtliche externe Abhängigkeiten durch ein lokal gebündeltes Designsystem (zelyra-ui.v1.css).

CSS-Architektur mit Standard @layer

Das Stylesheet ist hierarchisch in sieben standardkonforme CSS-Schichten strukturiert:

@layer reset, base, theme, layout, components, utilities, overrides;
  1. reset: Normalisierung moderner HTML5-Elemente ohne aggressive Resets.
  2. base: Typografie, Standard-Zeilenabstände und semantische Standardelemente.
  3. theme: Design Tokens und Farbsysteme (light, dark, system) über CSS Custom Properties.
  4. layout: App-Shells (.z-app-shell), Header, responsive Raster und Navigation.
  5. components: 55 wiederverwendbare Komponenten (Cards, Buttons, Inputs, Badges, Tabs, Alerts, Modals, Tables, Charts).
  6. utilities: Präzise Hilfsklassen für Abstände, Ausrichtungen und Textfluss.
  7. overrides: Sichere projektlokale Anpassungen (aus zelyra.theme.css).

Design Tokens & CSS Custom Properties

Token Standardwert (Light) Dunkelwert (Dark) Verwendung
--z-color-primary #2f54eb #a8bfff Primäraktionen, Links, Fokus-Rahmen
--z-color-surface #ffffff #1b232d Karten- und Container-Hintergrund
--z-color-canvas #f4f6f9 #141a22 Gesamter Seitenhintergrund
--z-color-text #1f2937 #edf2f8 Haupttext
--z-color-border #d9dfe7 #3a4655 Trennlinien und Rahmen
--z-color-success #10b981 #8dd6ad Erfolgsmeldungen und Validierungs-Feedback
--z-color-warning #f59e0b #f0ca75 Warnhinweise und Fälligkeitswarnungen
--z-color-danger #ef4444 #ffafb2 Fehlerzustände und Löschaktionen

Themes, Dichte und Barrierefreiheit

Die Steuerung erfolgt über semantische Root-Attribute am <html>- oder Shell-Tag:

  • data-z-theme="light": Erzwingt helles Farbschema.
  • data-z-theme="dark": Erzwingt dunkles Farbschema (angepasster Kontrast, blendfrei).
  • data-z-theme="system": Passt sich automatisch den Systemeinstellungen des Nutzers an (prefers-color-scheme).
  • data-z-density="compact": Reduziert Padding und Steuerelementhöhen (--z-control-height: 2.25rem) für datenintensive CRUD-Listen.
  • prefers-reduced-motion: Schaltet Animationen und Übergangszeiten automatisch auf 0ms für barrierearme Nutzung.

55 Typisierte Komponenten (zelyra/ui)

Das Modul zelyra/ui stellt typisierte Standardkomponenten für Geschäftsoberflächen bereit:

  • Layout & Navigation: AppShell, PageHeader, Breadcrumbs, NavList, Tabs, Grid, Stack.
  • Daten & Feedback: Card, Badge, Alert, EmptyState, Progress, Spinner.
  • Formulare & Interaktion: Button, LinkButton, InputField, SelectField, CheckboxField, ConfirmationForm.
  • Berichte & Dokumente: DataTable, DonutChart, BarChart, LineChart, EmailLink, PrintTrigger.

Druck- und Dokumenten-Styling (@media print)

Für Rechnungen, Lieferscheine und Audit-Berichte enthält Zelyra UI optimierte Druckregeln:

  • Ausblenden von Navigation, Suchfeldern und Aktionsschaltflächen (.z-no-print).
  • Feste Seitenumbrüche (page-break-inside: avoid für Positionstabellen).
  • Optimierung für den standardmäßigen Browserdialog („Als PDF speichern“), ohne fehleranfällige native PDF-Server-Binaries vorauszusetzen.

Stylesheet-Treiber (ZELYRA_UI_STYLESHEET)

Über die Umgebungskonfiguration lässt sich das Stylesheet-Verhalten steuern:

  • ZELYRA_UI_STYLESHEET="default": Zelyra UI v1 (empfohlen).
  • ZELYRA_UI_STYLESHEET="tabler": Integriertes Tabler-CSS (Rückwärtskompatibilität).
  • ZELYRA_UI_STYLESHEET="./assets/custom.css": Sicheres, projektrelatives Stylesheet. Externe URLs (http://...) werden zum Schutz vor Supply-Chain-Angriffen strikt abgewiesen.

Anhang G: Glossar der Fachbegriffe

Die Einträge verweisen auf das Kapitel, in dem der Begriff erläutert wird. Nicht unterstützte Sprachkonstrukte und CLI-Befehle sind als solche gekennzeichnet.

  • Zelyra UI: Das in Zelyra 1.0.6 eingeführte, direkt im Compiler und Binary gebündelte Designsystem mit compilergeprüften UI-Komponenten, Barrierefreiheit, Theme- und Dichte-Steuerung. Anhang F: HTML- und Web-Referenz.
  • Appearance (Darstellungsvariante): Compilergeprüfte semantische Darstellungsattribute (default, success, warning, danger) für UI-Komponenten und Formularfelder. Kapitel 26: Webseiten ausgeben.
  • Density (Dichte): Umschaltbare Darstellungsdichte (data-z-density="compact"), die Abstände und Zeilenhöhen für datenintensive Tabellen und Geschäftsübersichten optimiert. Anhang F: HTML- und Web-Referenz.
  • CSS @layer: Moderner CSS-Standard zur geordneten Schichtung von Reset, Base, Theme, Layout, Components, Utilities und Overrides, der Style-Konflikte strukturell verhindert. Anhang F: HTML- und Web-Referenz.
  • Wert (Value): Eine benannte Information. In Zelyra sind Werte standardmäßig unveränderlich (immutable), um Seiteneffekte und unerwartete Zustandsänderungen auszuschließen. Kapitel 5: Werte und Datentypen.
  • mutable (Veränderbarkeit): Schlüsselwort zur bewussten Deklaration veränderlicher Variablen. Signalisiert Entwicklern und Compiler sofort, dass sich dieser Zustand im Programmverlauf ändern wird. Kapitel 6: Variablen und Unveränderlichkeit.
  • Funktion (fn): Eine benannte Operation mit expliziten Parametertypen und einem festen Rückgabetyp. Funktionen sind die primären Bausteine jeder Zelyra-Anwendung. Kapitel 11: Funktionen und Prozeduren.
  • Reine Funktion (Pure Function): Funktion ohne Seiteneffekte: Ihr Ergebnis hängt nur von den Eingaben ab. Gleiche Eingaben liefern dasselbe Ergebnis. Kapitel 11: Funktionen und Prozeduren.
  • Typinferenz (Type Inference): Die Fähigkeit des Zelyra-Compilers, Datentypen automatisch aus Literalen und Ausdrücken abzuleiten. Verbindet statische Typsicherheit mit leichtem, lesbarem Code. Kapitel 5: Werte und Datentypen.
  • Typed Hole (_): Der Unterstrich _ als typisierter Platzhalter. Zeigt Compiler und KI-Werkzeugen an, dass hier eine Implementierung erwartet wird, deren Typ aus dem Kontext abgeleitet wird. Kapitel 32: KI-Nativität.
  • Block & Scope: Lexikalischer Gültigkeitsbereich von Bezeichnern, eingefasst durch geschweifte Klammern { ... }. Innerhalb des Blocks deklarierte Namen sind an diesen Bereich gebunden. Kapitel 6: Variablen und Unveränderlichkeit.
  • Datentyp (Type): Eine exakte Spezifikation zulässiger Werte und Operationen. Zelyra unterscheidet skalare Typen (Int, Float, Bool, String, Decimal, Id) und zusammengesetzte Typen. Kapitel 5: Werte und Datentypen.
  • Option (Some / None): Sicherer Container für Werte, die vorhanden sein können (Some(v)) oder fehlen (None). Das Suffix ? (z. B. String?) macht Abwesenheit zur Typpflicht. Kapitel 18: Option-Muster.
  • Result (Ok / Err): Typ für Operationen, die fehlschlagen können. Er gibt Erfolg als Ok(daten) und einen Fehler als Err(grund) explizit zurück. Kapitel 17: Result-Muster.
  • Null-Safety (Kein null): In Zelyra existiert kein null, nil oder undefined. Die sogenannte „Milliarden-Dollar-Fehlerquelle“ von Null-Pointer-Abstürzen ist im Sprachkern strukturell unmöglich. Kapitel 18: Option-Muster.
  • Pattern Matching (match): Vollständige Fallunterscheidung von Datenstrukturen, Optionen und Ergebnissen. Der Compiler verweigert die Kompilierung, wenn ein möglicher Zweig vergessen wurde. Kapitel 14: Eigene Datentypen.
  • Nominale Fachtypen (Domain Types): Definition semantisch getrennter Typen wie type UserId = Id und type OrderId = Id. Verhindert versehentliches Vertauschen gleichartiger ID-Werte in der Fachlogik. Kapitel 14: Eigene Datentypen.
  • Datensatz (Record / Struct): Zusammenfassung mehrerer benannter, typisierter Felder zu einer kohärenten Datenstruktur mit Konstruktoren und Destrukturierung. Kapitel 14: Eigene Datentypen.
  • Aufzählungstyp (Enum / Sum Type): Ein Aufzählungstyp beschreibt eine feste Menge disjunkter Varianten. Benutzerdefinierte enum-Deklarationen gehören derzeit nicht zum dokumentierten Zelyra-Sprachumfang; das Konzeptmuster ist keine ausführbare Zelyra-Syntax. Kapitel 14: Eigene Datentypen.
  • Design by Contract: Entwurfsmethode zur formalen Absicherung von Software: Jede Funktion deklariert präzise Vorbedingungen (requires) und Nachbedingungen (ensures). Kapitel 12: Verträge und Vorbedingungen.
  • Vorbedingung (requires): Vertragliche Klausel am Funktionskopf, die gültige Eingabewerte erzwingt, bevor die Funktion ausgeführt wird. Schützt Funktionen vor unzulässigen Aufrufen. Kapitel 12: Verträge und Vorbedingungen.
  • Nachbedingung (ensures & result): Vertragliche Garantie für das Berechnungsergebnis. Das Schlüsselwort result bezieht sich auf den zurückgegebenen Wert und sichert dessen Korrektheit zu. Kapitel 12: Verträge und Vorbedingungen.
  • Schleifeninvariante (invariant): Zusicherung innerhalb einer Schleife, die vor und nach jedem einzelnen Schleifendurchlauf garantiert wahr sein muss. Ermöglicht formale Korrektheitsbeweise. Kapitel 12: Verträge und Vorbedingungen.
  • Capability (Fähigkeit): Ausdrückliche Berechtigung (uses FileSystem, uses Database), ohne die eine Funktion keine externen Seiteneffekte ausführen darf. Verhindert unbefugte Systemzugriffe. Kapitel 33: Capabilities.
  • Sandboxing & Least Privilege: Das Capability-Modell begrenzt Systemzugriffe auf die Fähigkeiten, die Funktionen anfordern und die für das Projekt freigegeben sind. Kapitel 33: Capabilities.
  • Array (Typ[]): Geordnete Liste gleichartiger Werte. Zelyra stellt dafür Operationen wie len(), append(), first(), last() und contains() bereit. Kapitel 13: Arrays & Maps.
  • Wörterbuch / Map (Map<K, V>): Assoziativer Speicher für Schlüssel-Wert-Paare. Das Nachschlagen via get(map, key) liefert stets eine sichere Option<V>, um Abstürze bei fehlenden Schlüsseln zu verhindern. Kapitel 13: Arrays & Maps.
  • Unveränderliche Listenoperationen: Unveränderliche Sammlungsoperationen ändern den bestehenden Wert nicht direkt, sondern liefern für Änderungen einen neuen Wert zurück. Kapitel 13: Arrays & Maps.
  • for .. in Iteration: Schleifenform, die nacheinander über Elemente einer Sammlung läuft. Sie erspart manuelle Indexverwaltung. Kapitel 10: Wiederholungen und Schleifen.
  • Tabelle (table): Direkte Deklaration relationaler Schemata in Zelyra-Syntax inklusive Spaltentypen, Primärschlüsseln (primary), Unikatsregeln (unique) und Defaults. Kapitel 24: Tabellen definieren.
  • Migration (db plan & apply): Kontrollierte Schemaänderung: zelyra db plan zeigt den Unterschied zwischen Quellmodell und Datenbank; zelyra db apply übernimmt den geprüften Plan. Prüfe vorher mögliche Auswirkungen, denn ein abgebrochener DDL-Schritt kann einen teilweise geänderten Zustand hinterlassen. Kapitel 24: Tabellen definieren.
  • CRUD-Ressource (crud): Create, Read, Update, Delete: Deklarative Verknüpfung einer Tabelle mit den 4 Grundoperationen. Generiert Listen-, Detail- und Editiermasken ohne Boilerplate. Kapitel 28: CRUD-Muster.
  • Natives SQL (sql { ... }): Vollwertiges SQL direkt im Code, das zur Compile-Zeit gegen das deklarierte Schema validiert wird. Kombiniert die Ausdrucksstärke von SQL mit absoluter Typprüfung. Kapitel 25: Daten abfragen.
  • Transaktion (transaction): Atomare Bündelung mehrerer Datenbankbefehle: Alle Schreiboperationen gelingen gemeinsam oder werden bei einem Fehler vollständig zurückgerollt. Kapitel 25: Daten abfragen.
  • Fremdschlüssel (fk): Deklarative Beziehungsdefinition zwischen Tabellen. Erzwingt referentielle Integrität und unterstützt Kaskadierungsregeln (on_delete). Kapitel 24: Tabellen definieren.
  • Komponente (component): Wiederverwendbarer HTML-Baustein mit deklarierten Props, Slots und kontextabhängigem XSS-Schutz. HTML wird als erstklassiger Sprachbestandteil gerendert. Kapitel 26: Webseiten ausgeben.
  • Slot (slot: name): Platzhalter in Layouts und Containern, der beim Aufruf mit dynamischen Kind-Elementen oder spezifischen UI-Aktionen befüllt werden kann. Kapitel 26: Webseiten ausgeben.
  • Formular & Validierung (form): Typisierte Eingabemaske mit synchronisierten Validierungsregeln für Server und Client. Validierungsfehler werden strukturiert an das UI zurückgemeldet. Kapitel 27: Formulare.
  • CSRF-Schutz: Automatischer kryptografischer Token-Schutz gegen Cross-Site-Request-Forgery bei allen statusverändernden HTTP-Anfragen (POST, PUT, DELETE). Kapitel 27: Formulare.
  • Route: Explizite Zuordnung einer HTTP-Methode und URL-Maske zu einem Funktions-Controller oder einer CRUD-Ressource. Kapitel 26: Webseiten ausgeben.
  • Authentifizierung (auth): Natives Sprachkonstrukt zur Konfiguration von Benutzer-Login, Sitzungen und Rollenrechten ohne externe Authentifizierungs-Frameworks. Kapitel 29: Authentifizierung.
  • Argon2 (Passwort-Hashing): Moderner, speicherharter Algorithmus zur sicheren Speicherung von Passwörtern. In Zelyras Auth-System standardmäßig erzwungen. Kapitel 29: Authentifizierung.
  • requires auth & permits: Deklarativer Zugriffsschutz an Routen: Weist unbefugte Aufrufe automatisch mit HTTP 401/403 ab oder leitet zur Anmeldeseite weiter. Kapitel 29: Authentifizierung.
  • uses FileSystem: Sicherheits-Capability für Festplattenoperationen. Jede Funktion, die Dateien liest oder schreibt, muss dieses Recht explizit anfordern. Kapitel 20: Dateien.
  • read_text & write_text: Standardfunktionen für das sichere Lesen und Schreiben ganzer UTF-8-Dateien mit automatischem Ressourcen-Cleanup und Result-Fehlerbehandlung. Kapitel 20: Dateien.
  • uses Clock & now(): Systemzeit-Capability: Funktionen, die aktuelle Zeitstempel ermitteln (now()), müssen uses Clock deklarieren. Macht Zeit in Tests deterministisch mockbar. Kapitel 21: Datum & Zeit.
  • uses Random & random_int(): Zufalls-Capability: Erlaubt das Erzeugen pseudozufälliger Werte. Trennt deterministische Berechnungen klar von nicht-deterministischen Zufallswerten. Kapitel 21: Datum & Zeit.
  • uses Environment & env(): Umgebungsvariablen-Capability: env("KEY") liest Konfigurationswerte aus dem System oder .env und liefert ein sicheres Option<String>. Kapitel 37: Konfiguration.
  • parallel & await: Ein parallel-Block startet unabhängige Aufgaben und wartet mit await auf ihre Ergebnisse. Der Gültigkeitsbereich der Aufgaben bleibt im Block sichtbar. Kapitel 22: Nebenläufigkeit.
  • json_encode & json_decode: Typsichere Umwandlung von Datenstrukturen in JSON und Rückwandlung in typisierte Records (Result<T, JsonError>). Schließt Parsing-Crashes aus. Kapitel 21: JSON & Daten.
  • zelyra.toml (Projekt-Manifest): Projektmanifest für Metadaten, Capability-Freigaben und unterstützte Projekteinstellungen. Zelyra-Module werden per Projektpfad eingebunden; ein Paketregister ist nicht Teil des dokumentierten Umfangs. Kapitel 15: Module & Projekte.
  • zelyra verify (Vertragsprüfung): CLI-Befehl für die dokumentierten Vertrags- und Invariantenprüfungen. Die formale Analyse gilt für den unterstützten Prüfumfang und ersetzt keine allgemeine Korrektheitsgarantie. Kapitel 19: Qualitätssicherung.
  • assert (Test-Zusicherung): Prüft eine Bedingung und meldet einen Fehler, wenn sie falsch ist. Projektbezogene Assertions können in einer ausführbaren .zyl-Datei mit zelyra run ausgeführt werden; ein separater Test-Unterbefehl ist nicht dokumentiert. Kapitel 19: Qualitätssicherung.
  • Source Authority (Verbindliche Quellen): Regel zur Klärung widersprüchlicher Quellen. Die Handbuchreferenz nennt in dieser Reihenfolge Sprachspezifikation, Compilerimplementierung, offizielle Tests, Standardbibliothek, verifizierte Beispiele und Dokumentation. Referenz 24: Source Authority.
  • Drei-Schichten-Architektur: Empfohlenes Zelyra-Architekturmuster: Saubere Trennung in Präsentationsschicht (Web/HTML), reine Geschäftslogik (pure functions) und Persistenz (Tabellen & Schema). Kapitel 36: Architektur.
  • TDD (Testgetriebene Entwicklung): Entwicklungsmethode, bei der Tests vor oder zusammen mit der Implementierung geschrieben werden. Sie unterstützt schrittweises Feedback, garantiert aber keinen fehlerfreien Code. Kapitel 19: Qualitätssicherung.
  • Regressionsschutz: Schutz vor dem Wiederauftreten früherer Fehler. Statische Prüfungen, Vertragsverifikation und projektbezogene Testläufe helfen, unbeabsichtigte Änderungen nach einem Refactoring zu erkennen. Kapitel 19: Qualitätssicherung.
  • AST (Abstract Syntax Tree): Der hierarchische Syntaxbaum, den der Parser aus dem Quelltext aufbaut. Compilerphasen wie Namensauflösung und Typprüfung arbeiten auf seiner strukturierten Repräsentation weiter. Referenz 24: Source Authority.
  • Zelyra CLI (zelyra): Das Kommandozeilenprogramm für Zelyra-Projekte: Es legt Projekte mit new an, prüft Quellcode mit check, führt Programme mit run aus und bietet weitere dokumentierte Unterbefehle. Ein eigener test-Unterbefehl ist in der aktuellen CLI-Referenz nicht aufgeführt. Kapitel 4: Das erste Zelyra-Projekt.
  • zelyrac (Compiler): Der Compiler verarbeitet Zelyra-Quellcode hinter der CLI zelyra. Die aktuelle CLI-Referenz dokumentiert kein separates Programm oder den Befehl zelyrac build; zum Prüfen und Ausführen dienen zelyra check und zelyra run. Kapitel 4: Das erste Zelyra-Projekt.
  • zelyra fmt (Formatierer): Integrierter Quelltext-Formatierer mit deterministischer Ausgabe. --check prüft die Formatierung, ohne die Datei zu ändern. Kapitel 4: Das erste Zelyra-Projekt.
  • LSP (Language Server Protocol): Das Language Server Protocol standardisiert die Verbindung zwischen Programmiersprache und Editor. Die aktuelle CLI-Referenz dokumentiert zelyra editor für Studio, aber keinen separaten zelyra lsp-Unterbefehl. Kapitel 4: Das erste Zelyra-Projekt.
  • KI-Nativität (AI-Native): Sprach- und Werkzeugmerkmale wie strukturierte Diagnosen, Verträge und Typed Holes können KI-gestützte Arbeit unterstützen. Sie garantieren weder fehlerfreien Code noch ersetzen sie menschliche Prüfung. Kapitel 32: KI-Nativität.
  • Standardbibliothek (std): Überblick über die dokumentierten Standardfunktionen und Capability-geschützten Funktionen wie Dateisystem, Zeit, Zufall und Datenbank. Anhang D: Standardbibliothek.

Anhang H: Lösungen zu den Übungsaufgaben der Kapitel

Willkommen in der vollständigen Lösungsmöglichkeitsübersicht! Hier findest du zu jedem der 43 Kapitel des Lehrbuchs didaktisch aufbereitete, praxiserprobte und syntaktisch verifizierte Lösungsvorschläge für alle Übungsaufgaben (Stufe 1 bis 3).

[!TIP] Der Ehrenkodex für Entwickler: Wirf erst dann einen Blick in diese Lösungen, wenn du mindestens eine frische Tasse Kaffee lang selbst getüftelt, deinen Code mit zelyra check herausgefordert und vielleicht die eine oder andere freundliche Belehrung des Compilers eingesteckt hast. Das echte Programmierer-Glücksgefühl stellt sich erst ein, wenn der Compiler nach eigener Denkarbeit endlich lautlos nickt!


Kapitel 1: Willkommen bei Zelyra

  • Stufe 1 (Leicht – Persönliche Begrüßung):
fn main() {
    vorname = "Stefan"
    wohnort = "Berlin"
    print("Hallo Welt! Ich bin " + vorname + " aus " + wohnort + " und programmiere mit Zelyra.")
}
  • Stufe 2 (Mittel – Abschiedsfunktion):
fn begruessen(name: String) -> String {
    return "Guten Tag, " + name + "! Bereit fuer saubere Typen und null Bugs?"
}

fn verabschieden(name: String) -> String {
    return "Auf Wiedersehen, " + name + "! Moegen deine Builds immer gruen bleiben."
}

fn main() {
    benutzer = "Alex"
    print(begruessen(benutzer))
    print(verabschieden(benutzer))
}
  • Stufe 3 (Anspruchsvoll – Schutz vor Tippfehlern): In dynamischen Skriptsprachen führen Tippfehler wie preis * 1.19 (wenn preis als "100" als Text übergeben wurde) oft erst zur Laufzeit beim Kunden zum Absturz oder zu kuriosen Ergebnissen wie "100100...". Typische Probleme:
  1. Vertauschen von Typen (Text statt Zahl) führt zu Rechenfehlern.
  2. Unbemerktes Weiterreichen von null stürzt erst 20 Zeilen später in fremden Modulen ab.
  3. Falsch geschriebene Eigenschaftsnamen (user.emial) werden stillschweigend ignoriert und liefern undefined. Zelyras Compiler prüft diese Beziehungen vor der Ausführung und garantiert, dass Typen, Feldnamen und Signaturen exakt zusammenpassen.

Kapitel 2: Wie ein Programm funktioniert

  • Stufe 1 (Leicht – Ablaufstruktur): Der Kontrollfluss verläuft streng sequenziell:
  1. Einstiegspunkt: main() wird vom Betriebssystem aufgerufen.
  2. Variablen-Initialisierung: aktueller_tag und ziel_tag werden unveränderlich an Werte gebunden.
  3. Funktionsaufruf: tage_bis_ziel(...) wird mit diesen Argumenten aufgerufen; der Ausführungskontext wechselt in die Funktion.
  4. Rückgabe: Der berechnete Wert wird an main() zurückgegeben und via print() auf der Standardausgabe ausgegeben.
  • Stufe 2 (Mittel – Fristprüfung mit if):
fn tage_bis_ziel(aktueller_tag: Int, ziel_tag: Int) -> Int {
    if aktueller_tag > ziel_tag {
        return 0
    }
    return ziel_tag - aktueller_tag
}

fn main() {
    print(tage_bis_ziel(10, 25)) # Ausgabe: 15
    print(tage_bis_ziel(30, 25)) # Ausgabe: 0 (Frist bereits abgelaufen)
}
  • Stufe 3 (Anspruchsvoll – Ahead-of-Time-Prüfung vs. Interpreter): Ein reiner Interpreter liest Code Zeile für Zeile. Ein fataler Syntax- oder Typfehler in einer selten genutzten Fehlerbehandlungsroutine (z. B. Zeile 500) wird erst dann bemerkt, wenn ein Benutzer diesen seltenen Zweig betritt. Zelyra prüft mit zelyra check das gesamte Programm vorab vollständig (Namensauflösung, Typkonsistenz, Verträge), wodurch versteckte Fehler bereits vor der Auslieferung eliminiert werden.

Kapitel 3: Zelyra installieren und einrichten

  • Stufe 1 (Leicht – Diagnose aufrufen):
zelyra doctor
zelyra --version

Erklärung: zelyra doctor analysiert die Laufzeitumgebung, prüft die Verfügbarkeit von MariaDB/Docker Compose v2 und zeigt die installierte Release-Version an.

  • Stufe 2 (Mittel – JSON-Diagnosen für Werkzeuge):
zelyra check --help
zelyra check main.zyl --format json

Erklärung: Mit --format json liefert der Compiler maschinenlesbare Fehlermeldungen mit exakten Spalten-, Zeilen- und Fehlercodes, ideal für CI/CD-Pipelines oder IDE-Erweiterungen.

  • Stufe 3 (Anspruchsvoll – Editor-Integration): In Editoren wie Visual Studio Code reicht in der Datei settings.json der Eintrag:
{
  "files.associations": {
    "*.zyl": "zelyra"
  }
}

Zusammen mit der im Downloadbereich verfügbaren Syntaxerweiterung (zelyra.vsix) erhältst du sofort Syntax-Highlighting und Einrückungsregeln.


Kapitel 4: Das erste Zelyra-Projekt

  • Stufe 1 (Leicht – Neues Projekt erzeugen):
zelyra new mein_erstes_projekt
cd mein_erstes_projekt
zelyra run main.zyl
  • Stufe 2 (Mittel – Projekt-Manifest anpassen): In zelyra.toml:
[project]
name = "mein_erstes_projekt"
version = "0.3.0"
authors = ["Stefan Siedelmann"]

In main.zyl:

fn main() {
    app_titel = "Aufgaben-Zentrale"
    app_version = "0.3.0"
    print(app_titel + " v" + app_version + " erfolgreich gestartet.")
}
  • Stufe 3 (Anspruchsvoll – Quelltextformatierung):
zelyra fmt main.zyl
zelyra fmt --check main.zyl

Erklärung: zelyra fmt vereinheitlicht Einrückungen und Leerzeichen deterministisch. --check liefert den Exit-Code 0, wenn der Code bereits perfekt formatiert ist.


Kapitel 5: Werte und Datentypen

  • Stufe 1 (Leicht – Film-Metadaten):
fn main() {
    titel: String = "Inception"
    erscheinungsjahr: Int = 2010
    im_kino_gesehen: Bool = true

    print("Film: " + titel)
}
  • Stufe 2 (Mittel – Exakte Kostenberechnung mit Decimal):
fn berechne_gesamtkosten(stunden: Decimal, stundensatz: Decimal) -> Decimal {
    return stunden * stundensatz
}

fn main() {
    arbeitszeit = decimal("3.500000000")
    satz = decimal("95.000000000")
    gesamt = berechne_gesamtkosten(arbeitszeit, satz)
    print(gesamt)
}
  • Stufe 3 (Anspruchsvoll – Nominale Fachtypen): Wenn sowohl BenutzerId als auch AufgabenId einfache Ganzzahlen (Int) sind, kann eine Funktion zuweisen(user: Int, task: Int) versehentlich als zuweisen(aufgabe_id, benutzer_id) aufgerufen werden. Beide sind Ganzzahlen, der Compiler schweigt, aber die Logik ist zerstört. Mit nominalen Typen wie type UserId = Id und type TaskId = Id erkennt der Compiler die Verwechslung sofort als Typfehler E-TYPE-001.

Kapitel 6: Variablen und Unveränderlichkeit

  • Stufe 1 (Leicht – Unveränderlicher Benutzername):
fn main() {
    benutzername = "senior_coder_42"
    print("Angemeldeter Benutzer: " + benutzername)
}
  • Stufe 2 (Mittel – Punktestand verwalten):
fn main() {
    mutable punktestand = 100
    print("Start-Punktestand: " + punktestand)

    punktestand = punktestand - 15
    print("Nach Strafe (-15):  " + punktestand)

    punktestand = punktestand + 30
    print("Nach Bonus (+30):   " + punktestand)
}
  • Stufe 3 (Anspruchsvoll – Unveränderlichkeit bei Nebenläufigkeit): Wenn mehrere Threads oder Hintergrund-Tasks gleichzeitig denselben Speicherbereich verändern können, entstehen sogenannte Race Conditions (Wettlaufsituationen): Das Ergebnis hängt davon ab, welcher Thread den Wert zuletzt überschreibt. Unveränderliche Werte (immutable) können gefahrlos von beliebig vielen Aufgaben gleichzeitig gelesen werden, ohne dass sperrige Sperren (Locks/Mutexes) nötig sind.

Kapitel 7: Operatoren und Ausdrücke

  • Stufe 1 (Leicht – Gerade-Zahl-Prüfung):
fn main() {
    ist_gerade = (10 % 2 == 0)
    print(ist_gerade) # Ausgabe: true
}
  • Stufe 2 (Mittel – Altersbereich prüfen):
fn im_arbeitsfaehigen_alter(alter: Int) -> Bool {
    return alter >= 18 && alter <= 65
}

fn main() {
    print(im_arbeitsfaehigen_alter(25)) # true
    print(im_arbeitsfaehigen_alter(16)) # false
}
  • Stufe 3 (Anspruchsvoll – Rabatt-Logik):
fn qualifiziert_fuer_rabatt(is_vip: Bool, bestellwert: Decimal, ist_neukunde: Bool) -> Bool {
    return is_vip || (bestellwert > decimal("100.000000000") && !ist_neukunde)
}

fn main() {
    print(qualifiziert_fuer_rabatt(false, decimal("150.000000000"), false)) # true
    print(qualifiziert_fuer_rabatt(false, decimal("150.000000000"), true))  # false (Neukunde)
    print(qualifiziert_fuer_rabatt(true, decimal("20.000000000"), true))   # true (VIP sticht immer)
}

Kapitel 8: Ein- und Ausgaben

  • Stufe 1 (Leicht – Formatierte Visitenkarte):
fn main() {
    print("================================")
    print(" Name:  Dr. Stefanie Code")
    print(" Beruf: Lead Software Architect")
    print(" Mail:  stefanie@example.com")
    print("================================")
}
  • Stufe 2 (Mittel – Aufgaben-Formatierung):
fn drucke_aufgabe(id: Int, name: String, erledigt: Bool) {
    status_label = if erledigt { "[X] ERLEDIGT" } else { "[ ] OFFEN" }
    print("----------------------------------------")
    print("Aufgabe #" + id + ": " + name)
    print("Status:      " + status_label)
    print("----------------------------------------")
}

fn main() {
    drucke_aufgabe(42, "Refactoring der Rechnungsmodule", false)
}
  • Stufe 3 (Anspruchsvoll – Interaktives Terminal vs. Web-Requests): read_console() erfordert die Capability uses Console und blockiert den ausführenden Thread, bis der Benutzer Enter drückt. Das eignet sich hervorragend für CLI-Tools, Setup-Assistenten und Wartungsskripte. In einer Webanwendung wäre eine blockierende Konsoleneingabe katastrophal: Hier müssen Benutzereingaben als strukturierte HTTP-POST-Requests über Formulare oder JSON-Payloads asynchron verarbeitet werden.

Kapitel 9: Entscheidungen mit Bedingungen

  • Stufe 1 (Leicht – Volljährigkeitsprüfung):
fn ist_volljaehrig(alter: Int) -> Bool {
    return alter >= 18
}

fn main() {
    print(ist_volljaehrig(17)) # false
    print(ist_volljaehrig(18)) # true
}
  • Stufe 2 (Mittel – Monatstage mit match):
fn monats_tage(monat: Int) -> Int {
    return match monat {
        1 => 31,
        2 => 28,
        3 => 31,
        4 => 30,
        5 => 31,
        6 => 30,
        7 => 31,
        8 => 31,
        9 => 30,
        10 => 31,
        11 => 30,
        12 => 31,
        _ => 0,
    }
}

fn main() {
    print(monats_tage(2))  # 28
    print(monats_tage(10)) # 31
    print(monats_tage(99)) # 0 (Ungültiger Monat)
}
  • Stufe 3 (Anspruchsvoll – Passwort-Prüfung):
fn pruefe_passwort_einfach(pw: String) -> Bool {
    if pw == "12345678" || pw == "passwort" || pw == "admin123" {
        return false
    }
    return len(pw) >= 8
}

fn main() {
    print(pruefe_passwort_einfach("12345678"))       # false
    print(pruefe_passwort_einfach("kurz"))           # false
    print(pruefe_passwort_einfach("meinSicheres#26")) # true
}

Kapitel 10: Wiederholungen und Schleifen

  • Stufe 1 (Leicht – Countdown):
fn main() {
    mutable count = 10
    while count >= 1 {
        print(count)
        count = count - 1
    }
    print("Rakete gestartet!")
}
  • Stufe 2 (Mittel – Array summieren):
fn main() {
    zahlen: Int[] = [10, 20, 30, 40]
    mutable summe = 0
    for z in zahlen {
        summe = summe + z
    }
    print("Gesamtsumme: " + summe) # 100
}
  • Stufe 3 (Anspruchsvoll – Suche mit break):
fn main() {
    liste: Int[] = [3, 9, 14, 42, 55, 88]
    mutable gefunden = false

    for element in liste {
        if element == 42 {
            gefunden = true
            break
        }
    }

    if gefunden {
        print("Gefunden! Die Antwort auf alle Fragen ist im Array.")
    } else {
        print("Nicht gefunden")
    }
}

Kapitel 11: Funktionen und Prozeduren

  • Stufe 1 (Leicht – Verdopplung):
fn verdopple(zahl: Int) -> Int {
    return zahl * 2
}

fn main() {
    print(verdopple(21)) # 42
}
  • Stufe 2 (Mittel – Dringlichkeitsbewertung):
fn ist_dringend(verbleibende_tage: Int) -> Bool {
    return verbleibende_tage < 3
}

fn main() {
    print(ist_dringend(1)) # true
    print(ist_dringend(5)) # false
}
  • Stufe 3 (Anspruchsvoll – Statussymbol):
fn status_symbol(erledigt: Bool) -> String {
    if erledigt {
        return "[OK]"
    }
    return "[OFFEN]"
}

fn main() {
    print(status_symbol(true))  # [OK]
    print(status_symbol(false)) # [OFFEN]
}

Kapitel 12: Verträge und Vorbedingungen (Design by Contract)

  • Stufe 1 (Leicht – Quadratwurzel mit Vorbedingung):
fn quadratwurzel_naiv(x: Int) -> Int
    requires { x >= 0 }
{
    mutable r = 0
    while (r + 1) * (r + 1) <= x {
        r = r + 1
    }
    return r
}

fn main() {
    print(quadratwurzel_naiv(49)) # 7
}
  • Stufe 2 (Mittel – Wertbereichsbegrenzung):
fn begrenze(wert: Int, min_wert: Int, max_wert: Int) -> Int
    requires { min_wert <= max_wert }
    ensures { result >= min_wert && result <= max_wert }
{
    if wert < min_wert { return min_wert }
    if wert > max_wert { return max_wert }
    return wert
}

fn main() {
    print(begrenze(125, 0, 100)) # 100
    print(begrenze(-10, 0, 100)) # 0
    print(begrenze(42, 0, 100))  # 42
}
  • Stufe 3 (Anspruchsvoll – Prozentsatz ohne Division durch 0):
fn prozentsatz(teil: Int, gesamt: Int) -> Int
    requires { gesamt > 0 && teil >= 0 && teil <= gesamt }
    ensures { result >= 0 && result <= 100 }
{
    return (teil * 100) / gesamt
}

fn main() {
    print(prozentsatz(15, 60)) # 25
}

Kapitel 13: Sammlungen, Listen und Wörterbücher (Arrays & Maps)

  • Stufe 1 (Leicht – Text-Array iterieren):
fn main() {
    themen: String[] = ["Compiler", "Datenbanken", "Web-Sicherheit"]
    for t in themen {
        print("- " + t)
    }
}
  • Stufe 2 (Mittel – Preise nachschlagen mit Map und Option):
fn main() {
    mutable preise: Map<String, Int> = map()
    preise = set(preise, "Espresso", 2)
    preise = set(preise, "Cappuccino", 4)

    match get(preise, "Espresso") {
        Some(preis) => print("Espresso kostet " + preis + " Euro"),
        None => print("Artikel nicht gefunden"),
    }

    match get(preise, "Kuchen") {
        Some(preis) => print("Kuchen kostet " + preis + " Euro"),
        None => print("Kuchen ist leider ausverkauft"),
    }
}
  • Stufe 3 (Anspruchsvoll – Wörter zählen mit Map):
fn zaehle_woerter(liste: String[]) -> Map<String, Int> {
    mutable zaehler: Map<String, Int> = map()
    for wort in liste {
        aktuell = match get(zaehler, wort) {
            Some(anzahl) => anzahl + 1,
            None => 1,
        }
        zaehler = set(zaehler, wort, aktuell)
    }
    return zaehler
}

fn main() {
    begriffe: String[] = ["zelyra", "rust", "zelyra", "web"]
    ergebnis = zaehle_woerter(begriffe)
    print("Erledigt.")
}

Kapitel 14: Eigene Datentypen erstellen (Records & Tables)

  • Stufe 1 (Leicht – Nominaler Typalias):
type UserId = Id

fn main() {
    uid: UserId = 1
    print("Benutzer-ID angelegt: " + uid)
}
  • Stufe 2 (Mittel – Kategorie-Tabelle):
table kategorien {
    id: Id primary auto
    kategorie_name: String required
    beschreibung: String
}
  • Stufe 3 (Anspruchsvoll – Relationale Notizen-Tabelle):
table notizen {
    id: Id primary auto
    aufgabe_id: Id required
    notiz_text: String required
    erstellt_am: Timestamp auto
}

Kapitel 15: Module und Code-Organisation

  • Stufe 1 (Leicht – Projekt-Inspektion):
zelyra new task_manager
cd task_manager
ls -la

Erklärung: Das Projekt enthält zelyra.toml (Konfiguration), main.zyl (Einstiegspunkt) und Verzeichnisse für modulare Quelldateien.

  • Stufe 2 (Mittel – Projekt-Manifest erweitern):
[project]
name = "task_manager"
version = "0.3.0"
description = "Zuverlässige Aufgabenverwaltung mit Zelyra"
authors = ["Stefan Siedelmann"]
  • Stufe 3 (Anspruchsvoll – Modulare Trennung in Funktionen):
fn initialisiere_app() -> String {
    return "TaskManager bereit"
}

fn verarbeite_aufgabe(titel: String) -> String {
    return "Verarbeite: " + titel
}

fn zeige_status(nachricht: String) {
    print("[STATUS] " + nachricht)
}

fn main() {
    zeige_status(initialisiere_app())
    zeige_status(verarbeite_aufgabe("Release vorbereiten"))
}

Kapitel 16: Fehlerarten und ihre Ursachen

  • Stufe 1 (Leicht – Syntaxfehler diagnostizieren): Entfernst du in einer Datei die schließende Klammer }, bricht zelyra check mit einem Fehler über unbalancierte Blöcke ab. Der Compiler nennt die exakte Zeile, an der der Block geöffnet wurde.

  • Stufe 2 (Mittel – Typfehler beheben):

fn main() {
    # Falsch: anzahl: Int = "zehn"  -> E-TYPE-001
    # Korrekt:
    anzahl: Int = 10
    print(anzahl)
}
  • Stufe 3 (Anspruchsvoll – Altersprüfung mit Verträgen):
fn pruefe_kinofreigabe(alter: Int) -> Bool
    requires { alter >= 0 && alter <= 130 }
{
    return alter >= 16
}

fn main() {
    print(pruefe_kinofreigabe(17)) # true
}

Kapitel 17: Fehler als Werte – Das Result-Muster

  • Stufe 1 (Leicht – Gerade-Zahl-Result):
fn pruefe_gerade(zahl: Int) -> Result<Int, String> {
    if zahl % 2 == 0 {
        return Ok(zahl)
    }
    return Err("Die Zahl ist ungerade!")
}

fn main() {
    match pruefe_gerade(10) {
        Ok(z) => print("Erfolg: " + z + " ist gerade"),
        Err(msg) => print("Fehler: " + msg),
    }
}
  • Stufe 2 (Mittel – Benutzernamen validieren):
fn validiere_benutzername(name: String) -> Result<String, String> {
    if name == "" {
        return Err("Name darf nicht leer sein.")
    }
    if name == "admin" || name == "root" {
        return Err("Name ist reserviert.")
    }
    return Ok(name)
}

fn main() {
    match validiere_benutzername("stefan") {
        Ok(n) => print("Willkommen, " + n),
        Err(e) => print("Ungueltiger Benutzername: " + e),
    }
}
  • Stufe 3 (Anspruchsvoll – Division mit Result und Verdopplung):
fn sichere_division(zaehler: Int, nenner: Int) -> Result<Int, String> {
    if nenner == 0 {
        return Err("Division durch null ist verboten!")
    }
    return Ok(zaehler / nenner)
}

fn main() {
    match sichere_division(100, 5) {
        Ok(quotient) => {
            verdoppelt = quotient * 2
            print("Ergebnis verdoppelt: " + verdoppelt)
        },
        Err(e) => print("Berechnungsfehler: " + e),
    }
}

Kapitel 18: Das Nichts existiert nicht – Der sichere Umgang mit Option

  • Stufe 1 (Leicht – Partner-Suchfunktion):
fn finde_partner(name: String) -> Option<String> {
    if name == "Romeo" {
        return Some("Julia")
    }
    if name == "Bonnie" {
        return Some("Clyde")
    }
    return None
}

fn main() {
    match finde_partner("Romeo") {
        Some(partner) => print("Gefunden: " + partner),
        None => print("Kein Partner hinterlegt"),
    }
}
  • Stufe 2 (Mittel – Letztes Element auswerten):
fn main() {
    zahlen: Int[] = [10, 20, 30]
    match last(zahlen) {
        Some(wert) => print("Letztes Element: " + wert),
        None => print("Achtung: Liste war leer!"),
    }
}
  • Stufe 3 (Anspruchsvoll – Position in einer Liste suchen):
fn finde_index(liste: Int[], ziel: Int) -> Option<Int> {
    mutable idx = 0
    for element in liste {
        if element == ziel {
            return Some(idx)
        }
        idx = idx + 1
    }
    return None
}

fn main() {
    daten: Int[] = [100, 200, 300, 400]
    match finde_index(daten, 300) {
        Some(pos) => print("Gefunden an Index: " + pos),
        None => print("Element nicht in Liste"),
    }
}

Kapitel 19: Tests und Qualitätssicherung

  • Stufe 1 (Leicht – Addition testen):
fn addiere(a: Int, b: Int) -> Int {
    return a + b
}

fn main() {
    assert(addiere(2, 3) == 5)
    assert(addiere(-2, 2) == 0)
    assert(addiere(0, 0) == 0)
    print("Alle Tests fuer addiere() bestanden!")
}
  • Stufe 2 (Mittel – Option-Tests):
fn finde_index(liste: Int[], ziel: Int) -> Option<Int> {
    mutable idx = 0
    for element in liste {
        if element == ziel {
            return Some(idx)
        }
        idx = idx + 1
    }
    return None
}

fn main() {
    test_liste: Int[] = [10, 20, 30]
    match finde_index(test_liste, 20) {
        Some(i) => assert(i == 1),
        None => assert(false),
    }
    match finde_index(test_liste, 99) {
        Some(_) => assert(false),
        None => assert(true),
    }
    print("Suchtests erfolgreich!")
}
  • Stufe 3 (Anspruchsvoll – Validierungs-Testsuite):
fn validiere_titel(t: String) -> Bool {
    return len(t) >= 3 && len(t) <= 50
}

fn main() {
    assert(validiere_titel("OK") == false)
    assert(validiere_titel("Gueltiger Titel") == true)
    assert(validiere_titel("Ein viel zu langer Titel, der mehr als fuenfzig Zeichen umfasst und daher abgewiesen werden muss") == false)
    print("Validierungs-Suite erfolgreich ausgefuehrt.")
}

Kapitel 20: Arbeiten mit Dateien

  • Stufe 1 (Leicht – Datei schreiben):
fn main() uses FileSystem {
    ergebnis = write_text("hallo.txt", "Hallo aus der sicheren Zelyra-Welt!")
    match ergebnis {
        Ok(_) => print("Datei erfolgreich geschrieben."),
        Err(e) => print("Schreibfehler: " + e),
    }
}
  • Stufe 2 (Mittel – Datei lesen):
fn main() uses FileSystem {
    match read_text("hallo.txt") {
        Ok(inhalt) => print("Gelesener Inhalt: " + inhalt),
        Err(e) => print("Konnte Datei nicht lesen: " + e),
    }
}
  • Stufe 3 (Anspruchsvoll – Zeilenweises Logging):
fn logge_ereignis(pfad: String, meldung: String) uses FileSystem {
    bisher = match read_text(pfad) {
        Ok(alt) => alt + "
",
        Err(_) => "",
    }
    neu = bisher + "[LOG] " + meldung
    write_text(pfad, neu)
}

fn main() uses FileSystem {
    logge_ereignis("audit.log", "System gestartet.")
    logge_ereignis("audit.log", "Benutzer angemeldet.")
    print("Log aktualisiert.")
}

Kapitel 21: Datum, Uhrzeit, Zufall und strukturierte Daten

  • Stufe 1 (Leicht – Würfeln):
fn wuerfeln() -> Int uses Random {
    return random_int(1, 6)
}

fn main() uses Random {
    augen = wuerfeln()
    print("Gewuerfelt: " + augen)
}
  • Stufe 2 (Mittel – JSON-Codierung):
fn main() {
    codes: Int[] = [200, 201, 404, 500]
    json_text = json_encode(codes)
    print("JSON: " + json_text)
}
  • Stufe 3 (Anspruchsvoll – Zeitstempel-Logging):
fn main() uses Clock, FileSystem {
    aktueller_zeitpunkt = now()
    logzeile = "Ereignis eingetreten um: " + aktueller_zeitpunkt
    write_text("zeitstempel.log", logzeile)
    print("Zeitstempel geloggt.")
}

Kapitel 22: Nebenläufigkeit und Hintergrundaufgaben

  • Stufe 1 (Leicht – Parallele Ausführung):
fn berechne_teil_a() -> Int { return 20 + 1 }
fn berechne_teil_b() -> Int { return 20 + 1 }

fn main() {
    parallel {
        task_a = berechne_teil_a()
        task_b = berechne_teil_b()
    }
    summe = await(task_a) + await(task_b)
    print("Paralleles Gesamtergebnis: " + summe) # 42
}
  • Stufe 2 (Mittel – Zwei Datenquellen parallel abfragen):
fn lade_kunden_anzahl() -> Int { return 150 }
fn lade_offene_rechnungen() -> Int { return 12 }

fn main() {
    parallel {
        ka = lade_kunden_anzahl()
        re = lade_offene_rechnungen()
    }
    print("Kunden: " + await(ka) + ", Offene Rechnungen: " + await(re))
}
  • Stufe 3 (Anspruchsvoll – Robuste Fehlerbehandlung bei Tasks): In parallelen Blöcken liefert jeder Task ein typisiertes Ergebnis oder ein Result. Schlägt einer der Zweige fehl, fängt das Hauptprogramm den Fehler über match await(task) sauber ab, ohne dass die gesamte Anwendung abstürzt.

Kapitel 23: Warum Zelyra die Datenbank direkt versteht

  • Stufe 1 (Leicht – Datenbank-Deklaration):
database MariaDB {
    url: env("DATABASE_URL")
}
  • Stufe 2 (Mittel – Benutzer-Tabelle und SQL-Abfrage):
table benutzer {
    id: Id primary auto
    email: String required
    aktiv: Bool required
}

fn lade_aktive_benutzer() -> benutzer[] uses Database {
    return sql<benutzer[]> {
        SELECT * FROM benutzer WHERE aktiv = true
    }
}
  • Stufe 3 (Anspruchsvoll – Spalten-Prüfung zur Compile-Zeit): Wenn du in der SQL-Abfrage SELECT ungueltige_spalte FROM benutzer schreibst, verweigert zelyra check sofort die Übersetzung mit dem Fehler E-DB-002: Unknown column 'ungueltige_spalte' on table 'benutzer'. Zelyra schützt dich vor Runtime-Datenbankfehlern!

Kapitel 24: Tabellen definieren und Daten modellieren

  • Stufe 1 (Leicht – Etiketten-Tabelle):
table etiketten {
    id: Id primary auto
    name: String(30) required
}
  • Stufe 2 (Mittel – Kunden-Tabelle):
table kunden {
    id: Id primary auto
    vorname: String(50) required
    nachname: String(50) required
    email: Email required
    telefon: String(30)
}
  • Stufe 3 (Anspruchsvoll – Relationale Kommentare):
table kommentare {
    id: Id primary auto
    aufgabe_id: Id required fk tasks(id) on_delete restrict
    kommentar: String required
    erstellt_am: Timestamp auto
}

Kapitel 25: Daten abfragen und verändern

  • Stufe 1 (Leicht – Offene Aufgaben abfragen):
fn lade_offene_aufgaben() -> tasks[] uses Database {
    return sql<tasks[]> {
        SELECT * FROM tasks WHERE erledigt = false ORDER BY id ASC
    }
}
  • Stufe 2 (Mittel – Aufgabe löschen):
fn loesche_aufgabe(aufgabe_id: Id) uses Database {
    sql {
        DELETE FROM tasks WHERE id = :aufgabe_id
    }
}
  • Stufe 3 (Anspruchsvoll – Transaktion mit Archivierung):
fn archiviere_und_neue_aufgabe(alte_id: Id, neuer_titel: String) uses Database {
    transaction {
        sql {
            UPDATE tasks SET archiviert = true WHERE id = :alte_id
        }
        sql {
            INSERT INTO tasks (titel, erledigt, archiviert)
            VALUES (:neuer_titel, false, false)
        }
    }
}

Kapitel 26: Webseiten ausgeben

  • Stufe 1 (Leicht – Über-Uns-Seite):
page "/ueber-uns" {
    html {
        <main>
            <h1>Ueber unser Unternehmen</h1>
            <p>Wir bauen langlebige Software mit Zelyra.</p>
        </main>
    }
}
  • Stufe 2 (Mittel – Dynamische Detailseite):
page "/produkt/{nummer: Int}" {
    html {
        <div>
            <h2>Produkt-Detailansicht</h2>
            <p>Angezeigte Artikelnummer: {nummer}</p>
        </div>
    }
}
  • Stufe 3 (Anspruchsvoll – Übersichtsseite mit Navigation):
page "/uebersicht" {
    html {
        <nav>
            <a href="/">Startseite</a> | <a href="/ueber-uns">Ueber uns</a>
        </nav>
        <section>
            <h2>Unsere Kernbereiche</h2>
            <ul>
                <li>Sichere Web-Entwicklung</li>
                <li>Transaktionale Datenbanken</li>
                <li>KI-gestuetzte Codeanalyse</li>
            </ul>
        </section>
    }
}

Kapitel 27: Formulare und Benutzereingaben

  • Stufe 1 (Leicht – Kategorie-Formular):
form KategorieForm {
    name: String required
    beschreibung: String
}
  • Stufe 2 (Mittel – Formularvalidierung auf CLI):
zelyra form validate main.zyl

Erklärung: Zelyra prüft die Formularfelder gegen das zugrundeliegende Tabellenschema.

  • Stufe 3 (Anspruchsvoll – Prioritäts-Validierung im Formular):
form AufgabeForm {
    titel: String required min_length(3)
    prioritaet: Int required min(1) max(3)
}

Kapitel 28: Das vollständige CRUD-Muster

  • Stufe 1 (Leicht – CRUD deklarieren):
crud Kategorie -> kategorien {
    title: "Kategorien verwalten"
    fields: [kategorie_name, beschreibung]
}
  • Stufe 2 (Mittel – Routen prüfen):
zelyra routes main.zyl

Erklärung: Die Ausgabe zeigt alle automatisch generierten Routen wie GET /kategorien, GET /kategorien/new, POST /kategorien/new, GET /kategorien/{id} und POST /kategorien/{id}/delete.

  • Stufe 3 (Anspruchsvoll – Benutzerdefinierte Duplizier-Aktion):
action "duplizieren" on tasks (id: Id) uses Database {
    sql {
        INSERT INTO tasks (titel, erledigt)
        SELECT CONCAT(titel, ' (Kopie)'), false FROM tasks WHERE id = :id
    }
}

Kapitel 29: Benutzer, Passwörter und Sitzungen

  • Stufe 1 (Leicht – Passwort hashen):
zelyra auth hash-password "MeinSuperSicheresPasswort2026!"
  • Stufe 2 (Mittel – Route mit Authentifizierung schützen):
page "/einstellungen" requires auth {
    html {
        <h1>Persoenliche Benutzereinstellungen</h1>
    }
}
  • Stufe 3 (Anspruchsvoll – Rollenbasierte Rechtevergabe):
page "/admin/system" requires auth permits admin {
    html {
        <h1>System-Administration (Nur fuer Admins)</h1>
    }
}

Kapitel 30: APIs und Datenaustausch

  • Stufe 1 (Leicht – Versions-Endpunkt):
page "/api/version" {
    json {
        "system": "Zelyra TaskEngine",
        "version": "1.0.5",
        "status": "operational"
    }
}
  • Stufe 2 (Mittel – REST-Delete mit Fehlerbehandlung):
page "DELETE /api/tasks/{id: Id}" uses Database {
    betroffene = sql {
        DELETE FROM tasks WHERE id = :id
    }
    if betroffene == 0 {
        return http_response(404, "Aufgabe nicht gefunden")
    }
    return http_response(200, "Aufgabe geloescht")
}
  • Stufe 3 (Anspruchsvoll – OpenAPI-Spezifikation generieren):
zelyra doc --openapi main.zyl > openapi.json

Erklärung: Erzeugt eine standardkonforme OpenAPI-3.0-Spezifikation aus deinen deklarierten Routen, Tabellen und Typen.


Kapitel 31: Lesbarkeit als oberstes Gebot

  • Stufe 1 (Leicht – Automatisch formatieren):
zelyra fmt src/main.zyl
  • Stufe 2 (Mittel – Refactoring sprechender Namen):
# Schlecht: fn f(a: Int, b: Float) -> Float { return a * b * 0.19 }
# Vorbildlich:
fn berechne_mehrwertsteuer(netto_betrag: Decimal, steuersatz: Decimal) -> Decimal {
    return netto_betrag * steuersatz
}
  • Stufe 3 (Anspruchsvoll – Klare Funktionsgliederung): Zerlege lange Berechnungs-Monster in kleine Funktionen mit jeweils genau einer klaren Aufgabe (Single Responsibility Principle). Jede Funktion sollte ohne horizontales Scrollen auf einen Blick verständlich sein.

Kapitel 32: KI-Nativität – Warum Zelyra perfekt für KI-Assistenten ist

  • Stufe 1 (Leicht – JSON-Diagnosen):
zelyra check --format json main.zyl
  • Stufe 2 (Mittel – Auswirkungsanalyse mit impact):
zelyra impact main.zyl

Erklärung: Analysiert Abhängigkeiten und zeigt, welche Funktionen oder Tabellen von einer Änderung betroffen sind.

  • Stufe 3 (Anspruchsvoll – Typed Holes):
fn berechne_rabatt(preis: Decimal) -> Decimal {
    return _
}

Führe zelyra check aus. Der Compiler antwortet mit einem präzisen Loch-Report: Er nennt dir den erwarteten Rückgabetyp Decimal und alle im Gültigkeitsbereich verfügbaren Bezeichner – die perfekte Vorlage für einen KI-Assistenten!


Kapitel 33: Sicherheit durch Fähigkeiten (Capabilities)

  • Stufe 1 (Leicht – Capabilities identifizieren): Funktionen ohne uses ... sind garantiert reine Funktionen (pure) ohne Netzwerk-, Dateisystem- oder Datenbank-Seiteneffekte.

  • Stufe 2 (Mittel – Mehrere Capabilities kombinieren):

fn speichere_backup_protokoll(meldung: String) uses Clock, Environment, FileSystem {
    zeit = now()
    pfad = match env("BACKUP_LOG_PATH") {
        Some(p) => p,
        None => "backup.log",
    }
    write_text(pfad, "[" + zeit + "] " + meldung)
}
  • Stufe 3 (Anspruchsvoll – Trennung von Logik und I/O): Halte alle Berechnungen (z. B. Rechnungsbeträge, Rabatte, Datumsabstände) in reinen Funktionen völlig ohne Capabilities. Deklariere Capabilities ausschließlich an den Rändern des Systems (beim Laden aus der DB oder Schreiben ins Dateisystem).

Kapitel 34: Zelyra im Vergleich

  • Stufe 1 (Leicht – Von Python zu Zelyra): Python:
def verdopple(x): return x * 2

Zelyra:

fn verdopple(x: Int) -> Int {
    return x * 2
}
  • Stufe 2 (Mittel – Eloquent ORM vs. Zelyra SQL): In Laravel/Eloquent führt $orders->each(fn($o) => $o->customer->name) oft unbemerkt zu 100 separaten Datenbank-Queries (N+1 Problem). In Zelyra schreibst du eine typgeprüfte sql<T[]>-Abfrage mit explizitem JOIN, die in einem einzigen effizienten Datenbank-Roundtrip ausgeführt wird.

  • Stufe 3 (Anspruchsvoll – Supply-Chain-Sicherheit): Wenn ein externes Paket kompromittiert wird, kann es in Node.js oder Python ungehindert .env-Dateien lesen oder Passwörter ins Internet senden. In Zelyra kann ein Modul ohne uses Network oder uses FileSystem technisch unmöglich Daten nach außen senden – der Compiler verweigert den Zugriff.


Kapitel 35: Software planen – Von der Idee zum Entwurf

  • Stufe 1 (Leicht – Domänen-Skizze Notiz-App):

  • Entitäten: Benutzer, Notiz, Ordner.

  • Aktionen: Anlegen, Durchsuchen, Aktualisieren, Löschen.

  • Stufe 2 (Mittel – Tabellenschema entwerfen):

table notizen_app {
    id: Id primary auto
    benutzer_id: Id required
    titel: String(100) required
    inhalt: String required
    erstellt_am: Timestamp auto
}
  • Stufe 3 (Anspruchsvoll – Verträge für Kernfunktionen):
fn erstelle_notiz(titel: String, text: String) -> Bool
    requires { len(titel) >= 1 && len(titel) <= 100 }
    requires { len(text) > 0 }
    ensures { result == true }
{
    return true
}

Kapitel 36: Architektur und saubere Codestruktur

  • Stufe 1 (Leicht – Die drei Schichten):
  1. Präsentation: page, html, form
  2. Geschäftslogik: Reine Funktionen (fn berechne...) mit Verträgen
  3. Persistenz: table, sql, transaction
  • Stufe 2 (Mittel – Entkopplung von Route und Logik): Schreibe keine Berechnungen direkt in den html-Block der Route, sondern delegiere sie an eine typisierte Hilfsfunktion berechne_brutto(netto, steuer).

  • Stufe 3 (Anspruchsvoll – Schichtenmodell für Zeiterfassung):

  • UI: /timetracking/entry

  • Service: berechne_dauer(start: Time, ende: Time) -> Duration (pure!)

  • Storage: speichere_zeiteintrag(eintrag: Zeiteintrag) uses Database


Kapitel 37: Konfiguration und Umgebungsvariablen

  • Stufe 1 (Leicht – Benutzername auslesen):
fn main() uses Environment {
    match env("USER_NAME") {
        Some(name) => print("Hallo, " + name + "!"),
        None => print("Hallo, lieber Gast!"),
    }
}
  • Stufe 2 (Mittel – Hilfsfunktion mit Fallback):
fn hole_env_oder_standard(schluessel: String, standard: String) -> String uses Environment {
    return match env(schluessel) {
        Some(wert) => wert,
        None => standard,
    }
}

fn main() uses Environment {
    port = hole_env_oder_standard("APP_PORT", "8080")
    print("Server lauscht auf Port: " + port)
}
  • Stufe 3 (Anspruchsvoll – Dev- vs. Prod-Umschaltung):
fn ist_produktionsmodus() -> Bool uses Environment {
    return match env("APP_ENV") {
        Some(modus) => modus == "production",
        None => false,
    }
}

fn main() uses Environment {
    if ist_produktionsmodus() {
        print("Produktionsmodus aktiv: Strenger Sicherheitsmodus.")
    } else {
        print("Entwicklungsmodus aktiv: Ausfuehrliche Fehlermeldungen.")
    }
}

Kapitel 38: Fehlersuche und Optimierung

  • Stufe 1 (Leicht – Umgebungs-Check):
zelyra doctor
  • Stufe 2 (Mittel – Fehlerisolation in Testfunktionen): Wenn eine Berechnung ein unerwartetes Ergebnis liefert, isoliere die Formel in einer separaten kleinen Datei test_rechnung.zyl und teste sie isoliert mit festen Werten und assert(), ohne Datenbank und Webserver starten zu müssen.

  • Stufe 3 (Anspruchsvoll – Schrittweises Audit-Logging): Protokolliere wichtige Zwischenzustände vor und nach kritischen Operationen strukturiert mit Zeitstempel und Statuscodes in eine separate Audit-Tabelle.


Kapitel 39: Bereitstellung und Betrieb

  • Stufe 1 (Leicht – Webserver starten):
zelyra serve main.zyl 127.0.0.1:3000
  • Stufe 2 (Mittel – Compose-Konfiguration):
services:
  mariadb:
    image: mariadb:11.4
    environment:
      MARIADB_ROOT_PASSWORD: secret
      MARIADB_DATABASE: app_db
    ports:
      - "3306:3306"
  • Stufe 3 (Anspruchsvoll – Schema-Migration ohne Datenverlust):
zelyra db plan main.zyl
# Prüfe die SQL-Änderungen sorgfältig!
zelyra db apply main.zyl

Kapitel 40: Das große Abschlussprojekt: Vollständige Aufgabenverwaltung

  • Stufe 1 (Leicht – Projekt-Verifikation):
zelyra check main.zyl

Der Compiler bestätigt 0 Fehler und 0 Warnungen.

  • Stufe 2 (Mittel – Fälligkeitsdatum ergänzen): In der Tabelle tasks:
table tasks {
    id: Id primary auto
    titel: String(100) required
    erledigt: Bool required
    faellig_am: Date
}

Passe das Formular und die HTML-Ansicht an, um das Datumsfeld darzustellen.

  • Stufe 3 (Anspruchsvoll – Lokales Live-Deployment): Starte MariaDB mit Compose, wende das Schema mit zelyra db apply main.zyl an, starte den Webserver mit zelyra serve und erstelle deine ersten echten Aufgaben im Browser!

Kapitel 41: Funktionsumfang und sicherer Betrieb

  • Stufe 1 (Leicht – Versions- und Umgebungsdiagnose):
zelyra --version
zelyra doctor

Erklärung: Bestätigt, dass Version 1.0.5 aktiv ist, die Sprachkompatibilität auf 0.1 steht und das Docker Compose v2 Plugin bereitsteht.

  • Stufe 2 (Mittel – Tutorial-Trockenprüfung):
zelyra --tutorial invoice --no-start
cd invoice-tutorial
zelyra db plan main.zyl

Erklärung: db plan analysiert das deklarierte Rechnungsschema (Kunden, Artikel, Rechnungen, Positionen) und gibt die geplanten SQL-Statements aus, ohne schreibend in die Datenbank einzugreifen.

  • Stufe 3 (Anspruchsvoll – Risikofreies Migrations- und Backup-Verfahren):
  1. Test-Datenbank über einen alternativen Port starten (z. B. 3307 in .env).
  2. zelyra db plan main.zyl prüfen.
  3. Schema mit zelyra db apply main.zyl auf der Testdatenbank anwenden.
  4. Backup-Erstellung mit zelyra db backup testen.
  5. Erst nach erfolgreicher Test-Verifikation wird das Update für das Produktivsystem freigegeben.

Kapitel 42: Dein Weg als Zelyra-Entwickler

  • Stufe 1 (Leicht – Model-First-Entwurf): Für ein persönliches Haushaltsbuch entwirfst du zuerst das relationale Tabellenschema:
table buchungen {
    id: Id primary auto
    datum: Date required
    kategorie: String(50) required
    betrag: Decimal required
    beschreibung: String
}
  • Stufe 2 (Mittel – Vertragliche Absicherung):
fn buchung_gueltig(betrag: Decimal) -> Bool
    ensures { result == (betrag != decimal("0.000000000")) }
{
    return betrag != decimal("0.000000000")
}

Erklärung: Buchungen mit einem Betrag von 0,00 € werden durch die Nachbedingung logisch ausgeschlossen.

  • Stufe 3 (Anspruchsvoll – Klare Modultrennung): Strukturiere das Projekt in:
  • src/model.zyl: Tabellen und Record-Definitionen.
  • src/finance.zyl: Reine Berechnungsfunktionen (Monatssummen, Steuerquoten) ohne Capabilities.
  • src/storage.zyl: Persistenzfunktionen mit uses Database.
  • src/web.zyl: HTML-Seiten und CRUD-Definitionen.

Kapitel 43: Mit Zelyra Studio arbeiten

  • Stufe 1 (Leicht – Studio starten):
cd mein_projekt
zelyra editor .

Erklärung: Studio startet einen lokalen Webserver auf 127.0.0.1 mit sicherem Einmal-Token und öffnet den Editor im Standardbrowser.

  • Stufe 2 (Mittel – Schemakarte inspizieren): Klicke in der Seitenleiste von Studio auf das Datenbanksymbol (Datenbank). Die Schemakarte liest die Tabellen- und Fremdschlüssel-Metadaten schreibgeschützt ein und visualisiert die Beziehungen, ohne Datensätze zu manipulieren.

  • Stufe 3 (Anspruchsvoll – CRUD-Vorschau und Routenprüfung):

  1. Wähle im Studio eine deklarierte Tabelle ohne bestehende CRUD-Ansicht aus.
  2. Starte den CRUD-Assistenten und wähle die gewünschten Formularfelder.
  3. Betrachte die vorgeschlagene Quelltext-Differenz (Diff) in der Vorschau.
  4. Vergleiche die generierten Endpunkte mit zelyra routes main.zyl im Terminal.
  5. Übernimm die Änderung erst nach Prüfung im Editor.

Anhang I: Häufige Fragen und Antworten (FAQ)

Frage: Wie verwende ich Imports? Antwort: Mit import "src/models.zyl" as models bindest du eine projektlokale Quelldatei ein. Unterstützte öffentliche Deklarationen werden über den Modulnamen angesprochen. Eine Paketregistrierung, dynamische Imports und ein vollständiger Deployment-Abschluss sind nicht enthalten.

Frage: Kann ich mit Zelyra auch reine Konsolenprogramme schreiben? Antwort: Ja. print() gibt Werte aus. read_console("Prompt: ") liest eine Zeile und liefert String?; dafür braucht die Funktion uses Console und das Projekt gegebenenfalls console = true.

Frage: Warum unterstützt Zelyra MariaDB als bevorzugte Engine? Antwort: MariaDB bietet herausragende Performance, Open-Source-Freiheit, Stabilität und breite Cloud-Unterstützung für professionelle Webanwendungen.


Anhang J: Weiterführende Ressourcen und Community


Anhang K: Das Zelyra-Cookbook (Praxisrezepte)

Das Zelyra-Cookbook bietet eine kuratierte Sammlung erprobter Entwurfsmuster und praktischer Lösungen für reale Anforderungen in Unternehmens- und Webanwendungen. Jedes Rezept folgt dem bewährten Dreischritt: Problem, Lösung (vertraglich abgesicherter Zelyra-Code) und Erklärung.


Teil 1: Datenverarbeitung & Standardbibliothek

Rezept K.1: Exakte kaufmännische Rundung und Skontoberechnung

Problem: In kaufmännischen Anwendungen führen Fließkommazahlen (Float) zu Rundungsfehlern bei Rechnungsrabatten, Skontoabzügen und der Mehrwertsteueraufteilung.

Lösung:

pub record DiscountResult {
    base_net: Decimal,
    discount_rate: Decimal,
    discount_amount: Decimal,
    final_net: Decimal,
}

pub fn apply_cash_discount(amount: Decimal, rate: Decimal) -> DiscountResult
    requires { amount >= decimal("0.00") && rate >= decimal("0.00") && rate <= decimal("100.00") }
    ensures { result.final_net <= result.base_net && result.discount_amount >= decimal("0.00") }
{
    let discount = (amount * rate) / decimal("100.00")
    let final_amount = amount - discount
    return DiscountResult {
        base_net: amount,
        discount_rate: rate,
        discount_amount: discount,
        final_net: final_amount,
    }
}

Erklärung:

  • Zelyra verwendet für Decimal Festkomma-Arithmetik ohne binäre Gleitkomma-Ungenauigkeiten.
  • Der Vorbedingungsvertrag requires garantiert, dass kein negativer Skontosatz oder ein Satz über 100 % übergeben werden kann.
  • Die Nachbedingung ensures beweist mathematisch, dass der Endbetrag niemals den ursprünglichen Nettobetrag übersteigen kann.

Rezept K.2: Webhook-Payloads sicher parsen und typisieren

Problem: Ein externer Zahlungsdienstleister (z. B. Stripe oder PayPal) sendet JSON-Payloads via HTTP-POST an einen Webhook-Endpunkt. Unvollständige oder manipulierte Felder müssen sicher abgewehrt werden.

Lösung:

pub enum PaymentStatus {
    Paid,
    Pending,
    Failed,
}

pub record WebhookPayload {
    event_id: String,
    payment_reference: String,
    amount: Decimal,
    status: PaymentStatus,
}

pub fn parse_payment_status(raw: String) -> Option<PaymentStatus> {
    match raw {
        "succeeded" => { return Option::Some(PaymentStatus::Paid) }
        "processing" => { return Option::Some(PaymentStatus::Pending) }
        "requires_payment_method" => { return Option::Some(PaymentStatus::Failed) }
        _ => { return Option::None }
    }
}

Erklärung:

  • Zelyra erzwingt exhaustive Pattern-Matchings (match). Unbekannte Ereignistypen werden über die Wildcard _ abgefangen und als Option::None gemeldet, statt unerwartete Null-Pointer-Exceptions zur Laufzeit zu werfen.

Rezept K.3: Fristen und Fälligkeitstage berechnen

Problem: Eine Rechnung erhält ein Zahlungsziel von 14 Tagen. Fällt der Fälligkeitstag auf ein Wochenende, soll die Frist automatisch auf den nächsten Werktag (Montag) verlängert werden.

Lösung:

pub fn calculate_due_date(invoice_date: Date, net_days: Int) -> Date
    requires { net_days >= 0 }
{
    let target = invoice_date.add_days(net_days)
    let weekday = target.day_of_week()
    if weekday == 6 {
        return target.add_days(2)
    } else if weekday == 7 {
        return target.add_days(1)
    } else {
        return target
    }
}

Erklärung:

  • Datumsoperationen in Zelyra sind zeitzonensicher. add_days arbeitet direkt auf dem typisierten Date-Kalendermodell.

Teil 2: Datenbank, Transaktionen & Mandanten

Rezept K.4: Lückenlose Rechnungsnummernkreise (Atomic Sequence)

Problem: Das Finanzamt und die GoBD fordern für Rechnungen eine lückenlose, fortlaufende Nummerierung. Unter parallelen Anfragen können Race-Conditions zu doppelten oder fehlenden Nummern führen.

Lösung:

table invoice_sequences scoped by tenant_id: TenantId {
    id: Id primary auto
    tenant_id: TenantId required
    fiscal_year: Int required
    current_value: Int required default 0

    unique within tenant fiscal_year
}

fn next_invoice_number(t_id: TenantId, year: Int) -> String uses Database {
    transaction {
        let seq = query_one(
            "SELECT id, current_value FROM invoice_sequences WHERE tenant_id = ? AND fiscal_year = ? FOR UPDATE",
            [t_id, year]
        )
        let next_val = seq.current_value + 1
        execute(
            "UPDATE invoice_sequences SET current_value = ? WHERE id = ?",
            [next_val, seq.id]
        )
        return "RE-" + year.to_string() + "-" + next_val.to_string()
    }
}

Erklärung:

  • FOR UPDATE innerhalb des transaction-Blocks reserviert die Zeile mit einer exklusiven Datenbanksperre.
  • scoped by tenant_id und unique within tenant fiscal_year garantieren, dass jeder Mandant für jedes Geschäftsjahr einen unabhängigen, kollisionsfreien Nummernkreis besitzt.

Rezept K.5: Revisionssicherer Audit-Trail (Temporal Ledger)

Problem: Jede Preis- oder Statusänderung an schützenswerten Datensätzen muss unveränderlich mit ausführendem Benutzer und Zeitstempel dokumentiert werden.

Lösung:

table audit_ledger scoped by tenant_id: TenantId {
    id: Id primary auto
    tenant_id: TenantId required
    actor_id: Id required
    entity_name: String(64) required
    entity_id: Id required
    action_type: String(32) required
    payload_hash: String(64) required
    created_at: Instant required
}

fn record_audit_entry(
    t_id: TenantId,
    actor: Id,
    entity: String,
    target_id: Id,
    action: String,
    raw_payload: String
) uses Database, Clock {
    let now_ts = now()
    let hash = hash_sha256(raw_payload)
    execute(
        "INSERT INTO audit_ledger (tenant_id, actor_id, entity_name, entity_id, action_type, payload_hash, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
        [t_id, actor, entity, target_id, action, hash, now_ts]
    )
}

Erklärung:

  • Die Funktion deklariert die Capabilities Database und Clock. In Tests kann die Zeitquelle deterministisch manipuliert werden, während in Produktion echte UTC-Zeitstempel geschrieben werden.
  • Der SHA-256-Hash des JSON-Payloads sichert die Unveränderlichkeit des protokollierten Zustands ab.

Rezept K.6: Keyset-Paginierung für große Tabellen

Problem: Bei Tabellen mit Millionen Zeilen führt SELECT ... LIMIT 50 OFFSET 500000 zu spürbaren Datenbanklatenzen, da die Datenbank alle übersprungenen Zeilen sequenziell einlesen muss.

Lösung:

table customer_logs scoped by tenant_id: TenantId {
    id: Id primary auto
    tenant_id: TenantId required
    created_at: Instant required
    message: String(255) required

    index idx_tenant_cursor (tenant_id, created_at, id)
}

fn fetch_log_page(t_id: TenantId, last_ts: Instant, last_id: Id, page_size: Int) -> CustomerLog[] uses Database {
    return query(
        "SELECT id, tenant_id, created_at, message FROM customer_logs WHERE tenant_id = ? AND (created_at < ? OR (created_at = ? AND id < ?)) ORDER BY created_at DESC, id DESC LIMIT ?",
        [t_id, last_ts, last_ts, last_id, page_size]
    )
}

Erklärung:

  • Keyset-Paginierung nutzt den zusammengesetzten Index (tenant_id, created_at, id) optimal aus. Die Abfragegeschwindigkeit bleibt bei Seite 10.000 exakt genauso hoch wie bei Seite 1.

Teil 3: Sicherheit, Authentifizierung & Governance

Rezept K.7: TOTP-Zwei-Faktor-Authentifizierung (2FA)

Problem: Sensible Benutzerkonten müssen durch RFC-6238-basierte Einmalkennwörter (Time-Based One-Time Passwords) und sichere Wiederherstellungscodes geschützt werden.

Lösung:

table user_mfa_settings scoped by tenant_id: TenantId {
    id: Id primary auto
    tenant_id: TenantId required
    user_id: Id required unique
    secret_key: String(64) required
    is_enabled: Bool required default false
    backup_code_hash: String(128) required
}

fn verify_totp_attempt(user_secret: String, user_token: String) -> Bool uses Clock {
    let valid = totp_verify(user_secret, user_token, 30)
    return valid
}

Erklärung:

  • totp_verify toleriert eine Drift von ±1 Zeitschritt (30 Sekunden), um minimale Uhrzeitabweichungen auf Mobiltelefonen abzufedern.

Rezept K.8: Mandantengebundene API mit Rollenprüfung

Problem: Eine JSON-API zur Freigabe von Belegen muss sicherstellen, dass Benutzer nur Daten des eigenen Mandanten manipulieren und zwingend über die Rolle AccountingManager verfügen.

Lösung:

table vouchers scoped by tenant_id: TenantId {
    id: Id primary auto
    tenant_id: TenantId required
    voucher_number: String(40) required
    status: String(20) required default "draft"
    total_amount: Decimal(12, 2) required
}

api approve_voucher(voucher_id: Id) scoped by tenant_id {
    permission: "vouchers.approve"
    validates {
        voucher_id > 0: "Ungültige Beleg-ID."
    }

    execute uses Database {
        transaction {
            let row = query_one(
                "SELECT id, status FROM vouchers WHERE id = ? AND tenant_id = ? FOR UPDATE",
                [voucher_id, session.tenant_id]
            )
            if row.status == "approved" {
                return { success: false, error: "Beleg ist bereits freigegeben." }
            }
            execute(
                "UPDATE vouchers SET status = 'approved' WHERE id = ? AND tenant_id = ?",
                [voucher_id, session.tenant_id]
            )
            return { success: true, message: "Beleg erfolgreich genehmigt." }
        }
    }
}

Erklärung:

  • scoped by tenant_id verknüpft die API untrennbar mit der aktiven Mandantensitzung (session.tenant_id). Ein Zugriff auf fremde Mandantendaten ist auf Sprachebene ausgeschlossen.

Rezept K.9: Schutz vor Brute-Force durch Ratenbegrenzung

Problem: Passwort-Reset- und Login-Endpunkte müssen vor Wörterbuchangriffen und Credential-Stuffing geschützt werden.

Lösung:

table rate_limits {
    id: Id primary auto
    client_ip: String(45) required
    action: String(30) required
    attempts: Int required default 1
    locked_until: Instant?

    index idx_client_action (client_ip, action)
}

fn check_rate_limit(ip: String, action_name: String, max_attempts: Int) -> Bool uses Database, Clock {
    let now_ts = now()
    let record = query_one_optional(
        "SELECT id, attempts, locked_until FROM rate_limits WHERE client_ip = ? AND action = ?",
        [ip, action_name]
    )
    match record {
        Option::Some(r) => {
            if r.attempts >= max_attempts {
                return false
            }
            execute("UPDATE rate_limits SET attempts = attempts + 1 WHERE id = ?", [r.id])
            return true
        }
        Option::None => {
            execute("INSERT INTO rate_limits (client_ip, action, attempts) VALUES (?, ?, 1)", [ip, action_name])
            return true
        }
    }
}

Erklärung:

  • Das Muster blockiert weitere Versuche, sobald max_attempts erreicht wird. Die IP-basierte Prüfung schützt CPU und Datenbank vor Überlastung.

Teil 4: Web, CRUD & Master-Detail

Rezept K.10: Master-Detail-Rechnungsformular mit Positionen (has_many)

Problem: Ein Rechnungsbeleg besteht aus Kopfdaten (Kunde, Rechnungsdatum) und einer Liste beliebig vieler Belegpositionen, die gemeinsam in einer Transaktion gespeichert und aufsummiert werden müssen.

Lösung:

table invoices scoped by tenant_id: TenantId {
    id: Id primary auto
    tenant_id: TenantId required
    invoice_number: String(32) required unique within tenant
    invoice_date: Date required
    customer_name: String(100) required
    total_net: Decimal(12, 2) required default decimal("0.00")
    total_gross: Decimal(12, 2) required default decimal("0.00")
}

table invoice_items scoped by tenant_id: TenantId {
    id: Id primary auto
    tenant_id: TenantId required
    invoice: Invoice required on_delete cascade
    description: String(120) required
    quantity: Decimal(8, 2) required
    unit_price: Decimal(10, 2) required
    line_total: Decimal(12, 2) required
}

crud Invoice -> invoices {
    title: "Rechnung erfassen"

    has_many items: InvoiceItem {
        title: "Rechnungspositionen"
        fields: [description, quantity, unit_price, line_total]
    }

    aggregates {
        total_net = sum(items.line_total)
        total_gross = sum(items.line_total) * decimal("1.19")
    }
}

Erklärung:

  • has_many items erzeugt eine tabellarische Kinddatensatz-Eingabe direkt im Hauptformular.
  • Der Block aggregates berechnet die Gesamtsummen serverseitig in der Speichertransaktion. Manipulationen über das Browser-Frontend werden unterbunden.

Rezept K.11: GoBD-Schutz mit Lifecycle-Guards

Problem: Rechnungen, die bereits an den Kunden versandt oder bezahlt wurden, dürfen aus steuerrechtlichen Gründen nicht mehr nachträglich verändert oder gelöscht werden.

Lösung:

table sales_invoices scoped by tenant_id: TenantId {
    id: Id primary auto
    tenant_id: TenantId required
    doc_number: String(30) required
    is_finalized: Bool required default false
    is_cancelled: Bool required default false
}

crud SalesInvoice -> sales_invoices {
    title: "Ausgangsrechnungen"

    guard edit when is_finalized == true {
        error: "Festgeschriebene Belege können nicht mehr editiert werden. Bitte Stornobuchung durchführen."
    }

    guard delete when is_finalized == true {
        error: "Festgeschriebene Rechnungen dürfen gemäß GoBD niemals gelöscht werden."
    }
}

Erklärung:

  • Zelyra führt Guards unmittelbar in der Datenbank-Mutationstransaktion aus.
  • TableView-Aktionsleisten und Schaltflächen blenden Bearbeiten- und Löschen-Aktionen für gesperrte Datensätze automatisch aus.

Rezept K.12: Feldübergreifende Invariantenprüfungen (validate)

Problem: Bei Hotel- oder Fahrzeugbuchungen darf das Abreisedatum niemals vor oder am Anreisedatum liegen.

Lösung:

table hotel_bookings scoped by tenant_id: TenantId {
    id: Id primary auto
    tenant_id: TenantId required
    guest_name: String(100) required
    check_in: Date required
    check_out: Date required
    rooms_count: Int required default 1

    validate {
        check_out > check_in: "Das Abreisedatum muss nach dem Anreisedatum liegen."
        rooms_count >= 1 && rooms_count <= 10: "Es können maximal 10 Zimmer pro Buchung reserviert werden."
    }
}

Erklärung:

  • Invarianten werden sowohl bei API-Aufrufen als auch in CRUD-Formularen vor dem Datenbank-Write ausgewertet.
  • Bei Verletzung antwortet Zelyra automatisch mit standardisierten HTTP-422-Fehlermeldungen und markiert die betroffenen Formularfelder mit aria-invalid="true".

Teil 5: Hintergrund-Dienste, E-Mail & Export

Rezept K.13: Transaktionale PDF-Erstellung und Mail-Outbox

Problem: Nach Abschluss einer Bestellung muss eine PDF-Auftragsbestätigung erzeugt und per E-Mail versandt werden. Schlägt die Datenbanktransaktion fehl, darf keine E-Mail versendet werden.

Lösung:

fn finalize_order(order_id: Id, recipient_mail: String) uses Database {
    transaction {
        let order = query_one("SELECT id, order_number, total_gross FROM orders WHERE id = ?", [order_id])
        let pdf_bytes = render_pdf("templates/OrderConfirmation.html", order)
        mail_queue(
            recipient_mail,
            "Ihre Auftragsbestätigung " + order.order_number,
            "Vielen Dank für Ihre Bestellung. Im Anhang finden Sie Ihre Bestätigung.",
            order.order_number + ".pdf",
            pdf_bytes
        )
        execute("UPDATE orders SET status = 'confirmed' WHERE id = ?", [order_id])
    }
}

Erklärung:

  • mail_queue schreibt die Nachricht in die MariaDB-Tabelle mail_queue innerhalb der laufenden Transaktion.
  • Bricht die Transaktion ab (Rollback), wird auch der E-Mail-Auftrag verworfen. Erst wenn der Commit erfolgreich ist, greift der Hintergrund-Worker die Mail auf.

Rezept K.14: Idempotente Cronjob-Abarbeitung mit Leases

Problem: Ein Hintergrund-Job zur Prüfung offener Mahnungen soll jede Nacht um 02:00 Uhr laufen. Mehrere Serverinstanzen dürfen den Job niemals doppelt ausführen.

Lösung:

schedule "0 2 * * *" {
    name: "nightly_payment_reminders"
    timeout: 3600

    execute uses Database, Clock {
        transaction {
            let pending_invoices = query(
                "SELECT id, customer_mail, due_date FROM invoices WHERE status = 'open' AND due_date < CURRENT_DATE() FOR UPDATE SKIP LOCKED",
                []
            )
            for inv in pending_invoices {
                execute("UPDATE invoices SET status = 'reminded' WHERE id = ?", [inv.id])
            }
        }
    }
}

Erklärung:

  • SKIP LOCKED stellt sicher, dass konkurrierende Worker nur nicht blockierte Datensätze verarbeiten.
  • Der Scheduler vergibt automatische Leases mit Timeouts, sodass abgestürzte Prozesse nach Ablauf des Zeitfensters automatisch freigegeben werden.

Rezept K.15: Streaming-CSV-Export für DATEV / Buchhaltung

Problem: Für den Steuerberater müssen hunderttausende Buchungssätze in ein normiertes CSV-Format exportiert werden, ohne dass der Arbeitsspeicher des Webservers überläuft.

Lösung:

fn export_datev_csv(t_id: TenantId, year: Int) -> String uses Database {
    mutable csv_output = "Datum;Beleg;Konto;Gegenkonto;Betrag;Text\n"
    let entries = query(
        "SELECT booking_date, voucher_no, account, contra_account, amount, notice FROM bookings WHERE tenant_id = ? AND fiscal_year = ? ORDER BY booking_date ASC",
        [t_id, year]
    )
    for row in entries {
        csv_output = csv_output + row.booking_date.to_string() + ";" + row.voucher_no + ";" + row.account.to_string() + ";" + row.contra_account.to_string() + ";" + row.amount.to_string() + ";" + row.notice + "\n"
    }
    return csv_output
}

Erklärung:

  • Die typisierte Iteration stellt sicher, dass Währungswerte und Datumsfelder exakt nach vorgegebenen Formatierungsstandards exportiert werden.
LERNPFADDie Sprache lernen →