How packages, connections, capabilities and services fit together
On this page
Here is an app calling one outside API, with nothing left out: it books a
collection with a courier. Acme Couriers publishes no package, so the app
describes its API itself, in shared/providers/. With an installed package,
jadpo package add brings these package files and you write only the
connection, the setting and the action.
The connection goes in app.jadpo. connections: and its keys are built
in, couriers is your name for it, AcmeCouriers.v1 is the package below,
and shipping is the domain whose actions may call it.
app.jadpo:
application { connections: { couriers: { package: AcmeCouriers.v1 domains: [shipping] credential: config.couriers_api_key environment: config.couriers_environment } }}
The secret and which of the courier's hosts to use are settings.
ProviderEnvironment is built in, and is .sandbox or .live.
config.jadpo:
config { couriers_api_key: Text { secret: true } couriers_environment: ProviderEnvironment { default: ProviderEnvironment.sandbox }}
The package names itself and says how the API is reached.
shared/providers/acme_couriers/v1/package.jadpo:
/// Acme Couriers' booking API, written from its documentation.package AcmeCouriers.v1 { licence: Licence.proprietary jadpo: "1.0"}
licence: is required, and Licence.proprietary suits a package only your
app uses. jadpo: is also required: it is the lowest Jadpo version the
package says it needs, a major and minor number as text. Jadpo checks only that
form and compares the number with nothing yet, so "1.0" passes on every
release, even one before 1.0.
shared/providers/acme_couriers/v1/provider.jadpo:
/// Acme Couriers' booking API.provider { protocol: Protocol.json_http hosts: { live: "https://api.acme-couriers.example" sandbox: "https://sandbox.acme-couriers.example" } credential: CredentialScheme.bearer}
A resource is a group of the API's operations. This one has one operation,
which takes a CreateBooking and answers with a Booking, two types the
package declares. changes: true says the call changes something at the
courier. idempotent: true says the call is safe to send again, here because
repeating it books one collection, so Jadpo may retry it. source: PackageSource.written tells whoever reviews the package that a person wrote
this operation from the courier's documentation, rather than importing it
(Provenance and confirmation).
shared/providers/acme_couriers/v1/resources/bookings.jadpo:
public resource bookings { create: { method: HttpMethod.post path: "/v1/bookings" input: CreateBooking output: Booking changes: true idempotent: true source: PackageSource.written }}
shared/providers/acme_couriers/v1/types/create_booking.jadpo:
/// What booking a collection takes.public type CreateBooking = Object { postcode: Text}
shared/providers/acme_couriers/v1/types/booking.jadpo:
/// A booked collection.public type Booking = Object { id: Text}
The action calls the operation through the connection. use app brings in
the connection, and use shared/providers/acme_couriers brings in the
package, whose public names are written AcmeCouriers.<name>.
domains/shipping/actions/book.jadpo:
use app { couriers }use shared/providers/acme_couriers/// Books a collection with the courier, through its connection.action book(request: AcmeCouriers.CreateBooking) -> AcmeCouriers.Booking { return couriers.bookings.create(request)}
Run jadpo records update once, which writes down the hosts the app can reach,
then check the project:
jadpo checkNot yet confirmed Record each with `jadpo package record`, which checks a sandbox answer against its types (agent) couriers.bookings.createchecked: 1 unconfirmed, 8 files
The check passes. "Not yet confirmed" means no real answer from the courier
has been checked against the types you wrote, so they are only as right as what
you read. It doesn't fail the check, and jadpo approval shows a person what
ships unconfirmed. To confirm the call, put a sandbox key in .env.local and run jadpo package record couriers bookings.create --input '{"postcode": "M1 1AA"}', which calls the
sandbox once, checks the answer and keeps it as a recording in the package
(Provenance and confirmation).
A database and mail are connections too. They use a driver built into Jadpo,
so there is no package to write. email: says which connection sends the
app's email, whichever provider it is, which makes email a capability.
app.jadpo:
application { connections: { main: { driver: Driver.postgres url: config.database_url } mail: { driver: Driver.smtp host: config.smtp_host credential: { username: config.smtp_username password: config.smtp_password } } } email: { connection: mail from: config.mail_sender }}
config.jadpo:
config { database_url: PostgresUrl mail_sender: Email smtp_host: Hostname smtp_password: Text { secret: true } smtp_username: Text}
What each one is
| Term | What it is | Declared in | Runs |
|---|---|---|---|
| Package | Jadpo source shared between apps, laid out as shared/ is. A library package holds types, failures and pure functions (Postcodes.uk.parse); a provider package describes one outside API: its operations, types, failures, webhook events and how it is reached (Kinds of package, The package format) |
packages/<name>/<version>/ when installed; the app's own in shared/providers/ |
Compiled into the app and run in its process. A package is never a process, a container or native code of its own |
| Provider | The outside API a provider package describes: Stripe, a courier, another Jadpo app. The package's provider.jadpo says how it is reached |
Nowhere in the app: it is the other end of the call | Wherever its owner runs it: on the internet (hosts:), or at an address a setting gives (host: on the connection) |
| Connection | One named entry in connections: in app.jadpo: a built-in driver (driver: Driver.x) or a provider package (package: X.vN), with its settings, its credential and the domains whose actions may call it. One connection is one resource: a database, a store of files, one account at a provider (Connecting in app.jadpo) |
app.jadpo, its values read from config.jadpo settings |
Opened by the runtime, inside the app |
| Driver | Native code in Jadpo's runtime that speaks one protocol: PostgreSQL, SMTP, the disk, Cloudflare Queues, OpenAI-compatible model servers, OpenID Connect. Redis and S3 aren't built yet. Drivers arrive only with a Jadpo release, never in a package (No presets, no dependencies) | driver: on a connection |
Inside the app, talking to a server the operator runs |
| Capability | A job Jadpo itself does, with one API in app code whatever fills it: database, email, files, ai and the rest. One connection fills it: a driver, or a provider package whose provides/<capability>.jadpo maps the job onto its operations (Capabilities) |
Chosen in app.jadpo: email: { connection: mail }, store: sheets on a File field |
Wherever the connection that fills it runs |
| Built-in service | A backend feature Jadpo provides, so the app installs nothing: feature flags, rate limits, files, email, audit, search (Built-in services). Email, files and search do their outside work through a capability; flags, rate limits and audit keep their state in the app's own stores | Where Built-in services says: flags.jadpo, rate_limit:, type X = EmailMessage, File fields |
Inside the app's runtime. It is a feature, not a process or a container |
A service run by Jadpo is a seventh term, for a thing that isn't built yet: Running a service in a container.
Two words that mean two things
runs: is one key with two jobs, depending on where it is written:
| Written on | It says | Looks like | Built |
|---|---|---|---|
A deployable, in app.jadpo |
Which background work that deployable runs (runs:) |
runs: [Trigger.schedules] |
Yes |
A provider, in a package's provider.jadpo |
The container Jadpo starts for that provider (Running a service in a container) | runs: { image: "…" port: 3000 } |
Not yet |
"Service" has two meanings too. A built-in service, such as feature flags, is part of Jadpo's runtime, inside the app's process. A service run by Jadpo would be a container beside it, and isn't built yet.
How they relate
the app's process: jadpo dev's interpreter, or a native or Workers deployable│├── domain code ── calls ──► library package pure functions, in process│├── domain code ── calls ──► connection: provider package ── HTTPS ──► provider's API│ on the internet (hosts:), or at host:│└── built-in services: flags, rate limits, email, files, search, ... │ outside work goes through a capability └──► capability: email, files, database, ai, ... filled by one connection, either ├── a built-in driver ──────────── its protocol ──► a server the operator runs: │ PostgreSQL, SMTP └── a provider package's provides/ ── HTTPS ──────► provider's API: Resend
- Code calls a provider's operations only through a connection, from
an action in a domain the connection lists:
couriers.bookings.create(...)(Calling). - Code never names what fills a capability. It writes an
EmailMessagetype or aFilefield, andapp.jadpopicks the connection, so changing provider changesapp.jadpoand its settings, never domain code. - One provider package can be both. Resend's fills
emailthroughprovides/email.jadpo, and the domains its connection lists can call its own operation,emails.send, as any provider's. - Packages fill capabilities and never invent them, never bring a driver and never depend on another package.
- Anything that keeps data is the operator's. PostgreSQL and an SMTP server are reached through a driver at the address a setting gives; Jadpo never runs them. The operator may run them in Docker or anywhere else, and the app sees only the address.
Where each runs
jadpo dev |
jadpo test |
Native deployable | Workers deployable | |
|---|---|---|---|---|
| Library package | In process | In process | Compiled in | Compiled in |
| Provider package's calls | Faked by default, from its recordings or generated answers; operations named in development: { sandbox: } go to the sandbox, never live (Provider calls in development) |
Always faked; a test scripts answers with fakes: (Tests and fakes) |
HTTPS to the live or sandbox host the connection's environment: picks, or to its host: |
The same, through the Worker's fetch |
Database, Driver.postgres |
The address its setting gives, else a disposable PostgreSQL started from the machine's PostgreSQL tools | A copy per test on a disposable PostgreSQL, or on JADPO_TEST_DATABASE_URL's server |
The operator's PostgreSQL | The operator's PostgreSQL, through Hyperdrive |
| Email, by SMTP or a mail package | Caught in the dev inbox | Caught in email.sent |
The SMTP server, or the mail package's API | The same; SMTP on port 587 only |
| Files | The disk driver, in .jadpo/files/ |
An in-memory store | The disk driver, on one server | None yet: Workers has no disk, and the S3 driver isn't built |
jadpo check refuses a deployable whose target can't run a connection
it reaches, naming what to use instead: on Workers the disk and SMTP on
port 25 (Capabilities, "Targets").
An app needs no Docker or Podman to be checked, tested, run in
jadpo dev or built. The disposable PostgreSQL comes from the machine's own
PostgreSQL tools, a native deployable is one binary
(A native deployable) and a Workers
deployable is a Worker. Neither is an image.
What is built of these
Of what this page describes, these aren't built yet: running a service in a
container; the Redis and S3 drivers; OAuth grants; the cache, publishing,
documents, bot_check and scanning capabilities; an outside engine for
search; the hosted registry; importers other than openapi; and choosing
a provider when deploying.
Of the built-in services, flags, rate limits, files, audit and search are
partly built, email is built, and documents, bot protection and the
telemetry: section aren't built yet. Each page's "What is built" table is
the record: this page's What is built and
Built-in services'.
Markdown for agents: /docs/how-integrations-fit-together.md.Built from Jadpo main at aee2e8f.