itd-api 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1513,7 +1513,7 @@ interface RequestOptions {
1513
1513
  timeout?: number | undefined;
1514
1514
  /** Дополнительные заголовки. */
1515
1515
  headers?: Record<string, string> | undefined;
1516
- /** Повторы только для этого запроса. */
1516
+ /** Повторы только для этого запроса. Переопределяют глобальную настройку `retry`. */
1517
1517
  retry?: RetryOptions | false | undefined;
1518
1518
  }
1519
1519
  /** Полное описание запроса для низкоуровневого `itd.request()`. */
@@ -1540,14 +1540,14 @@ interface RawRequestOptions extends RequestOptions {
1540
1540
  raw?: boolean | undefined;
1541
1541
  }
1542
1542
 
1543
+ /** Версия библиотеки. Попадает в `User-Agent`. */
1544
+ declare const LIBRARY_VERSION = "0.0.8";
1545
+
1543
1546
  /** Базовый URL API итд.com. Домен записан в punycode: `итд.com`. */
1544
1547
  declare const DEFAULT_BASE_URL = "https://xn--d1ah4a.com";
1545
1548
  /** Таймаут запроса по умолчанию. Столько же использует официальный клиент итд.com. */
1546
1549
  declare const DEFAULT_TIMEOUT = 30000;
1547
- /**
1548
- * Версия библиотеки — попадает в `User-Agent`.
1549
- */
1550
- declare const LIBRARY_VERSION = "0.0.7";
1550
+
1551
1551
  /**
1552
1552
  * `User-Agent` по умолчанию.
1553
1553
  *
@@ -1558,51 +1558,22 @@ declare const LIBRARY_VERSION = "0.0.7";
1558
1558
  * В браузере заголовок не выставляется — `User-Agent` там запрещён к изменению, и среда
1559
1559
  * молча его игнорирует.
1560
1560
  */
1561
- declare const DEFAULT_USER_AGENT = "Mozilla/5.0 (compatible; itd-api/0.0.7; +https://github.com/KiowDev/itd-api)";
1561
+ declare const DEFAULT_USER_AGENT = "Mozilla/5.0 (compatible; itd-api/0.0.8; +https://github.com/KiowDev/itd-api)";
1562
1562
  /**
1563
- * Настройки повторов со всеми значениями по умолчанию.
1563
+ * Срез конфигурации, нужный слою авторизации.
1564
1564
  *
1565
- * Поля перечислены явно, а не через `Required<RetryOptions>`: тот снимает необязательность,
1566
- * но оставляет `| undefined` в типе значения, раз оно указано в исходном интерфейсе.
1565
+ * Выделен явно, чтобы {@link AuthManager} не получал целиком `ResolvedConfig` со всеми
1566
+ * настройками транспорта, повторов и очереди, к которым он отношения не имеет.
1567
+ * `ResolvedConfig` содержит все эти поля, поэтому подходит везде, где ждут `AuthConfig`.
1567
1568
  */
1568
- interface ResolvedRetryOptions {
1569
- attempts: number;
1570
- baseDelay: number;
1571
- maxDelay: number;
1572
- jitter: number;
1573
- retryWrites: boolean;
1574
- shouldRetry: ((error: unknown, attempt: number) => boolean) | undefined;
1575
- }
1576
- /** Настройки очереди со всеми значениями по умолчанию. */
1577
- interface ResolvedRateLimitOptions {
1578
- concurrency: number;
1579
- rps: number | undefined;
1580
- retryDelays: readonly number[];
1581
- respectHeaders: boolean;
1582
- }
1583
- /** Конфигурация клиента после подстановки значений по умолчанию и проверок. */
1584
- interface ResolvedConfig {
1569
+ interface AuthConfig {
1585
1570
  baseUrl: string;
1586
1571
  auth: AuthInput | undefined;
1587
1572
  storage: TokenStorage;
1588
- autoRefresh: boolean;
1573
+ useCookieJar: boolean;
1574
+ deviceId: string | undefined;
1589
1575
  reloginOnRefreshFailure: boolean;
1590
- fetch: typeof fetch;
1591
- timeout: number;
1592
- retry: ResolvedRetryOptions | undefined;
1593
- rateLimit: ResolvedRateLimitOptions | undefined;
1594
- hooks: ClientHooks;
1595
1576
  logger: Logger | undefined;
1596
- headers: Record<string, string>;
1597
- /** Значение заголовка `X-Device-Id`, если задано вручную. Иначе заводится само. */
1598
- deviceId: string | undefined;
1599
- /** Значение заголовка `User-Agent`. `undefined` — заголовок не выставляется. */
1600
- userAgent: string | undefined;
1601
- mode: RuntimeMode;
1602
- /** Вести ли собственный cookie-jar (вне браузера и React Native). */
1603
- useCookieJar: boolean;
1604
- /** Отправлять ли `credentials: 'include'` (в браузере). */
1605
- sendCredentials: boolean;
1606
1577
  }
1607
1578
 
1608
1579
  /** Имя cookie-флага «есть refresh-сессия». Ставится сайтом итд.com рядом с refresh-токеном. */
@@ -1719,190 +1690,31 @@ declare class Emitter<Events> {
1719
1690
  }
1720
1691
 
1721
1692
  /**
1722
- * Обёртка вокруг запроса.
1723
- *
1724
- * Получает описание запроса и продолжение цепочки. Может изменить запрос перед отправкой,
1725
- * посмотреть и подменить разобранный ответ или вовсе не вызывать `next` и вернуть своё.
1726
- *
1727
- * @param request что уходит на сервер; изменять сам объект не нужно — передайте копию в `next`
1728
- * @param next продолжение: либо следующая обёртка, либо настоящий запрос
1729
- * @returns тело ответа в том виде, в каком его получит вызывающий код
1730
- *
1731
- * @example Дописать заголовок ко всем запросам
1732
- * ```ts
1733
- * const transformer: Transformer = (request, next) =>
1734
- * next({ ...request, headers: { ...request.headers, 'X-Trace': trace() } });
1735
- * ```
1736
- */
1737
- type Transformer = (request: RawRequestOptions, next: (request: RawRequestOptions) => Promise<unknown>) => Promise<unknown>;
1738
- /** Что плагин получает при подключении. */
1739
- interface PluginContext {
1740
- /** Базовый URL клиента — например чтобы разобрать абсолютные ссылки из ответа. */
1741
- baseUrl: string;
1742
- /** Отладочный вывод клиента, если он включён. */
1743
- logger: Logger | undefined;
1744
- /** Добавляет обёртку запроса. Подключённые раньше оказываются снаружи. */
1745
- use(transformer: Transformer): void;
1746
- }
1747
- /**
1748
- * Плагин клиента.
1749
- *
1750
- * Подключается через `itd.use(plugin)` и работает на уровне транспорта: видит запрос
1751
- * до отправки и разобранный ответ. Библиотека не знает, что именно делает плагин, —
1752
- * ей достаточно списка обёрток и имён опций, которые он читает.
1753
- *
1754
- * @example
1755
- * ```ts
1756
- * const logging: ItdPlugin = {
1757
- * name: 'logging',
1758
- * install({ use, logger }) {
1759
- * use(async (request, next) => {
1760
- * logger?.info(`${request.method} ${request.path}`);
1761
- * return next(request);
1762
- * });
1763
- * },
1764
- * };
1765
- *
1766
- * itd.use(logging);
1767
- * ```
1768
- */
1769
- interface ItdPlugin {
1770
- /** Имя плагина. Должно быть уникальным: повторное подключение — ошибка. */
1771
- name: string;
1772
- /**
1773
- * Имена опций запроса, которые плагин читает у методов ресурсов.
1774
- *
1775
- * Библиотека этих опций не понимает и ничего с ними не делает — только доносит
1776
- * от вызова метода до обёртки нетронутыми. Без такого списка чужие поля отсеиваются,
1777
- * чтобы случайная опечатка в параметрах не уезжала на сервер.
1778
- *
1779
- * Имена полей самого запроса (`path`, `body`, `headers`, `signal` и прочие из
1780
- * `RawRequestOptions`) заявить нельзя: подключение такого плагина завершится ошибкой.
1781
- *
1782
- * Типы для них плагин объявляет сам, дополняя `RequestOptions`:
1783
- * ```ts
1784
- * declare module 'itd-api' {
1785
- * interface RequestOptions { encrypt?: string | undefined }
1786
- * }
1787
- * ```
1788
- */
1789
- optionKeys?: readonly string[];
1790
- /** Вызывается один раз при подключении. */
1791
- install(context: PluginContext): void;
1792
- }
1793
- /**
1794
- * Список подключённых плагинов и собранная из них цепочка обёрток.
1795
- *
1796
- * Живёт в клиенте, а работает в транспорте: {@link HttpClient} прогоняет через `run`
1797
- * каждый запрос, если плагины есть.
1798
- */
1799
- declare class PluginRegistry {
1800
- #private;
1801
- /** Сколько обёрток подключено. Ноль означает, что запрос идёт прежним путём. */
1802
- get size(): number;
1803
- /** Имена опций запроса, заявленные плагинами. */
1804
- get optionKeys(): ReadonlySet<string>;
1805
- /**
1806
- * Подключает плагин.
1807
- *
1808
- * @throws {ItdConfigError} если плагин задан неверно, уже подключён или заявил занятое
1809
- * имя опции
1810
- */
1811
- add(plugin: ItdPlugin, context: Omit<PluginContext, 'use'>): void;
1812
- /**
1813
- * Прогоняет запрос через цепочку обёрток.
1814
- *
1815
- * Цепочка собирается на каждый запрос заново: плагин можно подключить в любой момент,
1816
- * а обёрток единицы — экономить тут не на чем.
1817
- *
1818
- * @param execute настоящий запрос, вызывается самой внутренней обёрткой
1819
- */
1820
- run(request: RawRequestOptions, execute: (request: RawRequestOptions) => Promise<unknown>): Promise<unknown>;
1821
- }
1822
-
1823
- /**
1824
- * Подключаемые части конвейера.
1825
- *
1826
- * Авторизация, cookie, очередь и повторы живут в отдельных модулях и подставляются сюда.
1827
- * Благодаря этому транспорт тестируется изолированно, а `HttpClient` ничего не знает
1828
- * о том, как именно добывается токен.
1829
- */
1830
- interface HttpCollaborators {
1831
- /** Заголовки авторизации для запроса. Вызывается перед каждой попыткой. */
1832
- getAuthHeaders?(): Promise<Record<string, string>> | Record<string, string>;
1833
- /**
1834
- * Реакция на ответ `401`.
1835
- *
1836
- * Должна вернуть `true`, если токен обновлён и запрос имеет смысл повторить.
1837
- * Повтор выполняется ровно один раз.
1838
- */
1839
- onUnauthorized?(): Promise<boolean>;
1840
- /**
1841
- * Идентификатор устройства для заголовка `X-Device-Id`.
1842
- *
1843
- * Отдельно от {@link getAuthHeaders}, потому что нужен и на анонимных запросах —
1844
- * например на `sign-in`, где заголовка авторизации ещё нет.
1845
- */
1846
- getDeviceId?(): Promise<string> | string;
1847
- /** Значение заголовка `Cookie` для указанного URL. */
1848
- getCookieHeader?(url: string): string | undefined;
1849
- /** Приём `Set-Cookie` из ответа. */
1850
- saveCookies?(url: string, response: Response): void;
1851
- /** Очередь запросов: ограничение конкурентности и частоты. */
1852
- schedule?<T>(task: () => Promise<T>): Promise<T>;
1853
- /**
1854
- * Сообщает об остатке лимита из заголовков ответа.
1855
- *
1856
- * Вызывается после **каждого** ответа, включая ошибочные, — так очередь узнаёт
1857
- * об исчерпании лимита заранее и успевает притормозить до отказа сервера.
1858
- */
1859
- onRateLimit?(limit: number | undefined, remaining: number | undefined): void;
1860
- /**
1861
- * Планировщик повторов.
1862
- *
1863
- * Возвращает паузу в мс перед следующей попыткой либо `undefined`, если повторять не нужно.
1864
- */
1865
- nextRetryDelay?(error: unknown, attempt: number, method: string): number | undefined;
1866
- }
1867
- /**
1868
- * Транспортный слой: единственное место, откуда библиотека ходит в сеть.
1693
+ * Описание запроса внутри конвейера.
1869
1694
  *
1870
- * Отвечает за сборку URL, заголовки, таймауты, разбор ответа и превращение любой неудачи
1871
- * в типизированную ошибку. Авторизация, cookie, очередь и повторы подключаются извне
1872
- * через {@link HttpCollaborators}.
1695
+ * Отличается от публичного {@link RawRequestOptions} одним служебным полем: слои конвейера
1696
+ * должны уметь дописать заголовки так, чтобы пользовательские `headers` всё равно остались
1697
+ * важнее. Смешивать их в одном объекте нельзя — тогда слой авторизации перебивал бы
1698
+ * `Authorization`, заданный вызывающим кодом вручную.
1873
1699
  */
1874
- declare class HttpClient {
1875
- #private;
1876
- constructor(config: ResolvedConfig, collaborators?: HttpCollaborators);
1877
- /** Базовый URL, к которому обращается клиент. */
1878
- get baseUrl(): string;
1700
+ interface PipelineRequest extends RawRequestOptions {
1879
1701
  /**
1880
- * Имена опций запроса, заявленные плагинами.
1702
+ * Заголовки, добавленные слоями конвейера.
1881
1703
  *
1882
- * Читается ресурсами: они переносят в транспорт только известные поля, а чужие,
1883
- * если их никто не заявил, отсеивают.
1884
- */
1885
- get pluginOptionKeys(): ReadonlySet<string>;
1886
- /** Подключает список плагинов. Реестр общий с клиентом и пополняется через `itd.use()`. */
1887
- usePlugins(plugins: PluginRegistry): void;
1888
- /**
1889
- * Подключает недостающие части конвейера.
1704
+ * Ставятся до пользовательских `headers` и потому могут быть ими переопределены.
1890
1705
  *
1891
- * Нужно из-за кольцевой зависимости: слой авторизации сам выполняет запросы, поэтому
1892
- * не может быть передан в конструктор до создания транспорта.
1706
+ * @internal
1893
1707
  */
1894
- setCollaborators(collaborators: HttpCollaborators): void;
1708
+ layerHeaders?: Record<string, string> | undefined;
1895
1709
  /**
1896
- * Выполняет запрос к API.
1710
+ * Номер попытки, начиная с 1. Проставляет слой повторов, читают хуки.
1897
1711
  *
1898
- * @typeParam T ожидаемая форма ответа после снятия обёртки `{ data: … }`
1899
- * @throws {ItdApiError} если сервер ответил статусом ≥ 400
1900
- * @throws {ItdTimeoutError} если истёк таймаут
1901
- * @throws {ItdAbortError} если запрос отменён через `signal`
1902
- * @throws {ItdNetworkError} если запрос не дошёл до сервера
1712
+ * @internal
1903
1713
  */
1904
- request<T = unknown>(options: RawRequestOptions): Promise<T>;
1714
+ attempt?: number | undefined;
1905
1715
  }
1716
+ /** Обработчик запроса. Самый внутренний в цепочке — транспорт. */
1717
+ type RequestHandler = (request: PipelineRequest) => Promise<unknown>;
1906
1718
 
1907
1719
  /** Пути авторизации. Вынесены, чтобы не расходиться между модулями. */
1908
1720
  declare const AUTH_PATHS: {
@@ -1953,7 +1765,7 @@ interface AuthEvents {
1953
1765
  */
1954
1766
  declare class AuthManager {
1955
1767
  #private;
1956
- constructor(config: ResolvedConfig, http: HttpClient, jar: CookieJar);
1768
+ constructor(config: AuthConfig, send: RequestHandler, jar: CookieJar);
1957
1769
  /** Подписка на события авторизации. */
1958
1770
  get on(): Emitter<AuthEvents>['on'];
1959
1771
  /** Подписка на одно срабатывание. */
@@ -2015,6 +1827,108 @@ declare class AuthManager {
2015
1827
  clear(): Promise<void>;
2016
1828
  }
2017
1829
 
1830
+ /**
1831
+ * Обёртка вокруг запроса.
1832
+ *
1833
+ * Получает описание запроса и продолжение цепочки. Может изменить запрос перед отправкой,
1834
+ * посмотреть и подменить разобранный ответ или вовсе не вызывать `next` и вернуть своё.
1835
+ *
1836
+ * @param request что уходит на сервер; изменять сам объект не нужно — передайте копию в `next`
1837
+ * @param next продолжение: либо следующая обёртка, либо настоящий запрос
1838
+ * @returns тело ответа в том виде, в каком его получит вызывающий код
1839
+ *
1840
+ * @example Дописать заголовок ко всем запросам
1841
+ * ```ts
1842
+ * const transformer: Transformer = (request, next) =>
1843
+ * next({ ...request, headers: { ...request.headers, 'X-Trace': trace() } });
1844
+ * ```
1845
+ */
1846
+ type Transformer = (request: RawRequestOptions, next: (request: RawRequestOptions) => Promise<unknown>) => Promise<unknown>;
1847
+ /** Что плагин получает при подключении. */
1848
+ interface PluginContext {
1849
+ /** Базовый URL клиента — например чтобы разобрать абсолютные ссылки из ответа. */
1850
+ baseUrl: string;
1851
+ /** Отладочный вывод клиента, если он включён. */
1852
+ logger: Logger | undefined;
1853
+ /** Добавляет обёртку запроса. Подключённые раньше оказываются снаружи. */
1854
+ use(transformer: Transformer): void;
1855
+ }
1856
+ /**
1857
+ * Плагин клиента.
1858
+ *
1859
+ * Подключается через `itd.use(plugin)` и работает на уровне транспорта: видит запрос
1860
+ * до отправки и разобранный ответ. Библиотека не знает, что именно делает плагин, —
1861
+ * ей достаточно списка обёрток и имён опций, которые он читает.
1862
+ *
1863
+ * @example
1864
+ * ```ts
1865
+ * const logging: ItdPlugin = {
1866
+ * name: 'logging',
1867
+ * install({ use, logger }) {
1868
+ * use(async (request, next) => {
1869
+ * logger?.info(`${request.method} ${request.path}`);
1870
+ * return next(request);
1871
+ * });
1872
+ * },
1873
+ * };
1874
+ *
1875
+ * itd.use(logging);
1876
+ * ```
1877
+ */
1878
+ interface ItdPlugin {
1879
+ /** Имя плагина. Должно быть уникальным: повторное подключение — ошибка. */
1880
+ name: string;
1881
+ /**
1882
+ * Имена опций запроса, которые плагин читает у методов ресурсов.
1883
+ *
1884
+ * Библиотека этих опций не понимает и ничего с ними не делает — только доносит
1885
+ * от вызова метода до обёртки нетронутыми. Без такого списка чужие поля отсеиваются,
1886
+ * чтобы случайная опечатка в параметрах не уезжала на сервер.
1887
+ *
1888
+ * Имена полей самого запроса (`path`, `body`, `headers`, `signal` и прочие из
1889
+ * `RawRequestOptions`) заявить нельзя: подключение такого плагина завершится ошибкой.
1890
+ *
1891
+ * Типы для них плагин объявляет сам, дополняя `RequestOptions`:
1892
+ * ```ts
1893
+ * declare module 'itd-api' {
1894
+ * interface RequestOptions { encrypt?: string | undefined }
1895
+ * }
1896
+ * ```
1897
+ */
1898
+ optionKeys?: readonly string[];
1899
+ /** Вызывается один раз при подключении. */
1900
+ install(context: PluginContext): void;
1901
+ }
1902
+ /**
1903
+ * Список подключённых плагинов и собранная из них цепочка обёрток.
1904
+ *
1905
+ * Живёт в клиенте, а работает в транспорте: {@link HttpClient} прогоняет через `run`
1906
+ * каждый запрос, если плагины есть.
1907
+ */
1908
+ declare class PluginRegistry {
1909
+ #private;
1910
+ /** Сколько обёрток подключено. Ноль означает, что запрос идёт прежним путём. */
1911
+ get size(): number;
1912
+ /** Имена опций запроса, заявленные плагинами. */
1913
+ get optionKeys(): ReadonlySet<string>;
1914
+ /**
1915
+ * Подключает плагин.
1916
+ *
1917
+ * @throws {ItdConfigError} если плагин задан неверно, уже подключён или заявил занятое
1918
+ * имя опции
1919
+ */
1920
+ add(plugin: ItdPlugin, context: Omit<PluginContext, 'use'>): void;
1921
+ /**
1922
+ * Прогоняет запрос через цепочку обёрток.
1923
+ *
1924
+ * Цепочка собирается на каждый запрос заново: плагин можно подключить в любой момент,
1925
+ * а обёрток единицы — экономить тут не на чем.
1926
+ *
1927
+ * @param execute настоящий запрос, вызывается самой внутренней обёрткой
1928
+ */
1929
+ run(request: RawRequestOptions, execute: (request: RawRequestOptions) => Promise<unknown>): Promise<unknown>;
1930
+ }
1931
+
2018
1932
  /** Событие потока уведомлений после разбора. */
2019
1933
  interface NotificationEvent {
2020
1934
  /** Само уведомление в единой форме. */
@@ -2240,6 +2154,8 @@ interface RealtimeDeps {
2240
2154
  refresh: () => Promise<boolean>;
2241
2155
  /** Загружает начальное число непрочитанных. */
2242
2156
  fetchUnreadCount: () => Promise<number>;
2157
+ /** Вызывается при явном закрытии потока. */
2158
+ onClose?: (() => void) | undefined;
2243
2159
  logger?: Logger | undefined;
2244
2160
  }
2245
2161
  /**
@@ -2288,6 +2204,45 @@ declare class ItdRealtime {
2288
2204
  removeAllListeners(): void;
2289
2205
  }
2290
2206
 
2207
+ /** Что нужно фасаду для работы. */
2208
+ interface HttpClientDeps {
2209
+ /** Готовый обработчик — вся цепочка слоёв поверх транспорта. */
2210
+ handler: RequestHandler;
2211
+ /** Реестр плагинов: у него ресурсы спрашивают имена заявленных опций. */
2212
+ plugins: PluginRegistry;
2213
+ baseUrl: string;
2214
+ }
2215
+ /**
2216
+ * Точка входа ресурсов в конвейер запросов.
2217
+ *
2218
+ * Принимает готовый обработчик — цепочку слоёв поверх транспорта, собранную
2219
+ * в {@link ItdClient}, — и отдаёт ресурсам метод `request` и имена опций плагинов.
2220
+ * О слоях и их порядке ресурсы не знают.
2221
+ */
2222
+ declare class HttpClient {
2223
+ #private;
2224
+ constructor(deps: HttpClientDeps);
2225
+ /** Базовый URL, к которому обращается клиент. */
2226
+ get baseUrl(): string;
2227
+ /**
2228
+ * Имена опций запроса, заявленные плагинами.
2229
+ *
2230
+ * Читается ресурсами: они переносят в транспорт только известные поля, а чужие,
2231
+ * если их никто не заявил, отсеивают.
2232
+ */
2233
+ get pluginOptionKeys(): ReadonlySet<string>;
2234
+ /**
2235
+ * Выполняет запрос к API через собранный конвейер.
2236
+ *
2237
+ * @typeParam T ожидаемая форма ответа после снятия обёртки `{ data: … }`
2238
+ * @throws {ItdApiError} если сервер ответил статусом ≥ 400
2239
+ * @throws {ItdTimeoutError} если истёк таймаут
2240
+ * @throws {ItdAbortError} если запрос отменён через `signal`
2241
+ * @throws {ItdNetworkError} если запрос не дошёл до сервера
2242
+ */
2243
+ request<T = unknown>(options: RawRequestOptions): Promise<T>;
2244
+ }
2245
+
2291
2246
  /**
2292
2247
  * Страница списка — единая форма для всех трёх схем пагинации API.
2293
2248
  *
@@ -2410,17 +2365,50 @@ declare class Paginator<T> implements AsyncIterable<T> {
2410
2365
  collect(max?: number): Promise<T[]>;
2411
2366
  }
2412
2367
 
2368
+ /** Параметры перебираемого списка: опции запроса плюс предел числа страниц. */
2369
+ interface ListParams extends RequestOptions {
2370
+ /** Ограничение числа страниц при переборе. */
2371
+ maxPages?: number | undefined;
2372
+ }
2373
+ /**
2374
+ * Описание перебираемого эндпоинта.
2375
+ *
2376
+ * Одно место, где заданы путь, параметры запроса, чтение страницы и схема пагинации.
2377
+ * {@link BaseResource.paginated} строит из него и разовую загрузку, и перебор.
2378
+ *
2379
+ * @typeParam T тип элемента списка
2380
+ * @typeParam P тип параметров метода
2381
+ */
2382
+ interface ListingSpec<T, P extends ListParams> {
2383
+ /** Путь эндпоинта. */
2384
+ path: (params: P) => string;
2385
+ /** Параметры запроса без полей пагинации — их добавит перебор. */
2386
+ query: (params: P) => QueryParams;
2387
+ /** Читает страницу из ответа. Получает позицию — она нужна схеме со смещением. */
2388
+ read: (body: unknown, state: PageState) => Page<T>;
2389
+ /** Схема пагинации эндпоинта. */
2390
+ mode: PaginationMode;
2391
+ /** Начальная позиция, вычисленная из параметров (курсор, номер или смещение). */
2392
+ start: (params: P) => PageState;
2393
+ }
2394
+ /** Пара методов, собранная из {@link ListingSpec}: разовая загрузка и перебор. */
2395
+ interface Listing<T, P extends ListParams> {
2396
+ /** Загружает одну страницу с позиции, заданной параметрами. */
2397
+ list(params: P): Promise<Page<T>>;
2398
+ /** Перебирает страницы, сама подставляя позиции. */
2399
+ iterate(params: P): Paginator<T>;
2400
+ }
2413
2401
  /** Общая основа всех групп методов клиента. */
2414
2402
  declare class BaseResource {
2415
2403
  /** @internal */
2416
2404
  protected readonly http: HttpClient;
2417
2405
  constructor(http: HttpClient);
2418
2406
  /**
2419
- * Переносит общие поля опций запроса в параметры транспорта.
2407
+ * Переносит опции запроса в описание транспорта.
2420
2408
  *
2421
- * Поля перечислены поимённо, а не скопированы целиком: параметры методов наследуют
2422
- * {@link RequestOptions} и приносят с собой `limit`, `cursor` и прочее, чему в описании
2423
- * запроса делать нечего. Исключение опции, заявленные плагинами: их библиотека
2409
+ * Копируются только поля {@link REQUEST_OPTION_KEYS} и опции, заявленные плагинами:
2410
+ * параметры методов наследуют {@link RequestOptions} и приносят с собой `limit`, `cursor`
2411
+ * и прочее, чему в описании запроса делать нечего. Чужие опции плагинов библиотека
2424
2412
  * не понимает, но обязана донести до обёрток нетронутыми.
2425
2413
  */
2426
2414
  protected requestOptions(options: RequestOptions | undefined): Partial<RequestOptions>;
@@ -2435,6 +2423,24 @@ declare class BaseResource {
2435
2423
  maxPages?: number;
2436
2424
  start?: PageState;
2437
2425
  }): Paginator<T>;
2426
+ /**
2427
+ * Собирает пару «загрузка страницы + перебор» из одного описания.
2428
+ *
2429
+ * Путь, параметры запроса и разбор ответа задаются один раз; `list` и `iterate`
2430
+ * строятся из них.
2431
+ *
2432
+ * @example
2433
+ * ```ts
2434
+ * #feed = this.paginated<Post, FeedParams>({
2435
+ * path: () => '/api/posts',
2436
+ * query: (p) => ({ tab: p.tab, limit: p.limit }),
2437
+ * start: (p) => (p.cursor ? { cursor: p.cursor } : {}),
2438
+ * read: (body) => readCursorPage<Post>(body, 'posts'),
2439
+ * mode: PaginationMode.Cursor,
2440
+ * });
2441
+ * ```
2442
+ */
2443
+ protected paginated<T, P extends ListParams>(spec: ListingSpec<T, P>): Listing<T, P>;
2438
2444
  }
2439
2445
 
2440
2446
  /** Учётные данные для входа. */
@@ -2735,16 +2741,13 @@ type FileReader = (path: string) => Promise<{
2735
2741
  */
2736
2742
  declare class FilesResource extends BaseResource {
2737
2743
  #private;
2744
+ /**
2745
+ * @param deps.readFile чтение файлов с диска. Передаёт точка входа `itd-api/node`;
2746
+ * в основном бандле его нет, чтобы браузерные сборщики не пытались разрешить `node:fs`.
2747
+ */
2738
2748
  constructor(http: HttpClient, deps?: {
2739
2749
  readFile?: FileReader;
2740
2750
  });
2741
- /**
2742
- * Подключает чтение файлов с диска.
2743
- *
2744
- * Вызывается точкой входа `itd-api/node`; в основном бандле работы с файловой
2745
- * системой нет, чтобы браузерные сборщики не пытались разрешить `node:fs`.
2746
- */
2747
- setFileReader(readFile: FileReader): void;
2748
2751
  /**
2749
2752
  * Загружает файл и возвращает его идентификатор.
2750
2753
  *
@@ -2788,17 +2791,13 @@ interface HashtagPostsParams extends RequestOptions {
2788
2791
  cursor?: string;
2789
2792
  maxPages?: number;
2790
2793
  }
2791
- /** Результат глобального поиска. */
2792
- interface SearchResult {
2793
- users: UserSummary[];
2794
- hashtags: Hashtag[];
2795
- }
2796
2794
  /**
2797
2795
  * Хэштеги.
2798
2796
  *
2799
2797
  * Доступна как `itd.hashtags`.
2800
2798
  */
2801
2799
  declare class HashtagsResource extends BaseResource {
2800
+ #private;
2802
2801
  /**
2803
2802
  * Ищет хэштеги.
2804
2803
  *
@@ -2821,91 +2820,6 @@ declare class HashtagsResource extends BaseResource {
2821
2820
  /** Перебирает посты по хэштегу. */
2822
2821
  iteratePosts(tag: string, params?: HashtagPostsParams): Paginator<Post>;
2823
2822
  }
2824
- /**
2825
- * Глобальный поиск.
2826
- *
2827
- * Доступна как `itd.search`.
2828
- */
2829
- declare class SearchResource extends BaseResource {
2830
- /**
2831
- * Ищет пользователей и хэштеги одним запросом.
2832
- *
2833
- * @example
2834
- * ```ts
2835
- * const { users, hashtags } = await itd.search.all('арт');
2836
- * ```
2837
- */
2838
- all(query: string, options?: RequestOptions): Promise<SearchResult>;
2839
- }
2840
- /**
2841
- * Жалобы на контент и пользователей.
2842
- *
2843
- * Доступна как `itd.reports`.
2844
- */
2845
- declare class ReportsResource extends BaseResource {
2846
- /**
2847
- * Отправляет жалобу.
2848
- *
2849
- * Повторная жалоба на тот же объект отклоняется сервером с сообщением
2850
- * «Вы уже отправляли жалобу на этот контент».
2851
- *
2852
- * @example
2853
- * ```ts
2854
- * await itd.reports.create(report.post(postId).reason('spam'));
2855
- * await itd.reports.create({ targetType: 'user', targetId, reason: 'fraud' });
2856
- * ```
2857
- */
2858
- create(input: ReportInput, options?: RequestOptions): Promise<Report>;
2859
- }
2860
- /**
2861
- * Верификация профиля.
2862
- *
2863
- * Доступна как `itd.verification`.
2864
- */
2865
- declare class VerificationResource extends BaseResource {
2866
- /** Загружает статус заявки. Значение `none` означает, что заявка не подавалась. */
2867
- status(options?: RequestOptions): Promise<VerificationStatus>;
2868
- /** Подаёт заявку на верификацию с видео. */
2869
- submit(videoUrl: string, options?: RequestOptions): Promise<unknown>;
2870
- }
2871
- /**
2872
- * Подписка и способы оплаты.
2873
- *
2874
- * Доступна как `itd.subscription`.
2875
- */
2876
- declare class SubscriptionResource extends BaseResource {
2877
- /** Загружает состояние подписки и её цену. */
2878
- status(options?: RequestOptions): Promise<Subscription>;
2879
- /**
2880
- * Запускает оплату подписки.
2881
- *
2882
- * Форма ответа в документации API не описана, поэтому тип результата не уточняется.
2883
- */
2884
- pay(options?: RequestOptions): Promise<unknown>;
2885
- /** Включает или отключает автопродление. */
2886
- setAutoRenewal(enabled: boolean, options?: RequestOptions): Promise<unknown>;
2887
- /** Запускает привязку карты. */
2888
- bindCard(options?: RequestOptions): Promise<unknown>;
2889
- /** Загружает список способов оплаты. Пустой массив, если карт нет. */
2890
- methods(options?: RequestOptions): Promise<PaymentMethod[]>;
2891
- /** Делает способ оплаты основным. */
2892
- setDefaultMethod(methodId: string, options?: RequestOptions): Promise<unknown>;
2893
- /** Удаляет способ оплаты. */
2894
- removeMethod(methodId: string, options?: RequestOptions): Promise<void>;
2895
- }
2896
- /**
2897
- * Сведения о платформе: изменения, анонсы, баннер события.
2898
- *
2899
- * Доступна как `itd.platform`.
2900
- */
2901
- declare class PlatformResource extends BaseResource {
2902
- /** Загружает журнал изменений. */
2903
- changelog(options?: RequestOptions): Promise<ChangelogEntry[]>;
2904
- /** Загружает анонсы платформы. */
2905
- announcements(options?: RequestOptions): Promise<Announcement[]>;
2906
- /** Загружает баннер текущего события — виджет «портал». */
2907
- portal(options?: RequestOptions): Promise<Portal>;
2908
- }
2909
2823
 
2910
2824
  /** Параметры запроса списка уведомлений. */
2911
2825
  interface NotificationListParams extends RequestOptions {
@@ -2927,8 +2841,7 @@ declare class NotificationsResource extends BaseResource {
2927
2841
  /**
2928
2842
  * Загружает страницу уведомлений.
2929
2843
  *
2930
- * Пагинация здесь основана на смещении. Сайт итд.com оборачивает смещение в строку
2931
- * и притворяется, что это курсор; библиотека отдаёт честное число.
2844
+ * Пагинация здесь основана на смещении.
2932
2845
  *
2933
2846
  * @example
2934
2847
  * ```ts
@@ -2978,6 +2891,20 @@ declare class NotificationsResource extends BaseResource {
2978
2891
  updateSettings(input: UpdateNotificationSettingsInput, options?: RequestOptions): Promise<NotificationSettings>;
2979
2892
  }
2980
2893
 
2894
+ /**
2895
+ * Сведения о платформе: изменения, анонсы, баннер события.
2896
+ *
2897
+ * Доступна как `itd.platform`.
2898
+ */
2899
+ declare class PlatformResource extends BaseResource {
2900
+ /** Загружает журнал изменений. */
2901
+ changelog(options?: RequestOptions): Promise<ChangelogEntry[]>;
2902
+ /** Загружает анонсы платформы. */
2903
+ announcements(options?: RequestOptions): Promise<Announcement[]>;
2904
+ /** Загружает баннер текущего события — виджет «портал». */
2905
+ portal(options?: RequestOptions): Promise<Portal>;
2906
+ }
2907
+
2981
2908
  /** Параметры запроса ленты. */
2982
2909
  interface FeedParams extends RequestOptions {
2983
2910
  /** Вкладка ленты. По умолчанию сервер отдаёт популярное. */
@@ -3146,6 +3073,75 @@ declare class PostsResource extends BaseResource {
3146
3073
  voiceComment(postId: string, audio: FileInput, options?: RequestOptions): Promise<Comment>;
3147
3074
  }
3148
3075
 
3076
+ /**
3077
+ * Жалобы на контент и пользователей.
3078
+ *
3079
+ * Доступна как `itd.reports`.
3080
+ */
3081
+ declare class ReportsResource extends BaseResource {
3082
+ /**
3083
+ * Отправляет жалобу.
3084
+ *
3085
+ * Повторная жалоба на тот же объект отклоняется сервером с сообщением
3086
+ * «Вы уже отправляли жалобу на этот контент».
3087
+ *
3088
+ * @example
3089
+ * ```ts
3090
+ * await itd.reports.create(report.post(postId).reason('spam'));
3091
+ * await itd.reports.create({ targetType: 'user', targetId, reason: 'fraud' });
3092
+ * ```
3093
+ */
3094
+ create(input: ReportInput, options?: RequestOptions): Promise<Report>;
3095
+ }
3096
+
3097
+ /** Результат глобального поиска. */
3098
+ interface SearchResult {
3099
+ users: UserSummary[];
3100
+ hashtags: Hashtag[];
3101
+ }
3102
+ /**
3103
+ * Глобальный поиск.
3104
+ *
3105
+ * Доступна как `itd.search`.
3106
+ */
3107
+ declare class SearchResource extends BaseResource {
3108
+ /**
3109
+ * Ищет пользователей и хэштеги одним запросом.
3110
+ *
3111
+ * @example
3112
+ * ```ts
3113
+ * const { users, hashtags } = await itd.search.all('арт');
3114
+ * ```
3115
+ */
3116
+ all(query: string, options?: RequestOptions): Promise<SearchResult>;
3117
+ }
3118
+
3119
+ /**
3120
+ * Подписка и способы оплаты.
3121
+ *
3122
+ * Доступна как `itd.subscription`.
3123
+ */
3124
+ declare class SubscriptionResource extends BaseResource {
3125
+ /** Загружает состояние подписки и её цену. */
3126
+ status(options?: RequestOptions): Promise<Subscription>;
3127
+ /**
3128
+ * Запускает оплату подписки.
3129
+ *
3130
+ * Форма ответа в документации API не описана, поэтому тип результата не уточняется.
3131
+ */
3132
+ pay(options?: RequestOptions): Promise<unknown>;
3133
+ /** Включает или отключает автопродление. */
3134
+ setAutoRenewal(enabled: boolean, options?: RequestOptions): Promise<unknown>;
3135
+ /** Запускает привязку карты. */
3136
+ bindCard(options?: RequestOptions): Promise<unknown>;
3137
+ /** Загружает список способов оплаты. Пустой массив, если карт нет. */
3138
+ methods(options?: RequestOptions): Promise<PaymentMethod[]>;
3139
+ /** Делает способ оплаты основным. */
3140
+ setDefaultMethod(methodId: string, options?: RequestOptions): Promise<unknown>;
3141
+ /** Удаляет способ оплаты. */
3142
+ removeMethod(methodId: string, options?: RequestOptions): Promise<void>;
3143
+ }
3144
+
3149
3145
  /**
3150
3146
  * Общие опции запроса телеметрии.
3151
3147
  *
@@ -3371,6 +3367,34 @@ declare class UsersResource extends BaseResource {
3371
3367
  removePin(options?: RequestOptions): Promise<void>;
3372
3368
  }
3373
3369
 
3370
+ /**
3371
+ * Верификация профиля.
3372
+ *
3373
+ * Доступна как `itd.verification`.
3374
+ */
3375
+ declare class VerificationResource extends BaseResource {
3376
+ /** Загружает статус заявки. Значение `none` означает, что заявка не подавалась. */
3377
+ status(options?: RequestOptions): Promise<VerificationStatus>;
3378
+ /** Подаёт заявку на верификацию с видео. */
3379
+ submit(videoUrl: string, options?: RequestOptions): Promise<unknown>;
3380
+ }
3381
+
3382
+ declare global {
3383
+ interface SymbolConstructor {
3384
+ readonly asyncDispose: unique symbol;
3385
+ }
3386
+ }
3387
+ /**
3388
+ * Скрытые параметры конструктора — не часть публичного API.
3389
+ *
3390
+ * Через них точка входа `itd-api/node` передаёт чтение файлов с диска, не мутируя уже
3391
+ * созданный объект.
3392
+ *
3393
+ * @internal
3394
+ */
3395
+ interface ItdClientInternals {
3396
+ fileReader?: FileReader | undefined;
3397
+ }
3374
3398
  /**
3375
3399
  * Клиент API итд.com.
3376
3400
  *
@@ -3434,7 +3458,7 @@ declare class ItdClient {
3434
3458
  * @experimental Недокументированные эндпоинты. Библиотека никогда не отправляет их сама.
3435
3459
  */
3436
3460
  readonly telemetry: TelemetryResource;
3437
- constructor(options?: ItdClientOptions);
3461
+ constructor(options?: ItdClientOptions, internals?: ItdClientInternals);
3438
3462
  /** Базовый URL, к которому обращается клиент. */
3439
3463
  get baseUrl(): string;
3440
3464
  /**
@@ -3501,18 +3525,27 @@ declare class ItdClient {
3501
3525
  * ```
3502
3526
  */
3503
3527
  realtime(options?: RealtimeOptions): ItdRealtime;
3504
- /** Текущая сессия целиком — чтобы сохранить её самостоятельно. */
3505
- getSession(): Promise<ItdSession | null>;
3506
- /** Восстанавливает сохранённую сессию, включая cookie. */
3507
- setSession(session: ItdSession): Promise<void>;
3508
3528
  /**
3509
- * Подключает чтение файлов с диска.
3529
+ * Освобождает ресурсы клиента: останавливает очередь запросов (снимает отложенные паузы)
3530
+ * и закрывает все потоки уведомлений, созданные через {@link realtime}.
3510
3531
  *
3511
- * Вызывается из `itd-api/node`; напрямую обычно не нужно.
3532
+ * После вызова клиентом можно пользоваться снова — новые запросы поднимут всё заново,
3533
+ * но уже созданные потоки останутся закрытыми.
3512
3534
  *
3513
- * @internal
3535
+ * @example
3536
+ * ```ts
3537
+ * await using itd = new ItdClient({ auth: token });
3538
+ * // …работа…
3539
+ * // close() вызовется сам на выходе из блока
3540
+ * ```
3514
3541
  */
3515
- setFileReader(readFile: FileReader): void;
3542
+ close(): Promise<void>;
3543
+ /** Позволяет использовать клиент с `await using`. */
3544
+ [Symbol.asyncDispose](): Promise<void>;
3545
+ /** Текущая сессия целиком — чтобы сохранить её самостоятельно. */
3546
+ getSession(): Promise<ItdSession | null>;
3547
+ /** Восстанавливает сохранённую сессию, включая cookie. */
3548
+ setSession(session: ItdSession): Promise<void>;
3516
3549
  }
3517
3550
  /**
3518
3551
  * Создаёт клиент API итд.com.