itd-api 0.4.0 → 0.5.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.
- package/dist/index.cjs +2888 -1552
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +922 -416
- package/dist/index.d.ts +922 -416
- package/dist/index.js +2815 -1494
- package/dist/index.js.map +1 -1
- package/dist/{multi-storage-D4h6QA-w.js → multi-storage-B0r0AInH.js} +127 -83
- package/dist/multi-storage-B0r0AInH.js.map +1 -0
- package/dist/{multi-storage-DwaIh24p.d.ts → multi-storage-CLqmzq07.d.ts} +13 -39
- package/dist/{multi-storage-C8R5Crwo.cjs → multi-storage-mBuCOVZY.cjs} +164 -84
- package/dist/multi-storage-mBuCOVZY.cjs.map +1 -0
- package/dist/{multi-storage-DP_ujJw1.d.cts → multi-storage-s_PcWPGH.d.cts} +13 -39
- package/dist/node.cjs +62 -53
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +19 -5
- package/dist/node.d.ts +19 -5
- package/dist/node.js +62 -54
- package/dist/node.js.map +1 -1
- package/dist/{storage-D9tfHx7Z.js → storage-DWrK3Z4M.js} +201 -18
- package/dist/storage-DWrK3Z4M.js.map +1 -0
- package/dist/storage-Doe3lpFQ.d.cts +152 -0
- package/dist/storage-Doe3lpFQ.d.ts +152 -0
- package/dist/{storage-ycBqLBRB.cjs → storage-dF8Tio5y.cjs} +254 -17
- package/dist/storage-dF8Tio5y.cjs.map +1 -0
- package/dist/web.cjs +143 -40
- package/dist/web.cjs.map +1 -1
- package/dist/web.d.cts +40 -3
- package/dist/web.d.ts +40 -3
- package/dist/web.js +141 -41
- package/dist/web.js.map +1 -1
- package/package.json +3 -3
- package/dist/multi-storage-C8R5Crwo.cjs.map +0 -1
- package/dist/multi-storage-D4h6QA-w.js.map +0 -1
- package/dist/runtime-CFEsf-jD.cjs +0 -185
- package/dist/runtime-CFEsf-jD.cjs.map +0 -1
- package/dist/runtime-DHxDn8gf.js +0 -126
- package/dist/runtime-DHxDn8gf.js.map +0 -1
- package/dist/storage-BjNRlkbE.d.cts +0 -82
- package/dist/storage-BjNRlkbE.d.ts +0 -82
- package/dist/storage-D9tfHx7Z.js.map +0 -1
- package/dist/storage-ycBqLBRB.cjs.map +0 -1
package/dist/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.js","names":["#path","#writing","#enqueue","#inner","#read"],"sources":["../src/node.ts"],"sourcesContent":["/**\n * `itd-api/node` — файловая система для Node, Bun и Deno.\n *\n * Здесь лежит только то, что требует `node:fs` и потому не может попасть в основной бандл:\n * иначе браузерные сборщики пытались бы разрешить этот модуль. Клиент, аккаунты, билдеры,\n * типы и всё остальное берутся из `itd-api` — одинаково на всех платформах.\n *\n * @example\n * ```ts\n * import { ItdClient } from 'itd-api';\n * import { FileTokenStorage, fromPath } from 'itd-api/node';\n *\n * // Когда сессия уже сохранена, `auth` не нужен — токен возьмётся из хранилища.\n * const itd = new ItdClient({ storage: new FileTokenStorage('./.itd-session.json') });\n *\n * await itd.posts.create((p) => p.content('привет').attach(fromPath('./photo.jpg')));\n * ```\n *\n * @packageDocumentation\n */\n\nimport { boundedFileStream } from './core/attachments/bounded-stream.js';\nimport {\n type FileStreamOptions,\n FileTransferMode,\n type LazyFile,\n type StreamFile,\n} from './core/attachments/contracts.js';\nimport { resolveFileStreamOptions } from './core/attachments/options.js';\nimport { ItdConfigError, ItdFileError, ItdFileErrorReason } from './core/errors.js';\nimport { createRecordMultiStorage, type MultiTokenStorage } from './core/multi-storage.js';\nimport { copySession, type ItdSession, type TokenStorage } from './core/storage.js';\n\n/** Проверяет код системной ошибки без привязки к типам конкретного рантайма. */\nfunction hasErrorCode(error: unknown, code: string): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && error.code === code;\n}\n\n/** Что можно уточнить у файла с диска. */\nexport interface PathFileOptions extends FileStreamOptions {\n /** Имя файла. По умолчанию — имя из пути. */\n filename?: string | undefined;\n /** MIME-тип. По умолчанию определяется по расширению. */\n contentType?: string | undefined;\n}\n\n/**\n * Вложение из файла на диске.\n *\n * Клиент настраивать не нужно: чтение диска приезжает вместе со значением, а не через\n * опцию, поэтому один и тот же клиент принимает и файлы с диска, и всё остальное.\n *\n * По умолчанию файл читается целиком непосредственно перед отправкой. Режим\n * `{ mode: 'stream' }` передаёт его частями и открывает заново при повторной попытке.\n * Модуль `node:fs` подключается динамически.\n *\n * @example\n * ```ts\n * import { ItdClient } from 'itd-api';\n * import { fromPath } from 'itd-api/node';\n *\n * const itd = new ItdClient({ auth });\n *\n * await itd.posts.create((p) =>\n * p.content('смотрите').attach(fromPath('./photo.jpg')),\n * );\n * ```\n */\nexport function fromPath(\n path: string,\n options: PathFileOptions & { mode: typeof FileTransferMode.Stream },\n): StreamFile;\nexport function fromPath(\n path: string,\n options?: PathFileOptions & { mode?: typeof FileTransferMode.Buffer },\n): LazyFile;\nexport function fromPath(path: string, options: PathFileOptions): LazyFile | StreamFile;\nexport function fromPath(path: string, options: PathFileOptions = {}): LazyFile | StreamFile {\n const resolved = resolveFileStreamOptions(options);\n\n if (resolved.mode === FileTransferMode.Stream) {\n return {\n open: async ({ signal }) => {\n const [{ createReadStream }, { stat }, { basename }, { Readable }] = await Promise.all([\n import('node:fs'),\n import('node:fs/promises'),\n import('node:path'),\n import('node:stream'),\n ]);\n const info = await stat(path);\n if (resolved.maxBytes !== undefined && info.size > resolved.maxBytes) {\n throw new ItdFileError(\n `файл ${path} больше предела в ${resolved.maxBytes} байт: ${info.size}`,\n {\n reason: ItdFileErrorReason.TooLarge,\n limit: resolved.maxBytes,\n actual: info.size,\n },\n );\n }\n\n const file = createReadStream(path, {\n highWaterMark: Math.min(resolved.streamBufferBytes, 1024 * 1024),\n ...(signal ? { signal } : {}),\n });\n const source = Readable.toWeb(file) as ReadableStream<Uint8Array>;\n return {\n stream: boundedFileStream(source, {\n ...(resolved.maxBytes !== undefined ? { maxBytes: resolved.maxBytes } : {}),\n streamBufferBytes: resolved.streamBufferBytes,\n ...(signal ? { signal } : {}),\n }),\n filename: options.filename ?? basename(path),\n ...(options.contentType ? { contentType: options.contentType } : {}),\n size: info.size,\n close: () => {\n file.destroy();\n },\n };\n },\n };\n }\n\n return {\n load: async ({ signal }) => {\n const [{ readFile, stat }, { basename }] = await Promise.all([\n import('node:fs/promises'),\n import('node:path'),\n ]);\n if (resolved.maxBytes !== undefined) {\n const info = await stat(path);\n if (info.size > resolved.maxBytes) {\n throw new ItdFileError(\n `файл ${path} больше предела в ${resolved.maxBytes} байт: ${info.size}`,\n {\n reason: ItdFileErrorReason.TooLarge,\n limit: resolved.maxBytes,\n actual: info.size,\n },\n );\n }\n }\n\n const file = new Uint8Array(await readFile(path, signal ? { signal } : undefined));\n if (resolved.maxBytes !== undefined && file.byteLength > resolved.maxBytes) {\n throw new ItdFileError(\n `файл ${path} больше предела в ${resolved.maxBytes} байт: ${file.byteLength}`,\n {\n reason: ItdFileErrorReason.TooLarge,\n limit: resolved.maxBytes,\n actual: file.byteLength,\n },\n );\n }\n\n return {\n file,\n filename: options.filename ?? basename(path),\n ...(options.contentType ? { contentType: options.contentType } : {}),\n };\n },\n };\n}\n\n/**\n * Читает и разбирает JSON-файл.\n *\n * Отсутствующий файл означает `null`. Остальные ошибки файловой системы не скрываются.\n * Одиночное хранилище ради обратной совместимости считает повреждённый JSON пустым,\n * а мультихранилище требует строгого разбора: молча затереть файл с несколькими токенами\n * особенно опасно.\n */\nasync function readJsonFile(path: string, strict = false): Promise<unknown> {\n let raw: string;\n try {\n const { readFile } = await import('node:fs/promises');\n raw = await readFile(path, 'utf8');\n } catch (error) {\n if (hasErrorCode(error, 'ENOENT')) return null;\n throw error;\n }\n\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n if (!strict) return null;\n throw new ItdConfigError(\n `Файл ${path} повреждён: ожидался JSON с сессиями аккаунтов. ` +\n 'Исправьте файл или перенесите его перед следующим сохранением.',\n );\n }\n}\n\n/**\n * Записывает JSON во временный файл и переименовывает его поверх целевого.\n *\n * Права `0600` (чтение и запись только владельцу) — в файле лежат токены. Переименование\n * атомарно, поэтому падение процесса посреди сохранения не оставит повреждённого файла.\n */\nasync function writeJsonAtomic(path: string, value: unknown): Promise<void> {\n const { writeFile, rename, unlink } = await import('node:fs/promises');\n\n // Временный файл уникален на процесс и вызов — иначе параллельные записи (в том числе\n // из разных процессов) делят один `.tmp`.\n const pid = typeof process !== 'undefined' ? process.pid : 0;\n const temporary = `${path}.${pid}.${Math.random().toString(36).slice(2)}.tmp`;\n\n try {\n await writeFile(temporary, JSON.stringify(value, null, 2), { encoding: 'utf8', mode: 0o600 });\n await rename(temporary, path);\n } catch (error) {\n await unlink(temporary).catch(() => {});\n throw error;\n }\n}\n\n/** Удаляет файл. Благодаря `force` отсутствие файла ошибкой не считается. */\nasync function removeFile(path: string): Promise<void> {\n const { rm } = await import('node:fs/promises');\n await rm(path, { force: true });\n}\n\n/**\n * Хранит сессию в файле.\n *\n * Нужна долгоживущим процессам: без неё бот при каждом запуске входит заново — а вход\n * требует решённой капчи, да и серия входов подряд может привести к временной блокировке\n * аккаунта. С сохранённой сессией опция `auth` не нужна вовсе.\n *\n * Файл создаётся с правами `0600` (чтение и запись только владельцу) — в нём лежат токены.\n * Запись идёт через временный файл с последующим переименованием, поэтому падение процесса\n * посреди сохранения не оставит повреждённую сессию.\n *\n * @example\n * ```ts\n * const itd = new ItdClient({ storage: new FileTokenStorage('./.itd-session.json') });\n * ```\n */\nexport class FileTokenStorage implements TokenStorage {\n readonly #path: string;\n /**\n * Цепочка операций с файлом. Запись и удаление выполняются последовательно\n * в порядке вызова; ошибка одной операции не останавливает следующие.\n */\n #writing: Promise<void> = Promise.resolve();\n\n /** @param path путь к файлу сессии. Добавьте его в `.gitignore`. */\n constructor(path: string) {\n this.#path = path;\n }\n\n async get(): Promise<ItdSession | null> {\n // Чтение, начатое сразу после `set()`/`clear()`, должно видеть результат более ранней\n // операции, даже если вызывающий код не сохранил и не дождался её промиса.\n await this.#writing.then(\n () => undefined,\n () => undefined,\n );\n const parsed = await readJsonFile(this.#path);\n return typeof parsed === 'object' && parsed !== null ? copySession(parsed as ItdSession) : null;\n }\n\n set(session: ItdSession): Promise<void> {\n // Пользователь может изменить переданный объект сразу после вызова `set()`.\n // В очередь должен попасть снимок на момент вызова, а не живая ссылка.\n const snapshot = copySession(session);\n return this.#enqueue(() => writeJsonAtomic(this.#path, snapshot));\n }\n\n clear(): Promise<void> {\n return this.#enqueue(() => removeFile(this.#path));\n }\n\n /** Добавляет файловую операцию в очередь. */\n #enqueue(operation: () => Promise<void>): Promise<void> {\n this.#writing = this.#writing.then(operation, operation);\n return this.#writing;\n }\n}\n\n/** Версия формата файла с сессиями нескольких аккаунтов. */\nconst SESSIONS_FILE_VERSION = 1;\n\n/**\n * Хранит сессии нескольких аккаунтов в одном файле.\n *\n * Разбирается с гонкой «прочитать, изменить, записать»: десять аккаунтов пишут в один файл,\n * и без общего слепка с очередью записей они теряли бы сессии друг друга. Как и\n * {@link FileTokenStorage}, пишет через временный файл с правами `0600`.\n *\n * Формат — конверт `{ version, accounts }`, а не голая карта: так файл нескольких аккаунтов\n * не спутать с однопользовательским, и чужой не будет молча перезаписан.\n *\n * @example\n * ```ts\n * const accounts = new ItdAccounts({\n * storage: new FileMultiTokenStorage('./.itd-sessions.json'),\n * });\n * await accounts.restore();\n * ```\n */\nexport class FileMultiTokenStorage implements MultiTokenStorage {\n readonly #path: string;\n readonly #inner: MultiTokenStorage;\n\n /** @param path путь к файлу сессий. Добавьте его в `.gitignore`. */\n constructor(path: string) {\n this.#path = path;\n this.#inner = createRecordMultiStorage({\n read: () => this.#read(),\n write: (accounts) => writeJsonAtomic(path, { version: SESSIONS_FILE_VERSION, accounts }),\n // Последний аккаунт ушёл — файл с пустой картой выглядел бы мусором.\n remove: () => removeFile(path),\n });\n }\n\n get(account: string): Promise<ItdSession | null> {\n return Promise.resolve(this.#inner.get(account));\n }\n\n set(account: string, session: ItdSession): Promise<void> {\n return Promise.resolve(this.#inner.set(account, session));\n }\n\n clear(account: string): Promise<void> {\n return Promise.resolve(this.#inner.clear(account));\n }\n\n accounts(): Promise<readonly string[]> {\n return Promise.resolve(this.#inner.accounts());\n }\n\n async #read(): Promise<Record<string, ItdSession> | null> {\n const parsed = await readJsonFile(this.#path, true);\n if (parsed === null) return null;\n\n const envelope =\n typeof parsed === 'object' && parsed !== null\n ? (parsed as { version?: unknown; accounts?: unknown })\n : undefined;\n const accounts = envelope?.accounts;\n\n if (\n envelope?.version !== SESSIONS_FILE_VERSION ||\n typeof accounts !== 'object' ||\n accounts === null ||\n Array.isArray(accounts)\n ) {\n throw new ItdConfigError(\n `Файл ${this.#path} имеет неподдерживаемый формат: ожидается ` +\n `{ version: ${SESSIONS_FILE_VERSION}, accounts }. ` +\n 'Возможно, это файл одиночной сессии или файл другой версии — ' +\n 'возьмите отдельный путь, чтобы не перезаписать его.',\n );\n }\n\n return accounts as Record<string, ItdSession>;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"node.js","names":["#path","#inner","#read"],"sources":["../src/node.ts"],"sourcesContent":["/**\n * `itd-api/node` — файловая система для Node, Bun и Deno.\n *\n * Здесь лежит только то, что требует `node:fs` и потому не может попасть в основной бандл:\n * иначе браузерные сборщики пытались бы разрешить этот модуль. Клиент, аккаунты, билдеры,\n * типы и всё остальное берутся из `itd-api` — одинаково на всех платформах.\n *\n * @example\n * ```ts\n * import { ItdClient } from 'itd-api';\n * import { FileTokenStorage, fromPath } from 'itd-api/node';\n *\n * // Когда сессия уже сохранена, `auth` не нужен — токен возьмётся из хранилища.\n * const itd = new ItdClient({ storage: new FileTokenStorage('./.itd-session.json') });\n *\n * await itd.posts.create((p) => p.content('привет').attach(fromPath('./photo.jpg')));\n * ```\n *\n * @packageDocumentation\n */\n\nimport { boundedFileStream } from './core/attachments/bounded-stream.js';\nimport {\n type FileStreamOptions,\n FileTransferMode,\n type LazyFile,\n type StreamFile,\n} from './core/attachments/contracts.js';\nimport { resolveFileStreamOptions } from './core/attachments/options.js';\nimport { ItdConfigError, ItdFileError, ItdFileErrorReason } from './core/errors.js';\nimport { createRecordKeyValueStore, type EnumerableKeyValueStore } from './core/key-value-store.js';\nimport { createMultiTokenStorage, type MultiTokenStorage } from './core/multi-storage.js';\nimport { createTokenStorage, type ItdSession, type TokenStorage } from './core/storage.js';\n\n/** Проверяет код системной ошибки без привязки к типам конкретного рантайма. */\nfunction hasErrorCode(error: unknown, code: string): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && error.code === code;\n}\n\n/** Что можно уточнить у файла с диска. */\nexport interface PathFileOptions extends FileStreamOptions {\n /** Имя файла. По умолчанию — имя из пути. */\n filename?: string | undefined;\n /** MIME-тип. По умолчанию определяется по расширению. */\n contentType?: string | undefined;\n}\n\n/**\n * Вложение из файла на диске.\n *\n * Клиент настраивать не нужно: чтение диска приезжает вместе со значением, а не через\n * опцию, поэтому один и тот же клиент принимает и файлы с диска, и всё остальное.\n *\n * По умолчанию файл читается целиком непосредственно перед отправкой. Режим\n * `{ mode: 'stream' }` передаёт его частями и открывает заново при повторной попытке.\n * Модуль `node:fs` подключается динамически.\n *\n * @example\n * ```ts\n * import { ItdClient } from 'itd-api';\n * import { fromPath } from 'itd-api/node';\n *\n * const itd = new ItdClient({ auth });\n *\n * await itd.posts.create((p) =>\n * p.content('смотрите').attach(fromPath('./photo.jpg')),\n * );\n * ```\n */\nexport function fromPath(\n path: string,\n options: PathFileOptions & { mode: typeof FileTransferMode.Stream },\n): StreamFile;\nexport function fromPath(\n path: string,\n options?: PathFileOptions & { mode?: typeof FileTransferMode.Buffer },\n): LazyFile;\nexport function fromPath(path: string, options: PathFileOptions): LazyFile | StreamFile;\nexport function fromPath(path: string, options: PathFileOptions = {}): LazyFile | StreamFile {\n const resolved = resolveFileStreamOptions(options);\n\n if (resolved.mode === FileTransferMode.Stream) {\n return {\n open: async ({ signal }) => {\n const [{ createReadStream }, { stat }, { basename }, { Readable }] = await Promise.all([\n import('node:fs'),\n import('node:fs/promises'),\n import('node:path'),\n import('node:stream'),\n ]);\n const info = await stat(path);\n if (resolved.maxBytes !== undefined && info.size > resolved.maxBytes) {\n throw new ItdFileError(\n `файл ${path} больше предела в ${resolved.maxBytes} байт: ${info.size}`,\n {\n reason: ItdFileErrorReason.TooLarge,\n limit: resolved.maxBytes,\n actual: info.size,\n },\n );\n }\n\n const file = createReadStream(path, {\n highWaterMark: Math.min(resolved.streamBufferBytes, 1024 * 1024),\n ...(signal ? { signal } : {}),\n });\n const source = Readable.toWeb(file) as ReadableStream<Uint8Array>;\n return {\n stream: boundedFileStream(source, {\n ...(resolved.maxBytes !== undefined ? { maxBytes: resolved.maxBytes } : {}),\n streamBufferBytes: resolved.streamBufferBytes,\n ...(signal ? { signal } : {}),\n }),\n filename: options.filename ?? basename(path),\n ...(options.contentType ? { contentType: options.contentType } : {}),\n size: info.size,\n close: () => {\n file.destroy();\n },\n };\n },\n };\n }\n\n return {\n load: async ({ signal }) => {\n const [{ readFile, stat }, { basename }] = await Promise.all([\n import('node:fs/promises'),\n import('node:path'),\n ]);\n if (resolved.maxBytes !== undefined) {\n const info = await stat(path);\n if (info.size > resolved.maxBytes) {\n throw new ItdFileError(\n `файл ${path} больше предела в ${resolved.maxBytes} байт: ${info.size}`,\n {\n reason: ItdFileErrorReason.TooLarge,\n limit: resolved.maxBytes,\n actual: info.size,\n },\n );\n }\n }\n\n const file = new Uint8Array(await readFile(path, signal ? { signal } : undefined));\n if (resolved.maxBytes !== undefined && file.byteLength > resolved.maxBytes) {\n throw new ItdFileError(\n `файл ${path} больше предела в ${resolved.maxBytes} байт: ${file.byteLength}`,\n {\n reason: ItdFileErrorReason.TooLarge,\n limit: resolved.maxBytes,\n actual: file.byteLength,\n },\n );\n }\n\n return {\n file,\n filename: options.filename ?? basename(path),\n ...(options.contentType ? { contentType: options.contentType } : {}),\n };\n },\n };\n}\n\n/**\n * Читает и разбирает JSON-файл.\n *\n * Отсутствующий файл означает `undefined`. Повреждённый JSON не считается пустым хранилищем:\n * иначе следующая запись могла бы незаметно затереть данные.\n */\nasync function readJsonFile(path: string): Promise<unknown> {\n let raw: string;\n try {\n const { readFile } = await import('node:fs/promises');\n raw = await readFile(path, 'utf8');\n } catch (error) {\n if (hasErrorCode(error, 'ENOENT')) return undefined;\n throw error;\n }\n\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n throw new ItdConfigError(\n `Файл ${path} повреждён: ожидался JSON key-value хранилища. ` +\n 'Исправьте файл или перенесите его перед следующим сохранением.',\n );\n }\n}\n\n/**\n * Записывает JSON во временный файл и переименовывает его поверх целевого.\n *\n * Права `0600` (чтение и запись только владельцу) — в файле лежат токены. Переименование\n * атомарно, поэтому падение процесса посреди сохранения не оставит повреждённого файла.\n */\nasync function writeJsonAtomic(path: string, value: unknown): Promise<void> {\n const { writeFile, rename, unlink } = await import('node:fs/promises');\n\n // Временный файл уникален на процесс и вызов — иначе параллельные записи (в том числе\n // из разных процессов) делят один `.tmp`.\n const pid = typeof process !== 'undefined' ? process.pid : 0;\n const temporary = `${path}.${pid}.${Math.random().toString(36).slice(2)}.tmp`;\n\n try {\n await writeFile(temporary, JSON.stringify(value, null, 2), { encoding: 'utf8', mode: 0o600 });\n await rename(temporary, path);\n } catch (error) {\n await unlink(temporary).catch(() => {});\n throw error;\n }\n}\n\n/** Удаляет файл. Благодаря `force` отсутствие файла ошибкой не считается. */\nasync function removeFile(path: string): Promise<void> {\n const { rm } = await import('node:fs/promises');\n await rm(path, { force: true });\n}\n\n/** Версия общего формата файлового key-value backend. */\nconst FILE_STORE_VERSION = 1;\n\n/**\n * Key-value backend в одном JSON-файле.\n *\n * Изменения сериализуются внутри экземпляра и записываются атомарным переименованием. Несколько\n * экземпляров или процессов, направленных на один путь, требуют внешней синхронизации.\n */\nexport class FileKeyValueStore<T> {\n readonly #path: string;\n readonly #inner: EnumerableKeyValueStore<T>;\n\n /** @param path путь к JSON-файлу. Для секретов добавьте его в `.gitignore`. */\n constructor(path: string) {\n this.#path = path;\n this.#inner = createRecordKeyValueStore<T>({\n read: () => this.#read(),\n write: (values) => writeJsonAtomic(path, { version: FILE_STORE_VERSION, values }),\n delete: () => removeFile(path),\n });\n }\n\n get(key: string): Promise<T | undefined> {\n return Promise.resolve(this.#inner.get(key));\n }\n\n set(key: string, value: T): Promise<void> {\n return Promise.resolve(this.#inner.set(key, value));\n }\n\n delete(key: string): Promise<void> {\n return Promise.resolve(this.#inner.delete(key));\n }\n\n keys(prefix?: string): Promise<Iterable<string> | AsyncIterable<string>> {\n return Promise.resolve(this.#inner.keys(prefix));\n }\n\n async #read(): Promise<Readonly<Record<string, T>> | undefined> {\n const parsed = await readJsonFile(this.#path);\n if (parsed === undefined) return undefined;\n const envelope =\n typeof parsed === 'object' && parsed !== null\n ? (parsed as { version?: unknown; values?: unknown })\n : undefined;\n if (\n envelope?.version !== FILE_STORE_VERSION ||\n typeof envelope.values !== 'object' ||\n envelope.values === null ||\n Array.isArray(envelope.values)\n ) {\n throw new ItdConfigError(\n `Файл ${this.#path} имеет неподдерживаемый формат: ожидается ` +\n `{ version: ${FILE_STORE_VERSION}, values }. Возьмите другой путь, чтобы не ` +\n 'перезаписать посторонние данные.',\n );\n }\n return envelope.values as Readonly<Record<string, T>>;\n }\n}\n\n/**\n * Хранит сессию в файле.\n *\n * Нужна долгоживущим процессам: без неё бот при каждом запуске входит заново — а вход\n * требует решённой капчи, да и серия входов подряд может привести к временной блокировке\n * аккаунта. С сохранённой сессией опция `auth` не нужна вовсе.\n *\n * Файл создаётся с правами `0600` (чтение и запись только владельцу) — в нём лежат токены.\n * Запись идёт через временный файл с последующим переименованием, поэтому падение процесса\n * посреди сохранения не оставит повреждённую сессию.\n *\n * @example\n * ```ts\n * const itd = new ItdClient({ storage: new FileTokenStorage('./.itd-session.json') });\n * ```\n */\nexport class FileTokenStorage implements TokenStorage {\n readonly #inner: TokenStorage;\n\n /** @param path путь к файлу сессии. Добавьте его в `.gitignore`. */\n constructor(path: string) {\n this.#inner = createTokenStorage(new FileKeyValueStore<ItdSession>(path));\n }\n\n get(): Promise<ItdSession | null> {\n return Promise.resolve(this.#inner.get());\n }\n\n set(session: ItdSession): Promise<void> {\n return Promise.resolve(this.#inner.set(session));\n }\n\n clear(): Promise<void> {\n return Promise.resolve(this.#inner.clear());\n }\n}\n\n/**\n * Хранит сессии нескольких аккаунтов в одном файле.\n *\n * Разбирается с гонкой «прочитать, изменить, записать»: десять аккаунтов пишут в один файл,\n * и без общего слепка с очередью записей они теряли бы сессии друг друга. Как и\n * {@link FileTokenStorage}, пишет через временный файл с правами `0600`.\n *\n * Использует тот же версионированный {@link FileKeyValueStore}, что и одиночное хранилище.\n *\n * @example\n * ```ts\n * const accounts = new ItdAccounts({\n * storage: new FileMultiTokenStorage('./.itd-sessions.json'),\n * });\n * await accounts.restore();\n * ```\n */\nexport class FileMultiTokenStorage implements MultiTokenStorage {\n readonly #inner: MultiTokenStorage;\n\n /** @param path путь к файлу сессий. Добавьте его в `.gitignore`. */\n constructor(path: string) {\n this.#inner = createMultiTokenStorage(new FileKeyValueStore<ItdSession>(path));\n }\n\n get(account: string): Promise<ItdSession | null> {\n return Promise.resolve(this.#inner.get(account));\n }\n\n set(account: string, session: ItdSession): Promise<void> {\n return Promise.resolve(this.#inner.set(account, session));\n }\n\n clear(account: string): Promise<void> {\n return Promise.resolve(this.#inner.clear(account));\n }\n\n accounts(): Promise<readonly string[]> {\n return Promise.resolve(this.#inner.accounts());\n }\n}\n"],"mappings":"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"}
|
|
@@ -15,6 +15,8 @@ const ItdErrorKind = Object.freeze({
|
|
|
15
15
|
Timeout: "timeout",
|
|
16
16
|
/** Запрос отменён через `AbortSignal`. */
|
|
17
17
|
Abort: "abort",
|
|
18
|
+
/** Операция невозможна в текущем состоянии объекта. */
|
|
19
|
+
State: "state",
|
|
18
20
|
/** Не удалось получить или подготовить содержимое вложения. */
|
|
19
21
|
File: "file",
|
|
20
22
|
/** Некорректная конфигурация или аргументы — обнаружено до обращения к сети. */
|
|
@@ -300,14 +302,26 @@ var ItdAbortError = class extends ItdError {
|
|
|
300
302
|
}
|
|
301
303
|
};
|
|
302
304
|
/**
|
|
305
|
+
* Операция невозможна в текущем состоянии объекта.
|
|
306
|
+
*
|
|
307
|
+
* Например, клиент уже окончательно освобождён через `dispose()` и не может выполнять
|
|
308
|
+
* новые запросы или создавать realtime-потоки.
|
|
309
|
+
*/
|
|
310
|
+
var ItdStateError = class extends ItdError {
|
|
311
|
+
constructor(message, options) {
|
|
312
|
+
super(ItdErrorKind.State, message, options);
|
|
313
|
+
this.name = "ItdStateError";
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
/**
|
|
303
317
|
* Некорректная конфигурация или аргументы — обнаружено до обращения к сети.
|
|
304
318
|
*
|
|
305
319
|
* Этим же классом сообщают о нарушенных инвариантах билдеры: например, опрос
|
|
306
320
|
* с одним вариантом ответа.
|
|
307
321
|
*/
|
|
308
322
|
var ItdConfigError = class extends ItdError {
|
|
309
|
-
constructor(message) {
|
|
310
|
-
super(ItdErrorKind.Config, message);
|
|
323
|
+
constructor(message, options) {
|
|
324
|
+
super(ItdErrorKind.Config, message, options);
|
|
311
325
|
this.name = "ItdConfigError";
|
|
312
326
|
}
|
|
313
327
|
};
|
|
@@ -323,6 +337,10 @@ function isItdApiError(value) {
|
|
|
323
337
|
function isItdFileError(value) {
|
|
324
338
|
return isItdError(value) && value.kind === ItdErrorKind.File;
|
|
325
339
|
}
|
|
340
|
+
/** Операция невозможна в текущем состоянии объекта. */
|
|
341
|
+
function isItdStateError(value) {
|
|
342
|
+
return isItdError(value) && value.kind === ItdErrorKind.State;
|
|
343
|
+
}
|
|
326
344
|
/**
|
|
327
345
|
* Проверяет разновидность ошибки API.
|
|
328
346
|
*
|
|
@@ -366,6 +384,158 @@ function isItdServerError(value) {
|
|
|
366
384
|
return hasApiKind(value, ItdApiErrorKind.Server);
|
|
367
385
|
}
|
|
368
386
|
//#endregion
|
|
387
|
+
//#region src/core/key-value-store.ts
|
|
388
|
+
function isRecord(value) {
|
|
389
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
390
|
+
}
|
|
391
|
+
function requireStore(value) {
|
|
392
|
+
const store = value;
|
|
393
|
+
if (!isRecord(store)) throw new ItdConfigError("KeyValueStore должен быть объектом");
|
|
394
|
+
for (const method of [
|
|
395
|
+
"get",
|
|
396
|
+
"set",
|
|
397
|
+
"delete"
|
|
398
|
+
]) if (typeof store[method] !== "function") throw new ItdConfigError(`KeyValueStore.${method} должен быть функцией`);
|
|
399
|
+
if (store.keys !== void 0 && typeof store.keys !== "function") throw new ItdConfigError("KeyValueStore.keys должен быть функцией");
|
|
400
|
+
}
|
|
401
|
+
/** Собирает {@link KeyValueStore} из функций конкретного backend. */
|
|
402
|
+
function createKeyValueStore(handlers) {
|
|
403
|
+
requireStore(handlers);
|
|
404
|
+
return handlers;
|
|
405
|
+
}
|
|
406
|
+
/** Проверяет доступность перечисления ключей. */
|
|
407
|
+
function isEnumerableKeyValueStore(store) {
|
|
408
|
+
return typeof store.keys === "function";
|
|
409
|
+
}
|
|
410
|
+
/** @internal */
|
|
411
|
+
async function collectKeyValueStoreKeys(store, prefix) {
|
|
412
|
+
const source = await store.keys(prefix);
|
|
413
|
+
const result = [];
|
|
414
|
+
for await (const key of source) {
|
|
415
|
+
if (typeof key !== "string") throw new ItdConfigError("KeyValueStore.keys() должен возвращать только строки");
|
|
416
|
+
if (prefix === void 0 || key.startsWith(prefix)) result.push(key);
|
|
417
|
+
}
|
|
418
|
+
return result;
|
|
419
|
+
}
|
|
420
|
+
/** Key-value backend в памяти процесса. Значения сохраняются по ссылке. */
|
|
421
|
+
var MemoryKeyValueStore = class {
|
|
422
|
+
#values = /* @__PURE__ */ new Map();
|
|
423
|
+
constructor(initial) {
|
|
424
|
+
for (const [key, value] of initial ?? []) this.#values.set(key, value);
|
|
425
|
+
}
|
|
426
|
+
get(key) {
|
|
427
|
+
return this.#values.get(key);
|
|
428
|
+
}
|
|
429
|
+
set(key, value) {
|
|
430
|
+
this.#values.set(key, value);
|
|
431
|
+
}
|
|
432
|
+
delete(key) {
|
|
433
|
+
this.#values.delete(key);
|
|
434
|
+
}
|
|
435
|
+
keys(prefix = "") {
|
|
436
|
+
return [...this.#values.keys()].filter((key) => key.startsWith(prefix));
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
/** Добавляет namespace ко всем ключам, не меняя исходный backend. */
|
|
440
|
+
function withNamespace(store, namespace, separator = ":") {
|
|
441
|
+
requireStore(store);
|
|
442
|
+
if (typeof namespace !== "string" || typeof separator !== "string") throw new ItdConfigError("namespace и separator KeyValueStore должны быть строками");
|
|
443
|
+
const base = namespace.length === 0 ? "" : `${namespace}${separator}`;
|
|
444
|
+
const key = (value) => `${base}${value}`;
|
|
445
|
+
const namespaced = {
|
|
446
|
+
get: (value) => store.get(key(value)),
|
|
447
|
+
set: (value, data) => store.set(key(value), data),
|
|
448
|
+
delete: (value) => store.delete(key(value))
|
|
449
|
+
};
|
|
450
|
+
if (isEnumerableKeyValueStore(store)) namespaced.keys = async (prefix = "") => {
|
|
451
|
+
return (await collectKeyValueStoreKeys(store, key(prefix))).map((value) => value.slice(base.length));
|
|
452
|
+
};
|
|
453
|
+
return namespaced;
|
|
454
|
+
}
|
|
455
|
+
/** Преобразует значения на границе backend, сохраняя его пространство ключей. */
|
|
456
|
+
function withCodec(store, codec) {
|
|
457
|
+
requireStore(store);
|
|
458
|
+
if (!isRecord(codec) || typeof codec.encode !== "function" || typeof codec.decode !== "function") throw new ItdConfigError("codec должен содержать функции encode и decode");
|
|
459
|
+
const encoded = {
|
|
460
|
+
async get(key) {
|
|
461
|
+
const value = await store.get(key);
|
|
462
|
+
return value === void 0 ? void 0 : codec.decode(value);
|
|
463
|
+
},
|
|
464
|
+
async set(key, value) {
|
|
465
|
+
await store.set(key, await codec.encode(value));
|
|
466
|
+
},
|
|
467
|
+
delete: (key) => store.delete(key)
|
|
468
|
+
};
|
|
469
|
+
if (isEnumerableKeyValueStore(store)) encoded.keys = (prefix) => store.keys(prefix);
|
|
470
|
+
return encoded;
|
|
471
|
+
}
|
|
472
|
+
/** Создаёт запись без прототипа, чтобы любые пользовательские ключи оставались данными. */
|
|
473
|
+
function emptyRecord() {
|
|
474
|
+
return Object.create(null);
|
|
475
|
+
}
|
|
476
|
+
function copyRecord(record) {
|
|
477
|
+
const copy = emptyRecord();
|
|
478
|
+
for (const [key, value] of Object.entries(record ?? {})) copy[key] = value;
|
|
479
|
+
return copy;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Создаёт key-value backend поверх одной общей записи.
|
|
483
|
+
*
|
|
484
|
+
* Чтение выполняется один раз, изменения сразу попадают в локальный снимок, а записи
|
|
485
|
+
* выстраиваются последовательно. Несколько экземпляров или процессов требуют внешней
|
|
486
|
+
* синхронизации со стороны source.
|
|
487
|
+
*/
|
|
488
|
+
function createRecordKeyValueStore(source) {
|
|
489
|
+
if (!isRecord(source) || typeof source.read !== "function" || typeof source.write !== "function") throw new ItdConfigError("RecordKeyValueStoreSource должен содержать read и write");
|
|
490
|
+
if (source.delete !== void 0 && typeof source.delete !== "function") throw new ItdConfigError("RecordKeyValueStoreSource.delete должен быть функцией");
|
|
491
|
+
let snapshot;
|
|
492
|
+
let loading;
|
|
493
|
+
let writing = Promise.resolve();
|
|
494
|
+
const load = async () => {
|
|
495
|
+
if (snapshot !== void 0) return snapshot;
|
|
496
|
+
loading ??= Promise.resolve(source.read()).then((value) => {
|
|
497
|
+
if (value !== void 0 && !isRecord(value)) throw new ItdConfigError("RecordKeyValueStoreSource.read() должен вернуть объект");
|
|
498
|
+
snapshot = copyRecord(value);
|
|
499
|
+
return snapshot;
|
|
500
|
+
}).finally(() => {
|
|
501
|
+
loading = void 0;
|
|
502
|
+
});
|
|
503
|
+
return loading;
|
|
504
|
+
};
|
|
505
|
+
const flush = () => {
|
|
506
|
+
const current = copyRecord(snapshot);
|
|
507
|
+
const operation = async () => {
|
|
508
|
+
if (source.delete && Object.keys(current).length === 0) await source.delete();
|
|
509
|
+
else await source.write(current);
|
|
510
|
+
};
|
|
511
|
+
writing = writing.then(operation, operation);
|
|
512
|
+
return writing;
|
|
513
|
+
};
|
|
514
|
+
return {
|
|
515
|
+
async get(key) {
|
|
516
|
+
const current = await load();
|
|
517
|
+
await writing;
|
|
518
|
+
return Object.hasOwn(current, key) ? current[key] : void 0;
|
|
519
|
+
},
|
|
520
|
+
async set(key, value) {
|
|
521
|
+
const current = await load();
|
|
522
|
+
current[key] = value;
|
|
523
|
+
await flush();
|
|
524
|
+
},
|
|
525
|
+
async delete(key) {
|
|
526
|
+
const current = await load();
|
|
527
|
+
if (!Object.hasOwn(current, key)) return;
|
|
528
|
+
delete current[key];
|
|
529
|
+
await flush();
|
|
530
|
+
},
|
|
531
|
+
async keys(prefix = "") {
|
|
532
|
+
const current = await load();
|
|
533
|
+
await writing;
|
|
534
|
+
return Object.keys(current).filter((key) => key.startsWith(prefix));
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
//#endregion
|
|
369
539
|
//#region src/core/storage.ts
|
|
370
540
|
/**
|
|
371
541
|
* Создаёт независимый снимок сессии.
|
|
@@ -386,39 +556,52 @@ function copySession(session) {
|
|
|
386
556
|
* Хранилище в памяти процесса — вариант по умолчанию.
|
|
387
557
|
*
|
|
388
558
|
* Сессия теряется при перезапуске. Для долгоживущих ботов возьмите `FileTokenStorage`
|
|
389
|
-
* из `itd-api/node`, для браузера — `LocalStorageTokenStorage`
|
|
559
|
+
* из `itd-api/node`, для браузера — `LocalStorageTokenStorage` или
|
|
560
|
+
* `SessionStorageTokenStorage` из `itd-api/web`.
|
|
390
561
|
*/
|
|
391
562
|
var MemoryTokenStorage = class {
|
|
392
|
-
#
|
|
563
|
+
#store = new MemoryKeyValueStore();
|
|
393
564
|
constructor(initial) {
|
|
394
|
-
|
|
565
|
+
if (initial) this.#store.set(TOKEN_STORAGE_KEY, copySession(initial));
|
|
395
566
|
}
|
|
396
567
|
get() {
|
|
397
|
-
|
|
568
|
+
const session = this.#store.get(TOKEN_STORAGE_KEY);
|
|
569
|
+
return session ? copySession(session) : null;
|
|
398
570
|
}
|
|
399
571
|
set(session) {
|
|
400
|
-
this.#
|
|
572
|
+
this.#store.set(TOKEN_STORAGE_KEY, copySession(session));
|
|
401
573
|
}
|
|
402
574
|
clear() {
|
|
403
|
-
this.#
|
|
575
|
+
this.#store.delete(TOKEN_STORAGE_KEY);
|
|
404
576
|
}
|
|
405
577
|
};
|
|
578
|
+
const TOKEN_STORAGE_KEY = "session";
|
|
579
|
+
function isPromise(value) {
|
|
580
|
+
return typeof value === "object" && value !== null && typeof value.then === "function";
|
|
581
|
+
}
|
|
406
582
|
/**
|
|
407
|
-
*
|
|
583
|
+
* Создаёт доменное хранилище сессии поверх общего {@link KeyValueStore}.
|
|
408
584
|
*
|
|
409
585
|
* @example
|
|
410
586
|
* ```ts
|
|
411
|
-
* const storage = createTokenStorage(
|
|
412
|
-
* get: () => db.getSession(userId),
|
|
413
|
-
* set: (session) => db.saveSession(userId, session),
|
|
414
|
-
* clear: () => db.deleteSession(userId),
|
|
415
|
-
* });
|
|
587
|
+
* const storage = createTokenStorage(withNamespace(redisStore, 'itd'));
|
|
416
588
|
* ```
|
|
417
589
|
*/
|
|
418
|
-
function createTokenStorage(
|
|
419
|
-
|
|
590
|
+
function createTokenStorage(store, options = {}) {
|
|
591
|
+
const key = options.key ?? TOKEN_STORAGE_KEY;
|
|
592
|
+
if (typeof key !== "string") throw new ItdConfigError("key TokenStorage должен быть строкой");
|
|
593
|
+
const backend = createKeyValueStore(store);
|
|
594
|
+
return {
|
|
595
|
+
get() {
|
|
596
|
+
const value = backend.get(key);
|
|
597
|
+
if (isPromise(value)) return value.then((session) => session ? copySession(session) : null);
|
|
598
|
+
return value ? copySession(value) : null;
|
|
599
|
+
},
|
|
600
|
+
set: (session) => backend.set(key, copySession(session)),
|
|
601
|
+
clear: () => backend.delete(key)
|
|
602
|
+
};
|
|
420
603
|
}
|
|
421
604
|
//#endregion
|
|
422
|
-
export {
|
|
605
|
+
export { isItdApiError as A, isItdStateError as B, ItdNotFoundError as C, ItdStateError as D, ItdServerError as E, isItdForbiddenError as F, isItdNotFoundError as I, isItdPhoneVerificationError as L, isItdConflictError as M, isItdError as N, ItdTimeoutError as O, isItdFileError as P, isItdRateLimitError as R, ItdNetworkError as S, ItdRateLimitError as T, isItdValidationError as V, ItdError as _, collectKeyValueStoreKeys as a, ItdFileErrorReason as b, isEnumerableKeyValueStore as c, ItdAbortError as d, ItdApiError as f, ItdConflictError as g, ItdConfigError as h, MemoryKeyValueStore as i, isItdAuthError as j, ItdValidationError as k, withCodec as l, ItdAuthError as m, copySession as n, createKeyValueStore as o, ItdApiErrorKind as p, createTokenStorage as r, createRecordKeyValueStore as s, MemoryTokenStorage as t, withNamespace as u, ItdErrorKind as v, ItdPhoneVerificationError as w, ItdForbiddenError as x, ItdFileError as y, isItdServerError as z };
|
|
423
606
|
|
|
424
|
-
//# sourceMappingURL=storage-
|
|
607
|
+
//# sourceMappingURL=storage-DWrK3Z4M.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-DWrK3Z4M.js","names":["#values","#store"],"sources":["../src/core/errors.ts","../src/core/key-value-store.ts","../src/core/storage.ts"],"sourcesContent":["import type { ItdErrorCode } from '../types/enums.js';\n\n/** Бренд, по которому ошибки библиотеки распознаются надёжнее, чем через `instanceof`. */\nconst ITD_ERROR = Symbol.for('itd.error');\n\n/**\n * Категория ошибки. Определяет, какие поля у неё есть.\n *\n * Значение, а не только тип: категорию нужно с чем-то сравнивать в рантайме.\n */\nexport const ItdErrorKind = Object.freeze({\n /** Сервер ответил статусом ≥ 400. */\n Api: 'api',\n /** Запрос не дошёл до сервера. */\n Network: 'network',\n /** Истёк таймаут запроса. */\n Timeout: 'timeout',\n /** Запрос отменён через `AbortSignal`. */\n Abort: 'abort',\n /** Операция невозможна в текущем состоянии объекта. */\n State: 'state',\n /** Не удалось получить или подготовить содержимое вложения. */\n File: 'file',\n /** Некорректная конфигурация или аргументы — обнаружено до обращения к сети. */\n Config: 'config',\n} as const);\nexport type ItdErrorKind = (typeof ItdErrorKind)[keyof typeof ItdErrorKind];\n\n/**\n * Разновидность ошибки API — то же, что класс ошибки, но в виде данных.\n *\n * Существует потому, что `instanceof` подводит, когда в дереве зависимостей оказались\n * две копии пакета или смешаны сборки ESM и CJS: классы тогда разные, хотя ошибка та же.\n * Проверки {@link isItdValidationError} и соседние опираются на это поле, а не на класс.\n */\nexport const ItdApiErrorKind = Object.freeze({\n /** Ни одна из специализаций не подошла. */\n Generic: 'generic',\n Validation: 'validation',\n Auth: 'auth',\n Forbidden: 'forbidden',\n NotFound: 'not_found',\n Conflict: 'conflict',\n RateLimit: 'rate_limit',\n PhoneVerification: 'phone_verification',\n Server: 'server',\n} as const);\nexport type ItdApiErrorKind = (typeof ItdApiErrorKind)[keyof typeof ItdApiErrorKind];\n\n/** Ошибки по полям формы: `{ email: ['уже занят'] }`. */\nexport type ItdFieldErrors = Record<string, string[]>;\n\n/**\n * Базовый класс всех ошибок библиотеки.\n *\n * Ловить его имеет смысл, чтобы отделить проблемы обращения к итд.com от прочих исключений.\n * Для разбора конкретной причины используйте {@link isItdApiError} и поле {@link ItdApiError.code}.\n *\n * @example\n * ```ts\n * try {\n * await itd.posts.like(id);\n * } catch (e) {\n * if (isItdError(e)) console.error('итд.com:', e.message);\n * else throw e;\n * }\n * ```\n */\nexport class ItdError extends Error {\n /** @internal */\n readonly [ITD_ERROR] = true as const;\n\n /** Категория ошибки. */\n readonly kind: ItdErrorKind;\n\n constructor(kind: ItdErrorKind, message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.kind = kind;\n this.name = 'ItdError';\n }\n}\n\n/** Параметры конструктора {@link ItdApiError}. */\nexport interface ItdApiErrorInit {\n /** HTTP-статус ответа. */\n status: number;\n /** Строковый код ошибки из тела ответа. */\n code: ItdErrorCode;\n /** Человекочитаемое сообщение. */\n message: string;\n /** Расширенное описание, если сервер его прислал. */\n detail?: string | undefined;\n /** Заголовок ошибки, если сервер его прислал. */\n title?: string | undefined;\n /** Ошибки по конкретным полям (сведены из `errors` и `violations`). */\n fieldErrors?: ItdFieldErrors | undefined;\n /** Идентификатор запроса из заголовков ответа, если есть. */\n requestId?: string | undefined;\n /** HTTP-метод запроса. */\n method: string;\n /** Путь запроса без базового URL. */\n path: string;\n /** Тело ответа как оно пришло — на случай, если документация разошлась с реальностью. */\n raw: unknown;\n /** Сам объект ответа. Тело уже прочитано. */\n response?: Response | undefined;\n /** Значение `Retry-After` в миллисекундах, если заголовок был. */\n retryAfter?: number | undefined;\n /** Сколько запросов разрешено в окне (`x-ratelimit-limit`). */\n rateLimit?: number | undefined;\n /** Сколько запросов осталось в окне (`x-ratelimit-remaining`). */\n rateLimitRemaining?: number | undefined;\n}\n\n/**\n * Ошибка, возвращённая сервером итд.com (HTTP-статус ≥ 400).\n *\n * API отдаёт ошибки в двух разных формах — `{ error: { … } }` и `{ code, message, violations }`.\n * Библиотека сводит обе к этому классу, поэтому разбирать форму ответа вручную не нужно.\n *\n * @example\n * ```ts\n * try {\n * await itd.users.updateMe({ username: 'занятое_имя' });\n * } catch (e) {\n * if (e instanceof ItdValidationError) {\n * console.log(e.fieldErrors.username); // ['Имя уже занято']\n * }\n * }\n * ```\n */\nexport class ItdApiError extends ItdError {\n /**\n * Разновидность ошибки: та же информация, что и класс, но пригодная для сравнения.\n *\n * Позволяет разбирать ошибку через `switch`, а проверкам вроде {@link isItdAuthError} —\n * работать даже когда в проекте оказались две копии библиотеки.\n */\n readonly apiKind: ItdApiErrorKind;\n /** HTTP-статус ответа. */\n readonly status: number;\n /** Строковый код ошибки, например `VALIDATION_ERROR`. */\n readonly code: ItdErrorCode;\n /** Расширенное описание, если сервер его прислал. */\n readonly detail: string | undefined;\n /** Заголовок ошибки, если сервер его прислал. */\n readonly title: string | undefined;\n /** Ошибки по полям. Пустой объект, если сервер их не прислал. */\n readonly fieldErrors: ItdFieldErrors;\n /** Идентификатор запроса из заголовков ответа. */\n readonly requestId: string | undefined;\n /** HTTP-метод запроса. */\n readonly method: string;\n /** Путь запроса без базового URL. */\n readonly path: string;\n /** Тело ответа как оно пришло. */\n readonly raw: unknown;\n /** Объект ответа. Тело уже прочитано и повторно прочитано быть не может. */\n readonly response: Response | undefined;\n /** Пауза из заголовка `Retry-After` в миллисекундах. Сервер итд.com его не присылает. */\n readonly retryAfter: number | undefined;\n /**\n * Сколько запросов разрешено в окне — заголовок `x-ratelimit-limit`.\n *\n * Времени сброса окна сервер не сообщает, поэтому точный момент повтора неизвестен.\n */\n readonly rateLimit: number | undefined;\n /** Сколько запросов осталось в окне — заголовок `x-ratelimit-remaining`. */\n readonly rateLimitRemaining: number | undefined;\n\n /**\n * @param apiKind разновидность; подставляется подклассами, снаружи задавать не нужно\n */\n constructor(init: ItdApiErrorInit, apiKind: ItdApiErrorKind = ItdApiErrorKind.Generic) {\n super(ItdErrorKind.Api, init.message);\n this.name = 'ItdApiError';\n this.apiKind = apiKind;\n this.status = init.status;\n this.code = init.code;\n this.detail = init.detail;\n this.title = init.title;\n this.fieldErrors = init.fieldErrors ?? {};\n this.requestId = init.requestId;\n this.method = init.method;\n this.path = init.path;\n this.raw = init.raw;\n this.response = init.response;\n this.retryAfter = init.retryAfter;\n this.rateLimit = init.rateLimit;\n this.rateLimitRemaining = init.rateLimitRemaining;\n }\n\n /**\n * Проверяет код ошибки. Удобнее, чем сравнивать строки вручную.\n *\n * @example\n * ```ts\n * if (err.hasCode('OTP_INVALID', 'MISSING_FLOW_TOKEN')) await restartOtpFlow();\n * ```\n */\n hasCode(...codes: ItdErrorCode[]): boolean {\n return codes.includes(this.code);\n }\n\n /** Имеет ли смысл повторить запрос: `429` и серверные ошибки `5xx`. */\n get isRetryable(): boolean {\n return this.status === 429 || this.status >= 500;\n }\n}\n\n/** `400` / `422` — данные не прошли валидацию. Подробности в {@link ItdApiError.fieldErrors}. */\nexport class ItdValidationError extends ItdApiError {\n constructor(init: ItdApiErrorInit) {\n super(init, ItdApiErrorKind.Validation);\n this.name = 'ItdValidationError';\n }\n}\n\n/** `401` — токен отсутствует, истёк или отозван. */\nexport class ItdAuthError extends ItdApiError {\n constructor(init: ItdApiErrorInit) {\n super(init, ItdApiErrorKind.Auth);\n this.name = 'ItdAuthError';\n }\n}\n\n/** `403` — доступ запрещён либо действие ограничено настройками приватности. */\nexport class ItdForbiddenError extends ItdApiError {\n constructor(init: ItdApiErrorInit) {\n super(init, ItdApiErrorKind.Forbidden);\n this.name = 'ItdForbiddenError';\n }\n}\n\n/** `404` — сущность не найдена. */\nexport class ItdNotFoundError extends ItdApiError {\n constructor(init: ItdApiErrorInit) {\n super(init, ItdApiErrorKind.NotFound);\n this.name = 'ItdNotFoundError';\n }\n}\n\n/** `409` — сущность уже существует. */\nexport class ItdConflictError extends ItdApiError {\n constructor(init: ItdApiErrorInit) {\n super(init, ItdApiErrorKind.Conflict);\n this.name = 'ItdConflictError';\n }\n}\n\n/**\n * `429` — превышен лимит запросов.\n *\n * Если сервер прислал `Retry-After`, пауза доступна в {@link ItdApiError.retryAfter}\n * (в миллисекундах). При включённых ретраях библиотека выдерживает её автоматически.\n */\nexport class ItdRateLimitError extends ItdApiError {\n constructor(init: ItdApiErrorInit) {\n super(init, ItdApiErrorKind.RateLimit);\n this.name = 'ItdRateLimitError';\n }\n}\n\n/**\n * Действие требует подтверждённого телефона (`PHONE_VERIFICATION_REQUIRED`).\n *\n * Подтверждение проходит через Telegram-бота: ссылка лежит в {@link verificationUrl}.\n */\nexport class ItdPhoneVerificationError extends ItdApiError {\n /** Ссылка на бота подтверждения, если удалось определить идентификатор пользователя. */\n readonly verificationUrl: string | undefined;\n\n constructor(init: ItdApiErrorInit & { userId?: string | undefined }) {\n super(init, ItdApiErrorKind.PhoneVerification);\n this.name = 'ItdPhoneVerificationError';\n this.verificationUrl = init.userId\n ? `https://t.me/itd_verification_bot?start=${encodeURIComponent(init.userId)}`\n : undefined;\n }\n}\n\n/** `5xx` — ошибка на стороне сервера. */\nexport class ItdServerError extends ItdApiError {\n constructor(init: ItdApiErrorInit) {\n super(init, ItdApiErrorKind.Server);\n this.name = 'ItdServerError';\n }\n}\n\n/** Причина ошибки получения вложения. */\nexport const ItdFileErrorReason = Object.freeze({\n /** Сетевой сбой при получении источника. */\n Network: 'network',\n /** Источник ответил ошибочным HTTP-статусом. */\n Http: 'http',\n /** Источник превысил разрешённый размер. */\n TooLarge: 'too_large',\n /** Среда или источник не предоставили поток. */\n StreamUnavailable: 'stream_unavailable',\n /** Поток источника завершился ошибкой. */\n Read: 'read',\n} as const);\nexport type ItdFileErrorReason = (typeof ItdFileErrorReason)[keyof typeof ItdFileErrorReason];\n\n/** Не удалось получить или прочитать содержимое вложения. */\nexport class ItdFileError extends ItdError {\n readonly reason: ItdFileErrorReason;\n /** Адрес источника, если файл получался по сети. */\n readonly url: string | undefined;\n /** HTTP-статус источника. */\n readonly status: number | undefined;\n /** Разрешённый размер в байтах. */\n readonly limit: number | undefined;\n /** Обнаруженный размер в байтах. */\n readonly actual: number | undefined;\n /** Имеет ли смысл повторить получение источника. */\n readonly retryable: boolean;\n\n constructor(\n message: string,\n init: {\n reason: ItdFileErrorReason;\n url?: string | undefined;\n status?: number | undefined;\n limit?: number | undefined;\n actual?: number | undefined;\n retryable?: boolean | undefined;\n cause?: unknown;\n },\n ) {\n super(ItdErrorKind.File, message, { cause: init.cause });\n this.name = 'ItdFileError';\n this.reason = init.reason;\n this.url = init.url;\n this.status = init.status;\n this.limit = init.limit;\n this.actual = init.actual;\n this.retryable = init.retryable ?? false;\n }\n}\n\n/** Запрос не дошёл до сервера: DNS, обрыв соединения, отсутствие сети. */\nexport class ItdNetworkError extends ItdError {\n /** HTTP-метод запроса. */\n readonly method: string;\n /** Путь запроса без базового URL. */\n readonly path: string;\n\n constructor(message: string, init: { method: string; path: string; cause?: unknown }) {\n super(ItdErrorKind.Network, message, { cause: init.cause });\n this.name = 'ItdNetworkError';\n this.method = init.method;\n this.path = init.path;\n }\n}\n\n/** Истёк таймаут запроса, заданный опцией `timeout`. */\nexport class ItdTimeoutError extends ItdError {\n /** Значение таймаута в миллисекундах. */\n readonly timeout: number;\n /** HTTP-метод запроса. */\n readonly method: string;\n /** Путь запроса без базового URL. */\n readonly path: string;\n\n constructor(init: { timeout: number; method: string; path: string }) {\n super(\n ItdErrorKind.Timeout,\n `Запрос ${init.method} ${init.path} превысил таймаут ${init.timeout} мс`,\n );\n this.name = 'ItdTimeoutError';\n this.timeout = init.timeout;\n this.method = init.method;\n this.path = init.path;\n }\n}\n\n/** Запрос отменён через переданный `AbortSignal`. */\nexport class ItdAbortError extends ItdError {\n constructor(message = 'Запрос отменён', options?: { cause?: unknown }) {\n super(ItdErrorKind.Abort, message, options);\n this.name = 'ItdAbortError';\n }\n}\n\n/**\n * Операция невозможна в текущем состоянии объекта.\n *\n * Например, клиент уже окончательно освобождён через `dispose()` и не может выполнять\n * новые запросы или создавать realtime-потоки.\n */\nexport class ItdStateError extends ItdError {\n constructor(message: string, options?: { cause?: unknown }) {\n super(ItdErrorKind.State, message, options);\n this.name = 'ItdStateError';\n }\n}\n\n/**\n * Некорректная конфигурация или аргументы — обнаружено до обращения к сети.\n *\n * Этим же классом сообщают о нарушенных инвариантах билдеры: например, опрос\n * с одним вариантом ответа.\n */\nexport class ItdConfigError extends ItdError {\n constructor(message: string, options?: { cause?: unknown }) {\n super(ItdErrorKind.Config, message, options);\n this.name = 'ItdConfigError';\n }\n}\n\n/** Любая ошибка, порождённая этой библиотекой. */\nexport function isItdError(value: unknown): value is ItdError {\n return typeof value === 'object' && value !== null && ITD_ERROR in value;\n}\n\n/** Ошибка, пришедшая от сервера итд.com (статус ≥ 400). */\nexport function isItdApiError(value: unknown): value is ItdApiError {\n return isItdError(value) && value.kind === ItdErrorKind.Api;\n}\n\n/** Ошибка получения или чтения вложения. */\nexport function isItdFileError(value: unknown): value is ItdFileError {\n return isItdError(value) && value.kind === ItdErrorKind.File;\n}\n\n/** Операция невозможна в текущем состоянии объекта. */\nexport function isItdStateError(value: unknown): value is ItdStateError {\n return isItdError(value) && value.kind === ItdErrorKind.State;\n}\n\n/**\n * Проверяет разновидность ошибки API.\n *\n * Намеренно не `instanceof`: две копии пакета в дереве зависимостей или смешение сборок\n * ESM и CJS дают разные классы для одной и той же ошибки, и проверка по классу молча\n * перестаёт срабатывать. Поле {@link ItdApiError.apiKind} от этого не зависит.\n */\nfunction hasApiKind(value: unknown, kind: ItdApiErrorKind): boolean {\n return isItdApiError(value) && value.apiKind === kind;\n}\n\n/** Ошибка валидации: `VALIDATION_ERROR` либо статус `400`/`422`. */\nexport function isItdValidationError(value: unknown): value is ItdValidationError {\n return hasApiKind(value, ItdApiErrorKind.Validation);\n}\n\n/** Ошибка авторизации: истёкший или отозванный токен. */\nexport function isItdAuthError(value: unknown): value is ItdAuthError {\n return hasApiKind(value, ItdApiErrorKind.Auth);\n}\n\n/** Доступ запрещён либо действие ограничено настройками приватности. */\nexport function isItdForbiddenError(value: unknown): value is ItdForbiddenError {\n return hasApiKind(value, ItdApiErrorKind.Forbidden);\n}\n\n/** Сущность не найдена. */\nexport function isItdNotFoundError(value: unknown): value is ItdNotFoundError {\n return hasApiKind(value, ItdApiErrorKind.NotFound);\n}\n\n/** Сущность уже существует. */\nexport function isItdConflictError(value: unknown): value is ItdConflictError {\n return hasApiKind(value, ItdApiErrorKind.Conflict);\n}\n\n/** Превышен лимит запросов. */\nexport function isItdRateLimitError(value: unknown): value is ItdRateLimitError {\n return hasApiKind(value, ItdApiErrorKind.RateLimit);\n}\n\n/** Действие требует подтверждённого телефона. Ссылка — в `verificationUrl`. */\nexport function isItdPhoneVerificationError(value: unknown): value is ItdPhoneVerificationError {\n return hasApiKind(value, ItdApiErrorKind.PhoneVerification);\n}\n\n/** Ошибка на стороне сервера (`5xx`). */\nexport function isItdServerError(value: unknown): value is ItdServerError {\n return hasApiKind(value, ItdApiErrorKind.Server);\n}\n","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 * выстраиваются последовательно. Несколько экземпляров или процессов требуют внешней\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 snapshot: Record<string, T> | undefined;\n let loading: Promise<Record<string, T>> | undefined;\n let writing: Promise<void> = Promise.resolve();\n\n const load = async (): Promise<Record<string, T>> => {\n if (snapshot !== undefined) return snapshot;\n loading ??= Promise.resolve(source.read())\n .then((value) => {\n if (value !== undefined && !isRecord(value)) {\n throw new ItdConfigError('RecordKeyValueStoreSource.read() должен вернуть объект');\n }\n snapshot = copyRecord(value as Readonly<Record<string, T>> | undefined);\n return snapshot;\n })\n .finally(() => {\n loading = undefined;\n });\n return loading;\n };\n\n const flush = (): Promise<void> => {\n const current = copyRecord(snapshot);\n const operation = async () => {\n if (source.delete && Object.keys(current).length === 0) await source.delete();\n else await source.write(current);\n };\n writing = writing.then(operation, operation);\n return writing;\n };\n\n return {\n async get(key) {\n const current = await load();\n await writing;\n return Object.hasOwn(current, key) ? current[key] : undefined;\n },\n async set(key, value) {\n const current = await load();\n current[key] = value;\n await flush();\n },\n async delete(key) {\n const current = await load();\n if (!Object.hasOwn(current, key)) return;\n delete current[key];\n await flush();\n },\n async keys(prefix = '') {\n const current = await load();\n await writing;\n return Object.keys(current).filter((key) => key.startsWith(prefix));\n },\n };\n}\n","import { ItdConfigError } from './errors.js';\nimport { createKeyValueStore, type KeyValueStore, MemoryKeyValueStore } from './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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"}
|