diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f572743 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,113 @@ +# Dependencies +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Production build +dist + +# Environment files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE files +.vscode +.idea +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Logs +logs +*.log + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Dependency directories +node_modules/ +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Generated files +generated/ + +# Git +.git +.gitignore + +# Docker +Dockerfile +.dockerignore +docker-compose*.yml + +# Documentation +*.md +!README.md + +# Test files (keep only the Excel files) +test/** +!test/*.xlsx + +# Uploads (will be created at runtime) +uploads/ + +# Temporary files +.temp +.tmp diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..253338c --- /dev/null +++ b/.env.example @@ -0,0 +1,179 @@ +# ================================= +# APPLICATION CONFIGURATION +# ================================= + +# Environment mode: development, staging, production +NODE_ENV=development + +# Application port +PORT=3000 + +# Application host (for binding) +HOST=localhost + +# Application base URL (for CORS and other purposes) +APP_URL=http://localhost:3000 + +# ================================= +# DATABASE CONFIGURATION +# ================================= + +# PostgreSQL Database URL +# Format: postgresql://username:password@host:port/database_name +DATABASE_URL=postgresql://username:password@localhost:5432/claim_guard + +# Database connection pool settings +DB_POOL_MIN=2 +DB_POOL_MAX=10 + +# ================================= +# CORS CONFIGURATION +# ================================= + +# Allowed origins for CORS (comma-separated) +# Use * for allow all origins (NOT recommended for production) +# Examples: +# - Development: http://localhost:3000,http://localhost:3001 +# - Production: https://yourdomain.com,https://www.yourdomain.com +CORS_ORIGINS=http://localhost:3000,http://localhost:3001,http://localhost:8080 + +# Allowed methods for CORS (comma-separated) +CORS_METHODS=GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS + +# Allowed headers for CORS (comma-separated) +CORS_HEADERS=Content-Type,Accept,Authorization,X-Requested-With + +# Allow credentials in CORS requests +CORS_CREDENTIALS=true + +# ================================= +# SECURITY CONFIGURATION +# ================================= + +# JWT Secret for authentication (generate strong secret for production) +JWT_SECRET=your-super-secret-jwt-key-change-this-in-production + +# JWT Token expiration time +JWT_EXPIRES_IN=24h + +# JWT Refresh token expiration +JWT_REFRESH_EXPIRES_IN=7d + +# API Rate limiting (requests per minute) +RATE_LIMIT_MAX=100 + +# ================================= +# LOGGING CONFIGURATION +# ================================= + +# Log level: error, warn, info, debug, verbose +LOG_LEVEL=info + +# Log format: json, simple +LOG_FORMAT=simple + +# Enable request logging +LOG_REQUESTS=true + +# ================================= +# FILE UPLOAD CONFIGURATION +# ================================= + +# Maximum file size for uploads (in bytes) +# 10MB = 10485760, 50MB = 52428800 +MAX_FILE_SIZE=10485760 + +# Allowed file types for upload (comma-separated) +ALLOWED_FILE_TYPES=.xlsx,.xls,.csv + +# Upload directory path +UPLOAD_DIR=./uploads + +# ================================= +# CACHE CONFIGURATION +# ================================= + +# Redis URL for caching (optional) +# REDIS_URL=redis://localhost:6379 + +# Cache TTL in seconds (default: 1 hour) +CACHE_TTL=3600 + +# ================================= +# EMAIL CONFIGURATION (Optional) +# ================================= + +# SMTP configuration for sending emails +# SMTP_HOST=smtp.gmail.com +# SMTP_PORT=587 +# SMTP_SECURE=false +# SMTP_USER=your-email@gmail.com +# SMTP_PASS=your-email-password + +# Email from address +# EMAIL_FROM=noreply@yourdomain.com + +# ================================= +# THIRD-PARTY INTEGRATIONS +# ================================= + +# External API keys +# EXTERNAL_API_KEY=your-api-key +# EXTERNAL_API_URL=https://api.external-service.com + +# ================================= +# DEVELOPMENT SETTINGS +# ================================= + +# Enable API documentation (Swagger) +ENABLE_DOCS=true + +# Enable debug mode +DEBUG=true + +# Enable database logging +DB_LOGGING=true + +# ================================= +# PRODUCTION SETTINGS +# ================================= + +# When NODE_ENV=production, ensure these are set: +# - Strong JWT_SECRET +# - Specific CORS_ORIGINS (not *) +# - DB_LOGGING=false +# - DEBUG=false +# - LOG_LEVEL=warn or error + +# Health check endpoint settings +HEALTH_CHECK_ENABLED=true + +# Request timeout in milliseconds +REQUEST_TIMEOUT=30000 + +# ================================= +# ICD SPECIFIC CONFIGURATION +# ================================= + +# Path to ICD data files +ICD9_FILE_PATH=./test/[PUBLIC] ICD-9CM e-klaim.xlsx +ICD10_FILE_PATH=./test/[PUBLIC] ICD-10 e-klaim.xlsx + +# ICD import batch size +ICD_IMPORT_BATCH_SIZE=1000 + +# ================================= +# MONITORING & METRICS +# ================================= + +# Enable application metrics +METRICS_ENABLED=true + +# Metrics endpoint path +METRICS_PATH=/metrics + +# Enable health check endpoint +HEALTH_CHECK_PATH=/health + +OPENAI_API_KEY=xxxxxx +OPENAI_API_MODEL=text-embedding-ada-002 \ No newline at end of file diff --git a/README.md b/README.md index 751530d..f056859 100644 --- a/README.md +++ b/README.md @@ -1,194 +1,397 @@ -

- Nest Logo -

+# πŸ₯ Claim Guard Backend -[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 -[circleci-url]: https://circleci.com/gh/nestjs/nest +> **NestJS application for managing ICD-9 and ICD-10 medical codes with Excel import functionality** -

A progressive Node.js framework for building efficient and scalable server-side applications.

-

-NPM Version -Package License -NPM Downloads -CircleCI -Discord -Backers on Open Collective -Sponsors on Open Collective - Donate us - Support us - Follow us on Twitter -

- +[![NestJS](https://img.shields.io/badge/NestJS-E0234E?style=for-the-badge&logo=nestjs&logoColor=white)](https://nestjs.com/) +[![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org/) +[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-316192?style=for-the-badge&logo=postgresql&logoColor=white)](https://www.postgresql.org/) +[![Docker](https://img.shields.io/badge/Docker-2CA5E0?style=for-the-badge&logo=docker&logoColor=white)](https://www.docker.com/) +[![Prisma](https://img.shields.io/badge/Prisma-3982CE?style=for-the-badge&logo=Prisma&logoColor=white)](https://www.prisma.io/) -## Description +## ✨ Features -Claim Guard Backend - A NestJS application for managing ICD-9 and ICD-10 medical codes with Excel import functionality. +- πŸ₯ **ICD Code Management** - Import and manage ICD-9 and ICD-10 medical codes +- πŸ“Š **Excel Import** - Read data from Excel files and store in PostgreSQL +- πŸ” **Search & Filter** - Search codes by category, code, or display text +- 🌐 **REST API** - Full REST API with Swagger documentation +- πŸ“„ **Pagination** - Built-in pagination for large datasets +- 🐳 **Docker Ready** - PostgreSQL with pgvector extension +- πŸ”„ **Database Migrations** - Prisma migration system +- βœ… **Input Validation** - Type-safe DTOs with validation +- πŸ“š **API Documentation** - Interactive Swagger UI -## Features +## πŸš€ Quick Start -- **ICD Code Management**: Import and manage ICD-9 and ICD-10 medical codes -- **Excel Import**: Read data from Excel files and store in PostgreSQL database -- **Search & Filter**: Search codes by category, code, or display text -- **REST API**: Full REST API endpoints for accessing ICD data -- **Pagination**: Built-in pagination support for large datasets +### Prerequisites -## ICD Service Endpoints +- Node.js 18+ +- Docker & Docker Compose +- Git -### Import Data - -```bash -POST /icd/import -``` - -Imports ICD-9 and ICD-10 data from Excel files in the `test/` directory. - -### Search Codes - -```bash -GET /icd/search?category=ICD10&search=diabetes&page=1&limit=10 -``` - -Search ICD codes with optional filters: - -- `category`: Filter by ICD9 or ICD10 -- `search`: Search in code or display text -- `page`: Page number (default: 1) -- `limit`: Items per page (default: 10) - -### Get Statistics - -```bash -GET /icd/statistics -``` - -Returns count statistics for ICD codes. - -## Database Schema - -The application uses PostgreSQL with Prisma ORM. The ICD codes are stored in the `icd_codes` table with the following structure: - -```sql -CREATE TABLE "icd_codes" ( - "id" TEXT PRIMARY KEY DEFAULT gen_random_uuid(), - "code" TEXT UNIQUE NOT NULL, - "display" TEXT NOT NULL, - "version" TEXT NOT NULL, - "category" TEXT NOT NULL, -- "ICD9" or "ICD10" - "createdAt" TIMESTAMP DEFAULT NOW(), - "updatedAt" TIMESTAMP DEFAULT NOW() -); -``` - -**ID Format**: The `id` field now uses UUID (Universal Unique Identifier) format like `550e8400-e29b-41d4-a716-446655440000` instead of CUID. - -## Setup Instructions - -1. **Install Dependencies** +### 1. Clone & Install ```bash +git clone +cd claim-guard-be npm install ``` -2. **Database Setup** - Create a `.env` file with your PostgreSQL connection: +### 2. Start Database ```bash -DATABASE_URL="postgresql://username:password@localhost:5432/claim_guard_db?schema=public" +# Start PostgreSQL with pgvector +docker-compose up -d + +# Verify database is running +docker-compose ps ``` -3. **Generate Prisma Client** +### 3. Setup Environment ```bash +# Copy environment template (edit as needed) +copy .env.example .env + +# Update DATABASE_URL in .env: +DATABASE_URL=postgresql://postgres:postgres123@localhost:5432/claim_guard +``` + +### 4. Run Migrations + +```bash +# Apply database schema +npx prisma migrate deploy + +# Generate Prisma client npx prisma generate ``` -4. **Run Database Migrations** +### 5. Start Application ```bash -npx prisma db push +# Development mode +npm run start:dev + +# Production mode +npm run build +npm run start:prod ``` -5. **Place Excel Files** - Ensure the following files are in the `test/` directory: +### 6. Access Services -- `[PUBLIC] ICD-9CM e-klaim.xlsx` -- `[PUBLIC] ICD-10 e-klaim.xlsx` +- **API**: http://localhost:3000 +- **Swagger Docs**: http://localhost:3000/docs +- **Health Check**: http://localhost:3000/health -The Excel files should have at least 3 columns: +## πŸ“š API Endpoints -- Column 1: Code -- Column 2: Display/Description -- Column 3: Version +### ICD Management -## Project setup +| Method | Endpoint | Description | +| ------ | ----------------- | -------------------------------- | +| `POST` | `/icd/import` | Import ICD data from Excel files | +| `GET` | `/icd/search` | Search ICD codes with filters | +| `GET` | `/icd/statistics` | Get database statistics | + +### Health & Monitoring + +| Method | Endpoint | Description | +| ------ | --------------- | ----------------------------- | +| `GET` | `/health` | Application health check | +| `GET` | `/health/ready` | Readiness probe | +| `GET` | `/health/live` | Liveness probe | +| `GET` | `/docs` | Interactive API documentation | + +### Example Usage ```bash -$ npm install +# Import ICD data +curl -X POST http://localhost:3000/icd/import + +# Search for diabetes codes +curl "http://localhost:3000/icd/search?search=diabetes&page=1&limit=10" + +# Get statistics +curl http://localhost:3000/icd/statistics + +# Health check +curl http://localhost:3000/health ``` -## Compile and run the project +## 🐳 Docker Setup + +### Database Only (Recommended) ```bash -# development -$ npm run start +# Start PostgreSQL with pgvector +docker-compose up -d -# watch mode -$ npm run start:dev +# Stop database +docker-compose down -# production mode -$ npm run start:prod +# Reset database (deletes all data!) +docker-compose down -v ``` -## Run tests +### Connection Details + +```env +DATABASE_URL=postgresql://postgres:postgres123@localhost:5432/claim_guard +Host: localhost +Port: 5432 +Database: claim_guard +Username: postgres +Password: postgres123 +``` + +### Verify pgvector Extension ```bash -# unit tests -$ npm run test - -# e2e tests -$ npm run test:e2e - -# test coverage -$ npm run test:cov +docker-compose exec postgres psql -U postgres -d claim_guard -c "SELECT name, default_version, installed_version FROM pg_available_extensions WHERE name = 'vector';" ``` -## Deployment +## πŸ—‚οΈ Database Schema -When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information. +### IcdCode Model -If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps: +```prisma +model IcdCode { + id String @id @default(uuid()) + code String @unique + display String + version String + category String // "ICD9" or "ICD10" + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@map("icd_codes") +} +``` + +### Migration Commands ```bash -$ npm install -g @nestjs/mau -$ mau deploy +# Check migration status +npx prisma migrate status + +# Create new migration +npx prisma migrate dev --name description + +# Deploy to production +npx prisma migrate deploy + +# Reset database (development) +npx prisma migrate reset ``` -With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure. +## βš™οΈ Environment Configuration -## Resources +### Development -Check out a few resources that may come in handy when working with NestJS: +```env +NODE_ENV=development +PORT=3000 +DATABASE_URL=postgresql://postgres:postgres123@localhost:5432/claim_guard +CORS_ORIGINS=http://localhost:3000,http://localhost:3001 +ENABLE_DOCS=true +DEBUG=true +LOG_LEVEL=debug +``` -- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework. -- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy). -- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/). -- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks. -- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com). -- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com). -- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs). -- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com). +### Production -## Support +```env +NODE_ENV=production +PORT=3000 +DATABASE_URL=postgresql://user:pass@host:5432/db +CORS_ORIGINS=https://yourdomain.com +ENABLE_DOCS=false +DEBUG=false +LOG_LEVEL=warn +JWT_SECRET=strong-production-secret +``` -Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). +### Available Variables -## Stay in touch +| Variable | Description | Default | +| -------------- | ---------------------------- | ----------------------- | +| `NODE_ENV` | Environment mode | `development` | +| `PORT` | Application port | `3000` | +| `DATABASE_URL` | PostgreSQL connection string | Required | +| `CORS_ORIGINS` | Allowed CORS origins | `http://localhost:3000` | +| `ENABLE_DOCS` | Enable Swagger documentation | `true` | +| `LOG_LEVEL` | Logging level | `info` | +| `JWT_SECRET` | JWT signing secret | Required for auth | -- Author - [Kamil MyΕ›liwiec](https://twitter.com/kammysliwiec) -- Website - [https://nestjs.com](https://nestjs.com/) -- Twitter - [@nestframework](https://twitter.com/nestframework) +## πŸ§ͺ Testing -## License +### API Testing -Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE). +Use the included `icd.http` file with VS Code REST Client extension: + +```http +### Import ICD Data +POST http://localhost:3000/icd/import + +### Search ICD Codes +GET http://localhost:3000/icd/search?search=diabetes&page=1&limit=10 + +### Get Statistics +GET http://localhost:3000/icd/statistics +``` + +### Unit Tests + +```bash +# Run tests +npm run test + +# Watch mode +npm run test:watch + +# Coverage +npm run test:cov + +# E2E tests +npm run test:e2e +``` + +## πŸ”§ Development + +### Project Structure + +``` +src/ +β”œβ”€β”€ main.ts # Application entry point +β”œβ”€β”€ app.module.ts # Root module +β”œβ”€β”€ icd/ # ICD module +β”‚ β”œβ”€β”€ icd.controller.ts # REST endpoints +β”‚ β”œβ”€β”€ icd.service.ts # Business logic +β”‚ β”œβ”€β”€ icd.module.ts # Module definition +β”‚ └── dto/ # Data transfer objects +β”œβ”€β”€ health/ # Health check module +└── prisma/ # Database schema +``` + +### Scripts + +```bash +# Development +npm run start:dev # Start with hot reload +npm run start:debug # Start with debugger + +# Build +npm run build # Build for production +npm run start:prod # Start production build + +# Database +npx prisma studio # Open Prisma Studio +npx prisma db push # Push schema changes (dev only) +npx prisma generate # Generate Prisma client + +# Code Quality +npm run lint # Run ESLint +npm run format # Format with Prettier +``` + +### Adding New Features + +1. **Create DTOs** with validation decorators +2. **Add Swagger decorators** for API documentation +3. **Write unit tests** for services and controllers +4. **Update database schema** if needed +5. **Create migration** with descriptive name + +## πŸ“¦ Deployment + +### Docker Production + +```dockerfile +# Build stage +FROM node:18-alpine AS builder +WORKDIR /app +COPY package*.json ./ +RUN npm ci --only=production +COPY . . +RUN npm run build + +# Production stage +FROM node:18-alpine AS production +WORKDIR /app +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/node_modules ./node_modules +COPY package*.json ./ +EXPOSE 3000 +CMD ["node", "dist/main"] +``` + +### Environment Setup + +1. **Production Database**: Use managed PostgreSQL (AWS RDS, Google Cloud SQL) +2. **Environment Variables**: Set secure values for production +3. **SSL/TLS**: Enable HTTPS in production +4. **Monitoring**: Add application monitoring (Prometheus, Grafana) +5. **Logging**: Configure centralized logging + +## πŸ› Troubleshooting + +### Common Issues + +**Port 5432 already in use:** + +```bash +# Check what's using the port +netstat -ano | findstr :5432 +# Stop local PostgreSQL service +``` + +**Database connection failed:** + +```bash +# Check if container is running +docker-compose ps +# Check logs +docker-compose logs +# Restart database +docker-compose restart +``` + +**Prisma can't connect:** + +```bash +# Verify DATABASE_URL in .env +# Test connection +npx prisma db pull +``` + +**Build errors:** + +```bash +# Clean install +rm -rf node_modules package-lock.json +npm install +``` + +## πŸ“„ License + +This project is licensed under the MIT License. + +## 🀝 Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Add tests +5. Submit a pull request + +## πŸ“ž Support + +For questions and support: + +- Check the [API documentation](http://localhost:3000/docs) +- Review the troubleshooting section +- Create an issue in the repository + +--- + +**Ready to manage ICD codes efficiently!** πŸš€ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4f2b6db --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +services: + # PostgreSQL Database with pgvector extension + postgres: + image: pgvector/pgvector:pg15 + container_name: claim-guard-postgres + restart: unless-stopped + environment: + POSTGRES_DB: claim_guard + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres123 + ports: + - '5432:5432' + volumes: + - postgres_data:/var/lib/postgresql/data + - ./docker/postgres/init:/docker-entrypoint-initdb.d + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U postgres -d claim_guard'] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + +volumes: + postgres_data: + driver: local diff --git a/docker/postgres/init/01-init.sql b/docker/postgres/init/01-init.sql new file mode 100644 index 0000000..1cf3e19 --- /dev/null +++ b/docker/postgres/init/01-init.sql @@ -0,0 +1,32 @@ +-- ===================================================== +-- Claim Guard Database Initialization Script +-- ===================================================== + +-- Create database if it doesn't exist (handled by POSTGRES_DB env var) +-- But we can create additional databases if needed + +-- Enable pgvector extension +CREATE EXTENSION IF NOT EXISTS vector; + +-- Enable other useful extensions +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS "pg_trgm"; +CREATE EXTENSION IF NOT EXISTS "btree_gin"; +CREATE EXTENSION IF NOT EXISTS "btree_gist"; + +-- Create application user if needed (optional) +-- The main user is already created via POSTGRES_USER + +-- Set up database permissions +GRANT ALL PRIVILEGES ON DATABASE claim_guard TO postgres; + +-- Create schema for application (optional, Prisma will use public by default) +-- CREATE SCHEMA IF NOT EXISTS claim_guard; + +-- Log successful initialization +DO $$ +BEGIN + RAISE NOTICE 'Claim Guard database initialized successfully'; + RAISE NOTICE 'pgvector extension: %', (SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname = 'vector')); + RAISE NOTICE 'Database ready for Prisma migrations'; +END $$; diff --git a/docker/scripts/reset-db.bat b/docker/scripts/reset-db.bat new file mode 100644 index 0000000..c99e745 --- /dev/null +++ b/docker/scripts/reset-db.bat @@ -0,0 +1,43 @@ +@echo off +REM ===================================================== +REM Reset PostgreSQL Database (Windows) +REM ===================================================== + +echo πŸ—‘οΈ Resetting PostgreSQL database... + +REM Stop services +docker-compose down + +REM Remove volumes (this will delete all data!) +echo ⚠️ WARNING: This will delete ALL database data! +set /p confirm=Are you sure? (y/N): +if /i not "%confirm%"=="y" ( + echo ❌ Operation cancelled + exit /b 1 +) + +docker-compose down -v +docker volume rm claim-guard-be_postgres_data 2>nul +docker volume rm claim-guard-be_pgadmin_data 2>nul + +echo βœ… Database reset complete! +echo 🐳 Starting fresh database... + +REM Start database again +docker-compose up -d postgres + +echo ⏳ Waiting for PostgreSQL to be ready... + +REM Wait for PostgreSQL to be healthy +:wait_loop +docker-compose exec postgres pg_isready -U postgres -d claim_guard >nul 2>&1 +if %errorlevel% neq 0 ( + echo ⏳ PostgreSQL is unavailable - sleeping + timeout /t 2 /nobreak >nul + goto wait_loop +) + +echo βœ… Fresh database is ready! +echo πŸ“Š Run 'npx prisma migrate deploy' to setup schema + +pause diff --git a/docker/scripts/start-db.bat b/docker/scripts/start-db.bat new file mode 100644 index 0000000..a00cd89 --- /dev/null +++ b/docker/scripts/start-db.bat @@ -0,0 +1,29 @@ +@echo off +REM ===================================================== +REM Start PostgreSQL Database Only (Windows) +REM ===================================================== + +echo 🐳 Starting PostgreSQL with pgvector... + +REM Start only the database service +docker-compose up -d postgres + +echo ⏳ Waiting for PostgreSQL to be ready... + +REM Wait for PostgreSQL to be healthy +:wait_loop +docker-compose exec postgres pg_isready -U postgres -d claim_guard >nul 2>&1 +if %errorlevel% neq 0 ( + echo ⏳ PostgreSQL is unavailable - sleeping + timeout /t 2 /nobreak >nul + goto wait_loop +) + +echo βœ… PostgreSQL is ready! +echo πŸ“Š Database URL: postgresql://postgres:postgres123@localhost:5432/claim_guard + +REM Show logs +echo πŸ“‹ Database logs: +docker-compose logs postgres + +pause diff --git a/docker/scripts/start-db.sh b/docker/scripts/start-db.sh new file mode 100644 index 0000000..13fb03d --- /dev/null +++ b/docker/scripts/start-db.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# ===================================================== +# Start PostgreSQL Database Only +# ===================================================== + +echo "🐳 Starting PostgreSQL with pgvector..." + +# Start only the database service +docker-compose up -d postgres + +echo "⏳ Waiting for PostgreSQL to be ready..." + +# Wait for PostgreSQL to be healthy +until docker-compose exec postgres pg_isready -U postgres -d claim_guard; do + echo "⏳ PostgreSQL is unavailable - sleeping" + sleep 2 +done + +echo "βœ… PostgreSQL is ready!" +echo "πŸ“Š Database URL: postgresql://postgres:postgres123@localhost:5432/claim_guard" + +# Show logs +echo "πŸ“‹ Database logs:" +docker-compose logs postgres diff --git a/package-lock.json b/package-lock.json index 69d3739..38547d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,20 @@ "version": "0.0.1", "license": "UNLICENSED", "dependencies": { + "@langchain/community": "^0.3.53", + "@langchain/openai": "^0.6.9", "@nestjs/common": "^11.0.1", "@nestjs/core": "^11.0.1", "@nestjs/platform-express": "^11.0.1", + "@nestjs/swagger": "^11.2.0", "@prisma/client": "^6.14.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.2", + "langchain": "^0.3.31", + "pg": "^8.11.3", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", + "swagger-ui-express": "^5.0.1", "xlsx": "^0.18.5" }, "devDependencies": { @@ -203,6 +211,39 @@ "tslib": "^2.1.0" } }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.27.3.tgz", + "integrity": "sha512-IjLt0gd3L4jlOfilxVXTifn42FnVffMgDC04RJK1KDZpmkBWLv0XC92MVVmkxrFZNS/7l3xWgP/I3nqtX1sQHw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/@types/node": { + "version": "18.19.123", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.123.tgz", + "integrity": "sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT", + "peer": true + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -729,6 +770,66 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/@browserbasehq/sdk": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@browserbasehq/sdk/-/sdk-2.6.0.tgz", + "integrity": "sha512-83iXP5D7xMm8Wyn66TUaUrgoByCmAJuoMoZQI3sGg3JAiMlTfnCIMqyVBoNSaItaPIkaCnrsj6LiusmXV2X9YA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/@browserbasehq/sdk/node_modules/@types/node": { + "version": "18.19.123", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.123.tgz", + "integrity": "sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@browserbasehq/sdk/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT", + "peer": true + }, + "node_modules/@browserbasehq/stagehand": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@browserbasehq/stagehand/-/stagehand-1.14.0.tgz", + "integrity": "sha512-Hi/EzgMFWz+FKyepxHTrqfTPjpsuBS4zRy3e9sbMpBgLPv+9c0R+YZEvS7Bw4mTS66QtvvURRT6zgDGFotthVQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@anthropic-ai/sdk": "^0.27.3", + "@browserbasehq/sdk": "^2.0.0", + "ws": "^8.18.0", + "zod-to-json-schema": "^3.23.5" + }, + "peerDependencies": { + "@playwright/test": "^1.42.1", + "deepmerge": "^4.3.1", + "dotenv": "^16.4.5", + "openai": "^4.62.1", + "zod": "^3.23.8" + } + }, + "node_modules/@cfworker/json-schema": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", + "license": "MIT", + "peer": true + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -952,6 +1053,46 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "license": "MIT", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz", + "integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1018,6 +1159,38 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@ibm-cloud/watsonx-ai": { + "version": "1.6.10", + "resolved": "https://registry.npmjs.org/@ibm-cloud/watsonx-ai/-/watsonx-ai-1.6.10.tgz", + "integrity": "sha512-aZV50/s8VZc7w0t/qcaBw3RLT3WDsAeZUJlP8EbG/csZJF3a8F7alihbGOM4lJFM7R4Z81Lucz3nfHi2KR7J4Q==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/node": "^18.0.0", + "extend": "3.0.2", + "ibm-cloud-sdk-core": "^5.3.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@ibm-cloud/watsonx-ai/node_modules/@types/node": { + "version": "18.19.123", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.123.tgz", + "integrity": "sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@ibm-cloud/watsonx-ai/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT", + "peer": true + }, "node_modules/@inquirer/checkbox": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.2.1.tgz", @@ -2051,6 +2224,660 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@langchain/community": { + "version": "0.3.53", + "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.3.53.tgz", + "integrity": "sha512-TlQzXXuiqPkpALvvuClzt6K83EQ2oA+0B/UzWrrWQPCYpJtfy5kbT/Uwc4PPa8twdVUNpLcXgFaLtb3U1EXVAg==", + "license": "MIT", + "dependencies": { + "@langchain/openai": ">=0.2.0 <0.7.0", + "@langchain/weaviate": "^0.2.0", + "binary-extensions": "^2.2.0", + "expr-eval": "^2.0.2", + "flat": "^5.0.2", + "js-yaml": "^4.1.0", + "langchain": ">=0.2.3 <0.3.0 || >=0.3.4 <0.4.0", + "langsmith": "^0.3.46", + "uuid": "^10.0.0", + "zod": "^3.25.32" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@arcjet/redact": "^v1.0.0-alpha.23", + "@aws-crypto/sha256-js": "^5.0.0", + "@aws-sdk/client-bedrock-agent-runtime": "^3.749.0", + "@aws-sdk/client-bedrock-runtime": "^3.749.0", + "@aws-sdk/client-dynamodb": "^3.749.0", + "@aws-sdk/client-kendra": "^3.749.0", + "@aws-sdk/client-lambda": "^3.749.0", + "@aws-sdk/client-s3": "^3.749.0", + "@aws-sdk/client-sagemaker-runtime": "^3.749.0", + "@aws-sdk/client-sfn": "^3.749.0", + "@aws-sdk/credential-provider-node": "^3.388.0", + "@azure/search-documents": "^12.0.0", + "@azure/storage-blob": "^12.15.0", + "@browserbasehq/sdk": "*", + "@browserbasehq/stagehand": "^1.0.0", + "@clickhouse/client": "^0.2.5", + "@cloudflare/ai": "*", + "@datastax/astra-db-ts": "^1.0.0", + "@elastic/elasticsearch": "^8.4.0", + "@getmetal/metal-sdk": "*", + "@getzep/zep-cloud": "^1.0.6", + "@getzep/zep-js": "^0.9.0", + "@gomomento/sdk": "^1.51.1", + "@gomomento/sdk-core": "^1.51.1", + "@google-ai/generativelanguage": "*", + "@google-cloud/storage": "^6.10.1 || ^7.7.0", + "@gradientai/nodejs-sdk": "^1.2.0", + "@huggingface/inference": "^4.0.5", + "@huggingface/transformers": "^3.5.2", + "@ibm-cloud/watsonx-ai": "*", + "@lancedb/lancedb": "^0.12.0", + "@langchain/core": ">=0.3.58 <0.4.0", + "@layerup/layerup-security": "^1.5.12", + "@libsql/client": "^0.14.0", + "@mendable/firecrawl-js": "^1.4.3", + "@mlc-ai/web-llm": "*", + "@mozilla/readability": "*", + "@neondatabase/serverless": "*", + "@notionhq/client": "^2.2.10", + "@opensearch-project/opensearch": "*", + "@pinecone-database/pinecone": "*", + "@planetscale/database": "^1.8.0", + "@premai/prem-sdk": "^0.3.25", + "@qdrant/js-client-rest": "^1.15.0", + "@raycast/api": "^1.55.2", + "@rockset/client": "^0.9.1", + "@smithy/eventstream-codec": "^2.0.5", + "@smithy/protocol-http": "^3.0.6", + "@smithy/signature-v4": "^2.0.10", + "@smithy/util-utf8": "^2.0.0", + "@spider-cloud/spider-client": "^0.0.21", + "@supabase/supabase-js": "^2.45.0", + "@tensorflow-models/universal-sentence-encoder": "*", + "@tensorflow/tfjs-converter": "*", + "@tensorflow/tfjs-core": "*", + "@upstash/ratelimit": "^1.1.3 || ^2.0.3", + "@upstash/redis": "^1.20.6", + "@upstash/vector": "^1.1.1", + "@vercel/kv": "*", + "@vercel/postgres": "*", + "@writerai/writer-sdk": "^0.40.2", + "@xata.io/client": "^0.28.0", + "@zilliz/milvus2-sdk-node": ">=2.3.5", + "apify-client": "^2.7.1", + "assemblyai": "^4.6.0", + "azion": "^1.11.1", + "better-sqlite3": ">=9.4.0 <12.0.0", + "cassandra-driver": "^4.7.2", + "cborg": "^4.1.1", + "cheerio": "^1.0.0-rc.12", + "chromadb": "*", + "closevector-common": "0.1.3", + "closevector-node": "0.1.6", + "closevector-web": "0.1.6", + "cohere-ai": "*", + "convex": "^1.3.1", + "crypto-js": "^4.2.0", + "d3-dsv": "^2.0.0", + "discord.js": "^14.14.1", + "dria": "^0.0.3", + "duck-duck-scrape": "^2.2.5", + "epub2": "^3.0.1", + "fast-xml-parser": "*", + "firebase-admin": "^11.9.0 || ^12.0.0", + "google-auth-library": "*", + "googleapis": "*", + "hnswlib-node": "^3.0.0", + "html-to-text": "^9.0.5", + "ibm-cloud-sdk-core": "*", + "ignore": "^5.2.0", + "interface-datastore": "^8.2.11", + "ioredis": "^5.3.2", + "it-all": "^3.0.4", + "jsdom": "*", + "jsonwebtoken": "^9.0.2", + "llmonitor": "^0.5.9", + "lodash": "^4.17.21", + "lunary": "^0.7.10", + "mammoth": "^1.6.0", + "mariadb": "^3.4.0", + "mem0ai": "^2.1.8", + "mongodb": "^6.17.0", + "mysql2": "^3.9.8", + "neo4j-driver": "*", + "notion-to-md": "^3.1.0", + "officeparser": "^4.0.4", + "openai": "*", + "pdf-parse": "1.1.1", + "pg": "^8.11.0", + "pg-copy-streams": "^6.0.5", + "pickleparser": "^0.2.1", + "playwright": "^1.32.1", + "portkey-ai": "^0.1.11", + "puppeteer": "*", + "pyodide": ">=0.24.1 <0.27.0", + "redis": "*", + "replicate": "*", + "sonix-speech-recognition": "^2.1.1", + "srt-parser-2": "^1.2.3", + "typeorm": "^0.3.20", + "typesense": "^1.5.3", + "usearch": "^1.1.1", + "voy-search": "0.6.2", + "weaviate-client": "^3.5.2", + "web-auth-library": "^1.0.3", + "word-extractor": "*", + "ws": "^8.14.2", + "youtubei.js": "*" + }, + "peerDependenciesMeta": { + "@arcjet/redact": { + "optional": true + }, + "@aws-crypto/sha256-js": { + "optional": true + }, + "@aws-sdk/client-bedrock-agent-runtime": { + "optional": true + }, + "@aws-sdk/client-bedrock-runtime": { + "optional": true + }, + "@aws-sdk/client-dynamodb": { + "optional": true + }, + "@aws-sdk/client-kendra": { + "optional": true + }, + "@aws-sdk/client-lambda": { + "optional": true + }, + "@aws-sdk/client-s3": { + "optional": true + }, + "@aws-sdk/client-sagemaker-runtime": { + "optional": true + }, + "@aws-sdk/client-sfn": { + "optional": true + }, + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@aws-sdk/dsql-signer": { + "optional": true + }, + "@azure/search-documents": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@browserbasehq/sdk": { + "optional": true + }, + "@clickhouse/client": { + "optional": true + }, + "@cloudflare/ai": { + "optional": true + }, + "@datastax/astra-db-ts": { + "optional": true + }, + "@elastic/elasticsearch": { + "optional": true + }, + "@getmetal/metal-sdk": { + "optional": true + }, + "@getzep/zep-cloud": { + "optional": true + }, + "@getzep/zep-js": { + "optional": true + }, + "@gomomento/sdk": { + "optional": true + }, + "@gomomento/sdk-core": { + "optional": true + }, + "@google-ai/generativelanguage": { + "optional": true + }, + "@google-cloud/storage": { + "optional": true + }, + "@gradientai/nodejs-sdk": { + "optional": true + }, + "@huggingface/inference": { + "optional": true + }, + "@huggingface/transformers": { + "optional": true + }, + "@lancedb/lancedb": { + "optional": true + }, + "@layerup/layerup-security": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@mendable/firecrawl-js": { + "optional": true + }, + "@mlc-ai/web-llm": { + "optional": true + }, + "@mozilla/readability": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@notionhq/client": { + "optional": true + }, + "@opensearch-project/opensearch": { + "optional": true + }, + "@pinecone-database/pinecone": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@premai/prem-sdk": { + "optional": true + }, + "@qdrant/js-client-rest": { + "optional": true + }, + "@raycast/api": { + "optional": true + }, + "@rockset/client": { + "optional": true + }, + "@smithy/eventstream-codec": { + "optional": true + }, + "@smithy/protocol-http": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "@smithy/util-utf8": { + "optional": true + }, + "@spider-cloud/spider-client": { + "optional": true + }, + "@supabase/supabase-js": { + "optional": true + }, + "@tensorflow-models/universal-sentence-encoder": { + "optional": true + }, + "@tensorflow/tfjs-converter": { + "optional": true + }, + "@tensorflow/tfjs-core": { + "optional": true + }, + "@upstash/ratelimit": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@upstash/vector": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@writerai/writer-sdk": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "@zilliz/milvus2-sdk-node": { + "optional": true + }, + "apify-client": { + "optional": true + }, + "assemblyai": { + "optional": true + }, + "azion": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "cassandra-driver": { + "optional": true + }, + "cborg": { + "optional": true + }, + "cheerio": { + "optional": true + }, + "chromadb": { + "optional": true + }, + "closevector-common": { + "optional": true + }, + "closevector-node": { + "optional": true + }, + "closevector-web": { + "optional": true + }, + "cohere-ai": { + "optional": true + }, + "convex": { + "optional": true + }, + "crypto-js": { + "optional": true + }, + "d3-dsv": { + "optional": true + }, + "discord.js": { + "optional": true + }, + "dria": { + "optional": true + }, + "duck-duck-scrape": { + "optional": true + }, + "epub2": { + "optional": true + }, + "fast-xml-parser": { + "optional": true + }, + "firebase-admin": { + "optional": true + }, + "google-auth-library": { + "optional": true + }, + "googleapis": { + "optional": true + }, + "hnswlib-node": { + "optional": true + }, + "html-to-text": { + "optional": true + }, + "ignore": { + "optional": true + }, + "interface-datastore": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "it-all": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "jsonwebtoken": { + "optional": true + }, + "llmonitor": { + "optional": true + }, + "lodash": { + "optional": true + }, + "lunary": { + "optional": true + }, + "mammoth": { + "optional": true + }, + "mariadb": { + "optional": true + }, + "mem0ai": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "neo4j-driver": { + "optional": true + }, + "notion-to-md": { + "optional": true + }, + "officeparser": { + "optional": true + }, + "pdf-parse": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-copy-streams": { + "optional": true + }, + "pickleparser": { + "optional": true + }, + "playwright": { + "optional": true + }, + "portkey-ai": { + "optional": true + }, + "puppeteer": { + "optional": true + }, + "pyodide": { + "optional": true + }, + "redis": { + "optional": true + }, + "replicate": { + "optional": true + }, + "sonix-speech-recognition": { + "optional": true + }, + "srt-parser-2": { + "optional": true + }, + "typeorm": { + "optional": true + }, + "typesense": { + "optional": true + }, + "usearch": { + "optional": true + }, + "voy-search": { + "optional": true + }, + "weaviate-client": { + "optional": true + }, + "web-auth-library": { + "optional": true + }, + "word-extractor": { + "optional": true + }, + "ws": { + "optional": true + }, + "youtubei.js": { + "optional": true + } + } + }, + "node_modules/@langchain/core": { + "version": "0.3.72", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.72.tgz", + "integrity": "sha512-WsGWVZYnlKffj2eEfDocPNiaTRoxyYiLSQdQ7oxZvxGZBqo/90vpjbC33UGK1uPNBM4kT+pkdaol/MnvKUh8TQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@cfworker/json-schema": "^4.0.2", + "ansi-styles": "^5.0.0", + "camelcase": "6", + "decamelize": "1.2.0", + "js-tiktoken": "^1.0.12", + "langsmith": "^0.3.46", + "mustache": "^4.2.0", + "p-queue": "^6.6.2", + "p-retry": "4", + "uuid": "^10.0.0", + "zod": "^3.25.32", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@langchain/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@langchain/core/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@langchain/openai": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.6.9.tgz", + "integrity": "sha512-Dl+YVBTFia7WE4/jFemQEVchPbsahy/dD97jo6A9gLnYfTkWa/jh8Q78UjHQ3lobif84j2ebjHPcDHG1L0NUWg==", + "license": "MIT", + "dependencies": { + "js-tiktoken": "^1.0.12", + "openai": "5.12.2", + "zod": "^3.25.32" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.68 <0.4.0" + } + }, + "node_modules/@langchain/openai/node_modules/openai": { + "version": "5.12.2", + "resolved": "https://registry.npmjs.org/openai/-/openai-5.12.2.tgz", + "integrity": "sha512-xqzHHQch5Tws5PcKR2xsZGX9xtch+JQFz5zb14dGqlshmmDAFBFEWmeIpf7wVqWV+w7Emj7jRgkNJakyKE0tYQ==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@langchain/textsplitters": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@langchain/textsplitters/-/textsplitters-0.1.0.tgz", + "integrity": "sha512-djI4uw9rlkAb5iMhtLED+xJebDdAG935AdP4eRTB02R7OB/act55Bj9wsskhZsvuyQRpO4O1wQOp85s6T6GWmw==", + "license": "MIT", + "dependencies": { + "js-tiktoken": "^1.0.12" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.21 <0.4.0" + } + }, + "node_modules/@langchain/weaviate": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@langchain/weaviate/-/weaviate-0.2.2.tgz", + "integrity": "sha512-nMkK4ZwfKjQR98kzpL/PPdFixdmD/KX89lZ9R5rhEShv3nfVyfGW8bVMpmC91kqIWxsjeqaqUZ1ZAdzpZRnE/w==", + "license": "MIT", + "dependencies": { + "uuid": "^10.0.0", + "weaviate-client": "^3.5.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.21 <0.4.0" + } + }, "node_modules/@lukeed/csprng": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", @@ -2060,6 +2887,12 @@ "node": ">=8" } }, + "node_modules/@microsoft/tsdoc": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", + "integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==", + "license": "MIT" + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -2376,6 +3209,26 @@ } } }, + "node_modules/@nestjs/mapped-types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz", + "integrity": "sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "class-transformer": "^0.4.0 || ^0.5.0", + "class-validator": "^0.13.0 || ^0.14.0", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, "node_modules/@nestjs/platform-express": { "version": "11.1.6", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.6.tgz", @@ -2414,6 +3267,39 @@ "typescript": ">=4.8.2" } }, + "node_modules/@nestjs/swagger": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.0.tgz", + "integrity": "sha512-5wolt8GmpNcrQv34tIPUtPoV1EeFbCetm40Ij3+M0FNNnf2RJ3FyWfuQvI8SBlcJyfaounYVTKzKHreFXsUyOg==", + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.15.1", + "@nestjs/mapped-types": "2.1.0", + "js-yaml": "4.1.0", + "lodash": "4.17.21", + "path-to-regexp": "8.2.0", + "swagger-ui-dist": "5.21.0" + }, + "peerDependencies": { + "@fastify/static": "^8.0.0", + "@nestjs/common": "^11.0.1", + "@nestjs/core": "^11.0.1", + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "@fastify/static": { + "optional": true + }, + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, "node_modules/@nestjs/testing": { "version": "11.1.6", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.6.tgz", @@ -2543,6 +3429,22 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@playwright/test": { + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0.tgz", + "integrity": "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "playwright": "1.55.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@prisma/client": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.14.0.tgz", @@ -2628,6 +3530,77 @@ "@prisma/debug": "6.14.0" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, "node_modules/@sinclair/typebox": { "version": "0.34.40", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.40.tgz", @@ -2798,6 +3771,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -2918,16 +3901,33 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT", + "peer": true + }, "node_modules/@types/node": { "version": "22.17.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.2.tgz", "integrity": "sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -2942,6 +3942,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, "node_modules/@types/send": { "version": "0.17.5", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", @@ -2996,6 +4002,25 @@ "@types/superagent": "^8.1.0" } }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "license": "MIT" + }, + "node_modules/@types/validator": { + "version": "13.15.2", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.2.tgz", + "integrity": "sha512-y7pa/oEJJ4iGYBxOpfAKn5b9+xuihvzDVnC/OSvlVnGxVg0pOqmjiMafiJ1KVNQEaPZf9HsEp5icEwGg8uIe5Q==", + "license": "MIT" + }, "node_modules/@types/xlsx": { "version": "0.0.35", "resolved": "https://registry.npmjs.org/@types/xlsx/-/xlsx-0.0.35.tgz", @@ -3729,6 +4754,25 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "peer": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abort-controller-x": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/abort-controller-x/-/abort-controller-x-0.4.3.tgz", + "integrity": "sha512-VtUwTNU8fpMwvWGn4xE93ywbogTYsuT+AUxAXOeelbXuQVIwNmC5YLeho9sH4vZ4ITW8414TTAOG1nW6uIVHCA==", + "license": "MIT" + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -3800,6 +4844,19 @@ "node": ">=0.8" } }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3912,7 +4969,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -3978,7 +5034,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/array-timsort": { @@ -3999,9 +5054,20 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, "license": "MIT" }, + "node_modules/axios": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "license": "MIT", + "peer": true, + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-jest": { "version": "30.0.5", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.5.tgz", @@ -4111,7 +5177,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -4128,6 +5193,18 @@ ], "license": "MIT" }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -4265,12 +5342,28 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause", + "peer": true + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -4407,7 +5500,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -4496,6 +5588,23 @@ "dev": true, "license": "MIT" }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "license": "MIT" + }, + "node_modules/class-validator": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz", + "integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==", + "license": "MIT", + "dependencies": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.11.1", + "validator": "^13.9.0" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -4552,7 +5661,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -4567,7 +5675,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4577,7 +5684,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -4590,7 +5696,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -4645,7 +5750,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -4658,14 +5762,12 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -4749,6 +5851,15 @@ "node": "^14.18.0 || >=16.10.0" } }, + "node_modules/console-table-printer": { + "version": "2.14.6", + "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.14.6.tgz", + "integrity": "sha512-MCBl5HNVaFuuHW6FGbL/4fB7N/ormCy+tQ+sxTrF6QtSbSNETvPuOVbkJBhzDgYhvjWGrTma4eYJa37ZuoQsPw==", + "license": "MIT", + "dependencies": { + "simple-wcswidth": "^1.0.1" + } + }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", @@ -4868,6 +5979,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4900,6 +6020,16 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/dedent": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", @@ -4926,7 +6056,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4966,7 +6095,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -5023,7 +6151,6 @@ "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -5053,6 +6180,16 @@ "dev": true, "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5094,7 +6231,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/empathic": { @@ -5181,7 +6317,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5197,7 +6332,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5447,11 +6581,26 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.x" @@ -5516,6 +6665,12 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/expr-eval": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expr-eval/-/expr-eval-2.0.2.tgz", + "integrity": "sha512-4EMSHGOPSwAfBiibw3ndnP0AvjDWLsMvGOvWEZ2F96IGk0bIVdjQisOHxReSkE13mHcfbuCiXw+G4y0zv6N8Eg==", + "license": "MIT" + }, "node_modules/express": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", @@ -5565,6 +6720,13 @@ "devOptional": true, "license": "MIT" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT", + "peer": true + }, "node_modules/fast-check": { "version": "3.23.2", "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", @@ -5790,6 +6952,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -5811,6 +6982,27 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -5860,7 +7052,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -5873,11 +7064,17 @@ "node": ">= 6" } }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT", + "peer": true + }, "node_modules/form-data/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -5887,7 +7084,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -5896,6 +7092,20 @@ "node": ">= 0.6" } }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, "node_modules/formidable": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", @@ -6008,7 +7218,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -6191,11 +7400,33 @@ "dev": true, "license": "MIT" }, + "node_modules/graphql": { + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", + "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-6.1.0.tgz", + "integrity": "sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==", + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.2.0", + "cross-fetch": "^3.1.5" + }, + "peerDependencies": { + "graphql": "14 - 16" + } + }, "node_modules/handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "minimist": "^1.2.5", @@ -6217,7 +7448,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -6227,7 +7458,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6259,7 +7489,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -6325,6 +7554,150 @@ "node": ">=10.17.0" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/ibm-cloud-sdk-core": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/ibm-cloud-sdk-core/-/ibm-cloud-sdk-core-5.4.2.tgz", + "integrity": "sha512-5VFkKYU/vSIWFJTVt392XEdPmiEwUJqhxjn1MRO3lfELyU2FB+yYi8brbmXUgq+D1acHR1fpS7tIJ6IlnrR9Cg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/debug": "^4.1.12", + "@types/node": "^18.19.80", + "@types/tough-cookie": "^4.0.0", + "axios": "^1.11.0", + "camelcase": "^6.3.0", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "extend": "3.0.2", + "file-type": "16.5.4", + "form-data": "^4.0.4", + "isstream": "0.1.2", + "jsonwebtoken": "^9.0.2", + "mime-types": "2.1.35", + "retry-axios": "^2.6.0", + "tough-cookie": "^4.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/@types/node": { + "version": "18.19.123", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.123.tgz", + "integrity": "sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "license": "MIT", + "peer": true, + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/ibm-cloud-sdk-core/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT", + "peer": true + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -6361,7 +7734,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 4" @@ -6462,7 +7835,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6550,6 +7922,13 @@ "dev": true, "license": "ISC" }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT", + "peer": true + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -7431,6 +8810,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-tiktoken": { + "version": "1.0.21", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.21.tgz", + "integrity": "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.5.1" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7442,7 +8830,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -7525,6 +8912,61 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "peer": true, + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -7535,6 +8977,136 @@ "json-buffer": "3.0.1" } }, + "node_modules/langchain": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.3.31.tgz", + "integrity": "sha512-C7n7WGa44RytsuxEtGcArVcXidRqzjl6UWQxaG3NdIw4gIqErWoOlNC1qADAa04H5JAOARxuE6S99+WNXB/rzA==", + "license": "MIT", + "dependencies": { + "@langchain/openai": ">=0.1.0 <0.7.0", + "@langchain/textsplitters": ">=0.0.0 <0.2.0", + "js-tiktoken": "^1.0.12", + "js-yaml": "^4.1.0", + "jsonpointer": "^5.0.1", + "langsmith": "^0.3.46", + "openapi-types": "^12.1.3", + "p-retry": "4", + "uuid": "^10.0.0", + "yaml": "^2.2.1", + "zod": "^3.25.32" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/anthropic": "*", + "@langchain/aws": "*", + "@langchain/cerebras": "*", + "@langchain/cohere": "*", + "@langchain/core": ">=0.3.58 <0.4.0", + "@langchain/deepseek": "*", + "@langchain/google-genai": "*", + "@langchain/google-vertexai": "*", + "@langchain/google-vertexai-web": "*", + "@langchain/groq": "*", + "@langchain/mistralai": "*", + "@langchain/ollama": "*", + "@langchain/xai": "*", + "axios": "*", + "cheerio": "*", + "handlebars": "^4.7.8", + "peggy": "^3.0.2", + "typeorm": "*" + }, + "peerDependenciesMeta": { + "@langchain/anthropic": { + "optional": true + }, + "@langchain/aws": { + "optional": true + }, + "@langchain/cerebras": { + "optional": true + }, + "@langchain/cohere": { + "optional": true + }, + "@langchain/deepseek": { + "optional": true + }, + "@langchain/google-genai": { + "optional": true + }, + "@langchain/google-vertexai": { + "optional": true + }, + "@langchain/google-vertexai-web": { + "optional": true + }, + "@langchain/groq": { + "optional": true + }, + "@langchain/mistralai": { + "optional": true + }, + "@langchain/ollama": { + "optional": true + }, + "@langchain/xai": { + "optional": true + }, + "axios": { + "optional": true + }, + "cheerio": { + "optional": true + }, + "handlebars": { + "optional": true + }, + "peggy": { + "optional": true + }, + "typeorm": { + "optional": true + } + } + }, + "node_modules/langsmith": { + "version": "0.3.63", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.63.tgz", + "integrity": "sha512-GrioB7LOUksKIYsdYbBUwyD3ezy+OAQ5eu5vebytMsX3wT0xfW4rbM+vHqCY7RgZwUYLR/RlpuC18pdO+NqugA==", + "license": "MIT", + "dependencies": { + "@types/uuid": "^10.0.0", + "chalk": "^4.1.2", + "console-table-printer": "^2.12.1", + "p-queue": "^6.6.2", + "p-retry": "4", + "semver": "^7.6.3", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "*", + "@opentelemetry/exporter-trace-otlp-proto": "*", + "@opentelemetry/sdk-trace-base": "*", + "openai": "*" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/exporter-trace-otlp-proto": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, + "openai": { + "optional": true + } + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -7559,6 +9131,12 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.12.13", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.13.tgz", + "integrity": "sha512-QZXnR/OGiDcBjF4hGk0wwVrPcZvbSSyzlvkjXv5LFfktj7O2VZDrt4Xs8SgR/vOFco+qk1i8J43ikMXZoTrtPw==", + "license": "MIT" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -7615,9 +9193,56 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT", + "peer": true + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -7632,6 +9257,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT", + "peer": true + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -7649,6 +9281,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -7954,6 +9592,16 @@ "node": ">= 0.6" } }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "peer": true, + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/mute-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", @@ -8000,9 +9648,39 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/nice-grpc": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/nice-grpc/-/nice-grpc-2.1.12.tgz", + "integrity": "sha512-J1n4Wg+D3IhRhGQb+iqh2OpiM0GzTve/kf2lnlW4S+xczmIEd0aHUDV1OsJ5a3q8GSTqJf+s4Rgg1M8uJltarw==", + "license": "MIT", + "dependencies": { + "@grpc/grpc-js": "^1.13.1", + "abort-controller-x": "^0.4.0", + "nice-grpc-common": "^2.0.2" + } + }, + "node_modules/nice-grpc-client-middleware-retry": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nice-grpc-client-middleware-retry/-/nice-grpc-client-middleware-retry-3.1.11.tgz", + "integrity": "sha512-xW/imz/kNG2g0DwTfH2eYEGrg1chSLrXtvGp9fg2qkhTgGFfAS/Pq3+t+9G8KThcC4hK/xlEyKvZWKk++33S6A==", + "license": "MIT", + "dependencies": { + "abort-controller-x": "^0.4.0", + "nice-grpc-common": "^2.0.2" + } + }, + "node_modules/nice-grpc-common": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/nice-grpc-common/-/nice-grpc-common-2.0.2.tgz", + "integrity": "sha512-7RNWbls5kAL1QVUOXvBsv1uO0wPQK3lHv+cY1gwkTzirnG1Nop4cBJZubpgziNbaVc/bl9QJcyvsf/NQxa3rjQ==", + "license": "MIT", + "dependencies": { + "ts-error": "^1.0.6" + } + }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -8010,6 +9688,27 @@ "dev": true, "license": "MIT" }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", @@ -8020,6 +9719,26 @@ "lodash": "^4.17.21" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-fetch-native": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", @@ -8149,6 +9868,60 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openai": { + "version": "4.104.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", + "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.123", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.123.tgz", + "integrity": "sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/openai/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT", + "peer": true + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -8214,6 +9987,15 @@ "node": ">=8" } }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -8246,6 +10028,47 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -8263,6 +10086,12 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==", + "license": "MIT" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -8387,6 +10216,20 @@ "devOptional": true, "license": "MIT" }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -8394,6 +10237,97 @@ "devOptional": true, "license": "MIT" }, + "node_modules/pg": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz", + "integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==", + "license": "MIT", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.6.2", + "pg-pool": "^3.6.1", + "pg-protocol": "^1.6.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.1.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/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.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -8505,6 +10439,53 @@ "pathe": "^2.0.3" } }, + "node_modules/playwright": { + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz", + "integrity": "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "playwright-core": "1.55.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz", + "integrity": "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -8515,6 +10496,45 @@ "node": ">=4" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/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.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -8608,6 +10628,40 @@ } } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -8621,11 +10675,30 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT", + "peer": true + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "peer": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -8663,6 +10736,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT", + "peer": true + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -8750,6 +10830,65 @@ "node": ">= 6" } }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", + "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", + "license": "MIT", + "peer": true, + "dependencies": { + "readable-stream": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/readable-web-to-node-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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", + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "peer": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -8784,7 +10923,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8800,6 +10938,13 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT", + "peer": true + }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -8854,6 +10999,28 @@ "dev": true, "license": "ISC" }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-axios": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-2.6.0.tgz", + "integrity": "sha512-pOLi+Gdll3JekwuFjXO3fTq+L9lzMQGcSq7M5gIjExcl3Gu1hd4XXuf5o3+LuSBsaULQH7DiNbsqPd1chVpQGQ==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.7.0" + }, + "peerDependencies": { + "axios": "*" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -8963,7 +11130,6 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -9133,6 +11299,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-wcswidth": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.1.2.tgz", + "integrity": "sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==", + "license": "MIT" + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -9174,6 +11346,15 @@ "node": ">=0.10.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -9283,7 +11464,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -9337,7 +11517,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9347,7 +11526,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -9484,7 +11662,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -9493,6 +11670,30 @@ "node": ">=8" } }, + "node_modules/swagger-ui-dist": { + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.21.0.tgz", + "integrity": "sha512-E0K3AB6HvQd8yQNSMR7eE5bk+323AUxjtCz/4ZNKiahOlPhPJxqn3UPIGs00cyY/dhrTDJ61L7C/a8u6zhGrZg==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "license": "MIT", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -9787,6 +11988,38 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -9810,6 +12043,12 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-error": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ts-error/-/ts-error-1.0.6.tgz", + "integrity": "sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==", + "license": "MIT" + }, "node_modules/ts-jest": { "version": "29.4.1", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.1.tgz", @@ -10124,7 +12363,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/universalify": { @@ -10222,12 +12460,36 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -10250,6 +12512,15 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.15.15", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", + "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -10293,6 +12564,54 @@ "defaults": "^1.0.3" } }, + "node_modules/weaviate-client": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/weaviate-client/-/weaviate-client-3.8.1.tgz", + "integrity": "sha512-/bH5SO31gGGiI5RhvOEwQBs2DtORsssVjenWxdOQzGToAdmqRC4Oo9HZLIITX5BdFD0IqKDnY81nOZlJlHzn+g==", + "license": "BSD-3-Clause", + "dependencies": { + "abort-controller-x": "^0.4.3", + "graphql": "^16.11.0", + "graphql-request": "^6.1.0", + "long": "^5.3.2", + "nice-grpc": "^2.1.12", + "nice-grpc-client-middleware-retry": "^3.1.11", + "nice-grpc-common": "^2.0.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/weaviate-client/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, "node_modules/webpack": { "version": "5.101.3", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", @@ -10494,6 +12813,16 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -10542,7 +12871,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/wrap-ansi": { @@ -10645,6 +12974,28 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xlsx": { "version": "0.18.5", "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", @@ -10679,7 +13030,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -10692,11 +13042,22 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -10715,7 +13076,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -10756,6 +13116,25 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "license": "ISC", + "peer": true, + "peerDependencies": { + "zod": "^3.24.1" + } } } } diff --git a/package.json b/package.json index 3d6b927..cc1871e 100644 --- a/package.json +++ b/package.json @@ -20,12 +20,20 @@ "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { + "@langchain/community": "^0.3.53", + "@langchain/openai": "^0.6.9", "@nestjs/common": "^11.0.1", "@nestjs/core": "^11.0.1", "@nestjs/platform-express": "^11.0.1", + "@nestjs/swagger": "^11.2.0", "@prisma/client": "^6.14.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.2", + "langchain": "^0.3.31", + "pg": "^8.11.3", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", + "swagger-ui-express": "^5.0.1", "xlsx": "^0.18.5" }, "devDependencies": { diff --git a/prisma/migrations/20250822104301_init/migration.sql b/prisma/migrations/20250822104301_init/migration.sql new file mode 100644 index 0000000..d9a7623 --- /dev/null +++ b/prisma/migrations/20250822104301_init/migration.sql @@ -0,0 +1,26 @@ +-- Enable pgvector extension +CREATE EXTENSION IF NOT EXISTS vector; + +-- CreateTable +CREATE TABLE "icd_codes" ( + "id" TEXT NOT NULL, + "code" TEXT NOT NULL, + "display" TEXT NOT NULL, + "version" TEXT NOT NULL, + "category" TEXT NOT NULL, + "embedding" vector(1536), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "icd_codes_pkey" PRIMARY KEY ("id") +); + +-- Create unique index on code +CREATE UNIQUE INDEX "icd_codes_code_key" ON "icd_codes"("code"); + +-- Create ivfflat index for fast vector similarity search +CREATE INDEX "icd_codes_embedding_idx" ON "icd_codes" USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); + +-- Add comments for documentation +COMMENT ON COLUMN "icd_codes"."embedding" IS 'Vector embedding for semantic search using pgvector (1536 dimensions)'; +COMMENT ON INDEX "icd_codes_embedding_idx" IS 'IVFFlat index for fast cosine similarity search with 100 lists'; diff --git a/prisma/migrations/20250822104302_add_pgvector/migration.sql b/prisma/migrations/20250822104302_add_pgvector/migration.sql new file mode 100644 index 0000000..cc7de94 --- /dev/null +++ b/prisma/migrations/20250822104302_add_pgvector/migration.sql @@ -0,0 +1,27 @@ +-- Migration: Add pgvector support to icd_codes table + +-- Enable pgvector extension +CREATE EXTENSION IF NOT EXISTS vector; + +-- Add embedding column with pgvector type +ALTER TABLE "icd_codes" ADD COLUMN IF NOT EXISTS "embedding" vector(1536); + +-- Add metadata column for LangChain pgvector +ALTER TABLE "icd_codes" ADD COLUMN IF NOT EXISTS "metadata" JSONB; + +-- Add content column for LangChain pgvector +ALTER TABLE "icd_codes" ADD COLUMN IF NOT EXISTS "content" TEXT; + +-- Create ivfflat index for fast vector similarity search +CREATE INDEX IF NOT EXISTS "icd_codes_embedding_idx" ON "icd_codes" +USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); + +-- Create index on metadata for fast JSON queries +CREATE INDEX IF NOT EXISTS "icd_codes_metadata_idx" ON "icd_codes" USING GIN (metadata); + +-- Add comments for documentation +COMMENT ON COLUMN "icd_codes"."embedding" IS 'Vector embedding for semantic search using pgvector (1536 dimensions)'; +COMMENT ON COLUMN "icd_codes"."metadata" IS 'JSON metadata for LangChain pgvector operations'; +COMMENT ON COLUMN "icd_codes"."content" IS 'Text content for LangChain pgvector operations'; +COMMENT ON INDEX "icd_codes_embedding_idx" IS 'IVFFlat index for fast cosine similarity search with 100 lists'; +COMMENT ON INDEX "icd_codes_metadata_idx" IS 'GIN index for fast JSON metadata queries'; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9b04afd..693693f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,12 +1,5 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - -// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? -// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init - generator client { provider = "prisma-client-js" - output = "../generated/prisma" } datasource db { @@ -15,13 +8,18 @@ datasource db { } model IcdCode { - id String @id @default(uuid()) - code String @unique + id String @id @default(uuid()) + code String @unique display String version String - category String // "ICD9" or "ICD10" - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + category String + embedding Unsupported("vector")? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + metadata Json? + content String? + @@index([embedding]) + @@index([metadata], type: Gin) @@map("icd_codes") } diff --git a/src/app.module.ts b/src/app.module.ts index faca288..827aa67 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { IcdModule } from './icd/icd.module'; +import { HealthModule } from './health/health.module'; @Module({ - imports: [IcdModule], + imports: [IcdModule, HealthModule], controllers: [AppController], providers: [AppService], }) diff --git a/src/health/health.controller.ts b/src/health/health.controller.ts new file mode 100644 index 0000000..14152a7 --- /dev/null +++ b/src/health/health.controller.ts @@ -0,0 +1,83 @@ +import { Controller, Get } from '@nestjs/common'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiProperty, +} from '@nestjs/swagger'; + +export class HealthCheckResponseDto { + @ApiProperty({ example: 'ok' }) + status: string; + + @ApiProperty({ example: '2024-01-01T00:00:00.000Z' }) + timestamp: string; + + @ApiProperty({ example: 3600 }) + uptime: number; + + @ApiProperty({ example: 'development' }) + environment: string; + + @ApiProperty({ example: '1.0.0' }) + version: string; + + @ApiProperty({ example: { status: 'connected' } }) + database: { + status: string; + }; +} + +@ApiTags('Health') +@Controller('health') +export class HealthController { + @Get() + @ApiOperation({ + summary: 'Health check endpoint', + description: + 'Check the health status of the application and its dependencies', + }) + @ApiResponse({ + status: 200, + description: 'Application is healthy', + type: HealthCheckResponseDto, + }) + async getHealth(): Promise { + return { + status: 'ok', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + environment: process.env.NODE_ENV || 'development', + version: '1.0.0', + database: { + status: 'connected', // In real implementation, check actual DB connection + }, + }; + } + + @Get('ready') + @ApiOperation({ + summary: 'Readiness check', + description: 'Check if the application is ready to serve requests', + }) + @ApiResponse({ + status: 200, + description: 'Application is ready', + }) + async getReady() { + return { status: 'ready' }; + } + + @Get('live') + @ApiOperation({ + summary: 'Liveness check', + description: 'Check if the application is alive', + }) + @ApiResponse({ + status: 200, + description: 'Application is alive', + }) + async getLive() { + return { status: 'alive' }; + } +} diff --git a/src/health/health.module.ts b/src/health/health.module.ts new file mode 100644 index 0000000..7476abe --- /dev/null +++ b/src/health/health.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { HealthController } from './health.controller'; + +@Module({ + controllers: [HealthController], +}) +export class HealthModule {} diff --git a/src/icd/dto/icd-response.dto.ts b/src/icd/dto/icd-response.dto.ts new file mode 100644 index 0000000..3a39fbe --- /dev/null +++ b/src/icd/dto/icd-response.dto.ts @@ -0,0 +1,192 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +export class IcdCodeDto { + @ApiProperty({ + description: 'Unique identifier for the ICD code', + example: '550e8400-e29b-41d4-a716-446655440000', + }) + id: string; + + @ApiProperty({ + description: 'ICD code', + example: 'E11.9', + }) + code: string; + + @ApiProperty({ + description: 'Description of the ICD code', + example: 'Type 2 diabetes mellitus without complications', + }) + display: string; + + @ApiProperty({ + description: 'Version of the ICD standard', + example: '2024', + }) + version: string; + + @ApiProperty({ + description: 'ICD category', + example: 'ICD10', + enum: ['ICD9', 'ICD10'], + }) + category: string; + + @ApiProperty({ + description: 'Creation timestamp', + example: '2024-01-01T00:00:00.000Z', + }) + createdAt: Date; + + @ApiProperty({ + description: 'Last update timestamp', + example: '2024-01-01T00:00:00.000Z', + }) + updatedAt: Date; +} + +export class PaginationMetaDto { + @ApiProperty({ + description: 'Current page number', + example: 1, + }) + currentPage: number; + + @ApiProperty({ + description: 'Total number of pages', + example: 10, + }) + totalPages: number; + + @ApiProperty({ + description: 'Total number of items', + example: 100, + }) + totalItems: number; + + @ApiProperty({ + description: 'Number of items per page', + example: 10, + }) + itemsPerPage: number; + + @ApiProperty({ + description: 'Whether there is a next page', + example: true, + }) + hasNextPage: boolean; + + @ApiProperty({ + description: 'Whether there is a previous page', + example: false, + }) + hasPreviousPage: boolean; +} + +export class IcdSearchResponseDto { + @ApiProperty({ + description: 'Request success status', + example: true, + }) + success: boolean; + + @ApiProperty({ + description: 'Array of ICD codes', + type: [IcdCodeDto], + }) + data: IcdCodeDto[]; + + @ApiProperty({ + description: 'Pagination metadata', + type: PaginationMetaDto, + }) + pagination: PaginationMetaDto; + + @ApiPropertyOptional({ + description: 'Response message', + example: 'ICD codes retrieved successfully', + }) + message?: string; +} + +export class IcdImportResponseDto { + @ApiProperty({ + description: 'Request success status', + example: true, + }) + success: boolean; + + @ApiProperty({ + description: 'Success message', + example: 'ICD data imported successfully', + }) + message: string; + + @ApiProperty({ + description: 'Import statistics', + example: { + icd9Count: 150, + icd10Count: 250, + total: 400, + }, + }) + data: { + icd9Count: number; + icd10Count: number; + total: number; + }; +} + +export class IcdStatisticsDto { + @ApiProperty({ + description: 'Total number of ICD9 codes', + example: 150, + }) + icd9Count: number; + + @ApiProperty({ + description: 'Total number of ICD10 codes', + example: 250, + }) + icd10Count: number; + + @ApiProperty({ + description: 'Total number of all ICD codes', + example: 400, + }) + total: number; +} + +export class IcdStatisticsResponseDto { + @ApiProperty({ + description: 'Request success status', + example: true, + }) + success: boolean; + + @ApiProperty({ + description: 'ICD statistics data', + type: IcdStatisticsDto, + }) + data: IcdStatisticsDto; +} + +export class ErrorResponseDto { + @ApiProperty({ + description: 'Request success status', + example: false, + }) + success: boolean; + + @ApiProperty({ + description: 'Error message', + example: 'Failed to process request', + }) + message: string; + + @ApiPropertyOptional({ + description: 'Detailed error information', + example: 'Database connection failed', + }) + error?: string; +} diff --git a/src/icd/dto/search-icd.dto.ts b/src/icd/dto/search-icd.dto.ts index 8adf368..9a3e280 100644 --- a/src/icd/dto/search-icd.dto.ts +++ b/src/icd/dto/search-icd.dto.ts @@ -1,6 +1,62 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsOptional, + IsString, + IsNumber, + IsEnum, + Min, + Max, +} from 'class-validator'; +import { Type } from 'class-transformer'; + +export enum IcdCategory { + ICD9 = 'ICD9', + ICD10 = 'ICD10', +} + export class SearchIcdDto { - category?: 'ICD9' | 'ICD10'; + @ApiPropertyOptional({ + description: 'ICD category to filter by', + enum: IcdCategory, + example: 'ICD10', + }) + @IsOptional() + @IsEnum(IcdCategory) + category?: IcdCategory; + + @ApiPropertyOptional({ + description: 'Search term for ICD code or description', + example: 'diabetes', + minLength: 1, + maxLength: 100, + }) + @IsOptional() + @IsString() search?: string; + + @ApiPropertyOptional({ + description: 'Page number for pagination', + example: 1, + minimum: 1, + default: 1, + }) + @IsOptional() + @Type(() => Number) + @IsNumber() + @Min(1) page?: number; + + @ApiPropertyOptional({ + description: 'Number of items per page', + example: 10, + minimum: 1, + maximum: 100, + default: 10, + }) + @IsOptional() + @Type(() => Number) + @IsNumber() + @Min(1) + @Max(100) limit?: number; } diff --git a/src/icd/icd.controller.ts b/src/icd/icd.controller.ts index d97986c..24fada4 100644 --- a/src/icd/icd.controller.ts +++ b/src/icd/icd.controller.ts @@ -1,7 +1,22 @@ import { Controller, Get, Post, Query, Logger } from '@nestjs/common'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiQuery, + ApiBadRequestResponse, + ApiInternalServerErrorResponse, +} from '@nestjs/swagger'; import { IcdService } from './icd.service'; import { SearchIcdDto } from './dto/search-icd.dto'; +import { + IcdSearchResponseDto, + IcdImportResponseDto, + IcdStatisticsResponseDto, + ErrorResponseDto, +} from './dto/icd-response.dto'; +@ApiTags('ICD') @Controller('icd') export class IcdController { private readonly logger = new Logger(IcdController.name); @@ -9,7 +24,25 @@ export class IcdController { constructor(private readonly icdService: IcdService) {} @Post('import') - async importData() { + @ApiOperation({ + summary: 'Import ICD data from Excel files', + description: + 'Import ICD-9 and ICD-10 codes from Excel files located in the test directory. This operation will process both ICD files and insert/update the database with the latest codes.', + }) + @ApiResponse({ + status: 200, + description: 'ICD data imported successfully', + type: IcdImportResponseDto, + }) + @ApiBadRequestResponse({ + description: 'Bad request - Invalid file format or missing files', + type: ErrorResponseDto, + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error during import process', + type: ErrorResponseDto, + }) + async importData(): Promise { try { this.logger.log('Starting ICD data import...'); const result = await this.icdService.importIcdData(); @@ -20,21 +53,62 @@ export class IcdController { }; } catch (error) { this.logger.error('Error importing ICD data:', error); - return { - success: false, - message: 'Failed to import ICD data', - error: error.message, - }; + throw error; } } @Get('search') + @ApiOperation({ + summary: 'Search ICD codes with filters and pagination', + description: + 'Search for ICD codes using various filters like category, search term, with pagination support. Returns a paginated list of matching ICD codes.', + }) + @ApiQuery({ + name: 'category', + required: false, + description: 'Filter by ICD category', + enum: ['ICD9', 'ICD10'], + example: 'ICD10', + }) + @ApiQuery({ + name: 'search', + required: false, + description: 'Search term for ICD code or description', + example: 'diabetes', + }) + @ApiQuery({ + name: 'page', + required: false, + description: 'Page number for pagination', + example: 1, + type: 'number', + }) + @ApiQuery({ + name: 'limit', + required: false, + description: 'Number of items per page (max 100)', + example: 10, + type: 'number', + }) + @ApiResponse({ + status: 200, + description: 'ICD codes retrieved successfully', + type: IcdSearchResponseDto, + }) + @ApiBadRequestResponse({ + description: 'Bad request - Invalid query parameters', + type: ErrorResponseDto, + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error during search', + type: ErrorResponseDto, + }) async searchIcdCodes( @Query('category') category?: string, @Query('search') search?: string, @Query('page') page?: string, @Query('limit') limit?: string, - ) { + ): Promise { try { const pageNum = page ? parseInt(page, 10) : 1; const limitNum = limit ? parseInt(limit, 10) : 10; @@ -48,20 +122,38 @@ export class IcdController { return { success: true, - ...result, + data: result.data, + pagination: { + currentPage: result.page, + totalPages: result.totalPages, + totalItems: result.total, + itemsPerPage: result.limit, + hasNextPage: result.page < result.totalPages, + hasPreviousPage: result.page > 1, + }, }; } catch (error) { this.logger.error('Error searching ICD codes:', error); - return { - success: false, - message: 'Failed to search ICD codes', - error: error.message, - }; + throw error; } } @Get('statistics') - async getStatistics() { + @ApiOperation({ + summary: 'Get ICD database statistics', + description: + 'Retrieve statistics about the ICD database including total counts for ICD-9 and ICD-10 codes, and last import information.', + }) + @ApiResponse({ + status: 200, + description: 'Statistics retrieved successfully', + type: IcdStatisticsResponseDto, + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error while fetching statistics', + type: ErrorResponseDto, + }) + async getStatistics(): Promise { try { const stats = await this.icdService.getStatistics(); return { @@ -70,11 +162,7 @@ export class IcdController { }; } catch (error) { this.logger.error('Error getting statistics:', error); - return { - success: false, - message: 'Failed to get statistics', - error: error.message, - }; + throw error; } } } diff --git a/src/icd/icd.module.ts b/src/icd/icd.module.ts index 7af14a2..5d15022 100644 --- a/src/icd/icd.module.ts +++ b/src/icd/icd.module.ts @@ -1,10 +1,12 @@ import { Module } from '@nestjs/common'; import { IcdController } from './icd.controller'; import { IcdService } from './icd.service'; +import { PgVectorModule } from './pgvector.module'; @Module({ controllers: [IcdController], providers: [IcdService], - exports: [IcdService], + imports: [PgVectorModule], + exports: [IcdService, PgVectorModule], }) export class IcdModule {} diff --git a/src/icd/icd.service.ts b/src/icd/icd.service.ts index 7fdf3f6..23ed54a 100644 --- a/src/icd/icd.service.ts +++ b/src/icd/icd.service.ts @@ -181,6 +181,16 @@ export class IcdService { skip, take: limit, orderBy: { code: 'asc' }, + select: { + id: true, + code: true, + display: true, + version: true, + category: true, + createdAt: true, + updatedAt: true, + // Exclude embedding field to avoid deserialization error + }, }), this.prisma.icdCode.count({ where }), ]); diff --git a/src/icd/pgvector.controller.ts b/src/icd/pgvector.controller.ts new file mode 100644 index 0000000..4eefd30 --- /dev/null +++ b/src/icd/pgvector.controller.ts @@ -0,0 +1,670 @@ +import { + Controller, + Get, + Post, + Query, + Body, + HttpStatus, + ValidationPipe, + UsePipes, +} from '@nestjs/common'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiQuery, + ApiBody, + ApiProperty, + ApiConsumes, + ApiProduces, +} from '@nestjs/swagger'; +import { PgVectorService, VectorSearchResult } from './pgvector.service'; + +export class VectorSearchDto { + @ApiProperty({ + description: 'Search query text for vector similarity search', + example: 'diabetes mellitus type 2', + minLength: 1, + maxLength: 500, + }) + query: string; + + @ApiProperty({ + description: 'Maximum number of results to return', + example: 10, + required: false, + minimum: 1, + maximum: 100, + default: 10, + }) + limit?: number; + + @ApiProperty({ + description: 'ICD category filter to narrow down search results', + example: 'ICD10', + required: false, + enum: ['ICD9', 'ICD10'], + default: undefined, + }) + category?: string; + + @ApiProperty({ + description: 'Similarity threshold (0.0 - 1.0) for filtering results', + example: 0.7, + required: false, + minimum: 0.0, + maximum: 1.0, + default: 0.7, + }) + threshold?: number; +} + +export class EmbeddingRequestDto { + @ApiProperty({ + description: 'Text to generate vector embedding for', + example: 'diabetes mellitus', + minLength: 1, + maxLength: 1000, + }) + text: string; + + @ApiProperty({ + description: 'Embedding model to use for generation', + example: 'text-embedding-ada-002', + required: false, + default: 'text-embedding-ada-002', + }) + model?: string; +} + +export class VectorSearchResponseDto { + @ApiProperty({ + description: 'Array of search results with similarity scores', + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'string', + description: 'Unique identifier for the ICD code', + example: 'uuid-123', + }, + code: { + type: 'string', + description: 'ICD code (e.g., E11.9)', + example: 'E11.9', + }, + display: { + type: 'string', + description: 'Human readable description of the ICD code', + example: 'Type 2 diabetes mellitus without complications', + }, + version: { + type: 'string', + description: 'ICD version (e.g., ICD-10-CM)', + example: 'ICD-10-CM', + }, + category: { + type: 'string', + description: 'ICD category (ICD9 or ICD10)', + example: 'ICD10', + }, + similarity: { + type: 'number', + description: 'Similarity score between 0 and 1', + example: 0.89, + }, + }, + }, + }) + data: VectorSearchResult[]; + + @ApiProperty({ + description: 'Total number of results found', + example: 5, + minimum: 0, + }) + total: number; + + @ApiProperty({ + description: 'Search query that was used', + example: 'diabetes mellitus type 2', + }) + query: string; +} + +export class EmbeddingStatsResponseDto { + @ApiProperty({ + description: 'Total number of ICD codes in the system', + example: 1000, + minimum: 0, + }) + total: number; + + @ApiProperty({ + description: 'Number of ICD codes with generated embeddings', + example: 500, + minimum: 0, + }) + withEmbeddings: number; + + @ApiProperty({ + description: 'Number of ICD codes without embeddings', + example: 500, + minimum: 0, + }) + withoutEmbeddings: number; + + @ApiProperty({ + description: 'Percentage of codes with embeddings', + example: 50.0, + minimum: 0, + maximum: 100, + }) + percentage: number; + + @ApiProperty({ + description: 'Current status of the vector store', + example: 'Initialized', + enum: ['Initialized', 'Not Initialized', 'Error'], + }) + vectorStoreStatus: string; +} + +export class VectorStoreStatusDto { + @ApiProperty({ + description: 'Whether the vector store is currently initialized', + example: true, + }) + initialized: boolean; + + @ApiProperty({ + description: 'Number of documents currently in the vector store', + example: 1000, + minimum: 0, + }) + documentCount: number; + + @ApiProperty({ + description: 'Embedding model currently being used', + example: 'OpenAI text-embedding-ada-002', + enum: ['OpenAI text-embedding-ada-002', 'Not Available'], + }) + embeddingModel: string; + + @ApiProperty({ + description: 'Timestamp of last vector store update', + example: '2024-01-01T00:00:00.000Z', + }) + lastUpdated: Date; +} + +export class InitializeResponseDto { + @ApiProperty({ + description: 'Success message', + example: 'Pgvector store initialized successfully', + }) + message: string; + + @ApiProperty({ + description: 'Number of documents loaded into vector store', + example: 1000, + minimum: 0, + }) + documentCount: number; +} + +export class RefreshResponseDto { + @ApiProperty({ + description: 'Success message', + example: 'Pgvector store refreshed successfully', + }) + message: string; + + @ApiProperty({ + description: 'Number of documents in refreshed vector store', + example: 1000, + minimum: 0, + }) + documentCount: number; +} + +export class GenerateEmbeddingResponseDto { + @ApiProperty({ + description: 'Generated vector embedding array', + type: 'array', + items: { type: 'number' }, + example: [0.1, 0.2, 0.3, -0.1, 0.5], + }) + embedding: number[]; + + @ApiProperty({ + description: 'Number of dimensions in the embedding vector', + example: 1536, + minimum: 1, + }) + dimensions: number; + + @ApiProperty({ + description: 'Model used to generate the embedding', + example: 'text-embedding-ada-002', + }) + model: string; +} + +export class GenerateAllEmbeddingsResponseDto { + @ApiProperty({ + description: 'Number of embeddings successfully processed', + example: 500, + minimum: 0, + }) + processed: number; + + @ApiProperty({ + description: 'Number of errors encountered during processing', + example: 0, + minimum: 0, + }) + errors: number; + + @ApiProperty({ + description: 'Summary message of the operation', + example: 'Processed 500 embeddings with 0 errors', + }) + message: string; +} + +@ApiTags('PgVector Operations') +@Controller('pgvector') +@UsePipes(new ValidationPipe({ transform: true })) +export class PgVectorController { + constructor(private readonly pgVectorService: PgVectorService) {} + + @Post('search') + @ApiOperation({ + summary: 'PgVector similarity search', + description: + 'Search ICD codes using pgvector similarity with the given query. Returns results ordered by similarity score.', + tags: ['PgVector Operations'], + }) + @ApiConsumes('application/json') + @ApiProduces('application/json') + @ApiBody({ + type: VectorSearchDto, + description: 'Search parameters for pgvector similarity search', + examples: { + diabetes: { + summary: 'Search for diabetes', + value: { + query: 'diabetes mellitus type 2', + limit: 10, + category: 'ICD10', + threshold: 0.7, + }, + }, + heart: { + summary: 'Search for heart conditions', + value: { + query: 'heart attack myocardial infarction', + limit: 5, + category: 'ICD10', + threshold: 0.8, + }, + }, + }, + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Search results with similarity scores', + type: VectorSearchResponseDto, + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Invalid search parameters or query', + schema: { + type: 'object', + properties: { + statusCode: { type: 'number', example: 400 }, + message: { type: 'string', example: 'Query is required' }, + error: { type: 'string', example: 'Bad Request' }, + }, + }, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: 'Internal server error during pgvector search', + schema: { + type: 'object', + properties: { + statusCode: { type: 'number', example: 500 }, + message: { type: 'string', example: 'Internal server error' }, + error: { type: 'string', example: 'Internal Server Error' }, + }, + }, + }) + async vectorSearch( + @Body() searchDto: VectorSearchDto, + ): Promise { + const results = await this.pgVectorService.vectorSearch( + searchDto.query, + searchDto.limit || 10, + searchDto.category, + searchDto.threshold || 0.7, + ); + + return { + data: results, + total: results.length, + query: searchDto.query, + }; + } + + @Get('search') + @ApiOperation({ + summary: 'PgVector search via GET', + description: + 'Search ICD codes using pgvector similarity via query parameters. Alternative to POST method.', + tags: ['PgVector Operations'], + }) + @ApiConsumes('application/json') + @ApiProduces('application/json') + @ApiQuery({ + name: 'query', + description: 'Search query text for pgvector similarity search', + example: 'diabetes mellitus type 2', + required: true, + type: 'string', + }) + @ApiQuery({ + name: 'limit', + description: 'Maximum number of results to return', + example: 10, + required: false, + type: 'number', + minimum: 1, + maximum: 100, + }) + @ApiQuery({ + name: 'category', + description: 'ICD category filter to narrow down search results', + example: 'ICD10', + required: false, + type: 'string', + enum: ['ICD9', 'ICD10'], + }) + @ApiQuery({ + name: 'threshold', + description: 'Similarity threshold (0.0 - 1.0) for filtering results', + example: 0.7, + required: false, + type: 'number', + minimum: 0.0, + maximum: 1.0, + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Search results with similarity scores', + type: VectorSearchResponseDto, + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Invalid query parameters', + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: 'Internal server error during pgvector search', + }) + async vectorSearchGet( + @Query('query') query: string, + @Query('limit') limit?: string, + @Query('category') category?: string, + @Query('threshold') threshold?: string, + ): Promise { + const results = await this.pgVectorService.vectorSearch( + query, + limit ? parseInt(limit) : 10, + category, + threshold ? parseFloat(threshold) : 0.7, + ); + + return { + data: results, + total: results.length, + query, + }; + } + + @Post('hybrid-search') + @ApiOperation({ + summary: 'Hybrid search (PgVector + Text)', + description: + 'Combine pgvector similarity with text search for better and more accurate results. Combines semantic understanding with traditional text matching.', + tags: ['PgVector Operations'], + }) + @ApiConsumes('application/json') + @ApiProduces('application/json') + @ApiBody({ + type: VectorSearchDto, + description: 'Search parameters for hybrid search', + examples: { + diabetes: { + summary: 'Hybrid search for diabetes', + value: { + query: 'diabetes mellitus type 2', + limit: 15, + category: 'ICD10', + }, + }, + }, + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Hybrid search results combining pgvector and text search', + type: VectorSearchResponseDto, + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Invalid search parameters', + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: 'Internal server error during hybrid search', + }) + async hybridSearch( + @Body() searchDto: VectorSearchDto, + ): Promise { + const results = await this.pgVectorService.hybridSearch( + searchDto.query, + searchDto.limit || 10, + searchDto.category, + ); + + return { + data: results, + total: results.length, + query: searchDto.query, + }; + } + + @Post('generate-embedding') + @ApiOperation({ + summary: 'Generate text embedding', + description: + 'Generate vector embedding for the given text using OpenAI. Returns 1536-dimensional vector.', + tags: ['PgVector Operations'], + }) + @ApiConsumes('application/json') + @ApiProduces('application/json') + @ApiBody({ + type: EmbeddingRequestDto, + description: 'Text to generate embedding for', + examples: { + diabetes: { + summary: 'Generate embedding for diabetes text', + value: { + text: 'diabetes mellitus', + model: 'text-embedding-ada-002', + }, + }, + heart: { + summary: 'Generate embedding for heart condition', + value: { + text: 'acute myocardial infarction', + model: 'text-embedding-ada-002', + }, + }, + }, + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Generated embedding vector with metadata', + type: GenerateEmbeddingResponseDto, + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Invalid text input', + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: 'Error generating embedding', + }) + async generateEmbedding( + @Body() requestDto: EmbeddingRequestDto, + ): Promise { + const embedding = await this.pgVectorService.generateEmbedding( + requestDto.text, + requestDto.model, + ); + + return { + embedding, + dimensions: embedding.length, + model: requestDto.model || 'text-embedding-ada-002', + }; + } + + @Post('generate-and-store-all-embeddings') + @ApiOperation({ + summary: 'Generate and store embeddings for all ICD codes', + description: + 'Batch generate embeddings for all ICD codes and store them in the database with pgvector. This process may take some time depending on the number of codes.', + tags: ['PgVector Operations'], + }) + @ApiConsumes('application/json') + @ApiProduces('application/json') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Embedding generation and storage results summary', + type: GenerateAllEmbeddingsResponseDto, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: 'Error during batch embedding generation and storage', + }) + async generateAndStoreAllEmbeddings(): Promise { + const result = await this.pgVectorService.generateAndStoreAllEmbeddings(); + + return { + ...result, + message: `Processed ${result.processed} embeddings with ${result.errors} errors`, + }; + } + + @Get('stats') + @ApiOperation({ + summary: 'Get embedding statistics', + description: + 'Get comprehensive statistics about ICD codes and their embedding status in the pgvector store.', + tags: ['PgVector Operations'], + }) + @ApiProduces('application/json') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Embedding statistics and pgvector store status', + type: EmbeddingStatsResponseDto, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: 'Error retrieving statistics', + }) + async getEmbeddingStats(): Promise { + return await this.pgVectorService.getEmbeddingStats(); + } + + @Get('status') + @ApiOperation({ + summary: 'Get pgvector store status', + description: + 'Get current operational status of the pgvector store including initialization state and document count.', + tags: ['PgVector Operations'], + }) + @ApiProduces('application/json') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Current pgvector store status and configuration', + type: VectorStoreStatusDto, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: 'Error retrieving pgvector store status', + }) + async getVectorStoreStatus(): Promise { + return await this.pgVectorService.getVectorStoreStatus(); + } + + @Post('initialize') + @ApiOperation({ + summary: 'Initialize pgvector store', + description: + 'Initialize or reinitialize the pgvector store with all available ICD codes. This loads codes from the database into the pgvector store.', + tags: ['PgVector Operations'], + }) + @ApiConsumes('application/json') + @ApiProduces('application/json') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Pgvector store initialization results', + type: InitializeResponseDto, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: 'Error during pgvector store initialization', + }) + async initializeVectorStore(): Promise { + await this.pgVectorService.initializeVectorStore(); + const status = await this.pgVectorService.getVectorStoreStatus(); + + return { + message: 'Pgvector store initialized successfully', + documentCount: status.documentCount, + }; + } + + @Post('refresh') + @ApiOperation({ + summary: 'Refresh pgvector store', + description: + 'Refresh the pgvector store with the latest ICD codes data from the database. Useful after data updates.', + tags: ['PgVector Operations'], + }) + @ApiConsumes('application/json') + @ApiProduces('application/json') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Pgvector store refresh results', + type: RefreshResponseDto, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: 'Error during pgvector store refresh', + }) + async refreshVectorStore(): Promise { + await this.pgVectorService.refreshVectorStore(); + const status = await this.pgVectorService.getVectorStoreStatus(); + + return { + message: 'Pgvector store refreshed successfully', + documentCount: status.documentCount, + }; + } +} diff --git a/src/icd/pgvector.module.ts b/src/icd/pgvector.module.ts new file mode 100644 index 0000000..2cd7073 --- /dev/null +++ b/src/icd/pgvector.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { PgVectorController } from './pgvector.controller'; +import { PgVectorService } from './pgvector.service'; + +@Module({ + controllers: [PgVectorController], + providers: [PgVectorService], + exports: [PgVectorService], +}) +export class PgVectorModule {} diff --git a/src/icd/pgvector.service.ts b/src/icd/pgvector.service.ts new file mode 100644 index 0000000..0df4e8e --- /dev/null +++ b/src/icd/pgvector.service.ts @@ -0,0 +1,611 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { PrismaClient } from '../../generated/prisma'; +import { OpenAIEmbeddings } from '@langchain/openai'; +import { PGVectorStore } from '@langchain/community/vectorstores/pgvector'; +import { Document } from 'langchain/document'; +import { Pool } from 'pg'; + +export interface VectorSearchResult { + id: string; + code: string; + display: string; + version: string; + category: string; + similarity: number; +} + +export interface EmbeddingRequest { + text: string; + model?: string; +} + +@Injectable() +export class PgVectorService { + private readonly logger = new Logger(PgVectorService.name); + private readonly prisma = new PrismaClient(); + private readonly pool: Pool; + private vectorStore: PGVectorStore | null = null; + private embeddings: OpenAIEmbeddings | null = null; + + constructor() { + // Initialize PostgreSQL connection pool + this.pool = new Pool({ + connectionString: process.env.DATABASE_URL, + max: 20, + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 2000, + }); + + this.initializeEmbeddings(); + } + + /** + * Initialize OpenAI embeddings + */ + private async initializeEmbeddings() { + try { + const apiKey = process.env.OPENAI_API_KEY; + if (!apiKey) { + this.logger.error( + 'OPENAI_API_KEY not found. Vector operations require OpenAI API key.', + ); + throw new Error('OPENAI_API_KEY is required for vector operations'); + } + + const apiModel = process.env.OPENAI_API_MODEL; + const modelName = apiModel || 'text-embedding-ada-002'; + + this.embeddings = new OpenAIEmbeddings({ + openAIApiKey: apiKey, + modelName: modelName, + maxConcurrency: 5, + }); + + this.logger.log( + `OpenAI embeddings initialized successfully with model: ${modelName}`, + ); + } catch (error) { + this.logger.error('Failed to initialize OpenAI embeddings:', error); + throw new Error( + `Failed to initialize OpenAI embeddings: ${error.message}`, + ); + } + } + + /** + * Initialize pgvector store dengan LangChain + */ + async initializeVectorStore(): Promise { + try { + this.logger.log('Initializing pgvector store...'); + + if (!this.embeddings) { + throw new Error( + 'OpenAI embeddings not initialized. Cannot create vector store.', + ); + } + + // Get database connection string + const connectionString = process.env.DATABASE_URL; + if (!connectionString) { + throw new Error('DATABASE_URL not found'); + } + + // Initialize pgvector store without inserting data + this.vectorStore = await PGVectorStore.initialize(this.embeddings, { + postgresConnectionOptions: { + connectionString, + }, + tableName: 'icd_codes', + columns: { + idColumnName: 'id', + vectorColumnName: 'embedding', + contentColumnName: 'content', + metadataColumnName: 'metadata', + }, + }); + + this.logger.log('Pgvector store initialized successfully'); + } catch (error) { + this.logger.error('Error initializing pgvector store:', error); + throw error; + } + } + + /** + * Generate embedding untuk text menggunakan OpenAI + */ + async generateEmbedding( + text: string, + model: string = 'text-embedding-ada-002', + ): Promise { + try { + this.logger.log( + `Generating embedding for text: ${text.substring(0, 100)}...`, + ); + + if (!this.embeddings) { + throw new Error( + 'OpenAI embeddings not initialized. Please check your API configuration.', + ); + } + + // Use OpenAI embeddings + const embedding = await this.embeddings.embedQuery(text); + this.logger.log( + `Generated OpenAI embedding with ${embedding.length} dimensions`, + ); + return embedding; + } catch (error) { + this.logger.error('Error generating embedding:', error); + throw new Error(`Failed to generate embedding: ${error.message}`); + } + } + + /** + * Generate dan simpan embeddings untuk sample ICD codes (default: 100) + */ + async generateAndStoreAllEmbeddings(limit: number = 100): Promise<{ + processed: number; + errors: number; + totalSample: number; + }> { + try { + this.logger.log( + `Starting batch embedding generation and storage for sample ${limit} ICD codes...`, + ); + + // Get sample ICD codes without embeddings using raw SQL + const codesWithoutEmbedding = await this.pool.query( + 'SELECT id, code, display, version, category FROM icd_codes WHERE embedding IS NULL LIMIT $1', + [limit], + ); + + if (codesWithoutEmbedding.rows.length === 0) { + this.logger.log('All ICD codes already have embeddings'); + return { processed: 0, errors: 0, totalSample: 0 }; + } + + this.logger.log( + `Found ${codesWithoutEmbedding.rows.length} sample codes without embeddings (limited to ${limit})`, + ); + + let processed = 0; + let errors = 0; + + // Process each code + for (let i = 0; i < codesWithoutEmbedding.rows.length; i++) { + const code = codesWithoutEmbedding.rows[i]; + try { + // Create text representation for embedding + const text = `${code.code} - ${code.display}`; + + // Generate embedding + const embedding = await this.generateEmbedding(text); + + // Convert embedding array to proper vector format for pgvector + const vectorString = `[${embedding.join(',')}]`; + + // Update database with embedding, metadata, and content using raw SQL + await this.pool.query( + `UPDATE icd_codes + SET embedding = $1::vector, + metadata = $2::jsonb, + content = $3 + WHERE id = $4`, + [ + vectorString, + JSON.stringify({ + id: code.id, + code: code.code, + display: code.display, + version: code.version, + category: code.category, + }), + text, + code.id, + ], + ); + + processed++; + + if (processed % 10 === 0) { + this.logger.log( + `Processed ${processed}/${codesWithoutEmbedding.rows.length} sample embeddings`, + ); + } + } catch (error) { + this.logger.error(`Error processing code ${code.code}:`, error); + errors++; + } + } + + this.logger.log( + `Sample embedding generation and storage completed. Processed: ${processed}, Errors: ${errors}, Total Sample: ${codesWithoutEmbedding.rows.length}`, + ); + return { + processed, + errors, + totalSample: codesWithoutEmbedding.rows.length, + }; + } catch (error) { + this.logger.error('Error in generateAndStoreAllEmbeddings:', error); + throw error; + } + } + + /** + * Generate dan simpan embeddings untuk sample ICD codes dengan kategori tertentu + */ + async generateAndStoreSampleEmbeddingsByCategory( + category: string, + limit: number = 100, + ): Promise<{ + processed: number; + errors: number; + totalSample: number; + category: string; + }> { + try { + this.logger.log( + `Starting batch embedding generation for sample ${limit} ICD codes in category: ${category}`, + ); + + // Get sample ICD codes by category without embeddings using raw SQL + const codesWithoutEmbedding = await this.pool.query( + 'SELECT id, code, display, version, category FROM icd_codes WHERE embedding IS NULL AND category = $1 LIMIT $2', + [category, limit], + ); + + if (codesWithoutEmbedding.rows.length === 0) { + this.logger.log( + `No ICD codes found in category '${category}' without embeddings`, + ); + return { processed: 0, errors: 0, totalSample: 0, category }; + } + + this.logger.log( + `Found ${codesWithoutEmbedding.rows.length} sample codes in category '${category}' without embeddings (limited to ${limit})`, + ); + + let processed = 0; + let errors = 0; + + // Process each code + for (let i = 0; i < codesWithoutEmbedding.rows.length; i++) { + const code = codesWithoutEmbedding.rows[i]; + try { + // Create text representation for embedding + const text = `${code.code} - ${code.display}`; + + // Generate embedding + const embedding = await this.generateEmbedding(text); + + // Convert embedding array to proper vector format for pgvector + const vectorString = `[${embedding.join(',')}]`; + + // Update database with embedding, metadata, and content using raw SQL + await this.pool.query( + `UPDATE icd_codes + SET embedding = $1::vector, + metadata = $2::jsonb, + content = $3 + WHERE id = $4`, + [ + vectorString, + JSON.stringify({ + id: code.id, + code: code.code, + display: code.display, + version: code.version, + category: code.category, + }), + text, + code.id, + ], + ); + + processed++; + + if (processed % 10 === 0) { + this.logger.log( + `Processed ${processed}/${codesWithoutEmbedding.rows.length} sample embeddings in category '${category}'`, + ); + } + } catch (error) { + this.logger.error(`Error processing code ${code.code}:`, error); + errors++; + } + } + + this.logger.log( + `Sample embedding generation completed for category '${category}'. Processed: ${processed}, Errors: ${errors}, Total Sample: ${codesWithoutEmbedding.rows.length}`, + ); + return { + processed, + errors, + totalSample: codesWithoutEmbedding.rows.length, + category, + }; + } catch (error) { + this.logger.error( + `Error in generateAndStoreSampleEmbeddingsByCategory for category '${category}':`, + error, + ); + throw error; + } + } + + /** + * Vector similarity search menggunakan pgvector + */ + async vectorSearch( + query: string, + limit: number = 10, + category?: string, + threshold: number = 0.7, + ): Promise { + try { + this.logger.log(`Performing pgvector search for: ${query}`); + + if (!this.embeddings) { + throw new Error('OpenAI embeddings not initialized'); + } + + // Generate embedding for query + const queryEmbedding = await this.generateEmbedding(query); + + // Convert embedding array to proper vector format for pgvector + const vectorString = `[${queryEmbedding.join(',')}]`; + + // Build SQL query for vector similarity search + let sql = ` + SELECT + id, code, display, version, category, + 1 - (embedding <=> $1::vector) as similarity + FROM icd_codes + WHERE embedding IS NOT NULL + `; + + const params: any[] = [vectorString]; + let paramIndex = 2; + + if (category) { + sql += ` AND category = $${paramIndex}`; + params.push(category); + paramIndex++; + } + + sql += ` ORDER BY embedding <=> $1::vector ASC LIMIT $${paramIndex}`; + params.push(limit); + + // Execute raw SQL query + const result = await this.pool.query(sql, params); + + // Transform and filter results + const filteredResults: VectorSearchResult[] = result.rows + .filter((row: any) => row.similarity >= threshold) + .map((row: any) => ({ + id: row.id, + code: row.code, + display: row.display, + version: row.version, + category: row.category, + similarity: parseFloat(row.similarity), + })); + + this.logger.log( + `Pgvector search returned ${filteredResults.length} results for query: "${query}"`, + ); + return filteredResults; + } catch (error) { + this.logger.error('Error in pgvector search:', error); + throw error; + } + } + + /** + * Hybrid search: combine vector similarity dengan text search + */ + async hybridSearch( + query: string, + limit: number = 10, + category?: string, + vectorWeight: number = 0.7, + textWeight: number = 0.3, + ): Promise { + try { + this.logger.log(`Performing hybrid search for: ${query}`); + + // Get vector search results + const vectorResults = await this.vectorSearch( + query, + limit * 2, + category, + 0.5, + ); + + // Get text search results + const textResults = await this.textSearch(query, limit * 2, category); + + // Combine and score results + const combinedResults = new Map(); + + // Add vector results + for (const result of vectorResults) { + combinedResults.set(result.id, { + ...result, + similarity: result.similarity * vectorWeight, + }); + } + + // Add text results with text scoring + for (const result of textResults) { + const existing = combinedResults.get(result.id); + if (existing) { + // Combine scores + existing.similarity += (result.similarity || 0.5) * textWeight; + } else { + combinedResults.set(result.id, { + ...result, + similarity: (result.similarity || 0.5) * textWeight, + }); + } + } + + // Convert to array, sort by combined score, and limit + const results = Array.from(combinedResults.values()); + results.sort((a, b) => b.similarity - a.similarity); + + return results.slice(0, limit); + } catch (error) { + this.logger.error('Error in hybrid search:', error); + throw error; + } + } + + /** + * Text-based search dengan scoring + */ + private async textSearch( + query: string, + limit: number, + category?: string, + ): Promise { + try { + let sql = 'SELECT id, code, display, version, category FROM icd_codes'; + const params: any[] = []; + let whereConditions: string[] = []; + let paramIndex = 1; + + if (category) { + whereConditions.push(`category = $${paramIndex}`); + params.push(category); + paramIndex++; + } + + if (query) { + whereConditions.push( + `(code ILIKE $${paramIndex} OR display ILIKE $${paramIndex})`, + ); + params.push(`%${query}%`); + paramIndex++; + } + + if (whereConditions.length > 0) { + sql += ' WHERE ' + whereConditions.join(' AND '); + } + + sql += ' ORDER BY code ASC LIMIT $' + paramIndex; + params.push(limit); + + const result = await this.pool.query(sql, params); + + return result.rows.map((code) => ({ + id: code.id, + code: code.code, + display: code.display, + version: code.version, + category: code.category, + similarity: 0.5, // Default text similarity score + })); + } catch (error) { + this.logger.error('Error in text search:', error); + throw error; + } + } + + /** + * Get embedding statistics + */ + async getEmbeddingStats(): Promise<{ + total: number; + withEmbeddings: number; + withoutEmbeddings: number; + percentage: number; + vectorStoreStatus: string; + }> { + try { + // Use raw SQL to get embedding statistics + const [totalResult, withEmbeddingsResult] = await Promise.all([ + this.pool.query('SELECT COUNT(*) as count FROM icd_codes'), + this.pool.query( + 'SELECT COUNT(*) as count FROM icd_codes WHERE embedding IS NOT NULL', + ), + ]); + + const total = parseInt(totalResult.rows[0].count); + const withEmbeddings = parseInt(withEmbeddingsResult.rows[0].count); + const withoutEmbeddings = total - withEmbeddings; + const percentage = total > 0 ? (withEmbeddings / total) * 100 : 0; + const vectorStoreStatus = this.vectorStore + ? 'Initialized' + : 'Not Initialized'; + + return { + total, + withEmbeddings, + withoutEmbeddings, + percentage: Math.round(percentage * 100) / 100, + vectorStoreStatus, + }; + } catch (error) { + this.logger.error('Error getting embedding stats:', error); + throw error; + } + } + + /** + * Refresh vector store dengan data terbaru + */ + async refreshVectorStore(): Promise { + try { + this.logger.log('Refreshing pgvector store...'); + await this.initializeVectorStore(); + this.logger.log('Pgvector store refreshed successfully'); + } catch (error) { + this.logger.error('Error refreshing pgvector store:', error); + throw error; + } + } + + /** + * Get vector store status + */ + async getVectorStoreStatus(): Promise<{ + initialized: boolean; + documentCount: number; + embeddingModel: string; + lastUpdated: Date; + }> { + try { + // Get document count from database using raw SQL + const result = await this.pool.query( + 'SELECT COUNT(*) as count FROM icd_codes WHERE embedding IS NOT NULL', + ); + const documentCount = parseInt(result.rows[0].count); + + const status = { + initialized: !!this.vectorStore, + documentCount, + embeddingModel: this.embeddings + ? `OpenAI ${process.env.OPENAI_API_MODEL || 'text-embedding-ada-002'}` + : 'Not Available', + lastUpdated: new Date(), + }; + + return status; + } catch (error) { + this.logger.error('Error getting vector store status:', error); + throw error; + } + } + + /** + * Cleanup resources + */ + async onModuleDestroy() { + await this.prisma.$disconnect(); + await this.pool.end(); + } +} diff --git a/src/main.ts b/src/main.ts index f76bc8d..80aa6cf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,133 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; +import { Logger, ValidationPipe } from '@nestjs/common'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; async function bootstrap() { + const logger = new Logger('Bootstrap'); + const app = await NestFactory.create(AppModule); - await app.listen(process.env.PORT ?? 3000); + + // Environment configuration + const port = process.env.PORT ?? 3000; + const host = process.env.HOST ?? 'localhost'; + const nodeEnv = process.env.NODE_ENV ?? 'development'; + + // CORS Configuration + const corsOrigins = process.env.CORS_ORIGINS?.split(',') ?? [ + 'http://localhost:3000', + ]; + const corsMethods = process.env.CORS_METHODS?.split(',') ?? [ + 'GET', + 'HEAD', + 'PUT', + 'PATCH', + 'POST', + 'DELETE', + 'OPTIONS', + ]; + const corsHeaders = process.env.CORS_HEADERS?.split(',') ?? [ + 'Content-Type', + 'Accept', + 'Authorization', + 'X-Requested-With', + ]; + const corsCredentials = process.env.CORS_CREDENTIALS === 'true'; + + // Enable CORS + app.enableCors({ + origin: corsOrigins, + methods: corsMethods, + allowedHeaders: corsHeaders, + credentials: corsCredentials, + }); + + // Enable global validation pipe + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + transformOptions: { + enableImplicitConversion: true, + }, + }), + ); + + // Setup Swagger Documentation + if (process.env.ENABLE_DOCS === 'true') { + const config = new DocumentBuilder() + .setTitle('Claim Guard API') + .setDescription( + 'API documentation for Claim Guard Backend - ICD Code Management System', + ) + .setVersion('1.0.0') + .setContact( + 'Development Team', + 'https://github.com/your-org/claim-guard-be', + 'dev@yourdomain.com', + ) + .setLicense('MIT', 'https://opensource.org/licenses/MIT') + .addServer( + process.env.APP_URL || 'http://localhost:3000', + 'Development Server', + ) + .addTag('ICD', 'ICD Code management operations') + .addTag('Health', 'Application health and monitoring') + .addBearerAuth( + { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + name: 'JWT', + description: 'Enter JWT token', + in: 'header', + }, + 'JWT-auth', + ) + .build(); + + const document = SwaggerModule.createDocument(app, config); + SwaggerModule.setup('docs', app, document, { + swaggerOptions: { + persistAuthorization: true, + docExpansion: 'none', + filter: true, + showRequestDuration: true, + }, + customSiteTitle: 'Claim Guard API Documentation', + customfavIcon: '/favicon.ico', + customCss: '.swagger-ui .topbar { display: none }', + }); + + logger.log( + `πŸ“š Swagger Documentation enabled at: http://${host}:${port}/docs`, + ); + } + + // Global prefix for API endpoints (optional) + // app.setGlobalPrefix('api/v1'); + + // Request timeout + const requestTimeout = parseInt(process.env.REQUEST_TIMEOUT ?? '30000'); + + // Graceful shutdown + app.enableShutdownHooks(); + + await app.listen(port, host); + + logger.log(`πŸš€ Application is running on: http://${host}:${port}`); + logger.log(`🌍 Environment: ${nodeEnv}`); + logger.log(`πŸ” CORS Origins: ${corsOrigins.join(', ')}`); + + if (process.env.HEALTH_CHECK_ENABLED === 'true') { + logger.log( + `❀️ Health Check available at: http://${host}:${port}${process.env.HEALTH_CHECK_PATH || '/health'}`, + ); + } } -bootstrap(); + +bootstrap().catch((error) => { + console.error('❌ Error starting server:', error); + process.exit(1); +}); diff --git a/verify_migration.sql b/verify_migration.sql new file mode 100644 index 0000000..8a3af46 --- /dev/null +++ b/verify_migration.sql @@ -0,0 +1,112 @@ +-- ===================================================== +-- VERIFICATION: pgvector Migration Success +-- ===================================================== +-- File: verify_migration.sql +-- +-- Cara penggunaan: +-- 1. Connect ke database: psql -d claim_guard -U username +-- 2. Jalankan: \i verify_migration.sql +-- ===================================================== + +\echo 'πŸŽ‰ VERIFYING pgvector MIGRATION SUCCESS πŸŽ‰' +\echo '=====================================================' + +-- Check pgvector extension +SELECT + 'pgvector Extension' as component, + CASE + WHEN EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector') + THEN 'βœ… INSTALLED' + ELSE '❌ NOT INSTALLED' + END as status; + +-- Check table structure +SELECT + 'icd_codes Table' as component, + CASE + WHEN EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'icd_codes') + THEN 'βœ… EXISTS' + ELSE '❌ MISSING' + END as status; + +-- Check all columns +SELECT + column_name, + data_type, + is_nullable, + CASE + WHEN column_name IN ('embedding', 'metadata', 'content') + THEN 'βœ… pgvector column' + ELSE 'ℹ️ Standard column' + END as status +FROM information_schema.columns +WHERE table_name = 'icd_codes' +ORDER BY column_name; + +-- Check indexes +SELECT + indexname, + indexdef, + CASE + WHEN indexname LIKE '%embedding%' OR indexname LIKE '%metadata%' + THEN 'βœ… Performance index' + ELSE 'ℹ️ Standard index' + END as status +FROM pg_indexes +WHERE tablename = 'icd_codes' +ORDER BY indexname; + +-- Test pgvector functionality +\echo '' +\echo 'πŸ§ͺ TESTING pgvector FUNCTIONALITY πŸ§ͺ' + +-- Test vector creation +SELECT + '[1,2,3,4,5]'::vector(5) as test_vector_5d, + '[0.1,0.2,0.3]'::vector(3) as test_vector_3d; + +-- Test table data +SELECT + COUNT(*) as total_rows, + COUNT(embedding) as rows_with_embeddings, + COUNT(metadata) as rows_with_metadata, + COUNT(content) as rows_with_content +FROM icd_codes; + +-- Check sample data structure +SELECT + id, + code, + display, + version, + category, + CASE + WHEN embedding IS NOT NULL THEN 'βœ… Has embedding' + ELSE '❌ No embedding' + END as embedding_status, + CASE + WHEN metadata IS NOT NULL THEN 'βœ… Has metadata' + ELSE '❌ No metadata' + END as metadata_status, + CASE + WHEN content IS NOT NULL THEN 'βœ… Has content' + ELSE '❌ No content' + END as content_status +FROM icd_codes +LIMIT 5; + +\echo '' +\echo '🎯 MIGRATION VERIFICATION COMPLETE! 🎯' +\echo '=====================================================' +\echo 'βœ… pgvector extension installed' +\echo 'βœ… embedding column (vector type) added' +\echo 'βœ… metadata column (JSONB) added' +\echo 'βœ… content column (TEXT) added' +\echo 'βœ… Performance indexes created' +\echo '' +\echo 'πŸš€ NEXT STEPS:' +\echo '1. Start application: npm run start:dev' +\echo '2. Initialize vector store: POST /pgvector/initialize' +\echo '3. Generate embeddings: POST /pgvector/generate-and-store-all-embeddings' +\echo '4. Test vector search: POST /pgvector/search' +\echo '====================================================='