[549-27][@ant] refactor: вернуть имя parser2 обратно (полный реверт 549-20) — существующий @coopenomics/parser@2026.x в mono-ai живёт в продакшене (dicoop/cooparser всё ещё деплоится), параллельный publish @coopenomics/parser@1.0.x из этого репо создавал коллизию ожиданий у потребителей; раздельные имена parser2/parser устраняют путаницу. Папка packages/parser→parser2, пакет @coopenomics/parser→@coopenomics/parser2, docker image dicoop/parser→dicoop/parser2, Redis prefix parser:→parser2: (ce:parser:, abi, sub, sync, subs, reparse), метрики parser_*→parser2_*, bin parser2. Версия bumpnута до 1.0.2 для нового публичного пакета @coopenomics/parser2
This commit is contained in:
@@ -36,7 +36,7 @@ jobs:
|
||||
with: { node-version: '20', cache: 'pnpm' }
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm --filter @coopenomics/coopos-ship-reader build
|
||||
- run: pnpm --filter @coopenomics/parser typecheck
|
||||
- run: pnpm --filter @coopenomics/parser2 typecheck
|
||||
|
||||
unit:
|
||||
name: Unit tests
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
with: { node-version: '20', cache: 'pnpm' }
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm --filter @coopenomics/coopos-ship-reader build
|
||||
- run: pnpm --filter @coopenomics/parser test:unit -- --reporter=verbose --coverage
|
||||
- run: pnpm --filter @coopenomics/parser2 test:unit -- --reporter=verbose --coverage
|
||||
- uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm --filter @coopenomics/coopos-ship-reader build
|
||||
# Parser2 build: нужен dist/deserialize.worker.cjs для Piscina worker pool
|
||||
- run: pnpm --filter @coopenomics/parser build
|
||||
- run: pnpm --filter @coopenomics/parser2 build
|
||||
# Запускаем nodeos вручную через docker run: образ dicoop/blockchain:v5.1.0-dev
|
||||
# не имеет CMD по умолчанию, и GitHub Actions services: не поддерживает
|
||||
# передачу команды контейнеру. Монтируем config.ini (SHiP на 8080) и genesis.json.
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
run: |
|
||||
docker run -d --name blockchain \
|
||||
-p 8888:8888 -p 8080:8080 -p 9876:9876 \
|
||||
-v "${GITHUB_WORKSPACE}/packages/parser/test/integration/node-config:/mnt/dev/config" \
|
||||
-v "${GITHUB_WORKSPACE}/packages/parser2/test/integration/node-config:/mnt/dev/config" \
|
||||
dicoop/blockchain:v5.1.0-dev \
|
||||
/bin/bash -c '/usr/local/bin/nodeos -d /mnt/dev/data -p eosio --config-dir /mnt/dev/config --genesis-json /mnt/dev/config/genesis.json --disable-replay-opts'
|
||||
sleep 3
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
echo "ERROR: Chain API did not become ready. Container logs:"
|
||||
docker logs blockchain | tail -100
|
||||
exit 1
|
||||
- run: pnpm --filter @coopenomics/parser test:integration
|
||||
- run: pnpm --filter @coopenomics/parser2 test:integration
|
||||
env:
|
||||
REDIS_URL: redis://localhost:6379
|
||||
CHAIN_URL: http://localhost:8888
|
||||
@@ -125,4 +125,4 @@ jobs:
|
||||
with: { node-version: '20', cache: 'pnpm' }
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm --filter @coopenomics/coopos-ship-reader build
|
||||
- run: pnpm --filter @coopenomics/parser build
|
||||
- run: pnpm --filter @coopenomics/parser2 build
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Release
|
||||
|
||||
# Запускается при каждом push в main (обычно — merge PR из dev).
|
||||
# Если версия в packages/parser/package.json изменилась (не совпадает с
|
||||
# Если версия в packages/parser2/package.json изменилась (не совпадает с
|
||||
# существующим git-тегом v<version>) — тогда:
|
||||
# 1. Публикуем workspace-пакеты в npm
|
||||
# 2. Создаём git-тег v<version> и пушим его
|
||||
@@ -50,12 +50,12 @@ jobs:
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
# Читаем версию из packages/parser/package.json и проверяем
|
||||
# Читаем версию из packages/parser2/package.json и проверяем
|
||||
# что тега v<version> ещё не существует.
|
||||
- name: Check version change
|
||||
id: ver
|
||||
run: |
|
||||
VERSION=$(node -p "require('./packages/parser/package.json').version")
|
||||
VERSION=$(node -p "require('./packages/parser2/package.json').version")
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
|
||||
echo "Tag v$VERSION already exists — nothing to release"
|
||||
@@ -69,13 +69,13 @@ jobs:
|
||||
if: steps.ver.outputs.released == 'true'
|
||||
run: |
|
||||
pnpm --filter @coopenomics/coopos-ship-reader build
|
||||
pnpm --filter @coopenomics/parser build
|
||||
pnpm --filter @coopenomics/parser2 build
|
||||
|
||||
# Настраиваем npm auth для lerna/pnpm: NODE_AUTH_TOKEN из setup-node
|
||||
# уже прописывает _authToken в .npmrc, но lerna внутри читает из npm cli,
|
||||
# поэтому дублируем через env NPM_TOKEN → npm_config_//registry.npmjs.org/:_authToken.
|
||||
# npm trusted publishing: OIDC через id-token: write, без NPM_TOKEN.
|
||||
# Настраивается на стороне npm: packages/coopenomics-parser/settings/access
|
||||
# Настраивается на стороне npm: packages/coopenomics-parser2/settings/access
|
||||
# → Trusted Publisher → GitHub Actions (repo=coopenomics/parser,
|
||||
# workflow=.github/workflows/release.yml, environment="").
|
||||
# Provenance включён — sigstore подпишет каждый publish.
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
with: { node-version: '20', cache: 'pnpm' }
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm --filter @coopenomics/coopos-ship-reader build
|
||||
- run: pnpm --filter @coopenomics/parser build
|
||||
- run: pnpm --filter @coopenomics/parser2 build
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
# Docker Hub auth: требует secrets DOCKERHUB_USERNAME + DOCKERHUB_TOKEN
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: dicoop/parser
|
||||
images: dicoop/parser2
|
||||
tags: |
|
||||
type=semver,pattern={{version}},value=v${{ needs.release.outputs.version }}
|
||||
type=semver,pattern={{major}}.{{minor}},value=v${{ needs.release.outputs.version }}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Универсальный индексер блокчейнов EOSIO / Antelope: читает блоки из State History Plugin (SHiP) по WebSocket, декодирует actions и дельты таблиц с учётом исторических ABI, публикует унифицированный поток событий в Redis Stream. Потребители получают события через `ParserClient` с single-active-consumer lock'ом, recovery после сбоев и dead-letter для poison-messages.
|
||||
|
||||
[](https://github.com/coopenomics/parser/actions/workflows/ci.yml)
|
||||
[](https://www.npmjs.com/package/@coopenomics/parser)
|
||||
[](https://www.npmjs.com/package/@coopenomics/parser2)
|
||||
[](LICENSE)
|
||||
|
||||
## Зачем
|
||||
@@ -14,7 +14,7 @@ SHiP отдаёт сырые бинарные блоки — чтобы прев
|
||||
|
||||
| Пакет | Описание | npm |
|
||||
|:---|:---|:---|
|
||||
| [`@coopenomics/parser`](packages/parser) | Ядро индексера (Parser) + подписочный клиент (ParserClient), CLI, observability | [`@coopenomics/parser`](https://www.npmjs.com/package/@coopenomics/parser) |
|
||||
| [`@coopenomics/parser2`](packages/parser2) | Ядро индексера (Parser) + подписочный клиент (ParserClient), CLI, observability | [`@coopenomics/parser2`](https://www.npmjs.com/package/@coopenomics/parser2) |
|
||||
| [`@coopenomics/coopos-ship-reader`](packages/ship-reader) | Низкоуровневый WebSocket SHiP клиент с поддержкой 24 нативных системных таблиц | [`@coopenomics/coopos-ship-reader`](https://www.npmjs.com/package/@coopenomics/coopos-ship-reader) |
|
||||
|
||||
## Быстрый старт
|
||||
@@ -22,7 +22,7 @@ SHiP отдаёт сырые бинарные блоки — чтобы прев
|
||||
### 1. Установка
|
||||
|
||||
```bash
|
||||
pnpm add @coopenomics/parser
|
||||
pnpm add @coopenomics/parser2
|
||||
# или если используете только SHiP-клиент без Redis-пайплайна:
|
||||
pnpm add @coopenomics/coopos-ship-reader
|
||||
```
|
||||
@@ -66,12 +66,12 @@ metrics:
|
||||
parser start --config parser.config.yaml
|
||||
```
|
||||
|
||||
Парсер подключится к SHiP, начнёт читать блоки с последней checkpoint-позиции (или с head если запускается впервые), и публиковать события в Redis stream `ce:parser:<chainId>:events`.
|
||||
Парсер подключится к SHiP, начнёт читать блоки с последней checkpoint-позиции (или с head если запускается впервые), и публиковать события в Redis stream `ce:parser2:<chainId>:events`.
|
||||
|
||||
### 3. Подписка на события из приложения
|
||||
|
||||
```typescript
|
||||
import { ParserClient } from '@coopenomics/parser'
|
||||
import { ParserClient } from '@coopenomics/parser2'
|
||||
|
||||
const client = new ParserClient({
|
||||
subscriptionId: 'my-app',
|
||||
@@ -118,12 +118,12 @@ parser abi-prune --keep-last 10 --all-contracts
|
||||
|
||||
## Docker
|
||||
|
||||
Публичный образ: [`dicoop/parser`](https://hub.docker.com/r/dicoop/parser) — multi-arch (`linux/amd64`, `linux/arm64`).
|
||||
Публичный образ: [`dicoop/parser2`](https://hub.docker.com/r/dicoop/parser2) — multi-arch (`linux/amd64`, `linux/arm64`).
|
||||
|
||||
```bash
|
||||
docker pull dicoop/parser:latest
|
||||
docker pull dicoop/parser2:latest
|
||||
# или конкретная версия
|
||||
docker pull dicoop/parser:1.0.1
|
||||
docker pull dicoop/parser2:1.0.1
|
||||
```
|
||||
|
||||
### Минимальный запуск
|
||||
@@ -134,7 +134,7 @@ docker pull dicoop/parser:1.0.1
|
||||
docker run --rm \
|
||||
-v $(pwd)/parser.config.yaml:/app/parser.config.yaml:ro \
|
||||
--network host \
|
||||
dicoop/parser:latest \
|
||||
dicoop/parser2:latest \
|
||||
start --config /app/parser.config.yaml
|
||||
```
|
||||
|
||||
@@ -156,7 +156,7 @@ services:
|
||||
- "6379:6379"
|
||||
|
||||
parser:
|
||||
image: dicoop/parser:1.0.1
|
||||
image: dicoop/parser2:1.0.1
|
||||
depends_on:
|
||||
- redis
|
||||
volumes:
|
||||
@@ -210,7 +210,7 @@ irreversibleOnly: false # читать head-блоки (false) или жда
|
||||
# Посмотреть подписки в прод-Redis
|
||||
docker run --rm --network host \
|
||||
-e REDIS_URL=redis://localhost:6379 \
|
||||
dicoop/parser:latest \
|
||||
dicoop/parser2:latest \
|
||||
list-subscriptions
|
||||
```
|
||||
|
||||
@@ -247,7 +247,7 @@ docker run --rm --network host \
|
||||
```
|
||||
.
|
||||
├── packages/
|
||||
│ ├── parser/ # @coopenomics/parser — ядро + CLI + ParserClient
|
||||
│ ├── parser/ # @coopenomics/parser2 — ядро + CLI + ParserClient
|
||||
│ └── ship-reader/ # @coopenomics/coopos-ship-reader — SHiP WS клиент
|
||||
├── docs/ # redis taxonomy, disaster recovery
|
||||
├── examples/ # пример verifier-like подписчика
|
||||
@@ -264,19 +264,19 @@ pnpm install
|
||||
|
||||
```bash
|
||||
pnpm build # build всех пакетов
|
||||
pnpm --filter @coopenomics/parser build # только parser
|
||||
pnpm --filter @coopenomics/parser2 build # только parser
|
||||
```
|
||||
|
||||
### Тесты
|
||||
|
||||
```bash
|
||||
pnpm test # unit во всех пакетах
|
||||
pnpm --filter @coopenomics/parser test:unit
|
||||
pnpm --filter @coopenomics/parser test:integration # нужен Docker для блокчейн-ноды
|
||||
pnpm --filter @coopenomics/parser2 test:unit
|
||||
pnpm --filter @coopenomics/parser2 test:integration # нужен Docker для блокчейн-ноды
|
||||
```
|
||||
|
||||
**Текущее покрытие:**
|
||||
- `@coopenomics/parser`: 81% statements / 74% functions (205 unit-тестов)
|
||||
- `@coopenomics/parser2`: 81% statements / 74% functions (205 unit-тестов)
|
||||
- `@coopenomics/coopos-ship-reader`: 81% statements / 86% functions (51 unit-тест)
|
||||
|
||||
### Бенчмарк
|
||||
@@ -308,7 +308,7 @@ pnpm release
|
||||
# 5. GitHub Actions release.yml автоматически:
|
||||
# • lerna publish from-package — опубликует в npm только пакеты с новой версией
|
||||
# • changelogithub — создаст GitHub Release с changelog
|
||||
# • docker build-push — опубликует образ в docker.io/dicoop/parser:<version>
|
||||
# • docker build-push — опубликует образ в docker.io/dicoop/parser2:<version>
|
||||
```
|
||||
|
||||
Альтернативно, если lerna version не использовалась — можно вручную поднять версию в `packages/*/package.json`, смёрджить в main, и workflow опубликует.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Disaster Recovery Runbook
|
||||
|
||||
This document covers recovery procedures for common failure scenarios involving `@coopenomics/parser`.
|
||||
This document covers recovery procedures for common failure scenarios involving `@coopenomics/parser2`.
|
||||
|
||||
---
|
||||
|
||||
## Scenario 1 — Redis data loss (RDB/AOF corruption or total loss)
|
||||
|
||||
**Symptoms:** Parser starts but emits no events; `redis-cli HGET parser:sync:<chainId> block_num` returns nil.
|
||||
**Symptoms:** Parser starts but emits no events; `redis-cli HGET parser2:sync:<chainId> block_num` returns nil.
|
||||
|
||||
**Impact:** The sync checkpoint is lost. The parser will restart from block 0 (or the last known position in config). Consumers will re-process old events — their handlers must be idempotent.
|
||||
|
||||
@@ -19,7 +19,7 @@ This document covers recovery procedures for common failure scenarios involving
|
||||
|
||||
2. **Decide start block.** If you know the last safely processed block, set it manually:
|
||||
```bash
|
||||
redis-cli HSET parser:sync:<chainId> block_num <N> block_id <ID> last_updated $(date -u +%FT%TZ)
|
||||
redis-cli HSET parser2:sync:<chainId> block_num <N> block_id <ID> last_updated $(date -u +%FT%TZ)
|
||||
```
|
||||
If unknown, leave empty and the parser will replay from 0.
|
||||
|
||||
@@ -30,7 +30,7 @@ This document covers recovery procedures for common failure scenarios involving
|
||||
|
||||
4. **Restart the parser.**
|
||||
|
||||
5. **Monitor lag** via `/health` endpoint or `parser_indexing_lag_seconds` Prometheus metric.
|
||||
5. **Monitor lag** via `/health` endpoint or `parser2_indexing_lag_seconds` Prometheus metric.
|
||||
|
||||
---
|
||||
|
||||
@@ -48,7 +48,7 @@ Simply restart the parser. The sync hash guarantees replay from the last complet
|
||||
|
||||
## Scenario 3 — Consumer handler failures → dead-letter accumulation
|
||||
|
||||
**Symptoms:** `ce:parser:<chainId>:dead:<subId>` stream grows; `parser_client_dead_letters_total` counter rising.
|
||||
**Symptoms:** `ce:parser2:<chainId>:dead:<subId>` stream grows; `parser2_client_dead_letters_total` counter rising.
|
||||
|
||||
### Recovery steps
|
||||
|
||||
@@ -128,6 +128,6 @@ The parser will automatically re-fetch the ABI from the chain RPC on the next bl
|
||||
|---|---|
|
||||
| Parser lag | `curl http://localhost:9090/health` |
|
||||
| Prometheus metrics | `curl http://localhost:9090/metrics` |
|
||||
| Stream length | `redis-cli XLEN ce:parser:<chainId>:events` |
|
||||
| Sync position | `redis-cli HGETALL parser:sync:<chainId>` |
|
||||
| Stream length | `redis-cli XLEN ce:parser2:<chainId>:events` |
|
||||
| Sync position | `redis-cli HGETALL parser2:sync:<chainId>` |
|
||||
| Dead letters | `parser list-dead-letters --chain <chainId> --all` |
|
||||
|
||||
+11
-11
@@ -1,21 +1,21 @@
|
||||
# Redis Key Taxonomy
|
||||
|
||||
All keys used by `@coopenomics/parser` follow a consistent namespace scheme.
|
||||
Keys produced by the **parser (indexer)** use the prefix `ce:parser:` or `parser:`.
|
||||
All keys used by `@coopenomics/parser2` follow a consistent namespace scheme.
|
||||
Keys produced by the **parser (indexer)** use the prefix `ce:parser2:` or `parser:`.
|
||||
|
||||
## Key catalogue
|
||||
|
||||
| Key pattern | Type | TTL | Produced by | Consumed by | Description |
|
||||
|---|---|---|---|---|---|
|
||||
| `ce:parser:{chainId}:events` | Stream | ∞ (XTRIM) | Parser | ParserClient | Unified event stream: all `action`, `delta`, `native-delta`, `fork` events |
|
||||
| `ce:parser:{chainId}:dead:{subId}` | Stream | ∞ | FailureTracker | CLI / admin | Dead-letter stream for a specific subscription; entries include `data`, `failureCount`, `lastError`, `subId` |
|
||||
| `ce:parser:{chainId}:reparse:{jobId}` | Stream | ∞ | CLI (future) | Parser | On-demand reparse job stream |
|
||||
| `parser:abi:{contract}` | Sorted Set | ∞ | AbiStore | BlockProcessor / AbiBootstrapper | ABI version history; score = block_num, member = base64-encoded raw ABI bytes |
|
||||
| `parser:sync:{chainId}` | Hash | ∞ | Parser | Parser (crash-recovery) | Sync checkpoint: `block_num`, `block_id`, `last_updated` |
|
||||
| `parser:subs` | Hash | ∞ | ParserClient | CLI (list-subscriptions) | Subscription registry; field = subId, value = JSON metadata |
|
||||
| `parser:sub:{subId}:failures` | Hash | 24 h | FailureTracker | FailureTracker / CLI | Per-event failure counter; field = event_id, value = count |
|
||||
| `parser:sub:{subId}:lock` | String | 10 s (auto-renew) | SubscriptionLock | SubscriptionLock | Active-standby lock; value = instanceId |
|
||||
| `parser:reparse:{jobId}` | Hash | ∞ | CLI (future) | Parser | Reparse job metadata |
|
||||
| `ce:parser2:{chainId}:events` | Stream | ∞ (XTRIM) | Parser | ParserClient | Unified event stream: all `action`, `delta`, `native-delta`, `fork` events |
|
||||
| `ce:parser2:{chainId}:dead:{subId}` | Stream | ∞ | FailureTracker | CLI / admin | Dead-letter stream for a specific subscription; entries include `data`, `failureCount`, `lastError`, `subId` |
|
||||
| `ce:parser2:{chainId}:reparse:{jobId}` | Stream | ∞ | CLI (future) | Parser | On-demand reparse job stream |
|
||||
| `parser2:abi:{contract}` | Sorted Set | ∞ | AbiStore | BlockProcessor / AbiBootstrapper | ABI version history; score = block_num, member = base64-encoded raw ABI bytes |
|
||||
| `parser2:sync:{chainId}` | Hash | ∞ | Parser | Parser (crash-recovery) | Sync checkpoint: `block_num`, `block_id`, `last_updated` |
|
||||
| `parser2:subs` | Hash | ∞ | ParserClient | CLI (list-subscriptions) | Subscription registry; field = subId, value = JSON metadata |
|
||||
| `parser2:sub:{subId}:failures` | Hash | 24 h | FailureTracker | FailureTracker / CLI | Per-event failure counter; field = event_id, value = count |
|
||||
| `parser2:sub:{subId}:lock` | String | 10 s (auto-renew) | SubscriptionLock | SubscriptionLock | Active-standby lock; value = instanceId |
|
||||
| `parser2:reparse:{jobId}` | Hash | ∞ | CLI (future) | Parser | Reparse job metadata |
|
||||
|
||||
## Naming rules
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ pnpm install
|
||||
**Step 2 — Build the package**
|
||||
|
||||
```bash
|
||||
pnpm --filter @coopenomics/parser build
|
||||
pnpm --filter @coopenomics/parser2 build
|
||||
```
|
||||
|
||||
**Step 3 — Configure**
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
* See README.md for the 5-step startup guide.
|
||||
*/
|
||||
|
||||
import { Parser, ParserClient } from '@coopenomics/parser'
|
||||
import type { NativeDeltaEvent, NativePermissionRow } from '@coopenomics/parser'
|
||||
import { Parser, ParserClient } from '@coopenomics/parser2'
|
||||
import type { NativeDeltaEvent, NativePermissionRow } from '@coopenomics/parser2'
|
||||
|
||||
// ── Configuration ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"start": "tsx index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coopenomics/parser": "workspace:*"
|
||||
"@coopenomics/parser2": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsx": "^4.15.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@coopenomics/parser",
|
||||
"version": "1.0.1",
|
||||
"name": "@coopenomics/parser2",
|
||||
"version": "1.0.2",
|
||||
"description": "Universal EOSIO/Antelope SHiP-to-Redis blockchain indexer (parser)",
|
||||
"license": "MIT",
|
||||
"author": "Coopenomics contributors",
|
||||
@@ -19,7 +19,7 @@
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"bin": {
|
||||
"parser": "./dist/cli/index.js"
|
||||
"parser2": "./dist/cli/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
+3
-3
@@ -60,7 +60,7 @@ async function pruneContract(
|
||||
* @param contract — имя контракта (null если allContracts=true).
|
||||
* @param olderThan — block_num: удалить версии с block_num < olderThan.
|
||||
* @param dryRun — только показать, не удалять.
|
||||
* @param allContracts — обработать все контракты в Redis (SCAN parser:abi:*).
|
||||
* @param allContracts — обработать все контракты в Redis (SCAN parser2:abi:*).
|
||||
*/
|
||||
export async function abiPrune(
|
||||
redis: RedisStore,
|
||||
@@ -75,7 +75,7 @@ export async function abiPrune(
|
||||
|
||||
if (allContracts) {
|
||||
// SCAN по паттерну: находим все ABI-ключи во всех контрактах
|
||||
const keys = await redis.scan('parser:abi:*')
|
||||
const keys = await redis.scan('parser2:abi:*')
|
||||
if (keys.length === 0) {
|
||||
console.log('No ABI history found.')
|
||||
return
|
||||
@@ -83,7 +83,7 @@ export async function abiPrune(
|
||||
|
||||
const rows: Array<{ contract: string; pruned: number; remaining: number }> = []
|
||||
for (const key of keys) {
|
||||
const name = key.replace(/^parser:abi:/, '')
|
||||
const name = key.replace(/^parser2:abi:/, '')
|
||||
try {
|
||||
const result = await pruneContract(redis, name, olderThan, dryRun)
|
||||
rows.push({ contract: name, pruned: dryRun ? result.remaining : result.pruned, remaining: result.remaining })
|
||||
+4
-4
@@ -3,14 +3,14 @@
|
||||
*
|
||||
* Выводит содержимое dead-letter stream(ов) в табличном или JSON формате.
|
||||
*
|
||||
* Dead-letter stream: ce:parser:<chainId>:dead:<subId>
|
||||
* Dead-letter stream: ce:parser2:<chainId>:dead:<subId>
|
||||
* Каждая запись содержит поля:
|
||||
* data — оригинальный JSON ParserEvent
|
||||
* failureCount — число провалов (обычно = FAILURE_THRESHOLD = 3)
|
||||
* lastError — текст последнего исключения
|
||||
* subId — идентификатор подписки-владельца
|
||||
*
|
||||
* Режим --all: сканирует Redis по паттерну ce:parser:<chainId>:dead:*
|
||||
* Режим --all: сканирует Redis по паттерну ce:parser2:<chainId>:dead:*
|
||||
* и показывает все dead-letter стримы для цепи.
|
||||
*
|
||||
* Режим --json: выводит JSON-массив объектов с разобранными полями —
|
||||
@@ -105,10 +105,10 @@ export async function listDeadLetters(
|
||||
let streams: Array<{ key: string; subId: string }> = []
|
||||
|
||||
if (all) {
|
||||
// SCAN по паттерну: ce:parser:<chainId>:dead:*
|
||||
// SCAN по паттерну: ce:parser2:<chainId>:dead:*
|
||||
const pattern = RedisKeys.deadLetterStream(chainId, '*')
|
||||
const keys = await redis.scan(pattern)
|
||||
const prefix = `ce:parser:${chainId}:dead:`
|
||||
const prefix = `ce:parser2:${chainId}:dead:`
|
||||
streams = keys.map(k => ({ key: k, subId: k.slice(prefix.length) }))
|
||||
if (streams.length === 0) {
|
||||
console.log('No dead-letter streams found.')
|
||||
+4
-4
@@ -4,10 +4,10 @@
|
||||
* Показывает зарегистрированные подписки и их текущее состояние в consumer group.
|
||||
*
|
||||
* Источник данных — два ключа Redis:
|
||||
* 1. parser:subs — HASH: subId → JSON с метаданными подписки (filters, startFrom, registeredAt).
|
||||
* 1. parser2:subs — HASH: subId → JSON с метаданными подписки (filters, startFrom, registeredAt).
|
||||
* Записывается при вызове ParserClient.subscribe(), сохраняется между перезапусками.
|
||||
*
|
||||
* 2. ce:parser:<chainId>:events — Redis Stream с consumer groups.
|
||||
* 2. ce:parser2:<chainId>:events — Redis Stream с consumer groups.
|
||||
* XINFO GROUPS даёт для каждой группы: pending count, lag, last-delivered-id.
|
||||
* Если стрим ещё не создан (парсер не запускался) — xinfoGroups выбросит ошибку;
|
||||
* мы перехватываем её и показываем все подписки как «not started».
|
||||
@@ -26,7 +26,7 @@ import type { RedisStore } from '../../ports/RedisStore.js'
|
||||
import { RedisKeys } from '../../redis/keys.js'
|
||||
|
||||
/**
|
||||
* Метаданные подписки, хранимые в Redis HASH parser:subs.
|
||||
* Метаданные подписки, хранимые в Redis HASH parser2:subs.
|
||||
* Сохраняются при registerSubscription() и используются для восстановления после рестарта.
|
||||
*/
|
||||
interface SubMeta {
|
||||
@@ -81,7 +81,7 @@ function formatFilters(filters: Array<Record<string, string>>): string {
|
||||
* Основная функция команды list-subscriptions.
|
||||
*
|
||||
* Алгоритм:
|
||||
* 1. HGETALL parser:subs → все зарегистрированные подписки.
|
||||
* 1. HGETALL parser2:subs → все зарегистрированные подписки.
|
||||
* 2. XINFO GROUPS <stream> → runtime-статистика consumer groups (может упасть если стрим не создан).
|
||||
* 3. JOIN по subId: дополняем каждую подписку данными из consumer group.
|
||||
* 4. Вывод: JSON-массив или ASCII-таблица с выравниванием.
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* CLI точка входа для @coopenomics/parser.
|
||||
* CLI точка входа для @coopenomics/parser2.
|
||||
*
|
||||
* Инструмент командной строки `parser` предоставляет набор операционных команд
|
||||
* для управления парсером без его перезапуска:
|
||||
@@ -32,7 +32,7 @@ const program = new Command()
|
||||
|
||||
program
|
||||
.name('parser')
|
||||
.description('@coopenomics/parser — universal EOSIO/Antelope blockchain indexer')
|
||||
.description('@coopenomics/parser2 — universal EOSIO/Antelope blockchain indexer')
|
||||
.version('0.1.0')
|
||||
|
||||
/**
|
||||
@@ -103,7 +103,7 @@ program
|
||||
|
||||
/**
|
||||
* Команда `list-subscriptions`: показывает все зарегистрированные подписки.
|
||||
* Читает из parser:subs HASH и объединяет с данными XINFO GROUPS.
|
||||
* Читает из parser2:subs HASH и объединяет с данными XINFO GROUPS.
|
||||
* Полезна для мониторинга: видно pending, lag и last-delivered-id каждой группы.
|
||||
*/
|
||||
program
|
||||
@@ -165,7 +165,7 @@ program
|
||||
* Команда `abi-prune`: удаляет устаревшие версии ABI из Redis ZSET.
|
||||
* Без периодической очистки активные контракты накапливают сотни версий.
|
||||
* --older-than <block> → удалить версии с block_num < этого значения.
|
||||
* --all-contracts → SCAN по parser:abi:* и применить к каждому контракту.
|
||||
* --all-contracts → SCAN по parser2:abi:* и применить к каждому контракту.
|
||||
* --dry-run → показать количество версий для удаления без изменений.
|
||||
*/
|
||||
program
|
||||
@@ -196,7 +196,7 @@ program
|
||||
|
||||
/**
|
||||
* Команда `list-dead-letters`: инспектирует dead-letter stream(ы).
|
||||
* Dead-letter stream: ce:parser:<chainId>:dead:<subId>
|
||||
* Dead-letter stream: ce:parser2:<chainId>:dead:<subId>
|
||||
* Содержит события которые handler не смог обработать 3 раза подряд.
|
||||
* --all → сканировать все dead-letter стримы для цепи (SCAN dead:*).
|
||||
* --limit → максимум записей за вызов (пагинация через --from).
|
||||
+2
-2
@@ -5,7 +5,7 @@
|
||||
* Вместо немедленного перевода в dead-letter даём несколько попыток (FAILURE_THRESHOLD = 3).
|
||||
* После FAILURE_THRESHOLD провалов событие переводится в dead-letter stream.
|
||||
*
|
||||
* Счётчики хранятся в Redis Hash (parser:sub:<subId>:failures) с TTL 24 ч.
|
||||
* Счётчики хранятся в Redis Hash (parser2:sub:<subId>:failures) с TTL 24 ч.
|
||||
* TTL сбрасывается при каждом новом провале — чтобы не накапливать стали счётчики.
|
||||
*
|
||||
* Почему Redis, а не in-memory: при рестарте consumer'а незакрытые ошибки
|
||||
@@ -61,7 +61,7 @@ export class FailureTracker {
|
||||
|
||||
/**
|
||||
* Записывает событие в dead-letter stream с метаданными об ошибке.
|
||||
* Dead-letter stream: ce:parser:<chainId>:dead:<subId>
|
||||
* Dead-letter stream: ce:parser2:<chainId>:dead:<subId>
|
||||
* Поля записи: data (оригинальный payload), failureCount, lastError, subId.
|
||||
*/
|
||||
async routeToDeadLetter(
|
||||
+2
-2
@@ -5,7 +5,7 @@
|
||||
* получать события из блокчейна без прямого доступа к Redis или SHiP.
|
||||
*
|
||||
* Что делает:
|
||||
* 1. Регистрирует подписку (метаданные) в Redis Hash (parser:subs).
|
||||
* 1. Регистрирует подписку (метаданные) в Redis Hash (parser2:subs).
|
||||
* 2. Захватывает distributed lock (single-active-consumer) через SubscriptionLock.
|
||||
* 3. Читает события из Redis Stream через RedisConsumer (XREADGROUP).
|
||||
* 4. Применяет фильтры (matchFilters) — пропускает нерелевантные события.
|
||||
@@ -74,7 +74,7 @@ export class ParserClient {
|
||||
*
|
||||
* Этапы старта:
|
||||
* 1. Подключаемся к Redis.
|
||||
* 2. Регистрируем подписку в HSET parser:subs.
|
||||
* 2. Регистрируем подписку в HSET parser2:subs.
|
||||
* 3. Пытаемся захватить lock; если занят — ждём освобождения.
|
||||
* 4. Определяем startId для consumer group.
|
||||
* 5. Создаём consumer group (XGROUP CREATE).
|
||||
@@ -181,7 +181,7 @@ export class Parser {
|
||||
private async checkRedisPersistence(): Promise<void> {
|
||||
const redis = this.redis!
|
||||
try {
|
||||
const aofResult = await redis.hget('__parser_check__', '__noop__')
|
||||
const aofResult = await redis.hget('__parser2_check__', '__noop__')
|
||||
void aofResult
|
||||
} catch {
|
||||
// non-fatal check failure
|
||||
+1
-1
@@ -20,7 +20,7 @@ import type { RedisStore } from '../ports/RedisStore.js'
|
||||
|
||||
export interface XtrimSupervisorOpts {
|
||||
redis: RedisStore
|
||||
/** Имя стрима для очистки (обычно ce:parser:<chainId>:events). */
|
||||
/** Имя стрима для очистки (обычно ce:parser2:<chainId>:events). */
|
||||
stream: string
|
||||
/** Интервал между trim-циклами в мс. По умолчанию 60 000 (1 минута). */
|
||||
intervalMs?: number
|
||||
+2
-2
@@ -55,9 +55,9 @@ export interface ClientMetrics {
|
||||
/**
|
||||
* Создаёт набор клиентских метрик в изолированном Registry.
|
||||
*
|
||||
* @param prefix — префикс имён метрик. По умолчанию 'parser_client'.
|
||||
* @param prefix — префикс имён метрик. По умолчанию 'parser2_client'.
|
||||
*/
|
||||
export function createClientMetrics(prefix = 'parser_client'): ClientMetrics {
|
||||
export function createClientMetrics(prefix = 'parser2_client'): ClientMetrics {
|
||||
const registry = new Registry()
|
||||
|
||||
const handlerErrorsTotal = new Counter<'sub_id' | 'kind'>({
|
||||
@@ -5,51 +5,51 @@
|
||||
* поиск по кодовой базе. Полная документация формата — docs/redis-key-taxonomy.md.
|
||||
*
|
||||
* Префиксы:
|
||||
* ce:parser:<chainId>: — Stream-ключи, относящиеся к конкретной цепи.
|
||||
* ce:parser2:<chainId>: — Stream-ключи, относящиеся к конкретной цепи.
|
||||
* parser: — Hash/ZSET/String ключи с глобальным скоупом.
|
||||
*/
|
||||
export const RedisKeys = {
|
||||
/**
|
||||
* Главный поток событий парсера (unified event stream).
|
||||
* Тип Redis: Stream. Тримируется XtrimSupervisor'ом.
|
||||
* Пример: ce:parser:eos-mainnet:events
|
||||
* Пример: ce:parser2:eos-mainnet:events
|
||||
*/
|
||||
eventsStream: (chainId: string) => `ce:parser:${chainId}:events`,
|
||||
eventsStream: (chainId: string) => `ce:parser2:${chainId}:events`,
|
||||
|
||||
/**
|
||||
* Dead-letter поток для конкретной подписки.
|
||||
* Содержит сообщения, которые не смог обработать consumer после N попыток.
|
||||
* Тип Redis: Stream.
|
||||
* Пример: ce:parser:eos-mainnet:dead:verifier
|
||||
* Пример: ce:parser2:eos-mainnet:dead:verifier
|
||||
*/
|
||||
deadLetterStream: (chainId: string, subId: string) => `ce:parser:${chainId}:dead:${subId}`,
|
||||
deadLetterStream: (chainId: string, subId: string) => `ce:parser2:${chainId}:dead:${subId}`,
|
||||
|
||||
/**
|
||||
* Поток для задания on-demand reparse (зарезервировано для будущего).
|
||||
* Тип Redis: Stream.
|
||||
*/
|
||||
reparseStream: (chainId: string, jobId: string) => `ce:parser:${chainId}:reparse:${jobId}`,
|
||||
reparseStream: (chainId: string, jobId: string) => `ce:parser2:${chainId}:reparse:${jobId}`,
|
||||
|
||||
/**
|
||||
* История версий ABI конкретного контракта.
|
||||
* Тип Redis: Sorted Set. Score = block_num, member = base64(rawAbiBytes).
|
||||
* При поиске ABI для блока N используется ZREVRANGEBYSCORE … N -inf LIMIT 0 1.
|
||||
* Пример: parser:abi:eosio.token
|
||||
* Пример: parser2:abi:eosio.token
|
||||
*/
|
||||
abiZset: (contract: string) => `parser:abi:${contract}`,
|
||||
abiZset: (contract: string) => `parser2:abi:${contract}`,
|
||||
|
||||
/**
|
||||
* Контрольная точка синхронизации парсера (crash-recovery).
|
||||
* Тип Redis: Hash. Поля: block_num, block_id, last_updated.
|
||||
* При рестарте парсер читает отсюда позицию и продолжает с неё.
|
||||
*/
|
||||
syncHash: (chainId: string) => `parser:sync:${chainId}`,
|
||||
syncHash: (chainId: string) => `parser2:sync:${chainId}`,
|
||||
|
||||
/**
|
||||
* Реестр всех зарегистрированных подписок.
|
||||
* Тип Redis: Hash. Ключ поля = subId, значение = JSON-метаданные подписки.
|
||||
*/
|
||||
subsHash: () => `parser:subs`,
|
||||
subsHash: () => `parser2:subs`,
|
||||
|
||||
/**
|
||||
* Счётчики ошибок per-event для конкретной подписки.
|
||||
@@ -57,18 +57,18 @@ export const RedisKeys = {
|
||||
* TTL: 24 часа (обновляется при каждом новом провале).
|
||||
* Используется FailureTracker для решения о переводе в dead-letter.
|
||||
*/
|
||||
subFailuresHash: (subId: string) => `parser:sub:${subId}:failures`,
|
||||
subFailuresHash: (subId: string) => `parser2:sub:${subId}:failures`,
|
||||
|
||||
/**
|
||||
* Блокировка single-active-consumer для подписки.
|
||||
* Тип Redis: String (instanceId держателя блокировки). TTL: 10 с (автопродление).
|
||||
* Только один экземпляр consumer-а может быть active; остальные — standby.
|
||||
*/
|
||||
subLock: (subId: string) => `parser:sub:${subId}:lock`,
|
||||
subLock: (subId: string) => `parser2:sub:${subId}:lock`,
|
||||
|
||||
/**
|
||||
* Метаданные задания reparse (зарезервировано для будущего).
|
||||
* Тип Redis: Hash.
|
||||
*/
|
||||
reparseJobHash: (jobId: string) => `parser:reparse:${jobId}`,
|
||||
reparseJobHash: (jobId: string) => `parser2:reparse:${jobId}`,
|
||||
} as const
|
||||
+1
-1
@@ -85,7 +85,7 @@ export class WorkerPool {
|
||||
|
||||
/**
|
||||
* Доля занятых потоков от общего числа (0..1).
|
||||
* Полезно для метрики parser_worker_pool_queue_depth.
|
||||
* Полезно для метрики parser2_worker_pool_queue_depth.
|
||||
*/
|
||||
get utilization(): number {
|
||||
return this.pool.utilization
|
||||
+1
-1
@@ -116,7 +116,7 @@ describe('listDeadLetters — --all flag', () => {
|
||||
})
|
||||
|
||||
it('calls scan with correct pattern for --all', async () => {
|
||||
const redis = makeRedis({ scanResult: ['ce:parser:mainnet:dead:sub1'] })
|
||||
const redis = makeRedis({ scanResult: ['ce:parser2:mainnet:dead:sub1'] })
|
||||
vi.spyOn(redis, 'xlen').mockResolvedValue(0)
|
||||
vi.spyOn(redis, 'xrange').mockResolvedValue([])
|
||||
const spy = vi.spyOn(console, 'log').mockImplementation(() => undefined)
|
||||
+7
-7
@@ -38,23 +38,23 @@ describe('IoRedisStore', () => {
|
||||
})
|
||||
|
||||
it('zadd calls redis.zadd with score and member', async () => {
|
||||
await store.zadd('parser:abi:eosio', 100, '{"version":"eosio::abi/1.0"}')
|
||||
expect(store.client.zadd).toHaveBeenCalledWith('parser:abi:eosio', 100, '{"version":"eosio::abi/1.0"}')
|
||||
await store.zadd('parser2:abi:eosio', 100, '{"version":"eosio::abi/1.0"}')
|
||||
expect(store.client.zadd).toHaveBeenCalledWith('parser2:abi:eosio', 100, '{"version":"eosio::abi/1.0"}')
|
||||
})
|
||||
|
||||
it('zrangeByscoreRev calls redis.zrevrangebyscore with max first, then min', async () => {
|
||||
const result = await store.zrangeByscoreRev('parser:abi:eosio', '250', '-inf')
|
||||
const result = await store.zrangeByscoreRev('parser2:abi:eosio', '250', '-inf')
|
||||
expect(result).toHaveLength(1)
|
||||
expect(store.client.zrevrangebyscore).toHaveBeenCalledWith('parser:abi:eosio', '250', '-inf', 'LIMIT', 0, 1)
|
||||
expect(store.client.zrevrangebyscore).toHaveBeenCalledWith('parser2:abi:eosio', '250', '-inf', 'LIMIT', 0, 1)
|
||||
})
|
||||
|
||||
it('hset calls redis.hset with flattened args', async () => {
|
||||
await store.hset('parser:sync:mychain', { block_num: '100', block_id: 'abc' })
|
||||
expect(store.client.hset).toHaveBeenCalledWith('parser:sync:mychain', 'block_num', '100', 'block_id', 'abc')
|
||||
await store.hset('parser2:sync:mychain', { block_num: '100', block_id: 'abc' })
|
||||
expect(store.client.hset).toHaveBeenCalledWith('parser2:sync:mychain', 'block_num', '100', 'block_id', 'abc')
|
||||
})
|
||||
|
||||
it('hget returns value', async () => {
|
||||
const val = await store.hget('parser:sync:mychain', 'block_num')
|
||||
const val = await store.hget('parser2:sync:mychain', 'block_num')
|
||||
expect(val).toBe('100')
|
||||
})
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
* streams/locks и отслеживаем ack.
|
||||
*
|
||||
* Покрываем:
|
||||
* - Регистрация подписки в parser:subs
|
||||
* - Регистрация подписки в parser2:subs
|
||||
* - Определение startId по startFrom
|
||||
* - Ожидание промотации если lock занят
|
||||
* - Применение фильтров: не-matching события XACK'аются молча
|
||||
@@ -185,7 +185,7 @@ describe('ParserClient — subscription registration', () => {
|
||||
beforeEach(() => { fakeRedisInstances.length = 0 })
|
||||
afterEach(() => { vi.clearAllMocks() })
|
||||
|
||||
it('registers subscription metadata in parser:subs hash on stream() start', async () => {
|
||||
it('registers subscription metadata in parser2:subs hash on stream() start', async () => {
|
||||
const client = new ParserClient({
|
||||
subscriptionId: 'my-sub',
|
||||
filters: [{ kind: 'action', account: 'eosio.token' }],
|
||||
+7
-7
@@ -17,36 +17,36 @@ describe('createParserMetrics', () => {
|
||||
})
|
||||
|
||||
it('registry contains exactly 10 metrics', async () => {
|
||||
const m = createParserMetrics('test_parser_count')
|
||||
const m = createParserMetrics('test_parser2_count')
|
||||
const all = await m.registry.getMetricsAsJSON()
|
||||
expect(all.length).toBe(10)
|
||||
})
|
||||
|
||||
it('blocksProcessedTotal increments', async () => {
|
||||
const m = createParserMetrics('test_parser_inc')
|
||||
const m = createParserMetrics('test_parser2_inc')
|
||||
m.blocksProcessedTotal.inc()
|
||||
m.blocksProcessedTotal.inc()
|
||||
const all = await m.registry.getMetricsAsJSON()
|
||||
const metric = all.find(x => x.name === 'test_parser_inc_blocks_processed_total')
|
||||
const metric = all.find(x => x.name === 'test_parser2_inc_blocks_processed_total')
|
||||
expect(metric?.values[0]?.value).toBe(2)
|
||||
})
|
||||
|
||||
it('eventsPublishedTotal supports kind label', async () => {
|
||||
const m = createParserMetrics('test_parser_labels')
|
||||
const m = createParserMetrics('test_parser2_labels')
|
||||
m.eventsPublishedTotal.inc({ kind: 'action' })
|
||||
m.eventsPublishedTotal.inc({ kind: 'delta' })
|
||||
m.eventsPublishedTotal.inc({ kind: 'action' })
|
||||
const all = await m.registry.getMetricsAsJSON()
|
||||
const metric = all.find(x => x.name === 'test_parser_labels_events_published_total')
|
||||
const metric = all.find(x => x.name === 'test_parser2_labels_events_published_total')
|
||||
const actions = metric?.values.find(v => v.labels['kind'] === 'action')
|
||||
expect(actions?.value).toBe(2)
|
||||
})
|
||||
|
||||
it('indexingLagSeconds can be set to a float', async () => {
|
||||
const m = createParserMetrics('test_parser_lag')
|
||||
const m = createParserMetrics('test_parser2_lag')
|
||||
m.indexingLagSeconds.set(3.5)
|
||||
const all = await m.registry.getMetricsAsJSON()
|
||||
const metric = all.find(x => x.name === 'test_parser_lag_indexing_lag_seconds')
|
||||
const metric = all.find(x => x.name === 'test_parser2_lag_indexing_lag_seconds')
|
||||
expect(metric?.values[0]?.value).toBe(3.5)
|
||||
})
|
||||
})
|
||||
+23
-23
@@ -9,65 +9,65 @@ import { describe, it, expect } from 'vitest'
|
||||
import { RedisKeys } from '../../src/redis/keys.js'
|
||||
|
||||
describe('RedisKeys — event streams', () => {
|
||||
it('eventsStream embeds chainId in ce:parser:<chainId>:events', () => {
|
||||
expect(RedisKeys.eventsStream('eos-mainnet')).toBe('ce:parser:eos-mainnet:events')
|
||||
expect(RedisKeys.eventsStream('telos-test')).toBe('ce:parser:telos-test:events')
|
||||
it('eventsStream embeds chainId in ce:parser2:<chainId>:events', () => {
|
||||
expect(RedisKeys.eventsStream('eos-mainnet')).toBe('ce:parser2:eos-mainnet:events')
|
||||
expect(RedisKeys.eventsStream('telos-test')).toBe('ce:parser2:telos-test:events')
|
||||
})
|
||||
|
||||
it('deadLetterStream includes both chainId and subId', () => {
|
||||
expect(RedisKeys.deadLetterStream('eos', 'verifier'))
|
||||
.toBe('ce:parser:eos:dead:verifier')
|
||||
.toBe('ce:parser2:eos:dead:verifier')
|
||||
})
|
||||
|
||||
it('reparseStream includes chainId and jobId', () => {
|
||||
expect(RedisKeys.reparseStream('eos', 'job-42'))
|
||||
.toBe('ce:parser:eos:reparse:job-42')
|
||||
.toBe('ce:parser2:eos:reparse:job-42')
|
||||
})
|
||||
})
|
||||
|
||||
describe('RedisKeys — ABI and sync', () => {
|
||||
it('abiZset is namespaced by contract name', () => {
|
||||
expect(RedisKeys.abiZset('eosio.token')).toBe('parser:abi:eosio.token')
|
||||
expect(RedisKeys.abiZset('eosio')).toBe('parser:abi:eosio')
|
||||
expect(RedisKeys.abiZset('eosio.token')).toBe('parser2:abi:eosio.token')
|
||||
expect(RedisKeys.abiZset('eosio')).toBe('parser2:abi:eosio')
|
||||
})
|
||||
|
||||
it('syncHash is keyed by chainId only', () => {
|
||||
expect(RedisKeys.syncHash('eos-mainnet')).toBe('parser:sync:eos-mainnet')
|
||||
expect(RedisKeys.syncHash('eos-mainnet')).toBe('parser2:sync:eos-mainnet')
|
||||
})
|
||||
})
|
||||
|
||||
describe('RedisKeys — subscriptions', () => {
|
||||
it('subsHash is a global registry (no params)', () => {
|
||||
expect(RedisKeys.subsHash()).toBe('parser:subs')
|
||||
expect(RedisKeys.subsHash()).toBe('parser2:subs')
|
||||
})
|
||||
|
||||
it('subFailuresHash is keyed by subId', () => {
|
||||
expect(RedisKeys.subFailuresHash('verifier')).toBe('parser:sub:verifier:failures')
|
||||
expect(RedisKeys.subFailuresHash('verifier')).toBe('parser2:sub:verifier:failures')
|
||||
})
|
||||
|
||||
it('subLock is keyed by subId', () => {
|
||||
expect(RedisKeys.subLock('verifier')).toBe('parser:sub:verifier:lock')
|
||||
expect(RedisKeys.subLock('verifier')).toBe('parser2:sub:verifier:lock')
|
||||
})
|
||||
|
||||
it('reparseJobHash is keyed by jobId', () => {
|
||||
expect(RedisKeys.reparseJobHash('job-42')).toBe('parser:reparse:job-42')
|
||||
expect(RedisKeys.reparseJobHash('job-42')).toBe('parser2:reparse:job-42')
|
||||
})
|
||||
})
|
||||
|
||||
describe('RedisKeys — prefix namespacing invariants', () => {
|
||||
it('all stream keys start with ce:parser:', () => {
|
||||
expect(RedisKeys.eventsStream('x')).toMatch(/^ce:parser:/)
|
||||
expect(RedisKeys.deadLetterStream('x', 's')).toMatch(/^ce:parser:/)
|
||||
expect(RedisKeys.reparseStream('x', 'j')).toMatch(/^ce:parser:/)
|
||||
it('all stream keys start with ce:parser2:', () => {
|
||||
expect(RedisKeys.eventsStream('x')).toMatch(/^ce:parser2:/)
|
||||
expect(RedisKeys.deadLetterStream('x', 's')).toMatch(/^ce:parser2:/)
|
||||
expect(RedisKeys.reparseStream('x', 'j')).toMatch(/^ce:parser2:/)
|
||||
})
|
||||
|
||||
it('all non-stream keys start with parser:', () => {
|
||||
expect(RedisKeys.abiZset('x')).toMatch(/^parser:/)
|
||||
expect(RedisKeys.syncHash('x')).toMatch(/^parser:/)
|
||||
expect(RedisKeys.subsHash()).toMatch(/^parser:/)
|
||||
expect(RedisKeys.subFailuresHash('x')).toMatch(/^parser:/)
|
||||
expect(RedisKeys.subLock('x')).toMatch(/^parser:/)
|
||||
expect(RedisKeys.reparseJobHash('x')).toMatch(/^parser:/)
|
||||
it('all non-stream keys start with parser2:', () => {
|
||||
expect(RedisKeys.abiZset('x')).toMatch(/^parser2:/)
|
||||
expect(RedisKeys.syncHash('x')).toMatch(/^parser2:/)
|
||||
expect(RedisKeys.subsHash()).toMatch(/^parser2:/)
|
||||
expect(RedisKeys.subFailuresHash('x')).toMatch(/^parser2:/)
|
||||
expect(RedisKeys.subLock('x')).toMatch(/^parser2:/)
|
||||
expect(RedisKeys.reparseJobHash('x')).toMatch(/^parser2:/)
|
||||
})
|
||||
|
||||
it('keys for different chains are disjoint (no collision)', () => {
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Clean-room SHiP WebSocket client for EOSIO/Antelope blockchains. Streams blockchain blocks, deserializes actions and deltas via `@wharfkit/antelope`, with support for all 24 native system table delta types.
|
||||
|
||||
Primary consumer: [`@coopenomics/parser`](https://github.com/coopenomics/parser).
|
||||
Primary consumer: [`@coopenomics/parser2`](https://github.com/coopenomics/parser).
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -15,7 +15,7 @@ importers:
|
||||
specifier: ^5.4.5
|
||||
version: 5.9.3
|
||||
|
||||
packages/parser:
|
||||
packages/parser2:
|
||||
dependencies:
|
||||
'@coopenomics/coopos-ship-reader':
|
||||
specifier: workspace:*
|
||||
|
||||
Reference in New Issue
Block a user