itd-api 0.7.2 → 0.9.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 (104) hide show
  1. package/README.md +72 -27
  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 +234 -200
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +70 -64
  9. package/dist/index.d.ts +70 -64
  10. package/dist/index.js +202 -179
  11. package/dist/index.js.map +1 -1
  12. package/dist/node/index.cjs +6 -5
  13. package/dist/node/index.cjs.map +1 -1
  14. package/dist/node/index.d.cts +2 -2
  15. package/dist/node/index.d.ts +2 -2
  16. package/dist/node/index.js +5 -4
  17. package/dist/node/index.js.map +1 -1
  18. package/dist/rest/index.cjs +32 -26
  19. package/dist/rest/index.cjs.map +1 -1
  20. package/dist/rest/index.d.cts +9 -7
  21. package/dist/rest/index.d.ts +9 -7
  22. package/dist/rest/index.js +19 -21
  23. package/dist/rest/index.js.map +1 -1
  24. package/dist/shared/{errors-DfU8M5eS.cjs → errors-BmP3TKoW.cjs} +3 -3
  25. package/dist/shared/{errors-DfU8M5eS.cjs.map → errors-BmP3TKoW.cjs.map} +1 -1
  26. package/dist/shared/{errors-Bhrd2fJd.js → errors-GI10kZxk.js} +3 -3
  27. package/dist/shared/{errors-Bhrd2fJd.js.map → errors-GI10kZxk.js.map} +1 -1
  28. package/dist/shared/{websocket-BLR8eVJV.js → events-C5wnTXPT.js} +770 -490
  29. package/dist/shared/events-C5wnTXPT.js.map +1 -0
  30. package/dist/shared/events-CqtkP65d.d.ts +698 -0
  31. package/dist/shared/events-DGAWlCaI.d.cts +698 -0
  32. package/dist/shared/{websocket-C_eI4H2o.cjs → events-FZfnEez0.cjs} +834 -518
  33. package/dist/shared/events-FZfnEez0.cjs.map +1 -0
  34. package/dist/shared/{storage-BPJR_k4-.cjs → key-value-store-B5GYVEYZ.cjs} +2 -86
  35. package/dist/shared/key-value-store-B5GYVEYZ.cjs.map +1 -0
  36. package/dist/shared/{storage-D86edNCB.js → key-value-store-Bt2PSMxY.js} +3 -69
  37. package/dist/shared/key-value-store-Bt2PSMxY.js.map +1 -0
  38. package/dist/shared/key-value-store-COZaNHZS.d.cts +69 -0
  39. package/dist/shared/key-value-store-COZaNHZS.d.ts +69 -0
  40. package/dist/shared/{multi-storage-DccjD7Ww.d.ts → multi-storage-BEL7tcKn.d.cts} +3 -2
  41. package/dist/shared/{multi-storage--yTEqiod.cjs → multi-storage-CDKLa956.cjs} +11 -10
  42. package/dist/shared/{multi-storage--yTEqiod.cjs.map → multi-storage-CDKLa956.cjs.map} +1 -1
  43. package/dist/shared/{multi-storage-CjAPB5Kq.d.cts → multi-storage-LkljOSzA.d.ts} +3 -2
  44. package/dist/shared/{multi-storage-CkvTUC5m.js → multi-storage-SoW05uzO.js} +5 -4
  45. package/dist/shared/{multi-storage-CkvTUC5m.js.map → multi-storage-SoW05uzO.js.map} +1 -1
  46. package/dist/shared/{options-Dg5N3r1V.cjs → options-DOJYtoti.cjs} +8 -6
  47. package/dist/shared/options-DOJYtoti.cjs.map +1 -0
  48. package/dist/shared/{options-DtATYdLr.js → options-DhgGg7Ms.js} +8 -6
  49. package/dist/shared/options-DhgGg7Ms.js.map +1 -0
  50. package/dist/shared/{cookies-DZwFq6kr.cjs → redact-BP00URmQ.cjs} +312 -2
  51. package/dist/shared/redact-BP00URmQ.cjs.map +1 -0
  52. package/dist/shared/{cookies-tX2sNwxb.js → redact-Ba5iSVYZ.js} +235 -3
  53. package/dist/shared/redact-Ba5iSVYZ.js.map +1 -0
  54. package/dist/shared/{render-CgwKdOzu.d.ts → render-B4qis7Sb.d.ts} +775 -313
  55. package/dist/shared/{render-DO0F5YSm.d.cts → render-DDwy2Ame.d.cts} +775 -313
  56. package/dist/shared/{render-mcuELiYi.cjs → render-Dqi8Owqk.cjs} +1464 -662
  57. package/dist/shared/render-Dqi8Owqk.cjs.map +1 -0
  58. package/dist/shared/{render-C6HRPs10.js → render-hiWFh2s4.js} +1366 -618
  59. package/dist/shared/render-hiWFh2s4.js.map +1 -0
  60. package/dist/shared/storage-BQrtKon9.d.cts +86 -0
  61. package/dist/shared/storage-CTRGT5wF.d.ts +86 -0
  62. package/dist/shared/storage-CfHKzVHf.js +71 -0
  63. package/dist/shared/storage-CfHKzVHf.js.map +1 -0
  64. package/dist/shared/storage-QNVxzpDB.cjs +88 -0
  65. package/dist/shared/storage-QNVxzpDB.cjs.map +1 -0
  66. package/dist/shared/{url-BaMCQpYH.cjs → url-Bsb3xE5U.cjs} +577 -466
  67. package/dist/shared/url-Bsb3xE5U.cjs.map +1 -0
  68. package/dist/shared/{url-IU0xN9wX.js → url-DP33mp3s.js} +508 -409
  69. package/dist/shared/url-DP33mp3s.js.map +1 -0
  70. package/dist/shared/{url-DTfZ2toq.d.ts → url-XBsMdAcv.d.cts} +1313 -1200
  71. package/dist/shared/{url-DTfZ2toq.d.cts → url-XBsMdAcv.d.ts} +1313 -1200
  72. package/dist/web/index.cjs +5 -4
  73. package/dist/web/index.cjs.map +1 -1
  74. package/dist/web/index.d.cts +1 -1
  75. package/dist/web/index.d.ts +1 -1
  76. package/dist/web/index.js +3 -2
  77. package/dist/web/index.js.map +1 -1
  78. package/package.json +16 -15
  79. package/dist/realtime/index.cjs +0 -165
  80. package/dist/realtime/index.cjs.map +0 -1
  81. package/dist/realtime/index.d.cts +0 -51
  82. package/dist/realtime/index.d.ts +0 -51
  83. package/dist/realtime/index.js +0 -120
  84. package/dist/realtime/index.js.map +0 -1
  85. package/dist/shared/auth-provider-CG8oCQ9F.cjs +0 -108
  86. package/dist/shared/auth-provider-CG8oCQ9F.cjs.map +0 -1
  87. package/dist/shared/auth-provider-mYqxsSVa.js +0 -91
  88. package/dist/shared/auth-provider-mYqxsSVa.js.map +0 -1
  89. package/dist/shared/cookies-DZwFq6kr.cjs.map +0 -1
  90. package/dist/shared/cookies-tX2sNwxb.js.map +0 -1
  91. package/dist/shared/options-Dg5N3r1V.cjs.map +0 -1
  92. package/dist/shared/options-DtATYdLr.js.map +0 -1
  93. package/dist/shared/render-C6HRPs10.js.map +0 -1
  94. package/dist/shared/render-mcuELiYi.cjs.map +0 -1
  95. package/dist/shared/storage-BPJR_k4-.cjs.map +0 -1
  96. package/dist/shared/storage-C_eICCep.d.cts +0 -152
  97. package/dist/shared/storage-C_eICCep.d.ts +0 -152
  98. package/dist/shared/storage-D86edNCB.js.map +0 -1
  99. package/dist/shared/url-BaMCQpYH.cjs.map +0 -1
  100. package/dist/shared/url-IU0xN9wX.js.map +0 -1
  101. package/dist/shared/websocket-BLR8eVJV.js.map +0 -1
  102. package/dist/shared/websocket-C_eI4H2o.cjs.map +0 -1
  103. package/dist/shared/websocket-DF7XIMiX.d.cts +0 -562
  104. package/dist/shared/websocket-DYKBr8HF.d.ts +0 -562
@@ -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-Ba5iSVYZ.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.2";
88
+ const LIBRARY_VERSION = "0.9.0";
203
89
  //#endregion
204
90
  //#region src/core/config.ts
205
91
  /** Базовый URL API итд.com. Домен записан в punycode: `итд.com`. */
@@ -318,6 +204,10 @@ function requireConcurrency(value, name) {
318
204
  if (!Number.isInteger(value) || value < 1) throw new ItdConfigError(`${name} должен быть целым числом от 1, получено: ${value}`);
319
205
  return value;
320
206
  }
207
+ function requireRps(value, name) {
208
+ if (!Number.isFinite(value) || value <= 0) throw new ItdConfigError(`${name} должен быть положительным числом, получено: ${value}`);
209
+ return value;
210
+ }
321
211
  function resolvePacing(pacing) {
322
212
  const known = Object.values(RateLimitPacing);
323
213
  if (pacing !== void 0 && !known.includes(pacing)) throw new ItdConfigError(`rateLimit.pacing должен быть одним из ${known.join(", ")}, получено: ${pacing}`);
@@ -340,9 +230,11 @@ function resolveBucketOverrides(overrides, bucket, catalog) {
340
230
  assertKnownBucket(name, "rateLimit.bucketOverrides", bucket, catalog);
341
231
  if (override.concurrency !== void 0) requireConcurrency(override.concurrency, `rateLimit.bucketOverrides.${name}.concurrency`);
342
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`);
343
234
  const built = builtIn[name];
344
235
  resolved[name] = Object.freeze({
345
236
  concurrency: override.concurrency ?? built?.concurrency,
237
+ rps: override.rps ?? built?.rps,
346
238
  limit: override.limit ?? built?.limit
347
239
  });
348
240
  }
@@ -375,7 +267,7 @@ function resolveRateLimit(rateLimit, catalog) {
375
267
  };
376
268
  if (!rateLimit) return defaults;
377
269
  const concurrency = requireConcurrency(rateLimit.concurrency ?? 6, "rateLimit.concurrency");
378
- 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");
379
271
  const retryDelays = rateLimit.retryDelays ?? defaults.retryDelays;
380
272
  if (!Array.isArray(retryDelays)) throw new ItdConfigError("rateLimit.retryDelays должен быть массивом чисел");
381
273
  for (const delay of retryDelays) requirePositive(delay, "rateLimit.retryDelays");
@@ -660,7 +552,10 @@ var PluginRegistry = class {
660
552
  };
661
553
  const installed = plugin.install({
662
554
  ...context,
663
- operations: { use: (transformer) => register(transformers, transformer, "operations") },
555
+ operations: {
556
+ get: this.#options.operationMetadata,
557
+ use: (transformer) => register(transformers, transformer, "operations")
558
+ },
664
559
  attempts: { use: (interceptor) => register(interceptors, interceptor, "attempts") }
665
560
  });
666
561
  if (installed !== void 0 && typeof installed !== "function") throw new ItdConfigError(`install() плагина «${plugin.name}» должен вернуть функцию или void`);
@@ -699,8 +594,9 @@ var PluginRegistry = class {
699
594
  this.#removing.add(name);
700
595
  const deadline = createDeadline(this.#options.shutdownTimeout, this.#options.clock);
701
596
  const cleanup = this.#trackCleanup((async () => {
702
- const expired = await this.#release(entry, deadline);
703
- 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}»`);
704
600
  })());
705
601
  try {
706
602
  await cleanup;
@@ -728,8 +624,7 @@ var PluginRegistry = class {
728
624
  const previous = await Promise.allSettled(previousCleanups);
729
625
  for (const result of previous) if (result.status === "rejected") errors.push(result.reason);
730
626
  for (const entry of entries) try {
731
- const expired = await this.#release(entry, deadline);
732
- if (expired) errors.push(expired);
627
+ errors.push(...await this.#release(entry, deadline));
733
628
  } catch (error) {
734
629
  errors.push(error);
735
630
  } finally {
@@ -755,13 +650,29 @@ var PluginRegistry = class {
755
650
  * @param execute выполнение логической операции, вызываемое самым внутренним transformer
756
651
  */
757
652
  async run(request, execute) {
758
- const entries = [...this.#ordered];
653
+ const ordered = this.#ordered;
654
+ if (ordered.length === 0) return execute(request);
655
+ const entries = [...ordered];
759
656
  for (const entry of entries) entry.activeRequests += 1;
760
657
  const interceptorScope = entries.flatMap((entry) => entry.interceptors.map((interceptor) => ({
761
658
  plugin: entry.plugin.name,
762
659
  interceptor
763
660
  })));
764
- 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
+ };
765
676
  const chain = entries.flatMap((entry) => entry.transformers.map((transformer) => ({
766
677
  plugin: entry.plugin.name,
767
678
  transformer
@@ -770,9 +681,13 @@ var PluginRegistry = class {
770
681
  return transformer(scoped(current), (prepared) => {
771
682
  if (called) throw new ItdConfigError(`operation transformer плагина «${plugin}» вызвал next() больше одного раза`);
772
683
  called = true;
684
+ validateContract(prepared);
773
685
  return next(scoped(prepared));
774
686
  });
775
- }, (current) => execute(scoped(current)));
687
+ }, (current) => {
688
+ validateContract(current);
689
+ return execute(scoped(current));
690
+ });
776
691
  try {
777
692
  return await chain(scoped(request));
778
693
  } finally {
@@ -789,14 +704,23 @@ var PluginRegistry = class {
789
704
  /**
790
705
  * Дожидается операций плагина и освобождает его ресурсы.
791
706
  *
792
- * `teardown` выполняется в любом случае, в том числе после истечения срока.
707
+ * Функция очистки запускается после ожидания активных операций. Обе стадии ограничены
708
+ * общим сроком.
793
709
  *
794
- * @returns ошибка истёкшего срока, если ждать пришлось дольше отведённого
710
+ * @returns ошибки истёкшего срока для незавершённых стадий
795
711
  */
796
712
  async #release(entry, deadline) {
797
- const finished = await deadline.wait(this.#waitForDrain(entry));
798
- await entry.teardown?.();
799
- 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;
800
724
  }
801
725
  #waitForDrain(entry) {
802
726
  if (entry.activeRequests === 0) return Promise.resolve();
@@ -821,148 +745,118 @@ function queueAbortError() {
821
745
  function queueStoppedError() {
822
746
  return new ItdAbortError("Клиент закрыт, запрос отменён");
823
747
  }
748
+ /** Длина окна лимита на сервере. */
749
+ const RATE_LIMIT_WINDOW = 6e4;
824
750
  /**
825
- * Очередь запросов: ограничивает одновременность и частоту.
826
- *
827
- * Нужна прежде всего ботам: без неё цикл по сотне постов уходит в API одним залпом
828
- * и упирается в `RATE_LIMIT_EXCEEDED`.
751
+ * Пауза при исчерпании бакета неизвестной ёмкости.
829
752
  *
830
- * Частота выдерживается равномерным разносом стартов (`1000 / rps` между запросами),
831
- * а не окном со счётчиком: так нагрузка ровная, без всплеска в начале каждой секунды.
753
+ * Ёмкость приходит в заголовке вместе с остатком, поэтому случай возможен только у чужого
754
+ * прокси, который прислал `remaining` без `limit`.
755
+ */
756
+ const UNKNOWN_CAPACITY_PAUSE = 1e3;
757
+ /**
758
+ * Единый планировщик одного направления.
832
759
  *
833
- * @internal
760
+ * Он выбирает готовый бакет и только перед фактическим запуском атомарно занимает общий
761
+ * и локальный слоты. Одно ближайшее пробуждение обслуживает все бакеты направления.
834
762
  */
835
- var RequestQueue = class {
763
+ var DestinationScheduler = class {
836
764
  #concurrency;
837
- /** Минимальный промежуток между стартами, мс. `0` — без ограничения частоты. */
838
765
  #minGap;
839
- #onDispatch;
840
- #waiting = [];
766
+ #clock;
767
+ #buckets = [];
841
768
  #active = 0;
842
- /** Момент, раньше которого следующий запрос стартовать не должен. */
843
769
  #nextSlot = 0;
844
- #clock;
770
+ #cursor = 0;
845
771
  #cancelTimer;
846
- constructor(options, clock = systemClock) {
772
+ #wakeupAt;
773
+ constructor(options, clock) {
847
774
  this.#concurrency = options.concurrency;
848
775
  this.#minGap = options.rps ? 1e3 / options.rps : 0;
849
- this.#onDispatch = options.onDispatch;
850
776
  this.#clock = clock;
851
777
  }
852
- /** Сколько задач выполняется прямо сейчас. */
853
- get active() {
854
- return this.#active;
778
+ register(bucket) {
779
+ this.#buckets.push(bucket);
855
780
  }
856
- /** Сколько задач ждёт очереди. */
857
- get pending() {
858
- return this.#waiting.length;
781
+ /** Пересчитывает ближайший запуск после изменения очереди или ограничения. */
782
+ changed() {
783
+ this.#drain();
859
784
  }
860
- /**
861
- * Ставит задачу в очередь.
862
- *
863
- * @returns результат задачи; ошибка задачи пробрасывается без изменений
864
- */
865
- schedule(task, signal) {
866
- if (signal?.aborted) return Promise.reject(queueAbortError());
867
- return new Promise((resolve, reject) => {
868
- let queued;
869
- const detach = () => signal?.removeEventListener("abort", onAbort);
870
- const onAbort = () => {
871
- const index = this.#waiting.indexOf(queued);
872
- if (index < 0) return;
873
- this.#waiting.splice(index, 1);
874
- queued.cancel(queueAbortError());
875
- this.#drain();
876
- };
877
- queued = {
878
- run: () => {
879
- detach();
880
- this.#active += 1;
881
- this.#onDispatch?.();
882
- Promise.resolve().then(task).then(resolve, reject).finally(() => {
883
- this.#active -= 1;
884
- this.#drain();
885
- });
886
- },
887
- cancel: (reason) => {
888
- detach();
889
- reject(reason);
890
- }
891
- };
892
- this.#waiting.push(queued);
893
- signal?.addEventListener("abort", onAbort, { once: true });
894
- if (signal?.aborted) onAbort();
895
- else this.#drain();
896
- });
785
+ /** Перепланирует пробуждение после изменения времени готовности бакета. */
786
+ timingChanged() {
787
+ this.#cancelWakeup();
788
+ this.#drain();
897
789
  }
898
- /**
899
- * Останавливает очередь: снимает отложенную паузу и отклоняет ещё не начатые задачи
900
- * ошибкой `ItdAbortError`. Уже выполняющиеся задачи доводятся до конца.
901
- */
790
+ /** Отклоняет ожидающие задачи, сохраняя паузы и темп для повторного запуска клиента. */
902
791
  stop() {
903
- if (this.#cancelTimer) {
904
- this.#cancelTimer();
905
- this.#cancelTimer = void 0;
906
- }
907
- const pending = this.#waiting.splice(0, this.#waiting.length);
908
- for (const task of pending) task.cancel(queueStoppedError());
792
+ this.#cancelWakeup();
793
+ for (const bucket of this.#buckets) bucket.cancelWaiting(queueStoppedError(), false);
909
794
  }
910
- /** Придерживает очередь на заданное время: ждут все её задачи, а не только одна. */
911
- pause(ms) {
912
- if (ms <= 0) return;
913
- this.#nextSlot = Math.max(this.#nextSlot, this.#clock.now() + ms);
914
- }
915
- /** Запускает столько ожидающих задач, сколько позволяют ограничения. */
916
795
  #drain() {
917
- if (this.#waiting.length === 0) {
918
- if (this.#cancelTimer) {
919
- this.#cancelTimer();
920
- this.#cancelTimer = void 0;
921
- }
922
- return;
923
- }
924
796
  if (this.#active >= this.#concurrency) return;
925
- if (this.#cancelTimer) return;
926
797
  const now = this.#clock.now();
927
- if (this.#nextSlot > now) {
928
- this.#cancelTimer = this.#clock.schedule(() => {
929
- this.#cancelTimer = void 0;
930
- this.#drain();
931
- }, 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();
932
816
  return;
933
817
  }
934
- const next = this.#waiting.shift();
935
- if (!next) return;
818
+ this.#cursor = (selectedIndex + 1) % this.#buckets.length;
819
+ this.#active += 1;
936
820
  if (this.#minGap > 0) this.#nextSlot = now + this.#minGap;
937
- next.run();
821
+ selected.dispatch(now, () => {
822
+ this.#active -= 1;
823
+ this.changed();
824
+ });
938
825
  this.#drain();
939
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
+ }
940
843
  };
941
- /** Длина окна лимита на сервере. */
942
- const RATE_LIMIT_WINDOW = 6e4;
943
- /**
944
- * Пауза при исчерпании бакета неизвестной ёмкости.
945
- *
946
- * Ёмкость приходит в заголовке вместе с остатком, поэтому случай возможен только у чужого
947
- * прокси, который прислал `remaining` без `limit`.
948
- */
949
- const UNKNOWN_CAPACITY_PAUSE = 1e3;
950
844
  /**
951
- * Очередь одного бакета поверх общей очереди направления.
845
+ * Состояние и очередь одного серверного бакета.
952
846
  *
953
- * Задача занимает слот бакета, затем общий слот направления, поэтому суммарная
954
- * одновременность остаётся равной `concurrency`. Пауза бакета удерживает задачу до
955
- * захвата общего слота: притормозивший счётчик не занимает общую ёмкость.
847
+ * Класс хранит локальную конкурентность, темп и серверную квоту, а решение о старте
848
+ * делегирует общему планировщику направления.
956
849
  *
957
850
  * @internal
958
851
  */
959
852
  var BucketQueue = class {
960
853
  #destination;
961
854
  #bucket;
962
- #gate;
963
- #shared;
855
+ #scheduler;
964
856
  #clock;
965
857
  #pacing;
858
+ #concurrency;
859
+ #minGap;
966
860
  /** Ровный темп. Требует раздельных бакетов: без них ёмкость счётчика неизвестна. */
967
861
  #smooth;
968
862
  /**
@@ -979,6 +873,9 @@ var BucketQueue = class {
979
873
  /** Последнее, что сказал сервер. Живёт и в режиме `off` — ради `rateLimitState()`. */
980
874
  #limit;
981
875
  #remaining;
876
+ #active = 0;
877
+ #nextSlot = 0;
878
+ #waiting = [];
982
879
  /**
983
880
  * Оценка остатка для режима `smooth`.
984
881
  *
@@ -987,24 +884,19 @@ var BucketQueue = class {
987
884
  */
988
885
  #tokens = 1;
989
886
  #tokensAt;
990
- /**
991
- * Номер поколения очереди. `stop()` увеличивает его, отсекая задачи, которые уже взяли
992
- * слот бакета, но до общей очереди ещё не дошли.
993
- */
994
- #generation = 0;
995
- constructor(destination, bucket, shared, options, clock, featureDefinition) {
887
+ constructor(destination, bucket, scheduler, options, clock, featureDefinition) {
996
888
  this.#destination = destination;
997
889
  this.#bucket = bucket;
998
- this.#shared = shared;
890
+ this.#scheduler = scheduler;
999
891
  this.#clock = clock;
1000
892
  this.#pacing = options.pacing;
1001
893
  this.#smooth = options.buckets && options.pacing === RateLimitPacing.Smooth;
1002
894
  this.#flatPause = options.buckets ? void 0 : options.retryDelays[0] ?? 0;
1003
895
  this.#seedLimit = options.buckets ? featureDefinition?.limit ?? seedLimit(bucket, options) : void 0;
1004
- this.#gate = new RequestQueue({
1005
- concurrency: options.buckets ? featureDefinition?.concurrency ?? options.bucketOverrides[bucket]?.concurrency ?? options.bucketConcurrency : options.concurrency,
1006
- onDispatch: this.#smooth ? () => this.#spend() : void 0
1007
- }, clock);
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);
1008
900
  }
1009
901
  /** Имя счётчика. При `buckets: false` — всегда `default`, каким бы ни был запрос. */
1010
902
  get bucket() {
@@ -1012,19 +904,63 @@ var BucketQueue = class {
1012
904
  }
1013
905
  /** Запросов бакета прошло в общую очередь и ещё не завершилось. */
1014
906
  get active() {
1015
- return this.#gate.active;
907
+ return this.#active;
1016
908
  }
1017
909
  /** Запросов бакета ждёт своей очереди. */
1018
910
  get pending() {
1019
- return this.#gate.pending;
911
+ return this.#waiting.length;
1020
912
  }
1021
- /** Ставит запрос в очередь: сначала слот бакета, затем общий слот направления. */
913
+ /** Есть ли ожидающая задача, для которой свободен локальный слот. */
914
+ get canStart() {
915
+ return this.#waiting.length > 0 && this.#active < this.#concurrency;
916
+ }
917
+ /** Момент, раньше которого локальное ограничение не разрешает следующий старт. */
918
+ get readyAt() {
919
+ return this.#nextSlot;
920
+ }
921
+ /** Ставит запрос в очередь единого планировщика направления. */
1022
922
  schedule(task, signal) {
1023
- const generation = this.#generation;
1024
- return this.#gate.schedule(() => {
1025
- if (generation !== this.#generation) return Promise.reject(queueStoppedError());
1026
- return this.#shared.schedule(task, signal);
1027
- }, 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
+ });
1028
964
  }
1029
965
  /**
1030
966
  * Учитывает заголовки ответа.
@@ -1040,7 +976,7 @@ var BucketQueue = class {
1040
976
  if (this.#pacing === RateLimitPacing.Off || remaining === void 0) return 0;
1041
977
  if (this.#flatPause !== void 0) {
1042
978
  if (remaining > 0) return 0;
1043
- this.#gate.pause(this.#flatPause);
979
+ this.#hold(this.#flatPause);
1044
980
  return this.#flatPause;
1045
981
  }
1046
982
  const capacity = this.#capacity();
@@ -1048,11 +984,13 @@ var BucketQueue = class {
1048
984
  if (capacity === void 0) return 0;
1049
985
  this.#refill(capacity);
1050
986
  if (remaining < this.#tokens) this.#tokens = remaining;
1051
- return this.#armPause(capacity);
987
+ const wait = this.#armPause(capacity);
988
+ this.#scheduler.timingChanged();
989
+ return wait;
1052
990
  }
1053
991
  if (remaining > 0) return 0;
1054
992
  const wait = capacity === void 0 ? UNKNOWN_CAPACITY_PAUSE : Math.ceil(RATE_LIMIT_WINDOW / Math.max(capacity, 1));
1055
- this.#gate.pause(wait);
993
+ this.#hold(wait);
1056
994
  return wait;
1057
995
  }
1058
996
  /** Придерживает бакет на названное время — путь ответа `429`. Оценка остатка обнуляется. */
@@ -1061,7 +999,7 @@ var BucketQueue = class {
1061
999
  this.#tokens = 0;
1062
1000
  this.#tokensAt = this.#clock.now();
1063
1001
  }
1064
- this.#gate.pause(ms);
1002
+ this.#hold(ms);
1065
1003
  }
1066
1004
  /** Снимок для `rateLimitState()`. */
1067
1005
  state() {
@@ -1070,17 +1008,19 @@ var BucketQueue = class {
1070
1008
  bucket: this.#bucket,
1071
1009
  limit: this.#limit,
1072
1010
  remaining: this.#remaining,
1073
- active: this.#gate.active,
1074
- pending: this.#gate.pending
1011
+ active: this.#active,
1012
+ pending: this.#waiting.length
1075
1013
  };
1076
1014
  }
1077
- /**
1078
- * Останавливает уровень бакета. Общая очередь направления гасится пулом.
1079
- *
1080
- */
1015
+ /** Отклоняет ещё не начатые задачи этого бакета. */
1081
1016
  stop() {
1082
- this.#generation += 1;
1083
- 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();
1084
1024
  }
1085
1025
  /** Лимит бакета: сказанный сервером, иначе табличный. */
1086
1026
  #capacity() {
@@ -1107,9 +1047,15 @@ var BucketQueue = class {
1107
1047
  #armPause(capacity) {
1108
1048
  if (this.#tokens >= 1) return 0;
1109
1049
  const wait = Math.ceil((1 - this.#tokens) * RATE_LIMIT_WINDOW / capacity);
1110
- this.#gate.pause(wait);
1050
+ this.#nextSlot = Math.max(this.#nextSlot, this.#clock.now() + wait);
1111
1051
  return wait;
1112
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
+ }
1113
1059
  };
1114
1060
  /** Лимит бакета до первого ответа: поправка пользователя важнее табличного значения. */
1115
1061
  function seedLimit(bucket, options) {
@@ -1146,11 +1092,12 @@ var RequestQueuePool = class {
1146
1092
  defineBucket(name, definition) {
1147
1093
  const normalized = Object.freeze({
1148
1094
  ...definition.limit === void 0 ? {} : { limit: definition.limit },
1149
- ...definition.concurrency === void 0 ? {} : { concurrency: definition.concurrency }
1095
+ ...definition.concurrency === void 0 ? {} : { concurrency: definition.concurrency },
1096
+ ...definition.rps === void 0 ? {} : { rps: definition.rps }
1150
1097
  });
1151
1098
  const existing = this.#featureBuckets.get(name);
1152
1099
  if (existing) {
1153
- if (existing.definition.limit !== normalized.limit || existing.definition.concurrency !== normalized.concurrency) throw new ItdConfigError(`Бакет feature «${name}» уже зарегистрирован с другими ограничениями`);
1100
+ if (existing.definition.limit !== normalized.limit || existing.definition.concurrency !== normalized.concurrency || existing.definition.rps !== normalized.rps) throw new ItdConfigError(`Бакет feature «${name}» уже зарегистрирован с другими ограничениями`);
1154
1101
  existing.references += 1;
1155
1102
  } else this.#featureBuckets.set(name, {
1156
1103
  definition: normalized,
@@ -1174,14 +1121,14 @@ var RequestQueuePool = class {
1174
1121
  let entry = this.#destinations.get(destination);
1175
1122
  if (!entry) {
1176
1123
  entry = {
1177
- shared: new RequestQueue(this.#options, this.#clock),
1124
+ scheduler: new DestinationScheduler(this.#options, this.#clock),
1178
1125
  buckets: /* @__PURE__ */ new Map()
1179
1126
  };
1180
1127
  this.#destinations.set(destination, entry);
1181
1128
  }
1182
1129
  let queue = entry.buckets.get(name);
1183
1130
  if (!queue) {
1184
- queue = new BucketQueue(destination, name, entry.shared, this.#options, this.#clock, this.#featureBuckets.get(name)?.definition);
1131
+ queue = new BucketQueue(destination, name, entry.scheduler, this.#options, this.#clock, this.#featureBuckets.get(name)?.definition);
1185
1132
  entry.buckets.set(name, queue);
1186
1133
  }
1187
1134
  return queue;
@@ -1193,14 +1140,11 @@ var RequestQueuePool = class {
1193
1140
  return states;
1194
1141
  }
1195
1142
  /**
1196
- * Останавливает оба уровня всех очередей: ожидающие задачи отклоняются, а состояние
1197
- * счётчиков и отложенные паузы сохраняются до следующего запуска — путь `close()`.
1143
+ * Останавливает планировщики: ожидающие задачи отклоняются, а состояние счётчиков
1144
+ * и отложенные паузы сохраняются до следующего запуска — путь `close()`.
1198
1145
  */
1199
1146
  stop() {
1200
- for (const entry of this.#destinations.values()) {
1201
- for (const queue of entry.buckets.values()) queue.stop();
1202
- entry.shared.stop();
1203
- }
1147
+ for (const entry of this.#destinations.values()) entry.scheduler.stop();
1204
1148
  }
1205
1149
  /** Останавливает очереди и забывает всё, что известно о счётчиках, — путь `dispose()`. */
1206
1150
  clear() {
@@ -1392,14 +1336,14 @@ function markDisposeCleanupRequest(request) {
1392
1336
  [DISPOSE_CLEANUP_REQUEST]: true
1393
1337
  };
1394
1338
  }
1395
- /** Разрешено ли запросу завершать внутреннюю очистку после перехода клиента в terminal state. @internal */
1339
+ /** Разрешено ли запросу завершать внутреннюю очистку после `dispose()`. @internal */
1396
1340
  function isDisposeCleanupRequest(request) {
1397
1341
  return request[DISPOSE_CLEANUP_REQUEST] === true;
1398
1342
  }
1399
1343
  /**
1400
1344
  * Собирает слои в один обработчик.
1401
1345
  *
1402
- * Первый слой оказывается самым внешним. Порядок задаётся внутренним client runtime.
1346
+ * Первый слой становится внешним.
1403
1347
  *
1404
1348
  * @example
1405
1349
  * ```ts
@@ -1427,24 +1371,17 @@ function withLayerHeaders(request, headers) {
1427
1371
  /**
1428
1372
  * Точка входа ресурсов в конвейер запросов.
1429
1373
  *
1430
- * Принимает готовый обработчик цепочку слоёв поверх транспорта, собранную
1431
- * во внутреннем runtime клиента, — и отдаёт ресурсам методы `request`/`operation`.
1374
+ * Принимает готовую цепочку обработки и предоставляет ресурсам методы `request`/`execute`.
1432
1375
  * О слоях и их порядке ресурсы не знают.
1433
1376
  */
1434
1377
  var HttpClient = class {
1435
1378
  #handler;
1379
+ #plugins;
1436
1380
  #baseUrl;
1437
- #catalog;
1438
1381
  constructor(deps) {
1439
1382
  this.#handler = deps.handler;
1383
+ this.#plugins = deps.plugins;
1440
1384
  this.#baseUrl = deps.baseUrl;
1441
- this.#catalog = deps.catalog;
1442
- }
1443
- /** Метод операции из каталога. Отсутствие описания — ошибка сборки клиента, не запроса. */
1444
- #methodOf(operationId) {
1445
- const method = this.#catalog.methodOf(operationId);
1446
- if (method === void 0) throw new ItdConfigError(`каталог операций не знает «${operationId}»`);
1447
- return method;
1448
1385
  }
1449
1386
  /** Базовый URL, к которому обращается клиент. */
1450
1387
  get baseUrl() {
@@ -1460,23 +1397,34 @@ var HttpClient = class {
1460
1397
  * @throws {ItdNetworkError} если запрос не дошёл до сервера
1461
1398
  */
1462
1399
  request(options) {
1463
- 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);
1464
1406
  }
1465
- /** Выполняет встроенную семантическую операцию, подставляя её HTTP-метод из каталога. */
1466
- operation(operationId, options) {
1467
- return this.request({
1407
+ #run(operation, options) {
1408
+ const request = identifyRequest({
1468
1409
  ...options,
1469
- operationId,
1470
- method: this.#methodOf(operationId)
1410
+ operationId: operation.id,
1411
+ method: operation.method,
1412
+ retrySafety: options.retrySafety ?? operation.retrySafety
1471
1413
  });
1414
+ return this.#plugins.run(request, async (prepared) => operation.read(await this.#handler(prepared), prepared));
1472
1415
  }
1473
1416
  /** Выполняет внутреннюю операцию финализации после начала `ItdClient.dispose()`. @internal */
1474
- cleanupOperation(operationId, options) {
1475
- return this.request(markDisposeCleanupRequest({
1417
+ cleanupOperation(operation, options) {
1418
+ const request = identifyRequest({
1476
1419
  ...options,
1477
- operationId,
1478
- method: this.#methodOf(operationId)
1479
- }));
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
+ });
1480
1428
  }
1481
1429
  };
1482
1430
  //#endregion
@@ -1501,6 +1449,50 @@ const RetrySafety = Object.freeze({
1501
1449
  /** Повтор может создать ещё один побочный эффект. */
1502
1450
  Unsafe: "unsafe"
1503
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() {}
1504
1496
  //#endregion
1505
1497
  //#region src/core/scheduling/retry.ts
1506
1498
  /** Методы raw-запроса, безопасность которых гарантирована самим HTTP-контрактом. */
@@ -1610,9 +1602,6 @@ function createQueueMiddleware(schedule) {
1610
1602
  * разу, иначе, например, текст поста зашифруется дважды. Здесь же к операции привязывается
1611
1603
  * snapshot attempt interceptors; сами они выполняются транспортом на каждой попытке.
1612
1604
  */
1613
- function createPluginsMiddleware(plugins) {
1614
- return (request, next) => plugins.run(request, next);
1615
- }
1616
1605
  /**
1617
1606
  * Слой сервисов.
1618
1607
  *
@@ -1770,90 +1759,6 @@ function createRetryMiddleware(deps) {
1770
1759
  };
1771
1760
  }
1772
1761
  //#endregion
1773
- //#region src/core/redact.ts
1774
- /** Заголовки, значение которых нельзя писать в лог целиком. */
1775
- const SECRET_HEADERS = /* @__PURE__ */ new Set([
1776
- "authorization",
1777
- "cookie",
1778
- "set-cookie",
1779
- "x-api-key"
1780
- ]);
1781
- /** Поля тела запроса, значение которых нельзя писать в лог. */
1782
- const SECRET_FIELDS = /* @__PURE__ */ new Set([
1783
- "password",
1784
- "oldpassword",
1785
- "newpassword",
1786
- "accesstoken",
1787
- "refreshtoken",
1788
- "currentpassword",
1789
- "flowtoken",
1790
- "token",
1791
- "turnstiletoken",
1792
- "otp"
1793
- ]);
1794
- /** Query-параметры, содержащие секреты. */
1795
- const SECRET_QUERY_PARAMS = /* @__PURE__ */ new Set(["token", "access_token"]);
1796
- /**
1797
- * Прячет середину секрета, оставляя концы для сопоставления.
1798
- *
1799
- * @example
1800
- * ```ts
1801
- * maskSecret('eyJhbGciOiJIUzI1NiJ9.abc'); // 'eyJh…(24)…abc'
1802
- * ```
1803
- */
1804
- function maskSecret(value) {
1805
- if (value.length <= 8) return "…";
1806
- return `${value.slice(0, 4)}…(${value.length})…${value.slice(-3)}`;
1807
- }
1808
- /** Маскирует секретные query-параметры URL перед записью в логи и ошибки. */
1809
- function redactUrl(url) {
1810
- try {
1811
- const parsed = new URL(url);
1812
- const entries = [...parsed.searchParams.entries()];
1813
- parsed.search = "";
1814
- for (const [name, value] of entries) parsed.searchParams.append(name, SECRET_QUERY_PARAMS.has(name.toLowerCase()) ? maskSecret(value) : value);
1815
- return parsed.toString();
1816
- } catch {
1817
- return url.replace(/([?&](?:token|access_token)=)[^&#]*/gi, "$1[скрыто]");
1818
- }
1819
- }
1820
- /**
1821
- * Готовит заголовки к записи в лог.
1822
- *
1823
- * `Authorization` и `Cookie` маскируются: логи часто уезжают в системы сбора,
1824
- * и токен доступа в них попадать не должен.
1825
- */
1826
- function redactHeaders(headers) {
1827
- const result = {};
1828
- headers.forEach((value, name) => {
1829
- if (SECRET_HEADERS.has(name.toLowerCase())) {
1830
- const spaceAt = value.indexOf(" ");
1831
- result[name] = spaceAt > 0 ? `${value.slice(0, spaceAt)} ${maskSecret(value.slice(spaceAt + 1))}` : maskSecret(value);
1832
- return;
1833
- }
1834
- result[name] = value;
1835
- });
1836
- return result;
1837
- }
1838
- /**
1839
- * Готовит тело запроса к записи в лог: пароли, токены и коды OTP заменяются заглушкой.
1840
- *
1841
- * Обходит вложенные объекты и массивы. `FormData` и бинарные тела не раскрываются вовсе.
1842
- */
1843
- function redactBody(body) {
1844
- if (body === null || body === void 0) return body;
1845
- if (typeof FormData !== "undefined" && body instanceof FormData) return "[FormData]";
1846
- if (isBlob(body)) return "[Blob]";
1847
- if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return "[binary]";
1848
- if (Array.isArray(body)) return body.map(redactBody);
1849
- if (typeof body === "object") {
1850
- const result = {};
1851
- for (const [key, value] of Object.entries(body)) result[key] = SECRET_FIELDS.has(key.toLowerCase()) ? "[скрыто]" : redactBody(value);
1852
- return result;
1853
- }
1854
- return body;
1855
- }
1856
- //#endregion
1857
1762
  //#region src/core/unwrap.ts
1858
1763
  /**
1859
1764
  * Снимает внешнюю обёртку `{ data: … }`.
@@ -2489,7 +2394,7 @@ var Transport = class {
2489
2394
  };
2490
2395
  //#endregion
2491
2396
  //#region src/core/execution/client-runtime.ts
2492
- /** Имена стадий основного request pipeline в порядке выполнения. @internal */
2397
+ /** Имена стадий обработки запроса в порядке выполнения. @internal */
2493
2398
  const ClientRuntimeStage = Object.freeze({
2494
2399
  OperationPlugins: "operation_plugins",
2495
2400
  Services: "services",
@@ -2507,14 +2412,15 @@ function createServiceRegistry(config) {
2507
2412
  for (const service of config.services) services.define(service);
2508
2413
  return services;
2509
2414
  }
2510
- /** Собирает внутренний runtime клиента и единственный request pipeline. @internal */
2415
+ /** Собирает внутренние компоненты и цепочку запросов клиента. @internal */
2511
2416
  function createClientRuntime(options, internals) {
2512
2417
  const catalog = internals.catalog;
2513
2418
  const config = resolveRuntimeConfig(options, catalog);
2514
2419
  const jar = new CookieJar();
2515
2420
  const plugins = new PluginRegistry({
2516
2421
  shutdownTimeout: config.shutdownTimeout,
2517
- clock: config.clock
2422
+ clock: config.clock,
2423
+ operationMetadata: (operationId) => catalog.definitionOf(operationId)
2518
2424
  });
2519
2425
  const services = createServiceRegistry(config);
2520
2426
  const lifetime = new AbortController();
@@ -2563,10 +2469,6 @@ function createClientRuntime(options, internals) {
2563
2469
  lifetimeSignal: lifetime.signal
2564
2470
  });
2565
2471
  const stages = [
2566
- {
2567
- name: ClientRuntimeStage.OperationPlugins,
2568
- middleware: createPluginsMiddleware(plugins)
2569
- },
2570
2472
  {
2571
2473
  name: ClientRuntimeStage.Services,
2572
2474
  middleware: createServicesMiddleware(services)
@@ -2618,17 +2520,21 @@ function createClientRuntime(options, internals) {
2618
2520
  }
2619
2521
  return handler(request);
2620
2522
  };
2523
+ const http = new HttpClient({
2524
+ handler: clientHandler,
2525
+ plugins,
2526
+ baseUrl: config.baseUrl
2527
+ });
2621
2528
  auth = internals.auth({
2622
2529
  config,
2623
- handler: clientHandler,
2530
+ http,
2624
2531
  cookies: jar
2625
2532
  });
2626
- const http = new HttpClient({
2627
- handler: clientHandler,
2628
- baseUrl: config.baseUrl,
2629
- catalog
2630
- });
2631
- 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
+ ]);
2632
2538
  return {
2633
2539
  config,
2634
2540
  http,
@@ -2637,6 +2543,24 @@ function createClientRuntime(options, internals) {
2637
2543
  plugins,
2638
2544
  services,
2639
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
+ },
2640
2564
  platformHeaders: (url) => transport.platformHeaders(url),
2641
2565
  rateLimitState: () => queues?.states() ?? [],
2642
2566
  registerRateLimitBucket: (name, definition) => queues?.defineBucket(name, definition),
@@ -2684,7 +2608,16 @@ const BUCKET_LIMITS = Object.freeze({
2684
2608
  "posts.create": 5,
2685
2609
  "users.updateMe": 3,
2686
2610
  "reports.create": 3,
2687
- "verification.submit": 3
2611
+ "verification.submit": 3,
2612
+ shop: 150,
2613
+ "shop.delivery.cities": 60,
2614
+ "shop.delivery.calculate": 45,
2615
+ "shop.delivery.points": 30,
2616
+ "shop.consents.record": 15,
2617
+ "shop.orders.pay": 13,
2618
+ "shop.orders.verifyAccessCode": 13,
2619
+ "shop.orders.create": 12,
2620
+ "shop.orders.requestAccessCode": 4
2688
2621
  });
2689
2622
  /** Счётчик, из которого списывается путь без собственного правила на сервере. */
2690
2623
  const DEFAULT_RATE_LIMIT_BUCKET = "default";
@@ -3039,12 +2972,12 @@ const OPERATIONS = freezeOperations({
3039
2972
  method: "PUT",
3040
2973
  retrySafety: RetrySafety.Idempotent
3041
2974
  },
3042
- "realtime.poll.updates": {
2975
+ "events.notifications.poll.updates": {
3043
2976
  method: "GET",
3044
2977
  retrySafety: RetrySafety.Safe,
3045
2978
  bucket: "notifications"
3046
2979
  },
3047
- "realtime.poll.unread": {
2980
+ "events.notifications.poll.unread": {
3048
2981
  method: "GET",
3049
2982
  retrySafety: RetrySafety.Safe,
3050
2983
  bucket: "notifications"
@@ -3135,8 +3068,75 @@ const OPERATIONS = freezeOperations({
3135
3068
  "telemetry.interaction": {
3136
3069
  method: "POST",
3137
3070
  retrySafety: RetrySafety.Unsafe
3071
+ },
3072
+ "shop.products.list": {
3073
+ method: "GET",
3074
+ retrySafety: RetrySafety.Safe,
3075
+ bucket: "shop"
3076
+ },
3077
+ "shop.products.get": {
3078
+ method: "GET",
3079
+ retrySafety: RetrySafety.Safe,
3080
+ bucket: "shop"
3081
+ },
3082
+ "shop.delivery.countries": {
3083
+ method: "GET",
3084
+ retrySafety: RetrySafety.Safe,
3085
+ bucket: "shop"
3086
+ },
3087
+ "shop.delivery.cities": {
3088
+ method: "GET",
3089
+ retrySafety: RetrySafety.Safe,
3090
+ bucket: "shop.delivery.cities"
3091
+ },
3092
+ "shop.delivery.points": {
3093
+ method: "GET",
3094
+ retrySafety: RetrySafety.Safe,
3095
+ bucket: "shop.delivery.points"
3096
+ },
3097
+ "shop.delivery.calculate": {
3098
+ method: "POST",
3099
+ retrySafety: RetrySafety.Safe,
3100
+ bucket: "shop.delivery.calculate"
3101
+ },
3102
+ "shop.orders.create": {
3103
+ method: "POST",
3104
+ retrySafety: RetrySafety.Idempotent,
3105
+ bucket: "shop.orders.create"
3106
+ },
3107
+ "shop.orders.list": {
3108
+ method: "GET",
3109
+ retrySafety: RetrySafety.Safe
3110
+ },
3111
+ "shop.orders.get": {
3112
+ method: "GET",
3113
+ retrySafety: RetrySafety.Safe
3114
+ },
3115
+ "shop.orders.pay": {
3116
+ method: "POST",
3117
+ retrySafety: RetrySafety.Unsafe,
3118
+ bucket: "shop.orders.pay"
3119
+ },
3120
+ "shop.orders.requestAccessCode": {
3121
+ method: "POST",
3122
+ retrySafety: RetrySafety.Unsafe,
3123
+ bucket: "shop.orders.requestAccessCode"
3124
+ },
3125
+ "shop.orders.verifyAccessCode": {
3126
+ method: "POST",
3127
+ retrySafety: RetrySafety.Unsafe,
3128
+ bucket: "shop.orders.verifyAccessCode"
3129
+ },
3130
+ "shop.consents.record": {
3131
+ method: "POST",
3132
+ retrySafety: RetrySafety.Unsafe,
3133
+ bucket: "shop.consents.record"
3138
3134
  }
3139
3135
  });
3136
+ /** Создаёт контракт результата встроенной операции с проверяемым ID. */
3137
+ function defineBuiltInOperation(id, read = identityResult) {
3138
+ return defineOperation(id, OPERATIONS[id], read);
3139
+ }
3140
3140
  const BUILT_IN_IDS = new Set(Object.keys(OPERATIONS));
3141
3141
  /** Проверяет принадлежность ID встроенному каталогу. */
3142
3142
  function isBuiltInOperationId(value) {
@@ -3171,6 +3171,7 @@ function operationBucket(id) {
3171
3171
  * @internal
3172
3172
  */
3173
3173
  const ITD_CATALOG = Object.freeze({
3174
+ definitionOf: (id) => isBuiltInOperationId(id) ? OPERATIONS[id] : void 0,
3174
3175
  retrySafetyOf: (id) => isBuiltInOperationId(id) ? operationRetrySafety(id) : void 0,
3175
3176
  methodOf: (id) => isBuiltInOperationId(id) ? operationMethod(id) : void 0,
3176
3177
  bucketOf: (id) => operationBucket(id),
@@ -3257,8 +3258,8 @@ const ReportReason = Object.freeze({
3257
3258
  Fraud: "fraud",
3258
3259
  Other: "other"
3259
3260
  });
3260
- /** Состояние realtime-соединения. */
3261
- const RealtimeStatus = Object.freeze({
3261
+ /** Состояние соединения событийного канала. */
3262
+ const EventChannelStatus = Object.freeze({
3262
3263
  Connecting: "connecting",
3263
3264
  Connected: "connected",
3264
3265
  Error: "error",
@@ -3427,6 +3428,7 @@ const ItdErrorCode = Object.freeze({
3427
3428
  SESSION_NOT_FOUND: "SESSION_NOT_FOUND",
3428
3429
  MISSING_FLOW_TOKEN: "MISSING_FLOW_TOKEN",
3429
3430
  PROFILE_USERNAME_TAKEN: "PROFILE_USERNAME_TAKEN",
3431
+ PROFILE_USERNAME_RESERVED: "PROFILE_USERNAME_RESERVED",
3430
3432
  PROFILE_RESTRICTION_ACTIVE: "PROFILE_RESTRICTION_ACTIVE",
3431
3433
  PROFILE_MODIFICATION_RESTRICTED: "PROFILE_MODIFICATION_RESTRICTED",
3432
3434
  CONTENT_MODERATION_FAILED: "CONTENT_MODERATION_FAILED",
@@ -3577,6 +3579,103 @@ function readUnreadCountEvent(data) {
3577
3579
  return typeof payload.count === "number" ? payload.count : void 0;
3578
3580
  }
3579
3581
  //#endregion
3582
+ //#region src/operations/common.ts
3583
+ /** Контракт встроенной операции без дополнительного преобразования результата. */
3584
+ function passthroughOperation(id) {
3585
+ return defineBuiltInOperation(id);
3586
+ }
3587
+ /** Контракт встроенной операции без возвращаемого значения. */
3588
+ function voidOperation(id) {
3589
+ return defineBuiltInOperation(id, voidResult);
3590
+ }
3591
+ //#endregion
3592
+ //#region src/core/auth-provider.ts
3593
+ /**
3594
+ * Идентификатор устройства, общий для провайдеров без сессии.
3595
+ *
3596
+ * Значение своё у каждого провайдера: сервер различает по нему записи в списке сессий,
3597
+ * поэтому делить его между клиентами нельзя. Хранилища нет, так что перезапуск процесса
3598
+ * значение не переживает — в этом и разница с полноценной сессией.
3599
+ */
3600
+ function localDeviceId() {
3601
+ let deviceId;
3602
+ return () => {
3603
+ deviceId ??= createDeviceId();
3604
+ return Promise.resolve(deviceId);
3605
+ };
3606
+ }
3607
+ /**
3608
+ * Авторизации нет: заголовок не подставляется, ответ `401` не восстанавливается.
3609
+ *
3610
+ * @example
3611
+ * ```ts
3612
+ * const api = createRestClient(); // публичные эндпоинты доступны и без токена
3613
+ * ```
3614
+ */
3615
+ function anonymousAuth() {
3616
+ return {
3617
+ token: () => Promise.resolve(null),
3618
+ prepare: () => Promise.resolve(),
3619
+ currentHeaders: () => ({}),
3620
+ recover: () => Promise.resolve(false),
3621
+ deviceId: localDeviceId(),
3622
+ dispose: () => {}
3623
+ };
3624
+ }
3625
+ /**
3626
+ * Готовый Bearer-токен: ни хранилища, ни продления.
3627
+ *
3628
+ * Ответ `401` уходит вызывающему коду как есть — обновить токен провайдеру нечем.
3629
+ * Для сессии, которая продлевает себя сама, нужен полный клиент.
3630
+ *
3631
+ * @example
3632
+ * ```ts
3633
+ * const api = createRestClient({ auth: bearerToken(process.env.ITD_TOKEN) });
3634
+ * ```
3635
+ */
3636
+ function bearerToken(accessToken) {
3637
+ const headers = { Authorization: `Bearer ${accessToken}` };
3638
+ return {
3639
+ token: () => Promise.resolve(accessToken),
3640
+ prepare: () => Promise.resolve(),
3641
+ currentHeaders: () => headers,
3642
+ recover: () => Promise.resolve(false),
3643
+ deviceId: localDeviceId(),
3644
+ dispose: () => {}
3645
+ };
3646
+ }
3647
+ /**
3648
+ * Токен из внешнего источника — хранилища секретов, кэша, соседнего сервиса.
3649
+ *
3650
+ * Источник спрашивается на стадии подготовки, до входа в очередь: там ожидание безопасно,
3651
+ * а слот транспорта ещё не занят. Значение держится до следующей подготовки, потому что
3652
+ * подстановка заголовков обязана быть синхронной.
3653
+ *
3654
+ * @example
3655
+ * ```ts
3656
+ * const api = createRestClient({ auth: tokenProvider(() => vault.read('itd')) });
3657
+ * ```
3658
+ */
3659
+ function tokenProvider(getToken) {
3660
+ let cached = null;
3661
+ const read = async () => {
3662
+ cached = await getToken();
3663
+ return cached;
3664
+ };
3665
+ return {
3666
+ token: read,
3667
+ prepare: async () => {
3668
+ await read();
3669
+ },
3670
+ currentHeaders: () => cached ? { Authorization: `Bearer ${cached}` } : {},
3671
+ recover: () => Promise.resolve(false),
3672
+ deviceId: localDeviceId(),
3673
+ dispose: () => {
3674
+ cached = null;
3675
+ }
3676
+ };
3677
+ }
3678
+ //#endregion
3580
3679
  //#region src/notifications/text.ts
3581
3680
  /** Имя, которое подставляется, если участник неизвестен. */
3582
3681
  const UNKNOWN_ACTOR = "Пользователь";
@@ -3708,6 +3807,6 @@ function resolveNotificationUrl(notification) {
3708
3807
  return clickUrl || "/notifications";
3709
3808
  }
3710
3809
  //#endregion
3711
- export { requireOptionalBoolean as $, operationRetrySafety as A, maskSecret as B, ViewSource as C, isBuiltInOperationId as D, OPERATIONS as E, pickArray as F, assertPluginRemovable as G, RetrySafety as H, pickBoolean as I, DEFAULT_STATUS_BASE_URL as J, orderPluginDefinitions as K, pickNumber as L, DEFAULT_RATE_LIMIT_BUCKET as M, createClientRuntime as N, operationBucket as O, isRecord as P, isRecord$1 as Q, pickObject as R, ViewReason as S, ITD_CATALOG as T, mergeService as U, redactUrl as V, RequestQueuePool as W, resolveRateLimit as X, STATUS_SERVICE as Y, LIBRARY_VERSION as Z, RealtimeStatus as _, readUnreadCountEvent as a, supportsStreamingBody as at, ServiceState as b, AccessType as c, installAsyncDisposeFallback as ct, FeedTab as d, RateLimitPacing as et, IncidentKind as f, NotificationType as g, LikesVisibility as h, readNotificationEvent as i, isFile as it, BUCKET_LIMITS as j, operationMethod as k, AttachmentType as l, ItdErrorCode as m, formatNotificationText as n, createDeviceId as nt, canonicalNotificationType as o, createDeadline as ot, InteractionType as p, DEFAULT_BASE_URL as q, normalizeNotification as r, isBlob as rt, isKnownNotificationType as s, systemClock as st, resolveNotificationUrl as t, RuntimeMode as tt, CommentSort as u, ReportReason as v, WallAccess as w, SpanType as x, ReportTargetType as y, pickString as z };
3810
+ export { orderPluginDefinitions 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, assertPluginRemovable as Q, DEFAULT_RATE_LIMIT_BUCKET as R, NotificationType as S, ServiceState as T, pickNumber as U, pickArray as V, pickObject as W, mergeService as X, voidResult as Y, RequestQueuePool as Z, FeedTab as _, tokenProvider as a, isRecord$1 as at, ItdErrorCode as b, normalizeNotification as c, createDeadline as ct, canonicalNotificationType as d, DEFAULT_BASE_URL as et, isKnownNotificationType as f, EventChannelStatus as g, CommentSort as h, bearerToken as i, LIBRARY_VERSION as it, OPERATIONS as j, WallAccess as k, readNotificationEvent as l, systemClock as lt, AttachmentType as m, formatNotificationText as n, STATUS_SERVICE as nt, passthroughOperation as o, requireOptionalBoolean as ot, AccessType as p, defineOperation as q, anonymousAuth as r, resolveRateLimit as rt, voidOperation as s, RateLimitPacing as st, resolveNotificationUrl as t, DEFAULT_STATUS_BASE_URL as tt, readUnreadCountEvent as u, installAsyncDisposeFallback as ut, IncidentKind as v, ReportTargetType as w, LikesVisibility as x, InteractionType as y, createClientRuntime as z };
3712
3811
 
3713
- //# sourceMappingURL=url-IU0xN9wX.js.map
3812
+ //# sourceMappingURL=url-DP33mp3s.js.map