itd-api 0.7.1 → 0.8.0

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.
Files changed (84) hide show
  1. package/README.md +14 -14
  2. package/dist/events/index.cjs +53 -0
  3. package/dist/events/index.d.cts +3 -0
  4. package/dist/events/index.d.ts +3 -0
  5. package/dist/events/index.js +5 -0
  6. package/dist/index.cjs +269 -175
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +77 -56
  9. package/dist/index.d.ts +77 -56
  10. package/dist/index.js +251 -160
  11. package/dist/index.js.map +1 -1
  12. package/dist/node/index.cjs +4 -4
  13. package/dist/node/index.d.cts +1 -1
  14. package/dist/node/index.d.ts +1 -1
  15. package/dist/node/index.js +4 -4
  16. package/dist/rest/index.cjs +72 -14
  17. package/dist/rest/index.cjs.map +1 -1
  18. package/dist/rest/index.d.cts +11 -3
  19. package/dist/rest/index.d.ts +11 -3
  20. package/dist/rest/index.js +66 -8
  21. package/dist/rest/index.js.map +1 -1
  22. package/dist/shared/{errors-DfU8M5eS.cjs → errors-BmP3TKoW.cjs} +3 -3
  23. package/dist/shared/{errors-DfU8M5eS.cjs.map → errors-BmP3TKoW.cjs.map} +1 -1
  24. package/dist/shared/{errors-Bhrd2fJd.js → errors-GI10kZxk.js} +3 -3
  25. package/dist/shared/{errors-Bhrd2fJd.js.map → errors-GI10kZxk.js.map} +1 -1
  26. package/dist/shared/events-Br2r7coi.d.cts +698 -0
  27. package/dist/shared/events-QyhQZrnC.d.ts +698 -0
  28. package/dist/shared/{websocket-CbzB1Leq.js → events-TfhKh26b.js} +770 -490
  29. package/dist/shared/events-TfhKh26b.js.map +1 -0
  30. package/dist/shared/{websocket-sYlynrr0.cjs → events-iTteVsd_.cjs} +834 -518
  31. package/dist/shared/events-iTteVsd_.cjs.map +1 -0
  32. package/dist/shared/{multi-storage-CkvTUC5m.js → multi-storage-1AEZTczk.js} +4 -4
  33. package/dist/shared/{multi-storage-CkvTUC5m.js.map → multi-storage-1AEZTczk.js.map} +1 -1
  34. package/dist/shared/{multi-storage--yTEqiod.cjs → multi-storage-CCUgkqvI.cjs} +6 -6
  35. package/dist/shared/{multi-storage--yTEqiod.cjs.map → multi-storage-CCUgkqvI.cjs.map} +1 -1
  36. package/dist/shared/{options-DtATYdLr.js → options-BNP633A5.js} +8 -6
  37. package/dist/shared/options-BNP633A5.js.map +1 -0
  38. package/dist/shared/{options-Dg5N3r1V.cjs → options-BUfoHE9G.cjs} +8 -6
  39. package/dist/shared/options-BUfoHE9G.cjs.map +1 -0
  40. package/dist/shared/{cookies-DZwFq6kr.cjs → redact-DB9EMGJx.cjs} +301 -2
  41. package/dist/shared/redact-DB9EMGJx.cjs.map +1 -0
  42. package/dist/shared/{cookies-tX2sNwxb.js → redact-FNK29Fv-.js} +224 -3
  43. package/dist/shared/redact-FNK29Fv-.js.map +1 -0
  44. package/dist/shared/{render-DMp_3Nzk.d.cts → render-BszcKF7S.d.cts} +432 -254
  45. package/dist/shared/{render-vtLixIiU.js → render-CiL1VS0b.js} +1261 -621
  46. package/dist/shared/render-CiL1VS0b.js.map +1 -0
  47. package/dist/shared/{render-DyeHJNBw.cjs → render-DNTESBSl.cjs} +1335 -671
  48. package/dist/shared/render-DNTESBSl.cjs.map +1 -0
  49. package/dist/shared/{render-DZrxhC5_.d.ts → render-DVB7T8pm.d.ts} +432 -254
  50. package/dist/shared/{storage-BPJR_k4-.cjs → storage-BLmXmzKF.cjs} +2 -2
  51. package/dist/shared/{storage-BPJR_k4-.cjs.map → storage-BLmXmzKF.cjs.map} +1 -1
  52. package/dist/shared/{storage-D86edNCB.js → storage-CCxn0Kkv.js} +2 -2
  53. package/dist/shared/{storage-D86edNCB.js.map → storage-CCxn0Kkv.js.map} +1 -1
  54. package/dist/shared/{url-CYXgxqGx.js → url-B3ocFlHj.js} +491 -425
  55. package/dist/shared/url-B3ocFlHj.js.map +1 -0
  56. package/dist/shared/{url-B6-bXHKt.d.ts → url-Be9jlWRv.d.cts} +1233 -1195
  57. package/dist/shared/{url-B6-bXHKt.d.cts → url-Be9jlWRv.d.ts} +1233 -1195
  58. package/dist/shared/{url-yjl2c8Ie.cjs → url-CFN1sT9S.cjs} +562 -478
  59. package/dist/shared/url-CFN1sT9S.cjs.map +1 -0
  60. package/dist/web/index.cjs +2 -2
  61. package/dist/web/index.js +2 -2
  62. package/package.json +16 -15
  63. package/dist/realtime/index.cjs +0 -165
  64. package/dist/realtime/index.cjs.map +0 -1
  65. package/dist/realtime/index.d.cts +0 -51
  66. package/dist/realtime/index.d.ts +0 -51
  67. package/dist/realtime/index.js +0 -120
  68. package/dist/realtime/index.js.map +0 -1
  69. package/dist/shared/auth-provider-BfogACAb.js +0 -91
  70. package/dist/shared/auth-provider-BfogACAb.js.map +0 -1
  71. package/dist/shared/auth-provider-CTJkKfgy.cjs +0 -108
  72. package/dist/shared/auth-provider-CTJkKfgy.cjs.map +0 -1
  73. package/dist/shared/cookies-DZwFq6kr.cjs.map +0 -1
  74. package/dist/shared/cookies-tX2sNwxb.js.map +0 -1
  75. package/dist/shared/options-Dg5N3r1V.cjs.map +0 -1
  76. package/dist/shared/options-DtATYdLr.js.map +0 -1
  77. package/dist/shared/render-DyeHJNBw.cjs.map +0 -1
  78. package/dist/shared/render-vtLixIiU.js.map +0 -1
  79. package/dist/shared/url-CYXgxqGx.js.map +0 -1
  80. package/dist/shared/url-yjl2c8Ie.cjs.map +0 -1
  81. package/dist/shared/websocket-BMtihD56.d.ts +0 -562
  82. package/dist/shared/websocket-CbzB1Leq.js.map +0 -1
  83. package/dist/shared/websocket-D1p32SB2.d.cts +0 -562
  84. package/dist/shared/websocket-sYlynrr0.cjs.map +0 -1
@@ -1,97 +1,439 @@
1
- //#region src/core/operation.d.ts
2
- /** HTTP-метод операции. */
3
- type OperationMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
4
- /** Семантическая безопасность автоматического повтора операции. */
5
- declare const RetrySafety: Readonly<{
6
- /** Автоматический повтор не создаёт неприемлемого эффекта; обычно это чтение. */
7
- readonly Safe: "safe";
8
- /** Повтор операции приводит к тому же состоянию, что и один вызов. */
9
- readonly Idempotent: "idempotent";
10
- /** Повтор может создать ещё один побочный эффект. */
11
- readonly Unsafe: "unsafe";
1
+ //#region src/types/enums.d.ts
2
+ /**
3
+ * Enum API итд.com.
4
+ *
5
+ * Здесь намеренно не используется `enum` из TypeScript. Вместо него — пара «замороженный
6
+ * объект + одноимённый тип». Такой приём даёт всё, ради чего берут `enum`
7
+ * (`FeedTab.Popular`, перебор значений в рантайме), и при этом:
8
+ *
9
+ * - **стирается без остатка** — `enum` порождает рантайм-код и отвергается средами,
10
+ * которые просто срезают типы (`node --experimental-strip-types`);
11
+ * - **не запрещает обычные строки** — `itd.posts.list({ tab: 'popular' })` остаётся валидным,
12
+ * тогда как строковый `enum` считает это ошибкой типа и вынуждает всех импортировать себя;
13
+ * - **позволяет открытые множества** — там, где документация перечисляет значения не полностью,
14
+ * тип расширяется через {@link Loose}, а объект остаётся справочником известных значений.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { FeedTab } from 'itd-api';
19
+ *
20
+ * await itd.posts.list({ tab: FeedTab.Popular }); // без магических строк
21
+ * await itd.posts.list({ tab: 'popular' }); // и так тоже можно
22
+ *
23
+ * Object.values(FeedTab); // ['popular', 'following', 'clan']
24
+ * ```
25
+ *
26
+ * @packageDocumentation
27
+ */
28
+ /**
29
+ * Открытый строковый enum.
30
+ *
31
+ * Даёт автодополнение известных значений, но не ломается, если сервер пришлёт новое.
32
+ * Используется там, где документация API перечисляет значения не полностью («`everyone` и др.»).
33
+ */
34
+ type Loose<T extends string> = T | (string & {});
35
+ /**
36
+ * Вкладка ленты `GET /api/posts`.
37
+ *
38
+ * Множество закрытое: неизвестное значение сервер отвергнет.
39
+ */
40
+ declare const FeedTab: Readonly<{
41
+ /** Популярное. Курсор здесь — номер страницы в виде строки (`"2"`, `"6"`…). */
42
+ readonly Popular: "popular";
43
+ /** Записи тех, на кого вы подписаны. Курсор — отметка времени последнего поста. */
44
+ readonly Following: "following";
45
+ /** Лента клана. Курсор, как и в подписках, — отметка времени. */
46
+ readonly Clan: "clan";
12
47
  }>;
13
- type RetrySafety = (typeof RetrySafety)[keyof typeof RetrySafety];
48
+ type FeedTab = (typeof FeedTab)[keyof typeof FeedTab];
49
+ /** Порядок комментариев к посту. */
50
+ declare const CommentSort: Readonly<{
51
+ /** Сначала новые. */
52
+ readonly Newest: "newest";
53
+ /** Сначала старые. */
54
+ readonly Oldest: "oldest";
55
+ /** Сначала популярные. */
56
+ readonly Popular: "popular";
57
+ }>;
58
+ type CommentSort = (typeof CommentSort)[keyof typeof CommentSort];
59
+ /** Тип вложения. */
60
+ declare const AttachmentType: Readonly<{
61
+ readonly Image: "image";
62
+ readonly Video: "video";
63
+ /** Голосовые комментарии: `audio/ogg`, с полем `duration`. */
64
+ readonly Audio: "audio";
65
+ }>;
66
+ type AttachmentType = (typeof AttachmentType)[keyof typeof AttachmentType];
14
67
  /**
15
- * Минимальное стабильное описание операции, доступное core и плагинам.
68
+ * Тип фрагмента разметки в тексте поста или комментария.
16
69
  *
17
- * Форма описания принадлежит ядру; заполненный ими каталог доменному слою.
70
+ * Первые два сервер расставляет сам при разборе текста, остальные приходят от редактора.
71
+ * Тип открытый: набор может пополниться.
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * await itd.posts.update(postId, {
76
+ * content: 'жирное слово',
77
+ * spans: [{ type: SpanType.Bold, offset: 0, length: 6 }],
78
+ * });
79
+ * ```
18
80
  */
19
- interface OperationDefinition {
20
- readonly method: OperationMethod;
21
- readonly retrySafety: RetrySafety;
22
- /**
23
- * Бакет операции. Опущено — операция списывает из бакета по умолчанию.
24
- *
25
- * Счётчик определяется парой «путь + метод»: `GET /api/users/me` — 40 запросов
26
- * в минуту, `PUT` того же пути — 3, `DELETE` — 150.
27
- */
28
- readonly bucket?: string;
29
- }
30
- //#endregion
31
- //#region src/domain/buckets.d.ts
81
+ declare const SpanType: Readonly<{
82
+ /** Хэштег. Название без решётки лежит в `tag`. */
83
+ readonly Hashtag: "hashtag";
84
+ /** Упоминание. Имя пользователя лежит в `tag`. */
85
+ readonly Mention: "mention";
86
+ /** Ссылка. Адрес лежит в `url`, а не в `tag`. */
87
+ readonly Link: "link";
88
+ readonly Bold: "bold";
89
+ readonly Italic: "italic";
90
+ readonly Underline: "underline";
91
+ /** Зачёркнутый. */
92
+ readonly Strike: "strike";
93
+ /** Спойлер: текст скрыт до нажатия. */
94
+ readonly Spoiler: "spoiler";
95
+ /** Моноширинный. */
96
+ readonly Monospace: "monospace";
97
+ readonly Quote: "quote";
98
+ }>;
99
+ type SpanType = Loose<(typeof SpanType)[keyof typeof SpanType]>;
100
+ /** На что подаётся жалоба. */
101
+ declare const ReportTargetType: Readonly<{
102
+ readonly Post: "post";
103
+ readonly Comment: "comment";
104
+ readonly User: "user";
105
+ }>;
106
+ type ReportTargetType = (typeof ReportTargetType)[keyof typeof ReportTargetType];
107
+ /** Причина жалобы. Множество закрытое. */
108
+ declare const ReportReason: Readonly<{
109
+ readonly Spam: "spam";
110
+ readonly Violence: "violence";
111
+ readonly Hate: "hate";
112
+ readonly Adult: "adult";
113
+ readonly Fraud: "fraud";
114
+ readonly Other: "other";
115
+ }>;
116
+ type ReportReason = (typeof ReportReason)[keyof typeof ReportReason];
117
+ /** Состояние соединения событийного канала. */
118
+ declare const EventChannelStatus: Readonly<{
119
+ readonly Connecting: "connecting";
120
+ readonly Connected: "connected";
121
+ readonly Error: "error";
122
+ readonly Disconnected: "disconnected";
123
+ }>;
124
+ type EventChannelStatus = (typeof EventChannelStatus)[keyof typeof EventChannelStatus];
125
+ /** Состояние сервиса платформы. Тип открытый. */
126
+ declare const ServiceState: Readonly<{
127
+ /** Работает штатно. */
128
+ readonly Operational: "operational";
129
+ /** Работает с деградацией. */
130
+ readonly Degraded: "degraded";
131
+ /** Недоступен. */
132
+ readonly Downtime: "downtime";
133
+ }>;
134
+ type ServiceState = Loose<(typeof ServiceState)[keyof typeof ServiceState]>;
135
+ /** Вид происшествия в истории сервиса. Тип открытый. */
136
+ declare const IncidentKind: Readonly<{
137
+ /** Недоступен. */
138
+ readonly Down: "down";
139
+ /** Деградация. */
140
+ readonly Degraded: "deg";
141
+ }>;
142
+ type IncidentKind = Loose<(typeof IncidentKind)[keyof typeof IncidentKind]>;
32
143
  /**
33
- * Ёмкость серверных счётчиков частоты, запросов в минуту.
144
+ * Уровень доступа к разделу профиля.
34
145
  *
35
- * Таблица действует до первого ответа бакета; дальше ёмкость берётся из заголовка
36
- * `x-ratelimit-limit` и заменяет табличную. `default` счётчик любого пути без
37
- * собственного правила на сервере.
146
+ * Общий набор значений для полей `wallAccess` и `likesVisibility` настроек приватности.
147
+ * Тип открытый: сервер может прислать значение вне этого перечня.
38
148
  */
39
- declare const BUCKET_LIMITS: Readonly<{
40
- readonly 'posts.stats': 180;
41
- readonly default: 150;
42
- readonly feed: 90;
43
- readonly 'posts.like': 85;
44
- readonly 'posts.comments': 80;
45
- readonly hashtags: 50;
46
- readonly users: 40;
47
- readonly notifications: 40;
48
- readonly 'files.get': 40;
49
- readonly auth: 35;
50
- readonly 'auth.refresh': 25;
51
- readonly search: 25;
52
- readonly 'comments.like': 22;
53
- readonly 'files.upload': 15;
54
- readonly 'files.remove': 15;
55
- readonly 'posts.comment': 14;
56
- readonly 'hashtags.trending': 13;
57
- readonly 'posts.repost': 7;
58
- readonly 'users.follow': 7;
59
- readonly 'verification.status': 6;
60
- readonly 'posts.create': 5;
61
- readonly 'users.updateMe': 3;
62
- readonly 'reports.create': 3;
63
- readonly 'verification.submit': 3;
149
+ declare const AccessType: Readonly<{
150
+ /** Никто. */
151
+ readonly Nobody: "nobody";
152
+ /** Только взаимные подписки. */
153
+ readonly Mutual: "mutual";
154
+ /** Подписчики. */
155
+ readonly Followers: "followers";
156
+ /** Все. */
157
+ readonly Everyone: "everyone";
64
158
  }>;
65
- /** Имя встроенного бакета. */
66
- type RateLimitBucket = keyof typeof BUCKET_LIMITS;
67
- /** Счётчик, из которого списывается путь без собственного правила на сервере. */
68
- declare const DEFAULT_RATE_LIMIT_BUCKET: RateLimitBucket;
69
- //#endregion
70
- //#region src/domain/operations.d.ts
71
- /** Описание встроенной операции: та же форма, что знает ядро, но с именем известного бакета. */
72
- interface ItdOperationDefinition extends OperationDefinition {
73
- readonly bucket?: RateLimitBucket;
74
- }
159
+ type AccessType = Loose<(typeof AccessType)[keyof typeof AccessType]>;
160
+ /** Кто может писать на стену профиля. Псевдоним {@link AccessType}. */
161
+ declare const WallAccess: Readonly<{
162
+ /** Никто. */
163
+ readonly Nobody: "nobody";
164
+ /** Только взаимные подписки. */
165
+ readonly Mutual: "mutual";
166
+ /** Подписчики. */
167
+ readonly Followers: "followers";
168
+ /** Все. */
169
+ readonly Everyone: "everyone";
170
+ }>;
171
+ type WallAccess = AccessType;
172
+ /** Кто видит реакции пользователя. Псевдоним {@link AccessType}. */
173
+ declare const LikesVisibility: Readonly<{
174
+ /** Никто. */
175
+ readonly Nobody: "nobody";
176
+ /** Только взаимные подписки. */
177
+ readonly Mutual: "mutual";
178
+ /** Подписчики. */
179
+ readonly Followers: "followers";
180
+ /** Все. */
181
+ readonly Everyone: "everyone";
182
+ }>;
183
+ type LikesVisibility = AccessType;
75
184
  /**
76
- * Каталог встроенных операций.
185
+ * Канонический тип уведомления (новое поколение имён).
77
186
  *
78
- * ID описывает смысл вызова и не меняется при переносе HTTP-пути. Method и retrySafety
79
- * хранятся здесь, чтобы resources, retry и плагины не вели независимые таблицы операций.
187
+ * REST-эндпоинт `/api/notifications/` отдаёт старые имена (`like`, `comment`, `reply`,
188
+ * `repost`, `mention`), SSE-поток новые. Библиотека приводит их к этому набору,
189
+ * сохраняя исходное значение в поле `rawType`.
80
190
  */
81
- declare const OPERATIONS: Readonly<{
82
- readonly 'auth.check': Readonly<{
83
- readonly method: "GET";
84
- readonly retrySafety: "safe";
85
- }>;
86
- readonly 'auth.signUp': Readonly<{
87
- readonly method: "POST";
88
- readonly retrySafety: "unsafe";
89
- readonly bucket: "auth";
90
- }>;
91
- readonly 'auth.signIn': Readonly<{
92
- readonly method: "POST";
93
- readonly retrySafety: "safe";
94
- readonly bucket: "auth";
191
+ declare const NotificationType: Readonly<{
192
+ /** Реакция на пост. Старое имя — `like`. */
193
+ readonly PostReaction: "post_reaction";
194
+ /** Комментарий к посту. Старое имя — `comment`. */
195
+ readonly PostComment: "post_comment";
196
+ /** Ответ на комментарий. Старое имя — `reply`. */
197
+ readonly CommentReply: "comment_reply";
198
+ /** Репост. Старое имя — `repost`. */
199
+ readonly PostRepost: "post_repost";
200
+ /** Упоминание в посте. Старое имя — `mention`. */
201
+ readonly PostMention: "post_mention";
202
+ /** Реакция на комментарий. */
203
+ readonly CommentReaction: "comment_reaction";
204
+ /** Упоминание в комментарии. */
205
+ readonly CommentMention: "comment_mention";
206
+ /** Запись на вашей стене. */
207
+ readonly WallPost: "wall_post";
208
+ /** На вас подписались. */
209
+ readonly Follow: "follow";
210
+ /** Заявка на подписку (закрытый профиль). */
211
+ readonly FollowRequest: "follow_request";
212
+ /** Заявка на подписку принята. */
213
+ readonly FollowAccepted: "follow_accepted";
214
+ /** Верификация одобрена. Приходит только по REST. */
215
+ readonly VerificationApproved: "verification_approved";
216
+ /** Верификация отклонена. Приходит только по REST. */
217
+ readonly VerificationRejected: "verification_rejected";
218
+ }>;
219
+ type NotificationType = Loose<(typeof NotificationType)[keyof typeof NotificationType]>;
220
+ /**
221
+ * Тип взаимодействия с контентом в телеметрии (`POST /api/v1/x`, поле `t`).
222
+ *
223
+ * Кодируется числом.
224
+ */
225
+ declare const InteractionType: Readonly<{
226
+ /** Открытие фотографии. */
227
+ readonly PhotoOpen: 1;
228
+ /** Прогресс просмотра видео. Несёт поля `pm`/`dm`. */
229
+ readonly VideoProgress: 2;
230
+ }>;
231
+ type InteractionType = (typeof InteractionType)[keyof typeof InteractionType];
232
+ /**
233
+ * Источник показа поста в телеметрии (поле `s`).
234
+ *
235
+ * Кодируется числом. Поле применимо к источникам `PostPage` и `Link`; для лент источник
236
+ * передаётся контекстом `sc`.
237
+ */
238
+ declare const ViewSource: Readonly<{
239
+ readonly FeedGlobal: 1;
240
+ readonly FeedFollowing: 2;
241
+ readonly FeedClan: 3;
242
+ readonly Profile: 4;
243
+ readonly Hashtag: 5;
244
+ readonly PostPage: 6;
245
+ readonly Link: 7;
246
+ readonly Search: 8;
247
+ }>;
248
+ type ViewSource = (typeof ViewSource)[keyof typeof ViewSource];
249
+ /**
250
+ * Причина завершения просмотра поста в телеметрии (`POST /api/v1/i`, поле `r`).
251
+ *
252
+ * Кодируется числом.
253
+ */
254
+ declare const ViewReason: Readonly<{
255
+ /** Пост ушёл из зоны видимости при обычной прокрутке. */
256
+ readonly Normal: 0;
257
+ /** Потеря фокуса окна. */
258
+ readonly Blur: 1;
259
+ /** Вкладка скрыта. */
260
+ readonly Hidden: 2;
261
+ /** Уход со страницы (`pagehide`). */
262
+ readonly PageHide: 3;
263
+ /** Элемент перестал наблюдаться. */
264
+ readonly Unobserve: 4;
265
+ /** Достигнут порог времени просмотра. */
266
+ readonly ThresholdMet: 5;
267
+ }>;
268
+ type ViewReason = (typeof ViewReason)[keyof typeof ViewReason];
269
+ /**
270
+ * Строковые коды ошибок из поля `code`.
271
+ *
272
+ * Ключи намеренно повторяют написание сервера: код из ответа API можно найти здесь
273
+ * поиском один в один, без мысленного перевода регистра.
274
+ *
275
+ * Список открыт — сервер может добавить новый код, и это не должно ломать типизацию.
276
+ *
277
+ * @example
278
+ * ```ts
279
+ * if (err.hasCode(ItdErrorCode.OTP_INVALID)) await restartOtpFlow();
280
+ * ```
281
+ */
282
+ declare const ItdErrorCode: Readonly<{
283
+ readonly BAD_REQUEST: "BAD_REQUEST";
284
+ readonly UNAUTHORIZED: "UNAUTHORIZED";
285
+ readonly ACCESS_DENIED: "ACCESS_DENIED";
286
+ readonly ENTITY_NOT_FOUND: "ENTITY_NOT_FOUND";
287
+ readonly ENTITY_ALREADY_EXISTS: "ENTITY_ALREADY_EXISTS";
288
+ readonly VALIDATION_ERROR: "VALIDATION_ERROR";
289
+ readonly BUSINESS_RULE_VIOLATION: "BUSINESS_RULE_VIOLATION";
290
+ readonly RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED";
291
+ readonly UNKNOWN_ERROR: "UNKNOWN_ERROR";
292
+ /** Сервер отвечает так на `404`, `ENTITY_NOT_FOUND` в этом случае не приходит. */
293
+ readonly NOT_FOUND: "NOT_FOUND";
294
+ /** На практике не приходит: вместо него сервер шлёт `TURNSTILE_VERIFICATION_FAILED`. */
295
+ readonly CAPTCHA_FAILED: "CAPTCHA_FAILED";
296
+ /** Капча не пройдена: токен Turnstile недействителен, просрочен или уже использован. */
297
+ readonly TURNSTILE_VERIFICATION_FAILED: "TURNSTILE_VERIFICATION_FAILED";
298
+ readonly OTP_INVALID: "OTP_INVALID";
299
+ /** `flowToken` неизвестен или просрочен — поток подтверждения нужно начинать заново. */
300
+ readonly INVALID_FLOW_TOKEN: "INVALID_FLOW_TOKEN";
301
+ readonly ACCOUNT_DEACTIVATED: "ACCOUNT_DEACTIVATED";
302
+ readonly ACCOUNT_EMAIL_DOMAIN_NOT_ALLOWED: "ACCOUNT_EMAIL_DOMAIN_NOT_ALLOWED";
303
+ readonly ACCOUNT_INVALID_CREDENTIALS: "ACCOUNT_INVALID_CREDENTIALS";
304
+ readonly ACCOUNT_TEMPORARILY_LOCKED: "ACCOUNT_TEMPORARILY_LOCKED";
305
+ readonly ACCOUNT_CURRENT_PASSWORD_INCORRECT: "ACCOUNT_CURRENT_PASSWORD_INCORRECT";
306
+ readonly SESSION_EXPIRED: "SESSION_EXPIRED";
307
+ readonly SESSION_REVOKED: "SESSION_REVOKED";
308
+ readonly SESSION_INVALID_REFRESH_TOKEN: "SESSION_INVALID_REFRESH_TOKEN";
309
+ /** Запрос обновления пришёл без cookie `refresh_token` — продлевать нечего. */
310
+ readonly REFRESH_TOKEN_MISSING: "REFRESH_TOKEN_MISSING";
311
+ /** Cookie `refresh_token` есть, но сессии за ней уже нет: отозвана или истекла. */
312
+ readonly SESSION_NOT_FOUND: "SESSION_NOT_FOUND";
313
+ readonly MISSING_FLOW_TOKEN: "MISSING_FLOW_TOKEN";
314
+ readonly PROFILE_USERNAME_TAKEN: "PROFILE_USERNAME_TAKEN";
315
+ readonly PROFILE_RESTRICTION_ACTIVE: "PROFILE_RESTRICTION_ACTIVE";
316
+ readonly PROFILE_MODIFICATION_RESTRICTED: "PROFILE_MODIFICATION_RESTRICTED";
317
+ readonly CONTENT_MODERATION_FAILED: "CONTENT_MODERATION_FAILED";
318
+ readonly FILE_TOO_LARGE: "FILE_TOO_LARGE";
319
+ readonly UNSUPPORTED_FILE_TYPE: "UNSUPPORTED_FILE_TYPE";
320
+ readonly UPLOAD_FAILED: "UPLOAD_FAILED";
321
+ readonly VIDEO_REQUIRES_VERIFICATION: "VIDEO_REQUIRES_VERIFICATION";
322
+ readonly PHONE_VERIFICATION_REQUIRED: "PHONE_VERIFICATION_REQUIRED";
323
+ readonly WRITE_ACCESS_RESTRICTED: "WRITE_ACCESS_RESTRICTED";
324
+ }>;
325
+ type ItdErrorCode = Loose<(typeof ItdErrorCode)[keyof typeof ItdErrorCode]>;
326
+ //#endregion
327
+ //#region src/models/common.d.ts
328
+ /**
329
+ * Дата и время в формате ISO-8601, например `2026-07-21T14:30:00.000Z`.
330
+ *
331
+ * Библиотека не превращает такие поля в `Date`: строку проще сравнивать, логировать
332
+ * и передавать дальше без потерь. Для разбора есть `toDate()`.
333
+ */
334
+ type IsoDate = string;
335
+ /**
336
+ * Идентификатор пользователя — **строго UUID**.
337
+ *
338
+ * Отличается от {@link UserRef} тем, что имя пользователя здесь не подойдёт. Так помечены
339
+ * места, где API принимает только UUID: например `wallRecipientId` при постинге на чужую стену.
340
+ */
341
+ type UserId = string;
342
+ /**
343
+ * Ссылка на пользователя: **UUID либо имя пользователя**.
344
+ *
345
+ * Пути вида `/api/users/{id}` принимают оба варианта, поэтому `itd.users.get('nowkie')`
346
+ * работает так же, как `itd.users.get('9f1c…')`.
347
+ */
348
+ type UserRef = string;
349
+ /**
350
+ * Разметка в тексте поста или комментария.
351
+ *
352
+ * `offset` и `length` измеряются в UTF-16 code units: это те же индексы, которые используют
353
+ * `String#slice`, `substring` и DOM Selection в JavaScript. Эмодзи вне BMP обычно занимают
354
+ * две единицы.
355
+ */
356
+ interface Span {
357
+ /** Тип фрагмента — см. {@link SpanType}. */
358
+ type: SpanType;
359
+ /** Смещение от начала текста. */
360
+ offset: number;
361
+ /** Длина фрагмента. */
362
+ length: number;
363
+ /** Имя хэштега без решётки. У старых mention-объектов может содержать username. */
364
+ tag?: string;
365
+ /** Адрес ссылки. Только у `link`: у него вместо `tag` отдельное поле. */
366
+ url?: string;
367
+ /** Имя пользователя у `mention`. */
368
+ username?: string;
369
+ /** Идентификатор пользователя у некоторых ответов API с `mention`. */
370
+ id?: string;
371
+ }
372
+ //#endregion
373
+ //#region src/domain/buckets.d.ts
374
+ /**
375
+ * Ёмкость серверных счётчиков частоты, запросов в минуту.
376
+ *
377
+ * Таблица действует до первого ответа бакета; дальше ёмкость берётся из заголовка
378
+ * `x-ratelimit-limit` и заменяет табличную. `default` — счётчик любого пути без
379
+ * собственного правила на сервере.
380
+ */
381
+ declare const BUCKET_LIMITS: Readonly<{
382
+ readonly 'posts.stats': 180;
383
+ readonly default: 150;
384
+ readonly feed: 90;
385
+ readonly 'posts.like': 85;
386
+ readonly 'posts.comments': 80;
387
+ readonly hashtags: 50;
388
+ readonly users: 40;
389
+ readonly notifications: 40;
390
+ readonly 'files.get': 40;
391
+ readonly auth: 35;
392
+ readonly 'auth.refresh': 25;
393
+ readonly search: 25;
394
+ readonly 'comments.like': 22;
395
+ readonly 'files.upload': 15;
396
+ readonly 'files.remove': 15;
397
+ readonly 'posts.comment': 14;
398
+ readonly 'hashtags.trending': 13;
399
+ readonly 'posts.repost': 7;
400
+ readonly 'users.follow': 7;
401
+ readonly 'verification.status': 6;
402
+ readonly 'posts.create': 5;
403
+ readonly 'users.updateMe': 3;
404
+ readonly 'reports.create': 3;
405
+ readonly 'verification.submit': 3;
406
+ }>;
407
+ /** Имя встроенного бакета. */
408
+ type RateLimitBucket = keyof typeof BUCKET_LIMITS;
409
+ /** Счётчик, из которого списывается путь без собственного правила на сервере. */
410
+ declare const DEFAULT_RATE_LIMIT_BUCKET: RateLimitBucket;
411
+ //#endregion
412
+ //#region src/domain/operations.d.ts
413
+ /** Описание встроенной операции: та же форма, что знает ядро, но с именем известного бакета. */
414
+ interface ItdOperationDefinition extends OperationDefinition {
415
+ readonly bucket?: RateLimitBucket;
416
+ }
417
+ /**
418
+ * Каталог встроенных операций.
419
+ *
420
+ * ID описывает смысл вызова и не меняется при переносе HTTP-пути. Method и retrySafety
421
+ * хранятся здесь, чтобы resources, retry и плагины не вели независимые таблицы операций.
422
+ */
423
+ declare const OPERATIONS: Readonly<{
424
+ readonly 'auth.check': Readonly<{
425
+ readonly method: "GET";
426
+ readonly retrySafety: "safe";
427
+ }>;
428
+ readonly 'auth.signUp': Readonly<{
429
+ readonly method: "POST";
430
+ readonly retrySafety: "unsafe";
431
+ readonly bucket: "auth";
432
+ }>;
433
+ readonly 'auth.signIn': Readonly<{
434
+ readonly method: "POST";
435
+ readonly retrySafety: "safe";
436
+ readonly bucket: "auth";
95
437
  }>;
96
438
  readonly 'auth.verifyOtp': Readonly<{
97
439
  readonly method: "POST";
@@ -406,12 +748,12 @@ declare const OPERATIONS: Readonly<{
406
748
  readonly method: "PUT";
407
749
  readonly retrySafety: "idempotent";
408
750
  }>;
409
- readonly 'realtime.poll.updates': Readonly<{
751
+ readonly 'events.notifications.poll.updates': Readonly<{
410
752
  readonly method: "GET";
411
753
  readonly retrySafety: "safe";
412
754
  readonly bucket: "notifications";
413
755
  }>;
414
- readonly 'realtime.poll.unread': Readonly<{
756
+ readonly 'events.notifications.poll.unread': Readonly<{
415
757
  readonly method: "GET";
416
758
  readonly retrySafety: "safe";
417
759
  readonly bucket: "notifications";
@@ -495,10 +837,6 @@ declare const OPERATIONS: Readonly<{
495
837
  readonly method: "GET";
496
838
  readonly retrySafety: "safe";
497
839
  }>;
498
- readonly 'platform.status': Readonly<{
499
- readonly method: "GET";
500
- readonly retrySafety: "safe";
501
- }>;
502
840
  readonly 'telemetry.dwell': Readonly<{
503
841
  readonly method: "POST";
504
842
  readonly retrySafety: "unsafe";
@@ -513,7 +851,7 @@ type BuiltInOperationId = keyof typeof OPERATIONS;
513
851
  /** Пользовательская семантическая операция низкоуровневого запроса. */
514
852
  type CustomOperationId = `custom:${string}`;
515
853
  /** ID любого запроса, видимый transformers и hooks. */
516
- type OperationId = BuiltInOperationId | CustomOperationId | 'raw';
854
+ type OperationId = BuiltInOperationId | FeatureOperationId | CustomOperationId | 'raw';
517
855
  /** Проверяет принадлежность ID встроенному каталогу. */
518
856
  declare function isBuiltInOperationId(value: string): value is BuiltInOperationId;
519
857
  /** HTTP-метод встроенной операции. */
@@ -599,7 +937,7 @@ type QueryValue = string | number | boolean | null | undefined | readonly (strin
599
937
  type QueryParams = Record<string, QueryValue>;
600
938
  //#endregion
601
939
  //#region src/core/options.d.ts
602
- /** Куда библиотека пишет отладочные сообщения. Совместим с `console`. */
940
+ /** Логгер библиотеки. Совместим с `console`. */
603
941
  interface Logger {
604
942
  debug(message: string, ...args: unknown[]): void;
605
943
  info(message: string, ...args: unknown[]): void;
@@ -631,6 +969,8 @@ interface RetryDecisionContext {
631
969
  interface RateLimitBucketOverride {
632
970
  /** Одновременных запросов внутри бакета. */
633
971
  concurrency?: number | undefined;
972
+ /** Верхняя граница стартов внутри бакета в секунду. */
973
+ rps?: number | undefined;
634
974
  /** Ёмкость бакета до первого ответа, запросов в минуту. */
635
975
  limit?: number | undefined;
636
976
  }
@@ -651,8 +991,8 @@ interface RateLimitOptions {
651
991
  *
652
992
  * `false` — одна очередь на направление: её пауза придерживает все запросы разом.
653
993
  * В этом режиме ёмкость отдельного счётчика неизвестна, поэтому `bucketConcurrency`,
654
- * `bucketOverrides` и режим `pacing: 'smooth'` не действуют, а исчерпанный остаток
655
- * встречается первой ступенью `retryDelays`.
994
+ * все поля `bucketOverrides` и режим `pacing: 'smooth'` не действуют, а исчерпанный
995
+ * остаток встречается первой ступенью `retryDelays`. Общий `rps` продолжает действовать.
656
996
  */
657
997
  buckets?: boolean | undefined;
658
998
  /**
@@ -666,7 +1006,7 @@ interface RateLimitOptions {
666
1006
  *
667
1007
  * @example
668
1008
  * ```ts
669
- * rateLimit: { bucketOverrides: { 'posts.create': { limit: 10 }, feed: { concurrency: 2 } } }
1009
+ * rateLimit: { bucketOverrides: { 'posts.create': { rps: 2 }, feed: { concurrency: 2 } } }
670
1010
  * ```
671
1011
  */
672
1012
  bucketOverrides?: Record<string, RateLimitBucketOverride> | undefined;
@@ -720,7 +1060,7 @@ interface RetryContext extends RequestContext {
720
1060
  delay: number;
721
1061
  }
722
1062
  /**
723
- * Перехватчики жизненного цикла запроса.
1063
+ * Хуки запроса.
724
1064
  *
725
1065
  * Вызываются последовательно; исключение внутри хука прервёт запрос, поэтому свою логику
726
1066
  * лучше оборачивать в `try`.
@@ -738,8 +1078,8 @@ interface ClientHooks {
738
1078
  /**
739
1079
  * Настройки исполнения запросов: куда ходить, как долго ждать и чем представляться.
740
1080
  *
741
- * Всё, что нужно generic-ядру и ничего сверх того. Авторизация и сессия описаны отдельно
742
- * в {@link SessionOptions}, а полный набор опций клиента их объединяет.
1081
+ * Всё, что нужно generic-ядру и ничего сверх того. Авторизация и сессия описаны отдельно,
1082
+ * а полный набор опций клиента их объединяет.
743
1083
  *
744
1084
  * Все поля допускают явный `undefined`, чтобы можно было передавать значения, которых
745
1085
  * может не быть, — например `new ItdClient({ timeout: process.env.TIMEOUT })`.
@@ -775,921 +1115,338 @@ interface RuntimeOptions {
775
1115
  /** Таймаут запроса в мс. По умолчанию 30000 — столько же использует сайт итд.com. `0` снимает ограничение. */
776
1116
  timeout?: number | undefined;
777
1117
  /**
778
- * Сколько `close()` и `dispose()` ждут чужой код, мс. По умолчанию 10000.
779
- *
780
- * Ждут обработчиков realtime-потока и операций, вошедших в обёртки плагинов. По истечении
781
- * срока ресурсы всё равно освобождаются, а метод отклоняется `ItdStateError` с указанием
782
- * того, что удерживало остановку. `0` снимает ограничение.
783
- */
784
- shutdownTimeout?: number | undefined;
785
- /** Повторные попытки. `false` отключает их полностью. */
786
- retry?: RetryOptions | false | undefined;
787
- /** Ограничение нагрузки. `false` отключает очередь. */
788
- rateLimit?: RateLimitOptions | false | undefined;
789
- /** Своя реализация `fetch`: для Deno, React Native, тестов или прокси. */
790
- fetch?: typeof fetch | undefined;
791
- /** Часы для тайм-аутов, повторов и очередей. Обычно подменяются только в тестах. */
792
- clock?: ItdClock | undefined;
793
- /** Как обращаться с cookie. По умолчанию определяется по среде исполнения. */
794
- mode?: RuntimeMode | undefined;
795
- /** Заголовки, добавляемые ко всем запросам, — например `User-Agent` для бота. */
796
- headers?: Record<string, string> | undefined;
797
- /**
798
- * Значение заголовка `User-Agent`. `false` — не отправлять его вовсе.
799
- *
800
- * По умолчанию `Mozilla/5.0 (compatible; itd-api/<версия>; …)`: `fetch` в Node не шлёт
801
- * `User-Agent` сам, а сайт стоит за DDoS-Guard, который такие запросы может не пропустить.
802
- * В браузере опция не действует — там заголовок менять запрещено.
803
- */
804
- userAgent?: string | false | undefined;
805
- /** Перехватчики запросов. */
806
- hooks?: ClientHooks | undefined;
807
- /** Отладочный вывод. `true` — писать в `console`. */
808
- logger?: Logger | boolean | undefined;
809
- }
810
- /**
811
- * Namespaces расширений отдельной операции.
812
- *
813
- * Пакеты дополняют интерфейс через declaration merging и владеют только своим полем.
814
- * Core передаёт объект operation transformers без знания его содержимого.
815
- */
816
- interface RequestExtensions {}
817
- /** Опции выполнения отдельного запроса. Передаются последним аргументом методов ресурсов. */
818
- interface RequestOptions {
819
- /** Отмена запроса извне. */
820
- signal?: AbortSignal | undefined;
821
- /** Таймаут только для этого запроса, мс. */
822
- timeout?: number | undefined;
823
- /** Дополнительные заголовки. */
824
- headers?: Record<string, string> | undefined;
825
- /** Повторы только для этого запроса. Переопределяют глобальную настройку `retry`. */
826
- retry?: RetryOptions | false | undefined;
827
- /**
828
- * Явно переопределяет безопасность повтора операции.
829
- *
830
- * Встроенные resources получают значение из каталога. Опция нужна прежде всего custom/raw
831
- * интеграциям и осознанному переопределению серверного контракта.
832
- */
833
- retrySafety?: RetrySafety | undefined;
834
- /**
835
- * Имя бакета, из которого списывается запрос.
836
- *
837
- * Встроенные resources берут его из каталога операций; низкоуровневый вызов без этой
838
- * опции попадает в `default`.
839
- *
840
- * Имя сверяется со встроенной картой — незнакомое отвергается {@link ItdConfigError}
841
- * до отправки, независимо от того, включена ли очередь. Своё правило `rateLimit.bucket`
842
- * заводит собственное пространство имён и проверку снимает.
843
- */
844
- rateLimitBucket?: string | undefined;
845
- /** Настройки подключённых operation extensions, сгруппированные по владельцу. */
846
- extensions?: RequestExtensions | undefined;
847
- }
848
- /** Опции перебора страниц, не являющиеся параметрами endpoint. */
849
- interface PaginationOptions extends RequestOptions {
850
- /** Максимальное число страниц; без значения перебор продолжается до конца списка. */
851
- maxPages?: number | undefined;
852
- }
853
- /** Полное описание запроса для низкоуровневого `itd.request()`. */
854
- interface RawRequestOptions extends RequestOptions {
855
- /**
856
- * Семантическое имя низкоуровневого запроса. Встроенные resources выставляют его сами.
857
- * Пользовательские значения следует помещать в namespace `custom:`.
858
- */
859
- operationId?: OperationId | undefined;
860
- method: string;
861
- /** Путь с ведущим слэшем, например `/api/posts`. Завершающий слэш значим. */
862
- path: string;
863
- /**
864
- * Имя сервиса, на хост которого уйдёт запрос. Без него запрос идёт на основной `baseUrl`
865
- * клиента. Сервисы задаются опцией {@link RuntimeOptions.services}.
866
- */
867
- service?: string | undefined;
868
- /**
869
- * Хост этого запроса. Важнее, чем {@link RawRequestOptions.service}.
870
- *
871
- * На посторонний основному API хост Bearer-токен по умолчанию не отправляется.
872
- * Для осознанного разрешения укажите `skipAuth: false`.
873
- */
874
- baseUrl?: string | undefined;
875
- query?: QueryParams | undefined;
876
- /** Тело: будет отправлено как JSON. Для загрузки файлов передайте `FormData`. */
877
- body?: unknown;
878
- /**
879
- * Не подставлять заголовок авторизации.
880
- *
881
- * Явное `false` разрешает авторизацию и для разового внешнего `baseUrl`; без него
882
- * токен автоматически отправляется только основному хосту и его поддоменам.
883
- */
884
- skipAuth?: boolean | undefined;
885
- /** Не пытаться обновить токен при `401` — используется самими эндпоинтами авторизации. */
886
- skipAuthRefresh?: boolean | undefined;
887
- /**
888
- * Выполнить запрос мимо очереди.
889
- *
890
- * Продвинутый escape hatch для служебных интеграций. Встроенные refresh и sign-in проходят
891
- * обычную очередь: она охватывает только одну сетевую попытку и не создаёт deadlock.
892
- */
893
- skipQueue?: boolean | undefined;
894
- /** Вернуть тело ответа без снятия обёртки `{ data: … }`. */
895
- raw?: boolean | undefined;
896
- }
897
- /** Запрос внутри pipeline: в отличие от raw input всегда имеет семантический ID. */
898
- interface OperationRequestOptions extends RawRequestOptions {
899
- operationId: OperationId;
900
- }
901
- //#endregion
902
- //#region src/core/emitter.d.ts
903
- /** Обработчик события. */
904
- type Listener<T> = (payload: T) => void;
905
- /** Функция отписки, которую возвращает подписка на событие. */
906
- type Unsubscribe = () => void;
907
- /**
908
- * Минимальный типизированный источник событий.
909
- *
910
- * Своя реализация вместо `EventTarget` и `EventEmitter`: первый есть не везде и требует
911
- * обёрток `CustomEvent`, второй существует только в Node. Нужны ровно подписка и рассылка.
912
- *
913
- * Исключение в обработчике не прерывает рассылку остальным и не роняет библиотеку.
914
- *
915
- * @typeParam Events карта «имя события → тип полезной нагрузки». Задаётся интерфейсом,
916
- * поэтому ограничение на индексную сигнатуру намеренно не накладывается.
917
- */
918
- declare class Emitter<Events> {
919
- #private;
920
- constructor(onListenerError?: (error: unknown) => void);
921
- /**
922
- * Подписывается на событие.
923
- *
924
- * @returns функция отписки
925
- *
926
- * @example
927
- * ```ts
928
- * const off = realtime.on('notification', (event) => console.log(event));
929
- * off();
930
- * ```
931
- */
932
- on<K extends keyof Events>(event: K, listener: Listener<Events[K]>): Unsubscribe;
933
- /** Подписывается на одно срабатывание. */
934
- once<K extends keyof Events>(event: K, listener: Listener<Events[K]>): Unsubscribe;
935
- /** Отписывается от события. */
936
- off<K extends keyof Events>(event: K, listener: Listener<Events[K]>): void;
937
- /** Рассылает событие подписчикам. */
938
- emit<K extends keyof Events>(event: K, payload: Events[K]): void;
939
- /** Сколько подписчиков у события. */
940
- listenerCount(event: keyof Events): number;
941
- /** Снимает все подписки. */
942
- removeAllListeners(): void;
943
- }
944
- //#endregion
945
- //#region src/types/enums.d.ts
946
- /**
947
- * Перечисления API итд.com.
948
- *
949
- * Здесь намеренно не используется `enum` из TypeScript. Вместо него — пара «замороженный
950
- * объект + одноимённый тип». Такой приём даёт всё, ради чего берут `enum`
951
- * (`FeedTab.Popular`, перебор значений в рантайме), и при этом:
952
- *
953
- * - **стирается без остатка** — `enum` порождает рантайм-код и отвергается средами,
954
- * которые просто срезают типы (`node --experimental-strip-types`);
955
- * - **не запрещает обычные строки** — `itd.posts.list({ tab: 'popular' })` остаётся валидным,
956
- * тогда как строковый `enum` считает это ошибкой типа и вынуждает всех импортировать себя;
957
- * - **позволяет открытые множества** — там, где документация перечисляет значения не полностью,
958
- * тип расширяется через {@link Loose}, а объект остаётся справочником известных значений.
959
- *
960
- * @example
961
- * ```ts
962
- * import { FeedTab } from 'itd-api';
963
- *
964
- * await itd.posts.list({ tab: FeedTab.Popular }); // без магических строк
965
- * await itd.posts.list({ tab: 'popular' }); // и так тоже можно
966
- *
967
- * Object.values(FeedTab); // ['popular', 'following', 'clan']
968
- * ```
969
- *
970
- * @packageDocumentation
971
- */
972
- /**
973
- * Открытое строковое перечисление.
974
- *
975
- * Даёт автодополнение известных значений, но не ломается, если сервер пришлёт новое.
976
- * Используется там, где документация API перечисляет значения не полностью («`everyone` и др.»).
977
- */
978
- type Loose<T extends string> = T | (string & {});
979
- /**
980
- * Вкладка ленты `GET /api/posts`.
981
- *
982
- * Множество закрытое: неизвестное значение сервер отвергнет.
983
- */
984
- declare const FeedTab: Readonly<{
985
- /** Популярное. Курсор здесь — номер страницы в виде строки (`"2"`, `"6"`…). */
986
- readonly Popular: "popular";
987
- /** Записи тех, на кого вы подписаны. Курсор — отметка времени последнего поста. */
988
- readonly Following: "following";
989
- /** Лента клана. Курсор, как и в подписках, — отметка времени. */
990
- readonly Clan: "clan";
991
- }>;
992
- type FeedTab = (typeof FeedTab)[keyof typeof FeedTab];
993
- /** Порядок комментариев к посту. */
994
- declare const CommentSort: Readonly<{
995
- /** Сначала новые. */
996
- readonly Newest: "newest";
997
- /** Сначала старые. */
998
- readonly Oldest: "oldest";
999
- /** Сначала популярные. */
1000
- readonly Popular: "popular";
1001
- }>;
1002
- type CommentSort = (typeof CommentSort)[keyof typeof CommentSort];
1003
- /** Тип вложения. */
1004
- declare const AttachmentType: Readonly<{
1005
- readonly Image: "image";
1006
- readonly Video: "video";
1007
- /** Голосовые комментарии: `audio/ogg`, с полем `duration`. */
1008
- readonly Audio: "audio";
1009
- }>;
1010
- type AttachmentType = (typeof AttachmentType)[keyof typeof AttachmentType];
1011
- /**
1012
- * Тип фрагмента разметки в тексте поста или комментария.
1013
- *
1014
- * Первые два сервер расставляет сам при разборе текста, остальные приходят от редактора.
1015
- * Тип открытый: набор может пополниться.
1016
- *
1017
- * @example
1018
- * ```ts
1019
- * await itd.posts.update(postId, {
1020
- * content: 'жирное слово',
1021
- * spans: [{ type: SpanType.Bold, offset: 0, length: 6 }],
1022
- * });
1023
- * ```
1024
- */
1025
- declare const SpanType: Readonly<{
1026
- /** Хэштег. Название без решётки лежит в `tag`. */
1027
- readonly Hashtag: "hashtag";
1028
- /** Упоминание. Имя пользователя лежит в `tag`. */
1029
- readonly Mention: "mention";
1030
- /** Ссылка. Адрес лежит в `url`, а не в `tag`. */
1031
- readonly Link: "link";
1032
- readonly Bold: "bold";
1033
- readonly Italic: "italic";
1034
- readonly Underline: "underline";
1035
- /** Зачёркнутый. */
1036
- readonly Strike: "strike";
1037
- /** Спойлер: текст скрыт до нажатия. */
1038
- readonly Spoiler: "spoiler";
1039
- /** Моноширинный. */
1040
- readonly Monospace: "monospace";
1041
- readonly Quote: "quote";
1042
- }>;
1043
- type SpanType = Loose<(typeof SpanType)[keyof typeof SpanType]>;
1044
- /** На что подаётся жалоба. */
1045
- declare const ReportTargetType: Readonly<{
1046
- readonly Post: "post";
1047
- readonly Comment: "comment";
1048
- readonly User: "user";
1049
- }>;
1050
- type ReportTargetType = (typeof ReportTargetType)[keyof typeof ReportTargetType];
1051
- /** Причина жалобы. Множество закрытое. */
1052
- declare const ReportReason: Readonly<{
1053
- readonly Spam: "spam";
1054
- readonly Violence: "violence";
1055
- readonly Hate: "hate";
1056
- readonly Adult: "adult";
1057
- readonly Fraud: "fraud";
1058
- readonly Other: "other";
1059
- }>;
1060
- type ReportReason = (typeof ReportReason)[keyof typeof ReportReason];
1061
- /** Состояние realtime-соединения. */
1062
- declare const RealtimeStatus: Readonly<{
1063
- readonly Connecting: "connecting";
1064
- readonly Connected: "connected";
1065
- readonly Error: "error";
1066
- readonly Disconnected: "disconnected";
1067
- }>;
1068
- type RealtimeStatus = (typeof RealtimeStatus)[keyof typeof RealtimeStatus];
1069
- /** Состояние сервиса платформы. Тип открытый. */
1070
- declare const ServiceState: Readonly<{
1071
- /** Работает штатно. */
1072
- readonly Operational: "operational";
1073
- /** Работает с деградацией. */
1074
- readonly Degraded: "degraded";
1075
- /** Недоступен. */
1076
- readonly Downtime: "downtime";
1077
- }>;
1078
- type ServiceState = Loose<(typeof ServiceState)[keyof typeof ServiceState]>;
1079
- /** Вид происшествия в истории сервиса. Тип открытый. */
1080
- declare const IncidentKind: Readonly<{
1081
- /** Недоступен. */
1082
- readonly Down: "down";
1083
- /** Деградация. */
1084
- readonly Degraded: "deg";
1085
- }>;
1086
- type IncidentKind = Loose<(typeof IncidentKind)[keyof typeof IncidentKind]>;
1087
- /**
1088
- * Уровень доступа к разделу профиля.
1089
- *
1090
- * Общий набор значений для полей `wallAccess` и `likesVisibility` настроек приватности.
1091
- * Тип открытый: сервер может прислать значение вне этого перечня.
1092
- */
1093
- declare const AccessType: Readonly<{
1094
- /** Никто. */
1095
- readonly Nobody: "nobody";
1096
- /** Только взаимные подписки. */
1097
- readonly Mutual: "mutual";
1098
- /** Подписчики. */
1099
- readonly Followers: "followers";
1100
- /** Все. */
1101
- readonly Everyone: "everyone";
1102
- }>;
1103
- type AccessType = Loose<(typeof AccessType)[keyof typeof AccessType]>;
1104
- /** Кто может писать на стену профиля. Псевдоним {@link AccessType}. */
1105
- declare const WallAccess: Readonly<{
1106
- /** Никто. */
1107
- readonly Nobody: "nobody";
1108
- /** Только взаимные подписки. */
1109
- readonly Mutual: "mutual";
1110
- /** Подписчики. */
1111
- readonly Followers: "followers";
1112
- /** Все. */
1113
- readonly Everyone: "everyone";
1114
- }>;
1115
- type WallAccess = AccessType;
1116
- /** Кто видит реакции пользователя. Псевдоним {@link AccessType}. */
1117
- declare const LikesVisibility: Readonly<{
1118
- /** Никто. */
1119
- readonly Nobody: "nobody";
1120
- /** Только взаимные подписки. */
1121
- readonly Mutual: "mutual";
1122
- /** Подписчики. */
1123
- readonly Followers: "followers";
1124
- /** Все. */
1125
- readonly Everyone: "everyone";
1126
- }>;
1127
- type LikesVisibility = AccessType;
1128
- /**
1129
- * Канонический тип уведомления (новое поколение имён).
1130
- *
1131
- * REST-эндпоинт `/api/notifications/` отдаёт старые имена (`like`, `comment`, `reply`,
1132
- * `repost`, `mention`), SSE-поток — новые. Библиотека приводит их к этому набору,
1133
- * сохраняя исходное значение в поле `rawType`.
1134
- */
1135
- declare const NotificationType: Readonly<{
1136
- /** Реакция на пост. Старое имя — `like`. */
1137
- readonly PostReaction: "post_reaction";
1138
- /** Комментарий к посту. Старое имя — `comment`. */
1139
- readonly PostComment: "post_comment";
1140
- /** Ответ на комментарий. Старое имя — `reply`. */
1141
- readonly CommentReply: "comment_reply";
1142
- /** Репост. Старое имя — `repost`. */
1143
- readonly PostRepost: "post_repost";
1144
- /** Упоминание в посте. Старое имя — `mention`. */
1145
- readonly PostMention: "post_mention";
1146
- /** Реакция на комментарий. */
1147
- readonly CommentReaction: "comment_reaction";
1148
- /** Упоминание в комментарии. */
1149
- readonly CommentMention: "comment_mention";
1150
- /** Запись на вашей стене. */
1151
- readonly WallPost: "wall_post";
1152
- /** На вас подписались. */
1153
- readonly Follow: "follow";
1154
- /** Заявка на подписку (закрытый профиль). */
1155
- readonly FollowRequest: "follow_request";
1156
- /** Заявка на подписку принята. */
1157
- readonly FollowAccepted: "follow_accepted";
1158
- /** Верификация одобрена. Приходит только по REST. */
1159
- readonly VerificationApproved: "verification_approved";
1160
- /** Верификация отклонена. Приходит только по REST. */
1161
- readonly VerificationRejected: "verification_rejected";
1162
- }>;
1163
- type NotificationType = Loose<(typeof NotificationType)[keyof typeof NotificationType]>;
1164
- /**
1165
- * Тип взаимодействия с контентом в телеметрии (`POST /api/v1/x`, поле `t`).
1166
- *
1167
- * Кодируется числом.
1168
- */
1169
- declare const InteractionType: Readonly<{
1170
- /** Открытие фотографии. */
1171
- readonly PhotoOpen: 1;
1172
- /** Прогресс просмотра видео. Несёт поля `pm`/`dm`. */
1173
- readonly VideoProgress: 2;
1174
- }>;
1175
- type InteractionType = (typeof InteractionType)[keyof typeof InteractionType];
1176
- /**
1177
- * Источник показа поста в телеметрии (поле `s`).
1178
- *
1179
- * Кодируется числом. Поле применимо к источникам `PostPage` и `Link`; для лент источник
1180
- * передаётся контекстом `sc`.
1181
- */
1182
- declare const ViewSource: Readonly<{
1183
- readonly FeedGlobal: 1;
1184
- readonly FeedFollowing: 2;
1185
- readonly FeedClan: 3;
1186
- readonly Profile: 4;
1187
- readonly Hashtag: 5;
1188
- readonly PostPage: 6;
1189
- readonly Link: 7;
1190
- readonly Search: 8;
1191
- }>;
1192
- type ViewSource = (typeof ViewSource)[keyof typeof ViewSource];
1193
- /**
1194
- * Причина завершения просмотра поста в телеметрии (`POST /api/v1/i`, поле `r`).
1195
- *
1196
- * Кодируется числом.
1197
- */
1198
- declare const ViewReason: Readonly<{
1199
- /** Пост ушёл из зоны видимости при обычной прокрутке. */
1200
- readonly Normal: 0;
1201
- /** Потеря фокуса окна. */
1202
- readonly Blur: 1;
1203
- /** Вкладка скрыта. */
1204
- readonly Hidden: 2;
1205
- /** Уход со страницы (`pagehide`). */
1206
- readonly PageHide: 3;
1207
- /** Элемент перестал наблюдаться. */
1208
- readonly Unobserve: 4;
1209
- /** Достигнут порог времени просмотра. */
1210
- readonly ThresholdMet: 5;
1211
- }>;
1212
- type ViewReason = (typeof ViewReason)[keyof typeof ViewReason];
1213
- /**
1214
- * Строковые коды ошибок из поля `code`.
1215
- *
1216
- * Ключи намеренно повторяют написание сервера: код из ответа API можно найти здесь
1217
- * поиском один в один, без мысленного перевода регистра.
1218
- *
1219
- * Список открыт — сервер может добавить новый код, и это не должно ломать типизацию.
1220
- *
1221
- * @example
1222
- * ```ts
1223
- * if (err.hasCode(ItdErrorCode.OTP_INVALID)) await restartOtpFlow();
1224
- * ```
1225
- */
1226
- declare const ItdErrorCode: Readonly<{
1227
- readonly BAD_REQUEST: "BAD_REQUEST";
1228
- readonly UNAUTHORIZED: "UNAUTHORIZED";
1229
- readonly ACCESS_DENIED: "ACCESS_DENIED";
1230
- readonly ENTITY_NOT_FOUND: "ENTITY_NOT_FOUND";
1231
- readonly ENTITY_ALREADY_EXISTS: "ENTITY_ALREADY_EXISTS";
1232
- readonly VALIDATION_ERROR: "VALIDATION_ERROR";
1233
- readonly BUSINESS_RULE_VIOLATION: "BUSINESS_RULE_VIOLATION";
1234
- readonly RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED";
1235
- readonly UNKNOWN_ERROR: "UNKNOWN_ERROR";
1236
- /** Сервер отвечает так на `404`, `ENTITY_NOT_FOUND` в этом случае не приходит. */
1237
- readonly NOT_FOUND: "NOT_FOUND";
1238
- /** На практике не приходит: вместо него сервер шлёт `TURNSTILE_VERIFICATION_FAILED`. */
1239
- readonly CAPTCHA_FAILED: "CAPTCHA_FAILED";
1240
- /** Капча не пройдена: токен Turnstile недействителен, просрочен или уже использован. */
1241
- readonly TURNSTILE_VERIFICATION_FAILED: "TURNSTILE_VERIFICATION_FAILED";
1242
- readonly OTP_INVALID: "OTP_INVALID";
1243
- /** `flowToken` неизвестен или просрочен — поток подтверждения нужно начинать заново. */
1244
- readonly INVALID_FLOW_TOKEN: "INVALID_FLOW_TOKEN";
1245
- readonly ACCOUNT_DEACTIVATED: "ACCOUNT_DEACTIVATED";
1246
- readonly ACCOUNT_EMAIL_DOMAIN_NOT_ALLOWED: "ACCOUNT_EMAIL_DOMAIN_NOT_ALLOWED";
1247
- readonly ACCOUNT_INVALID_CREDENTIALS: "ACCOUNT_INVALID_CREDENTIALS";
1248
- readonly ACCOUNT_TEMPORARILY_LOCKED: "ACCOUNT_TEMPORARILY_LOCKED";
1249
- readonly ACCOUNT_CURRENT_PASSWORD_INCORRECT: "ACCOUNT_CURRENT_PASSWORD_INCORRECT";
1250
- readonly SESSION_EXPIRED: "SESSION_EXPIRED";
1251
- readonly SESSION_REVOKED: "SESSION_REVOKED";
1252
- readonly SESSION_INVALID_REFRESH_TOKEN: "SESSION_INVALID_REFRESH_TOKEN";
1253
- /** Запрос обновления пришёл без cookie `refresh_token` — продлевать нечего. */
1254
- readonly REFRESH_TOKEN_MISSING: "REFRESH_TOKEN_MISSING";
1255
- /** Cookie `refresh_token` есть, но сессии за ней уже нет: отозвана или истекла. */
1256
- readonly SESSION_NOT_FOUND: "SESSION_NOT_FOUND";
1257
- readonly MISSING_FLOW_TOKEN: "MISSING_FLOW_TOKEN";
1258
- readonly PROFILE_USERNAME_TAKEN: "PROFILE_USERNAME_TAKEN";
1259
- readonly PROFILE_RESTRICTION_ACTIVE: "PROFILE_RESTRICTION_ACTIVE";
1260
- readonly PROFILE_MODIFICATION_RESTRICTED: "PROFILE_MODIFICATION_RESTRICTED";
1261
- readonly CONTENT_MODERATION_FAILED: "CONTENT_MODERATION_FAILED";
1262
- readonly FILE_TOO_LARGE: "FILE_TOO_LARGE";
1263
- readonly UNSUPPORTED_FILE_TYPE: "UNSUPPORTED_FILE_TYPE";
1264
- readonly UPLOAD_FAILED: "UPLOAD_FAILED";
1265
- readonly VIDEO_REQUIRES_VERIFICATION: "VIDEO_REQUIRES_VERIFICATION";
1266
- readonly PHONE_VERIFICATION_REQUIRED: "PHONE_VERIFICATION_REQUIRED";
1267
- readonly WRITE_ACCESS_RESTRICTED: "WRITE_ACCESS_RESTRICTED";
1268
- }>;
1269
- type ItdErrorCode = Loose<(typeof ItdErrorCode)[keyof typeof ItdErrorCode]>;
1270
- //#endregion
1271
- //#region src/models/common.d.ts
1272
- /**
1273
- * Дата и время в формате ISO-8601, например `2026-07-21T14:30:00.000Z`.
1274
- *
1275
- * Библиотека не превращает такие поля в `Date`: строку проще сравнивать, логировать
1276
- * и передавать дальше без потерь. Для разбора есть `toDate()`.
1277
- */
1278
- type IsoDate = string;
1279
- /**
1280
- * Идентификатор пользователя — **строго UUID**.
1281
- *
1282
- * Отличается от {@link UserRef} тем, что имя пользователя здесь не подойдёт. Так помечены
1283
- * места, где API принимает только UUID: например `wallRecipientId` при постинге на чужую стену.
1284
- */
1285
- type UserId = string;
1286
- /**
1287
- * Ссылка на пользователя: **UUID либо имя пользователя**.
1288
- *
1289
- * Пути вида `/api/users/{id}` принимают оба варианта, поэтому `itd.users.get('nowkie')`
1290
- * работает так же, как `itd.users.get('9f1c…')`.
1291
- */
1292
- type UserRef = string;
1293
- /**
1294
- * Разметка в тексте поста или комментария.
1295
- *
1296
- * `offset` и `length` измеряются в UTF-16 code units: это те же индексы, которые используют
1297
- * `String#slice`, `substring` и DOM Selection в JavaScript. Эмодзи вне BMP обычно занимают
1298
- * две единицы.
1299
- */
1300
- interface Span {
1301
- /** Тип фрагмента — см. {@link SpanType}. */
1302
- type: SpanType;
1303
- /** Смещение от начала текста. */
1304
- offset: number;
1305
- /** Длина фрагмента. */
1306
- length: number;
1307
- /** Имя хэштега без решётки. У старых mention-объектов может содержать username. */
1308
- tag?: string;
1309
- /** Адрес ссылки. Только у `link`: у него вместо `tag` отдельное поле. */
1310
- url?: string;
1311
- /** Имя пользователя у `mention`. */
1312
- username?: string;
1313
- /** Идентификатор пользователя у некоторых ответов API с `mention`. */
1314
- id?: string;
1315
- }
1316
- //#endregion
1317
- //#region src/core/version.d.ts
1318
- /** Версия библиотеки. Попадает в `User-Agent`. */
1319
- declare const LIBRARY_VERSION = "0.7.1";
1320
- //#endregion
1321
- //#region src/core/config.d.ts
1322
- /** Базовый URL API итд.com. Домен записан в punycode: `итд.com`. */
1323
- declare const DEFAULT_BASE_URL = "https://xn--d1ah4a.com";
1324
- /** Имя встроенного сервиса статуса. */
1325
- declare const STATUS_SERVICE = "status";
1326
- //#endregion
1327
- //#region src/core/auth-provider.d.ts
1328
- /** Области аккаунта и конкретной сессии для локального состояния плагинов. */
1329
- interface AuthIdentity {
1330
- /** Идентификатор пользователя; отсутствует у непрозрачного или повреждённого токена. */
1331
- userId?: UserId | undefined;
1332
- /** Идентификатор серверной сессии; отсутствует у непрозрачного или повреждённого токена. */
1333
- sessionId?: string | undefined;
1118
+ * Сколько `close()` и `dispose()` ждут чужой код, мс. По умолчанию 10000.
1119
+ *
1120
+ * Ждут обработчиков событийного канала и операций, вошедших в обёртки плагинов. По истечении
1121
+ * срока ресурсы всё равно освобождаются, а метод отклоняется `ItdStateError` с указанием
1122
+ * того, что удерживало остановку. `0` снимает ограничение.
1123
+ */
1124
+ shutdownTimeout?: number | undefined;
1125
+ /** Повторные попытки. `false` отключает их полностью. */
1126
+ retry?: RetryOptions | false | undefined;
1127
+ /** Ограничение нагрузки. `false` отключает очередь. */
1128
+ rateLimit?: RateLimitOptions | false | undefined;
1129
+ /** Своя реализация `fetch`: для Deno, React Native, тестов или прокси. */
1130
+ fetch?: typeof fetch | undefined;
1131
+ /** Часы для тайм-аутов, повторов и очередей. Обычно подменяются только в тестах. */
1132
+ clock?: ItdClock | undefined;
1133
+ /** Как обращаться с cookie. По умолчанию определяется по среде исполнения. */
1134
+ mode?: RuntimeMode | undefined;
1135
+ /** Заголовки, добавляемые ко всем запросам, — например `User-Agent` для бота. */
1136
+ headers?: Record<string, string> | undefined;
1137
+ /**
1138
+ * Значение заголовка `User-Agent`. `false` — не отправлять его вовсе.
1139
+ *
1140
+ * По умолчанию `Mozilla/5.0 (compatible; itd-api/<версия>; …)`: `fetch` в Node не шлёт
1141
+ * `User-Agent` сам, а сайт стоит за DDoS-Guard, который такие запросы может не пропустить.
1142
+ * В браузере опция не действует — там заголовок менять запрещено.
1143
+ */
1144
+ userAgent?: string | false | undefined;
1145
+ /** Хуки запросов. */
1146
+ hooks?: ClientHooks | undefined;
1147
+ /** Логгер. `true` — использовать `console`. */
1148
+ logger?: Logger | boolean | undefined;
1334
1149
  }
1335
1150
  /**
1336
- * Что конвейер запросов спрашивает у авторизации.
1337
- *
1338
- * Узкий контракт вместо полноценного менеджера сессии: pipeline не должен знать ни про
1339
- * refresh-токены, ни про хранилище, ни про вход по паролю. Благодаря этому клиент с готовым
1340
- * токеном не тянет за собой сессионную машинерию — она подставляется вызывающим кодом.
1151
+ * Настройки плагинов для отдельной операции.
1341
1152
  *
1342
- * Каждый метод соответствует ровно одной стадии конвейера. Готовые реализации —
1343
- * {@link bearerToken}, {@link tokenProvider} и {@link anonymousAuth}.
1153
+ * Пакеты дополняют интерфейс и используют отдельные именованные поля.
1344
1154
  */
1345
- interface AuthProvider {
1155
+ interface RequestExtensions {}
1156
+ /** Опции выполнения отдельного запроса. Передаются последним аргументом методов ресурсов. */
1157
+ interface RequestOptions {
1158
+ /** Отмена запроса извне. */
1159
+ signal?: AbortSignal | undefined;
1160
+ /** Таймаут только для этого запроса, мс. */
1161
+ timeout?: number | undefined;
1162
+ /** Дополнительные заголовки. */
1163
+ headers?: Record<string, string> | undefined;
1164
+ /** Повторы только для этого запроса. Переопределяют глобальную настройку `retry`. */
1165
+ retry?: RetryOptions | false | undefined;
1346
1166
  /**
1347
- * Текущий токен доступа.
1167
+ * Явно переопределяет безопасность повтора операции.
1348
1168
  *
1349
- * Нужен там, где заголовок не поставить: SSE в браузере и WebSocket передают токен
1350
- * параметром адреса. Конвейеру запросов достаточно {@link currentHeaders}.
1169
+ * Встроенные ресурсы получают значение из каталога. Опция предназначена для произвольных
1170
+ * запросов.
1351
1171
  */
1352
- token(): Promise<string | null>;
1172
+ retrySafety?: RetrySafety | undefined;
1353
1173
  /**
1354
- * Готовит состояние авторизации до входа транспортной попытки в очередь.
1174
+ * Имя бакета, из которого списывается запрос.
1355
1175
  *
1356
- * Чтение хранилища, обращение к внешнему источнику токена и отложенный вход асинхронны,
1357
- * поэтому обязаны завершиться до захвата слота очереди.
1176
+ * Встроенные ресурсы берут его из каталога операций; низкоуровневый вызов без этой
1177
+ * опции попадает в `default`.
1178
+ *
1179
+ * Имя сверяется со встроенной картой — незнакомое отвергается {@link ItdConfigError}
1180
+ * до отправки, независимо от того, включена ли очередь. Своё правило `rateLimit.bucket`
1181
+ * заводит собственное пространство имён и проверку снимает.
1358
1182
  */
1359
- prepare(): Promise<void>;
1183
+ rateLimitBucket?: string | undefined;
1184
+ /** Настройки подключённых плагинов. */
1185
+ extensions?: RequestExtensions | undefined;
1186
+ }
1187
+ /** Опции перебора страниц, не являющиеся параметрами метода API. */
1188
+ interface PaginationOptions extends RequestOptions {
1189
+ /** Максимальное число страниц; без значения перебор продолжается до конца списка. */
1190
+ maxPages?: number | undefined;
1191
+ }
1192
+ /** Полное описание запроса для низкоуровневого `itd.request()`. */
1193
+ interface RawRequestOptions extends RequestOptions {
1360
1194
  /**
1361
- * Заголовки уже подготовленной авторизации.
1195
+ * Имя низкоуровневого запроса. Встроенные ресурсы задают его сами.
1196
+ * Пользовательские значения должны начинаться с `custom:`.
1197
+ */
1198
+ operationId?: OperationId | undefined;
1199
+ method: string;
1200
+ /** Путь с ведущим слэшем, например `/api/posts`. Завершающий слэш значим. */
1201
+ path: string;
1202
+ /**
1203
+ * Имя сервиса, на хост которого уйдёт запрос. Без него запрос идёт на основной `baseUrl`
1204
+ * клиента. Сервисы задаются опцией {@link RuntimeOptions.services}.
1205
+ */
1206
+ service?: string | undefined;
1207
+ /**
1208
+ * Хост этого запроса. Важнее, чем {@link RawRequestOptions.service}.
1362
1209
  *
1363
- * Синхронность существенна: слой стоит внутри очереди, непосредственно перед транспортом,
1364
- * и не должен запускать I/O. Зато запрос, отстоявший в очереди, получает самый свежий токен.
1210
+ * На посторонний основному API хост Bearer-токен по умолчанию не отправляется.
1211
+ * Для осознанного разрешения укажите `skipAuth: false`.
1365
1212
  */
1366
- currentHeaders(): Record<string, string>;
1213
+ baseUrl?: string | undefined;
1214
+ query?: QueryParams | undefined;
1215
+ /** Тело: будет отправлено как JSON. Для загрузки файлов передайте `FormData`. */
1216
+ body?: unknown;
1367
1217
  /**
1368
- * Реакция на ответ `401`.
1218
+ * Не подставлять заголовок авторизации.
1369
1219
  *
1370
- * @returns `true`, если токен обновлён и повторять попытку имеет смысл
1220
+ * Явное `false` разрешает авторизацию и для разового внешнего `baseUrl`; без него
1221
+ * токен автоматически отправляется только основному хосту и его поддоменам.
1371
1222
  */
1372
- recover(): Promise<boolean>;
1373
- /** Значение заголовка `X-Device-Id`. Отправляется и с анонимными запросами. */
1374
- deviceId(): Promise<string>;
1375
- /** Снимает подписки при терминальном освобождении владельца. */
1376
- dispose(): void;
1377
- }
1378
- /**
1379
- * Авторизации нет: заголовок не подставляется, ответ `401` не восстанавливается.
1380
- *
1381
- * @example
1382
- * ```ts
1383
- * const api = createRestClient(); // публичные эндпоинты доступны и без токена
1384
- * ```
1385
- */
1386
- declare function anonymousAuth(): AuthProvider;
1387
- /**
1388
- * Готовый Bearer-токен: ни хранилища, ни продления.
1389
- *
1390
- * Ответ `401` уходит вызывающему коду как есть — обновить токен провайдеру нечем.
1391
- * Для сессии, которая продлевает себя сама, нужен полный клиент.
1392
- *
1393
- * @example
1394
- * ```ts
1395
- * const api = createRestClient({ auth: bearerToken(process.env.ITD_TOKEN) });
1396
- * ```
1397
- */
1398
- declare function bearerToken(accessToken: string): AuthProvider;
1399
- /**
1400
- * Токен из внешнего источника — хранилища секретов, кэша, соседнего сервиса.
1401
- *
1402
- * Источник спрашивается на стадии подготовки, до входа в очередь: там ожидание безопасно,
1403
- * а слот транспорта ещё не занят. Значение держится до следующей подготовки, потому что
1404
- * подстановка заголовков обязана быть синхронной.
1405
- *
1406
- * @example
1407
- * ```ts
1408
- * const api = createRestClient({ auth: tokenProvider(() => vault.read('itd')) });
1409
- * ```
1410
- */
1411
- declare function tokenProvider(getToken: () => string | null | Promise<string | null>): AuthProvider;
1412
- //#endregion
1413
- //#region src/models/users.d.ts
1414
- /** Значок-«пин» в профиле — награда или отметка платформы. */
1415
- interface Pin {
1416
- /** Постоянный идентификатор, например `epepuy_202605_59`. */
1417
- slug: string;
1418
- /** Отображаемое название. */
1419
- name: string;
1420
- /** Описание, за что выдан. */
1421
- description: string;
1422
- /** Адрес изображения. */
1423
- url: string;
1424
- /** Когда выдан. Приходит только в списке своих пинов. */
1425
- grantedAt?: IsoDate;
1426
- }
1427
- /**
1428
- * Автор поста или комментария.
1429
- *
1430
- * Встречается внутри `post.author` и `comment.author`.
1431
- */
1432
- interface Author {
1433
- id: UserId;
1434
- username: string;
1435
- displayName: string;
1223
+ skipAuth?: boolean | undefined;
1224
+ /** Не пытаться обновить токен при `401` используется самими эндпоинтами авторизации. */
1225
+ skipAuthRefresh?: boolean | undefined;
1436
1226
  /**
1437
- * **Эмодзи, а не картинка.**
1227
+ * Выполнить запрос мимо очереди.
1438
1228
  *
1439
- * На итд.com аватар это символ клана (`🩵`, `🦎`), а не адрес изображения.
1440
- * Отрисовывать его нужно как текст.
1229
+ * Служебная настройка для интеграций. Встроенные вход и обновление токена проходят очередь.
1441
1230
  */
1442
- avatar: string;
1443
- /** Пройдена ли верификация. */
1444
- verified: boolean;
1445
- /** Активный значок профиля. Может отсутствовать. */
1446
- pin?: Pin | null;
1447
- /** Есть ли премиум-подписка (значок NUKSTA). */
1448
- hasNuksta?: boolean;
1449
- }
1450
- /**
1451
- * Участник события в уведомлении.
1452
- *
1453
- * Отличается от {@link Author} набором полей: вместо значков приходит связь с вами.
1454
- */
1455
- interface Actor {
1456
- id: UserId;
1457
- username: string;
1458
- displayName: string;
1459
- /** Эмодзи-аватар, см. {@link Author.avatar}. */
1460
- avatar: string;
1461
- /** Подписаны ли вы на этого пользователя. */
1462
- isFollowing?: boolean;
1463
- /** Подписан ли он на вас. */
1464
- isFollowedBy?: boolean;
1465
- }
1466
- /**
1467
- * Пользователь в списках.
1468
- *
1469
- * Набор полей зависит от эндпоинта: подписчики и подписки приносят `isFollowing`,
1470
- * поиск и рекомендации — `followersCount` и `hasNuksta`. Необязательные поля отражают
1471
- * это различие.
1472
- */
1473
- interface UserSummary {
1474
- id: UserId;
1475
- username: string;
1476
- displayName: string;
1477
- /** Эмодзи-аватар, см. {@link Author.avatar}. */
1478
- avatar: string;
1479
- verified: boolean;
1480
- /** Подписаны ли вы. Приходит в списках подписчиков и подписок. */
1481
- isFollowing?: boolean;
1482
- /** Есть ли премиум. Приходит в поиске и рекомендациях. */
1483
- hasNuksta?: boolean;
1484
- /** Число подписчиков. Приходит в поиске и рекомендациях. */
1485
- followersCount?: number;
1486
- }
1487
- /** Поля профиля, общие для своего и чужого. */
1488
- interface ProfileBase {
1489
- id: UserId;
1490
- username: string;
1491
- displayName: string;
1492
- /** Эмодзи-аватар, см. {@link Author.avatar}. */
1493
- avatar: string;
1494
- /** URL изображения баннера либо `null`. */
1495
- banner: string | null;
1496
- /** Описание профиля. */
1497
- bio: string;
1498
- verified: boolean;
1499
- pin?: Pin | null;
1500
- /** Кто может писать на стену. */
1501
- wallAccess: WallAccess;
1502
- /** Кто видит реакции. */
1503
- likesVisibility: LikesVisibility;
1504
- followersCount: number;
1505
- followingCount: number;
1506
- postsCount: number;
1507
- createdAt: IsoDate;
1508
- }
1509
- /** Состояние подписки на премиум. */
1510
- interface SubscriptionState {
1511
- isActive: boolean;
1512
- expiresAt: IsoDate | null;
1513
- autoRenewal: boolean;
1514
- }
1515
- /**
1516
- * Свой профиль — ответ `GET /api/users/me`.
1517
- *
1518
- * Отличается от чужого наличием {@link subscription} и {@link isPhoneVerified}
1519
- * и отсутствием полей связи (`isFollowing`, `online`).
1520
- */
1521
- interface MyProfile extends ProfileBase {
1522
- /** Закрыт ли профиль. */
1523
- isPrivate: boolean;
1524
- /** Подтверждён ли телефон. Без него часть действий недоступна. */
1525
- isPhoneVerified: boolean;
1526
- /** Своя премиум-подписка. */
1527
- subscription: SubscriptionState;
1231
+ skipQueue?: boolean | undefined;
1232
+ /** Вернуть тело ответа без снятия обёртки `{ data: … }`. */
1233
+ raw?: boolean | undefined;
1528
1234
  }
1529
- /**
1530
- * Состояние авторизации — ответ `GET /api/profile`.
1531
- *
1532
- * Endpoint доступен без сессии: в этом случае `authenticated` равен `false`,
1533
- * а `user` — `null`.
1534
- */
1535
- interface AuthState {
1536
- /** Есть ли действующая сессия. */
1537
- authenticated: boolean;
1538
- /** Заблокирован ли текущий аккаунт. */
1539
- banned: boolean;
1540
- /** Текущий пользователь либо `null` без действующей сессии. */
1541
- user: MyProfile | null;
1235
+ /** Подготовленный запрос с обязательным идентификатором операции. */
1236
+ interface OperationRequestOptions extends RawRequestOptions {
1237
+ operationId: OperationId;
1238
+ }
1239
+ //#endregion
1240
+ //#region src/core/operation.d.ts
1241
+ /** HTTP-метод операции. */
1242
+ type OperationMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
1243
+ /** ID операции подключаемого модуля: `<featureName>.<operationName>`. */
1244
+ type FeatureOperationId<TFeatureName extends string = string, TOperationName extends string = string> = `${TFeatureName}.${TOperationName}`;
1245
+ /** Семантическая безопасность автоматического повтора операции. */
1246
+ declare const RetrySafety: Readonly<{
1247
+ /** Автоматический повтор не создаёт неприемлемого эффекта; обычно это чтение. */
1248
+ readonly Safe: "safe";
1249
+ /** Повтор операции приводит к тому же состоянию, что и один вызов. */
1250
+ readonly Idempotent: "idempotent";
1251
+ /** Повтор может создать ещё один побочный эффект. */
1252
+ readonly Unsafe: "unsafe";
1253
+ }>;
1254
+ type RetrySafety = (typeof RetrySafety)[keyof typeof RetrySafety];
1255
+ /** Расширяемые метаданные операции. Плагины добавляют собственные поля. */
1256
+ interface OperationAnnotations {}
1257
+ /** Публичные неизменяемые метаданные семантической операции. */
1258
+ interface OperationMetadata {
1259
+ readonly method: OperationMethod;
1260
+ readonly retrySafety: RetrySafety;
1261
+ readonly bucket?: string;
1262
+ readonly annotations?: Readonly<OperationAnnotations>;
1542
1263
  }
1543
1264
  /**
1544
- * Чужой профиль ответ `GET /api/users/{id|username}`.
1265
+ * Контракт результата одного HTTP-запроса.
1545
1266
  *
1546
- * Вместо своей подписки содержит связь с вами и присутствие.
1267
+ * Функция чтения принадлежит исполнителю и не входит в API плагинов. Все вызовы одного `id`
1268
+ * используют один контракт, поэтому форма результата не зависит от места вызова.
1547
1269
  */
1548
- interface PublicProfile extends ProfileBase {
1549
- hasNuksta?: boolean;
1550
- /** Закреплённый пост, если он есть. */
1551
- pinnedPostId: string | null;
1552
- /** Подписаны ли вы на него. */
1553
- isFollowing: boolean;
1554
- /** Подписан ли он на вас. */
1555
- isFollowedBy: boolean;
1556
- /** Сейчас ли пользователь в сети. */
1557
- online: boolean;
1558
- /** Когда был в сети. `null`, если скрыто настройками приватности. */
1559
- lastSeen: IsoDate | null;
1560
- }
1561
- /** Профиль: свой либо чужой. Различаются функцией `isMyProfile()`. */
1562
- type Profile = MyProfile | PublicProfile;
1563
- /** Настройки приватности профиля. */
1564
- interface PrivacySettings {
1565
- /** Закрыт ли профиль: подписка требует одобрения. */
1566
- isPrivate: boolean;
1567
- wallAccess: WallAccess;
1568
- likesVisibility: LikesVisibility;
1569
- /** Показывать ли время последнего посещения. */
1570
- showLastSeen: boolean;
1270
+ interface OperationContract<T = unknown, TId extends string = string> extends OperationMetadata {
1271
+ readonly id: TId;
1272
+ /** Преобразует разобранное тело HTTP-ответа в результат операции. @internal */
1273
+ readonly read: (body: unknown, request: Readonly<OperationRequestOptions>) => T;
1571
1274
  }
1572
1275
  /**
1573
- * Результат подписки на пользователя.
1276
+ * Минимальное стабильное описание операции, доступное core и плагинам.
1574
1277
  *
1575
- * @example
1576
- * ```ts
1577
- * const result = await itd.users.follow('nowkie');
1578
- * // { following: true, followersCount: 11 }
1579
- * ```
1278
+ * Форма описания принадлежит ядру; заполненный ими каталог — доменному слою.
1580
1279
  */
1581
- interface FollowResult {
1582
- /** Подписка оформлена. У закрытого профиля отправляется заявка, и здесь будет `false`. */
1583
- following: boolean;
1584
- /** Сколько подписчиков стало у пользователя после действия. */
1585
- followersCount?: number;
1586
- /** Статус заявки, если профиль закрыт. */
1587
- status?: Loose<'following' | 'requested'>;
1588
- }
1589
- /** Закреплённые значки профиля и выбранный из них. */
1590
- interface PinsResult {
1591
- pins: Pin[];
1592
- /** Идентификатор активного значка — строка, а не объект. */
1593
- activePin: string | null;
1280
+ interface OperationDefinition extends OperationMetadata {
1281
+ /**
1282
+ * Бакет операции. Опущено — операция списывает из бакета по умолчанию.
1283
+ *
1284
+ * Счётчик определяется парой «путь + метод»: `GET /api/users/me` — 40 запросов
1285
+ * в минуту, `PUT` того же пути — 3, `DELETE` — 150.
1286
+ */
1287
+ readonly bucket?: string;
1594
1288
  }
1595
1289
  //#endregion
1596
- //#region src/models/notifications.d.ts
1290
+ //#region src/core/version.d.ts
1291
+ /** Версия библиотеки. Попадает в `User-Agent`. */
1292
+ declare const LIBRARY_VERSION = "0.8.0";
1293
+ //#endregion
1294
+ //#region src/core/config.d.ts
1295
+ /** Базовый URL API итд.com. Домен записан в punycode: `итд.com`. */
1296
+ declare const DEFAULT_BASE_URL = "https://xn--d1ah4a.com";
1297
+ /** Имя встроенного сервиса статуса. */
1298
+ declare const STATUS_SERVICE = "status";
1299
+ //#endregion
1300
+ //#region src/core/emitter.d.ts
1301
+ /** Обработчик события. */
1302
+ type Listener<T> = (payload: T) => void;
1303
+ /** Функция отписки, которую возвращает подписка на событие. */
1304
+ type Unsubscribe = () => void;
1597
1305
  /**
1598
- * Уведомление в единой форме.
1306
+ * Минимальный типизированный источник событий.
1599
1307
  *
1600
- * REST-список и SSE-поток отдают уведомления по-разному разные имена типов, разные имена
1601
- * полей, один участник против массива. Библиотека приводит оба вида к этой структуре,
1602
- * поэтому объекты из `itd.notifications.list()` и из потока можно складывать в один список.
1308
+ * Своя реализация вместо `EventTarget` и `EventEmitter`: первый есть не везде и требует
1309
+ * обёрток `CustomEvent`, второй существует только в Node. Нужны ровно подписка и рассылка.
1603
1310
  *
1604
- * Исходные данные не теряются: серверное имя типа остаётся в {@link rawType},
1605
- * а весь необработанный объект — в {@link raw}.
1311
+ * Исключение в обработчике не прерывает рассылку остальным и не роняет библиотеку.
1312
+ *
1313
+ * @typeParam Events карта «имя события → тип полезной нагрузки». Задаётся интерфейсом,
1314
+ * поэтому ограничение на индексную сигнатуру намеренно не накладывается.
1606
1315
  */
1607
- interface Notification {
1608
- id: string;
1609
- /** Канонический тип. Старые имена (`like`, `comment`) приведены к новым. */
1610
- type: NotificationType;
1611
- /** Имя типа в том виде, в каком его прислал сервер. */
1612
- rawType: string;
1613
- /** Объект события: пост, комментарий, пользователь. */
1614
- entityId: string | null;
1615
- /** Пост, которому принадлежит комментарий, если событие о комментарии. */
1616
- parentEntityId: string | null;
1617
- /** Прочитано ли уведомление. */
1618
- isRead: boolean;
1619
- /** Кто совершил действие. Для схлопнутых уведомлений — несколько человек. */
1620
- actors: Actor[];
1621
- /** Сколько участников схлопнуто в одно уведомление. Минимум 1. */
1622
- count: number;
1623
- /** Текст или заголовок объекта события. */
1624
- preview: string | null;
1625
- /** Ссылка перехода, предложенная сервером. Обычно точнее её `resolveNotificationUrl()`. */
1626
- clickUrl?: string;
1627
- createdAt: IsoDate;
1628
- /** Когда уведомление изменилось например было прочитано. */
1629
- updatedAt: IsoDate;
1630
- /** Исходный объект как он пришёл от сервера. */
1631
- raw: unknown;
1316
+ declare class Emitter<Events> {
1317
+ #private;
1318
+ constructor(onListenerError?: (error: unknown) => void);
1319
+ /**
1320
+ * Подписывается на событие.
1321
+ *
1322
+ * @returns функция отписки
1323
+ *
1324
+ * @example
1325
+ * ```ts
1326
+ * const off = events.on('notification', (event) => console.log(event));
1327
+ * off();
1328
+ * ```
1329
+ */
1330
+ on<K extends keyof Events>(event: K, listener: Listener<Events[K]>): Unsubscribe;
1331
+ /** Подписывается на одно срабатывание. */
1332
+ once<K extends keyof Events>(event: K, listener: Listener<Events[K]>): Unsubscribe;
1333
+ /** Отписывается от события. */
1334
+ off<K extends keyof Events>(event: K, listener: Listener<Events[K]>): void;
1335
+ /** Рассылает событие подписчикам. */
1336
+ emit<K extends keyof Events>(event: K, payload: Events[K]): void;
1337
+ /** Сколько подписчиков у события. */
1338
+ listenerCount(event: keyof Events): number;
1339
+ /** Снимает все подписки. */
1340
+ removeAllListeners(): void;
1341
+ }
1342
+ //#endregion
1343
+ //#region src/core/auth-provider.d.ts
1344
+ /** Области аккаунта и конкретной сессии для локального состояния плагинов. */
1345
+ interface AuthIdentity {
1346
+ /** Идентификатор пользователя; отсутствует у непрозрачного или повреждённого токена. */
1347
+ userId?: UserId | undefined;
1348
+ /** Идентификатор серверной сессии; отсутствует у непрозрачного или повреждённого токена. */
1349
+ sessionId?: string | undefined;
1632
1350
  }
1633
1351
  /**
1634
- * Настройки уведомлений.
1352
+ * Что конвейер запросов спрашивает у авторизации.
1635
1353
  *
1636
- * Сервер отдаёт плоский объект, но исторически знает два набора имён для одних и тех же
1637
- * настроек (`likes` и `reactions`, `comments` и `replies`). При сохранении библиотека
1638
- * отправляет оба, при чтении принимает любой.
1354
+ * Узкий контракт вместо полноценного менеджера сессии: pipeline не должен знать ни про
1355
+ * refresh-токены, ни про хранилище, ни про вход по паролю. Благодаря этому клиент с готовым
1356
+ * токеном не тянет за собой сессионную машинерию — она подставляется вызывающим кодом.
1357
+ *
1358
+ * Каждый метод соответствует ровно одной стадии конвейера. Готовые реализации —
1359
+ * `bearerToken()`, `tokenProvider()` и `anonymousAuth()`.
1639
1360
  */
1640
- interface NotificationSettings {
1641
- /** Общий выключатель доставки. */
1642
- enabled: boolean;
1643
- /** Звук уведомления. */
1644
- sound: boolean;
1645
- /** Новые подписчики. */
1646
- follows: boolean;
1647
- /** Записи на вашей стене. */
1648
- wallPosts: boolean;
1649
- /** Реакции на ваши записи. */
1650
- likes: boolean;
1651
- /** Комментарии и ответы. */
1652
- comments: boolean;
1653
- /** Упоминания. */
1654
- mentions: boolean;
1655
- }
1656
- //#endregion
1657
- //#region src/notifications/normalize.d.ts
1658
- /** Событие потока уведомлений после разбора. */
1659
- interface NotificationEvent {
1660
- /** Само уведомление в единой форме. */
1661
- notification: Notification;
1361
+ interface AuthProvider {
1662
1362
  /**
1663
- * Актуальное число непрочитанных, если сервер его сообщил.
1363
+ * Текущий токен доступа.
1664
1364
  *
1665
- * Клиент не увеличивает счётчик сам: значение приходит с сервера.
1365
+ * Нужен там, где заголовок не поставить: SSE в браузере и WebSocket передают токен
1366
+ * параметром адреса. Конвейеру запросов достаточно {@link currentHeaders}.
1666
1367
  */
1667
- unreadCount: number | undefined;
1668
- /** Нужно ли проиграть звук. */
1669
- sound: boolean;
1368
+ token(): Promise<string | null>;
1369
+ /**
1370
+ * Готовит состояние авторизации до входа транспортной попытки в очередь.
1371
+ *
1372
+ * Чтение хранилища, обращение к внешнему источнику токена и отложенный вход асинхронны,
1373
+ * поэтому обязаны завершиться до захвата слота очереди.
1374
+ */
1375
+ prepare(): Promise<void>;
1376
+ /**
1377
+ * Заголовки уже подготовленной авторизации.
1378
+ *
1379
+ * Синхронность существенна: слой стоит внутри очереди, непосредственно перед транспортом,
1380
+ * и не должен запускать I/O. Зато запрос, отстоявший в очереди, получает самый свежий токен.
1381
+ */
1382
+ currentHeaders(): Record<string, string>;
1383
+ /**
1384
+ * Реакция на ответ `401`.
1385
+ *
1386
+ * @returns `true`, если токен обновлён и повторять попытку имеет смысл
1387
+ */
1388
+ recover(): Promise<boolean>;
1389
+ /** Значение заголовка `X-Device-Id`. Отправляется и с анонимными запросами. */
1390
+ deviceId(): Promise<string>;
1391
+ /** Снимает подписки при терминальном освобождении владельца. */
1392
+ dispose(): void;
1670
1393
  }
1671
1394
  /**
1672
- * Приводит уведомление к единой форме.
1395
+ * Авторизации нет: заголовок не подставляется, ответ `401` не восстанавливается.
1673
1396
  *
1674
- * Нужна потому, что REST-список и поток событий описывают одно и то же событие по-разному:
1675
- * различаются имена типов (`like` против `post_reaction`), имена полей
1676
- * (`targetId`/`entityId`, `read`/`isRead`, `preview`/`entityPreview`) и число участников
1677
- * (`actor` против массива `actors`). После приведения объекты из обоих источников
1678
- * можно складывать в один список.
1397
+ * @example
1398
+ * ```ts
1399
+ * const api = createRestClient(); // публичные эндпоинты доступны и без токена
1400
+ * ```
1401
+ */
1402
+ declare function anonymousAuth(): AuthProvider;
1403
+ /**
1404
+ * Готовый Bearer-токен: ни хранилища, ни продления.
1405
+ *
1406
+ * Ответ `401` уходит вызывающему коду как есть — обновить токен провайдеру нечем.
1407
+ * Для сессии, которая продлевает себя сама, нужен полный клиент.
1679
1408
  *
1680
- * Исходные данные не теряются: имя типа с сервера остаётся в `rawType`,
1681
- * весь объект целиком — в `raw`.
1409
+ * @example
1410
+ * ```ts
1411
+ * const api = createRestClient({ auth: bearerToken(process.env.ITD_TOKEN) });
1412
+ * ```
1413
+ */
1414
+ declare function bearerToken(accessToken: string): AuthProvider;
1415
+ /**
1416
+ * Токен из внешнего источника — хранилища секретов, кэша, соседнего сервиса.
1682
1417
  *
1683
- * @param input уведомление из REST-ответа либо полезная нагрузка события потока
1418
+ * Источник спрашивается на стадии подготовки, до входа в очередь: там ожидание безопасно,
1419
+ * а слот транспорта ещё не занят. Значение держится до следующей подготовки, потому что
1420
+ * подстановка заголовков обязана быть синхронной.
1684
1421
  *
1685
1422
  * @example
1686
1423
  * ```ts
1687
- * const fromRest = normalizeNotification(restItem);
1688
- * const fromStream = normalizeNotification(event.payload);
1689
- * // одинаковая форма — можно объединять
1424
+ * const api = createRestClient({ auth: tokenProvider(() => vault.read('itd')) });
1690
1425
  * ```
1691
1426
  */
1692
- declare function normalizeNotification(input: unknown): Notification;
1427
+ declare function tokenProvider(getToken: () => string | null | Promise<string | null>): AuthProvider;
1428
+ //#endregion
1429
+ //#region src/core/connection.d.ts
1430
+ /**
1431
+ * Разрешённое окружение одного долговременного соединения клиента.
1432
+ *
1433
+ * Синхронизационные запросы выполняются через адаптер предметного модуля.
1434
+ */
1435
+ interface ClientConnection {
1436
+ /** Фактический HTTP(S)-адрес сервиса с учётом настроек клиента. */
1437
+ readonly baseUrl: string;
1438
+ /** Разрешено ли соединению передавать Bearer-токен этому сервису. */
1439
+ readonly authorize: boolean;
1440
+ readonly fetch: typeof fetch;
1441
+ readonly clock: ItdClock;
1442
+ readonly logger: Logger | undefined;
1443
+ /** Заголовки платформы и сервиса без Bearer-токена. */
1444
+ baseHeaders(url: string): Promise<Headers>;
1445
+ /** Текущий токен; способ его передачи выбирает транспорт. */
1446
+ getToken(): Promise<string | null>;
1447
+ /** Пытается восстановить авторизацию после отказа транспорта. */
1448
+ refreshAuth(): Promise<boolean>;
1449
+ }
1693
1450
  //#endregion
1694
1451
  //#region src/core/errors.d.ts
1695
1452
  /** Бренд, по которому ошибки библиотеки распознаются надёжнее, чем через `instanceof`. */
@@ -1763,268 +1520,549 @@ declare class ItdError extends Error {
1763
1520
  cause?: unknown;
1764
1521
  });
1765
1522
  }
1766
- /** Параметры конструктора {@link ItdApiError}. */
1767
- interface ItdApiErrorInit {
1768
- /** HTTP-статус ответа. */
1769
- status: number;
1770
- /** Строковый код ошибки из тела ответа. */
1771
- code: ItdErrorCode;
1772
- /** Человекочитаемое сообщение. */
1773
- message: string;
1774
- /** Расширенное описание, если сервер его прислал. */
1775
- detail?: string | undefined;
1776
- /** Заголовок ошибки, если сервер его прислал. */
1777
- title?: string | undefined;
1778
- /** Ошибки по конкретным полям (сведены из `errors` и `violations`). */
1779
- fieldErrors?: ItdFieldErrors | undefined;
1780
- /** Идентификатор запроса из заголовков ответа, если есть. */
1781
- requestId?: string | undefined;
1782
- /** HTTP-метод запроса. */
1783
- method: string;
1784
- /** Путь запроса без базового URL. */
1785
- path: string;
1786
- /** Тело ответа как оно пришло — на случай, если документация разошлась с реальностью. */
1787
- raw: unknown;
1788
- /** Сам объект ответа. Тело уже прочитано. */
1789
- response?: Response | undefined;
1790
- /** Значение `Retry-After` в миллисекундах, если заголовок был. */
1791
- retryAfter?: number | undefined;
1792
- /** Сколько запросов разрешено в окне (`x-ratelimit-limit`). */
1793
- rateLimit?: number | undefined;
1794
- /** Сколько запросов осталось в окне (`x-ratelimit-remaining`). */
1795
- rateLimitRemaining?: number | undefined;
1523
+ /** Параметры конструктора {@link ItdApiError}. */
1524
+ interface ItdApiErrorInit {
1525
+ /** HTTP-статус ответа. */
1526
+ status: number;
1527
+ /** Строковый код ошибки из тела ответа. */
1528
+ code: ItdErrorCode;
1529
+ /** Человекочитаемое сообщение. */
1530
+ message: string;
1531
+ /** Расширенное описание, если сервер его прислал. */
1532
+ detail?: string | undefined;
1533
+ /** Заголовок ошибки, если сервер его прислал. */
1534
+ title?: string | undefined;
1535
+ /** Ошибки по конкретным полям (сведены из `errors` и `violations`). */
1536
+ fieldErrors?: ItdFieldErrors | undefined;
1537
+ /** Идентификатор запроса из заголовков ответа, если есть. */
1538
+ requestId?: string | undefined;
1539
+ /** HTTP-метод запроса. */
1540
+ method: string;
1541
+ /** Путь запроса без базового URL. */
1542
+ path: string;
1543
+ /** Тело ответа как оно пришло — на случай, если документация разошлась с реальностью. */
1544
+ raw: unknown;
1545
+ /** Сам объект ответа. Тело уже прочитано. */
1546
+ response?: Response | undefined;
1547
+ /** Значение `Retry-After` в миллисекундах, если заголовок был. */
1548
+ retryAfter?: number | undefined;
1549
+ /** Сколько запросов разрешено в окне (`x-ratelimit-limit`). */
1550
+ rateLimit?: number | undefined;
1551
+ /** Сколько запросов осталось в окне (`x-ratelimit-remaining`). */
1552
+ rateLimitRemaining?: number | undefined;
1553
+ }
1554
+ /**
1555
+ * Ошибка, возвращённая сервером итд.com (HTTP-статус ≥ 400).
1556
+ *
1557
+ * API отдаёт ошибки в двух разных формах — `{ error: { … } }` и `{ code, message, violations }`.
1558
+ * Библиотека сводит обе к этому классу, поэтому разбирать форму ответа вручную не нужно.
1559
+ *
1560
+ * @example
1561
+ * ```ts
1562
+ * try {
1563
+ * await itd.users.updateMe({ username: 'занятое_имя' });
1564
+ * } catch (e) {
1565
+ * if (e instanceof ItdValidationError) {
1566
+ * console.log(e.fieldErrors.username); // ['Имя уже занято']
1567
+ * }
1568
+ * }
1569
+ * ```
1570
+ */
1571
+ declare class ItdApiError extends ItdError {
1572
+ /**
1573
+ * Разновидность ошибки: та же информация, что и класс, но пригодная для сравнения.
1574
+ *
1575
+ * Позволяет разбирать ошибку через `switch`, а проверкам вроде {@link isItdAuthError} —
1576
+ * работать даже когда в проекте оказались две копии библиотеки.
1577
+ */
1578
+ readonly apiKind: ItdApiErrorKind;
1579
+ /** HTTP-статус ответа. */
1580
+ readonly status: number;
1581
+ /** Строковый код ошибки, например `VALIDATION_ERROR`. */
1582
+ readonly code: ItdErrorCode;
1583
+ /** Расширенное описание, если сервер его прислал. */
1584
+ readonly detail: string | undefined;
1585
+ /** Заголовок ошибки, если сервер его прислал. */
1586
+ readonly title: string | undefined;
1587
+ /** Ошибки по полям. Пустой объект, если сервер их не прислал. */
1588
+ readonly fieldErrors: ItdFieldErrors;
1589
+ /** Идентификатор запроса из заголовков ответа. */
1590
+ readonly requestId: string | undefined;
1591
+ /** HTTP-метод запроса. */
1592
+ readonly method: string;
1593
+ /** Путь запроса без базового URL. */
1594
+ readonly path: string;
1595
+ /** Тело ответа как оно пришло. */
1596
+ readonly raw: unknown;
1597
+ /** Объект ответа. Тело уже прочитано и повторно прочитано быть не может. */
1598
+ readonly response: Response | undefined;
1599
+ /** Пауза из заголовка `Retry-After` в миллисекундах. Сервер итд.com его не присылает. */
1600
+ readonly retryAfter: number | undefined;
1601
+ /**
1602
+ * Сколько запросов разрешено в окне — заголовок `x-ratelimit-limit`.
1603
+ *
1604
+ * Времени сброса окна сервер не сообщает, поэтому точный момент повтора неизвестен.
1605
+ */
1606
+ readonly rateLimit: number | undefined;
1607
+ /** Сколько запросов осталось в окне — заголовок `x-ratelimit-remaining`. */
1608
+ readonly rateLimitRemaining: number | undefined;
1609
+ /**
1610
+ * @param apiKind разновидность; подставляется подклассами, снаружи задавать не нужно
1611
+ */
1612
+ constructor(init: ItdApiErrorInit, apiKind?: ItdApiErrorKind);
1613
+ /**
1614
+ * Проверяет код ошибки. Удобнее, чем сравнивать строки вручную.
1615
+ *
1616
+ * @example
1617
+ * ```ts
1618
+ * if (err.hasCode('OTP_INVALID', 'MISSING_FLOW_TOKEN')) await restartOtpFlow();
1619
+ * ```
1620
+ */
1621
+ hasCode(...codes: ItdErrorCode[]): boolean;
1622
+ /** Имеет ли смысл повторить запрос: `429` и серверные ошибки `5xx`. */
1623
+ get isRetryable(): boolean;
1624
+ }
1625
+ /** `400` / `422` — данные не прошли валидацию. Подробности в {@link ItdApiError.fieldErrors}. */
1626
+ declare class ItdValidationError extends ItdApiError {
1627
+ constructor(init: ItdApiErrorInit);
1628
+ }
1629
+ /** `401` — токен отсутствует, истёк или отозван. */
1630
+ declare class ItdAuthError extends ItdApiError {
1631
+ constructor(init: ItdApiErrorInit);
1632
+ }
1633
+ /** `403` — доступ запрещён либо действие ограничено настройками приватности. */
1634
+ declare class ItdForbiddenError extends ItdApiError {
1635
+ constructor(init: ItdApiErrorInit);
1636
+ }
1637
+ /** `404` — сущность не найдена. */
1638
+ declare class ItdNotFoundError extends ItdApiError {
1639
+ constructor(init: ItdApiErrorInit);
1640
+ }
1641
+ /** `409` — сущность уже существует. */
1642
+ declare class ItdConflictError extends ItdApiError {
1643
+ constructor(init: ItdApiErrorInit);
1644
+ }
1645
+ /**
1646
+ * `429` — превышен лимит запросов.
1647
+ *
1648
+ * Если сервер прислал `Retry-After`, пауза доступна в {@link ItdApiError.retryAfter}
1649
+ * (в миллисекундах). При включённых ретраях библиотека выдерживает её автоматически.
1650
+ */
1651
+ declare class ItdRateLimitError extends ItdApiError {
1652
+ constructor(init: ItdApiErrorInit);
1653
+ }
1654
+ /**
1655
+ * Действие требует подтверждённого телефона (`PHONE_VERIFICATION_REQUIRED`).
1656
+ *
1657
+ * Подтверждение проходит через Telegram-бота: ссылка лежит в {@link verificationUrl}.
1658
+ */
1659
+ declare class ItdPhoneVerificationError extends ItdApiError {
1660
+ /** Ссылка на бота подтверждения, если удалось определить идентификатор пользователя. */
1661
+ readonly verificationUrl: string | undefined;
1662
+ constructor(init: ItdApiErrorInit & {
1663
+ userId?: string | undefined;
1664
+ });
1665
+ }
1666
+ /** `5xx` — ошибка на стороне сервера. */
1667
+ declare class ItdServerError extends ItdApiError {
1668
+ constructor(init: ItdApiErrorInit);
1669
+ }
1670
+ /** Причина ошибки получения вложения. */
1671
+ declare const ItdFileErrorReason: Readonly<{
1672
+ /** Сетевой сбой при получении источника. */
1673
+ readonly Network: "network";
1674
+ /** Источник ответил ошибочным HTTP-статусом. */
1675
+ readonly Http: "http";
1676
+ /** Источник превысил разрешённый размер. */
1677
+ readonly TooLarge: "too_large";
1678
+ /** Среда или источник не предоставили поток. */
1679
+ readonly StreamUnavailable: "stream_unavailable";
1680
+ /** Поток источника завершился ошибкой. */
1681
+ readonly Read: "read";
1682
+ }>;
1683
+ type ItdFileErrorReason = (typeof ItdFileErrorReason)[keyof typeof ItdFileErrorReason];
1684
+ /** Не удалось получить или прочитать содержимое вложения. */
1685
+ declare class ItdFileError extends ItdError {
1686
+ readonly reason: ItdFileErrorReason;
1687
+ /** Адрес источника без секретных параметров запроса, если файл получался по сети. */
1688
+ readonly url: string | undefined;
1689
+ /** HTTP-статус источника. */
1690
+ readonly status: number | undefined;
1691
+ /** Разрешённый размер в байтах. */
1692
+ readonly limit: number | undefined;
1693
+ /** Обнаруженный размер в байтах. */
1694
+ readonly actual: number | undefined;
1695
+ /** Имеет ли смысл повторить получение источника. */
1696
+ readonly retryable: boolean;
1697
+ constructor(message: string, init: {
1698
+ reason: ItdFileErrorReason;
1699
+ url?: string | undefined;
1700
+ status?: number | undefined;
1701
+ limit?: number | undefined;
1702
+ actual?: number | undefined;
1703
+ retryable?: boolean | undefined;
1704
+ cause?: unknown;
1705
+ });
1706
+ }
1707
+ /** Запрос не дошёл до сервера: DNS, обрыв соединения, отсутствие сети. */
1708
+ declare class ItdNetworkError extends ItdError {
1709
+ /** HTTP-метод запроса. */
1710
+ readonly method: string;
1711
+ /** Путь запроса без базового URL. */
1712
+ readonly path: string;
1713
+ constructor(message: string, init: {
1714
+ method: string;
1715
+ path: string;
1716
+ cause?: unknown;
1717
+ });
1718
+ }
1719
+ /** Истёк таймаут запроса, заданный опцией `timeout`. */
1720
+ declare class ItdTimeoutError extends ItdError {
1721
+ /** Значение таймаута в миллисекундах. */
1722
+ readonly timeout: number;
1723
+ /** HTTP-метод запроса. */
1724
+ readonly method: string;
1725
+ /** Путь запроса без базового URL. */
1726
+ readonly path: string;
1727
+ constructor(init: {
1728
+ timeout: number;
1729
+ method: string;
1730
+ path: string;
1731
+ });
1732
+ }
1733
+ /** Запрос отменён через переданный `AbortSignal`. */
1734
+ declare class ItdAbortError extends ItdError {
1735
+ constructor(message?: string, options?: {
1736
+ cause?: unknown;
1737
+ });
1738
+ }
1739
+ /**
1740
+ * Операция невозможна в текущем состоянии объекта.
1741
+ *
1742
+ * Например, клиент уже окончательно освобождён через `dispose()` и не может выполнять
1743
+ * новые запросы или создавать событийные соединения.
1744
+ */
1745
+ declare class ItdStateError extends ItdError {
1746
+ constructor(message: string, options?: {
1747
+ cause?: unknown;
1748
+ });
1749
+ }
1750
+ /**
1751
+ * Некорректная конфигурация или аргументы — обнаружено до обращения к сети.
1752
+ *
1753
+ * Этим же классом сообщают о нарушенных инвариантах билдеры: например, опрос
1754
+ * с одним вариантом ответа.
1755
+ */
1756
+ declare class ItdConfigError extends ItdError {
1757
+ constructor(message: string, options?: {
1758
+ cause?: unknown;
1759
+ });
1760
+ }
1761
+ /** Любая ошибка, порождённая этой библиотекой. */
1762
+ declare function isItdError(value: unknown): value is ItdError;
1763
+ /** Ошибка, пришедшая от сервера итд.com (статус ≥ 400). */
1764
+ declare function isItdApiError(value: unknown): value is ItdApiError;
1765
+ /** Ошибка получения или чтения вложения. */
1766
+ declare function isItdFileError(value: unknown): value is ItdFileError;
1767
+ /** Операция невозможна в текущем состоянии объекта. */
1768
+ declare function isItdStateError(value: unknown): value is ItdStateError;
1769
+ /** Ошибка валидации: `VALIDATION_ERROR` либо статус `400`/`422`. */
1770
+ declare function isItdValidationError(value: unknown): value is ItdValidationError;
1771
+ /** Ошибка авторизации: истёкший или отозванный токен. */
1772
+ declare function isItdAuthError(value: unknown): value is ItdAuthError;
1773
+ /** Доступ запрещён либо действие ограничено настройками приватности. */
1774
+ declare function isItdForbiddenError(value: unknown): value is ItdForbiddenError;
1775
+ /** Сущность не найдена. */
1776
+ declare function isItdNotFoundError(value: unknown): value is ItdNotFoundError;
1777
+ /** Сущность уже существует. */
1778
+ declare function isItdConflictError(value: unknown): value is ItdConflictError;
1779
+ /** Превышен лимит запросов. */
1780
+ declare function isItdRateLimitError(value: unknown): value is ItdRateLimitError;
1781
+ /** Действие требует подтверждённого телефона. Ссылка — в `verificationUrl`. */
1782
+ declare function isItdPhoneVerificationError(value: unknown): value is ItdPhoneVerificationError;
1783
+ /** Ошибка на стороне сервера (`5xx`). */
1784
+ declare function isItdServerError(value: unknown): value is ItdServerError;
1785
+ //#endregion
1786
+ //#region src/models/users.d.ts
1787
+ /** Значок-«пин» в профиле — награда или отметка платформы. */
1788
+ interface Pin {
1789
+ /** Постоянный идентификатор, например `epepuy_202605_59`. */
1790
+ slug: string;
1791
+ /** Отображаемое название. */
1792
+ name: string;
1793
+ /** Описание, за что выдан. */
1794
+ description: string;
1795
+ /** Адрес изображения. */
1796
+ url: string;
1797
+ /** Когда выдан. Приходит только в списке своих пинов. */
1798
+ grantedAt?: IsoDate;
1796
1799
  }
1797
1800
  /**
1798
- * Ошибка, возвращённая сервером итд.com (HTTP-статус ≥ 400).
1799
- *
1800
- * API отдаёт ошибки в двух разных формах — `{ error: { … } }` и `{ code, message, violations }`.
1801
- * Библиотека сводит обе к этому классу, поэтому разбирать форму ответа вручную не нужно.
1801
+ * Автор поста или комментария.
1802
1802
  *
1803
- * @example
1804
- * ```ts
1805
- * try {
1806
- * await itd.users.updateMe({ username: 'занятое_имя' });
1807
- * } catch (e) {
1808
- * if (e instanceof ItdValidationError) {
1809
- * console.log(e.fieldErrors.username); // ['Имя уже занято']
1810
- * }
1811
- * }
1812
- * ```
1803
+ * Встречается внутри `post.author` и `comment.author`.
1813
1804
  */
1814
- declare class ItdApiError extends ItdError {
1815
- /**
1816
- * Разновидность ошибки: та же информация, что и класс, но пригодная для сравнения.
1817
- *
1818
- * Позволяет разбирать ошибку через `switch`, а проверкам вроде {@link isItdAuthError} —
1819
- * работать даже когда в проекте оказались две копии библиотеки.
1820
- */
1821
- readonly apiKind: ItdApiErrorKind;
1822
- /** HTTP-статус ответа. */
1823
- readonly status: number;
1824
- /** Строковый код ошибки, например `VALIDATION_ERROR`. */
1825
- readonly code: ItdErrorCode;
1826
- /** Расширенное описание, если сервер его прислал. */
1827
- readonly detail: string | undefined;
1828
- /** Заголовок ошибки, если сервер его прислал. */
1829
- readonly title: string | undefined;
1830
- /** Ошибки по полям. Пустой объект, если сервер их не прислал. */
1831
- readonly fieldErrors: ItdFieldErrors;
1832
- /** Идентификатор запроса из заголовков ответа. */
1833
- readonly requestId: string | undefined;
1834
- /** HTTP-метод запроса. */
1835
- readonly method: string;
1836
- /** Путь запроса без базового URL. */
1837
- readonly path: string;
1838
- /** Тело ответа как оно пришло. */
1839
- readonly raw: unknown;
1840
- /** Объект ответа. Тело уже прочитано и повторно прочитано быть не может. */
1841
- readonly response: Response | undefined;
1842
- /** Пауза из заголовка `Retry-After` в миллисекундах. Сервер итд.com его не присылает. */
1843
- readonly retryAfter: number | undefined;
1844
- /**
1845
- * Сколько запросов разрешено в окне — заголовок `x-ratelimit-limit`.
1846
- *
1847
- * Времени сброса окна сервер не сообщает, поэтому точный момент повтора неизвестен.
1848
- */
1849
- readonly rateLimit: number | undefined;
1850
- /** Сколько запросов осталось в окне — заголовок `x-ratelimit-remaining`. */
1851
- readonly rateLimitRemaining: number | undefined;
1852
- /**
1853
- * @param apiKind разновидность; подставляется подклассами, снаружи задавать не нужно
1854
- */
1855
- constructor(init: ItdApiErrorInit, apiKind?: ItdApiErrorKind);
1805
+ interface Author {
1806
+ id: UserId;
1807
+ username: string;
1808
+ displayName: string;
1856
1809
  /**
1857
- * Проверяет код ошибки. Удобнее, чем сравнивать строки вручную.
1810
+ * **Эмодзи, а не картинка.**
1858
1811
  *
1859
- * @example
1860
- * ```ts
1861
- * if (err.hasCode('OTP_INVALID', 'MISSING_FLOW_TOKEN')) await restartOtpFlow();
1862
- * ```
1812
+ * На итд.com аватар — это символ клана (`🩵`, `🦎`), а не адрес изображения.
1813
+ * Отрисовывать его нужно как текст.
1863
1814
  */
1864
- hasCode(...codes: ItdErrorCode[]): boolean;
1865
- /** Имеет ли смысл повторить запрос: `429` и серверные ошибки `5xx`. */
1866
- get isRetryable(): boolean;
1815
+ avatar: string;
1816
+ /** Пройдена ли верификация. */
1817
+ verified: boolean;
1818
+ /** Активный значок профиля. Может отсутствовать. */
1819
+ pin?: Pin | null;
1820
+ /** Есть ли премиум-подписка (значок NUKSTA). */
1821
+ hasNuksta?: boolean;
1867
1822
  }
1868
- /** `400` / `422` — данные не прошли валидацию. Подробности в {@link ItdApiError.fieldErrors}. */
1869
- declare class ItdValidationError extends ItdApiError {
1870
- constructor(init: ItdApiErrorInit);
1823
+ /**
1824
+ * Участник события в уведомлении.
1825
+ *
1826
+ * Отличается от {@link Author} набором полей: вместо значков приходит связь с вами.
1827
+ */
1828
+ interface Actor {
1829
+ id: UserId;
1830
+ username: string;
1831
+ displayName: string;
1832
+ /** Эмодзи-аватар, см. {@link Author.avatar}. */
1833
+ avatar: string;
1834
+ /** Подписаны ли вы на этого пользователя. */
1835
+ isFollowing?: boolean;
1836
+ /** Подписан ли он на вас. */
1837
+ isFollowedBy?: boolean;
1871
1838
  }
1872
- /** `401` — токен отсутствует, истёк или отозван. */
1873
- declare class ItdAuthError extends ItdApiError {
1874
- constructor(init: ItdApiErrorInit);
1839
+ /**
1840
+ * Пользователь в списках.
1841
+ *
1842
+ * Набор полей зависит от эндпоинта: подписчики и подписки приносят `isFollowing`,
1843
+ * поиск и рекомендации — `followersCount` и `hasNuksta`. Необязательные поля отражают
1844
+ * это различие.
1845
+ */
1846
+ interface UserSummary {
1847
+ id: UserId;
1848
+ username: string;
1849
+ displayName: string;
1850
+ /** Эмодзи-аватар, см. {@link Author.avatar}. */
1851
+ avatar: string;
1852
+ verified: boolean;
1853
+ /** Подписаны ли вы. Приходит в списках подписчиков и подписок. */
1854
+ isFollowing?: boolean;
1855
+ /** Есть ли премиум. Приходит в поиске и рекомендациях. */
1856
+ hasNuksta?: boolean;
1857
+ /** Число подписчиков. Приходит в поиске и рекомендациях. */
1858
+ followersCount?: number;
1875
1859
  }
1876
- /** `403` доступ запрещён либо действие ограничено настройками приватности. */
1877
- declare class ItdForbiddenError extends ItdApiError {
1878
- constructor(init: ItdApiErrorInit);
1860
+ /** Поля профиля, общие для своего и чужого. */
1861
+ interface ProfileBase {
1862
+ id: UserId;
1863
+ username: string;
1864
+ displayName: string;
1865
+ /** Эмодзи-аватар, см. {@link Author.avatar}. */
1866
+ avatar: string;
1867
+ /** URL изображения баннера либо `null`. */
1868
+ banner: string | null;
1869
+ /** Описание профиля. */
1870
+ bio: string;
1871
+ verified: boolean;
1872
+ pin?: Pin | null;
1873
+ /** Кто может писать на стену. */
1874
+ wallAccess: WallAccess;
1875
+ /** Кто видит реакции. */
1876
+ likesVisibility: LikesVisibility;
1877
+ followersCount: number;
1878
+ followingCount: number;
1879
+ postsCount: number;
1880
+ createdAt: IsoDate;
1879
1881
  }
1880
- /** `404` сущность не найдена. */
1881
- declare class ItdNotFoundError extends ItdApiError {
1882
- constructor(init: ItdApiErrorInit);
1882
+ /** Состояние подписки на премиум. */
1883
+ interface SubscriptionState {
1884
+ isActive: boolean;
1885
+ expiresAt: IsoDate | null;
1886
+ autoRenewal: boolean;
1883
1887
  }
1884
- /** `409` — сущность уже существует. */
1885
- declare class ItdConflictError extends ItdApiError {
1886
- constructor(init: ItdApiErrorInit);
1888
+ /**
1889
+ * Свой профиль ответ `GET /api/users/me`.
1890
+ *
1891
+ * Отличается от чужого наличием {@link subscription} и {@link isPhoneVerified}
1892
+ * и отсутствием полей связи (`isFollowing`, `online`).
1893
+ */
1894
+ interface MyProfile extends ProfileBase {
1895
+ /** Закрыт ли профиль. */
1896
+ isPrivate: boolean;
1897
+ /** Подтверждён ли телефон. Без него часть действий недоступна. */
1898
+ isPhoneVerified: boolean;
1899
+ /** Своя премиум-подписка. */
1900
+ subscription: SubscriptionState;
1901
+ }
1902
+ /**
1903
+ * Состояние авторизации — ответ `GET /api/profile`.
1904
+ *
1905
+ * Endpoint доступен без сессии: в этом случае `authenticated` равен `false`,
1906
+ * а `user` — `null`.
1907
+ */
1908
+ interface AuthState {
1909
+ /** Есть ли действующая сессия. */
1910
+ authenticated: boolean;
1911
+ /** Заблокирован ли текущий аккаунт. */
1912
+ banned: boolean;
1913
+ /** Текущий пользователь либо `null` без действующей сессии. */
1914
+ user: MyProfile | null;
1887
1915
  }
1888
1916
  /**
1889
- * `429`превышен лимит запросов.
1917
+ * Чужой профиль ответ `GET /api/users/{id|username}`.
1890
1918
  *
1891
- * Если сервер прислал `Retry-After`, пауза доступна в {@link ItdApiError.retryAfter}
1892
- * (в миллисекундах). При включённых ретраях библиотека выдерживает её автоматически.
1919
+ * Вместо своей подписки содержит связь с вами и присутствие.
1893
1920
  */
1894
- declare class ItdRateLimitError extends ItdApiError {
1895
- constructor(init: ItdApiErrorInit);
1921
+ interface PublicProfile extends ProfileBase {
1922
+ hasNuksta?: boolean;
1923
+ /** Закреплённый пост, если он есть. */
1924
+ pinnedPostId: string | null;
1925
+ /** Подписаны ли вы на него. */
1926
+ isFollowing: boolean;
1927
+ /** Подписан ли он на вас. */
1928
+ isFollowedBy: boolean;
1929
+ /** Сейчас ли пользователь в сети. */
1930
+ online: boolean;
1931
+ /** Когда был в сети. `null`, если скрыто настройками приватности. */
1932
+ lastSeen: IsoDate | null;
1933
+ }
1934
+ /** Профиль: свой либо чужой. Различаются функцией `isMyProfile()`. */
1935
+ type Profile = MyProfile | PublicProfile;
1936
+ /** Настройки приватности профиля. */
1937
+ interface PrivacySettings {
1938
+ /** Закрыт ли профиль: подписка требует одобрения. */
1939
+ isPrivate: boolean;
1940
+ wallAccess: WallAccess;
1941
+ likesVisibility: LikesVisibility;
1942
+ /** Показывать ли время последнего посещения. */
1943
+ showLastSeen: boolean;
1896
1944
  }
1897
1945
  /**
1898
- * Действие требует подтверждённого телефона (`PHONE_VERIFICATION_REQUIRED`).
1946
+ * Результат подписки на пользователя.
1899
1947
  *
1900
- * Подтверждение проходит через Telegram-бота: ссылка лежит в {@link verificationUrl}.
1948
+ * @example
1949
+ * ```ts
1950
+ * const result = await itd.users.follow('nowkie');
1951
+ * // { following: true, followersCount: 11 }
1952
+ * ```
1901
1953
  */
1902
- declare class ItdPhoneVerificationError extends ItdApiError {
1903
- /** Ссылка на бота подтверждения, если удалось определить идентификатор пользователя. */
1904
- readonly verificationUrl: string | undefined;
1905
- constructor(init: ItdApiErrorInit & {
1906
- userId?: string | undefined;
1907
- });
1908
- }
1909
- /** `5xx` — ошибка на стороне сервера. */
1910
- declare class ItdServerError extends ItdApiError {
1911
- constructor(init: ItdApiErrorInit);
1912
- }
1913
- /** Причина ошибки получения вложения. */
1914
- declare const ItdFileErrorReason: Readonly<{
1915
- /** Сетевой сбой при получении источника. */
1916
- readonly Network: "network";
1917
- /** Источник ответил ошибочным HTTP-статусом. */
1918
- readonly Http: "http";
1919
- /** Источник превысил разрешённый размер. */
1920
- readonly TooLarge: "too_large";
1921
- /** Среда или источник не предоставили поток. */
1922
- readonly StreamUnavailable: "stream_unavailable";
1923
- /** Поток источника завершился ошибкой. */
1924
- readonly Read: "read";
1925
- }>;
1926
- type ItdFileErrorReason = (typeof ItdFileErrorReason)[keyof typeof ItdFileErrorReason];
1927
- /** Не удалось получить или прочитать содержимое вложения. */
1928
- declare class ItdFileError extends ItdError {
1929
- readonly reason: ItdFileErrorReason;
1930
- /** Адрес источника, если файл получался по сети. */
1931
- readonly url: string | undefined;
1932
- /** HTTP-статус источника. */
1933
- readonly status: number | undefined;
1934
- /** Разрешённый размер в байтах. */
1935
- readonly limit: number | undefined;
1936
- /** Обнаруженный размер в байтах. */
1937
- readonly actual: number | undefined;
1938
- /** Имеет ли смысл повторить получение источника. */
1939
- readonly retryable: boolean;
1940
- constructor(message: string, init: {
1941
- reason: ItdFileErrorReason;
1942
- url?: string | undefined;
1943
- status?: number | undefined;
1944
- limit?: number | undefined;
1945
- actual?: number | undefined;
1946
- retryable?: boolean | undefined;
1947
- cause?: unknown;
1948
- });
1949
- }
1950
- /** Запрос не дошёл до сервера: DNS, обрыв соединения, отсутствие сети. */
1951
- declare class ItdNetworkError extends ItdError {
1952
- /** HTTP-метод запроса. */
1953
- readonly method: string;
1954
- /** Путь запроса без базового URL. */
1955
- readonly path: string;
1956
- constructor(message: string, init: {
1957
- method: string;
1958
- path: string;
1959
- cause?: unknown;
1960
- });
1961
- }
1962
- /** Истёк таймаут запроса, заданный опцией `timeout`. */
1963
- declare class ItdTimeoutError extends ItdError {
1964
- /** Значение таймаута в миллисекундах. */
1965
- readonly timeout: number;
1966
- /** HTTP-метод запроса. */
1967
- readonly method: string;
1968
- /** Путь запроса без базового URL. */
1969
- readonly path: string;
1970
- constructor(init: {
1971
- timeout: number;
1972
- method: string;
1973
- path: string;
1974
- });
1954
+ interface FollowResult {
1955
+ /** Подписка оформлена. У закрытого профиля отправляется заявка, и здесь будет `false`. */
1956
+ following: boolean;
1957
+ /** Сколько подписчиков стало у пользователя после действия. */
1958
+ followersCount?: number;
1959
+ /** Статус заявки, если профиль закрыт. */
1960
+ status?: Loose<'following' | 'requested'>;
1975
1961
  }
1976
- /** Запрос отменён через переданный `AbortSignal`. */
1977
- declare class ItdAbortError extends ItdError {
1978
- constructor(message?: string, options?: {
1979
- cause?: unknown;
1980
- });
1962
+ /** Закреплённые значки профиля и выбранный из них. */
1963
+ interface PinsResult {
1964
+ pins: Pin[];
1965
+ /** Идентификатор активного значка — строка, а не объект. */
1966
+ activePin: string | null;
1981
1967
  }
1968
+ //#endregion
1969
+ //#region src/models/notifications.d.ts
1982
1970
  /**
1983
- * Операция невозможна в текущем состоянии объекта.
1971
+ * Уведомление в единой форме.
1984
1972
  *
1985
- * Например, клиент уже окончательно освобождён через `dispose()` и не может выполнять
1986
- * новые запросы или создавать realtime-потоки.
1973
+ * REST-список и SSE-поток отдают уведомления по-разному разные имена типов, разные имена
1974
+ * полей, один участник против массива. Библиотека приводит оба вида к этой структуре,
1975
+ * поэтому объекты из `itd.notifications.list()` и из потока можно складывать в один список.
1976
+ *
1977
+ * Исходные данные не теряются: серверное имя типа остаётся в {@link rawType},
1978
+ * а весь необработанный объект — в {@link raw}.
1987
1979
  */
1988
- declare class ItdStateError extends ItdError {
1989
- constructor(message: string, options?: {
1990
- cause?: unknown;
1991
- });
1980
+ interface Notification {
1981
+ id: string;
1982
+ /** Канонический тип. Старые имена (`like`, `comment`) приведены к новым. */
1983
+ type: NotificationType;
1984
+ /** Имя типа в том виде, в каком его прислал сервер. */
1985
+ rawType: string;
1986
+ /** Объект события: пост, комментарий, пользователь. */
1987
+ entityId: string | null;
1988
+ /** Пост, которому принадлежит комментарий, если событие о комментарии. */
1989
+ parentEntityId: string | null;
1990
+ /** Прочитано ли уведомление. */
1991
+ isRead: boolean;
1992
+ /** Кто совершил действие. Для схлопнутых уведомлений — несколько человек. */
1993
+ actors: Actor[];
1994
+ /** Сколько участников схлопнуто в одно уведомление. Минимум 1. */
1995
+ count: number;
1996
+ /** Текст или заголовок объекта события. */
1997
+ preview: string | null;
1998
+ /** Ссылка перехода, предложенная сервером. Обычно точнее её `resolveNotificationUrl()`. */
1999
+ clickUrl?: string;
2000
+ createdAt: IsoDate;
2001
+ /** Когда уведомление изменилось — например было прочитано. */
2002
+ updatedAt: IsoDate;
2003
+ /** Исходный объект как он пришёл от сервера. */
2004
+ raw: unknown;
1992
2005
  }
1993
2006
  /**
1994
- * Некорректная конфигурация или аргументы — обнаружено до обращения к сети.
2007
+ * Настройки уведомлений.
1995
2008
  *
1996
- * Этим же классом сообщают о нарушенных инвариантах билдеры: например, опрос
1997
- * с одним вариантом ответа.
2009
+ * Сервер отдаёт плоский объект, но исторически знает два набора имён для одних и тех же
2010
+ * настроек (`likes` и `reactions`, `comments` и `replies`). При сохранении библиотека
2011
+ * отправляет оба, при чтении принимает любой.
1998
2012
  */
1999
- declare class ItdConfigError extends ItdError {
2000
- constructor(message: string, options?: {
2001
- cause?: unknown;
2002
- });
2013
+ interface NotificationSettings {
2014
+ /** Общий выключатель доставки. */
2015
+ enabled: boolean;
2016
+ /** Звук уведомления. */
2017
+ sound: boolean;
2018
+ /** Новые подписчики. */
2019
+ follows: boolean;
2020
+ /** Записи на вашей стене. */
2021
+ wallPosts: boolean;
2022
+ /** Реакции на ваши записи. */
2023
+ likes: boolean;
2024
+ /** Комментарии и ответы. */
2025
+ comments: boolean;
2026
+ /** Упоминания. */
2027
+ mentions: boolean;
2003
2028
  }
2004
- /** Любая ошибка, порождённая этой библиотекой. */
2005
- declare function isItdError(value: unknown): value is ItdError;
2006
- /** Ошибка, пришедшая от сервера итд.com (статус ≥ 400). */
2007
- declare function isItdApiError(value: unknown): value is ItdApiError;
2008
- /** Ошибка получения или чтения вложения. */
2009
- declare function isItdFileError(value: unknown): value is ItdFileError;
2010
- /** Операция невозможна в текущем состоянии объекта. */
2011
- declare function isItdStateError(value: unknown): value is ItdStateError;
2012
- /** Ошибка валидации: `VALIDATION_ERROR` либо статус `400`/`422`. */
2013
- declare function isItdValidationError(value: unknown): value is ItdValidationError;
2014
- /** Ошибка авторизации: истёкший или отозванный токен. */
2015
- declare function isItdAuthError(value: unknown): value is ItdAuthError;
2016
- /** Доступ запрещён либо действие ограничено настройками приватности. */
2017
- declare function isItdForbiddenError(value: unknown): value is ItdForbiddenError;
2018
- /** Сущность не найдена. */
2019
- declare function isItdNotFoundError(value: unknown): value is ItdNotFoundError;
2020
- /** Сущность уже существует. */
2021
- declare function isItdConflictError(value: unknown): value is ItdConflictError;
2022
- /** Превышен лимит запросов. */
2023
- declare function isItdRateLimitError(value: unknown): value is ItdRateLimitError;
2024
- /** Действие требует подтверждённого телефона. Ссылка в `verificationUrl`. */
2025
- declare function isItdPhoneVerificationError(value: unknown): value is ItdPhoneVerificationError;
2026
- /** Ошибка на стороне сервера (`5xx`). */
2027
- declare function isItdServerError(value: unknown): value is ItdServerError;
2029
+ //#endregion
2030
+ //#region src/notifications/normalize.d.ts
2031
+ /** Событие потока уведомлений после разбора. */
2032
+ interface NotificationEvent {
2033
+ /** Само уведомление в единой форме. */
2034
+ notification: Notification;
2035
+ /**
2036
+ * Актуальное число непрочитанных, если сервер его сообщил.
2037
+ *
2038
+ * Клиент не увеличивает счётчик сам: значение приходит с сервера.
2039
+ */
2040
+ unreadCount: number | undefined;
2041
+ /** Нужно ли проиграть звук. */
2042
+ sound: boolean;
2043
+ }
2044
+ /**
2045
+ * Приводит уведомление к единой форме.
2046
+ *
2047
+ * Нужна потому, что REST-список и поток событий описывают одно и то же событие по-разному:
2048
+ * различаются имена типов (`like` против `post_reaction`), имена полей
2049
+ * (`targetId`/`entityId`, `read`/`isRead`, `preview`/`entityPreview`) и число участников
2050
+ * (`actor` против массива `actors`). После приведения объекты из обоих источников
2051
+ * можно складывать в один список.
2052
+ *
2053
+ * Исходные данные не теряются: имя типа с сервера остаётся в `rawType`,
2054
+ * весь объект целиком — в `raw`.
2055
+ *
2056
+ * @param input уведомление из REST-ответа либо полезная нагрузка события потока
2057
+ *
2058
+ * @example
2059
+ * ```ts
2060
+ * const fromRest = normalizeNotification(restItem);
2061
+ * const fromStream = normalizeNotification(event.payload);
2062
+ * // одинаковая форма — можно объединять
2063
+ * ```
2064
+ */
2065
+ declare function normalizeNotification(input: unknown): Notification;
2028
2066
  //#endregion
2029
2067
  //#region src/notifications/text.d.ts
2030
2068
  /**
@@ -2079,5 +2117,5 @@ declare function isKnownNotificationType(type: string): boolean;
2079
2117
  */
2080
2118
  declare function resolveNotificationUrl(notification: Notification): string;
2081
2119
  //#endregion
2082
- export { UserSummary as $, RateLimitPacing as $t, isItdFileError as A, Emitter as At, Notification as B, RateLimitOptions as Bt, ItdStateError as C, ReportReason as Ct, isItdAuthError as D, ViewReason as Dt, isItdApiError as E, SpanType as Et, isItdServerError as F, Logger as Ft, FollowResult as G, ResponseContext as Gt, Actor as H, RequestContext as Ht, isItdStateError as I, OperationRequestOptions as It, PinsResult as J, RetryOptions as Jt, MyProfile as K, RetryContext as Kt, isItdValidationError as L, PaginationOptions as Lt, isItdNotFoundError as M, Unsubscribe as Mt, isItdPhoneVerificationError as N, ClientHooks as Nt, isItdConflictError as O, ViewSource as Ot, isItdRateLimitError as P, ErrorContextHook as Pt, SubscriptionState as Q, ServiceDefinition as Qt, NotificationEvent as R, RateLimitBucketContext as Rt, ItdServerError as S, RealtimeStatus as St, ItdValidationError as T, ServiceState as Tt, AuthState as U, RequestExtensions as Ut, NotificationSettings as V, RawRequestOptions as Vt, Author as W, RequestOptions as Wt, Profile as X, QueryParams as Xt, PrivacySettings as Y, RuntimeOptions as Yt, PublicProfile as Z, QueryValue as Zt, ItdForbiddenError as _, InteractionType as _t, ItdAbortError as a, ItdOperationDefinition as an, DEFAULT_BASE_URL as at, ItdPhoneVerificationError as b, Loose as bt, ItdApiErrorKind as c, isBuiltInOperationId as cn, IsoDate as ct, ItdConflictError as d, operationRetrySafety as dn, UserRef as dt, RuntimeMode as en, AuthIdentity as et, ItdError as f, BUCKET_LIMITS as fn, AccessType as ft, ItdFileErrorReason as g, RetrySafety as gn, IncidentKind as gt, ItdFileError as h, OperationMethod as hn, FeedTab as ht, formatNotificationText as i, CustomOperationId as in, tokenProvider as it, isItdForbiddenError as j, Listener as jt, isItdError as k, WallAccess as kt, ItdAuthError as l, operationBucket as ln, Span as lt, ItdFieldErrors as m, RateLimitBucket as mn, CommentSort as mt, canonicalNotificationType as n, systemClock as nn, anonymousAuth as nt, ItdApiError as o, OPERATIONS as on, STATUS_SERVICE as ot, ItdErrorKind as p, DEFAULT_RATE_LIMIT_BUCKET as pn, AttachmentType as pt, Pin as q, RetryDecisionContext as qt, isKnownNotificationType as r, BuiltInOperationId as rn, bearerToken as rt, ItdApiErrorInit as s, OperationId as sn, LIBRARY_VERSION as st, resolveNotificationUrl as t, ItdClock as tn, AuthProvider as tt, ItdConfigError as u, operationMethod as un, UserId as ut, ItdNetworkError as v, ItdErrorCode as vt, ItdTimeoutError as w, ReportTargetType as wt, ItdRateLimitError as x, NotificationType as xt, ItdNotFoundError as y, LikesVisibility as yt, normalizeNotification as z, RateLimitBucketOverride as zt };
2083
- //# sourceMappingURL=url-B6-bXHKt.d.ts.map
2120
+ export { isItdValidationError as $, RateLimitBucket as $t, ItdFieldErrors as A, ResponseContext as At, ItdTimeoutError as B, ItdClock as Bt, ItdApiErrorInit as C, RateLimitBucketContext as Ct, ItdConflictError as D, RequestContext as Dt, ItdConfigError as E, RawRequestOptions as Et, ItdNotFoundError as F, QueryParams as Ft, isItdError as G, OPERATIONS as Gt, isItdApiError as H, BuiltInOperationId as Ht, ItdPhoneVerificationError as I, QueryValue as It, isItdNotFoundError as J, operationBucket as Jt, isItdFileError as K, OperationId as Kt, ItdRateLimitError as L, ServiceDefinition as Lt, ItdFileErrorReason as M, RetryDecisionContext as Mt, ItdForbiddenError as N, RetryOptions as Nt, ItdError as O, RequestExtensions as Ot, ItdNetworkError as P, RuntimeOptions as Pt, isItdStateError as Q, DEFAULT_RATE_LIMIT_BUCKET as Qt, ItdServerError as R, RateLimitPacing as Rt, ItdApiError as S, PaginationOptions as St, ItdAuthError as T, RateLimitOptions as Tt, isItdAuthError as U, CustomOperationId as Ut, ItdValidationError as V, systemClock as Vt, isItdConflictError as W, ItdOperationDefinition as Wt, isItdRateLimitError as X, operationRetrySafety as Xt, isItdPhoneVerificationError as Y, operationMethod as Yt, isItdServerError as Z, BUCKET_LIMITS as Zt, Profile as _, ServiceState as _n, RetrySafety as _t, NotificationEvent as a, AttachmentType as an, tokenProvider as at, UserSummary as b, ViewSource as bn, Logger as bt, NotificationSettings as c, FeedTab as cn, Unsubscribe as ct, Author as d, ItdErrorCode as dn, LIBRARY_VERSION as dt, IsoDate as en, ClientConnection as et, FollowResult as f, LikesVisibility as fn, FeatureOperationId as ft, PrivacySettings as g, ReportTargetType as gn, OperationMethod as gt, PinsResult as h, ReportReason as hn, OperationMetadata as ht, formatNotificationText as i, AccessType as in, bearerToken as it, ItdFileError as j, RetryContext as jt, ItdErrorKind as k, RequestOptions as kt, Actor as l, IncidentKind as ln, DEFAULT_BASE_URL as lt, Pin as m, NotificationType as mn, OperationContract as mt, canonicalNotificationType as n, UserId as nn, AuthProvider as nt, normalizeNotification as o, CommentSort as on, Emitter as ot, MyProfile as p, Loose as pn, OperationAnnotations as pt, isItdForbiddenError as q, isBuiltInOperationId as qt, isKnownNotificationType as r, UserRef as rn, anonymousAuth as rt, Notification as s, EventChannelStatus as sn, Listener as st, resolveNotificationUrl as t, Span as tn, AuthIdentity as tt, AuthState as u, InteractionType as un, STATUS_SERVICE as ut, PublicProfile as v, SpanType as vn, ClientHooks as vt, ItdApiErrorKind as w, RateLimitBucketOverride as wt, ItdAbortError as x, WallAccess as xn, OperationRequestOptions as xt, SubscriptionState as y, ViewReason as yn, ErrorContextHook as yt, ItdStateError as z, RuntimeMode as zt };
2121
+ //# sourceMappingURL=url-Be9jlWRv.d.cts.map