# What package code may do

A provider package may act on its own API, but never on your app's data.
That way a package can carry what someone who knows the API has learned,
written once and tested, without reaching into the apps that install it.

- **No state and no database.** A package owns no entities or tables and
  never reads or writes the app's data. Data a package fetches reaches the
  app as a return value or an event, and the app's own action writes it.
- **Package actions call only their own API.** A provider package may
  hold `public action`s in `actions/`. They call the package's own
  operations in any order, pass answers between them, use its pure
  functions, and return a result or fail. They can't emit app events, send
  email, call any other connection, or read configuration or secrets. The
  app calls one as `connection.action(input)` inside its own action. One
  with several changing calls follows
  [Several changing calls in one action](several-changing-provider-calls.md).
  Inside a package, operations are named without the connection:
  `customers.create`.
- **Package subscribers on its own webhooks.** A package may subscribe to
  its own webhook events, call its own API to enrich them, and emit its
  own declared events, such as `Stripe.SubscriptionRenewed`.
- **Events only about the outside world.** A package emits events for
  webhooks, enriched webhooks and polls, never as a side effect of the app
  calling it. Where the app wants an event after a call, the app's own
  action emits it, where a reviewer sees it. So **an action that emits
  can't be `public`**: only the package's subscribers and polls run it.
  Package events travel through the app's outbox like any other.
- **Package code runs only when started**: by the app calling it, or by
  the outside world through a webhook or a poll the app subscribes to.

In this sketch, Stripe's package holds an action that makes several calls
in the order Stripe requires. Its subscriber `renewal` hears the raw
`InvoicePaid` webhook event, which isn't public, and runs
`announce_renewal`, which emits the public `SubscriptionRenewed`. The app
calls the action through its `stripe` connection:

```jadpo
// packages/stripe/v2024_06_20/actions/start_subscription.jadpo
/// Creates the customer, attaches the card, starts the subscription and
/// finalises its first invoice, in the order Stripe requires.
public action start_subscription(input: StartSubscription) fails CardDeclined -> StartedSubscription { ... }

// packages/stripe/v2024_06_20/subscribers/renewal.jadpo
subscriber renewal {
    on: InvoicePaid
    run: announce_renewal(event)
}

// domains/billing/actions/subscribe.jadpo
var started: Stripe.StartedSubscription = attempt stripe.start_subscription(request) {
    card_declined: PaymentDeclined
}
```

## A package action, compiled

Here a package's action calls its own operations, one of which the
package keeps to itself, and passes each answer to the next. The app calls
the action through its connection and maps its failure at the call. A
package action's changing calls are steps of the action that calls it
([Several changing calls in one action](several-changing-provider-calls.md)).
The choices are in "Package actions and recipes", further down this page.

The card can be declined after the customer was created. So the
package's surface lists `after_effect DeclinedCard` for
`start_subscription`, and the app's call of it gets the warning
`step.late_failure`: a point that can end `subscribe` for good once
something took effect ([Jobs and events](jobs-and-events.md)). Here
the empty customer left behind is harmless, so the warning is declined
with that reason.

```jadpo
/// Creates the customer, then attaches the card to it.
public action start_subscription(input: PayRequest) fails DeclinedCard -> PayReply {
    var customer: PayReply = customers.create(input)
    return attempt cards.attach(
        PayRequest {
            reference: customer.id
        }
    )
}
```

```jadpo
use app { pay }
use billing/failures { PaymentRefused }
use shared/providers/acme_pay

/// Starts a subscription: two steps, run by a subscriber.
decline step.late_failure: "A declined card leaves an empty customer at Acme Pay, which costs nothing"
action subscribe(tier: AcmePay.Reference) fails PaymentRefused -> AcmePay.PayReply {
    return attempt pay.start_subscription(
        AcmePay.PayRequest {
            reference: tier
        }
    ) {
        declined_card: PaymentRefused
    }
}
```

## A package subscriber, compiled

Here a package's subscriber hears an event its webhooks bring, which the
package keeps to itself. It runs the package's action, which looks the
subscription up through the connection the webhook came in on and emits
the package's own public event. The app's subscriber keeps that event.
The choices are under "Package subscribers and recipes in `sample/`",
further down this page. The event the action emits gives no `wire_name:`,
because `wire_name:` marks an event the provider sends.

```jadpo
/// An invoice was paid: all Acme Pay's webhook says, kept to the package.
type InvoicePaid = Event {
    wire_name: "invoice.paid"
    occurred_at: Instant
    subscription: Reference
}
```

```jadpo
/// A subscription was renewed, with its tier: the package's own event.
public type SubscriptionRenewed = Event {
    subscription: Reference
    tier: Reference
    paid_at: Instant
}
```

```jadpo
/// Looks up the subscription an invoice paid for, and announces it.
action announce_renewal(event: InvoicePaid) -> Unit {
    var renewed_subscription: Subscription = subscriptions.retrieve(
        SubscriptionAsked {
            id: event.subscription
        }
    )
    emit SubscriptionRenewed {
        subscription: renewed_subscription.id
        tier: renewed_subscription.tier
        paid_at: event.occurred_at
    }
}
```

```jadpo
/// Turns a paid invoice into a renewal.
subscriber renewal {
    on: InvoicePaid
    run: announce_renewal(event)
}
```

```jadpo
use billing/actions { record_renewal }
use shared/providers/acme_pay

/// Keeps each renewal Acme Pay announces.
subscriber renewed {
    on: AcmePay.SubscriptionRenewed
    run: record_renewal(event)
}
```

A signed `invoice.paid` reaches `renewal` once, and its lookup is sent
through `payments` with that connection's credential. The renewal it
emits reaches `renewed` through the app's outbox.

## Polls

**Polling turns an API without webhooks into events.** `polls:` in
`provider.jadpo` names an operation, a default interval and the event
emitted when the answer changes. The runtime calls the operation on that
interval and keeps what it last saw, as runtime state, like webhook
de-duplication. It emits an event only when the answer changes. A poll
runs only while something subscribes to its event, and the connection can
change the interval: `polls: { latest: 6h }`.

```jadpo
// packages/exchange_rates/v1/provider.jadpo
provider {
    protocol: Protocol.json_http
    hosts: {
        live: "https://api.rates.example"
    }
    credential: CredentialScheme.bearer
    polls: {
        latest: {
            call: rates.latest
            every: 1d
            emits: RatesPublished
        }
    }
}
```

Polls are for feeds that don't depend on your app's data, such as
exchange rates or a price list. To check each of your own records, such
as each parcel still in transit, write a schedule in your app that calls
the package, one event per record. A package can give that as a recipe.

Here a poll of Acme Fuel's price list emits the package's own public
`PricesPublished`, made from its answer. The operation takes nothing and
changes nothing. The connection polls it every six hours instead of once
a day, and the app keeps each change. `fetched_at`, which the event
doesn't carry, never counts as a change. The choices are under "How polls
and `max_concurrent:` run", further down this page.

```jadpo
application {
    connections: {
        main: {
            driver: Driver.postgres
        }
        fuel: {
            package: FuelPrices.v1
            domains: [pricing]
            credential: config.fuel_key
            environment: config.fuel_environment
            max_concurrent: 2
            polls: {
                latest: 6h
            }
        }
    }
    events: {
        store: main
    }
}
```

```jadpo
/// Acme Fuel's price list, which sends no webhooks.
provider {
    protocol: Protocol.json_http
    hosts: {
        live: "https://api.fuel-prices.example"
        sandbox: "https://sandbox.fuel-prices.example"
    }
    credential: CredentialScheme.bearer
    max_concurrent: 4
    polls: {
        latest: {
            call: pump_prices.latest
            every: 1d
            emits: PricesPublished
        }
    }
}
```

```jadpo
/// The day's pump prices, published.
public type PricesPublished = Event {
    region: Text
    diesel_pence: Int
    petrol_pence: Int
}
```

```jadpo
use pricing/actions { keep_prices }
use shared/providers/fuel_prices

/// Keeps each change of price.
subscriber prices_published {
    on: FuelPrices.PricesPublished
    run: keep_prices(event)
}
```

The tick, the runtime's regular check for background work that is due,
calls `pump_prices.latest` through `fuel` with its credential once the
poll is due. It keeps `PricesPublished` and its deliveries only when the
prices changed, and tries a failed call again a minute later. Without the
subscriber the poll never runs. Under `max_concurrent: 1` a call waits for
the connection's slot, and a delivery that would call through it waits in
the outbox.

## Services

A package's own API may be a container Jadpo runs, declared with `runs:`.
The package's actions call it. A service can't call another package or
connection.

```jadpo
// packages/gotenberg/v8/provider.jadpo
/// Gotenberg's HTTP API, run by Jadpo as a container beside the app.
provider {
    protocol: Protocol.multipart_http
    runs: {
        image: "gotenberg/gotenberg:8@sha256:…"
        port: 3000
        asks: ["fonts.googleapis.com"]
    }
}
```

- **Only stateless services.** A service holds no data and has no volume,
  and each app instance runs its own copy, so many servers need nothing
  shared. Anything that holds data (PostgreSQL, Redis) is infrastructure
  the operator provides and the app reaches through a driver connection.
- **A service never starts until approved.** Installing shows the image, its
  digest and the hosts it asks for; the app names it on a connection, and a
  new or changed image needs a person's acceptance in the approval report
  before it runs, so an upgrade cannot swap in a different image unseen. The
  image's digest is written in the package source, which the package's own
  digest pins; an own integration in `shared/providers/` has no lock entry,
  and its image is listed the same way.
- **Outbound network needs the app's permission.** A package only asks
  (`asks:`); the service gets none of those hosts until the connection
  grants them (`outbound: [...]`), and a new grant appears in the approval
  report. Until then `jadpo check` names the hosts asked for and the line
  that grants them.
- **Three settings Jadpo always applies** when it creates a container: no
  outbound network unless granted, never privileged, and no access to the
  host's disk. Jadpo creates the containers itself, so there is no
  configuration to audit.
- **Reaching a service.** A package says only the port its service listens
  on inside its container, so two packages can both use 3000. Jadpo gives
  each app a private network that only it and its services are on, and
  hands each connection the address: the service's name on that network
  when the app runs in a container, or a loopback port the engine picked
  when it runs as a plain process. Nobody writes an address.
- **Jadpo is a client, never a container runtime.** It talks to whatever
  engine the machine has through the Docker-compatible API, which Docker and
  Podman both serve, and leaves pulling, isolation and restart policies to
  it. An app with no service never needs an engine; when one is needed and
  none is found, `jadpo dev` and the deployable say so and how to install
  one.
- **Docker stays behind the scenes.** `jadpo services` lists each service
  (package, image, state, health, CPU and memory, restarts, uptime);
  `jadpo services start`, `stop` and `restart <name>`, `logs <name>`
  (`--follow`, `--since`) and `shell <name>` (development only) manage them,
  each with `--json`. In production the commands talk to the running app,
  which supervises its services. A service's output joins the app's
  OpenTelemetry logs under its name, and a call to it appears in the
  request's trace as a provider call.
- **Where services run.** `jadpo dev` starts the declared services, and
  tests keep using fakes. `jadpo build` writes the configuration that runs
  each one beside the app, Compose first. The native deployable supervises
  its services itself. On Workers, which runs no containers, a service is
  hosted elsewhere and the connection gives `host:`.

## Recipes

A package may ship `recipes/`: complete wiring examples (schedules, actions,
entities) that `jadpo package recipe <package> <recipe> --into <domain>`
copies into a domain, after which they are the app's own code. Each is
compiled and tested against the package's `sample/` app at publish and
listed in the surface. Library packages may ship recipes too.

> `jadpo package check`'s `recipes` row compiles each recipe on its own,
> copied into the one domain of the package's `sample/` app as `jadpo
> package recipe` copies it, the package placed as the sample's own
> integration; a recipe that doesn't compile is shown at its own path,
> `recipes/<recipe>/...`, and a package with recipes and no such sample
> is `publish.sample_missing`. The choices are under "Package
> subscribers and recipes in `sample/`".

Not built yet: services run by Jadpo (`runs:`), and running a recipe's
tests in `sample/`.
