The package format
On this page
A package is laid out as shared/ is, so one set of rules covers the app
and every package, and the compiler checks a package as it checks
shared/.
A library package:
postcodes/v2/ package.jadpo the index: description, licence, lowest Jadpo version types/uk_postcode.jadpo public type UkPostcode = Text { ... } failures/invalid_postcode.jadpo libraries/uk.jadpo public library uk { parse(...) normalise(...) } libraries/uk.test.jadpo tests beside the file, as in an app translations/ its own messages and failure wording GUIDE.md optional: how to use it well CHANGELOG.md surface.jadpo_record generated
A provider package adds provider.jadpo and the folders an API needs:
stripe/v2024_06_20/ package.jadpo the same two keys as every package provider.jadpo how the API is reached and called resources/payment_intents.jadpo imported, corrections merged in types/payment_intent.jadpo imported failures/card_declined.jadpo events/payment_intent_succeeded.jadpo actions/start_subscription.jadpo written: several of its own calls in order subscribers/renewal.jadpo written: enriches its own webhook libraries/tables.jadpo written: decoders and helpers provides/email.jadpo only when it fills a capability recipes/track_parcels/ wiring examples an app copies in recordings/payment_intents.create.json real responses that confirm the types surface.jadpo_record
- The index lists no files. A file is named after what it declares, so the compiler finds a resource by name and loads only what the app uses.
- A re-import rewrites only imported files. Written libraries, actions and corrections survive.
- A package's tests run when it is published, not in every app's
jadpo test; the digest guarantees the code is the code that passed. - No
emails/folder. Email templates, partials and layouts are the app's own, and anemails/folder in a package is a compile error. - Translations are only in library packages, for their own messages
and failures, laid out as a domain's
(Packages' text: groups, calls and the surface).
An app calls them as
Postcodes.translations.<message>(...)and rewords them in its roottranslations/. Provider packages show users no text, because their failures are always mapped to the app's own, so they carry none. GUIDE.mdis optional, and says how to use the package well. Every code example in it is compiled and run when the package is published, andjadpo package show <name> --guideprints it.
The index: package.jadpo
Every package's index has the same form: package Name.vN { }, a ///
description above it, and two keys. licence: names its licence, and
jadpo: the lowest Jadpo version it needs.
The licence is one of Licence.mit, .apache_2_0, .bsd_2_clause,
.bsd_3_clause, .isc, .mpl_2_0, .unlicense and .proprietary.
Only a package installed from git may be .proprietary; the public
registry refuses it. There is no requires:, because packages have no
dependencies, and no list of files.
// packages/postcodes/v2/package.jadpo/// UK postcodes: parsing, normalising and checking the outward code.package Postcodes.v2 { licence: Licence.mit jadpo: "1.0"}
How the API is reached: provider.jadpo
A package that describes an API has one provider.jadpo: a
provider { } declaration with a fixed set of keys. A package without
one is a library.
One package describes at most one API, because one API is one connection with one set of hosts and credentials. A vendor with two APIs under different credentials, such as Shopify's Admin and Storefront APIs, is two packages.
| Key | Says |
|---|---|
protocol: |
How bodies are sent: Protocol.json_http, .form_http, .multipart_http, .aws_json. Required |
hosts: |
live: and sandbox:; the same host twice when the sandbox is a test credential; a host may hold one placeholder filled from a setting ("https://{project_ref}.supabase.co") |
runs: |
A container Jadpo runs instead of a fixed host: image: pinned by digest, port: it listens on inside the container, asks: the outbound hosts it needs (see "Services") |
credential: |
The scheme (CredentialScheme.bearer, a named header, basic, AWS signing), which decides the shape of credential the app gives |
credential_header: |
For CredentialScheme.header, the named header the credential goes in alone ("x-api-key"); required for it and only for it |
headers: |
The fixed headers every request carries, each "Name: value" (["anthropic-version: 2023-06-01"]), never one the driver writes itself |
oauth: |
The provider's authorize, token and revoke addresses, for a connection whose credential is an OAuthGrant (see "Acting for each user") |
idempotency: |
header: (where the key goes), window: (how long the provider keeps a key; required), max_length:, the provider's error codes in_use: (the key's first request is still running), reused: (the key came with a different request) and new_key_on: (the first attempt failed and the key cannot be used again), and replays_failures: true (default false) for a provider that replays a failed answer under the same key. Left out when the API takes no key |
error_code: |
Where the provider's error code is in a failed response |
webhooks: |
check: (the signature scheme), event_id:, event_type:, occurred_at: where the body says when the event happened, header: for SignatureScheme.hmac_sha256, and handshake: where the provider needs one |
polls: |
Operations the runtime calls on a schedule, each with a default interval and the event emitted when the answer changes |
erasure: |
The operation that deletes a person at the provider and the key it takes (by:) |
max_concurrent: |
How many of its calls run at once per app instance |
settings: |
Typed values the app gives on the connection (message_stream:) |
imported: |
The description it was imported from, file:, that file's digest, digest:, and reviewed:, the package's types an agent has reviewed against the provider's documentation |
Where the API is comes from exactly one place:
hosts:when it is on the internet;runs:when Jadpo runs it;- neither, when the app gives
host:on the connection: another Jadpo app, a self-hosted product, or a service on Workers.
A connection's host: beside runs: means the service is already
running there, so Jadpo doesn't start it. A package with fixed hosts:
refuses a host:.
// packages/acme_couriers/v1/provider.jadpo/// Acme Couriers' booking and tracking API, written from its HTML docs.provider { protocol: Protocol.json_http hosts: { live: "https://api.acme-couriers.example" sandbox: "https://sandbox.acme-couriers.example" } credential: CredentialScheme.bearer idempotency: { header: "Idempotency-Key" window: 24h max_length: 255 in_use: "idempotency_key_in_use" reused: "idempotency_key_reused" } error_code: "error.code"}// packages/acme_couriers/v1/resources/bookings.jadpopublic resource bookings { create: { method: HttpMethod.post path: "/v1/bookings" input: CreateBooking output: Booking changes: true idempotent: true failures: [SlotTaken] source: PackageSource.written docs: "https://docs.acme-couriers.example/api#create-booking" }}// packages/acme_couriers/v1/types/create_booking.jadpopublic type CreateBooking = Object { postcode: Text slot: Text}// packages/acme_couriers/v1/types/booking.jadpopublic type Booking = Object { id: Text collect_at: Instant { wire: Wire.unix_seconds }}// packages/acme_couriers/v1/failures/slot_taken.jadpopublic type SlotTaken = Failure { kind: FailureKind.conflict message: "The collection slot is no longer available."}
idempotent: true on an operation means it is safe to send again: with
the package's idempotency: block, because it takes the key; without
one, because repeating it as it is changes nothing (a PUT that
replaces, a DELETE). An operation of a connection whose credential is
an OAuth grant also names the scope it needs, scope: "calendar.events".
Inside the package, its names are written bare. An app names them through
the package: AcmeCouriers.Booking, AcmeCouriers.SlotTaken. That holds
in a match arm too (AcmeCouriers.SlotTaken -> ...), and for an enum's
values, AcmeCouriers.Window.morning.
A failure's code, slot_taken from its name, is the provider's error
code. Jadpo reads it from the place in the response that error_code:
names.
What is public
publicmarks the package's API, as it marks what a domain offers other domains. Apublic resourceand apublic actioncan be called through the connection. Everything else can be called only by the package's own actions, subscribers and polls. As in an app,publicgoes on a library, never on a function: every function of apublic libraryis public, and internal helpers such as decoders live in a library withoutpublic(Project layout and modules). Internal declarations may change freely between versions. An importer marks every resource public.- Everything reachable from the public surface is public, and every
compile checks it. A failure a public library's function
failswith, a provider operation'sfailures:and the types in a public signature must themselves be public. Internal failures are mapped or handled before they leave a public declaration. A domain's public actions, queries and libraries follow the same rule. - A package's resources and actions share the connection's namespace
(
stripe.customers,stripe.start_subscription), so a clash between them is an error in the package. - Package events are public or not. The app may subscribe to a
publicevent; one withoutpublicreaches only the package's own subscribers, so a raw webhook a package enriches can stay internal while its enriched event is public. An importer marks webhook events public; subscribing to a package's non-public event is an error.
No presets, no dependencies
Built-in drivers are configured in app.jadpo, never packaged. A
driver is native code in Jadpo's runtime that speaks one protocol.
PostgreSQL, Redis, S3, SMTP and OpenID Connect are drivers. A vendor that
speaks one of them is that driver with the vendor's settings: Cloudflare
R2 is driver: Driver.s3 with its host and region: "auto", Neon is the
PostgreSQL driver with its pooling mode, and Gmail is the SMTP driver
with its host and port. A vendor with storage and an API is a driver
connection plus a package. A driver for one vendor comes in a Jadpo
release only where a flow needs native code.
// app.jadpoavatars: { driver: Driver.s3 host: config.r2_host region: "auto" bucket: "avatars" credential: { key_id: config.r2_key_id secret: config.r2_secret }}
- Each driver's page lists the settings for common services, so an
agent looks them up rather than guessing: R2, Backblaze B2,
DigitalOcean Spaces, MinIO and Amazon S3; Neon, Supabase and Amazon
RDS; Gmail, Microsoft 365 and Amazon SES's SMTP; Google and Microsoft.
Each is a working connection, checked with the pages' other examples.
jadpo checkwarns when it recognises a host that is missing one of its settings. - Sign-in providers are connections. Google, Microsoft and other
OpenID Connect providers are
google: { driver: Driver.oidc issuer: client_id: credential: }inconnections:, named bysign_in: { providers: [google, github] }(Identity). GitHub and Apple aren't plain OpenID Connect, so they are the built-inDriver.githubandDriver.apple.
Packages have no dependencies: a package can't require another.
- The standard library holds the shared vocabulary, such as money, countries, currencies and phone numbers, which is what packages elsewhere mostly depend on each other for.
- Anything else is copied. A package that needs another's code includes a copy, checked and tested by the publishing bar like the rest of it.
- Two packages' types for the same thing are converted at the call, usually in one line.
- Installing a package is one folder and one line in the lock.
Markdown for agents: /docs/the-package-format.md.Built from Jadpo main at a707efb.