itd-api 0.8.0 → 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 (80) hide show
  1. package/README.md +63 -18
  2. package/dist/events/index.cjs +3 -3
  3. package/dist/events/index.d.cts +2 -2
  4. package/dist/events/index.d.ts +2 -2
  5. package/dist/events/index.js +3 -3
  6. package/dist/index.cjs +26 -13
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +9 -6
  9. package/dist/index.d.ts +9 -6
  10. package/dist/index.js +13 -8
  11. package/dist/index.js.map +1 -1
  12. package/dist/node/index.cjs +5 -4
  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 +4 -3
  17. package/dist/node/index.js.map +1 -1
  18. package/dist/rest/index.cjs +16 -4
  19. package/dist/rest/index.cjs.map +1 -1
  20. package/dist/rest/index.d.cts +5 -3
  21. package/dist/rest/index.d.ts +5 -3
  22. package/dist/rest/index.js +9 -5
  23. package/dist/rest/index.js.map +1 -1
  24. package/dist/shared/{events-TfhKh26b.js → events-C5wnTXPT.js} +3 -3
  25. package/dist/shared/{events-TfhKh26b.js.map → events-C5wnTXPT.js.map} +1 -1
  26. package/dist/shared/{events-QyhQZrnC.d.ts → events-CqtkP65d.d.ts} +2 -2
  27. package/dist/shared/{events-Br2r7coi.d.cts → events-DGAWlCaI.d.cts} +2 -2
  28. package/dist/shared/{events-iTteVsd_.cjs → events-FZfnEez0.cjs} +3 -3
  29. package/dist/shared/{events-iTteVsd_.cjs.map → events-FZfnEez0.cjs.map} +1 -1
  30. package/dist/shared/{storage-BLmXmzKF.cjs → key-value-store-B5GYVEYZ.cjs} +1 -85
  31. package/dist/shared/key-value-store-B5GYVEYZ.cjs.map +1 -0
  32. package/dist/shared/{storage-CCxn0Kkv.js → key-value-store-Bt2PSMxY.js} +2 -68
  33. package/dist/shared/key-value-store-Bt2PSMxY.js.map +1 -0
  34. package/dist/shared/key-value-store-COZaNHZS.d.cts +69 -0
  35. package/dist/shared/key-value-store-COZaNHZS.d.ts +69 -0
  36. package/dist/shared/{multi-storage-DccjD7Ww.d.ts → multi-storage-BEL7tcKn.d.cts} +3 -2
  37. package/dist/shared/{multi-storage-CCUgkqvI.cjs → multi-storage-CDKLa956.cjs} +8 -7
  38. package/dist/shared/{multi-storage-CCUgkqvI.cjs.map → multi-storage-CDKLa956.cjs.map} +1 -1
  39. package/dist/shared/{multi-storage-CjAPB5Kq.d.cts → multi-storage-LkljOSzA.d.ts} +3 -2
  40. package/dist/shared/{multi-storage-1AEZTczk.js → multi-storage-SoW05uzO.js} +4 -3
  41. package/dist/shared/{multi-storage-1AEZTczk.js.map → multi-storage-SoW05uzO.js.map} +1 -1
  42. package/dist/shared/{options-BUfoHE9G.cjs → options-DOJYtoti.cjs} +2 -2
  43. package/dist/shared/{options-BUfoHE9G.cjs.map → options-DOJYtoti.cjs.map} +1 -1
  44. package/dist/shared/{options-BNP633A5.js → options-DhgGg7Ms.js} +2 -2
  45. package/dist/shared/{options-BNP633A5.js.map → options-DhgGg7Ms.js.map} +1 -1
  46. package/dist/shared/{redact-DB9EMGJx.cjs → redact-BP00URmQ.cjs} +15 -4
  47. package/dist/shared/{redact-DB9EMGJx.cjs.map → redact-BP00URmQ.cjs.map} +1 -1
  48. package/dist/shared/{redact-FNK29Fv-.js → redact-Ba5iSVYZ.js} +15 -4
  49. package/dist/shared/{redact-FNK29Fv-.js.map → redact-Ba5iSVYZ.js.map} +1 -1
  50. package/dist/shared/{render-DVB7T8pm.d.ts → render-B4qis7Sb.d.ts} +348 -3
  51. package/dist/shared/{render-BszcKF7S.d.cts → render-DDwy2Ame.d.cts} +348 -3
  52. package/dist/shared/{render-DNTESBSl.cjs → render-Dqi8Owqk.cjs} +457 -4
  53. package/dist/shared/render-Dqi8Owqk.cjs.map +1 -0
  54. package/dist/shared/{render-CiL1VS0b.js → render-hiWFh2s4.js} +411 -6
  55. package/dist/shared/render-hiWFh2s4.js.map +1 -0
  56. package/dist/shared/storage-BQrtKon9.d.cts +86 -0
  57. package/dist/shared/storage-CTRGT5wF.d.ts +86 -0
  58. package/dist/shared/storage-CfHKzVHf.js +71 -0
  59. package/dist/shared/storage-CfHKzVHf.js.map +1 -0
  60. package/dist/shared/storage-QNVxzpDB.cjs +88 -0
  61. package/dist/shared/storage-QNVxzpDB.cjs.map +1 -0
  62. package/dist/shared/{url-CFN1sT9S.cjs → url-Bsb3xE5U.cjs} +83 -4
  63. package/dist/shared/{url-CFN1sT9S.cjs.map → url-Bsb3xE5U.cjs.map} +1 -1
  64. package/dist/shared/{url-B3ocFlHj.js → url-DP33mp3s.js} +78 -5
  65. package/dist/shared/{url-B3ocFlHj.js.map → url-DP33mp3s.js.map} +1 -1
  66. package/dist/shared/{url-Be9jlWRv.d.ts → url-XBsMdAcv.d.cts} +75 -2
  67. package/dist/shared/{url-Be9jlWRv.d.cts → url-XBsMdAcv.d.ts} +75 -2
  68. package/dist/web/index.cjs +4 -3
  69. package/dist/web/index.cjs.map +1 -1
  70. package/dist/web/index.d.cts +1 -1
  71. package/dist/web/index.d.ts +1 -1
  72. package/dist/web/index.js +2 -1
  73. package/dist/web/index.js.map +1 -1
  74. package/package.json +1 -1
  75. package/dist/shared/render-CiL1VS0b.js.map +0 -1
  76. package/dist/shared/render-DNTESBSl.cjs.map +0 -1
  77. package/dist/shared/storage-BLmXmzKF.cjs.map +0 -1
  78. package/dist/shared/storage-CCxn0Kkv.js.map +0 -1
  79. package/dist/shared/storage-C_eICCep.d.cts +0 -152
  80. package/dist/shared/storage-C_eICCep.d.ts +0 -152
@@ -160,96 +160,18 @@ 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 require_errors.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
163
  Object.defineProperty(exports, "MemoryKeyValueStore", {
230
164
  enumerable: true,
231
165
  get: function() {
232
166
  return MemoryKeyValueStore;
233
167
  }
234
168
  });
235
- Object.defineProperty(exports, "MemoryTokenStorage", {
236
- enumerable: true,
237
- get: function() {
238
- return MemoryTokenStorage;
239
- }
240
- });
241
169
  Object.defineProperty(exports, "collectKeyValueStoreKeys", {
242
170
  enumerable: true,
243
171
  get: function() {
244
172
  return collectKeyValueStoreKeys;
245
173
  }
246
174
  });
247
- Object.defineProperty(exports, "copySession", {
248
- enumerable: true,
249
- get: function() {
250
- return copySession;
251
- }
252
- });
253
175
  Object.defineProperty(exports, "createKeyValueStore", {
254
176
  enumerable: true,
255
177
  get: function() {
@@ -262,12 +184,6 @@ Object.defineProperty(exports, "createRecordKeyValueStore", {
262
184
  return createRecordKeyValueStore;
263
185
  }
264
186
  });
265
- Object.defineProperty(exports, "createTokenStorage", {
266
- enumerable: true,
267
- get: function() {
268
- return createTokenStorage;
269
- }
270
- });
271
187
  Object.defineProperty(exports, "isEnumerableKeyValueStore", {
272
188
  enumerable: true,
273
189
  get: function() {
@@ -287,4 +203,4 @@ Object.defineProperty(exports, "withNamespace", {
287
203
  }
288
204
  });
289
205
 
290
- //# sourceMappingURL=storage-BLmXmzKF.cjs.map
206
+ //# sourceMappingURL=key-value-store-B5GYVEYZ.cjs.map
@@ -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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"}
@@ -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-CCxn0Kkv.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
1
  const require_errors = require("./errors-BmP3TKoW.cjs");
2
- const require_redact = require("./redact-DB9EMGJx.cjs");
3
- const require_storage = require("./storage-BLmXmzKF.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} — в таком виде его получает
@@ -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-CCUgkqvI.cjs.map
151
+ //# sourceMappingURL=multi-storage-CDKLa956.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"multi-storage-CCUgkqvI.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,gBAAAA,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,gBAAAA,oBAAoB,KAAK;CACzC,IAAI,CAACC,gBAAAA,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,gBAAAA,yBAAyB,SAAS,MAAM,EAAA,CAC/C,SAAS,UAAU,gBAAgB,MAAM,MAAM,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;EAClF;CACF;AACF"}
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":"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"}
@@ -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
1
  import { a as ItdConfigError } from "./errors-GI10kZxk.js";
2
- import { o as AUTH_FLAG_COOKIE, s as CookieJar } from "./redact-FNK29Fv-.js";
3
- import { a as collectKeyValueStoreKeys, c as isEnumerableKeyValueStore, i as MemoryKeyValueStore, n as copySession, o as createKeyValueStore } from "./storage-CCxn0Kkv.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-1AEZTczk.js.map
122
+ //# sourceMappingURL=multi-storage-SoW05uzO.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"multi-storage-1AEZTczk.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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"}