Declared deployables
On this page
An app can be built into several programs, for example a Worker that
serves its public API and a server 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:
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 } }}
$ jadpo build worker build/target/web/wrangler.json module build/target/web/web.wasm size 8.1 MB, 2.9 MB gzipped build b_1c5e09d2a7f3 serves main runs subscribers calls pdf 2 at once in each instance binary build/target/back_office/back_office build b_e20fc10f5474 serves backstage runs schedules commit 8d113e6 settings DATABASE_URL✓ built web and back_office · wasm and native in 61.3 s→ jadpo migrate apply they start only once their database has every migration
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 queuesreceiving: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.
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, as web above does (rate_limits.counted_in_memory).
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
(External services design notes).
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
(Records and manifest). 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 a707efb.