@coopenomics/parser 1.0.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/LICENSE +21 -0
- package/NOTICE +17 -0
- package/dist/cli/index.js +951 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/deserialize.worker.cjs +47 -0
- package/dist/deserialize.worker.cjs.map +1 -0
- package/dist/index.d.ts +1520 -0
- package/dist/index.js +1773 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts","../../src/config/index.ts","../../src/errors.ts","../../src/adapters/IoRedisStore.ts","../../src/redis/keys.ts","../../src/cli/commands/listSubscriptions.ts","../../src/cli/commands/resetSubscription.ts","../../src/cli/commands/abiPrune.ts","../../src/cli/commands/listDeadLetters.ts","../../src/cli/commands/replayDeadLetter.ts"],"sourcesContent":["/**\n * CLI точка входа для @coopenomics/parser.\n *\n * Инструмент командной строки `parser` предоставляет набор операционных команд\n * для управления парсером без его перезапуска:\n *\n * validate <config> — проверить YAML конфиг без запуска парсера\n * list-subscriptions — показать зарегистрированные подписки и их статус\n * reset-subscription — перемотать consumer group на конкретный блок\n * abi-prune — удалить устаревшие версии ABI из Redis\n * list-dead-letters — показать содержимое dead-letter stream(ов)\n * replay-dead-letter — переиграть события из dead-letter обратно в live stream\n *\n * Каждая команда, требующая Redis, принимает --config <file> для подключения.\n * Redis-соединение открывается и закрывается внутри команды (connect → quit).\n *\n * Паттерн обработки ошибок: catch → console.error → process.exit(1).\n * finally-блок гарантирует закрытие Redis-соединения даже при ошибке.\n */\n\nimport { Command } from 'commander'\nimport { fromConfigFile } from '../config/index.js'\nimport { IoRedisStore } from '../adapters/IoRedisStore.js'\nimport { ConfigValidationError, ConfigSecurityError } from '../errors.js'\nimport { listSubscriptions } from './commands/listSubscriptions.js'\nimport { resetSubscription } from './commands/resetSubscription.js'\nimport { abiPrune } from './commands/abiPrune.js'\nimport { listDeadLetters } from './commands/listDeadLetters.js'\nimport { replayDeadLetter } from './commands/replayDeadLetter.js'\n\nconst program = new Command()\n\nprogram\n .name('parser')\n .description('@coopenomics/parser — universal EOSIO/Antelope blockchain indexer')\n .version('0.1.0')\n\n/**\n * Команда `validate`: проверяет YAML конфиг без запуска парсера.\n *\n * Выходные коды:\n * 0 — конфиг валиден\n * 1 — ошибка валидации (неверная схема, пропущены обязательные поля)\n * 2 — ошибка безопасности (секреты хардкодированы вместо env-переменных)\n *\n * В stdout выводит (опционально) redacted конфиг — URL с заменёнными паролями.\n * Это позволяет операторам убедиться что env-подстановка сработала корректно.\n */\nprogram\n .command('validate <config-file>')\n .description('Validate config file without starting the parser')\n .option('--json', 'Output result as JSON')\n .action((configFile: string, opts: { json?: boolean }) => {\n try {\n const config = fromConfigFile(configFile)\n\n // Redacted конфиг: скрываем пароли в URL для безопасного вывода\n const redacted = {\n ship: { url: redactUrl(config.ship.url) },\n chain: config.chain ? { url: config.chain.url ? redactUrl(config.chain.url) : undefined, id: config.chain.id } : undefined,\n redis: { url: redactUrl(config.redis.url) },\n }\n\n if (opts.json) {\n console.log(JSON.stringify({ valid: true, config: redacted }))\n } else {\n console.log('✓ Config valid')\n console.log(JSON.stringify(redacted, null, 2))\n }\n\n process.exit(0)\n } catch (err) {\n if (err instanceof ConfigSecurityError) {\n if (opts.json) {\n console.error(JSON.stringify({ valid: false, errors: [`SECURITY: ${err.message}`] }))\n } else {\n console.error(`SECURITY: secrets must not be hardcoded`)\n console.error(err.message)\n }\n process.exit(2)\n }\n\n if (err instanceof ConfigValidationError) {\n const message = err.message\n if (opts.json) {\n console.error(JSON.stringify({ valid: false, errors: [message] }))\n } else {\n console.error('✗ Config invalid:')\n console.error(message)\n }\n process.exit(1)\n }\n\n if (opts.json) {\n console.error(JSON.stringify({ valid: false, errors: [String(err)] }))\n } else {\n console.error('✗ Config invalid:')\n console.error(String(err))\n }\n process.exit(1)\n }\n })\n\n/**\n * Команда `list-subscriptions`: показывает все зарегистрированные подписки.\n * Читает из parser:subs HASH и объединяет с данными XINFO GROUPS.\n * Полезна для мониторинга: видно pending, lag и last-delivered-id каждой группы.\n */\nprogram\n .command('list-subscriptions')\n .description('List registered subscriptions with consumer group stats')\n .requiredOption('--config <file>', 'Config file path')\n .option('--chain-id <id>', 'Override chain ID from config')\n .option('--json', 'Output as JSON')\n .action(async (opts: { config: string; chainId?: string; json?: boolean }) => {\n let redis: IoRedisStore | null = null\n try {\n const config = fromConfigFile(opts.config)\n const chainId = opts.chainId ?? config.chain?.id ?? 'default'\n redis = new IoRedisStore(config.redis)\n await redis.connect()\n await listSubscriptions(redis, chainId, opts.json ?? false)\n process.exit(0)\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n } finally {\n await redis?.quit()\n }\n })\n\n/**\n * Команда `reset-subscription`: перематывает позицию consumer group.\n * Используется для повторной обработки блоков после потери данных или при отладке.\n * --to-block 0/latest → '$' (пропустить всё, получать только новые события).\n * --to-block <N> → перемотать на конкретный блок в стриме.\n * --dry-run → показать XGROUP SETID команду без выполнения.\n */\nprogram\n .command('reset-subscription')\n .description('Rewind a subscription consumer group to a specific block')\n .requiredOption('--config <file>', 'Config file path')\n .requiredOption('--sub-id <id>', 'Subscription ID to reset')\n .requiredOption('--to-block <n>', 'Target block number (0 or \"latest\" = skip to end)')\n .option('--chain-id <id>', 'Override chain ID from config')\n .option('--dry-run', 'Show what would be done without executing')\n .action(async (opts: { config: string; subId: string; toBlock: string; chainId?: string; dryRun?: boolean }) => {\n let redis: IoRedisStore | null = null\n try {\n const config = fromConfigFile(opts.config)\n const chainId = opts.chainId ?? config.chain?.id ?? 'default'\n redis = new IoRedisStore(config.redis)\n await redis.connect()\n await resetSubscription(redis, chainId, opts.subId, opts.toBlock, opts.dryRun ?? false)\n process.exit(0)\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n } finally {\n await redis?.quit()\n }\n })\n\n/**\n * Команда `abi-prune`: удаляет устаревшие версии ABI из Redis ZSET.\n * Без периодической очистки активные контракты накапливают сотни версий.\n * --older-than <block> → удалить версии с block_num < этого значения.\n * --all-contracts → SCAN по parser:abi:* и применить к каждому контракту.\n * --dry-run → показать количество версий для удаления без изменений.\n */\nprogram\n .command('abi-prune')\n .description('Prune old ABI versions from a contract ZSET')\n .requiredOption('--config <file>', 'Config file path')\n .option('--contract <name>', 'Contract name to prune')\n .option('--older-than <block>', 'Remove versions older than this block number')\n .option('--dry-run', 'Show what would be done without executing')\n .option('--all-contracts', 'Apply prune to all contracts with ABI history')\n .action(async (opts: { config: string; contract?: string; olderThan?: string; dryRun?: boolean; allContracts?: boolean }) => {\n let redis: IoRedisStore | null = null\n try {\n const config = fromConfigFile(opts.config)\n redis = new IoRedisStore(config.redis)\n await redis.connect()\n const olderThan = opts.olderThan !== undefined ? Number(opts.olderThan) : 0\n if (isNaN(olderThan)) throw new Error(`Invalid --older-than value: ${opts.olderThan}`)\n await abiPrune(redis, opts.contract ?? null, olderThan, opts.dryRun ?? false, opts.allContracts ?? false)\n process.exit(0)\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n } finally {\n await redis?.quit()\n }\n })\n\n/**\n * Команда `list-dead-letters`: инспектирует dead-letter stream(ы).\n * Dead-letter stream: ce:parser:<chainId>:dead:<subId>\n * Содержит события которые handler не смог обработать 3 раза подряд.\n * --all → сканировать все dead-letter стримы для цепи (SCAN dead:*).\n * --limit → максимум записей за вызов (пагинация через --from).\n * --from → entry ID для начала XRANGE (исключительный старт следующей страницы).\n */\nprogram\n .command('list-dead-letters')\n .description('Inspect dead-letter stream for a subscription')\n .requiredOption('--config <file>', 'Config file path')\n .option('--sub-id <id>', 'Subscription ID to inspect')\n .option('--chain-id <id>', 'Override chain ID from config')\n .option('--all', 'Inspect all dead-letter streams')\n .option('--json', 'Output as JSON')\n .option('--limit <n>', 'Max entries to show', '100')\n .option('--from <entryId>', 'Start from this entry ID (pagination)', '-')\n .action(async (opts: { config: string; subId?: string; chainId?: string; all?: boolean; json?: boolean; limit?: string; from?: string }) => {\n let redis: IoRedisStore | null = null\n try {\n const config = fromConfigFile(opts.config)\n const chainId = opts.chainId ?? config.chain?.id ?? 'default'\n redis = new IoRedisStore(config.redis)\n await redis.connect()\n await listDeadLetters(\n redis,\n chainId,\n opts.subId ?? null,\n opts.json ?? false,\n Number(opts.limit ?? 100),\n opts.from ?? '-',\n opts.all ?? false,\n )\n process.exit(0)\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n } finally {\n await redis?.quit()\n }\n })\n\n/**\n * Команда `replay-dead-letter`: переигрывает события из dead-letter обратно в live stream.\n * Операция: XADD live-stream → XDEL dead-stream → HDEL failures-hash.\n * HDEL failures-hash важен: без него при первой же следующей ошибке событие снова\n * уйдёт в dead-letter (счётчик уже был = 3 = FAILURE_THRESHOLD).\n * --event-id <id> → найти и переиграть конкретное событие (XRANGE с поиском).\n * --all → переиграть все события из dead-letter stream подписки.\n * --dry-run → показать что будет сделано без изменений Redis.\n */\nprogram\n .command('replay-dead-letter')\n .description('Replay a dead-letter event back into the live stream')\n .requiredOption('--config <file>', 'Config file path')\n .requiredOption('--sub-id <id>', 'Subscription ID')\n .option('--event-id <id>', 'Event ID to replay')\n .option('--all', 'Replay all dead-letter events for the subscription')\n .option('--chain-id <id>', 'Override chain ID from config')\n .option('--dry-run', 'Show what would be done without executing')\n .action(async (opts: { config: string; subId: string; eventId?: string; all?: boolean; chainId?: string; dryRun?: boolean }) => {\n let redis: IoRedisStore | null = null\n try {\n const config = fromConfigFile(opts.config)\n const chainId = opts.chainId ?? config.chain?.id ?? 'default'\n redis = new IoRedisStore(config.redis)\n await redis.connect()\n await replayDeadLetter(\n redis,\n chainId,\n opts.subId,\n opts.eventId ?? null,\n opts.all ?? false,\n opts.dryRun ?? false,\n )\n process.exit(0)\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n } finally {\n await redis?.quit()\n }\n })\n\n/**\n * Redact-функция: скрывает пароль в Redis URL для безопасного вывода.\n * redis://:password@host → redis://:***@host\n * Использует URL-парсер — не регулярку — чтобы корректно обработать edge cases.\n */\nfunction redactUrl(url: string): string {\n try {\n const u = new URL(url)\n if (u.password) u.password = '***'\n return u.toString()\n } catch {\n return url\n }\n}\n\nprogram.parse(process.argv)\n","/**\n * Загрузка и валидация конфигурации парсера.\n *\n * Поддерживаемые форматы: YAML файл (fromConfigFile) или уже разобранный объект (parseConfig).\n *\n * Конвейер обработки:\n * 1. Чтение YAML → parseYaml → raw object.\n * 2. interpolateDeep: рекурсивно заменяет ${VAR} → process.env[VAR].\n * Если переменная не задана — оставляем плейсхолдер (не ломаем конфиг, но validate упадёт\n * если это обязательное поле).\n * 3. validate: проверяет обязательные поля (ship.url, redis.url) и enum-значения.\n * 4. checkPlainSecrets: запрещает хардкодированные пароли в Redis URL.\n * redis://:hardcoded-pass@host → ConfigSecurityError.\n * redis://${REDIS_PASSWORD}@host → OK (это плейсхолдер, не секрет).\n *\n * Почему env-интерполяция важна: операторы хранят конфиг в git без секретов,\n * инжектируя их через переменные среды в Kubernetes / Docker. Формат ${VAR} — стандарт.\n */\n\nimport { readFileSync } from 'node:fs'\nimport { parse as parseYaml } from 'yaml'\nimport { configSchema } from './schema.js'\nimport { ConfigValidationError, ConfigSecurityError } from '../errors.js'\n\n/**\n * Все настройки парсера в одном объекте.\n * Передаётся в конструктор Parser и ParserClient.\n * Все поля кроме ship и redis — опциональны (имеют дефолты в соответствующих модулях).\n */\nexport interface ParserOptions {\n /** SHiP WebSocket соединение. timeoutMs по умолчанию 10000. */\n ship: { url: string; timeoutMs?: number }\n /** Chain API для ABI fallback (abiFallback: 'rpc-current'). Опционален. */\n chain?: { url?: string; id?: string }\n /** Redis подключение. keyPrefix добавляет namespace к ключам (полезно при shared Redis). */\n redis: { url: string; password?: string; keyPrefix?: string }\n /** Piscina worker pool для десериализации. maxThreads по умолчанию = CPU count / 2. */\n workerPool?: { maxThreads?: number }\n /** Поведение при отсутствии ABI: 'rpc-current' = попробовать Chain API, 'fail' = ошибка. */\n abiFallback?: 'rpc-current' | 'fail'\n /** XtrimSupervisor: интервал проверки и включение/отключение автообрезки стрима. */\n xtrim?: { intervalMs?: number; enabled?: boolean }\n /** ReconnectSupervisor: максимум попыток и backoff-таблица в секундах. */\n reconnect?: { maxAttempts?: number; backoffSeconds?: number[] }\n /** Десериализатор ABI-данных. Единственный вариант — 'wharfkit'. */\n deserializer?: 'wharfkit'\n /** Pino logger настройки. pretty=true включает pino-pretty (для разработки). */\n logger?: { level?: string; pretty?: boolean }\n /** HTTP /health endpoint. Kubernetes liveness/readiness probe. */\n health?: { enabled?: boolean; port?: number; lagThresholdSeconds?: number }\n /** HTTP /metrics endpoint для Prometheus. */\n metrics?: { enabled?: boolean; port?: number }\n /** Обрабатывать только irreversible блоки (block_num <= lastIrreversible). */\n irreversibleOnly?: boolean\n /** Не устанавливать SIGTERM/SIGINT обработчики. Используется в тестах. */\n noSignalHandlers?: boolean\n}\n\n// configSchema экспортируется для внешних валидаторов (AJV, Ajv) и документации\nvoid configSchema\n\n/**\n * Паттерн для детекции хардкодированных паролей в Redis URL.\n * Срабатывает на: redis://:password@host или redis://user:pass@host\n * НЕ срабатывает на: redis://:${REDIS_PASSWORD}@host (env-переменная — ОК).\n * [^$\\s]* — не-$, не-пробел → означает отсутствие $ в начале пароля.\n */\nconst PLAIN_SECRET_RE = /redis:\\/\\/[^$\\s]*:[^@$\\s]+@/i\n\n/**\n * Заменяет одну ${VAR} подстановку в строке.\n * Если переменная не задана — возвращает исходный плейсхолдер (не падаем).\n */\nfunction interpolateEnv(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (_, varName: string) => {\n return process.env[varName] ?? `\\${${varName}}`\n })\n}\n\n/**\n * Рекурсивно обходит структуру данных и заменяет ${VAR} в строках.\n * Работает со строками, массивами и объектами.\n * Числа, булевы, null — возвращает без изменений.\n */\nfunction interpolateDeep(obj: unknown): unknown {\n if (typeof obj === 'string') return interpolateEnv(obj)\n if (Array.isArray(obj)) return obj.map(interpolateDeep)\n if (obj !== null && typeof obj === 'object') {\n const result: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {\n result[k] = interpolateDeep(v)\n }\n return result\n }\n return obj\n}\n\n/** Type guard: проверяет что значение является непустым объектом (не массивом). */\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === 'object' && !Array.isArray(v)\n}\n\n/**\n * Ручная валидация конфига (без AJV).\n * Проверяет только обязательные инварианты: ship.url, redis.url, enum-значения.\n * Выбрасывает ConfigValidationError с описанием всех нарушений.\n */\nfunction validate(raw: unknown): raw is ParserOptions {\n const errors: string[] = []\n if (!isObject(raw)) {\n errors.push('(root) must be an object')\n throw new ConfigValidationError(`Config validation failed: ${errors.join('; ')}`)\n }\n if (!isObject(raw['ship']) || typeof (raw['ship'] as Record<string, unknown>)['url'] !== 'string') {\n errors.push('ship.url is required and must be a string')\n }\n if (!isObject(raw['redis']) || typeof (raw['redis'] as Record<string, unknown>)['url'] !== 'string') {\n errors.push('redis.url is required and must be a string')\n }\n const abiFallback = raw['abiFallback']\n if (abiFallback !== undefined && abiFallback !== 'rpc-current' && abiFallback !== 'fail') {\n errors.push('abiFallback must be \"rpc-current\" or \"fail\"')\n }\n const deserializer = raw['deserializer']\n if (deserializer !== undefined && deserializer !== 'wharfkit') {\n errors.push('deserializer must be \"wharfkit\"')\n }\n if (errors.length > 0) {\n throw new ConfigValidationError(`Config validation failed: ${errors.join('; ')}`)\n }\n return true\n}\n\n/**\n * Проверяет что секреты не хардкодированы в Redis URL.\n * Хардкодированные секреты: попадут в git, логи, env dumps → критичная утечка.\n * Правило: используй ${REDIS_PASSWORD} вместо прямого пароля.\n */\nfunction checkPlainSecrets(opts: ParserOptions): void {\n if (PLAIN_SECRET_RE.test(opts.redis.url)) {\n throw new ConfigSecurityError(\n 'Secrets must be injected via env variables, not hardcoded in config',\n )\n }\n}\n\n/**\n * Парсит и валидирует конфиг из уже разобранного объекта (результат parseYaml или тест).\n * Применяет env-интерполяцию, валидацию, проверку безопасности.\n */\nexport function parseConfig(raw: unknown): ParserOptions {\n const interpolated = interpolateDeep(raw)\n validate(interpolated)\n const opts = interpolated as ParserOptions\n checkPlainSecrets(opts)\n return opts\n}\n\n/**\n * Читает YAML файл по пути и возвращает валидированные ParserOptions.\n * Основная точка входа для CLI команд и пользовательского кода.\n * Выбрасывает ConfigValidationError/ConfigSecurityError/Error при любых проблемах.\n */\nexport function fromConfigFile(filePath: string): ParserOptions {\n const text = readFileSync(filePath, 'utf8')\n const raw = parseYaml(text) as unknown\n return parseConfig(raw)\n}\n","/**\n * Доменные ошибки пакета.\n *\n * Каждый класс расширяет Error и устанавливает `name`, чтобы стек-трейсы\n * содержали читаемое имя вместо просто \"Error\".\n */\n\n/** Конфигурационный YAML не прошёл структурную валидацию. */\nexport class ConfigValidationError extends Error {\n override readonly cause?: unknown\n constructor(message: string, cause?: unknown) {\n super(message)\n this.name = 'ConfigValidationError'\n this.cause = cause\n }\n}\n\n/**\n * Секреты обнаружены прямо в конфигурационном файле (например пароль Redis\n * захардкожен в URL). Правильный способ — переменные окружения ${VAR}.\n */\nexport class ConfigSecurityError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ConfigSecurityError'\n }\n}\n\n/** Метод интерфейса объявлен, но не реализован в данном адаптере. */\nexport class NotImplementedError extends Error {\n constructor(method: string) {\n super(`${method} is not implemented`)\n this.name = 'NotImplementedError'\n }\n}\n\n/**\n * Chain ID в конфиге не совпал с реальным ID цепи, полученным из SHiP-рукопожатия.\n * Защищает от случайного подключения к неверной ноде.\n */\nexport class ChainIdMismatchError extends Error {\n constructor(expected: string, actual: string) {\n super(`Chain ID mismatch: expected ${expected}, got ${actual}`)\n this.name = 'ChainIdMismatchError'\n }\n}\n\n/**\n * ABI для указанного контракта не найден ни в Redis-кэше, ни по RPC,\n * а конфигурация abiFallback='fail' запрещает продолжение без него.\n */\nexport class AbiNotFoundError extends Error {\n constructor(contract: string, blockNum: number, abiFallback: string) {\n super(`ABI for ${contract} not found at block ${blockNum}, abiFallback=${abiFallback}`)\n this.name = 'AbiNotFoundError'\n }\n}\n","/**\n * Адаптер Redis — реализует интерфейс RedisStore через ioredis.\n *\n * Ioredis не имеет поля \"exports\" в package.json, поэтому для NodeNext\n * resolution используется динамический import() с явным приведением типа.\n *\n * Два Lua-скрипта реализуют атомарные операции для distributed lock:\n *\n * PEXPIRE_LUA — условное продление TTL:\n * «Продли TTL ключа key на ms миллисекунд, но только если его текущее\n * значение равно value (т.е. мы — владельцы lock'а)».\n * Атомарность важна: без неё возможен race condition между GET и PEXPIRE.\n *\n * DEL_LUA — условное удаление:\n * «Удали ключ key, но только если его текущее значение равно value».\n * Защищает от случайного удаления lock'а другого процесса, если наш TTL истёк.\n */\n\nimport type { RedisOptions } from 'ioredis'\nimport type { RedisStore, StreamMessage, XGroupInfo } from '../ports/RedisStore.js'\n\n// Ioredis не имеет \"exports\" поля — используем динамический import с приведением типа\ntype RedisConstructor = new (url: string, opts?: RedisOptions) => IRedisClient\n\n/**\n * Минимальный интерфейс ioredis-клиента с только нужными командами.\n * Сигнатуры точно соответствуют тому что возвращает ioredis — без обёрток.\n */\ninterface IRedisClient {\n connect(): Promise<void>\n xadd(stream: string, id: string, ...args: string[]): Promise<string | null>\n xtrim(stream: string, strategy: string, threshold: string): Promise<number>\n xgroup(action: string, stream: string, group: string, id: string, mkstream?: string): Promise<unknown>\n xinfo(subcommand: string, key: string): Promise<unknown>\n xreadgroup(\n group: string, groupName: string, consumerName: string,\n count: string, countVal: number,\n block: string, blockMs: number,\n streams: string, stream: string, id: string,\n ): Promise<Array<[string, Array<[string, string[]]>]> | null>\n xrange(key: string, start: string, end: string, count: string, countVal: number): Promise<Array<[string, string[]]>>\n xrevrange(key: string, end: string, start: string, count: string, countVal: number): Promise<Array<[string, string[]]>>\n xlen(key: string): Promise<number>\n xdel(key: string, ...ids: string[]): Promise<number>\n xack(stream: string, group: string, id: string): Promise<number>\n zadd(key: string, score: number, member: string): Promise<number>\n zrangebyscore(key: string, min: string, max: string, limit: string, offset: number, count: number): Promise<string[]>\n zrevrangebyscore(key: string, max: string, min: string, limit: string, offset: number, count: number): Promise<string[]>\n zcount(key: string, min: string, max: string): Promise<number>\n zremrangebyscore(key: string, min: string, max: string): Promise<number>\n zcard(key: string): Promise<number>\n hset(key: string, ...args: string[]): Promise<number>\n hget(key: string, field: string): Promise<string | null>\n hgetall(key: string): Promise<Record<string, string> | null>\n hincrby(key: string, field: string, increment: number): Promise<number>\n hdel(key: string, ...fields: string[]): Promise<number>\n set(key: string, value: string, nx: string, px: string, ms: number): Promise<string | null>\n eval(script: string, numkeys: number, ...args: string[]): Promise<unknown>\n expire(key: string, seconds: number): Promise<number>\n scan(cursor: string, match: string, pattern: string, count: string, countVal: number): Promise<[string, string[]]>\n quit(): Promise<string>\n}\n\nconst { default: RedisClass } = await import('ioredis') as unknown as { default: RedisConstructor }\n\n/**\n * Атомарно продлевает PEXPIRE только если мы — владелец lock'а.\n * ARGV[1] = ms (новый TTL), ARGV[2] = expectedValue.\n */\nconst PEXPIRE_LUA = `\nlocal current = redis.call('GET', KEYS[1])\nif current == ARGV[2] then\n redis.call('PEXPIRE', KEYS[1], ARGV[1])\n return 1\nend\nreturn 0\n`\n\n/**\n * Атомарно удаляет ключ только если мы — владелец lock'а.\n * ARGV[1] = expectedValue.\n */\nconst DEL_LUA = `\nlocal current = redis.call('GET', KEYS[1])\nif current == ARGV[1] then\n redis.call('DEL', KEYS[1])\n return 1\nend\nreturn 0\n`\n\n/**\n * Конвертирует сырой ответ XRANGE/XREADGROUP в массив StreamMessage.\n * Redis возвращает: [[id, [field1, val1, field2, val2, ...]], ...]\n * Мы конвертируем в: [{id, fields: {field1: val1, ...}}, ...]\n */\nfunction parseStreamEntries(raw: Array<[string, string[]]>): StreamMessage[] {\n const messages: StreamMessage[] = []\n for (const [msgId, rawFields] of raw) {\n const fields: Record<string, string> = {}\n // rawFields — плоский массив [key, val, key, val, ...], шагаем по 2\n for (let i = 0; i + 1 < rawFields.length; i += 2) {\n fields[rawFields[i] ?? ''] = rawFields[i + 1] ?? ''\n }\n messages.push({ id: msgId, fields })\n }\n return messages\n}\n\n/**\n * Конвертирует ответ XINFO GROUPS в XGroupInfo.\n * Redis < 7.0 возвращает плоский массив [key, val, key, val, ...].\n * Redis >= 7.0 может возвращать объект — обрабатываем оба случая.\n */\nfunction parseXGroupInfo(raw: unknown): XGroupInfo {\n let obj: Record<string, unknown>\n if (Array.isArray(raw)) {\n // Старый формат: плоский массив ключ-значение\n obj = {}\n for (let i = 0; i + 1 < raw.length; i += 2) {\n obj[raw[i] as string] = raw[i + 1]\n }\n } else {\n obj = raw as Record<string, unknown>\n }\n return {\n name: String(obj['name'] ?? ''),\n pending: Number(obj['pending'] ?? 0),\n // Поле 'last-delivered-id' в Redis (с дефисами), не 'lastDeliveredId'\n lastDeliveredId: String(obj['last-delivered-id'] ?? '0-0'),\n // lag появилось в Redis 7.0; null для старых версий\n lag: obj['lag'] != null ? Number(obj['lag']) : null,\n consumers: Number(obj['consumers'] ?? 0),\n }\n}\n\nexport class IoRedisStore implements RedisStore {\n /** Прямой доступ к ioredis-клиенту (для тестов и расширения). */\n readonly client: IRedisClient\n\n constructor(opts: { url: string; password?: string; keyPrefix?: string }) {\n const redisOpts: RedisOptions = {\n lazyConnect: true, // не подключаться в конструкторе — явный connect()\n enableReadyCheck: true, // проверять готовность перед командами\n }\n if (opts.password !== undefined) redisOpts.password = opts.password\n if (opts.keyPrefix !== undefined) redisOpts.keyPrefix = opts.keyPrefix\n\n this.client = new RedisClass(opts.url, redisOpts)\n }\n\n /** Явное подключение — вызывается один раз при старте Parser/ParserClient. */\n async connect(): Promise<void> {\n await this.client.connect()\n }\n\n /** XADD stream * field1 val1 … — возвращает присвоенный entry ID. */\n async xadd(stream: string, fields: Record<string, string>): Promise<string> {\n const args: string[] = []\n for (const [k, v] of Object.entries(fields)) args.push(k, v)\n const id = await this.client.xadd(stream, '*', ...args)\n return id ?? ''\n }\n\n /** XTRIM stream MINID minId — удаляет записи с ID < minId. */\n async xtrim(stream: string, minId: string): Promise<number> {\n return this.client.xtrim(stream, 'MINID', minId)\n }\n\n /**\n * XGROUP CREATE stream group startId MKSTREAM\n * MKSTREAM: создаёт стрим если не существует.\n * BUSYGROUP: группа уже существует — это нормально, поглощаем ошибку.\n */\n async xgroupCreate(stream: string, group: string, startId: string): Promise<void> {\n try {\n await this.client.xgroup('CREATE', stream, group, startId, 'MKSTREAM')\n } catch (err) {\n if (err instanceof Error && err.message.includes('BUSYGROUP')) return\n throw err\n }\n }\n\n /** XGROUP SETID stream group id — переставляет позицию group в стриме. */\n async xgroupSetId(stream: string, group: string, id: string): Promise<void> {\n await this.client.xgroup('SETID', stream, group, id)\n }\n\n /** XINFO GROUPS stream → список consumer groups с метриками. */\n async xinfoGroups(stream: string): Promise<XGroupInfo[]> {\n const raw = await this.client.xinfo('GROUPS', stream) as unknown[]\n return (raw ?? []).map(parseXGroupInfo)\n }\n\n /**\n * XREADGROUP GROUP group consumer COUNT count BLOCK blockMs STREAMS stream id\n * id='>' — только новые сообщения.\n * id='0' — PEL (pending): уже доставленные, но не подтверждённые (recovery).\n */\n async xreadGroup(\n stream: string,\n group: string,\n consumer: string,\n count: number,\n blockMs: number,\n id: string,\n ): Promise<StreamMessage[]> {\n const result = await this.client.xreadgroup(\n 'GROUP', group, consumer,\n 'COUNT', count,\n 'BLOCK', blockMs,\n 'STREAMS', stream, id,\n )\n if (!result) return []\n const messages: StreamMessage[] = []\n for (const [, entries] of result) {\n messages.push(...parseStreamEntries(entries))\n }\n return messages\n }\n\n /** XRANGE stream start end COUNT count. */\n async xrange(stream: string, start: string, end: string, count: number): Promise<StreamMessage[]> {\n const raw = await this.client.xrange(stream, start, end, 'COUNT', count)\n return parseStreamEntries(raw)\n }\n\n /** XREVRANGE stream end start COUNT count. */\n async xrevrange(stream: string, end: string, start: string, count: number): Promise<StreamMessage[]> {\n const raw = await this.client.xrevrange(stream, end, start, 'COUNT', count)\n return parseStreamEntries(raw)\n }\n\n /** XLEN stream. */\n async xlen(stream: string): Promise<number> {\n return this.client.xlen(stream)\n }\n\n /** XDEL stream id — удаляет запись по ID. */\n async xdel(stream: string, id: string): Promise<number> {\n return this.client.xdel(stream, id)\n }\n\n /** XACK stream group id — убирает из PEL. */\n async xack(stream: string, group: string, id: string): Promise<void> {\n await this.client.xack(stream, group, id)\n }\n\n /** ZADD key score member. */\n async zadd(key: string, score: number, member: string): Promise<void> {\n await this.client.zadd(key, score, member)\n }\n\n /**\n * ZREVRANGEBYSCORE key max min LIMIT 0 1\n * Возвращает максимум один элемент с score ≤ max.\n * Используется для поиска ABI: «последняя версия не позже блока N».\n */\n async zrangeByscoreRev(key: string, max: string, min: string): Promise<string[]> {\n return this.client.zrevrangebyscore(key, max, min, 'LIMIT', 0, 1)\n }\n\n /** ZRANGEBYSCORE key min max LIMIT 0 9999999 — все элементы в диапазоне. */\n async zrangeByScore(key: string, min: string, max: string): Promise<string[]> {\n return this.client.zrangebyscore(key, min, max, 'LIMIT', 0, 9_999_999)\n }\n\n /** ZCOUNT key min max. */\n async zcount(key: string, min: string, max: string): Promise<number> {\n return this.client.zcount(key, min, max)\n }\n\n /** ZREMRANGEBYSCORE key min max → число удалённых. */\n async zremRangeByScore(key: string, min: string, max: string): Promise<number> {\n return this.client.zremrangebyscore(key, min, max)\n }\n\n /** ZCARD key. */\n async zcard(key: string): Promise<number> {\n return this.client.zcard(key)\n }\n\n /** HSET key field1 val1 field2 val2 … */\n async hset(key: string, fields: Record<string, string>): Promise<void> {\n const args: string[] = []\n for (const [k, v] of Object.entries(fields)) args.push(k, v)\n if (args.length > 0) await this.client.hset(key, ...args)\n }\n\n /** HGET key field. */\n async hget(key: string, field: string): Promise<string | null> {\n return this.client.hget(key, field)\n }\n\n /** HGETALL key → пустой объект если ключ не существует (ioredis возвращает null). */\n async hgetAll(key: string): Promise<Record<string, string>> {\n const result = await this.client.hgetall(key)\n return result ?? {}\n }\n\n /** HINCRBY key field increment → новое значение счётчика. */\n async hincrby(key: string, field: string, increment: number): Promise<number> {\n return this.client.hincrby(key, field, increment)\n }\n\n /** HDEL key field. */\n async hdel(key: string, field: string): Promise<void> {\n await this.client.hdel(key, field)\n }\n\n /**\n * SET key value NX PX pxMs\n * NX: только если не существует. PX: TTL в миллисекундах.\n * Используется для захвата distributed lock'а.\n */\n async setNx(key: string, value: string, pxMs: number): Promise<boolean> {\n const result = await this.client.set(key, value, 'NX', 'PX', pxMs)\n return result === 'OK'\n }\n\n /**\n * Выполняет PEXPIRE_LUA: продлевает TTL lock'а только если мы — владелец.\n * Возвращает true если продление прошло успешно.\n */\n async pexpire(key: string, ms: number, value: string): Promise<boolean> {\n const result = await this.client.eval(PEXPIRE_LUA, 1, key, String(ms), value) as number\n return result === 1\n }\n\n /**\n * Выполняет DEL_LUA: удаляет lock только если мы — владелец.\n * Возвращает true если удаление прошло успешно.\n */\n async luaDel(key: string, value: string): Promise<boolean> {\n const result = await this.client.eval(DEL_LUA, 1, key, value) as number\n return result === 1\n }\n\n /** EXPIRE key seconds. */\n async expire(key: string, seconds: number): Promise<void> {\n await this.client.expire(key, seconds)\n }\n\n /**\n * Полный SCAN по паттерну: итерирует cursor пока не вернётся '0'.\n * @param count — подсказка Redis сколько ключей возвращать за итерацию.\n * @returns Полный список ключей (может быть большим для широких паттернов).\n */\n async scan(pattern: string, count = 100): Promise<string[]> {\n const keys: string[] = []\n let cursor = '0'\n do {\n const [nextCursor, batch] = await this.client.scan(cursor, 'MATCH', pattern, 'COUNT', count)\n keys.push(...batch)\n cursor = nextCursor\n } while (cursor !== '0')\n return keys\n }\n\n /** Закрывает соединение с Redis. */\n async quit(): Promise<void> {\n await this.client.quit()\n }\n}\n","/**\n * Единый реестр Redis-ключей.\n *\n * Все ключи определены в одном месте, чтобы избежать опечаток и упростить\n * поиск по кодовой базе. Полная документация формата — docs/redis-key-taxonomy.md.\n *\n * Префиксы:\n * ce:parser:<chainId>: — Stream-ключи, относящиеся к конкретной цепи.\n * parser: — Hash/ZSET/String ключи с глобальным скоупом.\n */\nexport const RedisKeys = {\n /**\n * Главный поток событий парсера (unified event stream).\n * Тип Redis: Stream. Тримируется XtrimSupervisor'ом.\n * Пример: ce:parser:eos-mainnet:events\n */\n eventsStream: (chainId: string) => `ce:parser:${chainId}:events`,\n\n /**\n * Dead-letter поток для конкретной подписки.\n * Содержит сообщения, которые не смог обработать consumer после N попыток.\n * Тип Redis: Stream.\n * Пример: ce:parser:eos-mainnet:dead:verifier\n */\n deadLetterStream: (chainId: string, subId: string) => `ce:parser:${chainId}:dead:${subId}`,\n\n /**\n * Поток для задания on-demand reparse (зарезервировано для будущего).\n * Тип Redis: Stream.\n */\n reparseStream: (chainId: string, jobId: string) => `ce:parser:${chainId}:reparse:${jobId}`,\n\n /**\n * История версий ABI конкретного контракта.\n * Тип Redis: Sorted Set. Score = block_num, member = base64(rawAbiBytes).\n * При поиске ABI для блока N используется ZREVRANGEBYSCORE … N -inf LIMIT 0 1.\n * Пример: parser:abi:eosio.token\n */\n abiZset: (contract: string) => `parser:abi:${contract}`,\n\n /**\n * Контрольная точка синхронизации парсера (crash-recovery).\n * Тип Redis: Hash. Поля: block_num, block_id, last_updated.\n * При рестарте парсер читает отсюда позицию и продолжает с неё.\n */\n syncHash: (chainId: string) => `parser:sync:${chainId}`,\n\n /**\n * Реестр всех зарегистрированных подписок.\n * Тип Redis: Hash. Ключ поля = subId, значение = JSON-метаданные подписки.\n */\n subsHash: () => `parser:subs`,\n\n /**\n * Счётчики ошибок per-event для конкретной подписки.\n * Тип Redis: Hash. Ключ поля = event_id, значение = число провалов.\n * TTL: 24 часа (обновляется при каждом новом провале).\n * Используется FailureTracker для решения о переводе в dead-letter.\n */\n subFailuresHash: (subId: string) => `parser:sub:${subId}:failures`,\n\n /**\n * Блокировка single-active-consumer для подписки.\n * Тип Redis: String (instanceId держателя блокировки). TTL: 10 с (автопродление).\n * Только один экземпляр consumer-а может быть active; остальные — standby.\n */\n subLock: (subId: string) => `parser:sub:${subId}:lock`,\n\n /**\n * Метаданные задания reparse (зарезервировано для будущего).\n * Тип Redis: Hash.\n */\n reparseJobHash: (jobId: string) => `parser:reparse:${jobId}`,\n} as const\n","/**\n * CLI-команда: parser list-subscriptions\n *\n * Показывает зарегистрированные подписки и их текущее состояние в consumer group.\n *\n * Источник данных — два ключа Redis:\n * 1. parser:subs — HASH: subId → JSON с метаданными подписки (filters, startFrom, registeredAt).\n * Записывается при вызове ParserClient.subscribe(), сохраняется между перезапусками.\n *\n * 2. ce:parser:<chainId>:events — Redis Stream с consumer groups.\n * XINFO GROUPS даёт для каждой группы: pending count, lag, last-delivered-id.\n * Если стрим ещё не создан (парсер не запускался) — xinfoGroups выбросит ошибку;\n * мы перехватываем её и показываем все подписки как «not started».\n *\n * Соединение двух источников: по subId.\n * HASH — регистрационные данные, XINFO — runtime-статистика.\n * Подписка может быть в HASH но не иметь consumer group (зарегистрирована, но не запущена).\n *\n * Колонка LAG: число сообщений в стриме после last-delivered-id этой группы.\n * Растущий LAG означает что consumer отстаёт или не работает.\n *\n * Режим --json: выводит полный JSON-массив SubStatus объектов — удобен для мониторинга и автоматизации.\n */\n\nimport type { RedisStore } from '../../ports/RedisStore.js'\nimport { RedisKeys } from '../../redis/keys.js'\n\n/**\n * Метаданные подписки, хранимые в Redis HASH parser:subs.\n * Сохраняются при registerSubscription() и используются для восстановления после рестарта.\n */\ninterface SubMeta {\n subId: string\n /** Массив фильтров (action/delta/native-delta); пустой массив = принять всё. */\n filters: Array<Record<string, string>>\n /** Начальный блок: число или 'latest'. Используется при первом запуске consumer group. */\n startFrom: string\n /** ISO-8601 время первой регистрации — для информационных целей. */\n registeredAt?: string\n}\n\n/**\n * Полная информация о подписке для вывода пользователю.\n * Объединяет данные из Redis HASH и XINFO GROUPS.\n */\nexport interface SubStatus {\n subId: string\n filters: Array<Record<string, string>>\n startFrom: string\n registeredAt: string\n /** Количество pending (непросмотренных) сообщений в PEL. null если группа не создана. */\n pending: number | null\n /** Отставание группы от конца стрима (сообщений). null если группа не создана. */\n lag: number | null\n /** ID последнего доставленного сообщения, или 'not started'. */\n lastDeliveredId: string\n}\n\n/**\n * Форматирует массив фильтров в компактную читаемую строку для таблицы.\n * Показывает максимум 2 первых фильтра, остальное обрезается.\n *\n * Примеры вывода:\n * action:eosio/setabi — конкретный экшн\n * delta:eosio.token — дельта таблицы по контракту\n * native-delta:accounts — нативная дельта по имени таблицы\n * * — пустой массив (принять всё)\n */\nfunction formatFilters(filters: Array<Record<string, string>>): string {\n if (!filters || filters.length === 0) return '*'\n return filters.slice(0, 2).map(f => {\n const kind = f['kind'] ?? '*'\n if (kind === 'action') return `action:${f['account'] ?? '*'}/${f['name'] ?? '*'}`\n if (kind === 'delta') return `delta:${f['code'] ?? '*'}`\n if (kind === 'native-delta') return `native-delta:${f['table'] ?? '*'}`\n return kind\n }).join(',')\n}\n\n/**\n * Основная функция команды list-subscriptions.\n *\n * Алгоритм:\n * 1. HGETALL parser:subs → все зарегистрированные подписки.\n * 2. XINFO GROUPS <stream> → runtime-статистика consumer groups (может упасть если стрим не создан).\n * 3. JOIN по subId: дополняем каждую подписку данными из consumer group.\n * 4. Вывод: JSON-массив или ASCII-таблица с выравниванием.\n *\n * @param redis — Redis-клиент.\n * @param chainId — идентификатор цепи (для построения имени стрима).\n * @param json — вывод в JSON вместо таблицы.\n */\nexport async function listSubscriptions(\n redis: RedisStore,\n chainId: string,\n json: boolean,\n): Promise<void> {\n // Читаем все зарегистрированные подписки из Redis HASH\n const allSubs = await redis.hgetAll(RedisKeys.subsHash())\n\n if (Object.keys(allSubs).length === 0) {\n console.log('No subscriptions registered.')\n return\n }\n\n const stream = RedisKeys.eventsStream(chainId)\n let groups: Awaited<ReturnType<typeof redis.xinfoGroups>> = []\n try {\n groups = await redis.xinfoGroups(stream)\n } catch {\n // Стрим не существует — парсер ещё ни разу не запускался.\n // Показываем подписки как зарегистрированные, но без runtime-статистики.\n }\n\n const results: SubStatus[] = []\n\n for (const rawJson of Object.values(allSubs)) {\n let meta: SubMeta\n try {\n meta = JSON.parse(rawJson) as SubMeta\n } catch {\n continue // пропускаем повреждённые записи\n }\n\n // Ищем соответствующую consumer group по имени (subId = groupName)\n const group = groups.find(g => g.name === meta.subId)\n results.push({\n subId: meta.subId,\n filters: meta.filters,\n startFrom: String(meta.startFrom),\n registeredAt: meta.registeredAt ?? '',\n pending: group !== undefined ? group.pending : null,\n lag: group !== undefined ? group.lag : null,\n lastDeliveredId: group?.lastDeliveredId ?? 'not started',\n })\n }\n\n if (json) {\n console.log(JSON.stringify(results, null, 2))\n return\n }\n\n // Ширина колонок ASCII-таблицы\n const cols = {\n subId: 16,\n filters: 25,\n pending: 9,\n lag: 6,\n lastDelivered: 21,\n startFrom: 10,\n }\n const header =\n 'SUB ID'.padEnd(cols.subId) +\n 'FILTERS'.padEnd(cols.filters) +\n 'PENDING'.padEnd(cols.pending) +\n 'LAG'.padEnd(cols.lag) +\n 'LAST DELIVERED'.padEnd(cols.lastDelivered) +\n 'START FROM'\n console.log(header)\n console.log('-'.repeat(header.length))\n\n for (const sub of results) {\n const row =\n sub.subId.slice(0, cols.subId - 1).padEnd(cols.subId) +\n formatFilters(sub.filters).slice(0, cols.filters - 1).padEnd(cols.filters) +\n String(sub.pending ?? '-').padEnd(cols.pending) +\n String(sub.lag ?? '-').padEnd(cols.lag) +\n sub.lastDeliveredId.slice(0, cols.lastDelivered - 1).padEnd(cols.lastDelivered) +\n sub.startFrom\n console.log(row)\n }\n}\n","/**\n * CLI-команда: parser reset-subscription\n *\n * Перемещает позицию consumer group в Redis Stream.\n *\n * Зачем нужно: после потери состояния Redis, ручного вмешательства или\n * при тестировании нужно заставить consumer повторно обработать события\n * начиная с конкретного блока (или пропустить всё до последнего).\n *\n * Операция: XGROUP SETID <stream> <groupName> <targetId>\n * После этого consumer при следующем старте начнёт читать с targetId.\n *\n * Нюансы:\n * - Если у group есть pending messages (PEL) — они будут повторно доставлены\n * при следующем recoverOwnPending. Предупреждаем об этом.\n * - Поиск targetId: сканируем XRANGE чтобы найти entry ID с block_num < targetBlock.\n * Устанавливаем group на этот ID — следующий XREADGROUP '>' начнёт с targetBlock.\n * - Если block_num уже вышел за пределы стрима (XTRIM) — выдаём ошибку.\n */\n\nimport type { RedisStore, StreamMessage } from '../../ports/RedisStore.js'\nimport { RedisKeys } from '../../redis/keys.js'\n\n/**\n * Сканирует стрим и находит последний entry ID с block_num < targetBlock.\n * Используется как позиция для XGROUP SETID: consumer начнёт с targetBlock,\n * а не с targetBlock-1 (т.к. читает '>' — после установленного ID).\n *\n * Пагинация через '(' + lastId (исключительный старт) чтобы не читать огромный стрим целиком.\n */\nasync function findSetidForBlock(\n redis: RedisStore,\n stream: string,\n targetBlock: number,\n): Promise<string> {\n let cursor = '-'\n let lastBeforeTarget = '0-0'\n\n while (true) {\n const entries: StreamMessage[] = await redis.xrange(stream, cursor, '+', 100)\n if (entries.length === 0) break\n\n for (const entry of entries) {\n let blockNum: number | undefined\n try {\n const event = JSON.parse(entry.fields['data'] ?? '{}') as { block_num?: unknown }\n if (event.block_num !== undefined) blockNum = Number(event.block_num)\n } catch { /* пропускаем записи с невалидным JSON */ }\n\n if (blockNum !== undefined) {\n if (blockNum < targetBlock) {\n // Эта запись — кандидат: block_num меньше целевого\n lastBeforeTarget = entry.id\n } else {\n // Нашли запись с block_num >= targetBlock — дальше искать не нужно\n return lastBeforeTarget\n }\n }\n }\n\n if (entries.length < 100) break\n const lastId = entries[entries.length - 1]!.id\n // Исключительный старт следующей страницы: '(' + id (Redis 6.2+)\n cursor = '(' + lastId\n }\n\n return lastBeforeTarget\n}\n\n/**\n * Выполняет сброс позиции consumer group.\n *\n * @param redis — Redis-клиент.\n * @param chainId — идентификатор цепи (для построения ключей).\n * @param subId — идентификатор подписки (имя consumer group).\n * @param toBlock — '0'/'latest' = '$' (конец стрима), или числовой block_num.\n * @param dryRun — только показать, не изменять Redis.\n */\nexport async function resetSubscription(\n redis: RedisStore,\n chainId: string,\n subId: string,\n toBlock: string,\n dryRun: boolean,\n): Promise<void> {\n const stream = RedisKeys.eventsStream(chainId)\n const groupName = subId\n\n // Проверяем что consumer group вообще существует — иначе сброс бессмысленен\n let groups: Awaited<ReturnType<typeof redis.xinfoGroups>> = []\n try {\n groups = await redis.xinfoGroups(stream)\n } catch {\n throw new Error(`Subscription ${subId} has no active consumer group. Start the consumer first.`)\n }\n\n const group = groups.find(g => g.name === groupName)\n if (!group) {\n throw new Error(`Subscription ${subId} has no active consumer group. Start the consumer first.`)\n }\n\n const pelCount = group.pending\n\n // Определяем целевой ID\n let targetId: string\n if (toBlock === '0' || toBlock === 'latest' || toBlock === '$') {\n // '$' = «последний записанный ID»: consumer будет получать только новые события\n targetId = '$'\n } else {\n const blockNum = Number(toBlock)\n if (isNaN(blockNum) || blockNum < 0) {\n throw new Error(`Invalid --to-block value: ${toBlock}. Use a block number, 0, or \"latest\".`)\n }\n\n // Проверяем доступность: стрим мог быть обрезан XTRIM\n const firstEntries = await redis.xrange(stream, '-', '+', 1)\n if (firstEntries.length > 0) {\n let earliestBlock: number | undefined\n try {\n const event = JSON.parse(firstEntries[0]!.fields['data'] ?? '{}') as { block_num?: unknown }\n if (event.block_num !== undefined) earliestBlock = Number(event.block_num)\n } catch { /* игнорируем невалидный JSON */ }\n\n if (earliestBlock !== undefined && blockNum < earliestBlock) {\n throw new Error(\n `Block ${blockNum} is before earliest available block ${earliestBlock} (stream trimmed). Cannot reset to trimmed range.`,\n )\n }\n }\n\n // Ищем entry ID, предшествующий targetBlock\n targetId = await findSetidForBlock(redis, stream, blockNum)\n }\n\n if (dryRun) {\n console.log(`[dry-run] Would execute: XGROUP SETID ${stream} ${groupName} ${targetId}`)\n if (pelCount > 0) {\n console.log(`Warning: PEL has ${pelCount} pending messages. They will be re-delivered on next consumer start.`)\n }\n return\n }\n\n await redis.xgroupSetId(stream, groupName, targetId)\n console.log(`Reset subscription ${subId} to entry ${targetId}.`)\n if (pelCount > 0) {\n console.log(`Warning: PEL has ${pelCount} pending messages. They will be re-delivered on next consumer start.`)\n }\n}\n","/**\n * CLI-команда: parser abi-prune\n *\n * Удаляет устаревшие версии ABI из Redis Sorted Set.\n *\n * Проблема: каждый вызов eosio::setabi сохраняет новую версию ABI в ZSET.\n * За месяцы/годы для активных контрактов накапливаются сотни версий,\n * большинство из которых уже никогда не понадобятся.\n *\n * Логика удаления: удаляем версии со score (block_num) < olderThan.\n * Исключительная верхняя граница: `(olderThan` — чтобы не удалить\n * версию ровно на границе (версия exactAt должна остаться как «начальная»\n * для блоков начиная с olderThan).\n *\n * Защита: не допускаем удаление ВСЕХ версий — хотя бы одна должна остаться.\n *\n * Режим --dry-run: показывает что было бы удалено, не изменяя Redis.\n */\n\nimport type { RedisStore } from '../../ports/RedisStore.js'\nimport { RedisKeys } from '../../redis/keys.js'\n\n/**\n * Выполняет prune для одного контракта.\n * Выбрасывает Error если все версии попадут под удаление (safety guard).\n */\nasync function pruneContract(\n redis: RedisStore,\n contract: string,\n olderThan: number,\n dryRun: boolean,\n): Promise<{ pruned: number; remaining: number; oldestScore: number | null; newestScore: number | null }> {\n const key = RedisKeys.abiZset(contract)\n\n const total = await redis.zcard(key)\n if (total === 0) {\n return { pruned: 0, remaining: 0, oldestScore: null, newestScore: null }\n }\n\n // Считаем кандидатов: score строго меньше olderThan (исключительная верхняя граница)\n const candidateCount = await redis.zcount(key, '-inf', `(${olderThan}`)\n const remaining = total - candidateCount\n\n // Safety: нельзя удалить последнюю версию — без ABI декодирование сломается\n if (remaining < 1 && candidateCount > 0) {\n throw new Error(`Cannot prune all ABI versions for ${contract} — at least one must remain`)\n }\n\n if (!dryRun && candidateCount > 0) {\n await redis.zremRangeByScore(key, '-inf', `(${olderThan}`)\n }\n\n return { pruned: dryRun ? 0 : candidateCount, remaining, oldestScore: null, newestScore: null }\n}\n\n/**\n * Основная функция команды abi-prune.\n *\n * @param redis — Redis-клиент.\n * @param contract — имя контракта (null если allContracts=true).\n * @param olderThan — block_num: удалить версии с block_num < olderThan.\n * @param dryRun — только показать, не удалять.\n * @param allContracts — обработать все контракты в Redis (SCAN parser:abi:*).\n */\nexport async function abiPrune(\n redis: RedisStore,\n contract: string | null,\n olderThan: number,\n dryRun: boolean,\n allContracts: boolean,\n): Promise<void> {\n if (!allContracts && !contract) {\n throw new Error('Specify --contract or --all-contracts')\n }\n\n if (allContracts) {\n // SCAN по паттерну: находим все ABI-ключи во всех контрактах\n const keys = await redis.scan('parser:abi:*')\n if (keys.length === 0) {\n console.log('No ABI history found.')\n return\n }\n\n const rows: Array<{ contract: string; pruned: number; remaining: number }> = []\n for (const key of keys) {\n const name = key.replace(/^parser:abi:/, '')\n try {\n const result = await pruneContract(redis, name, olderThan, dryRun)\n rows.push({ contract: name, pruned: dryRun ? result.remaining : result.pruned, remaining: result.remaining })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(` ${name}: ${msg}`)\n }\n }\n\n if (dryRun) {\n console.log('CONTRACT'.padEnd(30) + 'WOULD PRUNE'.padEnd(14) + 'REMAINING')\n console.log('-'.repeat(54))\n for (const row of rows) {\n const candidateCount = row.pruned\n console.log(row.contract.padEnd(30) + String(candidateCount).padEnd(14) + row.remaining)\n }\n }\n return\n }\n\n // Одиночный контракт\n const key = RedisKeys.abiZset(contract!)\n const total = await redis.zcard(key)\n if (total === 0) {\n console.log(`No ABI history found for contract ${contract}.`)\n return\n }\n\n // Исключительная верхняя граница '(N': не трогаем версию ровно на olderThan\n const candidateCount = await redis.zcount(key, '-inf', `(${olderThan}`)\n const remaining = total - candidateCount\n\n if (remaining < 1 && candidateCount > 0) {\n console.error(`Cannot prune all ABI versions — at least one must remain`)\n process.exitCode = 1\n return\n }\n\n if (dryRun) {\n console.log(`[dry-run] Would prune ${candidateCount} ABI version(s) for ${contract}. ${remaining} version(s) would remain.`)\n return\n }\n\n const pruned = candidateCount > 0 ? await redis.zremRangeByScore(key, '-inf', `(${olderThan}`) : 0\n const newTotal = await redis.zcard(key)\n\n if (pruned === 0) {\n console.log(`Pruned 0 ABI versions for ${contract}. ${newTotal} version(s) remain.`)\n return\n }\n\n console.log(`Pruned ${pruned} ABI version(s) for ${contract}. ${newTotal} version(s) remain.`)\n}\n","/**\n * CLI-команда: parser list-dead-letters\n *\n * Выводит содержимое dead-letter stream(ов) в табличном или JSON формате.\n *\n * Dead-letter stream: ce:parser:<chainId>:dead:<subId>\n * Каждая запись содержит поля:\n * data — оригинальный JSON ParserEvent\n * failureCount — число провалов (обычно = FAILURE_THRESHOLD = 3)\n * lastError — текст последнего исключения\n * subId — идентификатор подписки-владельца\n *\n * Режим --all: сканирует Redis по паттерну ce:parser:<chainId>:dead:*\n * и показывает все dead-letter стримы для цепи.\n *\n * Режим --json: выводит JSON-массив объектов с разобранными полями —\n * удобен для автоматизации (парсинг в скриптах, jq, etc).\n */\n\nimport type { RedisStore } from '../../ports/RedisStore.js'\nimport { RedisKeys } from '../../redis/keys.js'\n\n/** Нормализованная структура одной dead-letter записи для вывода. */\ninterface DeadLetterEntry {\n entryId: string\n eventId: string\n kind: string\n failureCount: number\n lastError: string\n /** ISO-8601 время попадания в dead-letter (из timestamp части entry ID). */\n deadLetteredAt: string\n originalPayload: unknown\n}\n\n/**\n * Конвертирует Redis Stream entry ID в ISO timestamp.\n * Entry ID формат: <unix_ms>-<seq>. Первая часть — Unix timestamp в мс.\n */\nfunction entryIdToTimestamp(entryId: string): string {\n const ms = parseInt(entryId.split('-')[0] ?? '0', 10)\n return new Date(ms).toISOString()\n}\n\n/** Обрезает строку до max символов с добавлением '…' при усечении. */\nfunction truncate(s: string, max: number): string {\n return s.length > max ? s.slice(0, max - 1) + '…' : s\n}\n\n/**\n * Читает dead-letter записи из стрима и разбирает JSON поля.\n * Ошибки парсинга игнорируются — выводим пустые строки вместо краша.\n */\nasync function readDeadLetters(\n redis: RedisStore,\n stream: string,\n limit: number,\n fromEntry: string,\n): Promise<DeadLetterEntry[]> {\n const raw = await redis.xrange(stream, fromEntry, '+', limit)\n return raw.map(msg => {\n let eventId = ''\n let kind = ''\n let originalPayload: unknown = null\n const dataStr = msg.fields['data']\n if (dataStr) {\n try {\n const parsed = JSON.parse(dataStr) as Record<string, unknown>\n eventId = typeof parsed['event_id'] === 'string' ? parsed['event_id'] : ''\n kind = typeof parsed['kind'] === 'string' ? parsed['kind'] : ''\n originalPayload = parsed\n } catch { /* невалидный JSON в поле data — оставляем пустые строки */ }\n }\n return {\n entryId: msg.id,\n eventId,\n kind,\n failureCount: parseInt(msg.fields['failureCount'] ?? '0', 10),\n lastError: msg.fields['lastError'] ?? '',\n deadLetteredAt: entryIdToTimestamp(msg.id),\n originalPayload,\n }\n })\n}\n\n/**\n * Основная функция команды list-dead-letters.\n *\n * @param redis — Redis-клиент.\n * @param chainId — идентификатор цепи.\n * @param subId — идентификатор подписки (null если all=true).\n * @param json — вывод в JSON вместо таблицы.\n * @param limit — максимум записей за вызов.\n * @param fromEntry — начало диапазона XRANGE (по умолчанию '-').\n * @param all — показать все dead-letter стримы для chainId.\n */\nexport async function listDeadLetters(\n redis: RedisStore,\n chainId: string,\n subId: string | null,\n json: boolean,\n limit: number,\n fromEntry: string,\n all: boolean,\n): Promise<void> {\n let streams: Array<{ key: string; subId: string }> = []\n\n if (all) {\n // SCAN по паттерну: ce:parser:<chainId>:dead:*\n const pattern = RedisKeys.deadLetterStream(chainId, '*')\n const keys = await redis.scan(pattern)\n const prefix = `ce:parser:${chainId}:dead:`\n streams = keys.map(k => ({ key: k, subId: k.slice(prefix.length) }))\n if (streams.length === 0) {\n console.log('No dead-letter streams found.')\n return\n }\n } else {\n if (!subId) throw new Error('--sub-id is required unless --all is specified')\n streams = [{ key: RedisKeys.deadLetterStream(chainId, subId), subId }]\n }\n\n if (json) {\n // JSON-режим: собираем все записи и выводим единым массивом\n const allEntries: Array<DeadLetterEntry & { subId: string }> = []\n for (const { key, subId: sid } of streams) {\n const entries = await readDeadLetters(redis, key, limit, fromEntry)\n allEntries.push(...entries.map(e => ({ ...e, subId: sid })))\n }\n console.log(JSON.stringify(allEntries, null, 2))\n return\n }\n\n // Табличный режим\n for (const { key, subId: sid } of streams) {\n const total = await redis.xlen(key)\n console.log(`Dead letters for ${sid}: ${total} total`)\n if (total === 0) {\n console.log(`No dead letters for subscription ${sid}.`)\n continue\n }\n\n const entries = await readDeadLetters(redis, key, limit, fromEntry)\n if (entries.length === 0) {\n console.log(`No dead letters for subscription ${sid}.`)\n continue\n }\n\n // Ширина колонок для выравнивания\n const cols = {\n entryId: 22,\n eventId: 50,\n kind: 14,\n failCount: 6,\n lastError: 40,\n }\n if (all) {\n // В режиме --all добавляем колонку SUB ID\n const subCol = 12\n console.log(\n 'SUB ID'.padEnd(subCol) +\n 'ENTRY ID'.padEnd(cols.entryId) +\n 'EVENT ID'.padEnd(cols.eventId) +\n 'KIND'.padEnd(cols.kind) +\n 'FAIL#'.padEnd(cols.failCount) +\n 'LAST ERROR',\n )\n for (const e of entries) {\n console.log(\n truncate(sid, subCol).padEnd(subCol) +\n truncate(e.entryId, cols.entryId).padEnd(cols.entryId) +\n truncate(e.eventId, cols.eventId).padEnd(cols.eventId) +\n truncate(e.kind, cols.kind).padEnd(cols.kind) +\n String(e.failureCount).padEnd(cols.failCount) +\n truncate(e.lastError, cols.lastError),\n )\n }\n } else {\n console.log(\n 'ENTRY ID'.padEnd(cols.entryId) +\n 'EVENT ID'.padEnd(cols.eventId) +\n 'KIND'.padEnd(cols.kind) +\n 'FAIL#'.padEnd(cols.failCount) +\n 'LAST ERROR',\n )\n for (const e of entries) {\n console.log(\n truncate(e.entryId, cols.entryId).padEnd(cols.entryId) +\n truncate(e.eventId, cols.eventId).padEnd(cols.eventId) +\n truncate(e.kind, cols.kind).padEnd(cols.kind) +\n String(e.failureCount).padEnd(cols.failCount) +\n truncate(e.lastError, cols.lastError),\n )\n }\n }\n }\n}\n","/**\n * CLI-команда: parser replay-dead-letter\n *\n * Перемещает события из dead-letter stream обратно в основной поток событий.\n *\n * Схема replay одного события:\n * 1. XRANGE dead-stream: ищем запись с нужным event_id (пагинация).\n * 2. XADD live-stream: добавляем оригинальный payload обратно в основной стрим.\n * 3. XDEL dead-stream: удаляем из dead-letter.\n * 4. HDEL failures-hash: обнуляем счётчик ошибок, иначе при первой же ошибке\n * событие снова уйдёт в dead-letter (счётчик уже = 3).\n *\n * Поиск по event_id: XRANGE не умеет искать по полям — только по ID записи.\n * Поэтому сканируем с пагинацией: читаем батчами по 100, ищем event_id в JSON.\n * Исключительный курсор '(' + lastId позволяет не читать уже просмотренные записи.\n *\n * Режим --all: воспроизводим все записи из dead-letter за один вызов.\n * Режим --dry-run: показывает что будет сделано без изменений.\n */\n\nimport type { RedisStore, StreamMessage } from '../../ports/RedisStore.js'\nimport { RedisKeys } from '../../redis/keys.js'\n\n/**\n * Ищет запись с нужным event_id в стриме путём последовательного XRANGE.\n * @returns StreamMessage если найдено, null если событие не существует.\n */\nasync function findByEventId(\n redis: RedisStore,\n stream: string,\n eventId: string,\n): Promise<StreamMessage | null> {\n let cursor = '-'\n while (true) {\n const batch = await redis.xrange(stream, cursor, '+', 100)\n if (batch.length === 0) return null\n for (const msg of batch) {\n const dataStr = msg.fields['data']\n if (dataStr) {\n try {\n const parsed = JSON.parse(dataStr) as Record<string, unknown>\n if (parsed['event_id'] === eventId) return msg\n } catch { /* пропускаем записи с невалидным JSON */ }\n }\n }\n const last = batch[batch.length - 1]\n // Если получили меньше 100 — конец стрима, событие не найдено\n if (!last || batch.length < 100) return null\n // '(' + lastId — исключительный старт следующей страницы\n cursor = '(' + last.id\n }\n}\n\n/**\n * Воспроизводит одно сообщение: XADD → XDEL → HDEL.\n * @returns Новый entry ID в live stream, или null в dry-run режиме.\n */\nasync function replaySingle(\n redis: RedisStore,\n liveStream: string,\n deadStream: string,\n subId: string,\n msg: StreamMessage,\n dryRun: boolean,\n): Promise<string | null> {\n const dataStr = msg.fields['data']\n if (!dataStr) return null\n\n let eventId = ''\n try {\n const parsed = JSON.parse(dataStr) as Record<string, unknown>\n eventId = typeof parsed['event_id'] === 'string' ? parsed['event_id'] : ''\n } catch { /* игнорируем невалидный JSON */ }\n\n if (dryRun) {\n console.log(`[dry-run] Would replay event ${eventId || msg.id} → live stream, delete from dead-letter.`)\n return null\n }\n\n // Шаг 1: добавляем оригинальный payload в основной стрим\n const newId = await redis.xadd(liveStream, { data: dataStr })\n // Шаг 2: удаляем из dead-letter\n await redis.xdel(deadStream, msg.id)\n // Шаг 3: сбрасываем счётчик ошибок — иначе событие снова уйдёт в DL при первой ошибке\n if (eventId) {\n await redis.hdel(RedisKeys.subFailuresHash(subId), eventId)\n }\n return newId\n}\n\n/**\n * Основная функция команды replay-dead-letter.\n *\n * @param redis — Redis-клиент.\n * @param chainId — идентификатор цепи.\n * @param subId — идентификатор подписки (определяет dead-letter stream).\n * @param eventId — event_id для воспроизведения (null если all=true).\n * @param all — воспроизвести все события из dead-letter.\n * @param dryRun — только показать, не изменять Redis.\n */\nexport async function replayDeadLetter(\n redis: RedisStore,\n chainId: string,\n subId: string,\n eventId: string | null,\n all: boolean,\n dryRun: boolean,\n): Promise<void> {\n const liveStream = RedisKeys.eventsStream(chainId)\n const deadStream = RedisKeys.deadLetterStream(chainId, subId)\n\n if (all) {\n let cursor = '-'\n let replayed = 0\n while (true) {\n const batch = await redis.xrange(deadStream, cursor, '+', 100)\n if (batch.length === 0) break\n for (const msg of batch) {\n const newId = await replaySingle(redis, liveStream, deadStream, subId, msg, dryRun)\n if (newId !== null) replayed++\n }\n if (dryRun) {\n // В dry-run не удаляем записи — показываем количество и выходим\n replayed = batch.length\n break\n }\n if (batch.length < 100) break\n // После удаления стрим укорачивается — начинаем сначала\n cursor = '-'\n if (replayed > 0) break // safety: предотвращаем бесконечный цикл\n }\n if (dryRun) {\n console.log(`[dry-run] Would replay ${replayed} events from dead-letter stream for ${subId}.`)\n } else {\n console.log(`Replayed ${replayed} events, removed ${replayed} from dead-letter.`)\n }\n return\n }\n\n if (!eventId) throw new Error('--event-id is required unless --all is specified')\n\n const msg = await findByEventId(redis, deadStream, eventId)\n if (!msg) {\n console.error(`Event not found in dead-letter stream for subscription ${subId}.`)\n process.exit(1)\n }\n\n const newId = await replaySingle(redis, liveStream, deadStream, subId, msg, dryRun)\n if (newId !== null) {\n console.log(`Replayed event ${eventId} → new entry id ${newId}. Removed from dead-letter.`)\n }\n}\n"],"mappings":"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","names":["key","candidateCount","msg","newId"]}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/workers/deserialize.worker.ts
|
|
21
|
+
var deserialize_worker_exports = {};
|
|
22
|
+
__export(deserialize_worker_exports, {
|
|
23
|
+
default: () => deserialize
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(deserialize_worker_exports);
|
|
26
|
+
var import_node_worker_threads = require("worker_threads");
|
|
27
|
+
var import_antelope = require("@wharfkit/antelope");
|
|
28
|
+
var import_node_crypto = require("crypto");
|
|
29
|
+
if (import_node_worker_threads.isMainThread) {
|
|
30
|
+
throw new Error("This file must be run in a worker thread");
|
|
31
|
+
}
|
|
32
|
+
var abiCache = /* @__PURE__ */ new Map();
|
|
33
|
+
function getAbi(abiJson) {
|
|
34
|
+
const hash = (0, import_node_crypto.createHash)("sha256").update(abiJson).digest("hex");
|
|
35
|
+
let parsed = abiCache.get(hash);
|
|
36
|
+
if (!parsed) {
|
|
37
|
+
parsed = import_antelope.ABI.from(JSON.parse(abiJson));
|
|
38
|
+
abiCache.set(hash, parsed);
|
|
39
|
+
}
|
|
40
|
+
return parsed;
|
|
41
|
+
}
|
|
42
|
+
function deserialize(task) {
|
|
43
|
+
const abi = getAbi(task.abiJson);
|
|
44
|
+
const raw = import_antelope.Serializer.decode({ data: task.rawBinary, type: task.typeName, abi });
|
|
45
|
+
return import_antelope.Serializer.objectify(raw);
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=deserialize.worker.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/workers/deserialize.worker.ts"],"sourcesContent":["/**\n * Worker-поток для ABI-десериализации (Piscina).\n *\n * Пискина запускает этот файл в отдельных worker_threads.\n * Десериализация — CPU-интенсивная операция (парсинг ABI + бинарное декодирование\n * через wharfkit/antelope), поэтому она вынесена в пул потоков чтобы не блокировать\n * event loop главного потока.\n *\n * ABI-кэш (abiCache): внутри worker'а держим Map<sha256 → ABI object>.\n * ABI одного контракта приходит в сотнях тысяч вызовов — кэш экономит повторный\n * ABI.from(JSON.parse(…)) при каждом декодировании.\n */\n\nimport { isMainThread } from 'node:worker_threads'\nimport { ABI, Serializer, type ABISerializable } from '@wharfkit/antelope'\nimport { createHash } from 'node:crypto'\n\n// Защита: этот файл нельзя запускать напрямую в главном потоке\nif (isMainThread) {\n throw new Error('This file must be run in a worker thread')\n}\n\ninterface WorkerTask {\n /** Сырые бинарные данные для декодирования (action data или table row). */\n rawBinary: Uint8Array\n /** Сериализованный ABI в JSON-формате (от ABI.from(…).toJSON()). */\n abiJson: string\n contract: string\n /** Имя типа в ABI-схеме, который нужно декодировать. */\n typeName: string\n kind: 'action' | 'delta'\n}\n\n/**\n * Кэш разобранных ABI по SHA-256 от JSON-строки.\n * Живёт в памяти worker'а на протяжении всего его существования.\n * При смене ABI (setabi) в главном потоке передаётся новый abiJson → новый хэш → новый элемент кэша.\n */\nconst abiCache = new Map<string, ABI>()\n\n/**\n * Возвращает ABI-объект для abiJson, используя кэш.\n * SHA-256 — достаточно быстрый и collision-resistant ключ кэша.\n */\nfunction getAbi(abiJson: string): ABI {\n const hash = createHash('sha256').update(abiJson).digest('hex')\n let parsed = abiCache.get(hash)\n if (!parsed) {\n parsed = ABI.from(JSON.parse(abiJson) as object)\n abiCache.set(hash, parsed)\n }\n return parsed\n}\n\n/**\n * Основная функция worker'а — экспортируется по default для Piscina.\n *\n * Шаги:\n * 1. Разбираем ABI (или берём из кэша).\n * 2. Serializer.decode — бинарное декодирование rawBinary согласно typeName в схеме ABI.\n * 3. Serializer.objectify — конвертирует wharfkit-объекты в plain JS Record.\n */\nexport default function deserialize(task: WorkerTask): Record<string, unknown> {\n const abi = getAbi(task.abiJson)\n const raw = Serializer.decode({ data: task.rawBinary, type: task.typeName, abi })\n return Serializer.objectify(raw as ABISerializable) as Record<string, unknown>\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,iCAA6B;AAC7B,sBAAsD;AACtD,yBAA2B;AAG3B,IAAI,yCAAc;AAChB,QAAM,IAAI,MAAM,0CAA0C;AAC5D;AAkBA,IAAM,WAAW,oBAAI,IAAiB;AAMtC,SAAS,OAAO,SAAsB;AACpC,QAAM,WAAO,+BAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAC9D,MAAI,SAAS,SAAS,IAAI,IAAI;AAC9B,MAAI,CAAC,QAAQ;AACX,aAAS,oBAAI,KAAK,KAAK,MAAM,OAAO,CAAW;AAC/C,aAAS,IAAI,MAAM,MAAM;AAAA,EAC3B;AACA,SAAO;AACT;AAUe,SAAR,YAA6B,MAA2C;AAC7E,QAAM,MAAM,OAAO,KAAK,OAAO;AAC/B,QAAM,MAAM,2BAAW,OAAO,EAAE,MAAM,KAAK,WAAW,MAAM,KAAK,UAAU,IAAI,CAAC;AAChF,SAAO,2BAAW,UAAU,GAAsB;AACpD;","names":[]}
|