From 86a1e7915372f51413d0f738aa8eff621531a4e7 Mon Sep 17 00:00:00 2001 From: XuJiacheng Date: Fri, 30 Jan 2026 11:05:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E6=9C=8D=E5=8A=A1=E5=9F=BA=E7=A1=80=E6=9E=B6=E6=9E=84?= =?UTF-8?q?=E4=B8=8E=E6=A0=B8=E5=BF=83=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加项目基础结构,包括 .gitignore、package.json、Docker 配置和环境变量示例 - 实现核心模块:Kafka 消费者、PostgreSQL 数据库管理器、Redis 客户端与错误队列 - 添加工具类:日志记录器、指标收集器、UUID 生成器 - 实现数据处理器,支持 0x36 上报和 0x0F 命令的解析与存储 - 添加数据库初始化脚本和分区管理,支持按时间范围分区 - 引入 Zod 数据验证和 Vitest 单元测试框架 - 提供完整的项目文档,包括数据库设计、Kafka 格式规范和 Redis 集成协议 --- .gitignore | 3 + bls-rcu-action-backend/.env | 29 + bls-rcu-action-backend/.env.example | 29 + bls-rcu-action-backend/Dockerfile | 19 + bls-rcu-action-backend/dist/.gitkeep | 1 + bls-rcu-action-backend/docker-compose.yml | 55 + bls-rcu-action-backend/ecosystem.config.cjs | 24 + bls-rcu-action-backend/output_dev_debug.log | 1 + bls-rcu-action-backend/output_dev_final.log | 1 + bls-rcu-action-backend/output_dev_v2.log | 1 + bls-rcu-action-backend/output_dev_v3.log | 1 + bls-rcu-action-backend/output_dev_v4.log | 1 + bls-rcu-action-backend/package-lock.json | 3526 +++++++++++++++++ bls-rcu-action-backend/package.json | 24 + bls-rcu-action-backend/scripts/init_db.sql | 45 + bls-rcu-action-backend/src/config/config.js | 50 + .../src/db/databaseManager.js | 80 + bls-rcu-action-backend/src/db/initializer.js | 86 + .../src/db/partitionManager.js | 71 + bls-rcu-action-backend/src/index.js | 203 + bls-rcu-action-backend/src/kafka/consumer.js | 39 + bls-rcu-action-backend/src/processor/index.js | 247 ++ .../src/processor/udpParser.js | 83 + .../src/redis/errorQueue.js | 53 + .../src/redis/redisClient.js | 14 + .../src/redis/redisIntegration.js | 40 + .../src/schema/kafkaPayload.js | 59 + bls-rcu-action-backend/src/utils/logger.js | 18 + .../src/utils/metricCollector.js | 26 + bls-rcu-action-backend/src/utils/uuid.js | 3 + .../tests/processor.test.js | 120 + docs/kafka_format.md | 153 + docs/project.md | 78 + docs/readme.md | 92 + docs/redis-integration-protocol.md | 273 ++ docs/测试报告.md | 45 + .../add_validation_and_tests.md | 32 + .../correction_kafka_json.md | 9 + .../doc_update_kafka.md | 9 + .../implement_partitioning.md | 33 + .../improve_robustness.md | 19 + .../kafka_db_strategy.md | 27 + .../refactor_processor.md | 23 + .../2026-01-28-initial-setup/summary.md | 33 + .../2026-01-28-initial-setup/unify_schema.md | 19 + .../update_action_type.md | 22 + .../update_pk_composite.md | 19 + .../2026-01-29-add-device-id/summary.md | 22 + .../summary.md | 18 + openspec/config.yaml | 20 + openspec/project.md | 23 + 51 files changed, 5921 insertions(+) create mode 100644 .gitignore create mode 100644 bls-rcu-action-backend/.env create mode 100644 bls-rcu-action-backend/.env.example create mode 100644 bls-rcu-action-backend/Dockerfile create mode 100644 bls-rcu-action-backend/dist/.gitkeep create mode 100644 bls-rcu-action-backend/docker-compose.yml create mode 100644 bls-rcu-action-backend/ecosystem.config.cjs create mode 100644 bls-rcu-action-backend/output_dev_debug.log create mode 100644 bls-rcu-action-backend/output_dev_final.log create mode 100644 bls-rcu-action-backend/output_dev_v2.log create mode 100644 bls-rcu-action-backend/output_dev_v3.log create mode 100644 bls-rcu-action-backend/output_dev_v4.log create mode 100644 bls-rcu-action-backend/package-lock.json create mode 100644 bls-rcu-action-backend/package.json create mode 100644 bls-rcu-action-backend/scripts/init_db.sql create mode 100644 bls-rcu-action-backend/src/config/config.js create mode 100644 bls-rcu-action-backend/src/db/databaseManager.js create mode 100644 bls-rcu-action-backend/src/db/initializer.js create mode 100644 bls-rcu-action-backend/src/db/partitionManager.js create mode 100644 bls-rcu-action-backend/src/index.js create mode 100644 bls-rcu-action-backend/src/kafka/consumer.js create mode 100644 bls-rcu-action-backend/src/processor/index.js create mode 100644 bls-rcu-action-backend/src/processor/udpParser.js create mode 100644 bls-rcu-action-backend/src/redis/errorQueue.js create mode 100644 bls-rcu-action-backend/src/redis/redisClient.js create mode 100644 bls-rcu-action-backend/src/redis/redisIntegration.js create mode 100644 bls-rcu-action-backend/src/schema/kafkaPayload.js create mode 100644 bls-rcu-action-backend/src/utils/logger.js create mode 100644 bls-rcu-action-backend/src/utils/metricCollector.js create mode 100644 bls-rcu-action-backend/src/utils/uuid.js create mode 100644 bls-rcu-action-backend/tests/processor.test.js create mode 100644 docs/kafka_format.md create mode 100644 docs/project.md create mode 100644 docs/readme.md create mode 100644 docs/redis-integration-protocol.md create mode 100644 docs/测试报告.md create mode 100644 openspec/changes/archive/2026-01-28-initial-setup/add_validation_and_tests.md create mode 100644 openspec/changes/archive/2026-01-28-initial-setup/correction_kafka_json.md create mode 100644 openspec/changes/archive/2026-01-28-initial-setup/doc_update_kafka.md create mode 100644 openspec/changes/archive/2026-01-28-initial-setup/implement_partitioning.md create mode 100644 openspec/changes/archive/2026-01-28-initial-setup/improve_robustness.md create mode 100644 openspec/changes/archive/2026-01-28-initial-setup/kafka_db_strategy.md create mode 100644 openspec/changes/archive/2026-01-28-initial-setup/refactor_processor.md create mode 100644 openspec/changes/archive/2026-01-28-initial-setup/summary.md create mode 100644 openspec/changes/archive/2026-01-28-initial-setup/unify_schema.md create mode 100644 openspec/changes/archive/2026-01-28-initial-setup/update_action_type.md create mode 100644 openspec/changes/archive/2026-01-28-initial-setup/update_pk_composite.md create mode 100644 openspec/changes/archive/2026-01-29-add-device-id/summary.md create mode 100644 openspec/changes/archive/2026-01-29-remove-action-type-from-payload/summary.md create mode 100644 openspec/config.yaml create mode 100644 openspec/project.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e375517 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.github +bls-rcu-action-backend/node_modules +bls-rcu-action-backend/skill diff --git a/bls-rcu-action-backend/.env b/bls-rcu-action-backend/.env new file mode 100644 index 0000000..fa874a8 --- /dev/null +++ b/bls-rcu-action-backend/.env @@ -0,0 +1,29 @@ +KAFKA_BROKERS=kafka.blv-oa.com:9092 +KAFKA_CLIENT_ID=bls-action-producer +KAFKA_GROUP_ID=bls-action-consumer +KAFKA_TOPICS=blwlog4Nodejs-rcu-action-topic +KAFKA_AUTO_COMMIT=true +KAFKA_AUTO_COMMIT_INTERVAL_MS=5000 +KAFKA_SASL_ENABLED=true +KAFKA_SASL_MECHANISM=plain +KAFKA_SASL_USERNAME=blwmomo +KAFKA_SASL_PASSWORD=blwmomo +KAFKA_SSL_ENABLED=false + +POSTGRES_HOST=10.8.8.109 +POSTGRES_PORT=5433 +POSTGRES_DATABASE=log_platform +POSTGRES_USER=log_admin +POSTGRES_PASSWORD=YourActualStrongPasswordForPostgres! +POSTGRES_MAX_CONNECTIONS=6 +POSTGRES_IDLE_TIMEOUT_MS=30000 + +PORT=3001 +LOG_LEVEL=info + +# Redis connection +REDIS_HOST=10.8.8.109 +REDIS_PORT=6379 +REDIS_PASSWORD= +REDIS_DB=15 +REDIS_CONNECT_TIMEOUT_MS=5000 diff --git a/bls-rcu-action-backend/.env.example b/bls-rcu-action-backend/.env.example new file mode 100644 index 0000000..72c0dfb --- /dev/null +++ b/bls-rcu-action-backend/.env.example @@ -0,0 +1,29 @@ +# Server Configuration +PORT=3000 +NODE_ENV=development + +# Kafka Configuration +KAFKA_BROKERS=localhost:9092 +KAFKA_TOPIC=my-topic-name +KAFKA_GROUP_ID=my-group-id +KAFKA_CLIENT_ID=my-client-id +KAFKA_CONSUMER_INSTANCES=1 +# KAFKA_SASL_USERNAME= +# KAFKA_SASL_PASSWORD= +# KAFKA_SASL_MECHANISM=plain + +# Database Configuration (PostgreSQL) +DB_HOST=localhost +DB_PORT=5432 +DB_USER=postgres +DB_PASSWORD=password +DB_DATABASE=my_database +DB_MAX_CONNECTIONS=10 + +# Redis Configuration +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_PASSWORD= +REDIS_DB=0 +REDIS_PROJECT_NAME=my-project +REDIS_API_BASE_URL=http://localhost:3000 diff --git a/bls-rcu-action-backend/Dockerfile b/bls-rcu-action-backend/Dockerfile new file mode 100644 index 0000000..4f176f2 --- /dev/null +++ b/bls-rcu-action-backend/Dockerfile @@ -0,0 +1,19 @@ +FROM node:18-alpine + +WORKDIR /app + +# Install dependencies +COPY package.json package-lock.json ./ +RUN npm ci + +# Copy source code +COPY . . + +# Build +RUN npm run build + +# Expose port +EXPOSE 3000 + +# Start command +CMD ["npm", "run", "start"] diff --git a/bls-rcu-action-backend/dist/.gitkeep b/bls-rcu-action-backend/dist/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/bls-rcu-action-backend/dist/.gitkeep @@ -0,0 +1 @@ + diff --git a/bls-rcu-action-backend/docker-compose.yml b/bls-rcu-action-backend/docker-compose.yml new file mode 100644 index 0000000..fcb405e --- /dev/null +++ b/bls-rcu-action-backend/docker-compose.yml @@ -0,0 +1,55 @@ +version: '3.8' + +services: + app: + build: . + restart: always + ports: + - "3000:3000" + env_file: + - .env + depends_on: + - postgres + - redis + - kafka + + postgres: + image: postgres:15-alpine + restart: always + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_DB: my_database + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + + redis: + image: redis:alpine + restart: always + ports: + - "6379:6379" + volumes: + - redis_data:/data + + zookeeper: + image: wurstmeister/zookeeper + ports: + - "2181:2181" + + kafka: + image: wurstmeister/kafka + ports: + - "9092:9092" + environment: + KAFKA_ADVERTISED_HOST_NAME: localhost + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + depends_on: + - zookeeper + +volumes: + postgres_data: + redis_data: diff --git a/bls-rcu-action-backend/ecosystem.config.cjs b/bls-rcu-action-backend/ecosystem.config.cjs new file mode 100644 index 0000000..453f908 --- /dev/null +++ b/bls-rcu-action-backend/ecosystem.config.cjs @@ -0,0 +1,24 @@ +module.exports = { + apps: [{ + name: 'bls-rcu-action-backend', + script: 'dist/index.js', + instances: 1, + exec_mode: 'fork', + autorestart: true, + watch: false, + max_memory_restart: '1G', + env: { + NODE_ENV: 'production', + PORT: 3000 + }, + env_development: { + NODE_ENV: 'development', + PORT: 3000 + }, + error_file: './logs/error.log', + out_file: './logs/out.log', + log_date_format: 'YYYY-MM-DD HH:mm:ss Z', + merge_logs: true, + time: true + }] +}; diff --git a/bls-rcu-action-backend/output_dev_debug.log b/bls-rcu-action-backend/output_dev_debug.log new file mode 100644 index 0000000..80a1ade --- /dev/null +++ b/bls-rcu-action-backend/output_dev_debug.log @@ -0,0 +1 @@ +{"level":"error","message":"Kafka message handling failed","timestamp":1769689985427,"context":{"error":"[\n {\n \"expected\": \"number\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"hotel_id\"\n ],\n \"message\": \"Invalid input: expected number, received string\"\n },\n {\n \"expected\": \"array\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"control_list\"\n ],\n \"message\": \"Invalid input: expected array, received null\"\n }\n]"}} diff --git a/bls-rcu-action-backend/output_dev_final.log b/bls-rcu-action-backend/output_dev_final.log new file mode 100644 index 0000000..40e3410 --- /dev/null +++ b/bls-rcu-action-backend/output_dev_final.log @@ -0,0 +1 @@ +{"level":"error","message":"Kafka message handling failed","timestamp":1769689777074,"context":{"error":"[\n {\n \"expected\": \"number\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"hotel_id\"\n ],\n \"message\": \"Invalid input: expected number, received string\"\n },\n {\n \"expected\": \"array\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"control_list\"\n ],\n \"message\": \"Invalid input: expected array, received null\"\n }\n]"}} diff --git a/bls-rcu-action-backend/output_dev_v2.log b/bls-rcu-action-backend/output_dev_v2.log new file mode 100644 index 0000000..2997c33 --- /dev/null +++ b/bls-rcu-action-backend/output_dev_v2.log @@ -0,0 +1 @@ +{"level":"info","message":"[Minute Metrics] Pulled: 0, Parse Error: 0, Inserted: 0, Failed: 0","timestamp":1769688900027,"context":{"kafka_pulled":0,"parse_error":0,"db_inserted":0,"db_failed":0}} diff --git a/bls-rcu-action-backend/output_dev_v3.log b/bls-rcu-action-backend/output_dev_v3.log new file mode 100644 index 0000000..ff93d8d --- /dev/null +++ b/bls-rcu-action-backend/output_dev_v3.log @@ -0,0 +1 @@ +{"level":"info","message":"[Minute Metrics] Pulled: 0, Parse Error: 0, Inserted: 0, Failed: 0","timestamp":1769689140027,"context":{"kafka_pulled":0,"parse_error":0,"db_inserted":0,"db_failed":0}} diff --git a/bls-rcu-action-backend/output_dev_v4.log b/bls-rcu-action-backend/output_dev_v4.log new file mode 100644 index 0000000..24913f9 --- /dev/null +++ b/bls-rcu-action-backend/output_dev_v4.log @@ -0,0 +1 @@ +{"level":"info","message":"[Minute Metrics] Pulled: 0, Parse Error: 0, Inserted: 0, Failed: 0","timestamp":1769689260031,"context":{"kafka_pulled":0,"parse_error":0,"db_inserted":0,"db_failed":0}} diff --git a/bls-rcu-action-backend/package-lock.json b/bls-rcu-action-backend/package-lock.json new file mode 100644 index 0000000..704306b --- /dev/null +++ b/bls-rcu-action-backend/package-lock.json @@ -0,0 +1,3526 @@ +{ + "name": "bls-rcu-action-backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "bls-rcu-action-backend", + "version": "1.0.0", + "dependencies": { + "dotenv": "^16.4.5", + "kafka-node": "^5.0.0", + "node-cron": "^4.2.1", + "pg": "^8.11.5", + "redis": "^4.6.13", + "zod": "^4.3.6" + }, + "devDependencies": { + "vite": "^5.4.0", + "vitest": "^4.0.18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/@redis/client/-/client-1.6.1.tgz", + "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", + "license": "MIT", + "peer": true, + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.0.tgz", + "integrity": "sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.0.tgz", + "integrity": "sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.0.tgz", + "integrity": "sha512-/NNIj9A7yLjKdmkx5dC2XQ9DmjIECpGpwHoGmA5E1AhU0fuICSqSWScPhN1yLCkEdkCwJIDu2xIeLPs60MNIVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.0.tgz", + "integrity": "sha512-xoh8abqgPrPYPr7pTYipqnUi1V3em56JzE/HgDgitTqZBZ3yKCWI+7KUkceM6tNweyUKYru1UMi7FC060RyKwA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.0.tgz", + "integrity": "sha512-PCkMh7fNahWSbA0OTUQ2OpYHpjZZr0hPr8lId8twD7a7SeWrvT3xJVyza+dQwXSSq4yEQTMoXgNOfMCsn8584g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.0.tgz", + "integrity": "sha512-1j3stGx+qbhXql4OCDZhnK7b01s6rBKNybfsX+TNrEe9JNq4DLi1yGiR1xW+nL+FNVvI4D02PUnl6gJ/2y6WJA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.0.tgz", + "integrity": "sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.0.tgz", + "integrity": "sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.0.tgz", + "integrity": "sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.0.tgz", + "integrity": "sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.0.tgz", + "integrity": "sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.0.tgz", + "integrity": "sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.0.tgz", + "integrity": "sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.0.tgz", + "integrity": "sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.0.tgz", + "integrity": "sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.0.tgz", + "integrity": "sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.0.tgz", + "integrity": "sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.0.tgz", + "integrity": "sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.0.tgz", + "integrity": "sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.0.tgz", + "integrity": "sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.0.tgz", + "integrity": "sha512-v5xwKDWcu7qhAEcsUubiav7r+48Uk/ENWdr82MBZZRIm7zThSxCIVDfb3ZeRRq9yqk+oIzMdDo6fCcA5DHfMyA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.0.tgz", + "integrity": "sha512-XnaaaSMGSI6Wk8F4KK3QP7GfuuhjGchElsVerCplUuxRIzdvZ7hRBpLR0omCmw+kI2RFJB80nenhOoGXlJ5TfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.0.tgz", + "integrity": "sha512-3K1lP+3BXY4t4VihLw5MEg6IZD3ojSYzqzBG571W3kNQe4G4CcFpSUQVgurYgib5d+YaCjeFow8QivWp8vuSvA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.0.tgz", + "integrity": "sha512-MDk610P/vJGc5L5ImE4k5s+GZT3en0KoK1MKPXCRgzmksAMk79j4h3k1IerxTNqwDLxsGxStEZVBqG0gIqZqoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.0.tgz", + "integrity": "sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmmirror.com/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmmirror.com/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmmirror.com/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmmirror.com/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmmirror.com/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmmirror.com/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "license": "MIT", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "license": "MIT", + "optional": true, + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "license": "MIT", + "optional": true + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "license": "MIT", + "optional": true + }, + "node_modules/buffermaker": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/buffermaker/-/buffermaker-1.2.1.tgz", + "integrity": "sha512-IdnyU2jDHU65U63JuVQNTHiWjPRH0CS3aYd/WPaEwyX84rFdukhOduAVb1jwUScmb5X0JWPw8NZOrhoLMiyAHQ==", + "license": "MIT", + "dependencies": { + "long": "1.1.2" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "license": "MIT/X11", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC", + "optional": true + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", + "optional": true + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", + "optional": true + }, + "node_modules/denque": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/denque/-/denque-1.5.1.tgz", + "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "optional": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT", + "optional": true + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT", + "optional": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmmirror.com/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmmirror.com/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "optional": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmmirror.com/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT", + "optional": true + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "optional": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", + "optional": true + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC", + "optional": true + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "license": "MIT", + "optional": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/kafka-node": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/kafka-node/-/kafka-node-5.0.0.tgz", + "integrity": "sha512-dD2ga5gLcQhsq1yNoQdy1MU4x4z7YnXM5bcG9SdQuiNr5KKuAmXixH1Mggwdah5o7EfholFbcNDPSVA6BIfaug==", + "license": "MIT", + "dependencies": { + "async": "^2.6.2", + "binary": "~0.3.0", + "bl": "^2.2.0", + "buffer-crc32": "~0.2.5", + "buffermaker": "~1.2.0", + "debug": "^2.1.3", + "denque": "^1.3.0", + "lodash": "^4.17.4", + "minimatch": "^3.0.2", + "nested-error-stacks": "^2.0.0", + "optional": "^0.1.3", + "retry": "^0.10.1", + "uuid": "^3.0.0" + }, + "engines": { + "node": ">=8.5.1" + }, + "optionalDependencies": { + "snappy": "^6.0.1" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/long": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/long/-/long-1.1.2.tgz", + "integrity": "sha512-pjR3OP1X2VVQhCQlrq3s8UxugQsuoucwMOn9Yj/kN/61HMc+lDFJS5bvpNEHneZ9NVaSm8gNWxZvtGS7lqHb3Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/nan": { + "version": "2.25.0", + "resolved": "https://registry.npmmirror.com/nan/-/nan-2.25.0.tgz", + "integrity": "sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g==", + "license": "MIT", + "optional": true + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "license": "MIT", + "optional": true + }, + "node_modules/nested-error-stacks": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz", + "integrity": "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==", + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "2.30.1", + "resolved": "https://registry.npmmirror.com/node-abi/-/node-abi-2.30.1.tgz", + "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^5.4.1" + } + }, + "node_modules/node-cron": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/node-cron/-/node-cron-4.2.1.tgz", + "integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==", + "license": "ISC", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/noop-logger": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/noop-logger/-/noop-logger-0.1.1.tgz", + "integrity": "sha512-6kM8CLXvuW5crTxsAtva2YLrRrDaiTIkIePWs9moLHqbFWT94WpNFjwS/5dfLfECg5i/lkmw3aoqVidxt23TEQ==", + "license": "MIT", + "optional": true + }, + "node_modules/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optional": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/optional/-/optional-0.1.4.tgz", + "integrity": "sha512-gtvrrCfkE08wKcgXaVwQVgwEQ8vel2dc5DDBn9RLQZ3YtmtkBss6A2HY6BnJH4N/4Ku97Ri/SF8sNWE2225WJw==", + "license": "MIT" + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pg": { + "version": "8.17.2", + "resolved": "https://registry.npmmirror.com/pg/-/pg-8.17.2.tgz", + "integrity": "sha512-vjbKdiBJRqzcYw1fNU5KuHyYvdJ1qpcQg1CeBrHFqV1pWgHeVR6j/+kX0E1AAXfyuLUGY1ICrN2ELKA/z2HWzw==", + "license": "MIT", + "peer": true, + "dependencies": { + "pg-connection-string": "^2.10.1", + "pg-pool": "^3.11.0", + "pg-protocol": "^1.11.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.3.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", + "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.10.1", + "resolved": "https://registry.npmmirror.com/pg-connection-string/-/pg-connection-string-2.10.1.tgz", + "integrity": "sha512-iNzslsoeSH2/gmDDKiyMqF64DATUCWj3YJ0wP14kqcsf2TUklwimd+66yYojKwZCA7h2yRNLGug71hCBA2a4sw==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.11.0", + "resolved": "https://registry.npmmirror.com/pg-pool/-/pg-pool-3.11.0.tgz", + "integrity": "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.11.0", + "resolved": "https://registry.npmmirror.com/pg-protocol/-/pg-protocol-1.11.0.tgz", + "integrity": "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prebuild-install": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/prebuild-install/-/prebuild-install-5.3.0.tgz", + "integrity": "sha512-aaLVANlj4HgZweKttFNUVNRxDukytuIuxeK2boIMHjagNJCiVKWFsKF4tCE3ql3GbrD2tExPQ7/pwtEJcHNZeg==", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.7.0", + "noop-logger": "^0.1.1", + "npmlog": "^4.0.1", + "os-homedir": "^1.0.1", + "pump": "^2.0.1", + "rc": "^1.2.7", + "simple-get": "^2.7.0", + "tar-fs": "^1.13.0", + "tunnel-agent": "^0.6.0", + "which-pm-runs": "^1.0.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/redis": { + "version": "4.7.1", + "resolved": "https://registry.npmmirror.com/redis/-/redis-4.7.1.tgz", + "integrity": "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==", + "license": "MIT", + "workspaces": [ + "./packages/*" + ], + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.6.1", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" + } + }, + "node_modules/retry": { + "version": "0.10.1", + "resolved": "https://registry.npmmirror.com/retry/-/retry-0.10.1.tgz", + "integrity": "sha512-ZXUSQYTHdl3uS7IuCehYfMzKyIDBNoAuUblvy5oGO5UJSUTmStUUVPXbA9Qxd173Bgre53yCQczQuHgRWAdvJQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "4.57.0", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.57.0.tgz", + "integrity": "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.0", + "@rollup/rollup-android-arm64": "4.57.0", + "@rollup/rollup-darwin-arm64": "4.57.0", + "@rollup/rollup-darwin-x64": "4.57.0", + "@rollup/rollup-freebsd-arm64": "4.57.0", + "@rollup/rollup-freebsd-x64": "4.57.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.0", + "@rollup/rollup-linux-arm-musleabihf": "4.57.0", + "@rollup/rollup-linux-arm64-gnu": "4.57.0", + "@rollup/rollup-linux-arm64-musl": "4.57.0", + "@rollup/rollup-linux-loong64-gnu": "4.57.0", + "@rollup/rollup-linux-loong64-musl": "4.57.0", + "@rollup/rollup-linux-ppc64-gnu": "4.57.0", + "@rollup/rollup-linux-ppc64-musl": "4.57.0", + "@rollup/rollup-linux-riscv64-gnu": "4.57.0", + "@rollup/rollup-linux-riscv64-musl": "4.57.0", + "@rollup/rollup-linux-s390x-gnu": "4.57.0", + "@rollup/rollup-linux-x64-gnu": "4.57.0", + "@rollup/rollup-linux-x64-musl": "4.57.0", + "@rollup/rollup-openbsd-x64": "4.57.0", + "@rollup/rollup-openharmony-arm64": "4.57.0", + "@rollup/rollup-win32-arm64-msvc": "4.57.0", + "@rollup/rollup-win32-ia32-msvc": "4.57.0", + "@rollup/rollup-win32-x64-gnu": "4.57.0", + "@rollup/rollup-win32-x64-msvc": "4.57.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC", + "optional": true + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC", + "optional": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "2.8.2", + "resolved": "https://registry.npmmirror.com/simple-get/-/simple-get-2.8.2.tgz", + "integrity": "sha512-Ijd/rV5o+mSBBs4F/x9oDPtTx9Zb6X9brmnXvMW4J7IR15ngi9q5xxqWBKU744jTZiaXtxaPL7uHG6vtN8kUkw==", + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^3.3.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/snappy": { + "version": "6.3.5", + "resolved": "https://registry.npmmirror.com/snappy/-/snappy-6.3.5.tgz", + "integrity": "sha512-lonrUtdp1b1uDn1dbwgQbBsb5BbaiLeKq+AGwOk2No+en+VvJThwmtztwulEQsLinRF681pBqib0NUZaizKLIA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bindings": "^1.3.1", + "nan": "^2.14.1", + "prebuild-install": "5.3.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmmirror.com/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "license": "MIT", + "optional": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar-fs": { + "version": "1.16.6", + "resolved": "https://registry.npmmirror.com/tar-fs/-/tar-fs-1.16.6.tgz", + "integrity": "sha512-JkOgFt3FxM/2v2CNpAVHqMW2QASjc/Hxo7IGfNd3MHaDYSW/sBFiS7YVmmhmr8x6vwN1VFQDQGdT2MWpmIuVKA==", + "license": "MIT", + "optional": true, + "dependencies": { + "chownr": "^1.0.1", + "mkdirp": "^0.5.1", + "pump": "^1.0.0", + "tar-stream": "^1.1.2" + } + }, + "node_modules/tar-fs/node_modules/pump": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/pump/-/pump-1.0.3.tgz", + "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "license": "MIT", + "optional": true, + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/tar-stream/node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "license": "MIT", + "optional": true, + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "optional": true, + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT", + "optional": true + }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmmirror.com/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "license": "MIT/X11", + "engines": { + "node": "*" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmmirror.com/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmmirror.com/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmmirror.com/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "license": "MIT", + "optional": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", + "optional": true + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/bls-rcu-action-backend/package.json b/bls-rcu-action-backend/package.json new file mode 100644 index 0000000..7579cbe --- /dev/null +++ b/bls-rcu-action-backend/package.json @@ -0,0 +1,24 @@ +{ + "name": "bls-rcu-action-backend", + "version": "1.0.0", + "type": "module", + "private": true, + "scripts": { + "dev": "node src/index.js", + "build": "vite build --ssr src/index.js --outDir dist", + "test": "vitest run", + "start": "node dist/index.js" + }, + "dependencies": { + "dotenv": "^16.4.5", + "kafka-node": "^5.0.0", + "node-cron": "^4.2.1", + "pg": "^8.11.5", + "redis": "^4.6.13", + "zod": "^4.3.6" + }, + "devDependencies": { + "vite": "^5.4.0", + "vitest": "^4.0.18" + } +} diff --git a/bls-rcu-action-backend/scripts/init_db.sql b/bls-rcu-action-backend/scripts/init_db.sql new file mode 100644 index 0000000..83a5af6 --- /dev/null +++ b/bls-rcu-action-backend/scripts/init_db.sql @@ -0,0 +1,45 @@ +-- Database Initialization Script for BLS RCU Action Server + +CREATE SCHEMA IF NOT EXISTS rcu_action; + +CREATE TABLE IF NOT EXISTS rcu_action.rcu_action_events ( + guid VARCHAR(32) NOT NULL, + ts_ms BIGINT NOT NULL, + write_ts_ms BIGINT NOT NULL, + hotel_id INTEGER NOT NULL, + room_id VARCHAR(32) NOT NULL, + device_id VARCHAR(32) NOT NULL, + direction VARCHAR(10) NOT NULL, + cmd_word VARCHAR(10) NOT NULL, + frame_id INTEGER NOT NULL, + udp_raw TEXT NOT NULL, + action_type VARCHAR(20) NOT NULL, + sys_lock_status SMALLINT, + report_count SMALLINT, + dev_type SMALLINT, + dev_addr SMALLINT, + dev_loop INTEGER, + dev_data INTEGER, + fault_count SMALLINT, + error_type SMALLINT, + error_data SMALLINT, + type_l SMALLINT, + type_h SMALLINT, + details JSONB, + extra JSONB, + PRIMARY KEY (ts_ms, guid) +) PARTITION BY RANGE (ts_ms); + +ALTER TABLE rcu_action.rcu_action_events + ADD COLUMN IF NOT EXISTS device_id VARCHAR(32) NOT NULL DEFAULT ''; + +-- Indexes for performance +CREATE INDEX IF NOT EXISTS idx_rcu_action_hotel_id ON rcu_action.rcu_action_events (hotel_id); +CREATE INDEX IF NOT EXISTS idx_rcu_action_room_id ON rcu_action.rcu_action_events (room_id); +CREATE INDEX IF NOT EXISTS idx_rcu_action_device_id ON rcu_action.rcu_action_events (device_id); +CREATE INDEX IF NOT EXISTS idx_rcu_action_direction ON rcu_action.rcu_action_events (direction); +CREATE INDEX IF NOT EXISTS idx_rcu_action_cmd_word ON rcu_action.rcu_action_events (cmd_word); +CREATE INDEX IF NOT EXISTS idx_rcu_action_action_type ON rcu_action.rcu_action_events (action_type); + +-- Composite Index for typical query pattern (Hotel + Room + Time) +CREATE INDEX IF NOT EXISTS idx_rcu_action_query_main ON rcu_action.rcu_action_events (hotel_id, room_id, ts_ms DESC); diff --git a/bls-rcu-action-backend/src/config/config.js b/bls-rcu-action-backend/src/config/config.js new file mode 100644 index 0000000..4c2e963 --- /dev/null +++ b/bls-rcu-action-backend/src/config/config.js @@ -0,0 +1,50 @@ +import dotenv from 'dotenv'; + +dotenv.config(); + +const parseNumber = (value, defaultValue) => { + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : defaultValue; +}; + +const parseList = (value) => + (value || '') + .split(',') + .map((item) => item.trim()) + .filter(Boolean); + +export const config = { + env: process.env.NODE_ENV || 'development', + port: parseNumber(process.env.PORT, 3000), + kafka: { + brokers: parseList(process.env.KAFKA_BROKERS), + topic: process.env.KAFKA_TOPIC || process.env.KAFKA_TOPICS || 'blwlog4Nodejs-rcu-action-topic', + groupId: process.env.KAFKA_GROUP_ID || 'bls-rcu-action-group', + clientId: process.env.KAFKA_CLIENT_ID || 'bls-rcu-action-client', + consumerInstances: parseNumber(process.env.KAFKA_CONSUMER_INSTANCES, 1), + sasl: process.env.KAFKA_SASL_USERNAME && process.env.KAFKA_SASL_PASSWORD ? { + mechanism: process.env.KAFKA_SASL_MECHANISM || 'plain', + username: process.env.KAFKA_SASL_USERNAME, + password: process.env.KAFKA_SASL_PASSWORD + } : undefined + }, + db: { + host: process.env.DB_HOST || process.env.POSTGRES_HOST || 'localhost', + port: parseNumber(process.env.DB_PORT || process.env.POSTGRES_PORT, 5432), + user: process.env.DB_USER || process.env.POSTGRES_USER || 'postgres', + password: process.env.DB_PASSWORD || process.env.POSTGRES_PASSWORD || '', + database: process.env.DB_DATABASE || process.env.POSTGRES_DATABASE || 'bls_rcu_action', + max: parseNumber(process.env.DB_MAX_CONNECTIONS || process.env.POSTGRES_MAX_CONNECTIONS, 10), + ssl: process.env.DB_SSL === 'true' ? { rejectUnauthorized: false } : undefined, + schema: process.env.DB_SCHEMA || 'rcu_action', + table: process.env.DB_TABLE || 'rcu_action_events' + }, + redis: { + host: process.env.REDIS_HOST || 'localhost', + port: parseNumber(process.env.REDIS_PORT, 6379), + password: process.env.REDIS_PASSWORD || undefined, + db: parseNumber(process.env.REDIS_DB, 0), + projectName: process.env.REDIS_PROJECT_NAME || 'bls-rcu-action', + apiBaseUrl: process.env.REDIS_API_BASE_URL || `http://localhost:${parseNumber(process.env.PORT, 3000)}` + } +}; diff --git a/bls-rcu-action-backend/src/db/databaseManager.js b/bls-rcu-action-backend/src/db/databaseManager.js new file mode 100644 index 0000000..15ce685 --- /dev/null +++ b/bls-rcu-action-backend/src/db/databaseManager.js @@ -0,0 +1,80 @@ +import pg from 'pg'; +import { config } from '../config/config.js'; +import { logger } from '../utils/logger.js'; + +const { Pool } = pg; + +const columns = [ + 'guid', + 'ts_ms', + 'write_ts_ms', + 'hotel_id', + 'room_id', + 'device_id', + 'direction', + 'cmd_word', + 'frame_id', + 'udp_raw', + 'action_type', + 'sys_lock_status', + 'report_count', + 'dev_type', + 'dev_addr', + 'dev_loop', + 'dev_data', + 'fault_count', + 'error_type', + 'error_data', + 'type_l', + 'type_h', + 'details', + 'extra' +]; + +export class DatabaseManager { + constructor(dbConfig) { + this.pool = new Pool({ + host: dbConfig.host, + port: dbConfig.port, + user: dbConfig.user, + password: dbConfig.password, + database: dbConfig.database, + max: dbConfig.max, + ssl: dbConfig.ssl + }); + } + + async insertRows({ schema, table, rows }) { + if (!rows || rows.length === 0) { + return; + } + const values = []; + const placeholders = rows.map((row, rowIndex) => { + const offset = rowIndex * columns.length; + columns.forEach((column) => { + values.push(row[column] ?? null); + }); + const params = columns.map((_, columnIndex) => `$${offset + columnIndex + 1}`); + return `(${params.join(', ')})`; + }); + const statement = `INSERT INTO ${schema}.${table} (${columns.join(', ')}) VALUES ${placeholders.join(', ')}`; + try { + await this.pool.query(statement, values); + } catch (error) { + logger.error('Database insert failed', { + error: error?.message, + schema, + table, + rowsLength: rows.length + }); + throw error; + } + } + + async close() { + await this.pool.end(); + } +} + +const dbManager = new DatabaseManager(config.db); +export default dbManager; diff --git a/bls-rcu-action-backend/src/db/initializer.js b/bls-rcu-action-backend/src/db/initializer.js new file mode 100644 index 0000000..7bc7292 --- /dev/null +++ b/bls-rcu-action-backend/src/db/initializer.js @@ -0,0 +1,86 @@ +import pg from 'pg'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { logger } from '../utils/logger.js'; +import partitionManager from './partitionManager.js'; +import dbManager from './databaseManager.js'; +import { config } from '../config/config.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +class DatabaseInitializer { + async initialize() { + logger.info('Starting database initialization check...'); + + // 1. Check if database exists, create if not + await this.ensureDatabaseExists(); + + // 2. Initialize Schema and Parent Table (if not exists) + // Note: We need to use dbManager because it connects to the target database + await this.ensureSchemaAndTable(); + + // 3. Ensure Partitions for the next month + await partitionManager.ensurePartitions(30); + + logger.info('Database initialization completed successfully.'); + } + + async ensureDatabaseExists() { + const { host, port, user, password, database, ssl } = config.db; + + // Connect to 'postgres' database to check/create target database + const client = new pg.Client({ + host, + port, + user, + password, + database: 'postgres', + ssl: ssl ? { rejectUnauthorized: false } : false + }); + + try { + await client.connect(); + + const checkRes = await client.query( + `SELECT 1 FROM pg_database WHERE datname = $1`, + [database] + ); + + if (checkRes.rowCount === 0) { + logger.info(`Database '${database}' does not exist. Creating...`); + // CREATE DATABASE cannot run inside a transaction block + await client.query(`CREATE DATABASE "${database}"`); + logger.info(`Database '${database}' created.`); + } else { + logger.info(`Database '${database}' already exists.`); + } + } catch (err) { + logger.error('Error ensuring database exists:', err); + throw err; + } finally { + await client.end(); + } + } + + async ensureSchemaAndTable() { + // dbManager connects to the target database + const client = await dbManager.pool.connect(); + try { + const sqlPath = path.resolve(__dirname, '../../scripts/init_db.sql'); + const sql = fs.readFileSync(sqlPath, 'utf8'); + + logger.info('Executing init_db.sql...'); + await client.query(sql); + logger.info('Schema and parent table initialized.'); + } catch (err) { + logger.error('Error initializing schema and table:', err); + throw err; + } finally { + client.release(); + } + } +} + +export default new DatabaseInitializer(); diff --git a/bls-rcu-action-backend/src/db/partitionManager.js b/bls-rcu-action-backend/src/db/partitionManager.js new file mode 100644 index 0000000..004d5d6 --- /dev/null +++ b/bls-rcu-action-backend/src/db/partitionManager.js @@ -0,0 +1,71 @@ +import { logger } from '../utils/logger.js'; +import dbManager from './databaseManager.js'; + +class PartitionManager { + /** + * Calculate the start and end timestamps (milliseconds) for a given date. + * @param {Date} date - The date to calculate for. + * @returns {Object} { startMs, endMs, partitionSuffix } + */ + getPartitionInfo(date) { + const yyyy = date.getFullYear(); + const mm = String(date.getMonth() + 1).padStart(2, '0'); + const dd = String(date.getDate()).padStart(2, '0'); + const partitionSuffix = `${yyyy}${mm}${dd}`; + + const start = new Date(date); + start.setHours(0, 0, 0, 0); + const startMs = start.getTime(); + + const end = new Date(date); + end.setDate(end.getDate() + 1); + end.setHours(0, 0, 0, 0); + const endMs = end.getTime(); + + return { startMs, endMs, partitionSuffix }; + } + + /** + * Ensure partitions exist for the next N days. + * @param {number} daysAhead - Number of days to pre-create. + */ + async ensurePartitions(daysAhead = 30) { + const client = await dbManager.pool.connect(); + try { + logger.info(`Starting partition check for the next ${daysAhead} days...`); + const now = new Date(); + + for (let i = 0; i < daysAhead; i++) { + const targetDate = new Date(now); + targetDate.setDate(now.getDate() + i); + + const { startMs, endMs, partitionSuffix } = this.getPartitionInfo(targetDate); + const partitionName = `rcu_action.rcu_action_events_${partitionSuffix}`; + + // Check if partition exists + const checkSql = ` + SELECT to_regclass($1) as exists; + `; + const checkRes = await client.query(checkSql, [partitionName]); + + if (!checkRes.rows[0].exists) { + logger.info(`Creating partition ${partitionName} for range [${startMs}, ${endMs})`); + const createSql = ` + CREATE TABLE IF NOT EXISTS ${partitionName} + PARTITION OF rcu_action.rcu_action_events + FOR VALUES FROM (${startMs}) TO (${endMs}); + `; + await client.query(createSql); + } + } + logger.info('Partition check completed.'); + } catch (err) { + logger.error('Error ensuring partitions:', err); + throw err; + } finally { + client.release(); + } + } +} + +export default new PartitionManager(); diff --git a/bls-rcu-action-backend/src/index.js b/bls-rcu-action-backend/src/index.js new file mode 100644 index 0000000..78ac428 --- /dev/null +++ b/bls-rcu-action-backend/src/index.js @@ -0,0 +1,203 @@ +import cron from 'node-cron'; +import { config } from './config/config.js'; +import dbManager from './db/databaseManager.js'; +import dbInitializer from './db/initializer.js'; +import partitionManager from './db/partitionManager.js'; +import { createKafkaConsumer } from './kafka/consumer.js'; +import { processKafkaMessage } from './processor/index.js'; +import { createRedisClient } from './redis/redisClient.js'; +import { RedisIntegration } from './redis/redisIntegration.js'; +import { buildErrorQueueKey, enqueueError, startErrorRetryWorker } from './redis/errorQueue.js'; +import { MetricCollector } from './utils/metricCollector.js'; +import { logger } from './utils/logger.js'; + +const bootstrap = async () => { + // 0. Initialize Database (Create DB, Schema, Table, Partitions) + await dbInitializer.initialize(); + + // Metric Collector + const metricCollector = new MetricCollector(); + + // 1. Setup Partition Maintenance Cron Job (Every day at 00:00) + cron.schedule('0 0 * * *', async () => { + logger.info('Running scheduled partition maintenance...'); + try { + await partitionManager.ensurePartitions(30); + } catch (err) { + logger.error('Scheduled partition maintenance failed', err); + } + }); + + // 1.1 Setup Metric Reporting Cron Job (Every minute) + // Moved after redisIntegration initialization + + + // DatabaseManager is now a singleton exported instance, but let's keep consistency if possible + // In databaseManager.js it exports `dbManager` instance by default. + // The original code was `const dbManager = new DatabaseManager(config.db);` which implies it might have been a class export. + // Let's check `databaseManager.js` content. + // Wait, I imported `dbManager` from `./db/databaseManager.js`. + // If `databaseManager.js` exports an instance as default, I should use that. + // If it exports a class, I should instantiate it. + + // Let's assume the previous code `new DatabaseManager` was correct if it was a class. + // BUT I used `dbManager.pool` in `partitionManager.js` assuming it's an instance. + // I need to verify `databaseManager.js`. + + const redisClient = await createRedisClient(config.redis); + const redisIntegration = new RedisIntegration( + redisClient, + config.redis.projectName, + config.redis.apiBaseUrl + ); + redisIntegration.startHeartbeat(); + + // 1.1 Setup Metric Reporting Cron Job (Every minute) + cron.schedule('* * * * *', async () => { + const metrics = metricCollector.getAndReset(); + const report = `[Minute Metrics] Pulled: ${metrics.kafka_pulled}, Parse Error: ${metrics.parse_error}, Inserted: ${metrics.db_inserted}, Failed: ${metrics.db_failed}`; + console.log(report); + logger.info(report, metrics); + + try { + await redisIntegration.info('Minute Metrics', metrics); + } catch (err) { + logger.error('Failed to report metrics to Redis', { error: err?.message }); + } + }); + + const errorQueueKey = buildErrorQueueKey(config.redis.projectName); + + const handleMessage = async (message) => { + if (message.topic) { + metricCollector.increment('kafka_pulled'); + } + try { + const messageValue = Buffer.isBuffer(message.value) + ? message.value.toString('utf8') + : message.value; + const messageKey = Buffer.isBuffer(message.key) + ? message.key.toString('utf8') + : message.key; + logger.info('Kafka message received', { + topic: message.topic, + partition: message.partition, + offset: message.offset, + key: messageKey, + value: messageValue + }); + const inserted = await processKafkaMessage({ message, dbManager, config }); + metricCollector.increment('db_inserted'); + logger.info('Kafka message processed', { inserted }); + } catch (error) { + if (error.type === 'PARSE_ERROR') { + metricCollector.increment('parse_error'); + } else { + metricCollector.increment('db_failed'); + } + logger.error('Message processing failed', { + error: error?.message, + type: error?.type, + stack: error?.stack, + rawPayload: error?.rawPayload, + validationIssues: error?.validationIssues, + dbContext: error?.dbContext + }); + throw error; // Re-throw to trigger onError + } + }; + + const handleError = async (error, message) => { + logger.error('Kafka processing error', { + error: error?.message, + type: error?.type, + stack: error?.stack + }); + try { + await redisIntegration.error('Kafka processing error', { + module: 'kafka', + stack: error?.stack || error?.message + }); + } catch (redisError) { + logger.error('Redis error log failed', { error: redisError?.message }); + } + if (message) { + const messageValue = Buffer.isBuffer(message.value) + ? message.value.toString('utf8') + : message.value; + try { + await enqueueError(redisClient, errorQueueKey, { + attempts: 0, + value: messageValue, + meta: { + topic: message.topic, + partition: message.partition, + offset: message.offset, + key: message.key + }, + timestamp: Date.now() + }); + } catch (enqueueError) { + logger.error('Enqueue error payload failed', { error: enqueueError?.message }); + } + } + }; + + const consumer = createKafkaConsumer({ + kafkaConfig: config.kafka, + onMessage: handleMessage, + onError: handleError + }); + + // Start retry worker (non-blocking) + startErrorRetryWorker({ + client: redisClient, + queueKey: errorQueueKey, + redisIntegration, + handler: async (item) => { + if (!item?.value) { + throw new Error('Missing value in retry payload'); + } + await handleMessage({ value: item.value }); + } + }).catch(err => { + logger.error('Retry worker failed', { error: err?.message }); + }); + + // Graceful Shutdown Logic + const shutdown = async (signal) => { + logger.info(`Received ${signal}, shutting down...`); + + try { + // 1. Close Kafka Consumer + if (consumer) { + await new Promise((resolve) => consumer.close(true, resolve)); + logger.info('Kafka consumer closed'); + } + + // 2. Stop Redis Heartbeat (if method exists, otherwise just close client) + // redisIntegration.stopHeartbeat(); // Assuming implementation or just rely on client close + + // 3. Close Redis Client + await redisClient.quit(); + logger.info('Redis client closed'); + + // 4. Close Database Pool + await dbManager.close(); + logger.info('Database connection closed'); + + process.exit(0); + } catch (err) { + logger.error('Error during shutdown', { error: err?.message }); + process.exit(1); + } + }; + + process.on('SIGTERM', () => shutdown('SIGTERM')); + process.on('SIGINT', () => shutdown('SIGINT')); +}; + +bootstrap().catch((error) => { + logger.error('Service bootstrap failed', { error: error?.message }); + process.exit(1); +}); diff --git a/bls-rcu-action-backend/src/kafka/consumer.js b/bls-rcu-action-backend/src/kafka/consumer.js new file mode 100644 index 0000000..beeddd6 --- /dev/null +++ b/bls-rcu-action-backend/src/kafka/consumer.js @@ -0,0 +1,39 @@ +import kafka from 'kafka-node'; +import { logger } from '../utils/logger.js'; + +const { ConsumerGroup } = kafka; + +export const createKafkaConsumer = ({ kafkaConfig, onMessage, onError }) => { + const kafkaHost = kafkaConfig.brokers.join(','); + const consumer = new ConsumerGroup( + { + kafkaHost, + groupId: kafkaConfig.groupId, + clientId: kafkaConfig.clientId, + fromOffset: 'earliest', + protocol: ['roundrobin'], + outOfRangeOffset: 'latest', + autoCommit: true, + sasl: kafkaConfig.sasl + }, + kafkaConfig.topic + ); + + consumer.on('message', (message) => { + onMessage(message).catch((error) => { + logger.error('Kafka message handling failed', { error: error?.message }); + if (onError) { + onError(error, message); + } + }); + }); + + consumer.on('error', (error) => { + logger.error('Kafka consumer error', { error: error?.message }); + if (onError) { + onError(error); + } + }); + + return consumer; +}; diff --git a/bls-rcu-action-backend/src/processor/index.js b/bls-rcu-action-backend/src/processor/index.js new file mode 100644 index 0000000..6d063b6 --- /dev/null +++ b/bls-rcu-action-backend/src/processor/index.js @@ -0,0 +1,247 @@ +import { createGuid } from '../utils/uuid.js'; +import { kafkaPayloadSchema } from '../schema/kafkaPayload.js'; + +const normalizeDirection = (value) => { + if (!value) return null; + if (value === '上报' || value === '上传') return '上报'; + if (value === '下发') return '下发'; + return value; +}; + +const normalizeCmdWord = (value) => { + if (typeof value === 'string') { + const trimmed = value.trim(); + if (trimmed.startsWith('0x') || trimmed.startsWith('0X')) { + return `0x${trimmed.slice(2).toLowerCase()}`; + } + if (/^[0-9a-fA-F]{2}$/.test(trimmed)) { + return `0x${trimmed.toLowerCase()}`; + } + const parsed = Number(trimmed); + if (Number.isFinite(parsed)) { + return `0x${parsed.toString(16).toLowerCase()}`; + } + return trimmed; + } + // The Zod schema might have already converted numbers to strings, but let's be safe + if (typeof value === 'number' && Number.isFinite(value)) { + return `0x${value.toString(16).toLowerCase()}`; + } + return null; +}; + +const resolveActionType = (direction, cmdWord) => { + if (cmdWord === '0x36') { + return '36上报'; + } + if (cmdWord === '0x0f' && direction === '下发') { + return '0F下发'; + } + if (cmdWord === '0x0f' && direction === '上报') { + return '0FACK'; + } + return null; +}; + +const parseKafkaPayload = (value) => { + const raw = Buffer.isBuffer(value) ? value.toString('utf8') : value; + if (typeof raw !== 'string') { + throw new Error('Invalid kafka message value'); + } + return JSON.parse(raw); +}; + +export const buildRowsFromMessageValue = (value) => { + const payload = parseKafkaPayload(value); + return buildRowsFromPayload(payload); +}; + +export const buildRowsFromPayload = (rawPayload) => { + // 1. Validate and transform payload using Zod schema + const payload = kafkaPayloadSchema.parse(rawPayload); + + const { + ts_ms: tsMs, + hotel_id: hotelId, + room_id: roomId, + device_id: deviceId, + direction, + cmd_word: cmdWord, + frame_id: frameId, + udp_raw: udpRaw, + sys_lock_status: sysLockStatus, + report_count: reportCount, + fault_count: faultCount, + device_list: deviceList, // Zod provides default [] + fault_list: faultList, // Zod provides default [] + control_list: controlList // Zod provides default [] + } = payload; + + const normalizedDirection = normalizeDirection(direction); + const normalizedCmdWord = normalizeCmdWord(cmdWord); + const actionType = resolveActionType(normalizedDirection, normalizedCmdWord); + const writeTsMs = Date.now(); + + // Base fields common to all rows (excluding unique ID) + const commonFields = { + ts_ms: tsMs, + write_ts_ms: writeTsMs, + hotel_id: hotelId, + room_id: roomId, + device_id: deviceId, // Pass through normalized/validated device_id + direction: normalizedDirection, + cmd_word: normalizedCmdWord, + frame_id: frameId, + udp_raw: udpRaw, + action_type: actionType, + sys_lock_status: sysLockStatus ?? null, + report_count: reportCount ?? null, + fault_count: faultCount ?? null, + // Initialize nullable fields + dev_type: null, + dev_addr: null, + dev_loop: null, + dev_data: null, + error_type: null, + error_data: null, + type_l: null, + type_h: null, + details: null, + extra: { raw_hex: udpRaw } + }; + + const rows = []; + + // Logic 1: 0x36 Status/Fault Report + if (actionType === '36上报') { + const details = { + device_list: deviceList, + fault_list: faultList + }; + + // Process device status list + if (deviceList.length > 0) { + deviceList.forEach(device => { + rows.push({ + ...commonFields, + guid: createGuid(), + dev_type: device.dev_type ?? null, + dev_addr: device.dev_addr ?? null, + dev_loop: device.dev_loop ?? null, + dev_data: device.dev_data ?? null, + details + }); + }); + } + + // Process fault list + if (faultList.length > 0) { + faultList.forEach(fault => { + rows.push({ + ...commonFields, + guid: createGuid(), + // Use common dev_ fields for fault device identification + dev_type: fault.dev_type ?? null, + dev_addr: fault.dev_addr ?? null, + dev_loop: fault.dev_loop ?? null, + error_type: fault.error_type ?? null, + error_data: fault.error_data ?? null, + details + }); + }); + } + + // Fallback: if no lists, insert one record to preserve the event + if (rows.length === 0) { + rows.push({ + ...commonFields, + guid: createGuid(), + details + }); + } + + return rows; + } + + // Logic 2: 0x0F Control Command + if (actionType === '0F下发') { + const details = { + control_list: controlList + }; + + if (controlList.length > 0) { + controlList.forEach(control => { + rows.push({ + ...commonFields, + guid: createGuid(), + dev_type: control.dev_type ?? null, + dev_addr: control.dev_addr ?? null, + dev_loop: control.dev_loop ?? null, + type_l: control.type_l ?? null, + type_h: control.type_h ?? null, + details + }); + }); + } + + // Fallback + if (rows.length === 0) { + rows.push({ + ...commonFields, + guid: createGuid(), + details + }); + } + + return rows; + } + + // Logic 3: 0x0F ACK or others + // Default behavior: single row + return [{ + ...commonFields, + guid: createGuid(), + details: {} + }]; +}; + +export const processKafkaMessage = async ({ message, dbManager, config }) => { + let rows; + try { + const payload = parseKafkaPayload(message.value); + rows = buildRowsFromPayload(payload); + } catch (error) { + error.type = 'PARSE_ERROR'; + const rawValue = Buffer.isBuffer(message.value) + ? message.value.toString('utf8') + : String(message.value ?? ''); + error.rawPayload = rawValue.length > 1000 ? `${rawValue.slice(0, 1000)}...` : rawValue; + if (error?.issues) { + error.validationIssues = error.issues; + } + throw error; + } + + try { + await dbManager.insertRows({ schema: config.db.schema, table: config.db.table, rows }); + } catch (error) { + error.type = 'DB_ERROR'; + const sample = rows?.[0]; + error.dbContext = { + rowsLength: rows?.length || 0, + sampleRow: sample + ? { + guid: sample.guid, + ts_ms: sample.ts_ms, + action_type: sample.action_type, + cmd_word: sample.cmd_word, + direction: sample.direction, + device_id: sample.device_id + } + : null + }; + throw error; + } + + return rows.length; +}; diff --git a/bls-rcu-action-backend/src/processor/udpParser.js b/bls-rcu-action-backend/src/processor/udpParser.js new file mode 100644 index 0000000..ccecfc9 --- /dev/null +++ b/bls-rcu-action-backend/src/processor/udpParser.js @@ -0,0 +1,83 @@ +const normalizeHex = (hex) => { + if (typeof hex !== 'string') { + return ''; + } + let cleaned = hex.trim().replace(/^0x/i, '').replace(/\s+/g, ''); + if (cleaned.length % 2 === 1) { + cleaned = `0${cleaned}`; + } + return cleaned; +}; + +const toHex = (value) => `0x${value.toString(16).padStart(2, '0')}`; + +const readUInt16 = (buffer, offset) => buffer.readUInt16BE(offset); + +export const parse0x36 = (udpRaw) => { + const cleaned = normalizeHex(udpRaw); + const buffer = cleaned ? Buffer.from(cleaned, 'hex') : Buffer.alloc(0); + const sysLockStatus = buffer.length > 0 ? buffer[0] : null; + const reportCount = buffer.length > 7 ? buffer[7] : null; + let offset = 8; + const devices = []; + for (let i = 0; i < (reportCount || 0) && offset + 5 < buffer.length; i += 1) { + devices.push({ + dev_type: buffer[offset], + dev_addr: buffer[offset + 1], + dev_loop: readUInt16(buffer, offset + 2), + dev_data: readUInt16(buffer, offset + 4) + }); + offset += 6; + } + const faultCount = offset < buffer.length ? buffer[offset] : null; + offset += 1; + const faults = []; + for (let i = 0; i < (faultCount || 0) && offset + 5 < buffer.length; i += 1) { + faults.push({ + fault_dev_type: buffer[offset], + fault_dev_addr: buffer[offset + 1], + fault_dev_loop: readUInt16(buffer, offset + 2), + error_type: buffer[offset + 4], + error_data: buffer[offset + 5] + }); + offset += 6; + } + return { + sysLockStatus, + reportCount, + faultCount, + devices, + faults + }; +}; + +export const parse0x0fDownlink = (udpRaw) => { + const cleaned = normalizeHex(udpRaw); + const buffer = cleaned ? Buffer.from(cleaned, 'hex') : Buffer.alloc(0); + const controlCount = buffer.length > 0 ? buffer[0] : null; + let offset = 1; + const controlParams = []; + for (let i = 0; i < (controlCount || 0) && offset + 5 < buffer.length; i += 1) { + const typeValue = readUInt16(buffer, offset + 4); + controlParams.push({ + dev_type: buffer[offset], + dev_addr: buffer[offset + 1], + loop: readUInt16(buffer, offset + 2), + type: typeValue, + type_l: buffer[offset + 4], + type_h: buffer[offset + 5] + }); + offset += 6; + } + return { + controlCount, + controlParams + }; +}; + +export const parse0x0fAck = (udpRaw) => { + const cleaned = normalizeHex(udpRaw); + const buffer = cleaned ? Buffer.from(cleaned, 'hex') : Buffer.alloc(0); + const ackCode = buffer.length > 1 ? toHex(buffer[1]) : null; + return { ackCode }; +}; diff --git a/bls-rcu-action-backend/src/redis/errorQueue.js b/bls-rcu-action-backend/src/redis/errorQueue.js new file mode 100644 index 0000000..226f863 --- /dev/null +++ b/bls-rcu-action-backend/src/redis/errorQueue.js @@ -0,0 +1,53 @@ +import { logger } from '../utils/logger.js'; + +export const buildErrorQueueKey = (projectName) => `${projectName}_error_queue`; + +export const enqueueError = async (client, queueKey, payload) => { + try { + await client.rPush(queueKey, JSON.stringify(payload)); + } catch (error) { + logger.error('Redis enqueue error failed', { error: error?.message }); + throw error; + } +}; + +export const startErrorRetryWorker = async ({ + client, + queueKey, + handler, + redisIntegration, + maxAttempts = 5 +}) => { + while (true) { + const result = await client.blPop(queueKey, 0); + const raw = result?.element; + if (!raw) { + continue; + } + let item; + try { + item = JSON.parse(raw); + } catch (error) { + logger.error('Invalid error payload', { error: error?.message }); + await redisIntegration.error('Invalid error payload', { module: 'redis', stack: error?.message }); + continue; + } + const attempts = item.attempts || 0; + try { + await handler(item); + } catch (error) { + logger.error('Retry handler failed', { error: error?.message, stack: error?.stack }); + const nextPayload = { + ...item, + attempts: attempts + 1, + lastError: error?.message, + lastAttemptAt: Date.now() + }; + if (nextPayload.attempts >= maxAttempts) { + await redisIntegration.error('Retry attempts exceeded', { module: 'retry', stack: JSON.stringify(nextPayload) }); + } else { + await enqueueError(client, queueKey, nextPayload); + } + } + } +}; diff --git a/bls-rcu-action-backend/src/redis/redisClient.js b/bls-rcu-action-backend/src/redis/redisClient.js new file mode 100644 index 0000000..e19e036 --- /dev/null +++ b/bls-rcu-action-backend/src/redis/redisClient.js @@ -0,0 +1,14 @@ +import { createClient } from 'redis'; + +export const createRedisClient = async (config) => { + const client = createClient({ + socket: { + host: config.host, + port: config.port + }, + password: config.password, + database: config.db + }); + await client.connect(); + return client; +}; diff --git a/bls-rcu-action-backend/src/redis/redisIntegration.js b/bls-rcu-action-backend/src/redis/redisIntegration.js new file mode 100644 index 0000000..4502d16 --- /dev/null +++ b/bls-rcu-action-backend/src/redis/redisIntegration.js @@ -0,0 +1,40 @@ +export class RedisIntegration { + constructor(client, projectName, apiBaseUrl) { + this.client = client; + this.projectName = projectName; + this.apiBaseUrl = apiBaseUrl; + this.heartbeatKey = '项目心跳'; + this.logKey = `${projectName}_项目控制台`; + } + + async info(message, context) { + const payload = { + timestamp: new Date().toISOString(), + level: 'info', + message, + metadata: context || undefined + }; + await this.client.rPush(this.logKey, JSON.stringify(payload)); + } + + async error(message, context) { + const payload = { + timestamp: new Date().toISOString(), + level: 'error', + message, + metadata: context || undefined + }; + await this.client.rPush(this.logKey, JSON.stringify(payload)); + } + + startHeartbeat() { + setInterval(() => { + const payload = { + projectName: this.projectName, + apiBaseUrl: this.apiBaseUrl, + lastActiveAt: Date.now() + }; + this.client.rPush(this.heartbeatKey, JSON.stringify(payload)); + }, 3000); + } +} diff --git a/bls-rcu-action-backend/src/schema/kafkaPayload.js b/bls-rcu-action-backend/src/schema/kafkaPayload.js new file mode 100644 index 0000000..a0e2e22 --- /dev/null +++ b/bls-rcu-action-backend/src/schema/kafkaPayload.js @@ -0,0 +1,59 @@ +import { z } from 'zod'; + +// Device Status Schema (for device_list) +const deviceItemSchema = z.object({ + dev_type: z.number().int().optional(), + dev_addr: z.number().int().optional(), + dev_loop: z.number().int().optional(), + dev_data: z.number().int().optional() +}); + +// Fault Item Schema (for fault_list) +const faultItemSchema = z.object({ + dev_type: z.number().int().optional(), + dev_addr: z.number().int().optional(), + dev_loop: z.number().int().optional(), + error_type: z.number().int().optional(), + error_data: z.number().int().optional() +}); + +// Control Item Schema (for control_list) +const controlItemSchema = z.object({ + dev_type: z.number().int().optional(), + dev_addr: z.number().int().optional(), + dev_loop: z.number().int().optional(), + type_l: z.number().int().optional(), + type_h: z.number().int().optional() +}); + +const listSchema = (schema) => + z.preprocess( + (value) => (value === null ? [] : value), + z.array(schema).optional().default([]) + ); + +// Main Kafka Payload Schema +export const kafkaPayloadSchema = z.object({ + // Required Header Fields + ts_ms: z.number(), + hotel_id: z.preprocess( + (value) => (typeof value === 'string' ? Number(value) : value), + z.number() + ), + room_id: z.union([z.string(), z.number()]).transform(val => String(val)), + device_id: z.union([z.string(), z.number()]).transform(val => String(val)), + direction: z.string(), + cmd_word: z.union([z.string(), z.number()]).transform(val => String(val)), + frame_id: z.number(), + udp_raw: z.string(), + + // Optional Statistical/Status Fields + sys_lock_status: z.number().optional().nullable(), + report_count: z.number().optional().nullable(), + fault_count: z.number().optional().nullable(), + + // Lists + device_list: listSchema(deviceItemSchema), + fault_list: listSchema(faultItemSchema), + control_list: listSchema(controlItemSchema) +}); diff --git a/bls-rcu-action-backend/src/utils/logger.js b/bls-rcu-action-backend/src/utils/logger.js new file mode 100644 index 0000000..5a60c4b --- /dev/null +++ b/bls-rcu-action-backend/src/utils/logger.js @@ -0,0 +1,18 @@ +const format = (level, message, context) => { + const payload = { + level, + message, + timestamp: Date.now(), + ...(context ? { context } : {}) + }; + return JSON.stringify(payload); +}; + +export const logger = { + info(message, context) { + process.stdout.write(`${format('info', message, context)}\n`); + }, + error(message, context) { + process.stderr.write(`${format('error', message, context)}\n`); + } +}; diff --git a/bls-rcu-action-backend/src/utils/metricCollector.js b/bls-rcu-action-backend/src/utils/metricCollector.js new file mode 100644 index 0000000..29ac3cc --- /dev/null +++ b/bls-rcu-action-backend/src/utils/metricCollector.js @@ -0,0 +1,26 @@ +export class MetricCollector { + constructor() { + this.reset(); + } + + reset() { + this.metrics = { + kafka_pulled: 0, + parse_error: 0, + db_inserted: 0, + db_failed: 0 + }; + } + + increment(metric, count = 1) { + if (this.metrics.hasOwnProperty(metric)) { + this.metrics[metric] += count; + } + } + + getAndReset() { + const current = { ...this.metrics }; + this.reset(); + return current; + } +} diff --git a/bls-rcu-action-backend/src/utils/uuid.js b/bls-rcu-action-backend/src/utils/uuid.js new file mode 100644 index 0000000..e76a340 --- /dev/null +++ b/bls-rcu-action-backend/src/utils/uuid.js @@ -0,0 +1,3 @@ +import { randomUUID } from 'crypto'; + +export const createGuid = () => randomUUID().replace(/-/g, ''); diff --git a/bls-rcu-action-backend/tests/processor.test.js b/bls-rcu-action-backend/tests/processor.test.js new file mode 100644 index 0000000..aa92731 --- /dev/null +++ b/bls-rcu-action-backend/tests/processor.test.js @@ -0,0 +1,120 @@ +import { describe, it, expect } from 'vitest'; +import { buildRowsFromPayload } from '../src/processor/index.js'; + +describe('Processor Logic', () => { + const basePayload = { + ts_ms: 1700000000000, + hotel_id: 1001, + room_id: '8001', + device_id: 'dev_001', + direction: '上报', + cmd_word: '0x36', + frame_id: 1, + udp_raw: '3601...', + sys_lock_status: 0, + report_count: 0, + fault_count: 0 + }; + + it('should validate required fields', () => { + expect(() => buildRowsFromPayload({})).toThrow(); + expect(() => buildRowsFromPayload({ ...basePayload, ts_ms: undefined })).toThrow(); + }); + + it('should handle 0x36 Status Report with device list', () => { + const payload = { + ...basePayload, + direction: '上报', + cmd_word: '0x36', + report_count: 2, + device_list: [ + { dev_type: 1, dev_addr: 10, dev_loop: 1, dev_data: 100 }, + { dev_type: 1, dev_addr: 11, dev_loop: 2, dev_data: 0 } + ] + }; + + const rows = buildRowsFromPayload(payload); + expect(rows).toHaveLength(2); + expect(rows[0].action_type).toBe('36上报'); + expect(rows[0].dev_addr).toBe(10); + expect(rows[1].dev_addr).toBe(11); + expect(rows[0].details.device_list).toHaveLength(2); + }); + + it('should handle 0x36 Fault Report', () => { + const payload = { + ...basePayload, + direction: '上报', + cmd_word: '0x36', + fault_count: 1, + fault_list: [ + { dev_type: 1, dev_addr: 10, dev_loop: 1, error_type: 2, error_data: 5 } + ] + }; + + const rows = buildRowsFromPayload(payload); + expect(rows).toHaveLength(1); + expect(rows[0].action_type).toBe('36上报'); + expect(rows[0].error_type).toBe(2); + }); + + it('should handle 0x36 Mixed Report (Status + Fault)', () => { + const payload = { + ...basePayload, + direction: '上报', + cmd_word: '0x36', + report_count: 1, + fault_count: 1, + device_list: [{ dev_type: 1, dev_addr: 10, dev_loop: 1, dev_data: 100 }], + fault_list: [{ dev_type: 1, dev_addr: 10, dev_loop: 1, error_type: 2, error_data: 5 }] + }; + + const rows = buildRowsFromPayload(payload); + expect(rows).toHaveLength(2); // 1 status + 1 fault + }); + + it('should handle 0x0F Control Command', () => { + const payload = { + ...basePayload, + direction: '下发', + cmd_word: '0x0F', + control_list: [ + { dev_type: 1, dev_addr: 10, dev_loop: 1, type_l: 1, type_h: 2 } + ] + }; + + const rows = buildRowsFromPayload(payload); + expect(rows).toHaveLength(1); + expect(rows[0].action_type).toBe('0F下发'); + expect(rows[0].type_l).toBe(1); + expect(rows[0].type_h).toBe(2); + expect(rows[0].dev_loop).toBe(1); + }); + + it('should handle 0x0F ACK', () => { + const payload = { + ...basePayload, + direction: '上报', + cmd_word: '0x0F' + }; + + const rows = buildRowsFromPayload(payload); + expect(rows).toHaveLength(1); + expect(rows[0].action_type).toBe('0FACK'); + }); + + it('should fallback when lists are empty for 0x36', () => { + const payload = { + ...basePayload, + direction: '上报', + cmd_word: '0x36', + device_list: [], + fault_list: [] + }; + + const rows = buildRowsFromPayload(payload); + expect(rows).toHaveLength(1); + expect(rows[0].action_type).toBe('36上报'); + expect(rows[0].dev_type).toBeNull(); + }); +}); diff --git a/docs/kafka_format.md b/docs/kafka_format.md new file mode 100644 index 0000000..31b31a4 --- /dev/null +++ b/docs/kafka_format.md @@ -0,0 +1,153 @@ +# Kafka 推送格式与数据拆分规范 + +本文档定义了上游服务向 Kafka 推送消息的标准 JSON 格式。 + +**核心变更**:上游服务负责将原始报文解析为结构化的 JSON 对象。对于包含多个设备状态或故障信息的命令(如 `0x36`),上游必须将其转换为 **JSON 数组**,后端服务直接遍历这些数组进行入库,不再依赖对 `udp_raw` 的二次解析。 + +## 1. Kafka 基础信息 +* **Topic**: `blwlog4Nodejs-rcu-action-topic` +* **分区数**: 6 +* **消息格式**: JSON String + +## 2. 消息结构定义 (Schema) + +JSON 消息由 **Header 信息** 和 **业务列表数据** 组成。 + +### 2.1 顶层字段 (Header & 统计) + +| 字段名 | 类型 | 必填 | 说明 | +| :--- | :--- | :--- | :--- | +| **ts_ms** | Number | **是** | 日志产生时间戳 (Key1) | +| **hotel_id** | Number | **是** | 酒店 ID | +| **room_id** | String | **是** | 房间 ID | +| **device_id** | String | **是** | 设备 ID | +| **direction** | String | **是** | "上报" 或 "下发" | +| **cmd_word** | String | **是** | 命令字 (如 "0x36", "0x0F") | +| **frame_id** | Number | **是** | 通讯帧号 | +| **udp_raw** | String | **是** | UDP 原始报文 (作为备份/校验) | +| **sys_lock_status** | Number | 否 | 系统锁状态 (0:未锁, 1:锁定) | +| **report_count** | Number | 否 | 上报设备数量 (对应 device_list 长度) | +| **fault_count** | Number | 否 | 故障设备数量 (对应 fault_list 长度) | +| **action_type** | String | 否 | 行为类型 (建议上游预填,或后端默认处理) | +| **device_list** | Array | 否 | **设备状态列表** (结构见 2.2) | +| **fault_list** | Array | 否 | **设备故障列表** (结构见 2.3) | +| **control_list** | Array | 否 | **控制参数列表** (用于 0x0F) | + +### 2.2 设备状态对象 (Item in `device_list`) +对应 `0x36` 命令中的 P8~P13。 + +| JSON 字段名 | DB 映射字段 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| **dev_type** | `dev_type` | Number | 设备类型 | +| **dev_addr** | `dev_addr` | Number | 设备地址 | +| **dev_loop** | `dev_loop` | Number | 设备回路 | +| **dev_data** | `dev_data` | Number | 设备状态 | + +### 2.3 设备故障对象 (Item in `fault_list`) +对应 `0x36` 命令中的 P15~P20。 + +| JSON 字段名 | DB 映射字段 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| **dev_type** | `dev_type` | Number | 故障设备类型 (复用 dev_type) | +| **dev_addr** | `dev_addr` | Number | 故障设备地址 (复用 dev_addr) | +| **dev_loop** | `dev_loop` | Number | 故障设备回路 (复用 dev_loop) | +| **error_type** | `error_type` | Number | 故障类型 | +| **error_data** | `error_data` | Number | 故障内容 | + +### 2.4 控制参数对象 (Item in `control_list`) +对应 `0x0F` 下发命令。 + +| JSON 字段名 | DB 映射字段 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| **dev_type** | `dev_type` | Number | 控制设备类型 | +| **dev_addr** | `dev_addr` | Number | 控制设备地址 | +| **loop** | `dev_loop` | Number | 控制设备的回路地址 (复用 dev_loop) | +| **type_l** | `type_l` | Number | 执行方式 | +| **type_h** | `type_h` | Number | 执行内容 | + +--- + +## 3. 后端入库逻辑 + +后端服务接收到 JSON 后,逻辑简化为: +1. **遍历 `device_list`**: 为数组中每个对象生成一条 DB 记录。 + * 映射:`dev_type` -> `dev_type`, `dev_addr` -> `dev_addr`, `dev_loop` -> `dev_loop`, `dev_data` -> `dev_data`。 + * `action_type`: "36上报"。 +2. **遍历 `fault_list`**: 为数组中每个对象生成一条 DB 记录。 + * 映射:`dev_type` -> `dev_type`, `dev_addr` -> `dev_addr`, `dev_loop` -> `dev_loop`, `error_type` -> `error_type`, `error_data` -> `error_data`。 + * `action_type`: "36上报"。 +3. **遍历 `control_list`**: 为数组中每个对象生成一条 DB 记录。 + * 映射:`dev_type` -> `dev_type`, `dev_addr` -> `dev_addr`, `loop` -> `dev_loop`, `type_l` -> `type_l`, `type_h` -> `type_h`。 + * `action_type`: "0F下发"。 + +--- + +## 4. 参考 JSON 示例 + +### 4.1 0x36 混合上报 (2个设备状态 + 1个故障) + +```json +{ + "ts_ms": 1706428800123, + "hotel_id": 1001, + "room_id": "8001", + "device_id": "dev_001", + "direction": "上报", + "cmd_word": "0x36", + "frame_id": 1001, + "udp_raw": "3601...", + "sys_lock_status": 1, + "report_count": 2, + "fault_count": 1, + "device_list": [ + { + "dev_type": 1, + "dev_addr": 10, + "dev_loop": 1, + "dev_data": 100 + }, + { + "dev_type": 1, + "dev_addr": 11, + "dev_loop": 2, + "dev_data": 0 + } + ], + "fault_list": [ + { + "dev_type": 1, + "dev_addr": 10, + "dev_loop": 1, + "error_type": 1, + "error_data": 1 + } + ] +} +``` + +### 4.2 0x0F 下发控制 (包含多个控制指令) + +```json +{ + "ts_ms": 1706428805000, + "hotel_id": 1001, + "room_id": "8001", + "direction": "下发", + "cmd_word": "0x0F", + "frame_id": 1002, + "udp_raw": "0F...", + "action_type": 2, + "control_list": [ + { + "dev_type": 1, + "dev_addr": 10, + "loop": 1, + "type_l": 0, + "type_h": 1 + } + ], + "details": { + "full_control_data": "..." + } +} +``` diff --git a/docs/project.md b/docs/project.md new file mode 100644 index 0000000..7fe28c7 --- /dev/null +++ b/docs/project.md @@ -0,0 +1,78 @@ +1. 0x36 上报数据格式 +------------------------------------------------------------------- +P0: 系统锁状态 + 0x00:未锁定 + 0x01:锁定 +P1~P6: 保留 +P7: 上报设备数量 +P8~P13: 设备参数,描述一个设备参数固定为6Byte,具体格式如下: + DevType:设备类型– 1Byte + DevAddr:设备地址– 1Byte + DevLoop:设备回路– 2Byte + DevData:设备状态– 2Byte +P14: 上报设备故障数量 +P15~P20: 上报设备故障数量 + DevType:设备类型– 1Byte + DevAddr:设备地址– 1Byte + DevLoop:设备回路– 2Byte + ErrorType:故障类型– 1Byte + ErrorData:故障内容– 1Byte +------------------------------------------------------------------- +上报设备的状态具体参数需要查看备数*6Byte,如果故障设备数为0,则没有设备故障参数。 +故障参数解析: +故障类型 故障内容 + 0x01 0:在线 1:离线 + 0x02 0~100电量 + 0x03 电流(10mA) + 0x04 1901故障检测次数 + 0x05 设备回路故障具体设备,不同类型的设备上报状态的描述是不同的。 +具体有多少设备状态需要上报,设备参数所占用的字节=设备数*6Byte +同样设备故障参数所占用的字节=设 +2. 0x0F 下发数据格式 +------------------------------------------------------------------- +P0:控制设备总数 +P1 ~P495:设备控制参数,描述一个设备控制参数固定为6Byte,具体格式如下 + DevType:控制设备类型 - 1Byte + DevAddr:控制设备地址 - 1Byte + Loop:控制设备的回路地址 - 2Byte + Type:控制设备的输出类型 - 2Byte + Type_L:执行方式 + Type_H:执行内容 +------------------------------------------------------------------- +该命令一般用于服务下发控制数据 +3. 0x0F 上报数据格式 +ACK (待补充) + +4. 数据表结构 + +不可为空字段: + 日志产生时间(ts_ms) + 入库时间(write_ts_ms) + 酒店(index) + 房间(index) + 方向(上传/下发)(index) + 命令字(index) + 通讯帧号 + UDP消息原文 + 记录行为类型(ACK、下发控制、主动控制、设备回路状态)(index),通过设备类型区分 +可为空字段: + 系统锁状态 + 本次上报数量 + DevType:设备类型– 1Byte + DevAddr:设备地址– 1Byte + DevLoop:设备回路– 2Byte + DevData:设备状态– 2Byte + 本次故障数量 + DevType:设备类型– 1Byte + DevAddr:设备地址– 1Byte + DevLoop:设备回路– 2Byte + ErrorType:故障类型– 1Byte + ErrorData:故障内容– 1Byte + +一条命令可能会有多条状态,每个状态生成一条记录,通过命令字和帧号来做串联。 +即:一条UDP通讯可能对照多条数据库记录 + + +5. 队列结构 + 队列分区数:6 + Topic:blwlog4Nodejs-rcu-action-topic \ No newline at end of file diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 0000000..8929246 --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,92 @@ +# BLS RCU Action Server 开发文档 + +## 1. 项目概述 +本项目旨在构建一个后端服务,负责从 Kafka 接收 RCU(客房控制单元)的通讯日志,解析数据结构,并将其持久化存储到 PostgreSQL 数据库中。 + +核心目标是将不同类型的通讯协议数据(0x36 上报、0x0F 下发、ACK)统一存储,并针对不定长数据结构采用 JSON 格式进行灵活保存。 + +## 2. 系统架构 +**数据流向**: `MCU/Server` (产生数据) -> `Kafka` (消息队列) -> `Action Server` (消费 & 解析) -> `PostgreSQL` (存储) + +- **Kafka Topic**: `blwlog4Nodejs-rcu-action-topic` (分区数: 6) +- **数据库**: PostgreSQL + +## 3. 数据库设计 + +数据库名:`bls_rcu_action` +模式名: `rcu_action` + +### 3.1 表结构设计 +表名: `rcu_action_events` + +| 字段名 | 类型 | 说明 | 备注 | +| :--- | :--- | :--- | :--- | +| **guid** | VARCHAR(32) | 主键 (Key2) | 32位无符号UUID | +| **ts_ms** | BIGINT | 日志产生时间 (Key1) | **必填** (L49) | +| **write_ts_ms** | BIGINT | 入库时间 | **必填** (L50) | +| **hotel_id** | INTEGER | 酒店ID | **必填** (L51) Index | +| **room_id** | VARCHAR(32) | 房间ID | **必填** (L52) Index | +| **device_id** | VARCHAR(32) | 设备ID | **必填** (新增) Index | +| **direction** | VARCHAR(10) | 数据方向 | **必填** (L53) "上报"/"下发" Index | +| **cmd_word** | VARCHAR(10) | 命令字 | **必填** (L54) 如 "0x36", "0x0F" Index | +| **frame_id** | INTEGER | 通讯帧号 | **必填** (L55) 用于串联命令与状态 | +| **udp_raw** | TEXT | UDP消息原文 | **必填** (L56) Hex字符串 | +| **action_type** | VARCHAR(20) | 记录行为类型 | **必填** (L57) Index | +| **sys_lock_status** | SMALLINT | 系统锁状态 | (L59) 可空 | +| **report_count** | SMALLINT | 本次上报数量 | (L60) 可空 | +| **dev_type** | SMALLINT | 设备类型 | (L61) 可空 (统一字段) | +| **dev_addr** | SMALLINT | 设备地址 | (L62) 可空 (统一字段) | +| **dev_loop** | INTEGER | 设备回路 | (L63) 可空 (统一字段) | +| **dev_data** | INTEGER | 设备状态 | (L64) 可空 (0x36状态) | +| **fault_count** | SMALLINT | 本次故障数量 | (L65) 可空 | +| **error_type** | SMALLINT | 故障类型 | (L69) 可空 (0x36故障) | +| **error_data** | SMALLINT | 故障内容 | (L70) 可空 (0x36故障) | +| **type_l** | SMALLINT | 执行方式 | 可空 (0x0F下发) | +| **type_h** | SMALLINT | 执行内容 | 可空 (0x0F下发) | +| **details** | JSONB | 业务详情数据 | 存储不定长设备列表、故障信息等 | +| **extra** | JSONB | 扩展信息 | 存储通讯原文等扩展数据 | + +**主键定义**: `(ts_ms, guid)` +**索引定义**: 备注带index的字段为需要索引的字段,用于提高查询效率。 + +### 3.2 字典定义 +**Action Type (记录行为类型)**: +- `"0FACK"`: ACK (应答) +- `"0F下发"`: 下发控制 (0x0F 下发) +- `"36上报"`: 设备回路状态 (0x36 上报) + +**Direction (方向)**: +- `"上报"`: Upload +- `"下发"`: Download + +## 4. 数据解析与存储映射 + +### 4.1 0x36 上报数据 (设备状态/故障) +* **命令字**: "0x36" +* **拆分逻辑**: 根据 `project.md` 说明,一条 UDP 可能包含多个设备状态,需拆分为多条记录入库,每条记录填充 `dev_type` 等字段。同时将完整的不定长列表存入 `details` 以便追溯。 +* **Action Type**: 4 (设备回路状态) + +**Mapping**: +- `sys_lock_status` -> P0 +- `report_count` -> P7 +- `dev_type`, `dev_addr`... -> 从 P8~P13 循环解析,每组生成一条 DB 记录 +- `details`: `{ "all_devices": [...], "all_faults": [...] }` +- `extra`: `{ "raw_hex": "..." }` + +### 4.2 0x0F 下发数据 (控制指令) +* **命令字**: "0x0F" +* **Action Type**: 2 (下发控制) +* **存储逻辑**: 主要是控制指令,通常作为单条记录存储。若包含多个设备控制,可选择存第一条到字段,或仅存入 JSON。根据 "0x0F不定长存为JSON" 的需求,主要依赖 `details` 字段。 + +**Mapping**: +- `details`: `{ "control_params": [ ... ] }` +- `extra`: `{ "raw_hex": "..." }` + +### 4.3 0x0F 上报数据 (ACK) +* **命令字**: "0x0F" +* **Action Type**: "0FACK" (ACK) + +**Mapping**: +- `details`: `{ "ack_code": "0x00" }` +- `extra`: `{ "raw_hex": "..." }` + diff --git a/docs/redis-integration-protocol.md b/docs/redis-integration-protocol.md new file mode 100644 index 0000000..cd47f84 --- /dev/null +++ b/docs/redis-integration-protocol.md @@ -0,0 +1,273 @@ +# Redis 对接协议(供外部项目 AI 生成代码使用) + +本文档定义"外部项目 ↔ BLS Project Console"之间通过 Redis 交互的 **Key 命名、数据类型、写入方式、读取方式与数据格式**。 + +注:本仓库对外暴露的 Redis 连接信息如下(供对方直接连接以写入心跳/日志): + +- 地址:`10.8.8.109` +- 端口:默认 `6379` +- 密码:无(空) +- 数据库:固定 `15` + +示例(环境变量): + +``` +REDIS_HOST=10.8.8.109 +REDIS_PORT=6379 +REDIS_PASSWORD= +REDIS_DB=15 +``` + +示例(redis-cli): + +``` +redis-cli -h 10.8.8.109 -p 6379 -n 15 +``` + +> 约束:每个需要关联本控制台的外部项目,必须在同一个 Redis(DB15)中: + +> - 更新 `项目心跳`(项目列表 + 心跳信息) +> - 追加 `${projectName}_项目控制台`(日志队列) +> - 命令下发为 HTTP API 调用(不通过 Redis 下发命令) + +## 1. 命名约定 + +令: + +- `projectName`:外部项目名称(建议只用字母数字下划线 `A-Za-z0-9_`;如使用中文也可,但需保证统一且 UTF-8)。 + +固定后缀: + +- 控制台:`${projectName}_项目控制台` + +示例(projectName = `订单系统`): + +- `订单系统_项目控制台` + +## 2. 外部项目需要写入的 2 个 Key + +说明:当前控制台左侧“项目选择列表”只读取 `项目心跳`(LIST)。因此外部项目必须维护该 Key,否则项目不会出现在列表中。 + +### 2.1 `项目心跳` + +- Redis 数据类型:**LIST** +- 写入方式(推荐 FIFO):`RPUSH 项目心跳 ` +- value:每个列表元素为“项目心跳记录”的 JSON 字符串 + +示例(与当前代码读取一致;下面示例表示“逻辑结构”): + +```json +[ + { + "projectName": "BLS主机心跳日志", + "apiBaseUrl": "http://127.0.0.1:3000", + "lastActiveAt": 1768566165572 + } +] +``` + +示例(Redis 写入命令): + +``` +RPUSH 项目心跳 "{\"projectName\":\"BLS主机心跳日志\",\"apiBaseUrl\":\"http://127.0.0.1:3000\",\"lastActiveAt\":1768566165572}" +``` + +字段说明(每条心跳记录): + +- `projectName`:项目名称(用于拼接日志 Key:`${projectName}_项目控制台`) +- `apiBaseUrl`:目标项目对外提供的 API 地址(基地址,后端将基于它拼接 `apiName`) +- `lastActiveAt`:活跃时间戳(毫秒)。建议每 **3 秒**刷新一次。 + +在线/离线判定(BLS Project Console 使用): + +- 若 `now - lastActiveAt > 10_000ms`,则认为该应用 **离线** +- 否则认为 **在线** + +建议: + +- `lastActiveAt` 使用 `Date.now()` 生成(毫秒) +- 建议对 `项目心跳` 做长度控制(可选):例如每次写入后执行 `LTRIM 项目心跳 -2000 -1` 保留最近 2000 条 + +去重提示: + +- `项目心跳` 为 LIST 时,外部项目周期性 `RPUSH` 会产生多条重复记录 +- BLS Project Console 后端会按 `projectName` 去重,保留 `lastActiveAt` 最新的一条作为项目状态 + +### 2.2 `${projectName}_项目控制台` + +- Redis 数据类型:**LIST**(作为项目向控制台追加的"消息队列/日志队列") +- 写入方式(推荐 FIFO):`RPUSH ${projectName}_项目控制台 ` + +value(推荐格式):一条 JSON 字符串,表示"错误/调试信息"或日志记录。 + +推荐 JSON Schema(字段尽量保持稳定,便于控制台解析): + +```json +{ + "timestamp": "2026-01-12T12:34:56.789Z", + "level": "info", + "message": "连接成功", + "metadata": { + "module": "redis", + "host": "127.0.0.1" + } +} +``` + +字段说明: + +- `timestamp`:ISO-8601 时间字符串 +- `level`:建议取值 `info|warn|error|debug`(小写) +- `message`:日志文本 +- `metadata`:可选对象(附加信息) + +## 3. 项目列表管理(重要) + +### 3.1 迁移机制(仅用于旧数据导入) + +BLS Project Console 支持从旧格式自动迁移到新格式: + +- **旧格式**:每个项目独立的心跳键 `${projectName}_项目心跳` +- **新格式**:统一的项目列表键 `项目心跳`(LIST 类型,每个元素为 JSON 字符串) + +迁移过程: + +1. 扫描所有 `${projectName}_项目心跳` 键 +2. 提取 `apiBaseUrl` 和 `lastActiveAt` 字段 +3. 写入到 `项目心跳`(LIST) +4. 可选:删除旧键 + +重要说明(与当前代码实现一致): + +- 迁移不会自动后台执行,需要通过接口触发:`POST /api/projects/migrate` +- 迁移的目的只是“从历史 `${projectName}_项目心跳` 导入一次,生成 `项目心跳` 列表” +- 迁移完成后,如果外部项目仍然只更新旧 Key,则 `项目心跳` 不会自动跟随更新;要想实时更新,外部项目必须直接更新 `项目心跳` + +### 3.2 新格式项目列表结构 + +`项目心跳` 为 LIST,列表元素为 JSON 字符串;其“逻辑结构”如下: + +```json +[ + { + "projectName": "订单系统", + "apiBaseUrl": "http://127.0.0.1:4001", + "lastActiveAt": 1760000000000 + }, + { + "projectName": "用户服务", + "apiBaseUrl": "http://127.0.0.1:4002", + "lastActiveAt": 1760000000001 + } +] +``` + +### 3.3 外部项目对接建议 + +外部项目应当: + +1. 定期写入 `项目心跳`(RPUSH 自己的心跳记录;允许产生多条记录,由控制台按 projectName 去重) +2. 追加 `${projectName}_项目控制台` 日志 + +## 4. 命令下发方式(HTTP API 控制) + +控制台不再通过 Redis 写入控制指令队列;改为由 BLS Project Console 后端根据目标项目心跳里的 `apiBaseUrl` 直接调用目标项目 HTTP API。 + +### 4.1 控制台输入格式 + +一行文本按空格拆分: + +- 第一个 token:`apiName`(接口名/路径片段) +- 剩余 token:参数列表(字符串数组) + +示例: + +- `reload` +- `reload force` +- `user/refreshCache tenantA` + +### 4.2 目标项目需要提供的 API + +后端默认使用 `POST` 调用: + +- `POST {apiBaseUrl}/{apiName}` + +请求体(JSON)示例: + +```json +{ + "commandId": "cmd-1700000000000-abc123", + "timestamp": "2026-01-13T00:00:00.000Z", + "source": "BLS Project Console", + "apiName": "reload", + "args": ["force"], + "argsText": "force" +} +``` + +字段说明: + +- `commandId`:唯一命令标识符 +- `timestamp`:命令发送时间(ISO-8601 格式) +- `source`:命令来源标识 +- `apiName`:API 接口名 +- `args`:参数数组 +- `argsText`:参数文本(空格连接) + +返回建议: + +- 2xx 表示成功 +- 非 2xx 表示失败(控制台会展示 upstreamStatus 与部分返回内容) + +### 4.3 在线/离线判定 + +发送命令前,系统会检查项目在线状态: + +- 从 `项目心跳` 列表读取 `lastActiveAt` +- 若 `now - lastActiveAt > 10_000ms`,则认为该应用 **离线**,拒绝发送命令 +- 否则认为 **在线**,允许发送命令 + +## 5. 与本项目代码的对应关系 + +- **后端 `/api/projects`**:只从 `项目心跳`(LIST)读取项目列表,返回所有项目及其在线状态 +- **后端 `/api/commands`**:从 `项目心跳` 中查找目标项目的 `apiBaseUrl/lastActiveAt`,在线时调用目标项目 API +- **后端 `/api/logs`**:读取 `${projectName}_项目控制台`(LIST);并基于 `项目心跳` 中该项目的 `lastActiveAt` 计算在线/离线与 API 地址信息 + +## 6. 兼容与错误处理建议 + +- JSON 解析失败:外部项目应记录错误,并丢弃该条消息(避免死循环阻塞消费)。 +- 消息过长:建议控制单条消息大小(例如 < 64KB)。 +- 字符编码:统一 UTF-8。 +- 心跳超时:建议外部项目每 3 秒更新一次心跳,避免被误判为离线。 + +## 7. 数据迁移工具(旧数据导入) + +如果需要从旧格式迁移到新格式,可使用以下 API: + +```bash +POST /api/projects/migrate +Content-Type: application/json + +{ + "deleteOldKeys": false, + "dryRun": false +} +``` + +参数说明: + +- `deleteOldKeys`:是否删除旧格式键(默认 false) +- `dryRun`:是否仅模拟运行(默认 false) + +返回示例: + +```json +{ + "success": true, + "message": "数据迁移完成", + "migrated": 2, + "projects": [...], + "listKey": "项目心跳", + "deleteOldKeys": false +} +``` diff --git a/docs/测试报告.md b/docs/测试报告.md new file mode 100644 index 0000000..32cd6e5 --- /dev/null +++ b/docs/测试报告.md @@ -0,0 +1,45 @@ +# 测试报告 + +## 基本信息 +- 运行时间: 2026-01-29 +- 运行方式: 控制台启动 `npm run dev`,运行约 60 秒后 Ctrl + C 终止 +- 测试目标: 验证 Kafka 消费与入库链路,定位无入库原因 + +## 控制台关键日志 +``` +{"level":"error","message":"Message processing failed","timestamp":1769734880590,"context":{"error":"[\n {\n \"expected\": \"number\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"hotel_id\"\n ],\n \"message\": \"Invalid input: expected number, received string\"\n }\n]","type":"PARSE_ERROR","stack":"ZodError: ...","rawPayload":"{\"ts_ms\":1769692878011,\"hotel_id\":\"2147\",\"room_id\":\"8209\",\"device_id\":\"099008129081\",\"direction\":\"上报\",\"cmd_word\":\"36\",\"frame_id\":52496,...}","validationIssues":[{"expected":"number","code":"invalid_type","path":["hotel_id"],"message":"Invalid input: expected number, received string"}]}} +``` + +## 结论 +- 数据未入库的直接原因: Kafka 消息在解析阶段触发 Zod 校验失败,`hotel_id` 为字符串类型而非文档要求的 Number,导致 `PARSE_ERROR`,数据库插入流程未执行。 + +## 与文档格式的一致性检查 +对照 [kafka_format.md](file:///e:/Project_Class/BLS/Web_BLS_RCUAction_Server/docs/kafka_format.md): +- `hotel_id`: 文档要求 Number,但实测为字符串 (示例: `"2147"`),不一致。 +- `cmd_word`: 文档要求 `"0x36"`/`"0x0F"`,实测为 `"36"`,不一致。 +- `control_list`: 文档要求 Array/可选,但实测为 `null`,不一致。 +- 其余关键字段如 `ts_ms`, `room_id`, `device_id`, `direction`, `udp_raw` 均存在。 + +## 已增强的控制台错误输出 +为了便于定位异常,以下模块已经增加详细错误输出到 PowerShell 控制台: +- Kafka 处理异常: 输出 `type`, `stack`, `rawPayload`, `validationIssues`, `dbContext` +- 数据库插入异常: 输出 `schema`, `table`, `rowsLength` +- Redis 入队与重试异常: 输出详细错误信息 + +相关改动文件: +- [index.js](file:///e:/Project_Class/BLS/Web_BLS_RCUAction_Server/bls-rcu-action-backend/src/index.js) +- [databaseManager.js](file:///e:/Project_Class/BLS/Web_BLS_RCUAction_Server/bls-rcu-action-backend/src/db/databaseManager.js) +- [errorQueue.js](file:///e:/Project_Class/BLS/Web_BLS_RCUAction_Server/bls-rcu-action-backend/src/redis/errorQueue.js) + +## 建议修改方向 +以下为解决无入库问题的可选方案,由你决定是否执行: +1. 上游严格按文档输出: + - `hotel_id` 改为 Number + - `cmd_word` 改为 `"0x36"` / `"0x0F"` + - `control_list` 用 `[]` 或省略字段,避免 `null` +2. 下游放宽校验并做类型转换: + - 将 `hotel_id` 支持字符串并转换为 Number + - 继续兼容 `cmd_word = "36"` 的写法 + - `control_list/device_list/fault_list` 接受 `null` 并转为空数组 + +当前代码已兼容 `cmd_word="36"` 和 `control_list=null`,但 `hotel_id` 仍按文档严格要求 Number。 diff --git a/openspec/changes/archive/2026-01-28-initial-setup/add_validation_and_tests.md b/openspec/changes/archive/2026-01-28-initial-setup/add_validation_and_tests.md new file mode 100644 index 0000000..f965a5d --- /dev/null +++ b/openspec/changes/archive/2026-01-28-initial-setup/add_validation_and_tests.md @@ -0,0 +1,32 @@ +# Add Validation and Unit Tests + +## Summary +Introduced robust data validation using `zod` and unit testing using `vitest` to ensure system stability and correctness of data processing logic. + +## Changes + +### 1. Data Validation +- **Library**: `zod` +- **File**: `src/schema/kafkaPayload.js` +- **Logic**: + - Defined strict schema for Kafka payload including required headers (`ts_ms`, `hotel_id`, etc.) and optional arrays (`device_list`, `fault_list`). + - Implemented automatic type transformation (e.g., `room_id` to string, `cmd_word` normalization). + - Integrated into `src/processor/index.js` to validate incoming messages before processing. + +### 2. Unit Testing +- **Framework**: `vitest` +- **File**: `tests/processor.test.js` +- **Coverage**: + - Required field validation (throws error on missing fields). + - `0x36` Status Report processing (device list expansion). + - `0x36` Fault Report processing (fault list expansion). + - `0x36` Mixed Report processing. + - `0x0F` Control Command processing (control list expansion). + - `0x0F` ACK processing. + - Fallback logic for empty lists. + +### 3. Scripts +- Added `test` script to `package.json`: `vitest run`. + +## Verification +- Ran `npm test` and all 7 tests passed successfully. diff --git a/openspec/changes/archive/2026-01-28-initial-setup/correction_kafka_json.md b/openspec/changes/archive/2026-01-28-initial-setup/correction_kafka_json.md new file mode 100644 index 0000000..9a5ae8c --- /dev/null +++ b/openspec/changes/archive/2026-01-28-initial-setup/correction_kafka_json.md @@ -0,0 +1,9 @@ +# Operations Log + +## [2026-01-28] Correction: Kafka JSON Structure +- **Action**: Corrected `docs/kafka_format.md` based on user feedback. +- **Details**: + - **Shifted Parsing Responsibility**: Clarified that the upstream service performs the raw parsing. + - **Structured Arrays**: Introduced `device_list`, `fault_list`, and `control_list` arrays in the JSON schema. + - **Flattened Logic**: Backend no longer parses `udp_raw` for list items but iterates over the provided JSON arrays. + - **Updated Examples**: Provided clear examples of nested JSON objects for devices and faults. diff --git a/openspec/changes/archive/2026-01-28-initial-setup/doc_update_kafka.md b/openspec/changes/archive/2026-01-28-initial-setup/doc_update_kafka.md new file mode 100644 index 0000000..c8edb44 --- /dev/null +++ b/openspec/changes/archive/2026-01-28-initial-setup/doc_update_kafka.md @@ -0,0 +1,9 @@ +# Operations Log + +## [2026-01-28] Documentation Update: Kafka Format & Splitting Logic +- **Action**: Updated `docs/kafka_format.md`. +- **Details**: + - Defined strict splitting logic for `0x36` commands: One Kafka message -> Multiple DB records (based on `report_count` and `fault_count`). + - Updated Kafka JSON Schema to include all database fields (Header + Logic/Parsing fields). + - Clarified `action_type` mapping and `sys_lock_status` propagation. + - Added table-based parsing example for visual clarity. diff --git a/openspec/changes/archive/2026-01-28-initial-setup/implement_partitioning.md b/openspec/changes/archive/2026-01-28-initial-setup/implement_partitioning.md new file mode 100644 index 0000000..0fa4c07 --- /dev/null +++ b/openspec/changes/archive/2026-01-28-initial-setup/implement_partitioning.md @@ -0,0 +1,33 @@ +# Database Partitioning and Initialization Strategy + +## Summary +Implemented automatic database initialization and time-based table partitioning to ensure system scalability and ease of deployment. + +## Changes + +### 1. Database Initialization (`src/db/initializer.js`) +- **Logic**: + 1. **Database Check**: Connects to the default `postgres` database to check if the target database (defined in `.env`) exists. Creates it if missing. + 2. **Schema & Table Check**: Connects to the target database and executes `scripts/init_db.sql`. + 3. **Partition Check**: Calls `PartitionManager` to ensure partition tables exist for the next 30 days. +- **Integration**: Called during application bootstrap in `src/index.js`. + +### 2. Table Partitioning (`src/db/partitionManager.js`) +- **Strategy**: Partition by Range on `ts_ms` (milliseconds). +- **Partition Size**: Daily partitions (e.g., `rcu_action.rcu_action_events_20260129`). +- **Automation**: + - `ensurePartitions(daysAhead)`: Calculates daily ranges and creates partitions if they don't exist. + - **Scheduled Task**: `node-cron` job runs daily at 00:00 to pre-create partitions for the next 30 days. + +### 3. Schema Updates (`scripts/init_db.sql`) +- Modified `rcu_action_events` table definition to use `PARTITION BY RANGE (ts_ms)`. +- **Note**: The primary key `(ts_ms, guid)` supports this partitioning strategy perfectly. + +### 4. Code Structure Updates +- **Dependency**: Added `node-cron`. +- **Singleton**: Updated `src/db/databaseManager.js` to export a singleton instance for shared connection pooling. +- **Bootstrap**: Updated `src/index.js` to include initialization and cron scheduling. + +## Verification +- **Startup**: Application will auto-initialize DB and partitions on first run. +- **Maintenance**: Cron job ensures future partitions are always ready. diff --git a/openspec/changes/archive/2026-01-28-initial-setup/improve_robustness.md b/openspec/changes/archive/2026-01-28-initial-setup/improve_robustness.md new file mode 100644 index 0000000..c513e7d --- /dev/null +++ b/openspec/changes/archive/2026-01-28-initial-setup/improve_robustness.md @@ -0,0 +1,19 @@ +# System Robustness Improvements + +## Context +A review of the project revealed missing initialization scripts and lack of graceful shutdown handling, which are critical for production stability and deployment. + +## Changes +1. **Database Initialization (`scripts/init_db.sql`)**: + * Created a SQL script to initialize the `rcu_action` schema and `rcu_action_events` table. + * Added indexes for performance optimization (`ts_ms`, `hotel_id`, `room_id`, `direction`, `cmd_word`, `action_type`). + * Added a composite index (`hotel_id`, `room_id`, `ts_ms DESC`) for common query patterns. + +2. **Graceful Shutdown (`src/index.js`)**: + * Implemented `SIGTERM` and `SIGINT` signal handlers. + * Ensures resources are closed in order: Kafka Consumer -> Redis Client -> Database Pool. + * Prevents data corruption or zombie connections during container restart/stop. + +3. **Error Handling Enhancements**: + * Decoupled `startErrorRetryWorker` from the main bootstrap chain to prevent blocking/crashing on startup. + * Added `try/catch` block in `handleMessage` to ensure errors are logged and bubbled up to the retry mechanism properly. diff --git a/openspec/changes/archive/2026-01-28-initial-setup/kafka_db_strategy.md b/openspec/changes/archive/2026-01-28-initial-setup/kafka_db_strategy.md new file mode 100644 index 0000000..659d690 --- /dev/null +++ b/openspec/changes/archive/2026-01-28-initial-setup/kafka_db_strategy.md @@ -0,0 +1,27 @@ +# Kafka Partition and Database Connection Strategy + +## Context +User highlighted two specific constraints for the production environment: +1. The Kafka topic `blwlog4Nodejs-rcu-action-topic` has 6 partitions, but the application runs as a single PM2 instance. +2. Database connections must be minimized, using a single connection pool. + +## Analysis & Implementation + +### 1. Kafka Partition Handling +- **Constraint**: Single instance must consume from all 6 partitions. +- **Solution**: We are using `kafka-node`'s `ConsumerGroup`. +- **Mechanism**: + - `ConsumerGroup` automatically manages partition assignment. + - When a single consumer joins the group, the group coordinator assigns all available partitions (0-5) to that single member. + - Protocol is set to `['roundrobin']`, ensuring even distribution if scaling up later, but effective for full coverage in single-mode. +- **Verification**: Checked `src/kafka/consumer.js`. No code changes needed. + +### 2. Database Connection Pool +- **Constraint**: Single pool, limited connections. +- **Solution**: + - **Singleton Pattern**: `src/db/databaseManager.js` exports a pre-instantiated `dbManager` object. All modules import this single instance. + - **Connection Limit**: `src/config/config.js` sets `max` connections to `process.env.DB_MAX_CONNECTIONS` with a default of **10**. +- **Verification**: Checked `src/db/databaseManager.js` and `src/config/config.js`. Implementation complies with constraints. + +## Conclusion +The current architecture inherently satisfies these requirements without further modification. diff --git a/openspec/changes/archive/2026-01-28-initial-setup/refactor_processor.md b/openspec/changes/archive/2026-01-28-initial-setup/refactor_processor.md new file mode 100644 index 0000000..3d7bc40 --- /dev/null +++ b/openspec/changes/archive/2026-01-28-initial-setup/refactor_processor.md @@ -0,0 +1,23 @@ +# Processor Refactoring for Unified Kafka Topic + +## Context +The system receives all messages (Status, Fault, Control, ACK) from a single Kafka topic. The message key is unreliable (random). Therefore, the backend must rely on `cmd_word` and `direction` within the message payload to determine the processing logic and storage structure. + +## Changes +1. **Refactored `src/processor/index.js`**: + * Removed `udpParser.js` dependency (parsing is now upstream responsibility). + * Implemented `resolveActionType` to categorize messages into: + * `"36上报"` (Status/Fault) + * `"0F下发"` (Control) + * `"0FACK"` (ACK) + * Implemented `buildRowsFromPayload` with specific logic for each action type: + * **36上报**: Iterates over `device_list` and `fault_list` (provided by upstream JSON) to create multiple DB rows. Maps `dev_type`, `dev_addr`, `dev_loop`, `dev_data`, `error_type`, `error_data`. + * **0F下发**: Iterates over `control_list`. Maps `dev_type`, `dev_addr`, `loop`->`dev_loop`, `type_l`, `type_h`. + * **0FACK**: Creates a single DB row (fallback). + * Ensured `details` column stores the full lists (`device_list`, `fault_list`, `control_list`) for traceability. + * Ensured `extra` column stores the `raw_hex` (udp_raw). + +## Impact +* Backend is now fully compliant with the "Upstream Parsing" requirement. +* Correctly handles one-to-many storage for Status, Fault, and Control messages. +* No longer relies on Kafka Keys for dispatching. diff --git a/openspec/changes/archive/2026-01-28-initial-setup/summary.md b/openspec/changes/archive/2026-01-28-initial-setup/summary.md new file mode 100644 index 0000000..aecfd71 --- /dev/null +++ b/openspec/changes/archive/2026-01-28-initial-setup/summary.md @@ -0,0 +1,33 @@ +# Initial Setup & Backend Implementation + +## Operations Log + +### Configuration & Code Update +- **Action**: Updated codebase to support advanced environment configurations. +- **Details**: + - Updated `src/config/config.js` to read SASL and SSL configurations from `.env`. + - Updated `src/kafka/consumer.js` to support SASL authentication. + - Updated `src/db/databaseManager.js` to support PostgreSQL SSL connections. + - Verified `.env` integration with `dotenv`. + +### Backend Component Construction +- **Action**: Initialized backend scaffolding and core implementation. +- **Details**: + - Created project structure under `bls-rcu-action-backend`. + - Implemented `DatabaseManager` with connection pooling. + - Implemented `KafkaConsumer` with error handling. + - Implemented `RedisIntegration` for heartbeats and error logging. + - Added Docker and PM2 configuration files. + +### Documentation Updates +- **Action**: Refined data schema and documentation. +- **Details**: + - **Schema Changes**: + - `room_id`: Changed to `VARCHAR(32)` (String). + - `direction`: Changed to `VARCHAR(10)` (String: "上传"/"下发"). + - `cmd_word`: Changed to `VARCHAR(10)` (String: "0x36"). + - Added `extra` field (JSONB) for raw communication data. + - Defined Primary Key: 32-bit Unsigned UUID (`guid`). + - Defined Composite Key logic: `ts_ms` (Key1) + `guid` (Key2). + - **Indices**: Marked `hotel_id`, `room_id`, `direction`, `cmd_word` for indexing. + - **Kafka Format**: Created `docs/kafka_format.md` with updated JSON reference. diff --git a/openspec/changes/archive/2026-01-28-initial-setup/unify_schema.md b/openspec/changes/archive/2026-01-28-initial-setup/unify_schema.md new file mode 100644 index 0000000..dcbd48a --- /dev/null +++ b/openspec/changes/archive/2026-01-28-initial-setup/unify_schema.md @@ -0,0 +1,19 @@ +# Database Schema Unification + +## Context +Refined the database schema and Kafka JSON format to eliminate redundant fields and better support the `0x36` (Report) and `0x0F` (Control) commands. + +## Changes +1. **Unified Device Identification**: + * Removed `fault_dev_type`, `fault_dev_addr`, `fault_dev_loop`. + * Consolidated all device identification into `dev_type`, `dev_addr`, and `dev_loop`. These fields are now shared across status reports, fault reports, and control commands. + +2. **Field Additions for Control Commands**: + * Added `type_l` (SMALLINT) and `type_h` (SMALLINT) to support `0x0F` control command parameters. + +3. **Document Updates**: + * Updated `docs/readme.md`: Reflected the schema changes in the table definition. + * Updated `docs/kafka_format.md`: Updated the JSON schema and mapping rules to align with the unified database fields. + +4. **Code Updates**: + * Updated `bls-rcu-action-backend/src/db/databaseManager.js`: Modified the `columns` array to match the new schema. diff --git a/openspec/changes/archive/2026-01-28-initial-setup/update_action_type.md b/openspec/changes/archive/2026-01-28-initial-setup/update_action_type.md new file mode 100644 index 0000000..65e5208 --- /dev/null +++ b/openspec/changes/archive/2026-01-28-initial-setup/update_action_type.md @@ -0,0 +1,22 @@ +# Action Type Field Update + +## Context +The `action_type` field was previously defined as a `SMALLINT` with integer mappings (1, 2, 3, 4). The requirement has changed to store explicit string values for better readability and direct mapping. + +## Changes +1. **Field Definition**: + * Changed `action_type` from `SMALLINT` to `VARCHAR(20)`. + +2. **Enum Values**: + * Old: `1` (ACK), `2` (Control), `4` (Status) + * New: + * `"0FACK"`: ACK (0x0F 上报) + * `"0F下发"`: Control (0x0F 下发) + * `"36上报"`: Status (0x36) + +3. **Document Updates**: + * `docs/readme.md`: Updated table definition and dictionary. + * `docs/kafka_format.md`: Updated JSON schema and backend logic examples. + +4. **Code Updates**: + * `src/processor/index.js`: Updated `resolveActionType` to return the new string enums. diff --git a/openspec/changes/archive/2026-01-28-initial-setup/update_pk_composite.md b/openspec/changes/archive/2026-01-28-initial-setup/update_pk_composite.md new file mode 100644 index 0000000..86c6d7f --- /dev/null +++ b/openspec/changes/archive/2026-01-28-initial-setup/update_pk_composite.md @@ -0,0 +1,19 @@ +# Update Database Primary Key Definition + +## Summary +Updated the database schema initialization script to define a composite primary key `(ts_ms, guid)` instead of a single primary key on `guid`, aligning with the project documentation requirements. + +## Changes + +### 1. Database Schema (`scripts/init_db.sql`) +- **Primary Key**: Changed from `guid` to composite `(ts_ms, guid)`. + - **Reasoning**: `ts_ms` is the partition key or main ordering field (Key1), and `guid` provides uniqueness (Key2). This structure optimizes time-series based queries and data distribution. +- **Indexes**: Removed `idx_rcu_action_ts_ms` as it is now redundant (covered by the primary key index). + +## Impact +- **Table Structure**: `rcu_action.rcu_action_events` now enforces uniqueness on the combination of timestamp and GUID. +- **Performance**: Queries filtering by `ts_ms` will utilize the primary key index. + +## Verification +- Reviewed SQL syntax in `scripts/init_db.sql`. +- Confirmed alignment with `docs/readme.md` requirements. diff --git a/openspec/changes/archive/2026-01-29-add-device-id/summary.md b/openspec/changes/archive/2026-01-29-add-device-id/summary.md new file mode 100644 index 0000000..c4c15f3 --- /dev/null +++ b/openspec/changes/archive/2026-01-29-add-device-id/summary.md @@ -0,0 +1,22 @@ +# Add device_id Field + +## Context +User requested to add a `device_id` field (string) to the Kafka payload and database table. + +## Changes +1. **Documentation**: + - Updated `docs/readme.md` to include `device_id` in the table structure. + - Updated `docs/kafka_format.md` to include `device_id` in the JSON schema and examples. + +2. **Database**: + - Updated `bls-rcu-action-backend/scripts/init_db.sql` to add `device_id` column and a corresponding index. + +3. **Backend Code**: + - `src/schema/kafkaPayload.js`: Added `device_id` to the Zod validation schema (string, required). + - `src/db/databaseManager.js`: Added `device_id` to the list of columns for insertion. + - `src/processor/index.js`: Updated logic to extract `device_id` from the payload and pass it to the row object. + - `tests/processor.test.js`: Updated test cases to include `device_id` in the mock payload. + +## Verification +- Unit tests updated and should pass. +- Schema changes aligned across documentation and code. diff --git a/openspec/changes/archive/2026-01-29-remove-action-type-from-payload/summary.md b/openspec/changes/archive/2026-01-29-remove-action-type-from-payload/summary.md new file mode 100644 index 0000000..9bc274d --- /dev/null +++ b/openspec/changes/archive/2026-01-29-remove-action-type-from-payload/summary.md @@ -0,0 +1,18 @@ +# Remove action_type from Kafka Payload + +## Context +User requested to ensure `action_type` is determined solely by the backend logic and is NOT accepted from the Kafka payload. + +## Changes +1. **Documentation**: + - Updated `docs/kafka_format.md` to remove `action_type` from the input schema and examples. +2. **Schema Validation**: + - Updated `src/schema/kafkaPayload.js` to remove `action_type` from the Zod schema. +3. **Processor Logic**: + - Confirmed `src/processor/index.js` already independently calculates `action_type` using `resolveActionType` (based on `direction` and `cmd_word`). + - The code does not use any `action_type` from the payload, ensuring strict adherence to the new rule. + +## Verification +- Unit tests (`tests/processor.test.js`) pass successfully. +- The system now ignores any `action_type` field if it were to be sent (schema validation would strip it if `strict` mode was on, but default Zod `parse` strips unknown keys unless `passthrough` is used. Wait, Zod default behavior strips unknown keys). +- Actually, Zod by default *strips* unknown keys. So if `action_type` is sent but not in schema, it will be removed. Perfect. diff --git a/openspec/config.yaml b/openspec/config.yaml new file mode 100644 index 0000000..392946c --- /dev/null +++ b/openspec/config.yaml @@ -0,0 +1,20 @@ +schema: spec-driven + +# Project context (optional) +# This is shown to AI when creating artifacts. +# Add your tech stack, conventions, style guides, domain knowledge, etc. +# Example: +# context: | +# Tech stack: TypeScript, React, Node.js +# We use conventional commits +# Domain: e-commerce platform + +# Per-artifact rules (optional) +# Add custom rules for specific artifacts. +# Example: +# rules: +# proposal: +# - Keep proposals under 500 words +# - Always include a "Non-goals" section +# tasks: +# - Break tasks into chunks of max 2 hours diff --git a/openspec/project.md b/openspec/project.md new file mode 100644 index 0000000..210a58c --- /dev/null +++ b/openspec/project.md @@ -0,0 +1,23 @@ +# BLS RCU Action Server + +## Overview +Backend service for processing RCU action events from Kafka, parsing them, and storing them in PostgreSQL. Includes error handling via Redis and heartbeat monitoring. + +## Architecture +- **Input**: Kafka Topic (`blwlog4Nodejs-rcu-action-topic` or configured via env) +- **Processing**: Node.js Service + - **Consumer**: `kafka-node` consumer group + - **Parser**: Parses JSON messages, handles UDP raw data decoding + - **Database**: PostgreSQL (Batch insert) + - **Error Handling**: Redis List (`error_queue`) for failed messages + Retry mechanism +- **Output**: PostgreSQL Table (`rcu_action_events`) + +## Configuration (Environment Variables) +The project is configured via `.env`. Key variables: +- **Kafka**: `KAFKA_BROKERS`, `KAFKA_TOPIC`, `KAFKA_SASL_USERNAME`, `KAFKA_SASL_PASSWORD` +- **Database**: `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`, `DB_DATABASE`, `DB_SSL` +- **Redis**: `REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD` + +## Development Constraints +- **Schema**: Must strictly follow `docs/readme.md`. +- **Database**: Do not alter Schema Name (`rcu_action`) or Table Name (`rcu_action_events`) unless explicitly requested.