bsuir-iis-api 0.9.1 → 0.10.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.10.1] - 2026-05-13
4
+
5
+ ### Documentation
6
+
7
+ - README: clarified `defaultRaw` behavior for `schedule.getGroup()` / `schedule.getEmployee()` when per-call `raw` is omitted.
8
+ - README: clarified retry semantics (GET retries cover retriable HTTP statuses and network/transport errors).
9
+ - README: documented `maxResponseBytes` failure behavior (`BsuirApiError` on oversized payloads).
10
+ - README: made usage snippets standalone by adding required imports/client initialization.
11
+ - README: added explicit public export reference for runtime helpers, error classes, and exported domain/types surface.
12
+
13
+ ### Changed
14
+
15
+ - Changelog restored explicit `0.8.0` section and separated `0.9.0` release-management metadata.
16
+ - Changelog now marks `0.10.0` behavior shifts with potentially breaking downstream impact notes.
17
+
18
+ ## [0.10.0] - 2026-05-13
19
+
20
+ ### Added
21
+
22
+ - `createBsuirClient` options: `allowedBaseUrlHosts`, `allowInsecureHttp`, and `maxResponseBytes`.
23
+ - HTTP internals split into focused modules under `src/client/http/*` (`requestJson`, `response`, `cache`, `retry`, `url`).
24
+ - Schedule internals split into `scheduleApi`, `scheduleFilter`, and `scheduleNormalize`.
25
+ - Shared `createListModule` used by catalog modules to remove duplicated list/read logic.
26
+ - CI/release workflows now generate CycloneDX SBOM (`sbom.cdx.json`) on Node 24.
27
+
28
+ ### Changed
29
+
30
+ - `createBsuirClient` now validates and normalizes `baseUrl` strictly (absolute URL, no credentials/query/hash, host allowlist, HTTPS by default).
31
+ - `validateResponses` is now enabled by default.
32
+ - Example scripts now fail with non-zero exit code on unhandled errors.
33
+ - GitHub Actions in CI/release workflows are pinned to commit SHAs.
34
+ - Dev dependencies in `package.json` are now pinned to exact versions.
35
+
36
+ ### Potentially Breaking
37
+
38
+ - `baseUrl` validation is now strict; previously accepted non-normalized/less-safe values can now throw `BsuirConfigurationError`.
39
+ - `validateResponses` defaults to `true`; integrations that relied on unchecked payloads may now see `BsuirResponseValidationError`.
40
+ - Response body size is now actively enforced via `maxResponseBytes`; oversized responses now fail fast with `BsuirApiError`.
41
+
42
+ ### Fixed
43
+
44
+ - Response parser now enforces `maxResponseBytes` for both `Content-Length` pre-checks and streamed body reads.
45
+ - `BsuirTimeoutError` now preserves upstream abort timeout cause.
46
+ - Public API report updates for schedule typings (`FlattenedLessonsByDay` and `lessonTypeAbbrev`) and utility export documentation.
47
+
3
48
  ## [0.9.1] - 2026-05-10
4
49
 
5
50
  ### Fixed
@@ -16,6 +61,12 @@
16
61
 
17
62
  ## [0.9.0] - 2026-05-10
18
63
 
64
+ ### Changed
65
+
66
+ - Release-management update from Changesets (`version: 0.9.0` + release metadata). Runtime API/content remained aligned with the `0.8.0` code snapshot.
67
+
68
+ ## [0.8.0] - 2026-05-10
69
+
19
70
  ### Added
20
71
 
21
72
  - Public exports for schedule utilities: `normalizeSchedule` and `filterLessons`.
package/README.md CHANGED
@@ -31,8 +31,12 @@ console.log(schedule.lessons.length);
31
31
  ## Client options
32
32
 
33
33
  ```ts
34
+ import { createBsuirClient } from "bsuir-iis-api";
35
+
34
36
  const client = createBsuirClient({
35
37
  baseUrl: "https://iis.bsuir.by/api/v1",
38
+ allowedBaseUrlHosts: ["iis.bsuir.by"],
39
+ allowInsecureHttp: false,
36
40
  timeoutMs: 10000,
37
41
  retries: 2,
38
42
  retryDelayMs: 300,
@@ -40,7 +44,8 @@ const client = createBsuirClient({
40
44
  retryJitter: true,
41
45
  cache: { ttlMs: 60_000, maxEntries: 200 },
42
46
  dedupeInFlight: true,
43
- validateResponses: false,
47
+ maxResponseBytes: 5_000_000,
48
+ validateResponses: true,
44
49
  hooks: {
45
50
  onRetry: ({ endpoint, delayMs, reason }) => {
46
51
  console.log("retry", endpoint, delayMs, reason);
@@ -51,10 +56,14 @@ const client = createBsuirClient({
51
56
  ```
52
57
 
53
58
  - `fetch` can be passed for custom runtime/testing.
59
+ - `baseUrl` is normalized and validated (absolute URL, no credentials/query/hash, host allowlist).
60
+ - `allowedBaseUrlHosts` controls which hosts are allowed for `baseUrl` (defaults to `["iis.bsuir.by"]`).
61
+ - `allowInsecureHttp` enables `http://` only for trusted local/test endpoints.
54
62
  - `signal` in `createBsuirClient({ signal })` acts as a global cancellation signal for all requests made by that client.
55
63
  - `cache` stores successful GET responses in-memory for the configured TTL.
56
64
  - `dedupeInFlight` reuses the same in-flight GET request for concurrent callers (when no per-request signal is passed).
57
- - `validateResponses` enables runtime payload-shape checks for key endpoints.
65
+ - `maxResponseBytes` limits body size per response to protect against memory spikes.
66
+ - `validateResponses` enables runtime payload-shape checks for key endpoints (enabled by default).
58
67
  - `hooks` provides lifecycle callbacks (`onRequest`, `onRetry`, `onResponse`, `onError`) for observability.
59
68
  - `AbortSignal` is supported by all read methods.
60
69
 
@@ -92,11 +101,20 @@ const client = createBsuirClient({
92
101
 
93
102
  When IIS responds with HTTP `404` or `400` (no list, missing resource, or endpoint quirks), these methods resolve to an empty array `[]` instead of throwing `BsuirApiError`. Client-side validation still runs first (`urlId`, department `id`). If IIS later returns a meaningful `400` for bad parameters, it will also map to `[]`; other HTTP errors are unchanged.
94
103
 
104
+ ### Public exports (runtime utilities and types)
105
+
106
+ - Core runtime API: `createBsuirClient`, `BsuirClient`
107
+ - Client/runtime option types: `BsuirClientOptions`, `CacheOptions`, `ClientHooks`, `RequestOptions`, `ReadOptions`, `RequestHookContext`, `RetryHookContext`, `ResponseHookContext`, `ErrorHookContext`
108
+ - Schedule utilities: `normalizeSchedule`, `filterLessons`, `ScheduleFilterOptions`
109
+ - Error classes: `BsuirApiError`, `BsuirNetworkError`, `BsuirTimeoutError`, `BsuirValidationError`, `BsuirResponseValidationError`, `BsuirConfigurationError`
110
+ - Domain types: `Announcement`, `ApiDateResponse`, `Auditory`, `AuditoryDepartment`, `AuditoryType`, `BuildingNumber`, `Department`, `EducationForm`, `Employee`, `EmployeeCatalogItem`, `Faculty`, `FlattenedLessonsByDay`, `FlattenedScheduleItem`, `LessonStudentGroup`, `Maybe`, `NormalizedScheduleResponse`, `ScheduleItem`, `ScheduleResponse`, `Speciality`, `StudentGroupCatalogItem`, `StudentGroupShort`, `Weekday`, `WeekScheduleMap`
111
+
95
112
  ## Errors
96
113
 
97
114
  SDK throws typed errors:
98
115
 
99
116
  - `BsuirApiError` for HTTP errors (contains `status`, `endpoint`, `body`). **Exception:** `client.announcements.byEmployee` / `byDepartment` resolve to `[]` on IIS HTTP `404` or `400` instead of throwing (see Announcements above).
117
+ - `BsuirApiError` is also used when response body size exceeds configured `maxResponseBytes`.
100
118
  - `BsuirNetworkError` for transport errors (contains `endpoint` and standard `cause`)
101
119
  - `BsuirResponseValidationError` for invalid payload shapes when `validateResponses: true`
102
120
  - `BsuirTimeoutError` for timeouts (contains `endpoint`, `timeoutMs`)
@@ -110,7 +128,7 @@ Validation rules:
110
128
  - `id` and `subgroup` parameters must be positive integers
111
129
  Retry and abort behavior:
112
130
 
113
- - Retries are applied to `429`, `500`, `502`, `503`, `504`
131
+ - Retries are applied to GET requests for transport/network errors and HTTP `429`, `500`, `502`, `503`, `504`
114
132
  - `Retry-After` is respected for retriable responses
115
133
  - Caller-provided aborted `AbortSignal` is re-thrown as native `AbortError`
116
134
  - Internal timeout is mapped to `BsuirTimeoutError`
@@ -124,17 +142,21 @@ For **2xx** responses the client reads the body as text, then applies `JSON.pars
124
142
  - Valid JSON is returned even when `Content-Type` does **not** include `application/json` (mislabeled responses still parse).
125
143
  - If `Content-Type` indicates **`application/json`** but the body is empty or not valid JSON, the client throws `BsuirApiError` (`Invalid JSON response payload`), same as for a truncated `{` payload.
126
144
  - If the body is **empty** and the content type does **not** indicate JSON, the result is an empty string `""` (analogous to reading plain text). Typical IIS catalog JSON endpoints return a non-empty body.
145
+ - If response body size exceeds `maxResponseBytes`, the client throws `BsuirApiError`.
127
146
 
128
147
  ## Raw vs normalized schedule response
129
148
 
130
149
  By default, schedule methods return a **normalized** `NormalizedScheduleResponse`: `lessons` is all flattened items (weekly + exams), each tagged with `source: "schedules" | "exams"`; `scheduleLessons` / `examLessons` are the same rows split by source; `lessonsByDay` groups by weekday.
131
150
 
132
151
  ```ts
152
+ import { createBsuirClient } from "bsuir-iis-api";
153
+
154
+ const client = createBsuirClient();
133
155
  const raw = await client.schedule.getGroup("053503", { raw: true });
134
156
  ```
135
157
 
136
158
  Use `defaultRaw: true` in `createBsuirClient` to change global behavior.
137
- When `raw` is omitted, `getGroup()` and `getEmployee()` return normalized payload.
159
+ When `raw` is omitted, `getGroup()` and `getEmployee()` follow client `defaultRaw` (`false` by default, so normalized unless explicitly changed to `true`).
138
160
  In raw mode API may return `schedules: null`; normalized mode always converts it to `{}`.
139
161
  In raw mode some lesson fields may also be nullable (`weekNumber`, `lessonTypeAbbrev`), so keep null checks if you consume raw payload directly.
140
162
  README examples match the installed package version; if types and docs ever diverge, rely on `NormalizedScheduleResponse` / `ScheduleResponse` from the same release.
@@ -147,6 +169,9 @@ The SDK normalizes `current-week` payloads, including plain-text responses like
147
169
  Filtering example:
148
170
 
149
171
  ```ts
172
+ import { createBsuirClient } from "bsuir-iis-api";
173
+
174
+ const client = createBsuirClient();
150
175
  const exams = await client.schedule.getGroupFiltered("053503", {
151
176
  source: "exams",
152
177
  lessonTypeAbbrev: ["Консультация", "Экзамен"]
@@ -154,6 +179,9 @@ const exams = await client.schedule.getGroupFiltered("053503", {
154
179
  ```
155
180
 
156
181
  ```ts
182
+ import { createBsuirClient } from "bsuir-iis-api";
183
+
184
+ const client = createBsuirClient();
157
185
  const subgroupLessons = await client.schedule.getEmployeeBySubgroup("s-nesterenkov", 1);
158
186
  ```
159
187
 
@@ -94,6 +94,22 @@ declare interface BsuirClientOptions {
94
94
  * @defaultValue "https://iis.bsuir.by/api/v1"
95
95
  */
96
96
  baseUrl?: string;
97
+ /**
98
+ * Allows using `http://` for `baseUrl`.
99
+ *
100
+ * Keep disabled unless you explicitly need local/non-TLS endpoints in tests.
101
+ *
102
+ * @defaultValue false
103
+ */
104
+ allowInsecureHttp?: boolean;
105
+ /**
106
+ * Allowed hostnames for `baseUrl`.
107
+ *
108
+ * Requests are rejected if `baseUrl` hostname is not in this list.
109
+ *
110
+ * @defaultValue ["iis.bsuir.by"]
111
+ */
112
+ allowedBaseUrlHosts?: string[];
97
113
  /**
98
114
  * Custom `fetch` implementation. Useful for environments where the global
99
115
  * `fetch` is unavailable (older Node.js versions) or when you want to wrap
@@ -196,6 +212,14 @@ declare interface BsuirClientOptions {
196
212
  * @defaultValue true
197
213
  */
198
214
  dedupeInFlight?: boolean;
215
+ /**
216
+ * Maximum allowed response body size (in bytes) for a single request.
217
+ *
218
+ * Helps prevent excessive memory usage on unexpectedly large payloads.
219
+ *
220
+ * @defaultValue 5_000_000 (5 MB)
221
+ */
222
+ maxResponseBytes?: number;
199
223
  /**
200
224
  * Enables runtime shape validation of API responses.
201
225
  *
@@ -207,7 +231,7 @@ declare interface BsuirClientOptions {
207
231
  * **Recommended to enable during development and in test environments.**
208
232
  * Can be left `false` in production for a small performance gain.
209
233
  *
210
- * @defaultValue false
234
+ * @defaultValue true
211
235
  *
212
236
  * @example
213
237
  * ```ts
@@ -348,6 +372,11 @@ export { BuildingNumber }
348
372
  export { BuildingNumber as BuildingNumber_alias_1 }
349
373
  export { BuildingNumber as BuildingNumber_alias_2 }
350
374
 
375
+ /**
376
+ * Builds absolute endpoint URL from base URL, path and query params.
377
+ */
378
+ export declare function buildUrl(baseUrl: string, path: string, query?: QueryParams): string;
379
+
351
380
  declare interface CacheOptions {
352
381
  /**
353
382
  * Cache TTL for successful GET responses, in milliseconds.
@@ -397,15 +426,10 @@ declare function createAnnouncementsModule(config: Readonly<InternalClientConfig
397
426
  export { createAnnouncementsModule }
398
427
  export { createAnnouncementsModule as createAnnouncementsModule_alias_1 }
399
428
 
429
+ /**
430
+ * Creates API module for `/auditories`.
431
+ */
400
432
  declare function createAuditoriesModule(config: Readonly<InternalClientConfig>): {
401
- /**
402
- * Returns the full list of auditories from `/auditories`.
403
- * If the caller aborts `options.signal`, the platform propagates `AbortError` (not wrapped by the SDK).
404
- *
405
- * @throws {BsuirApiError} When the API returns a non-success HTTP status
406
- * @throws {BsuirNetworkError} On transport failures after retries
407
- * @throws {BsuirTimeoutError} When the request exceeds `timeoutMs`
408
- */
409
433
  listAll(options?: ReadOptions): Promise<Auditory[]>;
410
434
  };
411
435
  export { createAuditoriesModule }
@@ -440,57 +464,37 @@ declare function createBsuirClient(options?: BsuirClientOptions & {
440
464
  export { createBsuirClient }
441
465
  export { createBsuirClient as createBsuirClient_alias_1 }
442
466
 
467
+ /**
468
+ * Creates API module for `/departments`.
469
+ */
443
470
  declare function createDepartmentsModule(config: Readonly<InternalClientConfig>): {
444
- /**
445
- * Returns the full list of departments from `/departments`.
446
- * If the caller aborts `options.signal`, the platform propagates `AbortError` (not wrapped by the SDK).
447
- *
448
- * @throws {BsuirApiError} When the API returns a non-success HTTP status
449
- * @throws {BsuirNetworkError} On transport failures after retries
450
- * @throws {BsuirTimeoutError} When the request exceeds `timeoutMs`
451
- */
452
471
  listAll(options?: ReadOptions): Promise<Department[]>;
453
472
  };
454
473
  export { createDepartmentsModule }
455
474
  export { createDepartmentsModule as createDepartmentsModule_alias_1 }
456
475
 
476
+ /**
477
+ * Creates API module for `/employees/all`.
478
+ */
457
479
  declare function createEmployeesModule(config: Readonly<InternalClientConfig>): {
458
- /**
459
- * Returns the full list of employees from `/employees/all`.
460
- * If the caller aborts `options.signal`, the platform propagates `AbortError` (not wrapped by the SDK).
461
- *
462
- * @throws {BsuirApiError} When the API returns a non-success HTTP status
463
- * @throws {BsuirNetworkError} On transport failures after retries
464
- * @throws {BsuirTimeoutError} When the request exceeds `timeoutMs`
465
- */
466
480
  listAll(options?: ReadOptions): Promise<EmployeeCatalogItem[]>;
467
481
  };
468
482
  export { createEmployeesModule }
469
483
  export { createEmployeesModule as createEmployeesModule_alias_1 }
470
484
 
485
+ /**
486
+ * Creates API module for `/faculties`.
487
+ */
471
488
  declare function createFacultiesModule(config: Readonly<InternalClientConfig>): {
472
- /**
473
- * Returns the full list of faculties from `/faculties`.
474
- * If the caller aborts `options.signal`, the platform propagates `AbortError` (not wrapped by the SDK).
475
- *
476
- * @throws {BsuirApiError} When the API returns a non-success HTTP status
477
- * @throws {BsuirNetworkError} On transport failures after retries
478
- * @throws {BsuirTimeoutError} When the request exceeds `timeoutMs`
479
- */
480
489
  listAll(options?: ReadOptions): Promise<Faculty[]>;
481
490
  };
482
491
  export { createFacultiesModule }
483
492
  export { createFacultiesModule as createFacultiesModule_alias_1 }
484
493
 
494
+ /**
495
+ * Creates API module for `/student-groups`.
496
+ */
485
497
  declare function createGroupsModule(config: Readonly<InternalClientConfig>): {
486
- /**
487
- * Returns the full list of student groups from `/student-groups`.
488
- * If the caller aborts `options.signal`, the platform propagates `AbortError` (not wrapped by the SDK).
489
- *
490
- * @throws {BsuirApiError} When the API returns a non-success HTTP status
491
- * @throws {BsuirNetworkError} On transport failures after retries
492
- * @throws {BsuirTimeoutError} When the request exceeds `timeoutMs`
493
- */
494
498
  listAll(options?: ReadOptions): Promise<StudentGroupCatalogItem[]>;
495
499
  };
496
500
  export { createGroupsModule }
@@ -498,6 +502,19 @@ export { createGroupsModule as createGroupsModule_alias_1 }
498
502
 
499
503
  export declare function createJsonResponse({ status, headers, body }: MockResponseInit): Response;
500
504
 
505
+ /**
506
+ * Creates a simple catalog-like module exposing `listAll()` for a fixed endpoint.
507
+ */
508
+ export declare function createListModule<T>(config: Readonly<InternalClientConfig>, endpoint: string): {
509
+ /**
510
+ * Returns all items from the configured endpoint.
511
+ */
512
+ listAll(options?: ReadOptions): Promise<T[]>;
513
+ };
514
+
515
+ /**
516
+ * Creates schedule API module with raw/normalized response support.
517
+ */
501
518
  declare function createScheduleModule<TRawDefault extends boolean>(config: Readonly<InternalClientConfig<TRawDefault>>): {
502
519
  getGroup: <TRaw extends boolean | undefined = undefined>(groupNumber: string, options?: ReadOptions & {
503
520
  raw?: TRaw;
@@ -507,14 +524,25 @@ declare function createScheduleModule<TRawDefault extends boolean>(config: Reado
507
524
  }) => Promise<ScheduleResponseByRawOption<TRaw, TRawDefault>>;
508
525
  getGroupFiltered: (groupNumber: string, filter: ScheduleFilterOptions, options?: ReadOptions) => Promise<FlattenedScheduleItem[]>;
509
526
  getEmployeeFiltered: (urlId: string, filter: ScheduleFilterOptions, options?: ReadOptions) => Promise<FlattenedScheduleItem[]>;
527
+ /**
528
+ * Returns exams for a group.
529
+ */
510
530
  getGroupExams(groupNumber: string, options?: ReadOptions): Promise<FlattenedScheduleItem[]>;
531
+ /**
532
+ * Returns exams for an employee.
533
+ */
511
534
  getEmployeeExams(urlId: string, options?: ReadOptions): Promise<FlattenedScheduleItem[]>;
535
+ /**
536
+ * Returns regular schedule lessons of a specific subgroup for a group.
537
+ */
512
538
  getGroupBySubgroup(groupNumber: string, subgroup: number, options?: ReadOptions): Promise<FlattenedScheduleItem[]>;
539
+ /**
540
+ * Returns regular schedule lessons of a specific subgroup for an employee.
541
+ */
513
542
  getEmployeeBySubgroup(urlId: string, subgroup: number, options?: ReadOptions): Promise<FlattenedScheduleItem[]>;
514
543
  getCurrentWeek: (options?: ReadOptions) => Promise<number>;
515
544
  /**
516
- * Calls IIS `/last-update-date/student-group`. That route is legacy and unsupported on the server;
517
- * it may return an error for newer group numbers (e.g. six-digit `524404`).
545
+ * Calls IIS `/last-update-date/student-group`.
518
546
  */
519
547
  getLastUpdateByGroup(params: {
520
548
  groupNumber: string;
@@ -522,8 +550,7 @@ declare function createScheduleModule<TRawDefault extends boolean>(config: Reado
522
550
  id: number;
523
551
  }, options?: ReadOptions): Promise<ApiDateResponse>;
524
552
  /**
525
- * Calls IIS `/last-update-date/employee`. That route is legacy and unsupported on the server; prefer
526
- * not relying on it for critical cache logic.
553
+ * Calls IIS `/last-update-date/employee`.
527
554
  */
528
555
  getLastUpdateByEmployee(params: {
529
556
  urlId: string;
@@ -533,16 +560,12 @@ declare function createScheduleModule<TRawDefault extends boolean>(config: Reado
533
560
  };
534
561
  export { createScheduleModule }
535
562
  export { createScheduleModule as createScheduleModule_alias_1 }
563
+ export { createScheduleModule as createScheduleModule_alias_2 }
536
564
 
565
+ /**
566
+ * Creates API module for `/specialities`.
567
+ */
537
568
  declare function createSpecialitiesModule(config: Readonly<InternalClientConfig>): {
538
- /**
539
- * Returns the full list of specialities from `/specialities`.
540
- * If the caller aborts `options.signal`, the platform propagates `AbortError` (not wrapped by the SDK).
541
- *
542
- * @throws {BsuirApiError} When the API returns a non-success HTTP status
543
- * @throws {BsuirNetworkError} On transport failures after retries
544
- * @throws {BsuirTimeoutError} When the request exceeds `timeoutMs`
545
- */
546
569
  listAll(options?: ReadOptions): Promise<Speciality[]>;
547
570
  };
548
571
  export { createSpecialitiesModule }
@@ -621,24 +644,12 @@ export { Faculty as Faculty_alias_1 }
621
644
  export { Faculty as Faculty_alias_2 }
622
645
 
623
646
  /**
624
- * Filters normalized schedule lessons by specified criteria.
625
- *
626
- * @param response - Normalized schedule response containing lessons
627
- * @param filter - Filter options with optional fields: source, weekday, weekNumber, subgroup,
628
- * lessonTypeAbbrev, subjectQuery, employeeUrlId, auditory
629
- * @returns Array of lessons matching all provided filter criteria
630
- *
631
- * @example
632
- * ```ts
633
- * const schedule = await client.schedule.getGroup("053503");
634
- * const mondayLessons = filterLessons(schedule, { weekday: "Понедельник" });
635
- * const practiceLessons = filterLessons(schedule, { lessonTypeAbbrev: "пр" });
636
- * const lectureLessons = filterLessons(schedule, { lessonTypeAbbrev: ["лк", "лекция"] });
637
- * ```
647
+ * Filters normalized schedule lessons by criteria.
638
648
  */
639
649
  declare function filterLessons(response: NormalizedScheduleResponse, filter: ScheduleFilterOptions): FlattenedScheduleItem[];
640
650
  export { filterLessons }
641
651
  export { filterLessons as filterLessons_alias_1 }
652
+ export { filterLessons as filterLessons_alias_2 }
642
653
 
643
654
  declare type FlattenedLessonsByDay = Record<Weekday, FlattenedScheduleItem[]>;
644
655
  export { FlattenedLessonsByDay }
@@ -653,6 +664,11 @@ export { FlattenedScheduleItem }
653
664
  export { FlattenedScheduleItem as FlattenedScheduleItem_alias_1 }
654
665
  export { FlattenedScheduleItem as FlattenedScheduleItem_alias_2 }
655
666
 
667
+ /**
668
+ * Calculates retry delay using `Retry-After` when present, otherwise exponential backoff.
669
+ */
670
+ export declare function getRetryDelayMs(config: Readonly<InternalClientConfig>, attempt: number, retryAfterHeader?: string | null): number;
671
+
656
672
  export declare interface InternalClientConfig<TRawDefault extends boolean = boolean> {
657
673
  baseUrl: string;
658
674
  fetchImpl: typeof globalThis.fetch;
@@ -666,6 +682,7 @@ export declare interface InternalClientConfig<TRawDefault extends boolean = bool
666
682
  cacheTtlMs: number | undefined;
667
683
  cacheMaxEntries: number;
668
684
  dedupeInFlight: boolean;
685
+ maxResponseBytes: number;
669
686
  validateResponses: boolean;
670
687
  hooks: ClientHooks;
671
688
  responseCache: Map<string, {
@@ -733,29 +750,18 @@ export { NormalizedScheduleResponse as NormalizedScheduleResponse_alias_1 }
733
750
  export { NormalizedScheduleResponse as NormalizedScheduleResponse_alias_2 }
734
751
 
735
752
  /**
736
- * Transforms raw API schedule response into a normalized structure with flattened lessons.
737
- *
738
- * Raw API response contains lessons grouped by weekday (`schedules` object with day keys)
739
- * and exams in a separate array. This function flattens them into a single `lessons` array
740
- * for easier filtering and iteration, while preserving day-grouped view in `lessonsByDay`.
741
- *
742
- * @param response - Raw schedule response from API
743
- * @returns Normalized schedule with additional computed fields: `lessons` (flattened array),
744
- * `lessonsByDay` (grouped by weekday), `scheduleLessons`, and `examLessons`
745
- *
746
- * @example
747
- * ```ts
748
- * const rawSchedule = await client.schedule.getGroup("053503", { raw: true });
749
- * const normalized = normalizeSchedule(rawSchedule);
750
- * console.log(normalized.lessons.length); // All lessons + exams flattened
751
- * console.log(normalized.lessonsByDay["Понедельник"]); // Monday-only lessons
752
- * console.log(normalized.scheduleLessons.length); // Only regular schedule
753
- * console.log(normalized.examLessons.length); // Only exams
754
- * ```
753
+ * Transforms raw schedule response into normalized structure with flattened lessons.
755
754
  */
756
755
  declare function normalizeSchedule(response: ScheduleResponse): NormalizedScheduleResponse;
757
756
  export { normalizeSchedule }
758
757
  export { normalizeSchedule as normalizeSchedule_alias_1 }
758
+ export { normalizeSchedule as normalizeSchedule_alias_2 }
759
+
760
+ /**
761
+ * Parses response body as JSON when possible, otherwise returns text.
762
+ * Throws `BsuirApiError` for declared JSON payloads that are empty/invalid.
763
+ */
764
+ export declare function parseBody(response: Response, maxResponseBytes: number): Promise<unknown>;
759
765
 
760
766
  /**
761
767
  * Normalizes current week payload from API to a positive integer.
@@ -796,7 +802,12 @@ declare interface RequestHookContext {
796
802
  export { RequestHookContext }
797
803
  export { RequestHookContext as RequestHookContext_alias_1 }
798
804
 
799
- export declare function requestJson<T>(config: Readonly<InternalClientConfig>, path: string, options?: RequestOptions): Promise<T>;
805
+ /**
806
+ * Executes JSON HTTP request with timeout, retry/backoff, deduplication and cache support.
807
+ */
808
+ declare function requestJson<T>(config: Readonly<InternalClientConfig>, path: string, options?: RequestOptions): Promise<T>;
809
+ export { requestJson }
810
+ export { requestJson as requestJson_alias_1 }
800
811
 
801
812
  export declare type RequestMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
802
813
 
@@ -836,6 +847,9 @@ declare interface ResponseHookContext extends RequestHookContext {
836
847
  export { ResponseHookContext }
837
848
  export { ResponseHookContext as ResponseHookContext_alias_1 }
838
849
 
850
+ /** HTTP status codes retriable by the request pipeline. */
851
+ export declare const RETRIABLE_STATUS_CODES: Set<number>;
852
+
839
853
  declare interface RetryHookContext extends RequestHookContext {
840
854
  delayMs: number;
841
855
  reason: "http_status" | "network_error";
@@ -896,6 +910,14 @@ export { ScheduleResponse as ScheduleResponse_alias_2 }
896
910
 
897
911
  declare type ScheduleResponseByRawOption<TRaw extends boolean | undefined, TRawDefault extends boolean> = TRaw extends true ? ScheduleResponse : TRaw extends false ? NormalizedScheduleResponse : TRawDefault extends true ? ScheduleResponse : NormalizedScheduleResponse;
898
912
 
913
+ /**
914
+ * Writes response value to cache and performs eviction when size approaches capacity.
915
+ */
916
+ export declare function setCache(config: Readonly<InternalClientConfig>, key: string, value: unknown): void;
917
+
918
+ /** Delays execution for a specified number of milliseconds. */
919
+ export declare function sleep(ms: number): Promise<void>;
920
+
899
921
  declare interface Speciality {
900
922
  id: number;
901
923
  name: string;
@@ -933,6 +955,11 @@ export { StudentGroupShort }
933
955
  export { StudentGroupShort as StudentGroupShort_alias_1 }
934
956
  export { StudentGroupShort as StudentGroupShort_alias_2 }
935
957
 
958
+ /**
959
+ * Reads a cached response value by key, applying TTL validation and LRU touch update.
960
+ */
961
+ export declare function tryReadCache(config: Readonly<InternalClientConfig>, key: string): unknown;
962
+
936
963
  declare type Weekday = "Понедельник" | "Вторник" | "Среда" | "Четверг" | "Пятница" | "Суббота";
937
964
  export { Weekday }
938
965
  export { Weekday as Weekday_alias_1 }