# Gizlang documentation Each page has one kind, and each fact has one page. Other pages link to it. Every page states what a test checks today. | Kind | You want to | Pages | | --- | --- | --- | | Start | See what Gizlang can do, install it and run a first program | [Tour](tour.md), [Start](start.md) | | Guides | Do a task, from an empty folder to a result | [Guides](#guides) | | Operations | Run a server on your own machines | [Operations](#operations) | | Reference | Find an exact fact: a declaration, a command, a rule | [Reference](reference/README.md) | | Internals | Change Gizlang itself | [Internals](#internals) | ## Start | Page | What it gives | | --- | --- | | [Tour](tour.md) | Each part of Gizlang in one short program that runs | | [Start](start.md) | Install, then make, run, test and build a first program | ## Guides | Guide | What you do | | --- | --- | | [The language](guides/language.md) | Learn Gizlang if you know TypeScript, JavaScript or Go | | [The command line](guides/command-line.md) | Make a project, then run, check, test and build it | | [Packages](guides/packages.md) | Use the manifest, pins, dependencies and npm packages, and publish a package | | [Web apps](guides/web.md) | Build a server-rendered web app with GizWeb | | [Model your data](guides/model.md) | Give one record type its table, typed queries, forms and views | | [GizDB](guides/data.md) | Use the database in a program or as a server | | [Vector search](guides/vector.md) | Index embeddings and find the nearest rows with SQL | | [Machine learning in SQL](guides/sql-ml.md) | Train, deploy and use models with SQL in GizDB | | [Compute](guides/compute.md) | Compute with tensors on the CPU and GPU, write kernels, take gradients | | [AI models](guides/ai.md) | Chat, make images and serve the OpenAI API by model name | | [Storage](guides/storage.md) | Manage buckets, objects, keys and volumes | | [JavaScript and Python](guides/javascript-python.md) | Run JavaScript, TypeScript and Python programs on the VM | | [Your Giz account](guides/giz-account.md) | Use models, Drive, tables, Computers, Apps and Agents from the `giz` command | | [Migrate](guides/migrate.md) | Map Node.js, Next.js, PostgreSQL, Redis and S3 to their Gizlang equivalents | ## Operations | Page | What you do | | --- | --- | | [The GizDB server](operations/gizdb-server.md) | Install, configure, back up and monitor the server on one Linux machine | | [A storage range](operations/storage-range.md) | Run storage on your own disks and machines | ## Reference [The reference index](reference/README.md) lists the language specification, the differences from TypeScript, GHTML, the settings, the error codes, the command line, and each module of the standard library and of the libraries. ## Internals | Page | How it works | | --- | --- | | [Compiler](internals/compiler.md) | Modules, the bootstrap from the seed, golden cases and the build cache | | [Runtime](internals/runtime.md) | The C platform layer that every program links | | [Memory](internals/memory.md) | Values, references, the collector of `Ref` objects and the memory limit | | [System](internals/system.md) | Files, processes, sockets, JavaScript and Python, and effects | | [Tools](internals/tools.md) | `bin/giz`, the command line and their caches | | [VM](internals/vm.md) | The interpreter, the JIT, Node and the CPython bridge | | [Web](internals/web.md) | GizWeb: the request path, pages, models, actions and live views | | [Localization](internals/localization.md) | Messages, catalogs and the language of a request | | [Database](internals/database.md) | GizDB: storage, transactions, SQL, analytics, search and the server | | [Storage](internals/storage.md) | GizFS: chunks, consensus, the file, block, object and S3 profiles | | [ML in SQL](internals/sql-ml.md) | The PostgresML interface of GizDB | | [Compute](internals/compute.md) | SIMD, tensors, GPU kernels and models on each device class | | [Performance](internals/performance.md) | The rules of measurement, the suites and their published results | ## Results and changes | Page | What it gives | | --- | --- | | [Benchmarks](benchmarks.md) | The measured results, each with its date, machine and method | | [Changelog](changelog.md) | The changes that users see, newest first | # Tour Gizlang is a language with the syntax of TypeScript that compiles to native code, and a toolchain with libraries for the work around it. One install gives all of the parts below. Each program here is a file in [`examples/tour`](https://github.com/gizai/gizlang/tree/main/examples/tour), and a test runs each one, so the output that you see is what it prints. Run one with `giz FILE`. | Part | Import | What it gives | | --- | --- | --- | | [The language](#the-language) | | Types, records, unions, `match`, `Result`, contracts, generics, markup | | [The standard library](#tasks) | `gizlang:NAME` | System, files, text, collections, JSON, HTTP, crypto, tasks, time, tests | | [GizDB](#gizdb) | `gizdb` | A SQL database in the program, or a PostgreSQL-protocol server | | [GizWeb](#gizweb) | `gizweb` | Server-rendered web apps from pages, models and actions | | [Compute](#compute) | `gizml` | Tensors on the CPU and GPU, gradients, GPU kernels, models | | [Storage](#storage) | `gizfs` | Buckets and objects, embedded or on a range of machines | | [Devices](#devices) | `gizhw` | GPUs, NPUs, cameras, audio, sensors and other devices | | [AI models](#ai-models) | `gizml:ai` | Chat, images and the OpenAI API by model name, on your machine | | [JavaScript and Python](#javascript-and-python) | | `.js`, `.ts` and `.py` programs on the Gizlang VM | ## The language Types are checked when you compile. A union names its cases, and `match` must cover each one. `try` returns the error of a failed step. ```gizlang run file="examples/tour/hello.giz" // The language: records, unions, match, Result and try. TypeScript syntax, native code. type Shape = | { kind: "circle"; r: f64; } | { kind: "square"; side: f64; }; function area(s: Shape): f64 { match (s) { Shape.circle(r) => { return 3.14159 * r * r; } Shape.square(side) => { return side * side; } } } function half(n: i64): Result { if (n % 2 !== 0) { return Result.err(`${n} is odd`); } return Result.ok(n / 2); } function quarter(n: i64): Result { return half(try half(n)); } function main(): void { const shapes: Vec = [Shape.circle(1.0), Shape.square(2.0)]; for (const s of shapes) { print(`area ${area(s)}`); } for (const n of [12, 6]) { match (quarter(n)) { Result.ok(q) => { print(`${n} / 4 = ${q}`); } Result.err(why) => { print(`${n}: ${why}`); } } } } ``` ```text output area 3.14159 area 4 12 / 4 = 3 6: 3 is odd ``` Next: [the language guide](guides/language.md), and [the differences from TypeScript](reference/typescript.md). ## Tasks `Task.parallel` runs a body on each core, and gives the results in index order. The standard library (`gizlang:`) also has threads, channels, timers and async I/O. ```gizlang run file="examples/tour/tasks.giz" // Tasks: Task.parallel runs a body on every core and gives the results in order. import * as Task from "gizlang:task"; function main(): void { const squares = Task.parallel(8, (i: i64): i64 => { return i * i; }); let sum = 0; for (const s of squares) { sum += s; } print(`${squares.map((s: i64): string => { return string(s); }).join(" ")} sum ${sum}`); } ``` ```text output 0 1 4 9 16 25 36 49 sum 140 ``` Next: [the standard library reference](reference/sdk.md). ## GizDB GizDB is a database in your program: a folder, SQL with parameters, transactions, and no server to run. The same engine is also a server that PostgreSQL clients use. ```gizlang file="examples/tour/data.giz" // GizDB in the program: a database in a folder, SQL with parameters, no server. import {System} from "gizlang:system"; import * as Store from "gizdb:store"; function main(sys: System): void { const folder = sys.temporaryDir()!; const db = Store.open(sys, folder.temporaryPath()!).unwrap(); db.exec("CREATE TABLE notes (id INT PRIMARY KEY, title TEXT, stars INT)").unwrap(); db.query("INSERT INTO notes VALUES ($1, $2, $3)", ["1", "Buy milk", "2"]).unwrap(); db.query("INSERT INTO notes VALUES ($1, $2, $3)", ["2", "Write the tour", "5"]).unwrap(); for (const row of db.query("SELECT title, stars FROM notes WHERE stars > $1", ["3"]).unwrap().rows) { print(`${row[0]!} ${row[1]!}`); } db.close(); } ``` ```text output Write the tour 5 ``` Next: [use GizDB](guides/data.md), [model your data](guides/model.md), [vector search](guides/vector.md), [machine learning in SQL](guides/sql-ml.md), [run the GizDB server](operations/gizdb-server.md). ## GizWeb A GizWeb site can be a folder of pages: each `.ghtml`, `.md` and `.html` file under `pages/` is an address. A `.ghtml` file is HTML with Gizlang in braces. This is `examples/web/pages`: ```gizlang file="examples/web/pages/main.giz" // A site from files, like PHP or Next.js: every .ghtml, .md and .html file of pages/ is an address (docs/reference/ghtml.md). // Run: giz examples/web/pages/main.giz, then open http://127.0.0.1:8080 import {System} from "gizlang:system"; import * as Web from "gizweb"; import * as Pages from "gizweb:pages"; import {site} from "./pages/.routes"; function main(sys: System): i64 { return Web.listen( sys, 8080, Pages.shell(""), Web.serving(), (own: System) => Pages.handler(site(), Pages.shell(""), Pages.standalone(own)) ); } ``` ```html file="examples/web/pages/pages/blog/[slug].ghtml" ``` `giz examples/web/pages/main.giz` serves it at `http://127.0.0.1:8080`, and `/blog/hello` shows "Post: hello". Next: [build a web app](guides/web.md), where one model gives the table, the form, the checks and the API. ## Compute `gizml` has tensors, fused elementwise chains, gradients and models. The same calls run on a GPU when the machine has one. ```gizlang run file="examples/tour/tensors.giz" // gizml: tensors on the CPU, or on a GPU when the machine has one. import * as T from "gizml:tensor"; function main(): void { const a = T.tensor([2, 3], [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); const b = T.tensor([3, 2], [1.0, 0.0, 0.0, 1.0, 1.0, 1.0]); print(a.matmul(b).text()); } ``` ```text output [[4, 5], [10, 11]] ``` Next: [tensors, GPU kernels and gradients](guides/compute.md). ## Storage `gizfs` keeps buckets and objects. Here it is embedded in the program; the same calls reach a range of machines or gizstack.com. ```gizlang file="examples/tour/storage.giz" // gizfs: buckets and objects, here in an embedded store in a folder. import {System} from "gizlang:system"; import {embedded} from "gizfs:node"; import * as Object from "gizfs:object"; function main(sys: System): void { const folder = sys.temporaryDir()!; const store = embedded(sys, folder.temporaryPath()!, "me").unwrap(); store.changeObject(Object.Command.bucket("me", "photos", false)).unwrap(); store.put("photos", "2026/hello.txt", utf8("Hello"), "text/plain").unwrap(); print(string(store.get("photos", "2026/hello.txt", 0).unwrap())); store.close(); } ``` ```text output Hello ``` Next: [use storage](guides/storage.md), [run a storage range](operations/storage-range.md). ## Devices `gizhw` reads and drives the machine: GPUs, NPUs, cameras, audio, video codecs and sensors. This program prints the platform and each sensor that the operating system shows, so its output is that of your machine. ```gizlang file="examples/tour/devices.giz" // gizhw: the machine's devices and sensors, read from the operating system. import {System} from "gizlang:system"; import * as Device from "gizhw:device"; function main(sys: System): void { print(Device.platform(sys)); for (const s of Device.sensors(sys).valueOr([])) { print(`${s.device} ${s.channel} ${s.value}`); } } ``` Next: [compute on the GPU](guides/compute.md#step-2-the-same-work-on-a-gpu). ## AI models `gizml:ai` runs models by name on your machine: chat, embeddings and images, and an OpenAI-compatible server. The first use downloads the weights. ```sh giz ai chat qwen3.5-4b "Name three rivers." ``` Next: [run AI models](guides/ai.md). ## JavaScript and Python The `giz` command also runs JavaScript, TypeScript and Python on the Gizlang VM, with npm packages, Node's modules and Python's standard library. ```typescript file="examples/tour/hello.ts" // TypeScript on the Gizlang VM: Node's modules work, with no Node.js installed. import { createHash } from "node:crypto"; const words: string[] = "the cat and the dog".split(" "); const counts = new Map(); for (const w of words) counts.set(w, (counts.get(w) ?? 0) + 1); console.log([...counts].map(([w, n]) => `${w}=${n}`).join(" ")); console.log(createHash("sha256").update("gizlang").digest("hex").slice(0, 16)); ``` ```text output the=2 cat=1 and=1 dog=1 db69adfa269a71dd ``` ```python file="examples/tour/hello.py" # Python on the Gizlang VM: the program runs in the VM's interpreter and JIT. import json counts = {} for word in "the cat and the dog".split(): counts[word] = counts.get(word, 0) + 1 print(json.dumps(counts, sort_keys=True)) ``` ```text output {"and": 1, "cat": 1, "dog": 1, "the": 2} ``` Next: [JavaScript, TypeScript and Python](guides/javascript-python.md). ## More programs | Program | Shows | | --- | --- | | `examples/cli/wordcount.giz` | A command-line tool: files, `Result` and `try`, maps and the exit code | | `examples/cli/orders.giz` | One record type gives the DDL, the form checks, the rows and the JSON ([model your data](guides/model.md)) | | `examples/web/http` | A server-rendered app on `gizlang:http` alone: routes, markup components and a form | | `examples/web/notes` | The same app on GizWeb, with unit tests and a journey test ([build a web app](guides/web.md)) | | `examples/web/admin` | An admin app from one annotated record type, with no page or action written | # Start You install the toolchain, make a project, then run, test, change and build it. To try Gizlang with no install, use the [Playground](https://gizstack.com/play). ## 1. Install :::code-group{label="Install"} ```sh title="macOS and Linux" curl -fsSL https://gizstack.com/install | sh ``` ```powershell title="Windows" irm https://gizstack.com/install.ps1 | iex ``` ::: :::status{of="install-script"} ::: One install gives the compiler, the standard library, the libraries (gizhw, gizml, gizfs, gizdb, gizweb), the test runner, the formatter, the language server, live code swap and the GizDB server. It needs no administrator account and no other build tools. The script finds your system, downloads the installer for it and checks its SHA-256. It then checks the release signature (Ed25519) and the SHA-256 of every file, activates the version in `~/.giz`, and adds `~/.giz/bin` to your `PATH`. It does nothing until its last line, so a download that stops part way changes nothing. Read it first with `curl -fsSL https://gizstack.com/install`. Check the install: ```sh giz --version ``` ### Install options Give options after `sh -s --`, for example `curl -fsSL https://gizstack.com/install | sh -s -- --version 0.2.0`. | Option | Meaning | | --- | --- | | `--version VERSION` | Install this version. The default is the newest. | | `--only gizdb` | Install only the GizDB server into `~/.gizdb` (`GIZDB_INSTALL=DIR` for another folder). See [the GizDB server](operations/gizdb-server.md). | | `--no-modify-path` | Do not change shell profiles. `NO_MODIFY_PATH=1` does the same. | | `GIZ_INSTALL=DIR` | Install into DIR instead of `~/.giz`. | ### Other channels :::status{of="channels"} ::: Each channel runs the same signed installer. | Channel | Command | | --- | --- | | npm | `npm install -g @gizai/cli`, `npx @gizai/cli`, `bunx @gizai/cli` | | Homebrew | `brew install gizai/tap/giz` | | Docker | `docker run --rm -v "$PWD":/work ghcr.io/gizai/giz run main.giz` | | GitHub Actions | `uses: gizai/setup-giz@v0` | | Signed installer | Download it from the [download page](https://gizstack.com/download) (its address is its SHA-256), check it with `sha256sum`, then run `./giz-installer --root "$HOME/.giz"` | The GizDB server alone has the same channels under the name `gizdb`. ### Platforms Linux x86-64 needs glibc 2.35 or later. macOS needs 11.0 or later. The [release catalog](https://gizstack.com/api/releases) lists every published release with its target, version and minimum platform. :::catalog{group="install"} ::: ## 2. Make a project ```sh giz init hello cd hello ``` | File | Contents | | --- | --- | | `main.giz` | The program | | `main.test.giz` | Its tests | | `package.json` | The manifest: the entry, the modules with their pins, and the toolchain version | ## 3. Run it ```gizlang run function main(): void { print("Hello, world"); } ``` ```text output Hello, world ``` Run it with `giz run main.giz`. The first run compiles the program to native code against the prebuilt standard library. Later runs start from the cache. `giz main.giz` is the short form. ## 4. Test it ```sh giz test ``` `giz test` runs each function whose name starts with `test` in the `.test.giz` files under the folder. It keeps a pass until the code that the test covers changes. ## 5. Change it live ```sh giz live main.giz ``` `giz live` runs the program and replaces its code each time you save a file. Named state stays, and a function that is running finishes on the code that it started with. ## 6. Build it ```sh giz build main.giz -o hello ./hello ``` The result is one native executable with no runtime to install. ## Keep the toolchain current | Task | How | | --- | --- | | Upgrade | `giz upgrade` (or `giz upgrade --version 0.2.0`). The active version stays installed, and stays active if the upgrade fails. | | Pin a project's version | Put `"version": "0.2.0"` in the `gizlang` section of `package.json`. `giz` then runs that version in the folder and below, and installs it on first use. `GIZ_VERSION=VERSION` selects a version for one command. | | Use an editor | Editors start `giz lsp` as their language server. The VS Code extension is in the [command-line guide](guides/command-line.md#vs-code). | | Uninstall | Remove `~/.giz` (or your `GIZ_INSTALL` folder) and the line `. "$HOME/.gizstack/env"` from your shell profiles. | A version never changes after it is published. Each file of a release has a SHA-256 in an envelope signed with Ed25519, and the third-party notices are in `share/licenses/`. Gizlang is open source under the MIT [license](https://gizstack.com/license), so CI, containers and agents run the same command as a person. ## Next - [Tour](tour.md): each part of Gizlang in one short program. - [The language](guides/language.md): the language for a TypeScript, JavaScript or Go developer. - [A web app](guides/web.md): routes, forms, a database and tests in one native process. - [Deploy to GizStack](https://gizstack.com/docs/platform/deploy): put an app at its own address. # Gizlang language guide This guide is for developers who know TypeScript, JavaScript or Go. You do not need to know Gizlang. - Part 1 is a tutorial. In approximately 15 minutes, you write, run and test a small program. - Part 2 is a tour of the main features. Each section is short and has one example. Every example on this page was compiled and run with the Gizlang toolchain. The output under each example is the real output. # Tutorial: your first Gizlang program In this tutorial, you write a small shopping cart program. It calculates a total and refuses a payment that is over a budget. Then you add tests. ## Before you start You need the `giz` command. To get it, refer to the [installation guide](../start.md). To try a program without an installation, use the [Playground](https://gizstack.com/play). ## Step 1: Hello, world Make a file with the name `hello.giz`: ```gizlang function main(): void { print("Hello, Gizlang."); } ``` Run it: ```sh giz hello.giz ``` ```text Hello, Gizlang. ``` - `main` is the entry point. - `: void` tells that the function gives no result. - `print` writes a line to the console. ## Step 2: A function and a type Make a file with the name `cart.giz`: ```gizlang type Item = { name: string; price: i64; qty: i64; }; function lineTotal(item: Item): i64 { return item.price * item.qty; } function main(): void { const pen: Item = { name: "pen", price: 3, qty: 2 }; print(pen.name + ": " + string(lineTotal(pen))); } ``` ```sh giz cart.giz ``` ```text pen: 6 ``` - `type Item = { ... }` declares a record. A record is a value with fixed fields. - `i64` is a 64-bit integer. - Each parameter and each result has a type. - `string(x)` converts a number to text. Gizlang does not convert types automatically. `"a" + 1` is a compile error. ## Step 3: A list of items Replace `main` and add `cartTotal`: ```gizlang type Item = { name: string; price: i64; qty: i64; }; function lineTotal(item: Item): i64 { return item.price * item.qty; } function cartTotal(items: Vec): i64 { let sum = 0; for (const item of items) { sum += lineTotal(item); } return sum; } function main(): void { let items: Vec = [{ name: "pen", price: 3, qty: 2 }]; items.push({ name: "pad", price: 5, qty: 1 }); print(string(items.length) + " items, total " + string(cartTotal(items))); } ``` ```text 2 items, total 11 ``` - `Vec` is a list of `Item` values. You can also write `Item[]`. - `let` declares a variable that you can change. `const` declares a value that you cannot change. - `items.push(x)` adds `x` to a `let` list in place. ## Step 4: Errors A function that can fail says so in its signature: `throws` and the type of the error. Add a `checkout` function and a `report` function: ```gizlang import {Result} from "gizlang:core"; type Item = { name: string; price: i64; qty: i64; }; function lineTotal(item: Item): i64 { return item.price * item.qty; } function cartTotal(items: Vec): i64 { let sum = 0; for (const item of items) { sum += lineTotal(item); } return sum; } function checkout(items: Vec, budget: i64): i64 throws string { const total = cartTotal(items); if (total > budget) { throw "over budget by " + string(total - budget); } return budget - total; } function report(items: Vec, budget: i64): void { try { const left = checkout(items, budget); print("paid, " + string(left) + " left"); } catch (message) { print("refused: " + message); } } function main(): void { const items: Vec = [ { name: "pen", price: 3, qty: 2 }, { name: "pad", price: 5, qty: 1 }, ]; report(items, 20); report(items, 10); } ``` ```text output paid, 9 left refused: over budget by 1 ``` - `: i64 throws string` means the function gives an `i64` or fails with a `string`. `throw` fails it. - A call gives the `i64`. When the call fails, the `try` block goes to its `catch`. A function that does not catch the failure must be a `throws` function itself, so the failure goes on to its caller. - Under the signature, the function returns a `Result`. A place that expects a `Result` takes it as it is, and `match` reads its cases. A `match` must write one arm for each case. ## Step 5: Tests A test is a function with a name that starts with `test`. It has no parameters and the result `void`. 1. Add the import of `gizlang:test` as line 2 of `cart.giz`, and two test functions at the end. The file is now: ```gizlang import {Result} from "gizlang:core"; import * as Test from "gizlang:test"; type Item = { name: string; price: i64; qty: i64; }; function lineTotal(item: Item): i64 { return item.price * item.qty; } function cartTotal(items: Vec): i64 { let sum = 0; for (const item of items) { sum += lineTotal(item); } return sum; } function checkout(items: Vec, budget: i64): i64 throws string { const total = cartTotal(items); if (total > budget) { throw "over budget by " + string(total - budget); } return budget - total; } function report(items: Vec, budget: i64): void { try { const left = checkout(items, budget); print("paid, " + string(left) + " left"); } catch (message) { print("refused: " + message); } } function main(): void { const items: Vec = [ { name: "pen", price: 3, qty: 2 }, { name: "pad", price: 5, qty: 1 }, ]; report(items, 20); report(items, 10); } function testCartTotal(): void { const items: Vec = [{ name: "pen", price: 3, qty: 4 }, { name: "pad", price: 5, qty: 1 }]; Test.equal(cartTotal(items), 17); } function testCheckoutRefusesOverBudget(): void { const items: Vec = [{ name: "pen", price: 3, qty: 2 }]; const refused: Result = Result.err("over budget by 2"); const answer: Result = checkout(items, 4); Test.equal(answer, refused); } ``` 2. Run the tests: ```sh giz test cart.giz ``` ```text 0 cached, 2 run, 0 failed ``` 3. Run the tests again: ```text 2 cached, 0 run, 0 failed ``` The runner records each pass. It runs a test again only when the code that the test uses changes. To see a failure, change `17` to `18` and run the tests: ```text fail testCartTotal R004 gizlang: R004 in equal line 2 (actual = 17, expected = 18) <- testCartTotal (cart.giz:44) gizlang: replay: function testReplayEqual(): void { equal(17, 18); } test runner failed or returned an incomplete verdict (1/1) 1 cached, 1 run, 2 failed ``` - The failure shows the actual and expected values, and the line of the test. - The `replay` line is a test that repeats the failed call. - The command exits with status 1. Change `18` back to `17`. ## Step 6: Run and check - `giz cart.giz` builds and runs the program. - `giz test cart.giz` runs the tests of the file. - `giz check cart.giz` checks the file for errors without a run of `main`. When the check passes, it also runs the tests that changed. You wrote a program with a record type, a list, a loop, error handling and tests. The tour that follows shows more of the language. --- # Language tour ## Values and types ```gizlang function main(): void { const count: i64 = 7; const ratio: f64 = 0.5; const big: u64 = 18446744073709551615u; const ready: bool = true; const name: string = "Gizlang"; const raw: bytes = b"GIZ"; print(string(count / 2)); print(string(f64(count) * ratio)); print(string(big)); print(string(ready)); print(name + " has " + string(name.length) + " bytes"); print(string(raw.length)); print(string("한글".length)); } ``` ```text 3 3.5 18446744073709551615 true Gizlang has 7 bytes 3 6 ``` | Type | Meaning | | --- | --- | | `i64`, `u64` | Checked 64-bit integers. Write a `u64` literal with the suffix `u`. | | `f64` | A 64-bit IEEE float. `number` is the same type. | | `bool` | `true` or `false`. `boolean` is the same type. | | `string` | UTF-8 text. `.length` and indexes count bytes, not characters. | | `bytes` | Immutable bytes. Write a literal as `b"..."`. | - Integer `/` truncates. - There are no implicit conversions. Use `i64(x)`, `u64(x)`, `f64(x)` and `string(x)`. - Integer overflow, division by zero and a bad index are faults. A fault stops the program. It is not a value. ```gizlang function double(x: i64): i64 { return x * 2; } function main(): void { print(string(double(9223372036854775807))); } ``` ```text gizlang: R003 in double line 2 (x = 9223372036854775807) <- main line 2 gizlang: replay: function testReplayDouble(): void { double(9223372036854775807); } runtime R003 ``` The fault report names the function, its arguments and the call path. The program exits with status 1. ## Variables ```gizlang const LIMIT = 3; function main(): void { const greeting = "hi"; let total = 0; total += LIMIT; total = total * 10; let words: Vec = []; words.push(greeting); words.push("there"); print(string(total) + " " + string(words.length)); } ``` ```text 30 2 ``` - `const` declares an immutable binding. `let` declares a mutable binding. - A module-level `const` must be a literal. Module-level `let` is not available. - You cannot assign to a `const`: ```gizlang error=E_IMMUTABLE function main(): void { const total = 1; total = 2; print(string(total)); } ``` ```sh giz check const_error.giz ``` ```text const_error.giz:3:5: error E_IMMUTABLE in main: cannot assign to, or pass as mut, a const binding or an immutable value: declare it with let total = 2; ^^^^^ ``` - An inner scope cannot declare a name again: ```gizlang error=E_SHADOW function main(): void { const total = 1; if (total > 0) { const total = 5; print(string(total)); } } ``` ```text shadow.giz:4:15: error E_SHADOW in main: a local cannot reuse a name already in scope; rename it const total = 5; ^^^^^ ``` ## Functions and closures ```gizlang function add(a: i64, b: i64): i64 { return a + b; } function apply(f: (x: i64) => i64, value: i64): i64 { return f(value); } function makeAdder(n: i64): (x: i64) => i64 { return (x: i64): i64 => { return x + n; }; } function main(): void { const addTen = makeAdder(10); const double = (x: i64): i64 => { return x * 2; }; print(string(add(2, 3))); print(string(apply(addTen, 5))); print(string(apply(double, 5))); const steps: Vec<(x: i64) => i64> = [addTen, double]; let value = 1; for (const step of steps) { value = step(value); } print(string(value)); print(string([1, 2, 3].map(double).length)); } ``` ```text 5 15 10 22 3 ``` - Declare a function with `function name(param: Type): Result`. - An arrow function `(x: T): R => { ... }` is a closure. - A function type `(x: T) => R` is a value type. A parameter, a result, a field or a `Vec` element can have it. - A closure copies the values that it captures. It cannot capture a `let` local: ```gizlang function main(): void { let count = 0; const next = (): i64 => { return count + 1; }; print(string(next())); } ``` ```text capture.giz:3:38: error E_ESCAPE in main: a closure cannot capture a mutable local or a view `count`: bind a const copy before the closure (const copy = count;) and use that const next = (): i64 => { return count + 1; }; ^^^^^ ``` ## Records and unions ```gizlang type Point = { x: i64; y: i64; }; type Shape = | { kind: "circle"; radius: f64; } | { kind: "rect"; width: f64; height: f64; }; function Point.add(self: Point, other: Point): Point { return { x: self.x + other.x, y: self.y + other.y }; } function moveRight(p: mut Point, dx: i64): void { p.x += dx; } function area(shape: Shape): f64 { match (shape) { Shape.circle(radius) => { return 3.14159 * radius * radius; } Shape.rect(width, height) => { return width * height; } } } function main(): void { const a: Point = { x: 1, y: 2 }; let b = a; b.x = 10; moveRight(b, 5); const c = a + b; const d = { ...c, y: 0 }; print(string(a.x) + " " + string(b.x) + " " + string(c.x) + "," + string(c.y) + " " + string(d.y)); const same: Point = { x: 1, y: 2 }; print(string(a === same)); const shapes: Vec = [Shape.circle(1.0), { kind: "rect", width: 2.0, height: 3.0 }]; for (const s of shapes) { print(string(area(s))); } print(string(shapes[1].kind === "rect")); } ``` ```text 1 15 16,4 0 true 3.14159 6 true ``` - Records are values. `let b = a` copies `a`. A change to `b` does not change `a`. - A `let` local changes in place at any field path, for example `b.x = 10`. - A function changes a value of its caller only through a `mut` parameter. - `function Point.add(self: Point, ...)` declares a method. Call it as `a.add(b)`. - An operator on a record is its method of that name: `a + b` is `a.add(b)`. The others are `- * /` (sub, mul, div), `< <= > >=` (lt, le, gt, ge) and unary `-` (neg). - `{ ...c, y: 0 }` copies `c` and replaces one field. The spread source must be a name, a field or an element, not a call. - A union is a set of cases. The string field `kind` names the case. Make a case with `Shape.circle(1.0)` or with an object literal that has an expected type. - `===` compares records by their fields. - There are no classes and no inheritance. ## Interfaces and generics ```gizlang import * as Text from "gizlang:text"; interface Named { label(self: Self): string; } type User = { name: string; }; type Team = { name: string; size: i64; }; function User.label(self: User): string { return "user " + self.name; } function Team.label(self: Team): string { return "team " + self.name + " (" + string(self.size) + ")"; } function shout(item: T): string { return item.label().toUpperCase(); } type Box = { value: T; }; function main(): void { const u: User = { name: "ana" }; const t: Team = { name: "core", size: 4 }; const all: Vec = [u, t]; for (const n of all) { print(n.label()); } print(shout(t)); const b: Box = { value: "boxed" }; print(b.value); } ``` ```text user ana team core (4) TEAM CORE (4) boxed ``` - An interface is a set of methods. Each method has `self: Self` as its first parameter. - A type implements an interface when it has those methods. You do not declare `implements`. - Use an interface as a bound (``) or as a type (`Vec`). - The compiler specializes generics for each type argument. - String methods such as `toUpperCase` come from `gizlang:text`, which every program has. ## Collections ```gizlang function main(): void { const nums: Vec = [5, 3, 8, 1]; const big = nums.filter(n => n > 2); const total = nums.reduce((acc, n) => acc + n, 0); const order = nums.toSorted((a, b) => a - b); print(`${big.length} ${total} ${order[0]}`); print(`${nums.findIndex(n => n === 8)} ${nums.includes(3)} ${nums.at(-1) ?? 0}`); print(string(nums.slice(1, 3).length)); const words = "the cat saw the dog".split(" "); const counts = new Map(); for (const w of words) { counts.merge(w, 1, (a, b) => a + b); } print(`${counts.get("the") ?? 0} ${counts.get("cow") ?? 0}`); print(counts.keys().join(",")); const seen = new Set(); seen.add("a"); seen.add("a"); print(`${seen.size} ${seen.has("a")}`); } ``` ```text 3 17 1 2 true 1 2 2 0 the,cat,saw,dog 1 true ``` - `Vec` is a value. `v.map(f)`, `v.slice(start, end)`, `v.length` and `v.push(x)` are built in. - `filter`, `find`, `findIndex`, `some`, `every`, `reduce`, `includes`, `indexOf`, `at`, `concat`, `sorted` and `count` come from `gizlang:collections`, which every program has: call them as methods with no import. - `findIndex` and `indexOf` give -1 when no element matches. `find` and `at` give an optional value. - `new Map()` and `new Set()` make a map and a set. They keep insertion order. - `m.get(k)` gives an optional value. `m.get(k) ?? fallback` reads it with a default. ## Control flow ```gizlang function label(n: i64): string { if (n % 15 === 0) { return "FizzBuzz"; } else if (n % 3 === 0) { return "Fizz"; } else if (n % 5 === 0) { return "Buzz"; } return string(n); } function main(): void { let line = ""; for (const i of range(1, 16)) { if (i === 8) { continue; } line = line + label(i) + " "; } print(line); let n = 27; let steps = 0; while (n !== 1) { let next = n / 2; if (n % 2 === 1) { next = 3 * n + 1; } n = next; steps += 1; if (steps > 1000) { break; } } print("steps: " + string(steps)); } ``` ```text 1 2 Fizz 4 Buzz Fizz 7 Fizz Buzz 11 Fizz 13 14 FizzBuzz steps: 111 ``` - `if`, `else if`, `while`, `break` and `continue` work as in TypeScript. - `for (const i of range(lo, hi))` counts from `lo` to `hi - 1`. - `for (const x of v)` reads each element of a `Vec`. - `match` reads a union. Refer to [Records and unions](#records-and-unions). - The `? :` operator and `switch` are not available. Use `if` and `match`. ## Errors: Result, Option and try ```gizlang import {Result, Option} from "gizlang:core"; import {parseI64, IntegerParse} from "gizlang:text"; function parseAge(text: string): Result { match (parseI64(text)) { IntegerParse.ok(n) => { if (n < 0) { return Result.err("negative: " + text); } return Result.ok(n); } IntegerParse.invalid(at) => { return Result.err("not a number: " + text); } IntegerParse.overflow(at) => { return Result.err("too large: " + text); } } } function ageNextYear(text: string): Result { const age = try parseAge(text); return Result.ok(age + 1); } function firstLong(words: Vec): Option { for (const w of words) { if (w.length > 3) { return Option.some(w); } } return Option.none(); } function main(): void { for (const input of ["41", "-2", "abc"]) { match (ageNextYear(input)) { Result.ok(n) => { print("next year: " + string(n)); } Result.err(e) => { print("error: " + e); } } } print(string(ageNextYear("x").valueOr(0))); print(firstLong(["a", "tree"]) ?? "none"); print(firstLong(["a"]) ?? "none"); print(firstLong(["house"])!); } ``` ```text next year: 42 error: negative: -2 error: not a number: abc 0 tree none house ``` - `Result` has the cases `ok(value)` and `err(error)`. `Option` has the cases `some(value)` and `none()`. Import them from `gizlang:core`. - `try e` gives the value of `ok` or `some`. For any other case, `try` returns that case from the function. The result type of the function must have the same case. - `result.valueOr(fallback)` and `option ?? fallback` give a default. - `x!` gives the value. When there is no value, it faults. - A match arm binds the whole payload of its case. `IntegerParse.invalid` has a payload (the byte position), so the arm is `IntegerParse.invalid(at)`. - `throw` and `try`-`catch` work with `throws`: `function f(): T throws E` returns a `Result` that its callers take as `T` (see [Step 4: Errors](#step-4-errors)). `finally` runs when the statement ends. ## Modules and imports A file is a module. Use `export` to make a declaration visible to other modules. Make a folder with these three files. `geometry.giz`: ```gizlang title="geometry.giz" /** A point on a grid. */ export type Point = { x: i64; y: i64; }; /** The Manhattan distance between two points. */ export function distance(a: Point, b: Point): i64 { return abs(a.x - b.x) + abs(a.y - b.y); } function abs(n: i64): i64 { if (n < 0) { return -n; } return n; } ``` `main.giz`: ```gizlang title="main.giz" import {Point, distance} from "./geometry.giz"; import * as Text from "gizlang:text"; function main(): void { const a: Point = { x: 0, y: 0 }; const b: Point = { x: 3, y: -4 }; const parts = [string(distance(a, b)).padStart(4, "."), "steps"]; print(Text.join(parts, " ")); } ``` ```sh giz main.giz ``` ```text output ...7 steps ``` `geometry.test.giz`: ```gizlang title="geometry.test.giz" import {Point, distance} from "./geometry.giz"; import * as Test from "gizlang:test"; function testDistance(): void { const a: Point = { x: 1, y: 1 }; const b: Point = { x: 4, y: 5 }; Test.equal(distance(a, b), 7); Test.equal(distance(b, a), 7); } ``` ```sh giz test geometry.test.giz ``` ```text 0 cached, 1 run, 0 failed ``` - Import a file of your project by a relative path: `"./geometry.giz"`. - Import an SDK module by name: `"gizlang:text"`, `"gizlang:collections"`, `"gizlang:core"`. - Named imports (`{a, b}`), namespace imports (`* as Text`) and aliases work as in TypeScript. - `abs` has no `export`. Other modules cannot import it. - Put the tests of `X.giz` in `X.test.giz` beside it. `giz check X.giz` also runs them. - `/** ... */` before a declaration is a doc comment. Tools read it. ## Packages A package is a folder with a `package.json`. The manifest names the entry and the modules, and pins the sha256 of each source. Use the same `geometry.giz` and this `main.giz`: ```gizlang error=E_MODULE import {Point, distance} from "geometry"; function main(): void { const a: Point = { x: 0, y: 0 }; const b: Point = { x: 2, y: 5 }; print("distance: " + string(distance(a, b))); } ``` `package.json`: ```json { "name": "shapes", "private": true, "gizlang": { "entry": { "path": "main.giz", "sha256": "" }, "modules": { "geometry": { "path": "geometry.giz", "sha256": "" } } } } ``` Write the pins, then run the package: ```sh giz pin . giz package.json ``` ```text distance: 7 ``` - `giz pin DIR` writes the sha256 of each source into `package.json`. - Inside a package, a module imports another module by its manifest name: `"geometry"`. - When a source changes after the pin, the package does not build. `giz get --frozen DIR` names the file: ```sh giz get --frozen . ``` ```text gizlang: geometry.giz is missing or does not match its sha256 ``` Run `giz pin .` again after each intended change. For dependencies and registries, refer to the [package guide](packages.md). ## Tasks and channels Gizlang has no `async` or `await`. A task is a closure that runs concurrently. `sleep`, `join` and I/O suspend only the calling task. ```gizlang import * as Task from "gizlang:task"; import {Result, Option} from "gizlang:core"; function fib(n: i64): i64 { if (n < 2) { return n; } return fib(n - 1) + fib(n - 2); } function main(): void { const slow = Task.spawn(() => { Task.sleep(20); return fib(20); }); const fast = Task.spawn(() => { return fib(10); }); match (Task.join(fast)) { Result.ok(n) => { print("fast: " + string(n)); } Result.err(f) => { print("fast failed: " + string(f.code)); } } match (Task.join(slow)) { Result.ok(n) => { print("slow: " + string(n)); } Result.err(f) => { print("slow failed: " + string(f.code)); } } const lines = Task.channel(4); Task.spawn(() => { for (const word of ["one", "two", "three"]) { Task.send(lines, word); } Task.close(lines); return 0; }); let more = true; while (more) { match (Task.receive(lines)) { Option.some(word) => { print("got " + word); } Option.none() => { more = false; } } } const squares = Task.parallel(4, (i: i64): i64 => { return i * i; }); print("squares: " + string(squares[3])); } ``` ```text fast: 55 slow: 6765 got one got two got three squares: 9 ``` - `Task.spawn(f)` starts a task. `Task.join(t)` waits for it and gives a `Result`. - `Task.channel(capacity)` makes a bounded queue. `send` waits while the queue is full. `receive` gives `Option.none()` when the queue is closed and empty. - `Task.parallel(n, body)` runs `body(i)` on the CPU threads and gives the results in index order. The body can only compute. It cannot do I/O or print. - A task cannot live longer than the task that started it. A fault in a task stops only that task. The parent gets it as `Result.err`: ```gizlang import * as Task from "gizlang:task"; import {Result} from "gizlang:core"; function divide(a: i64, b: i64): i64 { return a / b; } function main(): void { const bad = Task.spawn(() => { return divide(1, 0); }); match (Task.join(bad)) { Result.ok(n) => { print("value: " + string(n)); } Result.err(f) => { print("task fault: " + string(f.code)); } } print("main continues"); } ``` ```text task fault: 2 main continues ``` Fault 2 is division by zero. ## Files and the network A program gets access to the outside world through one value: `main(sys: System)`. Library code cannot reach files, the network, the clock or the environment unless the program gives it the `System` value or a value made from it. ```gizlang import {System, IoError} from "gizlang:system"; import {Result} from "gizlang:core"; function saveAndCount(sys: System, name: string): Result { const dir = try sys.files(); try dir.writeFile(name, utf8("line one\nline two\n")); const data = try dir.readFile(name); const text = string(data); try dir.remove(name); return Result.ok(text.split("\n").length - 1); } function main(sys: System): void { const args = sys.arguments(); print("arguments: " + string(args.length)); match (saveAndCount(sys, "notes.txt")) { Result.ok(lines) => { print("lines: " + string(lines)); } Result.err(e) => { print("io error: " + e.message()); } } match (sys.files()) { Result.ok(dir) => { match (dir.readFile("missing.txt")) { Result.ok(data) => { print("read " + string(data.length)); } Result.err(e) => { print("io error: " + e.message()); } } } Result.err(e) => { print("io error: " + e.message()); } } } ``` ```sh giz files.giz alpha beta ``` ```text arguments: 2 lines: 2 io error: no such file or directory ``` - `sys.files()` gives the working directory as a `Dir`. A `Dir` opens only paths below it: no `..`, no absolute paths. - `readFile` gives `bytes`. `string(data)` converts valid UTF-8 to a `string`. `utf8(text)` converts a `string` to `bytes`. - Each operation gives a `Result`. `e.message()` gives the text of the error. - `sys.arguments()` gives the command-line arguments after the file name. A TCP server and a client in one program: ```gizlang import {System, IoError} from "gizlang:system"; import {Result} from "gizlang:core"; import * as Task from "gizlang:task"; function serveOne(sys: System): Result { const listener = try sys.listen("127.0.0.1", 0); const port = listener.port(); const client = Task.spawn(() => { match (sys.connect("127.0.0.1", port)) { Result.ok(stream) => { stream.write(utf8("ping")); stream.close(); } Result.err(e) => { print("connect failed: " + e.message()); } } return 0; }); const conn = try listener.accept(); const data = try conn.readAll(); conn.close(); listener.close(); Task.join(client); return Result.ok(string(data)); } function main(sys: System): void { match (serveOne(sys)) { Result.ok(text) => { print("server got: " + text); } Result.err(e) => { print("io error: " + e.message()); } } } ``` ```text server got: ping ``` - `sys.listen(host, 0)` selects a free port. `listener.port()` gives it. - `sys.connect(host, port)` opens a TCP `Stream`. - `accept` and `readAll` suspend only the calling task. - For HTTP, use `gizlang:http`. For TLS, use `gizlang:tls`. ## Markup Markup is TSX in ordinary `.giz` files. It compiles to an `Html` value from `gizlang:html`. Text in holes is escaped. ```gizlang import {Html, each, when} from "gizlang:html"; type Todo = { id: i64; title: string; done: bool; }; type ListProps = { title: string; }; function Row(t: Todo): Html { return
  • {t.title}{when(t.done, () => (done))}
  • ; } function TodoList(p: ListProps, children: Html): Html { return

    {p.title}

    {children}
    ; } function main(): void { const todos: Vec = [ { id: 1, title: "Write ", done: true }, { id: 2, title: "Ship", done: false }, ]; const page =
      {each(todos, (t: Todo) => Row(t))}
    ; print(page.text()); } ``` ```text

    Today

    • Write <docs> (done)
    • Ship
    ``` - A lowercase tag writes an HTML element. - A tag that starts with an uppercase letter calls a component function. The attributes are one record argument. The children are one `Html` argument. - Markup has no spread, `&&`, `? :` or `.map`. Use `when(cond, f)`, `each(items, f)` and `raw(trustedText)`. - `page.text()` gives the HTML text. To serve pages, routes and static files, use Gizlang Web (`gizlang:http`). Refer to the [Gizlang Web guide](web.md). ## What differs from TypeScript Gizlang uses the spelling of TypeScript. A TypeScript feature that Gizlang does not have is a compile error. It never gets a different meaning. ```gizlang type Order = { id: string; qty: i64; }; function bump(o: mut Order): void { o.qty += 1; } function main(): void { const n = 3; let size = "small"; if (n > 2) { size = "big"; } print("n=" + string(n) + " is " + size); const first: Order = { id: "a1", qty: 1 }; let second = first; bump(second); print(string(first.qty) + " " + string(second.qty)); print(string(7 / 2) + " " + string(7.0 / 2.0)); } ``` ```text n=3 is big 1 2 3 3.5 ``` | TypeScript | Gizlang | | --- | --- | | `number` | `i64` or `f64`. `number` is `f64`. Integer `/` truncates. | | Implicit conversion, `"n=" + n` | `"n=" + string(n)`, or a template string `` `n=${n}` ``. | | `null`, `undefined`, `?.`, `??`, optional fields | The same, on `Option`: `x?: T` and `T \| undefined` are `Option`. | | `throw`, `try`-`catch` | The same, with `throws E` on the function: `function f(): T throws E`. A call to it gives the `T` and a failure returns from the caller. `finally` runs when the statement ends. | | `class`, inheritance | Not available. Use records, methods (`function T.m(self: T)`) and interfaces. | | `+`, `<` and the other operators on objects | An operator on a record calls its method: `a + b` is `a.add(b)`. | | Objects shared by reference | Records, `Vec`, strings and bytes are values. Use a `mut` parameter to change a value of the caller. | | `async`, `await` | Not available. Use `gizlang:task`. | | `enum` | Not available. Use a union. | | Array, `Map`, `Set` and string methods | The same names, with no import (`v.filter(f)`, `new Map()`). `v.sort` takes a `before` function: `(a, b) => a < b`. | | `any`, untyped values, `eval` | Compile errors. | | Module-level `let` | Not available. | | A name declared again in an inner scope | Compile error `E_SHADOW`. | | A closure that changes an outer `let` | Compile error `E_ESCAPE`. | ## Next steps - Use the [Playground](https://gizstack.com/play) to run programs without an installation. - Read the [package guide](packages.md) for dependencies and pins. - Read the [Gizlang Web guide](web.md) to serve a site. # Use the Gizlang command line This guide shows the daily work with the `giz` command: make a project, run it, check it, test it, format it, build an executable, update it live, and use packages. It also shows the editor setup and the machine-readable output for AI agents. - This guide is task-oriented. The command reference gives each option. - Each example shows real output from the current toolchain. Long output is trimmed (`…`). - To install the toolchain, refer to the [installation guide](../start.md). ## Before you start Run `giz help`. It prints the list of commands and exits with status 0. `giz` with no arguments does the same. ```sh giz help giz help check ``` ```text usage: giz COMMAND [ARGUMENT...] giz run [--record LOG] [--replay LOG] FILE [ARGUMENT...] Build FILE (cached) and run it with the arguments. giz check [--json] FILE Check FILE (`.giz`, `.gizgraph` or `package.json`). … ``` - `giz help COMMAND` and `giz COMMAND --help` print the flags, examples and exit codes of one command. - An unknown command prints `unknown command NAME` on stderr and exits with status 2. - `giz --version` (or `giz version`) prints the identity of the toolchain. `giz capabilities` gives it as JSON ([agents](#identify-the-toolchain)). - `source <(giz completion bash)` loads the bash completion. - `giz doc NAME` prints the docs of a module, a declaration or a command, for example `giz doc Json.get`. ## Make a project `giz init NAME` makes a folder with `main.giz`, `main.test.giz` and a pinned `package.json`. You can also make the files yourself, as this section shows. ```sh giz init hello ``` ### A program of one or more files A program is a `.giz` file with a `main` function. Relative imports bring in more files. No manifest is necessary. Make a folder `hello` with these three files. `main.giz`: ```gizlang title="main.giz" import {System} from "gizlang:system"; import {greeting} from "./greet.giz"; function main(sys: System): void { const args = sys.arguments(); let name = "world"; if (args.length > 0) { name = args[0]; } print(greeting(name)); } ``` `greet.giz`: ```gizlang title="greet.giz" export function greeting(name: string): string { return "Hello, " + name + "."; } ``` `greet.test.giz` (the tests of `greet.giz`): ```gizlang title="greet.test.giz" import {equal} from "gizlang:test"; import {greeting} from "./greet.giz"; function testGreeting(): void { equal(greeting("Ada"), "Hello, Ada."); } ``` Run the program: ```sh cd hello giz main.giz Ada ``` ```text Hello, Ada. ``` The first run compiles the program. The next runs use the cache: in this test, the first run took 0.66 s and the second run took 0.08 s. ### A package project Use a `package.json` manifest when you want pinned sources, dependencies or publication. The [next section](#the-project-manifest) shows the manifest. The Playground also exports a project of this type ([start](../start.md)). ## The project manifest The `gizlang` section of `package.json` declares the package. It is the same contract that the compiler uses ([packages](packages.md)). ```json { "name": "geo", "version": "1.0.0", "gizlang": { "entry": { "path": "main.giz" }, "modules": { "shapes": { "path": "shapes.giz" } } }, "license": "MIT" } ``` | Key | Meaning | | --- | --- | | `name`, `version` | The package identity. `giz publish` uses them. | | `gizlang.version` | The toolchain release that runs the project; `giz` installs and runs it in the folder and below ([install](../start.md#keep-the-toolchain-current)). `giz init` from a release writes it. | | `gizlang.entry` | The entry source. Necessary. Its `sha256` pin is optional (see below). | | `gizlang.modules` | The other sources, by module name, each with an optional pin. Use `{}` when there are no other sources. | | `gizlang.requires` | The lowest registry versions that the package needs, for example `{"geo": "1.0.0"}`. `giz get` writes it. | | `gizlang.dependencies` | Package folders inside this package (`vendor/NAME`), each pinned by the sha256 of its `package.json`. `giz get` writes it. | | `native` (on an entry or module) | The C libraries that the source can declare, for example `"native": ["m"]`. | A module imports another module of its package by name (`"shapes"`), or a module of a dependency as `"geo:shapes"`. ### Pin the sources A source of your package needs no pin: the compiler reads it from your folder. A source that has a sha256 pin must match it. When you change a pinned source, its pin becomes stale, and the package does not compile: ```text app/3:252: error E_PACKAGE_INTEGRITY: package manifest or pin problem ``` To refresh the pins, run `giz get DIR`. For a package without `requires`, it only writes the pins (and runs npm when the package has npm dependencies). A package that you publish needs its pins, because it becomes a dependency of other packages, and every source of a dependency is pinned. ```sh giz get geo ``` The command prints nothing and exits with status 0. Then `geo/package.json` contains the pins: ```json "entry": { "path": "main.giz", "sha256": "d4f817e71aebdbbe930308588ec05fe6c5ad2cbcb4d2b47033d55681dc10a611" }, ``` For a package with `requires`, `giz get DIR` also resolves the requirements from the registry. Use `giz pin DIR` to refresh only the pins. `giz pin DIR` refreshes the pins of `DIR/package.json`, and of the manifests in its dependency folders first. ## Run a program | Task | Command | | --- | --- | | Run a file | `giz main.giz ARG...` | | Run a file (the same, explicit) | `giz run main.giz ARG...` | | Run a package | `giz package.json ARG...` | | Record the inputs of a run | `giz run --record run.log main.giz ARG...` | | Run again from the record | `giz run --replay run.log main.giz` | ```sh giz run main.giz Bob ``` ```text Hello, Bob. ``` A package runs from its manifest: ```sh giz geo/package.json ``` ```text 5 ``` When `main` returns a value, the CLI prints it. Here `main` returns `distance(Point(3.0, 4.0))`. ### Scripts A `.giz` file can start with `#!/usr/bin/env giz`. Make it executable, and run it directly. `giz` must be on `PATH`. ```sh printf '#!/usr/bin/env giz\nfunction main(): void {\n print("Hello from a script.");\n}\n' > script.giz chmod +x script.giz ./script.giz ``` ```text Hello from a script. ``` ### Record and replay `--record LOG` writes the inputs of the run (arguments and other inputs) to LOG. `--replay LOG` runs the program again with those inputs. ```sh giz run --record run.log main.giz Zed giz run --replay run.log main.giz ``` ```text Hello, Zed. Hello, Zed. ``` ## Check a program `giz check FILE` checks the types and effects of FILE. FILE is a `.giz` file, a `.gizgraph` file or a `package.json`. Then the command runs the tests of FILE and of each `X.test.giz` file beside its sources. It runs only the tests whose meaning changed since they passed. ```sh giz check main.giz ``` ```text 0 cached, 1 run, 0 failed ``` An error shows its file, line, column, code and source line. The exit status is 1. ```sh giz check bad.giz ``` ```text bad.giz:2:11: error E_TYPE in main: type mismatch: expected i64, got string const n: i64 = "text"; ^ ``` | Exit status | Meaning | | --- | --- | | 0 | The source checks, and its tests pass. | | 1 | An error or a failed test. | | 2 | A usage error (for example, no FILE). | For JSON diagnostics without tests, use `giz check --json FILE` ([agents](#source-diagnostics-as-json)). ## Test a program A test is a function `testName(): void` with no parameters. It passes when it returns without a fault. The asserts are in `gizlang:test` (`equal`, `differ`, `check`, `includes`, `equalLines`, `equalItems`). Run the tests of one file: ```sh giz test greet.test.giz ``` ```text 0 cached, 1 run, 0 failed ``` Run them again. A test that passed at the same meaning does not run again: ```text 1 cached, 0 run, 0 failed ``` The meaning of a test does not include names, comments or layout. Thus a rename or a comment causes no test to run again. A failed assert shows the values, the line of the test, and a replay test: ```text fail testAdd R004 gizlang: R004 in equal line 2 (actual = 4, expected = 5) <- testAdd (fail.test.giz:4) gizlang: replay: function testReplayEqual(): void { equal(4, 5); } test runner failed or returned an incomplete verdict (1/1) 0 cached, 1 run, 2 failed ``` - `giz test FILE` runs only the tests in FILE. `giz test greet.giz` does not run `greet.test.giz`. Use `giz check greet.giz` to run both. - `giz test package.json` runs the tests of the package sources. - `giz test` with no argument runs every `*.test.giz` file under the current folder, outside hidden folders and `node_modules`. - At the top of a Gizlang checkout, `giz test` with no argument runs the toolchain's own cases and suites; there and below, a FOLDER, a FILTER or `--shard I/N` runs some of them ([the guide to the tests](../../tests/README.md)). ## Format source files `giz fmt FILE...` sets the indentation by bracket depth (4 spaces for each level) and the spaces between tokens. It writes a file only when the tokens stay the same. Thus it cannot change the meaning of a file. Before the layout, it rewrites old spellings, such as `join(v, s)` and `"n=" + string(n)`, to the modern `v.join(s)` and `` `n=${n}` ``, where the checker proves them the same, and removes unused imports. ```sh giz fmt --check messy.giz main.giz greet.giz ``` ```text messy.giz ``` `--check` lists the files that are not formatted, and exits with status 1. Without `--check`, the command formats the files and lists the changed files. ```sh giz fmt messy.giz ``` Before: ```gizlang function twice(x:i64):i64{ return x*2; } ``` After: ```gizlang function twice(x: i64): i64 { return x * 2; } ``` ## Build an executable `giz build FILE -o OUT` writes `OUT.licenses` beside the executable. Keep this file when you distribute the program. Each notice gives the component, its source or system package version, its SPDX identifier and its full license text. The link inputs select the components. A library outside those inputs is not listed. The runtime includes libuv. On Linux and macOS the program loads it from the toolchain's versions folder (`libgizlang_runtime.so` or `.dylib`, with OpenSSL), so a copy that runs on a machine without the toolchain is built with `GIZLANG_RUNTIME_STATIC=1`. The native driver also adds system inputs. The Wasm runtime has no third-party link inputs, so its notice file is empty. To print the native notices without building an executable, run: ```sh giz licenses main.giz ``` The text matches the native build's notice file on the same toolchain and system. The command emits link declarations and asks the driver for its link command. It does not run the linker. A missing component mapping, version or license text is an error. The toolchain holds the component mappings and their license texts. `giz build FILE` compiles FILE (a `.giz` file or a `package.json`) to a native executable for this CPU. It prints the path of the executable. ```sh giz build main.giz -o hello ./hello Eve ``` ```text hello Hello, Eve. ``` | Option | Result | | --- | --- | | (none) | An executable for any x86-64 or ARM64 CPU, in the build directory (`…/build/main`). A Linux program runs on glibc 2.35 and later. | | `-o PATH` | The executable at PATH. It may come before or after FILE. | | `--fast` | Link fast: -O0. The test runner builds its suites so, and every build inside a suite is linked so too. Do not use it for a program that you ship. | | `--release` | Link optimized (-O2, ThinLTO) inside a test run as well, for a suite that measures speed. | | `--store` | Build into the machine store only, with no output file. | | `--wasm` | A wasm64 module. | `giz run FILE` (and running a `.giz` file directly) is optimized without waiting: it runs the optimized program when the store has it; otherwise it runs a -O0 build at once and builds the optimized one in the background, which the next run takes. `run --release` waits for the optimized build; `run --fast` always takes -O0. - The build directory is `$GIZLANG_CACHE/build` for an installed toolchain ([environment variables](#environment-variables)). - No build is for the CPU of the machine that builds it, so you can copy an executable to another machine of the same architecture. On x86-64, each function that uses SIMD types also has copies for x86-64-v3 and v4, and runs the copy for the highest level that the CPU has. ### WebAssembly A wasm64 module gets only the console. A program that uses `System` (for example `sys.arguments()`) does not link as a module. ```sh giz build --wasm plain.giz -o plain.wasm giz wasm plain.giz ``` ```text plain.wasm Hello from wasm. ``` - `giz wasm FILE` runs the module in Node (`runtime/wasm.mjs`). An installed release does not have this host. It refuses the command and tells you to use `build --wasm`. ## Live updates `giz live FILE` runs `main`. When a `.giz` file or `package.json` in the folder of FILE changes, the command builds a new generation. It publishes that code when the code checks. With `--tests`, it also runs the tests first, also the `X.test.giz` files, and publishes the code only when they pass. The generation itself holds those tests: the command links no separate test program, and it runs them in a new process that loads the generation (`gizlang-live --run-tests`). A deployed host (`live --accept`, `deploy --live`) always tests a change. `tick.giz`: ```gizlang import * as Task from "gizlang:task"; function message(): string { return "version 1"; } function main(): void { let i = 0; while (i < 40) { print(message()); Task.sleep(500); i += 1; } } ``` ```sh giz live tick.giz ``` Change `"version 1"` to `"version 2"` in the file while the program runs. The output (status lines go to standard error): ```text gizlang: generation built: prepare 136 ms, emit 30 ms, tests 5 ms, link 1910 ms gizlang-live: generation 1 published (10 functions, loaded in 0 ms) version 1 version 1 … gizlang: generation built: prepare 9 ms, emit 16 ms, tests 7 ms, link 82 ms gizlang-live: generation 2 published (10 functions, loaded in 0 ms) version 2 version 2 … ``` - The loop of `main` keeps the code that it started with. Thus, put the work that you want to change in named functions. - When the new code does not check (or, with `--tests`, a test fails), the program continues with the old code: ```text tick.giz:4:5: error E_TYPE in message: type mismatch: expected string, got i64 return 7; ^^^^^^ gizlang-live: generation rejected; generation 1 keeps running ``` ### Deploy to a live host A deployed program runs under a live host too. Send it only the files that you changed. The process, its named state and its open connections stay. 1. On the server, run the program as a deployed host. KEYS is a file of the hex Ed25519 public keys that the host trusts, one on each line (`giz public-key SEED` prints one): ```sh giz live --accept 0.0.0.0:7070 --trusted /etc/app/keys \ --tls-chain /etc/app/chain.pem --tls-key /etc/app/key.pem app/main.giz ``` With `--tls-chain` and `--tls-key` (a PEM certificate chain and its key) the files travel over TLS. Without them they travel as plain HTTP, readable on the way: use that only on loopback or inside a tunnel. 2. In the folder of the program on your machine, send the changed files. `GIZLANG_SIGNING_KEY` is the file of your 32-byte seed: ```sh giz deploy --live https://server:7070 --json main.giz ``` ```text {"result":"published","generation":2,"error":"","ms":57} ``` The host writes the files into the folder of its program. Then it checks, tests and compiles them as for an edit, and publishes them into the running process. `ms` is the time from signing to the new code serving. The host refuses a unit, and the old code keeps serving, when: | Cause | Status | | --- | --- | | The unit has no signature, a key that the host does not trust, or a signature that does not verify. | 403 | | The unit was signed more than 5 minutes from the time of the host, or is older than the last unit taken. | 403 | | The compiler or runtime of the sender is not the compiler and runtime of the host. | 409 | | The unit does not check, or a test fails. The host takes its files out again. | 409 | The endpoint is plain HTTP. The signature authenticates each unit, but the source is not encrypted. Put a TLS proxy in front of the host when the network is not trusted. ## Dependencies and packages ### Registries The registry is `$GIZLANG_REGISTRY`. It is one of these: - a local folder of `NAME/VERSION` packages; - that folder served over HTTP or HTTPS. When `GIZLANG_REGISTRY` is not set, the registry is `https://gizstack.com/packages`. A version never changes after its publication. The examples below use a local folder registry: ```sh export GIZLANG_REGISTRY="$PWD/registry" ``` ### Publish a package `giz publish DIR` publishes the package in DIR under the `name` and `version` of its manifest. Pin the package first (`giz get DIR`). ```sh giz publish geo ``` ```text geo@1.0.0 ``` The optional second argument asserts the identity. A different identity fails: ```sh giz publish geo geo@2.0.0 ``` ```text gizlang: publish identity does not match package.json ``` A version that exists already fails: ```text gizlang: geo@1.0.0 is published already or publication failed; a version never changes ``` The local registry now has `registry/geo/1.0.0/` with `package.json`, `main.giz` and `shapes.giz`. Publication to an HTTPS registry needs `GIZLANG_REGISTRY_TOKEN`, `GIZLANG_REGISTRY_ORG` and `GIZLANG_REGISTRY_VISIBILITY=public` or `private`. Without them, the command stops with "set GIZLANG_REGISTRY_TOKEN, GIZLANG_REGISTRY_ORG and GIZLANG_REGISTRY_VISIBILITY=public|private to publish". Get the token at [gizstack.com/token](https://gizstack.com/token) after you sign in. ### Add a dependency Make a package `app` that uses `geo`. `app/main.giz`: ```gizlang error=E_MODULE import {Point, distance} from "geo:shapes"; function main(): void { print(string(distance(Point(6.0, 8.0)))); } ``` `app/package.json`: ```json { "name": "app", "private": true, "gizlang": { "entry": { "path": "main.giz", "sha256": "" }, "modules": {} } } ``` `giz get DIR NAME@VERSION` raises the requirement, puts the selected version in `DIR/vendor/`, and refreshes the pins. It prints each selected package. ```sh giz get app geo@1.0.0 giz app/package.json ``` ```text geo 1.0.0 10 ``` The manifest now has `requires` and `dependencies`: ```json "requires": { "geo": "1.0.0" }, "dependencies": { "geo": { "path": "vendor/geo", "sha256": "75009ea412cc175d868e6fd2280ea329ccf16084f5c063acd588a5d01d89fa4a" } } ``` - For each name, `get` selects the highest version that the requirement graph asks for (minimal version selection). The build changes only when a requirement changes. - A new major version is a new name. - Run `giz get DIR` again with no versions to restore the requirements and refresh the pins. This reads the registry. With the default registry, `get` stops with an error when the registry does not have a required package (for example `gizlang: package.json: registry returned HTTP 404`). ### Validate a package offline `giz get --frozen DIR` validates the pinned package closure that is on disk. It does not write files, select versions or use the network. Use it in CI and before a release. ```sh giz get --frozen app ``` ```text geo 1.0.0 ``` A changed dependency source fails: ```text gizlang: vendor/geo/shapes.giz is missing or does not match its sha256 ``` ### npm packages `giz get DIR npm:NAME@SPEC` runs `npm install --prefix DIR --save-exact --ignore-scripts` and records the package in the npm `dependencies` of `DIR/package.json`. Installation scripts never run. A program loads it on the VM in its own process: `Vm.javascript(sys).load("NAME")` ([SYSTEM](../internals/system.md#javascript-and-python)). ## The REPL `giz repl` reads, checks, runs and prints. Declarations stay in the session. Statements run after the earlier statements. The REPL prints the value of an expression. ```sh printf 'function square(x: i64): i64 { return x * x; }\nconst n = square(7);\nn + 1\n:quit\n' | giz repl ``` ```text Gizlang REPL. :show, :reset, :quit giz> giz> giz> 50 giz> ``` In a terminal, type the same lines after the `giz>` prompt. | Command | Effect | | --- | --- | | `:show` | Prints the program of the session. | | `:reset` | Starts a new session. | | `:quit` | Stops the REPL. | An entry continues on the next line while a bracket or a string is open. ## Editor setup `giz lsp` runs the language server on standard input and output (LSP 3.17). Editors use the same toolchain as the CLI. The server gives diagnostics on open and change, document symbols, hover with signatures and inferred effects, definitions, references, completion (the members of a value after `.`), signature help, formatting (as `giz fmt`), rename (checked by the compiler), semantic tokens, folding ranges and workspace symbols. Positions use UTF-16. A framed session with an `initialize` request and a `didOpen` of a file with a type error gave: ```text {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"positionEncoding":"utf-16","textDocumentSync":{"openClose":true,"change":1},"documentSymbolProvider":true,"hoverProvider":true,"definitionProvider":true,"referencesProvider":true,"completionProvider":{"triggerCharacters":["."]},"signatureHelpProvider":{"triggerCharacters":["(",","],"retriggerCharacters":[")"]},"documentFormattingProvider":true,"renameProvider":{"prepareProvider":true},"semanticTokensProvider":{"legend":{"tokenTypes":["type","enum","interface","function","method","variable"],"tokenModifiers":["declaration"]},"full":true},"foldingRangeProvider":true,"workspaceSymbolProvider":true,"inlayHintProvider":true,"codeActionProvider":{"codeActionKinds":["quickfix"]}},"serverInfo":{"name":"gizlang-lsp","version":"0.1"}}} {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///…/a.giz","version":1,"diagnostics":[{"range":{"start":{"line":1,"character":10},"end":{"line":1,"character":11}},"severity":1,"code":"E_TYPE","source":"gizlang","message":"E_TYPE in main"}]}} ``` ### VS Code :::status{of="lsp"} ::: The toolchain release carries the Gizlang extension. Install it, and again after each upgrade: ```sh giz vscode ``` This runs `code --install-extension` on the release's `share/gizlang/vscode/gizlang.vsix`. It starts `giz lsp` and adds the Test Explorer, debugging (see [Debugging](#debugging)), the `giz` task type and the Run, Debug, Check and Test File commands. Set `gizlang.path` to your `giz` when it is not on `PATH`. ### Neovim For Neovim 0.11 and later, put this in `init.lua`. The lspconfig plugin is not necessary. ```lua vim.filetype.add({ extension = { giz = 'gizlang' } }) vim.lsp.config('gizlang', { cmd = { '/absolute/path/to/gizlang', 'lsp' }, filetypes = { 'gizlang' }, root_markers = { 'package.json', '.git' }, }) vim.lsp.enable('gizlang') ``` ## Debugging `giz build --debug` builds a program for a debugger: unoptimized, with DWARF for its `.giz` lines, functions, arguments and locals. ```sh giz build --debug main.giz -o app gdb ./app ``` In gdb, `break main.giz:12` or `break total` stops there, `next` and `step` go by source line, `bt` shows the Gizlang functions and lines, and `info args`, `info locals` and `print` show values by their Gizlang names: ```text Breakpoint 1, show (n=4, label=...) at main.giz:8 (gdb) info locals p = {x = 4, y = 1.5} words = {data = 0x555555a60060, length = 4} flag = true (gdb) print *words.data@words.length $1 = {1, 2, 3, 4} ``` A `string`, `bytes` or `Vec` value is `{data, length}`. A record shows its fields. lldb reads the same information. In VS Code, the Gizlang extension debugs the open file with F5 or **Gizlang: Debug File**. It builds the file with `giz build --debug` and runs it under gdb 14 or later (`gdb -i=dap`), or under lldb-dap with `"debugger": "lldb-dap"` in `launch.json`. Breakpoints, stepping, the call stack and the variables work as in any other language: ```json { "type": "gizlang", "request": "launch", "name": "Debug Gizlang file", "program": "${file}", "args": [] } ``` ## Environment variables | Variable | Effect | | --- | --- | | `GIZLANG_CACHE` | The store of compiled units, test passes and executables. The default is `$XDG_CACHE_HOME/gizlang`, else `$HOME/.cache/gizlang`. One store serves all projects on the machine. Once an hour a command removes what nobody used for seven days, then the least recently used files past `GIZLANG_CACHE_GB` (default 150, at most half of its disk). | | `GIZLANG_REGISTRY` | The package registry: a folder, or an HTTP or HTTPS URL. The default is `https://gizstack.com/packages`. | | `GIZLANG_REGISTRY_TOKEN` | The bearer token for an HTTPS registry. Not sent over plain HTTP. | | `GIZLANG_REGISTRY_ORG`, `GIZLANG_REGISTRY_VISIBILITY` | The organization and the visibility (`public` or `private`) of a remote publication. | | `GIZLANG_SHARED_CACHE`, `GIZLANG_TRUSTED_KEYS` | A shared store of units and executables that other machines built, and the hex Ed25519 public keys (comma separated) whose signatures you accept. A file without a trusted signature is not used. | | `GIZLANG_SIGNING_KEY` | A file with a 32-byte Ed25519 seed. The machine signs what it builds, for a shared store. | To set the cache for one command: ```sh GIZLANG_CACHE="$HOME/tmp/gizlang-cache" giz main.giz Cy ``` ```text Hello, Cy. ``` The cache folder then contains `clang`, `linked`, `locks`, `runs` and `units`. You can remove the cache folder. The next command builds again. The same hourly pass removes the temporary folders of Gizlang programs (`$TMPDIR/gizlang-XXXXXX`, else `/tmp/gizlang-XXXXXX`) that are older than a day and that no process has open or works in. These folders are what a program that was killed left behind. `giz public-key SEED` prints the public key of a signing seed, for `GIZLANG_TRUSTED_KEYS`: ```sh giz public-key seed.key ``` ```text 1974b90e9fc9dd005bafce07b5914fd1b03c04c5cc18507f2980a2b26ff55a45 ``` ## Machine-readable output for agents Humans, editors and agents use the same `giz` command. Read the exit status together with the output. Refer also to [Gizlang for agents](https://gizstack.com/docs/platform/agents). | Task | Command | Output | | --- | --- | --- | | Identify the toolchain and its operations | `giz capabilities` | JSON (`gizlang.capabilities.v1`) | | Get source diagnostics, without tests or execution | `giz check --json FILE` | JSON, or nothing | | Read declarations, effects and the source hash | `giz outline --json FILE` | JSON | | Change declarations in one checked transaction | `giz edit FILE` (request on standard input) | Report, or JSON (`gizlang.edit`) | | Read the declarations about some words | `giz context FILE WORD...` | Source text | | Read the canonical source, with sugar written out | `giz project FILE` | Source text | | Make JSON functions for record types | `giz codec FILE TYPE...` | Source text | | Validate pinned packages offline | `giz get --frozen DIR` | One line for each package | ### Identify the toolchain ```sh giz capabilities ``` ```text {"schema":"gizlang.capabilities.v1","toolchain":{"id":"c1b4eff5e7c7e66d","runtime":"164448871966e74d","mode":"source","releaseTarget":null,"revision":null,"moduleAbi":null},"operations":[{"command":"FILE.giz","usage":"giz FILE.giz ARG... run FILE (also as #!/usr/bin/env giz)","available":true,"requiresPrivateSource":false},…],"safety":{…}} ``` - `operations` lists each command with `available` and `requiresPrivateSource`. - `safety` states the guarantees, for example: "check --json performs source diagnostics without tests or program execution". - The output grants no authority. Keep the same toolchain for all of an operation. ### Source diagnostics as JSON `giz check --json FILE` prints nothing and exits with 0 when the source checks. It does not run tests. An error is one JSON object, and the exit status is 1: ```sh giz check --json bad.giz ``` ```text {"error":{"code":"E_TYPE","token":8,"error_id":"gizlang.native.E_TYPE.8","stable_id":"main","location":{"buffer":"prepared_input","encoding":"utf8_bytes","start":55,"end":56,"source":{"path":"","start":55,"end":56}},"call_site":null,…,"counterexample":{"available":false,…},"replay":{"available":false,…}}} ``` - `code` is the error code. `stable_id` names the declaration. - The byte offsets in `location` are offsets in the prepared compiler input, not always in your file. For line and column, use the text output of `giz check FILE`. ### Outline ```sh giz outline greet.giz ``` ```text greet.giz:1 export function greeting(name: string): string effects {fail} ``` A script reads the same as JSON: ```sh giz outline --json greet.giz ``` ```text {"hash":"1da311351ed16034","entry":"","declarations":[{"name":"greeting","kind":"function","start":0,"end":85,"location":{"path":"","start":0,"end":85},…,"effects":"fail","exported":true,"parameters":[{"name":"name","type":"string",…}],"result":"string",…}],"files":[…],"symbols":[…],"bindings":[…]} ``` - `hash` is the base of an edit request. - For a program of more than one file, `location` gives the file and the byte span in that file. `start` and `end` are offsets in the compiler input. ### Edit declarations `giz edit FILE` applies a JSON edit request from standard input as one transaction. The edits change declarations by name: ```sh echo '{"base": "1da311351ed16034", "edits": [{"replace": "greeting", "with": "export function greeting(name: string): string { return `Hi, ${name}.`; }"}]}' | giz edit greet.giz ``` - An edit is `{"replace": NAME, "with": TEXT}`, `{"delete": NAME}` or `{"insert": TEXT}`. Instead of these, a request can hold only renames (`[{"rename": NAME, "to": NEW}]`) or one move to another module (`[{"move": NAME, "from": MODULE, "to": MODULE}]`), which also rewrites the importers. - `base` is the `hash` of `giz outline`. When the source changed since then, the edit is refused before anything is written. The edited program must also pass the full check, or no file changes. - The report names what changed, the effects that changed, the functions whose behavior can change and the tests that cover them. `--json` gives it as `gizlang.edit`. ### Context ```sh giz context greet.giz greeting ``` ```text // 1 of 1 declarations mention greeting export function greeting(name: string): string { return "Hello, " + name + "."; } ``` ### Canonical source and codecs `giz project FILE` prints the source with sugar written out: ```text function twice(x: i64): i64 { return (x * 2); } ``` `giz codec FILE TYPE...` prints `encodeT`, `decodeT`, `writeT` and `readT` for the record types: ```sh giz codec types.giz Item ``` ```text import {JsonReader, JsonError, reader, writeString, writeInteger, writeNumber} from "gizlang:json"; … export function encodeItem(value: Item): string { let out = b""; writeItem(out, value); return string(out); } export function decodeItem(text: string): _JsonCodecCore.Result { … ``` `giz bind HEADER LIB NAME...` prints `declare` lines for the named C functions of a header. Review the lines before you paste them. # Packages A Gizlang package is a folder with a `package.json` manifest. The manifest pins every source by SHA-256, so a build reads exactly the bytes that you reviewed. :::status{of="packages"} ::: ## The manifest ```json { "name": "geo", "version": "1.0.0", "gizlang": { "version": "0.2.0", "entry": { "path": "main.giz", "sha256": "…" }, "modules": { "shapes": { "path": "shapes.giz", "sha256": "…" } } } } ``` | Field | Meaning | | --- | --- | | `gizlang.version` | The toolchain version that builds the package | | `gizlang.entry` | The main source and its pin | | `gizlang.modules` | The other sources, by module name, each with its pin | `giz pin DIR` refreshes the pins after you edit a source. ## Use a package ```sh giz get . geo@1.0.0 ``` `giz get` records the dependency with its exact version and pins, and fetches it from the GizStack registry. ## Publish a package Your [terminal token](https://gizstack.com/token) page prints the three settings publication needs. Paste them, then: ```sh giz publish . ``` - A version is immutable. A yank hides it from new resolutions only. - Public or private, source or compiled: you choose each one. - You keep your code and choose its license. The [Gizlang license](https://gizstack.com/license) covers the toolchain, not your packages. ## Validate offline `giz get --frozen DIR` checks a package and its dependencies against their exact pins. It changes no file, selects no version and uses no network. A missing or edited source fails. ## npm packages `giz get DIR npm:NAME@SPEC` adds an npm package with its exact version. Installation scripts never run. [TypeScript and npm](javascript-python.md) shows how a program uses it. ## Playground projects The [Playground](https://gizstack.com/play) exports an ordinary project: `main.giz` and its pinned `package.json`. Save both in one folder and run `giz package.json`. # Build web apps with GizWeb This guide shows how to build a server-rendered web app with GizWeb. In about 20 minutes you model your data, add an action and two pages, keep the notes in a database, and test the app. - The guide is for developers who know Next.js, Remix or React. - The tutorial builds one app step by step. The how-to guides each do one task. The explanation compares the model with Next.js. - Each example ran on the current toolchain. Each response is the real response, from `curl` or from a browser. Long output is trimmed (`…`). - A section with a status badge describes GizWeb (`gizweb`). Every other section runs on the SDK modules of the released toolchain. - To install the toolchain, refer to the [installation guide](../start.md). For the `giz` command, refer to the [command line guide](command-line.md). ## What GizWeb is GizWeb is a set of SDK modules. The compiler contains the SDK, so a project needs no other installation. You import the modules with a `gizlang:` name: | Module | Use it for | | --- | --- | | `gizlang:http` | The HTTP server (`serve`), `Request`, `Response`, headers, streamed replies; routes (`router`, `Route`, `resolve`, `href`), redirects, cookies, static files, URL encoding, gzip | | `gizlang:html` | The `Html` type that markup compiles to, `each`, `when`, `Shadow` (declarative shadow DOM) | | `gizweb` | GizWeb: pages and the app shell, routes with a context (`Web.route`, `Web.path`, `Web.typed`, `Web.before`, `Web.limited`, `Web.serve`), static pages (`Web.prerender`), forms, actions, live views, content and data views | | `gizdb:model` | The record model: annotations, `Shape`, input checks, `Rejection`, DDL and JSON ([Model your data](model.md)) | | `gizdb:store` | The app's database: `Engine`, `Database` | | `gizlang:sqlite` | An embedded SQLite database | | `gizlang:security` | Signed session tokens | | `gizlang:tls` | HTTPS with your certificate | | `gizlang:test` | Assertions for `X.test.giz` files | Markup in `.giz` files has the same syntax as TSX. Its descriptive name is GSX. There is no other file suffix and no other parser. - A lowercase tag writes HTML. - A capitalized tag calls a typed component function. - `{value}` writes escaped text. Strings, integers, floats and `Html` values can be children. - A markup expression has the type `Html`. Its bytes are in `.markup`. ## Tutorial: build a small web app :::status{of="gizweb"} ::: You build "Notes": a list of notes, one page for each note, and a form that adds a note. At the end, the notes are in a GizDB folder and stay after a restart. One record type gives the table, the form, the checks, the JSON API and the agent tool. The finished app is `examples/web/notes`. The same app on `gizlang:http` alone, without a model, is `examples/web/http`. ### Before you start - Install the toolchain. `giz` must be on your `PATH`. - Make an empty folder, and in it a folder `notes`. Run all commands from the empty folder. - The examples listen on port 61100 of 127.0.0.1. If that port is in use, change the number in `notes/main.giz`. ### Step 1: write the model A model is a record type with annotations. Make `notes/model.giz`: ```gizlang file="examples/web/notes/model.giz" import {Table, Key, Label, Min, Max, Trim, Multiline, Submit} from "gizdb:model"; /** A saved note. */ @Table("notes") export type Note = { @Key id: string; title: string; body: string; }; /** The form that adds a note. */ @Submit("Add note") export type AddNote = { @Label("Title") @Min(1) @Max(80) @Trim title: string; @Label("Text") @Max(2000) @Multiline body: string; }; ``` - `Note` is what the database stores: the table `notes`, with `id` as its key. - `AddNote` is what the form sends. Its annotations are the label, the limits and the trim of each field. They make the HTML form, the server checks and the JSON schema. The checks run on the server for every caller: a browser, an API client and an agent. ### Step 2: write the action An action is an ordinary function, listed once in a registry. Make `notes/actions.giz`: ```gizlang file="examples/web/notes/actions.giz" import * as Model from "gizdb:model"; import {Note, AddNote} from "./model.giz"; import {Data} from "gizweb"; /** What an action runs with: the database and a fresh id. */ export type Session = { db: Data.Db; fresh: string; }; function addNote(s: Session, input: AddNote): Result { const note: Note = { id: s.fresh, title: input.title, body: input.body }; const shape = Model.shape(); try Data.write(s.db, [try Data.insert(shape, note)], ""); return Result.ok(note); } /** The actions: people (a form), pages and agents (a tool) call them by name. */ export function actions(): Vec> { return [Model.action("addNote", "Add a note.", addNote)]; } ``` - The function gets what it runs with (`Session`) and the checked input. It gives a `Result`. - `Model.action` puts it in the registry with its name and its doc. The input type gives the form, the checks and the tool schema, read from the type at run time. - `Model.shape()` reads the Shape of `Note` from its declaration. `Data.insert` makes a typed write from it. `Data.write` runs it in one transaction. - `try` returns the error of a failed step at once. ### Step 3: write the pages and serve The app is in `notes/app.giz`, and `notes/main.giz` starts it. Make `notes/app.giz`: ```gizlang file="examples/web/notes/app.giz" import * as Web from "gizweb"; import * as Model from "gizdb:model"; import * as Store from "gizdb:store"; import {System} from "gizlang:system"; import * as Http from "gizlang:http"; import * as Text from "gizlang:text"; import {Html, each} from "gizlang:html"; import {Data, Forms} from "gizweb"; import {Note} from "./model.giz"; import {Session, actions} from "./actions.giz"; /** What each serving thread shares. */ export type Site = { db: Data.Db; note: Model.Shape; shell: Web.Shell; add: Forms.Form; sys: System; }; type Handle = Web.Handle; /** Each route once: its method, pattern and handler. Links come from the same declaration. */ type NotePath = { id: string; }; function notePath(): Web.Path { return Web.path("GET", "/notes/{id}"); } function noteRoute(): Http.Route { return Web.typed(notePath(), (site: Site, request: Http.Request, p: NotePath): Http.Response => { match (Data.one(site.db, site.note, "id = $1", [p.id], Model.rowReader(site.note), "No such note.")) { Result.ok(n) => { return Web.page(request, site.shell, n.title,

    {n.title}

    {n.body}

    .markup, "no-cache"); } Result.err(r) => { return Web.respond(request, Result.err(r)); } } }); } function homeRoute(): Http.Route { return Web.route("GET", "/", (site: Site, request: Http.Request, p: Http.Params): Http.Response => { const notes = Data.find(site.db, site.note, "true", [], Model.rowReader(site.note))!; let form = b""; site.add.write(form, Model.noFields().with(Forms.GO, "/notes/:id")); const link = (n: Note): string => { return Web.link(notePath(), NotePath(n.id))!; }; const body =

    Notes

    {Html(form)}
    ; return Web.page(request, site.shell, "Notes", body.markup, "no-cache"); }); } /** The engine of the GizDB folder `path`, with the tables of the models. */ export function open(sys: System, path: string): Store.Engine { const engine = Web.store(sys, path)!.engine()!; Data.install(Data.of (engine, "notes")!, [Model.shape()])!; return engine; } export function shell(): Web.Shell { return Web.shell("en", Model.handlers(actions()), Web.uiStyle()); } /** The handler of one serving thread: its connection to the one engine of the process. */ export function app(own: System, engine: Store.Engine): (request: Http.Request) => Http.Response { const list = actions(); const handlers = Model.handlers(list); const site: Site = { db: Data.of (engine, "notes")!, note: Model.shape(), shell: shell(), add: Forms.compile(handlers[0], ""), sys: own }; const session = (s: Site, r: Http.Request): Session => { return { db: s.db, fresh: Text.hexEncode(s.sys.randomBytes(4)!) }; }; let routes = [homeRoute(), noteRoute()]; for (const r of Web.actions(handlers, session, ( s: Session, id: string, bare: bool, fields: Model.Fields ): Model.Outcome => { return Model.dispatch(list, s, id, bare, fields); })) { routes.push(r); } const router = Http.router(routes)!; return (request: Http.Request): Http.Response => { return Web.serve(router, site, request); }; } ``` - A route is declared once, as a function: method, pattern and handler. `noteRoute().href(params)` makes the link from the same declaration, so a changed pattern changes each link. - `Data.find` and `Data.one` read records of a Shape with a filter. The values are bound (`$1`), never put into the SQL text. `Model.rowReader` reads a row into a `Note`. - `Forms.compile` makes the form of an action once. The hidden field `_go` (`Forms.GO`) sends the browser to the new note after a valid post (`:id` is the `id` of the action's answer). - `Web.actions` adds the routes of the actions: a form posts to `/_a/ID`, an agent calls the name, and the tools are listed at `/_tools`. `Web.page` writes a page with the shell, the style and the ETag. - `open` makes the tables at start. The process holds one engine of the GizDB folder. Each serving thread makes its own connection to it (`Data.of`). Make `notes/main.giz`: ```gizlang file="examples/web/notes/main.giz" // The notes app on GizWeb (app.giz): one model, one action, two pages. Run this file with gizstack. import * as Web from "gizweb"; import {System} from "gizlang:system"; import * as Http from "gizlang:http"; import {open, shell, app} from "./app.giz"; function main(sys: System): i64 { const engine = open(sys, "data"); return Web.listen(sys, 61100, shell(), Web.serving(), (own: System): (request: Http.Request) => Http.Response => { return app(own, engine); }); } ``` Run the app, and use it: ```sh giz notes/main.giz ``` ```text listening on http://127.0.0.1:61100 ``` In another terminal, post the form the way a browser does. The hidden field `_go` is in the form: ```sh curl -i -H 'Referer: http://127.0.0.1:61100/' --data-urlencode 'title=Plan trip' \ --data-urlencode 'body=Book trains & hotel' --data-urlencode '_go=/notes/:id' http://127.0.0.1:61100/_a/addNote ``` ```text HTTP/1.1 303 See Other location: /notes/ccc1b3b1 … ``` The id is random, so yours is different. An empty title gets `303` back to the page, and the page shows the error next to the field. The same action answers a JSON client with the same check: ```sh curl -H 'content-type: application/json' -d '{"title":"","body":"x"}' http://127.0.0.1:61100/_a/addNote curl -H 'content-type: application/json' -d '{"title":"From JSON","body":"b"}' http://127.0.0.1:61100/_a/addNote ``` ```text {"ok":false,"status":422,"field":"title","message":"Title: Enter a value.","errors":[{"field":"title","message":"Title: Enter a value."}]} {"ok":true,"value":{"id":"ea053105","title":"From JSON","body":"b"}} ``` An agent finds the action as a tool, with the schema that the annotations gave: ```sh curl http://127.0.0.1:61100/_tools ``` ```text [{"name":"addNote","id":"addNote.6f1a…","description":"Add a note.","input_schema":{"type":"object","properties":{"title":{"type":"string","description":"Title","minLength":1,"maxLength":80},"body":{"type":"string","description":"Text","maxLength":2000}},"required":["title","body"]}}] ``` The list and a note are pages of server HTML. The text that the user typed comes back as escaped text: ```sh curl http://127.0.0.1:61100/ curl http://127.0.0.1:61100/notes/ccc1b3b1 ``` ```text …

    Notes

    …
    …

    Plan trip

    Book trains & hotel

    ``` Stop the server with Ctrl+C and start it again. The notes are still there, because they are in the folder `data`. The same action answers the form, a JSON client and an agent. The checks are the same for all three. ### Step 4: test the app A pure test needs no server and no database. Make `notes/main.test.giz`: ```gizlang file="examples/web/notes/main.test.giz" import * as Model from "gizdb:model"; import * as Test from "gizlang:test"; import {AddNote} from "./model.giz"; function testATitleIsRequired(): void { const fields = Model.formFields("title=+&body=x")!; match (Model.read(Model.shape(), fields)) { Result.ok(row) => { Test.check(false); } Result.err(r) => { Test.equal(r.status, 422); Test.includes(r.message, "Title"); } } } function testATitleIsTrimmed(): void { const row = Model.read(Model.shape(), Model.formFields("title=+Milk+&body=x")!)!; Test.equal(Model.valueOf(Model.shape(), row).title, "Milk"); } ``` ```sh giz test notes/main.test.giz ``` ```text 0 cached, 2 run, 0 failed ``` A test that needs the database runs the whole handler against a GizDB folder in a temporary directory. Make `notes/journey.giz`: ```gizlang file="examples/web/notes/journey.giz" import {System} from "gizlang:system"; import * as Http from "gizlang:http"; import * as Test from "gizlang:test"; import {open, app} from "./app.giz"; function post(path: string, form: string): Http.Request { return Http.Request("POST", path, [Http.Header("content-type", "application/x-www-form-urlencoded")], utf8(form)); } function main(sys: System): void { const dir = sys.temporaryDir()!; const handle = app(sys, open(sys, `${dir.temporaryPath()!}/data`)); const added = handle(post("/_a/addNote", "title=Milk&body=2+liters&_go=%2Fnotes%2F%3Aid")); Test.equal(added.status, 303); const location = Http.header(added.headers, "location") ?? ""; Test.check(location.startsWith("/notes/")); Test.includes(string(handle(Http.Request("GET", location, [], b"")).body), "

    Milk

    "); Test.includes(string(handle(Http.Request("GET", "/", [], b"")).body), "Milk"); Test.equal(handle(Http.Request("GET", "/notes/none", [], b"")).status, 404); const refused = handle(post("/_a/addNote", "title=+&body=x")); Test.equal(refused.status, 422); Test.includes(string(refused.body), "Title"); Test.includes(string(handle(Http.Request("GET", "/", [], b"")).body), "Milk"); dir.close(); print("journey ok"); } ``` ```sh giz notes/journey.giz ``` ```text journey ok ``` ### Step 5: use the app in a browser Open . Type a title and a text, and click **Add note**. The browser goes to the new note. Go back to the list: it shows the note. The form works with no JavaScript. You now have a server-rendered app with routes, a form, validation, a JSON API and a database. The next sections each do one task. [Choose how to reach the data](model.md#choose-how-to-reach-the-data) shows the other levels below the model. ## How-to guides ### Declare routes and read parameters A route is `Http.Route(METHOD, PATTERN, TARGET)`. `TARGET` is any value. Usually it is a union of endpoints. | Pattern part | Meaning | | --- | --- | | `/users` | A literal segment | | `/users/{id}` | `{id}` captures one segment | | `/files/*path` | `*path` captures the rest of the path, with its slashes | - A literal segment has priority over a capture. A capture has priority over a rest capture. The order of the declarations has no effect. - Two equal routes are refused when you call `Http.router`. - Each captured value is decoded one time. A bad escape such as `%ZZ` gets `400`. - `found.params.get(name)` gives the text. `found.params.integer(name)` gives an `Option`. - `route.href(Http.Params(names, values))` makes a URL from a declaration. It escapes each segment. A missing parameter is an error. - `Http.query(request)` decodes the query one time to `Params`. `get(name)` gives the first value, or `""`. A malformed query is an error with status 400 ([Serve HTTPS](#serve-https) has an example). The how-to examples are in a folder `howto`, next to `notes`. Run them from the same folder as the tutorial. `howto/routes.giz`: ```gizlang import {System} from "gizlang:system"; import {Result} from "gizlang:core"; import * as Http from "gizlang:http"; type Endpoint = | { kind: "readme"; } | { kind: "file"; } | { kind: "user"; }; function fileRoute(): Http.Route { return Http.Route("GET", "/files/*path", Endpoint.file()); } function main(sys: System): i64 { const routes = Http.router([ Http.Route("GET", "/files/readme", Endpoint.readme()), // a literal segment wins over a capture fileRoute(), // *path captures the rest of the path Http.Route("GET", "/users/{id}", Endpoint.user()), // {id} captures one segment ])!; print(fileRoute().href(Http.Params(["path"], ["docs/a b.txt"]))!); const listener = sys.listen("127.0.0.1", 61102)!; Http.serve(listener, (request: Http.Request): Http.Response => { match (routes.resolve(request)) { Result.err(response) => { return response; } // 404, 405 with Allow, or 400 for a bad escape Result.ok(found) => { match (found.target) { Endpoint.readme() => { return Http.text(200, "the readme"); } Endpoint.file() => { return Http.text(200, "file " + found.params.get("path")); } Endpoint.user() => { return Http.text(200, "user " + found.params.get("id")); } } } } })!; return 0; } ``` ```sh giz howto/routes.giz ``` It prints the URL that `href` made, and then serves requests: ```text /files/docs/a%20b.txt ``` ```sh curl http://127.0.0.1:61102/files/readme curl http://127.0.0.1:61102/files/docs/a%20b.txt curl http://127.0.0.1:61102/users/kim%2Fx curl -i http://127.0.0.1:61102/users/%ZZ curl -i -X DELETE http://127.0.0.1:61102/users/1 ``` ```text the readme file docs/a b.txt user kim/x HTTP/1.1 400 Bad Request content-type: text/plain; charset=utf-8 date: Wed, 14 Oct 2026 02:01:14 GMT content-length: 28 Invalid path escape or UTF-8 HTTP/1.1 405 Method Not Allowed content-type: text/plain; charset=utf-8 allow: GET, HEAD date: Wed, 14 Oct 2026 02:01:14 GMT content-length: 18 method not allowed ``` `%2F` in `{id}` stays in the one segment: the value is `kim/x`. Parameters are text. Convert them with `params.integer` or your own check. With GizWeb, a route target is a typed record, so `{id}` is an `i64` that the compiler checks. ### Name the parameters of a route in a record A parameter named in a pattern is text that a handler reads by name, and a misspelled name is found when the route runs. `Web.path

    (method, pattern)` takes the parameters from a record `P` instead. The names in the pattern are exactly the field names of `P`. The program stops at start-up, not at the first request, when they differ: ```gizlang import * as Http from "gizlang:http"; import * as Web from "gizweb"; type Site = { name: string; }; type Thread = { target: string; page: i64; }; /** The route of a page of a thread. */ export function threadRoute(): Http.Route> { const thread = Web.path("GET", "/t/{target}/{page}"); print(Web.link(thread, { target: "abc", page: 3 })!); // /t/abc/3 return Web.typed(thread, (site: Site, request: Http.Request, p: Thread): Http.Response => { return Http.text(Http.STATUS_OK, p.target + " " + string(p.page)); }); } ``` - A field type is checked and converted like a form field: `page` is a number, and `/t/abc/x` answers `400` with the field that is wrong. - `Web.link(path, params)` makes the URL and escapes each segment. A misspelled field is a compile error (`E_FIELD`), and a link cannot name a parameter the route does not have. - Use the same `path` value for the route and for its links. ### Make layouts and components A component is a function. It takes a props record, and, if it has children, an `Html` value. It returns `Html`. ```gizlang import {Html} from "gizlang:html"; type LayoutProps = { title: string; }; export function Layout(p: LayoutProps, children: Html): Html { return {p.title}

    Notes
    {children}
    ; } ``` - Call it as `…`. A props value in braces is a typed expression: ``. - A nested layout is a component that calls another component. - Lists: `{each(items, (n: Note) =>
  • {n.title}
  • )}`. - Conditions: `{when(condition, () =>

    …

    )}`. - A component has no state and no lifecycle. It runs on the server for each request. - Text that you trust as HTML goes in with `raw(text)` from `gizlang:html`. Do not use it for user input. ### Use Web Components Use a Web Component when a part of the page needs browser behavior. The `Shadow` component of `gizlang:html` writes a declarative shadow root. The browser makes the shadow DOM from the HTML, before scripts run. Keep the meaningful content in light DOM, and show it through a ``. `howto/components.giz`: ```gizlang import {System} from "gizlang:system"; import {Result} from "gizlang:core"; import * as Http from "gizlang:http"; import {Html, Shadow} from "gizlang:html"; type CounterProps = { start: i64; }; /** A Web Component rendered on the server: the shadow root is in the HTML. */ function Counter(p: CounterProps, children: Html): Html { return {p.start} {children} ; } type Endpoint = | { kind: "home"; } | { kind: "asset"; }; function main(sys: System): i64 { const routes = Http.router([Http.Route("GET", "/", Endpoint.home()), Http.Route("GET", "/static/*path", Endpoint.asset())])!; const listener = sys.listen("127.0.0.1", 61104)!; Http.serve(listener, (request: Http.Request): Http.Response => { match (routes.resolve(request)) { Result.err(response) => { return response; } Result.ok(found) => { match (found.target) { Endpoint.home() => { const view = Counter clicks; return Http.Response(200, [Http.Header("content-type", "text/html; charset=utf-8")], b"" + view.markup); } Endpoint.asset() => { const dir = sys.directory("howto/public")!; const response = Http.file(dir, request, found.params.get("path"), sys.time()); dir.close(); return response; } } } } })!; return 0; } ``` `howto/public/counter.js` is a plain JavaScript module in a static folder: ```js // Upgrades the server-rendered . The shadow root already exists. customElements.define("note-counter", class extends HTMLElement { connectedCallback() { const output = this.shadowRoot.querySelector("output"); this.shadowRoot.querySelector("button").addEventListener("click", () => { output.value = String(Number(output.value) + 1); }); } }); ``` Run `giz howto/components.giz`, and open . The server sends: ```text Counterclicks ``` In a Chromium browser, the element had its shadow root from the HTML. After two clicks, the output showed `5`, and the light DOM `` still said `clicks`. - The script does not make the DOM again. It only adds behavior to the DOM that the server sent. - With GizWeb, browser code can also be Gizlang: `client` compiles it to WebAssembly, and a live update replaces it in place. ### Handle forms and validate input GizWeb derives the whole pattern from one record type ([Step 3](#step-3-write-the-pages-and-serve)): the form, its parser and its validation. Without a model, the pattern is: 1. A native `
    `, with its URL from a route declaration. 2. A `POST` route. Its handler reads the fields from `request.body`. 3. Validation on the server. On failure, `422` and the same form, with the typed values and an error message. On success, `303` to the result page. `examples/web/http` is a complete app of this kind. - `Http.decode(text, true)` decodes one form value (`+` is a space). It gives none for a bad escape. - `gizlang:http` limits a request body to `Http.MOST_BODY` bytes (16 MiB). - `Forms.form(shape, fields)` writes the form of a Shape, and `Web.inputs(request, shape)` reads and checks it. A file field (`@File`) makes the form multipart ([Take files in a form](#take-files-in-a-form)). For a form that changes data, also check that the request comes from your site. A browser sends `Sec-Fetch-Site` and `Origin` headers. Read them with `Http.header(request.headers, name)`. Cookies from `Http.setCookie` are `SameSite=Lax`, so other sites do not send them with a cross-site `POST`. ### Take files in a form Mark a text field with `@File(accept, maxBytes, multiple)`. Its form control is then a file input, and the form is `multipart/form-data`. The field holds the file name (a JSON list of names with `multiple`), so a Shape checks that a file was chosen like any other value. ```gizlang import {Option} from "gizlang:core"; import {Submit, File} from "gizdb:model"; @Submit("Send") export type Attach = { note: string; @File("image/*,.pdf", 5000000, false) picture: string; @File("", 0, true) more: Option; }; ``` - `Web.inputs(request, shape)` reads the form and checks each file against `accept` and `maxBytes`. A file in a field that takes none, or two files in a single-file field, is a `422` for that field. - `Web.uploads(request, shape)` gives the files: `Upload { field, name, mediaType, size, bytes }`. - The kernel sends a form with a file input as `FormData`. A file dropped or pasted on the form fills its input. A form posted without script works the same. - The body is read whole (`Http.MOST_BODY`). A larger upload is streamed through `Serving.uploads`; store the bytes with Storage. ### Keep styles and scripts in files Write CSS and JavaScript as files and import them as text. `Assets.minifyStyle` and `Assets.minifyScript` make the served form. `site.css`: ```css title="site.css" body { margin: 0; font-family: system-ui, sans-serif; } ``` `style.giz`: ```gizlang import {text as siteStyle} from "./site.css"; import {Assets} from "gizweb"; export function style(): string { return Assets.minifyStyle(siteStyle); } ``` For an app with many files, `Assets.build(sources, roots, minify, maps)` reads the whole graph: - a script imports another by its logical name (`import {a} from "app/a.js"`); `import()` makes a lazy module; - a style sheet may `@import` another (inlined) or `layer()` it; `*.module.css` renames its classes and `:global(x)` keeps one; - `var(--x)` with no fallback and no declaration, an import cycle, an import of a file that is no source, and a source that no root reaches are problems, not silent output; - `graph.audit(forbidden)` reads every served file for texts that must not leave the server (a secret, `/home/`). `Web.sources(shell, graph, entries)` links the sheets, the import map, `modulepreload` and the entry scripts into every page. ### See an edit at once Under `giz live` on this machine, a page asks `/_giz/dev` which generation serves. A new generation updates the page in place, as a link does (the stylesheets sync, the form values stay); a changed kernel reloads it. If the new generation cannot start, or an edit does not compile, its reason or the compiler's error covers the page until an edit fixes it, while the generation before keeps serving. The terminal that runs `live` shows the same error. `Web.doctor(shell, graph, forbidden)` lists what is wrong with the setup (an empty language or name, a graph problem, a served file over `Web.HEAVY_BYTES`), `Web.doctorReport(found)` prints it, and `Web.weights(graph)` sizes each file. Run them in a test so a regression fails the suite. ### Answer slow or broken calls A handler call that runs longer than `Serving.limitMs` is cancelled and answers `504`; a fault answers `500`. A browser gets a page and a program gets the JSON error that every refusal uses. ### Update a page before the server answers Put attributes on a form (`data-a`): - `data-optimistic="KEY=FIELD KEY@ATTRIBUTE=FIELD"` shows the form's `FIELD` in the element keyed `KEY` (as its text, or as `ATTRIBUTE`) at once. If the action fails, the old value comes back; the server's patches replace it when it succeeds. - `data-retry="N"` sends the action again after a network failure (not after a rejection), 300 ms, 600 ms, ... apart. - A `data-cancel` control in a busy form stops its request. - `data-state` on the form is `busy`, `retrying`, `ok`, `error` or `cancelled`, for CSS. - A rejected field gets `aria-invalid`, its message is in an `aria-live` slot that the control names with `aria-describedby`, and the first bad control takes the focus. ### Use the form and interaction components `Web.Field`, `DateField`, `Combobox`, `Dialog`, `Menu` and `Chart` use the native elements (`input`, `datalist`, `dialog`, `popover`, `svg` with a table for assistive technology), so they work without script, and the browser supplies the keyboard, the language and right-to-left layout. `Combobox` adds the `giz-combobox` element (`Web.comboboxScript()`, register it with `Web.enhancement`): the arrow keys move, Enter chooses, Escape closes, and an IME composition is left alone. `Web.uiStyle()` covers dark mode, `prefers-contrast`, `forced-colors`, `prefers-reduced-motion`, right-to-left and print. ### Give an agent the contract `Web.contractRoute(shell, handlers, routes)` serves `/_contract`: the version, the routes, the actions (the same list as the agent tools at `/_tools`), the subscription path and the served files. It is made from the declarations, so it cannot disagree with them. ### Serve HTML without hydration Each page in this guide is complete HTML from the server. The pages in the tutorial have no `

    Todos ({todos.count(t => !t.done)} open)

    • filter === "all" || t.done === (filter === "done"))} key={t.id} class:done={t.done}> {t.text}

    Nothing to do.

    ``` `create={todos}` adds a row whose fields are the named inputs. `value={filter}` is two-way, and `checked={t.done}` writes the row. The model is a normal `@Model.Table` type (tests/web/todo/model.giz). Its `@Model.MaxLength` and `@Model.Min` are the rules of the form: a refusal is answered with status 422 and the reason in ``. ## 2. Three kinds of file | Kind | Name | Module | | --- | --- | --- | | Component | Capital first: `Card.ghtml` | `export type Props` from its `prop` lines, and `export function Card(_p: Props, children): Html` | | Library | Only `