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,5 +1,5 @@
1
- import { O as isItdRateLimitError, _ as ItdStateError, a as ItdConfigError, b as isItdApiError, d as ItdForbiddenError, f as ItdNetworkError, g as ItdServerError, h as ItdRateLimitError, i as ItdAuthError, l as ItdFileError, m as ItdPhoneVerificationError, n as ItdApiError, o as ItdConflictError, p as ItdNotFoundError, s as ItdError, t as ItdAbortError, v as ItdTimeoutError, y as ItdValidationError } from "./errors-Bhrd2fJd.js";
2
- import { a as buildQuery, c as isSameSite, d as originOf, l as joinUrl, n as CookieJar, s as hostOf, u as normalizeBaseUrl } from "./cookies-tX2sNwxb.js";
1
+ import { O as isItdRateLimitError, _ as ItdStateError, a as ItdConfigError, b as isItdApiError, d as ItdForbiddenError, f as ItdNetworkError, g as ItdServerError, h as ItdRateLimitError, i as ItdAuthError, l as ItdFileError, m as ItdPhoneVerificationError, n as ItdApiError, o as ItdConflictError, p as ItdNotFoundError, s as ItdError, t as ItdAbortError, v as ItdTimeoutError, y as ItdValidationError } from "./errors-GI10kZxk.js";
2
+ import { C as shouldUseCookieJar, S as shouldSendCredentials, _ as RuntimeMode, f as hostOf, g as originOf, h as normalizeBaseUrl, i as redactHeaders, m as joinUrl, n as redactBody, p as isSameSite, s as CookieJar, u as buildQuery, v as createDeviceId, x as resolveFetch, y as isBlob } from "./redact-FNK29Fv-.js";
3
3
  //#region src/core/async-dispose.ts
4
4
  /**
5
5
  * Подставляет метод `await using` там, где `Symbol.asyncDispose` отсутствует.
@@ -51,120 +51,6 @@ function createDeadline(timeout, clock = systemClock) {
51
51
  };
52
52
  }
53
53
  //#endregion
54
- //#region src/core/runtime.ts
55
- /**
56
- * Как библиотека обращается с cookie.
57
- *
58
- * - `browser` — cookie ведёт браузер, запросы уходят с `credentials: 'include'`;
59
- * - `server` — cookie ведёт встроенный jar, заголовок `Cookie` подставляется вручную;
60
- * - `auto` — определяется по среде исполнения (значение по умолчанию).
61
- */
62
- const RuntimeMode = Object.freeze({
63
- /** Определяется по среде исполнения. Значение по умолчанию. */
64
- Auto: "auto",
65
- /** Cookie ведёт браузер, запросы уходят с `credentials: 'include'`. */
66
- Browser: "browser",
67
- /** Cookie ведёт встроенный jar, заголовок `Cookie` подставляется вручную. */
68
- Server: "server"
69
- });
70
- /** Распознанная среда исполнения. */
71
- const DetectedRuntime = Object.freeze({
72
- Browser: "browser",
73
- /** Есть `window`, но нет `document`; cookie ведёт нативный сетевой слой. */
74
- ReactNative: "react-native",
75
- Server: "server"
76
- });
77
- /**
78
- * Определяет среду исполнения.
79
- *
80
- * React Native выделен отдельно намеренно: там есть `window`, но нет `document`, а cookie
81
- * хранит нативный сетевой слой (`NSHTTPCookieStorage` / `OkHttp`). Свой jar в этой среде
82
- * привёл бы к отправке cookie дважды.
83
- */
84
- function detectRuntime() {
85
- if (globalThis.navigator?.product === "ReactNative") return DetectedRuntime.ReactNative;
86
- if (typeof document !== "undefined") return DetectedRuntime.Browser;
87
- return DetectedRuntime.Server;
88
- }
89
- /**
90
- * Нужно ли вести собственный cookie-jar.
91
- *
92
- * Только в серверных средах: в браузере `Set-Cookie` из JS не читается и не нужен,
93
- * в React Native cookie ведёт нативный слой.
94
- */
95
- function shouldUseCookieJar(mode) {
96
- if (mode === RuntimeMode.Browser) return false;
97
- if (mode === RuntimeMode.Server) return true;
98
- return detectRuntime() === DetectedRuntime.Server;
99
- }
100
- /**
101
- * Нужно ли отправлять запросы с `credentials: 'include'`.
102
- *
103
- * Только в браузере: именно так refresh-cookie попадает в запрос `POST /api/v1/auth/refresh`.
104
- * Требует настроенного CORS на стороне итд.com — если его нет, укажите свой прокси в `baseUrl`.
105
- */
106
- function shouldSendCredentials(mode) {
107
- if (mode === RuntimeMode.Browser) return true;
108
- if (mode === RuntimeMode.Server) return false;
109
- return detectRuntime() === DetectedRuntime.Browser;
110
- }
111
- /**
112
- * Возвращает реализацию `fetch`.
113
- *
114
- * @param custom реализация из конфигурации клиента, если пользователь её передал
115
- * @throws {ItdConfigError} если `fetch` недоступен — например, на Node ниже 18
116
- */
117
- function resolveFetch(custom) {
118
- if (custom !== void 0) {
119
- if (typeof custom !== "function") throw new ItdConfigError("fetch должен быть функцией");
120
- return custom;
121
- }
122
- if (typeof globalThis.fetch === "function") return globalThis.fetch.bind(globalThis);
123
- throw new ItdConfigError("В этой среде нет глобального fetch. Обновитесь до Node 18+ либо передайте свою реализацию через опцию fetch.");
124
- }
125
- /**
126
- * Проверки бинарных типов, безопасные в любой среде.
127
- *
128
- * Обычный `instanceof` здесь недостаточен: конструктора может не быть вовсе, и тогда
129
- * проверка падает с `ReferenceError`, а не возвращает `false`. `Blob` есть в Node с 18.0,
130
- * но `File` стал глобальным только в Node 20 — при заявленной поддержке Node 18 голый
131
- * `instanceof File` ронял бы любую загрузку.
132
- */
133
- function isBlob(value) {
134
- return typeof Blob !== "undefined" && value instanceof Blob;
135
- }
136
- /** @see {@link isBlob} */
137
- function isFile(value) {
138
- return typeof File !== "undefined" && value instanceof File;
139
- }
140
- /** Доступно ли потоковое чтение тела ответа — от этого зависит, сработает ли SSE. */
141
- function supportsStreamingBody() {
142
- return typeof ReadableStream !== "undefined" && typeof TextDecoder !== "undefined";
143
- }
144
- /**
145
- * Создаёт идентификатор устройства для заголовка `X-Device-Id` — UUID v4.
146
- *
147
- * `crypto.randomUUID` есть в Node 19+, браузерах и Deno, но отсутствует в Node 18 вне
148
- * защищённого контекста, поэтому предусмотрен запасной путь.
149
- */
150
- function createDeviceId() {
151
- const webCrypto = globalThis.crypto;
152
- if (typeof webCrypto?.randomUUID === "function") return webCrypto.randomUUID();
153
- const bytes = /* @__PURE__ */ new Uint8Array(16);
154
- if (typeof webCrypto?.getRandomValues === "function") webCrypto.getRandomValues(bytes);
155
- else for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
156
- bytes[6] = bytes[6] & 15 | 64;
157
- bytes[8] = bytes[8] & 63 | 128;
158
- const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
159
- return [
160
- hex.slice(0, 8),
161
- hex.slice(8, 12),
162
- hex.slice(12, 16),
163
- hex.slice(16, 20),
164
- hex.slice(20, 32)
165
- ].join("-");
166
- }
167
- //#endregion
168
54
  //#region src/core/scheduling/pacing.ts
169
55
  /** Реакция на остаток лимита из заголовков ответа. */
170
56
  const RateLimitPacing = Object.freeze({
@@ -199,7 +85,7 @@ function requireOptionalBoolean(value, name) {
199
85
  //#endregion
200
86
  //#region src/core/version.ts
201
87
  /** Версия библиотеки. Попадает в `User-Agent`. */
202
- const LIBRARY_VERSION = "0.7.1";
88
+ const LIBRARY_VERSION = "0.8.0";
203
89
  //#endregion
204
90
  //#region src/core/config.ts
205
91
  /** Базовый URL API итд.com. Домен записан в punycode: `итд.com`. */
@@ -208,12 +94,6 @@ const DEFAULT_BASE_URL = "https://xn--d1ah4a.com";
208
94
  const DEFAULT_STATUS_BASE_URL = "https://xn--80a7abcbg.xn--d1ah4a.com";
209
95
  /** Имя встроенного сервиса статуса. */
210
96
  const STATUS_SERVICE = "status";
211
- /** Сервисы, зарегистрированные у любого клиента. */
212
- const BUILT_IN_SERVICES = Object.freeze([Object.freeze({
213
- name: STATUS_SERVICE,
214
- baseUrl: DEFAULT_STATUS_BASE_URL,
215
- auth: false
216
- })]);
217
97
  /**
218
98
  * `User-Agent` по умолчанию.
219
99
  *
@@ -324,6 +204,10 @@ function requireConcurrency(value, name) {
324
204
  if (!Number.isInteger(value) || value < 1) throw new ItdConfigError(`${name} должен быть целым числом от 1, получено: ${value}`);
325
205
  return value;
326
206
  }
207
+ function requireRps(value, name) {
208
+ if (!Number.isFinite(value) || value <= 0) throw new ItdConfigError(`${name} должен быть положительным числом, получено: ${value}`);
209
+ return value;
210
+ }
327
211
  function resolvePacing(pacing) {
328
212
  const known = Object.values(RateLimitPacing);
329
213
  if (pacing !== void 0 && !known.includes(pacing)) throw new ItdConfigError(`rateLimit.pacing должен быть одним из ${known.join(", ")}, получено: ${pacing}`);
@@ -346,9 +230,11 @@ function resolveBucketOverrides(overrides, bucket, catalog) {
346
230
  assertKnownBucket(name, "rateLimit.bucketOverrides", bucket, catalog);
347
231
  if (override.concurrency !== void 0) requireConcurrency(override.concurrency, `rateLimit.bucketOverrides.${name}.concurrency`);
348
232
  if (override.limit !== void 0 && (!Number.isFinite(override.limit) || override.limit <= 0)) throw new ItdConfigError(`rateLimit.bucketOverrides.${name}.limit должен быть положительным числом, получено: ${override.limit}`);
233
+ if (override.rps !== void 0) requireRps(override.rps, `rateLimit.bucketOverrides.${name}.rps`);
349
234
  const built = builtIn[name];
350
235
  resolved[name] = Object.freeze({
351
236
  concurrency: override.concurrency ?? built?.concurrency,
237
+ rps: override.rps ?? built?.rps,
352
238
  limit: override.limit ?? built?.limit
353
239
  });
354
240
  }
@@ -381,7 +267,7 @@ function resolveRateLimit(rateLimit, catalog) {
381
267
  };
382
268
  if (!rateLimit) return defaults;
383
269
  const concurrency = requireConcurrency(rateLimit.concurrency ?? 6, "rateLimit.concurrency");
384
- if (rateLimit.rps !== void 0 && (!Number.isFinite(rateLimit.rps) || rateLimit.rps <= 0)) throw new ItdConfigError(`rateLimit.rps должен быть положительным числом, получено: ${rateLimit.rps}`);
270
+ if (rateLimit.rps !== void 0) requireRps(rateLimit.rps, "rateLimit.rps");
385
271
  const retryDelays = rateLimit.retryDelays ?? defaults.retryDelays;
386
272
  if (!Array.isArray(retryDelays)) throw new ItdConfigError("rateLimit.retryDelays должен быть массивом чисел");
387
273
  for (const delay of retryDelays) requirePositive(delay, "rateLimit.retryDelays");
@@ -666,7 +552,10 @@ var PluginRegistry = class {
666
552
  };
667
553
  const installed = plugin.install({
668
554
  ...context,
669
- operations: { use: (transformer) => register(transformers, transformer, "operations") },
555
+ operations: {
556
+ get: this.#options.operationMetadata,
557
+ use: (transformer) => register(transformers, transformer, "operations")
558
+ },
670
559
  attempts: { use: (interceptor) => register(interceptors, interceptor, "attempts") }
671
560
  });
672
561
  if (installed !== void 0 && typeof installed !== "function") throw new ItdConfigError(`install() плагина «${plugin.name}» должен вернуть функцию или void`);
@@ -705,8 +594,9 @@ var PluginRegistry = class {
705
594
  this.#removing.add(name);
706
595
  const deadline = createDeadline(this.#options.shutdownTimeout, this.#options.clock);
707
596
  const cleanup = this.#trackCleanup((async () => {
708
- const expired = await this.#release(entry, deadline);
709
- if (expired) throw expired;
597
+ const expirationErrors = await this.#release(entry, deadline);
598
+ if (expirationErrors.length === 1) throw expirationErrors[0];
599
+ if (expirationErrors.length > 1) throw new AggregateError(expirationErrors, `Не удалось отключить плагин «${name}»`);
710
600
  })());
711
601
  try {
712
602
  await cleanup;
@@ -734,8 +624,7 @@ var PluginRegistry = class {
734
624
  const previous = await Promise.allSettled(previousCleanups);
735
625
  for (const result of previous) if (result.status === "rejected") errors.push(result.reason);
736
626
  for (const entry of entries) try {
737
- const expired = await this.#release(entry, deadline);
738
- if (expired) errors.push(expired);
627
+ errors.push(...await this.#release(entry, deadline));
739
628
  } catch (error) {
740
629
  errors.push(error);
741
630
  } finally {
@@ -761,13 +650,29 @@ var PluginRegistry = class {
761
650
  * @param execute выполнение логической операции, вызываемое самым внутренним transformer
762
651
  */
763
652
  async run(request, execute) {
764
- const entries = [...this.#ordered];
653
+ const ordered = this.#ordered;
654
+ if (ordered.length === 0) return execute(request);
655
+ const entries = [...ordered];
765
656
  for (const entry of entries) entry.activeRequests += 1;
766
657
  const interceptorScope = entries.flatMap((entry) => entry.interceptors.map((interceptor) => ({
767
658
  plugin: entry.plugin.name,
768
659
  interceptor
769
660
  })));
770
- const scoped = (current) => withAttemptInterceptorScope(current, interceptorScope);
661
+ const operationId = request.operationId;
662
+ const method = request.method.toUpperCase();
663
+ const retrySafety = request.retrySafety;
664
+ const scoped = (current) => {
665
+ const prepared = current.retrySafety === void 0 ? {
666
+ ...current,
667
+ retrySafety
668
+ } : current;
669
+ return interceptorScope.length === 0 ? prepared : withAttemptInterceptorScope(prepared, interceptorScope);
670
+ };
671
+ const validateContract = (prepared) => {
672
+ if (prepared.operationId !== operationId) throw new ItdConfigError(`operation transformer не может заменить operationId «${operationId}» на «${prepared.operationId}»`);
673
+ if (prepared.method.toUpperCase() !== method) throw new ItdConfigError(`operation transformer не может заменить метод «${method}» операции «${operationId}» на «${prepared.method}»`);
674
+ if (prepared.retrySafety !== void 0 && prepared.retrySafety !== retrySafety) throw new ItdConfigError(`operation transformer не может заменить retrySafety операции «${operationId}»`);
675
+ };
771
676
  const chain = entries.flatMap((entry) => entry.transformers.map((transformer) => ({
772
677
  plugin: entry.plugin.name,
773
678
  transformer
@@ -776,9 +681,13 @@ var PluginRegistry = class {
776
681
  return transformer(scoped(current), (prepared) => {
777
682
  if (called) throw new ItdConfigError(`operation transformer плагина «${plugin}» вызвал next() больше одного раза`);
778
683
  called = true;
684
+ validateContract(prepared);
779
685
  return next(scoped(prepared));
780
686
  });
781
- }, (current) => execute(scoped(current)));
687
+ }, (current) => {
688
+ validateContract(current);
689
+ return execute(scoped(current));
690
+ });
782
691
  try {
783
692
  return await chain(scoped(request));
784
693
  } finally {
@@ -795,14 +704,23 @@ var PluginRegistry = class {
795
704
  /**
796
705
  * Дожидается операций плагина и освобождает его ресурсы.
797
706
  *
798
- * `teardown` выполняется в любом случае, в том числе после истечения срока.
707
+ * Функция очистки запускается после ожидания активных операций. Обе стадии ограничены
708
+ * общим сроком.
799
709
  *
800
- * @returns ошибка истёкшего срока, если ждать пришлось дольше отведённого
710
+ * @returns ошибки истёкшего срока для незавершённых стадий
801
711
  */
802
712
  async #release(entry, deadline) {
803
- const finished = await deadline.wait(this.#waitForDrain(entry));
804
- await entry.teardown?.();
805
- return finished ? void 0 : new ItdStateError(`плагин «${entry.plugin.name}» не завершил операции за ${this.#options.shutdownTimeout} мс; ожидание прекращено, ресурсы плагина освобождены`);
713
+ const errors = [];
714
+ if (!await deadline.wait(this.#waitForDrain(entry))) errors.push(new ItdStateError(`плагин «${entry.plugin.name}» не завершил операции за ${this.#options.shutdownTimeout} мс`));
715
+ let teardownFailed = false;
716
+ let teardownFailure;
717
+ const teardown = Promise.resolve().then(() => entry.teardown?.()).catch((error) => {
718
+ teardownFailed = true;
719
+ teardownFailure = error;
720
+ });
721
+ if (!await deadline.wait(teardown)) errors.push(new ItdStateError(`плагин «${entry.plugin.name}» не завершил teardown за ${this.#options.shutdownTimeout} мс`));
722
+ else if (teardownFailed) throw teardownFailure;
723
+ return errors;
806
724
  }
807
725
  #waitForDrain(entry) {
808
726
  if (entry.activeRequests === 0) return Promise.resolve();
@@ -827,148 +745,118 @@ function queueAbortError() {
827
745
  function queueStoppedError() {
828
746
  return new ItdAbortError("Клиент закрыт, запрос отменён");
829
747
  }
748
+ /** Длина окна лимита на сервере. */
749
+ const RATE_LIMIT_WINDOW = 6e4;
830
750
  /**
831
- * Очередь запросов: ограничивает одновременность и частоту.
832
- *
833
- * Нужна прежде всего ботам: без неё цикл по сотне постов уходит в API одним залпом
834
- * и упирается в `RATE_LIMIT_EXCEEDED`.
751
+ * Пауза при исчерпании бакета неизвестной ёмкости.
835
752
  *
836
- * Частота выдерживается равномерным разносом стартов (`1000 / rps` между запросами),
837
- * а не окном со счётчиком: так нагрузка ровная, без всплеска в начале каждой секунды.
753
+ * Ёмкость приходит в заголовке вместе с остатком, поэтому случай возможен только у чужого
754
+ * прокси, который прислал `remaining` без `limit`.
755
+ */
756
+ const UNKNOWN_CAPACITY_PAUSE = 1e3;
757
+ /**
758
+ * Единый планировщик одного направления.
838
759
  *
839
- * @internal
760
+ * Он выбирает готовый бакет и только перед фактическим запуском атомарно занимает общий
761
+ * и локальный слоты. Одно ближайшее пробуждение обслуживает все бакеты направления.
840
762
  */
841
- var RequestQueue = class {
763
+ var DestinationScheduler = class {
842
764
  #concurrency;
843
- /** Минимальный промежуток между стартами, мс. `0` — без ограничения частоты. */
844
765
  #minGap;
845
- #onDispatch;
846
- #waiting = [];
766
+ #clock;
767
+ #buckets = [];
847
768
  #active = 0;
848
- /** Момент, раньше которого следующий запрос стартовать не должен. */
849
769
  #nextSlot = 0;
850
- #clock;
770
+ #cursor = 0;
851
771
  #cancelTimer;
852
- constructor(options, clock = systemClock) {
772
+ #wakeupAt;
773
+ constructor(options, clock) {
853
774
  this.#concurrency = options.concurrency;
854
775
  this.#minGap = options.rps ? 1e3 / options.rps : 0;
855
- this.#onDispatch = options.onDispatch;
856
776
  this.#clock = clock;
857
777
  }
858
- /** Сколько задач выполняется прямо сейчас. */
859
- get active() {
860
- return this.#active;
778
+ register(bucket) {
779
+ this.#buckets.push(bucket);
861
780
  }
862
- /** Сколько задач ждёт очереди. */
863
- get pending() {
864
- return this.#waiting.length;
781
+ /** Пересчитывает ближайший запуск после изменения очереди или ограничения. */
782
+ changed() {
783
+ this.#drain();
865
784
  }
866
- /**
867
- * Ставит задачу в очередь.
868
- *
869
- * @returns результат задачи; ошибка задачи пробрасывается без изменений
870
- */
871
- schedule(task, signal) {
872
- if (signal?.aborted) return Promise.reject(queueAbortError());
873
- return new Promise((resolve, reject) => {
874
- let queued;
875
- const detach = () => signal?.removeEventListener("abort", onAbort);
876
- const onAbort = () => {
877
- const index = this.#waiting.indexOf(queued);
878
- if (index < 0) return;
879
- this.#waiting.splice(index, 1);
880
- queued.cancel(queueAbortError());
881
- this.#drain();
882
- };
883
- queued = {
884
- run: () => {
885
- detach();
886
- this.#active += 1;
887
- this.#onDispatch?.();
888
- Promise.resolve().then(task).then(resolve, reject).finally(() => {
889
- this.#active -= 1;
890
- this.#drain();
891
- });
892
- },
893
- cancel: (reason) => {
894
- detach();
895
- reject(reason);
896
- }
897
- };
898
- this.#waiting.push(queued);
899
- signal?.addEventListener("abort", onAbort, { once: true });
900
- if (signal?.aborted) onAbort();
901
- else this.#drain();
902
- });
785
+ /** Перепланирует пробуждение после изменения времени готовности бакета. */
786
+ timingChanged() {
787
+ this.#cancelWakeup();
788
+ this.#drain();
903
789
  }
904
- /**
905
- * Останавливает очередь: снимает отложенную паузу и отклоняет ещё не начатые задачи
906
- * ошибкой `ItdAbortError`. Уже выполняющиеся задачи доводятся до конца.
907
- */
790
+ /** Отклоняет ожидающие задачи, сохраняя паузы и темп для повторного запуска клиента. */
908
791
  stop() {
909
- if (this.#cancelTimer) {
910
- this.#cancelTimer();
911
- this.#cancelTimer = void 0;
912
- }
913
- const pending = this.#waiting.splice(0, this.#waiting.length);
914
- for (const task of pending) task.cancel(queueStoppedError());
792
+ this.#cancelWakeup();
793
+ for (const bucket of this.#buckets) bucket.cancelWaiting(queueStoppedError(), false);
915
794
  }
916
- /** Придерживает очередь на заданное время: ждут все её задачи, а не только одна. */
917
- pause(ms) {
918
- if (ms <= 0) return;
919
- this.#nextSlot = Math.max(this.#nextSlot, this.#clock.now() + ms);
920
- }
921
- /** Запускает столько ожидающих задач, сколько позволяют ограничения. */
922
795
  #drain() {
923
- if (this.#waiting.length === 0) {
924
- if (this.#cancelTimer) {
925
- this.#cancelTimer();
926
- this.#cancelTimer = void 0;
927
- }
928
- return;
929
- }
930
796
  if (this.#active >= this.#concurrency) return;
931
- if (this.#cancelTimer) return;
932
797
  const now = this.#clock.now();
933
- if (this.#nextSlot > now) {
934
- this.#cancelTimer = this.#clock.schedule(() => {
935
- this.#cancelTimer = void 0;
936
- this.#drain();
937
- }, this.#nextSlot - now);
798
+ let selected;
799
+ let selectedIndex = -1;
800
+ let earliest = Number.POSITIVE_INFINITY;
801
+ for (let offset = 0; offset < this.#buckets.length; offset += 1) {
802
+ const index = (this.#cursor + offset) % this.#buckets.length;
803
+ const bucket = this.#buckets[index];
804
+ if (!bucket?.canStart) continue;
805
+ const readyAt = Math.max(this.#nextSlot, bucket.readyAt);
806
+ if (readyAt < earliest) earliest = readyAt;
807
+ if (readyAt <= now) {
808
+ selected = bucket;
809
+ selectedIndex = index;
810
+ break;
811
+ }
812
+ }
813
+ if (!selected) {
814
+ if (Number.isFinite(earliest)) this.#armWakeup(Math.max(0, earliest - now));
815
+ else this.#cancelWakeup();
938
816
  return;
939
817
  }
940
- const next = this.#waiting.shift();
941
- if (!next) return;
818
+ this.#cursor = (selectedIndex + 1) % this.#buckets.length;
819
+ this.#active += 1;
942
820
  if (this.#minGap > 0) this.#nextSlot = now + this.#minGap;
943
- next.run();
821
+ selected.dispatch(now, () => {
822
+ this.#active -= 1;
823
+ this.changed();
824
+ });
944
825
  this.#drain();
945
826
  }
827
+ #armWakeup(delay) {
828
+ const wakeupAt = this.#clock.now() + delay;
829
+ if (this.#cancelTimer && (this.#wakeupAt ?? Number.POSITIVE_INFINITY) <= wakeupAt) return;
830
+ this.#cancelWakeup();
831
+ this.#wakeupAt = wakeupAt;
832
+ this.#cancelTimer = this.#clock.schedule(() => {
833
+ this.#cancelTimer = void 0;
834
+ this.#wakeupAt = void 0;
835
+ this.#drain();
836
+ }, delay);
837
+ }
838
+ #cancelWakeup() {
839
+ this.#cancelTimer?.();
840
+ this.#cancelTimer = void 0;
841
+ this.#wakeupAt = void 0;
842
+ }
946
843
  };
947
- /** Длина окна лимита на сервере. */
948
- const RATE_LIMIT_WINDOW = 6e4;
949
844
  /**
950
- * Пауза при исчерпании бакета неизвестной ёмкости.
845
+ * Состояние и очередь одного серверного бакета.
951
846
  *
952
- * Ёмкость приходит в заголовке вместе с остатком, поэтому случай возможен только у чужого
953
- * прокси, который прислал `remaining` без `limit`.
954
- */
955
- const UNKNOWN_CAPACITY_PAUSE = 1e3;
956
- /**
957
- * Очередь одного бакета поверх общей очереди направления.
958
- *
959
- * Задача занимает слот бакета, затем общий слот направления, поэтому суммарная
960
- * одновременность остаётся равной `concurrency`. Пауза бакета удерживает задачу до
961
- * захвата общего слота: притормозивший счётчик не занимает общую ёмкость.
847
+ * Класс хранит локальную конкурентность, темп и серверную квоту, а решение о старте
848
+ * делегирует общему планировщику направления.
962
849
  *
963
850
  * @internal
964
851
  */
965
852
  var BucketQueue = class {
966
853
  #destination;
967
854
  #bucket;
968
- #gate;
969
- #shared;
855
+ #scheduler;
970
856
  #clock;
971
857
  #pacing;
858
+ #concurrency;
859
+ #minGap;
972
860
  /** Ровный темп. Требует раздельных бакетов: без них ёмкость счётчика неизвестна. */
973
861
  #smooth;
974
862
  /**
@@ -985,6 +873,9 @@ var BucketQueue = class {
985
873
  /** Последнее, что сказал сервер. Живёт и в режиме `off` — ради `rateLimitState()`. */
986
874
  #limit;
987
875
  #remaining;
876
+ #active = 0;
877
+ #nextSlot = 0;
878
+ #waiting = [];
988
879
  /**
989
880
  * Оценка остатка для режима `smooth`.
990
881
  *
@@ -993,24 +884,19 @@ var BucketQueue = class {
993
884
  */
994
885
  #tokens = 1;
995
886
  #tokensAt;
996
- /**
997
- * Номер поколения очереди. `stop()` увеличивает его, отсекая задачи, которые уже взяли
998
- * слот бакета, но до общей очереди ещё не дошли.
999
- */
1000
- #generation = 0;
1001
- constructor(destination, bucket, shared, options, clock) {
887
+ constructor(destination, bucket, scheduler, options, clock, featureDefinition) {
1002
888
  this.#destination = destination;
1003
889
  this.#bucket = bucket;
1004
- this.#shared = shared;
890
+ this.#scheduler = scheduler;
1005
891
  this.#clock = clock;
1006
892
  this.#pacing = options.pacing;
1007
893
  this.#smooth = options.buckets && options.pacing === RateLimitPacing.Smooth;
1008
894
  this.#flatPause = options.buckets ? void 0 : options.retryDelays[0] ?? 0;
1009
- this.#seedLimit = options.buckets ? seedLimit(bucket, options) : void 0;
1010
- this.#gate = new RequestQueue({
1011
- concurrency: options.buckets ? options.bucketOverrides[bucket]?.concurrency ?? options.bucketConcurrency : options.concurrency,
1012
- onDispatch: this.#smooth ? () => this.#spend() : void 0
1013
- }, clock);
895
+ this.#seedLimit = options.buckets ? featureDefinition?.limit ?? seedLimit(bucket, options) : void 0;
896
+ this.#concurrency = options.buckets ? featureDefinition?.concurrency ?? options.bucketOverrides[bucket]?.concurrency ?? options.bucketConcurrency : options.concurrency;
897
+ const rps = options.buckets ? featureDefinition?.rps ?? options.bucketOverrides[bucket]?.rps : void 0;
898
+ this.#minGap = rps ? 1e3 / rps : 0;
899
+ scheduler.register(this);
1014
900
  }
1015
901
  /** Имя счётчика. При `buckets: false` — всегда `default`, каким бы ни был запрос. */
1016
902
  get bucket() {
@@ -1018,19 +904,63 @@ var BucketQueue = class {
1018
904
  }
1019
905
  /** Запросов бакета прошло в общую очередь и ещё не завершилось. */
1020
906
  get active() {
1021
- return this.#gate.active;
907
+ return this.#active;
1022
908
  }
1023
909
  /** Запросов бакета ждёт своей очереди. */
1024
910
  get pending() {
1025
- return this.#gate.pending;
911
+ return this.#waiting.length;
912
+ }
913
+ /** Есть ли ожидающая задача, для которой свободен локальный слот. */
914
+ get canStart() {
915
+ return this.#waiting.length > 0 && this.#active < this.#concurrency;
1026
916
  }
1027
- /** Ставит запрос в очередь: сначала слот бакета, затем общий слот направления. */
917
+ /** Момент, раньше которого локальное ограничение не разрешает следующий старт. */
918
+ get readyAt() {
919
+ return this.#nextSlot;
920
+ }
921
+ /** Ставит запрос в очередь единого планировщика направления. */
1028
922
  schedule(task, signal) {
1029
- const generation = this.#generation;
1030
- return this.#gate.schedule(() => {
1031
- if (generation !== this.#generation) return Promise.reject(queueStoppedError());
1032
- return this.#shared.schedule(task, signal);
1033
- }, signal);
923
+ if (signal?.aborted) return Promise.reject(queueAbortError());
924
+ return new Promise((resolve, reject) => {
925
+ let queued;
926
+ const detach = () => signal?.removeEventListener("abort", onAbort);
927
+ const onAbort = () => {
928
+ const index = this.#waiting.indexOf(queued);
929
+ if (index < 0) return;
930
+ this.#waiting.splice(index, 1);
931
+ queued.cancel(queueAbortError());
932
+ this.#scheduler.timingChanged();
933
+ };
934
+ queued = {
935
+ start: (complete) => {
936
+ detach();
937
+ Promise.resolve().then(task).then(resolve, reject).finally(complete);
938
+ },
939
+ cancel: (reason) => {
940
+ detach();
941
+ reject(reason);
942
+ }
943
+ };
944
+ this.#waiting.push(queued);
945
+ signal?.addEventListener("abort", onAbort, { once: true });
946
+ if (signal?.aborted) onAbort();
947
+ else this.#scheduler.changed();
948
+ });
949
+ }
950
+ /** Запускает первую задачу; вызывается только планировщиком направления. */
951
+ dispatch(now, complete) {
952
+ const task = this.#waiting.shift();
953
+ if (!task) {
954
+ complete();
955
+ return;
956
+ }
957
+ this.#active += 1;
958
+ if (this.#smooth) this.#spend();
959
+ if (this.#minGap > 0) this.#nextSlot = Math.max(this.#nextSlot, now + this.#minGap);
960
+ task.start(() => {
961
+ this.#active -= 1;
962
+ complete();
963
+ });
1034
964
  }
1035
965
  /**
1036
966
  * Учитывает заголовки ответа.
@@ -1046,7 +976,7 @@ var BucketQueue = class {
1046
976
  if (this.#pacing === RateLimitPacing.Off || remaining === void 0) return 0;
1047
977
  if (this.#flatPause !== void 0) {
1048
978
  if (remaining > 0) return 0;
1049
- this.#gate.pause(this.#flatPause);
979
+ this.#hold(this.#flatPause);
1050
980
  return this.#flatPause;
1051
981
  }
1052
982
  const capacity = this.#capacity();
@@ -1054,11 +984,13 @@ var BucketQueue = class {
1054
984
  if (capacity === void 0) return 0;
1055
985
  this.#refill(capacity);
1056
986
  if (remaining < this.#tokens) this.#tokens = remaining;
1057
- return this.#armPause(capacity);
987
+ const wait = this.#armPause(capacity);
988
+ this.#scheduler.timingChanged();
989
+ return wait;
1058
990
  }
1059
991
  if (remaining > 0) return 0;
1060
992
  const wait = capacity === void 0 ? UNKNOWN_CAPACITY_PAUSE : Math.ceil(RATE_LIMIT_WINDOW / Math.max(capacity, 1));
1061
- this.#gate.pause(wait);
993
+ this.#hold(wait);
1062
994
  return wait;
1063
995
  }
1064
996
  /** Придерживает бакет на названное время — путь ответа `429`. Оценка остатка обнуляется. */
@@ -1067,7 +999,7 @@ var BucketQueue = class {
1067
999
  this.#tokens = 0;
1068
1000
  this.#tokensAt = this.#clock.now();
1069
1001
  }
1070
- this.#gate.pause(ms);
1002
+ this.#hold(ms);
1071
1003
  }
1072
1004
  /** Снимок для `rateLimitState()`. */
1073
1005
  state() {
@@ -1076,17 +1008,19 @@ var BucketQueue = class {
1076
1008
  bucket: this.#bucket,
1077
1009
  limit: this.#limit,
1078
1010
  remaining: this.#remaining,
1079
- active: this.#gate.active,
1080
- pending: this.#gate.pending
1011
+ active: this.#active,
1012
+ pending: this.#waiting.length
1081
1013
  };
1082
1014
  }
1083
- /**
1084
- * Останавливает уровень бакета. Общая очередь направления гасится пулом.
1085
- *
1086
- */
1015
+ /** Отклоняет ещё не начатые задачи этого бакета. */
1087
1016
  stop() {
1088
- this.#generation += 1;
1089
- this.#gate.stop();
1017
+ this.cancelWaiting(queueStoppedError());
1018
+ }
1019
+ /** Отклоняет ожидающие задачи; используется также при остановке всего направления. */
1020
+ cancelWaiting(reason, notify = true) {
1021
+ const pending = this.#waiting.splice(0, this.#waiting.length);
1022
+ for (const task of pending) task.cancel(reason);
1023
+ if (notify) this.#scheduler.timingChanged();
1090
1024
  }
1091
1025
  /** Лимит бакета: сказанный сервером, иначе табличный. */
1092
1026
  #capacity() {
@@ -1113,9 +1047,15 @@ var BucketQueue = class {
1113
1047
  #armPause(capacity) {
1114
1048
  if (this.#tokens >= 1) return 0;
1115
1049
  const wait = Math.ceil((1 - this.#tokens) * RATE_LIMIT_WINDOW / capacity);
1116
- this.#gate.pause(wait);
1050
+ this.#nextSlot = Math.max(this.#nextSlot, this.#clock.now() + wait);
1117
1051
  return wait;
1118
1052
  }
1053
+ /** Отодвигает ближайший допустимый старт и перепланирует общее пробуждение. */
1054
+ #hold(ms) {
1055
+ if (ms <= 0) return;
1056
+ this.#nextSlot = Math.max(this.#nextSlot, this.#clock.now() + ms);
1057
+ this.#scheduler.timingChanged();
1058
+ }
1119
1059
  };
1120
1060
  /** Лимит бакета до первого ответа: поправка пользователя важнее табличного значения. */
1121
1061
  function seedLimit(bucket, options) {
@@ -1137,10 +1077,43 @@ var RequestQueuePool = class {
1137
1077
  #clock;
1138
1078
  /** Ключ `undefined` — основная очередь внутренних клиентов без известного направления. */
1139
1079
  #destinations = /* @__PURE__ */ new Map();
1080
+ /** Динамические определения feature вместе с числом использующих их клиентов. */
1081
+ #featureBuckets = /* @__PURE__ */ new Map();
1140
1082
  constructor(options, clock = systemClock) {
1141
1083
  this.#options = options;
1142
1084
  this.#clock = clock;
1143
1085
  }
1086
+ /**
1087
+ * Регистрирует бакет подключаемого feature.
1088
+ *
1089
+ * Повтор той же декларации разрешён клиентам, разделяющим один pool через `ItdAccounts`.
1090
+ * Возвращённая функция откатывает регистрацию, пока очередь бакета ещё не создана.
1091
+ */
1092
+ defineBucket(name, definition) {
1093
+ const normalized = Object.freeze({
1094
+ ...definition.limit === void 0 ? {} : { limit: definition.limit },
1095
+ ...definition.concurrency === void 0 ? {} : { concurrency: definition.concurrency },
1096
+ ...definition.rps === void 0 ? {} : { rps: definition.rps }
1097
+ });
1098
+ const existing = this.#featureBuckets.get(name);
1099
+ if (existing) {
1100
+ if (existing.definition.limit !== normalized.limit || existing.definition.concurrency !== normalized.concurrency || existing.definition.rps !== normalized.rps) throw new ItdConfigError(`Бакет feature «${name}» уже зарегистрирован с другими ограничениями`);
1101
+ existing.references += 1;
1102
+ } else this.#featureBuckets.set(name, {
1103
+ definition: normalized,
1104
+ references: 1
1105
+ });
1106
+ let released = false;
1107
+ return () => {
1108
+ if (released) return;
1109
+ released = true;
1110
+ const current = this.#featureBuckets.get(name);
1111
+ if (!current) return;
1112
+ current.references -= 1;
1113
+ if (current.references > 0) return;
1114
+ if (![...this.#destinations.values()].some((entry) => entry.buckets.has(name))) this.#featureBuckets.delete(name);
1115
+ };
1116
+ }
1144
1117
  /** Очередь бакета на направлении. При `buckets: false` бакет всегда `default`. */
1145
1118
  for(destination, bucket) {
1146
1119
  const fallback = this.#options.defaultBucket;
@@ -1148,14 +1121,14 @@ var RequestQueuePool = class {
1148
1121
  let entry = this.#destinations.get(destination);
1149
1122
  if (!entry) {
1150
1123
  entry = {
1151
- shared: new RequestQueue(this.#options, this.#clock),
1124
+ scheduler: new DestinationScheduler(this.#options, this.#clock),
1152
1125
  buckets: /* @__PURE__ */ new Map()
1153
1126
  };
1154
1127
  this.#destinations.set(destination, entry);
1155
1128
  }
1156
1129
  let queue = entry.buckets.get(name);
1157
1130
  if (!queue) {
1158
- queue = new BucketQueue(destination, name, entry.shared, this.#options, this.#clock);
1131
+ queue = new BucketQueue(destination, name, entry.scheduler, this.#options, this.#clock, this.#featureBuckets.get(name)?.definition);
1159
1132
  entry.buckets.set(name, queue);
1160
1133
  }
1161
1134
  return queue;
@@ -1167,19 +1140,17 @@ var RequestQueuePool = class {
1167
1140
  return states;
1168
1141
  }
1169
1142
  /**
1170
- * Останавливает оба уровня всех очередей: ожидающие задачи отклоняются, а состояние
1171
- * счётчиков и отложенные паузы сохраняются до следующего запуска — путь `close()`.
1143
+ * Останавливает планировщики: ожидающие задачи отклоняются, а состояние счётчиков
1144
+ * и отложенные паузы сохраняются до следующего запуска — путь `close()`.
1172
1145
  */
1173
1146
  stop() {
1174
- for (const entry of this.#destinations.values()) {
1175
- for (const queue of entry.buckets.values()) queue.stop();
1176
- entry.shared.stop();
1177
- }
1147
+ for (const entry of this.#destinations.values()) entry.scheduler.stop();
1178
1148
  }
1179
1149
  /** Останавливает очереди и забывает всё, что известно о счётчиках, — путь `dispose()`. */
1180
1150
  clear() {
1181
1151
  this.stop();
1182
1152
  this.#destinations.clear();
1153
+ this.#featureBuckets.clear();
1183
1154
  }
1184
1155
  };
1185
1156
  //#endregion
@@ -1251,6 +1222,28 @@ var ServiceRegistry = class {
1251
1222
  has(name) {
1252
1223
  return this.#services.has(name);
1253
1224
  }
1225
+ /** Удаляет сервис при откате атомарной установки feature. @internal */
1226
+ delete(name) {
1227
+ return this.#services.delete(name);
1228
+ }
1229
+ /**
1230
+ * Атомарно заменяет существующее определение и возвращает прежнее.
1231
+ * Используется feature, накладывающим default на сервис из опций клиента.
1232
+ *
1233
+ * @internal
1234
+ */
1235
+ replace(definition) {
1236
+ const name = definition.name.trim();
1237
+ const previous = this.require(name);
1238
+ this.#services.delete(name);
1239
+ try {
1240
+ this.define(definition);
1241
+ } catch (error) {
1242
+ this.#services.set(name, previous);
1243
+ throw error;
1244
+ }
1245
+ return previous;
1246
+ }
1254
1247
  /**
1255
1248
  * Определение сервиса.
1256
1249
  *
@@ -1343,14 +1336,14 @@ function markDisposeCleanupRequest(request) {
1343
1336
  [DISPOSE_CLEANUP_REQUEST]: true
1344
1337
  };
1345
1338
  }
1346
- /** Разрешено ли запросу завершать внутреннюю очистку после перехода клиента в terminal state. @internal */
1339
+ /** Разрешено ли запросу завершать внутреннюю очистку после `dispose()`. @internal */
1347
1340
  function isDisposeCleanupRequest(request) {
1348
1341
  return request[DISPOSE_CLEANUP_REQUEST] === true;
1349
1342
  }
1350
1343
  /**
1351
1344
  * Собирает слои в один обработчик.
1352
1345
  *
1353
- * Первый слой оказывается самым внешним. Порядок задаётся внутренним client runtime.
1346
+ * Первый слой становится внешним.
1354
1347
  *
1355
1348
  * @example
1356
1349
  * ```ts
@@ -1378,24 +1371,17 @@ function withLayerHeaders(request, headers) {
1378
1371
  /**
1379
1372
  * Точка входа ресурсов в конвейер запросов.
1380
1373
  *
1381
- * Принимает готовый обработчик цепочку слоёв поверх транспорта, собранную
1382
- * во внутреннем runtime клиента, — и отдаёт ресурсам методы `request`/`operation`.
1374
+ * Принимает готовую цепочку обработки и предоставляет ресурсам методы `request`/`execute`.
1383
1375
  * О слоях и их порядке ресурсы не знают.
1384
1376
  */
1385
1377
  var HttpClient = class {
1386
1378
  #handler;
1379
+ #plugins;
1387
1380
  #baseUrl;
1388
- #catalog;
1389
1381
  constructor(deps) {
1390
1382
  this.#handler = deps.handler;
1383
+ this.#plugins = deps.plugins;
1391
1384
  this.#baseUrl = deps.baseUrl;
1392
- this.#catalog = deps.catalog;
1393
- }
1394
- /** Метод операции из каталога. Отсутствие описания — ошибка сборки клиента, не запроса. */
1395
- #methodOf(operationId) {
1396
- const method = this.#catalog.methodOf(operationId);
1397
- if (method === void 0) throw new ItdConfigError(`каталог операций не знает «${operationId}»`);
1398
- return method;
1399
1385
  }
1400
1386
  /** Базовый URL, к которому обращается клиент. */
1401
1387
  get baseUrl() {
@@ -1411,23 +1397,34 @@ var HttpClient = class {
1411
1397
  * @throws {ItdNetworkError} если запрос не дошёл до сервера
1412
1398
  */
1413
1399
  request(options) {
1414
- return this.#handler(identifyRequest(options));
1400
+ const request = identifyRequest(options);
1401
+ return this.#plugins.run(request, (prepared) => this.#handler(prepared));
1402
+ }
1403
+ /** Выполняет контракт операции; `next()` плагина возвращает результат после `read`. */
1404
+ execute(operation, options) {
1405
+ return this.#run(operation, options);
1415
1406
  }
1416
- /** Выполняет встроенную семантическую операцию, подставляя её HTTP-метод из каталога. */
1417
- operation(operationId, options) {
1418
- return this.request({
1407
+ #run(operation, options) {
1408
+ const request = identifyRequest({
1419
1409
  ...options,
1420
- operationId,
1421
- method: this.#methodOf(operationId)
1410
+ operationId: operation.id,
1411
+ method: operation.method,
1412
+ retrySafety: options.retrySafety ?? operation.retrySafety
1422
1413
  });
1414
+ return this.#plugins.run(request, async (prepared) => operation.read(await this.#handler(prepared), prepared));
1423
1415
  }
1424
1416
  /** Выполняет внутреннюю операцию финализации после начала `ItdClient.dispose()`. @internal */
1425
- cleanupOperation(operationId, options) {
1426
- return this.request(markDisposeCleanupRequest({
1417
+ cleanupOperation(operation, options) {
1418
+ const request = identifyRequest({
1427
1419
  ...options,
1428
- operationId,
1429
- method: this.#methodOf(operationId)
1430
- }));
1420
+ operationId: operation.id,
1421
+ method: operation.method,
1422
+ retrySafety: options.retrySafety ?? operation.retrySafety
1423
+ });
1424
+ return this.#plugins.run(request, async (prepared) => {
1425
+ const cleanupRequest = markDisposeCleanupRequest(prepared);
1426
+ return operation.read(await this.#handler(cleanupRequest), prepared);
1427
+ });
1431
1428
  }
1432
1429
  };
1433
1430
  //#endregion
@@ -1452,6 +1449,50 @@ const RetrySafety = Object.freeze({
1452
1449
  /** Повтор может создать ещё один побочный эффект. */
1453
1450
  Unsafe: "unsafe"
1454
1451
  });
1452
+ function snapshotAnnotation(value, operationId, copies) {
1453
+ if (value === null || typeof value !== "object" && typeof value !== "function") return value;
1454
+ if (typeof value === "function") throw new ItdConfigError(`annotations операции «${operationId}» не должны содержать функции`);
1455
+ const existing = copies.get(value);
1456
+ if (existing !== void 0) return existing;
1457
+ if (Array.isArray(value)) {
1458
+ const copy = [];
1459
+ copies.set(value, copy);
1460
+ for (const item of value) copy.push(snapshotAnnotation(item, operationId, copies));
1461
+ return Object.freeze(copy);
1462
+ }
1463
+ const prototype = Object.getPrototypeOf(value);
1464
+ if (prototype !== Object.prototype && prototype !== null) throw new ItdConfigError(`annotations операции «${operationId}» должны состоять из обычных объектов, массивов и примитивов`);
1465
+ const copy = Object.create(prototype);
1466
+ copies.set(value, copy);
1467
+ for (const key of Reflect.ownKeys(value)) {
1468
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
1469
+ if (!descriptor?.enumerable) continue;
1470
+ if (!("value" in descriptor)) throw new ItdConfigError(`annotations операции «${operationId}» не должны содержать getters или setters`);
1471
+ Object.defineProperty(copy, key, {
1472
+ value: snapshotAnnotation(descriptor.value, operationId, copies),
1473
+ enumerable: true,
1474
+ writable: true,
1475
+ configurable: true
1476
+ });
1477
+ }
1478
+ return Object.freeze(copy);
1479
+ }
1480
+ /** Создаёт неизменяемый контракт результата. @internal */
1481
+ function defineOperation(id, metadata, read) {
1482
+ const annotations = metadata.annotations === void 0 ? void 0 : snapshotAnnotation(metadata.annotations, id, /* @__PURE__ */ new Map());
1483
+ return Object.freeze({
1484
+ id,
1485
+ ...metadata,
1486
+ ...annotations === void 0 ? {} : { annotations },
1487
+ read
1488
+ });
1489
+ }
1490
+ /** Возвращает разобранное тело без дополнительного преобразования. @internal */
1491
+ function identityResult(body) {
1492
+ return body;
1493
+ }
1494
+ /** Отбрасывает служебное тело операции без результата. @internal */
1495
+ function voidResult() {}
1455
1496
  //#endregion
1456
1497
  //#region src/core/scheduling/retry.ts
1457
1498
  /** Методы raw-запроса, безопасность которых гарантирована самим HTTP-контрактом. */
@@ -1561,9 +1602,6 @@ function createQueueMiddleware(schedule) {
1561
1602
  * разу, иначе, например, текст поста зашифруется дважды. Здесь же к операции привязывается
1562
1603
  * snapshot attempt interceptors; сами они выполняются транспортом на каждой попытке.
1563
1604
  */
1564
- function createPluginsMiddleware(plugins) {
1565
- return (request, next) => plugins.run(request, next);
1566
- }
1567
1605
  /**
1568
1606
  * Слой сервисов.
1569
1607
  *
@@ -1721,90 +1759,6 @@ function createRetryMiddleware(deps) {
1721
1759
  };
1722
1760
  }
1723
1761
  //#endregion
1724
- //#region src/core/redact.ts
1725
- /** Заголовки, значение которых нельзя писать в лог целиком. */
1726
- const SECRET_HEADERS = /* @__PURE__ */ new Set([
1727
- "authorization",
1728
- "cookie",
1729
- "set-cookie",
1730
- "x-api-key"
1731
- ]);
1732
- /** Поля тела запроса, значение которых нельзя писать в лог. */
1733
- const SECRET_FIELDS = /* @__PURE__ */ new Set([
1734
- "password",
1735
- "oldpassword",
1736
- "newpassword",
1737
- "accesstoken",
1738
- "refreshtoken",
1739
- "currentpassword",
1740
- "flowtoken",
1741
- "token",
1742
- "turnstiletoken",
1743
- "otp"
1744
- ]);
1745
- /** Query-параметры, содержащие секреты. */
1746
- const SECRET_QUERY_PARAMS = /* @__PURE__ */ new Set(["token", "access_token"]);
1747
- /**
1748
- * Прячет середину секрета, оставляя концы для сопоставления.
1749
- *
1750
- * @example
1751
- * ```ts
1752
- * maskSecret('eyJhbGciOiJIUzI1NiJ9.abc'); // 'eyJh…(24)…abc'
1753
- * ```
1754
- */
1755
- function maskSecret(value) {
1756
- if (value.length <= 8) return "…";
1757
- return `${value.slice(0, 4)}…(${value.length})…${value.slice(-3)}`;
1758
- }
1759
- /** Маскирует секретные query-параметры URL перед записью в логи и ошибки. */
1760
- function redactUrl(url) {
1761
- try {
1762
- const parsed = new URL(url);
1763
- const entries = [...parsed.searchParams.entries()];
1764
- parsed.search = "";
1765
- for (const [name, value] of entries) parsed.searchParams.append(name, SECRET_QUERY_PARAMS.has(name.toLowerCase()) ? maskSecret(value) : value);
1766
- return parsed.toString();
1767
- } catch {
1768
- return url.replace(/([?&](?:token|access_token)=)[^&#]*/gi, "$1[скрыто]");
1769
- }
1770
- }
1771
- /**
1772
- * Готовит заголовки к записи в лог.
1773
- *
1774
- * `Authorization` и `Cookie` маскируются: логи часто уезжают в системы сбора,
1775
- * и токен доступа в них попадать не должен.
1776
- */
1777
- function redactHeaders(headers) {
1778
- const result = {};
1779
- headers.forEach((value, name) => {
1780
- if (SECRET_HEADERS.has(name.toLowerCase())) {
1781
- const spaceAt = value.indexOf(" ");
1782
- result[name] = spaceAt > 0 ? `${value.slice(0, spaceAt)} ${maskSecret(value.slice(spaceAt + 1))}` : maskSecret(value);
1783
- return;
1784
- }
1785
- result[name] = value;
1786
- });
1787
- return result;
1788
- }
1789
- /**
1790
- * Готовит тело запроса к записи в лог: пароли, токены и коды OTP заменяются заглушкой.
1791
- *
1792
- * Обходит вложенные объекты и массивы. `FormData` и бинарные тела не раскрываются вовсе.
1793
- */
1794
- function redactBody(body) {
1795
- if (body === null || body === void 0) return body;
1796
- if (typeof FormData !== "undefined" && body instanceof FormData) return "[FormData]";
1797
- if (isBlob(body)) return "[Blob]";
1798
- if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return "[binary]";
1799
- if (Array.isArray(body)) return body.map(redactBody);
1800
- if (typeof body === "object") {
1801
- const result = {};
1802
- for (const [key, value] of Object.entries(body)) result[key] = SECRET_FIELDS.has(key.toLowerCase()) ? "[скрыто]" : redactBody(value);
1803
- return result;
1804
- }
1805
- return body;
1806
- }
1807
- //#endregion
1808
1762
  //#region src/core/unwrap.ts
1809
1763
  /**
1810
1764
  * Снимает внешнюю обёртку `{ data: … }`.
@@ -2440,7 +2394,7 @@ var Transport = class {
2440
2394
  };
2441
2395
  //#endregion
2442
2396
  //#region src/core/execution/client-runtime.ts
2443
- /** Имена стадий основного request pipeline в порядке выполнения. @internal */
2397
+ /** Имена стадий обработки запроса в порядке выполнения. @internal */
2444
2398
  const ClientRuntimeStage = Object.freeze({
2445
2399
  OperationPlugins: "operation_plugins",
2446
2400
  Services: "services",
@@ -2452,26 +2406,21 @@ const ClientRuntimeStage = Object.freeze({
2452
2406
  AuthHeaders: "auth_headers",
2453
2407
  Transport: "transport"
2454
2408
  });
2455
- /** Регистрирует встроенные сервисы и накладывает пользовательские overrides. */
2409
+ /** Регистрирует сервисы, заранее объявленные в опциях клиента. */
2456
2410
  function createServiceRegistry(config) {
2457
2411
  const services = new ServiceRegistry(config.baseUrl);
2458
- const overrides = new Map(config.services.map((service) => [service.name.trim(), service]));
2459
- for (const builtIn of BUILT_IN_SERVICES) {
2460
- const override = overrides.get(builtIn.name);
2461
- overrides.delete(builtIn.name);
2462
- services.define(override ? mergeService(builtIn, override) : builtIn);
2463
- }
2464
- for (const service of overrides.values()) services.define(service);
2412
+ for (const service of config.services) services.define(service);
2465
2413
  return services;
2466
2414
  }
2467
- /** Собирает внутренний runtime клиента и единственный request pipeline. @internal */
2415
+ /** Собирает внутренние компоненты и цепочку запросов клиента. @internal */
2468
2416
  function createClientRuntime(options, internals) {
2469
2417
  const catalog = internals.catalog;
2470
2418
  const config = resolveRuntimeConfig(options, catalog);
2471
2419
  const jar = new CookieJar();
2472
2420
  const plugins = new PluginRegistry({
2473
2421
  shutdownTimeout: config.shutdownTimeout,
2474
- clock: config.clock
2422
+ clock: config.clock,
2423
+ operationMetadata: (operationId) => catalog.definitionOf(operationId)
2475
2424
  });
2476
2425
  const services = createServiceRegistry(config);
2477
2426
  const lifetime = new AbortController();
@@ -2520,10 +2469,6 @@ function createClientRuntime(options, internals) {
2520
2469
  lifetimeSignal: lifetime.signal
2521
2470
  });
2522
2471
  const stages = [
2523
- {
2524
- name: ClientRuntimeStage.OperationPlugins,
2525
- middleware: createPluginsMiddleware(plugins)
2526
- },
2527
2472
  {
2528
2473
  name: ClientRuntimeStage.Services,
2529
2474
  middleware: createServicesMiddleware(services)
@@ -2575,17 +2520,21 @@ function createClientRuntime(options, internals) {
2575
2520
  }
2576
2521
  return handler(request);
2577
2522
  };
2523
+ const http = new HttpClient({
2524
+ handler: clientHandler,
2525
+ plugins,
2526
+ baseUrl: config.baseUrl
2527
+ });
2578
2528
  auth = internals.auth({
2579
2529
  config,
2580
- handler: clientHandler,
2530
+ http,
2581
2531
  cookies: jar
2582
2532
  });
2583
- const http = new HttpClient({
2584
- handler: clientHandler,
2585
- baseUrl: config.baseUrl,
2586
- catalog
2587
- });
2588
- const stageOrder = Object.freeze([...stages.map(({ name }) => name), ClientRuntimeStage.Transport]);
2533
+ const stageOrder = Object.freeze([
2534
+ ClientRuntimeStage.OperationPlugins,
2535
+ ...stages.map(({ name }) => name),
2536
+ ClientRuntimeStage.Transport
2537
+ ]);
2589
2538
  return {
2590
2539
  config,
2591
2540
  http,
@@ -2594,8 +2543,27 @@ function createClientRuntime(options, internals) {
2594
2543
  plugins,
2595
2544
  services,
2596
2545
  stageOrder,
2546
+ connection: (serviceName) => {
2547
+ const service = serviceName === void 0 ? void 0 : services.require(serviceName);
2548
+ const baseUrl = service?.baseUrl ?? config.baseUrl;
2549
+ return Object.freeze({
2550
+ baseUrl,
2551
+ authorize: service?.auth ?? true,
2552
+ fetch: config.fetch,
2553
+ clock: config.clock,
2554
+ logger: config.logger,
2555
+ baseHeaders: async (url) => {
2556
+ const headers = await transport.platformHeaders(url);
2557
+ for (const [name, value] of Object.entries(service?.headers ?? {})) headers.set(name, value);
2558
+ return headers;
2559
+ },
2560
+ getToken: () => auth.token(),
2561
+ refreshAuth: () => auth.recover()
2562
+ });
2563
+ },
2597
2564
  platformHeaders: (url) => transport.platformHeaders(url),
2598
2565
  rateLimitState: () => queues?.states() ?? [],
2566
+ registerRateLimitBucket: (name, definition) => queues?.defineBucket(name, definition),
2599
2567
  close: () => {
2600
2568
  if (ownsQueues) queues?.stop();
2601
2569
  },
@@ -2995,12 +2963,12 @@ const OPERATIONS = freezeOperations({
2995
2963
  method: "PUT",
2996
2964
  retrySafety: RetrySafety.Idempotent
2997
2965
  },
2998
- "realtime.poll.updates": {
2966
+ "events.notifications.poll.updates": {
2999
2967
  method: "GET",
3000
2968
  retrySafety: RetrySafety.Safe,
3001
2969
  bucket: "notifications"
3002
2970
  },
3003
- "realtime.poll.unread": {
2971
+ "events.notifications.poll.unread": {
3004
2972
  method: "GET",
3005
2973
  retrySafety: RetrySafety.Safe,
3006
2974
  bucket: "notifications"
@@ -3084,10 +3052,6 @@ const OPERATIONS = freezeOperations({
3084
3052
  method: "GET",
3085
3053
  retrySafety: RetrySafety.Safe
3086
3054
  },
3087
- "platform.status": {
3088
- method: "GET",
3089
- retrySafety: RetrySafety.Safe
3090
- },
3091
3055
  "telemetry.dwell": {
3092
3056
  method: "POST",
3093
3057
  retrySafety: RetrySafety.Unsafe
@@ -3097,6 +3061,10 @@ const OPERATIONS = freezeOperations({
3097
3061
  retrySafety: RetrySafety.Unsafe
3098
3062
  }
3099
3063
  });
3064
+ /** Создаёт контракт результата встроенной операции с проверяемым ID. */
3065
+ function defineBuiltInOperation(id, read = identityResult) {
3066
+ return defineOperation(id, OPERATIONS[id], read);
3067
+ }
3100
3068
  const BUILT_IN_IDS = new Set(Object.keys(OPERATIONS));
3101
3069
  /** Проверяет принадлежность ID встроенному каталогу. */
3102
3070
  function isBuiltInOperationId(value) {
@@ -3131,6 +3099,7 @@ function operationBucket(id) {
3131
3099
  * @internal
3132
3100
  */
3133
3101
  const ITD_CATALOG = Object.freeze({
3102
+ definitionOf: (id) => isBuiltInOperationId(id) ? OPERATIONS[id] : void 0,
3134
3103
  retrySafetyOf: (id) => isBuiltInOperationId(id) ? operationRetrySafety(id) : void 0,
3135
3104
  methodOf: (id) => isBuiltInOperationId(id) ? operationMethod(id) : void 0,
3136
3105
  bucketOf: (id) => operationBucket(id),
@@ -3217,8 +3186,8 @@ const ReportReason = Object.freeze({
3217
3186
  Fraud: "fraud",
3218
3187
  Other: "other"
3219
3188
  });
3220
- /** Состояние realtime-соединения. */
3221
- const RealtimeStatus = Object.freeze({
3189
+ /** Состояние соединения событийного канала. */
3190
+ const EventChannelStatus = Object.freeze({
3222
3191
  Connecting: "connecting",
3223
3192
  Connected: "connected",
3224
3193
  Error: "error",
@@ -3537,6 +3506,103 @@ function readUnreadCountEvent(data) {
3537
3506
  return typeof payload.count === "number" ? payload.count : void 0;
3538
3507
  }
3539
3508
  //#endregion
3509
+ //#region src/operations/common.ts
3510
+ /** Контракт встроенной операции без дополнительного преобразования результата. */
3511
+ function passthroughOperation(id) {
3512
+ return defineBuiltInOperation(id);
3513
+ }
3514
+ /** Контракт встроенной операции без возвращаемого значения. */
3515
+ function voidOperation(id) {
3516
+ return defineBuiltInOperation(id, voidResult);
3517
+ }
3518
+ //#endregion
3519
+ //#region src/core/auth-provider.ts
3520
+ /**
3521
+ * Идентификатор устройства, общий для провайдеров без сессии.
3522
+ *
3523
+ * Значение своё у каждого провайдера: сервер различает по нему записи в списке сессий,
3524
+ * поэтому делить его между клиентами нельзя. Хранилища нет, так что перезапуск процесса
3525
+ * значение не переживает — в этом и разница с полноценной сессией.
3526
+ */
3527
+ function localDeviceId() {
3528
+ let deviceId;
3529
+ return () => {
3530
+ deviceId ??= createDeviceId();
3531
+ return Promise.resolve(deviceId);
3532
+ };
3533
+ }
3534
+ /**
3535
+ * Авторизации нет: заголовок не подставляется, ответ `401` не восстанавливается.
3536
+ *
3537
+ * @example
3538
+ * ```ts
3539
+ * const api = createRestClient(); // публичные эндпоинты доступны и без токена
3540
+ * ```
3541
+ */
3542
+ function anonymousAuth() {
3543
+ return {
3544
+ token: () => Promise.resolve(null),
3545
+ prepare: () => Promise.resolve(),
3546
+ currentHeaders: () => ({}),
3547
+ recover: () => Promise.resolve(false),
3548
+ deviceId: localDeviceId(),
3549
+ dispose: () => {}
3550
+ };
3551
+ }
3552
+ /**
3553
+ * Готовый Bearer-токен: ни хранилища, ни продления.
3554
+ *
3555
+ * Ответ `401` уходит вызывающему коду как есть — обновить токен провайдеру нечем.
3556
+ * Для сессии, которая продлевает себя сама, нужен полный клиент.
3557
+ *
3558
+ * @example
3559
+ * ```ts
3560
+ * const api = createRestClient({ auth: bearerToken(process.env.ITD_TOKEN) });
3561
+ * ```
3562
+ */
3563
+ function bearerToken(accessToken) {
3564
+ const headers = { Authorization: `Bearer ${accessToken}` };
3565
+ return {
3566
+ token: () => Promise.resolve(accessToken),
3567
+ prepare: () => Promise.resolve(),
3568
+ currentHeaders: () => headers,
3569
+ recover: () => Promise.resolve(false),
3570
+ deviceId: localDeviceId(),
3571
+ dispose: () => {}
3572
+ };
3573
+ }
3574
+ /**
3575
+ * Токен из внешнего источника — хранилища секретов, кэша, соседнего сервиса.
3576
+ *
3577
+ * Источник спрашивается на стадии подготовки, до входа в очередь: там ожидание безопасно,
3578
+ * а слот транспорта ещё не занят. Значение держится до следующей подготовки, потому что
3579
+ * подстановка заголовков обязана быть синхронной.
3580
+ *
3581
+ * @example
3582
+ * ```ts
3583
+ * const api = createRestClient({ auth: tokenProvider(() => vault.read('itd')) });
3584
+ * ```
3585
+ */
3586
+ function tokenProvider(getToken) {
3587
+ let cached = null;
3588
+ const read = async () => {
3589
+ cached = await getToken();
3590
+ return cached;
3591
+ };
3592
+ return {
3593
+ token: read,
3594
+ prepare: async () => {
3595
+ await read();
3596
+ },
3597
+ currentHeaders: () => cached ? { Authorization: `Bearer ${cached}` } : {},
3598
+ recover: () => Promise.resolve(false),
3599
+ deviceId: localDeviceId(),
3600
+ dispose: () => {
3601
+ cached = null;
3602
+ }
3603
+ };
3604
+ }
3605
+ //#endregion
3540
3606
  //#region src/notifications/text.ts
3541
3607
  /** Имя, которое подставляется, если участник неизвестен. */
3542
3608
  const UNKNOWN_ACTOR = "Пользователь";
@@ -3668,6 +3734,6 @@ function resolveNotificationUrl(notification) {
3668
3734
  return clickUrl || "/notifications";
3669
3735
  }
3670
3736
  //#endregion
3671
- export { RuntimeMode as $, operationRetrySafety as A, maskSecret as B, ViewSource as C, isBuiltInOperationId as D, OPERATIONS as E, pickArray as F, orderPluginDefinitions as G, RetrySafety as H, pickBoolean as I, resolveRateLimit as J, DEFAULT_BASE_URL as K, pickNumber as L, DEFAULT_RATE_LIMIT_BUCKET as M, createClientRuntime as N, operationBucket as O, isRecord as P, RateLimitPacing as Q, pickObject as R, ViewReason as S, ITD_CATALOG as T, RequestQueuePool as U, redactUrl as V, assertPluginRemovable as W, isRecord$1 as X, LIBRARY_VERSION as Y, requireOptionalBoolean as Z, RealtimeStatus as _, readUnreadCountEvent as a, systemClock as at, ServiceState as b, AccessType as c, FeedTab as d, createDeviceId as et, IncidentKind as f, NotificationType as g, LikesVisibility as h, readNotificationEvent as i, createDeadline as it, BUCKET_LIMITS as j, operationMethod as k, AttachmentType as l, ItdErrorCode as m, formatNotificationText as n, isFile as nt, canonicalNotificationType as o, installAsyncDisposeFallback as ot, InteractionType as p, STATUS_SERVICE as q, normalizeNotification as r, supportsStreamingBody as rt, isKnownNotificationType as s, resolveNotificationUrl as t, isBlob as tt, CommentSort as u, ReportReason as v, WallAccess as w, SpanType as x, ReportTargetType as y, pickString as z };
3737
+ export { DEFAULT_BASE_URL as $, ITD_CATALOG as A, isRecord as B, ReportReason as C, ViewReason as D, SpanType as E, operationMethod as F, pickString as G, pickBoolean as H, operationRetrySafety as I, identityResult as J, RetrySafety as K, BUCKET_LIMITS as L, defineBuiltInOperation as M, isBuiltInOperationId as N, ViewSource as O, operationBucket as P, orderPluginDefinitions as Q, DEFAULT_RATE_LIMIT_BUCKET as R, NotificationType as S, ServiceState as T, pickNumber as U, pickArray as V, pickObject as W, RequestQueuePool as X, mergeService as Y, assertPluginRemovable as Z, FeedTab as _, tokenProvider as a, requireOptionalBoolean as at, ItdErrorCode as b, normalizeNotification as c, systemClock as ct, canonicalNotificationType as d, DEFAULT_STATUS_BASE_URL as et, isKnownNotificationType as f, EventChannelStatus as g, CommentSort as h, bearerToken as i, isRecord$1 as it, OPERATIONS as j, WallAccess as k, readNotificationEvent as l, installAsyncDisposeFallback as lt, AttachmentType as m, formatNotificationText as n, resolveRateLimit as nt, passthroughOperation as o, RateLimitPacing as ot, AccessType as p, defineOperation as q, anonymousAuth as r, LIBRARY_VERSION as rt, voidOperation as s, createDeadline as st, resolveNotificationUrl as t, STATUS_SERVICE as tt, readUnreadCountEvent as u, IncidentKind as v, ReportTargetType as w, LikesVisibility as x, InteractionType as y, createClientRuntime as z };
3672
3738
 
3673
- //# sourceMappingURL=url-CYXgxqGx.js.map
3739
+ //# sourceMappingURL=url-B3ocFlHj.js.map