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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-value-store-B5GYVEYZ.cjs","names":["ItdConfigError","#values"],"sources":["../../src/core/key-value-store.ts"],"sourcesContent":["import { ItdConfigError } from './errors.js';\n\n/** Синхронный или асинхронный результат операции хранилища. */\nexport type KeyValueStoreResult<T> = T | Promise<T>;\n\n/** Синхронный или асинхронный источник ключей. */\nexport type KeyValueStoreKeys = Iterable<string> | AsyncIterable<string>;\n\n/**\n * Минимальный backend именованных значений.\n *\n * Контракт ничего не знает о сессиях, сериализации или конкретной базе данных. Значение\n * `undefined` означает отсутствие ключа; хранить `undefined` как самостоятельное значение нельзя.\n */\nexport interface KeyValueStore<T> {\n /** Читает значение либо возвращает `undefined`, если ключ отсутствует. */\n get(key: string): KeyValueStoreResult<T | undefined>;\n /** Полностью заменяет значение ключа. */\n set(key: string, value: T): KeyValueStoreResult<void>;\n /** Удаляет ключ. Отсутствующий ключ не должен считаться ошибкой. */\n delete(key: string): KeyValueStoreResult<void>;\n /** Перечисляет ключи, при возможности ограничивая их префиксом. */\n keys?(prefix?: string): KeyValueStoreResult<KeyValueStoreKeys>;\n}\n\n/** Backend, который гарантированно умеет перечислять ключи. */\nexport interface EnumerableKeyValueStore<T> extends KeyValueStore<T> {\n keys(prefix?: string): KeyValueStoreResult<KeyValueStoreKeys>;\n}\n\n/** Кодирует значения для хранения в backend другого типа. */\nexport interface KeyValueCodec<T, Stored> {\n /** Преобразует доменное значение перед записью. */\n encode(value: T): KeyValueStoreResult<Stored>;\n /** Восстанавливает доменное значение после чтения. */\n decode(value: Stored): KeyValueStoreResult<T>;\n}\n\n/** Источник одной записи, содержащей всю карту key-value. */\nexport interface RecordKeyValueStoreSource<T> {\n /** Читает всю карту. `undefined` означает отсутствие записи. */\n read(): KeyValueStoreResult<Readonly<Record<string, T>> | undefined>;\n /** Записывает стабильный снимок всей карты. */\n write(record: Readonly<Record<string, T>>): KeyValueStoreResult<void>;\n /** Удаляет запись, когда карта становится пустой. */\n delete?(): KeyValueStoreResult<void>;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction requireStore(value: unknown): asserts value is KeyValueStore<unknown> {\n const store = value as KeyValueStore<unknown>;\n if (!isRecord(store)) throw new ItdConfigError('KeyValueStore должен быть объектом');\n for (const method of ['get', 'set', 'delete'] as const) {\n if (typeof store[method] !== 'function') {\n throw new ItdConfigError(`KeyValueStore.${method} должен быть функцией`);\n }\n }\n if (store.keys !== undefined && typeof store.keys !== 'function') {\n throw new ItdConfigError('KeyValueStore.keys должен быть функцией');\n }\n}\n\nexport function createKeyValueStore<T>(\n handlers: EnumerableKeyValueStore<T>,\n): EnumerableKeyValueStore<T>;\nexport function createKeyValueStore<T>(handlers: KeyValueStore<T>): KeyValueStore<T>;\n/** Собирает {@link KeyValueStore} из функций конкретного backend. */\nexport function createKeyValueStore<T>(handlers: KeyValueStore<T>): KeyValueStore<T> {\n requireStore(handlers);\n return handlers;\n}\n\n/** Проверяет доступность перечисления ключей. */\nexport function isEnumerableKeyValueStore<T>(\n store: KeyValueStore<T>,\n): store is EnumerableKeyValueStore<T> {\n return typeof store.keys === 'function';\n}\n\n/** @internal */\nexport async function collectKeyValueStoreKeys<T>(\n store: EnumerableKeyValueStore<T>,\n prefix?: string,\n): Promise<string[]> {\n const source = await store.keys(prefix);\n const result: string[] = [];\n for await (const key of source) {\n if (typeof key !== 'string') {\n throw new ItdConfigError('KeyValueStore.keys() должен возвращать только строки');\n }\n if (prefix === undefined || key.startsWith(prefix)) result.push(key);\n }\n return result;\n}\n\n/** Key-value backend в памяти процесса. Значения сохраняются по ссылке. */\nexport class MemoryKeyValueStore<T> implements EnumerableKeyValueStore<T> {\n readonly #values = new Map<string, T>();\n\n constructor(initial?: Iterable<readonly [string, T]>) {\n for (const [key, value] of initial ?? []) this.#values.set(key, value);\n }\n\n get(key: string): T | undefined {\n return this.#values.get(key);\n }\n\n set(key: string, value: T): void {\n this.#values.set(key, value);\n }\n\n delete(key: string): void {\n this.#values.delete(key);\n }\n\n keys(prefix = ''): string[] {\n return [...this.#values.keys()].filter((key) => key.startsWith(prefix));\n }\n}\n\nexport function withNamespace<T>(\n store: EnumerableKeyValueStore<T>,\n namespace: string,\n separator?: string,\n): EnumerableKeyValueStore<T>;\nexport function withNamespace<T>(\n store: KeyValueStore<T>,\n namespace: string,\n separator?: string,\n): KeyValueStore<T>;\n/** Добавляет namespace ко всем ключам, не меняя исходный backend. */\nexport function withNamespace<T>(\n store: KeyValueStore<T>,\n namespace: string,\n separator = ':',\n): KeyValueStore<T> {\n requireStore(store);\n if (typeof namespace !== 'string' || typeof separator !== 'string') {\n throw new ItdConfigError('namespace и separator KeyValueStore должны быть строками');\n }\n const base = namespace.length === 0 ? '' : `${namespace}${separator}`;\n const key = (value: string) => `${base}${value}`;\n const namespaced: KeyValueStore<T> = {\n get: (value) => store.get(key(value)),\n set: (value, data) => store.set(key(value), data),\n delete: (value) => store.delete(key(value)),\n };\n\n if (isEnumerableKeyValueStore(store)) {\n namespaced.keys = async (prefix = '') => {\n const keys = await collectKeyValueStoreKeys(store, key(prefix));\n return keys.map((value) => value.slice(base.length));\n };\n }\n return namespaced;\n}\n\nexport function withCodec<T, Stored>(\n store: EnumerableKeyValueStore<Stored>,\n codec: KeyValueCodec<T, Stored>,\n): EnumerableKeyValueStore<T>;\nexport function withCodec<T, Stored>(\n store: KeyValueStore<Stored>,\n codec: KeyValueCodec<T, Stored>,\n): KeyValueStore<T>;\n/** Преобразует значения на границе backend, сохраняя его пространство ключей. */\nexport function withCodec<T, Stored>(\n store: KeyValueStore<Stored>,\n codec: KeyValueCodec<T, Stored>,\n): KeyValueStore<T> {\n requireStore(store);\n if (\n !isRecord(codec) ||\n typeof codec.encode !== 'function' ||\n typeof codec.decode !== 'function'\n ) {\n throw new ItdConfigError('codec должен содержать функции encode и decode');\n }\n const encoded: KeyValueStore<T> = {\n async get(key) {\n const value = await store.get(key);\n return value === undefined ? undefined : codec.decode(value);\n },\n async set(key, value) {\n await store.set(key, await codec.encode(value));\n },\n delete: (key) => store.delete(key),\n };\n if (isEnumerableKeyValueStore(store)) encoded.keys = (prefix) => store.keys(prefix);\n return encoded;\n}\n\n/** Создаёт запись без прототипа, чтобы любые пользовательские ключи оставались данными. */\nfunction emptyRecord<T>(): Record<string, T> {\n return Object.create(null) as Record<string, T>;\n}\n\nfunction copyRecord<T>(record: Readonly<Record<string, T>> | undefined): Record<string, T> {\n const copy = emptyRecord<T>();\n for (const [key, value] of Object.entries(record ?? {})) copy[key] = value;\n return copy;\n}\n\n/**\n * Создаёт key-value backend поверх одной общей записи.\n *\n * Чтение выполняется один раз, а записи выстраиваются последовательно. Изменение становится\n * видимым чтениям только после подтверждения source. Несколько экземпляров или процессов\n * требуют внешней синхронизации со стороны source.\n */\nexport function createRecordKeyValueStore<T>(\n source: RecordKeyValueStoreSource<T>,\n): EnumerableKeyValueStore<T> {\n if (\n !isRecord(source) ||\n typeof source.read !== 'function' ||\n typeof source.write !== 'function'\n ) {\n throw new ItdConfigError('RecordKeyValueStoreSource должен содержать read и write');\n }\n if (source.delete !== undefined && typeof source.delete !== 'function') {\n throw new ItdConfigError('RecordKeyValueStoreSource.delete должен быть функцией');\n }\n\n let committed: Record<string, T> | undefined;\n let loading: Promise<Record<string, T>> | undefined;\n let writes: Promise<void> = Promise.resolve();\n\n const load = async (): Promise<Record<string, T>> => {\n if (committed !== undefined) return committed;\n loading ??= Promise.resolve(source.read())\n .then((value) => {\n if (value !== undefined && !isRecord(value)) {\n throw new ItdConfigError('RecordKeyValueStoreSource.read() должен вернуть объект');\n }\n committed = copyRecord(value as Readonly<Record<string, T>> | undefined);\n return committed;\n })\n .finally(() => {\n loading = undefined;\n });\n return loading;\n };\n\n /**\n * Записывает изменение и фиксирует его только после подтверждения source.\n *\n * Черновик строится внутри очереди записей, от текущего зафиксированного состояния.\n *\n * @param apply вносит изменение в черновик; `false` — менять нечего\n */\n const mutate = (apply: (draft: Record<string, T>) => boolean): Promise<void> => {\n const run = async (): Promise<void> => {\n const draft = copyRecord(await load());\n if (!apply(draft)) return;\n\n if (source.delete && Object.keys(draft).length === 0) await source.delete();\n else await source.write(draft);\n committed = draft;\n };\n writes = writes.then(run, run);\n return writes;\n };\n\n return {\n async get(key) {\n const current = await load();\n return Object.hasOwn(current, key) ? current[key] : undefined;\n },\n set(key, value) {\n return mutate((draft) => {\n draft[key] = value;\n return true;\n });\n },\n delete(key) {\n return mutate((draft) => {\n if (!Object.hasOwn(draft, key)) return false;\n delete draft[key];\n return true;\n });\n },\n async keys(prefix = '') {\n const current = await load();\n return Object.keys(current).filter((key) => key.startsWith(prefix));\n },\n };\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { a as ItdConfigError } from "./errors-Bhrd2fJd.js";
1
+ import { a as ItdConfigError } from "./errors-GI10kZxk.js";
2
2
  //#region src/core/key-value-store.ts
3
3
  function isRecord(value) {
4
4
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -160,72 +160,6 @@ function createRecordKeyValueStore(source) {
160
160
  };
161
161
  }
162
162
  //#endregion
163
- //#region src/session/storage.ts
164
- /**
165
- * Создаёт независимый снимок сессии.
166
- *
167
- * Сессии пересекают границу пользовательского кода и внутреннего состояния клиента.
168
- * Возвращать или сохранять их по ссылке нельзя: последующая мутация объекта или массива
169
- * cookie меняла бы уже сохранённую сессию без вызова `set()`.
170
- *
171
- * @internal
172
- */
173
- function copySession(session) {
174
- return {
175
- ...session,
176
- ...session.cookies ? { cookies: [...session.cookies] } : {}
177
- };
178
- }
179
- /**
180
- * Хранилище в памяти процесса — вариант по умолчанию.
181
- *
182
- * Сессия теряется при перезапуске. Для долгоживущих ботов возьмите `FileTokenStorage`
183
- * из `itd-api/node`, для браузера — `LocalStorageTokenStorage` или
184
- * `SessionStorageTokenStorage` из `itd-api/web`.
185
- */
186
- var MemoryTokenStorage = class {
187
- #store = new MemoryKeyValueStore();
188
- constructor(initial) {
189
- if (initial) this.#store.set(TOKEN_STORAGE_KEY, copySession(initial));
190
- }
191
- get() {
192
- const session = this.#store.get(TOKEN_STORAGE_KEY);
193
- return session ? copySession(session) : null;
194
- }
195
- set(session) {
196
- this.#store.set(TOKEN_STORAGE_KEY, copySession(session));
197
- }
198
- clear() {
199
- this.#store.delete(TOKEN_STORAGE_KEY);
200
- }
201
- };
202
- const TOKEN_STORAGE_KEY = "session";
203
- function isPromise(value) {
204
- return typeof value === "object" && value !== null && typeof value.then === "function";
205
- }
206
- /**
207
- * Создаёт доменное хранилище сессии поверх общего {@link KeyValueStore}.
208
- *
209
- * @example
210
- * ```ts
211
- * const storage = createTokenStorage(withNamespace(redisStore, 'itd'));
212
- * ```
213
- */
214
- function createTokenStorage(store, options = {}) {
215
- const key = options.key ?? TOKEN_STORAGE_KEY;
216
- if (typeof key !== "string") throw new ItdConfigError("key TokenStorage должен быть строкой");
217
- const backend = createKeyValueStore(store);
218
- return {
219
- get() {
220
- const value = backend.get(key);
221
- if (isPromise(value)) return value.then((session) => session ? copySession(session) : null);
222
- return value ? copySession(value) : null;
223
- },
224
- set: (session) => backend.set(key, copySession(session)),
225
- clear: () => backend.delete(key)
226
- };
227
- }
228
- //#endregion
229
- export { collectKeyValueStoreKeys as a, isEnumerableKeyValueStore as c, MemoryKeyValueStore as i, withCodec as l, copySession as n, createKeyValueStore as o, createTokenStorage as r, createRecordKeyValueStore as s, MemoryTokenStorage as t, withNamespace as u };
163
+ export { isEnumerableKeyValueStore as a, createRecordKeyValueStore as i, collectKeyValueStoreKeys as n, withCodec as o, createKeyValueStore as r, withNamespace as s, MemoryKeyValueStore as t };
230
164
 
231
- //# sourceMappingURL=storage-D86edNCB.js.map
165
+ //# sourceMappingURL=key-value-store-Bt2PSMxY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-value-store-Bt2PSMxY.js","names":["#values"],"sources":["../../src/core/key-value-store.ts"],"sourcesContent":["import { ItdConfigError } from './errors.js';\n\n/** Синхронный или асинхронный результат операции хранилища. */\nexport type KeyValueStoreResult<T> = T | Promise<T>;\n\n/** Синхронный или асинхронный источник ключей. */\nexport type KeyValueStoreKeys = Iterable<string> | AsyncIterable<string>;\n\n/**\n * Минимальный backend именованных значений.\n *\n * Контракт ничего не знает о сессиях, сериализации или конкретной базе данных. Значение\n * `undefined` означает отсутствие ключа; хранить `undefined` как самостоятельное значение нельзя.\n */\nexport interface KeyValueStore<T> {\n /** Читает значение либо возвращает `undefined`, если ключ отсутствует. */\n get(key: string): KeyValueStoreResult<T | undefined>;\n /** Полностью заменяет значение ключа. */\n set(key: string, value: T): KeyValueStoreResult<void>;\n /** Удаляет ключ. Отсутствующий ключ не должен считаться ошибкой. */\n delete(key: string): KeyValueStoreResult<void>;\n /** Перечисляет ключи, при возможности ограничивая их префиксом. */\n keys?(prefix?: string): KeyValueStoreResult<KeyValueStoreKeys>;\n}\n\n/** Backend, который гарантированно умеет перечислять ключи. */\nexport interface EnumerableKeyValueStore<T> extends KeyValueStore<T> {\n keys(prefix?: string): KeyValueStoreResult<KeyValueStoreKeys>;\n}\n\n/** Кодирует значения для хранения в backend другого типа. */\nexport interface KeyValueCodec<T, Stored> {\n /** Преобразует доменное значение перед записью. */\n encode(value: T): KeyValueStoreResult<Stored>;\n /** Восстанавливает доменное значение после чтения. */\n decode(value: Stored): KeyValueStoreResult<T>;\n}\n\n/** Источник одной записи, содержащей всю карту key-value. */\nexport interface RecordKeyValueStoreSource<T> {\n /** Читает всю карту. `undefined` означает отсутствие записи. */\n read(): KeyValueStoreResult<Readonly<Record<string, T>> | undefined>;\n /** Записывает стабильный снимок всей карты. */\n write(record: Readonly<Record<string, T>>): KeyValueStoreResult<void>;\n /** Удаляет запись, когда карта становится пустой. */\n delete?(): KeyValueStoreResult<void>;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction requireStore(value: unknown): asserts value is KeyValueStore<unknown> {\n const store = value as KeyValueStore<unknown>;\n if (!isRecord(store)) throw new ItdConfigError('KeyValueStore должен быть объектом');\n for (const method of ['get', 'set', 'delete'] as const) {\n if (typeof store[method] !== 'function') {\n throw new ItdConfigError(`KeyValueStore.${method} должен быть функцией`);\n }\n }\n if (store.keys !== undefined && typeof store.keys !== 'function') {\n throw new ItdConfigError('KeyValueStore.keys должен быть функцией');\n }\n}\n\nexport function createKeyValueStore<T>(\n handlers: EnumerableKeyValueStore<T>,\n): EnumerableKeyValueStore<T>;\nexport function createKeyValueStore<T>(handlers: KeyValueStore<T>): KeyValueStore<T>;\n/** Собирает {@link KeyValueStore} из функций конкретного backend. */\nexport function createKeyValueStore<T>(handlers: KeyValueStore<T>): KeyValueStore<T> {\n requireStore(handlers);\n return handlers;\n}\n\n/** Проверяет доступность перечисления ключей. */\nexport function isEnumerableKeyValueStore<T>(\n store: KeyValueStore<T>,\n): store is EnumerableKeyValueStore<T> {\n return typeof store.keys === 'function';\n}\n\n/** @internal */\nexport async function collectKeyValueStoreKeys<T>(\n store: EnumerableKeyValueStore<T>,\n prefix?: string,\n): Promise<string[]> {\n const source = await store.keys(prefix);\n const result: string[] = [];\n for await (const key of source) {\n if (typeof key !== 'string') {\n throw new ItdConfigError('KeyValueStore.keys() должен возвращать только строки');\n }\n if (prefix === undefined || key.startsWith(prefix)) result.push(key);\n }\n return result;\n}\n\n/** Key-value backend в памяти процесса. Значения сохраняются по ссылке. */\nexport class MemoryKeyValueStore<T> implements EnumerableKeyValueStore<T> {\n readonly #values = new Map<string, T>();\n\n constructor(initial?: Iterable<readonly [string, T]>) {\n for (const [key, value] of initial ?? []) this.#values.set(key, value);\n }\n\n get(key: string): T | undefined {\n return this.#values.get(key);\n }\n\n set(key: string, value: T): void {\n this.#values.set(key, value);\n }\n\n delete(key: string): void {\n this.#values.delete(key);\n }\n\n keys(prefix = ''): string[] {\n return [...this.#values.keys()].filter((key) => key.startsWith(prefix));\n }\n}\n\nexport function withNamespace<T>(\n store: EnumerableKeyValueStore<T>,\n namespace: string,\n separator?: string,\n): EnumerableKeyValueStore<T>;\nexport function withNamespace<T>(\n store: KeyValueStore<T>,\n namespace: string,\n separator?: string,\n): KeyValueStore<T>;\n/** Добавляет namespace ко всем ключам, не меняя исходный backend. */\nexport function withNamespace<T>(\n store: KeyValueStore<T>,\n namespace: string,\n separator = ':',\n): KeyValueStore<T> {\n requireStore(store);\n if (typeof namespace !== 'string' || typeof separator !== 'string') {\n throw new ItdConfigError('namespace и separator KeyValueStore должны быть строками');\n }\n const base = namespace.length === 0 ? '' : `${namespace}${separator}`;\n const key = (value: string) => `${base}${value}`;\n const namespaced: KeyValueStore<T> = {\n get: (value) => store.get(key(value)),\n set: (value, data) => store.set(key(value), data),\n delete: (value) => store.delete(key(value)),\n };\n\n if (isEnumerableKeyValueStore(store)) {\n namespaced.keys = async (prefix = '') => {\n const keys = await collectKeyValueStoreKeys(store, key(prefix));\n return keys.map((value) => value.slice(base.length));\n };\n }\n return namespaced;\n}\n\nexport function withCodec<T, Stored>(\n store: EnumerableKeyValueStore<Stored>,\n codec: KeyValueCodec<T, Stored>,\n): EnumerableKeyValueStore<T>;\nexport function withCodec<T, Stored>(\n store: KeyValueStore<Stored>,\n codec: KeyValueCodec<T, Stored>,\n): KeyValueStore<T>;\n/** Преобразует значения на границе backend, сохраняя его пространство ключей. */\nexport function withCodec<T, Stored>(\n store: KeyValueStore<Stored>,\n codec: KeyValueCodec<T, Stored>,\n): KeyValueStore<T> {\n requireStore(store);\n if (\n !isRecord(codec) ||\n typeof codec.encode !== 'function' ||\n typeof codec.decode !== 'function'\n ) {\n throw new ItdConfigError('codec должен содержать функции encode и decode');\n }\n const encoded: KeyValueStore<T> = {\n async get(key) {\n const value = await store.get(key);\n return value === undefined ? undefined : codec.decode(value);\n },\n async set(key, value) {\n await store.set(key, await codec.encode(value));\n },\n delete: (key) => store.delete(key),\n };\n if (isEnumerableKeyValueStore(store)) encoded.keys = (prefix) => store.keys(prefix);\n return encoded;\n}\n\n/** Создаёт запись без прототипа, чтобы любые пользовательские ключи оставались данными. */\nfunction emptyRecord<T>(): Record<string, T> {\n return Object.create(null) as Record<string, T>;\n}\n\nfunction copyRecord<T>(record: Readonly<Record<string, T>> | undefined): Record<string, T> {\n const copy = emptyRecord<T>();\n for (const [key, value] of Object.entries(record ?? {})) copy[key] = value;\n return copy;\n}\n\n/**\n * Создаёт key-value backend поверх одной общей записи.\n *\n * Чтение выполняется один раз, а записи выстраиваются последовательно. Изменение становится\n * видимым чтениям только после подтверждения source. Несколько экземпляров или процессов\n * требуют внешней синхронизации со стороны source.\n */\nexport function createRecordKeyValueStore<T>(\n source: RecordKeyValueStoreSource<T>,\n): EnumerableKeyValueStore<T> {\n if (\n !isRecord(source) ||\n typeof source.read !== 'function' ||\n typeof source.write !== 'function'\n ) {\n throw new ItdConfigError('RecordKeyValueStoreSource должен содержать read и write');\n }\n if (source.delete !== undefined && typeof source.delete !== 'function') {\n throw new ItdConfigError('RecordKeyValueStoreSource.delete должен быть функцией');\n }\n\n let committed: Record<string, T> | undefined;\n let loading: Promise<Record<string, T>> | undefined;\n let writes: Promise<void> = Promise.resolve();\n\n const load = async (): Promise<Record<string, T>> => {\n if (committed !== undefined) return committed;\n loading ??= Promise.resolve(source.read())\n .then((value) => {\n if (value !== undefined && !isRecord(value)) {\n throw new ItdConfigError('RecordKeyValueStoreSource.read() должен вернуть объект');\n }\n committed = copyRecord(value as Readonly<Record<string, T>> | undefined);\n return committed;\n })\n .finally(() => {\n loading = undefined;\n });\n return loading;\n };\n\n /**\n * Записывает изменение и фиксирует его только после подтверждения source.\n *\n * Черновик строится внутри очереди записей, от текущего зафиксированного состояния.\n *\n * @param apply вносит изменение в черновик; `false` — менять нечего\n */\n const mutate = (apply: (draft: Record<string, T>) => boolean): Promise<void> => {\n const run = async (): Promise<void> => {\n const draft = copyRecord(await load());\n if (!apply(draft)) return;\n\n if (source.delete && Object.keys(draft).length === 0) await source.delete();\n else await source.write(draft);\n committed = draft;\n };\n writes = writes.then(run, run);\n return writes;\n };\n\n return {\n async get(key) {\n const current = await load();\n return Object.hasOwn(current, key) ? current[key] : undefined;\n },\n set(key, value) {\n return mutate((draft) => {\n draft[key] = value;\n return true;\n });\n },\n delete(key) {\n return mutate((draft) => {\n if (!Object.hasOwn(draft, key)) return false;\n delete draft[key];\n return true;\n });\n },\n async keys(prefix = '') {\n const current = await load();\n return Object.keys(current).filter((key) => key.startsWith(prefix));\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ //#region src/core/key-value-store.d.ts
2
+ /** Синхронный или асинхронный результат операции хранилища. */
3
+ type KeyValueStoreResult<T> = T | Promise<T>;
4
+ /** Синхронный или асинхронный источник ключей. */
5
+ type KeyValueStoreKeys = Iterable<string> | AsyncIterable<string>;
6
+ /**
7
+ * Минимальный backend именованных значений.
8
+ *
9
+ * Контракт ничего не знает о сессиях, сериализации или конкретной базе данных. Значение
10
+ * `undefined` означает отсутствие ключа; хранить `undefined` как самостоятельное значение нельзя.
11
+ */
12
+ interface KeyValueStore<T> {
13
+ /** Читает значение либо возвращает `undefined`, если ключ отсутствует. */
14
+ get(key: string): KeyValueStoreResult<T | undefined>;
15
+ /** Полностью заменяет значение ключа. */
16
+ set(key: string, value: T): KeyValueStoreResult<void>;
17
+ /** Удаляет ключ. Отсутствующий ключ не должен считаться ошибкой. */
18
+ delete(key: string): KeyValueStoreResult<void>;
19
+ /** Перечисляет ключи, при возможности ограничивая их префиксом. */
20
+ keys?(prefix?: string): KeyValueStoreResult<KeyValueStoreKeys>;
21
+ }
22
+ /** Backend, который гарантированно умеет перечислять ключи. */
23
+ interface EnumerableKeyValueStore<T> extends KeyValueStore<T> {
24
+ keys(prefix?: string): KeyValueStoreResult<KeyValueStoreKeys>;
25
+ }
26
+ /** Кодирует значения для хранения в backend другого типа. */
27
+ interface KeyValueCodec<T, Stored> {
28
+ /** Преобразует доменное значение перед записью. */
29
+ encode(value: T): KeyValueStoreResult<Stored>;
30
+ /** Восстанавливает доменное значение после чтения. */
31
+ decode(value: Stored): KeyValueStoreResult<T>;
32
+ }
33
+ /** Источник одной записи, содержащей всю карту key-value. */
34
+ interface RecordKeyValueStoreSource<T> {
35
+ /** Читает всю карту. `undefined` означает отсутствие записи. */
36
+ read(): KeyValueStoreResult<Readonly<Record<string, T>> | undefined>;
37
+ /** Записывает стабильный снимок всей карты. */
38
+ write(record: Readonly<Record<string, T>>): KeyValueStoreResult<void>;
39
+ /** Удаляет запись, когда карта становится пустой. */
40
+ delete?(): KeyValueStoreResult<void>;
41
+ }
42
+ declare function createKeyValueStore<T>(handlers: EnumerableKeyValueStore<T>): EnumerableKeyValueStore<T>;
43
+ declare function createKeyValueStore<T>(handlers: KeyValueStore<T>): KeyValueStore<T>;
44
+ /** Проверяет доступность перечисления ключей. */
45
+ declare function isEnumerableKeyValueStore<T>(store: KeyValueStore<T>): store is EnumerableKeyValueStore<T>;
46
+ /** Key-value backend в памяти процесса. Значения сохраняются по ссылке. */
47
+ declare class MemoryKeyValueStore<T> implements EnumerableKeyValueStore<T> {
48
+ #private;
49
+ constructor(initial?: Iterable<readonly [string, T]>);
50
+ get(key: string): T | undefined;
51
+ set(key: string, value: T): void;
52
+ delete(key: string): void;
53
+ keys(prefix?: string): string[];
54
+ }
55
+ declare function withNamespace<T>(store: EnumerableKeyValueStore<T>, namespace: string, separator?: string): EnumerableKeyValueStore<T>;
56
+ declare function withNamespace<T>(store: KeyValueStore<T>, namespace: string, separator?: string): KeyValueStore<T>;
57
+ declare function withCodec<T, Stored>(store: EnumerableKeyValueStore<Stored>, codec: KeyValueCodec<T, Stored>): EnumerableKeyValueStore<T>;
58
+ declare function withCodec<T, Stored>(store: KeyValueStore<Stored>, codec: KeyValueCodec<T, Stored>): KeyValueStore<T>;
59
+ /**
60
+ * Создаёт key-value backend поверх одной общей записи.
61
+ *
62
+ * Чтение выполняется один раз, а записи выстраиваются последовательно. Изменение становится
63
+ * видимым чтениям только после подтверждения source. Несколько экземпляров или процессов
64
+ * требуют внешней синхронизации со стороны source.
65
+ */
66
+ declare function createRecordKeyValueStore<T>(source: RecordKeyValueStoreSource<T>): EnumerableKeyValueStore<T>;
67
+ //#endregion
68
+ export { KeyValueStoreResult as a, createKeyValueStore as c, withCodec as d, withNamespace as f, KeyValueStoreKeys as i, createRecordKeyValueStore as l, KeyValueCodec as n, MemoryKeyValueStore as o, KeyValueStore as r, RecordKeyValueStoreSource as s, EnumerableKeyValueStore as t, isEnumerableKeyValueStore as u };
69
+ //# sourceMappingURL=key-value-store-COZaNHZS.d.cts.map
@@ -0,0 +1,69 @@
1
+ //#region src/core/key-value-store.d.ts
2
+ /** Синхронный или асинхронный результат операции хранилища. */
3
+ type KeyValueStoreResult<T> = T | Promise<T>;
4
+ /** Синхронный или асинхронный источник ключей. */
5
+ type KeyValueStoreKeys = Iterable<string> | AsyncIterable<string>;
6
+ /**
7
+ * Минимальный backend именованных значений.
8
+ *
9
+ * Контракт ничего не знает о сессиях, сериализации или конкретной базе данных. Значение
10
+ * `undefined` означает отсутствие ключа; хранить `undefined` как самостоятельное значение нельзя.
11
+ */
12
+ interface KeyValueStore<T> {
13
+ /** Читает значение либо возвращает `undefined`, если ключ отсутствует. */
14
+ get(key: string): KeyValueStoreResult<T | undefined>;
15
+ /** Полностью заменяет значение ключа. */
16
+ set(key: string, value: T): KeyValueStoreResult<void>;
17
+ /** Удаляет ключ. Отсутствующий ключ не должен считаться ошибкой. */
18
+ delete(key: string): KeyValueStoreResult<void>;
19
+ /** Перечисляет ключи, при возможности ограничивая их префиксом. */
20
+ keys?(prefix?: string): KeyValueStoreResult<KeyValueStoreKeys>;
21
+ }
22
+ /** Backend, который гарантированно умеет перечислять ключи. */
23
+ interface EnumerableKeyValueStore<T> extends KeyValueStore<T> {
24
+ keys(prefix?: string): KeyValueStoreResult<KeyValueStoreKeys>;
25
+ }
26
+ /** Кодирует значения для хранения в backend другого типа. */
27
+ interface KeyValueCodec<T, Stored> {
28
+ /** Преобразует доменное значение перед записью. */
29
+ encode(value: T): KeyValueStoreResult<Stored>;
30
+ /** Восстанавливает доменное значение после чтения. */
31
+ decode(value: Stored): KeyValueStoreResult<T>;
32
+ }
33
+ /** Источник одной записи, содержащей всю карту key-value. */
34
+ interface RecordKeyValueStoreSource<T> {
35
+ /** Читает всю карту. `undefined` означает отсутствие записи. */
36
+ read(): KeyValueStoreResult<Readonly<Record<string, T>> | undefined>;
37
+ /** Записывает стабильный снимок всей карты. */
38
+ write(record: Readonly<Record<string, T>>): KeyValueStoreResult<void>;
39
+ /** Удаляет запись, когда карта становится пустой. */
40
+ delete?(): KeyValueStoreResult<void>;
41
+ }
42
+ declare function createKeyValueStore<T>(handlers: EnumerableKeyValueStore<T>): EnumerableKeyValueStore<T>;
43
+ declare function createKeyValueStore<T>(handlers: KeyValueStore<T>): KeyValueStore<T>;
44
+ /** Проверяет доступность перечисления ключей. */
45
+ declare function isEnumerableKeyValueStore<T>(store: KeyValueStore<T>): store is EnumerableKeyValueStore<T>;
46
+ /** Key-value backend в памяти процесса. Значения сохраняются по ссылке. */
47
+ declare class MemoryKeyValueStore<T> implements EnumerableKeyValueStore<T> {
48
+ #private;
49
+ constructor(initial?: Iterable<readonly [string, T]>);
50
+ get(key: string): T | undefined;
51
+ set(key: string, value: T): void;
52
+ delete(key: string): void;
53
+ keys(prefix?: string): string[];
54
+ }
55
+ declare function withNamespace<T>(store: EnumerableKeyValueStore<T>, namespace: string, separator?: string): EnumerableKeyValueStore<T>;
56
+ declare function withNamespace<T>(store: KeyValueStore<T>, namespace: string, separator?: string): KeyValueStore<T>;
57
+ declare function withCodec<T, Stored>(store: EnumerableKeyValueStore<Stored>, codec: KeyValueCodec<T, Stored>): EnumerableKeyValueStore<T>;
58
+ declare function withCodec<T, Stored>(store: KeyValueStore<Stored>, codec: KeyValueCodec<T, Stored>): KeyValueStore<T>;
59
+ /**
60
+ * Создаёт key-value backend поверх одной общей записи.
61
+ *
62
+ * Чтение выполняется один раз, а записи выстраиваются последовательно. Изменение становится
63
+ * видимым чтениям только после подтверждения source. Несколько экземпляров или процессов
64
+ * требуют внешней синхронизации со стороны source.
65
+ */
66
+ declare function createRecordKeyValueStore<T>(source: RecordKeyValueStoreSource<T>): EnumerableKeyValueStore<T>;
67
+ //#endregion
68
+ export { KeyValueStoreResult as a, createKeyValueStore as c, withCodec as d, withNamespace as f, KeyValueStoreKeys as i, createRecordKeyValueStore as l, KeyValueCodec as n, MemoryKeyValueStore as o, KeyValueStore as r, RecordKeyValueStoreSource as s, EnumerableKeyValueStore as t, isEnumerableKeyValueStore as u };
69
+ //# sourceMappingURL=key-value-store-COZaNHZS.d.ts.map
@@ -1,4 +1,5 @@
1
- import { o as EnumerableKeyValueStore, r as TokenStorage, t as ItdSession } from "./storage-C_eICCep.js";
1
+ import { t as EnumerableKeyValueStore } from "./key-value-store-COZaNHZS.cjs";
2
+ import { r as TokenStorage, t as ItdSession } from "./storage-BQrtKon9.cjs";
2
3
  //#region src/session/multi-storage.d.ts
3
4
  /**
4
5
  * Хранилище сессий нескольких аккаунтов.
@@ -69,4 +70,4 @@ interface MultiTokenStorageAdapterOptions {
69
70
  declare function createMultiTokenStorage(store: EnumerableKeyValueStore<ItdSession>, options?: MultiTokenStorageAdapterOptions): MultiTokenStorage;
70
71
  //#endregion
71
72
  export { scopedTokenStorage as a, createMultiTokenStorage as i, MultiTokenStorage as n, MultiTokenStorageAdapterOptions as r, MemoryMultiTokenStorage as t };
72
- //# sourceMappingURL=multi-storage-DccjD7Ww.d.ts.map
73
+ //# sourceMappingURL=multi-storage-BEL7tcKn.d.cts.map
@@ -1,6 +1,7 @@
1
- const require_errors = require("./errors-DfU8M5eS.cjs");
2
- const require_cookies = require("./cookies-DZwFq6kr.cjs");
3
- const require_storage = require("./storage-BPJR_k4-.cjs");
1
+ const require_errors = require("./errors-BmP3TKoW.cjs");
2
+ const require_redact = require("./redact-BP00URmQ.cjs");
3
+ const require_key_value_store = require("./key-value-store-B5GYVEYZ.cjs");
4
+ const require_storage = require("./storage-QNVxzpDB.cjs");
4
5
  //#region src/session/multi-storage.ts
5
6
  /**
6
7
  * Срез мультихранилища как обычное {@link TokenStorage} — в таком виде его получает
@@ -24,9 +25,9 @@ function scopedTokenStorage(storage, account) {
24
25
  function isRestorableSession(session) {
25
26
  if (!session) return false;
26
27
  if (session.accessToken || session.refreshToken) return true;
27
- const jar = new require_cookies.CookieJar();
28
+ const jar = new require_redact.CookieJar();
28
29
  jar.deserialize(session.cookies);
29
- return jar.has(require_cookies.AUTH_FLAG_COOKIE);
30
+ return jar.has(require_redact.AUTH_FLAG_COOKIE);
30
31
  }
31
32
  /** @internal */
32
33
  function controlledTokenStorage(storage, account) {
@@ -60,7 +61,7 @@ function controlledTokenStorage(storage, account) {
60
61
  * из `itd-api/node` либо соберите своё через {@link createMultiTokenStorage}.
61
62
  */
62
63
  var MemoryMultiTokenStorage = class {
63
- #store = new require_storage.MemoryKeyValueStore();
64
+ #store = new require_key_value_store.MemoryKeyValueStore();
64
65
  constructor(initial) {
65
66
  for (const [account, session] of Object.entries(initial ?? {})) this.#store.set(accountKey(account), require_storage.copySession(session));
66
67
  }
@@ -100,8 +101,8 @@ function readAccountName(encoded) {
100
101
  function createMultiTokenStorage(store, options = {}) {
101
102
  const prefix = options.prefix ?? ACCOUNT_KEY_PREFIX;
102
103
  if (typeof prefix !== "string") throw new require_errors.ItdConfigError("prefix MultiTokenStorage должен быть строкой");
103
- const backend = require_storage.createKeyValueStore(store);
104
- if (!require_storage.isEnumerableKeyValueStore(backend)) throw new require_errors.ItdConfigError("MultiTokenStorage требует KeyValueStore с методом keys()");
104
+ const backend = require_key_value_store.createKeyValueStore(store);
105
+ if (!require_key_value_store.isEnumerableKeyValueStore(backend)) throw new require_errors.ItdConfigError("MultiTokenStorage требует KeyValueStore с методом keys()");
105
106
  const key = (account) => `${prefix}${encodeURIComponent(account)}`;
106
107
  return {
107
108
  async get(account) {
@@ -111,7 +112,7 @@ function createMultiTokenStorage(store, options = {}) {
111
112
  set: (account, session) => backend.set(key(account), require_storage.copySession(session)),
112
113
  clear: (account) => backend.delete(key(account)),
113
114
  async accounts() {
114
- return (await require_storage.collectKeyValueStoreKeys(backend, prefix)).flatMap((value) => readAccountName(value.slice(prefix.length)) ?? []);
115
+ return (await require_key_value_store.collectKeyValueStoreKeys(backend, prefix)).flatMap((value) => readAccountName(value.slice(prefix.length)) ?? []);
115
116
  }
116
117
  };
117
118
  }
@@ -147,4 +148,4 @@ Object.defineProperty(exports, "scopedTokenStorage", {
147
148
  }
148
149
  });
149
150
 
150
- //# sourceMappingURL=multi-storage--yTEqiod.cjs.map
151
+ //# sourceMappingURL=multi-storage-CDKLa956.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"multi-storage--yTEqiod.cjs","names":["CookieJar","AUTH_FLAG_COOKIE","#store","MemoryKeyValueStore","copySession","ItdConfigError","createKeyValueStore","isEnumerableKeyValueStore","collectKeyValueStoreKeys"],"sources":["../../src/session/multi-storage.ts"],"sourcesContent":["import { AUTH_FLAG_COOKIE, CookieJar } from '../core/cookies.js';\nimport { ItdConfigError } from '../core/errors.js';\nimport {\n collectKeyValueStoreKeys,\n createKeyValueStore,\n type EnumerableKeyValueStore,\n isEnumerableKeyValueStore,\n MemoryKeyValueStore,\n} from '../core/key-value-store.js';\nimport { copySession, type ItdSession, type TokenStorage } from './storage.js';\n\n/**\n * Хранилище сессий нескольких аккаунтов.\n *\n * Отличается от {@link TokenStorage} тем, что каждый метод получает **имя аккаунта**:\n * так адаптер сам решает, как строить ключ, и Redis, БД или связка ключей получают то,\n * что им нужно. Имя приходит ровно тем, под которым аккаунт заведён в {@link ItdAccounts}:\n * библиотека его не нормализует и не экранирует — префиксы, экранирование и ограничения\n * на длину ключа остаются за адаптером.\n *\n * Все методы могут быть как синхронными, так и асинхронными.\n *\n * @example Хранилище поверх enumerable Redis backend\n * ```ts\n * const storage = createMultiTokenStorage(withNamespace(redisStore, 'itd'));\n * ```\n */\nexport interface MultiTokenStorage {\n /** Прочитать сессию аккаунта. `null`, если её нет. */\n get(account: string): ItdSession | null | Promise<ItdSession | null>;\n /** Сохранить сессию аккаунта целиком. */\n set(account: string, session: ItdSession): void | Promise<void>;\n /** Удалить сессию аккаунта. Вызывается при выходе и при неудачном обновлении токена. */\n clear(account: string): void | Promise<void>;\n /**\n * Имена сохранённых записей — по ним `ItdAccounts.restore()` находит кандидатов\n * после перезапуска процесса.\n *\n * Список ведёт сам адаптер: у файлового и памятного он виден из самой записи,\n * а хранилищу «ключ — значение» придётся держать множество имён рядом с сессиями.\n * Перед восстановлением контейнер читает каждую запись и пропускает оставшийся после\n * выхода одинокий `deviceId`: без токена или refresh-сессии авторизоваться невозможно.\n * Пустой список означает лишь то, что кандидатов нет, — сами записи при этом могут быть\n * доступны по имени.\n */\n accounts(): readonly string[] | Promise<readonly string[]>;\n}\n\n/**\n * Срез мультихранилища как обычное {@link TokenStorage} — в таком виде его получает\n * отдельный `ItdClient`, который про соседние аккаунты ничего не знает.\n */\nexport function scopedTokenStorage(storage: MultiTokenStorage, account: string): TokenStorage {\n return {\n get: () => storage.get(account),\n set: (session) => storage.set(account, session),\n clear: () => storage.clear(account),\n };\n}\n\n/**\n * Можно ли восстановить авторизованный аккаунт из записи.\n *\n * Один `deviceId` не считается сессией: он намеренно переживает выход, но не позволяет\n * выполнить ни одного авторизованного запроса.\n *\n * @internal\n */\nexport function isRestorableSession(session: ItdSession | null): session is ItdSession {\n if (!session) return false;\n if (session.accessToken || session.refreshToken) return true;\n\n const jar = new CookieJar();\n jar.deserialize(session.cookies);\n return jar.has(AUTH_FLAG_COOKIE);\n}\n\n/**\n * Управляемый срез хранилища для клиента аккаунта.\n *\n * После `revoke()` новые записи не доходят до общего хранилища, а `drain()` позволяет\n * дождаться уже начатых. Так удалённый клиент или завершившийся с опозданием refresh\n * не воскресят сессию после `removeAccount(..., { forget: true })`.\n *\n * @internal\n */\nexport interface ControlledTokenStorage {\n storage: TokenStorage;\n revoke(): void;\n drain(): Promise<void>;\n}\n\n/** @internal */\nexport function controlledTokenStorage(\n storage: MultiTokenStorage,\n account: string,\n): ControlledTokenStorage {\n let revoked = false;\n const pending = new Set<Promise<void>>();\n\n const mutate = (operation: () => void | Promise<void>): Promise<void> => {\n if (revoked) return Promise.resolve();\n\n const promise = Promise.resolve().then(operation);\n pending.add(promise);\n void promise.then(\n () => pending.delete(promise),\n () => pending.delete(promise),\n );\n return promise;\n };\n\n return {\n storage: {\n get: () => (revoked ? null : storage.get(account)),\n set: (session) => mutate(() => storage.set(account, session)),\n clear: () => mutate(() => storage.clear(account)),\n },\n revoke() {\n revoked = true;\n },\n async drain() {\n await Promise.all([...pending]);\n },\n };\n}\n\n/**\n * Мультихранилище в памяти процесса — вариант по умолчанию.\n *\n * Сессии теряются при перезапуске. Для долгоживущих ботов возьмите `FileMultiTokenStorage`\n * из `itd-api/node` либо соберите своё через {@link createMultiTokenStorage}.\n */\nexport class MemoryMultiTokenStorage implements MultiTokenStorage {\n readonly #store = new MemoryKeyValueStore<ItdSession>();\n\n constructor(initial?: Readonly<Record<string, ItdSession>> | null) {\n for (const [account, session] of Object.entries(initial ?? {})) {\n this.#store.set(accountKey(account), copySession(session));\n }\n }\n\n get(account: string): ItdSession | null {\n const session = this.#store.get(accountKey(account));\n return session ? copySession(session) : null;\n }\n\n set(account: string, session: ItdSession): void {\n this.#store.set(accountKey(account), copySession(session));\n }\n\n clear(account: string): void {\n this.#store.delete(accountKey(account));\n }\n\n accounts(): string[] {\n return this.#store\n .keys(ACCOUNT_KEY_PREFIX)\n .flatMap((key) => readAccountName(key.slice(ACCOUNT_KEY_PREFIX.length)) ?? []);\n }\n}\n\nconst ACCOUNT_KEY_PREFIX = 'accounts/';\n\nfunction accountKey(account: string): string {\n return `${ACCOUNT_KEY_PREFIX}${encodeURIComponent(account)}`;\n}\n\n/** Читает имя аккаунта из ключа. `undefined` — ключ не декодируется и запись пропускается. */\nfunction readAccountName(encoded: string): string | undefined {\n try {\n return decodeURIComponent(encoded);\n } catch {\n console.warn(\n `[itd-api] запись хранилища пропущена: ключ ${JSON.stringify(encoded)} не декодируется`,\n );\n return undefined;\n }\n}\n\n/** Настройки доменного адаптера нескольких сессий. */\nexport interface MultiTokenStorageAdapterOptions {\n /** Префикс ключей аккаунтов. По умолчанию `accounts/`. */\n prefix?: string | undefined;\n}\n\n/**\n * Создаёт доменное хранилище нескольких сессий поверх enumerable key-value backend.\n *\n * Отдельный индекс аккаунтов не используется: `accounts()` перечисляет ключи backend. Поэтому\n * запись сессии не может разойтись с индексом, но backend обязан эффективно поддерживать `keys`.\n */\nexport function createMultiTokenStorage(\n store: EnumerableKeyValueStore<ItdSession>,\n options: MultiTokenStorageAdapterOptions = {},\n): MultiTokenStorage {\n const prefix = options.prefix ?? ACCOUNT_KEY_PREFIX;\n if (typeof prefix !== 'string') {\n throw new ItdConfigError('prefix MultiTokenStorage должен быть строкой');\n }\n const backend = createKeyValueStore(store);\n if (!isEnumerableKeyValueStore(backend)) {\n throw new ItdConfigError('MultiTokenStorage требует KeyValueStore с методом keys()');\n }\n const key = (account: string) => `${prefix}${encodeURIComponent(account)}`;\n return {\n async get(account) {\n const session = await backend.get(key(account));\n return session ? copySession(session) : null;\n },\n set: (account, session) => backend.set(key(account), copySession(session)),\n clear: (account) => backend.delete(key(account)),\n async accounts() {\n const keys = await collectKeyValueStoreKeys(backend, prefix);\n return keys.flatMap((value) => readAccountName(value.slice(prefix.length)) ?? []);\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"multi-storage-CDKLa956.cjs","names":["CookieJar","AUTH_FLAG_COOKIE","#store","MemoryKeyValueStore","copySession","ItdConfigError","createKeyValueStore","isEnumerableKeyValueStore","collectKeyValueStoreKeys"],"sources":["../../src/session/multi-storage.ts"],"sourcesContent":["import { AUTH_FLAG_COOKIE, CookieJar } from '../core/cookies.js';\nimport { ItdConfigError } from '../core/errors.js';\nimport {\n collectKeyValueStoreKeys,\n createKeyValueStore,\n type EnumerableKeyValueStore,\n isEnumerableKeyValueStore,\n MemoryKeyValueStore,\n} from '../core/key-value-store.js';\nimport { copySession, type ItdSession, type TokenStorage } from './storage.js';\n\n/**\n * Хранилище сессий нескольких аккаунтов.\n *\n * Отличается от {@link TokenStorage} тем, что каждый метод получает **имя аккаунта**:\n * так адаптер сам решает, как строить ключ, и Redis, БД или связка ключей получают то,\n * что им нужно. Имя приходит ровно тем, под которым аккаунт заведён в {@link ItdAccounts}:\n * библиотека его не нормализует и не экранирует — префиксы, экранирование и ограничения\n * на длину ключа остаются за адаптером.\n *\n * Все методы могут быть как синхронными, так и асинхронными.\n *\n * @example Хранилище поверх enumerable Redis backend\n * ```ts\n * const storage = createMultiTokenStorage(withNamespace(redisStore, 'itd'));\n * ```\n */\nexport interface MultiTokenStorage {\n /** Прочитать сессию аккаунта. `null`, если её нет. */\n get(account: string): ItdSession | null | Promise<ItdSession | null>;\n /** Сохранить сессию аккаунта целиком. */\n set(account: string, session: ItdSession): void | Promise<void>;\n /** Удалить сессию аккаунта. Вызывается при выходе и при неудачном обновлении токена. */\n clear(account: string): void | Promise<void>;\n /**\n * Имена сохранённых записей — по ним `ItdAccounts.restore()` находит кандидатов\n * после перезапуска процесса.\n *\n * Список ведёт сам адаптер: у файлового и памятного он виден из самой записи,\n * а хранилищу «ключ — значение» придётся держать множество имён рядом с сессиями.\n * Перед восстановлением контейнер читает каждую запись и пропускает оставшийся после\n * выхода одинокий `deviceId`: без токена или refresh-сессии авторизоваться невозможно.\n * Пустой список означает лишь то, что кандидатов нет, — сами записи при этом могут быть\n * доступны по имени.\n */\n accounts(): readonly string[] | Promise<readonly string[]>;\n}\n\n/**\n * Срез мультихранилища как обычное {@link TokenStorage} — в таком виде его получает\n * отдельный `ItdClient`, который про соседние аккаунты ничего не знает.\n */\nexport function scopedTokenStorage(storage: MultiTokenStorage, account: string): TokenStorage {\n return {\n get: () => storage.get(account),\n set: (session) => storage.set(account, session),\n clear: () => storage.clear(account),\n };\n}\n\n/**\n * Можно ли восстановить авторизованный аккаунт из записи.\n *\n * Один `deviceId` не считается сессией: он намеренно переживает выход, но не позволяет\n * выполнить ни одного авторизованного запроса.\n *\n * @internal\n */\nexport function isRestorableSession(session: ItdSession | null): session is ItdSession {\n if (!session) return false;\n if (session.accessToken || session.refreshToken) return true;\n\n const jar = new CookieJar();\n jar.deserialize(session.cookies);\n return jar.has(AUTH_FLAG_COOKIE);\n}\n\n/**\n * Управляемый срез хранилища для клиента аккаунта.\n *\n * После `revoke()` новые записи не доходят до общего хранилища, а `drain()` позволяет\n * дождаться уже начатых. Так удалённый клиент или завершившийся с опозданием refresh\n * не воскресят сессию после `removeAccount(..., { forget: true })`.\n *\n * @internal\n */\nexport interface ControlledTokenStorage {\n storage: TokenStorage;\n revoke(): void;\n drain(): Promise<void>;\n}\n\n/** @internal */\nexport function controlledTokenStorage(\n storage: MultiTokenStorage,\n account: string,\n): ControlledTokenStorage {\n let revoked = false;\n const pending = new Set<Promise<void>>();\n\n const mutate = (operation: () => void | Promise<void>): Promise<void> => {\n if (revoked) return Promise.resolve();\n\n const promise = Promise.resolve().then(operation);\n pending.add(promise);\n void promise.then(\n () => pending.delete(promise),\n () => pending.delete(promise),\n );\n return promise;\n };\n\n return {\n storage: {\n get: () => (revoked ? null : storage.get(account)),\n set: (session) => mutate(() => storage.set(account, session)),\n clear: () => mutate(() => storage.clear(account)),\n },\n revoke() {\n revoked = true;\n },\n async drain() {\n await Promise.all([...pending]);\n },\n };\n}\n\n/**\n * Мультихранилище в памяти процесса — вариант по умолчанию.\n *\n * Сессии теряются при перезапуске. Для долгоживущих ботов возьмите `FileMultiTokenStorage`\n * из `itd-api/node` либо соберите своё через {@link createMultiTokenStorage}.\n */\nexport class MemoryMultiTokenStorage implements MultiTokenStorage {\n readonly #store = new MemoryKeyValueStore<ItdSession>();\n\n constructor(initial?: Readonly<Record<string, ItdSession>> | null) {\n for (const [account, session] of Object.entries(initial ?? {})) {\n this.#store.set(accountKey(account), copySession(session));\n }\n }\n\n get(account: string): ItdSession | null {\n const session = this.#store.get(accountKey(account));\n return session ? copySession(session) : null;\n }\n\n set(account: string, session: ItdSession): void {\n this.#store.set(accountKey(account), copySession(session));\n }\n\n clear(account: string): void {\n this.#store.delete(accountKey(account));\n }\n\n accounts(): string[] {\n return this.#store\n .keys(ACCOUNT_KEY_PREFIX)\n .flatMap((key) => readAccountName(key.slice(ACCOUNT_KEY_PREFIX.length)) ?? []);\n }\n}\n\nconst ACCOUNT_KEY_PREFIX = 'accounts/';\n\nfunction accountKey(account: string): string {\n return `${ACCOUNT_KEY_PREFIX}${encodeURIComponent(account)}`;\n}\n\n/** Читает имя аккаунта из ключа. `undefined` — ключ не декодируется и запись пропускается. */\nfunction readAccountName(encoded: string): string | undefined {\n try {\n return decodeURIComponent(encoded);\n } catch {\n console.warn(\n `[itd-api] запись хранилища пропущена: ключ ${JSON.stringify(encoded)} не декодируется`,\n );\n return undefined;\n }\n}\n\n/** Настройки доменного адаптера нескольких сессий. */\nexport interface MultiTokenStorageAdapterOptions {\n /** Префикс ключей аккаунтов. По умолчанию `accounts/`. */\n prefix?: string | undefined;\n}\n\n/**\n * Создаёт доменное хранилище нескольких сессий поверх enumerable key-value backend.\n *\n * Отдельный индекс аккаунтов не используется: `accounts()` перечисляет ключи backend. Поэтому\n * запись сессии не может разойтись с индексом, но backend обязан эффективно поддерживать `keys`.\n */\nexport function createMultiTokenStorage(\n store: EnumerableKeyValueStore<ItdSession>,\n options: MultiTokenStorageAdapterOptions = {},\n): MultiTokenStorage {\n const prefix = options.prefix ?? ACCOUNT_KEY_PREFIX;\n if (typeof prefix !== 'string') {\n throw new ItdConfigError('prefix MultiTokenStorage должен быть строкой');\n }\n const backend = createKeyValueStore(store);\n if (!isEnumerableKeyValueStore(backend)) {\n throw new ItdConfigError('MultiTokenStorage требует KeyValueStore с методом keys()');\n }\n const key = (account: string) => `${prefix}${encodeURIComponent(account)}`;\n return {\n async get(account) {\n const session = await backend.get(key(account));\n return session ? copySession(session) : null;\n },\n set: (account, session) => backend.set(key(account), copySession(session)),\n clear: (account) => backend.delete(key(account)),\n async accounts() {\n const keys = await collectKeyValueStoreKeys(backend, prefix);\n return keys.flatMap((value) => readAccountName(value.slice(prefix.length)) ?? []);\n },\n };\n}\n"],"mappings":";;;;;;;;;AAoDA,SAAgB,mBAAmB,SAA4B,SAA+B;CAC5F,OAAO;EACL,WAAW,QAAQ,IAAI,OAAO;EAC9B,MAAM,YAAY,QAAQ,IAAI,SAAS,OAAO;EAC9C,aAAa,QAAQ,MAAM,OAAO;CACpC;AACF;;;;;;;;;AAUA,SAAgB,oBAAoB,SAAmD;CACrF,IAAI,CAAC,SAAS,OAAO;CACrB,IAAI,QAAQ,eAAe,QAAQ,cAAc,OAAO;CAExD,MAAM,MAAM,IAAIA,eAAAA,UAAU;CAC1B,IAAI,YAAY,QAAQ,OAAO;CAC/B,OAAO,IAAI,IAAIC,eAAAA,gBAAgB;AACjC;;AAkBA,SAAgB,uBACd,SACA,SACwB;CACxB,IAAI,UAAU;CACd,MAAM,0BAAU,IAAI,IAAmB;CAEvC,MAAM,UAAU,cAAyD;EACvE,IAAI,SAAS,OAAO,QAAQ,QAAQ;EAEpC,MAAM,UAAU,QAAQ,QAAQ,CAAC,CAAC,KAAK,SAAS;EAChD,QAAQ,IAAI,OAAO;EACnB,QAAa,WACL,QAAQ,OAAO,OAAO,SACtB,QAAQ,OAAO,OAAO,CAC9B;EACA,OAAO;CACT;CAEA,OAAO;EACL,SAAS;GACP,WAAY,UAAU,OAAO,QAAQ,IAAI,OAAO;GAChD,MAAM,YAAY,aAAa,QAAQ,IAAI,SAAS,OAAO,CAAC;GAC5D,aAAa,aAAa,QAAQ,MAAM,OAAO,CAAC;EAClD;EACA,SAAS;GACP,UAAU;EACZ;EACA,MAAM,QAAQ;GACZ,MAAM,QAAQ,IAAI,CAAC,GAAG,OAAO,CAAC;EAChC;CACF;AACF;;;;;;;AAQA,IAAa,0BAAb,MAAkE;CAChE,SAAkB,IAAIE,wBAAAA,oBAAgC;CAEtD,YAAY,SAAuD;EACjE,KAAK,MAAM,CAAC,SAAS,YAAY,OAAO,QAAQ,WAAW,CAAC,CAAC,GAC3D,KAAKD,OAAO,IAAI,WAAW,OAAO,GAAGE,gBAAAA,YAAY,OAAO,CAAC;CAE7D;CAEA,IAAI,SAAoC;EACtC,MAAM,UAAU,KAAKF,OAAO,IAAI,WAAW,OAAO,CAAC;EACnD,OAAO,UAAUE,gBAAAA,YAAY,OAAO,IAAI;CAC1C;CAEA,IAAI,SAAiB,SAA2B;EAC9C,KAAKF,OAAO,IAAI,WAAW,OAAO,GAAGE,gBAAAA,YAAY,OAAO,CAAC;CAC3D;CAEA,MAAM,SAAuB;EAC3B,KAAKF,OAAO,OAAO,WAAW,OAAO,CAAC;CACxC;CAEA,WAAqB;EACnB,OAAO,KAAKA,OACT,KAAK,kBAAkB,CAAC,CACxB,SAAS,QAAQ,gBAAgB,IAAI,MAAM,mBAAmB,MAAM,CAAC,KAAK,CAAC,CAAC;CACjF;AACF;AAEA,MAAM,qBAAqB;AAE3B,SAAS,WAAW,SAAyB;CAC3C,OAAO,GAAG,qBAAqB,mBAAmB,OAAO;AAC3D;;AAGA,SAAS,gBAAgB,SAAqC;CAC5D,IAAI;EACF,OAAO,mBAAmB,OAAO;CACnC,QAAQ;EACN,QAAQ,KACN,8CAA8C,KAAK,UAAU,OAAO,EAAE,iBACxE;EACA;CACF;AACF;;;;;;;AAcA,SAAgB,wBACd,OACA,UAA2C,CAAC,GACzB;CACnB,MAAM,SAAS,QAAQ,UAAU;CACjC,IAAI,OAAO,WAAW,UACpB,MAAM,IAAIG,eAAAA,eAAe,8CAA8C;CAEzE,MAAM,UAAUC,wBAAAA,oBAAoB,KAAK;CACzC,IAAI,CAACC,wBAAAA,0BAA0B,OAAO,GACpC,MAAM,IAAIF,eAAAA,eAAe,0DAA0D;CAErF,MAAM,OAAO,YAAoB,GAAG,SAAS,mBAAmB,OAAO;CACvE,OAAO;EACL,MAAM,IAAI,SAAS;GACjB,MAAM,UAAU,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC;GAC9C,OAAO,UAAUD,gBAAAA,YAAY,OAAO,IAAI;EAC1C;EACA,MAAM,SAAS,YAAY,QAAQ,IAAI,IAAI,OAAO,GAAGA,gBAAAA,YAAY,OAAO,CAAC;EACzE,QAAQ,YAAY,QAAQ,OAAO,IAAI,OAAO,CAAC;EAC/C,MAAM,WAAW;GAEf,QAAO,MADYI,wBAAAA,yBAAyB,SAAS,MAAM,EAAA,CAC/C,SAAS,UAAU,gBAAgB,MAAM,MAAM,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;EAClF;CACF;AACF"}
@@ -1,4 +1,5 @@
1
- import { o as EnumerableKeyValueStore, r as TokenStorage, t as ItdSession } from "./storage-C_eICCep.cjs";
1
+ import { t as EnumerableKeyValueStore } from "./key-value-store-COZaNHZS.js";
2
+ import { r as TokenStorage, t as ItdSession } from "./storage-CTRGT5wF.js";
2
3
  //#region src/session/multi-storage.d.ts
3
4
  /**
4
5
  * Хранилище сессий нескольких аккаунтов.
@@ -69,4 +70,4 @@ interface MultiTokenStorageAdapterOptions {
69
70
  declare function createMultiTokenStorage(store: EnumerableKeyValueStore<ItdSession>, options?: MultiTokenStorageAdapterOptions): MultiTokenStorage;
70
71
  //#endregion
71
72
  export { scopedTokenStorage as a, createMultiTokenStorage as i, MultiTokenStorage as n, MultiTokenStorageAdapterOptions as r, MemoryMultiTokenStorage as t };
72
- //# sourceMappingURL=multi-storage-CjAPB5Kq.d.cts.map
73
+ //# sourceMappingURL=multi-storage-LkljOSzA.d.ts.map
@@ -1,6 +1,7 @@
1
- import { a as ItdConfigError } from "./errors-Bhrd2fJd.js";
2
- import { n as CookieJar, t as AUTH_FLAG_COOKIE } from "./cookies-tX2sNwxb.js";
3
- import { a as collectKeyValueStoreKeys, c as isEnumerableKeyValueStore, i as MemoryKeyValueStore, n as copySession, o as createKeyValueStore } from "./storage-D86edNCB.js";
1
+ import { a as ItdConfigError } from "./errors-GI10kZxk.js";
2
+ import { o as AUTH_FLAG_COOKIE, s as CookieJar } from "./redact-Ba5iSVYZ.js";
3
+ import { a as isEnumerableKeyValueStore, n as collectKeyValueStoreKeys, r as createKeyValueStore, t as MemoryKeyValueStore } from "./key-value-store-Bt2PSMxY.js";
4
+ import { n as copySession } from "./storage-CfHKzVHf.js";
4
5
  //#region src/session/multi-storage.ts
5
6
  /**
6
7
  * Срез мультихранилища как обычное {@link TokenStorage} — в таком виде его получает
@@ -118,4 +119,4 @@ function createMultiTokenStorage(store, options = {}) {
118
119
  //#endregion
119
120
  export { scopedTokenStorage as a, isRestorableSession as i, controlledTokenStorage as n, createMultiTokenStorage as r, MemoryMultiTokenStorage as t };
120
121
 
121
- //# sourceMappingURL=multi-storage-CkvTUC5m.js.map
122
+ //# sourceMappingURL=multi-storage-SoW05uzO.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"multi-storage-CkvTUC5m.js","names":["#store"],"sources":["../../src/session/multi-storage.ts"],"sourcesContent":["import { AUTH_FLAG_COOKIE, CookieJar } from '../core/cookies.js';\nimport { ItdConfigError } from '../core/errors.js';\nimport {\n collectKeyValueStoreKeys,\n createKeyValueStore,\n type EnumerableKeyValueStore,\n isEnumerableKeyValueStore,\n MemoryKeyValueStore,\n} from '../core/key-value-store.js';\nimport { copySession, type ItdSession, type TokenStorage } from './storage.js';\n\n/**\n * Хранилище сессий нескольких аккаунтов.\n *\n * Отличается от {@link TokenStorage} тем, что каждый метод получает **имя аккаунта**:\n * так адаптер сам решает, как строить ключ, и Redis, БД или связка ключей получают то,\n * что им нужно. Имя приходит ровно тем, под которым аккаунт заведён в {@link ItdAccounts}:\n * библиотека его не нормализует и не экранирует — префиксы, экранирование и ограничения\n * на длину ключа остаются за адаптером.\n *\n * Все методы могут быть как синхронными, так и асинхронными.\n *\n * @example Хранилище поверх enumerable Redis backend\n * ```ts\n * const storage = createMultiTokenStorage(withNamespace(redisStore, 'itd'));\n * ```\n */\nexport interface MultiTokenStorage {\n /** Прочитать сессию аккаунта. `null`, если её нет. */\n get(account: string): ItdSession | null | Promise<ItdSession | null>;\n /** Сохранить сессию аккаунта целиком. */\n set(account: string, session: ItdSession): void | Promise<void>;\n /** Удалить сессию аккаунта. Вызывается при выходе и при неудачном обновлении токена. */\n clear(account: string): void | Promise<void>;\n /**\n * Имена сохранённых записей — по ним `ItdAccounts.restore()` находит кандидатов\n * после перезапуска процесса.\n *\n * Список ведёт сам адаптер: у файлового и памятного он виден из самой записи,\n * а хранилищу «ключ — значение» придётся держать множество имён рядом с сессиями.\n * Перед восстановлением контейнер читает каждую запись и пропускает оставшийся после\n * выхода одинокий `deviceId`: без токена или refresh-сессии авторизоваться невозможно.\n * Пустой список означает лишь то, что кандидатов нет, — сами записи при этом могут быть\n * доступны по имени.\n */\n accounts(): readonly string[] | Promise<readonly string[]>;\n}\n\n/**\n * Срез мультихранилища как обычное {@link TokenStorage} — в таком виде его получает\n * отдельный `ItdClient`, который про соседние аккаунты ничего не знает.\n */\nexport function scopedTokenStorage(storage: MultiTokenStorage, account: string): TokenStorage {\n return {\n get: () => storage.get(account),\n set: (session) => storage.set(account, session),\n clear: () => storage.clear(account),\n };\n}\n\n/**\n * Можно ли восстановить авторизованный аккаунт из записи.\n *\n * Один `deviceId` не считается сессией: он намеренно переживает выход, но не позволяет\n * выполнить ни одного авторизованного запроса.\n *\n * @internal\n */\nexport function isRestorableSession(session: ItdSession | null): session is ItdSession {\n if (!session) return false;\n if (session.accessToken || session.refreshToken) return true;\n\n const jar = new CookieJar();\n jar.deserialize(session.cookies);\n return jar.has(AUTH_FLAG_COOKIE);\n}\n\n/**\n * Управляемый срез хранилища для клиента аккаунта.\n *\n * После `revoke()` новые записи не доходят до общего хранилища, а `drain()` позволяет\n * дождаться уже начатых. Так удалённый клиент или завершившийся с опозданием refresh\n * не воскресят сессию после `removeAccount(..., { forget: true })`.\n *\n * @internal\n */\nexport interface ControlledTokenStorage {\n storage: TokenStorage;\n revoke(): void;\n drain(): Promise<void>;\n}\n\n/** @internal */\nexport function controlledTokenStorage(\n storage: MultiTokenStorage,\n account: string,\n): ControlledTokenStorage {\n let revoked = false;\n const pending = new Set<Promise<void>>();\n\n const mutate = (operation: () => void | Promise<void>): Promise<void> => {\n if (revoked) return Promise.resolve();\n\n const promise = Promise.resolve().then(operation);\n pending.add(promise);\n void promise.then(\n () => pending.delete(promise),\n () => pending.delete(promise),\n );\n return promise;\n };\n\n return {\n storage: {\n get: () => (revoked ? null : storage.get(account)),\n set: (session) => mutate(() => storage.set(account, session)),\n clear: () => mutate(() => storage.clear(account)),\n },\n revoke() {\n revoked = true;\n },\n async drain() {\n await Promise.all([...pending]);\n },\n };\n}\n\n/**\n * Мультихранилище в памяти процесса — вариант по умолчанию.\n *\n * Сессии теряются при перезапуске. Для долгоживущих ботов возьмите `FileMultiTokenStorage`\n * из `itd-api/node` либо соберите своё через {@link createMultiTokenStorage}.\n */\nexport class MemoryMultiTokenStorage implements MultiTokenStorage {\n readonly #store = new MemoryKeyValueStore<ItdSession>();\n\n constructor(initial?: Readonly<Record<string, ItdSession>> | null) {\n for (const [account, session] of Object.entries(initial ?? {})) {\n this.#store.set(accountKey(account), copySession(session));\n }\n }\n\n get(account: string): ItdSession | null {\n const session = this.#store.get(accountKey(account));\n return session ? copySession(session) : null;\n }\n\n set(account: string, session: ItdSession): void {\n this.#store.set(accountKey(account), copySession(session));\n }\n\n clear(account: string): void {\n this.#store.delete(accountKey(account));\n }\n\n accounts(): string[] {\n return this.#store\n .keys(ACCOUNT_KEY_PREFIX)\n .flatMap((key) => readAccountName(key.slice(ACCOUNT_KEY_PREFIX.length)) ?? []);\n }\n}\n\nconst ACCOUNT_KEY_PREFIX = 'accounts/';\n\nfunction accountKey(account: string): string {\n return `${ACCOUNT_KEY_PREFIX}${encodeURIComponent(account)}`;\n}\n\n/** Читает имя аккаунта из ключа. `undefined` — ключ не декодируется и запись пропускается. */\nfunction readAccountName(encoded: string): string | undefined {\n try {\n return decodeURIComponent(encoded);\n } catch {\n console.warn(\n `[itd-api] запись хранилища пропущена: ключ ${JSON.stringify(encoded)} не декодируется`,\n );\n return undefined;\n }\n}\n\n/** Настройки доменного адаптера нескольких сессий. */\nexport interface MultiTokenStorageAdapterOptions {\n /** Префикс ключей аккаунтов. По умолчанию `accounts/`. */\n prefix?: string | undefined;\n}\n\n/**\n * Создаёт доменное хранилище нескольких сессий поверх enumerable key-value backend.\n *\n * Отдельный индекс аккаунтов не используется: `accounts()` перечисляет ключи backend. Поэтому\n * запись сессии не может разойтись с индексом, но backend обязан эффективно поддерживать `keys`.\n */\nexport function createMultiTokenStorage(\n store: EnumerableKeyValueStore<ItdSession>,\n options: MultiTokenStorageAdapterOptions = {},\n): MultiTokenStorage {\n const prefix = options.prefix ?? ACCOUNT_KEY_PREFIX;\n if (typeof prefix !== 'string') {\n throw new ItdConfigError('prefix MultiTokenStorage должен быть строкой');\n }\n const backend = createKeyValueStore(store);\n if (!isEnumerableKeyValueStore(backend)) {\n throw new ItdConfigError('MultiTokenStorage требует KeyValueStore с методом keys()');\n }\n const key = (account: string) => `${prefix}${encodeURIComponent(account)}`;\n return {\n async get(account) {\n const session = await backend.get(key(account));\n return session ? copySession(session) : null;\n },\n set: (account, session) => backend.set(key(account), copySession(session)),\n clear: (account) => backend.delete(key(account)),\n async accounts() {\n const keys = await collectKeyValueStoreKeys(backend, prefix);\n return keys.flatMap((value) => readAccountName(value.slice(prefix.length)) ?? []);\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"multi-storage-SoW05uzO.js","names":["#store"],"sources":["../../src/session/multi-storage.ts"],"sourcesContent":["import { AUTH_FLAG_COOKIE, CookieJar } from '../core/cookies.js';\nimport { ItdConfigError } from '../core/errors.js';\nimport {\n collectKeyValueStoreKeys,\n createKeyValueStore,\n type EnumerableKeyValueStore,\n isEnumerableKeyValueStore,\n MemoryKeyValueStore,\n} from '../core/key-value-store.js';\nimport { copySession, type ItdSession, type TokenStorage } from './storage.js';\n\n/**\n * Хранилище сессий нескольких аккаунтов.\n *\n * Отличается от {@link TokenStorage} тем, что каждый метод получает **имя аккаунта**:\n * так адаптер сам решает, как строить ключ, и Redis, БД или связка ключей получают то,\n * что им нужно. Имя приходит ровно тем, под которым аккаунт заведён в {@link ItdAccounts}:\n * библиотека его не нормализует и не экранирует — префиксы, экранирование и ограничения\n * на длину ключа остаются за адаптером.\n *\n * Все методы могут быть как синхронными, так и асинхронными.\n *\n * @example Хранилище поверх enumerable Redis backend\n * ```ts\n * const storage = createMultiTokenStorage(withNamespace(redisStore, 'itd'));\n * ```\n */\nexport interface MultiTokenStorage {\n /** Прочитать сессию аккаунта. `null`, если её нет. */\n get(account: string): ItdSession | null | Promise<ItdSession | null>;\n /** Сохранить сессию аккаунта целиком. */\n set(account: string, session: ItdSession): void | Promise<void>;\n /** Удалить сессию аккаунта. Вызывается при выходе и при неудачном обновлении токена. */\n clear(account: string): void | Promise<void>;\n /**\n * Имена сохранённых записей — по ним `ItdAccounts.restore()` находит кандидатов\n * после перезапуска процесса.\n *\n * Список ведёт сам адаптер: у файлового и памятного он виден из самой записи,\n * а хранилищу «ключ — значение» придётся держать множество имён рядом с сессиями.\n * Перед восстановлением контейнер читает каждую запись и пропускает оставшийся после\n * выхода одинокий `deviceId`: без токена или refresh-сессии авторизоваться невозможно.\n * Пустой список означает лишь то, что кандидатов нет, — сами записи при этом могут быть\n * доступны по имени.\n */\n accounts(): readonly string[] | Promise<readonly string[]>;\n}\n\n/**\n * Срез мультихранилища как обычное {@link TokenStorage} — в таком виде его получает\n * отдельный `ItdClient`, который про соседние аккаунты ничего не знает.\n */\nexport function scopedTokenStorage(storage: MultiTokenStorage, account: string): TokenStorage {\n return {\n get: () => storage.get(account),\n set: (session) => storage.set(account, session),\n clear: () => storage.clear(account),\n };\n}\n\n/**\n * Можно ли восстановить авторизованный аккаунт из записи.\n *\n * Один `deviceId` не считается сессией: он намеренно переживает выход, но не позволяет\n * выполнить ни одного авторизованного запроса.\n *\n * @internal\n */\nexport function isRestorableSession(session: ItdSession | null): session is ItdSession {\n if (!session) return false;\n if (session.accessToken || session.refreshToken) return true;\n\n const jar = new CookieJar();\n jar.deserialize(session.cookies);\n return jar.has(AUTH_FLAG_COOKIE);\n}\n\n/**\n * Управляемый срез хранилища для клиента аккаунта.\n *\n * После `revoke()` новые записи не доходят до общего хранилища, а `drain()` позволяет\n * дождаться уже начатых. Так удалённый клиент или завершившийся с опозданием refresh\n * не воскресят сессию после `removeAccount(..., { forget: true })`.\n *\n * @internal\n */\nexport interface ControlledTokenStorage {\n storage: TokenStorage;\n revoke(): void;\n drain(): Promise<void>;\n}\n\n/** @internal */\nexport function controlledTokenStorage(\n storage: MultiTokenStorage,\n account: string,\n): ControlledTokenStorage {\n let revoked = false;\n const pending = new Set<Promise<void>>();\n\n const mutate = (operation: () => void | Promise<void>): Promise<void> => {\n if (revoked) return Promise.resolve();\n\n const promise = Promise.resolve().then(operation);\n pending.add(promise);\n void promise.then(\n () => pending.delete(promise),\n () => pending.delete(promise),\n );\n return promise;\n };\n\n return {\n storage: {\n get: () => (revoked ? null : storage.get(account)),\n set: (session) => mutate(() => storage.set(account, session)),\n clear: () => mutate(() => storage.clear(account)),\n },\n revoke() {\n revoked = true;\n },\n async drain() {\n await Promise.all([...pending]);\n },\n };\n}\n\n/**\n * Мультихранилище в памяти процесса — вариант по умолчанию.\n *\n * Сессии теряются при перезапуске. Для долгоживущих ботов возьмите `FileMultiTokenStorage`\n * из `itd-api/node` либо соберите своё через {@link createMultiTokenStorage}.\n */\nexport class MemoryMultiTokenStorage implements MultiTokenStorage {\n readonly #store = new MemoryKeyValueStore<ItdSession>();\n\n constructor(initial?: Readonly<Record<string, ItdSession>> | null) {\n for (const [account, session] of Object.entries(initial ?? {})) {\n this.#store.set(accountKey(account), copySession(session));\n }\n }\n\n get(account: string): ItdSession | null {\n const session = this.#store.get(accountKey(account));\n return session ? copySession(session) : null;\n }\n\n set(account: string, session: ItdSession): void {\n this.#store.set(accountKey(account), copySession(session));\n }\n\n clear(account: string): void {\n this.#store.delete(accountKey(account));\n }\n\n accounts(): string[] {\n return this.#store\n .keys(ACCOUNT_KEY_PREFIX)\n .flatMap((key) => readAccountName(key.slice(ACCOUNT_KEY_PREFIX.length)) ?? []);\n }\n}\n\nconst ACCOUNT_KEY_PREFIX = 'accounts/';\n\nfunction accountKey(account: string): string {\n return `${ACCOUNT_KEY_PREFIX}${encodeURIComponent(account)}`;\n}\n\n/** Читает имя аккаунта из ключа. `undefined` — ключ не декодируется и запись пропускается. */\nfunction readAccountName(encoded: string): string | undefined {\n try {\n return decodeURIComponent(encoded);\n } catch {\n console.warn(\n `[itd-api] запись хранилища пропущена: ключ ${JSON.stringify(encoded)} не декодируется`,\n );\n return undefined;\n }\n}\n\n/** Настройки доменного адаптера нескольких сессий. */\nexport interface MultiTokenStorageAdapterOptions {\n /** Префикс ключей аккаунтов. По умолчанию `accounts/`. */\n prefix?: string | undefined;\n}\n\n/**\n * Создаёт доменное хранилище нескольких сессий поверх enumerable key-value backend.\n *\n * Отдельный индекс аккаунтов не используется: `accounts()` перечисляет ключи backend. Поэтому\n * запись сессии не может разойтись с индексом, но backend обязан эффективно поддерживать `keys`.\n */\nexport function createMultiTokenStorage(\n store: EnumerableKeyValueStore<ItdSession>,\n options: MultiTokenStorageAdapterOptions = {},\n): MultiTokenStorage {\n const prefix = options.prefix ?? ACCOUNT_KEY_PREFIX;\n if (typeof prefix !== 'string') {\n throw new ItdConfigError('prefix MultiTokenStorage должен быть строкой');\n }\n const backend = createKeyValueStore(store);\n if (!isEnumerableKeyValueStore(backend)) {\n throw new ItdConfigError('MultiTokenStorage требует KeyValueStore с методом keys()');\n }\n const key = (account: string) => `${prefix}${encodeURIComponent(account)}`;\n return {\n async get(account) {\n const session = await backend.get(key(account));\n return session ? copySession(session) : null;\n },\n set: (account, session) => backend.set(key(account), copySession(session)),\n clear: (account) => backend.delete(key(account)),\n async accounts() {\n const keys = await collectKeyValueStoreKeys(backend, prefix);\n return keys.flatMap((value) => readAccountName(value.slice(prefix.length)) ?? []);\n },\n };\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- const require_errors = require("./errors-DfU8M5eS.cjs");
1
+ const require_errors = require("./errors-BmP3TKoW.cjs");
2
+ const require_redact = require("./redact-BP00URmQ.cjs");
2
3
  //#region src/core/attachments/contracts.ts
3
4
  /** Способ передачи содержимого вложения. */
4
5
  const FileTransferMode = Object.freeze({
@@ -19,10 +20,11 @@ function optionalBytes(value, name) {
19
20
  }
20
21
  /** Создаёт типизированную ошибку превышения размера вложения. */
21
22
  function fileTooLarge(url, limit, actual) {
22
- const source = url ? ` по адресу ${url}` : "";
23
+ const safeUrl = url ? require_redact.redactUrl(url) : void 0;
24
+ const source = safeUrl ? ` по адресу ${safeUrl}` : "";
23
25
  return new require_errors.ItdFileError(`файл${source} больше предела в ${limit} байт: ${actual}`, {
24
26
  reason: require_errors.ItdFileErrorReason.TooLarge,
25
- ...url ? { url } : {},
27
+ ...safeUrl ? { url: safeUrl } : {},
26
28
  limit,
27
29
  actual
28
30
  });
@@ -98,9 +100,9 @@ function boundedFileStream(source, options = {}) {
98
100
  }
99
101
  controller.error(new require_errors.ItdFileError("не удалось прочитать поток вложения", {
100
102
  reason: require_errors.ItdFileErrorReason.Read,
101
- ...options.url ? { url: options.url } : {},
103
+ ...options.url ? { url: require_redact.redactUrl(options.url) } : {},
102
104
  retryable: options.retryableRead ?? false,
103
- cause: error
105
+ cause: require_redact.redactErrorCause(error)
104
106
  }));
105
107
  }
106
108
  },
@@ -186,4 +188,4 @@ Object.defineProperty(exports, "resolveFileStreamOptions", {
186
188
  }
187
189
  });
188
190
 
189
- //# sourceMappingURL=options-Dg5N3r1V.cjs.map
191
+ //# sourceMappingURL=options-DOJYtoti.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options-DOJYtoti.cjs","names":["ItdConfigError","redactUrl","ItdFileError","ItdFileErrorReason","ItdConfigError","ItdFileError","ItdFileErrorReason","redactUrl","redactErrorCause","ItdConfigError"],"sources":["../../src/core/attachments/contracts.ts","../../src/core/attachments/limits.ts","../../src/core/attachments/bounded-stream.ts","../../src/core/attachments/options.ts"],"sourcesContent":["/** Способ передачи содержимого вложения. */\nexport const FileTransferMode = Object.freeze({\n /** Сначала получить файл целиком, затем отправить его как `Blob`. */\n Buffer: 'buffer',\n /** Передавать данные по мере чтения, не собирая файл целиком в памяти. */\n Stream: 'stream',\n} as const);\nexport type FileTransferMode = (typeof FileTransferMode)[keyof typeof FileTransferMode];\n\n/** Размер очереди потокового вложения по умолчанию — 4 МиБ. */\nexport const DEFAULT_FILE_STREAM_BUFFER_BYTES = 4 * 1024 * 1024;\n\n/** Предел размера файла, скачиваемого по адресу, — 100 МиБ. */\nexport const DEFAULT_URL_FILE_MAX_BYTES = 100 * 1024 * 1024;\n\n/** Что доступно источнику при открытии файла. */\nexport interface FileContext {\n /** Общая отмена текущей попытки, включая её таймаут. */\n signal?: AbortSignal | undefined;\n /** `fetch` клиента — с переданными пользователем настройками. */\n fetch: typeof fetch;\n /** Номер попытки отправки, начиная с 1. */\n attempt: number;\n}\n\n/** Готовое содержимое файла. */\nexport interface FileContent {\n file: Blob | ArrayBuffer | Uint8Array;\n /** Имя влияет на определение MIME, если `contentType` не задан. */\n filename?: string | undefined;\n /** MIME без параметров. Если не указан, определяется по имени или `Blob`. */\n contentType?: string | undefined;\n}\n\n/** Открытый поток файла. */\nexport interface FileStreamContent {\n stream: ReadableStream<Uint8Array>;\n filename?: string | undefined;\n contentType?: string | undefined;\n /** Размер, если он известен до чтения. */\n size?: number | undefined;\n /** Освобождает внешний ресурс после завершения попытки. */\n close?: (() => void | Promise<void>) | undefined;\n}\n\n/** Буферный источник, который вычисляется непосредственно перед первой попыткой. */\nexport interface LazyFile {\n load: (context: FileContext) => FileContent | Promise<FileContent>;\n}\n\n/**\n * Повторяемый потоковый источник.\n *\n * `open` вызывается для каждой попытки отправки. Он обязан возвращать новый поток:\n * уже прочитанный `ReadableStream` повторно использовать нельзя.\n */\nexport interface StreamFile {\n open: (context: FileContext) => FileStreamContent | Promise<FileStreamContent>;\n}\n\n/** Общие настройки потоковой передачи. */\nexport interface FileStreamOptions {\n /** Буферный или потоковый режим. По умолчанию `'buffer'`. */\n mode?: FileTransferMode | undefined;\n /** Максимальный размер файла. Без значения размер не ограничивается. */\n maxBytes?: number | undefined;\n /**\n * Верхняя граница очереди, которой управляет библиотека. По умолчанию 4 МиБ.\n *\n * Сетевой рантайм может иметь собственные внутренние буферы сверх этого значения.\n */\n streamBufferBytes?: number | undefined;\n}\n\n/** Файл, который нужно получить по HTTP(S). */\nexport interface UrlFile extends UrlFileOptions {\n url: string;\n}\n\n/** Настройки файла по HTTP(S). */\nexport interface UrlFileOptions extends FileStreamOptions {\n filename?: string | undefined;\n /** По умолчанию берётся из `Content-Type` ответа. */\n contentType?: string | undefined;\n}\n\n/** Файл для загрузки. Голый поток не принимается, потому что его нельзя повторить. */\nexport type FileInput =\n | Blob\n | ArrayBuffer\n | Uint8Array\n | FileContent\n | UrlFile\n | LazyFile\n | StreamFile;\n\n/** Настройки фабрики пользовательского потока. */\nexport interface FromStreamOptions extends Omit<FileStreamOptions, 'mode'> {\n filename?: string | undefined;\n contentType?: string | undefined;\n /** Размер, если он известен заранее. */\n size?: number | undefined;\n}\n","import { ItdConfigError, ItdFileError, ItdFileErrorReason } from '../errors.js';\nimport { redactUrl } from '../redact.js';\n\n/** Проверяет числовую границу до обращения к источнику. */\nexport function optionalBytes(value: number | undefined, name: string): number | undefined {\n if (value === undefined) return undefined;\n if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) {\n throw new ItdConfigError(\n `${name} должен быть неотрицательным целым числом, получено: ${value}`,\n );\n }\n return value;\n}\n\n/** Создаёт типизированную ошибку превышения размера вложения. */\nexport function fileTooLarge(url: string | undefined, limit: number, actual: number): ItdFileError {\n const safeUrl = url ? redactUrl(url) : undefined;\n const source = safeUrl ? ` по адресу ${safeUrl}` : '';\n return new ItdFileError(`файл${source} больше предела в ${limit} байт: ${actual}`, {\n reason: ItdFileErrorReason.TooLarge,\n ...(safeUrl ? { url: safeUrl } : {}),\n limit,\n actual,\n });\n}\n","import { ItdConfigError, ItdFileError, ItdFileErrorReason } from '../errors.js';\nimport { redactErrorCause, redactUrl } from '../redact.js';\nimport { DEFAULT_FILE_STREAM_BUFFER_BYTES } from './contracts.js';\nimport { fileTooLarge, optionalBytes } from './limits.js';\n\n/** Потоки, на которые уже наложены счётчик размера и backpressure. */\nconst BOUNDED_FILE_STREAMS = new WeakSet<ReadableStream<Uint8Array>>();\n\n/** Настройки ограничивающей поток обёртки. */\nexport interface BoundedFileStreamOptions {\n maxBytes?: number | undefined;\n streamBufferBytes?: number | undefined;\n signal?: AbortSignal | undefined;\n url?: string | undefined;\n retryableRead?: boolean | undefined;\n}\n\n/** Приводит произвольный чанк потока к байтам. */\nfunction asBytes(value: Uint8Array): Uint8Array {\n return value instanceof Uint8Array ? value : new Uint8Array(value);\n}\n\n/** Проверяет значение без обращения к методам потенциально чужого объекта. */\nexport function isReadableByteStream(value: unknown): value is ReadableStream<Uint8Array> {\n return typeof ReadableStream !== 'undefined' && value instanceof ReadableStream;\n}\n\n/**\n * Ограничивает размер и очередь потока. Отмена результирующего потока отменяет исходный.\n *\n * @internal\n */\nexport function boundedFileStream(\n source: ReadableStream<Uint8Array>,\n options: BoundedFileStreamOptions = {},\n): ReadableStream<Uint8Array> {\n const maxBytes = optionalBytes(options.maxBytes, 'maxBytes');\n const streamBufferBytes =\n optionalBytes(\n options.streamBufferBytes ?? DEFAULT_FILE_STREAM_BUFFER_BYTES,\n 'streamBufferBytes',\n ) ?? DEFAULT_FILE_STREAM_BUFFER_BYTES;\n if (streamBufferBytes === 0) {\n throw new ItdConfigError('streamBufferBytes должен быть больше нуля');\n }\n\n const reader = source.getReader();\n let total = 0;\n let finished = false;\n let abortListener: (() => void) | undefined;\n\n const cleanup = () => {\n if (abortListener) options.signal?.removeEventListener('abort', abortListener);\n abortListener = undefined;\n };\n\n const stream = new ReadableStream<Uint8Array>(\n {\n start(controller) {\n if (!options.signal) return;\n abortListener = () => {\n finished = true;\n void reader.cancel(options.signal?.reason).catch(() => {});\n controller.error(options.signal?.reason ?? new DOMException('Aborted', 'AbortError'));\n cleanup();\n };\n if (options.signal.aborted) abortListener();\n else options.signal.addEventListener('abort', abortListener, { once: true });\n },\n async pull(controller) {\n if (finished) return;\n\n try {\n const next = await reader.read();\n if (finished) return;\n if (next.done) {\n finished = true;\n cleanup();\n controller.close();\n return;\n }\n\n const chunk = asBytes(next.value);\n total += chunk.byteLength;\n if (maxBytes !== undefined && total > maxBytes) {\n finished = true;\n await reader.cancel().catch(() => {});\n cleanup();\n controller.error(fileTooLarge(options.url, maxBytes, total));\n return;\n }\n\n controller.enqueue(chunk);\n } catch (error) {\n finished = true;\n cleanup();\n if (error instanceof ItdFileError) {\n controller.error(error);\n return;\n }\n controller.error(\n new ItdFileError('не удалось прочитать поток вложения', {\n reason: ItdFileErrorReason.Read,\n ...(options.url ? { url: redactUrl(options.url) } : {}),\n retryable: options.retryableRead ?? false,\n cause: redactErrorCause(error),\n }),\n );\n }\n },\n async cancel(reason) {\n finished = true;\n cleanup();\n await reader.cancel(reason).catch(() => {});\n },\n },\n {\n highWaterMark: streamBufferBytes,\n size: (chunk) => chunk.byteLength,\n },\n );\n BOUNDED_FILE_STREAMS.add(stream);\n return stream;\n}\n\n/** Наложены ли на поток ограничения библиотеки. @internal */\nexport function isBoundedFileStream(stream: ReadableStream<Uint8Array>): boolean {\n return BOUNDED_FILE_STREAMS.has(stream);\n}\n","import { ItdConfigError } from '../errors.js';\nimport {\n DEFAULT_FILE_STREAM_BUFFER_BYTES,\n type FileStreamOptions,\n FileTransferMode,\n} from './contracts.js';\nimport { optionalBytes } from './limits.js';\n\n/** Проверяет и дополняет настройки потока. @internal */\nexport function resolveFileStreamOptions(\n options: FileStreamOptions,\n defaultMaxBytes?: number,\n): {\n mode: FileTransferMode;\n maxBytes: number | undefined;\n streamBufferBytes: number;\n} {\n const mode = options.mode ?? FileTransferMode.Buffer;\n if (mode !== FileTransferMode.Buffer && mode !== FileTransferMode.Stream) {\n throw new ItdConfigError(`mode вложения должен быть 'buffer' или 'stream', получено: ${mode}`);\n }\n if (mode === FileTransferMode.Stream && typeof ReadableStream === 'undefined') {\n throw new ItdConfigError(\n \"эта среда не поддерживает ReadableStream; используйте mode: 'buffer'\",\n );\n }\n\n const maxBytes = optionalBytes(options.maxBytes ?? defaultMaxBytes, 'maxBytes');\n const streamBufferBytes =\n optionalBytes(\n options.streamBufferBytes ?? DEFAULT_FILE_STREAM_BUFFER_BYTES,\n 'streamBufferBytes',\n ) ?? DEFAULT_FILE_STREAM_BUFFER_BYTES;\n if (streamBufferBytes === 0) {\n throw new ItdConfigError('streamBufferBytes должен быть больше нуля');\n }\n\n return { mode, maxBytes, streamBufferBytes };\n}\n"],"mappings":"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"}