From a01a4c5d8299934305669552dbc13b07694f078f Mon Sep 17 00:00:00 2001 From: KavyaCSH Date: Wed, 29 Jul 2026 11:38:27 +0530 Subject: [PATCH] Initial commit --- .env | 15 + .gitignore | 11 + AI_Pharmacy_Postman_Collection.json | 305 +++ README.md | 1 + alter_db.js | 17 + dummy_rx.jpg | 1 + list_models.ts | 11 + package-lock.json | 3951 +++++++++++++++++++++++++++ package.json | 37 + src/db/init.ts | 167 ++ src/db/pool.ts | 18 + src/index.ts | 59 + src/middleware/auth.ts | 58 + src/middleware/error.ts | 18 + src/middleware/headers.ts | 16 + src/middleware/idempotency.ts | 47 + src/modules/ai/router.ts | 20 + src/modules/ai/service.ts | 214 ++ src/modules/audit/router.ts | 122 + src/modules/catalogue/router.ts | 62 + src/modules/catalogue/service.ts | 99 + src/modules/dispensing/router.ts | 109 + src/modules/dispensing/service.ts | 293 ++ src/modules/identity/router.ts | 40 + src/modules/inventory/router.ts | 66 + src/modules/inventory/service.ts | 149 + src/modules/prescription/router.ts | 67 + src/modules/prescription/service.ts | 175 ++ src/modules/webhooks/service.ts | 47 + test-api.ts | 41 + test-db.js | 12 + test-db2.js | 16 + test-db3.js | 12 + test-dispense.ts | 57 + test-gemini.js | 19 + test-grn.ts | 60 + test-history.js | 29 + test-models.js | 8 + test-phase2.ts | 116 + test-remaining.ts | 61 + test_ai.ts | 11 + tsconfig.json | 13 + update_core_schema.js | 46 + update_schema.js | 31 + uploads/1784888629279.jpg | 1 + 45 files changed, 6728 insertions(+) create mode 100644 .env create mode 100644 .gitignore create mode 100644 AI_Pharmacy_Postman_Collection.json create mode 100644 README.md create mode 100644 alter_db.js create mode 100644 dummy_rx.jpg create mode 100644 list_models.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/db/init.ts create mode 100644 src/db/pool.ts create mode 100644 src/index.ts create mode 100644 src/middleware/auth.ts create mode 100644 src/middleware/error.ts create mode 100644 src/middleware/headers.ts create mode 100644 src/middleware/idempotency.ts create mode 100644 src/modules/ai/router.ts create mode 100644 src/modules/ai/service.ts create mode 100644 src/modules/audit/router.ts create mode 100644 src/modules/catalogue/router.ts create mode 100644 src/modules/catalogue/service.ts create mode 100644 src/modules/dispensing/router.ts create mode 100644 src/modules/dispensing/service.ts create mode 100644 src/modules/identity/router.ts create mode 100644 src/modules/inventory/router.ts create mode 100644 src/modules/inventory/service.ts create mode 100644 src/modules/prescription/router.ts create mode 100644 src/modules/prescription/service.ts create mode 100644 src/modules/webhooks/service.ts create mode 100644 test-api.ts create mode 100644 test-db.js create mode 100644 test-db2.js create mode 100644 test-db3.js create mode 100644 test-dispense.ts create mode 100644 test-gemini.js create mode 100644 test-grn.ts create mode 100644 test-history.js create mode 100644 test-models.js create mode 100644 test-phase2.ts create mode 100644 test-remaining.ts create mode 100644 test_ai.ts create mode 100644 tsconfig.json create mode 100644 update_core_schema.js create mode 100644 update_schema.js create mode 100644 uploads/1784888629279.jpg diff --git a/.env b/.env new file mode 100644 index 0000000..287c49b --- /dev/null +++ b/.env @@ -0,0 +1,15 @@ +DATABASE_URL=postgres://postgres:csh%402018@localhost:5432/ai_pharmacy +PORT=8080 +JWT_SECRET=super_secret_pharmacy_key_2026 + +# S3 CONFIGURATION +S3_KEY=AKIA3GRU7BB33WCCTN4V +S3_SECRET=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 +S3_REGION=ap-south-1 +S3_BUCKET=a2zschoolfolio +S3_BASE_PATH=temp/ +S3_PUBLIC_BASE_PATH=https://a2zschoolfolio.s3.ap-south-1.amazonaws.com/ + + +GEMINI_API_KEY=AQ.Ab8RN6KkBPhZo9kmwV-mFBtriT3LF8vPWenXNfA2jSyVWWwMMQ + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b05ec77 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +node_modules/ +.env +.env.* +!.env.example +dist/ +build/ +.DS_Store +npm-debug.log* +yarn-debug.log* +yarn-error.log* +coverage/ diff --git a/AI_Pharmacy_Postman_Collection.json b/AI_Pharmacy_Postman_Collection.json new file mode 100644 index 0000000..1d95247 --- /dev/null +++ b/AI_Pharmacy_Postman_Collection.json @@ -0,0 +1,305 @@ +{ + "info": { + "name": "AI Pharmacy Core API", + "description": "Complete collection for Phase 1 APIs.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "variable": [ + { + "key": "base_url", + "value": "http://localhost:8080", + "type": "string" + }, + { + "key": "access_token", + "value": "", + "type": "string" + } + ], + "item": [ + { + "name": "Health Check", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/health", + "host": [ + "{{base_url}}" + ], + "path": [ + "health" + ] + } + }, + "response": [] + }, + { + "name": "1. Authentication", + "item": [ + { + "name": "Get Access Token", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();", + "if (jsonData.access_token) {", + " pm.collectionVariables.set(\"access_token\", jsonData.access_token);", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"grant_type\": \"client_credentials\",\n \"client_id\": \"jayam_web_ai\",\n \"client_secret\": \"my_secret_key\"\n}" + }, + "url": { + "raw": "{{base_url}}/v1/auth/token", + "host": [ + "{{base_url}}" + ], + "path": [ + "v1", + "auth", + "token" + ] + } + } + } + ] + }, + { + "name": "2. Catalogue", + "item": [ + { + "name": "Search Drugs", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "X-Actor-Id", + "value": "usr_ph_1234" + } + ], + "url": { + "raw": "{{base_url}}/v1/drugs/search?q=Losartan", + "host": [ + "{{base_url}}" + ], + "path": [ + "v1", + "drugs", + "search" + ], + "query": [ + { + "key": "q", + "value": "Losartan" + } + ] + } + } + } + ] + }, + { + "name": "3. Inventory", + "item": [ + { + "name": "Receive Goods (GRN)", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "X-Actor-Id", + "value": "usr_ph_admin" + }, + { + "key": "idempotency-key", + "value": "idemp-{{$timestamp}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"lines\": [\n {\n \"batch_no\": \"BAT-{{$timestamp}}\",\n \"product_id\": \"prd_4410\",\n \"expiry\": \"2028-12-31\",\n \"qty\": 500,\n \"purchase_price\": \"50.00\"\n }\n ]\n}" + }, + "url": { + "raw": "{{base_url}}/v1/inventory/grn", + "host": [ + "{{base_url}}" + ], + "path": [ + "v1", + "inventory", + "grn" + ] + } + } + }, + { + "name": "Get Expiring Batches", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "X-Actor-Id", + "value": "usr_ph_1234" + } + ], + "url": { + "raw": "{{base_url}}/v1/inventory/expiring", + "host": [ + "{{base_url}}" + ], + "path": [ + "v1", + "inventory", + "expiring" + ] + } + } + } + ] + }, + { + "name": "4. Dispensing", + "item": [ + { + "name": "Dispense Order", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "X-Actor-Id", + "value": "usr_ph_1234" + }, + { + "key": "idempotency-key", + "value": "dispense-{{$timestamp}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"patient_id\": \"pat_101\",\n \"items\": [\n {\n \"product_id\": \"prd_4410\",\n \"qty\": 30\n }\n ]\n}" + }, + "url": { + "raw": "{{base_url}}/v1/orders/dispense", + "host": [ + "{{base_url}}" + ], + "path": [ + "v1", + "orders", + "dispense" + ] + } + } + } + ] + }, + { + "name": "5. Audit", + "item": [ + { + "name": "Get Audit Logs", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "X-Actor-Id", + "value": "usr_ph_admin" + } + ], + "url": { + "raw": "{{base_url}}/v1/audit/logs?limit=10", + "host": [ + "{{base_url}}" + ], + "path": [ + "v1", + "audit", + "logs" + ], + "query": [ + { + "key": "limit", + "value": "10" + } + ] + } + } + } + ] + } + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..3cff59a --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# AI_Pharmacy_Api diff --git a/alter_db.js b/alter_db.js new file mode 100644 index 0000000..ba9f2db --- /dev/null +++ b/alter_db.js @@ -0,0 +1,17 @@ +const { Pool } = require('pg'); + +const pool = new Pool({ + connectionString: 'postgres://postgres:csh%402018@localhost:5432/ai_pharmacy' +}); + +async function run() { + try { + await pool.query('ALTER TABLE dispensing_records ADD COLUMN IF NOT EXISTS prescription_id VARCHAR(50)'); + console.log('Successfully added prescription_id column.'); + } catch (err) { + console.error('Error:', err); + } finally { + pool.end(); + } +} +run(); diff --git a/dummy_rx.jpg b/dummy_rx.jpg new file mode 100644 index 0000000..016a09f --- /dev/null +++ b/dummy_rx.jpg @@ -0,0 +1 @@ +dummy image content \ No newline at end of file diff --git a/list_models.ts b/list_models.ts new file mode 100644 index 0000000..c072cc1 --- /dev/null +++ b/list_models.ts @@ -0,0 +1,11 @@ +import { GoogleGenAI } from '@google/genai'; +import * as dotenv from 'dotenv'; +dotenv.config(); +const ai = new GoogleGenAI(); +async function run() { + const models = await ai.models.list(); + for await (const m of models) { + console.log(m.name); + } +} +run(); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6644a73 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3951 @@ +{ + "name": "ai_pharmacy_ai", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ai_pharmacy_ai", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@aws-sdk/client-s3": "^3.1094.0", + "@google/genai": "^2.13.0", + "cors": "^2.8.6", + "dotenv": "^17.4.2", + "express": "^5.2.1", + "helmet": "^8.3.0", + "jsonwebtoken": "^9.0.3", + "multer": "^2.2.0", + "pg": "^8.22.0", + "redis": "^6.1.0" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.6", + "@types/jsonwebtoken": "^9.0.10", + "@types/multer": "^2.2.0", + "@types/node": "^26.1.1", + "@types/pg": "^8.20.0", + "ts-node-dev": "^2.0.0", + "tsx": "^4.23.1", + "typescript": "^7.0.2" + } + }, + "node_modules/@aws-sdk/checksums": { + "version": "3.1000.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.19.tgz", + "integrity": "sha512-Hc4N100RdkuWshKBnhPzmpdftfi9mCLz+OHFELHM1QIgMH4QRUUWyWgfiebta/YX2Bd62wTcm3EqAP8TeXv0gA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.1094.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1094.0.tgz", + "integrity": "sha512-Qkz3HXW9bBajTO1Pgvbxkj2THliDnYPFBaJwIF1mDmnPe1E7reV9V/QJxBz14slIvdcU0tiyB+z37Qns0EY9Zg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/checksums": "^3.1000.19", + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/credential-provider-node": "^3.972.71", + "@aws-sdk/middleware-sdk-s3": "^3.972.65", + "@aws-sdk/signature-v4-multi-region": "^3.996.41", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/fetch-http-handler": "^5.6.6", + "@smithy/node-http-handler": "^4.9.6", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.976.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.976.0.tgz", + "integrity": "sha512-0cjRaEdlVoOrsNb9pP5q1Syyc8pXw5xSj2Np2ryReRTr9FppIIRVSdZK4lbnfmc2Hvgux/xBOUU6baB7z8//uA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.2", + "@aws-sdk/xml-builder": "^3.972.36", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.29.4", + "@smithy/signature-v4": "^5.6.5", + "@smithy/types": "^4.16.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.60", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.60.tgz", + "integrity": "sha512-BAkxdoe7tpDDqCghGpuOeHQRbm/2znVvOQm0AvpQbA2tbfMN46doN4zx65fv85ImP3KADwc2zQPmbrlI9MPfMg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.62", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.62.tgz", + "integrity": "sha512-g/0fGqKTb9xpKdd9AtpmV5Eo3DFKbnkpA2+w0peISSlu7NfAoWOuYBFxsu+yWBtxU89ka55ezoZBCbFaS8pjYQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/fetch-http-handler": "^5.6.6", + "@smithy/node-http-handler": "^4.9.6", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.973.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.5.tgz", + "integrity": "sha512-ylubazcRfq2TVus/qXucSXeC42Qdjp5HQxTu68K/BsdMiZlcSLD1zkpoCgApXZX1Y6YJhtGGs7ZHhO/GuIgBlw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/credential-provider-env": "^3.972.60", + "@aws-sdk/credential-provider-http": "^3.972.62", + "@aws-sdk/credential-provider-login": "^3.972.67", + "@aws-sdk/credential-provider-process": "^3.972.60", + "@aws-sdk/credential-provider-sso": "^3.973.4", + "@aws-sdk/credential-provider-web-identity": "^3.972.66", + "@aws-sdk/nested-clients": "^3.997.34", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/credential-provider-imds": "^4.4.9", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.67", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.67.tgz", + "integrity": "sha512-CCygIKJ9YbI3n84OClSaSppkgKKHVj2TGT33c6FRORZrYNZQ1POmD+ip0FLYokiJAK7sSdc3YVkOsBm90oxWMQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/nested-clients": "^3.997.34", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.71", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.71.tgz", + "integrity": "sha512-HIg7Q2osBzajQwL+1Vkyh2E7Gim3eTNb9RHIsOxDGjW0eZg4oEKtRs5sioCnc73ilhaOm4gX2lHVF8J7+nt2rg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.60", + "@aws-sdk/credential-provider-http": "^3.972.62", + "@aws-sdk/credential-provider-ini": "^3.973.5", + "@aws-sdk/credential-provider-process": "^3.972.60", + "@aws-sdk/credential-provider-sso": "^3.973.4", + "@aws-sdk/credential-provider-web-identity": "^3.972.66", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/credential-provider-imds": "^4.4.9", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.60", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.60.tgz", + "integrity": "sha512-YIo3f99hM43QdYG8hDzwGemnR/pU95b0kramqSJUTleCqaB7+HwKf7YZFHqvOgTqZTPx/mRmNIqoDRr3U0Z3Tw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.973.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.4.tgz", + "integrity": "sha512-BPdmL8sSBOCv4ngZ+3LHxyc3CNqDCEK37CHioCk7zGrTMY5sUtkH8q+o6qA80nn6w3/fyBPGNE7OIRlmoOxRQA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/nested-clients": "^3.997.34", + "@aws-sdk/token-providers": "3.1092.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.66", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.66.tgz", + "integrity": "sha512-kSAziJboOmZmsR9/MTbiNjowl2BPes1bQuJpne4qAZ62ubi8fjfr/aupJSQje6udBoYxXTQbsL0e0kby2la3ng==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/nested-clients": "^3.997.34", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.65", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.65.tgz", + "integrity": "sha512-udwNhRfDTfCB98mAHjjgsnKQlxygB4e0X+Obne/XjJpvVsF0YCQC8ZErd/8Z6IPoLQjtiKHzwqEDbZiLrJEnOg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/signature-v4-multi-region": "^3.996.41", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.34", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.34.tgz", + "integrity": "sha512-Y9REVrSwmLM+Qy6sZJ7ofMC2S3Hr3tPP/4CzL5U1olPP7OGoF+6+Px0E49cVQBtSxJtyeLJMf0UaBErfeSahAA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/signature-v4-multi-region": "^3.996.41", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/fetch-http-handler": "^5.6.6", + "@smithy/node-http-handler": "^4.9.6", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.41.tgz", + "integrity": "sha512-QMUytg+FQMGouc8gHS00KoYih3+N6cqmVI/pQGOIo7Nr7OpQaiXjSYOuL+vsPZ1tymY4LAQ8MYcHJmws5LRxng==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.2", + "@smithy/signature-v4": "^5.6.5", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1092.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1092.0.tgz", + "integrity": "sha512-hBYUAr6iBLNFcsiWTgtBb0stdSw39VOUq4Sp4A5caCNf66BAZplWN4FleKrVpJx5li2YgdnK2DqoFSMWC642FQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.976.0", + "@aws-sdk/nested-clients": "^3.997.34", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.974.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.2.tgz", + "integrity": "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.36", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.36.tgz", + "integrity": "sha512-RdGmS1GLrtaTOLE1ElSluMldNrpk9Emq6uYs8SS8iHlu5xTAmM9rRkM91o48+rIRryBtyO9t+uLYCoMG6jVMVA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/genai": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-2.13.0.tgz", + "integrity": "sha512-GM7C8Kaomvjz05x5JEO6+l3d/pciL9LxAG9dUjJLD7nTPZ9X0Cfsf2Z7eET6UjgWyUmxXCHtYnQoQ77F9+ZIOQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "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.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "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/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.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "license": "BSD-3-Clause" + }, + "node_modules/@redis/bloom": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-6.1.0.tgz", + "integrity": "sha512-Rzascjd9J9bJsM45T/Z9CTg1QY/B63B6YO8QorLVMeXnbBDsKiSCVR/+GQ061hYPk8FpTzWmPY8tAv2sT+JEtQ==", + "license": "MIT", + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@redis/client": "^6.1.0" + } + }, + "node_modules/@redis/client": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-6.1.0.tgz", + "integrity": "sha512-7u1LefkezJF0HESlhO7ZFLEPfyY+NejP3SGv+Z4pGaT3oM5GVVLa0u3f4rDLUrcw+SRo8IlX9Y8JAONeDdg1Ag==", + "license": "MIT", + "peer": true, + "dependencies": { + "cluster-key-slot": "1.1.2" + }, + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@node-rs/xxhash": "^1.1.0", + "@opentelemetry/api": ">=1 <2" + }, + "peerDependenciesMeta": { + "@node-rs/xxhash": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@redis/json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-6.1.0.tgz", + "integrity": "sha512-/GFjQA6bu5pG9ClCJAI5Xx4bNXe7UTpxBBlIupBNTrn1+nY860apGnYJuaSCDV2BmEbTidpa7O2qa28oxKx+rg==", + "license": "MIT", + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@redis/client": "^6.1.0" + } + }, + "node_modules/@redis/search": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-6.1.0.tgz", + "integrity": "sha512-kS5agg+3yZbrdrt8omrew7FLCD8eOm7tarG1CROekPBRe+QGDR9aOpnHIQaYsYi6wPRTH70nQiF06AIjgURefQ==", + "license": "MIT", + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@redis/client": "^6.1.0" + } + }, + "node_modules/@redis/time-series": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-6.1.0.tgz", + "integrity": "sha512-uIDBtV8MmG/xpJsRqbGSO4iX6ryj37MLMP82lRpFvI7ykAVe5GyqgxigEbU+uZNv9kDPNMKw3dvI/S/J1BNBzA==", + "license": "MIT", + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@redis/client": "^6.1.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.29.8", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.8.tgz", + "integrity": "sha512-rpCbCV+TimOBi3VLNBMmtTvgfOWcFIEAru3+TFlG87SL2F+te4jOnnNR+cf3uR4eJ5Qf4LnT80fqnBKgPRS6zA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.13.tgz", + "integrity": "sha512-X+2HNZhWi5i3rJsCas0LPf6fTQUaKyJ40zd8aTO/bwpRfpU3biYaqLr7C1WMibL7PVKJalpi1PyybjGPNoHC8Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.29.8", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.6.10", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.10.tgz", + "integrity": "sha512-5/Yj9mS2JjTsB3B8ZX7euh77mrY9aXW23ag1yAmFykSRmA6vldqBrgqmSeQ50EjY+5SB8+aE4w14B6LKbBVEhQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.29.8", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.9.10", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.10.tgz", + "integrity": "sha512-ETQz9v/Z+nTQc6fRWTXxUpxJqwpmzB3Tn3WKAdHwWkeT+m+HE5czs6GNG8vW+4vyxXSls65RVcvOZwk7Q/PS/Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.29.8", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.6.9", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.9.tgz", + "integrity": "sha512-g5rnEii/mkT0mjVJmlsaOfyNBtHNTecD9Lo4NP8D5HzMUEnZNpz7/FbvBCjNcV4vteHFAxOGiLUYNxPkDZZAPw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.29.8", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.1.tgz", + "integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.2.tgz", + "integrity": "sha512-d3KvEXBSo/lOAMc2u6fkyDHBvetBHeqD7wm/AcXfLpSOQwlmG9D/aQ0SFswVjv05p7ullQS7Mjohj6/VdbZuTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/multer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.2.0.tgz", + "integrity": "sha512-3U1troeqGV8Ntp7Q3klwf4zr23VEoqYVocYXaswm9+8z3O9UHDYAqLxjJ/h550iRADTjKdOdhhasXw6gD6kYtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/node": { + "version": "26.1.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", + "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "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": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "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" + }, + "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/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + } + }, + "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", + "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", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaxios": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.0.tgz", + "integrity": "sha512-RB5vLV+vvQeoFPCX4QMK6/hjVkbIamPp1QSUD0CiZcnj12qbpiL+pLbYtgD+oZkWl0tl9z+o2Utp+MpM3QRhBA==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/google-auth-library": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.1.tgz", + "integrity": "sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/helmet": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.3.0.tgz", + "integrity": "sha512-Qgpiaws3Sm30Av8Eah6sjMCZZwjlBu+E68rhpCWBshY1lb09HtLwj5GviX0OyQIn+ulUS0iX0AxN5n3tLZzz1w==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/EvanHahn" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "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": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "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" + }, + "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" + }, + "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" + }, + "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" + }, + "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" + }, + "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" + }, + "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" + }, + "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/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.2.0.tgz", + "integrity": "sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "type-is": "^1.6.18" + }, + "engines": { + "node": ">= 10.16.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/multer/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/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", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/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", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "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/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pg": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.22.0.tgz", + "integrity": "sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==", + "license": "MIT", + "peer": true, + "dependencies": { + "pg-connection-string": "^2.14.0", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.15.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.4.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.0.tgz", + "integrity": "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==", + "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.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.15.0.tgz", + "integrity": "sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==", + "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/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.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/protobufjs": { + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", + "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "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", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redis": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-6.1.0.tgz", + "integrity": "sha512-0kvUPM8RHP/ZMa0xYaDTcG5e8tIGW6kz6MToVT0V8iOnk6bkXp2jncGRGe2bZEk41lZwiDspUqjZCSk5ohjcKw==", + "license": "MIT", + "dependencies": { + "@redis/bloom": "6.1.0", + "@redis/client": "6.1.0", + "@redis/json": "6.1.0", + "@redis/search": "6.1.0", + "@redis/time-series": "6.1.0" + }, + "engines": { + "node": ">= 20.0.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.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/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node-dev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", + "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.6", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^10.4.0", + "tsconfig": "^7.0.0" + }, + "bin": { + "ts-node-dev": "lib/bin.js", + "tsnd": "lib/bin.js" + }, + "engines": { + "node": ">=0.8.0" + }, + "peerDependencies": { + "node-notifier": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz", + "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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/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", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "license": "MIT", + "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/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0824f7f --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "ai_pharmacy_ai", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "dependencies": { + "@aws-sdk/client-s3": "^3.1094.0", + "@google/genai": "^2.13.0", + "cors": "^2.8.6", + "dotenv": "^17.4.2", + "express": "^5.2.1", + "helmet": "^8.3.0", + "jsonwebtoken": "^9.0.3", + "multer": "^2.2.0", + "pg": "^8.22.0", + "redis": "^6.1.0" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.6", + "@types/jsonwebtoken": "^9.0.10", + "@types/multer": "^2.2.0", + "@types/node": "^26.1.1", + "@types/pg": "^8.20.0", + "ts-node-dev": "^2.0.0", + "tsx": "^4.23.1", + "typescript": "^7.0.2" + } +} diff --git a/src/db/init.ts b/src/db/init.ts new file mode 100644 index 0000000..d70d20f --- /dev/null +++ b/src/db/init.ts @@ -0,0 +1,167 @@ +import { pool } from './pool'; + +async function initDB() { + const client = await pool.connect(); + try { + await client.query('BEGIN'); + + console.log('Creating tables...'); + + // 1. Catalogue Module + await client.query(` + CREATE TABLE IF NOT EXISTS drugs ( + id VARCHAR(50) PRIMARY KEY, + generic_name VARCHAR(255) NOT NULL, + atc_code VARCHAR(50), + schedule VARCHAR(20), + is_nti BOOLEAN DEFAULT false, + pregnancy_category VARCHAR(10) + ); + `); + + await client.query(` + CREATE TABLE IF NOT EXISTS products ( + id VARCHAR(50) PRIMARY KEY, + drug_id VARCHAR(50) REFERENCES drugs(id), + brand_name VARCHAR(255) NOT NULL, + manufacturer VARCHAR(255), + strength VARCHAR(50), + form VARCHAR(50), + mrp DECIMAL(10, 2), + dpco_ceiling DECIMAL(10, 2) + ); + `); + + // 2. Inventory Module + await client.query(` + CREATE TABLE IF NOT EXISTS batches ( + batch_no VARCHAR(100) PRIMARY KEY, + product_id VARCHAR(50) REFERENCES products(id), + expiry DATE NOT NULL, + qty_on_hand INTEGER NOT NULL CHECK (qty_on_hand >= 0), + purchase_price DECIMAL(10, 2), + status VARCHAR(50) DEFAULT 'available' + ); + `); + + await client.query(` + CREATE TABLE IF NOT EXISTS stock_movements ( + id SERIAL PRIMARY KEY, + batch_no VARCHAR(100) REFERENCES batches(batch_no), + movement_type VARCHAR(50) NOT NULL, -- 'receipt', 'dispense', 'adjustment' + qty_delta INTEGER NOT NULL, + reference_id VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + `); + + // 3. Dispensing & Audit + await client.query(` + CREATE TABLE IF NOT EXISTS dispensing_records ( + id VARCHAR(50) PRIMARY KEY, + subject_ref VARCHAR(100) NOT NULL, + tenant_id VARCHAR(50) NOT NULL, + dispensed_by VARCHAR(100) NOT NULL, + prescription_id VARCHAR(50), + total_amount DECIMAL(10, 2) DEFAULT 0, + payment_method VARCHAR(50) DEFAULT 'cash', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + `); + + await client.query(` + CREATE TABLE IF NOT EXISTS overrides_log ( + id SERIAL PRIMARY KEY, + dispensing_id VARCHAR(50) REFERENCES dispensing_records(id), + code VARCHAR(50) NOT NULL, + reason TEXT NOT NULL, + actor_user_id VARCHAR(100) NOT NULL, + occurred_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + `); + + await client.query(` + CREATE TABLE IF NOT EXISTS h1_registers ( + id VARCHAR(50) PRIMARY KEY, + dispensing_id VARCHAR(50) REFERENCES dispensing_records(id), + patient_name VARCHAR(255) NOT NULL, + prescriber_name VARCHAR(255), + drug_name VARCHAR(255) NOT NULL, + batch_no VARCHAR(100), + qty INTEGER NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + `); + + await client.query(` + CREATE TABLE IF NOT EXISTS order_drafts ( + id VARCHAR(50) PRIMARY KEY, + subject_ref VARCHAR(100), + prescription_ref VARCHAR(50), + items JSONB, + status VARCHAR(20) DEFAULT 'draft', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + `); + + await client.query(` + CREATE TABLE IF NOT EXISTS audit_logs ( + id SERIAL PRIMARY KEY, + call_id VARCHAR(100), + actor_user_id VARCHAR(100), + task_type VARCHAR(50), + details JSONB, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + `); + + // 4. Phase 2: Prescriptions + await client.query(` + CREATE TABLE IF NOT EXISTS prescriptions ( + id VARCHAR(50) PRIMARY KEY, + image_path TEXT NOT NULL, + image_hash VARCHAR(64) NOT NULL, + status VARCHAR(20) DEFAULT 'processing', + extracted_data JSONB, + confirmed_data JSONB, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + `); + + console.log('Seeding initial data...'); + // Seed Dummy Data for UI + await client.query(` + INSERT INTO drugs (id, generic_name, schedule) VALUES + ('drg_001', 'Losartan Potassium', 'H'), + ('drg_002', 'Sertraline', 'H') + ON CONFLICT (id) DO NOTHING; + `); + + await client.query(` + INSERT INTO products (id, drug_id, brand_name, form, mrp) VALUES + ('prd_4410', 'drg_001', 'Losar 50', 'tablet', 78.50), + ('prd_5510', 'drg_002', 'Sertra 50', 'tablet', 110.00) + ON CONFLICT (id) DO NOTHING; + `); + + // Add batches. Let's add a near-expiry batch so the UI alert triggers + await client.query(` + INSERT INTO batches (batch_no, product_id, expiry, qty_on_hand, purchase_price) VALUES + ('B4421', 'prd_4410', CURRENT_DATE + INTERVAL '15 days', 240, 50.00), + ('B4630', 'prd_5510', CURRENT_DATE + INTERVAL '200 days', 500, 80.00) + ON CONFLICT (batch_no) DO NOTHING; + `); + + await client.query('COMMIT'); + console.log('Database initialization and seeding completed successfully!'); + } catch (error) { + await client.query('ROLLBACK'); + console.error('Error initializing database:', error); + } finally { + client.release(); + pool.end(); + } +} + +initDB(); diff --git a/src/db/pool.ts b/src/db/pool.ts new file mode 100644 index 0000000..1759654 --- /dev/null +++ b/src/db/pool.ts @@ -0,0 +1,18 @@ +import { Pool } from 'pg'; +import dotenv from 'dotenv'; + +dotenv.config(); + +export const pool = new Pool({ + connectionString: process.env.DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/ai_pharmacy', +}); + +// Test connection +pool.on('connect', () => { + console.log('Connected to the PostgreSQL database.'); +}); + +pool.on('error', (err) => { + console.error('Unexpected error on idle client', err); + process.exit(-1); +}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..1ea038e --- /dev/null +++ b/src/index.ts @@ -0,0 +1,59 @@ +import express from 'express'; +import cors from 'cors'; +import helmet from 'helmet'; +import { errorHandler } from './middleware/error'; + +// Import routers +import identityRouter from './modules/identity/router'; +import catalogueRouter from './modules/catalogue/router'; +import inventoryRouter from './modules/inventory/router'; +import dispensingRouter from './modules/dispensing/router'; +import auditRouter from './modules/audit/router'; +import prescriptionRouter from './modules/prescription/router'; +import aiRouter from './modules/ai/router'; + +const app = express(); +const PORT = process.env.PORT || 8080; + +app.use(helmet()); +app.use(cors()); +app.use(express.json()); + +// Detailed Network Logger Middleware +app.use((req, res, next) => { + const start = Date.now(); + console.log(`\n======================================================`); + console.log(`[NETWORK REQUEST] ${req.method} ${req.originalUrl}`); + console.log(`[HEADERS]`, JSON.stringify(req.headers, null, 2)); + if (req.body && Object.keys(req.body).length > 0) { + console.log(`[BODY]`, JSON.stringify(req.body, null, 2)); + } + + res.on('finish', () => { + const duration = Date.now() - start; + console.log(`[NETWORK RESPONSE] ${req.method} ${req.originalUrl} - Status: ${res.statusCode} (${duration}ms)`); + console.log(`======================================================\n`); + }); + next(); +}); + +// API Routes +app.use('/v1/auth', identityRouter); +app.use('/v1', catalogueRouter); +app.use('/v1/inventory', inventoryRouter); +app.use('/v1/orders', dispensingRouter); +app.use('/v1/audit', auditRouter); +app.use('/v1/rx', prescriptionRouter); +app.use('/v1/ai', aiRouter); + +// Health check +app.get('/health', (req, res) => { + res.status(200).json({ status: 'ok' }); +}); + +// Global Error Handler +app.use(errorHandler); + +app.listen(PORT, () => { + console.log(`Pharmacy Core API listening on port ${PORT}`); +}); diff --git a/src/middleware/auth.ts b/src/middleware/auth.ts new file mode 100644 index 0000000..5a8edc1 --- /dev/null +++ b/src/middleware/auth.ts @@ -0,0 +1,58 @@ +import { Request, Response, NextFunction, RequestHandler } from 'express'; +import jwt from 'jsonwebtoken'; + +const JWT_SECRET = process.env.JWT_SECRET || 'super_secret_pharmacy_key_2026'; + +declare global { + namespace Express { + interface Request { + actorId?: string; + tenantId?: string; + } + } +} + +export const requireAuth: RequestHandler = (req: Request, res: Response, next: NextFunction): void => { + const authHeader = req.headers.authorization; + const actorId = req.headers['x-actor-id'] as string; + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + res.status(401).json({ + error: 'UNAUTHORIZED', + message: 'Missing or invalid Authorization header.', + details: {}, + retryable: false + }); + return; + } + + if (!actorId) { + res.status(403).json({ + error: 'FORBIDDEN', + message: 'Missing X-Actor-Id header.', + details: {}, + retryable: false + }); + return; + } + + const token = authHeader.split(' ')[1]; + + try { + // Cryptographically verify the token using our secret key + const payload = jwt.verify(token, JWT_SECRET) as any; + + // Automatically attach the tenant (e.g., jayam_web_ai) to the request context + req.tenantId = payload.tenantId; + req.actorId = actorId; + + next(); + } catch (error) { + res.status(401).json({ + error: 'UNAUTHORIZED', + message: 'Invalid or expired token.', + details: {}, + retryable: false + }); + } +}; diff --git a/src/middleware/error.ts b/src/middleware/error.ts new file mode 100644 index 0000000..df9e32e --- /dev/null +++ b/src/middleware/error.ts @@ -0,0 +1,18 @@ +import { Request, Response, NextFunction } from 'express'; + +export const errorHandler = (err: any, req: Request, res: Response, next: NextFunction) => { + console.error(err); + + const status = err.status || 500; + const errorName = err.name || 'INTERNAL_SERVER_ERROR'; + const message = err.message || 'An unexpected error occurred.'; + const correlationId = req.headers['x-correlation-id'] || 'unknown'; + + res.status(status).json({ + error: errorName, + message: message, + details: err.details || {}, + correlation_id: correlationId, + retryable: err.retryable || false + }); +}; diff --git a/src/middleware/headers.ts b/src/middleware/headers.ts new file mode 100644 index 0000000..61f30a0 --- /dev/null +++ b/src/middleware/headers.ts @@ -0,0 +1,16 @@ +import { Request, Response, NextFunction } from 'express'; + +export const requireHeaders = (req: Request, res: Response, next: NextFunction) => { + const actorId = req.headers['x-actor-id']; + + if (!actorId) { + return next({ + status: 400, + name: 'MISSING_HEADER', + message: 'X-Actor-Id header is required on all mutating endpoints.', + retryable: false + }); + } + + next(); +}; diff --git a/src/middleware/idempotency.ts b/src/middleware/idempotency.ts new file mode 100644 index 0000000..8fa20df --- /dev/null +++ b/src/middleware/idempotency.ts @@ -0,0 +1,47 @@ +import { Request, Response, NextFunction } from 'express'; +import { pool } from '../db/pool'; + +export const requireIdempotency = async (req: Request, res: Response, next: NextFunction) => { + const key = req.headers['idempotency-key']; + + if (!key || typeof key !== 'string') { + return next({ + status: 400, + name: 'MISSING_IDEMPOTENCY_KEY', + message: 'Idempotency-Key header is mandatory for this endpoint.', + retryable: false + }); + } + + try { + // Clean up old keys (older than 24 hours) - in production this would be a cron job + await pool.query(`DELETE FROM idempotency_keys WHERE created_at < NOW() - INTERVAL '24 hours'`); + + const { rows } = await pool.query(`SELECT * FROM idempotency_keys WHERE key = $1`, [key]); + + if (rows.length > 0) { + // Key exists, return previous response + const record = rows[0]; + return res.status(record.response_status).json(record.response_body); + } + + // Key doesn't exist, wrap res.json to capture response + const originalJson = res.json; + res.json = function(body: any) { + // Store the response asynchronously + pool.query(` + INSERT INTO idempotency_keys (key, method, path, response_body, response_status) + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (key) DO NOTHING + `, [key, req.method, req.originalUrl, body, res.statusCode]).catch(err => { + console.error('Failed to save idempotency key:', err); + }); + + return originalJson.call(this, body); + }; + + next(); + } catch (error) { + next(error); + } +}; diff --git a/src/modules/ai/router.ts b/src/modules/ai/router.ts new file mode 100644 index 0000000..6f9443c --- /dev/null +++ b/src/modules/ai/router.ts @@ -0,0 +1,20 @@ +import { Router } from 'express'; +import { AIService } from './service'; + +const router = Router(); +const aiService = new AIService(); + +router.post('/voice', async (req, res, next) => { + try { + const { transcript } = req.body; + if (!transcript) { + return res.status(400).json({ error: 'Transcript is required' }); + } + const result = await aiService.processVoiceCommand(transcript); + res.json(result); + } catch (error) { + next(error); + } +}); + +export default router; diff --git a/src/modules/ai/service.ts b/src/modules/ai/service.ts new file mode 100644 index 0000000..515e6de --- /dev/null +++ b/src/modules/ai/service.ts @@ -0,0 +1,214 @@ +import { GoogleGenAI } from '@google/genai'; +import { pool } from '../../db/pool'; + +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); + +export class AIService { + async processVoiceCommand(transcript: string) { + const prompt = `You are a helpful pharmacy AI assistant. +You can understand English and Tanglish (Tamil written in English). +The user is speaking to you. Respond conversationally. Do not use markdown. +If the user asks about low stock, use get_low_stock. +If the user asks about total medicines, use get_total_medicines. +If the user asks about today's sales or revenue, use get_today_sales. +If the user asks about expiring medicines, use get_expiring_medicines. +If the user asks to open or navigate to a page (e.g. "open prescriptions", "take me to pharmacy"), use navigate_to with the appropriate target. +If the user asks to search for a specific medicine or asks if a medicine is available (e.g., "search privika", "do we have calpol"), use search_medicine. +If the user asks to add a new medicine to the system, use add_medicine. +If the user asks to add stock for a medicine, use add_stock. +User transcript: "${transcript}"`; + + const aiTools = { + functionDeclarations: [ + { + name: "get_low_stock", + description: "Get the count of medicines that are running low on stock.", + }, + { + name: "get_total_medicines", + description: "Get the total number of unique medicines in the system.", + }, + { + name: "get_today_sales", + description: "Get the total revenue or sales for today.", + }, + { + name: "get_expiring_medicines", + description: "Get the count of medicines expiring within the next 30 days.", + }, + { + name: "search_medicine", + description: "Search for a specific medicine by name to check if it's in stock.", + parameters: { + type: "OBJECT", + properties: { + query: { + type: "STRING", + description: "The name of the medicine to search for (e.g., 'calpol', 'privika')." + } + }, + required: ["query"] + } + }, + { + name: "add_medicine", + description: "Add a new medicine to the product catalog.", + parameters: { + type: "OBJECT", + properties: { + brand_name: { type: "STRING", description: "The brand name of the medicine (e.g., Dolo 650)." }, + mrp: { type: "NUMBER", description: "The Maximum Retail Price (MRP) in rupees." }, + form: { type: "STRING", description: "The physical form of the medicine (e.g., tablet, syrup, injection)." } + }, + required: ["brand_name", "mrp"] + } + }, + { + name: "add_stock", + description: "Add stock (a new batch) for an existing medicine.", + parameters: { + type: "OBJECT", + properties: { + product_name: { type: "STRING", description: "The brand name of the medicine to add stock to." }, + qty: { type: "NUMBER", description: "The quantity of stock to add." }, + batch_no: { type: "STRING", description: "The batch number. Omit if the user doesn't provide one." }, + expiry: { type: "STRING", description: "The expiry date. Omit if the user doesn't provide one." } + }, + required: ["product_name", "qty"] + } + }, + { + name: "navigate_to", + description: "Navigate the user to a specific page.", + parameters: { + type: "OBJECT", + properties: { + target: { + type: "STRING", + description: "The page to navigate to. Allowed values: 'dashboard', 'pharmacy', 'inventory', 'prescriptions'" + } + }, + required: ["target"] + } + } + ] + }; + + try { + const response = await ai.models.generateContent({ + model: 'gemini-3.6-flash', + contents: [prompt], + config: { + tools: [aiTools as any], + } + }); + + const functionCalls = response.functionCalls; + + if (functionCalls && functionCalls.length > 0) { + const call = functionCalls[0]; + let toolData = null; + let actionCommand = 'NONE'; + + if (call.name === 'get_low_stock') { + const { rows } = await pool.query('SELECT count(*) as count FROM batches WHERE qty_on_hand <= 50'); + toolData = { items_running_low: rows[0].count }; + actionCommand = 'VIEW_LOW_STOCK'; + } else if (call.name === 'get_total_medicines') { + const { rows } = await pool.query('SELECT count(*) as count FROM products'); + toolData = { total_unique_medicines: rows[0].count }; + } else if (call.name === 'get_today_sales') { + const { rows } = await pool.query(`SELECT sum(total_amount) as total FROM dispensing_records WHERE DATE(created_at) = CURRENT_DATE`); + toolData = { todays_revenue_rupees: rows[0].total || 0 }; + } else if (call.name === 'get_expiring_medicines') { + const { rows } = await pool.query(`SELECT count(*) as count FROM batches WHERE expiry <= CURRENT_DATE + interval '30 days'`); + toolData = { batches_expiring_in_30_days: rows[0].count }; + actionCommand = 'VIEW_EXPIRING_STOCK'; + } else if (call.name === 'search_medicine') { + const query = (call.args as any)?.query || ''; + const { rows } = await pool.query( + `SELECT p.brand_name, COALESCE(SUM(b.qty_on_hand), 0) as total_stock + FROM products p + LEFT JOIN batches b ON p.id = b.product_id + WHERE p.brand_name ILIKE $1 + GROUP BY p.brand_name`, + [`%${query}%`] + ); + if (rows.length > 0) { + toolData = { search_results: rows }; + } else { + toolData = { message: `No medicine found matching '${query}'.` }; + } + actionCommand = 'NAVIGATE_INVENTORY'; + } else if (call.name === 'add_medicine') { + const args = call.args as any; + const id = `prd_${Date.now()}`; + await pool.query( + 'INSERT INTO products (id, brand_name, form, mrp) VALUES ($1, $2, $3, $4)', + [id, args.brand_name, args.form || 'tablet', args.mrp || 0] + ); + toolData = { success: true, message: `Added ${args.brand_name} successfully.` }; + actionCommand = 'NAVIGATE_CATALOGUE'; // Assume there's a catalogue or inventory view + } else if (call.name === 'add_stock') { + const args = call.args as any; + const pRes = await pool.query('SELECT id, brand_name FROM products WHERE brand_name ILIKE $1 LIMIT 1', [`%${args.product_name}%`]); + + if (pRes.rows.length > 0) { + const pid = pRes.rows[0].id; + const batchNo = args.batch_no || `B${Date.now()}`; + // Default expiry to 1 year from now if not provided + const expiry = args.expiry || new Date(Date.now() + 31536000000).toISOString().split('T')[0]; + + await pool.query( + 'INSERT INTO batches (batch_no, product_id, expiry, qty_on_hand, purchase_price) VALUES ($1, $2, $3, $4, $5)', + [batchNo, pid, expiry, args.qty, 0] + ); + toolData = { success: true, message: `Added ${args.qty} stock to ${pRes.rows[0].brand_name}.` }; + actionCommand = 'NAVIGATE_INVENTORY'; + } else { + toolData = { success: false, message: `Could not find product ${args.product_name} in database. Please add the medicine first.` }; + } + } else if (call.name === 'navigate_to') { + const target = (call.args as any)?.target; + toolData = { action: 'Navigating to ' + target }; + if (target === 'dashboard') actionCommand = 'NAVIGATE_DASHBOARD'; + if (target === 'pharmacy') actionCommand = 'NAVIGATE_PHARMACY'; + if (target === 'inventory') actionCommand = 'NAVIGATE_INVENTORY'; + if (target === 'prescriptions') actionCommand = 'NAVIGATE_PRESCRIPTIONS'; + } + + // Step 2: Ask Gemini to generate the final response in the user's language + const finalPrompt = `You are a helpful pharmacy AI assistant. +User's query: "${transcript}" +Database result: ${JSON.stringify(toolData)} + +CRITICAL INSTRUCTIONS: +1. Detect the language of the User's query (it may be English, Tamil script, or Tanglish - Tamil in English letters). +2. You MUST reply in the EXACT SAME LANGUAGE as the user. +3. If the user spoke Tanglish (e.g., "evlo irukku"), you MUST reply in Tanglish (e.g., "Motham 10 irukku"). +4. NEVER reply in English unless the user spoke in English. Keep it conversational and concise.`; + + const finalResponse = await ai.models.generateContent({ + model: 'gemini-3.6-flash', + contents: [finalPrompt] + }); + + return { + replyText: finalResponse.text || "Action completed.", + actionCommand + }; + } + + return { + replyText: response.text || "I didn't quite catch that.", + actionCommand: 'NONE' + }; + } catch (e) { + console.error("AI Error:", e); + return { + replyText: "Sorry, I am having trouble connecting to the AI system.", + actionCommand: 'NONE' + }; + } + } +} diff --git a/src/modules/audit/router.ts b/src/modules/audit/router.ts new file mode 100644 index 0000000..4f90ced --- /dev/null +++ b/src/modules/audit/router.ts @@ -0,0 +1,122 @@ +import { Router } from 'express'; +import { requireAuth } from '../../middleware/auth'; +import { pool } from '../../db/pool'; + +const router = Router(); + +// GET /v1/audit/ai-calls +router.get('/ai-calls', requireAuth, async (req, res, next) => { + try { + const { from, to, limit } = req.query; + + let query = ` + SELECT id, call_id, actor_user_id, task_type, details, created_at + FROM audit_logs + WHERE 1=1 + `; + const values: any[] = []; + + if (from) { + values.push(from); + query += ` AND created_at >= $${values.length}`; + } + + if (to) { + values.push(to); + query += ` AND created_at <= $${values.length}`; + } + + values.push(limit ? parseInt(limit as string) : 50); + query += ` ORDER BY created_at DESC LIMIT $${values.length}`; + + const { rows } = await pool.query(query, values); + + // Also grab the stock movements for full audit visibility + const { rows: stockMovements } = await pool.query(` + SELECT id, batch_no, movement_type, qty_delta, reference_id, created_at + FROM stock_movements + ORDER BY created_at DESC + LIMIT $1 + `, [limit ? parseInt(limit as string) : 50]); + + res.status(200).json({ + ai_calls: rows, + stock_movements: stockMovements + }); + } catch (error) { + next(error); + } +}); + +// GET /v1/audit/overrides +router.get('/overrides', requireAuth, async (req, res, next) => { + try { + const { rows } = await pool.query(` + SELECT + dispensing_id, + code, + reason, + actor_user_id, + occurred_at + FROM overrides_log + ORDER BY occurred_at DESC + LIMIT 100 + `); + + // Grouping for summary + const by_actor: Record = {}; + const by_code: Record = {}; + + for (const row of rows) { + by_actor[row.actor_user_id] = (by_actor[row.actor_user_id] || 0) + 1; + by_code[row.code] = (by_code[row.code] || 0) + 1; + } + + res.status(200).json({ + overrides: rows, + summary: { + by_actor, + by_code + } + }); + } catch (error) { + next(error); + } +}); + +// GET /v1/audit/h1-register +router.get('/h1-register', requireAuth, async (req, res, next) => { + try { + const { rows } = await pool.query(` + SELECT + id, dispensing_id, patient_name, prescriber_name, drug_name, batch_no, qty, created_at + FROM h1_registers + ORDER BY created_at DESC + `); + res.status(200).json({ register: rows }); + } catch (error) { + next(error); + } +}); + +// GET /v1/audit/sales-register +router.get('/sales-register', requireAuth, async (req, res, next) => { + try { + const { rows } = await pool.query(` + SELECT + d.id as invoice_no, + d.created_at as invoice_date, + d.subject_ref as patient, + d.dispensed_by, + d.total_amount, + d.payment_method + FROM dispensing_records d + ORDER BY d.created_at DESC + `); + res.status(200).json({ register: rows }); + } catch (error) { + next(error); + } +}); + +export default router; diff --git a/src/modules/catalogue/router.ts b/src/modules/catalogue/router.ts new file mode 100644 index 0000000..b372959 --- /dev/null +++ b/src/modules/catalogue/router.ts @@ -0,0 +1,62 @@ +import { Router } from 'express'; +import { requireAuth } from '../../middleware/auth'; +import { CatalogueService } from './service'; + +const router = Router(); +const service = new CatalogueService(); + +// GET /v1/drugs +router.get('/drugs', requireAuth, async (req, res, next) => { + try { + const limit = req.query.limit ? parseInt(req.query.limit as string) : 50; + const drugs = await service.getAllDrugs(limit); + res.status(200).json({ drugs }); + } catch (error) { + next(error); + } +}); + +// POST /v1/drugs +router.post('/drugs', requireAuth, async (req, res, next) => { + try { + const result = await service.addMedicine(req.body); + res.status(201).json(result); + } catch (error) { + next(error); + } +}); + +// GET /v1/drugs/search +router.get('/drugs/search', requireAuth, async (req, res, next) => { + try { + const q = (req.query.q as string) || ''; + const form = req.query.form ? (req.query.form as string) : undefined; + const limit = req.query.limit ? parseInt(req.query.limit as string) : 10; + + if (!q || q.length < 2) { + res.status(400).json({ + error: 'BAD_REQUEST', + message: 'Search query "q" is required and must be at least 2 characters.', + }); + return; + } + + const candidates = await service.searchDrugs(q, form, limit); + res.status(200).json({ candidates }); + } catch (error) { + next(error); + } +}); + +// GET /v1/products/:product_id/batches +router.get('/products/:product_id/batches', requireAuth, async (req, res, next) => { + try { + const productId = req.params.product_id as string; + const batches = await service.getProductBatches(productId); + res.status(200).json({ batches }); + } catch (error) { + next(error); + } +}); + +export default router; diff --git a/src/modules/catalogue/service.ts b/src/modules/catalogue/service.ts new file mode 100644 index 0000000..9e8ae86 --- /dev/null +++ b/src/modules/catalogue/service.ts @@ -0,0 +1,99 @@ +import { pool } from '../../db/pool'; + +export class CatalogueService { + async searchDrugs(q: string, form?: string, limit: number = 10) { + const query = ` + SELECT + p.id as product_id, + p.brand_name, + d.generic_name, + p.strength, + p.form, + p.manufacturer, + d.schedule, + d.is_nti, + d.pregnancy_category, + p.mrp, + p.dpco_ceiling, + (SELECT COALESCE(SUM(qty_on_hand), 0) FROM batches WHERE product_id = p.id AND expiry > CURRENT_DATE) as total_stock + FROM products p + JOIN drugs d ON p.drug_id = d.id + WHERE p.brand_name ILIKE $1 OR d.generic_name ILIKE $1 + LIMIT $2 + `; + const values = [`%${q}%`, limit]; + + const { rows } = await pool.query(query, values); + return rows; + } + + async getAllDrugs(limit: number = 50) { + const query = ` + SELECT + p.id as product_id, + p.brand_name, + d.generic_name, + p.strength, + p.form, + p.manufacturer, + d.schedule, + d.is_nti, + d.pregnancy_category, + p.mrp, + p.dpco_ceiling, + (SELECT COALESCE(SUM(qty_on_hand), 0) FROM batches WHERE product_id = p.id AND expiry > CURRENT_DATE) as total_stock + FROM products p + JOIN drugs d ON p.drug_id = d.id + ORDER BY p.brand_name ASC + LIMIT $1 + `; + const { rows } = await pool.query(query, [limit]); + return rows; + } + + async getProductBatches(productId: string) { + const query = ` + SELECT + batch_no, + expiry, + qty_on_hand, + EXTRACT(DAY FROM (expiry - CURRENT_DATE)) as days_to_expiry, + CASE + WHEN expiry <= CURRENT_DATE THEN 'expired' + WHEN expiry <= CURRENT_DATE + interval '30 days' THEN 'near_expiry' + ELSE 'available' + END as status + FROM batches + WHERE product_id = $1 AND qty_on_hand > 0 + ORDER BY expiry ASC + `; + + const { rows } = await pool.query(query, [productId]); + return rows; + } + + async addMedicine(payload: any) { + const { generic_name, schedule, brand_name, manufacturer, strength, form, mrp } = payload; + + // Check if generic exists + let drugId; + const { rows: drugs } = await pool.query(`SELECT id FROM drugs WHERE generic_name ILIKE $1`, [generic_name]); + if (drugs.length > 0) { + drugId = drugs[0].id; + } else { + drugId = 'drg_' + Date.now(); + await pool.query( + `INSERT INTO drugs (id, generic_name, schedule) VALUES ($1, $2, $3)`, + [drugId, generic_name, schedule || 'H'] + ); + } + + const productId = 'prd_' + Date.now(); + await pool.query(` + INSERT INTO products (id, drug_id, brand_name, manufacturer, strength, form, mrp) + VALUES ($1, $2, $3, $4, $5, $6, $7) + `, [productId, drugId, brand_name, manufacturer, strength, form, mrp]); + + return { product_id: productId, drug_id: drugId }; + } +} diff --git a/src/modules/dispensing/router.ts b/src/modules/dispensing/router.ts new file mode 100644 index 0000000..7647087 --- /dev/null +++ b/src/modules/dispensing/router.ts @@ -0,0 +1,109 @@ +import { Router } from 'express'; +import { requireAuth } from '../../middleware/auth'; +import { requireHeaders } from '../../middleware/headers'; +import { requireIdempotency } from '../../middleware/idempotency'; +import { DispensingService } from './service'; +import { pool } from '../../db/pool'; + +const router = Router(); +const service = new DispensingService(); + +// GET /v1/orders/history +router.get('/history', requireAuth, async (req, res, next) => { + try { + const history = await service.getDispensingHistory(); + res.status(200).json({ history }); + } catch (error) { + next(error); + } +}); + +// GET /v1/orders/h1-register +router.get('/h1-register', requireAuth, async (req, res, next) => { + try { + const records = await service.getH1Register(); + res.status(200).json({ records }); + } catch (error) { + next(error); + } +}); + +// POST /v1/orders +router.post('/', requireAuth, requireHeaders, async (req, res, next) => { + try { + const draft = await service.createOrderDraft(req.body); + res.status(201).json(draft); + } catch (error) { + next(error); + } +}); + +// PATCH /v1/orders/:id/items +router.patch('/:id/items', requireAuth, requireHeaders, async (req, res, next) => { + try { + const draft = await service.updateOrderItems(req.params.id as string, req.body.items); + res.status(200).json(draft); + } catch (error) { + next(error); + } +}); + +// POST /v1/orders/:id/precheck +router.post('/:id/precheck', requireAuth, async (req, res, next) => { + try { + const { clinical_context } = req.body; + // We would fetch the order items from the DB here, but for now we accept them from body or mock + const items = req.body.items || []; + + if (!clinical_context) { + return res.status(400).json({ error: 'CONTEXT_INCOMPLETE' }); + } + + const precheckResult = await service.runGates(clinical_context, items); + res.status(200).json(precheckResult); + } catch (error) { + next(error); + } +}); + +// POST /v1/orders/:id/dispense +router.post('/:id/dispense', requireAuth, async (req, res, next) => { + try { + const idempotencyKey = req.headers['idempotency-key']; + if (!idempotencyKey) { + return res.status(400).json({ error: 'MISSING_IDEMPOTENCY_KEY' }); + } + + const actorId = req.actorId; + if (!actorId) { + return res.status(403).json({ error: 'MISSING_ACTOR_ID' }); + } + + const prescriptionId = req.params.id; + let { subject_ref, prescriber_name, items, payment_method, overrides } = req.body; + + // If items are not provided, fetch them from the draft order + if (!items || items.length === 0) { + const draftRes = await pool.query('SELECT items FROM order_drafts WHERE id = $1', [prescriptionId]); + if (draftRes.rows.length > 0 && draftRes.rows[0].items) { + items = typeof draftRes.rows[0].items === 'string' ? JSON.parse(draftRes.rows[0].items) : draftRes.rows[0].items; + } + } + + if (!items || items.length === 0) { + return res.status(400).json({ error: 'NO_ITEMS_PROVIDED' }); + } + + // Pass to service which runs the SERIALIZABLE transaction + const result = await service.executeDispense(subject_ref, prescriber_name || 'Unknown Prescriber', items, actorId as string, req.tenantId as string | undefined, prescriptionId as string | undefined, payment_method as string, overrides || []); + + res.status(201).json(result); + } catch (error: any) { + if (error.status === 409) { + return res.status(409).json(error); + } + next(error); + } +}); + +export default router; diff --git a/src/modules/dispensing/service.ts b/src/modules/dispensing/service.ts new file mode 100644 index 0000000..d5555d1 --- /dev/null +++ b/src/modules/dispensing/service.ts @@ -0,0 +1,293 @@ +import { pool } from '../../db/pool'; +import { webhookService } from '../webhooks/service'; + +export class DispensingService { + async createOrderDraft(payload: any) { + const draftId = "ord_" + Date.now() + Math.floor(Math.random() * 1000); + const items = payload.items ? JSON.stringify(payload.items) : '[]'; + + await pool.query(` + INSERT INTO order_drafts (id, subject_ref, prescription_ref, items) + VALUES ($1, $2, $3, $4) + `, [draftId, payload.subject_ref, payload.prescription_ref || null, items]); + + return { order_draft_id: draftId, status: 'draft' }; + } + + async updateOrderItems(draftId: string, items: any[]) { + await pool.query(` + UPDATE order_drafts + SET items = $1, updated_at = CURRENT_TIMESTAMP + WHERE id = $2 + `, [JSON.stringify(items), draftId]); + + return { order_draft_id: draftId, updated_items: items.length }; + } + + async runGates(clinicalContext: any, items: { product_id: string, qty: number }[]) { + // Phase 1: Basic hard/soft gate mock implementations based on clinical context + const blocks: any[] = []; + const warnings: any[] = []; + let tier = 'routine'; + let totalAmount = 0; + + if (clinicalContext?.pregnancy?.status === 'pregnant') { + blocks.push({ + code: 'PREGNANCY_CATEGORY_D_X', + severity: 'hard', + detail: 'Fetotoxic in pregnancy.', + overridable: false + }); + tier = 'elevated'; + } + + if (clinicalContext?.allergies?.length > 0) { + tier = 'elevated'; + } + + // Check expiry soft gates from DB and calculate total amount + for (const item of items) { + const prodRes = await pool.query(`SELECT mrp, dpco_ceiling FROM products WHERE id = $1`, [item.product_id]); + if (prodRes.rows.length > 0) { + const prod = prodRes.rows[0]; + const mrp = parseFloat(prod.mrp) || 0; + const dpco = parseFloat(prod.dpco_ceiling); + if (!isNaN(dpco) && mrp > dpco) { + blocks.push({ + code: 'DPCO_CEILING_EXCEEDED', + severity: 'hard', + detail: `Billed price (MRP ${mrp}) exceeds DPCO ceiling (${dpco}).`, + overridable: false + }); + tier = 'elevated'; + } + totalAmount += mrp * item.qty; + } + const { rows } = await pool.query(` + SELECT batch_no, expiry + FROM batches + WHERE product_id = $1 AND qty_on_hand > 0 + ORDER BY expiry ASC LIMIT 1 + `, [item.product_id]); + + if (rows.length > 0) { + const batch = rows[0]; + const daysToExpiry = (new Date(batch.expiry).getTime() - Date.now()) / (1000 * 60 * 60 * 24); + + if (daysToExpiry <= 30 && daysToExpiry > 0) { + warnings.push({ + code: 'NEAR_EXPIRY', + batch_no: batch.batch_no, + expiry: batch.expiry, + overridable: true + }); + tier = tier === 'routine' ? 'standard' : tier; + } else if (daysToExpiry <= 0) { + blocks.push({ + code: 'EXPIRED_BATCH', + severity: 'hard', + detail: 'Batch past expiry', + overridable: false + }); + tier = 'elevated'; + } + } + } + + return { + blocks, + interactions: [], + warnings, + confirmation_tier: tier, + total_amount: totalAmount, + context_as_of: clinicalContext?.as_of || new Date().toISOString() + }; + } + + async executeDispense(subjectRef: string, prescriberName: string, items: any[], actorId: string, tenantId?: string, prescriptionId?: string, paymentMethod: string = 'cash', overrides: any[] = []) { + const client = await pool.connect(); + const h1Entries: string[] = []; + let dispensingId = ''; + + try { + // API spec requires SERIALIZABLE transaction for absolute safety against concurrent dispensings + await client.query('BEGIN ISOLATION LEVEL SERIALIZABLE'); + + dispensingId = "dsp_" + Date.now() + Math.floor(Math.random() * 1000); + + let totalAmount = 0; + const pendingQueries: { query: string, params: any[] }[] = []; + const allocations = []; + + for (const item of items) { + let remainingQtyToAllocate = item.qty; + + // Fetch product and drug details first + const prodRes = await client.query(` + SELECT p.brand_name, p.mrp, p.dpco_ceiling, d.schedule, d.generic_name + FROM products p + JOIN drugs d ON p.drug_id = d.id + WHERE p.id = $1 + `, [item.product_id]); + + if (prodRes.rows.length === 0) { + throw { status: 400, error: 'INVALID_PRODUCT', message: `Product ${item.product_id} not found.` }; + } + + const prod = prodRes.rows[0]; + const mrp = parseFloat(prod.mrp) || 0; + const dpco = parseFloat(prod.dpco_ceiling); + if (!isNaN(dpco) && mrp > dpco) { + throw { + status: 400, + error: 'DPCO_CEILING_EXCEEDED', + message: `Billed price for ${prod.brand_name} exceeds DPCO ceiling of ${dpco}.` + }; + } + totalAmount += mrp * item.qty; + + // FEFO Allocation: Order by expiry ASC + const { rows: batches } = await client.query(` + SELECT batch_no, qty_on_hand, expiry + FROM batches + WHERE product_id = $1 AND qty_on_hand > 0 AND expiry > CURRENT_DATE + ORDER BY expiry ASC + FOR UPDATE + `, [item.product_id]); + + let allocatedTotal = 0; + + for (const batch of batches) { + if (remainingQtyToAllocate <= 0) break; + + const takeQty = Math.min(batch.qty_on_hand, remainingQtyToAllocate); + remainingQtyToAllocate -= takeQty; + allocatedTotal += takeQty; + + // Decrement stock in DB (DB CHECK constraint ensures qty_on_hand >= 0) + await client.query(` + UPDATE batches + SET qty_on_hand = qty_on_hand - $1 + WHERE product_id = $2 AND batch_no = $3 + `, [takeQty, item.product_id, batch.batch_no]); + + // Write Stock Movement Audit (matches schema) + pendingQueries.push({ + query: ` + INSERT INTO stock_movements (batch_no, movement_type, qty_delta, reference_id) + VALUES ($1, 'dispense', $2, $3) + `, + params: [batch.batch_no, -takeQty, dispensingId] + }); + + // Write H1 Register Entry if applicable + if (prod.schedule === 'H1' || prod.schedule === 'H') { + const h1Id = "h1_" + Date.now() + Math.floor(Math.random() * 1000); + pendingQueries.push({ + query: ` + INSERT INTO h1_registers (id, dispensing_id, patient_name, prescriber_name, drug_name, batch_no, qty) + VALUES ($1, $2, $3, $4, $5, $6, $7) + `, + params: [h1Id, dispensingId, subjectRef, prescriberName, `${prod.brand_name} (${prod.generic_name})`, batch.batch_no, takeQty] + }); + h1Entries.push(h1Id); + } + + allocations.push({ + product_id: item.product_id, + batch_no: batch.batch_no, + qty: takeQty, + expiry: batch.expiry + }); + } + + if (allocatedTotal < item.qty) { + throw { + status: 409, + error: 'INSUFFICIENT_STOCK', + details: { product_id: item.product_id, requested: item.qty, available: allocatedTotal } + }; + } + } + + // Record the dispensing transaction with billing details now that totalAmount is calculated + await client.query(` + INSERT INTO dispensing_records (id, tenant_id, subject_ref, dispensed_by, prescription_id, total_amount, payment_method) + VALUES ($1, $2, $3, $4, $5, $6, $7) + `, [dispensingId, tenantId || 'unknown', subjectRef, actorId, prescriptionId || null, totalAmount, paymentMethod]); + + // Execute accumulated stock movements and H1 registers now that dispensing_records exists + for (const pq of pendingQueries) { + await client.query(pq.query, pq.params); + } + + // Log Overrides + for (const override of overrides) { + await client.query(` + INSERT INTO overrides_log (dispensing_id, code, reason, actor_user_id) + VALUES ($1, $2, $3, $4) + `, [dispensingId, override.code, override.reason, actorId]); + } + + await client.query('COMMIT'); + + // Dispatch webhook + await webhookService.dispatchEvent('dispensing.completed', `evt_${dispensingId}`, { + dispensing_id: dispensingId, + subject_ref: subjectRef, + occurred_at: new Date().toISOString() + }); + + return { + dispensing_id: dispensingId, + batch_allocations: allocations, + h1_register_entry_ids: h1Entries, + invoice_id: "inv_" + Math.floor(Math.random() * 10000) + }; + + } catch (e) { + await client.query('ROLLBACK'); + throw e; + } finally { + client.release(); + } + } + + async getDispensingHistory() { + const query = ` + SELECT + d.id as dispensing_id, + d.subject_ref as patient_name, + d.dispensed_by, + d.created_at, + d.prescription_id, + d.total_amount, + d.payment_method, + json_agg( + json_build_object( + 'batch_no', sm.batch_no, + 'qty', ABS(sm.qty_delta), + 'brand_name', p.brand_name, + 'strength', p.strength + ) + ) as items + FROM dispensing_records d + JOIN stock_movements sm ON sm.reference_id = d.id + JOIN batches b ON b.batch_no = sm.batch_no + JOIN products p ON p.id = b.product_id + GROUP BY d.id + ORDER BY d.created_at DESC + `; + const { rows } = await pool.query(query); + return rows; + } + + async getH1Register() { + const query = ` + SELECT * FROM h1_registers + ORDER BY created_at DESC + `; + const { rows } = await pool.query(query); + return rows; + } +} diff --git a/src/modules/identity/router.ts b/src/modules/identity/router.ts new file mode 100644 index 0000000..fd2b69d --- /dev/null +++ b/src/modules/identity/router.ts @@ -0,0 +1,40 @@ +import { Router } from 'express'; +import jwt from 'jsonwebtoken'; + +const router = Router(); +const JWT_SECRET = process.env.JWT_SECRET || 'super_secret_pharmacy_key_2026'; + +// POST /v1/auth/token +router.post('/token', (req, res) => { + const { grant_type, client_id, client_secret } = req.body; + + if (grant_type !== 'client_credentials') { + return res.status(400).json({ error: 'unsupported_grant_type' }); + } + + // Recognize our specific external tenants + const validClients = ['jayam_web_ai', 'telegaruda']; + + if (client_id && validClients.includes(client_id) && client_secret) { + // Generate a secure signed token specifically for this hospital + const token = jwt.sign( + { + tenantId: client_id, + scopes: ['rx:read', 'rx:write', 'dispense:human', 'catalogue:read'] + }, + JWT_SECRET, + { expiresIn: '8h' } + ); + + return res.status(200).json({ + access_token: token, + token_type: 'Bearer', + expires_in: 28800, + scopes: ['rx:read', 'rx:write', 'dispense:human', 'catalogue:read'] + }); + } + + return res.status(401).json({ error: 'invalid_client' }); +}); + +export default router; diff --git a/src/modules/inventory/router.ts b/src/modules/inventory/router.ts new file mode 100644 index 0000000..b1a376f --- /dev/null +++ b/src/modules/inventory/router.ts @@ -0,0 +1,66 @@ +import { Router } from 'express'; +import { requireAuth } from '../../middleware/auth'; +import { requireHeaders } from '../../middleware/headers'; +import { requireIdempotency } from '../../middleware/idempotency'; +import { InventoryService } from './service'; + +const router = Router(); +const service = new InventoryService(); + +// POST /v1/inventory/grn +router.post('/grn', requireAuth, requireHeaders, requireIdempotency, async (req, res, next) => { + try { + const actorId = req.actorId as string; + const result = await service.receiveGoods(req.body, actorId); + res.status(201).json(result); + } catch (error: any) { + if (error.status === 422) { + return res.status(422).json(error); + } + next(error); + } +}); + +// GET /v1/inventory/expiring +router.get('/expiring', requireAuth, async (req, res, next) => { + try { + const days = req.query.days ? parseInt(req.query.days as string) : 30; + const batches = await service.getExpiringBatches(days); + res.status(200).json({ batches }); + } catch (error) { + next(error); + } +}); + +// GET /v1/inventory/low-stock +router.get('/low-stock', requireAuth, async (req, res, next) => { + try { + const threshold = req.query.threshold ? parseInt(req.query.threshold as string) : 50; + const batches = await service.getLowStockBatches(threshold); + res.status(200).json({ batches }); + } catch (error) { + next(error); + } +}); + +// POST /v1/inventory/adjustments +router.post('/adjustments', requireAuth, requireHeaders, async (req, res, next) => { + try { + const result = await service.adjustStock(req.body, req.actorId as string); + res.status(201).json(result); + } catch (error) { + next(error); + } +}); + +// GET /v1/inventory/recall/:batch_no +router.get('/recall/:batch_no', requireAuth, async (req, res, next) => { + try { + const result = await service.recallTrace(req.params.batch_no as string); + res.status(200).json(result); + } catch (error) { + next(error); + } +}); + +export default router; diff --git a/src/modules/inventory/service.ts b/src/modules/inventory/service.ts new file mode 100644 index 0000000..47cb0d2 --- /dev/null +++ b/src/modules/inventory/service.ts @@ -0,0 +1,149 @@ +import { pool } from '../../db/pool'; + +export class InventoryService { + async adjustStock(payload: any, actorId: string) { + const { batch_no, qty_delta, reason_code, notes } = payload; + const client = await pool.connect(); + try { + await client.query('BEGIN ISOLATION LEVEL SERIALIZABLE'); + + await client.query(` + UPDATE batches + SET qty_on_hand = qty_on_hand + $1 + WHERE batch_no = $2 + `, [qty_delta, batch_no]); + + await client.query(` + INSERT INTO inventory_adjustments (batch_no, qty_delta, reason_code, notes, approved_by) + VALUES ($1, $2, $3, $4, $5) + `, [batch_no, qty_delta, reason_code, notes, actorId]); + + await client.query(` + INSERT INTO stock_movements (batch_no, movement_type, qty_delta, reference_id) + VALUES ($1, 'adjustment', $2, 'adj_' || extract(epoch from now())) + `, [batch_no, qty_delta]); + + await client.query('COMMIT'); + return { status: 'success', batch_no, adjusted: qty_delta }; + } catch (e) { + await client.query('ROLLBACK'); + throw e; + } finally { + client.release(); + } + } + + async recallTrace(batch_no: string) { + const { rows } = await pool.query(` + SELECT d.id as dispensing_id, d.tenant_id, d.subject_ref, sm.qty_delta as qty, d.created_at as dispensed_at + FROM stock_movements sm + JOIN dispensing_records d ON sm.reference_id = d.id + WHERE sm.batch_no = $1 AND sm.movement_type = 'dispense' + `, [batch_no]); + + return { + batch_no, + dispensings: rows.map(r => ({ + dispensing_id: r.dispensing_id, + tenant_id: r.tenant_id, + subject_ref: r.subject_ref, + qty: Math.abs(r.qty), + dispensed_at: r.dispensed_at + })), + total_affected: rows.length + }; + } + + async getExpiringBatches(daysThreshold: number = 30) { + const query = ` + SELECT + b.batch_no, + b.product_id, + p.brand_name, + b.qty_on_hand, + b.expiry, + (b.expiry - CURRENT_DATE) as days_to_expiry + FROM batches b + JOIN products p ON b.product_id = p.id + WHERE b.qty_on_hand > 0 + AND b.expiry <= CURRENT_DATE + $1::int + ORDER BY b.expiry ASC + `; + + const { rows } = await pool.query(query, [daysThreshold]); + return rows; + } + + async getLowStockBatches(qtyThreshold: number = 50) { + const query = ` + SELECT + b.batch_no, + b.product_id, + p.brand_name, + b.qty_on_hand, + b.expiry, + (b.expiry - CURRENT_DATE) as days_to_expiry + FROM batches b + JOIN products p ON b.product_id = p.id + WHERE b.qty_on_hand > 0 + AND b.qty_on_hand <= $1::int + ORDER BY b.qty_on_hand ASC + `; + + const { rows } = await pool.query(query, [qtyThreshold]); + return rows; + } + + async receiveGoods(payload: any, actorId: string) { + const client = await pool.connect(); + + try { + await client.query('BEGIN'); + + const batchesCreated = []; + const grnId = "grn_" + Date.now() + Math.floor(Math.random() * 1000); + + for (const line of payload.lines) { + // Validate expiry + if (new Date(line.expiry) <= new Date()) { + throw { + status: 422, + error: 'INVALID_EXPIRY', + details: { batch_no: line.batch_no, expiry: line.expiry, reason: 'expiry_in_past' } + }; + } + + // Insert or Update Batch (Upsert) + await client.query(` + INSERT INTO batches (batch_no, product_id, expiry, qty_on_hand, purchase_price) + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (batch_no) DO UPDATE + SET qty_on_hand = batches.qty_on_hand + EXCLUDED.qty_on_hand, + purchase_price = EXCLUDED.purchase_price + `, [line.batch_no, line.product_id, line.expiry, line.qty, line.purchase_price]); + + // Log Receipt Movement + await client.query(` + INSERT INTO stock_movements (batch_no, movement_type, qty_delta, reference_id) + VALUES ($1, 'receipt', $2, $3) + `, [line.batch_no, line.qty, grnId]); + + batchesCreated.push(line.batch_no); + } + + await client.query('COMMIT'); + + return { + grn_id: grnId, + batches_created: batchesCreated, + three_way_match: { status: "matched", discrepancies: [] } + }; + + } catch (e) { + await client.query('ROLLBACK'); + throw e; + } finally { + client.release(); + } + } +} diff --git a/src/modules/prescription/router.ts b/src/modules/prescription/router.ts new file mode 100644 index 0000000..1f7e451 --- /dev/null +++ b/src/modules/prescription/router.ts @@ -0,0 +1,67 @@ +import { Router } from 'express'; +import multer from 'multer'; +import path from 'path'; +import { requireAuth } from '../../middleware/auth'; +import { PrescriptionService } from './service'; + +const router = Router(); +const service = new PrescriptionService(); + +// Setup Multer for memory storage (for uploading to S3) +const storage = multer.memoryStorage(); +const upload = multer({ storage: storage }); + +// POST /v1/rx +router.post('/', requireAuth, upload.single('image'), async (req, res, next) => { + try { + if (!req.file) { + return res.status(400).json({ error: 'MISSING_IMAGE', message: 'No image file uploaded.' }); + } + + const actorId = req.actorId; + const result = await service.uploadPrescription(req.file, actorId!); + + res.status(202).json(result); // 202 Accepted (processing asynchronously) + } catch (error) { + next(error); + } +}); + +// GET /v1/prescriptions +router.get('/', requireAuth, async (req, res, next) => { + try { + const prescriptions = await service.getAllPrescriptions(); + res.status(200).json({ prescriptions }); + } catch (error) { + next(error); + } +}); + +// GET /v1/prescriptions/:id +router.get('/:id', requireAuth, async (req, res, next) => { + try { + const record = await service.getPrescription(req.params.id as string); + if (!record) return res.status(404).json({ error: 'NOT_FOUND' }); + + res.status(200).json(record); + } catch (error) { + next(error); + } +}); + +// POST /v1/prescriptions/:id/confirm +router.post('/:id/confirm', requireAuth, async (req, res, next) => { + try { + const actorId = req.actorId; + const result = await service.confirmPrescription(req.params.id as string, req.body, actorId!); + + res.status(200).json(result); + } catch (error: any) { + if (error.status === 422 || error.status === 400 || error.status === 404) { + return res.status(error.status).json(error); + } + next(error); + } +}); + +export default router; diff --git a/src/modules/prescription/service.ts b/src/modules/prescription/service.ts new file mode 100644 index 0000000..45b3938 --- /dev/null +++ b/src/modules/prescription/service.ts @@ -0,0 +1,175 @@ +import { pool } from '../../db/pool'; +import crypto from 'crypto'; +import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; + +let s3Client: S3Client | null = null; +if (process.env.S3_KEY && process.env.S3_SECRET) { + s3Client = new S3Client({ + region: process.env.S3_REGION, + credentials: { + accessKeyId: process.env.S3_KEY, + secretAccessKey: process.env.S3_SECRET + } + }); +} + +export class PrescriptionService { + + // Hash file buffer to ensure provable unalterability + hashBuffer(buffer: Buffer): string { + const hashSum = crypto.createHash('sha256'); + hashSum.update(buffer); + return hashSum.digest('hex'); + } + + async uploadPrescription(file: Express.Multer.File, actorId: string) { + const hash = this.hashBuffer(file.buffer); + const id = "rx_" + Date.now() + Math.floor(Math.random() * 1000); + + // Construct S3 key + const fileName = `${Date.now()}-${file.originalname}`; + let s3Url = ''; + + if (process.env.S3_KEY && process.env.S3_SECRET) { + const s3Key = `${process.env.S3_BASE_PATH || ''}${fileName}`; + s3Url = `${process.env.S3_PUBLIC_BASE_PATH || ''}${s3Key}`; + + // Upload to S3 + const command = new PutObjectCommand({ + Bucket: process.env.S3_BUCKET, + Key: s3Key, + Body: file.buffer, + ContentType: file.mimetype + }); + + await s3Client!.send(command); + } else { + console.warn("S3 Credentials not found, mocking S3 upload."); + s3Url = `https://mock-s3-bucket.s3.amazonaws.com/${fileName}`; + } + + const client = await pool.connect(); + try { + await client.query('BEGIN'); + + await client.query(` + INSERT INTO prescriptions (id, image_path, image_hash, status) + VALUES ($1, $2, $3, 'processing') + `, [id, s3Url, hash]); + + // Enqueue async extraction job + this.enqueueExtraction(id, file); + + await client.query('COMMIT'); + return { id, status: 'processing', hash }; + } catch (e) { + await client.query('ROLLBACK'); + throw e; + } finally { + client.release(); + } + } + + // Real-time Background Vision Model Extraction using Gemini + async enqueueExtraction(id: string, file: Express.Multer.File) { + setTimeout(async () => { + try { + const { GoogleGenAI } = require('@google/genai'); + const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); + + const prompt = `You are an AI assistant for a pharmacy. + Extract the information from this handwritten or printed medical prescription. + Return ONLY a JSON response in the following format, with NO markdown formatting, NO backticks, NO markdown JSON block, just the raw JSON object string: + { + "prescriber": { "name": "string", "registration_number": "string" }, + "patient": { "name": "string", "age": number }, + "image_quality": "good" | "poor", + "items": [ + { + "verbatim": "string (the exact written text)", + "parsed": { "brand": "string", "form": "tablet/capsule/syrup/etc", "dose": "string (e.g. 1-0-1)", "qty": number }, + "confidence": number (0.0 to 1.0), + "confusable_with": ["string"] + } + ] + }`; + + const response = await ai.models.generateContent({ + model: 'gemini-3.6-flash', + contents: [ + prompt, + { + inlineData: { + data: file.buffer.toString("base64"), + mimeType: file.mimetype + } + } + ], + }); + + const rawText = response.text || "{}"; + // Clean up markdown if the AI includes it by mistake + const cleanJsonText = rawText.replace(/```json/g, '').replace(/```/g, '').trim(); + const extractedData = JSON.parse(cleanJsonText); + + await pool.query(` + UPDATE prescriptions + SET status = 'pending_review', extracted_data = $1 + WHERE id = $2 + `, [extractedData, id]); + + console.log(`[AI Scan Complete] Prescription ${id} processed successfully.`); + } catch (err) { + console.error("Extraction job failed for", id, err); + // Fallback to error status + try { + await pool.query(`UPDATE prescriptions SET status = 'error' WHERE id = $1`, [id]); + } catch(e) {} + } + }, 100); // slight delay to allow response to return to client first + } + + async getPrescription(id: string) { + const { rows } = await pool.query(`SELECT * FROM prescriptions WHERE id = $1`, [id]); + return rows[0]; + } + + async getAllPrescriptions() { + const { rows } = await pool.query(`SELECT * FROM prescriptions ORDER BY created_at DESC`); + return rows; + } + + async confirmPrescription(id: string, confirmedData: any, actorId: string) { + // Human Gate 1: Validate that unconfirmed fields are explicitly reviewed + const { rows } = await pool.query(`SELECT status, extracted_data FROM prescriptions WHERE id = $1`, [id]); + const record = rows[0]; + + if (!record) throw { status: 404, error: 'NOT_FOUND' }; + if (record.status !== 'pending_review') throw { status: 400, error: 'INVALID_STATUS' }; + + const originalData = record.extracted_data; + + // Check if any low confidence items were not explicitly reviewed + for (let i = 0; i < originalData.items.length; i++) { + const origItem = originalData.items[i]; + if (origItem.confidence < 0.85 || origItem.confusable_with.length > 0) { + // The pharmacist must supply a confirmed item for this index + if (!confirmedData.items || !confirmedData.items[i] || !confirmedData.items[i].explicitly_confirmed) { + throw { + status: 422, + error: 'MISSING_CONFIRMATION', + message: `Item at index ${i} requires explicit positive selection due to low confidence or confusable candidates.` + }; + } + } + } + + await pool.query(` + UPDATE prescriptions + SET status = 'confirmed', confirmed_data = $1 + WHERE id = $2 + `, [confirmedData, id]); + + return { id, status: 'confirmed' }; + } +} diff --git a/src/modules/webhooks/service.ts b/src/modules/webhooks/service.ts new file mode 100644 index 0000000..870674a --- /dev/null +++ b/src/modules/webhooks/service.ts @@ -0,0 +1,47 @@ +import crypto from 'crypto'; + +const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || 'test_secret_for_webhooks'; +// For demo purposes, we log it or hit a dummy URL. +const WEBHOOK_URL = process.env.WEBHOOK_URL || 'http://localhost:8081/webhooks/pharmacy'; + +export class WebhookService { + async dispatchEvent(event: string, idempotencyKey: string, payload: any) { + const timestamp = Math.floor(Date.now() / 1000); + const bodyString = JSON.stringify(payload); + const dataToSign = `${timestamp}.${bodyString}`; + + const signature = crypto + .createHmac('sha256', WEBHOOK_SECRET) + .update(dataToSign) + .digest('hex'); + + const signatureHeader = `t=${timestamp},v1=${signature}`; + + console.log(`[WEBHOOK] Dispatching event: ${event} (Idempotency: ${idempotencyKey})`); + + try { + // Assuming Node 20+, native fetch is available + const response = await fetch(WEBHOOK_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Pharmacy-Event': event, + 'X-Pharmacy-Signature': signatureHeader, + 'Idempotency-Key': idempotencyKey, + }, + body: bodyString, + }); + + if (!response.ok) { + console.error(`[WEBHOOK] Dispatch failed with status: ${response.status}`); + } else { + console.log(`[WEBHOOK] Successfully dispatched event: ${event}`); + } + } catch (error: any) { + console.error(`[WEBHOOK] Failed to dispatch webhook: ${error.message}`); + // In a real system, this would queue for retry with exponential backoff. + } + } +} + +export const webhookService = new WebhookService(); diff --git a/test-api.ts b/test-api.ts new file mode 100644 index 0000000..44fe2f4 --- /dev/null +++ b/test-api.ts @@ -0,0 +1,41 @@ +async function testAPI() { + console.log("1. Requesting Token for jayam_web_ai..."); + + const tokenResponse = await fetch("http://localhost:8080/v1/auth/token", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + grant_type: "client_credentials", + client_id: "jayam_web_ai", + client_secret: "my_secret_key" + }) + }); + + const tokenData = await tokenResponse.json(); + + if (!tokenData.access_token) { + console.error("Failed to get token!", tokenData); + return; + } + + console.log("✅ Successfully received real JWT token!"); + console.log("Token:", tokenData.access_token.substring(0, 30) + "...\n"); + + console.log("2. Using Token to search for drugs (Catalogue API)..."); + + const searchResponse = await fetch("http://localhost:8080/v1/drugs/search?q=Losartan", { + method: "GET", + headers: { + "Authorization": `Bearer ${tokenData.access_token}`, + "X-Actor-Id": "usr_ph_1234" + } + }); + + const searchData = await searchResponse.json(); + + console.log(`✅ API Response Status: ${searchResponse.status}`); + console.log("Drug Search Results:"); + console.log(JSON.stringify(searchData, null, 2)); +} + +testAPI(); diff --git a/test-db.js b/test-db.js new file mode 100644 index 0000000..bd0a515 --- /dev/null +++ b/test-db.js @@ -0,0 +1,12 @@ +const { Pool } = require('pg'); +const pool = new Pool({ connectionString: 'postgres://postgres:csh%402018@localhost:5432/ai_pharmacy' }); + +pool.query("SELECT id, status, extracted_data FROM prescriptions ORDER BY created_at DESC LIMIT 2") + .then(res => { + console.log(res.rows); + pool.end(); + }) + .catch(err => { + console.error(err); + pool.end(); + }); diff --git a/test-db2.js b/test-db2.js new file mode 100644 index 0000000..5283a36 --- /dev/null +++ b/test-db2.js @@ -0,0 +1,16 @@ +const { Pool } = require('pg'); +const pool = new Pool({ connectionString: 'postgres://postgres:csh%402018@localhost:5432/ai_pharmacy' }); + +pool.query("SELECT * FROM dispensing_records ORDER BY created_at DESC LIMIT 5") + .then(res => { + console.log('dispensing_records:', res.rows); + return pool.query("SELECT * FROM stock_movements ORDER BY created_at DESC LIMIT 5"); + }) + .then(res => { + console.log('stock_movements:', res.rows); + pool.end(); + }) + .catch(err => { + console.error(err); + pool.end(); + }); diff --git a/test-db3.js b/test-db3.js new file mode 100644 index 0000000..f44c849 --- /dev/null +++ b/test-db3.js @@ -0,0 +1,12 @@ +const { Pool } = require('pg'); +const pool = new Pool({ connectionString: 'postgres://postgres:csh%402018@localhost:5432/ai_pharmacy' }); + +pool.query("SELECT id, brand_name FROM products") + .then(res => { + console.log('products:', res.rows); + pool.end(); + }) + .catch(err => { + console.error(err); + pool.end(); + }); diff --git a/test-dispense.ts b/test-dispense.ts new file mode 100644 index 0000000..3dbc3f5 --- /dev/null +++ b/test-dispense.ts @@ -0,0 +1,57 @@ +async function testDispense() { + console.log("1. Logging in as jayam_web_ai..."); + + const tokenResponse = await fetch("http://localhost:8080/v1/auth/token", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + grant_type: "client_credentials", + client_id: "jayam_web_ai", + client_secret: "my_secret_key" + }) + }); + + const token = (await tokenResponse.json()).access_token; + + console.log("\n2. Running Precheck for a pregnant patient..."); + const precheckResponse = await fetch("http://localhost:8080/v1/orders/ord_123/precheck", { + method: "POST", + headers: { + "Authorization": `Bearer ${token}`, + "X-Actor-Id": "usr_ph_1234", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + clinical_context: { + pregnancy: { status: "pregnant" } + }, + items: [ + { product_id: "prd_4410", qty: 30 } // Losar 50 + ] + }) + }); + + console.log("Precheck Results:", JSON.stringify(await precheckResponse.json(), null, 2)); + + console.log("\n3. Executing Dispense transaction (requesting 30 units of Losar 50)..."); + const dispenseResponse = await fetch("http://localhost:8080/v1/orders/ord_123/dispense", { + method: "POST", + headers: { + "Authorization": `Bearer ${token}`, + "X-Actor-Id": "usr_ph_1234", + "Idempotency-Key": "random-uuid-here-" + Date.now(), + "Content-Type": "application/json" + }, + body: JSON.stringify({ + subject_ref: "pat_123", + items: [ + { product_id: "prd_4410", qty: 30 } + ] + }) + }); + + console.log("Dispense Status:", dispenseResponse.status); + console.log("Dispense Results:", JSON.stringify(await dispenseResponse.json(), null, 2)); +} + +testDispense(); diff --git a/test-gemini.js b/test-gemini.js new file mode 100644 index 0000000..765d4ed --- /dev/null +++ b/test-gemini.js @@ -0,0 +1,19 @@ +require('dotenv').config(); +const { GoogleGenAI } = require('@google/genai'); +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); +ai.models.generateContent({ + model: 'gemini-3.6-flash', + contents: [ + 'Hello', + { + inlineData: { + data: Buffer.from('hello').toString("base64"), + mimeType: 'text/plain' + } + } + ], +}).then(res => { + console.log('Success:', res.text); +}).catch(err => { + console.error('Error:', err); +}); diff --git a/test-grn.ts b/test-grn.ts new file mode 100644 index 0000000..141c1fa --- /dev/null +++ b/test-grn.ts @@ -0,0 +1,60 @@ +import { pool } from './src/db/pool'; + +async function testGRN() { + console.log("1. Requesting Token..."); + const tokenResponse = await fetch("http://localhost:8080/v1/auth/token", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + grant_type: "client_credentials", + client_id: "jayam_web_ai", + client_secret: "my_secret_key" + }) + }); + const tokenData = await tokenResponse.json(); + const token = tokenData.access_token; + + if (!token) { + console.error("No token received", tokenData); + return; + } + + const batchNo = "BAT-" + Date.now(); + + console.log(`2. Sending GRN for Losar 50 (prd_4410), Batch: ${batchNo}...`); + const grnResponse = await fetch("http://localhost:8080/v1/inventory/grn", { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}`, + "X-Actor-Id": "usr_ph_admin", + "idempotency-key": "idemp-" + Date.now() + }, + body: JSON.stringify({ + lines: [ + { + batch_no: batchNo, + product_id: "prd_4410", + expiry: "2027-12-31", + qty: 500, + purchase_price: "50.00" + } + ] + }) + }); + + const grnData = await grnResponse.json(); + console.log("GRN Response Status:", grnResponse.status); + console.log("GRN Response Data:", grnData); + + console.log("\n3. Verifying Database..."); + const batchRes = await pool.query("SELECT * FROM batches WHERE batch_no = $1", [batchNo]); + console.log("Batch in DB:", batchRes.rows); + + const stockMoveRes = await pool.query("SELECT * FROM stock_movements WHERE batch_no = $1", [batchNo]); + console.log("Stock Movements in DB:", stockMoveRes.rows); + + process.exit(0); +} + +testGRN(); diff --git a/test-history.js b/test-history.js new file mode 100644 index 0000000..aeac9a0 --- /dev/null +++ b/test-history.js @@ -0,0 +1,29 @@ +const http = require('http'); + +const options = { + hostname: 'localhost', + port: 8080, + path: '/v1/orders/history', + method: 'GET', + headers: { + 'Authorization': 'Bearer test', + 'X-Actor-Id': 'act_admin' + } +}; + +const req = http.request(options, res => { + console.log(`statusCode: ${res.statusCode}`); + let data = ''; + res.on('data', d => { + data += d; + }); + res.on('end', () => { + console.log(data); + }); +}); + +req.on('error', error => { + console.error(error); +}); + +req.end(); diff --git a/test-models.js b/test-models.js new file mode 100644 index 0000000..06028bb --- /dev/null +++ b/test-models.js @@ -0,0 +1,8 @@ +require('dotenv').config(); +const { GoogleGenAI } = require('@google/genai'); +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); +ai.models.list().then(res => { + console.log(res); +}).catch(err => { + console.error('Error:', err); +}); diff --git a/test-phase2.ts b/test-phase2.ts new file mode 100644 index 0000000..54f5a35 --- /dev/null +++ b/test-phase2.ts @@ -0,0 +1,116 @@ +import fs from 'fs'; +import path from 'path'; + +async function testPhase2() { + console.log("1. Creating dummy image file..."); + const dummyFilePath = path.join(__dirname, 'dummy_rx.jpg'); + fs.writeFileSync(dummyFilePath, 'dummy image content'); + + console.log("2. Requesting Token..."); + const tokenResponse = await fetch("http://localhost:8080/v1/auth/token", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + grant_type: "client_credentials", + client_id: "jayam_web_ai", + client_secret: "my_secret_key" + }) + }); + const tokenData = await tokenResponse.json(); + const token = tokenData.access_token; + + if (!token) { + console.error("No token received", tokenData); + return; + } + + console.log("3. Uploading Prescription (POST /v1/prescriptions/upload)..."); + + // Use fetch with FormData to upload file + const formData = new FormData(); + const fileBlob = new Blob([fs.readFileSync(dummyFilePath)], { type: 'image/jpeg' }); + formData.append('image', fileBlob, 'dummy_rx.jpg'); + + const uploadResponse = await fetch("http://localhost:8080/v1/prescriptions/upload", { + method: "POST", + headers: { + "Authorization": `Bearer ${token}`, + "X-Actor-Id": "usr_ph_admin" + }, + body: formData as any + }); + + const uploadData = await uploadResponse.json(); + console.log("Upload Status:", uploadResponse.status); + console.log("Upload Data:", uploadData); + const rxId = uploadData.id; + + if (uploadResponse.status !== 202) { + console.error("Upload failed"); + return; + } + + console.log("\n4. Polling for Extraction Results (waiting 6 seconds)..."); + await new Promise(resolve => setTimeout(resolve, 6000)); + + const pollResponse = await fetch(`http://localhost:8080/v1/prescriptions/${rxId}`, { + method: "GET", + headers: { + "Authorization": `Bearer ${token}`, + "X-Actor-Id": "usr_ph_admin" + } + }); + + const pollData = await pollResponse.json(); + console.log("Poll Status:", pollResponse.status); + console.log("Image Path (S3 URL):", pollData.image_path); + console.log("Extracted Data:", JSON.stringify(pollData.extracted_data, null, 2)); + + console.log("\n5. Testing Confirmation - Human Gate 1"); + console.log("Attempting to confirm without explicit selection on low-confidence item..."); + + const badConfirmPayload = { + items: [ + { explicitly_confirmed: true }, + { explicitly_confirmed: false } // Fails Gate 1 + ] + }; + + const badConfirmResponse = await fetch(`http://localhost:8080/v1/prescriptions/${rxId}/confirm`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}`, + "X-Actor-Id": "usr_ph_admin" + }, + body: JSON.stringify(badConfirmPayload) + }); + + console.log("Bad Confirm Status (Expect 422):", badConfirmResponse.status); + console.log("Bad Confirm Response:", await badConfirmResponse.json()); + + console.log("\nAttempting to confirm properly..."); + const goodConfirmPayload = { + items: [ + { explicitly_confirmed: true }, + { explicitly_confirmed: true, parsed: { brand: "Hydralazine 25mg" } } + ] + }; + + const goodConfirmResponse = await fetch(`http://localhost:8080/v1/prescriptions/${rxId}/confirm`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}`, + "X-Actor-Id": "usr_ph_admin" + }, + body: JSON.stringify(goodConfirmPayload) + }); + + console.log("Good Confirm Status (Expect 200):", goodConfirmResponse.status); + console.log("Good Confirm Response:", await goodConfirmResponse.json()); + + process.exit(0); +} + +testPhase2(); diff --git a/test-remaining.ts b/test-remaining.ts new file mode 100644 index 0000000..d22d942 --- /dev/null +++ b/test-remaining.ts @@ -0,0 +1,61 @@ +async function testRemaining() { + console.log("1. Logging in as jayam_web_ai..."); + + const tokenResponse = await fetch("http://localhost:8080/v1/auth/token", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + grant_type: "client_credentials", + client_id: "jayam_web_ai", + client_secret: "my_secret_key" + }) + }); + + const token = (await tokenResponse.json()).access_token; + + console.log("\n2. Executing Goods Receipt Note (GRN)..."); + const grnResponse = await fetch("http://localhost:8080/v1/inventory/grn", { + method: "POST", + headers: { + "Authorization": `Bearer ${token}`, + "X-Actor-Id": "usr_mgr_1234", + "Idempotency-Key": "grn-uuid-" + Date.now(), + "Content-Type": "application/json" + }, + body: JSON.stringify({ + supplier_id: "sup_120", + invoice_no: "INV/2026/9999", + invoice_date: "2026-07-24", + po_ref: "po_8891", + lines: [ + { + product_id: "prd_4410", + batch_no: "B9999-" + Date.now(), + expiry: "2028-12-31", + qty: 500, + purchase_price: 54.20, + mrp: 78.50 + } + ] + }) + }); + + console.log("GRN Status:", grnResponse.status); + console.log("GRN Results:", JSON.stringify(await grnResponse.json(), null, 2)); + + console.log("\n3. Fetching Audit Logs..."); + const auditResponse = await fetch("http://localhost:8080/v1/audit/logs", { + method: "GET", + headers: { + "Authorization": `Bearer ${token}`, + "X-Actor-Id": "usr_mgr_1234" + } + }); + + console.log("Audit Status:", auditResponse.status); + const auditData = await auditResponse.json(); + console.log("Found Stock Movements:", auditData.stock_movements.length); + console.log("Latest Stock Movement:", JSON.stringify(auditData.stock_movements[0], null, 2)); +} + +testRemaining(); diff --git a/test_ai.ts b/test_ai.ts new file mode 100644 index 0000000..ba15b54 --- /dev/null +++ b/test_ai.ts @@ -0,0 +1,11 @@ +import { AIService } from './src/modules/ai/service'; +import * as dotenv from 'dotenv'; +dotenv.config(); +const s = new AIService(); +s.processVoiceCommand('low stock').then(res => { + console.log("SUCCESS:", res); + process.exit(0); +}).catch(err => { + console.error("FAIL:", err); + process.exit(1); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c3329ec --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "commonjs", + "rootDir": "./src", + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + }, + "include": ["src/**/*"] +} diff --git a/update_core_schema.js b/update_core_schema.js new file mode 100644 index 0000000..11ad9c4 --- /dev/null +++ b/update_core_schema.js @@ -0,0 +1,46 @@ +const { Pool } = require('pg'); +require('dotenv').config(); + +const pool = new Pool({ connectionString: process.env.DATABASE_URL }); + +async function updateDB() { + try { + await pool.query(` + CREATE TABLE IF NOT EXISTS idempotency_keys ( + key VARCHAR(100) PRIMARY KEY, + method VARCHAR(10), + path VARCHAR(255), + response_body JSONB, + response_status INTEGER, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS order_drafts ( + id VARCHAR(50) PRIMARY KEY, + subject_ref VARCHAR(100) NOT NULL, + prescription_ref VARCHAR(50), + items JSONB DEFAULT '[]'::jsonb, + status VARCHAR(20) DEFAULT 'draft', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS inventory_adjustments ( + id SERIAL PRIMARY KEY, + batch_no VARCHAR(100) REFERENCES batches(batch_no), + qty_delta INTEGER NOT NULL, + reason_code VARCHAR(50) NOT NULL, + notes TEXT, + approved_by VARCHAR(100) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + `); + console.log('Core API schema updated successfully'); + } catch (err) { + console.error('Error:', err); + } finally { + pool.end(); + } +} + +updateDB(); diff --git a/update_schema.js b/update_schema.js new file mode 100644 index 0000000..2720256 --- /dev/null +++ b/update_schema.js @@ -0,0 +1,31 @@ +const { Pool } = require('pg'); +require('dotenv').config(); + +const pool = new Pool({ connectionString: process.env.DATABASE_URL }); + +async function updateDB() { + try { + await pool.query(` + CREATE TABLE IF NOT EXISTS h1_registers ( + id VARCHAR(50) PRIMARY KEY, + dispensing_id VARCHAR(50) REFERENCES dispensing_records(id), + patient_name VARCHAR(255) NOT NULL, + prescriber_name VARCHAR(255), + drug_name VARCHAR(255) NOT NULL, + batch_no VARCHAR(100) NOT NULL, + qty INTEGER NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + ALTER TABLE products ADD COLUMN IF NOT EXISTS hsn_code VARCHAR(20); + UPDATE products SET dpco_ceiling = 80.00 WHERE id = 'prd_4410'; + UPDATE products SET dpco_ceiling = 120.00 WHERE id = 'prd_5510'; + `); + console.log('Schema updated successfully'); + } catch (err) { + console.error('Error:', err); + } finally { + pool.end(); + } +} + +updateDB(); diff --git a/uploads/1784888629279.jpg b/uploads/1784888629279.jpg new file mode 100644 index 0000000..016a09f --- /dev/null +++ b/uploads/1784888629279.jpg @@ -0,0 +1 @@ +dummy image content \ No newline at end of file