# Code in a site

A site is files by default. A site can also run code: an Astro action, an
API route, or a page rendered on demand. Pages stay files, and a page view
runs no code.

The plan must allow code. Until it does, a publish with code is refused.

## Turn it on

Add the adapter to `astro.config.mjs`. It is already installed.

```js
import cloudflare from '@astrojs/cloudflare';

export default defineConfig({
  adapter: cloudflare(),
  // ...
});
```

Do not use `Astro.session`. It needs a binding the site does not get.

## What the code gets

| In `env` | Is |
|---|---|
| `DB` | the site's [store](/api/store), when the site has migrations |
| `CLAVE_API`, `CLAVE_SITE_KEY` | the [runtime API](/api/runtime); a draft gets a key of its own |
| `CLAVE_ENV` | `live` or `draft` |
| your own names | the site's [secrets](/api/secrets); the draft has values of its own |

A request from the code goes out only to hosts on the site's
egress list (`egress` in [`clave.site.json`](/concepts/settings)), and to
the runtime API. Each call has limits:
50 ms of CPU time and 50 requests.

## clave.site.json

A file named `clave.site.json` at the root of the repository holds the
site's [settings](/concepts/settings). Three of its keys say what the code
asks of the platform: `events`, `schedules` and `private`. `egress` is the
list of hosts the code may call.

```json
{
  "events": ["payment.settled", "payment.refunded"],
  "schedules": [{ "name": "reminders", "cron": "0 9 * * *" }],
  "private": ["/staff/", { "path": "/admin/", "roles": ["owner"], "labels": ["admin"] }]
}
```

A fault in it stops the publish, and the message says which part.

### Events

The platform sends `POST /clave/events` to the live site for each event it
asked for. The body is:

```json
{ "id": "evt_...", "kind": "payment.settled", "created_at": "...", "data": { "payment_id": "pay_...", "reference": "order_123", "amount_minor": 2500, "currency": "eur", "late": false } }
```

The events today:

| Kind | `data` |
|---|---|
| `payment.settled` | `payment_id`, `reference`, `currency`, `amount_minor`, `late` |
| `payment.refunded` | `payment_id`, `reference`, `currency`, `amount_minor`, `refunded_minor` |

`reference` is the site's own, and is empty when the site gave none.
`late` is true when the money arrived after the hold ran out: the site's
code decides what that means. `refunded_minor` is the total given back so
far, not the amount of this one refund. It equals `amount_minor` when all
of it went back.

Answer with a 2xx only when the event is handled. A 2xx is final: an
event answered with one is never sent again, even when the code ignored
it. Any other answer, a redirect included, or no answer in 30 seconds, is
sent again. The wait starts at 30 seconds and doubles to at most one hour,
for 24 hours. An event can arrive twice, so keep the ids you handled in
the store.

Write the page at `src/pages/clave/events.ts`. Only the platform can call
it: a request to `/clave/` without the platform's signature gets a 404.
The platform checks the signature and takes the header off, so the page
never sees it and has no need to check it.

### Schedules

The platform sends `POST /clave/schedules/{name}` when the cron
expression is due. The draft keeps its own timers, from the
`clave.site.json` it was built with, and they go to the draft's address. Five fields, in UTC. The shortest interval is 15
minutes. A cron in `wrangler.jsonc` does nothing.

### Private paths

A path in `private` is for the site's staff, who sign in with Clave. A
draft guards its own private paths the same way, on its own address. The
site's code then gets a `Clave-Identity` on every request. See
[Staff](/api/staff).

A path is a string, or `{ "path": ..., "roles": [...], "labels": [...] }`.
A string opens for anyone with a role on the site. With `roles` or
`labels`, a person needs one of those roles (`owner`, `collaborator`,
`staff`) or one of those labels, from the staff list. When two paths
match, such as `/admin/` and `/admin/money/`, the person must pass both.
At most 5 paths, and 5 labels for each.

## Logs

[Logs](/api/logs) answers with what the code logged and threw in the last
hour.

## Restore

Restoring an old version brings back code and not data. The store stays as
it is, so code must not assume the schema it had yesterday.

---

## Where this page sits

This is one page of the Clave documentation.
Read it as a web page at https://docs.heyclave.com/concepts/code.
Every page has a `.md` address, and https://docs.heyclave.com/llms.txt lists them all.

- Previous: [Analytics](https://docs.heyclave.com/concepts/analytics.md)
- Next: [Limits](https://docs.heyclave.com/concepts/limits.md)
- Index: [Overview](https://docs.heyclave.com/index.md)
