# SDK

`@pairx/apps-sdk` — тонкая типизированная обёртка над [мостом](https://dev.pairx.org/reference/bridge) для проектов со сборкой. Без зависимостей, ESM, TypeScript. Всё, что делает SDK, можно сделать и прямыми вызовами `pairx.call` — SDK избавляет от рутины: порядка `seq`, `storage.reset`, повторов при `conflict`.

```sh
npm install https://dev.pairx.org/download/pairx-apps-sdk-0.1.0.tgz
```

Приложение со сборкой отправляется в Pairx одним HTML-файлом: соберите проект в один файл (для Vite — `vite-plugin-singlefile`).

## Пример: очередь

```ts
import { app, openStorage, ui } from "@pairx/apps-sdk";

const storage = await openStorage();
const queue = storage.log<string>("queue");

function render() {
  const people = queue.entries();
  list.textContent = people.map((e, i) => `${i + 1}. ${e.user === storage.me ? "Вы" : "Участник"}`).join("\n");
  ui.mainButton.setText(queue.mine().length ? "Выйти из очереди" : "Встать в очередь");
}

ui.mainButton.show("Встать в очередь", async () => {
  const mine = queue.mine()[0];
  ui.mainButton.setProgress(true);
  try { mine ? await queue.retract(mine.entry) : await queue.append("join"); }
  finally { ui.mainButton.setProgress(false); }
});
storage.subscribe(render);
render();
await app.ready();
```

## Приложение и человек

| API | Что делает |
| --- | --- |
| `app.ready()` | Убрать заставку Pairx. |
| `app.close()` | Закрыть приложение. |
| `app.launch()` | [Параметры запуска](https://dev.pairx.org/reference/bridge#launch): приложение, контекст, версия, токен. |
| `user()` | `{ id, locale }` — человек внутри этого приложения. |
| `context()` | `{ kind, preview }` — где открыто приложение. |
| `inPairx()` | Запущено ли внутри Pairx (или эмулятора) — для тестов и сборки. |
| `call(method, params)`, `on(event, cb)` | Прямой доступ к мосту с типами. |

## Хранилище

`openStorage()` читает снимок и подписывается на ленту изменений; дальше состояние всегда текущее.

| API | Что делает |
| --- | --- |
| `storage.me` | ID текущего человека. |
| `storage.subscribe(cb)` | Вызывается после каждого применённого изменения и после `storage.reset`; возвращает отписку. |
| `storage.reload()` | Перечитать снимок. |
| `storage.log(name)` | `entries()`, `all()` (вместе с убранными), `mine()`, `append(value)`, `retract(entry)`. |
| `storage.slot(name)` | `get(user?)`, `values()`, `set(value)`, `clear()`. |
| `storage.doc(name)` | `value`, `version`, `set(value, version?)`, `update(fn)` — сам повторяет при `conflict`. |
| `storage.counter(name)` | `total(key)`, `mine(key)`, `totals()`, `add(key, amount)`. |

```ts
const board = storage.doc<{ items: string[] }>("board");
await board.update((current) => ({ items: [...(current?.items ?? []), "Новый пункт"] }));
```

## Интерфейс

| API | Что делает |
| --- | --- |
| `ui.mainButton.show(text, onClick)` | Показать нативную кнопку; возвращает функцию, которая снимает этот обработчик. |
| `ui.mainButton.setText`, `setEnabled`, `setProgress`, `hide`, `onClick` | Управление кнопкой. |
| `ui.backButton.show(onClick)`, `hide()`, `onClick` | «Назад» в шапке Pairx. |
| `ui.theme()`, `ui.onTheme(cb)` | Тема Pairx и её смена. |
| `ui.applyTheme(theme)` | Ставит CSS-переменные `--pairx-accent`, `--pairx-background`, `--pairx-surface`, `--pairx-text`, `--pairx-muted` и `color-scheme`. |
| `ui.popup(message, buttons)`, `ui.toast(text)` | Окна и подсказки Pairx. |
| `clipboard.writeText(text)` | Копирование по нажатию человека. |

## Ошибки

Методы SDK бросают `PairxError` с полем `code` — те же [коды моста](https://dev.pairx.org/reference/bridge#errors):

```ts
import { PairxError } from "@pairx/apps-sdk";

try { await queue.append("join"); }
catch (error) {
  if (error instanceof PairxError && error.code === "rate_limited") ui.toast("Слишком часто");
}
```

## Тесты приложения

`inPairx()` отвечает `false` вне Pairx: логику приложения можно тестировать без моста, подставив свой `window.pairx`. Сами тесты SDK идут против хозяина Pairx в памяти с правилами хранилища сервиса.
