The Zelyra Handbook
From foundations to database-backed web applications. State intent. Prove correctness.
Welcome to the complete Zelyra Handbook. It includes both the introductory textbook "Learning Zelyra – Understandable Programming from the Foundations to Your Own Application" (Parts I to X, Chapters 1 to 43), the Technical Reference Manual (Sections 1 to 40), and comprehensive Appendices (A to K).
Zelyra 1.2.39: current scope
This handbook describes the latest published stable release, Zelyra 1.2.39 (as of October 21, 2026). Changes after that release are described as available only after publication. Zelyra 1.0.0 was its first stable major release; the language compatibility line remains 0.1. Compiler, database, operational, and IDE capabilities have defined limits described in their relevant sections. The former zelyra migrate laravel tool was removed in 1.1.2. The later, separate zelyra import laravel workflow analyzes selected project structures and generates reviewable Zelyra foundations; it does not port a complete Laravel website.
- Language and type system: typed functions, immutable bindings, user-defined generic records and enums, payload-carrying enums, named function values (
&name), exhaustivematchchecking, exact 9-scale decimal arithmetic (Decimal), typed calendar/temporal values (Date,Time,DateTime,Instant), contracts (requires,ensures), and declared capabilities. - Language extensions and runtime: typed value-producing
ifexpressions, functional record updates (Customer { ..old, active: true }), typed lexical lambdas in pipelines (map,filter,find,reduce), boolean match guards and nested record patterns, first-class tuples and destructuring bindings, contextual field names (version,type), nominal Id aliases (type OrderId = Id), configurable execution step budgets (--max-steps N), server-side local text-PDF rendering (render_pdf), and transactional PDF mail attachments via MariaDB outbox. - Databases and relational CRUD: multi-backend support for tested workflows across MariaDB 11.4.13, MySQL 8.4.11, and PostgreSQL 16.15. Typed parameterized SQL, transactions with dynamic deadline-bound locks (
innodb_lock_wait_timeout), real foreign key constraints with orphan row rejection (error[E-DB-005]), relational joins, tenant-scoped CRUD, ordered restrictive deletion (RESTRICT), and schema previews with controlled apply boundaries (zelyra db plan/apply/history). - Tenant isolation and composite foreign keys (v1.2.17 – v1.2.19):
table ... scoped by tenant_id: TenantIdenforces multi-tenancy at the database level. Zelyra generatesuq_{table}_tenant_id UNIQUE (tenant_id, id)and composite foreign keysFOREIGN KEY (tenant_id, fk_id) REFERENCES target (tenant_id, id) ON DELETE RESTRICT.unique within tenantdeclares tenant-scoped uniqueness (customer_number: String(50) required unique within tenant), and optional relations (Customer?) generate typedBIGINT NULL. - Table invariants and typed validation (v1.2.20):
table ... { validate { due_date >= invoice_date: "..." } }anchors typed, cross-field invariants directly to the data schema. Declarative forms, CRUD, and mutating APIs (validates) enforce these rules before committing writes, returning standardized HTTP 422 contracts. - Lifecycle guards and row policies (v1.2.21): Declarative locks for
editanddelete(guard edit when is_locked == true { error: "..." }) block illegal mutations directly inside the mutation transaction.tableviewaction slots and detail pages dynamically disable or hide edit and delete actions based on row state. - Transactional master-detail CRUD forms and multiple child collections (v1.2.22 – v1.2.24):
has_many items: InvoiceItem { ... }and additional collections (has_many attachments: InvoiceAttachment { ... }) render server-side 1:N line-item grids and attachments with atomic parent-child transactions and automated server-side summary aggregates (aggregates). - Validated tenant APIs (v1.2.23): Tenant-scoped APIs (
scoped by tenant_id) strictly validate tenant context from authenticated user sessions. - Canonical project layout (v1.2.13 – v1.2.14):
zelyra new --layout standardscaffolds a verified directory layout (routes/,domain/,schema/,public/,assets/). Architecture linting (strict_architecture = true) physically prevents raw database mutations inside routes (E-ARCH-001);zelyra migrate-layout --checkprovides a read-only preview. - Extensible CRUD and TableViews (v1.2.15 – v1.2.16): compiler-checked extension regions, field help, and row action slots on generated
crud.view.create,crud.view.edit, andtableviewpages while preserving generated routing, validation, CSRF, and tenant scoping. - Systemd services, scheduler, and jobs (v1.2.5 – v1.2.9):
zelyra service install --port <port>installs a managed Linux systemd user service with fixed-port verification. The resident scheduler processes declared crons, dispatching persistent background jobs with renewable leases to the MariaDB outbox (zelyra schedule jobs). - Package ecosystem (v1.1.5 – v1.2.12): reusable local and registry packages (
pkg:), offline lockfile (zelyra.lock), Ed25519-signed packages, recoverable mutations with transactional journal rollback (zelyra package recover), and authenticated app-level module settings (/__zelyra/module-settings). - Native database CLI and direct queries (v1.2.25):
zelyra db consolelaunches the native MariaDB/MySQL or PostgreSQL client with automated password handover.zelyra db queryexecutes single statements inside read-only transactions by default, with--allow-mutation, table/JSON/CSV formatting, and tenant binding (:tenant_id). - Interactive AST language console (v1.2.26):
zelyra consoleprovides an interactive REPL with persistent typed bindings, tab completion, history in~/.zelyra_history, and a transactional MariaDB/MySQL sandbox (--sandbox) rolling back changes automatically upon exit. - Laravel import analysis and migration foundation (v1.2.27):
zelyra import laravel analyzeandgenerateinspect existing Laravel applications with source evidence in read-only mode. Generates native Zelyra tables, invariants, models, and auth packages without executing untrusted PHP code. - Typed transactional CRUD actions (v1.2.28):
action <name>(...)binds typed parameters, the exclusively locked row (row), and verified caller session data (session). Pure guards evaluate under row lock; business mutations, audit logs, and PDF outbox participate in a shared atomic transaction. - Secure multipart file uploads and tenant storage (v1.2.29): Typed
Fileform fields with byte limits and signature validation (magic bytes for PDF, PNG, JPEG). Private storage outside web root with opaqueFileReftokens, authorized CRUD downloads, orphan cleanup, and checksum-verified backup/restore (zelyra files cleanup|backup|restore). - Tenant-safe schema references (v1.2.30): Scalar ID fields gain explicit foreign key constraints with
references <table>. Between tenant-scoped tables, composite foreign keysFOREIGN KEY (tenant_id, fk_id) REFERENCES target (tenant_id, id)ensure strict isolation. - Tenant-aware number series (v1.2.31):
auto_sequence("KD-{0001}")andauto_sequence("AG-{year}-{0001}")assign tamper-resistant document and customer numbers transactionally per tenant (and UTC calendar year). - Typed computed business fields and FileRef attachments (v1.2.32):
compute(expr)calculates derived fields (e.g. line totals) deterministically with server-side enforcement across CRUD and APIs.FileRef? attachmentconnects private file storage with byte-limit and MIME verification directly to generated forms. - Multi-backend parity, transaction rollback, and PDF outbox (v1.2.33): Source-level
--max-stepsaccounting aligned across AST, MIR, and Bytecode VM; transactional PDF mail outbox (render_pdf+mail_queue) with atomic rollbacks on transaction errors. - Process integration and Laravel schema analysis (v1.2.34): Bounded
run_process_pipedwith a capability allowlist, size limit, and timeout, more precise handling of selected text capacities, and narrowly scoped static recognition of conditional Laravel migrations. This is not a sandbox for arbitrary code or a complete website import. - Laravel integer widths (v1.2.35): Supported unsigned integer widths are preserved more precisely during schema analysis; backend differences and unsupported cases are reported.
- CRUD action runtime (v1.2.36): A successful guard and its typed handler share the AST runtime context; rejected guards still skip the handler.
- CSV importer and backend fixes (v1.2.37–v1.2.39): The reusable CSV example package supports explicitly selected text, integer, Boolean, Decimal, Date, and Time conversions. Values are parsed strictly without guessing locale; qualified enum variants for MIR and Bytecode were also corrected. The importer does not infer schemas or write database records itself.
- Zelyra UI & Design System: locally bundled, compiler-checked design system without npm or Webpack (
zelyra-ui.v1.css). Typed UI components, themes (light,dark,system), harmonized app shell, modal dialogs, and responsive print layouts. - Zelyra Studio (IDE): local editor with session protection, saving, live diagnostics, contextual help, action finder, code completion, navigation, bounded execution, structured logs panel, theme editor, and CRUD preview.
- Invoice tutorial: customers, items, invoices, and line items in a unified Zelyra UI theme with an interactive "Code Explained" dialog, aligned cards, and browser printing.
- Email and security: transactional MariaDB outbox (
mail_queue), SMTP worker with TLS and CA validation, header-injection protection, restart-resilient persistent password reset limits (password_reset_limits), and integrated TOTP two-factor authentication (RFC 6238, AES-256-GCM, Argon2id recovery codes). - Notifications: tenant- and recipient-scoped in-app center (
recipient: user_id), dynamic unread counter (unread: read_at), personal CSRF-protected read receipts with audit logging, expiry filtering, and archiving. - AST, MIR, and Bytecode execution: The AST interpreter remains the default path. MIR and Bytecode support selected tested workflows; complete web/runtime parity and open 1.3.0 gates are unfinished. No performance gain over AST is claimed.
Changes since version 1.0.0
- 1.0.1: Compose v2 is supported; Python Compose v1 is rejected with an update hint. Studio's Run button starts the project entry point.
- 1.0.2: Studio can start MariaDB for the local invoice tutorial, apply its schema, and load sample data.
forranges accept step sizes;range(start, end, step)returns the corresponding sequence. - 1.0.3: The standard library expanded across math, text, arrays, sets, maps, dates, parsing, encodings, and paths. The release also adds type-checked
|>pipelines, flatelse ifchains, schema display while the database is unavailable, automatic tutorial setup, free ports, repeatable sample data, and bounded recursion. Full signatures are in Appendix D. - 1.0.4: Invoice tutorials receive a dedicated Compose project name. Older volumes can fall back to an isolated database without deleting or changing the previous volume. Compose detection accepts short versions such as
2.40. - 1.0.5: Studio adds local structured Logs;
zelyra uninstallsafely removes the active user-owned binary after confirmation. The invoice tutorial adds date-only input, server-assigned invoice numbers, a workflow home, a named invoice overview, and detailed English and German source guides. - 1.0.6: Zelyra UI supplies compiler-checked components and locally bundled styles for generated CRUD, starter projects, Studio properties, and the invoice tutorial. It adds themes, responsive layouts, chart components, browser print/PDF, and prefilled email drafts.
- 1.0.7: Value-producing
ifexpressions, record updates (Customer { ..old, active: true }), typed lexical lambdas for pipelines (map,filter,find,reduce), boolean match guards, nested record patterns, tuple binding, step budgets (--max-steps), nominal Id aliases, server-side text PDF rendering (render_pdf), and transactional PDF mail attachments via the outbox. - 1.1.0: Studio additions with typed-hole suggestions, data schema map, static page preview without code execution, and theme styling editor.
- 1.1.1: CLI and editor stability improvements for JSON-RPC diagnostics.
- 1.1.2: Removal of the experimental Laravel analyzer and scaffold command (
zelyra migrate laravel). The migrator and previous reports were completely removed from Zelyra in favor of native contract-driven architecture. - 1.1.5: Reusable local packages with typed public APIs, offline lockfiles, module inspection and bundling, local package settings, and CSV examples.
- 1.2.0: Operational and deployment foundation, declarative commands and schedules, static
public/directory with MIME delivery, Apache/Nginx reverse proxy guides, and fixed-port service installation. - 1.2.5: Fixed-port Linux systemd service management (
zelyra service), resident scheduler, Ed25519-signed source packages, and idempotent outbox initialization. - 1.2.6: Recoverable package mutations: serialized transactions, versioned journals, and quarantine recovery via
zelyra package recover. - 1.2.7: Cryptographically signed transitive registry packages with whole-closure trust checking and cycle detection.
- 1.2.8: Typed SQL metadata lowering from HIR to MIR with plan re-verification (
E-MIR-VERIFY-004) and strict form redirect validation to local absolute paths (E-FORM-007). - 1.2.9: Durable scheduler jobs in the MariaDB outbox; serial lease worker; job inspection with
zelyra schedule jobs. - 1.2.10: Serialized module settings persistence in Studio preventing secret leakage; versioned HostServices embedding contract.
- 1.2.11: Unified Zelyra UI styling for invoice tutorial, CLI SIGPIPE compliance, and improved Compose v2 packaging support.
- 1.2.12: Authenticated application-level module settings page (
/__zelyra/module-settings), automatic modal dialog defaults, and direct theme file access in Studio. - 1.2.13: Canonical project layout (
zelyra new --layout standard) withroutes/,domain/,schema/,public/,assets/, and namespace imports (import "domain::sales::actions"). - 1.2.14: Deterministic
zelyra migrate-layout --checkpreview,strict_architecture = truelinter (E-ARCH-001), styled invoice document, and "Code Explained" dialog. - 1.2.15: Extensible generated CRUD create/edit pages (
crud.view.create,crud.view.edit), field help regions, and row actions intableview. - 1.2.16: Typed TableView cell content and formatted row action slots with HTML templates and safe escaping.
- 1.2.17: Enforced tenant isolation (
scoped by tenant_id: TenantId), composite foreign keysFOREIGN KEY (tenant_id, fk_id) REFERENCES target (tenant_id, id) ON DELETE RESTRICT, typed optional relations (Customer?), and exact commercial decimal scaling (Decimal(p, s)). - 1.2.18 / 1.2.19: Tenant-scoped uniqueness
unique within tenantwith automatic composite MariaDB unique indexUNIQUE (tenant_id, column). - 1.2.20: Typed cross-field table invariants (
validate { condition: "error" }) in forms and CRUD, plus typed API input validation withvalidates. - 1.2.21: Record-dependent lifecycle guards (
guard edit when ...,guard delete when ...) and policy evaluation in detail views and TableView slots. - 1.2.22: Transactional master-detail CRUD forms (
has_many) with atomic saving of parent and child records, cascade deletion, and server-side aggregates (aggregates). - 1.2.23: Validation of tenant-scoped APIs (
scoped by tenant_id) from authenticated user sessions; robust tenant isolation at API boundaries. - 1.2.24: Support for multiple direct
has_manychild collections per CRUD resource (e.g. items and attachments) with atomic saving, independent row limits, and server-side aggregates. - 1.2.25: Native database console
zelyra db consolefor MariaDB/MySQL/PostgreSQL and direct execution of single SQL statements withzelyra db query(read-only by default, table/JSON/CSV output,--tenantparameter). - 1.2.26: Interactive AST language console
zelyra consolewith persistent typed bindings, multiline input, tab completion, and transactional MariaDB/MySQL sandbox with automatic rollback (:commit,:rollback,:tables,:schema). - 1.2.27: Read-only Laravel import analyzer (
zelyra import laravel analyze|generate) for source-evidenced reconstruction of schemas, model invariants, and reusable native authentication packages. - 1.2.28: Typed transactional CRUD actions (
action) with compiler-checkedrowandsessioncontexts, pure guards evaluated under row locks, and a shared request transaction for business writes, audit logging, and PDF outbox tasks. REPL sandbox hardening against implicit DDL commits. - 1.2.29: Secure multipart file uploads with typed
Fileform fields, byte limits, and signature validation (PDF/PNG/JPEG). Private tenant-scoped storage with opaqueFileReftokens, authorized CRUD downloads, orphan cleanup, and checksum-verified backup/restore (zelyra files cleanup/backup/restore). - 1.2.30: Scalar foreign key targets (
references <table>), composite tenant constraints, extended Laravel import analysis for pivot tables and tenant route tokens. - 1.2.31: Tenant-aware business number series (
auto_sequence), automatic UTC calendar year partitioning ({year}), managed internal table_zelyra_number_sequences, MariaDB composite constraint fix, and explicit SQLite rejection (E-DB-001). - 1.2.32: Typed computed business fields (
compute) with server-side recomputation and rejection of client overrides,FileRefattachments in generated CRUD with protected downloads, localizedhas_manylegends, and account approval guard (login_enabled). - 1.2.33: Source-level step budget alignment (
--max-steps) across AST, MIR, and Bytecode VM, transactional PDF mail outbox (render_pdf+mail_queue), aligned transaction failure diagnostics. - Milestone 1.3.0 outlook: Canonical vertical slice standard, dual-tier pipeline with Bytecode VM v24, single-binary zero-ops distribution (
zelyra build --bundle), audit trails (auditablewithas_oftime travel), and zero-sum ledgers (ledger).
Release details: Find the complete published version history in the Zelyra releases on GitHub. The Studio guide, local logs guide, uninstall guide, mail guide, and database matrix describe additional capabilities and limits.
Development status and agreed architecture
Zelyra is at stable release 1.2.39. The Zelyra roadmap is the source of truth for ongoing work and open goals.
- Resident schedules and integrated scheduler (✅ Implemented since 1.2.5/1.2.9): Commands and five-field numeric schedules (
schedule <name> { cron: "..."; timezone: "..."; command: ... }) are declared directly in Zelyra source code. For instance, a weekly run at noon on Saturday usescron: "0 12 * * 6"andtimezone: "Europe/Berlin". The built-in resident scheduler runs automatically within the active Zelyra service (zelyra serviceorzelyra serve). At each minute boundary, it evaluates pending schedules and enqueues jobs transactionally into the MariaDB outbox. A serial lease worker (zelyra schedule jobs) processes pending tasks with lease renewals and bounded retries. Manual hostcrontab -econfiguration is not required for regular operation. Thezelyra schedule exportcommand serves only as an optional preview or export for external third-party systems; Zelyra never silently touches external crontabs. - Operating system services (✅ Implemented):
zelyra service install --port <port>configures a systemd user service on an explicitly selected fixed port.zelyra service status,restart,change --port <port>, andremovemanage its lifecycle with atomic rollback on failure. - Canonical project structure and vertical slices (✅ Implemented since 1.2.13/1.2.14):
--layout standardenforces a structured layout (schema/,domain/,routes/, staticpublic/).strict_architecture = truevalidates architectural boundaries (E-ARCH-001), andzelyra migrate-layout --checkprovides deterministic layout migration previews. - Extensible CRUD and TableView pages (✅ Implemented since 1.2.15/1.2.16): Generated create and edit pages provide compiler-checked extension points (
crud.view.create,crud.view.edit), while table views support field help and row actions. - Tenant isolation and composite foreign keys (✅ Implemented since 1.2.17/1.2.19):
table ... scoped by tenant_id: TenantIdenforces multi-tenant boundary on database level using composite foreign keys.unique within tenantprevents numbering sequence clashes per tenant. - Table invariants and validation rules (✅ Implemented since 1.2.20): Typed table invariants (
validate) are evaluated prior to database writes in forms and CRUD. - Lifecycle guards and master-detail CRUD with multiple child collections (✅ Implemented since 1.2.21 – 1.2.24): GoBD-compliant mutation guards for
editanddelete(guard), transactional collection forms for multiple 1:N child collections (has_many) with server-side aggregates, and API tenant validation from verified sessions. - Direct database CLI and interactive language console (✅ Implemented since 1.2.25/1.2.26):
zelyra db console,zelyra db query, andzelyra consolewith transactional sandbox and rollback. - Laravel import foundation (✅ Implemented since 1.2.27): Read-only structural analysis and selective generation of native Zelyra modules.
- Typed CRUD actions (✅ Implemented since 1.2.28): Domain actions with row locks, pure guards, and atomic transaction bundling with audit logs and outbox.
- Secure file uploads and tenant storage (✅ Implemented since 1.2.29): Bounded multipart uploads, signature validation, private storage with
FileRef, and file store backup/restore. - Generated
.envfiles: Supported project templates include commented optional configuration examples alongside active values. Comment lines are documentation only and are never executed as shell scripts or Docker commands. Keep credentials in the protected local.env, never in Git or source code. - Database development: Zelyra projects describe schema declaratively with Zelyra tables.
zelyra db plan,apply, andhistoryare the primary tools; Laravel migration files are not a runtime requirement. The former experimental Laravel migrator was removed from the Zelyra product. - Roadmap through 1.3.0: Open compiler, runtime, package, and project-structure work is tracked in the roadmap. Planned capabilities, including full backend parity, are not described as already released.
Schedules and outbox in operations
In Zelyra projects, the system idempotently ensures all required outbox and scheduler tables during setup. The resident scheduler starts automatically whenever the project runs under its service (zelyra service) or zelyra serve.
The following commands are available for inspection and management:
zelyra check main.zyl
zelyra task list main.zyl
zelyra schedule list main.zyl
zelyra schedule jobs main.zyl
zelyra schedule status main.zyl
A manual host crontab entry (crontab -e) is not required for Zelyra operation. If an external monitoring system or third-party server scheduler needs to trigger declared commands, you can generate an exportable preview with:
zelyra schedule export main.zyl --format cron --output deploy/zelyra.cron
zelyra schedule export does not overwrite existing files or modify the host crontab. See the environment reference for supported variables, and refer to Chapter 28 for the complete scheduler and task architecture.
Status marks
- ✅ Implemented and verified: present in the documented scope and supported by the cited sources.
- 🧪 Experimental: present, but early or constrained.
- 🗺️ Planned: part of the language vision, not yet reliably available.
- ❌ Currently unavailable: not present in the current CLI.
Table of Contents
Learning Zelyra – The Textbook
- PART I – UNDERSTANDING ZELYRA AND PROGRAMMING
- PART II – LANGUAGE FUNDAMENTALS
- PART III – STRUCTURING PROGRAMS
- PART IV – SAFETY AND ERROR HANDLING
- PART V – PRACTICAL DATA PROCESSING
- PART VI – DATABASES WITH ZELYRA
- PART VII – WEB APPLICATIONS AND FORMS
- PART VIII – THE DISTINCTIVE FEATURES OF ZELYRA
- PART IX – FROM DESIGN TO FINISHED APPLICATION
- PART X – CAPSTONE PROJECT AND LOOKING AHEAD
Technical Reference Manual
-
24. Authoritative Sources and Compiler Verification (Source Authority)
-
36. CSS Design System and UI Styling for CRUD and TableViews
-
42. Typed Computed Business Fields (
compute) and FileRef Attachments -
43. Multi-Backend Parity, Transaction Rollbacks, and PDF Outbox
Appendices
- Appendix A: Quickstart / Cheat Sheet (Syntax Cheat Sheet)
- Appendix B: Complete Zelyra Diagnostic & Error Code Reference
- Appendix C: Zelyra CLI Command Reference
- Appendix D: Standard Library Overview
- Appendix E: SQL Cheat Sheet for Zelyra Developers
- Appendix F: HTML and Web Reference in Zelyra
- Appendix G: Glossary of Technical Terms
- Appendix H: Solutions to Chapter Exercises
- Appendix I: Frequently Asked Questions (FAQ)
- Appendix J: Next Resources and Community
- Appendix K: The Zelyra Cookbook (Practical Recipes)
PART I – UNDERSTANDING ZELYRA AND PROGRAMMING
Chapter 1: Welcome to Zelyra
1. What will I learn in this chapter?
In this introductory chapter, you will learn:
- What a programming language is at its core and what purpose it serves.
- What makes Zelyra distinctive and why it was created as an independent programming language.
- Which practical goals Zelyra pursues and what kinds of tasks it is specifically tailored for.
- How Zelyra guarantees readability, reliability, and safety right from the start.
- Why Zelyra was intentionally designed for both human software engineers and AI coding assistants.
- How this textbook is structured and how you can work with it most effectively.
2. Why is this topic important?
Before you write your first line of code, you should understand the fundamental problem that Zelyra solves. Modern software engineering—especially when building data-intensive and web-driven applications—frequently suffers from massive fragmentation: you design a database table schema in SQL, re-implement the exact same rules inside a backend framework (such as Laravel, Express, or Django), re-validate the same constraints a third time in the frontend (HTML/JavaScript), and then manually generate API schemas. Zelyra breaks through this fragmentation: you define your data models, business rules, and interfaces in one cohesive language—and Zelyra derives verified, secure application components from that single source of truth.
3. Understandable explanation without unnecessary jargon
A programming language is a precise set of rules and instructions. It allows you to give unambiguous commands to a computer. Computers are extraordinarily fast, but they have no innate common sense: if an instruction is ambiguous or an unhandled condition occurs, the application either crashes or produces critical bugs.
Zelyra is a statically typed language. This means the compiler checks types during compilation and can report certain type mismatches before execution. Which additional rules it checks depends on the implemented checking scope; this does not guarantee that a program is error-free.
At the same time, Zelyra is database- and web-centric:
- A table schema (
table) is not an isolated SQL migration file, but a first-class language element. - Variables are immutable by default. As a result, values cannot change unexpectedly behind the scenes.
- No null pointer surprises: missing or optional values must be declared explicitly as
Option.
4. Small, progressive examples
Let us examine a first minimal Zelyra program:
# Our first Zelyra program: A greeting
fn main() {
print("Welcome to Zelyra!")
}
To give this program more modular structure, we can break the task down into a reusable function:
fn greet(name: String) -> String {
return "Hello, " + name + "! Welcome to the world of Zelyra."
}
fn main() {
message = greet("Developer")
print(message)
}
Comments: # and ##
# starts a line comment, either at the start of a line or after a statement, and continues to the end of that line. A block comment is enclosed by two ## delimiters. Each delimiter stands alone on its own line; the content may span multiple lines. Block comments do not nest. Inside a string, # is ordinary text.
# A line comment
##
This text is a block comment.
It can span multiple lines.
##
fn main() {
print("Hello") # Comment after a statement
print("# remains visible inside the string")
}
Existing // comments remain compatible. Use # in new examples. The compact spelling ## text ## on one line does not start a multiline block in the documented lexer; use the standalone delimiters shown above.
5. Typical errors and their causes
- Error: Placing a semicolon at the end of a line (as in Java, C++, or PHP). Cause: In Zelyra, line breaks cleanly delimit statements. Redundant semicolons clutter the code.
- Error: Mistaking Zelyra for a framework or configuration language. Cause: Zelyra has its own syntax and type system. Its compiler translates and checks Zelyra programs; the runtime and project tools complement the language for supported use cases.
6. Key takeaways
- Zelyra unifies data models, business logic, and user interfaces within a single cohesive language.
- What you mean is explicitly written in the code: no hidden magic, no implicit null values.
- The compiler reports detected syntax and type errors before execution. Runtime errors and cases outside its checks can still occur.
7. Exercises
- Level 1 (Easy): Modify the greeting program so that it prints your own first name and hometown.
- Level 2 (Medium): Write a second function
farewell(name: String) -> Stringthat constructs a parting message, and call both functions withinmain(). - Level 3 (Challenging): Identify three common bugs that frequently occur in dynamically typed languages due to typos or type mismatches (such as passing a number where text is expected), and explain how a static compiler prevents them prior to deployment.
8. Practical project task: Task Management
Throughout this book, we will step-by-step construct a robust, real-world Task Management system. We begin by laying the foundation:
Create a file named tasks_start.zyl that cleanly prints the system name and version number to the screen:
fn main() {
system_name = "Zelyra TaskManager"
app_version = "0.9.9"
print(system_name + " (Version " + app_version + ") started.")
}
9. Summary
- Zelyra is a statically typed, secure, and highly readable language designed for business logic, databases, and the web.
- Zelyra eliminates redundancies between database schemas, validation layers, and API definitions.
- Variables are immutable by default, enabling the compiler to guarantee stability and clarity.
10. Self-check review questions
- What fundamentally distinguishes a statically typed language from a dynamically typed language?
- Why is it advantageous to declare database table schemas directly within the programming language itself?
- Why are immutable values by default safer than mutable variables?
Chapter 2: How a Program Works
1. What will I learn in this chapter?
In this chapter, you will learn:
- How your written source code is systematically transformed into an executing program.
- The roles played by the Lexer, Parser, Type Checker, Verifier, and Interpreter in Zelyra.
- What precisely takes place when a Zelyra program boots up.
- The fundamental difference between syntax (form) and semantics (meaning).
- What an algorithm is and how the IPO model (Input, Process, Output) operates.
2. Why is this topic important?
Diagnosing and fixing programming bugs becomes intuitive once you understand at which stage of the toolchain an issue is detected. A syntax error indicates malformed text; a type error indicates contradictory logic; a runtime error indicates an unforeseen condition encountered during execution. Understanding this processing pipeline eliminates fear when facing compiler diagnostics.
3. Understandable explanation without unnecessary jargon
A computer processor directly understands only binary zeros and ones (machine code). When we humans author a text file containing Zelyra code (e.g., program.zyl), that code traverses several distinct stages:
Source Code (.zyl)
│
▼
[1. Lexer]: Breaks down raw text into words/tokens
│
▼
[2. Parser]: Builds an Abstract Syntax Tree (AST)
│
▼
[3. Type Checker]: Validates types, symbols, and capabilities
│
▼
[4. Verifier]: Formally checks loop invariants and contracts
│
▼
[5. Runtime / Interpreter]: Executes the verified instructions
- Syntax represents grammar and structure: Are brackets balanced? Are keywords spelled correctly?
- Semantics represents meaning: Writing
age = "twenty-five"is syntactically valid text, but if you attempt arithmetic operations with it, it is semantically meaningless. - Algorithm: A finite, step-by-step procedure designed to solve a specific problem.
- IPO Model: Input is received, processed according to strict rules, and output is produced.
4. Small, progressive examples
A straightforward algorithm calculating remaining days until a deadline:
fn days_until_target(target_day: Int, current_day: Int) -> Int {
remaining = target_day - current_day
return remaining
}
fn main() {
today = 10
due_date = 24
days = days_until_target(due_date, today)
print(days)
}
5. Typical errors and their causes
- Error: Opening a curly brace
{without providing a closing brace}. Cause: This is a syntax error (E-PARSE-001). The parser halts immediately because the syntax tree cannot be completed. - Error: Assigning a string to an integer variable:
age: Int = "20". Cause: This is a type error (E-TYPE-001). The parser understands the grammatical structure, but the type checker rejects the assignment.
6. Key takeaways
- The lexer scans characters, the parser understands structure, and the type checker validates semantic meaning.
- The earlier an error is intercepted (at compile time rather than in production), the safer and more cost-effective the software is.
- Every program adheres to the IPO model: take input, process using unambiguous logic, and deliver output.
7. Exercises
- Level 1 (Easy): Draw a flowchart illustrating the execution steps of
main()in the deadline example above. - Level 2 (Medium): Extend the
days_until_targetfunction with anifstatement: ifcurrent_day > target_day, return0. - Level 3 (Challenging): Explain in your own words why Zelyra statically validates code (
zelyra check) before running rather than blindly executing statements line by line.
8. Practical project task: Task Management
In our Task Management application, we must determine task urgency based on remaining days. If fewer than or equal to 3 days remain, the task is considered urgent:
fn is_urgent(remaining_days: Int) -> Bool {
return remaining_days <= 3
}
fn main() {
deadline_in_days = 2
urgent = is_urgent(deadline_in_days)
if urgent {
print("Warning: Task has high priority!")
} else {
print("Task is on schedule.")
}
}
9. Summary
- Code moves through a defined pipeline: lexing, parsing, type checking, verification, and execution.
- Zelyra guarantees that syntax and types are thoroughly validated before any program runs.
- Algorithms convert inputs into reliable outputs through clear, logical sequences.
10. Self-check review questions
- At what point in the toolchain is an unclosed quotation mark detected?
- What does the IPO (Input, Process, Output) model describe?
- Why does a statically typed system refuse to execute when text and numbers are improperly combined?
Chapter 3: Installing and Setting Up Zelyra
1. What will I learn in this chapter?
- System requirements for the Zelyra development environment on Linux, macOS, and Windows.
- Platform-specific Docker setup and verification with
docker compose version. - How to install Zelyra from source (
./install.sh/install.ps1) or the release archive (--release v1.2.39). - Complete compiler version query with
zelyra --versionand environment diagnosis withzelyra doctor. - The integrated, token-protected web setup assistant (
zelyra setup --web). - Typical permission and port conflicts (such as Docker socket permissions and automatic port selection).
2. Why is this topic important?
A smooth toolchain is the foundation of every successful project. If commands are missing or environment variables misconfigured, valuable time is lost. Zelyra provides a lean, user-local installer that requires no third-party package managers or root privileges. Additionally, the new web setup assistant visually guides beginners through database and container initialization.
3. Understandable explanation without unnecessary jargon
Zelyra requires neither Apache nor heavy third-party runtimes for simple programs. The Zelyra CLI (zelyra) is a single, highly optimized binary executable.
Choose the path that fits your operating system:
- Linux / macOS: Clone the official repository and execute
./install.sh(or install a release binary). Zelyra is installed user-locally to~/.local/bin. For Docker, use the official Linux engine instructions on Linux and Docker Desktop for Mac on macOS. - Windows: On Windows,
install.ps1for PowerShell andinstall.cmdfor the command prompt are provided. For complete MariaDB projects, Docker Desktop for Windows with enabled Compose support is recommended. - Platform Docker Checks: Zelyra deliberately does not install Docker itself or request root permissions. Before starting Compose services, verify your environment with
docker compose version. If Compose is missing or socket access is denied, Zelyra outputs clear, platform-specific hints.
4. Small, progressive examples
Step 1: Install Zelyra From source (Linux / macOS):
git clone https://github.com/sf1976/zelyra.git
cd zelyra
./install.sh
Or directly as a precompiled release archive without Rust:
./install.sh --release v1.2.39
On Windows (PowerShell):
git clone https://github.com/sf1976/zelyra.git
Set-Location zelyra
.\install.ps1 -Release v1.2.39
Step 2: Verify version and help
zelyra --version
zelyra --help
zelyra --version outputs the full compiler and package version (for example, zelyra 1.2.39). The language compatibility line remains 0.1.
Step 3: Verify Docker Compose (for MariaDB projects)
docker compose version
Step 4: Run system diagnostics
zelyra doctor
This command analyzes environment paths, ports, and tools. Use zelyra doctor --json for structured machine-readable output.
Step 5: Guided Web Setup Assistant (Optional) In any MariaDB project directory, start the visual assistant:
zelyra setup --web
Zelyra opens a local HTTP server on 127.0.0.1:3030 with a random, single-use security token. There, you can generate your .env configuration, start the MariaDB container, and apply database schemas with one click.
zelyra setup prepares the MariaDB environment. A minimal project created
with zelyra init without --mariadb runs directly with
zelyra run main.zyl and does not need this setup step. Paths are interpreted
literally: after mkdir test_a, ./test_a points to that directory, while
/test_a on Linux means a directory directly under the filesystem root. If
only the relative directory exists, the CLI suggests that correction.
5. Typical errors and their causes
- Error:
zelyra: command not foundCause: The directory~/.local/binis not yet in your$PATHvariable. Runexport PATH="$HOME/.local/bin:$PATH"or restart your terminal. - Error:
permission denied while trying to connect to the Docker daemon socketCause: On Linux systems, your user does not yet belong to thedockergroup. Runsudo usermod -aG docker $USERand log back in. Zelyra catches this and provides a clear hint. - Error: Default port 3000 or 3306 is occupied.
Cause: Another local service is using the port.
zelyra setupandzelyra newautomatically detect collisions and select the next free host port.
6. Key takeaways
- The Zelyra CLI bundles compiler, runner, form checker, web server, and setup assistant in a single tool.
docker compose versionandzelyra doctorverify the health of your environment at any time.- The release can be installed without Rust using
--release v1.2.39. zelyra setup --webprovides an intuitive, browser-based initial setup with a secure one-time token.
7. Exercises
- Level 1 (Easy): Run
zelyra --versionandzelyra doctorin your terminal and note the output. - Level 2 (Medium): Explore the help page with
zelyra check --helpand review the--format jsonoption. - Level 3 (Challenging): Set up file association in your text editor so that
.zylfiles are highlighted cleanly.
8. Practical project task: Prepare the task manager environment
Create a directory on your machine and ensure the Zelyra toolchain runs properly:
mkdir my-taskmanager
cd my-taskmanager
echo 'fn main() { print("TaskManager environment ready.") }' > test.zyl
zelyra check test.zyl
zelyra run test.zyl
9. Summary
- Zelyra is installed via
./install.sh, Windows PowerShell script, or precompiled release archives. - The command-line tool
zelyracontains all necessary functions. zelyra doctorensures everything is configured correctly.
10. Self-check review questions
- Which command displays the full compiler and package version?
- Why does Zelyra require no external web server like Apache for web services?
- What does
zelyra doctorcheck?
Chapter 4: The First Zelyra Project
1. What will I learn in this chapter?
- How to create a Zelyra project with
zelyra neworzelyra init(including--mariadb). - How a standard project folder is structured (
main.zyl,zelyra.toml,.env). - Automatic port selection (
ZELYRA_HOST_PORTandZELYRA_DB_HOST_PORT) when defaults are occupied. - How
zelyra setup --allandzelyra setup --webautomate the first-run workflow. - Optional feature switches (
[features]inzelyra.tomlor.env) and inspection withzelyra config. - When a program requires a
main()function and how to check, run, and format code.
2. Why is this topic important?
As soon as programs exceed ten lines, they belong in a clean project structure. A standardized directory structure ensures that configurations, database models, web routes, and business logic have a predictable location.
3. Understandable explanation without unnecessary jargon
With zelyra new <projectname>, you create a turnkey project:
main.zyl: The entry file. Here you define eitherfn main()or declare tables, web pages, and APIs.zelyra.toml: The durable project configuration (name, version, capabilities, and optional feature switches likeweb,api,crud,auth,audit)..env: Local secrets and ports not committed to version control (DATABASE_URL,ZELYRA_HOST_PORT,ZELYRA_DB_HOST_PORT).- Docker & MariaDB: With
--mariadb, Zelyra generatesDockerfile,docker-compose.mariadb.yml, and.env.example.
Automatic Port Selection: If default ports 3000 (web) or 3306 (MariaDB) are already in use, Zelyra automatically scans and assigns free ports in the newly generated .env.
Optional Feature Switches: You can enable or disable project surfaces in zelyra.toml or .env:
[features]
web = true
api = true
crud = true
auth = true
audit = true
If source code uses a disabled surface, the compiler reports E-FEATURE-001. You can inspect the effective configuration at any time without exposing secrets:
zelyra config main.zyl
zelyra config main.zyl --format=json
4. Small, progressive examples
Create a project (Minimal or MariaDB):
# Minimal script project:
zelyra new taskmanager --template minimal
cd taskmanager
# Or complete MariaDB web project:
zelyra new taskmanager-web --mariadb
cd taskmanager-web
First-time setup in one command:
zelyra setup --all
This creates a protected .env, starts the MariaDB containers, and applies the schema.
Inspect effective project configuration:
zelyra config main.zyl
Check and run project:
zelyra check main.zyl
zelyra run main.zyl
Format project code:
zelyra fmt main.zyl
Other documented CLI commands used during development:
zelyra verify main.zyl
zelyra db plan main.zyl
zelyra editor
zelyra verify runs the supported contract and invariant checks; Chapter 19 explains their scope. zelyra db plan shows proposed schema changes. Review the plan before applying changes with zelyra db apply; Chapter 24 describes the workflow. zelyra editor starts Zelyra Studio.
The current CLI reference lists neither a zelyra test subcommand nor separate zelyrac build or zelyra lsp programs. Write assertions with assert(...) in an executable .zyl file and run it with zelyra run; the reference does not document a dedicated test runner.
5. Typical errors and their causes
- Error: Creating files without the
.zylextension. Cause: The compiler strictly expects files ending in.zyl. - Error: Running a CLI program without
fn main(). Cause:zelyra runlooks forfn main(). In web services running withzelyra serve,main()is optional.
6. Key takeaways
zelyra newcreates a standardized project structure.- In
zelyra.toml, name, version, capabilities, and feature switches are managed. zelyra setup --allandzelyra setup --webstreamline MariaDB setup.zelyra fmtensures consistent formatting across the team.
7. Exercises
- Level 1 (Easy): Create a project
my_first_projectwithzelyra newand run it. - Level 2 (Medium): Run
zelyra config main.zyland inspect the active feature switches. - Level 3 (Challenging): Format an unformatted
.zylfile usingzelyra fmt main.zyl.
8. Practical project task: Initialize the Task Management project
Create the project that will accompany us throughout this book:
zelyra new zelyra-tasks --template minimal
cd zelyra-tasks
Add a menu to main.zyl:
fn show_menu() {
print("=================================")
print(" ZELYRA TASK MANAGEMENT ")
print("=================================")
print("1: List all tasks")
print("2: Create new task")
print("3: Exit")
}
fn main() {
show_menu()
}
Check with zelyra check main.zyl and run with zelyra run main.zyl.
9. Summary
- Zelyra projects have a clean structure of source code (
.zyl) and configuration (zelyra.toml). zelyra checkverifies correctness;zelyra runexecutes the program.zelyra fmtformats code according to standard conventions.
10. Self-check review questions
- What is the role of
zelyra.toml? - What happens if a default host port is occupied when creating a MariaDB project?
- When does a Zelyra program require a
main()function?
PART II – LANGUAGE FUNDAMENTALS
Chapter 5: Values and Data Types
1. What will I learn in this chapter?
In this chapter, you will learn:
- What values and data types are and why they form the backbone of reliable software.
- The fundamental numeric types:
Int,UInt,Float, andDecimal. - Text and character types:
StringandChar. - Boolean truth values (
Bool) and the empty type (Unit). - Date and time representations:
Timestamp,Date,Time, andDuration. - The difference between automatic type inference and explicit type annotations.
2. Why is this topic important?
In the real world, you cannot add apples to oranges. Without a type system, however, a computer would do precisely that: it would happily attempt to multiply a postal code by a monetary price, or interpret arbitrary binary gibberish as a calendar date. In Zelyra, the type system prevents such absurdities before code ever runs. A data type specifies exactly which values are valid and which operations are permitted on them.
3. Understandable explanation without unnecessary jargon
Every value in Zelyra possesses a definite type. You can either specify the type explicitly or let Zelyra infer it automatically based on the assigned expression:
fn main() {
# Explicit type annotation: Name followed by colon and type
count: Int = 10
# Automatic type inference: Zelyra immediately detects that this is a String
task_name = "Important Meeting"
print(task_name)
}
The core data types in Zelyra:
Int: 64-bit signed integers, e.g.,-5,0,42.UInt: Unsigned integers (>= 0), ideal for record IDs and positive counters.Float: Floating-point numbers for scientific calculations, e.g.,3.1415.Decimal: Fixed-point values created withdecimal("0.10"), with nine fractional digits. Base-10 arithmetic is supported; excess precision, overflow, and division by zero are rejected. Define business rounding rules for money explicitly.Bool: Boolean values representing truth. Exactly two states exist:trueorfalse.String: UTF-8 character text enclosed in double quotation marks:"Hello World".Char: Individual characters enclosed in single quotation marks:'A','z'.Unit: Represents "no meaningful value", analogous tovoidin other languages. When a function only performs side effects and returns nothing, its type isUnit.
4. Small, progressive examples
fn main() {
task_id: Int = 101
task_name: String = "Update server"
is_done: Bool = false
estimated_hours: Float = 2.5
hourly_rate: Float = 85.50
print(task_name)
print(is_done)
}
Zelyra strictly guards against type mismatches:
If you attempt to write task_id = "one hundred", the compiler rejects the assignment immediately with error E-TYPE-001.
5. Typical errors and their causes
- Error: Computing financial amounts using
Float. Cause: IEEE-754 floating-point numbers can introduce subtle inaccuracies such as0.1 + 0.2 = 0.30000000000000004. In Zelyra, always useDecimalfor financial logic. - Error: Enclosing a single character in double quotes when a
Charis expected. Cause:"A"is aString, whereas'A'is aChar.
6. Key takeaways
- Data types prevent invalid operations between incompatible kinds of information.
- For financial balances and currency: always use
Decimal, neverFloat. - Zelyra infers types accurately, but explicit type annotations clearly document design intent.
7. Exercises
- Level 1 (Easy): Declare three variables for your favorite book: title (
String), publication year (Int), and whether you have finished reading it (Bool). - Level 2 (Medium): Compute the total cost of a task from
estimated_hoursandhourly_rate, and print the result. - Level 3 (Challenging): Explain why an entity ID is typically better modeled as an
Intor a nominal typetype TaskId = Idrather than an arbitraryString.
8. Practical project task: Task Management
Extend our Task Management project in main.zyl by defining the typed attributes of an individual task:
fn main() {
task_id: Int = 1
task_name: String = "Verify database schema"
is_done: Bool = false
priority: Int = 1 # 1 = high, 2 = medium, 3 = low
print("Task #" + "1" + ": " + task_name)
if is_done {
print("Status: Done")
} else {
print("Status: Open (Priority: high urgency)")
}
}
9. Summary
- Zelyra provides a rich palette of primitive data types for numbers, text, and logic.
- Types can be explicitly annotated or automatically inferred by the compiler.
- Strict compile-time type verification catches logical bugs during development.
10. Self-check review questions
- Why should
Decimalalways be chosen overFloatfor currency calculations? - What is the fundamental syntactic and semantic difference between
"Z"and'Z'? - Which two distinct values can a
Boolhold?
Chapter 6: Variables and Immutability
1. What will I learn in this chapter?
In this chapter, you will learn:
- What a variable represents in computer memory.
- Why variables in Zelyra are immutable by default.
- How to declare mutable variables explicitly using the
mutablekeyword. - Variable scopes and lifetimes within code blocks.
- Why immutability makes software dramatically more robust and predictable.
2. Why is this topic important?
One of the most frequent causes of subtle bugs in languages such as JavaScript, Python, or C++ is uncontrolled mutability: one function quietly mutates a shared variable, causing an entirely unrelated module to crash.
Zelyra adheres to a clear principle: Constants are the baseline. If a value is intended to change during runtime execution, you must consciously signal that behavior using mutable.
3. Understandable explanation without unnecessary jargon
Think of a variable as a labeled box stored in computer memory:
-
Immutable Binding (Default):
fn main() { task_name = "Tax Return" print(task_name) }You place the text
"Tax Return"into the box labeledtask_nameand seal it. Nobody is permitted to replace the content of that box. Any code reading the box can rely on its contents remaining invariant. -
Mutable Variable (
mutable):fn main() { mutable counter = 0 counter = counter + 1 print(counter) }Here, the box remains unsealed. You are allowed to remove the current value and replace it with a new one.
Scope and Lifetimes:
Variables exist exclusively within the block { ... } in which they are declared. When execution exits that block, Zelyra automatically reclaims the variable. This frees memory and eliminates naming conflicts.
4. Small, progressive examples
Example 1: Immutability prevents accidental overwrites
fn main() {
project = "Zelyra Core"
# project = "New Project" // ERROR: Compiler blocks assignment to immutable variable!
print(project)
}
Example 2: When mutable is appropriate (counters and accumulators)
fn main() {
mutable open_tasks = 5
print(open_tasks)
# One task has been completed:
open_tasks = open_tasks - 1
print(open_tasks)
}
Example 3: Variable Scopes
fn main() {
scope_var = "Global in main"
if true {
local_var = "Only visible inside if"
print(local_var)
print(scope_var)
}
# print(local_var) // ERROR: `local_var` no longer exists outside the block!
}
5. Typical errors and their causes
- Error: Attempting to reassign a variable declared without
mutable. Cause: Zelyra issuescannot assign to immutable variable. If a value must change over time, declare it asmutable name = .... - Error: Declaring every variable as
mutableout of habit. Cause: Poor design style. Restrictmutableto locations where values truly need to evolve (e.g., loop counters or accumulators).
6. Key takeaways
- In Zelyra, variables are immutable by default.
- When a value is intended to change,
mutablemust be explicitly declared. - Variables exist exclusively within their declared block
{ ... }.
7. Exercises
- Level 1 (Easy): Create an immutable variable holding your username and print it.
- Level 2 (Medium): Create
mutable score = 100, deduct 15 points, add 30 points, and print the intermediate values. - Level 3 (Challenging): Explain why immutability provides significant safety benefits in concurrent applications running multiple operations in parallel.
8. Practical project task: Task Management
In our Task Management application, we want to count how many tasks remain to be completed:
fn main() {
mutable open_count = 3
print("Start: Tasks to complete: ")
print(open_count)
# First task completed:
open_count = open_count - 1
print("Intermediate count: Still open:")
print(open_count)
# Second task completed:
open_count = open_count - 1
print("Final count: Still open:")
print(open_count)
}
9. Summary
- Immutability is Zelyra's default, preventing unintended side effects.
- Mutable variables must be explicitly declared with
mutable. - Curly braces define the scope and lifetime of all variables.
10. Self-check review questions
- What occurs if you assign a new value to a variable declared without
mutable? - Why does immutability serve as a fundamental security and stability feature?
- Can a variable declared inside an inner block be accessed outside of that block?
Chapter 7: Operators and Expressions
1. What will I learn in this chapter?
In this chapter, you will learn:
- What an operator is and what constitutes an expression.
- Arithmetic operators for numbers:
+,-,*,/,%. - Comparison operators:
==,!=,<,<=,>,>=. - Logical operators:
&&(AND),||(OR), and!(NOT). - Operator precedence and clean expression grouping using parentheses.
2. Why is this topic important?
Software does not merely store static values; it computes results, compares facts, and combines logical conditions. An operator is the mechanism that transforms raw data into actionable knowledge. Mastering operators enables you to translate complex business rules into crisp, reliable expressions.
3. Understandable explanation without unnecessary jargon
- An expression is any segment of source code that evaluates to a concrete value. For instance,
5 + 3is an expression that yields8. - An operator is the symbolic token that dictates the operation (such as
+or==).
Arithmetic Operators:
+: Addition (also used for string concatenation and array merging).-: Subtraction (or numerical negation:-x).*: Multiplication./: Division.%: Modulo (remainder of integer division, e.g.,7 % 3 == 1).
Comparison Operators (always evaluate to Bool):
==: Is equal to?!=: Is not equal to?</<=: Less than / Less than or equal to?>/>=: Greater than / Greater than or equal to?
Logical Operators:
&&(AND): Evaluates totrueonly if both sides aretrue(true && true == true).||(OR): Evaluates totrueif at least one side istrue.!(NOT): Inverts a truth value (!true == false).
4. Small, progressive examples
fn main() {
# Arithmetic
base_time = 60
buffer_time = 15
total_time = base_time + buffer_time
print(total_time)
# Comparison
is_long = total_time > 60
print(is_long)
# Logical combination
has_buffer = buffer_time > 0
is_critical = total_time > 120 && !has_buffer
print(is_critical)
}
5. Typical errors and their causes
- Error: Using a single equals sign
=inside a comparison check:if x = 5. Cause:=is the assignment operator! Equality comparisons in Zelyra strictly require the double equals==. - Error: Division by zero (
x / 0). Cause: Results in a runtime abort. Always validate that the divisor is non-zero before dividing.
6. Key takeaways
- Assign values using
=, compare values using==. - Arithmetic expressions respect operator precedence; use parentheses whenever clarity is needed.
&&demands that both operands are true, while||succeeds when either operand is true.
7. Exercises
- Level 1 (Easy): Use
==and%to verify whether10 % 2equals0(even-number check). - Level 2 (Medium): Write an expression testing whether an integer
agefalls within the range18to65(inclusive). - Level 3 (Challenging): Write an expression for a discount rule: a customer qualifies for a discount if they are a VIP (
is_vip == true) OR if their order total exceeds 100 AND they are not a new customer.
8. Practical project task: Task Management
In our Task Management project, we need to determine whether a task is overdue and requires immediate attention:
fn main() {
days_remaining = -2
is_done = false
is_blocked = false
# A task is overdue if days < 0 and it is not yet completed
is_overdue = days_remaining < 0 && !is_done
# High urgency: Overdue and not blocked by other tasks
requires_attention = is_overdue && !is_blocked
print("Task overdue?")
print(is_overdue)
print("Needs immediate intervention?")
print(requires_attention)
}
9. Summary
- Operators combine individual values into expressive statements.
- Comparison operators yield boolean values (
Bool). - Logical operators (
&&,||,!) allow the expression of intricate business rules.
10. Self-check review questions
- What is the difference between
=and==? - What value does the expression
5 > 3 && 2 > 10evaluate to? - What does the
%modulo operator compute?
Chapter 8: Input and Output
1. What will I learn in this chapter?
In this chapter, you will learn:
- How to display information reliably on the console using
print(). - How to read a line interactively from the terminal with
read_console(). - How text and variables are formatted via string concatenation.
- How Zelyra receives input through parameters, environment variables, files, the terminal, and web routes.
- Why Zelyra requires explicit
Capabilitiesfor external system interactions.
2. Why is this topic important?
A program that cannot receive input or communicate output is useless to users. Input and output (I/O) connect the computational logic of your code with the real world. Because interactions with the keyboard, filesystem, or network introduce security risks, Zelyra regulates these operations far more strictly than older languages.
3. Understandable explanation without unnecessary jargon
- Output: The built-in
print(value)instruction accepts numbers, booleans, strings, and structured objects, printing them directly to standard output (stdout). - Formatting: Multiple strings and values can be combined using the plus operator
+. - Input in Zelyra:
Programs receive input through parameters, environment variables, files, web requests, or interactively from the terminal:
- Function Parameters: Input data is passed directly during invocation.
- Environment Variables:
env("MY_KEY")reads configuration parameters from the operating system. - Files:
read_text("input.txt")ingests persisted data. - Web Requests: Form declarations (
form) and route endpoints (page "/user/{id}") process browser input. - Terminal:
read_console("Prompt: ")displays a prompt and reads one line. It returnsString?:Nonemeans end of input, while an empty line isSome("").
Terminal access is a capability. The calling function must declare uses Console. Projects with a [capabilities] section must also set
console = true; new project templates leave this grant disabled by default.
Console input is intended for zelyra run. Web applications should use typed
requests and forms instead.
Input is not hidden; do not use read_console() for passwords or other
secrets.
fn main() uses Console {
date = read_console("Date: ")
match date {
Some(value) => {
print("Entered: " + value)
}
None => {
print("No input.")
}
}
}
[capabilities]
console = true
4. Small, progressive examples
Formatting Output:
fn main() {
task_name = "Release 1.0"
percentage = 80
print("Progress for " + task_name + ":")
print(percentage)
}
Input via Function Parameters and Environment Variables:
fn process_task(task_name: String, priority_level: Int) {
print("Processing: " + task_name)
print("Priority level:")
print(priority_level)
}
fn main() uses Environment {
mode = env("APP_MODE")
print("Current mode:")
print(mode)
process_task("Create backup", 1)
}
5. Typical errors and their causes
- Error: Attempting to concatenate an integer directly with a string using
+:"Value: " + 5. Cause: Zelyra requires type compatibility.5is anInt, not aString. Print the number separately viaprint(5)or use conversion helpers. - Error: Calling
env()without declaringuses Environmenton the enclosing function. Cause: Zelyra's capability security model requires functions interacting with the host environment to declare their capabilities explicitly.
6. Key takeaways
print()reliably outputs values and text to the console.- Inputs enter Zelyra programs via parameters, environment variables, files, the terminal, or web routes.
- External system access requires declaring the corresponding capability (such as
uses Environment).
7. Exercises
- Level 1 (Easy): Print a formatted contact card (name, job title, email address) using multiple
print()statements. - Level 2 (Medium): Write a function
print_task_entry(id: Int, task_name: String, is_done: Bool)that prints all three fields cleanly formatted. - Level 3 (Challenging): Write a CLI program using
read_console()and explain when terminal input is useful and when structured web requests are a better fit.
8. Practical project task: Task Management
Construct an output formatting utility for our Task Management application:
fn print_header(section_name: String) {
print("----------------------------------------")
print("SECTION: " + section_name)
print("----------------------------------------")
}
fn print_task_entry(entry_num: Int, task_name: String, is_done: Bool) {
print("Task No: ")
print(entry_num)
print("Name: " + task_name)
if is_done {
print("Status: [X] DONE")
} else {
print("Status: [ ] OPEN")
}
print("----------------------------------------")
}
fn main() {
print_header("TODAY'S TASKS")
print_task_entry(1, "Work through the handbook", true)
print_task_entry(2, "Practice Zelyra examples", false)
}
9. Summary
- Console output is performed cleanly and reliably using
print(). - External environment interactions are protected by explicit capabilities.
- Inputs are received through parameters, files, environment variables,
read_console(), or web requests.
10. Self-check review questions
- Which built-in function is used in Zelyra for console text output?
- Why does accessing
env()mandate theuses Environmentdeclaration? - Which input channels are typical for server-based Zelyra software?
Chapter 9: Decisions with Conditions
1. What will I learn in this chapter?
In this chapter, you will learn:
- How programs execute conditional branches using
ifandelse. - How to evaluate multi-way alternatives using
else if. - How to combine multiple conditions logically.
- How to author clean and exhaustive pattern matches with
match. - Common logic pitfalls with nested conditions and how to structure code cleanly.
2. Why is this topic important?
Without conditional branching, software would be as rigid as a music box cylinder: it would replay the exact same steps every single time. Software becomes truly capable when it dynamically reacts to changing circumstances: Is the user authenticated? Has the deadline passed? Is the balance sufficient? With if and match, you grant your code the ability to make decisions.
3. Understandable explanation without unnecessary jargon
-
if/else: Evaluates a boolean condition. If it evaluates totrue, the first block executes; otherwise, theelseblock runs:fn check_result(score: Int) { if score >= 50 { print("Passed!") } else { print("Unfortunately failed.") } } fn main() { check_result(75) } -
match: When evaluating a value against multiple known cases,matchis far cleaner and more readable than sprawlingif / else ifcascades. In Zelyra, the compiler verifies that all possible cases are covered (exhaustiveness):fn show_status(status_code: Int) { match status_code { 1 => { print("New") } 2 => { print("In Progress") } 3 => { print("Done") } _ => { print("Unknown Status") } } } fn main() { show_status(2) }The underscore
_is the wildcard pattern: it matches all remaining values that were not explicitly listed.
4. Small, progressive examples
Simple Branching:
fn main() {
open_tasks = 0
if open_tasks == 0 {
print("Great! All tasks are completed.")
} else {
print("There are still open tasks.")
}
}
Multi-Branch Decision with match:
fn evaluate_priority(level: Int) -> String {
match level {
1 => { return "VERY URGENT" }
2 => { return "NORMAL" }
3 => { return "LOW" }
_ => { return "UNKNOWN" }
}
}
fn main() {
print(evaluate_priority(1))
print(evaluate_priority(2))
}
Pattern Matching Status Codes:
fn status_description(code: Int) -> String {
match code {
0 => { return "Draft" }
1 => { return "Active" }
2 => { return "Archived" }
_ => { return "Invalid" }
}
}
fn main() {
print(status_description(1))
print(status_description(99))
}
5. Typical errors and their causes
- Error: Omitting the wildcard
_branch when matching over numbers. Cause: Numbers can take virtually infinite values. If you only handle1and2, the compiler signals anon-exhaustive match. - Error: Excessive nesting (the arrow anti-pattern:
if { if { if { ... } } }). Cause: Hard to read and debug. Flatten deep nesting using early returns ormatch.
6. Key takeaways
ifbranches based on a boolean value (Bool).matchchecks values against patterns and enforces exhaustiveness.- The wildcard
_safely catches all unlisted cases.
7. Exercises
- Level 1 (Easy): Write a function
is_adult(age: Int) -> Boolthat checks ifage >= 18. - Level 2 (Medium): Write a function
days_in_month(month: Int) -> Intusingmatchthat returns the number of days for months 1 through 12 (standard 28 days for February). - Level 3 (Challenging): Build a validation function that checks whether a password meets length criteria (at least 8 characters) and is not equal to
"12345678".
8. Practical project task: Task Management
Implement automated traffic-light priority classification for our Task Management system:
fn calculate_traffic_light(remaining_days: Int, is_finished: Bool) -> String {
if is_finished {
return "GREEN: Task is completed"
} else {
if remaining_days < 0 {
return "RED: Deadline has passed!"
} else {
if remaining_days <= 2 {
return "YELLOW: Due soon, please handle"
} else {
return "BLUE: On schedule"
}
}
}
}
fn main() {
print(calculate_traffic_light(5, false))
print(calculate_traffic_light(1, false))
print(calculate_traffic_light(-1, false))
print(calculate_traffic_light(-1, true))
}
9. Summary
ifandelsedirect control flow based on boolean predicates.matchfacilitates clean, compiler-verified pattern branching.- Well-structured conditional logic keeps business rules clear and maintainable.
10. Self-check review questions
- When is
matchpreferable to anif / else ifchain? - What role does the wildcard pattern
_perform inmatch? - Why does Zelyra mandate that pattern matches be exhaustive?
Chapter 10: Repetition and Loops
1. What will I learn in this chapter?
In this chapter, you will learn:
- Why loops are indispensable in data processing and computation.
- Counted and condition-driven loops:
while condition { ... }. - Iteration across collections and arrays:
for element in array { ... }. - Unconditional loops:
loop { ... }. - Fine-grained loop control using
break(exit) andcontinue(skip). - Loop invariants (
invariant), enabling formal mathematical proof of loop correctness.
2. Why is this topic important?
Computers were invented to perform repetitive, tedious tasks swiftly and without human fatigue or error. If you need to retrieve, inspect, and render 1,000 records from a database, you never write the processing logic a thousand times—you write it once inside a loop. Zelyra provides robust looping constructs along with formal invariant verification.
3. Understandable explanation without unnecessary jargon
Zelyra supports three primary loop forms:
-
for ... in: The safest and most concise way to traverse a collection of elements. The loop iterates over each element in sequence and halts automatically:fn main() { for num in [1, 2, 3] { print(num) } } -
while condition: Continues execution as long as the condition evaluates totrue. Useful when the exact number of iterations is not known in advance. -
loop: A continuous loop that runs indefinitely until an innerbreakstatement is encountered.
Loop Control Commands:
break: Terminates the loop immediately. Execution resumes after the loop block.continue: Halts the current iteration and jumps directly to the next cycle.
Loop Invariants (invariant):
An invariant is a logical proposition that must remain true before, during, and after every single iteration of a loop (e.g., invariant { counter >= 0 }). The Zelyra verification engine (zelyra verify) checks loop invariants mathematically to prove that the loop cannot enter invalid states!
4. Small, progressive examples
Example 1: for ... in across an Array
fn main() {
tasks = ["Plan", "Program", "Test", "Deploy"]
for t in tasks {
print("Step: " + t)
}
}
Example 2: while with Counter and invariant
fn main() {
mutable counter = 1
while counter <= 3
invariant { counter >= 1 }
{
print(counter)
counter = counter + 1
}
}
Example 3: Targeted use of break and continue
fn main() {
for num in [1, 2, 3, 4, 5] {
if num == 2 {
# Skip the number 2:
continue
}
if num == 4 {
# Abort completely at 4:
break
}
print(num)
}
}
Output: Prints 1 and 3!
5. Typical errors and their causes
- Error: Forgetting to increment the loop counter inside a
whileloop (counter = counter + 1). Cause: The loop condition remains permanently true, resulting in an infinite loop that freezes the program. - Error: Off-by-one boundary errors with manual index counters.
Cause: Whenever possible, use
for element in arrayto avoid boundary mistakes completely.
6. Key takeaways
- Prefer
for ... infor collections and arrays. breakexits the loop immediately;continueproceeds to the next iteration.- Invariants document and mathematically prove the correctness and safety of loops.
7. Exercises
- Level 1 (Easy): Use a
whileloop to print numbers counting down from 10 to 1. - Level 2 (Medium): Compute the sum of all numbers in the array
[10, 20, 30, 40]using aforloop. - Level 3 (Challenging): Search an array of numbers for the target value
42. If found, print"Found!"and exit the loop immediately viabreak. If the number is not present, print"Not found"at the end.
8. Practical project task: Task Management
Let us apply loops to our Task Management project: we inspect the task list, mark completed tasks, and count total completions:
fn main() {
tasks = ["Write specification", "Setup database", "Write tests"]
mutable completed_count = 0
print("Reviewing task list:")
for t in tasks {
if t == "Write specification" {
print("[X] " + t)
completed_count = completed_count + 1
} else {
print("[ ] " + t)
}
}
print("Total completed tasks:")
print(completed_count)
}
9. Summary
- Loops automate monotonous, repetitive processing tasks.
for ... insafely iterates over arrays;whileiterates conditionally.breakandcontinueprovide exact control over iteration flow.invariantenables formal mathematical verification withzelyra verify.
10. Self-check review questions
- What is the difference between
breakandcontinue? - Why is
for ... insafer when iterating over arrays than a manualwhileloop? - What role does an
invariantplay in program verification?
PART III – STRUCTURING PROGRAMS
Chapter 11: Functions and Procedures
1. What will I learn in this chapter?
In this chapter, you will learn:
- What functions are and how they make programs structured, readable, and reusable.
- How to pass parameters and declare return types using
-> Type. - What pure functions (functions without side effects) are and why they are invaluable.
- What procedures are (functions without a return value or with type
Unit) that execute actions. - How Zelyra fosters clear naming conventions and clean programming style.
2. Why is this topic important?
If you repeat every calculation and console output ten times in different places across your codebase, the dreaded "spaghetti code" phenomenon quickly takes over. When a business rule changes (for example, how a task deadline is calculated), you would have to locate and adjust all ten places—inevitably introducing bugs. Functions group a logical operation under a descriptive name: you write it once, test it thoroughly, and reuse it anywhere as often as needed.
3. Understandable explanation without unnecessary jargon
Think of a function like a kitchen appliance:
- You put ingredients in (parameters).
- The appliance processes the ingredients according to a fixed recipe (function body).
- At the end, a finished dish comes out (return value).
In Zelyra, you define functions using the fn keyword:
fn add(a: Int, b: Int) -> Int {
return a + b
}
fn main() {
print(add(2, 3))
}
If a function has no return value because it merely performs an action (such as printing a line of text to the console), its return type is Unit (and can simply be omitted):
fn print_separator() {
print("----------------------------------------")
}
fn main() {
print_separator()
}
4. Small, progressive examples
Example 1: Calculation with a return value
fn calculate_days_until_deadline(today_day: Int, deadline_day: Int) -> Int {
return deadline_day - today_day
}
fn main() {
days = calculate_days_until_deadline(10, 18)
print(days)
}
Example 2: Text formatting in a helper function
fn format_task(id_text: String, text: String, done: Bool) -> String {
mutable status_mark = "[ ]"
if done {
status_mark = "[X]"
}
return status_mark + " #" + id_text + ": " + text
}
fn main() {
formatted = format_task("1", "Read documentation", true)
print(formatted)
}
Example 3: Procedure for header output
fn show_header(user_name: String) {
print("Logged in as: " + user_name)
print("========================================")
}
fn main() {
show_header("Alice")
}
5. Typical errors and their causes
- Error: Forgetting a
returnstatement when a return type was declared. Cause: When a return type is specified, Zelyra strictly requires a matching value on every execution path. - Error: Wrong argument order during a function call.
Cause: Zelyra enforces strict parameter type checking. If the first parameter is an
Int, you cannot pass aString. - Error: Attempting to concatenate
StringandIntdirectly with+. Cause: In Zelyra, the+operator either concatenates two strings or adds two numbers of the same type. Convert or format values as strings or print them separately viaprint().
6. Key takeaways
- A function should fulfill exactly one single, clearly defined task.
- Pure functions always produce identical outputs for identical inputs and produce no side effects.
- Function names should be descriptive verbs or verb phrases (e.g.,
calculate_difference,format_task).
7. Exercises
- Level 1 (Easy): Write a function
double_val(num: Int) -> Intthat returns twice the given number. - Level 2 (Medium): Write a function
is_urgent(days: Int) -> Boolthat returnstrueif fewer than 3 days remain. - Level 3 (Challenging): Write a function
status_symbol(done: Bool) -> Stringthat returns"[OK]"if done and"[OPEN]"otherwise.
8. Practical project task: Task Management – Modularizing Task Display
Write a Zelyra program that formats and displays three tasks with an ID string, task name, and completion status using a reusable formatting function:
fn format_entry(id_text: String, name: String, done: Bool) -> String {
mutable status_mark = "[OPEN]"
if done {
status_mark = "[OK]"
}
return status_mark + " Task " + id_text + ": " + name
}
fn main() {
print(format_entry("1", "Collect mail", true))
print(format_entry("2", "Pay invoice", false))
print(format_entry("3", "Create backup", false))
}
9. Summary
- Functions divide programs into logical, manageable building blocks.
- Parameters and return types are strictly typed in Zelyra.
- Pure functions keep code maintainable, testable, and resilient against unexpected bugs.
10. Self-check review questions
- What type does a function have that does not return any value?
- Why are pure functions much easier to test than functions with global side effects?
- What does the Zelyra compiler check during every function call?
Chapter 12: Contracts and Preconditions (Design by Contract)
1. What will I learn in this chapter?
In this chapter, you will learn:
- What the concept of Design by Contract means.
- How to define preconditions using
requires. - How to formulate postconditions using
ensuresand theresultkeyword. - How loop invariants are established with
invariant. - Why contracts are superior to defensive cascades of
ifstatements.
2. Why is this topic important?
Bugs frequently occur because developers make unstated assumptions that are documented nowhere: "This function must never be called with a negative number!" When someone calls that function six months later with -1, the application fails unexpectedly. In traditional languages, developers either write endless defensive if checks or rely on wishful thinking and outdated comments. Zelyra elevates contracts to first-class language constructs: preconditions and postconditions are declared directly on function signatures and guaranteed to be enforced.
3. Understandable explanation without unnecessary jargon
A contract in Zelyra functions like a formal legal agreement between the caller and the function:
requires(Precondition): The caller promises to supply only arguments that satisfy the condition (e.g.,value > 0).ensures(Postcondition): In return, the function guarantees that its computed outcome (result) satisfies specific properties (e.g.,result >= 0).
If a contract is violated, Zelyra halts immediately with a clear contract error (E-CONTRACT-*), identifying exactly which party breached the agreement.
4. Small, progressive examples
Example 1: Precondition with requires
fn divide(numerator: Int, denominator: Int) -> Int
requires { denominator != 0 }
{
return numerator / denominator
}
fn main() {
result_val = divide(100, 4)
print(result_val)
}
Example 2: Combining precondition and postcondition
fn adjust_priority(current_level: Int, delta: Int) -> Int
requires { current_level >= 1 && delta >= 0 }
ensures { result >= 1 }
{
new_level = current_level + delta
return new_level
}
fn main() {
p = adjust_priority(2, 1)
print(p)
}
Example 3: Loop invariant
fn count_up_to(limit: Int) -> Int
requires { limit >= 0 }
ensures { result == limit }
{
mutable i = 0
while i < limit
invariant { i >= 0 }
{
i = i + 1
}
return i
}
fn main() {
print(count_up_to(5))
}
5. Typical errors and their causes
- Error: Attempting to validate raw user input via
requires. Cause: Contracts are internal safeguards against programmer error and logic defects, not input sanitizers for untrusted user inputs. For user input, use form validation rules (form) or theResulttype. - Error: Writing an
ensuresclause that the function implementation logically cannot satisfy. Cause: If the function returns-5but theensuresclause demands{ result >= 0 }, the postcondition check will fail.
6. Key takeaways
requiresprotects the function against invalid inputs provided by the caller.ensuresguarantees the caller a valid outcome referenced viaresult.- Contracts transform implicit mental assumptions into verifiable, living specifications.
7. Exercises
- Level 1 (Easy): Write a function
naive_sqrt(x: Int) -> Intwith the preconditionrequires { x >= 0 }. - Level 2 (Medium): Write a function
clamp_val(val: Int, min_val: Int, max_val: Int) -> Intwith preconditions requiringmin_val <= max_valand appropriate postconditions. - Level 3 (Challenging): Secure a function
percentage(part: Int, total_count: Int) -> Intensuring division by zero never occurs and the result is strictly between 0 and 100.
8. Practical project task: Task Management – Task Progress Calculator
Write a contract-guaranteed function for task progress in task management:
fn calculate_progress(completed: Int, total_count: Int) -> Int
requires { total_count > 0 && completed >= 0 && completed <= total_count }
ensures { result >= 0 && result <= 100 }
{
return (completed * 100) / total_count
}
fn main() {
ratio = calculate_progress(3, 4)
print(ratio)
}
9. Summary
- Contracts (
requires,ensures) document and enforce programming assumptions at runtime. resultinensuresrefers to the computed return value of the function.- Loop invariants verify the integrity of the loop state across every iteration.
10. Self-check review questions
- When is a
requirescondition evaluated: before or after function execution? - What does the keyword
resultrefer to in a contract? - Why does a contract not replace an HTML form validation pattern?
Chapter 13: Collections, Lists, and Dictionaries (Arrays & Maps)
1. What will I learn in this chapter?
In this chapter, you will learn:
- How to store multiple homogeneous values in an array (
Type[]). - The most important built-in collection functions:
len,append,contains,first,last. - How to iterate through collections using
for .. in. - How associative key-value collections are declared and used with
Map<Key, Value>. - The most important map functions:
get,put,contains,keys,values. - Why Zelyra's
get()always returns a safeOptionto eliminate null pointer crashes.
2. Why is this topic important?
An application capable of storing only isolated values would be practically useless. In real-world software, we almost always work with collections: lists of tasks, emails, or table rows fetched from a database.
Sometimes we need to access items by sequential order (lists or arrays). Very often, however, we need to look up data directly using a unique key—such as user preferences by user ID, localized translation dictionaries, or telephone country codes. For these use cases, Zelyra provides typed dictionaries (Map).
3. Understandable explanation without unnecessary jargon
Part A: Arrays – The Ordered List
An array is like a compartmentalized organizer or a pillbox with numbered slots: every slot has a fixed position and holds exactly one element. In Zelyra, all elements within the same container must have the identical type (Int[], String[], etc.):
fn main() {
numbers: Int[] = [10, 20, 30, 40]
print(len(numbers))
}
Zelyra provides powerful built-in functions for arrays:
len(items): Returns the number of elements.append(items, value): Returns a new array with the element appended.first(items): Returns the first element as anOption.last(items): Returns the last element as anOption.contains(items, value): Checks whether an element is present (Bool).
Part B: Maps – Associative Key-Value Dictionaries
A Map maps each unique key (Key) to exactly one corresponding value (Value).
- Type notation:
Map<KeyType, ValueType>, for exampleMap<String, Int>orMap<String, String>. - Literal syntax:
Map { "key": value } - Key types: The documented types are
Int,UInt,String,Bool,Char, andTimestamp. JSON requires String keys.
fn main() {
country_codes: Map<String, Int> = Map {
"de": 49
"at": 43
"ch": 41
}
}
Key operations on maps:
get(map, key): Looks up a key. Because a key might not exist,get()always returns a safeOption<Value>(Some(val)orNone)—never a null pointer crash!put(map, key, value): Inserts a key-value pair or updates an existing key. Following Zelyra's functional immutability principles,put()returns a new, updated Map.contains(map, key): Returnstrueif the key exists in the map.keys(map): Returns all keys as a typed array (KeyType[]).values(map): Returns all values as a typed array (ValueType[]).
4. Small, progressive examples
Example 1: Creating and iterating over an array
fn main() {
task_ids: Int[] = [101, 102, 103, 104]
for id in task_ids {
print(id)
}
}
Example 2: Appending elements to an array
fn main() {
mutable items: Int[] = [1, 2, 3]
items = append(items, 4)
print(len(items))
}
Example 3: Creating, updating, and querying a Map
fn main() {
prices: Map<String, Int> = Map {
"Coffee": 3
"Tea": 2
}
# Add a new item
mutable current_prices = put(prices, "Cake", 4)
# Update existing price
current_prices = put(current_prices, "Coffee", 4)
# Safely query using match
match get(current_prices, "Coffee") {
Some(price) => {
print("Coffee price in Euro:")
print(price)
}
None => {
print("Item not found.")
}
}
# Output all keys
print(keys(current_prices))
}
Example 4: Checking key existence in a Map
fn main() {
settings: Map<String, Bool> = Map {
"dark_mode": true
"notifications": false
}
if contains(settings, "dark_mode") {
print("Dark mode setting is explicitly configured.")
}
}
5. Typical errors and their causes
- Error: Attempting to mix different types within a single array or map (e.g.,
[1, "Hello"]). Cause: Zelyra is strictly homogeneous. All elements in an array, and all keys and values in a map, must conform to their declared types. - Error: Treating
map.get("key")as a raw value without unwrapping theOption. Cause: Zelyra guarantees compile-time safety. Because a key might not be in the dictionary, the compiler forces you to handleSomeandNoneusingmatchor default values. - Error: Using non-scalar types (such as arrays) as Map keys.
Cause: Map keys must use a supported comparable type (
Int,UInt,String,Bool,Char,Timestamp). JSON supports only String keys.
6. Key takeaways
- Arrays in Zelyra are strictly homogeneous:
Int[],String[],Bool[]. - Iteration is performed safely and cleanly with
for element in collection { ... }. Map<Key, Value>stores unique key-value associations.get(map, key)always returns anOption(SomeorNone), preventing runtime exceptions.put(map, key, value)functionally produces a new, updated dictionary.
7. Exercises
- Level 1 (Easy): Create an array containing three strings and print each element using a
forloop. - Level 2 (Medium): Create a
Map<String, Int>with three product names and their prices. Query both an existing and a non-existing item usingget()andmatch. - Level 3 (Challenging): Write a function
count_words(words: String[]) -> Map<String, Int>that counts how often each word occurs in a list and returns the result as a Map.
8. Practical project task: Task Management – Priority Registry
Build a priority lookup table for our task management system:
fn show_priority(priorities: Map<String, Int>, task_name: String) {
match get(priorities, task_name) {
Some(level) => {
print("Priority for " + task_name + ":")
print(level)
}
None => {
print("No priority registered for: " + task_name)
}
}
}
fn main() {
prio_map: Map<String, Int> = Map {
"Database migration": 1
"Tweak CSS styles": 3
"Write documentation": 2
}
show_priority(prio_map, "Database migration")
show_priority(prio_map, "Coffee break")
}
9. Summary
- Arrays (
Type[]) store ordered sequences of homogeneous elements. - Maps (
Map<Key, Value>) store key-value dictionaries with safeget(),put(),contains(),keys(), andvalues(). - Both collection types are fully type-safe and integrate seamlessly with Zelyra's
Optiontype system.
10. Self-check review questions
- What type does the expression
["A", "B", "C"]evaluate to? - Why does
append([1, 2], "Three")fail at compile time? - How do you efficiently check whether a value exists inside an array?
Chapter 14: Creating Custom Data Types (Records & Tables)
1. What will I learn in this chapter?
In this chapter, you will learn:
- How to create your own domain-specific data types.
- What nominal types (
type TaskId = Id) are and how they prevent accidental ID confusion. - What enum and sum types mean, and which variants are currently built into Zelyra.
- How records are defined in Zelyra as a
tablewith attributes (id: Id primary auto). - Why strong typing fundamentally elevates software quality.
2. Why is this topic important?
In fragile software architectures, almost everything is represented as a primitive integer or string (an anti-pattern known as "Primitive Obsession"). If a function expects verify(user_id: Int, task_id: Int) and you accidentally swap the arguments, a compiler using raw Int types will never notice—and your application might assign sensitive records to the wrong user. With distinct custom types, Zelyra distinguishes between a UserId and a TaskId right at compile time.
3. Understandable explanation without unnecessary jargon
Custom data types allow you to model real-world concepts accurately:
-
Domain Type Aliases:
type TaskId = Id fn main() { print("Type alias TaskId active") }The alias gives the ID a domain name. It has no separate runtime identity; business rules need validation within the actual supported checking scope.
-
Enum and sum types:
An enum describes a value that must be one of a fixed set of named variants. A variant may also carry a value; this is why enums are often called sum types. The general concept can be sketched as
enum State { Draft, Active, Archived }, but this is conceptual notation, not supported Zelyra source syntax. User-defined enum declarations are outside the current documented language surface. Zelyra does provide the built-in sum typesOption<T>andResult<T, E>; usematchto handle their alternatives. -
Tables as Structured Data Types:
table tasks { id: Id primary auto description: String required done: Bool } fn main() { print("Table schema defined") }This schema does not merely specify a database table; in Zelyra, it simultaneously declares the in-memory data type for a task!
4. Small, progressive examples
Example 1: Defining a custom type alias for IDs
type TaskId = Id
fn main() {
print("Type alias defined successfully")
}
Example 2: Defining a data model as a table
table tasks {
id: Id primary auto
description: String required
done: Bool
}
fn main() {
print("Table schema for tasks defined")
}
Example 3: Working with typed attributes in functions
table projects {
id: Id primary auto
name: String required
active: Bool
}
fn show_project_status(p_name: String, p_active: Bool) {
mutable status_text = "Paused"
if p_active {
status_text = "Active"
}
print("Project: " + p_name + " [" + status_text + "]")
}
fn main() {
show_project_status("Web Portal", true)
}
5. Typical errors and their causes
- Error: Naming a table field with a reserved keyword such as
title,list, ordetail. Cause: In Zelyra, these identifiers are reserved for queries and UI views. Use descriptive names likename,description, orsubjectinstead. - Error: Declaring the primary key with
primary keyinstead ofprimary. Cause: In Zelyra, the standard column specification isid: Id primary auto.
6. Key takeaways
- Custom data types reflect your business domain and eliminate accidental parameter swapping.
tabledeclarations in Zelyra act as both database schemas and native language types.- Reserved words (
title,list,action,field, etc.) must never be used as column or variable names.
7. Exercises
- Level 1 (Easy): Create a nominal type alias
type UserId = Id. - Level 2 (Medium): Model a table
categorieswithid: Id primary autoandcategory_name: String required. - Level 3 (Challenging): Model a table
notesthat links to a task via a fieldtask_id: Id.
8. Practical project task: Task Management – Core Task Schema
Define the complete core Zelyra schema for our task management system:
table tasks {
id: Id primary auto
name: String required
description: String
priority: Int
is_done: Bool
}
fn main() {
print("Core schema of task management active.")
}
9. Summary
- Custom types give raw data unambiguous meaning and context.
tableseamlessly unifies schema declaration and static type modeling in the language.- Static typing catches logical mismatches while you write your code.
10. Self-check review questions
- What advantage does
type TaskId = Idoffer compared to a simpleInt? - Why must table fields in Zelyra always possess an explicit type?
- Which words must be avoided when naming fields?
Chapter 15: Modules and Code Organization
1. What will I learn in this chapter?
In this chapter, you will learn:
- How a standard Zelyra project is structured on disk.
- The central role of the
zelyra.tomlproject configuration manifest. - How to separate code into logical concerns (schema, business logic, views).
- How the Zelyra CLI verifies and builds cohesive multi-file projects.
- The current development status and roadmap for modules and imports.
2. Why is this topic important?
When starting out, it is tempting to write an entire program in a single file. However, as your task management application expands—incorporating database tables, dozens of business functions, web forms, and validation rules—navigating a monolithic 2,000-line file becomes unmanageable. Professional software engineering demands structuring code so that every team member can locate schemas, business logic, and UI definitions instantly.
3. Understandable explanation without unnecessary jargon
A professional Zelyra project follows a clean, standardized directory layout:
zelyra.toml: The birth certificate and configuration manifest of the project. It declares the package name, version, and security capabilities.src/schema.zyl: Contains all table declarations (table) and custom domain types.src/main.zyl: Contains the primary entry point (fn main()) and application orchestration.
[package]
name = "task_planner"
version = "0.1.0"
authors = ["Developer <dev@example.com>"]
[capabilities]
filesystem = false
database = true
Language compatibility note: Zelyra supports explicit project-relative imports such as import "src/models.zyl" as models. These imports connect source files inside one project; they do not provide a package registry or dynamic runtime imports. The dependency graph and deployment closure remain experimental and may be incomplete. The language compatibility line remains 0.1.
4. Small, progressive examples
Example 1: A clean main entry point
fn main() {
print("Zelyra task system ready.")
}
Example 2: Separating logic functions
fn format_system_status(status_text: String) -> String {
return "[STATUS] " + status_text
}
fn main() {
print(format_system_status("Database connected"))
}
Example 3: Declaring capabilities in the project manifest
In zelyra.toml, you explicitly configure which system resources the project is allowed to request. If your application accesses a database, database = true must be enabled under [capabilities].
5. Typical errors and their causes
- Error: Attempting to use an
importsyntax from other languages such as Python, JavaScript, or Rust. Cause: Zelyra supports project-relative imports such asimport "src/models.zyl" as models, as well as a separate signed registry-package workflow. Use the module name for declarations imported from local project files; see the package guide for registry package commands and limits. - Error: Deleting
zelyra.tomlor executing CLI commands from outside the project root directory. Cause: Commands likezelyra runlook forzelyra.tomlin the current working directory to configure capabilities and compilation paths.
6. Key takeaways
zelyra.tomlgoverns project metadata, dependencies, and capability security policies.- Maintain a clean separation of concerns: data models (
table), business logic (fn), and UI views. - A clean project layout prevents accidental coupling and speeds up teamwork.
7. Exercises
- Level 1 (Easy): Generate a new project skeleton using
zelyra new task_appand explore the generated files. - Level 2 (Medium): Update
zelyra.tomlwith a project description and increment the version number to0.3.0. - Level 3 (Challenging): Write an application structured into three separate functions handling initialization, business processing, and output reporting.
8. Practical project task: Task Management – Project Structure
Establish the task management architecture with the following implementation in main.zyl:
table tasks {
id: Id primary auto
name: String required
done: Bool
}
fn start_system() {
print("========================================")
print(" TASK MANAGER SUCCESSFULLY STARTED")
print("========================================")
}
fn main() {
start_system()
}
9. Summary
- Projects are configured and secured via
zelyra.toml. - Zelyra evaluates project files as a cohesive, strongly typed whole.
- Modular architecture ensures sustainable scalability and seamless team collaboration.
10. Self-check review questions
- Which file contains the metadata and capability definitions of a Zelyra project?
- Why is decoupling the data schema from execution logic recommended?
- How does the Zelyra CLI compile and verify an entire project at once?
PART IV – SAFETY AND ERROR HANDLING
Chapter 16: Error Types and Their Causes
1. What will I learn in this chapter?
In this chapter, you will learn:
- The four primary categories of errors encountered in software development.
- The differences between syntax errors, type errors, contract/capability errors, and logic errors.
- How the Zelyra compiler guides you with precise diagnostics detailing line numbers, column locations, and actionable hints.
- Why early compile-time error detection saves substantial time and prevents costly bugs in production.
2. Why is this topic important?
Errors are a natural, everyday part of programming. Even seasoned software engineers encounter dozens of compiler errors every day. The difference between struggling and proficient developers is not making zero mistakes, but being able to read and understand error diagnostics quickly. Zelyra was intentionally designed so that the vast majority of errors are caught right at compile time (zelyra check) long before your application ever reaches an end user.
3. Understandable explanation without unnecessary jargon
In software engineering, we distinguish between four fundamental classes of errors:
- Syntax Errors (
E-LEX-*,E-PARSE-*): You violated the grammar of the programming language—analogous to a punctuation or spelling mistake in human language (such as forgetting a closing curly bracket). - Type Errors (
E-TYPE-*): The grammar is syntactically valid, but the data types do not fit together (such as attempting to add text to an integer). - Contract and Capability Errors (
E-CONTRACT-*,E-CAP-*): A pre-agreed precondition was breached, or a function attempted to access restricted system resources (such as reading the filesystem without permission). - Logic Errors: The program compiles and executes without crashing, but produces incorrect results because the algorithm was wrong (e.g., adding a discount instead of subtracting it).
4. Small, progressive examples
Example 1: Typical syntax error (and reading compiler output) When you write clean, matching brackets:
# Syntactically correct:
fn correct_brackets() {
print("All brackets are closed.")
}
fn main() {
correct_brackets()
}
Example 2: Capability error (Security capability verification) If a function attempts to access restricted system resources without declaring the required permission, Zelyra stops immediately:
fn read_file(path: String) -> String
uses FileSystem
{
return read_text(path)
}
fn main() {
print("File access properly declared.")
}
Example 3: Exposing logic errors through contracts
fn calculate_discount_price(original: Int, discount: Int) -> Int
requires { original >= 0 && discount >= 0 && discount <= original }
ensures { result <= original }
{
return original - discount
}
fn main() {
price = calculate_discount_price(100, 20)
print(price)
}
5. Typical errors and their causes
- Error: Ignoring compiler diagnostics without looking at the line and column indicators. Cause: Zelyra points you directly to the exact source location where the issue originated.
- Error: Using
+between strings and numbers. Cause: Zelyra strictly enforces static type safety. Format values as strings or print them as separate arguments.
6. Key takeaways
- A compiler error is not a failure—it is an automated, instant code review.
- The earlier an error is caught (compile time vs. runtime), the safer and cheaper your application is to maintain.
- Contracts (
requires,ensures) turn subtle, creeping logic bugs into immediate, reproducible contract violations.
7. Exercises
- Level 1 (Easy): Intentionally trigger a syntax error (e.g., omitting a bracket) and examine the output of
zelyra check. - Level 2 (Medium): Write a function with a type mismatch error and resolve it following the compiler's diagnostic hints.
- Level 3 (Challenging): Write an age verification function equipped with contracts that immediately rejects invalid ages (e.g., negative numbers).
8. Practical project task: Task Management – Fault-Tolerant Task Duration Logging
Write a verified function for task management that prevents negative hours from being recorded:
fn log_hours(previous_hours: Int, new_hours: Int) -> Int
requires { previous_hours >= 0 && new_hours >= 0 }
ensures { result >= previous_hours }
{
return previous_hours + new_hours
}
fn main() {
total_hours = log_hours(5, 3)
print(total_hours)
}
9. Summary
- Zelyra categorizes errors into syntax, type, contract/capability, and logic categories.
- Static checking and contract enforcement catch defects before code ever deploys.
10. Self-check review questions
- What error code family (
E-...) is generated when a keyword is misspelled or missing? - Why can a program compile completely without errors and still calculate incorrect values?
- How do formal contracts help uncover hidden logical reasoning bugs?
Chapter 17: Errors as Values – The Result Pattern
1. What will I learn in this chapter?
In this chapter, you will learn:
- What the
Resultpattern is and why Zelyra avoids uncontrolled exceptions. - The two variants:
Ok(value)for success andErr(message)for failure. - How to deconstruct results safely using
match. - Why treating errors as regular values makes your software transparent, predictable, and crash-proof.
2. Why is this topic important?
In many traditional languages (like Java, Python, or PHP), functions signal failure by throwing exceptions. If an exception goes uncaught anywhere in the call stack, the entire web request crashes with an HTTP 500 error. In Zelyra, there are no uncontrolled runtime exceptions: whenever an operation can fail (such as a missing record or an invalid input), it explicitly returns a Result<T, E>. The compiler guarantees that you handle both success and error outcomes before your code can run.
3. Understandable explanation without unnecessary jargon
Imagine receiving a delivery parcel:
- If the courier delivers the package successfully, you open it and find the item inside:
Ok(content). - If the address does not exist, you receive a return slip stating the reason:
Err("Address unknown").
A parcel never "explodes" in your face—you simply inspect what arrived:
fn divide_safe(a: Int, b: Int) -> Result<Int, String> {
if b == 0 {
return Err("Division by zero is not allowed")
}
return Ok(a / b)
}
fn main() {
print("Safe division defined.")
}
4. Small, progressive examples
Example 1: Defining and handling a function with Result
fn check_priority(level: Int) -> Result<Int, String> {
if level < 1 {
return Err("Priority too low (minimum 1)")
}
if level > 3 {
return Err("Priority too high (maximum 3)")
}
return Ok(level)
}
fn main() {
res = check_priority(2)
match res {
Ok(level) => {
print("Valid priority:")
print(level)
}
Err(err_msg) => {
print(err_msg)
}
}
}
Example 2: Handling the error case
fn get_balance(pin: Int) -> Result<Int, String> {
if pin != 1234 {
return Err("Incorrect PIN!")
}
return Ok(500)
}
fn main() {
attempt = get_balance(9999)
match attempt {
Ok(amount) => {
print(amount)
}
Err(err_msg) => {
print("Denied: " + err_msg)
}
}
}
Example 3: Safe value validation
fn check_task_name_length(task_name: String) -> Result<String, String> {
if task_name == "" {
return Err("Task name must not be empty.")
}
return Ok(task_name)
}
fn main() {
outcome = check_task_name_length("Project Report")
match outcome {
Ok(t) => {
print("Valid name: " + t)
}
Err(e) => {
print("Error: " + e)
}
}
}
5. Typical errors and their causes
- Error: Attempting to access the inner value directly without unpacking it using
match. Cause:Result<T, E>is a container type. You must unwrap it through pattern matching. - Error: Omitting one of the two branches (
OkorErr) in amatchexpression. Cause: Zelyra enforces exhaustive pattern matching to ensure unhandled failure paths are impossible.
6. Key takeaways
Result<T, E>makes error handling explicit by treating errors as regular return values.Ok(v)represents success carrying a value, whileErr(e)represents an explained failure.- Pattern matching with
matchrequires handling all outcomes, preventing silent uncaught crashes.
7. Exercises
- Level 1 (Easy): Write a function
check_even(num: Int) -> Result<Int, String>that returnsOk(num)if divisible by 2, orErr("Odd")otherwise. - Level 2 (Medium): Write a function
validate_username(name: String) -> Result<String, String>that rejects empty strings or"admin". - Level 3 (Challenging): Implement an arithmetic division function that validates inputs and returns twice the result on success.
8. Practical project task: Task Management – Validating Task Creation
Create a robust validation function for adding new tasks:
fn create_task_checked(name: String, priority: Int) -> Result<String, String> {
if name == "" {
return Err("Name must not be empty!")
}
if priority < 1 {
return Err("Priority must be at least 1!")
}
return Ok("Task [" + name + "] created successfully.")
}
fn main() {
item1 = create_task_checked("Complete documentation", 1)
match item1 {
Ok(msg) => {
print(msg)
}
Err(err_msg) => {
print("Error: " + err_msg)
}
}
item2 = create_task_checked("", 0)
match item2 {
Ok(msg) => {
print(msg)
}
Err(err_msg) => {
print("Error: " + err_msg)
}
}
}
9. Summary
- The
Resultpattern replaces uncontrolled exceptions with strictly typed values. - Zelyra guarantees at compile time that failure cases cannot be silently ignored.
10. Self-check review questions
- What do
TandErepresent inResult<T, E>? - Why do unhandled errors in Zelyra never cause unexpected application crashes?
- How do you safely extract the payload from a
Resultvalue?
Chapter 18: Nothingness Does Not Exist – Working Safely with Option
1. What will I learn in this chapter?
In this chapter, you will learn:
- Why Tony Hoare described the invention of
nullas his "billion-dollar mistake." - How Zelyra completely eliminates
nulland replaces it with the type-safeOption<T>(shorthandT?). - How to wrap values in
Some(value)and represent their absence withNone. - How built-in collection operations like
firstandlastleverageOption.
2. Why is this topic important?
In languages like JavaScript, Java, PHP, or C, the value null lurks everywhere. Whenever code inadvertently calls a method or reads an attribute on a null reference, the entire application crashes with a dreaded NullPointerException or TypeError: Cannot read properties of null. In Zelyra, null simply does not exist. Standard values are strictly guaranteed to exist. Whenever a value may legitimately be absent, it must be explicitly typed as an Option.
3. Understandable explanation without unnecessary jargon
Think of an Option as a gift box:
- The box may contain a present:
Some("Smartphone"). - Or the box is completely empty:
None.
You cannot accidentally use the gift without first unpacking the box using match:
fn find_task_by_id(id: Int) -> Option<String> {
if id == 42 {
return Some("Set up server")
}
return None
}
fn main() {
print("Task search defined.")
}
4. Small, progressive examples
Example 1: Creating and matching an Option
fn main() {
found: Option<String> = Some("Zelyra 0.1 Handbook")
match found {
Some(item_name) => {
print("Found: " + item_name)
}
None => {
print("No match found")
}
}
}
Example 2: Safe array inspection with first() and last()
Accessing an empty collection in Zelyra never crashes your software—it cleanly returns None:
fn main() {
my_numbers: Int[] = [100, 200, 300]
first_item = first(my_numbers)
match first_item {
Some(val) => {
print("First value:")
print(val)
}
None => {
print("The list is empty!")
}
}
}
Example 3: Providing fallback defaults with Option
fn task_name_or_default(opt_name: Option<String>) -> String {
match opt_name {
Some(t) => {
return t
}
None => {
return "Untitled"
}
}
}
fn main() {
print(task_name_or_default(Some("Important Meeting")))
print(task_name_or_default(None))
}
5. Typical errors and their causes
- Error: Assuming that an
Option<String>can be used directly as aString. Cause: The box is not the gift. You must unpack it using pattern matching (match). - Error: Attempting to assign
Noneto a standard non-optional variable. Cause: Regular variables are guaranteed to always contain a concrete value.
6. Key takeaways
- Zelyra completely eliminates
null, renderingNullPointerExceptioncrashes impossible. - Whenever a value may be absent, declare it as
Option<T>orT?. Some(x)wraps a present value, whileNoneexplicitly indicates absence.
7. Exercises
- Level 1 (Easy): Write a function
find_partner(name: String) -> Option<String>that returnsSome("Juliet")when given"Romeo", andNoneotherwise. - Level 2 (Medium): Retrieve the last element of an integer array using
last()and print its value or an empty-list notice. - Level 3 (Challenging): Write a search function that searches an array of IDs and returns the matching index position as
Option<Int>.
8. Practical project task: Task Management – Safe Task Detail Lookup
Implement safe task description retrieval for our task management system:
fn find_task_description(id: Int) -> Option<String> {
if id == 1 {
return Some("Create database schema for Zelyra")
}
if id == 2 {
return Some("Design web interface")
}
return None
}
fn main() {
lookup1 = find_task_description(1)
match lookup1 {
Some(text) => {
print("Task 1: " + text)
}
None => {
print("Task 1 not found!")
}
}
lookup99 = find_task_description(99)
match lookup99 {
Some(text) => {
print("Task 99: " + text)
}
None => {
print("Task 99 not found!")
}
}
}
9. Summary
Option<T>shields applications from the disastrous errors caused by unexpected null references.- Zelyra statically enforces that every possible
Nonecondition is addressed before execution.
10. Self-check review questions
- Why is there no
nullkeyword or concept in Zelyra? - What is the fundamental difference between
StringandOption<String>? - Which two branches must always be present when pattern matching an
Option?
Chapter 19: Testing and Quality Assurance
1. What will I learn in this chapter?
In this chapter, you will learn:
- Why automated testing forms the indispensable backbone of reliable, sustainable software.
- How Zelyra's verification tool (
zelyra verify) formally proves function contracts. - How to design and structure assertion-based test suites.
- The philosophy of Test-Driven Development (TDD).
- How assertions and project-specific test runners work, and which test commands the current CLI provides.
2. Why is this topic important?
Manual testing—clicking around in a browser or manually calling functions—is tedious, inconsistent, and error-prone. As software grows in complexity, even minor modifications in one subsystem inevitably introduce regressions in seemingly unrelated modules. Automated tests act as an automated safety net, proving that existing business logic continues to function according to specification days, months, and years into the future.
3. Understandable explanation without unnecessary jargon
Quality assurance in Zelyra rests on two powerful pillars:
- Formal Contract Verification with
zelyra verify: The compiler mathematically checks whether preconditions and postconditions (requires,ensures) hold under all possible valid inputs. - Automated Assertion Testing:
Test routines supply sample inputs to business functions and compare actual results with expected outcomes. The built-in
assert(condition, message)check reports a failure when its condition is false.
The current CLI reference does not list a zelyra test subcommand. Keep
project-specific test routines in an executable .zyl file and run them with
zelyra run <file.zyl>. Use zelyra check before execution and
zelyra verify for the supported formal contract checks.
4. Small, progressive examples
Example 1: A direct assertion
fn main() {
result = 6 * 7
assert(result == 42, "Expected the multiplication result to be 42")
}
Example 2: A lightweight assertion helper
fn assert_test(test_name: String, condition: Bool) {
if condition {
print("[PASS] " + test_name)
} else {
print("[FAIL] " + test_name)
}
}
fn double_val(x: Int) -> Int {
return x * 2
}
fn main() {
assert_test("Double 5 is 10", double_val(5) == 10)
assert_test("Double 0 is 0", double_val(0) == 0)
}
Example 3: Testing Option return values
fn is_adult(age: Int) -> Option<Bool> {
if age < 0 {
return None
}
return Some(age >= 18)
}
fn main() {
test1 = is_adult(20)
match test1 {
Some(ok) => {
if ok {
print("[PASS] 20 years is of age")
} else {
print("[FAIL] Unexpected state")
}
}
None => {
print("[FAIL] Invalid age")
}
}
}
Example 4: Protecting logic with formal contract verification
fn calculate_overtime(hours: Int, regular_hours: Int) -> Int
requires { hours >= 0 && regular_hours >= 0 }
ensures { result >= 0 }
{
if hours > regular_hours {
return hours - regular_hours
}
return 0
}
fn main() {
print(calculate_overtime(45, 40))
}
5. Typical errors and their causes
- Error: Testing only the "happy path" and ignoring boundary conditions (zero values, negative numbers, empty collections). Cause: Most real-world regressions occur at the extreme boundaries of input ranges.
- Error: Forgetting to run automated checks after refactoring code.
Cause: Always make
zelyra checkand your test suites an automatic habit whenever you edit source files.
6. Key takeaways
- Untested code is broken code that has simply not failed in public yet.
zelyra verifymathematically evaluates function contracts directly at the language level.- High-value tests deliberately target edge cases, boundaries, and failure paths.
7. Exercises
- Level 1 (Easy): Write three test cases for a function
add(a: Int, b: Int) -> Int. - Level 2 (Medium): Write assertion tests for the
Option-based lookup function from Chapter 18. - Level 3 (Challenging): Implement a comprehensive test suite for a function that checks if a task name satisfies business rules (non-empty, minimum length).
8. Practical project task: Task Management – Test Runner for Task Business Logic
Construct a lightweight test suite for the core business rules of the task management application:
fn test_case(description: String, ok: Bool) {
if ok {
print("OK: " + description)
} else {
print("ERROR: " + description)
}
}
fn matches_priority(p: Int) -> Bool {
return p == 1
}
fn main() {
print("Starting test suite: Task logic")
test_case("Priority 1 matches", matches_priority(1) == true)
test_case("Priority 2 is ignored", matches_priority(2) == false)
print("Test suite completed.")
}
9. Summary
- Automated verification safeguards software reliability across long development cycles.
- Combining
zelyra verifywith assertion test suites provides comprehensive confidence in business logic.
10. Self-check review questions
- What does the term "regression" mean in software development?
- What responsibility does the CLI command
zelyra verifyperform? - Why are boundary values (e.g., 0 or maximum capacity) critical in test design?
PART V – PRACTICAL DATA PROCESSING
Chapter 20: Working with Files
1. What will I learn in this chapter?
In this chapter, you will learn:
- How to create, read, list, and delete text files using Zelyra.
- Why file operations in Zelyra strictly require the
uses FileSystemcapability. - The most important standard library functions:
read_text,write_text,delete_file, andlist_dir. - How to persist task lists to disk as files.
2. Why is this topic important?
Variables in memory are volatile; they vanish the moment your program exits or the operating system restarts. To preserve data permanently—such as export files, application settings, or audit logs—it must be written to non-volatile storage. At the same time, arbitrary file system access represents a significant security liability. Zelyra protects your system by enforcing fine-grained capabilities: functions cannot touch the disk unless they explicitly declare the appropriate permissions upfront.
3. Understandable explanation without unnecessary jargon
Think of the file system as a secure document archive:
- When you want to file a document, you write text into it (
write_text). - When you want to inspect a file, you read its contents (
read_text). - You can only enter the physical archive room if you hold the keycard:
uses FileSystem.
fn save_note(path: String, content: String)
uses FileSystem
{
write_text(path, content)
}
fn main() uses FileSystem {
save_note("note.txt", "Shopping list: Milk, Bread")
print("Note saved.")
}
4. Small, progressive examples
Example 1: Writing and reading text files
fn file_workflow() uses FileSystem {
path = "tasks_export.txt"
write_text(path, "Task 1: Learn Zelyra")
text = read_text(path)
print("Read content: " + text)
}
fn main() uses FileSystem {
file_workflow()
}
Example 2: Cleaning up files with delete_file
fn cleanup_file(path: String) uses FileSystem {
delete_file(path)
print("File deleted.")
}
fn main() uses FileSystem {
write_text("temp.txt", "Short-lived")
cleanup_file("temp.txt")
}
Example 3: Listing directory contents
fn show_files(dir_path: String) uses FileSystem {
files = list_dir(dir_path)
for file_name in files {
print("Found file: " + file_name)
}
}
fn main() uses FileSystem {
show_files(".")
}
5. Typical errors and their causes
- Error: Calling
read_textorwrite_textwithout declaringuses FileSystem. Cause: Zelyra's capability system (E-CAP-001) prevents any unauthorized access to secondary storage. - Error: Forgetting that
main()must also declareuses FileSystemwhen calling functions that access the file system. Cause: Capabilities propagate up the call stack; a caller cannot invoke an effectful function without having that permission itself.
6. Key takeaways
- Any function interacting with the file system must declare
uses FileSystem. write_textcreates a new file or completely overwrites an existing one.read_textreturns the entire contents of a file as aString.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Write a program that writes a greeting message into
hello.txt. - Level 2 (Medium): Write a function that reads a file and displays its content on the console.
- Level 3 (Challenging): Implement a simple logging function that appends status messages line by line and persists them to disk.
8. Practical project task: Task Management
Export the pending tasks of our application into a text file:
fn export_tasks(path: String) uses FileSystem {
content = "[ ] Complete documentation\n[OK] Install Zelyra compiler"
write_text(path, content)
print("Tasks successfully exported to " + path + ".")
}
fn main() uses FileSystem {
export_tasks("tasks_today.txt")
}
9. Summary
- Zelyra provides clean, safe, and efficient primitives for file input and output.
- The capability-based security model prevents unauthorized file manipulation and data leaks.
10. Self-check review questions
- Which capability must a function request before it can invoke
write_text? - Why does Zelyra require
uses FileSystemonmain()even ifmainonly delegates to another function? - Which standard library function returns a list of all filenames inside a directory?
Chapter 21: Date, Time, Randomness, and Structured Data
1. What will I learn in this chapter?
In this chapter, you will learn:
- How to retrieve current timestamps with
now()(uses Clock). - How to generate pseudo-random values using
random_int(min, max)(uses Random). - How to serialize data structures into universal JSON with
json_encode. - How to parse JSON strings back into strongly typed structures using
json_decode<T>.
2. Why is this topic important?
Virtually every real-world application requires handling temporal events: When was a task created? When does a deadline expire? Similarly, structured formats such as JSON serve as the lingua franca across the web—from REST APIs to configuration files. Zelyra incorporates time, randomness, and JSON natively into the language, providing strict type-safety without external dependencies.
3. Understandable explanation without unnecessary jargon
- Timestamps: Calling
now()returns the precise current system time as aTimestamp. Because reading the system clock is an external, non-deterministic effect, your function must declareuses Clock. - Random Numbers: Calling
random_int(1, 10)generates a pseudo-random integer between 1 and 10, requiringuses Random. - JSON: JSON is a lightweight text format easily parsed by machines and humans. With
json_encode, you turn Zelyra arrays and records into a serialized string ready for network transfer or file storage.
fn show_time() uses Clock {
current_time = now()
print("Current system time recorded")
}
fn main() uses Clock {
show_time()
}
4. Small, progressive examples
Example 1: Deadlines and time tracking with Clock
fn log_creation(task_name: String) uses Clock {
created_at = now()
print("Task created: " + task_name)
}
fn main() uses Clock {
log_creation("Patch server")
}
Example 2: Generating random ticket numbers
fn generate_ticket_number() -> Int uses Random {
return random_int(1000, 9999)
}
fn main() uses Random {
ticket = generate_ticket_number()
print("Your ticket code:")
print(ticket)
}
Example 3: Exporting data as JSON
fn export_ids_as_json(ids: Int[]) -> String {
return json_encode(ids)
}
fn main() {
ids: Int[] = [101, 102, 103]
json_text = export_ids_as_json(ids)
print("JSON output: " + json_text)
}
5. Typical errors and their causes
- Error: Calling
now()without declaringuses Clockin the function signature. Cause: Clock queries are inherently non-deterministic; Zelyra requires explicit capability declarations. - Error: Passing malformed JSON text into
json_decode. Cause: Zelyra strictly validates JSON input; schema mismatches or syntax errors result in aResultfailure.
6. Key takeaways
now()yields the current timestamp and requiresuses Clock.random_intproduces random integers and demandsuses Random.json_encodeconverts Zelyra data structures into standard JSON strings.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Generate a random integer between 1 and 6 (simulating a dice roll) and print it.
- Level 2 (Medium): Write a function that serializes an array of integer status codes into JSON.
- Level 3 (Challenging): Combine
uses Clockanduses FileSystemto write a timestamped log entry intolog.txt.
8. Practical project task: Task Management
Create a JSON snapshot of current task IDs and persist it to disk:
fn save_snapshot(file_name: String, ids: Int[])
uses Clock, FileSystem
{
json_data = json_encode(ids)
write_text(file_name, json_data)
print("Snapshot saved successfully.")
}
fn main() uses Clock, FileSystem {
current_ids: Int[] = [1, 2, 5, 8]
save_snapshot("tasks_snapshot.json", current_ids)
}
9. Summary
- Zelyra includes native, type-safe support for timestamps, randomness, and JSON data exchange.
- Effect capabilities (
Clock,Random) guarantee auditability and safety across your codebase.
10. Self-check review questions
- Which capability must a function declare to call
now()? - Why does Zelyra enforce the
uses Randompermission for generating random numbers? - What standard format does
json_encodeproduce?
Chapter 22: Concurrency and Background Tasks
1. What will I learn in this chapter?
In this chapter, you will learn:
- What concurrency is and when tasks should be executed simultaneously.
- How Zelyra's structured
parallelblock works:parallel { a = await ...; b = await ... }. - Why Zelyra avoids uncontrolled raw threads and callback hell.
- How deterministic concurrency keeps your application performant and free of race conditions.
2. Why is this topic important?
Modern CPUs and servers feature multiple cores. When an application needs to generate independent reports or query several remote APIs, executing them sequentially one after another wastes time. Running tasks in parallel allows programs to complete in a fraction of the time. However, in many languages concurrency introduces insidious bugs such as race conditions and deadlocks. Zelyra eliminates these dangers through structured, deterministic concurrency.
3. Understandable explanation without unnecessary jargon
Imagine a commercial restaurant kitchen:
- If the chef fries the steak first, then fries the potatoes, and finally washes the salad, the steak gets cold.
- A skilled chef begins all three tasks simultaneously and waits until all three dishes are ready to be served.
In Zelyra, you achieve this using the parallel block paired with await:
fn calculate_part_1() -> Int {
return 40
}
fn calculate_part_2() -> Int {
return 60
}
fn main() {
parallel {
result_1 = await calculate_part_1()
result_2 = await calculate_part_2()
}
print("Both subtasks completed in parallel.")
}
4. Small, progressive examples
Example 1: Parallel sub-computations
fn compute_statistics() -> String {
return "Statistics computed"
}
fn load_archive() -> String {
return "Archive loaded"
}
fn main() {
parallel {
stats = await compute_statistics()
archive = await load_archive()
}
print("Parallel loading successful.")
}
Example 2: Independent data retrieval
fn fetch_sum_a() -> Int {
return 100
}
fn fetch_sum_b() -> Int {
return 250
}
fn main() {
parallel {
val_a = await fetch_sum_a()
val_b = await fetch_sum_b()
}
print("Sums calculated.")
}
5. Typical errors and their causes
- Error: Using
awaitoutside of aparallelblock. Cause: Zelyra strictly restrictsawaitto the body of aparallel { ... }block. - Error: Placing general statements or complex branching inside a
parallelblock. Cause: Aparallelblock is dedicated to concurrent evaluation; its statements must conform to theidentifier = await call()assignment pattern.
6. Key takeaways
parallel { ... }executes independent operations concurrently.- Every line in a
parallelblock adheres tovariable = await expression(). - Structured concurrency guarantees completion and prevents dangling background processes.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Define two simple arithmetic functions and execute them inside a
parallelblock. - Level 2 (Medium): Create two functions that each produce a string message, and evaluate both concurrently.
- Level 3 (Challenging): Simulate checking two project validation conditions in parallel before initializing a workflow.
8. Practical project task: Task Management
Accelerate application startup by fetching user profiles and task lists simultaneously:
fn load_user_profile() -> String {
return "Profile: Developer"
}
fn load_task_list() -> String {
return "5 tasks loaded"
}
fn main() {
print("Starting parallel retrieval...")
parallel {
profile = await load_user_profile()
tasks = await load_task_list()
}
print("Dashboard ready.")
}
9. Summary
- Concurrency in Zelyra is structured, deterministic, and protected against race conditions.
- The
parallelconstruct neatly unifies asynchronous computations in a concise block.
10. Self-check review questions
- Where in a Zelyra program is the
awaitkeyword permitted? - What specific syntax must statements inside a
parallelblock follow? - What is the primary benefit of parallel execution over sequential evaluation?
PART VI – DATABASES WITH ZELYRA
Chapter 23: Why Zelyra Understands Databases Directly
1. What will I learn in this chapter?
In this chapter, you will learn:
- Why the integration between programming languages and relational databases has traditionally been prone to subtle bugs.
- What the object-relational impedance mismatch problem is and why ORMs struggle with it.
- How Zelyra integrates relational databases as first-class citizens directly into the language.
- How Zelyra verifies SQL statements for syntax and type correctness at compile time.
2. Why is this topic important?
In nearly all popular web frameworks (such as PHP/Laravel, Python/Django, or Node/TypeORM), an awkward divide exists: developers compose SQL strings or rely on heavyweight object-relational mapping (ORM) abstractions. Typos in column names—such as user.emaiil—often remain undetected until an end user encounters an HTTP 500 error in production. Zelyra eliminates this hazard at the root: if an SQL query does not match the declared table schema, the compiler rejects the build immediately.
3. Understandable explanation without unnecessary jargon
In Zelyra, you define your database configuration directly in your source code using the database keyword:
database main {
engine: mariadb
database: "tasks_db"
}
fn main() {
print("Database configuration initialized.")
}
When querying data, you write authentic SQL—yet the compiler knows every existing table and column:
- Writing
SELECT id, description FROM tasksis completely valid. - Writing
SELECT non_existent FROM taskscauses Zelyra to report a compile-time error immediately:unknown column non_existent.
4. Small, progressive examples
Example 1: Declaring database and table
database main {
engine: mariadb
database: "test_db"
}
table tasks {
id: Id primary auto
description: String(255) required
}
fn main() {
print("Database and table checked.")
}
Example 2: Compile-time SQL validation
database main {
engine: mariadb
database: "test_db"
}
table tasks {
id: Id primary auto
description: String(255) required
}
fn show_tasks() uses Database {
records = sql<Task[]> {
SELECT id, description
FROM tasks
}
print("SQL type-checked.")
}
fn main() uses Database {
show_tasks()
}
5. Typical errors and their causes
- Error: Executing SQL queries without declaring
uses Databaseon the enclosing function. Cause: Zelyra's capability system protects against unauthorized database queries. - Error: Omitting the
database mainblock. Cause: Without a target engine declaration, Zelyra cannot verify SQL dialect semantics or the schema.
6. Key takeaways
- Zelyra bridges the gap between application code and relational databases.
- SQL queries are type-checked at compile time.
- Database operations strictly require
uses Database.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Create a
databaseblock for SQLite or MariaDB. - Level 2 (Medium): Model a
userstable and write an SQL query that selects all users. - Level 3 (Challenging): Deliberately introduce a typo into an SQL column name and observe how Zelyra pinpoints the error.
8. Practical project task: Task Management
Set up the database foundation for our task management system:
database main {
engine: mariadb
database: "zelyra_tasks"
}
table tasks {
id: Id primary auto
name: String(100) required
done: Bool
}
fn status_report() uses Database {
records = sql<Task[]> {
SELECT id, name, done
FROM tasks
}
print("Database for task management ready.")
}
fn main() uses Database {
status_report()
}
9. Summary
- Databases and schemas are native, integral components of Zelyra.
- Typos in SQL statements are caught and prevented at compile time.
10. Self-check review questions
- What problem do Zelyra's type-checked SQL blocks solve compared to ordinary SQL strings?
- Which capability must be declared by a function executing
sql? - How does Zelyra derive the type
Taskfrom the tabletasks?
Chapter 24: Defining Tables and Data Modeling
1. What will I learn in this chapter?
In this chapter, you will learn:
- How relational tables are declared with
table. - The syntax for primary keys:
id: Id primary auto. - How column modifiers are defined:
required, length constraints likeString(100), and default values. - How relationships between tables are modeled.
2. Why is this topic important?
The data model is the bedrock of any application. If schema design is neglected early on, technical debt, corrupt data, and performance bottlenecks will linger for years. Zelyra enforces mandatory constraints, explicit types, and referential integrity right from day one.
3. Understandable explanation without unnecessary jargon
A table (table) is like a structured filing cabinet for uniform record sheets:
- Every sheet possesses a unique serial number:
id: Id primary auto. - Certain attributes must never be omitted:
required. - Text columns can have strict length bounds:
String(100).
table categories {
id: Id primary auto
name: String(50) required
}
From the definition table categories, Zelyra automatically generates the strongly typed struct Category with corresponding fields.
4. Small, progressive examples
Example 1: Simple table with required fields
database main {
engine: mariadb
database: "app_db"
}
table projects {
id: Id primary auto
name: String(80) required
active: Bool
}
fn main() {
print("Table projects declared.")
}
Example 2: Table with date and numeric fields
database main {
engine: mariadb
database: "app_db"
}
table time_entries {
id: Id primary auto
hours: Float
recorded_at: Timestamp
}
fn main() {
print("Table time_entries declared.")
}
Example 3: Linking two tables via IDs
database main {
engine: mariadb
database: "app_db"
}
table tasks {
id: Id primary auto
description: String(200) required
project_id: Id
}
fn main() {
print("Relationship tasks -> project_id created.")
}
5. Typical errors and their causes
- Error: Omitting a required field during record creation.
Cause: Columns flagged with
requiredmust hold valid values in every record. - Error: Using reserved keywords such as
action,field,title, orlistas column names. Cause: These identifiers are reserved language syntax elements in Zelyra.
6. Key takeaways
- Every table must define a primary key
id: Id primary auto. - The
requiredmodifier prohibits null or absent values at both database and type levels. - The singular name of a plural table (e.g.,
Taskfortasks) becomes an automatic Zelyra data type.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Define a table
tagswith a mandatory columnname: String(30) required. - Level 2 (Medium): Create a table
customerswithemail: Emailandphone: String(30). - Level 3 (Challenging): Model a table
commentslinked viatask_id: Idto a task and having acreated_at: Timestampcolumn.
8. Practical project task: Task Management
Create the complete production data model for our task management system:
database main {
engine: mariadb
database: "zelyra_tasks"
}
table tasks {
id: Id primary auto
name: String(120) required
description: String(500)
priority: Int
is_done: Bool
}
fn main() {
print("Complete task schema active.")
}
9. Summary
tabledefines the structure, types, and constraints of data records.- Zelyra guarantees that database schemas and application types stay strictly in sync.
10. Self-check review questions
- What is the purpose of the
autoattribute on primary keys? - What does the
requiredkeyword enforce on a table column? - What type name is automatically synthesized from the table
projects?
Chapter 25: Querying and Modifying Data
1. What will I learn in this chapter?
In this chapter, you will learn:
- How to safely query records from the database using
sql<T[]>. - How SQL injection is made impossible through parameterized queries (
:param). - How to mutate data using
INSERT,UPDATE, andDELETE. - Why modifications must be grouped into
transaction { ... }blocks. - The CLI database migration commands (
zelyra db setup,zelyra db apply).
2. Why is this topic important?
SQL injection has remained one of the top web security threats for over two decades: an attacker inputs malicious payloads into a form, extracting password hashes or destroying tables. Zelyra protects your software by construction: SQL parameters bound with a colon (:name) are strictly treated as data and securely escaped by the engine. Furthermore, transactions ensure that failures never leave half-committed, corrupted state in your database.
3. Understandable explanation without unnecessary jargon
-
Reading with sql<T[]>:
database main { engine: mariadb database: "tasks_db" } table tasks { id: Id primary auto name: String required is_done: Bool } fn load_tasks(filter_val: Bool) uses Database { my_tasks = sql<Task[]> { SELECT id, name, is_done FROM tasks WHERE is_done = :filter_val } print("Tasks loaded") } fn main() uses Database { load_tasks(false) } -
Writing inside a transaction:
database main { engine: mariadb database: "tasks_db" } table tasks { id: Id primary auto name: String required is_done: Bool } fn create_task(new_name: String) uses Database { done_flag = false transaction { sql { INSERT INTO tasks (name, is_done) VALUES (:new_name, :done_flag) } } } fn main() uses Database { create_task("Test") }If an error occurs during execution, the database automatically rolls back all intermediate operations.
4. Small, progressive examples
Example 1: Inserting a new record
database main {
engine: mariadb
database: "tasks_db"
}
table tasks {
id: Id primary auto
name: String(100) required
done: Bool
}
fn add_task(task_text: String) uses Database {
done_status = false
transaction {
sql {
INSERT INTO tasks (name, done)
VALUES (:task_text, :done_status)
}
}
print("Task saved.")
}
fn main() uses Database {
add_task("Answer email")
}
Example 2: Querying typed records
database main {
engine: mariadb
database: "tasks_db"
}
table tasks {
id: Id primary auto
name: String(100) required
done: Bool
}
fn load_all() uses Database {
records = sql<Task[]> {
SELECT id, name, done
FROM tasks
}
print("Task list loaded.")
}
fn main() uses Database {
load_all()
}
Example 3: Updating a record
database main {
engine: mariadb
database: "tasks_db"
}
table tasks {
id: Id primary auto
name: String(100) required
done: Bool
}
fn mark_as_done(task_id: Int) uses Database {
transaction {
sql {
UPDATE tasks
SET done = true
WHERE id = :task_id
}
}
print("Status updated.")
}
fn main() uses Database {
mark_as_done(1)
}
5. Typical errors and their causes
- Error: Attempting string concatenation in SQL queries (
"WHERE id = " + id). Cause: Zelyra forbids raw string concatenation in SQL blocks. Always use named parameters with a colon (:id). - Error: Forgetting that mutating operations must reside inside a
transaction { ... }block. Cause: Zelyra requires explicit transaction boundaries for any write operations.
6. Key takeaways
- Always bind input values in SQL using
:parameterto prevent SQL injection vulnerabilities. - All write operations must be enclosed within
transaction { ... }. zelyra db applymigrates the declared schema to the live database.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Write an SQL query selecting all uncompleted tasks (
done = false). - Level 2 (Medium): Write a function to delete a task by its ID (
DELETE FROM tasks WHERE id = :id). - Level 3 (Challenging): Implement a function that archives an old task and creates a successor task within a single transaction.
8. Practical project task: Task Management
Write complete database access routines for our task management application:
database main {
engine: mariadb
database: "zelyra_tasks"
}
table tasks {
id: Id primary auto
name: String(100) required
is_done: Bool
}
fn create_task(task_name: String) uses Database {
initial_status = false
transaction {
sql {
INSERT INTO tasks (name, is_done)
VALUES (:task_name, :initial_status)
}
}
print("Task created.")
}
fn complete_task(target_id: Int) uses Database {
transaction {
sql {
UPDATE tasks
SET is_done = true
WHERE id = :target_id
}
}
print("Task completed.")
}
fn main() uses Database {
create_task("Launch first Zelyra project")
complete_task(1)
}
9. Summary
- SQL in Zelyra is native, type-safe, and automatically defended against injection attacks.
- The
transactionblock guarantees database consistency across all mutations.
10. Self-check review questions
- How does Zelyra prevent malicious SQL injection attacks?
- Why must mutating SQL statements be placed inside a
transactionblock? - Which CLI command provisions and migrates database tables according to your code?
PART VII – WEB APPLICATIONS AND FORMS
Chapter 26: Rendering Web Pages
1. What will I learn in this chapter?
In this chapter, you will learn:
- How to create web pages and routes rapidly using the
pagekeyword. - How to pass dynamic URL parameters (such as
/tasks/{id}). - How HTML templates are declared directly within Zelyra code (
html { ... }). - How Zelyra completely prevents Cross-Site Scripting (XSS) through automatic HTML escaping.
2. Why is this topic important?
Traditional web development forces you to coordinate multiple disjoint tools: web servers (like Nginx or Apache), URL routers, template engines (Blade, Jinja, Twig), and backend business logic. If a developer forgets to escape HTML special characters in just one place, attackers can inject malicious JavaScript (XSS) into users' browsers. In Zelyra, the HTTP server is built-in (zelyra serve), and context-aware HTML escaping is automatic and unavoidable.
3. Understandable explanation without unnecessary jargon
With the page keyword, you define both an HTTP route and its corresponding HTML markup in one unified block:
page "/welcome" {
html {
<h1>Welcome to Zelyra</h1>
<p>Your modern web application is running!</p>
}
}
Whenever you want to render dynamic values, place them inside curly braces: {name}. Zelyra replaces the placeholder securely with properly escaped text.
4. Small, progressive examples
Example 1: Simple static welcome page
page "/hello" {
html {
<html>
<body>
<h1>Hello Zelyra World!</h1>
</body>
</html>
}
}
Example 2: Dynamic route with URL parameter
page "/user/{name}" {
html {
<html>
<body>
<h1>Profile of {name}</h1>
<p>Welcome back to the dashboard.</p>
</body>
</html>
}
}
Example 3: Safe escaping against XSS attacks
If a malicious user submits <script>alert('hack')</script> as their name, Zelyra renders it in the browser as benign text—the script will never execute:
page "/secure/{user_input}" {
html {
<div>Input: {user_input}</div>
}
}
Example 4: Reusable view layouts and named slots (from Zelyra 0.1.41)
Instead of repeating <html>, <head>, headers, and footers on every page, you declare reusable layout shells with view. A view defines exactly one default slot <slot /> and optional named slots with safe fallback content:
view AppShell {
html {
<html lang="en">
<head><title>Zelyra Application</title></head>
<body>
<header>
<slot name="header"><h1>Zelyra Portal</h1></slot>
</header>
<main>
<slot />
</main>
<footer>
<slot name="footer"><p>Built with Zelyra</p></slot>
</footer>
</body>
</html>
}
}
page "/dashboard" {
view: AppShell
html {
<slot name="header"><h1>My Dashboard</h1></slot>
<p>Specific page content is inserted into the default slot of the AppShell.</p>
}
}
Example 5: Declarative search, filtering, and pagination (from Zelyra 0.1.40) For data-driven collection pages, Zelyra automatically generates semantic form controls for search, sorting, and pagination with full URL state preservation:
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>Tasks ({total} total, page {page} of {pages})</h1>
<ul>
for task in tasks {
<li>{task.title}</li>
}
</ul>
}
}
Zelyra automatically runs the optimized COUNT(*) query in the background, binds total and pages as typed UInt variables, and renders semantic filter fieldsets.
5. Typical errors and their causes
- Error: Failing to properly close HTML tags (e.g.,
<h1>without</h1>). Cause: Zelyra strictly parses the HTML tree for well-formedness at compile time. - Error: Inconsistent parameter names in curly braces.
Cause: The route placeholder (e.g.,
{id}) must match the variable identifier used in the HTML template. - Errors
E-VIEW-010toE-VIEW-015: Undeclared variables or type mismatches in view interpolation. Cause: The Zelyra compiler checks view bindings and component properties at compile time against declared routes, types, and SQL loads. - Error: Supplying undeclared or duplicate slots in a
page. Cause: A page may only provide content for named slots that its declaredviewexplicitly defines.
6. Key takeaways
page "/path"defines a web route and serves verified, well-formed HTML.- Variables in HTML templates are interpolated via
{variable}and escaped automatically. view Name { ... }defines reusable master layout shells with<slot />and named slots (<slot name="...">).search,filter, andpaginatedautomatically generate semantic query controls with URL state preservation.- The command
zelyra servestarts the built-in HTTP server without external web server configurations.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Create a
page "/about"that displays an informative description of a company. - Level 2 (Medium): Create a dynamic route
/product/{item_id}displaying a product detail view. - Level 3 (Challenging): Design an overview page containing headings, navigation links, and an ordered list inside the HTML block.
8. Practical project task: Task Management
Build the main landing page for our task management application:
page "/tasks" {
html {
<html>
<head>
<title>Zelyra Task Management</title>
</head>
<body>
<h1>My Tasks</h1>
<p>Welcome to your personal task manager.</p>
<a href="/tasks/new">Create New Task</a>
</body>
</html>
}
}
Start the development server with zelyra serve main.zyl and navigate to http://localhost:8080/tasks in your browser!
9. Summary
- Web pages are declared directly and concisely using
pageandhtml. - Automatic context-aware escaping protects your users against web security threats.
10. Self-check review questions
- Which keyword introduces a web page definition in Zelyra?
- How are dynamic values bound into HTML markup?
- Why are XSS vulnerabilities precluded by default in Zelyra HTML rendering?
Chapter 27: Forms and User Inputs
1. What will I learn in this chapter?
In this chapter, you will learn:
- How to define secure input forms bound to database tables using
form. - How automatic Cross-Site Request Forgery (CSRF) protection works behind the scenes.
- How Zelyra validates input data against strong types (such as
Emailor length limits). - How to test and inspect forms prior to runtime using
zelyra form validate.
2. Why is this topic important?
Untrusted user inputs represent the single largest vector for web vulnerabilities: attackers submit empty required values, spoofed foreign IDs, or trick authenticated users into unauthorized requests (CSRF attacks). In other frameworks, developers must manually stitch together input forms, validation rules, error feedback, and CSRF tokens. Zelyra's form construct derives the input mask directly from your database table schema, safeguarding all interactions automatically.
3. Understandable explanation without unnecessary jargon
A form directly connects an input mask with a target database table:
database main {
engine: mariadb
}
table tasks {
id: Id primary auto
name: String(100) required
description: String(500)
}
form TaskCreate -> tasks {
fields {
name
description
}
}
From this concise definition, Zelyra automatically produces:
- HTML input elements matching the schema types (
<input type="text">, etc.). - An invisible cryptographic CSRF token preventing unauthorized form submissions.
- Server-side validation rules (e.g.,
namecannot exceed 100 characters and cannot be omitted).
4. Small, progressive examples
Example 1: Basic form for customer data
database main {
engine: mariadb
}
table customers {
id: Id primary auto
name: String(80) required
email: Email?
}
form CustomerForm -> customers {
fields {
name
email
}
}
Example 2: Minimal form with single field
database main {
engine: mariadb
}
table tasks {
id: Id primary auto
name: String(100) required
}
form NewTaskForm -> tasks {
fields {
name
}
}
5. Typical errors and their causes
- Error: Listing a field inside
fieldsthat does not exist in the target table. Cause: Zelyra strictly validatesfieldsagainst columns declared on the underlying table. - Error: Attempting to disable CSRF protection. Cause: In Zelyra, CSRF defense is an uncompromised, mandatory architectural standard.
6. Key takeaways
form Name -> target_tablegenerates a strongly validated, secure input form.- All table column constraints (lengths, required flags, types) are automatically enforced on input.
- CSRF and XSS protections are integral and active by default.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Define a form
CategoryCreatefor acategoriestable. - Level 2 (Medium): Test form validation rules on the command line using
zelyra form validate. - Level 3 (Challenging): Add a priority column to the task form and test server-side validation against erroneous submissions.
8. Practical project task: Task Management
Define the creation form for new tasks:
database main {
engine: mariadb
}
table tasks {
id: Id primary auto
name: String(100) required
priority: Int
}
form TaskCreate -> tasks {
fields {
name
priority
}
}
fn main() {
print("Task form ready.")
}
9. Summary
- Forms bridge database tables directly with secure web input masks.
- Zelyra manages validation, CSRF tokens, and error handling with zero boilerplate.
10. Self-check review questions
- What does the arrow
->signify inform TaskCreate -> tasks? - Why don't Zelyra developers need to manually insert CSRF tokens into templates?
- Which column validations are automatically enforced on the form?
Chapter 28: The Complete CRUD Pattern
1. What will I learn in this chapter?
In this chapter, you will learn:
- What CRUD (Create, Read, Update, Delete) means and why it forms the backbone of business software.
- How Zelyra synthesizes a complete administrative web interface with a single
crudblock. - How to configure list, detail, form, and delete views.
- How to attach custom operations using the
actionkeyword.
2. Why is this topic important?
Over 80% of routine web development consists of the exact same repetitive pattern: listing records in a table, displaying details, editing fields, and deleting entries. Developers frequently spend weeks implementing controllers, routes, views, and confirmation dialogs. In Zelyra, you achieve all of this in a few declarative lines of code—bulletproof, secure, and uniform.
3. Understandable explanation without unnecessary jargon
The crud keyword encapsulates all foundational operations for an entity:
- Create: Add new records.
- Read: Browse lists and inspect detail views.
- Update: Edit and persist existing records.
- Delete: Remove records safely with an explicit confirmation step.
database main {
engine: mariadb
}
table tasks {
id: Id primary auto
name: String(100) required
done: Bool default false
}
crud Task -> tasks {
title: "Task Management"
view {
fields {
name
done
}
list {
mode: cards
empty: "No tasks available."
}
}
}
Automatic Detail Linking with Hidden ID : When the technical
idcolumn is omitted from thefieldsblock (as shown here, where onlynameanddoneare visible), Zelyra automatically links the first displayed field (name) to the record detail page. This applies to both table (table) and card (cards) layouts.
4. Small, progressive examples
Example 1: A complete CRUD module
database main {
engine: mariadb
}
table tasks {
id: Id primary auto
name: String(100) required
active: Bool default true
}
crud Task -> tasks {
title: "Tasks"
view {
fields {
name
active
}
list {
mode: cards
empty: "No tasks available."
}
detail {
mode: cards
title: "Task Details"
}
form {
mode: cards
title: "Edit Task"
submit: "Save"
}
delete {
title: "Delete Task"
message: "This action cannot be undone."
submit: "Delete Now"
}
}
}
Example 2: Custom actions in the CRUD interface You can add custom action buttons—for example, to mark a task as completed immediately:
database main {
engine: mariadb
}
table tasks {
id: Id primary auto
name: String(100) required
done: Bool default false
}
crud Task -> tasks {
title: "Tasks"
view {
fields {
name
done
}
}
action complete {
label: "Mark as completed"
confirm: "Do you want to complete this task?"
sql {
UPDATE tasks
SET done = true
WHERE id = :id
}
success "Task completed successfully."
redirect "/tasks"
}
}
Transactional behavior and limits of CRUD actions:
- Multiple SQL statements in one transaction: An action can execute multiple SQL queries sequentially within its
sql { ... }block (e.g. updating a record and appending an audit record). Zelyra bundles all statements into a single MariaDB transaction. If any statement fails, the entire transaction is rolled back atomically.- Automatic parameter binding: The
:idparameter is bound automatically from the URL path of the target row. Custom fields declared withfield name: Typeare rendered in the confirmation dialog, validated upfront, and passed as:nameparameters to the SQL statements.- Current execution scope: Since 1.2.28, CRUD actions support typed handlers, row and session data, and checked guards within the existing server-side transaction context. Allowed expressions and effects follow the documented action syntax; actions are not arbitrary Zelyra programs.
Example 3: CRUD resources with reusable view layouts (from Zelyra 0.1.43)
With layout: ViewName, you automatically embed all generated CRUD screens (list, details, form, and delete dialogs) into an existing layout shell:
view AppShell {
html {
<html lang="en">
<body>
<nav><a href="/">Home</a> | <a href="/tasks">Tasks</a></nav>
<main>
<slot />
</main>
</body>
</html>
}
}
crud Task -> tasks {
title: "Task Management"
layout: AppShell
view {
fields {
name
done
}
}
}
The generated CRUD resource adopts the navigation and container structure of AppShell, while security checks, permissions, and CSRF protection remain fully active.
5. Typical errors and their causes
- Error: Omitting the
viewblock orfieldsdeclaration inside acrudblock. Cause: Zelyra needs an explicit list of columns to display across the generated UI views. - Error: Writing an
UPDATEstatement in anactionwithoutWHERE id = :id. Cause: CRUD actions always target the specific selected record identified by:id.
6. Key takeaways
crud Name -> tablegenerates a complete, secure administration interface.- Views (
list,detail,form,delete) can be individually styled and tailored. - Custom actions (
action) extend basic CRUD functionality with specific business logic.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Create a CRUD interface for a
categoriestable. - Level 2 (Medium): Customize the titles, button labels, and empty messages in a CRUD block.
- Level 3 (Challenging): Implement a custom action
duplicatethat clones the selected task.
8. Practical project task: Task Management
Construct the full production CRUD interface for our task manager:
database main {
engine: mariadb
database: "tasks_app"
}
table tasks {
id: Id primary auto
name: String(120) required
done: Bool default false
}
crud Task -> tasks {
title: "My Task Manager"
view {
fields {
name
done
}
list {
mode: cards
empty: "Great job! All tasks are completed."
}
detail {
mode: cards
title: "View Task"
}
form {
mode: cards
title: "Create or Edit Task"
submit: "Save Task"
}
delete {
title: "Delete Task"
message: "Are you sure you want to permanently delete this task?"
submit: "Delete"
}
}
}
9. Summary
- The CRUD pattern dramatically accelerates web development for data-driven applications.
- Zelyra automatically generates routes, forms, validation, and database operations.
10. Self-check review questions
- What four core operations make up the acronym CRUD?
- How does Zelyra react if an invalid column name appears inside
fields? - What is the role of the
actionkeyword in a CRUD declaration?
Chapter 29: Users, Passwords, and Sessions
1. What will I learn in this chapter?
In this chapter, you will learn:
- How Zelyra provides native authentication with the
authblock. - How passwords are hashed using state-of-the-art Argon2.
- How user sessions and role-based permissions are structured and secured.
- How pages and actions are protected using
requires authandpermits.
2. Why is this topic important?
Security cannot be treated as an optional plugin. Storing plaintext passwords or using outdated hashing algorithms (like MD5 or SHA1) leads to catastrophic security failures. In Zelyra, authentication is baked directly into the language design: passwords default to Argon2 hashing, session tokens are securely managed, and route permissions are verified declaratively.
3. Understandable explanation without unnecessary jargon
An authentication block ties together users, sessions, and permissions:
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
}
To restrict a web page exclusively to authenticated users, annotate the route:
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 "/secret" {
requires auth
html {
<h1>Visible only to authenticated users!</h1>
}
}
4. Small, progressive examples
Example 1: The standard tables for authentication
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
}
Example 2: Protected page with permissions check
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>Task Dashboard</h1>
<p>You are authorized.</p>
}
}
5. Typical errors and their causes
- Error: Storing plaintext passwords inside the user table.
Cause: Zelyra expects a
password_hashcolumn and provideszelyra auth hash-passwordfor hashing. - Error: Omitting the
auth_sessionstable. Cause: Zelyra requires an explicit table for tracking session tokens cryptographically.
6. Key takeaways
authdeclares user, session, and permission schemas at a single centralized location.- Protected routes require
requires authand optionalpermits "permission". - Passwords are exclusively preserved as secure Argon2 hashes.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Generate a password hash using the CLI command
zelyra auth hash-password. - Level 2 (Medium): Protect a
/settingsroute usingrequires auth. - Level 3 (Challenging): Assign a specific permission to a user role using
zelyra auth role-permission.
8. Practical project task: Task Management
Lock down the task management application against unauthorized access:
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 "/my-tasks" {
requires auth
html {
<h1>Protected Task Area</h1>
<p>Accessible only to authenticated users.</p>
}
}
9. Summary
- Authentication and authorization are native, first-class features in Zelyra.
- Modern security standards (Argon2, session tokens, RBAC) work out of the box with no third-party libraries.
10. Self-check review questions
- Which state-of-the-art algorithm does Zelyra use for hashing passwords?
- Which declaration restricts a web page to authenticated users?
- What is the responsibility of the
auth_sessionstable?
Chapter 30: APIs and Data Exchange
1. What will I learn in this chapter?
In this chapter, you will learn:
- How to define typed REST API endpoints using the
apikeyword. - How to type HTTP methods (
GET,POST,PUT,DELETE) alongside inputs and outputs. - How to declare standard error status codes (
404 NotFound,400 ValidationError). - How Zelyra generates complete OpenAPI/Swagger documentation automatically (
zelyra doc --openapi).
2. Why is this topic important?
Modern applications do not operate in a vacuum: mobile clients (iOS/Android), frontend frameworks (React, Vue), and external partner services must communicate with your backend. With conventional frameworks, API documentation falls out of sync almost immediately. In Zelyra, the API definition is the executable code itself: every route, payload parameter, and response code is strictly typed—and the OpenAPI specification is derived directly from the code.
3. Understandable explanation without unnecessary jargon
With the api keyword, you declare an unambiguous API contract:
- Method and Path: e.g.,
GET "/tasks/{id}" - Input: What parameters does the request require?
- Output: What data type is returned as JSON?
- Errors: What HTTP status codes can occur in error cases?
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 endpoint defined.")
}
4. Small, progressive examples
Example 1: A GET endpoint with return type
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 checked.")
}
Example 2: A POST endpoint for creating resources
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 checked.")
}
Example 3: Generating OpenAPI documentation Execute:
zelyra doc main.zyl --openapi
Zelyra generates a standards-compliant openapi.json file ready for import into Swagger UI, Postman, or API gateway catalogs.
5. Typical errors and their causes
- Error: Specifying an undefined type for
output. Cause: Zelyra verifies that the return type (e.g.,Task) exists as a table or record declaration. - Error: Returning undeclared HTTP error codes. Cause: Typed API contracts require all possible error responses to be explicitly declared.
6. Key takeaways
api METHOD "/path"declares a strongly typed REST endpoint.input,output, anderrorsdefine a comprehensive API contract.zelyra doc --openapioutputs OpenAPI specifications directly from source code.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Define an endpoint
GET "/api/version"that returns a version string. - Level 2 (Medium): Create an endpoint
DELETE "/api/tasks/{id}"declaring the404 NotFounderror code. - Level 3 (Challenging): Generate an OpenAPI schema using
zelyra doc --openapiand inspect the output JSON structure.
8. Practical project task: Task Management
Define the official REST API for our task management application:
type TaskId = Id
table tasks {
id: TaskId primary auto
name: String(120) required
done: 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("Task REST API ready.")
}
9. Summary
- APIs in Zelyra are type-safe, self-documenting, and standards-compliant.
- With minimal declarative code, you create robust interfaces for web and mobile frontends.
10. Self-check review questions
- Which four clauses make up a complete
apideclaration? - Why is automated OpenAPI generation superior to manual documentation?
- What occurs when a client sends invalid data to an endpoint's
inputblock?
PART VIII – THE DISTINCTIVE FEATURES OF ZELYRA
Chapter 31: Readability as the Highest Priority
1. What will I learn in this chapter?
In this chapter, you will learn:
- Why in real-world engineering code is read up to ten times more often than it is written.
- Which deliberate design decisions Zelyra made to achieve maximum clarity.
- Why Zelyra intentionally avoids unreadable syntax acrobatics and cryptic symbols.
- How the built-in code formatter
zelyra fmtenforces a uniform code standard across entire teams.
2. Why is this topic important?
Many programming languages allow the exact same logic to be expressed in ten disparate ways—frequently in ultra-compact one-liners saturated with symbols that no one understands three months later. In large development teams and long-lived projects, this results in staggering maintenance overhead. Zelyra follows a core philosophy: There is exactly one obvious, readable way to solve any given problem.
3. Understandable explanation without unnecessary jargon
Readability in Zelyra means:
- Expressive names over obscure abbreviations: Functions and variables speak clear language (
priorityinstead ofprio_lvl_fn()). - Unambiguous blocks: Every conditional branch, loop, and function declaration uses explicit curly braces
{}. - Automated formatting: No team member ever needs to debate indentation, whitespace, or bracket placement. The command
zelyra fmtaligns every single line strictly to the official Zelyra style guide.
fn calculate_total_duration(task_durations: Int[]) -> Int {
mutable sum = 0
for duration in task_durations {
sum = sum + duration
}
return sum
}
fn main() {
durations: Int[] = [15, 30, 45]
print(calculate_total_duration(durations))
}
4. Small, progressive examples
Example 1: Self-documenting function signatures
fn is_task_overdue(deadline_days: Int) -> Bool {
return deadline_days < 0
}
fn main() {
print(is_task_overdue(-2))
}
Example 2: Clear, readable control flow
fn status_display(status_code: Int) -> String {
match status_code {
1 => {
return "New"
}
2 => {
return "In Progress"
}
3 => {
return "Completed"
}
_ => {
return "Unknown"
}
}
}
fn main() {
print(status_display(2))
}
5. Typical errors and their causes
- Error: Naming variables with arbitrary single letters (
a,x,tmp) whose intent is obscure. Cause: Zelyra code is intended to read like clear prose. Use descriptive names such astask_indexorelapsed_seconds. - Error: Manually trying to fix inconsistent indentation.
Cause: Simply run
zelyra fmt main.zyl—the compiler formats everything automatically and deterministically.
6. Key takeaways
- Always write code for the developer who must maintain it six months from now.
zelyra fmtguarantees a clean, uniform programming style across the entire codebase.- Descriptive, expressive identifiers are the highest-value documentation you can write.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Format an unformatted or messy source file using
zelyra fmt. - Level 2 (Medium): Refactor a function with cryptic variable names into clean, self-explanatory Zelyra code.
- Level 3 (Challenging): Structure a multi-stage computation function so cleanly that no single line exceeds 80 characters, while retaining total clarity.
8. Practical project task: Task Management
Design the filter logic of our Task Management system with maximum readability:
fn is_urgent_and_open(priority: Int, completed: Bool) -> Bool {
is_priority_one = priority == 1
is_not_yet_completed = !completed
return is_priority_one && is_not_yet_completed
}
fn main() {
print(is_urgent_and_open(1, false))
print(is_urgent_and_open(2, false))
}
9. Summary
- Readability is your primary safeguard against software rot and maintenance friction.
- Zelyra enforces clarity both by language design and through standard tooling.
10. Self-check review questions
- Why does highly readable code save substantial time and money over the lifespan of a software project?
- Which Zelyra CLI command automatically reformats source files to the canonical standard?
- Why does Zelyra deliberately avoid having multiple redundant syntax options for the same logical operation?
Chapter 32: AI-Nativity – Why Zelyra Is Built for AI Assistants
1. What will I learn in this chapter?
In this chapter, you will learn:
- What it means for a programming language to be truly "AI-native".
- Why modern Large Language Models (such as Claude, GPT, and Gemini) hallucinate significantly less when generating Zelyra code.
- How Typed Holes (
_) assist both human developers and AI coding agents during iterative synthesis. - How CLI commands equipped with
--format jsonexpose rich, machine-readable interfaces for automated tooling. - How
zelyra impactandzelyra editsafeguard automated refactoring workflows.
2. Why is this topic important?
Most existing programming languages were designed decades ago exclusively for human keyboard input. When modern AI assistants generate code in Python or JavaScript, they frequently invent nonexistent APIs, misjudge types, or fail to account for hidden side effects. Zelyra was engineered from inception so that human developers and AI assistants can collaborate with unprecedented precision and zero ambiguity.
3. Understandable explanation without unnecessary jargon
Zelyra accelerates and safeguards AI-assisted development through four key architectural strengths:
- Unambiguous, context-free grammar: The language contains no syntactic ambiguities, eliminating parse guesswork.
- Machine-readable JSON diagnostics: Nearly all compiler commands support
--format json(for example,zelyra check --format json), enabling AI agents to ingest diagnostics directly as structured objects. - Typed Holes (
_): When you or an AI model are unsure how a specific value should be derived, you place an underscore_. The compiler immediately provides exact feedback: What type is expected? What variables are in scope? What contracts apply? - Impact Analysis (
zelyra impact): Zelyra computes ahead of time exactly which downstream components of a program are affected by a proposed code edit.
4. Small, progressive examples
Example 1: Typed contracts guide AI generation without guesswork Because contracts define explicit boundaries, AI agents can generate provably correct implementations:
fn normalize_scale(value: Int) -> Int
requires { value >= 0 && value <= 100 }
ensures { result >= 0 && result <= 10 }
{
return value / 10
}
fn main() {
print(normalize_scale(85))
}
Example 2: Machine-readable error analysis When an AI tool invokes:
zelyra check main.zyl --format json
it receives structured JSON with exact error codes, file paths, line numbers, column offsets, and actionable remediation hints.
5. Typical errors and their causes
- Error: Leaving Typed Holes (
_) in production code. Cause: Typed Holes are development scratchpads. Before final compilation, every_must be replaced with concrete code. - Error: Accepting AI-generated code blindly without running
zelyra check. Cause: Always use the Zelyra compiler as an incorruptible arbiter of correctness.
6. Key takeaways
- Zelyra is an AI-native language: unambiguous, structured, and tool-friendly.
- Typed Holes
_serve as precise design prompts for both the compiler and AI assistants. --format jsonenables seamless, zero-overhead integration into autonomous AI coding agents and modern IDEs.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Execute
zelyra checkwith the--format jsonflag and inspect the structured JSON schema of the output. - Level 2 (Medium): Run
zelyra impactagainst a file and analyze which dependents are flagged for recompilation. - Level 3 (Challenging): Insert a Typed Hole
_into a calculation function and examine the compiler diagnostics to see all inferred type and scope data.
8. Practical project task: Task Management
Write a well-formed function signature with contracts, providing an optimal specification for an AI assistant to complete:
fn calculate_remaining_time(target_hour: Int, current_hour: Int) -> Int
requires { target_hour >= current_hour }
ensures { result >= 0 }
{
return target_hour - current_hour
}
fn main() {
remaining = calculate_remaining_time(18, 14)
print(remaining)
}
9. Summary
- Zelyra eliminates lexical and semantic ambiguities that historically confuse language models.
- Typed Holes and structured JSON outputs make pair programming with AI agents exceptionally reliable and deterministic.
10. Self-check review questions
- What does the concept of "Typed Holes" signify in Zelyra?
- Why do AI coding systems benefit substantially from the compiler's
--format jsonoption? - How do formal contracts (
requires,ensures) guide an AI assistant in generating correct implementations?
Chapter 33: Safety through Capabilities
1. What will I learn in this chapter?
In this chapter, you will learn:
- What the capability security model is and why it is superior to conventional permission architectures.
- System capabilities such as
Console,Database,Network,FileSystem,Process,Environment,Clock, andRandom. - How capabilities are declared on functions, propagated through call graphs, and constrained in
zelyra.toml. - Why Zelyra is naturally immune to supply chain attacks and rogue dependencies.
2. Why is this topic important?
In contemporary package ecosystems such as npm (JavaScript) or PyPI (Python), projects routinely incorporate hundreds of third-party dependencies. If any single transitive dependency is compromised, it can silently read environment secrets, exfiltrate data across the internet, or encrypt local files. In Zelyra, this attack vector is eliminated at compile time: a function cannot send a single byte over the network without explicitly declaring uses Network—otherwise the compiler rejects the code outright!
3. Understandable explanation without unnecessary jargon
Think of Zelyra's capability system as a physical security clearance:
- If a function needs to write data to disk, it must explicitly hold the
uses FileSystembadge on its signature. - If it lacks that capability, it cannot touch the file system—even if malicious code tries to execute a write.
- Crucially, if function
Acalls functionB, andBrequires a capability,Amust also declare that capability. A quick glance atmain()immediately reveals the total security boundary of the entire program!
fn safe_operation() {
# This function has NO capabilities.
# It is mathematically impossible for it to damage the disk or access the network!
print("Guaranteed side-effect free.")
}
fn main() {
safe_operation()
}
4. Small, progressive examples
Example 1: Safe declaration of environment access
fn read_configuration(key: String) -> Option<String>
uses Environment
{
return env(key)
}
fn main() uses Environment {
print("Configuration access allowed.")
}
Example 2: Database access guarded by capabilities
database main {
engine: mariadb
database: "tasks_db"
}
table tasks {
id: Id primary auto
name: String required
}
fn load_data() uses Database {
records = sql<Task[]> {
SELECT id, name
FROM tasks
}
print("Database access granted.")
}
fn main() uses Database {
load_data()
}
5. Typical errors and their causes
- Error: Forgetting to declare capabilities on upstream caller functions.
Cause: If function
a()calls functionb() uses FileSystem, thena()must also be annotated withuses FileSystem. - Error: Capability denied by project configuration in
zelyra.toml. Cause: The project configuration file sets the ceiling of permitted capabilities for the application.
6. Key takeaways
- No function can secretly access files, databases, or the network.
- All side effects are explicitly documented in function signatures.
- Pure functions without capabilities are guaranteed to be side-effect free and immune to external tampering.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Inspect an existing codebase and list all functions that require system capabilities.
- Level 2 (Medium): Write a utility function that combines
uses Clockanduses Environment. - Level 3 (Challenging): Architect an application such that 100% of business domain logic resides in pure functions that require zero capabilities.
8. Practical project task: Task Management
Isolate capability boundaries in our Task Management system:
# 1. Pure logic: NO capabilities required
fn is_ready_for_export(task_count: Int) -> Bool {
return task_count > 0
}
# 2. I/O logic: Explicit FileSystem capability
fn execute_export(filename: String, content: String) uses FileSystem {
write_text(filename, content)
print("Export completed.")
}
fn main() uses FileSystem {
if is_ready_for_export(5) {
execute_export("tasks.txt", "Task 1")
}
}
9. Summary
- Zelyra's capability model protects against malicious packages and unintended side effects.
- Applications become secure by design through explicit permission declaration and static enforcement.
10. Self-check review questions
- Name three system capabilities provided by Zelyra.
- Why must caller functions declare the capabilities required by the sub-functions they invoke?
- How does Zelyra prevent supply chain attacks originating from third-party libraries?
Chapter 34: Zelyra in Comparison
1. What will I learn in this chapter?
In this chapter, you will learn:
- How Zelyra compares directly with Python, PHP/Laravel, TypeScript/Node, and Rust.
- Where each language excels and why Zelyra was custom-tailored for modern data-driven web and AI applications.
- How Zelyra marries the rigorous type safety of Rust with the development velocity of Python.
2. Why is this topic important?
No single programming language is optimal for every possible domain: C and Rust are unmatched for operating system kernels; Python reigns in data science; JavaScript dominates the browser. However, when building database-backed business applications and web backends, developers in those languages often battle significant legacy baggage. Zelyra unifies the greatest strengths of these ecosystems.
3. Understandable explanation without unnecessary jargon: Language Comparison
| Feature | Python | PHP / Laravel | TypeScript / Node | Rust | Zelyra |
|---|---|---|---|---|---|
| Typing System | Dynamic | Dynamic/Optional | Static (erased to JS) | Static (very strict) | Static & Unambiguous |
| Null Safety | None crashes at runtime |
null crashes at runtime |
undefined crashes |
Absolute (Option) |
Absolute (Option) |
| SQL Integration | ORM (String-based) | ORM (Eloquent) | ORM (Prisma/TypeORM) | Diesel / SQLx | Native & Type-Checked |
| Contracts | No (only assert) |
No | No | External libraries | Built-in (requires, ensures) |
| Security Capabilities | No (Full OS access) | No (Full OS access) | No (Full OS access) | No | Built-in (uses ...) |
| AI Tooling Support | Moderate (Ambiguous) | Moderate | Moderate | Difficult for LLMs | Native (JSON, Typed Holes) |
4. Small, progressive examples
Comparison: How Zelyra catches bugs that slip into production in other languages
In Python / JavaScript (potential runtime crash on missing value):
An unhandled missing field triggers a production server crash: AttributeError: 'NoneType' object has no attribute 'title'.
In Zelyra (guaranteed safe handling enforced at compile time):
fn show_title(opt_title: Option<String>) {
match opt_title {
Some(t) => {
print("Title: " + t)
}
None => {
print("No title provided.")
}
}
}
fn main() {
show_title(Some("Project X"))
show_title(None)
}
5. Typical errors and their causes
- Error: Attempting to write Zelyra like Python (using indentation instead of curly braces, or attempting dynamic type reassignment). Cause: Zelyra utilizes curly braces and enforces strict, static type stability.
- Error: Confusing Zelyra with low-level systems languages like Rust (searching for manual memory management or complex lifetime annotations). Cause: Zelyra manages memory completely automatically, freeing the developer to focus on business logic.
6. Key takeaways
- Zelyra combines an approachable development workflow with its own static type system and compiler checks. Which issues those checks catch depends on their current scope.
- Databases, web endpoints, and interfaces are native language constructs rather than disparate third-party libraries.
- Reliability and security are enforced structurally by the compiler rather than added as an afterthought.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Port a simple calculation script from Python into idiomatic, statically typed Zelyra.
- Level 2 (Medium): Compare an Eloquent database query in PHP/Laravel with Zelyra's type-safe
sql<T[]>. - Level 3 (Challenging): Explain, using a real-world supply chain vulnerability example, how Zelyra's capability system completely neutralizes malicious third-party dependencies.
8. Practical project task: Task Management
Consolidate key strengths of Zelyra within a concise demonstration of our Task Management system:
database main {
engine: mariadb
database: "tasks_demo"
}
table tasks {
id: Id primary auto
name: String(100) required
completed: Bool default false
}
fn count_open_tasks() -> Int uses Database {
open_tasks = sql<Task[]> {
SELECT id, name, completed
FROM tasks
WHERE completed = false
}
return len(open_tasks)
}
fn main() uses Database {
count = count_open_tasks()
print("Open tasks determined.")
}
9. Summary
- Zelyra is an independent compiled language. Comparisons with scripting or systems languages should name specific properties and use cases.
- For modern web backends, data processing, and AI workflows, Zelyra offers an exceptionally dependable, developer-friendly platform.
10. Self-check review questions
- What concrete advantage does Zelyra's
Optiontype offer compared to Python'sNoneor JavaScript'snull? - Why is native database integration in Zelyra safer than traditional ORM libraries?
- What vital role do capabilities play in safeguarding software from malicious third-party packages?
PART IX – FROM DESIGN TO FINISHED APPLICATION
Chapter 35: Planning Software – From Idea to Design
1. What will I learn in this chapter?
In this chapter, you will learn:
- How to transform a vague product idea into a precise, implementable software architecture.
- How to sketch domain entities and their relationships on paper or in your editor.
- Why defining your schema early in Zelyra simplifies every subsequent development step.
- How to decompose requirements into small, independently testable milestones.
2. Why is this topic important?
The single most common mistake made by beginners (and careless veterans) is rushing to write code before planning the underlying data flow. If you realize midway through implementation that a crucial table column or entity relation is missing, you often spend days refactoring existing code. Zelyra rewards disciplined planning: once your table schema is established, forms, validations, and APIs emerge naturally and predictably from that single source of truth.
3. Understandable explanation without unnecessary jargon
Every successful software project progresses through four planning phases:
- Clarify purpose and target audience: Who uses the system? What primary problem must it solve? (e.g., "A team lead wants to create, assign, and mark tasks as completed").
- Design the data model: What entities exist? Which fields are mandatory? (e.g.,
taskswithname,priority, andcompleted). - Security and access rules: Who is permitted to perform which operations? Do we require authentication and permissions?
- Iterative implementation: First the schema (
table), then domain logic (fn), then web views (crud/page), and finally APIs (api).
4. Small, progressive examples
Example 1: The first milestone – The data model
database main {
engine: mariadb
database: "task_planner"
}
table tasks {
id: Id primary auto
name: String(100) required
priority: Int default 2
completed: Bool default false
}
fn main() {
print("Planning Step 1: Schema established.")
}
Example 2: Planning business logic as pure functions
fn validate_deadline(days: Int) -> Bool {
return days >= 0
}
fn main() {
print(validate_deadline(3))
}
5. Typical errors and their causes
- Error: Attempting to program all features simultaneously before validating the core foundation.
Cause: Build software incrementally: verify each milestone immediately with
zelyra check. - Error: Ambiguous mandatory fields in the data model.
Cause: Decide from the beginning which fields are strictly
requiredand which may be empty (Option<T>).
6. Key takeaways
- Failing to plan is planning to fail in software architecture.
- A clean, unambiguous data model is the backbone of any robust application.
- Decompose complex problems into small, independently verifiable functions.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Outline the feature set and data fields of a simple note-taking application as bullet points.
- Level 2 (Medium): Design a relational table schema for users, tasks, and categories with appropriate Zelyra types and constraints.
- Level 3 (Challenging): Formulate pre- and postconditions (
requires,ensures) for all core domain functions of your planned application.
8. Practical project task: Task Management
Consolidate the architectural blueprint for our Task Management system:
database main {
engine: mariadb
database: "tasks_pro"
}
table tasks {
id: Id primary auto
name: String(120) required
priority: Int default 1
completed: Bool default false
}
fn calculate_urgency(priority: Int, remaining_days: Int) -> String {
if priority == 1 {
return "HIGHEST PRIORITY"
}
if remaining_days <= 1 {
return "URGENT DUE TO DEADLINE"
}
return "NORMAL"
}
fn main() {
status = calculate_urgency(1, 5)
print("Architecture plan verified: " + status)
}
9. Summary
- Structured planning saves development time and prevents costly architectural dead-ends.
- Zelyra's declarative language structure integrates seamlessly into agile development workflows.
10. Self-check review questions
- What four phases constitute professional software design?
- Why should the data model be defined before implementing user interfaces?
- How do preconditions assist in formulating unambiguous software specifications?
Chapter 36: Architecture and Clean Code Structure
1. What will I learn in this chapter?
In this chapter, you will learn:
- How to structure your Zelyra application using the established three-layer architectural model.
- The clean separation of persistence (
table), business logic (fn), and presentation (page,crud,api). - How to minimize coupling and keep modules maintainable over long lifecycles.
- Why a clean architecture protects you from unexpected regressions when adding new features.
2. Why is this topic important?
When database queries, business calculations, and HTML markup are tangled together in the same file or function, the result is fragile, unmaintainable "spaghetti code." If the database schema changes later, the frontend unexpectedly breaks. A clean architecture establishes clear boundaries: each layer has a single, well-defined responsibility.
3. Understandable explanation without unnecessary jargon
A well-architected Zelyra application is divided into three distinct layers:
- Data and Persistence Layer: Table declarations (
table) and strongly typed SQL queries (sql<T[]>). - Business Logic Layer: Pure calculation and validation functions protected by formal contracts (
requires,ensures). - Presentation and Interface Layer: Web interfaces (
crud,page) and REST endpoints (api).
# 1. Data Model
table tasks {
id: Id primary auto
name: String required
}
# 2. Business Logic
fn format_name(raw_text: String) -> String {
return "[TASK] " + raw_text
}
# 3. Entry point / Execution
fn main() {
print(format_name("Check server"))
}
4. Small, progressive examples
Example 1: Decoupling domain logic from I/O
# Pure calculation function: No capabilities needed
fn calculate_percentage(value: Int, max_value: Int) -> Int
requires { max_value > 0 && value >= 0 }
{
return (value * 100) / max_value
}
# I/O function: Uses the logic and prints output
fn main() {
percentage = calculate_percentage(45, 50)
print(percentage)
}
Example 2: Clear structure through expressive component names
table settings {
id: Id primary auto
app_name: String(60) required
}
fn show_system_info(name: String) {
print("System running: " + name)
}
fn main() {
show_system_info("Zelyra Task Suite")
}
5. Typical errors and their causes
- Error: Embedding complex business calculations directly into SQL queries or HTML template blocks. Cause: Extract calculations into dedicated helper functions so they can be independently unit-tested.
- Error: Introducing circular dependencies between modules. Cause: Ensure data flow travels cleanly downward (Presentation -> Logic -> Data).
6. Key takeaways
- Strictly separate data models, domain business rules, and UI presentation.
- Core business logic should remain free of side effects and capabilities whenever possible.
- Clean architectural layers ensure applications remain maintainable and straightforward to extend.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Identify the three distinct layers in an existing code snippet.
- Level 2 (Medium): Refactor calculations embedded in a web route into pure standalone helper functions.
- Level 3 (Challenging): Design a full three-layer architectural blueprint for an employee time-tracking system.
8. Practical project task: Task Management
Implement the layered architectural structure for our Task Management system:
database main {
engine: mariadb
database: "tasks_architecture"
}
# Layer 1: Persistence
table tasks {
id: Id primary auto
name: String required
completed: Bool default false
}
# Layer 2: Business Logic
fn is_task_important(name: String, urgent: Bool) -> Bool {
return urgent
}
# Layer 3: Application / Execution
fn main() uses Database {
important = is_task_important("Submit tax return", true)
print("Task architecture verified.")
}
9. Summary
- The three-layer model provides long-term maintainability, clarity, and structural safety.
- Zelyra's type system and declarative constructs enforce these boundaries naturally.
10. Self-check review questions
- What three layers form the foundation of a clean Zelyra application?
- Why should core business logic avoid requiring capabilities whenever feasible?
- How does separating presentation from data persistence simplify future UI redesigns?
Chapter 37: Configuration and Environment Variables
1. What will I learn in this chapter?
In this chapter, you will learn:
- How to securely manage configuration values via environment variables (
.env). - How to use the standard library function
env(key)and its required capabilityuses Environment. - How to safely handle the
Option<String>return value fromenv(). - Why passwords, database secrets, and API tokens must never be hard-coded into source code.
2. Why is this topic important?
Accidentally committing database credentials or API keys into public Git repositories is one of the most common and disastrous security vulnerabilities in modern software. Furthermore, applications must adapt dynamically across development, testing, and production environments (e.g., using different database hosts). Environment variables cleanly decouple source code from confidential, environment-specific configuration.
3. Understandable explanation without unnecessary jargon
In Zelyra, you read environment variables using the built-in function env(). Because an environment variable may or may not be defined on the host system, env() always returns an Option<String>:
fn read_port() -> String uses Environment {
opt_port = env("APP_PORT")
match opt_port {
Some(p) => {
return p
}
None => {
return "8080"
}
}
}
fn main() uses Environment {
port = read_port()
print("Server listening on port: " + port)
}
4. Small, progressive examples
Example 1: Configuring database host dynamically
fn get_db_host() -> String uses Environment {
match env("DB_HOST") {
Some(host) => {
return host
}
None => {
return "127.0.0.1"
}
}
}
fn main() uses Environment {
print("Connecting to: " + get_db_host())
}
Example 2: Dynamically checking debug mode
fn is_debug_active() -> Bool uses Environment {
match env("APP_DEBUG") {
Some(val) => {
return val == "true"
}
None => {
return false
}
}
}
fn main() uses Environment {
if is_debug_active() {
print("Debug mode is ON")
} else {
print("Debug mode is OFF")
}
}
5. Typical errors and their causes
- Error: Hardcoding sensitive database passwords directly in
.zylfiles. Cause: Always store secrets in a local.envfile and read them viaenv(). - Error: Calling
env()without declaringuses Environment. Cause: Zelyra's capability security model prevents unauthorized access to host environment state.
6. Key takeaways
- Never commit confidential secrets or credentials into version control.
env(name)returns anOption<String>and requires theuses Environmentcapability.- Always supply safe, predictable default fallback values for missing environment variables.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Read an environment variable
USER_NAMEand print a personalized greeting to the console. - Level 2 (Medium): Write a reusable helper function
get_env_or_default(key: String, default_val: String) -> String. - Level 3 (Challenging): Configure an application so that it switches its logging and database endpoints between development and production modes based on environment settings.
8. Practical project task: Task Management
Construct the configuration management component for our Task Management system:
fn load_app_title() -> String uses Environment {
match env("APP_TITLE") {
Some(app_title) => {
return app_title
}
None => {
return "Zelyra Task Manager 0.1"
}
}
}
fn main() uses Environment {
print("System started: " + load_app_title())
}
9. Summary
- Environment variables provide flexible, secure configuration management across deployment tiers.
- Zelyra's
Optiontype ensures you explicitly handle missing configuration values without runtime crashes.
10. Self-check review questions
- Why must confidential API keys and credentials never be hard-coded into application source files?
- What return type does the standard function
env()produce? - Which capability is required by a function that reads environment variables?
Chapter 38: Debugging and Optimization
1. What will I learn in this chapter?
In this chapter, you will learn:
- How to diagnose your project and environment thoroughly using the CLI command
zelyra doctor. - How to leverage compiler diagnostics and actionable error hints effectively.
- Systematic debugging methodologies for isolating logic bugs.
- How to identify and eliminate performance bottlenecks.
2. Why is this topic important?
Even with careful design, unexpected issues arise during software development: a database port might be unreachable, an environment variable might be misconfigured, or an algorithm might execute redundant operations. Guessing randomly wastes precious hours. Systematic debugging with Zelyra's built-in toolchain leads to immediate solutions in minutes.
3. Understandable explanation without unnecessary jargon
Zelyra provides a comprehensive toolkit for proactive system diagnostics:
zelyra check: Validates syntax, types, capabilities, and formal contracts.zelyra doctor: Audits host system environments, database connections, open ports, and package configurations.zelyra impact: Pinpoints precisely which functions and modules are affected by a proposed change.
zelyra doctor main.zyl
If your MariaDB server is offline or unreachable, zelyra doctor immediately highlights the exact cause instead of leaving you stranded.
4. Small, progressive examples
Example 1: Systematic diagnostic print statements
fn calculate_sum(numbers: Int[]) -> Int {
mutable total = 0
for n in numbers {
total = total + n
}
return total
}
fn main() {
values: Int[] = [10, 20, 30]
result = calculate_sum(values)
print("Calculated result:")
print(result)
}
Example 2: Guarding against infinite loops using loop invariants
fn safe_count(limit: Int) -> Int
requires { limit > 0 }
{
mutable i = 0
while i < limit
invariant { i >= 0 }
{
i = i + 1
}
return i
}
fn main() {
print(safe_count(10))
}
5. Typical errors and their causes
- Error: Modifying lines of code at random when an unexpected behavior occurs.
Cause: Accurately isolate the problem first using
zelyra check, log output, and compiler hints. - Error: Suspecting database driver bugs when the project configuration in
zelyra.tomlsimply lacks required permissions. Cause: Runzelyra doctorto detect configuration issues instantly.
6. Key takeaways
zelyra doctoris your first step when investigating environment, network, or database failures.- Contracts and invariants catch algorithmic errors before code ever enters production.
- Systematic root-cause debugging is faster and far more reliable than trial-and-error guessing.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Run
zelyra doctoragainst your Task Management project and inspect each verification line. - Level 2 (Medium): Create a function with a deliberate logic error in an isolated test harness and analyze compiler feedback.
- Level 3 (Challenging): Write a multi-step data transformation pipeline that outputs structured status logs at each milestone.
8. Practical project task: Task Management
Build an internal health check routine for our Task Management system:
fn perform_self_test() -> Bool {
test_ok = 1 + 1 == 2
return test_ok
}
fn main() {
print("Starting system self-test...")
if perform_self_test() {
print("[OK] System functioning properly.")
} else {
print("[ERROR] Internal system error.")
}
}
9. Summary
- The Zelyra toolchain (
doctor,check,impact) provides rapid clarity when encountering defects. - Contracts and loop invariants prevent logical state corruption before it becomes a production bug.
10. Self-check review questions
- What system and project aspects are audited by
zelyra doctor? - How can you systematically isolate a defect within an iterative calculation?
- What role do loop invariants play in preventing logical regression?
Chapter 39: Deployment and Operations
1. What will I learn in this chapter?
In this chapter, you will learn:
- How Zelyra web applications are deployed to production environments.
- How to package and operate your application inside lightweight Docker containers.
- How Zelyra connects to a production MariaDB cluster.
- How to maintain high availability using the built-in HTTP server with
zelyra serve.
2. Why is this topic important?
Software is of little value if it only runs on a developer's local workstation. It must operate reliably 24/7 in production or cloud infrastructure. In traditional technology stacks, deployment often involves labyrinthine configurations of PHP-FPM, Apache/Nginx reverse proxies, and process managers. Zelyra radically simplifies operations: a single configuration file and a lightweight binary container are all you need.
3. Understandable explanation without unnecessary jargon
Zelyra includes its own high-performance, asynchronous web server:
zelyra serve src/main.zyl 0.0.0.0:8080
For professional production deployments, you package your application into a Docker container:
- The container contains the compiled Zelyra binary, your project source files, and
zelyra.toml. - On startup, the container automatically runs database schema migrations via
zelyra db applyand launches the web server.
4. Small, progressive examples
Example 1: Production-ready project configuration
# zelyra.toml
[package]
name = "tasks_production"
version = "1.0.0"
[capabilities]
database = true
filesystem = false
network = true
Example 2: Clean web server entry point
database main {
engine: mariadb
database: "tasks_prod"
}
table tasks {
id: Id primary auto
name: String(120) required
done: Bool default false
}
page "/" {
html {
<h1>Zelyra Task Manager Live</h1>
<p>Production system active and secure.</p>
}
}
Example 3: Server startup commands On your production host or orchestration cluster:
zelyra db apply src/main.zyl
zelyra serve src/main.zyl 0.0.0.0:80
5. Typical errors and their causes
- Error: Starting the web server before applying database migrations (
zelyra db apply). Cause: New tables and columns must exist in MariaDB before incoming HTTP requests attempt to query them. - Error: Forgetting to expose port
8080in Docker. Cause: Ensure yourdocker runcommand or compose definition includes-p 8080:8080.
6. Key takeaways
zelyra servelaunches the built-in HTTP server without external web server dependencies.zelyra db applysafely migrates production schemas to the latest version.- Explicit capability constraints in
zelyra.tomlharden your production server against unauthorized system access.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Launch your web application locally on port 3000 using
zelyra serve main.zyl 127.0.0.1:3000. - Level 2 (Medium): Author a
docker-compose.ymlfile orchestrating MariaDB and your Zelyra application container. - Level 3 (Challenging): Simulate a production schema update: add a new column, run
zelyra db planto review SQL statements, and executezelyra db apply.
8. Practical project task: Task Management
Consolidate all production deployment settings for our Task Management system:
database main {
engine: mariadb
database: "tasks_production"
}
table tasks {
id: Id primary auto
name: String(100) required
completed: Bool default false
}
page "/" {
html {
<html>
<body>
<h1>Task Management - Production System</h1>
<p>System ready for user traffic.</p>
</body>
</html>
}
}
9. Summary
- Zelyra applications run directly and performantly without complex server stack dependencies.
- Database migrations, type-checked schemas, and web serving mesh seamlessly into one unified workflow.
10. Self-check review questions
- Which command launches the integrated web application server?
- Why must
zelyra db applybe executed prior to starting the production HTTP server? - What architectural advantages does an embedded HTTP server offer over external web server setups?
PART X – CAPSTONE PROJECT AND LOOKING AHEAD
Chapter 40: The Grand Capstone Project: Complete Task Management
1. What will I learn in this chapter?
In this grand finale, you will learn:
- How all the individual building blocks learned throughout this book fuse into a cohesive, production-ready application.
- How databases, authentication, role permissions, CRUD dashboards, REST APIs, and business contracts mesh seamlessly together.
- How to read, understand, compile, and deploy the entire monolithic application on a live server.
2. Why is this topic important?
Understanding isolated code snippets is one thing—building a real, production-ready web application from a single cohesive blueprint is the true craft of software engineering. This capstone project demonstrates the extraordinary elegance of Zelyra: in a single, well-structured file, you create a database-backed, authenticated web system with UI views and REST APIs that would require dozens of fragmented files in legacy frameworks.
3. Understandable explanation without unnecessary jargon: Overview of the Complete Project
Our complete Task Management application comprises:
- Database & Schema Persistence: Target MariaDB engine with relational tables for tasks (
tasks), users (users), sessions (auth_sessions), and permissions (user_permissions). - Authentication: Integrated
auth usersmechanism with protected session tokens and secure Argon2 password hashing. - Business Domain Logic: Pure functions governed by formal contracts (
requires,ensures) for calculating completion rates and priority classifications. - CRUD Interface: Full administrative web dashboard featuring card-mode listings, detail views, creation/edit forms, and safe deletion workflows.
- REST API: Strongly typed JSON endpoints for programmatic external access.
4. Small, progressive examples: The Complete Project: Final Source Code
database main {
engine: mariadb
database: "zelyra_tasks_app"
}
# ==========================================
# 1. AUTHENTICATION & USER MANAGEMENT
# ==========================================
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. TASK DATA MODEL
# ==========================================
type TaskId = Id
table tasks {
id: TaskId primary auto
name: String(120) required
description: String(500)
priority: Int default 2
completed: Bool default false
}
# ==========================================
# 3. BUSINESS LOGIC WITH CONTRACTS
# ==========================================
fn calculate_success_rate(completed_count: Int, total_count: Int) -> Int
requires { total_count > 0 && completed_count >= 0 && completed_count <= total_count }
ensures { result >= 0 && result <= 100 }
{
return (completed_count * 100) / total_count
}
fn priority_label(level: Int) -> String {
match level {
1 => {
return "HIGH"
}
2 => {
return "MEDIUM"
}
3 => {
return "LOW"
}
_ => {
return "NORMAL"
}
}
}
# ==========================================
# 4. WEB UI & CRUD INTERFACE
# ==========================================
crud Task -> tasks {
title: "Zelyra Task Management"
view {
fields {
name
priority
completed
}
list {
mode: cards
empty: "No tasks found. Create your first task!"
}
detail {
mode: cards
title: "Task Details"
}
form {
mode: cards
title: "Edit Task"
submit: "Save Task"
}
delete {
title: "Delete Task"
message: "Do you really want to delete this task?"
submit: "Delete Now"
}
}
action complete_task {
label: "Mark as completed"
confirm: "Complete this task?"
sql {
UPDATE tasks
SET completed = true
WHERE id = :id
}
success "Task successfully completed."
redirect "/tasks"
}
}
# ==========================================
# 5. REST API ENDPOINTS
# ==========================================
api GET "/api/tasks/{id}" {
input {
id: TaskId
}
output Task
errors {
404 NotFound
}
}
api POST "/api/tasks" {
input {
name: String
priority: Int
}
output Task
errors {
400 ValidationError
}
}
# ==========================================
# 6. LANDING PAGE
# ==========================================
page "/" {
html {
<html>
<head>
<title>Zelyra Task System</title>
</head>
<body>
<h1>Zelyra Task Management</h1>
<p>The complete capstone project is ready to run.</p>
<a href="/tasks">Go to task overview</a>
</body>
</html>
}
}
# ==========================================
# 7. APPLICATION ENTRY POINT
# ==========================================
fn main() {
print("Zelyra task management fully initialized.")
}
5. Typical errors and their causes
- Error: Launching the web server before executing
zelyra db apply. Cause: MariaDB tables and columns must be migrated before incoming web traffic can be serviced. - Error: Missing database credentials in your
.envconfiguration. Cause: EnsureDB_HOST,DB_USER, andDB_PASSWORDare configured in your local.envfile.
6. Key takeaways
- In Zelyra, a complete, secure web application is declared in one cohesive, maintainable source file.
- Type safety, contracts, user authentication, and REST APIs integrate with zero boilerplate.
- This application compiles instantly with
zelyra checkand serves live traffic viazelyra serve.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Compile the complete project using
zelyra checkand verify that 0 errors are reported. - Level 2 (Medium): Extend the
tasksschema with afaellig_am: Datedeadline field and update the form and list views accordingly. - Level 3 (Challenging): Set up a local MariaDB instance, apply the schema with
zelyra db apply, and create your first real production tasks through your web browser.
8. Practical project task: Task Management - Launching Your Own Production Server
Initialize the project directory and start the server:
zelyra new task_manager --template minimal
cd task_manager
# Insert the complete source code above into src/main.zyl
zelyra check src/main.zyl
zelyra serve src/main.zyl 0.0.0.0:8080
Navigate to http://localhost:8080 in your browser—your own live Zelyra application is operational!
9. Summary
- The capstone project unites all nine preceding parts of this textbook into one production-grade system.
- You have learned how to build modern, fault-tolerant web applications from the ground up.
10. Self-check review questions
- What components were successfully combined in the capstone project?
- Why does Zelyra require so few lines of code to deliver a comprehensive CRUD system?
- What sequential steps are required to deploy this project to a fresh production server?
Chapter 41: Functionality and safe operation
1. What will I learn in this chapter?
- What changed in Zelyra 1.0.1 through 1.0.7.
- How compiler versions, language compatibility, and database support relate.
- How to review local changes safely and edit the invoice tutorial in Studio.
2. Why does this matter?
A program can compile successfully and still contain business errors. A successful schema update does not replace a backup. Check the application and its database workflow before applying a new version.
3. Current scope
Zelyra 1.2.39 is the current stable release; the language compatibility line remains 0.1. MariaDB has the broadest tested runtime path. PostgreSQL and MySQL support selected workflows, but do not automatically provide identical CRUD, authentication, mail, or recovery features. See the database matrix for tested paths.
Since 1.0.0, releases added Compose v2 detection and a repaired Studio Run entry point (1.0.1); automatic local tutorial database, schema, and sample-data setup plus for ... step and range (1.0.2); the expanded standard library, |> pipelines, else if, free ports, and repeatable sample data (1.0.3); dedicated Compose project names and isolated database fallback without deleting old volumes (1.0.4); local Studio logs, safe uninstall, date-only invoice entry, server-assigned invoice numbers, a workflow-focused tutorial home, and detailed bilingual source guides (1.0.5); Zelyra UI compiler-loaded component library with compiler-checked UI components, themes, variants, responsive layouts, charts, and browser print previews (1.0.6); value-producing if expressions, functional record updates (Customer { ..old, active: true }), typed lexical lambdas for pipelines (map, filter, find, reduce), boolean match guards, nested record patterns, tuple binding, execution step budgets (--max-steps), nominal Id aliases (type OrderId = Id), server-side text PDF rendering (render_pdf), and transactional PDF mail attachments via the outbox (1.0.7). The guide explains that crud Invoice -> invoices declares the resource from which Zelyra generates routes such as /invoices/new. Full lists are in Appendix D and the release notes.
4. Invoice tutorial in Studio
Generate the tutorial without a separate CLI startup, then open it in the editor:
zelyra --tutorial invoice --no-start
zelyra editor ./invoice-tutorial
Select main.zyl and click Run. With the local .env configuration, Studio starts MariaDB through Docker Compose, applies the schema and repeatable sample data, then starts the web preview. MariaDB runs in a container; the web application is a local Zelyra process. Docker Compose v2 or newer is required; Python Compose v1 is unsupported. Check the plugin with docker compose version.
Edit src/customers.zyl, src/items.zyl, src/invoices.zyl, src/dashboard.zyl, or src/shell.zyl in the project tree. Local style rules are in zelyra.theme.css. Add business records through the forms; the seed contains repeatable sample data only. The / page guides the invoice workflow; /views/invoicedashboard is the invoice overview. src/dashboard.zyl declares the overview, and crud Invoice -> invoices generates the standard routes, including /invoices/new. The bilingual TUTORIAL.en.md and TUTORIAL.de.md files explain every setup command, option, source file, and source line. Invoice dates use a date-only input with a format hint, while the server assigns invoice numbers.
5. Installation and safe changes
Check the installed version with zelyra --version and the environment with zelyra doctor. Back up source, configuration, and database before changes. Read zelyra db plan main.zyl before running db apply. A reverse migration can remove later data; interrupted DDL can leave a partially changed state.
6. 1.0.7 release and tutorial details
The 1.0.7 release notes and roadmap distinguish released features from future work. The tutorial now documents every setup command and option, describes each source file and explains its source lines. In particular, crud Invoice -> invoices in src/invoices.zyl declares an invoice resource; Zelyra derives routes such as /invoices/new from that declaration and its standard CRUD actions. See the generated project guides TUTORIAL.en.md and TUTORIAL.de.md, or the invoice tutorial guide. Studio Logs show local Run and preview output and setup events; zelyra uninstall removes the active user-owned binary after confirmation and does not remove project data.
7. Common errors
- Compose is not detected: Check
docker compose version. Zelyra requires the Compose v2 plugin; Python Compose v1 is not sufficient. - Studio shows no tutorial data: Check that Run completed and local MariaDB is reachable. Studio does not automatically start external or overridden database connections.
- A check is mistaken for a general correctness proof: Compiler and integration checks cover defined workflows; they do not prove arbitrary applications correct.
8. Practical task: Prepare changes
zelyra --version
zelyra doctor
zelyra check main.zyl
zelyra db plan main.zyl
Review the output. Rehearse backup and restore in a separate test database before applying schema changes.
9. Summary
Zelyra 1.2.39 is the current stable release. The invoice tutorial starts MariaDB in Docker and runs the web preview as a local Zelyra process. Zelyra UI supplies locally served, compiler-checked components. Further features and their limits are described above, in the release notes, and in the database matrix. The CSV example package provides explicit typed conversions but does not write database records.
10. Review questions
- What is the difference between compiler version and language compatibility line?
- Which important language and tutorial improvements arrived in 1.0.3, 1.0.6, and 1.0.7?
- Which tutorial components run in Docker, and which source files do you edit in Studio?
- How do release notes differ from the roadmap?
Chapter 42: Your Journey as a Zelyra Developer
1. What will I learn in this chapter?
In this final chapter, you will learn:
- How to deepen your acquired expertise and apply it to ambitious real-world applications.
- Core architectural principles for engineering durable, maintainable software systems.
- How to participate in the growing Zelyra community and contribute to its open-source ecosystem.
2. Why is this topic important?
Programming cannot be mastered merely by reading; it is mastered by building. This textbook has provided you with a rigorous foundation—now your personal journey as an autonomous software engineer begins. With Zelyra, you command a modern, statically verified, and future-proof language engineered to support you at every stage of development.
3. Understandable explanation without unnecessary jargon: The Five Golden Rules for Zelyra Developers
- Model First: Always initiate projects with your
tableschema. A well-modeled data schema eliminates half of all future implementation defects. - Define Contracts: Anchor critical functions with
requiresandensures. Contracts simultaneously serve as live documentation, formal verification, and unit tests. - Grant Capabilities Deliberately: Keep the vast majority of your codebase purely functional (zero capabilities), and isolate external side effects explicitly.
- Treat Errors as Values: Leverage
ResultandOption. Eliminate excuses for unhandled runtime crashes and null-pointer exceptions. - Collaborate with AI: Exploit Zelyra's
--format jsondiagnostics and Typed Holes_to turn modern AI coding assistants into reliable, high-velocity copilots.
4. Small, progressive examples: Architectural Patterns and Best Practices
Example 1: Pure functions safeguarded by contracts
fn calculate_completion_percentage(completed: Int, total: Int) -> Int
requires { total > 0 && completed >= 0 && completed <= total }
ensures { result >= 0 && result <= 100 }
{
return (completed * 100) / total
}
fn main() {
print(calculate_completion_percentage(4, 5))
}
Example 2: Explicit capability boundaries separating domain logic from I/O
fn format_system_status(service_name: String, is_active: Bool) -> String {
if is_active {
return service_name + " is active."
}
return service_name + " is offline."
}
fn log_status(message: String) uses FileSystem {
write_text("system.log", message)
print("Logged: " + message)
}
fn main() uses FileSystem {
status = format_system_status("TaskWorker", true)
log_status(status)
}
5. Typical errors and their causes
- Error: Abandoning contracts and type safety when rushing through new projects.
Cause: In the long run, skipped contracts cause difficult-to-trace regression bugs. Invest the few seconds to specify
requiresandensures. - Error: Over-assigning capabilities indiscriminately.
Cause: Giving every function
uses Database, FileSystem, Networkdefeats the purpose of the capability security model.
6. Key takeaways
- Software quality starts with a clean data schema and contract-based design.
- Pure functions make up the reliable core of your application; side effects are strictly declared capabilities.
- Continuous learning and practical coding are the keys to becoming an accomplished Zelyra developer.
7. Exercises (Level 1 Easy, Level 2 Medium, Level 3 Challenging)
- Level 1 (Easy): Review your previous code snippets from earlier chapters and verify that all functions have descriptive parameter names.
- Level 2 (Medium): Take one of your earlier exercises and add strict
requiresandensurescontracts to all calculation functions. - Level 3 (Challenging): Draft a complete architecture plan (database schema, capabilities, pure domain functions, and REST routes) for one of the suggested next projects.
8. Practical project task: Task Management - Final Reflection and System Extension Ideas
Reflect on the complete Task Management system built across this textbook and consider extensions:
- Personal Household Budget: Incomes, expenditures, categories, and monthly summaries utilizing Zelyra CRUD.
- Support Ticket System: Customer inquiries, priority escalation, staff assignments, and automated email dispatches.
- Client & Project Time Tracking: Timesheet tracking with
uses Clockand billing reports exported as structured JSON.
9. Summary
Congratulations! You have successfully mastered all 10 parts and 42 chapters of Learning Zelyra. You master the fundamentals, type system, error handling, databases, web applications, APIs, and secure software architecture. You are now equipped to create your own robust, production-grade applications with Zelyra!
10. Self-check review questions
- Which unique feature of Zelyra do you appreciate most after completing this course?
- Why is the seamless interplay of language, compiler, and database in Zelyra so revolutionary?
- Which project will you build next with Zelyra?
Chapter 43: Working with Zelyra Studio
1. What will I learn in this chapter?
- How to start the local Zelyra editor and work with project source files.
- How to inspect a database schema without changing records.
- How guided CRUD preview generates and checks source before saving it.
2. What is Zelyra Studio?
Zelyra Studio is the web editor shipped with the Zelyra CLI. It helps you open a small Zelyra project, edit source, read compiler diagnostics, and inspect relevant database information in one place. You do not need a cloud editor or an external AI service.
Studio is a local development tool. It is not yet a complete IDE and it does not make product decisions for you. Generated code remains ordinary Zelyra source that you can read, check, and version outside the editor.
3. Start Studio
Change to your project directory and start the editor:
cd machine-management
zelyra editor
You can also pass the project directory directly:
zelyra editor ./machine-management
The CLI opens the default browser and prints the local address. On SSH or headless systems, use zelyra editor . --no-open and open the displayed address yourself. Editing does not require a running database. Studio binds to 127.0.0.1; the startup address contains a random session token. Do not share it publicly. Browser writes require the token and the same browser origin.
4. Edit and check source
The project tree lists Zelyra source files. Open a file in a tab, search for text or symbols, use the available completion, and format source when needed. Before saving, Studio checks whether another program changed the file after it was loaded. On a conflict it does not overwrite the external version; reload the file and review the edits.
Compiler diagnostics appear in the Problems panel and help you find errors in the editor. The action finder opens common actions; contextual help explains supported terms, options, and examples. Ctrl+Space offers completion from the compiler project model and local bindings when the current code parses. F12 opens an unambiguous definition in the current or directly imported file. Shift+F12 finds lexically identical identifiers in saved .zyl files but does not resolve scopes.
The project map links recognized modules, imports, tables, TableViews, views, CRUD resources, pages, APIs, and functions to source locations. Page and CRUD layouts resolve within their module or through explicitly imported public views. Ambiguous and missing relationships receive stable diagnostic codes. Ambiguous and dynamic relationships are not guessed; size limits can make the map incomplete. Ctrl+S saves current content. Formatting and Run are explicit actions. Run executes main.zyl for at most 15 seconds and limits each output stream to 64 KiB; recognized environment and .env secrets are redacted. Interactive stdin is unavailable. Saving and running do not silently change other project files.
Accessibility and font size scaling
For comfortable reading and diverse display resolutions, Zelyra Studio includes built-in three-tier font scaling (Default 100%, Medium +15%, and Large +30%):
- Topbar toggle: The
#font-size-button(aA,aA⁺,aA⁺⁺) in the top navigation cycles through font scale levels. - Keyboard shortcuts: Press
Ctrl++(orCtrl+=) to increase size,Ctrl+-to decrease size, andCtrl+0to reset to default (on macOS, useCmd). - Command palette: The action finder (
F1/Ctrl+Shift+P) provides “Editor: Increase font size”, “Editor: Decrease font size”, and “Editor: Reset font size to default”. - Flash-free persistence: The chosen scaling tier is saved to browser
localStorage(zelyra-editor-font-size) and evaluated before DOM render to prevent any flash of unstyled content (FOUC). Both CodeMirror editor instances and Studio UI elements resize dynamically.
5. Inspect the database schema
Choose Database in Studio and load the schema. DATABASE_URL must be available in the process environment or local project .env file. The view reads table and column metadata and relationships. It does not read business records or apply schema changes.
The map shows tables and foreign-key relationships. Within the tested scope it compares declared tables and columns with the live database and marks differences. Grouping by source file helps you navigate the project; it does not grant permissions.
Visual schema inspection is available for the tested MariaDB and PostgreSQL paths. MySQL has a bounded parameterized query path in this release, but no live Studio introspection. See the exact database support matrix.
Browse data and edit source columns
The data browser reads records only on explicit request, with at most 25 rows per page. It is read-only. The schema view itself reads metadata; opening it does not automatically load business records.
The column assistant adds source fields and edits simple existing fields. Key, unique, and default-valued fields are excluded from visual editing. Studio shows the diff and compiler diagnostics and rechecks the file hash before apply. Applying writes source only. Review zelyra db plan main.zyl afterward and perform database changes separately.
5a. Start the invoice tutorial in Studio
For the IDE workflow, generate the project files without starting the separate CLI workflow:
zelyra --tutorial invoice --no-start
zelyra editor ./invoice-tutorial
Select main.zyl in the project tree and click Run. With the local tutorial configuration in .env, Studio starts MariaDB through Docker Compose, waits for the database, applies the declared schema, and loads the repeatable sample data. Studio then starts the web preview and shows its address. Click Stop preview to end it.
Docker Engine and Docker Compose v2 or newer must be installed and accessible to your user. Python Compose v1 is unsupported. Check with docker compose version; Zelyra does not install Docker or Compose. If the Compose plugin is missing, follow the Docker Engine installation guide for Linux.
In this workflow, MariaDB runs in a Docker container. The web application runs as a local Zelyra process; it is not itself inside an application container. Studio does not start or automatically modify an external database or a connection overridden through the process environment.
The tutorial includes two customers, three items, and the draft invoice RE-2026-001 with two line items. Repeated starts do not duplicate the marked sample data. Add your own customers, items, and invoices through the application forms.
5b. Edit the invoice tutorial
Edit and save the files in Studio's project tree:
src/customers.zyl: customer fields, form, and list.src/items.zyl: items, descriptions, and net prices.src/invoices.zyl: invoice fields, line items, and print view.src/dashboard.zyl: the tabular invoice overview.src/shell.zyl: shared page frame and navigation.zelyra.theme.css: local style overrides.
Studio shows compiler diagnostics while you edit. Select main.zyl and click Run again to start the local preview with your changes. Enter business records through the application at /customers, /items, and /invoices; the invoice overview is currently available at /views/invoicedashboard.
/invoices/new is not declared as an explicit page route. The crud Invoice -> invoices declaration in src/invoices.zyl generates CRUD routes for the invoices table path, including GET /invoices/new for the form and POST /invoices/new to save it. The name after -> supplies the URL base path. By contrast, the print route page "/invoices/{invoice_number}/print" is written explicitly in the source. Run zelyra routes main.zyl to inspect where a route comes from: its output lists the method, path, route kind, source file, and line. /invoices/new therefore points back to the CRUD declaration.
The /views/invoicedashboard route is a searchable and filterable invoice table with calculated net, tax, and gross totals. It is not a second dashboard home page. The / route currently shows metrics and guides you through the tutorial workflow. The separation and design of these views are scheduled for review in 1.0.5.
If you run zelyra update while Studio is open, stop the session with Ctrl+C and restart Studio afterward. An IDE session that is already running does not automatically load a replaced executable.
If loading or saving a customer shows “The requested data could not be loaded”, first check that the local database started and schema setup succeeded. zelyra db create main.zyl prints SQL for review but does not create the database or tables itself.
6. Generate CRUD source with the guide
When a table is declared in your Zelyra project, exists in the live database, and does not yet have a CRUD resource, you can use the guide. Choose the source file, resource name, title, and visible fields. Studio shows affected file, table, and relationship information as well as the generated routes.
The workflow has three steps:
- Choose: Select the declared table, source file, and visible form fields.
- Check code and preview: Studio asks the normal project compiler to check the candidate and shows generated source, the diff, and any diagnostics.
- Apply change: The checked file is written only after your explicit confirmation. Immediately before writing, Studio checks the file hash again. If the source changed, it requires a fresh preview.
This lets you inspect the proposed change before it enters your project. Apply writes source code. It does not migrate the database or create or delete records.
7. A simple workflow
For a small customer application, you can:
- Create a project with a declared table or open an existing project.
- Start Studio and check that the database connection is available.
- Inspect the table and foreign keys in the database map.
- Generate a CRUD preview and read the diff.
- Apply the change only after the file, fields, and routes match your intent.
- Check the project again with
zelyra check main.zyland, when useful, run it withzelyra run main.zyl.
For customers, items, and invoices, you can instead start the guided CLI tutorial with zelyra --tutorial invoice. It walks through the connected application step by step.
Use the invoice tutorial
The tutorial uses multiple source files and connects customers and items to invoices and linked lines. The dashboard counts saved customers and invoices. Line items determine net, 19% VAT, and gross amounts; totals include drafts and do not represent posted revenue. The browser can save the print view as PDF. Tabler Core 1.6.1 ships locally as CSS without a CDN or external JavaScript. The layout stylesheet zelyra.css incorporates responsive layout rules so that the main content grid utilizes the full available width even on narrow viewports without collapsing into a narrow side column. The example is not an independently reviewed accounting application and does not contain a native PDF engine.
8. Limits and safety
- Studio runs locally and is not designed as a public multi-user editor.
- The database map is read-only. It is not a drag-and-drop database designer.
- The CRUD guide uses tables that are already declared and live; it does not design arbitrary data models.
- Database support depends on both backend and feature. Read the support matrix before planning a workflow.
- Preview and compiler checks help with safe edits, but they do not prove the entire application correct.
- Zelyra 1.0.4 is a stable release. Check documented feature, database, and security limits for the intended workflow.
9. Exercises
- Level 1: Start
zelyra editorin a learning project and openmain.zyl. - Level 2: Open the database map and note one declared relationship. Do not change database objects.
- Level 3: Generate a CRUD preview, inspect the diff, and compare its listed routes with
zelyra routes main.zyl.
10. Summary
Studio brings together source editing, compiler feedback, a schema overview, and guided CRUD source preview. You keep control over explicit apply; the database map remains read-only and generated CRUD remains ordinary Zelyra source.
TECHNICAL REFERENCE MANUAL
1. What makes Zelyra different
A typical business application describes the same fact repeatedly: in the database, backend, form, and API. Zelyra aims to turn those copies into one traceable chain:
Table → Types → SQL → Forms → CRUD → Web page → API/OpenAPI
This field:
email: Email?
already says that the value is an email address, may be absent, affects SQL nullability, can select a suitable form control, and must be handled safely by views.
SQL remains SQL. Zelyra does not force a respectable JOIN to disguise itself
as a 38-link method chain. SQL has suffered enough.
2. Installation
Prerequisites
For language examples, you need:
- Linux, macOS, or Windows (PowerShell/WSL);
curl;- a working shell.
MariaDB is required only for database, form actions, auth, and CRUD examples.
Install from the repository
git clone https://github.com/sf1976/zelyra.git
cd zelyra
./install.sh
The installer is repeatable and user-local. Control options:
./install.sh --help
./install.sh --dry-run --root "$HOME/.local"
./install.sh --check
./install.sh --uninstall
Use --no-rustup to disable automatic Rust installation; --no-path suppresses PATH instructions. With --root PATH or ZELYRA_INSTALL_ROOT, you can choose a different user-local target. Outdated cargo PATH entries are detected and not executed blindly.
Published releases for Linux x86_64 and Windows x86_64 can be installed without Rust or Cargo. The archive is downloaded over HTTPS and verified with SHA-256:
./install.sh --release v1.0.5
On Windows, install.ps1 is available for PowerShell and install.cmd for the Command Prompt:
git clone https://github.com/sf1976/zelyra.git
Set-Location zelyra
.\install.ps1
zelyra --version
Release archive installation on Windows:
.\install.ps1 -Release v1.0.5
Then:
zelyra --version
zelyra --help
If your shell cannot find zelyra:
export PATH="$HOME/.local/bin:$PATH"
Docker and container environment
Zelyra does not install Docker itself, modify operating-system packages, or request root privileges. For container and MariaDB workflows, Docker with Compose support is required:
- Linux: Follow the official Linux installation guide.
- Windows: Use Docker Desktop for Windows with Compose support.
- macOS: Use Docker Desktop for Mac.
Verify Docker Compose before first run:
docker compose version
If Docker is installed but access to its socket is denied, Zelyra reports a safe Linux group-membership remedy (sudo usermod -aG docker $USER) instead of raw Docker errors. Port collisions are also reported safely without exposing credentials.
Updating Zelyra
An update consists of two separate parts: updating the compiler in the compiler repository, and verifying your application project. Your .zyl files, zelyra.toml, and .env live in your application project and are never overwritten by install.sh.
Update from the Git repository
cd /path/to/zelyra
git status --short
git pull --ff-only origin main
cargo check --workspace
./install.sh
zelyra doctor /path/to/your-project/main.zyl --json
Check the installed version with zelyra --version:
zelyra --version
3. Your first program
Create hello.zyl:
fn main() {
print("Hello from Zelyra")
}
Run it:
zelyra run hello.zyl
A slightly more ambitious program:
fn fibonacci(n: Int) -> Int {
if n <= 1 {
return n
}
return fibonacci(n - 1) + fibonacci(n - 2)
}
fn main() {
print(fibonacci(10))
}
The result is 55. The computer survived. We may continue.
4. Create a project and use the CLI
Compiler repository and application projects
The GitHub repository sf1976/zelyra is the compiler repository. It contains lexer, parser, runtime, database, and web modules, as well as the CLI. Your own application is a separate directory. You do not need to work inside the Zelyra compiler source, and you should never store credentials there.
The project root is the directory containing your Zelyra source file and, if present, zelyra.toml.
✅ Create projects with the CLI
zelyra new addressbook
cd addressbook
zelyra run main.zyl
Initialize an existing directory:
mkdir addressbook
cd addressbook
zelyra init
For a local MariaDB and web-server template:
zelyra new machine-management --mariadb
cd machine-management
This creates main.zyl, .env.example, Dockerfile, docker-compose.mariadb.yml, and a protected .env with random passwords.
Automatic Port Selection on Conflict:
If default ports 3000 (web) or 3306 (MariaDB) are occupied, zelyra new, zelyra init, and zelyra setup automatically select the next free host ports and record them in .env. The optional --web-port <p>, --host-port <p>, and --db-host-port <p> flags enforce exact ports.
✅ Zelyra Setup Assistant (Console and Web)
zelyra setup provides unified setup actions across CLI and browser:
Console setup
From a MariaDB project directory:
zelyra setup
zelyra setup --database
zelyra setup --schema
zelyra setup --all
zelyra setup --host-port 18080 --db-host-port 3308
zelyra setup: Creates a protected.envif missing. Existing.envfiles are never overwritten.zelyra setup --database: Starts Compose services (detectsdocker composeor legacydocker-compose).zelyra setup --schema: Starts services and safely applies the schema frommain.zyl.zelyra setup --all: Executes configuration, container startup, and schema migration in one step.
Choose the project type before running setup:
-
A minimal project without a database does not need
setup:zelyra init ./test_a cd ./test_a zelyra check main.zyl zelyra run main.zyl -
For a new MariaDB project,
zelyra new test_a --mariadbcreates the required Compose and configuration files. Thencd test_a && zelyra setup --allprepares the database and schema. Alternatively,zelyra init ./test_a --mariadbenables MariaDB when initializing a project.
Project paths are relative or absolute, and those forms are not interchangeable:
zelyra setup ./test_a
zelyra setup /test_a
The first command looks for a directory under the current directory; the second
looks for /test_a under the filesystem root. If the
requested path does not exist, Zelyra reports E-SETUP-001; when a same-named
relative directory exists, the error suggests the matching relative command.
Local browser setup (zelyra setup --web)
zelyra setup --web
The server binds strictly to 127.0.0.1:3030 by default and outputs a one-time URL with a secure random token:
Zelyra setup web is running on http://127.0.0.1:3030/
open: http://127.0.0.1:3030/?token=<local-token>
- Provides browser actions to configure
.env, start containers, and apply schemas. - Strictly local-only for security; never expose to public interfaces.
- If port
3030is busy, the assistant automatically chooses the next free port. Stop withCtrl+C.
Verify your project state non-destructively:
zelyra doctor main.zyl --env-file .env
For a complete business starter project:
zelyra new machine-management --template mariadb-crud
cd machine-management
zelyra setup --all
Check, run, format, and verify
zelyra is the documented user-facing CLI. Use zelyra check <file.zyl> to
check syntax, types, contracts, and capabilities before running a program with
zelyra run <file.zyl>. The formatter is zelyra fmt <file.zyl>; add
--check to check formatting without writing changes. Use
zelyra verify <file.zyl> for the supported contract and invariant checks.
The current CLI reference does not list a zelyra test subcommand. Put
project-specific test routines in an executable source file and run that file
with zelyra run; Chapter 19 explains assertion-based checks. Likewise, the
reference does not list a separate zelyrac build executable: the compiler is
invoked through zelyra check and zelyra run.
LSP means Language Server Protocol, the standard for editor-to-language-server
communication. The documented editor entry point is zelyra editor, which
starts local Studio; the CLI reference does not list a separate zelyra lsp
subcommand.
5. Variables, types, and functions
Values are immutable by default:
machine_name = "Press 7"
capacity: Int = 120
active = true
Mutation is explicit:
mutable completed = 0
completed = completed + 1
The compiler is not paranoid. It has simply seen things.
Core types include:
Int UInt Float Decimal Bool String Char Bytes
Timestamp Date Time Duration Email Url Uuid Money
Functions are typed:
fn available_capacity(total: Int, reserved: Int) -> Int {
return total - reserved
}
Type names document the meaning of IDs:
type MachineId = Id
type OrderId = Id
These aliases have no separate runtime identity. Validate ID assignments and tenant boundaries in business logic; a type name alone does not establish access protection. Business mistakes do not become correct by sharing a storage type.
6. Option, Result, and pattern matching
Ordinary types are never null. Absence is explicit:
email: Email?
Handle both cases:
match email {
Some(value) => print(value)
None => print("No email address")
}
Pattern matching must be exhaustive, preventing the forgotten case that would otherwise introduce itself during a Friday deployment.
Functions expose failures through their return type. The current language core
uses Result<T, E> with Ok or Err:
fn load_number(found: Bool) -> Result<Int, String> {
if found {
return Ok(42)
}
return Err("not found")
}
7. MariaDB and tables
✅ MariaDB is the default backend and primary runtime reference. The generated
Compose template uses mariadb:11; the compiler does not enforce a specific
MariaDB server version. Database commands call the external mariadb client.
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 recognizes the relationship and can use it for foreign keys, forms, and select controls.
| Zelyra | MariaDB |
|---|---|
Id primary auto |
generated primary ID |
String(100) |
VARCHAR(100) |
String |
TEXT |
Email |
email-compatible text column |
Bool |
Boolean database value |
Timestamp |
timestamp value |
Prepare MariaDB safely
Create a dedicated database and application user. Do not run the Zelyra web
server as the MariaDB root account. The following is run as an administrative
MariaDB user; the password is requested interactively:
mariadb --host=127.0.0.1 --port=3307 --user=root --password
CREATE DATABASE `address_book`
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE USER 'zelyra'@'127.0.0.1'
IDENTIFIED BY 'ENTER_A_LOCAL_PASSWORD_HERE';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, REFERENCES
ON `address_book`.* TO 'zelyra'@'127.0.0.1';
SHOW GRANTS FOR 'zelyra'@'127.0.0.1';
Use IF NOT EXISTS when the database must be repeatably prepared. The grants
are deliberately limited to this database; never use GRANT ALL ON *.* in an
application quickstart. InnoDB is the expected engine for transactions and
foreign keys, but the current Zelyra SQL generator does not add
ENGINE=InnoDB itself. Inspect and supplement generated SQL before applying it
if your server policy requires that clause.
The listed grants cover normal CRUD work and the initial schema. A destructive
db apply --allow-destructive may additionally need DROP; grant it only
deliberately and, where possible, temporarily. db setup is an administrator
operation because it creates the database itself.
The generated Compose file publishes 127.0.0.1:3306 by default. If that host
port is occupied, change its binding to 127.0.0.1:3307:3306: the host port is
then 3307, while MariaDB remains on 3306 inside the container. Another
Compose service connects to host mariadb and port 3306; a host process uses
127.0.0.1 and the published port.
Inspect the address schema
For the example, zelyra db create src/main.zyl currently produces, in essence:
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;
For MariaDB, the generated table statements include
ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci.
The output is therefore ready for the documented MariaDB baseline, but it is
still a reviewed schema proposal: inspect db plan before applying changes to
an existing database.
Additional database backends
Besides MariaDB, the schema CLI can currently process SQLite and PostgreSQL.
Select the backend in the .zyl file:
database main { engine: sqlite database: "address-book.sqlite3" }
database main { engine: postgres database: "address-book" }
For SQLite, DATABASE_URL uses the sqlite:// or sqlite: scheme; MariaDB
accepts mariadb:// and the compatible mysql:// name. The create,
inspect, plan, and apply schema operations account for tables, columns,
foreign keys, and indexes. Automatic database creation through db setup and
db bootstrap currently supports MariaDB and SQLite; for PostgreSQL use
db create, db inspect, db plan, and then db apply.
export DATABASE_URL='sqlite:///tmp/address-book.sqlite3'
zelyra db bootstrap src/main.zyl
zelyra db inspect src/main.zyl
The native SQL runtime is still primarily exercised with MariaDB. A supported schema backend therefore does not automatically mean that every runtime query behaves identically on every backend.
8. Inspecting and applying schemas
Zelyra uses no migration classes. The source file is the desired schema; the CLI compares it with the current database. The actual workflow is:
- Check the source:
zelyra check src/main.zyl. - Print SQL only:
zelyra db create src/main.zyl. - Read the current schema:
zelyra db inspect src/main.zyl. - Plan the difference:
zelyra db plan src/main.zyl. - Read the plan and assess its risk.
- Apply only after review:
zelyra db apply src/main.zyl.
Supply the connection only through the process environment:
export DATABASE_URL='mariadb://zelyra:ENTER_A_LOCAL_PASSWORD_HERE@127.0.0.1:3307/address_book'
⚠️ Zelyra does not load .env automatically. It is a safe local place to
store values, but the process must receive the variables before the CLI runs;
see section 16.
Print the desired SQL:
zelyra db create src/main.zyl > sql/addresses.generated.sql
If you added ENGINE=InnoDB and the charset clauses after review, apply that
exact file manually with the MariaDB client:
mariadb --host=127.0.0.1 --port=3307 --user=zelyra --password \
address_book < sql/addresses.generated.sql
zelyra db apply does not read an edited SQL file; it regenerates its plan from
the .zyl source. Use either the unchanged Zelyra plan or the reviewed manual
client path, rather than blindly doing both.
Inspect, plan, and apply:
zelyra db inspect src/main.zyl
zelyra db plan src/main.zyl
zelyra db apply src/main.zyl
Destructive changes require explicit permission:
zelyra db apply src/main.zyl --allow-destructive
That flag does not mean “probably fine.” It means “I read the plan, have a backup, and my pulse is normal.”
For MariaDB, db setup and its db bootstrap alias first try to create the
database and then apply the generated schema. DATABASE_URL therefore needs
administrative privileges for that operation. An application user with limited
grants should use db create and have an administrator apply the SQL instead.
Without DATABASE_URL, db plan can plan against an empty schema; that is a
preview, not a connection test.
❌ There is no zelyra db check, zelyra schema inspect, zelyra schema plan,
or zelyra schema apply command. The actual readiness test is
zelyra doctor src/main.zyl; it checks static rules, Cargo, DATABASE_URL
when set, and the local web port.
In 10 minutes to a first Zelyra application
These steps consistently use the directory address-book, source file
src/main.zyl, database address_book, user zelyra, and host port 3307.
-
Create the project and copy the environment template:
zelyra new address-book --mariadb cd address-book cp .env.example .env -
Put local placeholder values into
.env. Compose reads theMARIADB_*values when the container is first initialized; Zelyra itself reads onlyDATABASE_URL. Change the Compose binding to127.0.0.1:3307:3306before starting it. -
Start MariaDB:
docker compose -f docker-compose.mariadb.yml up -d mariadb docker compose -f docker-compose.mariadb.yml ps -
As a MariaDB administrator, create the database and user with the SQL from section 7. Use an obvious local placeholder, never a real password in docs.
-
Save the valid address-book source as
src/main.zyland check it:zelyra check src/main.zyl -
Load
DATABASE_URLinto the process and test readiness..envis not automatically loaded by Zelyra:set -a . ./.env set +a zelyra doctor src/main.zyl --json -
Generate and inspect SQL:
zelyra db create src/main.zyl > sql/addresses.generated.sql sed -n '1,160p' sql/addresses.generated.sql -
Inspect and plan:
zelyra db inspect src/main.zyl zelyra db plan src/main.zyl -
Apply only after review:
zelyra db apply src/main.zyl -
Start the web server. A CRUD route also needs a web definition accepted by the server;
crudalone is not a static export:zelyra serve src/main.zyl 127.0.0.1:3000
On Windows, use Copy-Item and set the process variable in PowerShell:
Copy-Item .env.example .env
$env:DATABASE_URL = 'mariadb://zelyra:ENTER_A_LOCAL_PASSWORD_HERE@127.0.0.1:3307/address_book'
zelyra doctor src/main.zyl --json
The generated Compose template publishes host port 3306 by default. 3307
in this handbook is a deliberate alternative to avoid a collision; change the
Compose port binding before copying the quickstart.
9. Native SQL
✅ SQL is a language element:
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
}
}
Named parameters are bound safely:
fn load_machine(id: MachineId) -> Machine?
uses Database
{
return sql<Machine?> {
SELECT id, number, name, department_id, active
FROM machines
WHERE id = :id
}
}
Where schema information is available, Zelyra checks tables, columns, aliases,
parameters, nullability, result mappings, and the Database capability.
Writes can be grouped in transactions:
transaction {
sql {
UPDATE machines
SET active = false
WHERE id = :id
}
}
10. Web pages
✅ A complete, safe web layer with typed data bindings, declarative query controls, and reusable views is implemented.
page "/machines/{name}" {
html {
<html>
<body>
<h1>Machine {name}</h1>
<p>Running smoothly.</p>
</body>
</html>
}
}
Start the web server:
zelyra serve app.zyl
Then visit:
http://127.0.0.1:3000/machines/Press-7
Path parameters are HTML-escaped by default. The web core handles GET routes, path parameters, query strings, and HTML responses.
Typed view data loading and collection loops
View interpolations are checked before the server starts. A page may load a typed record and use checked field access:
page "/customers/{name}" {
load customer = sql<Customer> {
SELECT id, name FROM customers WHERE name = :name
}
html { <h1>{customer.name}</h1> }
}
SQL is checked against the schema, route parameters are safely bound, and capabilities and permissions are enforced. Collections can be rendered with a typed server-side loop:
page "/customers" {
load customers = sql<Customer[]> { SELECT id, name FROM customers }
html { <ul>for customer in customers { <li>{customer.name}</li> }</ul> }
}
Declarative web query controls: Search, sort, pagination, and filter
Pages can declare typed query inputs for explicit SQL:
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>Search: {search}</p> }
}
For page collections declaring search, sorting, pagination, or filtering, Zelyra automatically generates semantic query controls and preserves URL state:
page "/customers" {
search { name email }
sort { name }
paginated 25
filter { name quantity }
load customers = sql<Customer[]> { SELECT id, name, quantity FROM customers }
html {
<p>Page: {page} of {pages} (Total: {total})</p>
<p>Sort: {sort} ({order})</p>
}
}
search { name email }: Compiler-checked whitelist for?search=..., parameterizedLIKEquery.sort { name }: Accepts declared result fields;orderacceptsascordesc(/customers?sort=name&order=desc).paginated 25: Validatespageas positive integer, applies parameterizedLIMIT/OFFSET, and exposespage,pages, andtotalasUInt.filter { ... }: Supports typed operators: text fields supporteq,contains,starts_with,ends_with, and null checks; numeric fields additionally supportgt,gte,lt,lte. Examples:/customers?filter_name__contains=Acmeor/customers?filter_quantity__gte=10. Invalid operators return controlled HTTP 400.
Reusable view layouts and components
A named view provides a reusable page layout with slots:
view SiteShell {
html {
<html><body><main><slot /></main></body></html>
}
}
page "/customers" {
view: SiteShell
html { <h1>Customers</h1> }
}
Typed components declare properties via props:
component Badge {
props { text: String }
html { <span class="badge">{text}</span> }
}
page "/status" {
html { <Badge text="Ready" /> }
}
Components accept child HTML through default and named slots with fallback content:
component Panel {
html {
<section class="panel">
<header><slot name="header">Default Header</slot></header>
<div class="body"><slot /></div>
</section>
}
}
page "/dashboard" {
html {
<Panel>
<slot name="header"><h1>My Dashboard</h1></slot>
<p>Main panel body content.</p>
</Panel>
}
}
Note: Component slots nested inside a component invocation no longer require an outer page view: layout.
11. Forms
✅ Forms can inherit table rules:
form MachineCreate -> machines {
fields {
number
name
department
active
}
}
An explicit definition can add presentation and validation rules:
form ContactForm {
field email: Email {
label: "Email"
required
max: 255
widget: email
}
}
Validate without a server:
zelyra form validate examples/customer_form.zyl CustomerCreate \
name="Example Ltd" email=info@example.test
With zelyra serve, Zelyra exposes a form at /forms/FormName. GET renders a
CSRF token; POST checks the token and validates the submitted values.
Database-backed action:
form CustomerCreate -> customers {
fields { name email }
action save {
requires auth
permits "customers.save"
sql {
INSERT INTO customers (name, email)
VALUES (:name, :email)
}
redirect "/customers"
}
}
Form actions may declare their own authorization. The permission is checked when the form is rendered and again before submission.
12. CRUD
✅ The shortest case is satisfyingly short:
crud Machine -> machines
Configured resource:
crud Machine -> machines {
title: "Machines"
list { number name department active }
search { number name }
filter { department active }
}
Zelyra provides list and detail pages, Create/Edit forms, search, filters, allowlisted sorting, pagination, and CSRF-protected deletion. Configured fields are checked against the schema.
Example URLs:
/machines
/machines?search=Press
/machines?filter_active=true
/machines?sort=number&order=asc
/machines/new
/machines/42/edit
🗺️ Fully custom typed components and fine-grained view overrides are part of the continuing view roadmap.
CRUD views, actions, and soft delete
🧪 The current CRUD layer can be customized inside the safe default paths. A list can use cards without losing search, filters, sorting, pagination, output escaping, or permission checks:
crud Customer -> customers {
view {
list {
mode: cards
empty: "No customers found."
}
detail {
mode: cards
title: "Customer details"
}
form {
mode: cards
title: "Customer form"
submit: "Save customer"
}
delete {
title: "Delete customer"
message: "This action cannot be undone."
submit: "Delete now"
}
loading { message: "Loading customers ..." }
error {
title: "Customers unavailable"
message: "Please try again later."
}
}
}
For the common case, a shared field profile can uniformly control the generated list, detail view, and create/edit forms:
crud Customer -> customers {
view {
fields { name email active }
}
}
An explicit list { ... } remains a local override for list/detail. Primary keys
and auto-generated fields are automatically excluded from forms; the compiler
rejects unknown profile fields. When the technical id column is omitted from
visible fields (as in name email active above), Zelyra automatically links
the first displayed field (name) to the record detail page for both table and
card layouts.
Domain actions remain POST-only, parameterized, and protected. An action can bundle multiple SQL statements executed atomically within a shared MariaDB transaction:
crud Invoice -> invoices {
action lock {
label: "Post and lock (audit compliance)"
confirm: "Do you really want to permanently post and lock this invoice?"
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 "Invoice successfully posted and locked."
redirect "/invoices"
}
}
Execution Model and Transactional Behavior of Actions
- Atomic database transaction: All SQL statements within
sql { ... }run together in a single MariaDB transaction (with_decision_transaction). If any statement fails (e.g., due to a constraint violation), the entire transaction is rolled back atomically. - Parameter binding: Parameters such as
:idare bound automatically from the URL path of the target row. Custom fields declared withfield <name>: <Type>are rendered in the confirmation dialog as typed inputs, validated upfront, and passed as:nameparameters to the SQL statements. - Current execution scope: Since 1.2.28, CRUD actions support typed handlers, row and session data, and checked guards within the existing server-side transaction context. Allowed expressions and effects follow the documented action syntax; actions are not arbitrary Zelyra programs.
The runtime enforces the database capability, CSRF protection, authentication, and the declared permission. Actions can also declare typed fields, a server-side confirmation page, and custom success or error pages. This extends the existing CRUD runtime; it is not a freely programmable frontend generator.
Reversible deletion is available through soft delete:
table customers {
id: Id primary auto
name: String(100) required
deleted_at: Timestamp?
}
crud Customer -> customers {
soft_delete { column: deleted_at }
}
Normal lists and details show only rows whose column is NULL; archived
records are available through ?archived=true and can be restored through a
CSRF-protected action. Permanent deletion, retention rules, and bulk archiving
remain planned.
When an auth definition declares an audit table, CRUD create, update, delete, archive, restore, and custom actions write events in the same MariaDB transaction. Passwords, tokens, secrets, and hashes are removed from change details.
13. Authentication and permissions
🧪 Zelyra supports Argon2 login, persistent MariaDB sessions, logout, route guards, and database-backed permission checks. Five failed attempts for the same normalized e-mail address within 15 minutes trigger a 60-second HTTP 429 lockout. A successful login rotates and invalidates the previous browser session token.
Every state-changing browser form requires its CSRF token and a same-origin
Origin or Referer matching the request Host and effective scheme.
Malformed, missing, or cross-origin evidence is rejected, so copying a token
from another browser cannot authorize a cross-site form submission. API
requests carrying browser-origin information or a browser session cookie also
receive this check, including GET requests because handlers are not yet
statically restricted to read-only behavior. Cross-origin API access is
possible only for an exact origin explicitly listed in the project's CORS
policy; a session cookie additionally requires credentialed CORS. The current
CSRF token is process-scoped rather than individually stored per session, so
these origin checks are a required part of the protection.
The Zelyra server currently speaks plain HTTP. Put it behind a trusted TLS
terminating proxy before exposing it beyond a local development machine. The
proxy must preserve the public Host, overwrite X-Forwarded-Proto with the
actual external scheme, and prevent direct public access to the application
port. Zelyra uses that header to validate the effective origin and to add the
Secure attribute to session cookies for HTTPS requests. Never trust a
client-supplied forwarded header at an exposed proxy boundary.
The server also checks every supplied Host header against
ZELYRA_ALLOWED_HOSTS. The default permits only localhost, 127.0.0.1, and
[::1], which also blocks DNS rebinding through arbitrary hostnames. Add the
actual hostname explicitly to the comma-separated .env setting when using a
custom domain or LAN host. Schemes, ports, and wildcards are not accepted.
Duplicate security-sensitive request headers such as Host, Origin,
Referer, Cookie, and Authorization are rejected to avoid ambiguous
parsing. The response policy Referrer-Policy: same-origin allows
same-origin API GETs to provide that evidence without sending referrer
information to other origins.
Process environment overrides the project .env, which overrides the
fallback. The allowlist does not replace TLS or origin/CSRF checks.
Create a value for the required password_hash column with the CLI. The
interactive command disables password echo and asks for confirmation:
zelyra auth hash-password
For deliberate automation, pass one password line through --stdin. Do not
put real passwords in command arguments or commit generated hashes to source
control:
printf '%s\n' 'change-this-password' | 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>Machine management</h1>
}
}
The optional permissions table contains user_id and permission for
direct grants. Role groups are enabled with roles and role_permissions:
the first table contains user_id and role, and the second contains role
and permission. Effective permissions are the union of direct grants and
all permissions inherited from the user's roles. In the built-in administration
view, the permission revocation form transmits the valid CSRF token and
reliably removes selected permissions.
The same guards protect typed API handlers:
api GET "/api/machines/{id}" {
handler get_machine
requires auth
permits "machines.view"
input { id: MachineId }
output Machine
errors { 404 NotFound }
}
Protected API failures use JSON with an error code and message. A handler
can return Err("NotFound") to select a matching status from the declared
errors block; undeclared errors become 500 responses. Richer domain-error
values remain future work. JSON arrays can be bound to typed fields such as
Int[] or MachineId[]. Nested JSON input uses declared records:
struct Address { city: String }
struct CustomerInput { name: String address: Address }
api POST "/customers" {
handler echo_customer
input { customer: CustomerInput }
output CustomerInput
}
Unknown record fields and missing required fields are rejected. In the language
core, arrays support literals, indexing, len, append, contains, first,
last, and concatenation with +. Record literals and checked field access
are available for nested values:
customer = CustomerInput {
name: "Anna"
address: Address { city: "Berlin" }
}
print(customer.address.city)
Array iteration uses for ... in; the loop variable is immutable and scoped to
the loop body. break and continue are supported.
Generate a browser or Node-compatible TypeScript client from the same API declarations:
zelyra doc examples/api_records.zyl --typescript > customer-client.ts
The generated client uses standard fetch, includes declared records and
tables as TypeScript types, and handles path/query parameters, JSON bodies,
bearer tokens, response types, and HTTP errors. Declared API error names are
available through ZelyraApiErrorCode; ZelyraApiError.fromResponse extracts
the status, code, and server message while preserving the raw response body.
API errors may also carry a checked payload. Add the payload type after a
colon and use the same type as the error side of the handler's Result:
struct ValidationProblem {
field: String
message: String
}
api POST "/customers/validate" {
handler validate_customer
output Result<String, ValidationProblem>
errors { 422 ValidationError: ValidationProblem }
}
The response retains error.code and error.message and adds the serialized
payload as error.details. OpenAPI includes the details schema, while the
generated client exposes it through ZelyraApiErrorPayloads and the generic
ZelyraApiError.details field. Existing untyped API errors remain compatible.
Browser access is disabled by default. Enable exact origins in the project configuration when a separate frontend needs to call an API:
[web]
allowed_origins = ["http://localhost:5173"]
allow_credentials = false
Zelyra answers API OPTIONS preflight requests automatically and adds CORS
headers only to declared API routes. Wildcard origins are rejected, and CORS
does not bypass authentication or permissions. Enable credentials only when
browser session cookies are required; the client must also use
credentials: "include".
An origin must begin with http:// or https://. Paths, queries, fragments,
wildcards, and a trailing slash are not allowed. Allowed responses receive
Access-Control-Allow-Origin and Vary: Origin; enabled credentials add
Access-Control-Allow-Credentials: true. A preflight response returns HTTP 204
with the allowed methods and requested headers. Forbidden origins or methods
return structured JSON errors; method errors include the Allow header.
A hidden button is not a security boundary. Authorization must be enforced on the server-side action. Browsers become remarkably creative when trusted.
API input and secure response defaults
API request bodies for methods other than GET and DELETE may currently use
application/json or application/x-www-form-urlencoded. Unsupported media
types return HTTP 415 as a structured JSON error. JSON bodies must be objects;
each declared field is then converted to and checked against its Zelyra type.
The HTTP parser checks Content-Length, reads complete bodies across multiple
network reads, and limits request bodies to 1 MiB. Headers are limited to 64
KiB. An oversized body is rejected with HTTP 413 before the handler runs.
All HTML, JSON, redirect, error, and preflight responses receive these secure default headers:
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: same-origin
These defaults do not replace TLS, authentication, authorization, CSRF protection, or an appropriate Content-Security-Policy.
Role management and tamper-evident audit
🧪 Roles and role permissions can be managed with the available CLI commands
when the auth definition configures the corresponding tables:
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:password@127.0.0.1:3306/app' \
zelyra auth role grant app.zyl 42 manager
DATABASE_URL='mariadb://user:password@127.0.0.1:3306/app' \
zelyra auth role-permission grant app.zyl manager customers.edit
revoke removes the respective assignment. The commands bind values as SQL
parameters and check the project schema first. Do not use a real password in
the example value.
With audit: auth_audit_log, authentication, role, and CRUD events can be
inspected or exported:
DATABASE_URL='mariadb://user:password@127.0.0.1:3306/app' \
zelyra audit inspect app.zyl --limit 100
DATABASE_URL='mariadb://user:password@127.0.0.1:3306/app' \
zelyra audit export app.zyl --format json > audit.json
zelyra audit verify app.zyl
For visible tamper detection, enable chaining explicitly:
auth users {
table: users
audit: auth_audit_log
audit_chain: true
}
The audit table then needs id, previous_hash, and entry_hash, usually
String(64). Zelyra stores lowercase SHA-256 hex values. The hash covers the
canonical pipe-separated sequence
previous_hash|actor_user_id|event|target_user_id|details|created_at.
zelyra audit verify checks both links and hashes. Pruning is deliberately
disabled for chained logs because deleting an entry would break the chain.
Non-chained old entries can be deliberately removed with
zelyra audit prune ... --before ... --confirm.
An optional browser administration page is enabled by admin_path,
admin_permission, and admin_role. It can manage users, passwords, active
status, roles, and role permissions; its forms are CSRF-protected. The last
active administrator remains protected.
14. Capabilities
✅ External powers are declared explicitly:
fn load_machines() -> Machine[]
uses Database
{
return sql<Machine[]> {
SELECT id, number, name FROM machines
}
}
Known capabilities:
Database Network FileSystem Environment Process Clock Random Console Email
Calling functions must propagate required capabilities. Projects grant them
through zelyra.toml:
[capabilities]
database = true
network = false
Static checking and runtime enforcement at function, native-SQL, forms, CRUD, and authentication database boundaries are implemented when project grants are supplied. A complete operating-system sandbox for every capability is not yet available.
Two safe host APIs are implemented:
fn runtime_timestamp() -> Timestamp uses Clock {
return now()
}
fn configured_mode() -> String? uses Environment {
return env("ZELYRA_MODE")
}
now() requires Clock and returns Unix-epoch milliseconds. env(name) requires Environment and returns String?; a missing variable becomes None. Values are not logged or exposed automatically. Network, file-system, process, and random host APIs are implemented, but each requires its own resource grant and remains deliberately limited in this first version.
The Random capability provides secure integer generation:
fn dice_roll() -> Int uses Random {
return random_int(1, 6)
}
The range is inclusive on both sides. Invalid ranges fail at runtime, and random values are not emitted implicitly. Process execution is available only through the explicitly bounded API below.
The first Network host API is http_get:
fn load_status(url: String) -> String uses Network {
return http_get(url)
}
Project execution uses an exact host allowlist and bounded resources:
[network]
allowed_hosts = ["127.0.0.1:8080", "api.example.com"]
timeout_ms = 5000
max_response_bytes = 1048576
Without [network], a project allows no hosts. The transport supports
http:// and https:// with Rustls certificate verification enabled by
default. It does not follow redirects and returns only successful UTF-8 GET
response bodies within the configured limits. The http_get helper remains
the simple GET-only convenience API; use http_request when request headers,
request bodies, or response status are needed.
Typed requests use http_request:
fn create_customer(url: String) -> HttpResponse uses Network {
return http_request(
"POST",
url,
["Content-Type: application/json"],
Some("{\"name\":\"Anna\"}")
)
}
The method accepts GET, POST, PUT, PATCH, DELETE, and HEAD.
Headers are Name: value strings, the body is String?, and the typed result
contains status: Int, headers: String[], and body: String. GET/HEAD
requests cannot carry a body.
JSON values can be converted to and from checked Zelyra values. Records and their nested fields are validated against the declared schema:
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<Type>(text) requires one target type argument and supports
records, nested records, arrays, options, and scalar values. json_encode
serializes the same value families. Malformed JSON, type mismatches, unknown
record fields, and missing required fields are explicit runtime errors.
For a complete typed JSON request/response flow, use http_json with separate
request and response type arguments:
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))
}
The request record is serialized automatically and the response body is
decoded into the response record. If no Content-Type header is supplied,
application/json is added. Non-2xx responses are explicit runtime errors;
the helper returns the decoded value rather than response headers.
When response metadata must remain available, use http_result:
fn submit(url: String, payload: CustomerCreate) -> HttpResult<Customer> uses Network {
return http_result<CustomerCreate, Customer>("POST", url, [], Some(payload))
}
HttpResult<Response> contains status: Int, headers: String[],
body: String, data: Response?, and error: HttpError?. Successful 2xx
responses populate data; non-2xx responses populate error with status,
headers, body, and message. Transport failures and invalid success JSON remain
runtime errors.
The Process capability exposes a shell-free command API:
fn render_report(input: String) -> String uses Process {
return run_process("/usr/bin/printf", ["%s", input])
}
Project execution requires an exact command allowlist:
[process]
allowed_commands = ["/usr/bin/printf"]
timeout_ms = 5000
max_output_bytes = 1048576
Without [process], no command may run. The child environment is cleared,
stdin is closed, timeouts terminate the process, and stdout/stderr are
bounded. Shell execution, environment forwarding, working directories, and
pipelines are future work.
The FileSystem host APIs are:
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)
}
All four APIs require FileSystem. Reads and directory listings use read_roots; writes and deletes use write_roots. Relative paths are resolved from the project directory, and existing symlink targets are canonicalized before access. Without a filesystem section, project reads are limited to the project directory while writes and deletes are denied:
[filesystem]
read_roots = ["."]
write_roots = ["data"]
The configured directories must already exist. A new write target must have an existing parent directory.
Structured concurrency
An initial, limited concurrency flow is available through parallel and
await:
parallel {
customer = await load_customer()
orders = await load_orders()
}
Each branch binds its result with await. Branches receive an immutable
snapshot of surrounding values and are merged in source order before execution
continues. An error in one branch fails the whole block after the started
branches finish. The type checker rejects await outside a parallel block.
The current runtime uses one worker thread per branch; cancellation and
database connection-pool integration are not implemented yet.
15. Contracts and verification
🧪 Preconditions and postconditions:
fn reserve(stock: Int, amount: Int) -> Int
requires {
amount > 0
stock >= amount
}
ensures {
result >= 0
result == stock - amount
}
{
return stock - amount
}
requires runs before the body; ensures runs afterward, with result
representing the returned value.
zelyra verify examples/contracts.zyl
Statuses:
PROVEN
RUNTIME_CHECK
UNPROVEN
FAILED
Only PROVEN means proven. RUNTIME_CHECK does not wear a fake moustache and
claim to be mathematics.
The verifier also summarizes bounded function calls. A callee with multiple
return paths, such as an absolute-value function, contributes its path
conditions when a caller returns that call. Callee requires clauses are
checked after argument substitution; caller requires clauses are assumptions
when proving caller ensures. Complex, recursive, or unresolved cases remain
RUNTIME_CHECK.
Local state is included in these summaries. Both next: Int = value + 1 and
the concise next = value + 1, followed by return next, are analyzed like a
direct return. Simple linear mutable assignments such as next = next + 1 are
also tracked. Statically bounded loops with a linear counter are unfolded;
break exits the current loop and continue starts its next iteration as
separate symbolic paths. Nonlinear assignments and unbounded loops without a
proven invariant remain conservative.
Loop invariants
A while or unconditional loop may declare one or more explicit invariants:
while current > 0
invariant { current >= 0 }
{
current = current - 1
}
The verifier checks the invariant at entry and after supported body paths. A
proven invariant can summarize an otherwise unbounded linear while loop; an
unconditional loop can use it with a modeled break exit. Runtime execution
checks it before and after each iteration. Unsupported or unproven invariants
remain conservative and do not produce PROVEN results.
zelyra verify reports each declared invariant separately, after a function's
ensures results. Invariant indexes are zero-based:
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)
Every result includes a stable code and a source range as
(file.zyl:start-line:start-column-end-line:end-column). The codes are
V-001 (PROVEN), V-002 (RUNTIME_CHECK), V-003 (UNPROVEN), and
V-004 (FAILED). For IDEs and CI, use zelyra verify app.zyl --json; the
JSON output contains the same result data, a human-readable message, an
optional counterexample object, and a structured location object. A
counterexample is emitted only when a bounded search verifies a small linear
integer witness. The current search covers up to three linear variables in the
range -32..=32, including failed loop-invariant checks; otherwise it is
null. Text output also shows an explanation and a source-line excerpt with a
caret marker for every result.
FAILED means that the invariant is false on a feasible analyzed path or is
not preserved by the loop body. RUNTIME_CHECK means that runtime checking
is required because the symbolic verifier cannot complete the proof. Only
PROVEN is a mathematical proof.
16. Configuration and secrets
Project configuration belongs in zelyra.toml; secrets do not:
[project]
name = "machine-management"
version = "0.9.9"
zelyra = "0.1"
[capabilities]
database = true
network = false
Connections and secrets are supplied via protected environment variables:
export DATABASE_URL='mariadb://user:password@127.0.0.1:3306/zelyra_demo'
Rules:
- never commit
.envto version control; - never place production credentials in code examples;
- do not log secrets;
- separate development, test, and production databases;
- never run destructive tests against production.
Simple defaults, optional feature switches
The beginner path does not require a feature configuration. Advanced project surfaces can be selected in zelyra.toml, while environment-specific, non-secret overrides can be placed in .env or the process environment:
[features]
web = true
api = true
crud = true
auth = true
audit = true
| Switch | .env / Process variable |
Default | Meaning |
|---|---|---|---|
web |
ZELYRA_FEATURE_WEB |
true |
Pages, forms, and web resources |
api |
ZELYRA_FEATURE_API |
true |
api declarations and API surface |
crud |
ZELYRA_FEATURE_CRUD |
true |
crud declarations and generated CRUD surface |
auth |
ZELYRA_FEATURE_AUTH |
true |
auth declarations and authentication surface |
audit |
ZELYRA_FEATURE_AUDIT |
true |
Audit trail configuration |
Precedence order:
Process environment → .env → zelyra.toml → safe defaults
When source code uses a disabled surface, the compiler reports E-FEATURE-001. Feature switches cannot disable type checking, SQL validation, capabilities, contracts, CSRF protection, or security rules.
Inspect effective configuration safely without exposing secrets:
zelyra config main.zyl
zelyra config main.zyl --format=json
.env values in generated projects
| Variable | Default in generated project | Usage | Secret |
|---|---|---|---|
ZELYRA_WEB_PORT |
3000 |
Port of internal web server inside container | no |
ZELYRA_HOST_PORT |
3000 (or auto-selected free port) |
Locally published web port | no |
ZELYRA_DB_HOST_PORT |
3306 (or auto-selected free port) |
Locally published MariaDB port | no |
DATABASE_URL |
project-dependent | MariaDB connection URI (mariadb://user:pass@host:port/db) |
yes |
MARIADB_DATABASE |
zelyra_app |
Compose: database name | no |
MARIADB_USER |
zelyra |
Compose: application user | no |
MARIADB_PASSWORD |
randomly generated | Compose: user password | yes |
MARIADB_ROOT_PASSWORD |
randomly generated | Compose: root password | yes |
ZELYRA_AUTH_TOKEN |
none | Optional local bearer token for protected endpoints | yes |
ZELYRA_AUTH_PERMISSIONS |
empty list | Comma-separated local permission allowlist | no |
Test and CI variables
Variables prefixed with ZELYRA_INSTALL_ROOT, ZELYRA_BIN, *_E2E_*, and GENERATED_* serve internal CI and local integration tests (such as tests/generated-project-docker-e2e.sh, tests/sqlite-e2e.sh). They are not application configuration and must never contain production secrets.
Environment access inside the language
Via the built-in env(name) function, Zelyra code can read environment variables if uses Environment and [capabilities] environment = true are declared:
fn configured_mode() -> String? uses Environment {
return env("ZELYRA_MODE")
}
DATABASE_URL and sensitive secrets must never be exposed via env(...) to unverified code.
17. Diagnostics and troubleshooting
Zelyra aims to explain errors without requiring an archaeological excavation through a stack trace.
Test the connection independently
Test MariaDB without Zelyra first. The password is requested interactively and is not written into shell history:
mariadb \
--host=127.0.0.1 \
--port=3307 \
--user=zelyra \
--password \
address_book
Then run zelyra doctor src/main.zyl --json (optionally with --env-file .env
and --port 18080), the implemented Zelyra readiness test. There is no
zelyra db check command. doctor checks source code, schema, DB
connectivity, Docker Compose, and host ports. Without DATABASE_URL, doctor
reports a warning; with an unreachable URL it reports a failure. A running
database container alone does not prove that host, port, user, and database
match.
Safe diagnostic commands
pwd
ls -la
docker compose ps
docker compose logs mariadb
ss -ltn
mariadb --version
In PowerShell, use Get-Location, Get-ChildItem, docker compose ps, and
mariadb --version as the first checks. Never print DATABASE_URL or a
password in diagnostics.
Common failures
| Error | Likely cause | Fix |
|---|---|---|
Permission denied |
insufficient file permissions or no access to the client | inspect ls -la, use chmod 600 .env, and check the install path |
Access denied for user |
wrong password or user is granted for another host | inspect SHOW GRANTS FOR 'zelyra'@'127.0.0.1'; and rotate the password |
Connection refused |
no service listens on the host/port | inspect docker compose ps, ss -ltn, and the published port |
Can't connect to server |
wrong host/port or container is not ready | inspect docker compose logs mariadb; use 127.0.0.1:3307 from the host and mariadb:3306 inside Compose |
Unknown database |
URL and MariaDB use different database names | run SHOW DATABASES; and correct DATABASE_URL |
| wrong port | confused host 3307 with container 3306 |
host uses 3307; a Compose service uses 3306 |
| MariaDB container not started | Compose error, occupied port, or unhealthy volume | inspect docker compose ps and docker compose logs mariadb |
| user granted only for another host | 'zelyra'@'localhost' is not always 'zelyra'@'127.0.0.1' |
grant the exact host and inspect grants |
| missing environment variable | DATABASE_URL was not exported |
load .env or set the process variable; Zelyra does not load it itself |
.env not found |
wrong working directory or assumed automatic loader | use pwd, ls -la, and export the variable explicitly |
| invalid numeric port | URI port is not numeric | use digits such as 3307; otherwise URL parsing fails |
| wrong charset | database uses another charset/collation | inspect the database; db setup uses utf8mb4/utf8mb4_unicode_ci |
| TLS error | unsupported TLS parameters were appended to the URL | use the current URL form; TLS configuration is planned |
| test database rejected for safety | a protection mechanism is expected but not implemented | Zelyra does not automatically prevent production access in tests; check variables manually |
| PostgreSQL SQL sent to MariaDB | wrong backend or incompatible DDL | inspect the .zyl backend and read zelyra db create output before applying |
If mariadb cannot be started at all, Zelyra reports the external process
start error. The CLI does not contain its own MariaDB driver.
zelyra check app.zyl
Common error classes include unknown names and types, mutation of immutable
values, incomplete pattern matches, unknown tables or columns, missing SQL
parameters, incompatible result mappings, missing capabilities, invalid form
fields, failed contracts, and incomplete typed holes (_).
During development, you can place _ as a placeholder for an unfinished
expression (typed hole). While zelyra check intentionally rejects incomplete
code for compilation, it reports helpful contextual diagnostics: the expected
type, visible variables and functions, active capabilities, contract obligations,
and source spans.
Language-only checks work without DATABASE_URL. Database operations report a
missing connection in a controlled way. Without the variable, run uses the
pure runtime; serve can start routes, but database-backed pages return a
controlled error.
18. Testing and contributing
Before every commit:
cargo fmt --all
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
A language feature is complete only when it has documented syntax, AST/HIR support, static checks, useful diagnostics, positive and negative tests, a runnable example, and synchronized German and English documentation.
Tests that are green only because they never ran are decoration.
19. What comes next
Major planned areas include:
- fully customizable typed view components;
- safe named email templates and attachments;
- personal read receipts and recipient authorization in the notification center;
- general background jobs beyond the existing bounded MariaDB outbox;
- activity, audit, and technical logs;
- typed connections and secret providers;
- ODBC and external read-only databases;
- richer domain-error values beyond typed API payloads and advanced runtime request/response processing;
- cancellation and database-pool integration for structured concurrency;
- broader formal verification;
- optimization models for real planning problems.
Zelyra keeps the common case short without locking the door when the special case arrives:
Generated when possible. Custom where needed. Verified everywhere.
Now build a table, read the SQL, and check the backup. In that order.
20. Zelyra compared with Rust
The most important statement first
Zelyra is developed with Rust. The Zelyra compiler, language crates, and parts of the runtime are Rust crates in the compiler repository. That does not mean that Zelyra changes Rust or that Zelyra applications are Rust programs.
Zelyra is not modified Rust. Zelyra is an independent language whose compiler and runtime are developed in Rust.
The Rust compiler is not forked or extended with Zelyra keywords. Zelyra is not
a Rust library and not a preprocessor that rewrites ordinary Rust code. A
.zyl file is read by Zelyra's own lexer and parser, converted into its own
AST/HIR structures, type-checked, and then processed by the Zelyra runtime.
Similar spelling in fn, braces, if, match, or static typing is a design
choice, not proof of identity. Grammar, semantics, and the programming model
are what matter. Zelyra publishes stable releases and continues active
development; compiler, database, and runtime guarantees apply only to the
documented and tested constructs.
The statements in this chapter were checked against the current source:
the lexer and TokenKind define Zelyra tokens, the parser produces its own
AST structures, and later modules perform resolution, type checking, contract
checking, SQL analysis, web processing, and runtime execution. The CLI and
database modules were reviewed as well. Where a feature exists only as a token,
AST node, or target design, it is not presented as a fully executable language
feature.
General comparison
| Area | Rust | Zelyra | Essential difference | Zelyra status |
|---|---|---|---|---|
| Language category | general-purpose systems and application language | independent declarative language for business and web applications | different grammar and semantics | 🧪 |
| Main use | systems, services, CLI, embedded, WebAssembly, web | database-backed business and web applications | Zelyra bundles application layers | 🧪 |
| Compiler | rustc and Cargo ecosystem |
its own Rust program in the Zelyra repository | Rust compiles the compiler; rustc does not compile .zyl |
✅ |
| Runtime | native Rust or a chosen runtime | its own Zelyra runtime, implemented in Rust | Zelyra executes its own values and rules | 🧪 |
| Memory management | ownership, borrowing, lifetimes | largely automatic/hidden for application code | no Rust borrow checker in .zyl code |
🧪 |
| Ownership | central Rust semantics | no equivalent .zyl construct |
memory rules are not the same | 🗺️ |
| Borrowing | references and borrow checker | no equivalent .zyl construct |
no Rust reference syntax | 🗺️ |
| Lifetimes | explicit or inferred lifetimes | no lifetime syntax | Zelyra does not expose this layer yet | 🗺️ |
| Static typing | mature, generic, trait-based | own checker with Int, String, Option, records, and more |
Zelyra types are not Rust types | ✅ |
| Nullability | Option<T> |
T?, such as Email? |
Zelyra can derive schema/form rules | ✅ |
| Error handling | Result<T, E>, Option<T>, ? operator |
Result<T, E>, Some/None, runtime diagnostics |
no Rust ? operator syntax in Zelyra |
✅ |
| Database integration | external crates such as SQLx, Diesel, or SeaORM | intended as part of language, CLI, and runtime | different integration model; external client today | 🧪 |
| SQL | libraries, macros, or strings | native sql<T> { ... } with schema/parameter checks |
Zelyra knows SQL as an expression | ✅ |
| MariaDB schema | not a Rust-language responsibility | tables are described in Zelyra | generator prints visible SQL | 🧪 |
| Forms | connect framework, templates, and validation yourself | form construct and table rules |
the standard path is part of the model | 🧪 |
| CRUD | must be programmed or generated by a framework | declarative crud Name -> table with shared view field profile |
current runtime serves CRUD routes | 🧪 |
| Views | external libraries or frameworks | page/html, named view layouts, typed component blocks, named slots, and Zelyra UI components |
separate web model; backend-specific limits apply | 🧪 |
| Code formatting | cargo fmt, rustfmt |
zelyra fmt <file.zyl> [--check] |
deterministic Zelyra formatter, protects SQL and HTML | ✅ |
| Refactoring/impact | rust-analyzer, compiler APIs |
zelyra impact, zelyra edit |
versioned atomic JSON machine interfaces | 🧪 |
| Authentication | external web/auth crates | auth definition, sessions, and permission checks in web crate | Zelyra bundles the standard case | 🧪 |
| Permissions | user-designed types and middleware | requires auth, permits, and CRUD action permissions |
declarative rules are checked server-side | 🧪 |
| Contracts | manual or library-based | native requires {} and ensures {} plus verify |
contract syntax belongs to Zelyra | ✅ |
| Capabilities | APIs and libraries regulate effects | uses Database, uses Network, and more |
effect declarations are language syntax | ✅ |
| external SMTP/mail crates | mail_queue, mail_deliver, uses Email |
MariaDB outbox, TLS, host permission, and an explicitly started worker | 🧪 | |
| Jobs | external job/queue systems | declared schedules, resident scheduler, outbox jobs, and lease worker | delivery and backend limits are documented | ✅ |
| Audit | external logging/audit crates | audit table, CLI inspection, and optional hash chain | tied to auth/CRUD and still experimental | 🧪 |
| Deployment | Cargo, containers, CI, and infrastructure are free choices | generated Docker/Compose template exists | template is a development start, not a production platform | 🧪 |
| Production maturity | widely used in production | stable releases; evaluate the exact application and deployment path | the release status is not a blanket production certification | 🧪 |
| Ecosystem | very large: crates, tools, frameworks | small repository and few integrations | Zelyra cannot directly import Rust crates | 🧪 |
Rust is the technical foundation, not the application language behind Zelyra. A
Rust Customer struct and a Zelyra customers table may describe similar data,
but they do not create the same behavior.
Detailed syntax comparison
In this chapter, ✅ means that the construct is present in the current source
and was checked with the installed Rust toolchain or Zelyra CLI. 🧪, 🗺️, and
❌ continue to identify limited, planned, or currently unavailable features.
| Language feature | Zelyra syntax | Rust syntax | Semantic difference | Zelyra status |
|---|---|---|---|---|
| File extension | app.zyl |
main.rs |
own lexer and file type | ✅ |
| Program entry | fn main() { ... } |
fn main() { ... } |
same spelling, different language | ✅ |
| Function definition | fn add(a: Int) -> Int { ... } |
fn add(a: i64) -> i64 { ... } |
own AST types | ✅ |
| Parameters | name: String |
name: String |
similar position, different type semantics | ✅ |
| Return type | -> Int |
-> i64 |
Zelyra abstracts a business type | ✅ |
| Return value | return value |
value or return value; |
final Rust expression returns a value | ✅ |
| Immutable variable | value = 1 |
let value = 1; |
Zelyra is immutable without mutable |
✅ |
| Mutable variable | mutable value = 1 |
let mut value = 1; |
different marker for mutation | ✅ |
| Integer | Int or UInt |
i32, i64, u32, u64 |
Rust requires a concrete width; Zelyra abstracts it today | ✅ |
| Decimal | Float or Decimal |
f64 or f32 |
Zelyra width and exact overflow rules are not fully specified | 🧪 |
| Boolean | true, false, Bool |
true, false, bool |
own base-type model | ✅ |
| String | String and character literals |
String, &str, character literals |
Rust distinguishes ownership and borrowing | ✅ |
| Optional value | Email? or Option<String> |
Option<String> |
? is Zelyra's Option shorthand |
✅ |
| Missing value | None |
None |
same name in different enum models | ✅ |
| Lists/arrays | Int[], [1, 2, 3] |
Vec<i64>, vec![1, 2, 3] |
no Rust macro syntax in Zelyra | ✅ |
| Named data types | type CustomerId = Id, struct Customer { ... } |
type CustomerId = u64, struct Customer { ... } |
records and structs are not interchangeable | ✅ |
| Conditions | if ok { ... } |
if ok { ... } |
block and expression rules differ | ✅ |
else |
else { ... } |
else { ... } |
similar control-flow form | ✅ |
match |
match value { Some(x) => ... None => ... } |
match value { Some(x) => ..., None => ... } |
both require exhaustive cases | ✅ |
| Loops | for item in items, while, loop |
for item in items, while, loop |
no Rust iterator traits in Zelyra | ✅ |
| Function call | add(1, 2) |
add(1, 2) |
same surface, different name resolution | ✅ |
| Output | print(value) |
println!("{}", value); |
Rust uses a macro with ! |
✅ |
| Comments | # comment, standalone ## as block delimiters |
// comment, /* ... */ |
# is standard; // remains compatible; ## blocks are not nested |
✅ |
| Newlines | usually separators; continuation after operators | usually whitespace | parser rules are independent | ✅ |
| Semicolons | accepted as statement separators, not required | often statement separators | Zelyra is not semicolon-dependent | ✅ |
| Blocks | { ... } |
{ ... } |
braces determine blocks in both | ✅ |
| Indentation | readability, not block semantics | readability, not block semantics | whitespace is not Python-style structure | ✅ |
| Error handling | Result<T, E>, Some/None |
Result<T, E>, ?, panic! |
Zelyra has no Rust ? operator |
✅ |
| String interpolation | HTML bodies may use {name} |
format!("{name}") or println!("{}", name) |
no general Zelyra string interpolation | 🧪 |
| Modules | Not yet specified | mod name {}, files and modules |
no mod syntax in parser |
❌ |
| Imports | Not yet specified | use crate::module::Item; |
no import syntax | ❌ |
| Generics | Option<T>, Result<T, E>, limited built-in type arguments |
general generics and traits | no user-defined Zelyra generics | 🧪 |
| Async functions | no async fn; await/parallel are limited |
async fn, .await, futures |
no stable Zelyra async model | 🧪 |
| Tables | table customers { ... } |
no language construct | Zelyra connects table and schema | ✅ |
| Database types | Id, String(100), Email, Bool |
Rust types and external mapping crates | Zelyra emits SQL types from a table | ✅ |
| Relationships | department: Department required |
field plus query/mapping logic | Zelyra derives foreign keys | ✅ |
| SQL queries | sql<Customer[]> { SELECT ... } |
string/macro from a DB crate | Zelyra checks schema, parameters, result | ✅ |
| Forms | form CustomerCreate -> customers { ... } |
no native form | web framework and validation required | ✅ |
| CRUD | crud Customer -> customers |
no native CRUD | Zelyra runtime serves standard routes | 🧪 |
| Views | page, view SiteShell, and component Badge |
no native view construct | named views/components exist; input/render/?? remain target syntax |
🧪 |
| Preconditions | requires { amount > 0 } |
no built-in equivalent | contract is part of the Zelyra function | ✅ |
| Postconditions | ensures { result >= 0 } |
no built-in equivalent | verifier and runtime know Zelyra contracts | ✅ |
| Old values | Not yet specified; old(...) is not parsed |
no general built-in contract standard | do not invent old syntax |
❌ |
| Capabilities | uses Database |
no identical language construct | effects are declared visibly in Zelyra | ✅ |
mail_queue and mail_deliver |
external crate/API | Email capability; enqueue inside transaction with Database(write) |
🧪 | |
| Background jobs | Not yet specified | external queue/runtime crate | no job syntax | ❌ |
| Audit | auth users { audit: auth_audit_log } |
external logging/audit crate | Zelyra ties audit to auth and CRUD events | 🧪 |
| API definitions | api GET "/customers" { ... } |
router, handler, and types separately | Zelyra bundles route and contract | ✅ |
Functions: same idea, different language
Both snippets express a small function in their own language. The Zelyra form uses function syntax processed by the parser and was checked with the current CLI.
fn add(a: Int, b: Int) -> Int {
return a + b
}
fn add(a: i64, b: i64) -> i64 {
a + b
}
Rust uses concrete integer types such as i32, i64, u32, or u64.
Zelyra offers Int for typical business logic; its definitive size, overflow
behavior, and database mapping still need complete specification.
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));
}
✅ The Zelyra version uses Int, print, and explicit return; Rust uses
u64, the println! macro, and the final expression as the return value. Rust
macros carry !. Semicolons are not required in Zelyra. Braces determine block
structure in both examples; indentation is for readability. A newline after +
continues the expression because the parser skips it there. Recursion works in
Zelyra only because function resolution and the runtime support it. This code
was not executed in this work run.
Optional values
email: Email?
email: Option<String>
Email? is shorter and also expresses the domain type. Rust uses the general
generic Option<T>. Zelyra can account for Email? in table, form, and SQL
checking; a general automatic view/validation derivation is not available for
every surface.
Tables
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,
}
The Rust struct creates no database table, SQL columns, validation, form, or CRUD interface. Rust needs additional crates, macros, queries, handlers, and templates. The Zelyra table is included in schema derivation and, where the web features exist, in forms and CRUD.
CRUD
crud Customer -> customers
Rust has no native equivalent. A typical Rust implementation combines a web framework, routing, database crate, data model, queries, request types, validation, handlers, templates or a frontend, error handling, and authorization. Zelyra parses this declarative definition and the current runtime serves CRUD routes, forms, search, filters, and CSRF-protected actions. This is experimental; it is not a static frontend generator.
SQL
The target syntax from the request included with { ... }. That is not in the
current parser. Parameters currently arrive as function parameters:
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 does not have SQL as a language feature; SQLx and other crates can provide
additional compile-time checks. Zelyra's current SQL checker validates, where
schema and types are known, tables, columns, aliases, parameters, nullability,
result mapping, and the Database capability. This does not automatically make
Zelyra better than SQLx. The current MariaDB generator and its missing table
options remain documented in section 7.
Views: current syntax, not the target model
The target syntax with view, input, render, components, and ?? is not
current parser syntax. The current web core uses:
page "/customers/{name}" {
html {
<h1>Customer {name}</h1>
}
}
Rust has no built-in HTML or component syntax; templates and web frameworks are
added externally. Zelyra's page/html form, named views, and typed components
exist. Multiple slots, nested components, and the target input/render
syntax are not stable yet; ?? is not an implemented Zelyra operator.
Contracts
The target syntax require amount > 0 and old(...) is not current syntax. The
parser accepts requires {} and ensures {}:
fn reserve(stock: Int, amount: Int) -> Int
requires {
amount > 0
stock >= amount
}
ensures {
result >= 0
result == stock - amount
}
{
return stock - amount
}
Rust has no direct built-in equivalent. requires describes preconditions and
ensures postconditions. Runtime checks and formal verification differ:
zelyra verify can report PROVEN, RUNTIME_CHECK, UNPROVEN, or FAILED.
Accessing an old value with old(...) is not implemented.
Capabilities
fn load_customers() -> Customer[] uses Database {
return sql<Customer[]> {
SELECT id, name, email, active FROM customers
}
}
uses makes allowed side effects visible in a signature. Database, Network,
FileSystem, Environment, Process, Clock, Random, and Console are known
capabilities in the current runtime code. Rust has no identical built-in
capability system; access is usually organized through types, values, and
library APIs. Email is the application-mail capability. mail_queue also requires Database(write) and a transaction; SMTP delivery checks the allowed host.
What Rust developers should not look for in Zelyra
For typical business applications, Zelyra is not intended to require explicit lifetimes in ordinary application code, borrowing debug sessions for simple forms, choosing among many integer widths, or assembling a web framework from many crates. That is an abstraction, not a claim that memory and runtime concerns disappear.
Zelyra is inspired by static typing, useful diagnostics, safe defaults, explicit mutation, pattern matching, records/enums, clear boundaries, reproducible tooling, and formal checks.
What makes Zelyra its own language
| Zelyra feature | Benefit | Status |
|---|---|---|
| One domain definition | fewer contradictory duplicate definitions | 🧪 |
| native checked SQL | catch database errors before execution where possible | ✅ |
| declarative CRUD | standard administration with little code | 🧪 |
page/html web core |
simple typed path values and HTML responses | 🧪 |
| visible MariaDB SQL | traceable schema changes | 🧪 |
requires and ensures |
state business rules explicitly | ✅ |
| capabilities | make allowed effects visible | ✅ |
| integrated audit | trace changes | 🧪 |
Honest conclusion
Zelyra looks similar to Rust in places because both are modern, statically typed languages with braces and clear function signatures. Zelyra nevertheless follows a different programming model: database, SQL, forms, CRUD, views, and business rules are intended to be parts of one language system. Rust is the technical foundation of the compiler—not the language Zelyra application developers write.
Status: Zelyra publishes stable releases. Some constructs and workflows remain limited or incomplete. The status next to each example describes the supported scope and should be checked against the current release handbook.
Further reading: Introduction, language basics, MariaDB, SQL, forms, views/web pages, CRUD, contracts, capabilities, implementation status, and roadmap. The live repository status is also shown on the status page.
21. Positioning and current development status
The current docs/positioning.md describes Zelyra as an independent language
for database-backed business applications. It does not replace compiler tests;
it explains how the implemented pieces are intended to fit together.
What makes Zelyra distinct
- One source of truth: schema, types, SQL, forms, CRUD, views, and APIs should come from definitions that can be checked together.
- SQL stays first-class: SQL is not hidden behind an ORM abstraction; the program can check tables, parameters, and result shapes.
- Business features are language primitives: tables, forms, CRUD, pages, authentication, permissions, and contracts belong to one model.
- Secure defaults are visible: HTML escaping, parameterized SQL, CSRF protection, null safety, and server-side permissions are more than advice.
- Proof claims are honest:
PROVEN,RUNTIME_CHECK,UNPROVEN, andFAILEDdistinguish static proofs from runtime checks and open cases. - A short path and a full language: the declarative standard case is short, while custom functions and native SQL remain available for domain logic.
- A small starting stack: the built-in server and CLI aim to support local learning and development without Apache, PHP, or a mandatory framework set.
The project is actively developed. The status marks and the roadmap describe the supported scope more accurately than a broad product claim.
22. Support and technical evidence
Choose a backend using the database matrix. MariaDB 11.4.13 (alongside 10.11.19, 11.8.9, and 12.3.3), MySQL Server 8.4.11, and PostgreSQL 16.15 form the verified multi-backend server matrix. Relational CRUD featuring true foreign key constraints (fk_...), preflight orphan rejection (error[E-DB-005]) with data preservation, relational joins, and restrictive deletion protection (RESTRICT policy) is documented and proven across all three engines. On MySQL, write locks are dynamically bound to transaction deadlines via innodb_lock_wait_timeout. Native decision transactions (with_decision_transaction) guarantee rollback, unwinding, and deadline safety on a borrowed connection. SQLite supplements local schema and basic CRUD workflows.
This evidence applies to the listed workflows, not arbitrary database features. Advanced authentication, mail outbox, and notification examples primarily utilize MariaDB and MySQL.
Installation, upgrade, rollback, migration, and restore must match the platform and server version in use. Checksums, SBOMs, and attestations identify release artifacts; they do not establish application correctness. The operational verification goals require reproducible rehearsals; a goal alone is not a passing result.
For a learning project, begin with check and run, then schema preview and planning, then pages/forms/CRUD, and finally authentication, mail, and integrations.
23. AI-native development
The current architecture and specification documents add an important principle:
The AI writes. Zelyra checks.
Zelyra should be useful to humans and AI systems alike while remaining fully independent of AI. The compiler and tests are the trust boundary; a plausible model explanation is not proof of correctness. Human-written and generated code go through the same lexer, parser, name, type, SQL, capability, contract, test, and runtime checks.
Machine interfaces available today
🧪 Tool-facing JSON uses a common envelope with schema_version: "1". Human
output remains the default; request JSON explicitly with --format=json:
{
"schema_version": "1",
"command": "check",
"success": false,
"diagnostics": []
}
Output is deterministic. schema_version is mandatory; new optional fields
may be added within a version, while incompatible changes require a new
version. JSON goes only to stdout, technical messages to stderr. Source
spans use zero-based UTF-8 byte offsets, one-based line/byte columns, and a
half-open interval. Secrets, timestamps, random IDs, machine-dependent
absolute paths, and live database contents do not belong in these outputs.
Canonical code formatting
✅ zelyra fmt <file.zyl> produces deterministic code formatting after
successful lexing and parsing. zelyra fmt <file.zyl> --check writes no files
and returns an error code if reformatting is required, allowing CI to enforce
canonical source formatting.
zelyra fmt examples/fibonacci.zyl
zelyra fmt examples/fibonacci.zyl --check
The formatter preserves line comments and treats SQL and HTML blocks as opaque source. It is idempotent: formatting an already formatted document yields byte-for-byte identical output.
Typed holes
✅ Expression typed holes with _ are available as an initial safe stage. The
compiler reports expected contextual types, visible values and functions, active
capabilities, contract obligations, and source spans:
fn double(x: Int) -> Int {
return _
}
zelyra check reports diagnosis E-HOLE-001 with the expected type Int and
visible identifiers. Buildable commands (build, run, serve) reject
incomplete code before lowering and execution. Declaration-level holes remain
planned.
Structured project context
✅ context is read-only: it does not connect to MariaDB, use the network, send
email, run jobs, or expose secrets:
zelyra context examples/auth_crud_api.zyl --format=json
It reports declared functions, tables, SQL queries, CRUD resources, forms, APIs, and source spans.
Deterministic impact analysis
🧪 Source dependencies can be deterministically analyzed:
zelyra impact examples/auth_crud_api.zyl --format=json
zelyra impact examples/auth_crud_api.zyl --symbol table:customers --format=json
The impact response reports source-based tables, SQL, forms, CRUD resources,
views, APIs, permissions, contracts, and a deterministic references edge
list for known relationships. Each known edge includes source, target, kind,
and source span. Email, job, test, and live schema impacts remain explicitly
empty or unavailable; the command never connects to MariaDB.
Using --symbol <kind:name> focuses the output on a known node such as
table:customers. The focused response contains only directly connected
references and related node IDs. Unknown nodes return E-IMPACT-001 and a
non-zero exit code.
Atomic semantic edits
🧪 A validated symbol rename can be previewed without modifying source:
{
"schema_version": "1",
"entry": "examples/fibonacci.zyl",
"expected_source_fingerprint": "fnv1a64:18f35ecb3e2f99c4",
"operations": [
{"kind": "rename", "symbol": "function", "from": "fibonacci", "to": "fib"}
]
}
Save this as change.json and run:
zelyra edit --format=json change.json
The request is versioned and must point to an existing .zyl file inside the
resolved Zelyra project root. Source code before and after the change must pass
all compiler checks. The result reports exact token spans and a deterministic
source fingerprint.
When applying changes with --apply, the request must include the fingerprint
from the preview to prevent overwriting concurrently modified files (stale-source
protection). Without the explicit --apply flag, it remains a preview:
zelyra edit --format=json --apply change.json
Before atomic replacement, the source is re-parsed and fully validated; an invalid or semantically unsafe change will not be written.
Renames of functions, types, and records are AST-based: declarations and known
references are updated while local bindings of the same name remain untouched.
Table, view, form, and CRUD declarations and their structured references are
also supported. Table renames update checked SQL table positions (FROM,
JOIN, INTO, UPDATE) while preserving literals, comments, parameters, and
HTML. Component renames update both the declaration and known opening and
closing component tags in HTML bodies.
Safety boundary and benchmark
AI tools must not silently add capabilities, widen permissions, execute destructive SQL, weaken diagnostics, disable tests, or reveal secrets. Destructive schema changes and security-sensitive changes require visible human approval. Zelyra does not automatically send source code to external AI services; planned integrations should be open, local-capable, vendor-neutral, and versioned.
The new AI-authoring benchmark is a specification in
docs/benchmarks/ai-authoring.md. With versioned fixtures and identical tasks,
it is intended to measure first-pass compilation, correction loops, time to
passing tests, tokens, security failures, missed dependencies, unsafe schema
changes, and human review effort. No comparative results have been published.
A secret leak or unapproved destructive change is a security failure and cannot
be balanced by claimed productivity.
24. Authoritative Sources and Compiler Verification (Source Authority)
Principle: Zelyra is its own language. The parser and verified tests decide what exists.
When sources disagree, report the disagreement explicitly and use this authoritative order:
- Formal Language Specification and phase documents
- Compiler implementation code: Lexer, AST, parser, name resolution, type checking, and semantic compiler code across
lexer,parser,ast,hir,cli,runtime,database,web, andforms - Official automated language and integration tests: Workspace tests (
cargo test --workspace), machine-interface tests, and E2E scripts intests/ - Official standard library: (once formally introduced)
- Official Zelyra examples:
.zylfiles inexamples/verified by the current compiler - Documentation and handbook explanations
Invariants for developers and AI agents
- Roadmap is planning, not syntax: Future phase proposals become valid syntax only after implementation in lexer and parser.
- Rust is the implementation language, not Zelyra: Zelyra is developed in Rust, but Rust syntax in a
.zylfile is invalid unless Zelyra's grammar explicitly defines it. - No invented commands: Every CLI command must exist in
cli/src/main.rs. - Quality gate: Every feature undergoes formatting (
cargo fmt), workspace checks (cargo check), clippy linter (cargo clippy), and test suite execution (cargo test).
25. Application mail and SMTP
Application mail is enqueued in the same MariaDB transaction as the business change. The web request does not wait for SMTP; a separate worker delivers due messages.
Configure project and transport
[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=replace-with-a-secret
The default implicit_tls uses port 465; starttls uses port 587. Certificate and hostname verification remain active. Optional ZELYRA_SMTP_CA_CERT adds a PEM root certificate from a regular file without symlinks, up to 128 KiB. local_plaintext is restricted to loopback without credentials. The exact SMTP host must appear in the allowlist. Store credentials in local .env or the process environment and keep them out of version control.
Enqueue mail
Save this example as main.zyl in a configured MariaDB project:
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?) is a statement directly inside transaction. The function requires Email and Database(write). Header injection and oversized messages are rejected; arguments must be values or side-effect-free string expressions. Rolling back the transaction also rolls back its queued mail. Escape untrusted HTML before enqueueing it.
Outbox and 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
Set up the outbox before running the program. --once processes one due entry; without it, the worker processes mapped due events until none remain. mail_deliver(event_id, payload_json) returns a Result; the handler acknowledges success with true and permits retry with false. SMTP has a five-second transport timeout and redacted errors.
Delivery is at least once. A worker can fail after SMTP acceptance and before outbox acknowledgement, allowing duplicates. The event ID serves as a stable message ID but does not guarantee exactly-once delivery. The documented API supports text/HTML alternatives through the MariaDB outbox. Named templates, attachments, and a PostgreSQL mail outbox are not established as available APIs. See the mail reference.
Persistent password reset limits
To defend against brute-force and denial-of-service attempts, Zelyra persists password reset request limits in the password_reset_limits table. Rate limits remain effective across application process restarts. Expired time windows are purged automatically in batch cycles, while operator diagnostics remain strictly sanitized to avoid leaking email addresses or tokens into logs.
26. Tenant-scoped notifications
The in-app center works without SMTP. It uses authenticated tenant membership, a shared notification table, and tenant-scoped CRUD. The active tenant bounds lists, direct access, and the unread count. Expired and archived entries are hidden.
Complete MariaDB example
Save this example in a MariaDB project with database, web, CRUD, and authentication enabled. Set up its schema, users, password hashes, memberships, and permissions before using it in the browser:
# 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 binds the resource to the active tenant and its membership. unread: read_at provides the unread count; expires: expires_at hides expired entries. soft_delete archives through archived_at. notifications.view permits reading, while notifications.manage protects administration and changes. A tenant field without authentication/membership configuration does not establish isolation.
Read state, retention, and mail
read_at records the timestamp on notification items. Through recipient scoping (recipient: user_id), notifications are restricted to their authenticated recipient. Personal read receipts are confirmed with CSRF protection and atomic audit logging. The preference table stores email_enabled and retention_days. Expiry is checked on reads; archiving is not physical deletion. The example shows the existing scope.
Local TOTP MFA
For multi-factor authentication, Zelyra supports time-based one-time passwords (TOTP per RFC 6238). Secret keys are encrypted at rest using AES-256-GCM. One-time recovery codes are securely hashed with Argon2id. The implementation defends against replay attacks and race conditions, automatically enforcing a temporary lockout (HTTP 429) after five consecutive failed attempts.
27. Language, Runtime, and PDF Mail
This section documents features introduced in Zelyra 1.0.7. The release extends the language and runtime for testable business logic and adds a local server-side text-PDF pipeline for views and transactional email attachments.
Value-producing expressions and record updates
An if expression yields the same checked type in both branches. Records can be
functionally copied and updated using ..old:
fn invoice_label(paid: Bool) -> String {
return if paid { "Paid" } else { "Open" }
}
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 }
}
Typed lambdas and pipelines
Lambdas are lexical closures. The compiler statically validates callback inputs and returns; pipelines remain synchronous and deterministic:
fn calculate_tax_totals(amounts: Int[]) -> Int[] {
return amounts
|> filter(|a| a > 0)
|> map(|a| a + (a * 19 / 100))
}
Nested record patterns, match guards, and tuples
match supports nested record patterns and boolean guards. Tuples can serve as
first-class return values and destructuring targets:
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 large order", true)
Order { status: "paid", total } => ("Standard order", true)
_ => ("Pending", false)
}
}
Bounded execution budgets
zelyra run file.zyl --max-steps N bounds statement and expression evaluations
across all parallel tasks and reports overruns as E-RUNTIME-001 at the source
expression:
zelyra run main.zyl --max-steps 5000
Server-side text PDF and transactional outbox email
render_pdf("InvoicePrint", invoice) renders a declared view locally into
Bytes. Within the same database transaction, mail_queue can enqueue a PDF
attachment into MariaDB's outbox:
fn send_invoice(recipient: String, invoice: Invoice) uses Email, Database(write) {
transaction {
mail_queue(
recipient,
"Invoice " + invoice.number,
"Your invoice is attached.",
"invoice-" + invoice.number + ".pdf",
render_pdf("InvoicePrint", invoice)
)
}
}
28. Systemd Services, Scheduler, and Jobs
Zelyra integrates operating system service management, resident background schedules, and persistent jobs directly into its toolchain, without depending on external cron daemons.
Linux systemd Service Management
Use zelyra service to manage a dedicated Linux systemd user service with an explicit, fixed port:
# Install systemd user service on a verified port 8080
zelyra service install --port 8080
# Check service status and HTTP listener reachability
zelyra service status
# Restart service (synchronously confirms socket binding)
zelyra service restart
# Change port with atomic verification and rollback
zelyra service change --port 8081
# Stop and remove the service unit
zelyra service remove
If the chosen port is occupied, installation fails (fail-closed). Zelyra never silently rebinds to an arbitrary port. During service change, the old unit and process are preserved until the new listener is verified.
Resident Scheduler and MariaDB Outbox Jobs
Schedules are declared natively in Zelyra code:
schedule weekly_report {
cron: "0 8 * * 1"
timezone: "Europe/Berlin"
command: generate_reports
}
The resident scheduler runs strictly inside the active project systemd service. At every minute boundary, it evaluates declared schedules and enqueues due jobs into the MariaDB outbox. A background worker acquires jobs using renewable leases and bounded retries.
# Inspect all queued, running, and completed jobs for the project
zelyra schedule jobs main.zyl
# Check resident scheduler status
zelyra schedule status main.zyl
# Export a standard crontab preview for external inspection
zelyra schedule export main.zyl --format cron
29. Canonical Project Layout and Vertical Slices
Starting in version 1.2.13, Zelyra introduces a validated Vertical Slice Architecture designed to separate HTTP transport from pure domain calculations.
Scaffolding Standard Projects
zelyra new my-erp --layout standard
A standard layout application follows this verified structure:
my-erp/
├── zelyra.toml # Project manifest (layout = "standard")
├── schema/ # Single source of database truth
│ └── tables.zyl # Table declarations, indices, relationships
├── domain/ # Vertical feature slices
│ ├── invoices/ # Invoices feature module
│ │ ├── actions.zyl # Business logic and calculations
│ │ └── views.zyl # UI templates and components
│ └── customers/ # Customers feature module
│ ├── actions.zyl
│ └── views.zyl
├── routes/ # Lean HTTP gatekeepers
│ ├── web.zyl # Web routes and form handlers
│ └── api.zyl # REST / JSON endpoints
├── public/ # Static files (served directly)
└── assets/ # Embedded web assets (CSS/JS)
├── css/
└── js/
Namespace Imports and Strict Architecture Linting
Imports resolve cleanly through canonical module roots:
use schema::tables::*
use domain::invoices::actions as invoice_ops
Setting strict_architecture = true in zelyra.toml activates compile-time architecture linting:
Direct database mutations inside route handlers (
routes/*.zyl) are strictly forbidden.
Mutating queries (insert, update, delete) in route files fail the build with E-ARCH-001. Domain logic must reside in domain/<feature>/actions.zyl.
# Check layout migration without moving or modifying files
zelyra migrate-layout --check main.zyl
30. Extensible CRUD and TableViews
Zelyra 1.2.15 introduces compiler-checked extension regions for generated CRUD views and data tables, allowing deep UI customization while keeping routing, validation, permissions, and CSRF protection centrally enforced.
Customizing Create and Edit Views
crud.view.create and crud.view.edit configure page titles, submit labels, and surrounding content blocks:
crud CustomerManagement for Customer {
view create {
title: "Register New Customer"
submit_label: "Create Account"
help: "Please verify tax ID before submitting."
}
view edit {
title: "Edit Customer Details"
submit_label: "Save Changes"
}
}
Custom view regions use the checked template context with automatic XSS protection. If validation fails, customized content and field help remain visible alongside error bags.
TableView Enhancements and Row Actions
Tabular data views (tableview) support header/footer additions and contextual row actions:
tableview InvoiceList for Invoice {
columns: [number, customer_name, total_gross, status]
row_actions: [
action "Details" -> "/invoices/show/{id}",
action "PDF" -> "/invoices/{id}/pdf"
]
}
Row actions generate clean URLs that respect tenant isolation and permission policies.
31. AST, MIR, and Bytecode Execution
Status at stable release 1.2.39: The AST interpreter remains the default execution path. MIR and Bytecode support selected workflows, while full web/runtime parity remains open work on the roadmap to 1.3.0. This section describes the architecture and current boundaries; it does not claim a general performance improvement or completed enterprise guarantees.
Source Code (.zyl)
│
▼
Lexer & Parser
│
▼
AST with Source Spans
│
Shared Semantic Checks (Types, Contracts, Scopes)
│
├───────────────────────────────┐
▼ ▼
[1. AST Interpreter] [2. MIR and Bytecode]
- Default execution path - Available for supported workflows
- Source diagnostics - Verified intermediate representations
- Reference for comparison - Parity gates remain open
- AST interpreter: The default path for supported program execution and the reference when comparing backends.
- Typed MIR: Carries checked program metadata for supported constructs; the verifier rejects inconsistent SQL metadata with
E-MIR-VERIFY-004. - Bytecode VM: Executes the supported bytecode subset. Feature and backend parity is tested where implemented; the tests do not guarantee parity for every language feature or web workflow.
32. Tenant Isolation and Composite Foreign Keys
In multi-tenant business software, no entity may exist without tenant affiliation, and foreign key relations must never point across tenant boundaries. Zelyra embeds tenant scoping directly into table definitions (scoped by tenant_id: TenantId).
Declaring Multi-Tenant Tables
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
}
Database-Level Integrity Guarantees
- Composite Primary Keys & Unique Indexes: Zelyra automatically creates
uq_{table}_tenant_id UNIQUE (tenant_id, id). - Tenant-Isolated Foreign Keys: Relationships between scoped tables (
customer: Customer?) automatically generate composite foreign keys:
These constraints reject cross-tenant foreign-key assignments at the database layer for the schema paths that generate them.FOREIGN KEY (tenant_id, customer_id) REFERENCES customers (tenant_id, id) ON DELETE RESTRICT - Tenant-Scoped Uniqueness: Fields marked
unique within tenantproduceUNIQUE KEY (tenant_id, customer_number). Different tenants can use identical numbering schemes without collisions. - Optional Relations & Decimals: Optional relationships (
Customer?) generate typedBIGINT NULL.Decimal(p, s)(e.g.Decimal(12, 2)) guarantees exact commercial rounding without IEEE 754 floating-point inaccuracies.
33. Table Invariants and Validation Rules
Business logic belongs directly on the data model. Instead of duplicating validation rules in controllers, form requests, and UI forms, Zelyra allows you to declare typed table invariants inside the table declaration.
Declaring Table Invariants
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: "Due date must be on or after the invoice date."
discount_days >= 0: "Discount days cannot be negative."
}
}
Enforcement in Forms and APIs
- Declarative CRUD & Forms: Prior to committing create or edit writes, Zelyra evaluates all declared invariants atomically. If any condition fails, the write is aborted, and the form renders the message with HTTP status 422.
- Typed API Input Validation: APIs can bind request payloads to tables (
validates). Validation evaluates table invariants and returns standardized HTTP 422 error contracts before executing the handler.
34. Lifecycle Guards, Policies, and Master-Detail CRUD
Compliance regulations (e.g., GoBD) require that posted and locked business documents cannot be modified or deleted. Additionally, business documents like invoices and sales orders require parent records and line items to be saved in an atomic transaction.
Record-Dependent 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: "Invoices"
guard edit when is_locked == true {
error: "Locked invoices cannot be edited (audit compliance protection)."
}
guard delete when is_locked == true {
error: "Locked invoices cannot be deleted."
}
}
- Guard semantics as a rejection condition: A guard blocks an operation whenever its condition evaluates to true. To protect against unauthorized editing or re-locking, Zelyra evaluates
guard edit when is_locked == true. A condition likeis_locked == falsewould erroneously reject open drafts. - Transactional Evaluation: The guard condition is evaluated after row locks are acquired within the database transaction. Direct POST/DELETE requests cannot bypass this protection.
- UI Integration: In
tableviewrow action slots and detail views, edit and delete actions are automatically disabled or hidden when the condition matches.
Interplay with GoBD Compliance, Hashes, and Capabilities
Production business applications require complementary primitives alongside lifecycle guards, provided directly through Zelyra's standard library and capability system:
- Cryptographic Checksums (
hash_sha256): The standard library functionhash_sha256(text: String) -> Stringcomputes the hexadecimal SHA-256 digest of a string, fixing document integrity upon final posting:fn document_hash(doc_number: String, gross: Decimal) -> String { return hash_sha256(doc_number + "#" + gross) } - Deterministic Time (
now()anduses Clock): Functions querying the current system timestamp must declare theClockcapability (fn post_invoice(...) uses Clock). This guarantees time remains strictly mockable and deterministic across automated tests. - Document PDFs and Outbox Mail (
render_pdfandmail_queue):render_pdf("InvoicePrint", invoice)compiles a view into PDF bytes server-side. Within an atomic MariaDBtransaction,mail_queue(to, subject, text, filename, pdf_bytes)enqueues the document and its binary attachment transactionally into the outbox. - Current Scope in CRUD Actions: Since version 1.2.28, CRUD actions support typed handlers with row and session data, checked guards, and the documented server-side transaction behavior. The action grammar still limits which statements, expressions, and effects are valid; this is not arbitrary code execution.
Transactional Master-Detail CRUD Forms (has_many)
With has_many, CRUD resources declare direct 1:N line-item collections:
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: "Line gross cannot be less than line net."
}
}
crud Invoice -> invoices {
title: "Invoices"
view {
fields { invoice_date }
form {
title: "Invoice"
submit: "Save invoice"
}
}
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)
}
}
}
- Server-Side Rendering: Child rows are generated server-side in HTML. Rows can be added or removed without requiring client-side JavaScript.
- Atomic Transaction: Header fields, child items, and computed summaries (
aggregates) are committed together in a single database transaction. An invalid line item rolls back the entire submission. - Automatic Aggregation: Summary columns such as
net_amountorgross_amountare recalculated server-side from child lines and updated atomically on the parent invoice.
Multiple Child Collections per CRUD Resource (from Zelyra 1.2.24)
Starting in Zelyra 1.2.24, generated CRUD create and edit forms can manage multiple direct has_many collections for the same parent record (such as line items and attachments on an invoice within a unified form):
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: "Invoices with items and attachments"
view {
fields { reference invoice_date }
form {
title: "Invoice"
submit: "Save invoice"
}
}
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
}
}
- Independent Sections and Actions: Each collection appears in its own distinct form section with its own row count and independent row action identifiers (
add_item,remove_item,add_attachment, etc.). - Independent Row Limits: The server-side limit of 50 rows applies to each declared collection independently.
- Atomic Validation and Persistence: Zelyra validates all collections before committing writes. Parent data, all child rows across every collection, and declared aggregate expressions are saved in a single database transaction. An invalid collection or database error rolls back the entire submission atomically.
- Child Table Uniqueness: A child table may only be bound once to a CRUD resource; multiple collections may not ambiguously target the same aggregate destinations.
35. The Zelyra Ecosystem: Packages, Modules, and Settings
The Zelyra module and package ecosystem enables clean modularization of applications into reusable, independently testable, and cryptographically verifiable components. It combines offline-first principles with strict typing and compile-time guarantees.
Foundations and Ecosystem Philosophy
- Offline-First and Deterministic: A Zelyra project builds and verifies completely offline. There are no implicit downloads during compilation (
zelyra check/zelyra run), no uncontrolled network requests, and no arbitrary third-party build scripts. - Integrity and Lockfile: Every dependency is locked with a cryptographic SHA-256 digest in
zelyra.lock. Any unauthorized modification to source code or package manifests is caught immediately by the compiler and requires an explicit update (zelyra package lock --update). - Cryptographic Trust Model (Ed25519): For sharing and registries, Zelyra uses signed
.zpkgsource archives. An archive is signed with a publisher's private Ed25519 key. Consumers explicitly record authorized public keys inzelyra.trust.toml. Without explicit publisher trust, Zelyra refuses installation. - Compiler Effect Ceilings (
effects): A package can declare upper bounds on capabilities that its functions may request (for example,effects = ["Database(read)"]oreffects = []for purely computational libraries). If any function inside the package exceeds this ceiling, the compiler produces an error.
Initializing a New Package: zelyra module init
A new module package is initialized using the CLI command zelyra module init:
zelyra module init packages/finance_tools --name finance_tools --version 0.1.0
The command generates standard scaffolding without network access and without overwriting existing files:
packages/finance_tools/
├── zelyra.toml
├── README.md
└── src/
└── lib.zyl
The Package Manifest: zelyra.toml
The zelyra.toml manifest declares the package identity, version compatibility, and effect ceilings:
[package]
name = "finance_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 = "Timeout for external queries in seconds"
[settings.api_token]
type = "String"
required = true
secret = true
description = "Private API key for financial data"
name: Package name in lowercase ASCII with digits,_, and-.version: Exact SemVer version (e.g.0.1.0).entry: Entry source file, defaulting tosrc/lib.zylfor libraries ormain.zylfor standalone apps.zelyra: SemVer constraint for the Zelyra CLI. If the running compiler version falls outside this range, Zelyra rejects compilation upfront.effects: Optional list of allowed capability names.[]enforces pure, side-effect-free functions. Omission allows all capabilities permitted by the host application.
Writing and Exporting Module Code
Functions, records, and enums intended for external use must be exported with the pub keyword:
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,
}
}
Declarations without pub remain strictly private to the package. Contracts (requires, ensures) are fully evaluated and enforced by the compiler across package boundaries.
Typed Module Settings
Zelyra strictly decouples package configuration schemas from application data and secrets:
- Schema Declaration: The schema is defined in the package manifest under
[settings.<field>]. Supported scalar types areString(up to 4 KiB),Bool,Int, andFloat. Secrets declaresecret = trueand must not have default values. - Application Configuration in
zelyra.settings.toml: Values reside in the main application workspace, decoupled fromzelyra.tomlso package digests remain immutable:
Secrets are strictly referenced via[package_settings."packages/finance_tools"] api_timeout = 30 api_token = { env = "FINANCE_API_TOKEN" }{ env = "ENV_VAR" }. The Zelyra runtime resolves the value from process environment variables and prevents secret leakage into error output or logs. - Retrieval in Zelyra Code: Package functions read configured values with full type safety using the internal generic function
module_setting<T>:fn get_timeout_seconds() -> Int { let timeout = module_setting<Int>("api_timeout") return timeout } - Management via Web and Studio:
- CLI Validation:
zelyra module settings check main.zylvalidates types, required fields, and environment variable presence. - Zelyra Studio: Under Module Settings, Studio provides a revision-locked settings editor backed by
.zelyra.settings.lock. - Runtime Web Page: When authentication is active and
admin_permissionis defined,/__zelyra/module-settingsprovides a CSRF-protected web management portal for administrators.
- CLI Validation:
Integrating a Module into a Zelyra Application
To integrate a module into a main application, declare it in the application's zelyra.toml under [dependencies.<alias>]:
[package]
name = "my_app"
version = "1.0.0"
entry = "main.zyl"
zelyra = ">=1.2.0"
[dependencies.finance]
path = "packages/finance_tools"
version = "^0.1"
In your Zelyra source code, import the package using the pkg:<alias> prefix:
import "pkg:finance" as finance
fn main() {
let result = finance::calculate_tax(200.0, 19.0)
print(result.gross)
}
After adding a dependency, synchronize and verify the lockfile:
zelyra package lock .
zelyra check main.zyl
Module Inspection, Planning, and Bundling
Zelyra provides dedicated commands to inspect, verify, and bundle modules:
- List Modules and Exports (
module list):
Deterministically inspects source paths, import aliases, public exports, checksums, effect ceilings, and settings schemas.zelyra module list main.zyl zelyra module list main.zyl --format json - Analyze Dependency Graph (
module plan):
Computes the static source closure for the target module and catches unresolved or circular dependencies before deployment.zelyra module plan main.zyl pkg:finance - Bundle for Portable Deployment (
module bundle):
Produces a self-contained source bundle with isolated package dependencies and fresh lockfile, ready for offline execution.zelyra module bundle main.zyl pkg:finance --output ./dist/finance-bundle
Signed Packages, Registries, and Transactional Recovery
For cross-team or cross-organization distribution, Zelyra includes cryptographic package tooling:
- Keypairs and Signatures:
zelyra package keygen --publisher financeteam --output financeteam.key zelyra package sign packages/finance_tools --publisher financeteam --key financeteam.key --output finance_tools-0.1.0.zpkg - Authorizing Publisher Trust:
zelyra package trust add financeteam <public-key-hex> zelyra package verify finance_tools-0.1.0.zpkg - Building and Searching Static Registries:
zelyra package registry build ./published-packages zelyra package search https://packages.example.test/index.json finance - Verified Installation:
zelyra package add https://packages.example.test/index.json finance_tools 0.1.0 --as finance zelyra package update https://packages.example.test/index.json finance_tools 0.2.0 --as finance zelyra package remove finance - Transactional Recovery (
package recover): All mutating package operations write a local transaction journal. If an operation is interrupted, package commands block until checked:zelyra package recover zelyra package recover --applyzelyra package recover --applyrestores manifests andzelyra.lockto the previous verified state and cleans up incomplete cache artifacts.
36. CSS Design System and UI Styling for CRUD and TableViews
Zelyra applies the same design philosophy to its web frontend as it does to the language and database engines: built-in safety without runtime bloat. Rather than requiring heavy JavaScript toolchains, Node.js runtimes, or npm dependencies, Zelyra ships with a compiled, standalone CSS design system (zelyra-ui.v1.css) served directly by the built-in HTTP server.
This chapter details the architecture of Zelyra UI, its design tokens and class naming conventions, and provides complete, production-ready CSS recipes for tailoring CRUD pages, TableViews, master-detail forms, and query toolbars.
1. Architecture of the Zelyra UI Design System
Zelyra UI is built entirely upon standard web specifications and CSS Cascade Layers (@layer).
┌────────────────────────────────────────────────────────────────────────┐
│ CSS Cascade Layers │
│ │
│ @layer reset Browser normalization & box-sizing │
│ ↓ │
│ @layer base Root tokens, typography, standard HTML elements │
│ ↓ │
│ @layer theme Color schemes (light, dark, system), density │
│ ↓ │
│ @layer layout App shell, grids, containers, scrolling regions │
│ ↓ │
│ @layer components Cards, buttons, badges, tables, form controls │
│ ↓ │
│ @layer utilities Utility helper classes (margins, alignment) │
│ ↓ │
│ @layer overrides Zelyra standard views (CRUD, TableViews) │
│ ↓ │
│ [Project CSS] Custom zelyra.theme.css (highest precedence) │
└────────────────────────────────────────────────────────────────────────┘
The Root Selector and Specificity
All Zelyra UI styles are scoped using the CSS pseudo-class :where(html[data-z-ui]):
:where(html[data-z-ui]) .z-table {
width: 100%;
border-collapse: collapse;
}
The :where() pseudo-class reduces its selector's specificity to 0. Consequently, developers can override any default Zelyra UI styling in their project's zelyra.theme.css using standard single-class selectors (such as .z-table), completely avoiding !important flags or unwieldy specificity battles.
Loading Project Stylesheets
Zelyra automatically loads custom styles through two mechanisms:
- Automatic Discovery: If a file named
zelyra.theme.cssorassets/css/theme.cssexists in your project root, the built-in server injects it into the<head>of all rendered pages immediately following the base framework styles. - Environment Variable / CLI Flag: You can specify an explicit stylesheet path using the
ZELYRA_UI_STYLESHEET=/path/to/custom.cssenvironment variable or the--stylesheetCLI argument.
Per the CSS Cascade Layers specification, any unlayered CSS rule takes precedence over all @layer declarations. Thus, rules written directly in zelyra.theme.css naturally override framework defaults:
/* In zelyra.theme.css: cleanly overrides framework defaults */
.z-table th {
background: #1a2234;
color: #ffffff;
}
Alternatively, rules may explicitly target @layer overrides:
@layer overrides {
.z-table th {
background: #1a2234;
color: #ffffff;
}
}
2. Design Tokens and CSS Custom Properties
Zelyra UI declares a typed system of CSS custom properties on html[data-z-ui].
Color Tokens
| Token | Default (Light) | Purpose / Usage |
|---|---|---|
--z-color-canvas |
#f4f6fa |
Document canvas background color |
--z-color-surface |
#ffffff |
Background for cards, forms, tables, and dialogs |
--z-color-surface-raised |
#ffffff |
Elevated areas (table headers, popovers, dropdowns) |
--z-color-text |
#182230 |
Primary text and heading color |
--z-color-text-muted |
#526174 |
Subdued text for helper hints, metadata, and labels |
--z-color-border |
#d8e0e9 |
Border color for tables, inputs, and container cards |
--z-color-primary |
#3157a4 |
Accent color for buttons, active tabs, focus rings |
--z-color-on-primary |
#ffffff |
Contrasting text color on primary backgrounds |
--z-color-primary-hover |
#274889 |
Hover state for primary buttons |
--z-color-primary-soft |
#eaf0fb |
Soft background for row hovers and active states |
--z-color-success |
#16734c |
Success badges, paid invoices, confirmation banners |
--z-color-success-soft |
#e9f6ef |
Soft background for success badges and alerts |
--z-color-warning |
#795000 |
Warning badges, draft records, payment reminders |
--z-color-warning-soft |
#fff4d8 |
Soft background for warning alerts |
--z-color-danger |
#a12e39 |
Errors, cancellation notices, delete buttons |
--z-color-danger-soft |
#fff0f0 |
Soft background for field error states and summaries |
--z-color-focus |
#275fc5 |
Accessible focus indicator ring (:focus-visible) |
--z-color-link |
#244f9a |
Hyperlinks and table detail links |
Dimensions, Spacing, and Radii
:where(html[data-z-ui]) {
/* Typography */
--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;
/* Controls and Canvas Width */
--z-control-height: 2.75rem; /* Standard input & button height */
--z-content-width: 76rem; /* Maximum page content width */
/* Spacing Scale */
--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 */
/* Border Radii */
--z-radius-sm: 0.35rem; /* Inputs, buttons */
--z-radius-md: 0.65rem; /* Cards, dialogs, badges */
--z-radius-lg: 1rem; /* Shell containers */
--z-radius-pill: 999px; /* Pill badges, switch tracks */
/* Z-Index Hierarchy */
--z-layer-sticky: 10;
--z-layer-dropdown: 20;
--z-layer-dialog: 100;
--z-layer-toast: 120;
}
Compact Density (compact)
In data-dense applications such as accounting portals or ERP backoffices, compact spacing minimizes unnecessary scrolling. Zelyra UI supports this via the data-z-density="compact" attribute:
<html data-z-ui data-z-density="compact">
In compact mode, control heights shrink from 2.75rem to 2.25rem, and cell paddings are tightened across all form controls and table views. You can also scope compact density to a specific table container:
<div class="z-table-scroll" data-z-density="compact">
<table class="z-table">...</table>
</div>
3. Styling and Optimizing TableViews
Table views (tableview) represent core workhorses in business software, displaying relational query results with filtering, sorting, pagination, and inline actions.
Standard HTML Hierarchy
A standard Zelyra TableView generates the following markup:
<div class="z-generated-page">
<header class="z-page-header">
<div class="z-page-header__copy">
<h1 class="z-page-header__title">Invoices</h1>
<p class="z-page-header__description">All recorded invoices for the active tenant.</p>
</div>
<div class="z-page-header__actions">
<a class="z-button z-button--primary" href="/invoices/new">+ New Invoice</a>
</div>
</header>
<form class="zelyra-query-controls" method="get">
<fieldset>
<legend>Search & Filters</legend>
<div class="z-query-controls__fields">
<!-- Filter controls -->
</div>
</fieldset>
</form>
<div class="z-table-scroll">
<table class="z-table">
<thead>
<tr>
<th>Invoice #</th>
<th>Customer</th>
<th>Date</th>
<th>Status</th>
<th style="text-align: right;">Gross Amount</th>
<th class="z-table__actions-head">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>INV-2026-0042</strong></td>
<td>Acme Corp</td>
<td>2026-10-19</td>
<td><span class="z-badge z-badge--success">Paid</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">View</a>
<a class="z-button z-button--sm" href="/invoices/42/edit">Edit</a>
</td>
</tr>
</tbody>
</table>
</div>
<nav class="z-pagination" aria-label="Pagination">
<div class="z-pagination__summary">Showing 1 to 25 of 148 entries</div>
<div class="z-pagination__controls">...</div>
</nav>
</div>
Sticky Table Headers
When working with long result sets, sticky headers prevent the loss of column context:
/* In zelyra.theme.css: Sticky Table Header */
.z-table-scroll {
max-height: 70vh;
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 Striping and Interactive Row Hover
Zebra striping assists in scanning across wide rows:
/* Zebra striping for even rows */
.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%);
}
/* Distinct row hover */
.z-table tbody tr:hover {
background-color: var(--z-color-primary-soft);
transition: background-color var(--z-motion-fast) ease-in-out;
}
Right-Aligned Tabular Numerals (Tabular Nums)
Financial amounts and decimal numbers must always be right-aligned so decimal points align vertically. Enabling tabular-nums prevents variable-width digits (such as 1) from disrupting alignment:
/* Exact numeric alignment for currency and quantity columns */
.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;
}
/* Subtly emphasize monetary amounts */
.z-table td.col-currency {
font-weight: 600;
letter-spacing: -0.01em;
}
Styling Row Actions (.z-table__actions)
Row action columns group viewing, editing, and deletion triggers:
/* Compact, neatly aligned row action buttons */
.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. Search, Filter, and Query Tools
Query toolbars rendered above TableViews use .zelyra-query-controls and .z-query-controls__fields:
/* Polished query filter bar */
.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;
}
/* Filter inputs */
.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. Pagination and Empty States
Pagination (.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;
}
Empty States (.z-empty-state)
When an inquiry yields no records, Zelyra renders the .z-empty-state container:
.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. Styling CRUD and Master-Detail Forms
Compiler-generated create and edit views (crud.view.create, crud.view.edit) are encapsulated inside the .zelyra-crud-form-card container.
Responsive Two-Column Grid
/* Outer form card */
.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);
}
/* Two-column layout */
.zelyra-crud-form-card form {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1.25rem 1.5rem;
}
/* Spanning full-width fields */
.zelyra-crud-form-card .z-field--full,
.zelyra-crud-form-card .z-fieldset,
.zelyra-crud-form-card .z-form-actions {
grid-column: 1 / -1;
}
/* Single column on mobile devices */
@media (max-width: 768px) {
.zelyra-crud-form-card form {
grid-template-columns: 1fr;
}
}
Fields, Labels, and Hints
.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);
}
/* Required asterisk indicator */
.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;
}
Validation Errors and Invariant Indicators ([aria-invalid="true"])
When a typed invariant or field contract fails (e.g., total_gross >= total_net), Zelyra applies aria-invalid="true":
/* Signal borders for invalid fields */
.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);
}
/* Form error summary */
.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 Child Tables (has_many) and Aggregate Summaries
In transactional master-detail forms declaring has_many collections (such as line items), child grids are integrated cleanly:
/* Child collection table inside form */
.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);
}
/* Compact inputs in line items table */
.z-master-detail-items input.z-input {
min-height: 2.15rem;
padding: 0.25rem 0.5rem;
font-size: 0.9rem;
}
/* Aggregate totals box (net, tax, gross) */
.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. Modal Dialogs and Status Badges
Native Dialogs (.z-dialog)
Zelyra uses standard HTML5 <dialog> elements with built-in backdrop support:
.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;
}
/* Dark backdrop with blur */
.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;
}
/* Badges for status flags */
.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. Complete Real-World CSS Recipes for zelyra.theme.css
The following recipes are ready to copy directly into your project's zelyra.theme.css.
Recipe 1: Modern Dark Theme with Accent Colors
A rich, eye-friendly dark theme with cyan and indigo accents for administration dashboards:
/* ==========================================================================
Recipe 1: Modern Dark Theme for zelyra.theme.css
========================================================================== */
:where(html[data-z-ui][data-z-theme="dark"]),
:where(html[data-z-ui]:not([data-z-theme])) {
/* Slate base palette */
--z-color-canvas: #0b0f17;
--z-color-surface: #121824;
--z-color-surface-raised: #1a2233;
--z-color-border: #28354b;
/* Typography */
--z-color-text: #f1f5f9;
--z-color-text-muted: #94a3b8;
/* Radiant Cyan/Indigo Primary Accent */
--z-color-primary: #38bdf8;
--z-color-on-primary: #082f49;
--z-color-primary-hover: #7dd3fc;
--z-color-primary-soft: #0c2b45;
/* Status Colors */
--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;
/* Shadows */
--z-shadow-sm: 0 1px 3px rgb(0 0 0 / 50%);
--z-shadow-md: 0 12px 32px rgb(0 0 0 / 70%);
}
/* Distinct Table Header in Dark Mode */
.z-table thead th {
background: #172030;
color: #38bdf8;
border-bottom: 2px solid #28354b;
}
/* Smooth Row Hover */
.z-table tbody tr:hover {
background-color: #17243b;
}
Recipe 2: Compact Financial and Accounting Table
Designed for financial lists and accounting ledgers requiring high data density, sticky headers, and clear monetary alignment:
/* ==========================================================================
Recipe 2: Financial and Accounting Table with 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;
}
/* Sticky Header */
.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);
}
/* Compact Rows */
.z-table td {
padding: 0.5rem 0.75rem;
border-bottom: 1px solid var(--z-color-border);
vertical-align: middle;
}
/* Subtle Striping */
.z-table tbody tr:nth-child(even) td {
background-color: rgb(0 0 0 / 1.5%);
}
/* Right-aligned numeric columns with tabular figures */
.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);
}
/* Compact Row Actions */
.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;
}
Recipe 3: Master-Detail Invoice Form with Card Design
Provides an elevated, modern card layout for master-detail forms with partitioned header metadata, a line-item grid, and a summary totals card:
/* ==========================================================================
Recipe 3: Master-Detail Invoice Form 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%);
}
/* Form Header */
.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);
}
/* Master Header Partition */
.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;
}
}
/* Line-Item Grid */
.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);
}
/* Highlighted Totals Card */
.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);
}
/* Form Actions */
.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 Database CLI and Interactive Language Console
Zelyra 1.2.25 and 1.2.26 expanded the developer and administrative workflow with a direct database CLI and an interactive AST language console.
Direct Database Console (zelyra db console)
The command zelyra db console [main.zyl] resolves the configured project database connection from environment variables (ZELYRA_DATABASE_<NAME>_URL or DATABASE_URL) as well as the project .env file, and directly launches the corresponding native client (MariaDB/MySQL mysql or PostgreSQL psql). Passwords are securely passed via environment variables (MYSQL_PWD or PGPASSWORD) rather than command-line flags, keeping them hidden from process listings and logs:
# Launch the native database client with project connection parameters
zelyra db console main.zyl
Direct Query Execution (zelyra db query)
The zelyra db query command executes a single SQL statement in a transaction. To prevent unintended data modification, statements run by default in a database-enforced read-only transaction:
# Default read-only query with aligned table formatting
zelyra db query main.zyl "SELECT id, email, created_at FROM customers" --format table
# JSON or CSV output for scripting and pipeline processing
zelyra db query main.zyl "SELECT id, email FROM customers" --format json
zelyra db query main.zyl "SELECT id, email FROM customers" --format csv
# Mutating DML operations require the explicit --allow-mutation flag
zelyra db query main.zyl "UPDATE customers SET active = true WHERE id = 1" --allow-mutation
# Row limiting and tenant parameter binding via :tenant_id
zelyra db query main.zyl "SELECT * FROM invoices WHERE tenant_id = :tenant_id" --tenant 42 --limit 50
[!NOTE] DDL statements (
ALTER,DROP,CREATE) are deliberately disallowed inzelyra db querybecause several relational engines implicitly commit transactions upon DDL execution, bypassing transactional safety boundaries.
Interactive Language Console (zelyra console)
Introduced in Zelyra 1.2.26, the language console launches an interactive Read-Eval-Print Loop (REPL) backed by the AST interpreter:
zelyra console main.zyl
Key features of the language console:
- Persistent Bindings: Variables (
let), functions, records, and types declared during a session remain active across subsequent input lines. - Transaction Sandbox: On MariaDB and MySQL, the console executes within an active transaction with rollback by default upon exit (
--sandbox). Mutations can be made permanent with:commit. The--persistflag bypasses the sandbox. - Interactive Shell Features: Full command history (
~/.zelyra_history), tab completion for declared functions, tables, records, and fields, and automatic continuation prompts for multiline blocks. - Meta Commands:
:help– Displays an overview of available console commands:tables– Lists all declared database tables in the project:schema <name>– Displays column types, foreign keys, and invariants with source positions:tenant <id>– Sets the active tenant context (tenant_id):commit/:rollback– Explicitly commits or rolls back the current sandbox transaction:clear– Clears the terminal screen:quitor:exit– Terminates the console session
38. Laravel Import Analysis and Migration Foundation
Zelyra 1.2.27 introduced a dedicated CLI workflow to analyze existing Laravel web applications and reconstruct core schemas and structures as native, compiler-checked Zelyra modules.
Read-Only-First Analysis
The importer operates on a Read-Only First foundation. It never executes untrusted PHP application code, relying instead on static AST and syntactic analysis of:
- Migrations and schema definitions
- Eloquent models, relationships, and property casts
- Validation rules in FormRequest classes and controllers
- Blade templates and static asset references
# Inspect architecture and migration candidates without modifying files
zelyra import laravel analyze /path/to/laravel-project
Selective Code Generation
Generating native Zelyra artifacts is explicit and granular:
# Generate native Zelyra table and record models for a specific table
zelyra import laravel generate /path/to/laravel-project --table customers
# Generate all supported core models and authentication scaffolding
zelyra import laravel generate /path/to/laravel-project --all-supported
Key transformations performed:
- Tenant Isolation: Conventional tenant identifiers (such as
tenant_idcolumns and pivot memberships) are transformed into native Zelyra constructs (table ... scoped by tenant_id: TenantId). - Invariants: Declared validation rules (
required,numeric,min,max, date sequences) are converted into database-level table invariants (validate { ... }). - Authentication Modules: Conventional user authentication tables (
users,sessions,password_resets) are emitted as a standalone, type-checked Zelyra auth package.
39. Typed Transactional CRUD Actions
Zelyra 1.2.28 introduced typed CRUD actions. They allow developers to bind business operations directly to tables and row contexts without compromising CRUD boundary guarantees.
Action Declaration
An action binds typed parameters, the locked database record (row), and verified caller session data (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: "Only draft invoices can be approved."
}
guard row.locked == false {
error: "Locked invoices cannot be approved."
}
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;
// Enqueue audit log and PDF generation in the same atomic transaction
enqueue_audit_log("invoice.approved", row.id);
}
}
}
Transactional Guarantees
- Row Locks: Before evaluating guards and executing the
executeblock, the target record is locked usingSELECT ... FOR UPDATE. Concurrent modifications are serialized, preventing race conditions. - Pure Guards: Guard predicates evaluate purely against the freshly locked row snapshot.
- Shared Request Transaction: Data mutation, audit logging, and outbox tasks (such as background PDF generation) participate in a single shared database transaction. If any step fails, all mutations roll back atomically.
40. Secure File Uploads and Tenant File Storage
Zelyra 1.2.29 added bounded multipart file uploads. They connect compiler-checked form declarations with private, tenant-isolated filesystem storage.
Declaring File Fields
File upload fields declare strict byte limits and permissible MIME types:
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()
}
Security Architecture
- Magic-Byte Signature Inspection: File validation does not rely on client-supplied file extensions or HTTP headers; the server inspects the initial byte sequences (PDF, PNG, and JPEG magic signatures).
- Private Storage Outside Web Root: Accepted files are saved with opaque SHA-256 tokens (
FileRef) within a private, tenant-scoped filesystem directory. Direct unauthenticated HTTP access is impossible. - Protected Downloads: File downloads are routed through authorized CRUD handlers that verify the caller's tenant context and record permissions before streaming content.
- Lifecycle and Orphan Cleanup:
- When a file is updated, the previous blob is removed only after the database transaction commits successfully.
- When a row is deleted, the file is only pruned if no other
FileRefcolumn in the database points to that blob. zelyra files cleanupdiscovers unreferenced staging files and orphaned blobs. It runs in dry-run mode by default and requires--apply --maintenance-windowfor deletion under an exclusive lock.
- Checksum-Verified Backup:
# Create a checksum-verified snapshot of the tenant file store zelyra files backup /var/backups/zelyra-files.tar.gz # Restore and verify all SHA-256 checksums zelyra files restore /var/backups/zelyra-files.tar.gz
41. Tenant-Aware Number Series (auto_sequence)
Zelyra 1.2.31 introduced declared, tenant-safe number sequences for string columns in database tables and generated CRUD interfaces.
Declaring Number Sequences
A sequence is configured directly on a column declaration using auto_sequence("PATTERN"):
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
}
Format Tokens:
{0001}: Formatted incremental counter with zero padding (here 4 digits:0001,0002, ...).{year}: Current UTC calendar year (e.g.2026). Starts a separate counter for each calendar year.
Internal Management and Database Guarantees
- Managed Sequence Table: The schema planner automatically generates and tracks the internal
_zelyra_number_sequencestable. - Atomic Transaction: Number assignment and row insertion participate in the same database transaction.
- Tamper Resistance: In generated CRUD forms and API endpoints, values supplied by the client for sequenced fields are ignored. On edit forms, generated numbers are presented as read-only.
- No Gap-Free Guarantee: Zelyra guarantees monotonic increments and tenant-scoped uniqueness, but intentionally does not guarantee gapless numbering. On transaction rollbacks, assigned numbers are not reused, preserving auditability and regulatory compliance.
- Backend Support: MariaDB and PostgreSQL are fully supported. SQLite rejects sequence declarations during schema checks with
error[E-DB-001].
42. Typed Computed Business Fields (compute) and FileRef Attachments
Zelyra 1.2.32 introduces two foundational features for business workflows: typed, server-calculated fields and schema-backed FileRef attachments in generated CRUD interfaces.
Typed Computed Fields (compute)
Columns declared with compute(expr) calculate their values deterministically from other columns in the same table:
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)
}
Characteristics and Compiler Checks:
- Pure Deterministic Expressions: The compiler verifies field dependencies, return types, and absence of cycles at compile time.
- Server-Side Enforcement: Neither CRUD forms nor mutating APIs (
POST,PUT,PATCH) allow client overrides. Submitted client values are rejected. - Partial Updates (
PATCH): During partial API mutations, omitted inputs are loaded from the existing tenant record before validation and computation are evaluated. - Exact Decimal Arithmetic: Computations with
Decimalround half-values away from zero and reject overflows.
FileRef Attachments in Generated CRUD
Columns of type FileRef can be integrated directly into generated CRUD interfaces using an attachment block:
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 {}
- Native File Input: Generated create and edit forms render a native, secure HTML file input.
- Preservation on Edit: If no new file is submitted during an edit, the existing file reference is preserved.
- Tenant-Scoped Downloads: Downloads are served via protected routes like
/contracts/{id}/files/signed_pdf, enforcing session, tenant, and permission checks. - Supported MIME Types: PDF, PNG, and JPEG with magic-byte signature validation.
43. Multi-Backend Parity, Transaction Rollbacks, and PDF Outbox
Zelyra 1.2.33 introduced semantic-alignment work across the AST reference interpreter, Typed MIR pipeline, and Bytecode VM (v24). The current scope and remaining backend-parity work are described in Chapter 31 and the roadmap.
Execution Budget Alignment (--max-steps)
The configurable execution limit --max-steps N is accounted for based on identical source spans and expression nodes across AST, MIR, and Bytecode. Infinite loops and runaway computations are aborted deterministically at the exact same threshold across all backends.
Transactional PDF Mail Outbox
Server-side PDF generation (render_pdf) and transactional email queueing (mail_queue) are atomically coordinated:
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)
)
}
}
- Atomicity: If the enclosing transaction fails (e.g. due to database errors or invariant violations), neither the PDF bytes nor the mail queue entry are committed to the outbox.
- Backend Equivalence: Differential test suites verify that AST, MIR, and the Bytecode VM produce identical error diagnostics and consistent rollback behavior on transaction failure.
APPENDICES
Appendix A: Quickstart / Cheat Sheet (Syntax Cheat Sheet)
Basic Syntax
# comment continues to the end of the line. For a block, opening and closing ## each stand alone on their own line, with comment text between them.
# Functions with contracts
fn sum(a: Int, b: Int) -> Int
requires { a >= 0 && b >= 0 }
ensures { result >= 0 }
{
return a + b
}
# Entry point and variables
fn main() {
x = 10 # Type inference (immutable)
mutable counter = 0 # Mutable
name: String = "Zelyra" # Explicit type
print(sum(3, 7))
}
Types
- Numbers:
Int(64-bit signed integer),UInt(unsigned integer),Float,Decimal(fixed-point arithmetic) - Text & Characters:
String,Char - Booleans:
Bool(true,false) - Collections:
Int[],String[] - Optionality:
Option<T>(Some(x),None), shorthandT? - Error Handling:
Result<T, E>(Ok(x),Err(e)) - System & Chronology:
Timestamp,Date,Time,Duration
Control Flow
fn control_flow(x: Int) {
if x > 10 {
print("Large")
} else {
print("Small")
}
match x {
1 => { print("One") }
2 => { print("Two") }
_ => { print("Other") }
}
mutable i = 0
while i < 3 invariant { i >= 0 } {
i = i + 1
}
for n in [1, 2, 3] {
print(n)
}
}
fn main() {
control_flow(1)
}
Database & Web
database main {
engine: mariadb
database: "app"
}
table items {
id: Id primary auto
description: String required
}
page "/items" {
html {
<h1>Item List</h1>
}
}
Appendix B: Complete Zelyra Diagnostic & Error Code Reference
| Error Code | Category | Description | Typical Fix |
|---|---|---|---|
E-LEX-001 |
Lexer | Unexpected character / lexical error | Remove typographical or illegal special characters |
E-PARSE-001 |
Parser | Syntax error (e.g. missing brace, invalid token) | Correct syntax per Zelyra grammar |
E-NAME-001 |
Resolution | Unknown name / variable not declared | Check declaration or correct typo |
E-TYPE-001 |
Type checking | Type mismatch (e.g. String assigned to Int) | Align types or add conversion |
E-FEATURE-001 |
Feature switch | Access to disabled language surface (web, api, crud, auth, audit) |
Enable feature in zelyra.toml or .env |
E-CAP-001 / E-CAP-002 |
Capabilities | Missing capability permission (e.g. database, network) |
Grant capability in zelyra.toml under [capabilities] |
E-POLICY-001 / 002 |
Policy | Security or audit policy violation | Review security policy declarations |
E-DB-001 - E-DB-005 |
Database | Database connection or driver error | Verify DATABASE_URL, ensure MariaDB is running |
E-SQL-001 - E-SQL-004 |
SQL | Invalid SQL / schema mismatch / unknown column | Check SQL statement against table schema |
E-VIEW-001 - E-VIEW-009 |
Views & Pages | Error in view interpolation, slots, or data binding | Check slot names and binding data types |
E-VIEW-010 - E-VIEW-015 |
Query controls | Invalid search, sort, pagination, or filter fields | Check declared whitelist (search, sort, filter) |
E-FORM-001 - E-FORM-004 |
Forms | Validation error or invalid form field types | Review form declaration and input payload |
E-CRUD-001 - E-CRUD-006 |
CRUD | Invalid CRUD resource, schema conflict, or layout error | Verify table binding and layout slots |
E-AUTH-001 - E-AUTH-028 |
Authentication | Session, password, or permission conflict | Check roles (permits), requires auth, and password hashes |
E-AUDIT-001 - E-AUDIT-010 |
Audit trail | Failure in cryptographic hash chain of audit log | Validate checksums and audit table integrity |
E-SETUP-001 - E-SETUP-006 |
Setup flow | Port conflict, socket error, or Compose failure | Choose free ports, check Docker socket permissions |
E-SETUP-WEB-001 |
Web setup | Invalid or expired setup token | Restart setup assistant and use tokenized URL |
E-IMPACT-001 |
Impact analysis | Cyclical or invalid dependencies | Untangle code dependencies |
E-RUNTIME-001 |
Runtime | Unhandled runtime error | Check contracts (requires, ensures) or error values |
Appendix C: Zelyra CLI Command Reference
| Command | Option / Flag | Description |
|---|---|---|
zelyra --version |
Outputs full compiler and package version | |
zelyra new <dir> |
--template minimal|mariadb-crud|... |
Creates a new Zelyra project with template |
--mariadb |
Generates MariaDB project with Compose, Dockerfile, and .env |
|
--web-port <p> --host-port <p> --db-host-port <p> |
Configures container and host ports | |
zelyra init |
[--mariadb] |
Initializes current directory as Zelyra project |
zelyra check <file.zyl> |
[--format json] |
Statically checks syntax, types, contracts, and capabilities |
zelyra run <file.zyl> |
Compiles and executes a Zelyra program | |
zelyra serve <file.zyl> |
[host:port] |
Starts the built-in HTTP web server |
zelyra routes <entry.zyl> |
[--format human|json] |
Lists recognized routes with HTTP methods and source locations; same-method overlaps fail with E-ROUTE-001 |
zelyra setup |
[--database] |
MariaDB |
[--schema] |
Starts environment and applies database schema | |
[--all] |
Executes configuration, container startup, and schema migration | |
[--host-port <p>] [--db-host-port <p>] |
Enforces exact host ports for new .env |
|
zelyra setup --web |
[--port <p>] |
Starts local token-protected browser setup assistant |
zelyra config <file.zyl> |
[--format json] |
Safely displays effective configuration and feature switches |
zelyra doctor <file.zyl> |
[--port <p>] [--json] |
Checks toolchain, MariaDB, Docker, and ports non-destructively |
[--env-file <file>] |
Reads DATABASE_URL from specified file |
|
zelyra fmt <file.zyl> |
[--check] |
Formats source code according to official standards |
zelyra verify <file.zyl> |
Performs formal contract verification | |
zelyra doc <file.zyl> |
--openapi |
Generates OpenAPI 3.0 specifications |
--typescript |
Generates typed dependency-free TypeScript client | |
zelyra db create <file.zyl> |
Generates database setup SQL | |
zelyra db setup <file.zyl> |
Sets up MariaDB database initially | |
zelyra db map <file.zyl> |
[--format=text|json] |
Read-only comparison of declared tables with live tables, columns, and foreign keys |
zelyra db plan <file.zyl> |
Shows the schema changes that would be applied; review the plan before migration | |
zelyra db apply <file.zyl> |
Applies schema migrations safely | |
zelyra auth hash-password |
[--stdin] |
Generates secure Argon2 password hashes |
zelyra form validate <file> <Form> |
Tests forms with sample values on console | |
zelyra context <file.zyl> |
[--format json] |
Emits semantic source context for developer tools |
zelyra editor [directory] |
[--port <p>] [--no-open] |
starts Studio |
zelyra --tutorial invoice [directory] |
[--no-start] |
creates the invoice tutorial |
zelyra outbox setup <file.zyl> |
sets up the MariaDB outbox | |
zelyra outbox list <file.zyl> |
[--limit <1..200>] [--format text|json] |
lists entries |
zelyra outbox run <file.zyl> |
--handler <event.type=function> [--once] |
processes due events |
zelyra outbox requeue <event-id> <file.zyl> |
requeues an event | |
| `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>] |
Scaffolds an offline-first local Zelyra module package |
zelyra module list <file.zyl> |
[--format human|json] |
Inspects source modules, exports, checksums, and settings schemas |
zelyra module settings check <file.zyl> |
[--format json] |
Validates typed module settings and environment secret availability |
zelyra module plan <entry.zyl> <target> |
Analyzes static module source closure and dependency graph | |
zelyra module bundle <entry> <target> |
--output <dir> [--docker] |
Creates a portable source bundle with isolated package dependencies |
zelyra package lock [file.zyl] |
[--check|--update] |
Verifies or updates SHA-256 package digests in zelyra.lock |
zelyra package keygen|sign|verify |
[options] |
Creates Ed25519 keypairs and signs/verifies .zpkg source archives |
zelyra package trust <add|list|remove> |
[publisher] [key-hex] |
Manages trusted publisher public keys in zelyra.trust.toml |
zelyra package registry build <dir> |
Builds a static index (index.json) from signed .zpkg files |
|
zelyra package search <url> [filter] |
Searches a static package index by name | |
zelyra package add <url> <pkg> <ver> |
--as <alias> |
Installs an exact, verified package version |
zelyra package update <url> <pkg> <ver> |
--as <alias> [--plan <file>] |
Deterministically upgrades package dependencies |
zelyra package remove <alias> |
Removes a package dependency and refreshes zelyra.lock |
|
zelyra package cache clean |
[--apply] |
Safely previews and prunes unreferenced cached package versions |
zelyra package recover |
[--apply] |
Restores manifests and lockfiles after interrupted transactions |
zelyra reflect <entry.zyl> |
[--format=json|human] |
Exports symbols, schemas, routes, and contracts as versioned JSON |
zelyra audit <inspect|export|verify|prune> |
<file.zyl> [options] |
Inspects, verifies, or prunes application audit logs |
zelyra migrate-layout --check |
[entry.zyl] [--format human|json] |
Verifies compliance with the canonical standard project layout |
zelyra db history <file.zyl> |
Displays applied database migration history | |
zelyra update |
[--check] |
Checks or installs the stable release |
Appendix D: Standard Library Overview (Zelyra 1.0.3 to 1.0.7)
Zelyra's standard library provides built-in functions for mathematics, text,
collections, sets, maps, date arithmetic, conversions, encodings, paths, and
file access. Calls can be chained in typed |> pipelines, which support lexical
lambdas since 1.0.7. Operations that access host resources or external services
(filesystem, clock, random, network, database, email/PDF) require their declared
capability.
1. Mathematics and numerics
abs(x: Int | Float | Decimal): Returns the absolute value. The minimumIntorDecimaland non-finiteFloatvalues produce runtime errors.min(a, b)andmax(a, b): Select the smaller or larger of two values with the same type:Int,UInt,Float,Decimal, orString. Non-finiteFloatvalues produce runtime errors.clamp(value, minimum, maximum): Bounds a numeric value of typeInt,UInt,Float, orDecimalto the inclusive interval. Non-finite floats or a minimum greater than the maximum produce runtime errors.sqrt(x: Float): Computes the square root of a finite, non-negative float.pow(base: Float, exponent: Float): Computesbase ^ exponent; undefined or non-finite results produce runtime errors.floor(x: Float)andceil(x: Float): Round a finite float down or up.round(x: Float, decimals: Int): Rounds halfway cases away from zero.decimalsmust be between-15and15; a non-finite result is an error.
2. Strings and text
len(text: String) -> Int: Counts Unicode code points. For arrays,lenreturns the number of elements.text_to_lower(text)andtext_to_upper(text): Convert the whole string to lowercase or uppercase.text_starts_with(text, prefix)andtext_ends_with(text, suffix): Test a prefix or suffix.text_replace(text, search, replacement): Replaces all matches.text_split(text, delimiter)and aliassplit: Split a string into aString[].text_pad_left(text, target_length, pad)andtext_pad_right(text, target_length, pad): Pad to a Unicode code-point length, cycling through the pad string's code points. An empty pad string uses spaces. Negative lengths and output over 16 MiB are runtime errors.text_index_of(text, search) -> Option<Int>: Returns the zero-based Unicode code-point index of the first match, orNone.text_repeat(text, count): Repeats the text; negative counts and output over 16 MiB are runtime errors.text_is_empty(text)andtext_is_blank(text): Check for an empty string or a string containing only whitespace.text_capitalize(text): Uppercases the first Unicode character.text_substring(text, start, length): Extracts a substring using Unicode code-point indexes. Negative indexes and lengths are treated as zero.text_trim,text_trim_start, andtext_trim_end: Remove surrounding, leading, or trailing whitespace.text_contains(text, search)andtext_chars(text): Test containment or split text into one-code-point strings.
3. Arrays and collections
append(array, value) -> T[]andconcat(left, right) -> T[]: Return arrays with the value appended or both arrays joined.contains(array, value) -> Bool,first(array) -> Option<T>, andlast(array) -> Option<T>: Search or retrieve an end element.sort,sort_desc, andreverse: Return sorted or reversed arrays.slice(array, start, length): Extracts a bounded portion of an array.join(array, delimiter) -> String: Joins rendered elements with a delimiter.unique(array)and aliasdedup: Keep the first occurrence of each value.array_sum(array): AddsInt,Float, orDecimalelements. Integer and decimal overflow and non-finite float values produce runtime errors; an empty array returns0.avg(array): AveragesIntandFloatarrays toFloat, andDecimalarrays toDecimal. Decimal division truncates at the fixed Decimal scale. Empty arrays and overflow produce runtime errors.min_of(array) -> Option<T>andmax_of(array) -> Option<T>: Select the smallest or largest number or string; an empty array returnsNone, and non-finite floats produce runtime errors.chunk(array, size): Splits an array into chunks no larger thansize;sizemust be greater than zero.flatten(array): Flattens one level of nested arrays.zip(left, right) -> Tuple<T, U>[]: Pairs corresponding elements through the shorter input array.map(array, transform) -> U[]: Transforms array elements (supports lexical lambdas like|x| x * 2since 1.0.7).filter(array, predicate) -> T[]: Filters elements with a predicate or lambda (|x| x > 0).find(array, predicate) -> Option<T>: Returns the first matching element orNone.reduce(array, initial, accumulator) -> U: Folds an array with an initial value and accumulator function.
4. Sets and maps
set_union,set_intersection, andset_difference: Return set union, intersection, or difference.get(map, key) -> Option<V>andget_or(map, key, default) -> V(aliasget_or_default): Look up a value, with or without a fallback.has_key(map, key) -> Bool: Checks whether a map contains a key.put(map, key, value) -> Map<K, V>: Functionally inserts or updates a pair.map_remove(map, key)andmap_merge(left, right): Return an updated map; values in the right map replace matching keys from the left.map_increment(map: Map<K, Int>, key: K) -> Result<Map<K, Int>, String>: Increments a typed counter or inserts it with value1. The input map stays unchanged; integer overflow is returned asErr.keys(map) -> K[]andvalues(map) -> V[]: Return the keys or values.entries(map) -> Tuple<K, V>[]: Returns key-value pairs that can be type checked and destructured in a loop:for (key, value) in entries(map).
5. Dates, options, results, and conversions
date_add_days(date, days) -> Date: Adds or subtracts calendar days; results must remain in years 0001 through 9999.date_diff_days(start, end) -> Int: Returnsstart - endin calendar days. For remaining time until a deadline, usedate_diff_days(deadline, today).is_before(a, b)andis_after(a, b): Compare two dates chronologically.is_ok,is_err,is_some, andis_none: TestResultandOptionvalues.unwrap_or(option, fallback): Returns the contained value or fallback.panic(message): Stops execution with a runtime error.to_string(value): Returns the Zelyra display form of a value.to_int(value) -> Int: ConvertsInt,UInt, finiteFloat,Bool, or a numeric string. Floats truncate toward zero. Out-of-range values and invalid strings produce runtime errors.to_float(value) -> Float: ConvertsFloat,Int,UInt, or a numeric string to a finite float; invalid or non-finite values produce runtime errors.parse_int(text) -> Result<Int, String>,parse_float(text) -> Result<Float, String>, andparse_bool(text) -> Result<Bool, String>: Parse strings without throwing; invalid values returnErr.parse_boolacceptstrue,false,1, and0, case-insensitively for words.
6. Hashing, encodings, and paths
hash_sha256(text) -> String: Returns the lowercase hexadecimal SHA-256 hash of the UTF-8 bytes.base64_encode(text)andbase64_decode(text): Encode UTF-8 text, or returnResult<String, String>when decoding.url_encode(text): Percent-encodes bytes except RFC 3986 unreserved bytes.url_decode(text) -> Result<String, String>: Decodes percent escapes;+is treated as a space.uuid_v4() -> String(uses Random): Creates a cryptographically secure version 4 UUID.path_join,path_filename,path_extension, andpath_dir: Perform path operations using the host operating system's path conventions, without filesystem access or a capability.
7. Capability-guarded system APIs
uses Console:read_console(prompt: String) -> String?prints a prompt to standard output and reads one line;Noneindicates EOF.uses Clock:now() -> Timestampreturns the current timestamp in milliseconds since the Unix epoch.uses Random:random_int(min: Int, max: Int) -> Intchooses from the inclusive range;uuid_v4()creates a version 4 UUID.uses Environment:env(name: String) -> Option<String>reads a host environment variable.uses FileSystem:read_text,write_text,delete_file,list_dir,file_exists,dir_exists, andfile_sizeoperate within the configured filesystem permissions.write_textreplaces existing file contents inside allowed write paths;file_sizereturnsResult<Int, String>.uses Network:http_get(url: String) -> Stringperforms an allowed GET request.http_request(method: String, url: String, headers: String[], body: String?) -> HttpResponsesends a request with an optional text body.http_json<Req, Res>(method, url, headers, body: Req?) -> Resdecodes a successful JSON response.http_result<Req, Res>(method, url, headers, body: Req?) -> HttpResult<Res>also returns status and headers.uses Process:run_process(command: String, args: String[]) -> ProcessResultstarts an explicitly permitted process without a shell.uses Database:sql<T[]> { SELECT ... }executes typed queries with bound parameters;transaction { ... }groups database operations atomically.uses Email:render_pdf(view_name, record_data) -> Bytes(new in 1.0.7) renders a declared view with record fields into paginated text PDF bytes;mail_queue(to, subject, text, filename, pdf_bytes)enqueues transactional PDF email attachments in MariaDB outbox.
Appendix E: SQL Cheat Sheet for Zelyra Developers
SQL statements embedded directly within Zelyra are executed with sql<T[]> or sql:
database main {
engine: mariadb
database: "app"
}
table tasks {
id: Id primary auto
name: String required
completed: Bool default false
}
fn sql_examples() uses Database {
# 1. SELECT with typed return type and safe parameter
status = false
filtered = sql<Task[]> {
SELECT id, name, completed
FROM tasks
WHERE completed = :status
}
# 2. INSERT within a transaction
text = "New task"
transaction {
sql {
INSERT INTO tasks (name, completed)
VALUES (:text, false)
}
}
# 3. UPDATE
target_id = 1
transaction {
sql {
UPDATE tasks
SET completed = true
WHERE id = :target_id
}
}
}
fn main() uses Database {
print("SQL cheat sheet validated.")
}
Appendix F: HTML and Web Reference in Zelyra
Web structures and declarations
| Element | Declaration | Purpose |
|---|---|---|
| Page | page "/path/{param}" { ... } |
Defines an HTTP GET route with path parameters and HTML response |
| View Layout | view LayoutName { html { ... <slot /> ... } } |
Reusable layout with default and named slots |
| Component | component Name { props { ... } html { ... } } |
Reusable HTML component with typed properties |
| Named Slot | <slot name="header">Fallback</slot> |
Placeholder in layout/component with optional default content |
| Slot Injection | <slot name="header">Content</slot> |
Passes child content into matching slot |
| Data Loading | load item = sql<Item> { SELECT ... } |
Typed loading of single record with field access {item.field} |
| Collection Loop | for item in items { <li>{item.name}</li> } |
Typed server-side iteration over loaded collection |
| Search Control | search { col1 col2 } |
Whitelist-checked URL search with parameterized LIKE query |
| Sort Control | sort { col1 col2 } |
Typed sorting via ?sort=col&order=asc|desc |
| Pagination | paginated 25 |
Pagination with LIMIT/OFFSET, page, pages, and total |
| Filter Control | filter { col1 col2 } |
Typed filter operators (eq, contains, starts_with, gt, lte etc.) |
| CRUD Layout | crud Res { table tbl layout: LayoutName } |
Embeds CRUD views into slots title, nav, content, actions |
Appendix G: Glossary of Technical Terms
Each entry links to the chapter that explains the term. Unsupported language constructs and CLI commands are marked as such.
- Value: A named piece of data. In Zelyra, values are immutable by default to prevent hidden side effects and state bugs. Chapter 5: Values and Data Types.
- mutable (Mutability): Keyword required to declare variables that may be reassigned. Makes state mutations explicitly visible to developers and compilers. Chapter 6: Variables and Immutability.
- Function (fn): A named operation with explicit parameter types and a definite return type. Functions are the foundational building blocks of Zelyra programs. Chapter 11: Functions and Procedures.
- Pure Function: A function without side effects: its result depends only on its inputs, so identical inputs produce the same result. Chapter 11: Functions and Procedures.
- Type Inference: The compiler's capability to automatically deduce data types from values and expressions, combining static safety with concise syntax. Chapter 5: Values and Data Types.
- Typed Hole (_): The underscore
_as a formal typed placeholder. Instructs compiler and AI tools where implementation is needed, with expected types inferred from surrounding context. Chapter 32: AI-Nativity. - Block & Scope: The lexical scope of identifiers, bounded by curly braces
{ ... }. Names declared inside a block are bound to that scope. Chapter 6: Variables and Immutability. - Data Type: A precise description of allowable values and valid operations. Includes scalars (Int, Float, Bool, String, Decimal, Id) and compound types. Chapter 5: Values and Data Types.
- Option (Some / None): Type representing a value that may be present (
Some(v)) or absent (None). The question mark suffixType?guarantees absence is handled explicitly. Chapter 18: Option. - Result (Ok / Err): A type for operations that may fail. It returns success as
Ok(data)and an error asErr(reason)explicitly. Chapter 17: Result Pattern. - Null Safety (Zero null): Zelyra has no concept of
null,nil, orundefined. The "billion-dollar mistake" of null-pointer exceptions is structurally excluded by the type system. Chapter 18: Option. - Pattern Matching (match): Exhaustive pattern evaluation across data structures, options, and results. The compiler rejects code if any branch is left unhandled. Chapter 14: Custom Data Types.
- Domain Types (Nominal): Nominal type definitions like
type UserId = Idandtype OrderId = Id. Prevents accidentally swapping identically represented primitives in business logic. Chapter 14: Custom Data Types. - Record / Struct: A structured collection of named, strongly typed fields representing a concrete entity, with constructor and destructuring support. Chapter 14: Custom Data Types.
- Enum / Sum Type: An enum describes a fixed set of distinct variants. User-defined
enumdeclarations are not part of the currently documented Zelyra language surface; the conceptual pattern is not executable Zelyra syntax. Chapter 14: Custom Data Types. - Design by Contract: Formal software engineering method ensuring correctness: functions declare rigorous preconditions (
requires) and postconditions (ensures). Chapter 12: Contracts and Preconditions. - Precondition (requires): Contract clause attached to a function signature enforcing valid caller inputs before body execution. Shields functions from invalid state. Chapter 12: Contracts and Preconditions.
- Postcondition (ensures & result): Contractual guarantee about the computation. The keyword
resultreferences the returned value and verifies its invariants. Chapter 12: Contracts and Preconditions. - Loop Invariant (invariant): Formal assertion inside loops that must remain true before and after each iteration, facilitating mathematical verification. Chapter 12: Contracts and Preconditions.
- Capability: Explicit permission clause (
uses FileSystem,uses Database) without which code cannot perform I/O or access external resources. Chapter 33: Capabilities. - Sandboxing & Least Privilege: The capability model limits system access to capabilities requested by functions and enabled for the project. Chapter 33: Capabilities.
- Array (Type[]): A homogeneous ordered collection of elements. Supports indexed access and standard utilities like
len(),append(),first(), andcontains(). Chapter 13: Arrays & Maps. - Map (Map<K, V>): Associative key-value dictionary. Querying via
get(map, key)always returns a safeOption<V>, eliminating crashes on missing keys. Chapter 13: Arrays & Maps. - Immutable Collections: Immutable collection operations do not change the existing value directly; they return a new value for a change. Chapter 13: Arrays & Maps.
- for .. in Iteration: A loop form that visits collection elements in sequence and avoids manual index management. Chapter 10: Repetition and Loops.
- Table Schema (table): Direct relational table declaration in Zelyra syntax including types, primary keys, uniqueness constraints, and default values. Chapter 24: Defining Tables.
- Migration (db plan & apply): Controlled schema changes:
zelyra db planshows the difference between the source model and database;zelyra db applyapplies the reviewed plan. Review possible effects first, since interrupted DDL can leave a partially changed state. Chapter 24: Defining Tables. - CRUD Resource (crud): Create, Read, Update, Delete: Declarative resource binding generating query routines, forms, and layouts without repetitive boilerplate. Chapter 28: CRUD Pattern.
- Native SQL (sql { ... }): First-class SQL validated against schemas at compile-time. Delivers raw query performance and expressiveness without ORM friction. Chapter 25: Querying Data.
- Transaction (transaction): Atomic database transaction block: all database operations succeed together or automatically roll back upon any failure. Chapter 25: Querying Data.
- Foreign Key (fk): Declarative relational link between tables enforcing referential integrity and cascade rules directly at database level. Chapter 24: Defining Tables.
- Component (component): Reusable UI template unit with typed props, slots, and automated contextual HTML escaping against XSS vulnerabilities. Chapter 26: Rendering Web Pages.
- Slot (slot: name): A named injection slot in container components enabling flexible layout composition with dynamic child elements. Chapter 26: Rendering Web Pages.
- Form & Validation (form): Declarative web form specification providing unified server and browser validation with structured error feedback. Chapter 27: Forms.
- CSRF Protection: Built-in cryptographic token mechanism preventing Cross-Site Request Forgery on all state-modifying requests. Chapter 27: Forms.
- Route: Explicit binding of HTTP method and URL endpoint to an executable controller handler or CRUD resource. Chapter 26: Rendering Web Pages.
- Authentication (auth): Native declarative block configuring user login, session lifecycle, and credentials without third-party frameworks. Chapter 29: Authentication.
- Argon2 Password Hashing: State-of-the-art memory-hard hashing algorithm natively enforced across Zelyra authentication modules. Chapter 29: Authentication.
- requires auth & permits: Declarative route authorization attribute restricting endpoints to logged-in users or specific privilege roles. Chapter 29: Authentication.
- uses FileSystem: Security capability governing storage I/O. Functions reading or writing files must declare this permission at the boundary. Chapter 20: Files.
- read_text & write_text: Standard library utilities for atomic, safe UTF-8 file reading and writing with automatic resource closing. Chapter 20: Files.
- uses Clock & now(): System time capability: querying current timestamps via
now()requiresuses Clock, enabling deterministic mocking in automated tests. Chapter 21: Date & Time. - uses Random & random_int(): Randomness capability: grants access to pseudo-random generators while keeping pure algorithms cleanly separated. Chapter 21: Date & Time.
- uses Environment & env(): Environment variable capability:
env("KEY")queries runtime configuration or.envand yields a null-safeOption<String>. Chapter 37: Configuration. - parallel & await: A
parallelblock starts independent tasks and usesawaitto wait for their results. The task scope remains visible in the block. Chapter 22: Concurrency. - json_encode & json_decode: Type-safe JSON serialization and deserialization yielding explicit
Result<T, JsonError>containers. Chapter 21: JSON & Data. - zelyra.toml (Project Manifest): Project manifest for metadata, capability grants, and supported project settings. Zelyra modules are included by project path; a package registry is outside the documented scope. Chapter 15: Modules & Projects.
- zelyra verify (Contract Prover): CLI command for the documented contract and invariant checks. Formal analysis is limited to the supported verification scope and does not guarantee overall program correctness. Chapter 19: QA & Testing.
- assert (Test Assertion): Checks a condition and reports an error when it is false. Project-specific assertions can run in an executable
.zylfile withzelyra run; a separate test subcommand is not documented. Chapter 19: QA & Testing. - Source Authority Principle: A rule for resolving conflicting sources. The handbook reference orders them as language specification, compiler implementation, official tests, standard library, verified examples, and documentation. Ref 24: Source Authority.
- Three-Layer Architecture: Canonical Zelyra architectural pattern strictly isolating UI presentation, pure business logic, and schema persistence. Chapter 36: Architecture.
- TDD (Test-Driven Development): Methodology where formal contracts and automated test assertions precede the actual function implementation. Chapter 19: QA & Testing.
- Regression Prevention: Prevention of bugs reappearing after refactoring. Static checks, contract verification, and project-specific test runs help catch unintended changes. Chapter 19: QA & Testing.
- AST (Abstract Syntax Tree): The hierarchical syntax tree built by the parser from source code. Compiler phases such as name resolution and type checking continue from this structured representation. Chapter 24: Source Authority.
- Zelyra CLI (zelyra): The command-line interface for Zelyra projects: it creates projects with
new, checks source withcheck, runs programs withrun, and provides other documented subcommands. The current CLI reference does not list a dedicatedtestsubcommand. Chapter 4: The First Zelyra Project. - zelyrac (Compiler): The compiler processes Zelyra source behind the
zelyraCLI. The current CLI reference does not document a separatezelyracexecutable orzelyrac build; usezelyra checkandzelyra runto check and execute programs. Chapter 4: The First Zelyra Project. - zelyra fmt (Formatter): Built-in source formatter with deterministic output.
--checkchecks formatting without changing the file. Chapter 4: The First Zelyra Project. - LSP (Language Server Protocol): The Language Server Protocol standardizes communication between a language and an editor. The current CLI reference documents
zelyra editorfor Studio, but no separatezelyra lspsubcommand. Chapter 4: The First Zelyra Project. - AI-Native Language Design: Language and tooling features such as structured diagnostics, contracts, and typed holes can support AI-assisted work. They do not guarantee error-free code or replace human review. Chapter 32: AI-Nativity.
- Standard Library (std): Overview of documented standard functions and capability-guarded functions such as filesystem, time, randomness, and database access. Appendix D: Standard Library.
Appendix H: Solutions to Chapter Exercises
Chapter 1: Greeting
fn main() {
print("Hello World from Zelyra!")
}
Chapter 5: Calculate Discount Price
fn calculate_discount(original: Float, percent: Float) -> Float {
return original * (1.0 - (percent / 100.0))
}
fn main() {
print(calculate_discount(100.0, 20.0))
}
Chapter 11: Double Numbers
fn double_number(number: Int) -> Int {
return number * 2
}
fn main() {
print(double_number(21))
}
Chapter 12: Pre- and Postconditions
fn clamp(value: Int, min_val: Int, max_val: Int) -> Int
requires { min_val <= max_val }
ensures { result >= min_val && result <= max_val }
{
if value < min_val { return min_val }
if value > max_val { return max_val }
return value
}
fn main() {
print(clamp(120, 0, 100))
}
Chapter 13: Sum an Array
fn sum_array(numbers: Int[]) -> Int {
mutable total = 0
for n in numbers {
total = total + n
}
return total
}
fn main() {
items: Int[] = [1, 2, 3, 4, 5]
print(sum_array(items))
}
Appendix I: Frequently Asked Questions (FAQ)
Question: How do I use imports?
Answer: import "src/models.zyl" as models includes a project-local source file. Access supported public declarations through the module name. A package registry, dynamic imports, and complete deployment closure are not included.
Question: Can I build command-line applications with Zelyra?
Answer: Yes. print() emits values. read_console("Prompt: ") reads a line and returns String?; the function needs uses Console and the project may also need console = true.
Question: Why does Zelyra emphasize MariaDB as its primary database engine? Answer: MariaDB provides exceptional transactional performance, open-source licensing, robust cloud compatibility, and rock-solid reliability for enterprise web applications.
Appendix J: Next Resources and Community
- Official GitHub Repository: https://github.com/sf1976/zelyra
- Documentation & Online Handbook: https://siedelmann.com/handbook / https://siedelmann.com/handbuch
- Examples & Templates: Check the
examples/directory in the official repository for runnable templates covering authentication, CRUD views, REST APIs, and database migrations.
Appendix K: The Zelyra Cookbook (Practical Recipes)
The Zelyra Cookbook provides a curated collection of proven patterns, architectural recipes, and concrete solutions for real-world business and web engineering challenges. Each recipe follows a practical three-tier format: Problem, Solution (contract-verified Zelyra code), and Explanation.
Part 1: Data Processing & Standard Library
Recipe K.1: Exact Commercial Rounding & Cash Discount Calculations
Problem:
In commercial and accounting applications, binary floating-point numbers (Float) introduce rounding errors into discounts, VAT splits, and ledger totals.
Solution:
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,
}
}
Explanation:
- Zelyra's
Decimaltype implements high-precision fixed-point arithmetic without binary representation errors. - The precondition contract
requiresguarantees that negative discount rates or values above 100% cannot be passed into the function. - The postcondition
ensuresverifies mathematically that the final discounted total can never exceed the starting net amount.
Recipe K.2: Safely Parsing & Typing External Webhook Payloads
Problem: An external payment provider (such as Stripe or PayPal) transmits event notifications via HTTP POST webhooks. Missing or malicious attributes must be rejected with full type safety.
Solution:
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 }
}
}
Explanation:
- Zelyra requires exhaustive
matchchecking. Unrecognized status payloads hit the fallback wildcard_and returnOption::None, eliminating unhandled runtime crashes.
Recipe K.3: Computing Payment Due Dates and Business Days
Problem: An invoice requires a 14-day payment window. If the due date falls on a weekend, the payment deadline must automatically shift to the subsequent business day (Monday).
Solution:
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
}
}
Explanation:
- Calendar arithmetic in Zelyra operates on typed
Daterecords and accounts for calendar months and leap years without timezone ambiguity.
Part 2: Database, Transactions & Multi-Tenancy
Recipe K.4: Gapless Invoice Number Sequences (Atomic Sequence)
Problem: Tax authorities and accounting regulations require gapless, strictly sequential document numbering. Under high concurrency, naive increments create race conditions and duplicates.
Solution:
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 "INV-" + year.to_string() + "-" + next_val.to_string()
}
}
Explanation:
- The
FOR UPDATEquery inside an explicittransactionblock acquires an exclusive row lock. scoped by tenant_idpaired withunique within tenant fiscal_yearisolates number series between separate tenants and fiscal years.
Recipe K.5: Immutable Audit Trail (Temporal Ledger)
Problem: Mutations to sensitive business data (prices, bank routing numbers, administrative permissions) must be recorded permanently with the acting user and timestamp.
Solution:
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]
)
}
Explanation:
- Explicit capabilities
DatabaseandClockguarantee reproducible unit tests via virtual clock mocks while recording strict UTC timestamps in production. - Computing a SHA-256 hash fixes the payload cryptographically for forensic audits.
Recipe K.6: High-Performance Keyset Pagination for Large Datasets
Problem:
On tables with millions of rows, OFFSET-based pagination (SELECT ... LIMIT 50 OFFSET 500000) degrades query performance because the engine must traverse all preceding rows.
Solution:
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]
)
}
Explanation:
- Keyset (cursor) pagination uses the compound index
(tenant_id, created_at, id)to seek directly to the next page, maintaining sub-millisecond execution regardless of table size.
Part 3: Security, Authentication & Governance
Recipe K.7: Two-Factor Authentication with TOTP & Recovery Codes
Problem: Sensitive user profiles must enforce RFC 6238 time-based one-time passwords (TOTP) alongside cryptographic recovery codes.
Solution:
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
}
Explanation:
totp_verifyaccepts a 30-second skew window (±1 step) to accommodate minor drift between client mobile authenticators and servers.
Recipe K.8: Tenant-Bound API with Role Verification
Problem:
A JSON API approving accounting vouchers must ensure that users can only mutate records belonging to their own tenant and strictly possess the vouchers.approve permission.
Solution:
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: "Invalid voucher 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: "Voucher has already been approved." }
}
execute(
"UPDATE vouchers SET status = 'approved' WHERE id = ? AND tenant_id = ?",
[voucher_id, session.tenant_id]
)
return { success: true, message: "Voucher approved successfully." }
}
}
}
Explanation:
scoped by tenant_idguarantees that requests are bound to the caller's authenticated tenant (session.tenant_id), rendering cross-tenant data leakage impossible at compile time.
Recipe K.9: Rate Limiting & Brute-Force Defense
Problem: Authentication and credential endpoints require rate limiting to prevent brute-force attacks and credential stuffing.
Solution:
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
}
}
}
Explanation:
- Once
max_attemptsis reached, further invocations are blocked immediately, mitigating credential stuffing and conserving backend resources.
Part 4: Web, CRUD & Master-Detail
Recipe K.10: Master-Detail Invoice Form with Line Items (has_many)
Problem: An invoice requires header fields (customer, invoice date) alongside a variable collection of line items, committed atomically with server-calculated summary aggregates.
Solution:
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: "Create Invoice"
has_many items: InvoiceItem {
title: "Line Items"
fields: [description, quantity, unit_price, line_total]
}
aggregates {
total_net = sum(items.line_total)
total_gross = sum(items.line_total) * decimal("1.19")
}
}
Explanation:
has_many itemsgenerates child item grids directly within the parent form.- The
aggregatesblock computes monetary sums inside the storage transaction on the server, preventing client-side price tampering.
Recipe K.11: Lifecycle Guards for Immutable Finalized Documents
Problem: Invoices that have been finalized or paid must be permanently protected against subsequent editing or deletion.
Solution:
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: "Sales Invoices"
guard edit when is_finalized == true {
error: "Finalized documents cannot be edited. Create a cancellation credit note instead."
}
guard delete when is_finalized == true {
error: "Finalized invoices cannot be deleted under regulatory compliance rules."
}
}
Explanation:
- Zelyra evaluates guards within the database mutation transaction.
- In addition, generated
tableviewaction columns automatically hide or disable edit and delete actions when the condition evaluates totrue.
Recipe K.12: Cross-Field Invariant Enforcement (validate)
Problem: In hotel and rental reservations, the departure date must always be after the arrival date.
Solution:
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: "Check-out date must be after check-in date."
rooms_count >= 1 && rooms_count <= 10: "Between 1 and 10 rooms can be booked per reservation."
}
}
Explanation:
- Invariants are enforced across forms, CRUD actions, and API mutations before committing writes, returning standardized HTTP 422 errors and setting
aria-invalid="true".
Part 5: Background Services, Email & Export
Recipe K.13: Transactional PDF Invoicing & Email Outbox
Problem: When an order completes, an official PDF receipt must be generated and queued for email delivery without risking orphaned emails if the database transaction rolls back.
Solution:
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,
"Your Order Confirmation " + order.order_number,
"Thank you for your order. Please find your official confirmation attached.",
order.order_number + ".pdf",
pdf_bytes
)
execute("UPDATE orders SET status = 'confirmed' WHERE id = ?", [order_id])
}
}
Explanation:
mail_queueenqueues the message directly into MariaDB'smail_queuetable inside the active transaction. If the transaction rolls back, no email is sent.
Recipe K.14: Idempotent Background Cron Jobs with Renewable Leases
Problem: A nightly reminder job must execute at 02:00 UTC without duplicate processing across clustered servers.
Solution:
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])
}
}
}
}
Explanation:
- Combining
FOR UPDATE SKIP LOCKEDwith Zelyra's leased scheduler ensures that multiple server nodes work concurrently without overlapping task execution.
Recipe K.15: Streaming CSV Export for Accounting Integration
Problem: Exporting accounting journals to CSV for tax advisors must maintain low memory usage even with tens of thousands of records.
Solution:
fn export_datev_csv(t_id: TenantId, year: Int) -> String uses Database {
mutable csv_output = "Date;Voucher;Account;ContraAccount;Amount;Notice\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
}
Explanation:
- Explicit types guarantee that numerical values and dates are exported using deterministic formatting standards.
