bsuir-iis-api 0.9.0 → 0.10.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.
package/CHANGELOG.md CHANGED
@@ -1,18 +1,44 @@
1
1
  # Changelog
2
2
 
3
- ## 0.9.0
3
+ ## [0.10.0] - 2026-05-13
4
4
 
5
- ### Minor Changes
5
+ ### Added
6
+
7
+ - `createBsuirClient` options: `allowedBaseUrlHosts`, `allowInsecureHttp`, and `maxResponseBytes`.
8
+ - HTTP internals split into focused modules under `src/client/http/*` (`requestJson`, `response`, `cache`, `retry`, `url`).
9
+ - Schedule internals split into `scheduleApi`, `scheduleFilter`, and `scheduleNormalize`.
10
+ - Shared `createListModule` used by catalog modules to remove duplicated list/read logic.
11
+ - CI/release workflows now generate CycloneDX SBOM (`sbom.cdx.json`) on Node 24.
12
+
13
+ ### Changed
14
+
15
+ - `createBsuirClient` now validates and normalizes `baseUrl` strictly (absolute URL, no credentials/query/hash, host allowlist, HTTPS by default).
16
+ - `validateResponses` is now enabled by default.
17
+ - Example scripts now fail with non-zero exit code on unhandled errors.
18
+ - GitHub Actions in CI/release workflows are pinned to commit SHAs.
19
+ - Dev dependencies in `package.json` are now pinned to exact versions.
20
+
21
+ ### Fixed
22
+
23
+ - Response parser now enforces `maxResponseBytes` for both `Content-Length` pre-checks and streamed body reads.
24
+ - `BsuirTimeoutError` now preserves upstream abort timeout cause.
25
+ - Public API report updates for schedule typings (`FlattenedLessonsByDay` and `lessonTypeAbbrev`) and utility export documentation.
26
+
27
+ ## [0.9.1] - 2026-05-10
6
28
 
7
- - 323feb4: New public APIs, cache, hooks, deps versions update, new JSdoc, etc.
29
+ ### Fixed
30
+
31
+ - `http.ts`: cache eviction now performs true LRU — entries are sorted by `accessedAt` timestamp before eviction instead of relying on Map insertion order (which produced FIFO behaviour, not LRU).
32
+ - `http.ts`: `setCache` now removes the key before re-inserting on a cache refresh, ensuring Map insertion order stays consistent with actual last-write time.
33
+ - `http.ts`: `canUseCaching` and `canUseDedup` now check `config.signal?.aborted` in addition to per-request `options.signal`; previously a globally aborted client signal did not suppress cache reads, which could return stale data after cancellation.
8
34
 
9
- All notable changes to this project are documented in this file.
35
+ ### Documentation
10
36
 
11
- The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
12
- and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
13
- This changelog is maintained manually and updated in release commits.
37
+ - `types.ts`: all `BsuirClientOptions` fields now have full JSDoc — description, `@defaultValue`, `@example`, and side-effect notes where applicable.
38
+ - `types.ts`: `validateResponses` JSDoc explicitly recommends `process.env.NODE_ENV !== "production"` as a sensible default pattern and clarifies the performance trade-off.
39
+ - `types.ts`: `signal` JSDoc clarifies that an aborted global signal suppresses both cache reads and in-flight deduplication.
14
40
 
15
- ## [0.8.0] - 2026-05-10
41
+ ## [0.9.0] - 2026-05-10
16
42
 
17
43
  ### Added
18
44
 
package/README.md CHANGED
@@ -33,6 +33,8 @@ console.log(schedule.lessons.length);
33
33
  ```ts
34
34
  const client = createBsuirClient({
35
35
  baseUrl: "https://iis.bsuir.by/api/v1",
36
+ allowedBaseUrlHosts: ["iis.bsuir.by"],
37
+ allowInsecureHttp: false,
36
38
  timeoutMs: 10000,
37
39
  retries: 2,
38
40
  retryDelayMs: 300,
@@ -40,7 +42,8 @@ const client = createBsuirClient({
40
42
  retryJitter: true,
41
43
  cache: { ttlMs: 60_000, maxEntries: 200 },
42
44
  dedupeInFlight: true,
43
- validateResponses: false,
45
+ maxResponseBytes: 5_000_000,
46
+ validateResponses: true,
44
47
  hooks: {
45
48
  onRetry: ({ endpoint, delayMs, reason }) => {
46
49
  console.log("retry", endpoint, delayMs, reason);
@@ -51,10 +54,14 @@ const client = createBsuirClient({
51
54
  ```
52
55
 
53
56
  - `fetch` can be passed for custom runtime/testing.
57
+ - `baseUrl` is normalized and validated (absolute URL, no credentials/query/hash, host allowlist).
58
+ - `allowedBaseUrlHosts` controls which hosts are allowed for `baseUrl` (defaults to `["iis.bsuir.by"]`).
59
+ - `allowInsecureHttp` enables `http://` only for trusted local/test endpoints.
54
60
  - `signal` in `createBsuirClient({ signal })` acts as a global cancellation signal for all requests made by that client.
55
61
  - `cache` stores successful GET responses in-memory for the configured TTL.
56
62
  - `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.
63
+ - `maxResponseBytes` limits body size per response to protect against memory spikes.
64
+ - `validateResponses` enables runtime payload-shape checks for key endpoints (enabled by default).
58
65
  - `hooks` provides lifecycle callbacks (`onRequest`, `onRetry`, `onResponse`, `onError`) for observability.
59
66
  - `AbortSignal` is supported by all read methods.
60
67
 
@@ -88,43 +88,205 @@ export { BsuirClient as BsuirClient_alias_1 }
88
88
  * Options accepted by `createBsuirClient`.
89
89
  */
90
90
  declare interface BsuirClientOptions {
91
+ /**
92
+ * Base URL of the BSUIR IIS API.
93
+ *
94
+ * @defaultValue "https://iis.bsuir.by/api/v1"
95
+ */
91
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[];
113
+ /**
114
+ * Custom `fetch` implementation. Useful for environments where the global
115
+ * `fetch` is unavailable (older Node.js versions) or when you want to wrap
116
+ * requests with a proxy, MSW handler, or test mock.
117
+ *
118
+ * @defaultValue globalThis.fetch
119
+ * @example
120
+ * ```ts
121
+ * import nodeFetch from "node-fetch";
122
+ * const client = createBsuirClient({ fetch: nodeFetch as typeof fetch });
123
+ * ```
124
+ */
92
125
  fetch?: typeof globalThis.fetch;
93
- /** Optional global signal to cancel all client requests. */
126
+ /**
127
+ * Global `AbortSignal` that cancels **all** requests made by this client
128
+ * instance. Per-call signals are combined with this one.
129
+ *
130
+ * Note: caching and in-flight deduplication are disabled for requests that
131
+ * carry a signal (per-call or global) to prevent stale data from being
132
+ * returned after cancellation.
133
+ */
94
134
  signal?: AbortSignal;
95
- /** Request timeout per attempt, in milliseconds. */
135
+ /**
136
+ * Request timeout per attempt, in milliseconds.
137
+ *
138
+ * If a single fetch attempt does not complete within this window it is
139
+ * aborted and a `BsuirTimeoutError` is thrown (or the request is retried
140
+ * if retries remain).
141
+ *
142
+ * @defaultValue 10_000 (10 seconds)
143
+ */
96
144
  timeoutMs?: number;
97
- /** Number of retry attempts for retriable GET failures. */
145
+ /**
146
+ * Number of additional retry attempts for retriable GET failures (HTTP 429,
147
+ * 500, 502, 503, 504 and network errors). Set to `0` to disable retries.
148
+ *
149
+ * @defaultValue 1
150
+ */
98
151
  retries?: number;
99
- /** Base retry delay before backoff, in milliseconds. */
152
+ /**
153
+ * Base delay before the first retry, in milliseconds. Subsequent retries use
154
+ * exponential backoff: `retryDelayMs * 2^attempt`, capped by `retryMaxDelayMs`.
155
+ *
156
+ * @defaultValue 300
157
+ */
100
158
  retryDelayMs?: number;
101
- /** Upper bound for retry delay, in milliseconds. */
159
+ /**
160
+ * Upper bound for the retry delay after backoff, in milliseconds.
161
+ *
162
+ * @defaultValue 3_000 (3 seconds)
163
+ */
102
164
  retryMaxDelayMs?: number;
103
- /** Enable jitter for retry backoff to avoid synchronized retries. */
165
+ /**
166
+ * When `true`, a random jitter factor (±25 %) is applied to each retry delay
167
+ * to avoid synchronized retries from multiple clients hitting the API at the
168
+ * same time.
169
+ *
170
+ * @defaultValue true
171
+ */
104
172
  retryJitter?: boolean;
105
- /** Optional User-Agent header (used mainly in Node.js runtimes). */
173
+ /**
174
+ * Value sent as the `User-Agent` request header. Mainly useful in Node.js
175
+ * environments where servers can log the client identity.
176
+ *
177
+ * @example
178
+ * ```ts
179
+ * const client = createBsuirClient({ userAgent: "my-app/1.0.0" });
180
+ * ```
181
+ */
106
182
  userAgent?: string;
107
- /** In-memory cache configuration for successful GET responses. */
183
+ /**
184
+ * In-memory response cache configuration for successful GET requests.
185
+ *
186
+ * When configured, responses are stored in a `Map` keyed by the full request
187
+ * URL. Cache hits skip the network entirely and fire `onResponse` with
188
+ * `fromCache: true`. The cache uses a true LRU eviction policy — the
189
+ * least-recently-*read* entry is evicted first when `maxEntries` is exceeded.
190
+ *
191
+ * Caching is automatically skipped for requests that carry an `AbortSignal`
192
+ * (per-call or global) to prevent serving stale data after cancellation.
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * // Cache for 5 minutes, keep at most 500 entries
197
+ * const client = createBsuirClient({
198
+ * cache: { ttlMs: 5 * 60 * 1000, maxEntries: 500 },
199
+ * });
200
+ * ```
201
+ */
108
202
  cache?: CacheOptions;
109
- /** Enables in-flight GET request deduplication by URL. */
203
+ /**
204
+ * Enables in-flight GET request deduplication by full URL.
205
+ *
206
+ * When two identical GET requests are made concurrently, only the first one
207
+ * hits the network; the second one awaits the same `Promise`. This prevents
208
+ * duplicate API calls in scenarios like parallel component rendering.
209
+ *
210
+ * Disabled automatically when the request carries an `AbortSignal`.
211
+ *
212
+ * @defaultValue true
213
+ */
110
214
  dedupeInFlight?: boolean;
111
- /** Enables runtime validation of API response shapes. */
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;
223
+ /**
224
+ * Enables runtime shape validation of API responses.
225
+ *
226
+ * When `true`, each response is checked against the expected TypeScript type
227
+ * at runtime. An `BsuirApiError` is thrown if the payload does not match,
228
+ * which makes integration issues with the upstream API visible immediately
229
+ * instead of causing silent type-cast bugs later.
230
+ *
231
+ * **Recommended to enable during development and in test environments.**
232
+ * Can be left `false` in production for a small performance gain.
233
+ *
234
+ * @defaultValue true
235
+ *
236
+ * @example
237
+ * ```ts
238
+ * // Enable only in non-production environments
239
+ * const client = createBsuirClient({
240
+ * validateResponses: process.env.NODE_ENV !== "production",
241
+ * });
242
+ * ```
243
+ */
112
244
  validateResponses?: boolean;
113
- /** Lifecycle hooks for request/response/retry/error events. */
245
+ /**
246
+ * Lifecycle hooks called at various stages of the request pipeline.
247
+ *
248
+ * Useful for logging, metrics collection, or custom error reporting.
249
+ *
250
+ * @example
251
+ * ```ts
252
+ * const client = createBsuirClient({
253
+ * hooks: {
254
+ * onRequest: ({ method, path }) => console.log(`→ ${method} ${path}`),
255
+ * onResponse: ({ path, durationMs, fromCache }) =>
256
+ * console.log(`← ${path} ${durationMs}ms${fromCache ? " (cache)" : ""}`),
257
+ * onRetry: ({ path, attempt, reason }) =>
258
+ * console.warn(`↺ ${path} retry #${attempt} (${reason})`),
259
+ * onError: ({ path, error }) => console.error(`✗ ${path}`, error),
260
+ * },
261
+ * });
262
+ * ```
263
+ */
114
264
  hooks?: ClientHooks;
115
265
  /**
116
- * Force raw API payload for schedule endpoints by default.
117
- * This changes return types for `schedule.getGroup/getEmployee` when `raw` is omitted.
266
+ * Controls the default return type of `schedule.getGroup` and
267
+ * `schedule.getEmployee` when the per-call `raw` option is omitted.
268
+ *
269
+ * - `false` (default) — returns `NormalizedScheduleResponse` with flattened
270
+ * `lessons`, `lessonsByDay`, `scheduleLessons`, and `examLessons` arrays.
271
+ * - `true` — returns the raw `ScheduleResponse` exactly as received from the
272
+ * BSUIR IIS API.
118
273
  *
119
- * Per-call `raw` option always takes precedence over this default.
274
+ * A per-call `raw` option always takes precedence over this default.
275
+ *
276
+ * @defaultValue false
120
277
  *
121
278
  * @example
122
279
  * ```ts
123
- * const client = createBsuirClient({ defaultRaw: true });
124
- * // Returns ScheduleResponse (raw)
125
- * const raw = await client.schedule.getGroup("053503");
126
- * // Returns NormalizedScheduleResponse (per-call override)
127
- * const normalized = await client.schedule.getGroup("053503", { raw: false });
280
+ * // Normalized (default):
281
+ * const client = createBsuirClient();
282
+ * const norm = await client.schedule.getGroup("053503"); // NormalizedScheduleResponse
283
+ *
284
+ * // Raw by default:
285
+ * const rawClient = createBsuirClient({ defaultRaw: true });
286
+ * const raw = await rawClient.schedule.getGroup("053503"); // ScheduleResponse
287
+ *
288
+ * // Per-call override (always wins):
289
+ * const override = await client.schedule.getGroup("053503", { raw: true }); // ScheduleResponse
128
290
  * ```
129
291
  */
130
292
  defaultRaw?: boolean;
@@ -210,13 +372,32 @@ export { BuildingNumber }
210
372
  export { BuildingNumber as BuildingNumber_alias_1 }
211
373
  export { BuildingNumber as BuildingNumber_alias_2 }
212
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
+
213
380
  declare interface CacheOptions {
214
381
  /**
215
382
  * Cache TTL for successful GET responses, in milliseconds.
383
+ *
384
+ * After this duration has elapsed the cached entry is considered stale and
385
+ * the next request will hit the network again.
386
+ *
387
+ * @example
388
+ * ```ts
389
+ * // Cache responses for 5 minutes
390
+ * const client = createBsuirClient({ cache: { ttlMs: 5 * 60 * 1000 } });
391
+ * ```
216
392
  */
217
393
  ttlMs: number;
218
394
  /**
219
- * Maximum number of cached entries kept in memory.
395
+ * Maximum number of entries kept in the in-memory cache.
396
+ *
397
+ * When the limit is exceeded the least-recently-used (LRU) entries are evicted
398
+ * first. Expired entries are always removed before LRU eviction runs.
399
+ *
400
+ * @defaultValue 200
220
401
  */
221
402
  maxEntries?: number;
222
403
  }
@@ -245,15 +426,10 @@ declare function createAnnouncementsModule(config: Readonly<InternalClientConfig
245
426
  export { createAnnouncementsModule }
246
427
  export { createAnnouncementsModule as createAnnouncementsModule_alias_1 }
247
428
 
429
+ /**
430
+ * Creates API module for `/auditories`.
431
+ */
248
432
  declare function createAuditoriesModule(config: Readonly<InternalClientConfig>): {
249
- /**
250
- * Returns the full list of auditories from `/auditories`.
251
- * If the caller aborts `options.signal`, the platform propagates `AbortError` (not wrapped by the SDK).
252
- *
253
- * @throws {BsuirApiError} When the API returns a non-success HTTP status
254
- * @throws {BsuirNetworkError} On transport failures after retries
255
- * @throws {BsuirTimeoutError} When the request exceeds `timeoutMs`
256
- */
257
433
  listAll(options?: ReadOptions): Promise<Auditory[]>;
258
434
  };
259
435
  export { createAuditoriesModule }
@@ -288,57 +464,37 @@ declare function createBsuirClient(options?: BsuirClientOptions & {
288
464
  export { createBsuirClient }
289
465
  export { createBsuirClient as createBsuirClient_alias_1 }
290
466
 
467
+ /**
468
+ * Creates API module for `/departments`.
469
+ */
291
470
  declare function createDepartmentsModule(config: Readonly<InternalClientConfig>): {
292
- /**
293
- * Returns the full list of departments from `/departments`.
294
- * If the caller aborts `options.signal`, the platform propagates `AbortError` (not wrapped by the SDK).
295
- *
296
- * @throws {BsuirApiError} When the API returns a non-success HTTP status
297
- * @throws {BsuirNetworkError} On transport failures after retries
298
- * @throws {BsuirTimeoutError} When the request exceeds `timeoutMs`
299
- */
300
471
  listAll(options?: ReadOptions): Promise<Department[]>;
301
472
  };
302
473
  export { createDepartmentsModule }
303
474
  export { createDepartmentsModule as createDepartmentsModule_alias_1 }
304
475
 
476
+ /**
477
+ * Creates API module for `/employees/all`.
478
+ */
305
479
  declare function createEmployeesModule(config: Readonly<InternalClientConfig>): {
306
- /**
307
- * Returns the full list of employees from `/employees/all`.
308
- * If the caller aborts `options.signal`, the platform propagates `AbortError` (not wrapped by the SDK).
309
- *
310
- * @throws {BsuirApiError} When the API returns a non-success HTTP status
311
- * @throws {BsuirNetworkError} On transport failures after retries
312
- * @throws {BsuirTimeoutError} When the request exceeds `timeoutMs`
313
- */
314
480
  listAll(options?: ReadOptions): Promise<EmployeeCatalogItem[]>;
315
481
  };
316
482
  export { createEmployeesModule }
317
483
  export { createEmployeesModule as createEmployeesModule_alias_1 }
318
484
 
485
+ /**
486
+ * Creates API module for `/faculties`.
487
+ */
319
488
  declare function createFacultiesModule(config: Readonly<InternalClientConfig>): {
320
- /**
321
- * Returns the full list of faculties from `/faculties`.
322
- * If the caller aborts `options.signal`, the platform propagates `AbortError` (not wrapped by the SDK).
323
- *
324
- * @throws {BsuirApiError} When the API returns a non-success HTTP status
325
- * @throws {BsuirNetworkError} On transport failures after retries
326
- * @throws {BsuirTimeoutError} When the request exceeds `timeoutMs`
327
- */
328
489
  listAll(options?: ReadOptions): Promise<Faculty[]>;
329
490
  };
330
491
  export { createFacultiesModule }
331
492
  export { createFacultiesModule as createFacultiesModule_alias_1 }
332
493
 
494
+ /**
495
+ * Creates API module for `/student-groups`.
496
+ */
333
497
  declare function createGroupsModule(config: Readonly<InternalClientConfig>): {
334
- /**
335
- * Returns the full list of student groups from `/student-groups`.
336
- * If the caller aborts `options.signal`, the platform propagates `AbortError` (not wrapped by the SDK).
337
- *
338
- * @throws {BsuirApiError} When the API returns a non-success HTTP status
339
- * @throws {BsuirNetworkError} On transport failures after retries
340
- * @throws {BsuirTimeoutError} When the request exceeds `timeoutMs`
341
- */
342
498
  listAll(options?: ReadOptions): Promise<StudentGroupCatalogItem[]>;
343
499
  };
344
500
  export { createGroupsModule }
@@ -346,6 +502,19 @@ export { createGroupsModule as createGroupsModule_alias_1 }
346
502
 
347
503
  export declare function createJsonResponse({ status, headers, body }: MockResponseInit): Response;
348
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
+ */
349
518
  declare function createScheduleModule<TRawDefault extends boolean>(config: Readonly<InternalClientConfig<TRawDefault>>): {
350
519
  getGroup: <TRaw extends boolean | undefined = undefined>(groupNumber: string, options?: ReadOptions & {
351
520
  raw?: TRaw;
@@ -355,14 +524,25 @@ declare function createScheduleModule<TRawDefault extends boolean>(config: Reado
355
524
  }) => Promise<ScheduleResponseByRawOption<TRaw, TRawDefault>>;
356
525
  getGroupFiltered: (groupNumber: string, filter: ScheduleFilterOptions, options?: ReadOptions) => Promise<FlattenedScheduleItem[]>;
357
526
  getEmployeeFiltered: (urlId: string, filter: ScheduleFilterOptions, options?: ReadOptions) => Promise<FlattenedScheduleItem[]>;
527
+ /**
528
+ * Returns exams for a group.
529
+ */
358
530
  getGroupExams(groupNumber: string, options?: ReadOptions): Promise<FlattenedScheduleItem[]>;
531
+ /**
532
+ * Returns exams for an employee.
533
+ */
359
534
  getEmployeeExams(urlId: string, options?: ReadOptions): Promise<FlattenedScheduleItem[]>;
535
+ /**
536
+ * Returns regular schedule lessons of a specific subgroup for a group.
537
+ */
360
538
  getGroupBySubgroup(groupNumber: string, subgroup: number, options?: ReadOptions): Promise<FlattenedScheduleItem[]>;
539
+ /**
540
+ * Returns regular schedule lessons of a specific subgroup for an employee.
541
+ */
361
542
  getEmployeeBySubgroup(urlId: string, subgroup: number, options?: ReadOptions): Promise<FlattenedScheduleItem[]>;
362
543
  getCurrentWeek: (options?: ReadOptions) => Promise<number>;
363
544
  /**
364
- * Calls IIS `/last-update-date/student-group`. That route is legacy and unsupported on the server;
365
- * it may return an error for newer group numbers (e.g. six-digit `524404`).
545
+ * Calls IIS `/last-update-date/student-group`.
366
546
  */
367
547
  getLastUpdateByGroup(params: {
368
548
  groupNumber: string;
@@ -370,8 +550,7 @@ declare function createScheduleModule<TRawDefault extends boolean>(config: Reado
370
550
  id: number;
371
551
  }, options?: ReadOptions): Promise<ApiDateResponse>;
372
552
  /**
373
- * Calls IIS `/last-update-date/employee`. That route is legacy and unsupported on the server; prefer
374
- * not relying on it for critical cache logic.
553
+ * Calls IIS `/last-update-date/employee`.
375
554
  */
376
555
  getLastUpdateByEmployee(params: {
377
556
  urlId: string;
@@ -381,16 +560,12 @@ declare function createScheduleModule<TRawDefault extends boolean>(config: Reado
381
560
  };
382
561
  export { createScheduleModule }
383
562
  export { createScheduleModule as createScheduleModule_alias_1 }
563
+ export { createScheduleModule as createScheduleModule_alias_2 }
384
564
 
565
+ /**
566
+ * Creates API module for `/specialities`.
567
+ */
385
568
  declare function createSpecialitiesModule(config: Readonly<InternalClientConfig>): {
386
- /**
387
- * Returns the full list of specialities from `/specialities`.
388
- * If the caller aborts `options.signal`, the platform propagates `AbortError` (not wrapped by the SDK).
389
- *
390
- * @throws {BsuirApiError} When the API returns a non-success HTTP status
391
- * @throws {BsuirNetworkError} On transport failures after retries
392
- * @throws {BsuirTimeoutError} When the request exceeds `timeoutMs`
393
- */
394
569
  listAll(options?: ReadOptions): Promise<Speciality[]>;
395
570
  };
396
571
  export { createSpecialitiesModule }
@@ -469,24 +644,12 @@ export { Faculty as Faculty_alias_1 }
469
644
  export { Faculty as Faculty_alias_2 }
470
645
 
471
646
  /**
472
- * Filters normalized schedule lessons by specified criteria.
473
- *
474
- * @param response - Normalized schedule response containing lessons
475
- * @param filter - Filter options with optional fields: source, weekday, weekNumber, subgroup,
476
- * lessonTypeAbbrev, subjectQuery, employeeUrlId, auditory
477
- * @returns Array of lessons matching all provided filter criteria
478
- *
479
- * @example
480
- * ```ts
481
- * const schedule = await client.schedule.getGroup("053503");
482
- * const mondayLessons = filterLessons(schedule, { weekday: "Понедельник" });
483
- * const practiceLessons = filterLessons(schedule, { lessonTypeAbbrev: "пр" });
484
- * const lectureLessons = filterLessons(schedule, { lessonTypeAbbrev: ["лк", "лекция"] });
485
- * ```
647
+ * Filters normalized schedule lessons by criteria.
486
648
  */
487
649
  declare function filterLessons(response: NormalizedScheduleResponse, filter: ScheduleFilterOptions): FlattenedScheduleItem[];
488
650
  export { filterLessons }
489
651
  export { filterLessons as filterLessons_alias_1 }
652
+ export { filterLessons as filterLessons_alias_2 }
490
653
 
491
654
  declare type FlattenedLessonsByDay = Record<Weekday, FlattenedScheduleItem[]>;
492
655
  export { FlattenedLessonsByDay }
@@ -501,6 +664,11 @@ export { FlattenedScheduleItem }
501
664
  export { FlattenedScheduleItem as FlattenedScheduleItem_alias_1 }
502
665
  export { FlattenedScheduleItem as FlattenedScheduleItem_alias_2 }
503
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
+
504
672
  export declare interface InternalClientConfig<TRawDefault extends boolean = boolean> {
505
673
  baseUrl: string;
506
674
  fetchImpl: typeof globalThis.fetch;
@@ -514,6 +682,7 @@ export declare interface InternalClientConfig<TRawDefault extends boolean = bool
514
682
  cacheTtlMs: number | undefined;
515
683
  cacheMaxEntries: number;
516
684
  dedupeInFlight: boolean;
685
+ maxResponseBytes: number;
517
686
  validateResponses: boolean;
518
687
  hooks: ClientHooks;
519
688
  responseCache: Map<string, {
@@ -581,29 +750,18 @@ export { NormalizedScheduleResponse as NormalizedScheduleResponse_alias_1 }
581
750
  export { NormalizedScheduleResponse as NormalizedScheduleResponse_alias_2 }
582
751
 
583
752
  /**
584
- * Transforms raw API schedule response into a normalized structure with flattened lessons.
585
- *
586
- * Raw API response contains lessons grouped by weekday (`schedules` object with day keys)
587
- * and exams in a separate array. This function flattens them into a single `lessons` array
588
- * for easier filtering and iteration, while preserving day-grouped view in `lessonsByDay`.
589
- *
590
- * @param response - Raw schedule response from API
591
- * @returns Normalized schedule with additional computed fields: `lessons` (flattened array),
592
- * `lessonsByDay` (grouped by weekday), `scheduleLessons`, and `examLessons`
593
- *
594
- * @example
595
- * ```ts
596
- * const rawSchedule = await client.schedule.getGroup("053503", { raw: true });
597
- * const normalized = normalizeSchedule(rawSchedule);
598
- * console.log(normalized.lessons.length); // All lessons + exams flattened
599
- * console.log(normalized.lessonsByDay["Понедельник"]); // Monday-only lessons
600
- * console.log(normalized.scheduleLessons.length); // Only regular schedule
601
- * console.log(normalized.examLessons.length); // Only exams
602
- * ```
753
+ * Transforms raw schedule response into normalized structure with flattened lessons.
603
754
  */
604
755
  declare function normalizeSchedule(response: ScheduleResponse): NormalizedScheduleResponse;
605
756
  export { normalizeSchedule }
606
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>;
607
765
 
608
766
  /**
609
767
  * Normalizes current week payload from API to a positive integer.
@@ -644,7 +802,12 @@ declare interface RequestHookContext {
644
802
  export { RequestHookContext }
645
803
  export { RequestHookContext as RequestHookContext_alias_1 }
646
804
 
647
- 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 }
648
811
 
649
812
  export declare type RequestMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
650
813
 
@@ -684,6 +847,9 @@ declare interface ResponseHookContext extends RequestHookContext {
684
847
  export { ResponseHookContext }
685
848
  export { ResponseHookContext as ResponseHookContext_alias_1 }
686
849
 
850
+ /** HTTP status codes retriable by the request pipeline. */
851
+ export declare const RETRIABLE_STATUS_CODES: Set<number>;
852
+
687
853
  declare interface RetryHookContext extends RequestHookContext {
688
854
  delayMs: number;
689
855
  reason: "http_status" | "network_error";
@@ -744,6 +910,14 @@ export { ScheduleResponse as ScheduleResponse_alias_2 }
744
910
 
745
911
  declare type ScheduleResponseByRawOption<TRaw extends boolean | undefined, TRawDefault extends boolean> = TRaw extends true ? ScheduleResponse : TRaw extends false ? NormalizedScheduleResponse : TRawDefault extends true ? ScheduleResponse : NormalizedScheduleResponse;
746
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
+
747
921
  declare interface Speciality {
748
922
  id: number;
749
923
  name: string;
@@ -781,6 +955,11 @@ export { StudentGroupShort }
781
955
  export { StudentGroupShort as StudentGroupShort_alias_1 }
782
956
  export { StudentGroupShort as StudentGroupShort_alias_2 }
783
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
+
784
963
  declare type Weekday = "Понедельник" | "Вторник" | "Среда" | "Четверг" | "Пятница" | "Суббота";
785
964
  export { Weekday }
786
965
  export { Weekday as Weekday_alias_1 }