Skip to content
You are reading v0.1, which is not the current release. Go to v0.4.

Modules and packages

A Khora package groups source, dependencies and compiler configuration behind a khora.toml manifest. Modules give stable paths and visibility to the declarations inside it.

The syntax of module, import, pub and const is in Declarations. This page is what surrounds it: the manifest, the programs a package builds, dependency resolution, and where the boundary of a package falls.

A minimal package:

[package]
name = "orders"
version = "0.1.0"
edition = "2026"

Source lives under src/. Your first Khora project builds one end to end.

Khora spells compile-time names and runtime values differently, and the difference is load-bearing when reading unfamiliar code:

let response = http::Response::text(200, "ok");
print(Int::to_string(response.status));
  • :: walks modules, types, constructors and associated items;
  • . projects a field, or calls behaviour on a runtime value.

So Response::text is visibly not response.status.

Importing a function that returns a record is not enough to read that record’s fields. The type has to be in scope as well:

// Not enough: `origin()` is callable, but `.x` is not readable.
import shapes::{origin};
// Both.
import shapes::{Point, origin};

A field is looked up on a type, and a type the file cannot name is one it knows nothing about, its fields included. The compiler says so directly — Point is not in scope here, so nothing is known about its fields — but importing the pair from the start is easier.

The same applies to a trait’s methods and to an effect’s operations. A capability can arrive in a function without its type being named anywhere, and calling an operation on it needs the effect imported.

src/main.kh is the package’s program. A package that needs more than one — a migration, a backfill, a one-off report — puts each in its own file under src/bin/:

myapp/
khora.toml
src/
main.kh -> build/myapp.exe
shared.kh the modules both use
bin/
backfill.kh -> build/backfill.exe
report.kh -> build/report.exe

khora build . builds all of them. Each is its own compilation: it gets the package’s modules and not the other programs, which is what stops two main functions from meeting. A program is named after its file, so src/bin/backfill.kh is build/backfill.exe.

One file per program. A program needing several modules of its own is a package, and that is the shape to reach for rather than a directory inside src/bin.

khora run . runs the package’s own program. To run one of the others, name it:

Terminal window
khora run src/bin/backfill.kh

A package with no src/main.kh has no default program, and khora run . says which ones it does have rather than reporting that it has none.

Package dependencies are source dependencies. Khora fetches and compiles the package’s source; there are no binary artifacts to publish or to trust.

[dependencies]
# From a git repository, at a branch, tag or commit.
postgres = { git = "https://github.com/codyspate/khoralang", rev = "main", subdir = "packages/postgres" }
# From a directory on this machine, for a package you are also editing.
shared = { path = "../shared" }

subdir is for a repository holding more than one package — a git URL names a repository, and the two are the same thing only in the simplest layout.

There is no registry yet, so version = "..." has nothing to resolve against. Use git for anything you did not write and path for anything you did.

khora build resolves what it needs, so there is no fetch step to remember. To add a dependency without editing the manifest by hand:

Terminal window
khora install https://github.com/codyspate/khoralang --subdir packages/postgres

That finds out the package’s real name and whether it offers itself at all before writing the entry — two things a line typed into [dependencies] cannot check. --rev takes a branch, tag or commit and defaults to main. With no URL, khora install fetches and locks whatever the manifest already declares, which is the command to run after cloning a project.

khora why <package> explains what pulled something in, and khora graph draws the whole thing.

Resolution writes khora.lock, and a locked build never asks the network again:

[[package]]
name = "postgres"
source = "git"
url = "https://github.com/codyspate/khoralang"
revision = "0fcf6d65a2cf8c1b1636da586ed47839152c315c"
path = "packages/postgres"
checksum = "001bf5bf28448ba94bd6c08d2a8a3c55535692be5c61113fbfd94df74ff1ff55"

A branch name resolves to the commit it pointed at, and the commit is what is recorded — so rev = "main" is a convenience at the moment the dependency is added, not a moving target afterwards.

The checksum is verified, not merely recorded. Every resolution hashes what arrived and compares it against the lockfile. If the same commit id ever produces different bytes, the build stops and says so rather than compiling what turned up.

Commit the lockfile for applications and anywhere else reproducible builds matter, and review a lockfile change like any other dependency change.

A package is consumable when its manifest says so:

[package]
name = "postgres"
version = "0.1.0"
publish = true

Absent means no. Publishing here is passive — a pushed repository is already fetchable — so the marker records an intention rather than granting a permission. What it prevents is depending on somebody’s application, or their unfinished experiment, because it happened to sit in a repository you fetched.

A package can pin the compiler it expects:

[toolchain]
version = "0.2.0"

The pin takes precedence over the machine default. See Installation for toolchain management.

Expose the domain types and operations callers need, and keep helpers private. Capabilities are useful at a package boundary because a signature can state the external authority the package needs without prescribing how an application supplies it.

Adding a required capability or a recoverable failure to a public function changes what callers must provide or handle, so both belong to the package’s public compatibility surface. See Compatibility and stability.

Khora packages distribute source and are compiled as part of the consuming program. Whole-program monomorphization and native optimization mean Khora defines no stable Khora-to-Khora binary ABI for separately compiled package binaries.

Where a stable C-compatible boundary is required, use extern fn and the library build flow in FFI.

Khora 0.1.0built frombd964738afb6on 2026-10-17 20:41ZVersioningSecurityContributingReleasesSource