# Runtime API

> Let HTML artifacts save and read data with window.openArtifacts.

An HTML artifact can keep data with `window.openArtifacts`. Polls, trackers, and checklists all work this way. The data belongs to the artifact, not a version, so a new version sees the same data.

## Ready and theme

`openArtifacts.ready` is a promise that resolves once the viewer shell has connected; wait for it before your first call. In the raw view, with no shell, it resolves at once.

`openArtifacts.theme` is `"light"` or `"dark"`. It starts from the viewer's system setting and follows the shell's theme once it connects. `onTheme(callback)` calls back whenever it changes and returns a function that stops listening.

```js
await openArtifacts.ready;
document.documentElement.dataset.theme = openArtifacts.theme;
openArtifacts.onTheme((t) => (document.documentElement.dataset.theme = t));
```

## Capabilities

An artifact declares what it uses when it's published, for example `capabilities: { db: true, viewer: true }`. Calls to anything it didn't declare are rejected.

| Capability | Gives the page |
|---|---|
| `db` | Shared data that every viewer sees |
| `viewer` | Who is viewing |
| `storage` | Private data for each viewer |

## Shared data: `db`

`db.collection(name)` returns a collection with:

- `get(id)` to read one document, or `null`
- `list({ cursor, limit })` and `query(filter)` to read many. Each returns a page, `{ items, nextCursor }`; pass `nextCursor` back as `cursor` for the next page.
- `set(id, doc)`, `update(id, patch)`, and `delete(id)` to write
- `subscribe(callback)` for live updates when anyone changes the collection. It returns a function that unsubscribes.

Each document is `{ id, data, updatedAt, updatedBy }`, with your value in `data`.

Each `subscribe` callback gets `{ docs, deleted }`: the documents that changed and the ids of those deleted. If the live connection drops and reconnects, changes may have been missed, so the callback gets `{ docs: [], deleted: [], resync: true }` instead. On `resync`, refetch with `list()` or `query()`.

```js
const votes = openArtifacts.db.collection("votes");
const all = new Map();
const refetch = async () => {
  all.clear();
  for (const doc of (await votes.list()).items) all.set(doc.id, doc);
  render([...all.values()]);
};
votes.subscribe((e) => {
  if (e.resync) return refetch();
  for (const doc of e.docs) all.set(doc.id, doc);
  for (const id of e.deleted) all.delete(id);
  render([...all.values()]);
});
await refetch();
await votes.set(crypto.randomUUID(), { option: "Lisbon" });
```

## The viewer: `viewer`

`viewer.get()` returns `{ email, name }`, or `null` for an anonymous viewer of a public artifact.

## Per-viewer data: `storage`

`storage` keeps documents keyed to the current viewer, such as which boxes they ticked, with `get(key)`, `set(key, value)`, `delete(key)`, and `list()`, which returns the viewer's keys.

## Who can write

- Signed-in viewers who can see the artifact can read and write.
- Anonymous viewers of a public artifact can read. They can write only if the publisher turned on `allowAnonymousWrites`, and those writes are rate-limited per IP address.

## Limits

| Limit | Default |
|---|---|
| Document size | 1 MB |
| Data per artifact | 50 MB |
| Writes per viewer | 100 per minute |
| Anonymous writes per IP | 30 per minute |

## How it works

The artifact runs in a sandboxed iframe with no credentials. Each call goes by `postMessage` to the viewer shell around it, which checks the call and makes it with the viewer's session. Raw view drops the shell, so the runtime API isn't available there.
