Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Reference: Tooling

Tooling

A native deployable

On this page

A deployable is one program your app is built into. An app that declares no deployables: has exactly one, app: a native server binary that serves every API and runs all the background work. An app that declares them is built as Declared deployables says.

text
$ jadpo build  binary    build/target/app/app  build     b_e20fc10f5474  commit    8d113e6  settings  DATABASE_URL✓ built  app · native in 49.1 s→ jadpo migrate apply  it starts only once its database has every migration

jadpo build and jadpo build app build it. Any other name is cli.build_arguments.

What jadpo build writes

It checks as jadpo check does, writing the same files in build/. At the first error it stops with the same screen, ending ✗ build stopped.

Otherwise it writes the deployable's Rust into build/rust/app/: the program's crate, jadpo-program-app, and a binary named after the deployable, with the toolchain this Jadpo pins in rust-toolchain.toml and the runtime crates' Cargo.lock. It compiles that in release, offline and --locked, in the release profile its workspace's Cargo.toml gives: fat link-time optimisation in one codegen unit at opt-level = 3, its symbols kept. The paths it was built from are remapped and SOURCE_DATE_EPOCH is fixed: the variable's value when it is set, else the commit's time.

The binary is build/target/app/app, and each API's OpenAPI document and <api>.failures.<locale>.json are copied into build/target/app/openapi/. It is built without the runtime's testing feature.

cargo failing is toolchain.build_failed, since the Rust Jadpo writes always compiles. A file it can't write is toolchain.artifact_write_failed.

What it compiles with

Everything a build needs comes with jadpo or is fetched by it, so a released jadpo builds anywhere and you don't install Rust. A native build still needs your machine's C compiler, which links the binary and compiles ring's cryptography.

  • The runtime crates' source is carried in the jadpo binary, with their Cargo.lock and the toolchain file.
  • The Rust toolchain that file pins (rustc, cargo and the standard library for your machine, and wasm32's once a Worker is built) is fetched from Rust's download server the first time a build needs it, into jadpo/toolchains/<version>-<host>/ of your cache folder. Each archive is checked against the SHA-256 the release's manifest gives, and the manifest against the digest this Jadpo pins. JADPO_RUST_DIST_SERVER names a mirror, whose manifest is checked against the .sha256 beside it.
  • The crates the lock names come from crates.io, or whatever cargo's own configuration puts in its place. cargo fetches them only when its cache can't build offline, into CARGO_HOME when that is set, else the cache folder's jadpo/cargo/, each checked against the lock's checksum.

Each fetch says what it is fetching (Fetching Rust 1.99.0 for aarch64-apple-darwin, Fetching the runtime's crates). Where stderr animates, it is on the live line, each archive with a bar; otherwise it is a line on stderr; under --json it says nothing. The screen's fetched line, and --json's fetched, say what came (Rust 1.99.0 (146.9 MB) and 292 crates). A fetch that fails is toolchain.fetch_failed.

With CARGO naming a cargo, that cargo builds instead and nothing is fetched. cargo sets CARGO for what it runs, and the gate does for this repository's tests.

The build stamp

Every build carries a stamp saying what it is (Records and manifest):

  • the build id: b_ and the first twelve hex digits of a SHA-256 over the deployable's program (what it is compiled from: its Rust and its plan, its name and its stores' migrations), the compiler's version, the target and jadpo.lock;
  • the commit git says the project is at, and whether its tree holds changes the commit doesn't (build/ and .jadpo/ aside); a project git doesn't track has no commit and counts as changed;
  • the compiler, the deployable and the target.

The binary holds the stamp as a constant. The runtime plan it holds records the build id where jadpo dev records the graph's digest (Several changing calls in one action).

Starting

The binary reads only the variables its settings name, and refuses to start when one is missing or invalid. It names each setting and its variable, never the value (Configuration and time).

It builds each connection's driver. One it can't build is listed as it starts, and answered misconfigured when used. A database whose connection names no url: stops the start, since only jadpo dev and jadpo test give one an address of their own.

Each database the project keeps migrations in is compared with the migrations the build holds. A migration not applied, or one applied that the build doesn't have or that changed since, stops the start (Migrations). A database it can't reach yet is waited for, trying again after 1 second and doubling to 10, since a database that is down isn't a configuration error.

Then it listens on PORT (8080 when unset) on HOST (every IPv4 interface when unset), with a worker per core.

Running and stopping

Each worker serves requests and runs the tick, the runtime's regular pass over background work. The first SIGTERM or ctrl-c stops it: it accepts nothing more, answers the requests it is reading, lets its background work give back what it holds, and ends with code 0. A second ends it at once. A start that fails ends with code 1.

Running one tick

The binary takes one command. None, or serve, serves as above. <deployable> tick runs one tick and ends (The tick). Another scheduler runs it when schedules: { trigger: ScheduleTrigger.external } says the server's own timer never ticks.

tick reads its settings, builds its drivers and compares its stores' migrations as serving does, but waits for no database: one it can't reach ends it with code 1, store_unreachable. It ends with code 0, or with 1 when the tick couldn't work in a store or didn't finish, having panicked or outlasted the drain. The first SIGTERM or ctrl-c drains it, giving back what it holds. Any other command ends with code 2, command_unknown, without repeating what was given.

jadpo build gives such a deployable a tick line, with the command and how often to run it (build/target/app/app tick · each minute), and --json a tick (command, every_ms).

What it logs

It logs one JSON object a line on standard output, its keys sorted, each with the stamp's fields (build, commit, modified, compiler, deployable, target):

  • runtime.startup_failed, with every problem that stopped the start (problems, each with a problem code, the setting, variable or store it is about, and a message);
  • runtime.waiting, while a database can't be reached;
  • runtime.started, with the address, workers, trigger (native or external) and each connection whose driver wasn't built (unbuilt);
  • runtime.ticked, after each tick, the server's own timer's as well as a tick command's: ran, each schedule it ran or skipped by name with its outcome, deliveries, received, polled, deleted, alerts, problems, each store it couldn't work in, unbuilt and ms;
  • operation.failed, for a fault behind a 500, with its classification, route, request_id and .jadpo frames (Failures, Debugging and review). When the fault is a bug in Jadpo, bug_in_jadpo is true and report holds the issue form's address; otherwise they are false and null;
  • runtime.stopped.

The rest of a deployable's log comes with telemetry (Built-in services).

Markdown for agents: /docs/a-native-deployable.md.Built from Jadpo main at a707efb.