Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 5 Nov 2026. What's built · Install 0.3.0
jadpo
ReferenceBuilding and deploying

ToolsBuilding and deploying

Declared deployables

On this page

An app can be built into several programs, for example one server that serves its public API and another that runs its back office. application { deployables: { } } in app.jadpo names each one, what it serves, what it runs and what it is built for:

app.jadpo:

example
application {    apis: {        main: {            prefix: /        }        backstage: {            prefix: /backstage        }    }    deployables: {        web: {            serves: [main]            runs: [Trigger.subscribers]            target: Target.native        }        back_office: {            serves: [backstage]            runs: [Trigger.schedules]            target: Target.native        }    }}

web, back_office and backstage are names you choose, and main is the API every app has. Trigger and Target are built in.

text
$ jadpo build  binary    build/target/back_office/back_office  build     b_6c016d57e7c3  serves    backstage  runs      schedules  binary    build/target/web/web  build     b_0e9644cb2148  serves    main  runs      subscribers  commit    8d113e6✓ built  back_office and web · native in 61.3 s→ ./build/target/back_office/back_office  runs it, reading its settings from the environment

serves:, the APIs it answers

serves: lists APIs by name: those apis: declares, or main, and webhooks once a connection receives webhooks. A deployable answers only those APIs' routes, each API's openapi.json among them, and a webhook only when it serves webhooks. Every deployable answers its health checks. Two deployables may serve one API.

runs:, the background work it runs

runs: lists the background work it runs:

  • Trigger.schedules: the app's schedules, each entity's purge, each poll, and a batch run picked up again;
  • Trigger.subscribers: each delivery of an event or an email, after its action commits and from the outbox, and the queues receiving: reads.

An action it serves still writes its events to the outbox, for a deployable that runs them. A delivery in a deployable that doesn't run subscribers, or a batch run in one that doesn't run schedules, waits for one that does. Every deployable's tick deletes what has expired, which any instance may.

A package's provider.jadpo has a runs: of its own, for a container, which isn't built yet and is a different thing.

target:, what it is built for

target: is Target.native, a server, or Target.wasm, WebAssembly on Cloudflare Workers. A deployable with no target: has none, like the app of an app that declares no deployables, and is built natively (design notes, "A deployable's target when none is declared").

What each reaches

A deployable reaches everything its work can lead to. From the routes of the APIs it serves, the webhooks' replies, the actions its schedules and subscribers run and the batch runs it carries on, through every callable they call, to the connections they use: a store of files, a provider's operation, a package's action, ai:'s model. With them come its polls, the webhooks it receives and the queues receiving: reads. Where it runs subscribers, it also reaches email:'s connection, through which the app's emails and the sign-in email are delivered, unless magic_link: { send: } names an action that sends the sign-in email.

It also reaches what Jadpo runs by name: the action on_first_sign_in: names, where it serves an API; the action magic_link: { send: } names, and every email's templates, where it runs subscribers; and every provider package's decode: and encode:.

What each holds

A deployable's program holds only what it reaches: those callables, the routes of the APIs it serves, its schedules and the subscribers' actions only where it runs them, the connections it reaches with every database, and the settings those read. So it reads no other setting as it starts. A subscriber it doesn't run is still listed, so what it emits waits for the deployable that does.

An app's wording and its stores are kept whole, and so is who signs in. But a sign-in method's signing_key: is kept only where the deployable serves an API that takes that method, and origin: only where it serves one that takes browser sessions. So a Worker that only runs the subscribers starts without either.

Its target, checked

A deployable that declares a target is refused each connection it reaches whose driver that target can't run, and the error names the alternative (deployable.target_unsupported; External services):

Target Refused Use instead
Workers The disk A package over S3's API, such as Cloudflare R2
Workers A service Jadpo runs without host: host:, with where it is hosted
Workers SMTP on port 25 Port 587
Native A Cloudflare queue, which only a Worker receives A package over Cloudflare's HTTP pull API
Native Cloudflare's rate-limiting binding as the store of its rate limits Redis

A deployable built for Workers that serves an API needs a store for its rate limits, rate_limits: { store: } (Rate limits; rate_limits.store_required). Until Redis's driver comes, a Redis store there would leave each instance counting in its own memory. That is a judgement call, which a decline line answers (rate_limits.counted_in_memory). Here web is a Worker:

app.jadpo:

example
application {    apis: {        main: {            prefix: /        }        backstage: {            prefix: /backstage        }    }    connections: {        cache: {            driver: Driver.redis        }    }    rate_limits: {        store: cache    }    deployables: {        decline rate_limits.counted_in_memory: "One instance of `web` is enough until Redis's driver comes"        web: {            serves: [main]            runs: [Trigger.subscribers]            target: Target.wasm        }        back_office: {            serves: [backstage]            runs: [Trigger.schedules]            target: Target.native        }    }}

A deployable built for Workers is refused a client_address: source other than cloudflare, at its source: (Client addresses).

A deployable built for Workers that reaches a connection with max_concurrent: is listed under "On Workers" once jadpo check passes, and jadpo build says so on the Worker's calls line. Workers runs as many instances as traffic needs, and each counts the cap on its own.

Nothing left unserved

Once deployables: is declared, every API with routes needs a deployable that serves it, and the app's schedules and subscribers need one that runs them (deployable.unserved). A route with settle: is served only where subscribers run (deployable.settle_unrun).

The build

jadpo build builds every deployable, and jadpo build <deployable> one. Each goes into build/target/<deployable>/ with the documents of the APIs it serves, and its Rust into build/rust/<deployable>/. They are all compiled into a cargo target in your cache folder, shared by every project built for the same host and set of locales, so the runtime crates compile once for them all (design notes, "Where deployables are compiled").

A name the app doesn't declare is cli.build_arguments, with the nearest. A deployable built for Target.wasm is built for Workers (A Workers deployable).

Each binary's stamp names it, and its build id is its own. The id digests the deployable's program, so a change it doesn't reach leaves it alone (Review and records). jadpo check writes each deployable's target and build id in the manifest, with no build id while the project has errors. settings lists the variables the deployables read, each once.

Markdown for agents: /docs/declared-deployables.md.Built from Jadpo main at aee2e8f.