itd-api 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +14 -14
  2. package/dist/events/index.cjs +53 -0
  3. package/dist/events/index.d.cts +3 -0
  4. package/dist/events/index.d.ts +3 -0
  5. package/dist/events/index.js +5 -0
  6. package/dist/index.cjs +269 -175
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +77 -56
  9. package/dist/index.d.ts +77 -56
  10. package/dist/index.js +251 -160
  11. package/dist/index.js.map +1 -1
  12. package/dist/node/index.cjs +4 -4
  13. package/dist/node/index.d.cts +1 -1
  14. package/dist/node/index.d.ts +1 -1
  15. package/dist/node/index.js +4 -4
  16. package/dist/rest/index.cjs +72 -14
  17. package/dist/rest/index.cjs.map +1 -1
  18. package/dist/rest/index.d.cts +11 -3
  19. package/dist/rest/index.d.ts +11 -3
  20. package/dist/rest/index.js +66 -8
  21. package/dist/rest/index.js.map +1 -1
  22. package/dist/shared/{errors-DfU8M5eS.cjs → errors-BmP3TKoW.cjs} +3 -3
  23. package/dist/shared/{errors-DfU8M5eS.cjs.map → errors-BmP3TKoW.cjs.map} +1 -1
  24. package/dist/shared/{errors-Bhrd2fJd.js → errors-GI10kZxk.js} +3 -3
  25. package/dist/shared/{errors-Bhrd2fJd.js.map → errors-GI10kZxk.js.map} +1 -1
  26. package/dist/shared/events-Br2r7coi.d.cts +698 -0
  27. package/dist/shared/events-QyhQZrnC.d.ts +698 -0
  28. package/dist/shared/{websocket-CbzB1Leq.js → events-TfhKh26b.js} +770 -490
  29. package/dist/shared/events-TfhKh26b.js.map +1 -0
  30. package/dist/shared/{websocket-sYlynrr0.cjs → events-iTteVsd_.cjs} +834 -518
  31. package/dist/shared/events-iTteVsd_.cjs.map +1 -0
  32. package/dist/shared/{multi-storage-CkvTUC5m.js → multi-storage-1AEZTczk.js} +4 -4
  33. package/dist/shared/{multi-storage-CkvTUC5m.js.map → multi-storage-1AEZTczk.js.map} +1 -1
  34. package/dist/shared/{multi-storage--yTEqiod.cjs → multi-storage-CCUgkqvI.cjs} +6 -6
  35. package/dist/shared/{multi-storage--yTEqiod.cjs.map → multi-storage-CCUgkqvI.cjs.map} +1 -1
  36. package/dist/shared/{options-DtATYdLr.js → options-BNP633A5.js} +8 -6
  37. package/dist/shared/options-BNP633A5.js.map +1 -0
  38. package/dist/shared/{options-Dg5N3r1V.cjs → options-BUfoHE9G.cjs} +8 -6
  39. package/dist/shared/options-BUfoHE9G.cjs.map +1 -0
  40. package/dist/shared/{cookies-DZwFq6kr.cjs → redact-DB9EMGJx.cjs} +301 -2
  41. package/dist/shared/redact-DB9EMGJx.cjs.map +1 -0
  42. package/dist/shared/{cookies-tX2sNwxb.js → redact-FNK29Fv-.js} +224 -3
  43. package/dist/shared/redact-FNK29Fv-.js.map +1 -0
  44. package/dist/shared/{render-DMp_3Nzk.d.cts → render-BszcKF7S.d.cts} +432 -254
  45. package/dist/shared/{render-vtLixIiU.js → render-CiL1VS0b.js} +1261 -621
  46. package/dist/shared/render-CiL1VS0b.js.map +1 -0
  47. package/dist/shared/{render-DyeHJNBw.cjs → render-DNTESBSl.cjs} +1335 -671
  48. package/dist/shared/render-DNTESBSl.cjs.map +1 -0
  49. package/dist/shared/{render-DZrxhC5_.d.ts → render-DVB7T8pm.d.ts} +432 -254
  50. package/dist/shared/{storage-BPJR_k4-.cjs → storage-BLmXmzKF.cjs} +2 -2
  51. package/dist/shared/{storage-BPJR_k4-.cjs.map → storage-BLmXmzKF.cjs.map} +1 -1
  52. package/dist/shared/{storage-D86edNCB.js → storage-CCxn0Kkv.js} +2 -2
  53. package/dist/shared/{storage-D86edNCB.js.map → storage-CCxn0Kkv.js.map} +1 -1
  54. package/dist/shared/{url-CYXgxqGx.js → url-B3ocFlHj.js} +491 -425
  55. package/dist/shared/url-B3ocFlHj.js.map +1 -0
  56. package/dist/shared/{url-B6-bXHKt.d.ts → url-Be9jlWRv.d.cts} +1233 -1195
  57. package/dist/shared/{url-B6-bXHKt.d.cts → url-Be9jlWRv.d.ts} +1233 -1195
  58. package/dist/shared/{url-yjl2c8Ie.cjs → url-CFN1sT9S.cjs} +562 -478
  59. package/dist/shared/url-CFN1sT9S.cjs.map +1 -0
  60. package/dist/web/index.cjs +2 -2
  61. package/dist/web/index.js +2 -2
  62. package/package.json +16 -15
  63. package/dist/realtime/index.cjs +0 -165
  64. package/dist/realtime/index.cjs.map +0 -1
  65. package/dist/realtime/index.d.cts +0 -51
  66. package/dist/realtime/index.d.ts +0 -51
  67. package/dist/realtime/index.js +0 -120
  68. package/dist/realtime/index.js.map +0 -1
  69. package/dist/shared/auth-provider-BfogACAb.js +0 -91
  70. package/dist/shared/auth-provider-BfogACAb.js.map +0 -1
  71. package/dist/shared/auth-provider-CTJkKfgy.cjs +0 -108
  72. package/dist/shared/auth-provider-CTJkKfgy.cjs.map +0 -1
  73. package/dist/shared/cookies-DZwFq6kr.cjs.map +0 -1
  74. package/dist/shared/cookies-tX2sNwxb.js.map +0 -1
  75. package/dist/shared/options-Dg5N3r1V.cjs.map +0 -1
  76. package/dist/shared/options-DtATYdLr.js.map +0 -1
  77. package/dist/shared/render-DyeHJNBw.cjs.map +0 -1
  78. package/dist/shared/render-vtLixIiU.js.map +0 -1
  79. package/dist/shared/url-CYXgxqGx.js.map +0 -1
  80. package/dist/shared/url-yjl2c8Ie.cjs.map +0 -1
  81. package/dist/shared/websocket-BMtihD56.d.ts +0 -562
  82. package/dist/shared/websocket-CbzB1Leq.js.map +0 -1
  83. package/dist/shared/websocket-D1p32SB2.d.cts +0 -562
  84. package/dist/shared/websocket-sYlynrr0.cjs.map +0 -1
@@ -1,4 +1,4 @@
1
- const require_errors = require("./errors-DfU8M5eS.cjs");
1
+ const require_errors = require("./errors-BmP3TKoW.cjs");
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);
@@ -287,4 +287,4 @@ Object.defineProperty(exports, "withNamespace", {
287
287
  }
288
288
  });
289
289
 
290
- //# sourceMappingURL=storage-BPJR_k4-.cjs.map
290
+ //# sourceMappingURL=storage-BLmXmzKF.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage-BPJR_k4-.cjs","names":["ItdConfigError","#values","#store","ItdConfigError"],"sources":["../../src/core/key-value-store.ts","../../src/session/storage.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","import { ItdConfigError } from '../core/errors.js';\nimport {\n createKeyValueStore,\n type KeyValueStore,\n MemoryKeyValueStore,\n} from '../core/key-value-store.js';\n\n/**\n * Сохранённая сессия.\n *\n * Кроме токенов сюда попадают cookie: refresh-токен итд.com живёт именно в cookie, и без них\n * восстановить сессию после перезапуска процесса невозможно.\n */\nexport interface ItdSession {\n /** Токен доступа для заголовка `Authorization: Bearer`. */\n accessToken?: string | undefined;\n /**\n * Refresh-токен, если удалось получить его явным значением.\n *\n * Обычно сервер держит его в httpOnly-cookie и наружу не отдаёт — тогда поле останется\n * пустым, а обновление пойдёт через {@link ItdSession.cookies}.\n */\n refreshToken?: string | undefined;\n /** Сырые cookie в форме `имя=значение`, привязанные к origin API. */\n cookies?: string[] | undefined;\n /**\n * Идентификатор устройства для заголовка `X-Device-Id`.\n *\n * Сервер различает по нему записи в списке сессий, поэтому значение должно пережить\n * перезапуск процесса — иначе каждый старт бота порождает новую сессию. Библиотека\n * заводит его сама при первом запросе и хранит здесь.\n */\n deviceId?: string | undefined;\n /** Когда сессия получена, мс с начала эпохи. Нужно для диагностики. */\n obtainedAt?: number | undefined;\n}\n\n/**\n * Создаёт независимый снимок сессии.\n *\n * Сессии пересекают границу пользовательского кода и внутреннего состояния клиента.\n * Возвращать или сохранять их по ссылке нельзя: последующая мутация объекта или массива\n * cookie меняла бы уже сохранённую сессию без вызова `set()`.\n *\n * @internal\n */\nexport function copySession(session: ItdSession): ItdSession {\n return {\n ...session,\n ...(session.cookies ? { cookies: [...session.cookies] } : {}),\n };\n}\n\n/**\n * Хранилище сессии.\n *\n * Подключаемый компонент: библиотека не знает, где вы держите токены, и обращается к ним\n * только через этот интерфейс. Все методы могут быть как синхронными, так и асинхронными.\n *\n * @example Своё хранилище поверх AsyncStorage в React Native\n * ```ts\n * const backend = withCodec(createKeyValueStore({\n * get: (key) => AsyncStorage.getItem(key).then((value) => value ?? undefined),\n * set: (key, value) => AsyncStorage.setItem(key, value),\n * delete: (key) => AsyncStorage.removeItem(key),\n * }), { encode: JSON.stringify, decode: JSON.parse });\n * const storage = createTokenStorage(withNamespace(backend, 'my-app'));\n * ```\n */\nexport interface TokenStorage {\n /** Прочитать сессию. `null`, если её нет. */\n get(): ItdSession | null | Promise<ItdSession | null>;\n /** Сохранить сессию целиком. */\n set(session: ItdSession): void | Promise<void>;\n /** Удалить сессию. Вызывается при выходе и при неудачном обновлении токена. */\n clear(): void | Promise<void>;\n}\n\n/**\n * Хранилище в памяти процесса — вариант по умолчанию.\n *\n * Сессия теряется при перезапуске. Для долгоживущих ботов возьмите `FileTokenStorage`\n * из `itd-api/node`, для браузера — `LocalStorageTokenStorage` или\n * `SessionStorageTokenStorage` из `itd-api/web`.\n */\nexport class MemoryTokenStorage implements TokenStorage {\n readonly #store = new MemoryKeyValueStore<ItdSession>();\n\n constructor(initial?: ItdSession | null) {\n if (initial) this.#store.set(TOKEN_STORAGE_KEY, copySession(initial));\n }\n\n get(): ItdSession | null {\n const session = this.#store.get(TOKEN_STORAGE_KEY);\n return session ? copySession(session) : null;\n }\n\n set(session: ItdSession): void {\n this.#store.set(TOKEN_STORAGE_KEY, copySession(session));\n }\n\n clear(): void {\n this.#store.delete(TOKEN_STORAGE_KEY);\n }\n}\n\nconst TOKEN_STORAGE_KEY = 'session';\n\n/** Настройки доменного адаптера одной сессии. */\nexport interface TokenStorageAdapterOptions {\n /** Ключ сессии в backend. По умолчанию `session`. */\n key?: string | undefined;\n}\n\nfunction isPromise<T>(value: T | Promise<T>): value is Promise<T> {\n return (\n typeof value === 'object' && value !== null && typeof (value as Promise<T>).then === 'function'\n );\n}\n\n/**\n * Создаёт доменное хранилище сессии поверх общего {@link KeyValueStore}.\n *\n * @example\n * ```ts\n * const storage = createTokenStorage(withNamespace(redisStore, 'itd'));\n * ```\n */\nexport function createTokenStorage(\n store: KeyValueStore<ItdSession>,\n options: TokenStorageAdapterOptions = {},\n): TokenStorage {\n const key = options.key ?? TOKEN_STORAGE_KEY;\n if (typeof key !== 'string') throw new ItdConfigError('key TokenStorage должен быть строкой');\n const backend = createKeyValueStore(store);\n return {\n get() {\n const value = backend.get(key);\n if (isPromise(value)) {\n return value.then((session) => (session ? copySession(session) : null));\n }\n return value ? copySession(value) : null;\n },\n set: (session) => backend.set(key, copySession(session)),\n clear: () => backend.delete(key),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"storage-BLmXmzKF.cjs","names":["ItdConfigError","#values","#store","ItdConfigError"],"sources":["../../src/core/key-value-store.ts","../../src/session/storage.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","import { ItdConfigError } from '../core/errors.js';\nimport {\n createKeyValueStore,\n type KeyValueStore,\n MemoryKeyValueStore,\n} from '../core/key-value-store.js';\n\n/**\n * Сохранённая сессия.\n *\n * Кроме токенов сюда попадают cookie: refresh-токен итд.com живёт именно в cookie, и без них\n * восстановить сессию после перезапуска процесса невозможно.\n */\nexport interface ItdSession {\n /** Токен доступа для заголовка `Authorization: Bearer`. */\n accessToken?: string | undefined;\n /**\n * Refresh-токен, если удалось получить его явным значением.\n *\n * Обычно сервер держит его в httpOnly-cookie и наружу не отдаёт — тогда поле останется\n * пустым, а обновление пойдёт через {@link ItdSession.cookies}.\n */\n refreshToken?: string | undefined;\n /** Сырые cookie в форме `имя=значение`, привязанные к origin API. */\n cookies?: string[] | undefined;\n /**\n * Идентификатор устройства для заголовка `X-Device-Id`.\n *\n * Сервер различает по нему записи в списке сессий, поэтому значение должно пережить\n * перезапуск процесса — иначе каждый старт бота порождает новую сессию. Библиотека\n * заводит его сама при первом запросе и хранит здесь.\n */\n deviceId?: string | undefined;\n /** Когда сессия получена, мс с начала эпохи. Нужно для диагностики. */\n obtainedAt?: number | undefined;\n}\n\n/**\n * Создаёт независимый снимок сессии.\n *\n * Сессии пересекают границу пользовательского кода и внутреннего состояния клиента.\n * Возвращать или сохранять их по ссылке нельзя: последующая мутация объекта или массива\n * cookie меняла бы уже сохранённую сессию без вызова `set()`.\n *\n * @internal\n */\nexport function copySession(session: ItdSession): ItdSession {\n return {\n ...session,\n ...(session.cookies ? { cookies: [...session.cookies] } : {}),\n };\n}\n\n/**\n * Хранилище сессии.\n *\n * Подключаемый компонент: библиотека не знает, где вы держите токены, и обращается к ним\n * только через этот интерфейс. Все методы могут быть как синхронными, так и асинхронными.\n *\n * @example Своё хранилище поверх AsyncStorage в React Native\n * ```ts\n * const backend = withCodec(createKeyValueStore({\n * get: (key) => AsyncStorage.getItem(key).then((value) => value ?? undefined),\n * set: (key, value) => AsyncStorage.setItem(key, value),\n * delete: (key) => AsyncStorage.removeItem(key),\n * }), { encode: JSON.stringify, decode: JSON.parse });\n * const storage = createTokenStorage(withNamespace(backend, 'my-app'));\n * ```\n */\nexport interface TokenStorage {\n /** Прочитать сессию. `null`, если её нет. */\n get(): ItdSession | null | Promise<ItdSession | null>;\n /** Сохранить сессию целиком. */\n set(session: ItdSession): void | Promise<void>;\n /** Удалить сессию. Вызывается при выходе и при неудачном обновлении токена. */\n clear(): void | Promise<void>;\n}\n\n/**\n * Хранилище в памяти процесса — вариант по умолчанию.\n *\n * Сессия теряется при перезапуске. Для долгоживущих ботов возьмите `FileTokenStorage`\n * из `itd-api/node`, для браузера — `LocalStorageTokenStorage` или\n * `SessionStorageTokenStorage` из `itd-api/web`.\n */\nexport class MemoryTokenStorage implements TokenStorage {\n readonly #store = new MemoryKeyValueStore<ItdSession>();\n\n constructor(initial?: ItdSession | null) {\n if (initial) this.#store.set(TOKEN_STORAGE_KEY, copySession(initial));\n }\n\n get(): ItdSession | null {\n const session = this.#store.get(TOKEN_STORAGE_KEY);\n return session ? copySession(session) : null;\n }\n\n set(session: ItdSession): void {\n this.#store.set(TOKEN_STORAGE_KEY, copySession(session));\n }\n\n clear(): void {\n this.#store.delete(TOKEN_STORAGE_KEY);\n }\n}\n\nconst TOKEN_STORAGE_KEY = 'session';\n\n/** Настройки доменного адаптера одной сессии. */\nexport interface TokenStorageAdapterOptions {\n /** Ключ сессии в backend. По умолчанию `session`. */\n key?: string | undefined;\n}\n\nfunction isPromise<T>(value: T | Promise<T>): value is Promise<T> {\n return (\n typeof value === 'object' && value !== null && typeof (value as Promise<T>).then === 'function'\n );\n}\n\n/**\n * Создаёт доменное хранилище сессии поверх общего {@link KeyValueStore}.\n *\n * @example\n * ```ts\n * const storage = createTokenStorage(withNamespace(redisStore, 'itd'));\n * ```\n */\nexport function createTokenStorage(\n store: KeyValueStore<ItdSession>,\n options: TokenStorageAdapterOptions = {},\n): TokenStorage {\n const key = options.key ?? TOKEN_STORAGE_KEY;\n if (typeof key !== 'string') throw new ItdConfigError('key TokenStorage должен быть строкой');\n const backend = createKeyValueStore(store);\n return {\n get() {\n const value = backend.get(key);\n if (isPromise(value)) {\n return value.then((session) => (session ? copySession(session) : null));\n }\n return value ? copySession(value) : null;\n },\n set: (session) => backend.set(key, copySession(session)),\n clear: () => backend.delete(key),\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);
@@ -228,4 +228,4 @@ function createTokenStorage(store, options = {}) {
228
228
  //#endregion
229
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 };
230
230
 
231
- //# sourceMappingURL=storage-D86edNCB.js.map
231
+ //# sourceMappingURL=storage-CCxn0Kkv.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage-D86edNCB.js","names":["#values","#store"],"sources":["../../src/core/key-value-store.ts","../../src/session/storage.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","import { ItdConfigError } from '../core/errors.js';\nimport {\n createKeyValueStore,\n type KeyValueStore,\n MemoryKeyValueStore,\n} from '../core/key-value-store.js';\n\n/**\n * Сохранённая сессия.\n *\n * Кроме токенов сюда попадают cookie: refresh-токен итд.com живёт именно в cookie, и без них\n * восстановить сессию после перезапуска процесса невозможно.\n */\nexport interface ItdSession {\n /** Токен доступа для заголовка `Authorization: Bearer`. */\n accessToken?: string | undefined;\n /**\n * Refresh-токен, если удалось получить его явным значением.\n *\n * Обычно сервер держит его в httpOnly-cookie и наружу не отдаёт — тогда поле останется\n * пустым, а обновление пойдёт через {@link ItdSession.cookies}.\n */\n refreshToken?: string | undefined;\n /** Сырые cookie в форме `имя=значение`, привязанные к origin API. */\n cookies?: string[] | undefined;\n /**\n * Идентификатор устройства для заголовка `X-Device-Id`.\n *\n * Сервер различает по нему записи в списке сессий, поэтому значение должно пережить\n * перезапуск процесса — иначе каждый старт бота порождает новую сессию. Библиотека\n * заводит его сама при первом запросе и хранит здесь.\n */\n deviceId?: string | undefined;\n /** Когда сессия получена, мс с начала эпохи. Нужно для диагностики. */\n obtainedAt?: number | undefined;\n}\n\n/**\n * Создаёт независимый снимок сессии.\n *\n * Сессии пересекают границу пользовательского кода и внутреннего состояния клиента.\n * Возвращать или сохранять их по ссылке нельзя: последующая мутация объекта или массива\n * cookie меняла бы уже сохранённую сессию без вызова `set()`.\n *\n * @internal\n */\nexport function copySession(session: ItdSession): ItdSession {\n return {\n ...session,\n ...(session.cookies ? { cookies: [...session.cookies] } : {}),\n };\n}\n\n/**\n * Хранилище сессии.\n *\n * Подключаемый компонент: библиотека не знает, где вы держите токены, и обращается к ним\n * только через этот интерфейс. Все методы могут быть как синхронными, так и асинхронными.\n *\n * @example Своё хранилище поверх AsyncStorage в React Native\n * ```ts\n * const backend = withCodec(createKeyValueStore({\n * get: (key) => AsyncStorage.getItem(key).then((value) => value ?? undefined),\n * set: (key, value) => AsyncStorage.setItem(key, value),\n * delete: (key) => AsyncStorage.removeItem(key),\n * }), { encode: JSON.stringify, decode: JSON.parse });\n * const storage = createTokenStorage(withNamespace(backend, 'my-app'));\n * ```\n */\nexport interface TokenStorage {\n /** Прочитать сессию. `null`, если её нет. */\n get(): ItdSession | null | Promise<ItdSession | null>;\n /** Сохранить сессию целиком. */\n set(session: ItdSession): void | Promise<void>;\n /** Удалить сессию. Вызывается при выходе и при неудачном обновлении токена. */\n clear(): void | Promise<void>;\n}\n\n/**\n * Хранилище в памяти процесса — вариант по умолчанию.\n *\n * Сессия теряется при перезапуске. Для долгоживущих ботов возьмите `FileTokenStorage`\n * из `itd-api/node`, для браузера — `LocalStorageTokenStorage` или\n * `SessionStorageTokenStorage` из `itd-api/web`.\n */\nexport class MemoryTokenStorage implements TokenStorage {\n readonly #store = new MemoryKeyValueStore<ItdSession>();\n\n constructor(initial?: ItdSession | null) {\n if (initial) this.#store.set(TOKEN_STORAGE_KEY, copySession(initial));\n }\n\n get(): ItdSession | null {\n const session = this.#store.get(TOKEN_STORAGE_KEY);\n return session ? copySession(session) : null;\n }\n\n set(session: ItdSession): void {\n this.#store.set(TOKEN_STORAGE_KEY, copySession(session));\n }\n\n clear(): void {\n this.#store.delete(TOKEN_STORAGE_KEY);\n }\n}\n\nconst TOKEN_STORAGE_KEY = 'session';\n\n/** Настройки доменного адаптера одной сессии. */\nexport interface TokenStorageAdapterOptions {\n /** Ключ сессии в backend. По умолчанию `session`. */\n key?: string | undefined;\n}\n\nfunction isPromise<T>(value: T | Promise<T>): value is Promise<T> {\n return (\n typeof value === 'object' && value !== null && typeof (value as Promise<T>).then === 'function'\n );\n}\n\n/**\n * Создаёт доменное хранилище сессии поверх общего {@link KeyValueStore}.\n *\n * @example\n * ```ts\n * const storage = createTokenStorage(withNamespace(redisStore, 'itd'));\n * ```\n */\nexport function createTokenStorage(\n store: KeyValueStore<ItdSession>,\n options: TokenStorageAdapterOptions = {},\n): TokenStorage {\n const key = options.key ?? TOKEN_STORAGE_KEY;\n if (typeof key !== 'string') throw new ItdConfigError('key TokenStorage должен быть строкой');\n const backend = createKeyValueStore(store);\n return {\n get() {\n const value = backend.get(key);\n if (isPromise(value)) {\n return value.then((session) => (session ? copySession(session) : null));\n }\n return value ? copySession(value) : null;\n },\n set: (session) => backend.set(key, copySession(session)),\n clear: () => backend.delete(key),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"storage-CCxn0Kkv.js","names":["#values","#store"],"sources":["../../src/core/key-value-store.ts","../../src/session/storage.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","import { ItdConfigError } from '../core/errors.js';\nimport {\n createKeyValueStore,\n type KeyValueStore,\n MemoryKeyValueStore,\n} from '../core/key-value-store.js';\n\n/**\n * Сохранённая сессия.\n *\n * Кроме токенов сюда попадают cookie: refresh-токен итд.com живёт именно в cookie, и без них\n * восстановить сессию после перезапуска процесса невозможно.\n */\nexport interface ItdSession {\n /** Токен доступа для заголовка `Authorization: Bearer`. */\n accessToken?: string | undefined;\n /**\n * Refresh-токен, если удалось получить его явным значением.\n *\n * Обычно сервер держит его в httpOnly-cookie и наружу не отдаёт — тогда поле останется\n * пустым, а обновление пойдёт через {@link ItdSession.cookies}.\n */\n refreshToken?: string | undefined;\n /** Сырые cookie в форме `имя=значение`, привязанные к origin API. */\n cookies?: string[] | undefined;\n /**\n * Идентификатор устройства для заголовка `X-Device-Id`.\n *\n * Сервер различает по нему записи в списке сессий, поэтому значение должно пережить\n * перезапуск процесса — иначе каждый старт бота порождает новую сессию. Библиотека\n * заводит его сама при первом запросе и хранит здесь.\n */\n deviceId?: string | undefined;\n /** Когда сессия получена, мс с начала эпохи. Нужно для диагностики. */\n obtainedAt?: number | undefined;\n}\n\n/**\n * Создаёт независимый снимок сессии.\n *\n * Сессии пересекают границу пользовательского кода и внутреннего состояния клиента.\n * Возвращать или сохранять их по ссылке нельзя: последующая мутация объекта или массива\n * cookie меняла бы уже сохранённую сессию без вызова `set()`.\n *\n * @internal\n */\nexport function copySession(session: ItdSession): ItdSession {\n return {\n ...session,\n ...(session.cookies ? { cookies: [...session.cookies] } : {}),\n };\n}\n\n/**\n * Хранилище сессии.\n *\n * Подключаемый компонент: библиотека не знает, где вы держите токены, и обращается к ним\n * только через этот интерфейс. Все методы могут быть как синхронными, так и асинхронными.\n *\n * @example Своё хранилище поверх AsyncStorage в React Native\n * ```ts\n * const backend = withCodec(createKeyValueStore({\n * get: (key) => AsyncStorage.getItem(key).then((value) => value ?? undefined),\n * set: (key, value) => AsyncStorage.setItem(key, value),\n * delete: (key) => AsyncStorage.removeItem(key),\n * }), { encode: JSON.stringify, decode: JSON.parse });\n * const storage = createTokenStorage(withNamespace(backend, 'my-app'));\n * ```\n */\nexport interface TokenStorage {\n /** Прочитать сессию. `null`, если её нет. */\n get(): ItdSession | null | Promise<ItdSession | null>;\n /** Сохранить сессию целиком. */\n set(session: ItdSession): void | Promise<void>;\n /** Удалить сессию. Вызывается при выходе и при неудачном обновлении токена. */\n clear(): void | Promise<void>;\n}\n\n/**\n * Хранилище в памяти процесса — вариант по умолчанию.\n *\n * Сессия теряется при перезапуске. Для долгоживущих ботов возьмите `FileTokenStorage`\n * из `itd-api/node`, для браузера — `LocalStorageTokenStorage` или\n * `SessionStorageTokenStorage` из `itd-api/web`.\n */\nexport class MemoryTokenStorage implements TokenStorage {\n readonly #store = new MemoryKeyValueStore<ItdSession>();\n\n constructor(initial?: ItdSession | null) {\n if (initial) this.#store.set(TOKEN_STORAGE_KEY, copySession(initial));\n }\n\n get(): ItdSession | null {\n const session = this.#store.get(TOKEN_STORAGE_KEY);\n return session ? copySession(session) : null;\n }\n\n set(session: ItdSession): void {\n this.#store.set(TOKEN_STORAGE_KEY, copySession(session));\n }\n\n clear(): void {\n this.#store.delete(TOKEN_STORAGE_KEY);\n }\n}\n\nconst TOKEN_STORAGE_KEY = 'session';\n\n/** Настройки доменного адаптера одной сессии. */\nexport interface TokenStorageAdapterOptions {\n /** Ключ сессии в backend. По умолчанию `session`. */\n key?: string | undefined;\n}\n\nfunction isPromise<T>(value: T | Promise<T>): value is Promise<T> {\n return (\n typeof value === 'object' && value !== null && typeof (value as Promise<T>).then === 'function'\n );\n}\n\n/**\n * Создаёт доменное хранилище сессии поверх общего {@link KeyValueStore}.\n *\n * @example\n * ```ts\n * const storage = createTokenStorage(withNamespace(redisStore, 'itd'));\n * ```\n */\nexport function createTokenStorage(\n store: KeyValueStore<ItdSession>,\n options: TokenStorageAdapterOptions = {},\n): TokenStorage {\n const key = options.key ?? TOKEN_STORAGE_KEY;\n if (typeof key !== 'string') throw new ItdConfigError('key TokenStorage должен быть строкой');\n const backend = createKeyValueStore(store);\n return {\n get() {\n const value = backend.get(key);\n if (isPromise(value)) {\n return value.then((session) => (session ? copySession(session) : null));\n }\n return value ? copySession(value) : null;\n },\n set: (session) => backend.set(key, copySession(session)),\n clear: () => backend.delete(key),\n };\n}\n"],"mappings":";;AAgDA,SAAS,SAAS,OAA4D;CAC5E,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,aAAa,OAAyD;CAC7E,MAAM,QAAQ;CACd,IAAI,CAAC,SAAS,KAAK,GAAG,MAAM,IAAI,eAAe,oCAAoC;CACnF,KAAK,MAAM,UAAU;EAAC;EAAO;EAAO;CAAQ,GAC1C,IAAI,OAAO,MAAM,YAAY,YAC3B,MAAM,IAAI,eAAe,iBAAiB,OAAO,sBAAsB;CAG3E,IAAI,MAAM,SAAS,KAAA,KAAa,OAAO,MAAM,SAAS,YACpD,MAAM,IAAI,eAAe,yCAAyC;AAEtE;;AAOA,SAAgB,oBAAuB,UAA8C;CACnF,aAAa,QAAQ;CACrB,OAAO;AACT;;AAGA,SAAgB,0BACd,OACqC;CACrC,OAAO,OAAO,MAAM,SAAS;AAC/B;;AAGA,eAAsB,yBACpB,OACA,QACmB;CACnB,MAAM,SAAS,MAAM,MAAM,KAAK,MAAM;CACtC,MAAM,SAAmB,CAAC;CAC1B,WAAW,MAAM,OAAO,QAAQ;EAC9B,IAAI,OAAO,QAAQ,UACjB,MAAM,IAAI,eAAe,sDAAsD;EAEjF,IAAI,WAAW,KAAA,KAAa,IAAI,WAAW,MAAM,GAAG,OAAO,KAAK,GAAG;CACrE;CACA,OAAO;AACT;;AAGA,IAAa,sBAAb,MAA0E;CACxE,0BAAmB,IAAI,IAAe;CAEtC,YAAY,SAA0C;EACpD,KAAK,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAG,KAAKA,QAAQ,IAAI,KAAK,KAAK;CACvE;CAEA,IAAI,KAA4B;EAC9B,OAAO,KAAKA,QAAQ,IAAI,GAAG;CAC7B;CAEA,IAAI,KAAa,OAAgB;EAC/B,KAAKA,QAAQ,IAAI,KAAK,KAAK;CAC7B;CAEA,OAAO,KAAmB;EACxB,KAAKA,QAAQ,OAAO,GAAG;CACzB;CAEA,KAAK,SAAS,IAAc;EAC1B,OAAO,CAAC,GAAG,KAAKA,QAAQ,KAAK,CAAC,CAAC,CAAC,QAAQ,QAAQ,IAAI,WAAW,MAAM,CAAC;CACxE;AACF;;AAaA,SAAgB,cACd,OACA,WACA,YAAY,KACM;CAClB,aAAa,KAAK;CAClB,IAAI,OAAO,cAAc,YAAY,OAAO,cAAc,UACxD,MAAM,IAAI,eAAe,0DAA0D;CAErF,MAAM,OAAO,UAAU,WAAW,IAAI,KAAK,GAAG,YAAY;CAC1D,MAAM,OAAO,UAAkB,GAAG,OAAO;CACzC,MAAM,aAA+B;EACnC,MAAM,UAAU,MAAM,IAAI,IAAI,KAAK,CAAC;EACpC,MAAM,OAAO,SAAS,MAAM,IAAI,IAAI,KAAK,GAAG,IAAI;EAChD,SAAS,UAAU,MAAM,OAAO,IAAI,KAAK,CAAC;CAC5C;CAEA,IAAI,0BAA0B,KAAK,GACjC,WAAW,OAAO,OAAO,SAAS,OAAO;EAEvC,QAAO,MADY,yBAAyB,OAAO,IAAI,MAAM,CAAC,EAAA,CAClD,KAAK,UAAU,MAAM,MAAM,KAAK,MAAM,CAAC;CACrD;CAEF,OAAO;AACT;;AAWA,SAAgB,UACd,OACA,OACkB;CAClB,aAAa,KAAK;CAClB,IACE,CAAC,SAAS,KAAK,KACf,OAAO,MAAM,WAAW,cACxB,OAAO,MAAM,WAAW,YAExB,MAAM,IAAI,eAAe,gDAAgD;CAE3E,MAAM,UAA4B;EAChC,MAAM,IAAI,KAAK;GACb,MAAM,QAAQ,MAAM,MAAM,IAAI,GAAG;GACjC,OAAO,UAAU,KAAA,IAAY,KAAA,IAAY,MAAM,OAAO,KAAK;EAC7D;EACA,MAAM,IAAI,KAAK,OAAO;GACpB,MAAM,MAAM,IAAI,KAAK,MAAM,MAAM,OAAO,KAAK,CAAC;EAChD;EACA,SAAS,QAAQ,MAAM,OAAO,GAAG;CACnC;CACA,IAAI,0BAA0B,KAAK,GAAG,QAAQ,QAAQ,WAAW,MAAM,KAAK,MAAM;CAClF,OAAO;AACT;;AAGA,SAAS,cAAoC;CAC3C,OAAO,OAAO,OAAO,IAAI;AAC3B;AAEA,SAAS,WAAc,QAAoE;CACzF,MAAM,OAAO,YAAe;CAC5B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU,CAAC,CAAC,GAAG,KAAK,OAAO;CACrE,OAAO;AACT;;;;;;;;AASA,SAAgB,0BACd,QAC4B;CAC5B,IACE,CAAC,SAAS,MAAM,KAChB,OAAO,OAAO,SAAS,cACvB,OAAO,OAAO,UAAU,YAExB,MAAM,IAAI,eAAe,yDAAyD;CAEpF,IAAI,OAAO,WAAW,KAAA,KAAa,OAAO,OAAO,WAAW,YAC1D,MAAM,IAAI,eAAe,uDAAuD;CAGlF,IAAI;CACJ,IAAI;CACJ,IAAI,SAAwB,QAAQ,QAAQ;CAE5C,MAAM,OAAO,YAAwC;EACnD,IAAI,cAAc,KAAA,GAAW,OAAO;EACpC,YAAY,QAAQ,QAAQ,OAAO,KAAK,CAAC,CAAC,CACvC,MAAM,UAAU;GACf,IAAI,UAAU,KAAA,KAAa,CAAC,SAAS,KAAK,GACxC,MAAM,IAAI,eAAe,wDAAwD;GAEnF,YAAY,WAAW,KAAgD;GACvE,OAAO;EACT,CAAC,CAAC,CACD,cAAc;GACb,UAAU,KAAA;EACZ,CAAC;EACH,OAAO;CACT;;;;;;;;CASA,MAAM,UAAU,UAAgE;EAC9E,MAAM,MAAM,YAA2B;GACrC,MAAM,QAAQ,WAAW,MAAM,KAAK,CAAC;GACrC,IAAI,CAAC,MAAM,KAAK,GAAG;GAEnB,IAAI,OAAO,UAAU,OAAO,KAAK,KAAK,CAAC,CAAC,WAAW,GAAG,MAAM,OAAO,OAAO;QACrE,MAAM,OAAO,MAAM,KAAK;GAC7B,YAAY;EACd;EACA,SAAS,OAAO,KAAK,KAAK,GAAG;EAC7B,OAAO;CACT;CAEA,OAAO;EACL,MAAM,IAAI,KAAK;GACb,MAAM,UAAU,MAAM,KAAK;GAC3B,OAAO,OAAO,OAAO,SAAS,GAAG,IAAI,QAAQ,OAAO,KAAA;EACtD;EACA,IAAI,KAAK,OAAO;GACd,OAAO,QAAQ,UAAU;IACvB,MAAM,OAAO;IACb,OAAO;GACT,CAAC;EACH;EACA,OAAO,KAAK;GACV,OAAO,QAAQ,UAAU;IACvB,IAAI,CAAC,OAAO,OAAO,OAAO,GAAG,GAAG,OAAO;IACvC,OAAO,MAAM;IACb,OAAO;GACT,CAAC;EACH;EACA,MAAM,KAAK,SAAS,IAAI;GACtB,MAAM,UAAU,MAAM,KAAK;GAC3B,OAAO,OAAO,KAAK,OAAO,CAAC,CAAC,QAAQ,QAAQ,IAAI,WAAW,MAAM,CAAC;EACpE;CACF;AACF;;;;;;;;;;;;ACpPA,SAAgB,YAAY,SAAiC;CAC3D,OAAO;EACL,GAAG;EACH,GAAI,QAAQ,UAAU,EAAE,SAAS,CAAC,GAAG,QAAQ,OAAO,EAAE,IAAI,CAAC;CAC7D;AACF;;;;;;;;AAkCA,IAAa,qBAAb,MAAwD;CACtD,SAAkB,IAAI,oBAAgC;CAEtD,YAAY,SAA6B;EACvC,IAAI,SAAS,KAAKC,OAAO,IAAI,mBAAmB,YAAY,OAAO,CAAC;CACtE;CAEA,MAAyB;EACvB,MAAM,UAAU,KAAKA,OAAO,IAAI,iBAAiB;EACjD,OAAO,UAAU,YAAY,OAAO,IAAI;CAC1C;CAEA,IAAI,SAA2B;EAC7B,KAAKA,OAAO,IAAI,mBAAmB,YAAY,OAAO,CAAC;CACzD;CAEA,QAAc;EACZ,KAAKA,OAAO,OAAO,iBAAiB;CACtC;AACF;AAEA,MAAM,oBAAoB;AAQ1B,SAAS,UAAa,OAA4C;CAChE,OACE,OAAO,UAAU,YAAY,UAAU,QAAQ,OAAQ,MAAqB,SAAS;AAEzF;;;;;;;;;AAUA,SAAgB,mBACd,OACA,UAAsC,CAAC,GACzB;CACd,MAAM,MAAM,QAAQ,OAAO;CAC3B,IAAI,OAAO,QAAQ,UAAU,MAAM,IAAI,eAAe,sCAAsC;CAC5F,MAAM,UAAU,oBAAoB,KAAK;CACzC,OAAO;EACL,MAAM;GACJ,MAAM,QAAQ,QAAQ,IAAI,GAAG;GAC7B,IAAI,UAAU,KAAK,GACjB,OAAO,MAAM,MAAM,YAAa,UAAU,YAAY,OAAO,IAAI,IAAK;GAExE,OAAO,QAAQ,YAAY,KAAK,IAAI;EACtC;EACA,MAAM,YAAY,QAAQ,IAAI,KAAK,YAAY,OAAO,CAAC;EACvD,aAAa,QAAQ,OAAO,GAAG;CACjC;AACF"}