itd-api 0.0.10 → 0.1.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 (41) hide show
  1. package/README.md +8 -4
  2. package/dist/{chunk-SMV7TF5P.js → chunk-6FB4HTKH.js} +768 -194
  3. package/dist/chunk-6FB4HTKH.js.map +1 -0
  4. package/dist/{chunk-BN4AC3DP.cjs → chunk-73CISRBG.cjs} +768 -194
  5. package/dist/chunk-73CISRBG.cjs.map +1 -0
  6. package/dist/{index-CSjDNGCE.d.cts → index-BZF4K90s.d.cts} +169 -29
  7. package/dist/{index-CSjDNGCE.d.ts → index-BZF4K90s.d.ts} +169 -29
  8. package/dist/index.cjs +109 -109
  9. package/dist/index.d.cts +1 -1
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/node.cjs +112 -112
  13. package/dist/node.d.cts +2 -2
  14. package/dist/node.d.ts +2 -2
  15. package/dist/node.js +2 -2
  16. package/guides/README.md +2 -0
  17. package/guides/multi-accounts/README.md +2 -1
  18. package/guides/plugins/README.md +93 -2
  19. package/guides/reference/README.md +67 -0
  20. package/guides/reference/accounts.md +101 -0
  21. package/guides/reference/auth.md +141 -0
  22. package/guides/reference/builders.md +135 -0
  23. package/guides/reference/client.md +184 -0
  24. package/guides/reference/comments.md +58 -0
  25. package/guides/reference/discovery.md +81 -0
  26. package/guides/reference/enums.md +103 -0
  27. package/guides/reference/errors.md +107 -0
  28. package/guides/reference/files.md +73 -0
  29. package/guides/reference/models.md +448 -0
  30. package/guides/reference/notifications.md +77 -0
  31. package/guides/reference/pagination.md +82 -0
  32. package/guides/reference/platform.md +47 -0
  33. package/guides/reference/posts.md +157 -0
  34. package/guides/reference/realtime.md +78 -0
  35. package/guides/reference/reports.md +28 -0
  36. package/guides/reference/subscription.md +41 -0
  37. package/guides/reference/users.md +146 -0
  38. package/guides/reference/verification.md +24 -0
  39. package/package.json +1 -1
  40. package/dist/chunk-BN4AC3DP.cjs.map +0 -1
  41. package/dist/chunk-SMV7TF5P.js.map +0 -1
@@ -0,0 +1,448 @@
1
+ # Модели данных
2
+
3
+ Формы объектов, которые возвращает API. Все поля дат — строки ISO-8601 (`IsoDate`); для разбора
4
+ есть [`toDate()`](#вспомогательные-функции). Смещения разметки измеряются в единицах UTF-16.
5
+
6
+ Особенности, о которых легко забыть:
7
+
8
+ - **`avatar` — это эмодзи, а не URL.** На итд.com аватар — символ клана (`🩵`, `🦎`).
9
+ Отрисовывать его нужно как текст. Исключение — `banner`: это настоящий URL.
10
+ - **`UserRef`** = UUID или username; **`UserId`** = строго UUID.
11
+
12
+ ## Пользователи
13
+
14
+ ### Author
15
+
16
+ Автор поста или комментария (`post.author`, `comment.author`).
17
+
18
+ ```ts
19
+ interface Author {
20
+ id: UserId;
21
+ username: string;
22
+ displayName: string;
23
+ avatar: string; // эмодзи, не URL
24
+ verified: boolean;
25
+ pin?: Pin | null; // активный значок
26
+ hasNuksta?: boolean; // премиум-подписка
27
+ }
28
+ ```
29
+
30
+ ### Actor
31
+
32
+ Участник события в уведомлении.
33
+
34
+ ```ts
35
+ interface Actor {
36
+ id: UserId;
37
+ username: string;
38
+ displayName: string;
39
+ avatar: string;
40
+ isFollowing?: boolean; // подписаны ли вы на него
41
+ isFollowedBy?: boolean; // подписан ли он на вас
42
+ }
43
+ ```
44
+
45
+ ### UserSummary
46
+
47
+ Пользователь в списках. Набор необязательных полей зависит от эндпоинта.
48
+
49
+ ```ts
50
+ interface UserSummary {
51
+ id: UserId;
52
+ username: string;
53
+ displayName: string;
54
+ avatar: string;
55
+ verified: boolean;
56
+ isFollowing?: boolean; // в списках подписчиков/подписок
57
+ hasNuksta?: boolean; // в поиске/рекомендациях
58
+ followersCount?: number; // в поиске/рекомендациях
59
+ }
60
+ ```
61
+
62
+ ### MyProfile
63
+
64
+ Свой профиль — ответ `itd.users.me()`.
65
+
66
+ ```ts
67
+ interface MyProfile {
68
+ id: UserId; username: string; displayName: string;
69
+ avatar: string; banner: string | null; bio: string;
70
+ verified: boolean; pin?: Pin | null;
71
+ wallAccess: WallAccess; // кто может писать на стену
72
+ likesVisibility: LikesVisibility; // кто видит реакции
73
+ followersCount: number; followingCount: number; postsCount: number;
74
+ createdAt: IsoDate;
75
+ isPrivate: boolean;
76
+ isPhoneVerified: boolean;
77
+ subscription: SubscriptionState; // { isActive, expiresAt, autoRenewal }
78
+ }
79
+ ```
80
+
81
+ ### PublicProfile
82
+
83
+ Чужой профиль — ответ `itd.users.get()`.
84
+
85
+ ```ts
86
+ interface PublicProfile {
87
+ id: UserId; username: string; displayName: string;
88
+ avatar: string; banner: string | null; bio: string;
89
+ verified: boolean; pin?: Pin | null;
90
+ wallAccess: WallAccess; likesVisibility: LikesVisibility;
91
+ followersCount: number; followingCount: number; postsCount: number;
92
+ createdAt: IsoDate;
93
+ hasNuksta?: boolean;
94
+ pinnedPostId: string | null;
95
+ isFollowing: boolean; // подписаны ли вы
96
+ isFollowedBy: boolean; // подписан ли он на вас
97
+ online: boolean;
98
+ lastSeen: IsoDate | null; // null, если скрыто приватностью
99
+ }
100
+
101
+ type Profile = MyProfile | PublicProfile;
102
+ isMyProfile(profile): profile is MyProfile // различает их
103
+ ```
104
+
105
+ ### Pin
106
+
107
+ Значок-«пин» в профиле.
108
+
109
+ ```ts
110
+ interface Pin {
111
+ slug: string; // постоянный идентификатор
112
+ name: string;
113
+ description: string;
114
+ url: string; // адрес изображения
115
+ grantedAt?: IsoDate; // только в списке своих пинов
116
+ }
117
+ ```
118
+
119
+ ### PinsResult
120
+
121
+ ```ts
122
+ interface PinsResult {
123
+ pins: Pin[];
124
+ activePin: string | null; // идентификатор, а не объект
125
+ }
126
+ ```
127
+
128
+ ### PrivacySettings
129
+
130
+ ```ts
131
+ interface PrivacySettings {
132
+ isPrivate: boolean; // подписка требует одобрения
133
+ wallAccess: WallAccess;
134
+ likesVisibility: LikesVisibility;
135
+ showLastSeen: boolean;
136
+ }
137
+ ```
138
+
139
+ ### FollowResult
140
+
141
+ ```ts
142
+ interface FollowResult {
143
+ following: boolean; // false, если у закрытого профиля отправлена заявка
144
+ followersCount?: number;
145
+ status?: 'following' | 'requested' | (string & {});
146
+ }
147
+ ```
148
+
149
+ ### Clan
150
+
151
+ ```ts
152
+ interface Clan {
153
+ avatar: string; // эмодзи клана
154
+ memberCount: number;
155
+ }
156
+ ```
157
+
158
+ ## Посты и комментарии
159
+
160
+ ### Post
161
+
162
+ ```ts
163
+ interface Post {
164
+ id: string;
165
+ content: string;
166
+ spans: Span[]; // разметка текста
167
+ author: Author;
168
+ attachments: Attachment[];
169
+ likesCount: number; commentsCount: number; repostsCount: number; viewsCount: number;
170
+ wallRecipientId: UserId | null; // чья стена, если пост не у себя
171
+ wallRecipient?: Author | null; // владелец стены
172
+ isLiked: boolean; isReposted: boolean; isViewed: boolean; isOwner: boolean;
173
+ originalPost?: Post | null; // если это репост
174
+ poll?: Poll | null;
175
+ dominantEmoji?: string | null; // преобладающая реакция
176
+ editedAt: IsoDate | null;
177
+ createdAt: IsoDate;
178
+ comments?: Comment[]; // только в ответе itd.posts.get()
179
+ }
180
+ ```
181
+
182
+ ### Comment
183
+
184
+ ```ts
185
+ interface Comment {
186
+ id: string;
187
+ content: string; // у голосового пустой
188
+ spans?: Span[];
189
+ author: Author;
190
+ likesCount: number; repliesCount: number;
191
+ isLiked: boolean;
192
+ createdAt: IsoDate;
193
+ attachments?: Attachment[]; // у голосового — одно audio/ogg
194
+ replies?: Comment[]; // превью; полный список — comments.replies()
195
+ replyTo?: CommentReplyTo; // { id, username, displayName } — только у ответов
196
+ }
197
+ ```
198
+
199
+ ### Attachment
200
+
201
+ ```ts
202
+ interface Attachment {
203
+ id: string;
204
+ type: AttachmentType; // 'image' | 'video' | 'audio'
205
+ url: string; // адрес на CDN
206
+ width?: number; height?: number;
207
+ mimeType: string;
208
+ filename?: string; size?: number; // приходят не всегда
209
+ duration?: number | null; // аудио/видео, секунды
210
+ order?: number; // порядок во вложениях
211
+ }
212
+ ```
213
+
214
+ ### Poll
215
+
216
+ ```ts
217
+ interface Poll {
218
+ id: string; postId: string;
219
+ question: string;
220
+ multipleChoice: boolean;
221
+ options: PollOption[]; // { id, text, votesCount, position }
222
+ totalVotes: number;
223
+ hasVoted: boolean;
224
+ votedOptionIds: string[];
225
+ createdAt: IsoDate;
226
+ }
227
+ ```
228
+
229
+ ### PostStats
230
+
231
+ ```ts
232
+ interface PostStats {
233
+ id: string;
234
+ likesCount: number; commentsCount: number; repostsCount: number; viewsCount: number;
235
+ dominantEmoji: string | null;
236
+ }
237
+ ```
238
+
239
+ ### LikeResult
240
+
241
+ ```ts
242
+ interface LikeResult { liked: boolean; likesCount: number; }
243
+ ```
244
+
245
+ ### PinPostResult
246
+
247
+ ```ts
248
+ interface PinPostResult { success: boolean; pinnedPostId: string | null; }
249
+ ```
250
+
251
+ ### Span
252
+
253
+ Фрагмент разметки. `offset`/`length` — в единицах UTF-16.
254
+
255
+ ```ts
256
+ interface Span {
257
+ type: SpanType;
258
+ offset: number;
259
+ length: number;
260
+ tag?: string; // имя хэштега без решётки
261
+ url?: string; // только у link
262
+ username?: string; // у mention
263
+ id?: string; // id пользователя у некоторых mention
264
+ }
265
+ ```
266
+
267
+ ## Уведомления
268
+
269
+ ### Notification
270
+
271
+ Единая форма для REST-списка и SSE-потока.
272
+
273
+ ```ts
274
+ interface Notification {
275
+ id: string;
276
+ type: NotificationType; // канонический тип
277
+ rawType: string; // имя типа как прислал сервер
278
+ entityId: string | null; // объект события
279
+ parentEntityId: string | null; // родитель (пост комментария)
280
+ isRead: boolean;
281
+ actors: Actor[]; // для схлопнутых — несколько
282
+ count: number; // сколько участников схлопнуто; минимум 1
283
+ preview: string | null;
284
+ clickUrl?: string; // ссылка от сервера (resolveNotificationUrl обычно точнее)
285
+ createdAt: IsoDate; updatedAt: IsoDate;
286
+ raw: unknown; // исходный объект
287
+ }
288
+ ```
289
+
290
+ ### NotificationSettings
291
+
292
+ ```ts
293
+ interface NotificationSettings {
294
+ enabled: boolean; // общий выключатель
295
+ sound: boolean;
296
+ follows: boolean;
297
+ wallPosts: boolean;
298
+ likes: boolean;
299
+ comments: boolean;
300
+ mentions: boolean;
301
+ }
302
+ ```
303
+
304
+ ## Авторизация и подписка
305
+
306
+ ### Session
307
+
308
+ ```ts
309
+ interface Session {
310
+ id: string;
311
+ isCurrent: boolean;
312
+ createdAt: IsoDate; lastUsedAt: IsoDate; expiresAt: IsoDate;
313
+ ipAddress: string; ipCountry: string | null; ipCity: string | null;
314
+ deviceType: 'desktop' | 'mobile' | (string & {});
315
+ osName: string | null; osVersion: string | null;
316
+ clientName: string | null; clientVersion: string | null;
317
+ deviceModel: string | null;
318
+ }
319
+ ```
320
+
321
+ ### Subscription
322
+
323
+ ```ts
324
+ interface Subscription {
325
+ active: boolean;
326
+ recurringEnabled: boolean; // автопродление
327
+ price: number; // рубли
328
+ }
329
+ ```
330
+
331
+ ### PaymentMethod
332
+
333
+ ```ts
334
+ interface PaymentMethod {
335
+ id: string;
336
+ last4?: string;
337
+ brand?: string; // 'visa' | 'mastercard' | 'mir'
338
+ isDefault?: boolean;
339
+ expiresAt?: IsoDate | null;
340
+ }
341
+ ```
342
+
343
+ ### VerificationStatus
344
+
345
+ ```ts
346
+ interface VerificationStatus {
347
+ status: 'none' | 'pending' | 'approved' | 'rejected' | (string & {});
348
+ }
349
+ ```
350
+
351
+ ## Поиск и платформа
352
+
353
+ ### Hashtag
354
+
355
+ ```ts
356
+ interface Hashtag {
357
+ id: string;
358
+ name: string; // без решётки
359
+ postsCount: number;
360
+ }
361
+ ```
362
+
363
+ ### Report
364
+
365
+ ```ts
366
+ interface Report { id: string; createdAt: IsoDate; }
367
+ ```
368
+
369
+ ### Portal
370
+
371
+ ```ts
372
+ interface Portal { active: boolean; title: string; url: string; }
373
+ ```
374
+
375
+ ### ChangelogEntry
376
+
377
+ ```ts
378
+ interface ChangelogEntry { version: string; date: string; changes: string[]; }
379
+ ```
380
+
381
+ ### Announcement
382
+
383
+ ```ts
384
+ interface Announcement {
385
+ id: string;
386
+ image: { url: string; width: number; height: number };
387
+ title: string;
388
+ description: string;
389
+ additional_text?: string;
390
+ buttons: AnnouncementButton[]; // { title, style, action }
391
+ }
392
+ ```
393
+
394
+ ### PlatformStatus
395
+
396
+ ```ts
397
+ interface PlatformStatus {
398
+ overall_status: ServiceState; // худшее среди сервисов
399
+ updated_at: IsoDate;
400
+ services: ServiceStatus[];
401
+ }
402
+ ```
403
+
404
+ ### ServiceStatus
405
+
406
+ ```ts
407
+ interface ServiceStatus {
408
+ id: string; // 'auth' | 'main' | 'media' | …
409
+ name: string;
410
+ current_status: ServiceState;
411
+ current_message: string;
412
+ latency_ms: number;
413
+ last_checked: IsoDate; // приведён к ISO
414
+ uptime_90d: number; // проценты
415
+ days: Record<string, StatusDay | undefined>; // разреженный; ровный массив — statusDays()
416
+ }
417
+ ```
418
+
419
+ ### StatusDay
420
+
421
+ ```ts
422
+ interface StatusDay {
423
+ type: ServiceState; // худшее состояние за сутки
424
+ date_key: string; // YYYY-MM-DD, нарезка по UTC
425
+ uptime: number;
426
+ lines: StatusIncidentLine[]; // { t: IncidentKind; text } — text готов к показу, время МСК
427
+ }
428
+ ```
429
+
430
+ ## Вспомогательные функции
431
+
432
+ Экспортируются из корня пакета:
433
+
434
+ ```ts
435
+ toDate(value: IsoDate | null | undefined): Date | null
436
+ ```
437
+ Разбирает дату API в `Date`; `null`, если строки нет или она не разбирается.
438
+
439
+ ```ts
440
+ statusDays(service: ServiceStatus): (StatusDay | null)[]
441
+ ```
442
+ Разворачивает разреженную историю сервиса в массив на 90 суток. Индекс — сколько суток назад,
443
+ `[0]` — сегодня, пропуски равны `null`.
444
+
445
+ ```ts
446
+ isMyProfile(profile: Profile): profile is MyProfile
447
+ ```
448
+ Свой ли это профиль.
@@ -0,0 +1,77 @@
1
+ # Уведомления — `itd.notifications`
2
+
3
+ Список, счётчик, отметки о прочтении и настройки. Все уведомления приведены к единой форме,
4
+ поэтому объекты отсюда и из [потока](./realtime.md) можно складывать в один список. См.
5
+ [`Notification`](./models.md#notification), [`NotificationType`](./enums.md#notificationtype).
6
+
7
+ ## Список
8
+
9
+ ```ts
10
+ list(params?: NotificationListParams): Promise<Page<Notification>>
11
+ iterate(params?: NotificationListParams): Paginator<Notification>
12
+ ```
13
+ Страница уведомлений / перебор. Пагинация по смещению (`offset` / `nextOffset`).
14
+
15
+ ```ts
16
+ count(): Promise<number>
17
+ ```
18
+ Число непрочитанных уведомлений.
19
+
20
+ ## Отметки о прочтении
21
+
22
+ ```ts
23
+ markRead(notificationId: string): Promise<number>
24
+ ```
25
+ Отмечает одно уведомление прочитанным. Возвращает, сколько записей отметил сервер.
26
+
27
+ ```ts
28
+ markReadBatch(ids: string[]): Promise<number>
29
+ ```
30
+ Отмечает несколько. Список автоматически режется на части по 20 идентификаторов, части уходят
31
+ последовательно, результат суммируется.
32
+
33
+ ```ts
34
+ markAllRead(): Promise<number>
35
+ ```
36
+ Отмечает прочитанными все уведомления.
37
+
38
+ ## Настройки
39
+
40
+ ```ts
41
+ getSettings(): Promise<NotificationSettings>
42
+ updateSettings(input: UpdateNotificationSettingsInput): Promise<NotificationSettings>
43
+ ```
44
+ Читает / обновляет настройки. Отправляются только изменяемые поля. Отсутствующая настройка
45
+ считается включённой. См. [`NotificationSettings`](./models.md#notificationsettings).
46
+
47
+ ## Типы
48
+
49
+ ```ts
50
+ interface NotificationListParams extends RequestOptions {
51
+ limit?: number;
52
+ offset?: number; // смещение от начала списка
53
+ maxPages?: number;
54
+ }
55
+
56
+ type UpdateNotificationSettingsInput = Partial<NotificationSettings>;
57
+ ```
58
+
59
+ ## Вспомогательные функции
60
+
61
+ Экспортируются из корня пакета, работают с любым `Notification`:
62
+
63
+ ```ts
64
+ formatNotificationText(notification: Notification): string
65
+ ```
66
+ Готовый текст: «Аня и ещё 2 оценили ваш пост».
67
+
68
+ ```ts
69
+ resolveNotificationUrl(notification: Notification): string | null
70
+ ```
71
+ Ссылка перехода: `/@anya/post/9f1c…`. Обычно точнее серверного `clickUrl`.
72
+
73
+ ```ts
74
+ canonicalNotificationType(rawType: string): NotificationType
75
+ isKnownNotificationType(type: string): boolean
76
+ ```
77
+ Приведение серверного имени типа к каноническому и проверка известности.
@@ -0,0 +1,82 @@
1
+ # Пагинация — `Page<T>`, `Paginator<T>`
2
+
3
+ Три разные схемы API (курсор, страницы, смещение) приведены к одной форме. Методы-списки идут
4
+ парами: `list()` и подобные возвращают одну `Page<T>`, а `iterate()` и подобные — `Paginator<T>`
5
+ для `for await`.
6
+
7
+ ## `Page<T>`
8
+
9
+ Одна страница. Какие необязательные поля заполнены — зависит от эндпоинта.
10
+
11
+ ```ts
12
+ interface Page<T> {
13
+ items: T[];
14
+ hasMore: boolean;
15
+ nextCursor?: string | null; // курсорная схема (лента, хэштеги, комментарии к посту)
16
+ page?: number; // постраничная схема (ответы, списки пользователей)
17
+ limit?: number;
18
+ total?: number; // если сервер сообщил
19
+ nextOffset?: number; // схема со смещением (уведомления)
20
+ raw: unknown; // исходный ответ
21
+ }
22
+ ```
23
+
24
+ Курсор **непрозрачен**: у вкладки `popular` это номер страницы, у `following` — отметка
25
+ времени. Передавайте его обратно как есть.
26
+
27
+ ## `Paginator<T>`
28
+
29
+ Перебор страниц. Реализует `AsyncIterable<T>` — годится для `for await`. **Одноразовый**:
30
+ позиция хранится внутри, второй проход по тому же объекту ничего не выдаст.
31
+
32
+ ```ts
33
+ [Symbol.asyncIterator](): AsyncGenerator<T> // for await (const item of paginator)
34
+ ```
35
+ Перебирает элементы всех страниц подряд.
36
+
37
+ ```ts
38
+ pages(): AsyncGenerator<Page<T>>
39
+ ```
40
+ Перебирает страницы целиком — когда нужны сведения о самой странице (`total`, `page`).
41
+
42
+ ```ts
43
+ next(): Promise<Page<T> | null>
44
+ ```
45
+ Загружает следующую страницу или `null`, если перебор закончен.
46
+
47
+ ```ts
48
+ collect(max?: number): Promise<T[]>
49
+ ```
50
+ Собирает элементы в массив. `max` — сколько достаточно (можно остановиться раньше конца).
51
+
52
+ ```ts
53
+ // пример форм
54
+ for await (const post of itd.posts.iterate({ tab: 'popular' })) { … }
55
+ for await (const page of itd.posts.iterateComments(postId).pages()) { page.total; }
56
+ const first100 = await itd.posts.iterate({ tab: 'popular' }).collect(100);
57
+ ```
58
+
59
+ ## Ограничение числа страниц
60
+
61
+ Параметры-списки принимают `maxPages` — предохранитель от бесконечного перебора (по умолчанию
62
+ 1000). Перебор также сам останавливается на пустой странице, неизменившемся или отсутствующем
63
+ курсоре.
64
+
65
+ ## Схемы
66
+
67
+ ```ts
68
+ const PaginationMode = { Cursor: 'cursor', Page: 'page', Offset: 'offset' } as const;
69
+ ```
70
+
71
+ | Схема | Эндпоинты | Позиция |
72
+ |---|---|---|
73
+ | `Cursor` | лента, стена, лайки, посты по хэштегу, комментарии к посту | `cursor` / `nextCursor` |
74
+ | `Page` | подписчики, подписки, заблокированные, ответы на комментарий | `page` |
75
+ | `Offset` | уведомления | `offset` / `nextOffset` |
76
+
77
+ ## Вспомогательное
78
+
79
+ ```ts
80
+ mapPage<T, R>(page: Page<T>, map: (item: T) => R): Page<R>
81
+ ```
82
+ Преобразует элементы страницы, сохраняя сведения о пагинации.
@@ -0,0 +1,47 @@
1
+ # Платформа — `itd.platform`
2
+
3
+ Журнал изменений, анонсы, баннер события и статус сервисов. Авторизация этим методам не нужна.
4
+
5
+ ## Методы
6
+
7
+ ```ts
8
+ changelog(): Promise<ChangelogEntry[]>
9
+ ```
10
+ Журнал изменений платформы. См. [`ChangelogEntry`](./models.md#changelogentry).
11
+
12
+ ```ts
13
+ announcements(): Promise<Announcement[]>
14
+ ```
15
+ Анонсы на главной странице. См. [`Announcement`](./models.md#announcement).
16
+
17
+ ```ts
18
+ portal(): Promise<Portal>
19
+ ```
20
+ Баннер текущего события — виджет «портал». См. [`Portal`](./models.md#portal). Также в
21
+ [Поиск и обнаружение](./discovery.md).
22
+
23
+ ```ts
24
+ status(): Promise<PlatformStatus>
25
+ ```
26
+ Состояние сервисов за последние 90 суток. Идёт на отдельный хост `статус.итд.com` без
27
+ авторизации, ответ кэшируется сервером на минуту. Работает из браузера напрямую (в отличие
28
+ от остального API). См. [`PlatformStatus`](./models.md#platformstatus).
29
+
30
+ ## Статус: разбор истории
31
+
32
+ История по суткам приходит разреженным объектом (`days: Record<string, StatusDay>`); сутки
33
+ без данных сервер пропускает. Ровный массив на 90 элементов даёт функция из корня пакета:
34
+
35
+ ```ts
36
+ statusDays(service: ServiceStatus): (StatusDay | null)[]
37
+ ```
38
+ Индекс — сколько суток назад: `[0]` — сегодня, пропуски равны `null`. См.
39
+ [`ServiceStatus`](./models.md#servicestatus), [`StatusDay`](./models.md#statusday),
40
+ [`ServiceState`](./enums.md#servicestate).
41
+
42
+ ```ts
43
+ const status = await itd.platform.status();
44
+ const auth = status.services.find((s) => s.id === 'auth');
45
+ const days = auth ? statusDays(auth) : [];
46
+ days[0]?.uptime; // доступность за сегодня
47
+ ```