From 89fad9ab64d9feab07891fd70f6e480135953b64 Mon Sep 17 00:00:00 2001 From: admin Date: Mon, 27 Apr 2026 04:25:52 +0000 Subject: [PATCH] Primer commit --- .env.example | 33 + .gitignore | 81 + README.md | 106 + bus-gateway/.env.example | 37 + bus-gateway/.gitignore | 3 + bus-gateway/Dockerfile | 12 + bus-gateway/README.md | 150 + bus-gateway/app.js | 77 + bus-gateway/bin/www | 90 + bus-gateway/config/index.js | 58 + bus-gateway/controllers/iti104.js | 36 + bus-gateway/controllers/iti65.js | 160 + bus-gateway/controllers/iti67.js | 68 + bus-gateway/controllers/iti68.js | 55 + bus-gateway/controllers/iti78.js | 36 + bus-gateway/controllers/vhlIssue.js | 46 + bus-gateway/controllers/vhlVerify.js | 67 + bus-gateway/docker-compose.yml | 8 + ...ctureDefinition-MSALDocumentReference.json | 214 + .../docs/StructureDefinition-Patient.json | 231 + bus-gateway/docs/iti104.mmd | 13 + bus-gateway/docs/iti65.mmd | 20 + bus-gateway/docs/iti67.mmd | 27 + bus-gateway/docs/iti78.mmd | 13 + bus-gateway/docs/obtener_qr_vhl.mmd | 11 + bus-gateway/docs/patientExample.json | 139 + bus-gateway/docs/validar_qr_vhl.mmd | 27 + bus-gateway/package-lock.json | 5770 +++++++++++++++++ bus-gateway/package.json | 23 + bus-gateway/routes/iti104.js | 10 + bus-gateway/routes/iti65.js | 9 + bus-gateway/routes/iti67.js | 9 + bus-gateway/routes/iti68.js | 8 + bus-gateway/routes/iti78.js | 10 + bus-gateway/routes/vhl.js | 21 + bus-gateway/services/documentReference.js | 109 + bus-gateway/services/patient.js | 138 + bus-gateway/services/vhlIssue.js | 109 + bus-gateway/services/vhlVerify.js | 50 + .../tests/services/documentReference.test.js | 179 + bus-gateway/tests/services/patient.test.js | 164 + bus-gateway/tests/utils/busAuth.test.js | 107 + bus-gateway/utils/busAuth.js | 114 + bus-gateway/utils/cbor.js | 109 + bus-gateway/utils/logger.js | 20 + bus-gateway/utils/vhlCrypto.js | 182 + bus-gateway/utils/vhlKeys.js | 79 + bus-gateway/utils/vhlStorage.js | 141 + docker-compose.yml | 86 + hapi-config/application.yaml | 207 + init_snowstorm.sh | 29 + nginx/http.conf | 79 + nginx/https.conf | 95 + tests/fixtures/iti65/bundle-document-ips.json | 363 ++ .../iti65/bundle-transaction-iti65.json | 573 ++ tests/fixtures/iti65/patient.json | 43 + 56 files changed, 10654 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 bus-gateway/.env.example create mode 100644 bus-gateway/.gitignore create mode 100644 bus-gateway/Dockerfile create mode 100644 bus-gateway/README.md create mode 100644 bus-gateway/app.js create mode 100755 bus-gateway/bin/www create mode 100644 bus-gateway/config/index.js create mode 100644 bus-gateway/controllers/iti104.js create mode 100644 bus-gateway/controllers/iti65.js create mode 100644 bus-gateway/controllers/iti67.js create mode 100644 bus-gateway/controllers/iti68.js create mode 100644 bus-gateway/controllers/iti78.js create mode 100644 bus-gateway/controllers/vhlIssue.js create mode 100644 bus-gateway/controllers/vhlVerify.js create mode 100644 bus-gateway/docker-compose.yml create mode 100644 bus-gateway/docs/StructureDefinition-MSALDocumentReference.json create mode 100644 bus-gateway/docs/StructureDefinition-Patient.json create mode 100644 bus-gateway/docs/iti104.mmd create mode 100644 bus-gateway/docs/iti65.mmd create mode 100644 bus-gateway/docs/iti67.mmd create mode 100644 bus-gateway/docs/iti78.mmd create mode 100644 bus-gateway/docs/obtener_qr_vhl.mmd create mode 100644 bus-gateway/docs/patientExample.json create mode 100644 bus-gateway/docs/validar_qr_vhl.mmd create mode 100644 bus-gateway/package-lock.json create mode 100644 bus-gateway/package.json create mode 100644 bus-gateway/routes/iti104.js create mode 100644 bus-gateway/routes/iti65.js create mode 100644 bus-gateway/routes/iti67.js create mode 100644 bus-gateway/routes/iti68.js create mode 100644 bus-gateway/routes/iti78.js create mode 100644 bus-gateway/routes/vhl.js create mode 100644 bus-gateway/services/documentReference.js create mode 100644 bus-gateway/services/patient.js create mode 100644 bus-gateway/services/vhlIssue.js create mode 100644 bus-gateway/services/vhlVerify.js create mode 100644 bus-gateway/tests/services/documentReference.test.js create mode 100644 bus-gateway/tests/services/patient.test.js create mode 100644 bus-gateway/tests/utils/busAuth.test.js create mode 100644 bus-gateway/utils/busAuth.js create mode 100644 bus-gateway/utils/cbor.js create mode 100644 bus-gateway/utils/logger.js create mode 100644 bus-gateway/utils/vhlCrypto.js create mode 100644 bus-gateway/utils/vhlKeys.js create mode 100644 bus-gateway/utils/vhlStorage.js create mode 100644 docker-compose.yml create mode 100644 hapi-config/application.yaml create mode 100644 init_snowstorm.sh create mode 100644 nginx/http.conf create mode 100644 nginx/https.conf create mode 100644 tests/fixtures/iti65/bundle-document-ips.json create mode 100644 tests/fixtures/iti65/bundle-transaction-iti65.json create mode 100644 tests/fixtures/iti65/patient.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..299c0df --- /dev/null +++ b/.env.example @@ -0,0 +1,33 @@ +# Configuración del contenedor de NGINX (ajusta según tus necesidades, por ejemplo, para SSL) +NGINX_CONF=http +# NGINX_CONF=http +# SSL_CERT_PATH=./certs/server.crt +# SSL_KEY_PATH=./certs/server.key + +# Bus FHIR (base, usado como fallback si no se definen MPI_URL o DOCUMENT_REGISTRY_URL) +BUS_URL=http://bus-host:8080 +BUS_JWT_SECRET=your-shared-secret +BUS_ISSUER=https://your-repositorio-url + +# Servicios del Bus (opcionales, por defecto usan BUS_URL) +MPI_URL=http://mpi-host:8080 +DOCUMENT_REGISTRY_URL=http://document-registry-host:8080 + +# Scopes por servicio +MPI_SCOPE=Patient/*.read,Patient/*.write +DOCUMENT_REGISTRY_SCOPE=DocumentReference/*.read,DocumentReference/*.write + + +# Habilita logs de requests/responses salientes al Bus (true | false) +BUS_DEBUG=false + + +SPRING_CONFIG_LOCATION="file:///data/hapi/application.yaml" +SPRING_DATASOURCE_URL="jdbc:postgresql://hapi-db:5433/root" +SPRING_DATASOURCE_USERNAME="root" +SPRING_DATASOURCE_PASSWORD="hapifhir2023" +SPRING_DATASOURCE_DRIVERCLASSNAME="org.postgresql.Driver" +SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT="ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect" +POSTGRES_DB="root" +POSTGRES_USER="root" +POSTGRES_PASSWORD="hapifhir2023" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0bc627d --- /dev/null +++ b/.gitignore @@ -0,0 +1,81 @@ +.idea +.env +.vscode +.github +.DS_Store +postgres-data +snomed/elastic +mongo-data + +*.pem +*.cer +*.key +*.p8 +elastic +lacchain-postgres-data +certs +did.txt +/fhir-terminology/package +/fhir-terminology/*.tgz + +postgres-data-app +json +.snowstorm-init-status + +# Python cache and bytecode files +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Virtual environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version +postgres-data-app/ +snomed/elastic +mongo-data +.snowstorm-init-status/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..d8ccf72 --- /dev/null +++ b/README.md @@ -0,0 +1,106 @@ +# IPS Dominio Backend + +Proyecto Docker Compose con los servicios necesarios para el nodo de dominio IPS (International Patient Summary). + +## Servicios + +| Servicio | Imagen / Fuente | Descripción | +|---|---|---| +| `hapi-fhir` | `hapiproject/hapi:latest` | Servidor FHIR R4 (implementación Spring Boot) | +| `hapi-db` | `postgres:14.6` | Base de datos PostgreSQL para HAPI FHIR | +| `bus-gateway` | `./bus-gateway` | Gateway hacia el Bus de salud (MPI y Document Registry) | +| `nginx` | `nginx:alpine` | Proxy inverso — punto de entrada HTTP/HTTPS | + +### Routing nginx + +| Ruta | Destino | +|---|---| +| `/bus-gateway/*` | `bus-gateway:3000` | +| todo lo demás | `hapi-fhir:8080` | + +## Requisitos + +- Docker >= 20.10.8 +- Docker Compose >= 1.29.2 + +## Configuración + +### 1. Variables de entorno + +Copia el archivo de ejemplo y completa los valores: + +```bash +cp .env.example .env +``` + +| Variable | Descripción | +|---|---| +| `BUS_URL` | URL base del Bus de salud | +| `BUS_JWT_SECRET` | Secreto compartido para JWT con el Bus | +| `BUS_ISSUER` | Issuer del token JWT | +| `MPI_URL` | URL del servicio MPI (default: `BUS_URL`) | +| `DOCUMENT_REGISTRY_URL` | URL del Document Registry (default: `BUS_URL`) | +| `MPI_SCOPE` | Scopes OAuth para MPI | +| `DOCUMENT_REGISTRY_SCOPE` | Scopes OAuth para Document Registry | +| `BUS_DEBUG` | Habilita logs de requests al Bus (`true` / `false`) | +| `SPRING_DATASOURCE_URL` | JDBC URL de la BD — debe usar puerto `5433` (`jdbc:postgresql://hapi-db:5433/root`) | +| `SPRING_DATASOURCE_USERNAME` | Usuario de la base de datos | +| `SPRING_DATASOURCE_PASSWORD` | Contraseña de la base de datos | +| `POSTGRES_DB` | Nombre de la base de datos PostgreSQL | +| `POSTGRES_USER` | Usuario PostgreSQL | +| `POSTGRES_PASSWORD` | Contraseña PostgreSQL | + +### 2. Configuración de nginx (HTTP o HTTPS) + +La variable `NGINX_CONF` en el `.env` selecciona el modo: + +**HTTP** (por defecto): +```env +NGINX_CONF=http +``` + +**HTTPS** (requiere certificados): +```env +NGINX_CONF=https +SSL_CERT_PATH=./certs/server.crt +SSL_KEY_PATH=./certs/server.key +``` + +Los certificados se inyectan como Docker secrets y nginx los lee desde `/run/secrets/ssl_cert` y `/run/secrets/ssl_key`. En modo HTTPS el tráfico HTTP (puerto 80) se redirige automáticamente a HTTPS (443). + +Los archivos de configuración están en [nginx/http.conf](nginx/http.conf) y [nginx/https.conf](nginx/https.conf). + +### 3. Configuración HAPI FHIR + +Los parámetros del servidor FHIR se ajustan en [hapi-config/application.yaml](hapi-config/application.yaml). El servidor usa **Lucene** como backend de búsqueda (índice local, no requiere servicio externo). + +## Levantar los servicios + +```bash +docker compose up -d +``` + +HAPI FHIR tarda aproximadamente **30-40 segundos** en arrancar completamente. + +## Verificar el despliegue + +```bash +# Estado de los contenedores (hapi-db debe aparecer "healthy") +docker compose ps + +# CapabilityStatement FHIR a través de nginx +curl http://localhost/fhir/metadata + +# Bus Gateway a través de nginx +curl http://localhost/bus-gateway/health +``` + +## Detener los servicios + +```bash +# Solo detener +docker compose down + +# Detener y eliminar volúmenes (borra datos de la BD) +docker compose down -v +``` diff --git a/bus-gateway/.env.example b/bus-gateway/.env.example new file mode 100644 index 0000000..1bd63b7 --- /dev/null +++ b/bus-gateway/.env.example @@ -0,0 +1,37 @@ +BASE_URL=localhost + +# Bus FHIR (base, usado como fallback si no se definen MPI_URL o DOCUMENT_REGISTRY_URL) +BUS_URL=http://bus-host:8080 +BUS_JWT_SECRET=your-shared-secret +BUS_ISSUER=https://your-repositorio-url + +# Servicios del Bus (opcionales, por defecto usan BUS_URL) +MPI_URL=http://mpi-host:8080 +DOCUMENT_REGISTRY_URL=http://document-registry-host:8080 + +# Scopes por servicio +MPI_SCOPE=Patient/*.read,Patient/*.write +DOCUMENT_REGISTRY_SCOPE=DocumentReference/*.read,DocumentReference/*.write + +# HAPI FHIR Server +FHIR_URL=http://hapi-fhir-host:8080/fhir + +LOG_LEVEL=debug + +# Habilita logs de requests/responses salientes al Bus (true | false) +BUS_DEBUG=false + +# --- VHL (Verifiable Health Link) --- +# Ruta al archivo PEM con la clave privada EC P-256 para firmar los CWT. +# Si no se configura, se genera una clave efímera en cada inicio (no apta para producción). +VHL_PRIVATE_KEY_FILE=/etc/bus-gateway/vhl-private-key.pem + +# URI del emisor del CWT (claim "iss"). Por defecto usa BUS_ISSUER. +# VHL_ISSUER=https://gateway.salud.gob.ar + +# URL pública base de este gateway, usada para construir las URLs de manifest y documento. +# Requerida para usar los endpoints VHL. +VHL_BASE_URL=https://gateway.salud.gob.ar + +# TTL de los tokens VHL y documentos cifrados, en segundos (por defecto: 604800 = 7 días). +# VHL_TOKEN_TTL=604800 diff --git a/bus-gateway/.gitignore b/bus-gateway/.gitignore new file mode 100644 index 0000000..df4c291 --- /dev/null +++ b/bus-gateway/.gitignore @@ -0,0 +1,3 @@ +node_modules +.env +.local-content \ No newline at end of file diff --git a/bus-gateway/Dockerfile b/bus-gateway/Dockerfile new file mode 100644 index 0000000..c2bc76b --- /dev/null +++ b/bus-gateway/Dockerfile @@ -0,0 +1,12 @@ +FROM node:20-alpine + +WORKDIR /app + +COPY package*.json ./ +RUN npm install --omit=dev + +COPY . . + +EXPOSE 3000 + +CMD ["node", "./bin/www"] diff --git a/bus-gateway/README.md b/bus-gateway/README.md new file mode 100644 index 0000000..06463c8 --- /dev/null +++ b/bus-gateway/README.md @@ -0,0 +1,150 @@ +# bus-gateway + +Gateway de interoperabilidad FHIR que expone los perfiles IHE MHD y PDQm/PMIR como fachada hacia un Bus de Interoperabilidad (Federador MSAL) y un servidor HAPI FHIR local. + +## Descripción + +El componente actúa exclusivamente como gateway: no contiene lógica de negocio propia. Su responsabilidad es: + +- Obtener el token de autenticación del Bus. +- Redirigir las llamadas entrantes al servicio de backend correspondiente (MPI, Document Registry o HAPI FHIR). +- Devolver los resultados al cliente tal como los retornan los servicios subyacentes. + +## Transacciones IHE implementadas + +| Transacción | Método | Ruta | Descripción | +|---|---|---|---| +| ITI-65 | `POST` | `/fhir/Bundle` | Provide Document Bundle: almacena el Bundle en HAPI FHIR, resuelve el ID nacional del paciente vía `$match` en el Bus y registra el DocumentReference en el Document Registry. | +| ITI-67 | `GET` | `/fhir/DocumentReference` | Find Document References: busca el paciente por ID local en el MPI para obtener su ID nacional y consulta los DocumentReferences en el Document Registry. | +| ITI-78 | `GET` | `/fhir/Patient` | Patient Demographics Query: búsqueda de pacientes en el MPI. | +| ITI-78 | `GET` | `/fhir/Patient/:id` | Patient Demographics Query: obtención de un paciente por ID en el MPI. | +| ITI-104 | `POST` | `/fhir/Patient` | Patient Identity Feed: alta de paciente en el MPI. | +| ITI-104 | `PUT` | `/fhir/Patient/:id` | Patient Identity Feed: actualización de paciente en el MPI. | + +## Variables de entorno + +Copiá `.env.example` a `.env` y completá los valores: + +```bash +cp .env.example .env +``` + +| Variable | Requerida | Descripción | +|---|---|---| +| `BUS_URL` | Sí | URL base del Bus. Se usa para autenticación y como fallback si no se definen `MPI_URL` ni `DOCUMENT_REGISTRY_URL`. | +| `BUS_JWT_SECRET` | Sí | Secreto compartido para firmar el JWT de autenticación contra el Bus. | +| `BUS_ISSUER` | Sí | Issuer del JWT (URL del repositorio). | +| `MPI_SCOPE` | Sí | Scopes OAuth para el MPI (ej: `Patient/*.read,Patient/*.write`). | +| `DOCUMENT_REGISTRY_SCOPE` | Sí | Scopes OAuth para el Document Registry (ej: `DocumentReference/*.read,DocumentReference/*.write`). | +| `MPI_URL` | No | URL base del servicio MPI (Master Patient Index). Si no se define, usa `BUS_URL`. | +| `DOCUMENT_REGISTRY_URL` | No | URL base del Document Registry. Si no se define, usa `BUS_URL`. | +| `FHIR_URL` | Sí | URL base del servidor HAPI FHIR local. | +| `PORT` | No | Puerto en que escucha el servidor. Por defecto `3000`. | + +### Usar la misma URL para todos los servicios del Bus + +Si el MPI y el Document Registry están expuestos bajo la misma URL que el Bus, alcanza con omitir `MPI_URL` y `DOCUMENT_REGISTRY_URL`: + +```env +BUS_URL=http://bus-host:8080 +BUS_JWT_SECRET=your-shared-secret +BUS_ISSUER=https://your-repositorio-url +MPI_SCOPE=Patient/*.read,Patient/*.write +DOCUMENT_REGISTRY_SCOPE=DocumentReference/*.read,DocumentReference/*.write + +FHIR_URL=http://hapi-fhir-host:8080/fhir +``` + +`MPI_URL` y `DOCUMENT_REGISTRY_URL` toman el valor de `BUS_URL` automáticamente. + +### Usar URLs diferentes por servicio + +Si cada servicio está en un host o contexto distinto: + +```env +BUS_URL=http://bus-host:8080 +BUS_JWT_SECRET=your-shared-secret +BUS_ISSUER=https://your-repositorio-url +MPI_SCOPE=Patient/*.read,Patient/*.write +DOCUMENT_REGISTRY_SCOPE=DocumentReference/*.read,DocumentReference/*.write + +MPI_URL=http://mpi-host:8080 +DOCUMENT_REGISTRY_URL=http://document-registry-host:8080 + +FHIR_URL=http://hapi-fhir-host:8080/fhir +``` + +## Ejecución local (sin Docker) + +### Requisitos + +- Node.js 20+ +- Acceso al Bus de Interoperabilidad y al servidor HAPI FHIR + +### Instalación + +```bash +npm install +``` + +### Inicio + +```bash +cp .env.example .env # completar las variables +npm start +``` + +El servidor queda disponible en `http://localhost:3000`. + +Para usar un puerto diferente: + +```bash +PORT=8080 npm start +``` + +### Tests + +```bash +npm test +``` + +Para correr un archivo específico: + +```bash +npm test tests/utils/busAuth.test.js +npm test tests/services/patient.test.js +npm test tests/services/documentReference.test.js +``` + +## Ejecución con Docker + +```bash +cp .env.example .env # configurar variables +docker compose up -d +``` + +Para ver los logs: + +```bash +docker compose logs -f +``` + +## Headers requeridos por transacción + +### ITI-65 `POST /fhir/Bundle` + +| Header | Descripción | +|---|---| +| `x-custodian-id` | Identificador del efector en el sistema Federador MSAL. | +| `Content-Type` | `application/fhir+json` | + +## Estructura del proyecto + +``` +bin/ Entrypoint del servidor Express +config/ Carga y validación de variables de entorno +controllers/ Lógica de cada transacción IHE +routes/ Definición de rutas HTTP por transacción +services/ Clientes de los servicios externos (MPI, Document Registry) +utils/ Autenticación contra el Bus (JWT + token) +``` diff --git a/bus-gateway/app.js b/bus-gateway/app.js new file mode 100644 index 0000000..649b7e9 --- /dev/null +++ b/bus-gateway/app.js @@ -0,0 +1,77 @@ +require('./config'); + +var createError = require('http-errors'); +var express = require('express'); +var logger = require('morgan'); + +var iti65Router = require('./routes/iti65'); +var iti67Router = require('./routes/iti67'); +var iti68Router = require('./routes/iti68'); +var iti78Router = require('./routes/iti78'); +var iti104Router = require('./routes/iti104'); +var vhlRouter = require('./routes/vhl'); + +var app = express(); + +app.use(logger('dev')); +app.use(express.json({ type: ['application/json', 'application/fhir+json'] })); + +// ITI-65: Provide Document Bundle → POST /fhir +app.use('/fhir', iti65Router); + +// ITI-67: Find Document References → GET /fhir/DocumentReference +app.use('/fhir', iti67Router); + +// ITI-68: Retrieve Document → GET /fhir/Binary?url=[URL_Directa] +app.use('/fhir', iti68Router); + +// ITI-78: Patient Demographics → GET /fhir/Patient[/:id] +app.use('/fhir', iti78Router); + +// ITI-104: Patient Identity Feed → POST /fhir/Patient, PUT /fhir/Patient/:id +app.use('/fhir', iti104Router); + +// VHL: Issue VHL → POST /vhl/:patientId +// Serve manifest → POST /vhl/manifest/:id +// Serve encrypted document → GET /vhl/document/:id +app.use('/vhl', vhlRouter); + +// 404 +app.use(function (req, res, next) { + const host = req.get('host') || 'localhost'; + const protocol = req.protocol; + const fullUrl = `${protocol}://${host}${req.originalUrl}`; + + const headerFlags = Object.entries(req.headers) + .map(([k, v]) => `-H '${k}: ${v}'`) + .join(' \\\n '); + + let curlCmd = `curl -X ${req.method} '${fullUrl}' \\\n ${headerFlags}`; + + if (req.body && Object.keys(req.body).length > 0) { + curlCmd += ` \\\n -d '${JSON.stringify(req.body)}'`; + } + + console.warn(`[${new Date().toISOString()}] 404 Not Found\n${curlCmd}`); + next(createError(404)); +}); + +// Error handler +app.use(function (err, req, res, _next) { + const status = err.status || 500; + if (status >= 500) { + console.error(`[${new Date().toISOString()}] ${req.method} ${req.originalUrl} -> ${status}`, err); + } else { + console.warn(`[${new Date().toISOString()}] ${req.method} ${req.originalUrl} -> ${status}: ${err.message}`); + } + res.status(status).json({ + resourceType: 'OperationOutcome', + issue: [{ + severity: 'error', + code: 'exception', + diagnostics: err.message, + }], + }); +}); + +module.exports = app; diff --git a/bus-gateway/bin/www b/bus-gateway/bin/www new file mode 100755 index 0000000..c57792a --- /dev/null +++ b/bus-gateway/bin/www @@ -0,0 +1,90 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var app = require('../app'); +var debug = require('debug')('bus-gateway:server'); +var http = require('http'); + +/** + * Get port from environment and store in Express. + */ + +var port = normalizePort(process.env.PORT || '3000'); +app.set('port', port); + +/** + * Create HTTP server. + */ + +var server = http.createServer(app); + +/** + * Listen on provided port, on all network interfaces. + */ + +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + var addr = server.address(); + var bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + debug('Listening on ' + bind); +} diff --git a/bus-gateway/config/index.js b/bus-gateway/config/index.js new file mode 100644 index 0000000..eda38d6 --- /dev/null +++ b/bus-gateway/config/index.js @@ -0,0 +1,58 @@ +require('dotenv').config(); + +const config = { + bus: { + url: process.env.BUS_URL, + mpiUrl: process.env.MPI_URL || process.env.BUS_URL, + documentRegistryUrl: process.env.DOCUMENT_REGISTRY_URL || process.env.BUS_URL, + jwtSecret: process.env.BUS_JWT_SECRET, + issuer: process.env.BUS_ISSUER, + mpiScope: process.env.MPI_SCOPE, + documentRegistryScope: process.env.DOCUMENT_REGISTRY_SCOPE, + }, + fhir: { + url: process.env.FHIR_URL, + }, + logging: { + enabled: true, + level: 'debug', + depthLimit: 5, + edgeLimit: 100, + msgPrefix: undefined, + crlf: false, + messageKey: 'msg', + errorKey: 'err', + nestedKey: null, + }, + vhl: { + // Path to an EC P-256 private key in PEM format for signing CWTs. + // If omitted, an ephemeral key is generated at startup (lost on restart). + privateKeyFile: process.env.VHL_PRIVATE_KEY_FILE, + // URI identifying this gateway as the CWT issuer (iss claim). + // Defaults to BUS_ISSUER so no extra variable is needed in most deployments. + issuer: process.env.VHL_ISSUER || process.env.BUS_ISSUER, + // Public base URL of this gateway, used to build manifest and document URLs. + // Required when the VHL endpoints are used. + baseUrl: process.env.VHL_BASE_URL, + // VHL token and document TTL in seconds (default: 7 days). + ttl: parseInt(process.env.VHL_TOKEN_TTL || '604800', 10), + }, + baseURL: process.env.BASE_URL || 'http://localhost', + debug: process.env.BUS_DEBUG === 'true', +}; + +const required = [ + ['BUS_URL', config.bus.url], + ['BUS_JWT_SECRET', config.bus.jwtSecret], + ['BUS_ISSUER', config.bus.issuer], + ['MPI_SCOPE', config.bus.mpiScope], + ['DOCUMENT_REGISTRY_SCOPE', config.bus.documentRegistryScope], + ['FHIR_URL', config.fhir.url], +]; + +const missing = required.filter(([, val]) => !val).map(([key]) => key); +if (missing.length > 0) { + throw new Error(`Missing required environment variables: ${missing.join(', ')}`); +} + +module.exports = config; diff --git a/bus-gateway/controllers/iti104.js b/bus-gateway/controllers/iti104.js new file mode 100644 index 0000000..391e3f7 --- /dev/null +++ b/bus-gateway/controllers/iti104.js @@ -0,0 +1,36 @@ +const config = require('../config'); +const { postPatient, putPatient, getPatientById } = require('../services/patient'); +const { getBusToken} = require('../utils/busAuth'); + +/** + * ITI-104: Patient Identity Feed FHIR (PMIR) + * + * POST /fhir/Patient + * Forwards the Patient resource to the Bus and returns the result. + */ +async function createPatient(req, res, next) { + try { + const token = await getBusToken(config.bus.url, config.bus.jwtSecret, config.bus.issuer, config.bus.mpiScope); + const response = await postPatient(token, req.body) + const patientResponse = await getPatientById(token, response.headers['location']); + res.status(200).json(patientResponse.data); + } catch (err) { + next(err); + } +} + +/** + * PUT /fhir/Patient/:id + * Forwards the update to the Bus and returns the result. + */ +async function updatePatient(req, res, next) { + try { + const token = await getBusToken(config.bus.url, config.bus.jwtSecret, config.bus.issuer, config.bus.mpiScope); + const response = await putPatient(token, req.body) + res.status(200).json(response.data); + } catch (err) { + next(err); + } +} + +module.exports = { createPatient, updatePatient }; diff --git a/bus-gateway/controllers/iti65.js b/bus-gateway/controllers/iti65.js new file mode 100644 index 0000000..b88f1fe --- /dev/null +++ b/bus-gateway/controllers/iti65.js @@ -0,0 +1,160 @@ +const axios = require('axios'); +const createError = require('http-errors'); +const config = require('../config'); +const { getBusToken, createBusRequest } = require('../utils/busAuth'); +const { searchPatient } = require('../services/patient'); +const { postDocumentReference, fetchDocumentReference } = require('../services/documentReference'); +const { logger } = require('../utils/logger') + + + +const DOCUMENT_REFERENCE_RESOURCE_TYPE = "DocumentReference"; +const IPS_DOCUMENT_RESOURCE_TYPE = 'Bundle'; +const PATIENT_RESOURCE_TYPE = 'Patient'; +const LIST_RESOURCE_TYPE = 'List'; + + +const NATIONAL_ID_SYSTEM = 'https://federador.msal.gob.ar/patient-id'; +const DNI_SYSTEM = 'http://www.renaper.gob.ar/dni'; +const CUSTODIAN_ID_SYSTEM = 'https://federador.msal.gob.ar/uri'; + + +/** + * Procesa una transacción y devuelve los recursos persistidos en el servidor HAPI FHIR + * @param {*} transactionBundle + * @returns + */ +async function processTransaction(transactionBundle) { + const response = await axios.post(`${config.fhir.url}`, transactionBundle, { + headers: { 'Content-Type': 'application/fhir+json' }, + }); + if (response.status !== 200) { + throw createError(response.status, response.data.issue.diagnostics); + } + const processed = response.data; + const resources = []; + const responses = processed.entry.map(e => e.response); + for (i = 0; i < responses.length; i++) { + const resource = await axios.get(`${config.fhir.url}/${responses[i].location}`) + resources.push(resource); + } + return Promise.resolve(resources); +} + +function extractResource(resources, resourceType) { + return resources.map(r => r.data).filter(r => r.resourceType === resourceType)[0]; +} + +function extractLocalIdentifier(patient) { + return patient.identifier.filter(i => { + return i.system !== NATIONAL_ID_SYSTEM && ((!!i.use && i.use === 'official') || (!i.use)) + })[0]; +} + +/** + * + * @param {*} documentReference + * @returns + */ +async function getBusCustodianIdentifier() { + return Promise.resolve({ system: CUSTODIAN_ID_SYSTEM, value: config.bus.issuer }); +} + +async function getBusPatientReference(patient, token) { + const localIdentifier = extractLocalIdentifier(patient); + const nationalPatientResponse = await searchPatient(token, { identifier: `${localIdentifier.system}|${localIdentifier.value}` }); + const nationalPatientBundle = nationalPatientResponse.data; + if (nationalPatientBundle.total == 0) { + throw createError(404, 'Patient does not exists'); + } + // NOTE:La siguiente consulta no debería traer mas de un resultado. + return Promise.resolve(nationalPatientBundle.entry[0].fullUrl); +} + +async function getLocalIPSDocumentReference(localIPSDocument) { + return Promise.resolve(`${config.baseURL}/fhir/Bundle/${localIPSDocument.id}`); +} + +/** + * Crea un nuevo document refrence en el bus + * @param {*} token + * @param {*} localIPSDocument + * @param {*} localPatient + * @param {*} localDocumentReference + * @returns + */ +async function createBusDocumentReference(token, localIPSDocument, localPatient) { + const busCustodianIdentifier = await getBusCustodianIdentifier(); + const busPatientReference = await getBusPatientReference(localPatient, token); + const localIPSDocumentReference = await getLocalIPSDocumentReference(localIPSDocument); + const busDocumentReference = { + resourceType: 'DocumentReference', + status: 'current', + type: { + coding: [ + { + system: 'http://loinc.org', + code: '60591-5', + display: 'Patient Summary Document', + }, + ], + }, + subject: { + reference: busPatientReference + }, + custodian: { + identifier: busCustodianIdentifier, + }, + content: [ + { + attachment: { + url: localIPSDocumentReference, + }, + }, + ], + }; + const response = await postDocumentReference(token, busDocumentReference); + const created = await fetchDocumentReference(token, response.headers['location']); + return created.data; +} + +/** + * ITI-65: Provide Document Bundle (MHD) + * + * POST /fhir/Bundle + * + * Flow: + * 1. Store the IPS Bundle in HAPI FHIR. + * 2. Resolve the patient national identifier via Bus $match. + * 3. Register a DocumentReference in the Bus pointing to the stored Bundle. + * + * Required header: + * x-custodian-id — Efector identifier value in the Federador MSAL system. + */ +async function provideDocumentBundle(req, res, next) { + try { + const transaction = req.body; + const token = await getBusToken( + config.bus.url, + config.bus.jwtSecret, + config.bus.issuer, + [config.bus.mpiScope, config.bus.documentRegistryScope].join(',') + ); + + // Paso 1: Ejecuto la transcción en el servidor hapi subyacente + const resources = await processTransaction(transaction); + // Paso 2: Obtengo el paciente local (tal como se guardo en la trasnaccion) + const localPatient = extractResource(resources, PATIENT_RESOURCE_TYPE); + // Paso 3: Obtengo el documento IPS local (tal como se guardo en la transacción) + const localIPSDocument = extractResource(resources, IPS_DOCUMENT_RESOURCE_TYPE) + // Paso 4: Creo el document reference en el indice de atenciones + const busDocumentReference = await createBusDocumentReference(token, localIPSDocument, localPatient); + + return status(200).json(busDocumentReference); + + } catch (err) { + next(err); + } +} + +module.exports = { provideDocumentBundle }; diff --git a/bus-gateway/controllers/iti67.js b/bus-gateway/controllers/iti67.js new file mode 100644 index 0000000..83d1e64 --- /dev/null +++ b/bus-gateway/controllers/iti67.js @@ -0,0 +1,68 @@ +const createError = require('http-errors'); +const config = require('../config'); +const { getBusToken } = require('../utils/busAuth'); +const { getPatientById, searchPatient } = require('../services/patient'); +const { searchDocumentReferenceByPatient } = require('../services/documentReference'); + +const NATIONAL_ID_SYSTEM = 'https://federador.msal.gob.ar/patient-id'; + +/** + * Extracts the Federador national identifier from a Patient resource. + */ +function extractNationalIdentifier(patient) { + const identifiers = Array.isArray(patient.identifier) ? patient.identifier : []; + return identifiers.find(id => id.system === NATIONAL_ID_SYSTEM) || null; +} + +async function getPatient(token, identifier) { + const response = await searchPatient(token, { identifier: identifier }); + return Promise.resolve(response.data.entry[0].fullUrl); +} + +async function getDocumentReferencesForPatient(token, patient) { + const response = await searchDocumentReferenceByPatient(token, patient) + return Promise.resolve(response.data); +} +/** + * ITI-67: Find Document References (MHD) + * + * GET /fhir/DocumentReference?subject=:localId + * + * Flow: + * 1. Fetch the Patient from the Bus by local ID to obtain the national identifier. + * 2. Search DocumentReferences in the Bus by that national identifier. + * 3. Return the resulting Bundle. + * + * Required query parameter: + * subject — local Patient ID in the Bus. + */ +async function findDocumentReferences(req, res, next) { + try { + const localPatientIdentifier = req.query.subject; + if (!localPatientIdentifier) { + throw createError(400, 'Missing required query parameter: subject'); + } + const token = await getBusToken( + config.bus.url, + config.bus.jwtSecret, + config.bus.issuer, + [ + config.bus.mpiScope, + config.bus.documentRegistryScope + ].join(',') + ); + // 1. Obtengo el identificador nacional del paciente en forma de referencia + const patientId = await getPatient(token, localPatientIdentifier); + if (!patientId) { + throw createError(422, 'Could not resolve national identifier for the given patient'); + } + // 2. Obtengo el listado de entradas del indice de atenciones asociadada al id de paciente. + const bundle = await getDocumentReferencesForPatient(token, patientId); + + res.status(200).json(bundle); + } catch (err) { + next(err); + } +} + +module.exports = { findDocumentReferences }; diff --git a/bus-gateway/controllers/iti68.js b/bus-gateway/controllers/iti68.js new file mode 100644 index 0000000..38d1078 --- /dev/null +++ b/bus-gateway/controllers/iti68.js @@ -0,0 +1,55 @@ +const createError = require('http-errors'); +const axios = require('axios'); + + +async function getRemoteDocument(url){ + const response = await axios.get(parsedUrl.toString(), { + responseType: 'arraybuffer', + headers: { + Accept: req.headers['accept'] || '*/*', + }, + }); + return response +} + + +/** + * ITI-68: Retrieve Document (MHD) + * + * GET /fhir/Binary?url=[URL_Directa] + * + * Flow (diagram iti67.mmd, step 21-24): + * 1. HIS_A supplies the direct document URL obtained from a prior ITI-67 response. + * 2. This gateway performs a P2P GET to that URL, bypassing the Bus. + * 3. The raw document (PDF, XML, etc.) is forwarded back to HIS_A. + */ +async function download(req, res, next) { + try { + const url = req.query.url; + if (!url) { + throw createError(400, 'Missing required query parameter: url'); + } + let parsedUrl; + try { + parsedUrl = new URL(url); + } catch { + throw createError(400, 'Invalid document URL'); + } + const response = await getRemoteDocument(url); + + const contentType = response.headers['content-type']; + if (contentType) res.setHeader('Content-Type', contentType); + + const contentDisposition = response.headers['content-disposition']; + if (contentDisposition) res.setHeader('Content-Disposition', contentDisposition); + + res.status(200).send(response.data); + } catch (err) { + if (err.response) { + return next(createError(err.response.status, `Document source returned ${err.response.status}`)); + } + next(err); + } +} + +module.exports = { download }; diff --git a/bus-gateway/controllers/iti78.js b/bus-gateway/controllers/iti78.js new file mode 100644 index 0000000..0983f63 --- /dev/null +++ b/bus-gateway/controllers/iti78.js @@ -0,0 +1,36 @@ +const config = require('../config'); +const { getBusToken, createBusRequest } = require('../utils/busAuth'); +const { searchPatient, getPatientById } = require('../services/patient'); + +/** + * ITI-78: Mobile Patient Demographics Query (PDQm) + * + * GET /fhir/Patient + * Forwards the raw FHIR query parameters to the Bus and returns the Bundle result. + */ +async function queryPatientDemographics(req, res, next) { + try { + const token = await getBusToken(config.bus.url, config.bus.jwtSecret, config.bus.issuer, config.bus.mpiScope); + const response = await searchPatient(token, req.query); + res.status(200).json(response.data); + } catch (err) { + next(err); + } +} + +/** + * GET /fhir/Patient/:id + * Forwards the request to the Bus and returns the Patient resource. + */ +async function findPatientById(req, res, next) { + try { + const { id } = req.params; + const token = await getBusToken(config.bus.url, config.bus.jwtSecret, config.bus.issuer, config.bus.mpiScope); + const response = await getPatientById(token, id) + res.status(200).json(response.data); + } catch (err) { + next(err); + } +} + +module.exports = { queryPatientDemographics, getPatientById: findPatientById }; diff --git a/bus-gateway/controllers/vhlIssue.js b/bus-gateway/controllers/vhlIssue.js new file mode 100644 index 0000000..d86c22f --- /dev/null +++ b/bus-gateway/controllers/vhlIssue.js @@ -0,0 +1,46 @@ +'use strict'; + +const createError = require('http-errors'); +const { issueVHL } = require('../services/vhlIssue'); + +/** + * POST /vhl/:patientId + * + * Issues a Verifiable Health Link (VHL) for a patient. + * Corresponds to Fase 1 (Emisión) in obtener_qr_vhl.mmd: + * MiArgentina → BusNación: solicita generación de VHL para [PacienteID] + * BusNación → MiArgentina: retorna VHL firmado (CWT) + * + * Path parameter: + * :patientId — FHIR Patient logical ID in the local HAPI FHIR server. + * + * Body (optional, application/json): + * { "pin": "1234" } ← citizen-configured PIN; protects the manifest. + * + * Response 201: + * { "cwt": "" } + * + * MiArgentina then: + * - Decodes the base64url to get the CWT bytes. + * - Compresses with ZLIB and encodes as Base45 to produce the QR payload. + * - Displays the QR code to the citizen. + */ +async function issueVHLController(req, res, next) { + try { + const { patientId } = req.params; + if (!patientId) { + throw createError(400, 'Missing path parameter: patientId'); + } + + const pin = req.body && typeof req.body.pin === 'string' + ? req.body.pin.trim() || undefined + : undefined; + + const result = await issueVHL({ patientId, pin }); + res.status(201).json(result); + } catch (err) { + next(err); + } +} + +module.exports = { issueVHLController }; diff --git a/bus-gateway/controllers/vhlVerify.js b/bus-gateway/controllers/vhlVerify.js new file mode 100644 index 0000000..ee032c3 --- /dev/null +++ b/bus-gateway/controllers/vhlVerify.js @@ -0,0 +1,67 @@ +'use strict'; + +const { getManifest, getDocument } = require('../services/vhlVerify'); + +/** + * POST /vhl/manifest/:manifestId + * + * Returns the VHL manifest for a given manifest ID. + * Corresponds to Fase 4 step 6 in validar_qr_vhl.mmd: + * NodoDominio → BusNación: POST [Manifest_URL] (envía PIN si el ciudadano lo configuró) + * BusNación → NodoDominio: 200 OK (retorna Manifest con link al NodoDominio_B) + * + * Path parameter: + * :manifestId — ID portion of the manifest URL embedded in the CWT. + * + * Body (optional, application/json): + * { "pin": "1234" } ← required only if the VHL was issued with a PIN. + * + * Response 200: + * { + * "files": [ + * { "contentType": "application/fhir+json", "location": "https://..." } + * ] + * } + */ +async function getManifestController(req, res, next) { + try { + const { manifestId } = req.params; + const pin = req.body && typeof req.body.pin === 'string' + ? req.body.pin.trim() || undefined + : undefined; + + const manifest = getManifest(manifestId, pin); + res.status(200).json(manifest); + } catch (err) { + next(err); + } +} + +/** + * GET /vhl/document/:documentId + * + * Returns the AES-256-GCM encrypted IPS document. + * Corresponds to Fase 4 step 8 in validar_qr_vhl.mmd: + * NodoDominio → NodoDominio_B: GET [Document_URL] (descarga P2P directa del archivo) + * NodoDominio_B → NodoDominio: 200 OK (retorna IPS encriptado) + * + * The caller decrypts with the symmetric key extracted from the CWT: + * Wire format: [ 12 bytes IV ][ N bytes ciphertext ][ 16 bytes GCM tag ] + * Algorithm: AES-256-GCM + * + * Response 200: application/octet-stream (binary encrypted payload) + */ +async function getDocumentController(req, res, next) { + try { + const { documentId } = req.params; + const payload = getDocument(documentId); + res + .status(200) + .set('Content-Type', 'application/octet-stream') + .send(payload); + } catch (err) { + next(err); + } +} + +module.exports = { getManifestController, getDocumentController }; diff --git a/bus-gateway/docker-compose.yml b/bus-gateway/docker-compose.yml new file mode 100644 index 0000000..a971866 --- /dev/null +++ b/bus-gateway/docker-compose.yml @@ -0,0 +1,8 @@ +services: + bus-gateway: + build: . + ports: + - "3000:3000" + env_file: + - .env + restart: unless-stopped diff --git a/bus-gateway/docs/StructureDefinition-MSALDocumentReference.json b/bus-gateway/docs/StructureDefinition-MSALDocumentReference.json new file mode 100644 index 0000000..8326913 --- /dev/null +++ b/bus-gateway/docs/StructureDefinition-MSALDocumentReference.json @@ -0,0 +1,214 @@ +{ + "resourceType": "StructureDefinition", + "id": "MSALDocumentReference", + "url": "https://federador.msal.gob.ar/fhir/StructureDefinition/MSALDocumentReference", + "version": "1.0.0", + "name": "MSALDocumentReference", + "title": "MSAL DocumentReference - Índice de Atenciones", + "status": "active", + "experimental": false, + "date": "2026-03-17", + "publisher": "Ministerio de Salud de la Nación Argentina (MSAL)", + "contact": [ + { + "name": "MSAL - FHIR Team", + "telecom": [ + { + "system": "url", + "value": "https://www.argentina.gob.ar/salud" + } + ] + } + ], + "description": "Perfil de DocumentReference para el Índice de Atenciones del sistema federado de salud. Implementa el patrón MHD (Mobile Health Documents) de IHE sobre FHIR R4, restringido al intercambio de Resúmenes de Historia Clínica (IPS - International Patient Summary) entre efectores de salud federados por el Ministerio de Salud de la Nación.", + "purpose": "Permitir que los efectores de salud registren y consulten referencias a documentos clínicos de sus pacientes federados, identificando al custodio mediante el sistema de identificación del Federador MSAL.", + "fhirVersion": "4.0.1", + "kind": "resource", + "abstract": false, + "type": "DocumentReference", + "baseDefinition": "http://hl7.org/fhir/StructureDefinition/DocumentReference", + "derivation": "constraint", + "differential": { + "element": [ + { + "id": "DocumentReference", + "path": "DocumentReference", + "short": "Referencia a documento clínico en el índice MSAL", + "definition": "Referencia a un Resumen de Historia Clínica (IPS) registrado por un efector de salud federado.", + "constraint": [ + { + "key": "msal-docref-1", + "severity": "error", + "human": "El custodian debe ser una referencia lógica (identifier), no una referencia literal", + "expression": "custodian.reference.exists().not() or custodian.reference.startsWith('#')", + "source": "https://federador.msal.gob.ar/fhir/StructureDefinition/MSALDocumentReference" + } + ] + }, + { + "id": "DocumentReference.status", + "path": "DocumentReference.status", + "short": "Estado del documento", + "definition": "Estado del DocumentReference. Debe ser 'current' para documentos activos.", + "min": 1, + "max": "1", + "mustSupport": true + }, + { + "id": "DocumentReference.type", + "path": "DocumentReference.type", + "short": "Tipo de documento: IPS (LOINC 60591-5)", + "definition": "Tipo de documento clínico. Restringido al Resumen de Historia Clínica Internacional (IPS). Debe contener exactamente un coding con sistema LOINC y código 60591-5.", + "min": 1, + "max": "1", + "mustSupport": true, + "binding": { + "strength": "required", + "description": "Tipo de documento: únicamente IPS", + "valueSet": "https://federador.msal.gob.ar/fhir/ValueSet/msal-document-type" + } + }, + { + "id": "DocumentReference.type.coding", + "path": "DocumentReference.type.coding", + "short": "Código LOINC del tipo de documento", + "definition": "Coding que identifica el tipo de documento. Exactamente uno, con sistema LOINC y código 60591-5 (Patient Summary Document).", + "min": 1, + "max": "1", + "mustSupport": true + }, + { + "id": "DocumentReference.type.coding.system", + "path": "DocumentReference.type.coding.system", + "short": "Sistema de codificación: LOINC", + "definition": "Sistema de codificación. Debe ser el sistema LOINC.", + "min": 1, + "max": "1", + "fixedUri": "http://loinc.org", + "mustSupport": true + }, + { + "id": "DocumentReference.type.coding.code", + "path": "DocumentReference.type.coding.code", + "short": "Código LOINC: 60591-5 (Patient Summary Document)", + "definition": "Código del tipo de documento. Fijo en 60591-5 (Patient Summary Document - International Patient Summary).", + "min": 1, + "max": "1", + "fixedCode": "60591-5", + "mustSupport": true + }, + { + "id": "DocumentReference.type.coding.display", + "path": "DocumentReference.type.coding.display", + "short": "Nombre del código LOINC", + "definition": "Descripción del código LOINC. Se recomienda 'Patient Summary Document'.", + "min": 0, + "max": "1", + "example": [ + { + "label": "Descripción IPS", + "valueString": "Patient Summary Document" + } + ] + }, + { + "id": "DocumentReference.subject", + "path": "DocumentReference.subject", + "short": "Paciente al que refiere el documento", + "definition": "Referencia al paciente. Se utiliza como parámetro de búsqueda mediante el identificador del paciente en el sistema del federador (subject:identifier).", + "min": 1, + "max": "1", + "type": [ + { + "code": "Reference", + "targetProfile": [ + "http://hl7.org/fhir/StructureDefinition/Patient" + ] + } + ], + "mustSupport": true + }, + { + "id": "DocumentReference.custodian", + "path": "DocumentReference.custodian", + "short": "Efector custodio del documento (referencia lógica al Federador)", + "definition": "Organización responsable del documento. Debe ser una referencia lógica (solo identifier, sin reference literal) con el sistema de identificación del Federador MSAL. El valor identifica al efector de salud en el registro del Federador.", + "min": 1, + "max": "1", + "mustSupport": true + }, + { + "id": "DocumentReference.custodian.reference", + "path": "DocumentReference.custodian.reference", + "short": "NO usar referencia literal", + "definition": "No se permite referencia literal. El custodian debe identificarse mediante su identifier en el sistema del Federador.", + "max": "0" + }, + { + "id": "DocumentReference.custodian.identifier", + "path": "DocumentReference.custodian.identifier", + "short": "Identificador del efector en el Federador MSAL", + "definition": "Identificador lógico del efector custodio en el sistema del Federador MSAL. El sistema es siempre 'https://federador.msal.gob.ar/uri'.", + "min": 1, + "max": "1", + "mustSupport": true + }, + { + "id": "DocumentReference.custodian.identifier.system", + "path": "DocumentReference.custodian.identifier.system", + "short": "Sistema de identificación del Federador", + "definition": "Sistema de identificación del Federador MSAL. Fijo en 'https://federador.msal.gob.ar/uri'.", + "min": 1, + "max": "1", + "fixedUri": "https://federador.msal.gob.ar/uri", + "mustSupport": true + }, + { + "id": "DocumentReference.custodian.identifier.value", + "path": "DocumentReference.custodian.identifier.value", + "short": "Identificador del efector (dominio del efector en el MPI)", + "definition": "Valor del identificador del efector de salud en el sistema del Federador. Debe corresponder al dominio registrado en el MPI del Federador.", + "min": 1, + "max": "1", + "mustSupport": true + }, + { + "id": "DocumentReference.content", + "path": "DocumentReference.content", + "short": "Contenido del documento referenciado", + "definition": "Contenido del DocumentReference. Debe haber exactamente uno, apuntando al Bundle IPS o al Binary que contiene el documento clínico.", + "min": 1, + "max": "1", + "mustSupport": true + }, + { + "id": "DocumentReference.content.attachment", + "path": "DocumentReference.content.attachment", + "short": "Referencia (URL) al documento clínico", + "definition": "Attachment que contiene la URL al recurso que almacena el documento. Puede ser una URL relativa tipo 'Bundle/{id}' (resuelta contra el base URL del servidor) o una URL absoluta del servidor del efector.", + "min": 1, + "max": "1", + "mustSupport": true + }, + { + "id": "DocumentReference.content.attachment.url", + "path": "DocumentReference.content.attachment.url", + "short": "URL al Bundle IPS o Binary del documento", + "definition": "URL que apunta al recurso que contiene el documento clínico. Puede ser: (1) relativa tipo 'Bundle/{uuid}' resuelta contra el servidor MSAL, o (2) absoluta apuntando al servidor del efector custodio (debe coincidir con el base URL del Endpoint registrado).", + "min": 1, + "max": "1", + "mustSupport": true, + "example": [ + { + "label": "URL relativa (Bundle local)", + "valueUrl": "Bundle/3a2e5c1d-4f6b-4a8c-9b2e-1d3f5a7c9e0b" + }, + { + "label": "URL absoluta (Bundle remoto)", + "valueUrl": "https://hce.efector.gob.ar/fhir/Bundle/abc123" + } + ] + } + ] + } +} diff --git a/bus-gateway/docs/StructureDefinition-Patient.json b/bus-gateway/docs/StructureDefinition-Patient.json new file mode 100644 index 0000000..a83f1a5 --- /dev/null +++ b/bus-gateway/docs/StructureDefinition-Patient.json @@ -0,0 +1,231 @@ +{ + "resourceType": "StructureDefinition", + "meta": { + "lastUpdated": "2018-02-07T11:29:47.769-03:00" + }, + "url": "https://federador.msal.gob.ar/StructureDefinition/Patient", + "name": "FederadorPatient", + "status": "draft", + "date": "2018-02-07T08:45:59.565-03:00", + "fhirVersion": "3.0.1", + "kind": "resource", + "abstract": false, + "type": "Patient", + "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient", + "derivation": "constraint", + "differential": { + "element": [ + { + "id": "Patient.identifier", + "path": "Patient.identifier", + "min": 2, + "mustSupport": true + }, + { + "id": "Patient.identifier.system", + "path": "Patient.identifier.system", + "mustSupport": true + }, + { + "id": "Patient.identifier.value", + "path": "Patient.identifier.value", + "mustSupport": true + }, + { + "id": "Patient.name", + "path": "Patient.name", + "min": 1, + "max": "2", + "mustSupport": true + }, + { + "id": "Patient.name.use", + "path": "Patient.name.use", + "min": 1, + "defaultValueCode": "official", + "mustSupport": true + }, + { + "id": "Patient.name.text", + "path": "Patient.name.text", + "mustSupport": true + }, + { + "id": "Patient.name.family", + "path": "Patient.name.family", + "mustSupport": true + }, + { + "id": "Patient.name.family.extension", + "path": "Patient.name.family.extension", + "slicing": { + "discriminator": [ + { + "type": "value", + "path": "url" + } + ], + "rules": "open" + }, + "mustSupport": true + }, + { + "id": "Patient.name.family.extension:fathers-family", + "path": "Patient.name.family.extension", + "sliceName": "fathers-family", + "min": 1, + "max": "1", + "type": [ + { + "code": "Extension", + "profile": "http://hl7.org/fhir/StructureDefinition/humanname-fathers-family" + } + ] + }, + { + "id": "Patient.name.family.extension:fathers-family.valueString:valueString", + "path": "Patient.name.family.extension.valueString", + "sliceName": "valueString" + }, + { + "id": "Patient.name.family.extension:mothers-family", + "path": "Patient.name.family.extension", + "sliceName": "mothers-family", + "max": "1", + "type": [ + { + "code": "Extension", + "profile": "http://hl7.org/fhir/StructureDefinition/humanname-mothers-family" + } + ] + }, + { + "id": "Patient.name.family.extension:mothers-family.valueString:valueString", + "path": "Patient.name.family.extension.valueString", + "sliceName": "valueString" + }, + { + "id": "Patient.name.given", + "path": "Patient.name.given", + "min": 1, + "maxLength": 30, + "mustSupport": true + }, + { + "id": "Patient.name.prefix", + "path": "Patient.name.prefix", + "max": "0" + }, + { + "id": "Patient.name.suffix", + "path": "Patient.name.suffix", + "max": "0" + }, + { + "id": "Patient.name.period", + "path": "Patient.name.period", + "max": "0" + }, + { + "id": "Patient.telecom", + "path": "Patient.telecom", + "max": "1", + "mustSupport": true + }, + { + "id": "Patient.telecom.system", + "path": "Patient.telecom.system", + "max": "1", + "mustSupport": true + }, + { + "id": "Patient.telecom.value", + "path": "Patient.telecom.value", + "maxLength": 20, + "mustSupport": true + }, + { + "id": "Patient.gender", + "path": "Patient.gender", + "min": 1, + "mustSupport": true + }, + { + "id": "Patient.birthDate", + "path": "Patient.birthDate", + "min": 1, + "mustSupport": true + }, + { + "id": "Patient.deceased[x]:deceasedDateTime", + "path": "Patient.deceasedDateTime", + "sliceName": "deceasedDateTime", + "type": [ + { + "code": "dateTime" + } + ] + }, + { + "id": "Patient.address", + "path": "Patient.address", + "max": "0" + }, + { + "id": "Patient.maritalStatus", + "path": "Patient.maritalStatus", + "max": "0" + }, + { + "id": "Patient.multipleBirth[x]", + "path": "Patient.multipleBirth[x]", + "max": "0" + }, + { + "id": "Patient.photo", + "path": "Patient.photo", + "max": "0" + }, + { + "id": "Patient.contact", + "path": "Patient.contact", + "max": "0" + }, + { + "id": "Patient.communication", + "path": "Patient.communication", + "max": "0" + }, + { + "id": "Patient.generalPractitioner", + "path": "Patient.generalPractitioner", + "max": "0" + }, + { + "id": "Patient.managingOrganization", + "path": "Patient.managingOrganization", + "max": "0" + }, + { + "id": "Patient.link", + "path": "Patient.link", + "max": "1" + }, + { + "id": "Patient.link.other.reference", + "path": "Patient.link.other.reference", + "min": 1 + }, + { + "id": "Patient.link.other.identifier", + "path": "Patient.link.other.identifier", + "max": "0" + }, + { + "id": "Patient.link.type", + "path": "Patient.link.type", + "fixedCode": "refer" + } + ] + } +} diff --git a/bus-gateway/docs/iti104.mmd b/bus-gateway/docs/iti104.mmd new file mode 100644 index 0000000..f5d89d2 --- /dev/null +++ b/bus-gateway/docs/iti104.mmd @@ -0,0 +1,13 @@ +sequenceDiagram + autonumber + participant HIS_A + participant NodoDominio as NodoDominio (HIS_A) + participant BusNacion as BusNacion + participant FederadorNacion as Federador + Note over HIS_A,FederadorNacion: Gestion de Identidad (PIXm/PDQm) + HIS_A->>NodoDominio: ITI-104 (Crea/Actualiza) + NodoDominio->>BusNacion: Enruta petición FHIR (POST /Patient) + BusNacion->>FederadorNacion: Ejecuta operación en el Federador + FederadorNacion-->>BusNacion: 201 Created (Recurso) + BusNacion-->>NodoDominio: Retorna Respuesta FHIR + NodoDominio-->>HIS_A: Confirma Operación diff --git a/bus-gateway/docs/iti65.mmd b/bus-gateway/docs/iti65.mmd new file mode 100644 index 0000000..2150de8 --- /dev/null +++ b/bus-gateway/docs/iti65.mmd @@ -0,0 +1,20 @@ +sequenceDiagram + autonumber + participant HIS_A + participant NodoDominio as "Nodo Dominio (emisor)" + participant BusNacion as "Bus Nacion" + participant FederadorNacion as "Federador Nacion" + participant IndiceNacion as "Indice Nacion" + Note over HIS_A,IndiceNacion: 1. Recepcion Local del Documento Bundle (POST /Bundle) + HIS_A->>NodoDominio: ITI-65 (Crea/Actualiza) + Note over NodoDominio,FederadorNacion: 2. Resolución de Identidad (ITI-83) + NodoDominio->>BusNacion: Get /Patient/$ihe-pix?sourceIdentifier=[ID_Local] + BusNacion->>FederadorNacion: Consulta correspondencia + FederadorNacion-->>BusNacion: 200 OK (Retorna ID Nacional) + BusNacion-->>NodoDominio: Entrega ID Nacional + Note over NodoDominio,IndiceNacion: 3. Enrutamiento de la Petición (POST /Bundle) + NodoDominio->>BusNacion: Publica Document Reference (GET /DocumentRefereence?subject=[ID_Nacional]) + BusNacion->>IndiceNacion: Registra Referencia + IndiceNacion-->>BusNacion: 201 (Created) + BusNacion-->>NodoDominio: Confirma operación + NodoDominio-->>HIS_A: 200 OK (Publicación exitosa) \ No newline at end of file diff --git a/bus-gateway/docs/iti67.mmd b/bus-gateway/docs/iti67.mmd new file mode 100644 index 0000000..9b875ab --- /dev/null +++ b/bus-gateway/docs/iti67.mmd @@ -0,0 +1,27 @@ +sequenceDiagram + autonumber + participant HIS_A + participant NodoDominio as "Nodo Dominio (emisor)" + participant BusNacion as "Bus Nacion" + participant FederadorNacion as "Federador Nacion" + participant IndiceNacion as "Indice Nacion" + participant NodoDominio2 as "Nodo Dominio (receptor)" + Note over HIS_A,FederadorNacion: 1. Busqueda y resolución de Identidad (PIXm ITI-83) + HIS_A->>NodoDominio: ITI-67: GET /DocumentReference?subject=[ID_Lcocal] + NodoDominio->>BusNacion: GET /Patient/$ihe-pix?sourceIdentifier=[ID_Local] + BusNacion->>FederadorNacion: Consulta correspondencia en MPI + FederadorNacion-->>BusNacion: 200 OK (Retorna ID Nacional) + BusNacion-->>NodoDominio: Entrega ID Nacional + Note over NodoDominio,IndiceNacion: 2. Busqueda de Metadatos (MHD ITI-67 adaptada) + NodoDominio->>BusNacion: GET /DocumentReference?subject=[ID_Nacional] + BusNacion->>IndiceNacion: Consulta en el índice de referencias + IndiceNacion-->>BusNacion: 200 OK (Bundle "searchset") + BusNacion-->>NodoDominio: Retorna Bundle con Document Reference + Note over HIS_A,NodoDominio2: 3. Descarga del documento (ITI-68 P2P) + HIS_A->>NodoDominio: Solicitar descarga de [URL_Directa] + NodoDominio->>NodoDominio2: GET [URL_Directa] (Conexión P2P, bypass BusNación) + NodoDominio2-->>NodoDominio: 200 OK (Retorna el documento clinico) + NodoDominio-->>HIS_A: Entrega documento fisico + + + diff --git a/bus-gateway/docs/iti78.mmd b/bus-gateway/docs/iti78.mmd new file mode 100644 index 0000000..7123267 --- /dev/null +++ b/bus-gateway/docs/iti78.mmd @@ -0,0 +1,13 @@ +sequenceDiagram + autonumber + participant HIS_A + participant NodoDominio as "Nodo Dominio (Repositorio Local)" + participant BusNacion as "Bus Nacion" + participant FederadorNacion as "Federador Nacion" + Note over HIS_A,FederadorNacion: Gestion de Identidad (PIXm/PDQm) + HIS_A->>NodoDominio: ITI-78 (Consulta) + NodoDominio->>BusNacion: Enruta petición FHIR (GET /Partient) + BusNacion->>FederadorNacion: Ejecuta operación en el Federador + FederadorNacion-->>BusNacion: 200 OK (Bundle) + BusNacion-->>NodoDominio: Retorna Respuesta FHIR + NodoDominio-->>HIS_A: Confirma Operación \ No newline at end of file diff --git a/bus-gateway/docs/obtener_qr_vhl.mmd b/bus-gateway/docs/obtener_qr_vhl.mmd new file mode 100644 index 0000000..c365b82 --- /dev/null +++ b/bus-gateway/docs/obtener_qr_vhl.mmd @@ -0,0 +1,11 @@ +sequenceDiagram + autonumber + actor Ciudadano as Ciudadano (VHL Holder) + participant MiArgentina as MiArgentina (Wallet) + participant BusNacion as Bus Nacion (VHL Sharer / Emisor) + Note over Ciudadano,BusNacion: Fase 1. Emisión del Verifiable Health Link (Issue VHL) + Ciudadano->>MiArgentina: Solicita compartir IPS (configurar PIN opcional) + MiArgentina->>BusNacion: Solicita generación de VHL para [PacienteID], + BusNacion-->>MiArgentina: Retorna VHL Firmado + MiArgentina->>MiArgentina: Comprime y codifica en Base45 (genera QR) + MiArgentina-->>Ciudadano: Despliega QR en pantalla \ No newline at end of file diff --git a/bus-gateway/docs/patientExample.json b/bus-gateway/docs/patientExample.json new file mode 100644 index 0000000..46bf89d --- /dev/null +++ b/bus-gateway/docs/patientExample.json @@ -0,0 +1,139 @@ +{ + "resourceType": "Bundle", + "id": "3c5a0da1-d409-4432-b133-761971cb8805", + "meta": { + "lastUpdated": "2026-03-31T19:57:00.849-03:00" + }, + "type": "searchset", + "total": 2, + "link": [ + { + "relation": "self", + "url": "https://federador.qa-bus-interoperabilidad.svc.cluster.local:8080/masterfile-federacion-service/fhir/Patient?identifier=http%3A%2F%2Fwww.renaper.gob.ar%2Fdni%7C30945027" + } + ], + "entry": [ + { + "fullUrl": "https://federador.qa-bus-interoperabilidad.svc.cluster.local:8080/masterfile-federacion-service/fhir/Patient/5037097", + "resource": { + "resourceType": "Patient", + "id": "5037097", + "identifier": [ + { + "use": "official", + "system": "https://federador.msal.gob.ar/patient-id", + "value": "5037097", + "period": { + "start": "2026-03-12T14:48:59-03:00" + } + }, + { + "use": "usual", + "system": "http://www.renaper.gob.ar/dni", + "value": "30945027" + }, + { + "use": "official", + "system": "https://agomez.pythonanywhere.com/hsi", + "value": "c1234567", + "period": { + "start": "2026-03-12T14:48:59-03:00" + } + } + ], + "active": true, + "name": [ + { + "use": "official", + "text": "Alejandro Lopez Auad", + "family": "Lopez Auad", + "_family": { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/humanname-fathers-family", + "valueString": "Lopez" + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/humanname-mothers-family", + "valueString": "Auad" + } + ] + }, + "given": [ + "Alejandro" + ] + } + ], + "telecom": [ + { + "system": "phone", + "value": "1141233100" + } + ], + "gender": "male", + "birthDate": "1983-04-17" + } + }, + { + "fullUrl": "https://federador.qa-bus-interoperabilidad.svc.cluster.local:8080/masterfile-federacion-service/fhir/Patient/5037096", + "resource": { + "resourceType": "Patient", + "id": "5037096", + "identifier": [ + { + "use": "official", + "system": "https://federador.msal.gob.ar/patient-id", + "value": "5037096", + "period": { + "start": "2026-03-12T14:38:09-03:00" + } + }, + { + "use": "usual", + "system": "http://www.renaper.gob.ar/dni", + "value": "30945027" + }, + { + "use": "official", + "system": "https://agomez.pythonanywhere.com/hsi", + "value": "b1234567", + "period": { + "start": "2026-03-12T14:38:09-03:00" + } + } + ], + "active": true, + "name": [ + { + "use": "official", + "text": "Alejandro Gomez Auad", + "family": "Gomez Auad", + "_family": { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/humanname-fathers-family", + "valueString": "Gomez" + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/humanname-mothers-family", + "valueString": "Auad" + } + ] + }, + "given": [ + "Alejandro" + ] + } + ], + "telecom": [ + { + "system": "phone", + "value": "1141233100" + } + ], + "gender": "male", + "birthDate": "1983-04-17" + } + } + ] +} \ No newline at end of file diff --git a/bus-gateway/docs/validar_qr_vhl.mmd b/bus-gateway/docs/validar_qr_vhl.mmd new file mode 100644 index 0000000..93e4d97 --- /dev/null +++ b/bus-gateway/docs/validar_qr_vhl.mmd @@ -0,0 +1,27 @@ +sequenceDiagram + autonumber + actor Ciudadano as Ciudadano (VHL Holder) + participant HIS_A as HIS_A (Médico / Consumidor) + participant NodoDominio as NodoDominio (Gateway origen) + participant RedDeConfianza (PKI / GDHCN) + participant BusNacion as BusNación + participant NodoDominio_B as NodoDominio_B (Repositorio destino) + Note over Ciudadano,HIS_A: Fase 2: Presentación (Provide VHL) + Ciudadano->>HIS_A: Muestra el código QR + HIS_A->>HIS_A: Escanea QR (decodifica Base45 y extgrae el CWT) + HIS_A->>NodoDominio: Delega validación y descarga (Envía el CWT) + Note over NodoDominio,RedDeConfianza: Fase 3. Verificación de confianza (Trust & Verify) + Note over NodoDominio,RedDeConfianza: Extrae el "kid" (Key ID) del encabezado del CWT + NodoDominio->>RedDeConfianza: Consulta clave pública del emisor + RedDeConfianza-->>NodoDominio: Retorna clave pública + NodoDominio->>NodoDominio: Verifica la firma digital del WCT (valida Autenticidad) + Note over NodoDominio,NodoDominio_B: Fase 4. Descarga segura P2P (Retrive VHL Manifest & Docs) + Note over NodoDominio: Extrae URL del Man ifest y Clave simétrica del payload del VHL + NodoDominio->>BusNacion: POST [Manifest_URL](Envía PIN si el ciudadano lo configuró) + BusNacion-->>NodoDominio: 200 OK (Retorna Manifest con link al NodoDominio_B) + NodoDominio->>NodoDominio_B: GET [Document_URL] (Descarga P2P directa del archivo) + NodoDominio_B-->>NodoDominio: 200 Ok (Retorna IPS encriptado) + NodoDominio->>NodoDominio: Desencripta el IPS usando la clave del QR + NodoDominio-->>HIS_A: Retorna documento clinico en texto claro + HIS_A-->>Ciudadano: Renderiza el documento para el médico. + diff --git a/bus-gateway/package-lock.json b/bus-gateway/package-lock.json new file mode 100644 index 0000000..0b1c719 --- /dev/null +++ b/bus-gateway/package-lock.json @@ -0,0 +1,5770 @@ +{ + "name": "bus-gateway", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "bus-gateway", + "version": "0.0.0", + "dependencies": { + "axios": "^1.3.5", + "cookie-parser": "~1.4.4", + "debug": "~2.6.9", + "dotenv": "^16.0.0", + "express": "~4.16.1", + "http-errors": "~1.6.3", + "jade": "~1.11.0", + "morgan": "~1.9.1", + "pino": "^10.3.1" + }, + "devDependencies": { + "jest": "^30.3.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", + "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", + "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.3.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.3.0", + "jest-config": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-resolve-dependencies": "30.3.0", + "jest-runner": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "jest-watcher": "30.3.0", + "pretty-format": "30.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", + "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-mock": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "30.3.0", + "jest-snapshot": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", + "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", + "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@sinonjs/fake-timers": "^15.0.0", + "@types/node": "*", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", + "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/types": "30.3.0", + "jest-mock": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", + "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", + "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", + "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.3.0", + "@jest/types": "30.3.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", + "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "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.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.2.1.tgz", + "integrity": "sha512-QdfpQFIwYrTK8lFsII4bJ1AO1ZLbw7B+oxfP+/qSsiTrVerFp7aY2O+d2GNGrTxP58ezEbjbf7mTTLMsd7M7XQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", + "integrity": "sha512-pXK8ez/pVjqFdAgBkF1YPVRacuLQ9EXBKaKWaeh58WNfMkCmZhOZzu+NtKSPD5PHmCCHheQ5cD29qM1K4QTxIg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz", + "integrity": "sha512-j3/4pkfih8W4NK22gxVSXcEonTpAHOHh0hu5BoZrKcOsW/4oBPxTi4Yk3SAj+FhC1f3+bRTkXdm4019gw1vg9g==", + "license": "MIT", + "dependencies": { + "acorn": "^2.1.0" + } + }, + "node_modules/align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==", + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", + "license": "BSD-3-Clause OR MIT", + "engines": { + "node": ">=0.4.2" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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/anymatch/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/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/asap/-/asap-1.0.0.tgz", + "integrity": "sha512-Ej9qjcXY+8Tuy1cNqiwNMwFRXOy9UwgTeMA8LxreodygIPV48lx8PU1ecFxb5ZeU1DpMKxiq6vGLTxcitWZPbA==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/axios": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", + "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/babel-jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", + "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "30.3.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.3.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", + "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", + "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + } + }, + "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/baseline-browser-mapping": { + "version": "2.10.13", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.13.tgz", + "integrity": "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha512-YQyoqQG3sO8iCmf8+hyVpgHHOv0/hCEFiS4zTGUwTA1HjAFX66wRcNQrVCeJq9pgESMRvUAOvSil5MJlmccuKQ==", + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "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/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001784", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz", + "integrity": "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ==", + "license": "MIT", + "dependencies": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/character-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-1.2.1.tgz", + "integrity": "sha512-6OEBVBlf/y8LaAphnbAnt743O3zMhlBer+FO5D40H6wqAdU9B1TvuApkejgLW0cvv0tEZNLktv1AnRI+C87ueQ==", + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/clean-css": { + "version": "3.4.28", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.28.tgz", + "integrity": "sha512-aTWyttSdI2mYi07kWqHi24NUU9YlELFKGOAgFzZjDN1064DMAOy2FBuoyGmkKRlXkbpXd0EVHmiVkbKhKoirTw==", + "license": "MIT", + "dependencies": { + "commander": "2.8.x", + "source-map": "0.4.x" + }, + "bin": { + "cleancss": "bin/cleancss" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-css/node_modules/commander": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "integrity": "sha512-+pJLBFVk+9ZZdlAOB5WuIElVPPth47hILFkmGym57aq8kwxsowvByvB0DHs1vQAhyMZzdcpTtF0VDKGkSDR4ZQ==", + "license": "MIT", + "dependencies": { + "graceful-readlink": ">= 1.0.0" + }, + "engines": { + "node": ">= 0.6.x" + } + }, + "node_modules/cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha512-GIOYRizG+TGoc7Wgc1LiOTLare95R3mzKgoln+Q/lE4ceiYH19gUpl0l0Ffq4lJDEf3FxujMe6IBfOCs7pfqNA==", + "license": "ISC", + "dependencies": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.6.0.tgz", + "integrity": "sha512-PhbTMT+ilDXZKqH8xbvuUY2ZEQNef0Q7DKxgoEKb4ccytsdvVVJmYqR0sGbi96nxU6oGrwEIQnclpK2NBZuQlg==", + "license": "MIT", + "engines": { + "node": ">= 0.6.x" + } + }, + "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/constantinople": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.0.2.tgz", + "integrity": "sha512-UnEggAQrmhxuTxlb7n1OsTtagNXWUv2CRlOogZhWOU4jLK4EJEbF8UDSNxuGu+jVtWNtO2j51ab2H1wlBIzF/w==", + "deprecated": "Please update to at least constantinople 3.1.1", + "license": "MIT", + "dependencies": { + "acorn": "^2.1.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "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-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/css/-/css-1.0.8.tgz", + "integrity": "sha512-qmTYWhHk910nQWnGqMAiWWPQlB6tESiWgNebQJmiozOAGcBAQ1+U/UzUOkhdrcshlkSRRiKWodwmVvO0OmnIGg==", + "dependencies": { + "css-parse": "1.0.4", + "css-stringify": "1.0.5" + } + }, + "node_modules/css-parse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.0.4.tgz", + "integrity": "sha512-pfstzKVRZiHprDXdsmtfH1HYUEw22lzjuHdnpe1hscwoQvgW2C5zDQIBE0RKoALEReTn9W1ECdY8uaT/kO4VfA==" + }, + "node_modules/css-stringify": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/css-stringify/-/css-stringify-1.0.5.tgz", + "integrity": "sha512-aIThpcErhG5EyHorGqNlTh0TduNBqLrrXLO3x5rku3ZKBxuVfY+T7noyM2G2X/01iQANqJUb6d3+FLoa+N7Xwg==" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", + "license": "MIT" + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.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/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "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/electron-to-chromium": { + "version": "1.5.330", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.330.tgz", + "integrity": "sha512-jFNydB5kFtYUobh4IkWUnXeyDbjf/r9gcUEXe1xcrcUxIGfTdzPXA+ld6zBRbwvgIGVzDll/LTIiDztEtckSnA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "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/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/express": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.3", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha512-+IJOX0OqlHCszo2mBUq+SrEbCj6w7Kpffqx60zYbPTFaO4+yYgRjHwcZNpWvaTylDHaV7PPmBHzSecZiMhtPgw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "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": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "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-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "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/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "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": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==", + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "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.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "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-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jade": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/jade/-/jade-1.11.0.tgz", + "integrity": "sha512-J76sbGKeLtu7uwW97Ntzb1UvGnpKTDplYa9ROr2gNRhM+SxvlBSG0Ees3TQ8+7ya2UVkzMEeFxhRhEpN68s7Tg==", + "deprecated": "Jade has been renamed to pug, please install the latest version of pug instead of jade", + "license": "MIT", + "dependencies": { + "character-parser": "1.2.1", + "clean-css": "^3.1.9", + "commander": "~2.6.0", + "constantinople": "~3.0.1", + "jstransformer": "0.0.2", + "mkdirp": "~0.5.0", + "transformers": "2.1.0", + "uglify-js": "^2.4.19", + "void-elements": "~2.0.1", + "with": "~4.0.0" + }, + "bin": { + "jade": "bin/jade.js" + } + }, + "node_modules/jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", + "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.3.0", + "@jest/types": "30.3.0", + "import-local": "^3.2.0", + "jest-cli": "30.3.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", + "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.3.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", + "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "p-limit": "^3.1.0", + "pretty-format": "30.3.0", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", + "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-cli/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-cli/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-config": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", + "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.3.0", + "@jest/types": "30.3.0", + "babel-jest": "30.3.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-circus": "30.3.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-runner": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "parse-json": "^5.2.0", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.3.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", + "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "jest-util": "30.3.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", + "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-mock": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-leak-detector": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", + "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", + "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", + "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", + "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", + "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.3.0", + "@jest/environment": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-leak-detector": "30.3.0", + "jest-message-util": "30.3.0", + "jest-resolve": "30.3.0", + "jest-runtime": "30.3.0", + "jest-util": "30.3.0", + "jest-watcher": "30.3.0", + "jest-worker": "30.3.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", + "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/globals": "30.3.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", + "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "pretty-format": "30.3.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", + "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.3.0", + "string-length": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", + "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.3.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jstransformer": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-0.0.2.tgz", + "integrity": "sha512-b7tmf91j1ChMuYhwbPBnNgB62dmHuqiHpOdd6QLKzde8HydZqm+ud3qWreGWecSxPBFFNOf1Ozjx0xo2plFdHA==", + "license": "MIT", + "dependencies": { + "is-promise": "^2.0.0", + "promise": "^6.0.1" + } + }, + "node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "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": "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/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "license": "MIT", + "bin": { + "mime": "cli.js" + } + }, + "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/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/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/morgan": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.0", + "debug": "2.6.9", + "depd": "~1.1.2", + "on-finished": "~2.3.0", + "on-headers": "~1.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "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/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optimist": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "integrity": "sha512-TCx0dXQzVtSCg2OgY/bO9hjM9cV4XYx09TVK+s3+FhkjT6LovsLe+pPMzpWf+6yXK/hUizs2gUoTw3jHM0VaTQ==", + "license": "MIT/X11", + "dependencies": { + "wordwrap": "~0.0.2" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=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-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==" + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/promise": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-6.1.0.tgz", + "integrity": "sha512-O+uwGKreKNKkshzZv2P7N64lk6EP17iXBn0PbUnNQhk+Q0AHLstiTrjkx3v5YBd3cxUe7Sq6KyRhl/A0xUjk7Q==", + "license": "MIT", + "dependencies": { + "asap": "~1.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/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha512-yqINtL/G7vs2v+dFIZmFUDbnVyFUJFKd6gK22Kgo6R4jfJGFtisKyncWDDULgjfqf4ASQuIQyjJ7XZ+3aWpsAg==", + "license": "MIT", + "dependencies": { + "align-text": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "engines": { + "node": ">=10" + } + }, + "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": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha512-Y8nIfcb1s/7DcobUz1yOO1GSp7gyL+D9zLHDehT7iRESqGSxjJ448Sg7rvfgsRJCnKLdSl11uGf0s9X80cH0/A==", + "license": "BSD-3-Clause", + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/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/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/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/test-exclude/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/thread-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz", + "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", + "dependencies": { + "real-require": "^0.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/transformers": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/transformers/-/transformers-2.1.0.tgz", + "integrity": "sha512-zJf5m2EIOngmBbDe2fhTPpCjzM2qkZVqrFJZc2jaln+KBeEaYKhS2QMOIkfVrNUyoOwqgbTwOHATzr3jZRQDyg==", + "deprecated": "Deprecated, use jstransformer", + "license": "MIT", + "dependencies": { + "css": "~1.0.8", + "promise": "~2.0", + "uglify-js": "~2.2.5" + } + }, + "node_modules/transformers/node_modules/is-promise": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-1.0.1.tgz", + "integrity": "sha512-mjWH5XxnhMA8cFnDchr6qRP9S/kLntKuEfIYku+PaN1CnS8v+OG9O/BKpRCVRJvpIkgAZm0Pf5Is3iSSOILlcg==", + "license": "MIT" + }, + "node_modules/transformers/node_modules/promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-2.0.0.tgz", + "integrity": "sha512-OgMc+sxI3zWF8D5BJGtA0z7/IsrDy1/0cPaDv6HPpqa2fSTo7AdON5U10NbZCUeF+zCAj3PtfPE50Hf02386aA==", + "license": "MIT", + "dependencies": { + "is-promise": "~1" + } + }, + "node_modules/transformers/node_modules/source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha512-VtCvB9SIQhk3aF6h+N85EaqIaBFIAfZ9Cu+NJHHVvc8BbEcnvDcFw6sqQ2dQrT6SlOrZq3tIvyD9+EGq/lJryQ==", + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/transformers/node_modules/uglify-js": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.2.5.tgz", + "integrity": "sha512-viLk+/8G0zm2aKt1JJAVcz5J/5ytdiNaIsKgrre3yvSUjwVG6ZUujGH7E2TiPigZUwLYCe7eaIUEP2Zka2VJPA==", + "dependencies": { + "optimist": "~0.3.5", + "source-map": "~0.1.7" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.4.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==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha512-qLq/4y2pjcU3vhlhseXGGJ7VbFO4pBANu0kwl8VCa9KEI0V8VfZIx2Fy3w01iSTA/pGwKZSmu/+I4etLNDdt5w==", + "license": "BSD-2-Clause", + "dependencies": { + "source-map": "~0.5.1", + "yargs": "~3.10.0" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + }, + "optionalDependencies": { + "uglify-to-browserify": "~1.0.0" + } + }, + "node_modules/uglify-js/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha512-vb2s1lYx2xBtUgy+ta+b2J/GLVUR+wmpINwHePmPRhOsIVCG2wDzKJ0n14GslH1BifsqVzSOwQhRaCAsZ/nI4Q==", + "license": "MIT", + "optional": true + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "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/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "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/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha512-1pTPQDKTdd61ozlKGNCjhNRd+KPmgLSGa3mZTHoOliaGcESD8G1PXhh7c1fgiPjVbNVfgy2Faw4BI8/m0cC8Mg==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/with": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/with/-/with-4.0.3.tgz", + "integrity": "sha512-mJZFpyEc1JTAdxhi/vhVeAM2S7vsltEKDiexDDo1HuAzlYKhcVUU6cwY8cHrFYdt82ZNkfKCeyhA3IYFegI0Kg==", + "license": "MIT", + "dependencies": { + "acorn": "^1.0.1", + "acorn-globals": "^1.0.3" + } + }, + "node_modules/with/node_modules/acorn": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz", + "integrity": "sha512-FsqWmApWGMGLKKNpHt12PMc5AK7BaZee0WRh04fCysmTzHe+rrKOa2MKjORhnzfpe4r0JnfdqHn02iDA9Dqj2A==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==", + "license": "MIT/X11", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha512-QFzUah88GAGy9lyDKGBqZdkYApt63rCXYBGYnEP4xDJPXNqXXnBDACnbrXnViV6jRSqAePwrATi2i8mfYm4L1A==", + "license": "MIT", + "dependencies": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/bus-gateway/package.json b/bus-gateway/package.json new file mode 100644 index 0000000..5f544dd --- /dev/null +++ b/bus-gateway/package.json @@ -0,0 +1,23 @@ +{ + "name": "bus-gateway", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "node ./bin/www", + "test": "jest" + }, + "dependencies": { + "axios": "^1.3.5", + "cookie-parser": "~1.4.4", + "debug": "~2.6.9", + "dotenv": "^16.0.0", + "express": "~4.16.1", + "http-errors": "~1.6.3", + "jade": "~1.11.0", + "morgan": "~1.9.1", + "pino": "^10.3.1" + }, + "devDependencies": { + "jest": "^30.3.0" + } +} diff --git a/bus-gateway/routes/iti104.js b/bus-gateway/routes/iti104.js new file mode 100644 index 0000000..0f1507a --- /dev/null +++ b/bus-gateway/routes/iti104.js @@ -0,0 +1,10 @@ +const express = require('express'); +const router = express.Router(); +const { createPatient, updatePatient } = require('../controllers/iti104'); + +// ITI-104: Patient Identity Feed FHIR +// TODO: Cambiar ruta por ITI104 +router.post('/iti104', createPatient); +router.put('/iti104/:id', updatePatient); + +module.exports = router; diff --git a/bus-gateway/routes/iti65.js b/bus-gateway/routes/iti65.js new file mode 100644 index 0000000..6c9531d --- /dev/null +++ b/bus-gateway/routes/iti65.js @@ -0,0 +1,9 @@ +const express = require('express'); +const router = express.Router(); +const { provideDocumentBundle } = require('../controllers/iti65'); + +// ITI-65: Provide Document Bundle +// TODO: Cambiar la ruta de acceso para que no se confunda con el recurso Bundle cambiar por iti65 +router.post('/iti65', provideDocumentBundle); + +module.exports = router; diff --git a/bus-gateway/routes/iti67.js b/bus-gateway/routes/iti67.js new file mode 100644 index 0000000..b0a7437 --- /dev/null +++ b/bus-gateway/routes/iti67.js @@ -0,0 +1,9 @@ +const express = require('express'); +const router = express.Router(); +const { findDocumentReferences } = require('../controllers/iti67'); + +// ITI-67: Find Document References +// TODO: Cambiar ruta por ITI67 +router.get('/iti67', findDocumentReferences); + +module.exports = router; diff --git a/bus-gateway/routes/iti68.js b/bus-gateway/routes/iti68.js new file mode 100644 index 0000000..a4fffa7 --- /dev/null +++ b/bus-gateway/routes/iti68.js @@ -0,0 +1,8 @@ +const express = require('express'); +const router = express.Router(); +const { download } = require('../controllers/iti68'); + +// ITI-68: Retrieve Document → GET /fhir/Binary?url=[URL_Directa] +router.get('/Binary', download); + +module.exports = router; diff --git a/bus-gateway/routes/iti78.js b/bus-gateway/routes/iti78.js new file mode 100644 index 0000000..70200cc --- /dev/null +++ b/bus-gateway/routes/iti78.js @@ -0,0 +1,10 @@ +const express = require('express'); +const router = express.Router(); +const { queryPatientDemographics, getPatientById } = require('../controllers/iti78'); + +// ITI-78: Mobile Patient Demographics Query +// TOOD: Cambiar ruta por ITI78 +router.get('/iti78', queryPatientDemographics); +router.get('/iti78/:id', getPatientById); + +module.exports = router; diff --git a/bus-gateway/routes/vhl.js b/bus-gateway/routes/vhl.js new file mode 100644 index 0000000..45c3380 --- /dev/null +++ b/bus-gateway/routes/vhl.js @@ -0,0 +1,21 @@ +'use strict'; + +const express = require('express'); +const router = express.Router(); +const { issueVHLController } = require('../controllers/vhlIssue'); +const { getManifestController, getDocumentController } = require('../controllers/vhlVerify'); + +// Specific sub-paths are registered before /:patientId to avoid ambiguity. +// Express matches routes in registration order; 'manifest' and 'document' +// are literal path segments, so they won't be captured by /:patientId. + +// Fase 4 — Retrieve VHL Manifest (validar_qr_vhl.mmd, step 6) +router.post('/manifest/:manifestId', getManifestController); + +// Fase 4 — Serve encrypted IPS document (validar_qr_vhl.mmd, step 8) +router.get('/document/:documentId', getDocumentController); + +// Fase 1 — Issue VHL (obtener_qr_vhl.mmd, steps 2–3) +router.post('/:patientId', issueVHLController); + +module.exports = router; diff --git a/bus-gateway/services/documentReference.js b/bus-gateway/services/documentReference.js new file mode 100644 index 0000000..8532e04 --- /dev/null +++ b/bus-gateway/services/documentReference.js @@ -0,0 +1,109 @@ +const config = require('../config'); +const { createBusRequest } = require('../utils/busAuth'); +const { default: axios } = require('axios'); + + +const INDICE_ATENCION_URL = config.bus.documentRegistryUrl; + +const LOCAL_URL = "https://federador.qa-bus-interoperabilidad.svc.cluster.local:8080/fhir/DocumentReference"; + +function maskPrivateURL(patientId) { + return patientId.replace(LOCAL_URL, INDICE_ATENCION_URL); +} + +async function fetchDocumentReference(token, id) { + const request = createBusRequest(token); + const response = await request.get( + URL.parse(id, INDICE_ATENCION_URL) + ); + return response; +} + +/** + * Posts a DocumentReference to the document registry + * (POST /fhir/DocumentReference). + * @param {string} documentRegistryUrl - Base URL of the document registry service. + * @param {string} token - Bearer access token. + * @param {object} documentReference - FHIR DocumentReference resource to register. + * @returns {Promise} The created DocumentReference resource returned by the registry. + * @todo Agregar system http://federador.msal.gob.ar/uri para armar el identificador del custodian: http://federador.msal.gob.ar/uri| + */ +async function postDocumentReference(token, documentReference) { + const request = createBusRequest(token); + const response = await request.post( + URL.parse(INDICE_ATENCION_URL), + documentReference + ); + response.headers['location'] = maskPrivateURL(response.headers['location']); + return response; +} +/** + * Searches DocumentReferences in the document registry by patient subject identifier + * (GET /fhir/DocumentReference?subject=system|value). + * @param {string} documentRegistryUrl - Base URL of the document registry service. + * @param {string} token - Bearer access token. + * @param {string} subjectSystem - Identifier system of the patient. + * @param {string} subjectValue - Identifier value of the patient. + * @returns {Promise} FHIR Bundle (searchset) with matching DocumentReferences. + */ +async function searchDocumentReferenceBySubject(token, subjectSystem, subjectValue) { + const request = createBusRequest(token); + const response = await request.get( + URL.parse(INDICE_ATENCION_URL), + { + params: { subject: `${subjectSystem}|${subjectValue}` }, + } + ); + return response; +} + +/** + * Searches DocumentReferences in the document registry by patient internal ID + * (GET /fhir/DocumentReference?patient=value). + * @param {string} documentRegistryUrl - Base URL of the document registry service. + * @param {string} token - Bearer access token. + * @param {string} patientId - Internal patient ID in the document registry. + * @returns {Promise} FHIR Bundle (searchset) with matching DocumentReferences. + */ +async function searchDocumentReferenceByPatient(token, patientId) { + const request = createBusRequest(token); + const response = await request.get( + URL.parse(INDICE_ATENCION_URL), + { + params: { subject: patientId }, + } + ); + return response; +} + +/** + * Searches DocumentReferences in the document registry filtered by subject, custodian and type + * (GET /fhir/DocumentReference?subject=...&custodian=...&type=...). + * @param {string} documentRegistryUrl - Base URL of the document registry service. + * @param {string} token - Bearer access token. + * @param {{ system: string, value: string }} subject - Patient identifier (TokenParam). + * @param {{ system: string, value: string }} custodian - Custodian identifier (TokenParam). + * @param {{ system: string, value: string }} type - Document type (TokenParam, e.g. { system: 'http://loinc.org', value: '60591-5' }). + * @returns {Promise} FHIR Bundle (searchset) with matching DocumentReferences. + */ +async function searchDocumentReference(token, subject, custodian, type) { + const request = createBusRequest(token); + const response = await request.get( + URL.parse(INDICE_ATENCION_URL), + { + params: { + subject: `${subject.system}|${subject.value}`, + custodian: `${custodian.system}|${custodian.value}`, + type: `${type.system}|${type.value}`, + }, + }); + return response; +} + +module.exports = { + fetchDocumentRefernce: fetchDocumentReference, + postDocumentReference, + searchDocumentReferenceBySubject, + searchDocumentReferenceByPatient, + searchDocumentReference, +}; diff --git a/bus-gateway/services/patient.js b/bus-gateway/services/patient.js new file mode 100644 index 0000000..d702418 --- /dev/null +++ b/bus-gateway/services/patient.js @@ -0,0 +1,138 @@ +const config = require('../config') +const { createBusRequest } = require('../utils/busAuth'); + + +const MPI_URL = config.bus.mpiUrl; + + +const LOCAL_URL = "https://federador.qa-bus-interoperabilidad.svc.cluster.local:8080/masterfile-federacion-service/fhir/Patient" + +function maskPrivateURL(patientId) { + return patientId.replace(LOCAL_URL, MPI_URL); +} + +/** + * POSTs a Patient to the MPI (Master Patient Index). + * @param {string} mpiUrl - Base URL of the MPI service. + * @param {string} token - Bearer access token obtained from getBusToken. + * @param {object} patient - The Patient resource. + * @returns {Promise} Response data from the MPI. + */ +async function postPatient(token, patient) { + const request = createBusRequest(token); + const response = await request.post( + URL.parse(MPI_URL), + patient + ); + response.headers['location'] = maskPrivateURL(response.headers['location']); + return response; +} + + +async function putPatient(token, patient, id) { + const request = createBusRequest(token); + const response = await request.put( + URL.parse(`${MPI_URL}/${id}`), + patient + ); + return response; +} + +/** + * Searches patients on the MPI using FHIR search criteria (GET /fhir/Patient). + * @param {string} mpiUrl - Base URL of the MPI service. + * @param {string} token - Bearer access token. + * @param {object} criteria - Search criteria (all optional): + * @param {string} [criteria.name] - Given name + * @param {string} [criteria.family] - Compound family name (fathers + mothers) + * @param {string} [criteria.fathersFamily] - Fathers family name + * @param {string} [criteria.mothersFamily] - Mothers family name + * @param {string} [criteria.identifierSystem] - Identifier system (OID) + * @param {string} [criteria.identifierValue] - Identifier value + * @param {string} [criteria.birthdate] - Birth date (YYYY-MM-DD) + * @param {string} [criteria.phone] - Phone number + * @param {string} [criteria.gender] - Gender (male | female | other | unknown) + * @returns {Promise} FHIR Bundle (searchset) with matching patients. + */ +async function searchPatient(token, criteria = {}) { + const request = createBusRequest(token); + + const params = {}; + if (criteria.name) params.name = criteria.name; + if (criteria.family) params.family = criteria.family; + if (criteria.fathersFamily) params.fathersFamily = criteria.fathersFamily; + if (criteria.mothersFamily) params.mothersFamily = criteria.mothersFamily; + if (criteria.birthdate) params.birthdate = criteria.birthdate; + if (criteria.phone) params.phone = criteria.phone; + if (criteria.gender) params.gender = criteria.gender; + if (criteria.identifier) params.identifier = criteria.identifier; + + const response = await request.get( + URL.parse(MPI_URL), + { params } + ); + if (!!response.data.entry) { + response.data.entry = response.data.entry.map(e => { + e.fullUrl = maskPrivateURL(e.fullUrl); + return e; + }); + } + if (!!response.data.linke) { + response.data.link = response.data.link.map(l => { + l.url = maskPrivateURL(l.url); + return l; + }); + } + return response; +} + +/** + * Fetches a single Patient by logical ID from the MPI (GET /fhir/Patient/:id). + * @param {string} mpiUrl - Base URL of the MPI service. + * @param {string} token - Bearer access token. + * @param {string} id - Logical Patient ID. + * @returns {Promise} FHIR Patient resource. + */ +async function getPatientById(token, id) { + const request = createBusRequest(token); + const response = await request.get( + URL.parse(`${MPI_URL}/${id}`) + ); + return response; +} + +/** + * Matches a patient against the MPI using the FHIR $match operation (POST /fhir/Patient/$match). + * @param {string} mpiUrl - Base URL of the MPI service. + * @param {string} token - Bearer access token. + * @param {object} patient - The Patient resource to match. + * @param {number} [count] - Maximum number of results to return (must be > 0). + * @returns {Promise} FHIR Bundle (searchset) with match scores. + */ +async function matchPatient(token, patient, count) { + const request = createBusRequest(token); + const parameters = { + resourceType: 'Parameters', + parameter: [ + { + name: 'resource', + resource: patient, + }, + ], + }; + + if (count != null) { + parameters.parameter.push({ + name: 'count', + valueInteger: count, + }); + } + + const response = await request.post( + URL.parse('$match', MPI_URL), + parameters + ); + return response; +} + +module.exports = { putPatient, postPatient, getPatientById, searchPatient, matchPatient }; diff --git a/bus-gateway/services/vhlIssue.js b/bus-gateway/services/vhlIssue.js new file mode 100644 index 0000000..eb0723f --- /dev/null +++ b/bus-gateway/services/vhlIssue.js @@ -0,0 +1,109 @@ +'use strict'; + +const axios = require('axios'); +const createError = require('http-errors'); +const config = require('../config'); +const { getPrivateKey, getKid } = require('../utils/vhlKeys'); +const { buildVHLCWT, encryptBundle, serializeEncrypted } = require('../utils/vhlCrypto'); +const { storeDocument, storeManifest } = require('../utils/vhlStorage'); + +/** + * Fetches the most recent IPS document-type Bundle for a patient from HAPI FHIR. + * + * Searches using the FHIR chained parameter: + * GET /Bundle?composition.subject=Patient/{patientId}&type=document&_sort=-_lastUpdated&_count=1 + * + * @param {string} patientId - FHIR Patient logical ID. + * @returns {Promise} FHIR Bundle resource. + * @throws {HttpError} 404 if no Bundle is found; 502 on FHIR server error. + */ +async function fetchIPSBundle(patientId) { + let response; + try { + response = await axios.get(`${config.fhir.url}/Bundle`, { + params: { + 'composition.subject': `Patient/${patientId}`, + type: 'document', + _sort: '-_lastUpdated', + _count: 1, + }, + headers: { Accept: 'application/fhir+json' }, + }); + } catch (err) { + throw createError(502, `HAPI FHIR error fetching Bundle: ${err.message}`); + } + + const searchSet = response.data; + const entries = Array.isArray(searchSet.entry) ? searchSet.entry : []; + if (entries.length === 0) { + throw createError(404, `No se encontró un IPS Bundle para el paciente ${patientId}`); + } + + const bundle = entries[0].resource; + if (!bundle) { + throw createError(502, 'HAPI FHIR returned an entry without a resource'); + } + return bundle; +} + +/** + * Issues a Verifiable Health Link (VHL) for a patient. + * + * Flow (matching obtener_qr_vhl.mmd — Fase 1. Emisión del VHL): + * 1. Fetch the patient's IPS Bundle from HAPI FHIR. + * 2. Encrypt the Bundle with AES-256-GCM (random key per issuance). + * 3. Store the encrypted document; derive its public URL. + * 4. Build a manifest { files: [{ contentType, location }] } and store it. + * 5. Sign a CWT (COSE_Sign1 / tag-18) containing the manifest URL and the + * symmetric key so the holder can share both atomically via a QR code. + * 6. Return the raw CWT bytes as base64url (MiArgentina encodes as Base45 + QR). + * + * @param {object} opts + * @param {string} opts.patientId - FHIR Patient logical ID. + * @param {string} [opts.pin] - Optional PIN; the manifest will require it. + * @returns {Promise<{ cwt: string }>} Base64url-encoded COSE_Sign1 CWT. + */ +async function issueVHL({ patientId, pin }) { + if (!config.vhl.baseUrl) { + throw createError(500, 'VHL_BASE_URL is not configured'); + } + + const bundle = await fetchIPSBundle(patientId); + + const encrypted = encryptBundle(bundle); + const serialized = serializeEncrypted(encrypted); + + const ttl = config.vhl.ttl; + const baseUrl = config.vhl.baseUrl.replace(/\/$/, ''); + + const documentId = storeDocument(serialized, ttl); + const documentUrl = `${baseUrl}/vhl/document/${documentId}`; + + const manifest = { + files: [ + { + contentType: 'application/fhir+json', + location: documentUrl, + }, + ], + }; + const manifestId = storeManifest(manifest, ttl, pin); + const manifestUrl = `${baseUrl}/vhl/manifest/${manifestId}`; + + const cwtBytes = buildVHLCWT( + { + issuer: config.vhl.issuer, + patientId, + manifestUrl, + symmetricKey: encrypted.key, + ttl, + pin, + }, + getPrivateKey(), + getKid(), + ); + + return { cwt: cwtBytes.toString('base64url') }; +} + +module.exports = { issueVHL, fetchIPSBundle }; diff --git a/bus-gateway/services/vhlVerify.js b/bus-gateway/services/vhlVerify.js new file mode 100644 index 0000000..87fe064 --- /dev/null +++ b/bus-gateway/services/vhlVerify.js @@ -0,0 +1,50 @@ +'use strict'; + +const createError = require('http-errors'); +const { + getManifest: getManifestFromStore, + getDocument: getDocumentFromStore, +} = require('../utils/vhlStorage'); + +/** + * Retrieves a VHL manifest by ID, enforcing PIN validation when required. + * + * Called by NodoDominio during Fase 4 (Retrieve VHL Manifest & Docs) of + * validar_qr_vhl.mmd: NodoDominio extracts the manifest URL from the CWT + * and POSTs to it (with optional PIN) to get the document locations. + * + * @param {string} manifestId + * @param {string} [pin] + * @returns {object} Manifest { files: [{ contentType, location }] } + * @throws {HttpError} 404 if not found/expired; 401/403 on PIN failure. + */ +function getManifest(manifestId, pin) { + // Delegate to storage; it throws with .status on PIN failures + const manifest = getManifestFromStore(manifestId, pin); + if (!manifest) { + throw createError(404, 'Manifest no encontrado o expirado'); + } + return manifest; +} + +/** + * Retrieves a serialized AES-256-GCM encrypted document by ID. + * + * Called by NodoDominio via a direct P2P download (step 8 in validar_qr_vhl.mmd). + * The caller decrypts using the symmetric key extracted from the CWT. + * + * Wire format: [ 12 bytes IV ][ N bytes ciphertext ][ 16 bytes GCM tag ] + * + * @param {string} documentId + * @returns {Buffer} Serialized encrypted document. + * @throws {HttpError} 404 if not found or expired. + */ +function getDocument(documentId) { + const doc = getDocumentFromStore(documentId); + if (!doc) { + throw createError(404, 'Documento no encontrado o expirado'); + } + return doc; +} + +module.exports = { getManifest, getDocument }; diff --git a/bus-gateway/tests/services/documentReference.test.js b/bus-gateway/tests/services/documentReference.test.js new file mode 100644 index 0000000..d8a53b9 --- /dev/null +++ b/bus-gateway/tests/services/documentReference.test.js @@ -0,0 +1,179 @@ +require('dotenv').config(); +const axios = require('axios'); +const { getBusToken } = require('../../utils/busAuth'); +const { + createDocumentReference, + postDocumentReference, + searchDocumentReferenceBySubject, + searchDocumentReferenceByPatient, + searchDocumentReference, +} = require('../../services/documentReference'); + +jest.mock('axios'); + +const BUS_URL = process.env.BUS_URL; +const REGISTRY_URL = process.env.DOCUMENT_REGISTRY_URL || process.env.BUS_URL; +const BUS_JWT_SECRET = process.env.BUS_JWT_SECRET; +const BUS_ISSUER = process.env.BUS_ISSUER; +const BUS_SCOPE = process.env.DOCUMENT_REGISTRY_SCOPE; + +const mockRequest = { get: jest.fn(), post: jest.fn() }; + +beforeEach(() => { + jest.clearAllMocks(); + axios.create.mockReturnValue(mockRequest); +}); + +async function acquireToken() { + axios.post.mockResolvedValueOnce({ data: { accessToken: 'acquired-token' } }); + return getBusToken(BUS_URL, BUS_JWT_SECRET, BUS_ISSUER, BUS_SCOPE); +} + +const CUSTODIAN_ID = 'efector-001'; +const BUNDLE_URL = 'http://hapi-fhir-host:8080/fhir/Bundle/abc123'; + +const IPS_BUNDLE = { + resourceType: 'Bundle', + id: 'bundle-001', + entry: [ + { + resource: { + resourceType: 'Patient', + identifier: [ + { system: 'https://federador.msal.gob.ar/patient-id', value: '5037097' }, + ], + }, + }, + ], +}; + +describe('createDocumentReference', () => { + it('builds a DocumentReference from IPS bundle using the first patient identifier', () => { + const dr = createDocumentReference(IPS_BUNDLE, CUSTODIAN_ID, BUNDLE_URL); + + expect(dr.resourceType).toBe('DocumentReference'); + expect(dr.status).toBe('current'); + expect(dr.type.coding[0].code).toBe('60591-5'); + expect(dr.subject.identifier).toEqual({ + system: 'https://federador.msal.gob.ar/patient-id', + value: '5037097', + }); + expect(dr.custodian.identifier).toEqual({ + system: 'https://federador.msal.gob.ar/uri', + value: CUSTODIAN_ID, + }); + expect(dr.content[0].attachment.url).toBe(BUNDLE_URL); + }); + + it('uses subjectIdentifier when provided instead of patient identifier', () => { + const subjectIdentifier = { system: 'http://www.renaper.gob.ar/dni', value: '30945027' }; + const dr = createDocumentReference(IPS_BUNDLE, CUSTODIAN_ID, BUNDLE_URL, subjectIdentifier); + + expect(dr.subject.identifier).toEqual(subjectIdentifier); + }); + + it('falls back to Bundle/{id} when bundleUrl is not provided', () => { + const dr = createDocumentReference(IPS_BUNDLE, CUSTODIAN_ID); + + expect(dr.content[0].attachment.url).toBe(`Bundle/${IPS_BUNDLE.id}`); + }); + + it('throws when the bundle has no Patient entry', () => { + const bundleWithoutPatient = { ...IPS_BUNDLE, entry: [] }; + + expect(() => createDocumentReference(bundleWithoutPatient, CUSTODIAN_ID)).toThrow( + 'IPS Bundle does not contain a Patient resource' + ); + }); + + it('throws when the patient has no identifier', () => { + const bundleNoIdentifier = { + ...IPS_BUNDLE, + entry: [{ resource: { resourceType: 'Patient', identifier: [] } }], + }; + + expect(() => createDocumentReference(bundleNoIdentifier, CUSTODIAN_ID)).toThrow( + 'Patient resource has no identifier' + ); + }); +}); + +describe('postDocumentReference', () => { + it('POSTs a DocumentReference to /fhir/DocumentReference and returns response data', async () => { + const token = await acquireToken(); + const responseData = { resourceType: 'DocumentReference', id: 'dr-001' }; + mockRequest.post.mockResolvedValue({ data: responseData }); + + const result = await postDocumentReference(REGISTRY_URL, token, IPS_BUNDLE, CUSTODIAN_ID, BUNDLE_URL); + + expect(axios.create).toHaveBeenCalledWith({ + baseURL: REGISTRY_URL, + headers: { + 'Content-Type': 'application/fhir+json', + 'Authorization': `Bearer ${token}`, + }, + }); + expect(mockRequest.post).toHaveBeenCalledWith( + '/fhir/DocumentReference', + expect.objectContaining({ resourceType: 'DocumentReference' }) + ); + expect(result).toEqual(responseData); + }); +}); + +describe('searchDocumentReferenceBySubject', () => { + it('GETs /fhir/DocumentReference with subject param as system|value', async () => { + const token = await acquireToken(); + const bundle = { resourceType: 'Bundle', entry: [] }; + mockRequest.get.mockResolvedValue({ data: bundle }); + + const result = await searchDocumentReferenceBySubject( + REGISTRY_URL, token, + 'https://federador.msal.gob.ar/patient-id', + '5037097' + ); + + expect(mockRequest.get).toHaveBeenCalledWith('/fhir/DocumentReference', { + params: { subject: 'https://federador.msal.gob.ar/patient-id|5037097' }, + }); + expect(result).toEqual(bundle); + }); +}); + +describe('searchDocumentReferenceByPatient', () => { + it('GETs /fhir/DocumentReference with patient param', async () => { + const token = await acquireToken(); + const bundle = { resourceType: 'Bundle', entry: [] }; + mockRequest.get.mockResolvedValue({ data: bundle }); + + const result = await searchDocumentReferenceByPatient(REGISTRY_URL, token, '5037097'); + + expect(mockRequest.get).toHaveBeenCalledWith('/fhir/DocumentReference', { + params: { patient: '5037097' }, + }); + expect(result).toEqual(bundle); + }); +}); + +describe('searchDocumentReference', () => { + it('GETs /fhir/DocumentReference with subject, custodian and type params', async () => { + const token = await acquireToken(); + const bundle = { resourceType: 'Bundle', entry: [] }; + mockRequest.get.mockResolvedValue({ data: bundle }); + + const subject = { system: 'https://federador.msal.gob.ar/patient-id', value: '5037097' }; + const custodian = { system: 'https://federador.msal.gob.ar/uri', value: 'efector-001' }; + const type = { system: 'http://loinc.org', value: '60591-5' }; + + const result = await searchDocumentReference(REGISTRY_URL, token, subject, custodian, type); + + expect(mockRequest.get).toHaveBeenCalledWith('/fhir/DocumentReference', { + params: { + subject: 'https://federador.msal.gob.ar/patient-id|5037097', + custodian: 'https://federador.msal.gob.ar/uri|efector-001', + type: 'http://loinc.org|60591-5', + }, + }); + expect(result).toEqual(bundle); + }); +}); diff --git a/bus-gateway/tests/services/patient.test.js b/bus-gateway/tests/services/patient.test.js new file mode 100644 index 0000000..06c4151 --- /dev/null +++ b/bus-gateway/tests/services/patient.test.js @@ -0,0 +1,164 @@ +require('dotenv').config(); +const axios = require('axios'); +const { getBusToken } = require('../../utils/busAuth'); +const { postPatient, getPatientById, searchPatient, matchPatient } = require('../../services/patient'); + +jest.mock('axios'); + +const BUS_URL = process.env.BUS_URL; +const MPI_URL = process.env.MPI_URL || process.env.BUS_URL; +const BUS_JWT_SECRET = process.env.BUS_JWT_SECRET; +const BUS_ISSUER = process.env.BUS_ISSUER; +const BUS_SCOPE = process.env.MPI_SCOPE; + +const mockRequest = { get: jest.fn(), post: jest.fn() }; + +beforeEach(() => { + jest.clearAllMocks(); + axios.create.mockReturnValue(mockRequest); +}); + +async function acquireToken() { + axios.post.mockResolvedValueOnce({ data: { accessToken: 'acquired-token' } }); + return getBusToken(BUS_URL, BUS_JWT_SECRET, BUS_ISSUER, BUS_SCOPE); +} + +describe('postPatient', () => { + it('POSTs the patient to /fhir/Patient and returns response data', async () => { + const token = await acquireToken(); + const patient = { resourceType: 'Patient', id: '1' }; + const responseData = { resourceType: 'Patient', id: '1', meta: {} }; + mockRequest.post.mockResolvedValue({ data: responseData }); + + const result = await postPatient(MPI_URL, token, patient); + + expect(axios.create).toHaveBeenCalledWith({ + baseURL: MPI_URL, + headers: { + 'Content-Type': 'application/fhir+json', + 'Authorization': `Bearer ${token}`, + }, + }); + expect(mockRequest.post).toHaveBeenCalledWith('/fhir/Patient', patient); + expect(result).toEqual(responseData); + }); +}); + +describe('getPatientById', () => { + it('GETs /fhir/Patient/:id and returns response data', async () => { + const token = await acquireToken(); + const responseData = { resourceType: 'Patient', id: '42' }; + mockRequest.get.mockResolvedValue({ data: responseData }); + + const result = await getPatientById(MPI_URL, token, '42'); + + expect(mockRequest.get).toHaveBeenCalledWith('/fhir/Patient/42'); + expect(result).toEqual(responseData); + }); +}); + +describe('searchPatient', () => { + it('GETs /fhir/Patient with no params when criteria is empty', async () => { + const token = await acquireToken(); + const bundle = { resourceType: 'Bundle', entry: [] }; + mockRequest.get.mockResolvedValue({ data: bundle }); + + const result = await searchPatient(MPI_URL, token, {}); + + expect(mockRequest.get).toHaveBeenCalledWith('/fhir/Patient', { params: {} }); + expect(result).toEqual(bundle); + }); + + it('maps criteria fields to query params', async () => { + const token = await acquireToken(); + mockRequest.get.mockResolvedValue({ data: {} }); + + await searchPatient(MPI_URL, token, { + name: 'Juan', + family: 'Perez', + birthdate: '1983-04-17', + gender: 'male', + phone: '1141233100', + }); + + expect(mockRequest.get).toHaveBeenCalledWith('/fhir/Patient', { + params: { + name: 'Juan', + family: 'Perez', + birthdate: '1983-04-17', + gender: 'male', + phone: '1141233100', + }, + }); + }); + + it('combines identifierSystem and identifierValue into identifier param', async () => { + const token = await acquireToken(); + mockRequest.get.mockResolvedValue({ data: {} }); + + await searchPatient(MPI_URL, token, { + identifierSystem: 'http://www.renaper.gob.ar/dni', + identifierValue: '30945027', + }); + + expect(mockRequest.get).toHaveBeenCalledWith('/fhir/Patient', { + params: { identifier: 'http://www.renaper.gob.ar/dni|30945027' }, + }); + }); + + it('uses identifierValue alone when identifierSystem is not provided', async () => { + const token = await acquireToken(); + mockRequest.get.mockResolvedValue({ data: {} }); + + await searchPatient(MPI_URL, token, { identifierValue: '30945027' }); + + expect(mockRequest.get).toHaveBeenCalledWith('/fhir/Patient', { + params: { identifier: '30945027' }, + }); + }); +}); + +describe('matchPatient', () => { + it('POSTs a Parameters resource to /fhir/Patient/$match', async () => { + const token = await acquireToken(); + const patient = { resourceType: 'Patient', id: '1' }; + const bundle = { resourceType: 'Bundle', entry: [] }; + mockRequest.post.mockResolvedValue({ data: bundle }); + + const result = await matchPatient(MPI_URL, token, patient); + + expect(mockRequest.post).toHaveBeenCalledWith('/fhir/Patient/$match', { + resourceType: 'Parameters', + parameter: [{ name: 'resource', resource: patient }], + }); + expect(result).toEqual(bundle); + }); + + it('includes count parameter when provided', async () => { + const token = await acquireToken(); + const patient = { resourceType: 'Patient' }; + mockRequest.post.mockResolvedValue({ data: {} }); + + await matchPatient(MPI_URL, token, patient, 3); + + expect(mockRequest.post).toHaveBeenCalledWith('/fhir/Patient/$match', { + resourceType: 'Parameters', + parameter: [ + { name: 'resource', resource: patient }, + { name: 'count', valueInteger: 3 }, + ], + }); + }); + + it('does not include count parameter when not provided', async () => { + const token = await acquireToken(); + const patient = { resourceType: 'Patient' }; + mockRequest.post.mockResolvedValue({ data: {} }); + + await matchPatient(MPI_URL, token, patient); + + const call = mockRequest.post.mock.calls[0]; + const parameters = call[1]; + expect(parameters.parameter).toHaveLength(1); + }); +}); diff --git a/bus-gateway/tests/utils/busAuth.test.js b/bus-gateway/tests/utils/busAuth.test.js new file mode 100644 index 0000000..bd85174 --- /dev/null +++ b/bus-gateway/tests/utils/busAuth.test.js @@ -0,0 +1,107 @@ +require('dotenv').config(); +const axios = require('axios'); +const { getBusToken, createBusRequest } = require('../../utils/busAuth'); + +jest.mock('axios'); + +const BUS_URL = process.env.BUS_URL; +const BUS_JWT_SECRET = process.env.BUS_JWT_SECRET; +const BUS_ISSUER = process.env.BUS_ISSUER; +const BUS_SCOPE = process.env.MPI_SCOPE; + +beforeEach(() => { + jest.clearAllMocks(); +}); + +/** + * Decodes a JWT without verifying the signature. + */ +function decodeJwt(jwt) { + const [header, payload] = jwt.split('.'); + return { + header: JSON.parse(Buffer.from(header, 'base64').toString()), + payload: JSON.parse(Buffer.from(payload, 'base64').toString()), + }; +} + +describe('getBusToken', () => { + it('POSTs to /bus-auth/v2/auth on the given busURL', async () => { + axios.post.mockResolvedValue({ data: 'access-token' }); + + await getBusToken(BUS_URL, BUS_JWT_SECRET, BUS_ISSUER, BUS_SCOPE); + + expect(axios.post).toHaveBeenCalledWith( + `${BUS_URL}/bus-auth/v2/auth`, + expect.any(Object) + ); + }); + + it('sends the correct grantType, scope and clientAssertionType', async () => { + axios.post.mockResolvedValue({ data: 'access-token' }); + + await getBusToken(BUS_URL, BUS_JWT_SECRET, BUS_ISSUER, BUS_SCOPE); + + const body = axios.post.mock.calls[0][1]; + expect(body.grantType).toBe('client_credentials'); + expect(body.scope).toBe(BUS_SCOPE); + expect(body.clientAssertionType).toBe( + 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' + ); + }); + + it('sends a HS256 JWT signed with the secret as clientAssertion', async () => { + axios.post.mockResolvedValue({ data: 'access-token' }); + + await getBusToken(BUS_URL, BUS_JWT_SECRET, BUS_ISSUER, BUS_SCOPE); + + const { clientAssertion } = axios.post.mock.calls[0][1]; + expect(clientAssertion.split('.')).toHaveLength(3); + + const { header } = decodeJwt(clientAssertion); + expect(header.alg).toBe('HS256'); + expect(header.typ).toBe('JWT'); + }); + + it('JWT payload contains the correct issuer and expiration fields', async () => { + axios.post.mockResolvedValue({ data: 'access-token' }); + + const before = Math.floor(Date.now() / 1000); + await getBusToken(BUS_URL, BUS_JWT_SECRET, BUS_ISSUER, BUS_SCOPE); + const after = Math.floor(Date.now() / 1000); + + const { clientAssertion } = axios.post.mock.calls[0][1]; + const { payload } = decodeJwt(clientAssertion); + + expect(payload.iss).toBe(BUS_ISSUER); + expect(payload.iat).toBeGreaterThanOrEqual(before); + expect(payload.iat).toBeLessThanOrEqual(after); + expect(payload.exp).toBeGreaterThan(payload.iat); + }); + + it('returns the accessToken field from the bus auth response', async () => { + const tokenResponse = { accessToken: 'abc123', tokenType: 'bearer', expiresIn: 180000 }; + axios.post.mockResolvedValue({ data: tokenResponse }); + + const result = await getBusToken(BUS_URL, BUS_JWT_SECRET, BUS_ISSUER, BUS_SCOPE); + + expect(result).toBe('abc123'); + }); +}); + +describe('createBusRequest', () => { + it('creates an axios instance with the correct baseURL and headers', () => { + const mockInstance = { get: jest.fn(), post: jest.fn() }; + axios.create.mockReturnValue(mockInstance); + + const token = 'my-token'; + createBusRequest(BUS_URL, token); + + expect(axios.create).toHaveBeenCalledWith({ + baseURL: BUS_URL, + headers: { + 'Content-Type': 'application/fhir+json', + 'Authorization': `Bearer ${token}`, + }, + }); + }); +}); diff --git a/bus-gateway/utils/busAuth.js b/bus-gateway/utils/busAuth.js new file mode 100644 index 0000000..ac3e20c --- /dev/null +++ b/bus-gateway/utils/busAuth.js @@ -0,0 +1,114 @@ +const config = require('../config') +const crypto = require('crypto'); +const axios = require('axios'); + +function base64url(buffer) { + return Buffer.from(buffer) + .toString('base64') + .replace(/=+$/, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); +} + +/** + * Generates a signed JWT (HS256) for Bus auth. + * @param {string} jwtSecret - The shared secret to sign the token. + * @param {string} issuer - The issuer URL (repositorioURL). + * @returns {string} Signed JWT token. + */ +function generateBusJwt(jwtSecret, issuer) { + const header = { typ: 'JWT', alg: 'HS256' }; + const currentTimestamp = Math.floor(Date.now() / 1000); + + const data = { + iss: issuer, + iat: currentTimestamp, + exp: currentTimestamp + 6000000, + aud: 'aud', + sub: 'sub', + name: 'name', + ident: 'ident', + role: 'role', + }; + + const encodedHeader = base64url(JSON.stringify(header)); + const encodedData = base64url(JSON.stringify(data)); + const token = `${encodedHeader}.${encodedData}`; + + const signature = crypto + .createHmac('sha256', jwtSecret) + .update(token) + .digest('base64') + .replace(/=+$/, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + + return `${token}.${signature}`; +} + +/** + * Authenticates against the Bus and returns the access token. + * @param {string} busURL - Base URL of the Bus (e.g. http://bus-host). + * @param {string} jwtSecret - The shared secret to sign the JWT. + * @param {string} issuer - The issuer URL (repositorioURL). + * @param {string} scope - OAuth scopes (e.g. "MedicationRequest/*.read,MedicationRequest/*.write"). + * @returns {Promise} The access token returned by the Bus. + */ +async function getBusToken(busURL, jwtSecret, issuer, scope) { + const clientAssertion = generateBusJwt(jwtSecret, issuer); + + const response = await axios.post(`${busURL}/bus-auth/v2/auth`, { + grantType: 'client_credentials', + scope, + clientAssertionType: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', + clientAssertion, + }); + + return response.data.accessToken; +} + +/** + * Creates an axios instance pre-configured for the Bus FHIR API. + * All requests will include the Authorization and Content-Type headers. + * @param {string} busURL - Base URL of the Bus. + * @param {string} token - Bearer access token obtained from getBusToken. + * @returns {import('axios').AxiosInstance} + */ +function createBusRequest(token, axiosConfig = {}, busUrl = null, url = null) { + + const instance = axios.create({ + ...(!!url ? { url: url } : !!busUrl ? { baseURL: busURL } : {}), + ...axiosConfig, + headers: { + 'Content-Type': 'application/fhir+json', + 'Authorization': `Bearer ${token}`, + ...(axiosConfig.headers || {}), + }, + }); + + if (config.debug) { + instance.interceptors.request.use((req) => { + const url = req.baseURL + req.url + (req.params ? '?' + new URLSearchParams(req.params).toString() : ''); + console.log(`[${new Date().toISOString()}] --> ${req.method.toUpperCase()} ${url}`); + return req; + }); + + instance.interceptors.response.use( + (res) => { + console.log(`[${new Date().toISOString()}] <-- ${res.status} ${res.config.baseURL}${res.config.url}`); + return res; + }, + (err) => { + const res = err.response; + if (res) { + console.error(`[${new Date().toISOString()}] <-- ${res.status} ${res.config.baseURL}${res.config.url}`); + } + return Promise.reject(err); + } + ); + } + + return instance; +} + +module.exports = { getBusToken, createBusRequest }; diff --git a/bus-gateway/utils/cbor.js b/bus-gateway/utils/cbor.js new file mode 100644 index 0000000..1a88632 --- /dev/null +++ b/bus-gateway/utils/cbor.js @@ -0,0 +1,109 @@ +'use strict'; + +/** + * Minimal CBOR encoder for CWT/COSE structures (RFC 7049). + * + * Supports: + * - null / undefined + * - booleans + * - integers (positive and negative) + * - byte strings (Buffer) + * - text strings + * - fixed-length arrays + * - maps (ES6 Map — preserves key order; plain objects — string keys only) + * - tagged values + * + * Not supported: floats, indefinite-length items. + */ + +/** + * Encodes a CBOR "head": major type (3 bits) + additional info (variable length). + * @param {number} majorType - 0..7 + * @param {number} n - Argument value (uint). + * @returns {Buffer} + */ +function encodeHead(majorType, n) { + const mt = majorType << 5; + if (n <= 23) return Buffer.from([mt | n]); + if (n <= 0xff) return Buffer.from([mt | 24, n]); + if (n <= 0xffff) return Buffer.from([mt | 25, (n >> 8) & 0xff, n & 0xff]); + return Buffer.from([ + mt | 26, + (n >>> 24) & 0xff, + (n >>> 16) & 0xff, + (n >>> 8) & 0xff, + n & 0xff, + ]); +} + +/** + * CBOR-encodes a JavaScript value. + * + * - null / undefined → 0xf6 (null) + * - boolean → 0xf4 / 0xf5 + * - integer >= 0 → major type 0 + * - integer < 0 → major type 1 + * - Buffer → major type 2 (byte string) + * - string → major type 3 (text string, UTF-8) + * - Array → major type 4 + * - Map → major type 5 (keys encoded in insertion order) + * - plain object → major type 5 (string keys, own enumerable) + * + * @param {*} value + * @returns {Buffer} + */ +function encode(value) { + if (value === null || value === undefined) { + return Buffer.from([0xf6]); + } + if (typeof value === 'boolean') { + return Buffer.from([value ? 0xf5 : 0xf4]); + } + if (typeof value === 'number') { + if (!Number.isInteger(value)) { + throw new Error('cbor: float encoding not supported'); + } + if (value >= 0) return encodeHead(0, value); // unsigned int + return encodeHead(1, -1 - value); // negative int + } + if (typeof value === 'string') { + const buf = Buffer.from(value, 'utf8'); + return Buffer.concat([encodeHead(3, buf.length), buf]); + } + if (Buffer.isBuffer(value)) { + return Buffer.concat([encodeHead(2, value.length), value]); + } + if (Array.isArray(value)) { + const parts = [encodeHead(4, value.length)]; + for (const item of value) parts.push(encode(item)); + return Buffer.concat(parts); + } + if (value instanceof Map) { + const parts = [encodeHead(5, value.size)]; + for (const [k, v] of value.entries()) { + parts.push(encode(k), encode(v)); + } + return Buffer.concat(parts); + } + if (typeof value === 'object') { + const keys = Object.keys(value); + const parts = [encodeHead(5, keys.length)]; + for (const k of keys) { + parts.push(encode(k), encode(value[k])); + } + return Buffer.concat(parts); + } + throw new Error(`cbor: cannot encode type "${typeof value}"`); +} + +/** + * Wraps an already-encoded CBOR Buffer with a CBOR tag (major type 6). + * @param {number} tagNum - Tag number (e.g. 18 for COSE_Sign1). + * @param {Buffer} encoded - Pre-encoded CBOR value to tag. + * @returns {Buffer} + */ +function tagged(tagNum, encoded) { + return Buffer.concat([encodeHead(6, tagNum), encoded]); +} + +module.exports = { encode, tagged }; diff --git a/bus-gateway/utils/logger.js b/bus-gateway/utils/logger.js new file mode 100644 index 0000000..17f8b1d --- /dev/null +++ b/bus-gateway/utils/logger.js @@ -0,0 +1,20 @@ +const pino = require('pino') +const config = require('../config') + + +const pinoConfig = { + level: config.logging.level, + depthLimit: config.logging.depthLimit, + edgeLimit: config.logging.edgeLimit, + msgPrefix: config.logging.msgPrefix, + enabled: config.logging.enabled, + crlf: config.logging.crlf, + messageKey: config.logging.messageKey, + errorKey: config.logging.errorKey, + nestedKey: config.logging.nestedKey, +} + + +const logger = pino(pinoConfig, process.stderr); + +module.exports = { logger } \ No newline at end of file diff --git a/bus-gateway/utils/vhlCrypto.js b/bus-gateway/utils/vhlCrypto.js new file mode 100644 index 0000000..86d34e0 --- /dev/null +++ b/bus-gateway/utils/vhlCrypto.js @@ -0,0 +1,182 @@ +'use strict'; + +const crypto = require('crypto'); +const cbor = require('./cbor'); + +// COSE algorithm identifier for ES256 (ECDSA w/ SHA-256 over P-256), per RFC 8152 +const COSE_ALG_ES256 = -7; +// CBOR tag 18 = COSE_Sign1 +const COSE_SIGN1_TAG = 18; + +/** + * Creates a signed COSE_Sign1 structure (tag 18) over a CBOR-encoded payload. + * + * Structure per RFC 8152 §4.2: + * COSE_Sign1 = [ + * protected: bstr .cbor header_map, // { 1: -7, 4: kid } + * unprotected: {}, + * payload: bstr, + * signature: bstr // ECDSA P-256, IEEE P1363 (r || s) + * ] + * Tagged with #6.18 + * + * @param {Buffer} payloadBstr - CBOR-encoded CWT claims map. + * @param {crypto.KeyObject} privateKey - EC P-256 private key. + * @param {string} kid - Key ID included in the protected header. + * @returns {Buffer} COSE_Sign1 tagged with #6.18. + */ +function createCOSESign1(payloadBstr, privateKey, kid) { + // Protected header: { 1: -7 (ES256), 4: kid_as_bstr } + const protectedHeader = new Map([ + [1, COSE_ALG_ES256], + [4, Buffer.from(kid, 'utf8')], + ]); + const protectedBstr = cbor.encode(protectedHeader); + + // Sig_Structure = ["Signature1", protected_bstr, external_aad, payload] + // external_aad is empty bstr per RFC 8152 §4.4 + const sigStructure = cbor.encode([ + 'Signature1', + protectedBstr, + Buffer.alloc(0), + payloadBstr, + ]); + + // Sign with ECDSA P-256 + SHA-256. + // dsaEncoding: 'ieee-p1363' produces the raw r||s format required by COSE + // (as opposed to DER, which Node.js uses by default). + const signature = crypto.sign('SHA256', sigStructure, { + key: privateKey, + dsaEncoding: 'ieee-p1363', + }); + + const coseSign1 = cbor.encode([ + protectedBstr, + new Map(), // unprotected header: empty map + payloadBstr, + signature, + ]); + + return cbor.tagged(COSE_SIGN1_TAG, coseSign1); +} + +/** + * Builds and signs a CWT (CBOR Web Token) containing VHL claims. + * + * CWT claims (RFC 8392 integer keys): + * 1 = iss (issuer URI) + * 2 = sub (patient ID) + * 4 = exp (expiration, Unix timestamp) + * 6 = iat (issued at, Unix timestamp) + * -1 = vhl (private claim: manifest URL + symmetric key) + * + * VHL claim map (text keys): + * "url" → manifest URL + * "key" → base64url-encoded AES-256-GCM symmetric key + * "label" → human-readable label + * "flag" → "P" if PIN-protected (optional) + * + * @param {object} opts + * @param {string} opts.issuer - VHL_ISSUER URI. + * @param {string} opts.patientId - Patient FHIR logical ID (becomes CWT "sub"). + * @param {string} opts.manifestUrl - URL where the VHL manifest is served. + * @param {Buffer} opts.symmetricKey - 32-byte AES-256-GCM key for the encrypted document. + * @param {number} opts.ttl - Token TTL in seconds. + * @param {string} [opts.pin] - Optional PIN (signals flag="P" in the VHL claim). + * @param {crypto.KeyObject} privateKey - EC P-256 private key. + * @param {string} kid - Key ID for the COSE protected header. + * @returns {Buffer} Signed CWT bytes (COSE_Sign1 tagged). + */ +function buildVHLCWT({ issuer, patientId, manifestUrl, symmetricKey, ttl, pin }, privateKey, kid) { + const now = Math.floor(Date.now() / 1000); + + const vhlClaim = new Map([ + ['url', manifestUrl], + ['key', symmetricKey.toString('base64url')], + ['label', 'Resumen de Salud del Paciente'], + ]); + if (pin) { + vhlClaim.set('flag', 'P'); // "P" = PIN required, per SMART Health Links convention + } + + const claims = new Map([ + [1, issuer], + [2, patientId], + [6, now], + [4, now + ttl], + [-1, vhlClaim], + ]); + + const payloadBstr = cbor.encode(claims); + return createCOSESign1(payloadBstr, privateKey, kid); +} + +/** + * Encrypts a FHIR Bundle (JSON) with AES-256-GCM. + * + * @param {object} bundle - FHIR Bundle resource. + * @returns {{ ciphertext: Buffer, key: Buffer, iv: Buffer, tag: Buffer }} + */ +function encryptBundle(bundle) { + const key = crypto.randomBytes(32); // 256-bit key + const iv = crypto.randomBytes(12); // 96-bit GCM nonce (recommended size) + + const plaintext = Buffer.from(JSON.stringify(bundle), 'utf8'); + const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); + const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]); + const tag = cipher.getAuthTag(); // 128-bit authentication tag + + return { ciphertext, key, iv, tag }; +} + +/** + * Decrypts an AES-256-GCM encrypted FHIR Bundle. + * + * @param {{ ciphertext: Buffer, key: Buffer, iv: Buffer, tag: Buffer }} enc + * @returns {object} Parsed FHIR Bundle. + */ +function decryptBundle({ ciphertext, key, iv, tag }) { + const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); + decipher.setAuthTag(tag); + const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]); + return JSON.parse(plaintext.toString('utf8')); +} + +/** + * Serializes an encrypted document to a single Buffer for storage and transport. + * + * Wire format: [ 12 bytes IV ][ N bytes ciphertext ][ 16 bytes GCM tag ] + * + * The decrypting side (NodoDominio) reconstructs iv/ciphertext/tag from + * this layout using the symmetric key extracted from the CWT. + * + * @param {{ ciphertext: Buffer, iv: Buffer, tag: Buffer }} enc + * @returns {Buffer} + */ +function serializeEncrypted({ ciphertext, iv, tag }) { + return Buffer.concat([iv, ciphertext, tag]); +} + +/** + * Deserializes a Buffer produced by serializeEncrypted. + * + * @param {Buffer} buf + * @returns {{ iv: Buffer, ciphertext: Buffer, tag: Buffer }} + */ +function deserializeEncrypted(buf) { + if (buf.length < 28) { + throw new Error('vhlCrypto: encrypted payload too short'); + } + const iv = buf.slice(0, 12); + const tag = buf.slice(buf.length - 16); + const ciphertext = buf.slice(12, buf.length - 16); + return { iv, ciphertext, tag }; +} + +module.exports = { + buildVHLCWT, + encryptBundle, + decryptBundle, + serializeEncrypted, + deserializeEncrypted, +}; diff --git a/bus-gateway/utils/vhlKeys.js b/bus-gateway/utils/vhlKeys.js new file mode 100644 index 0000000..80e249e --- /dev/null +++ b/bus-gateway/utils/vhlKeys.js @@ -0,0 +1,79 @@ +'use strict'; + +const fs = require('fs'); +const crypto = require('crypto'); + +let _privateKey = null; +let _publicKey = null; +let _kid = null; + +/** + * Lazy-loads or generates the EC P-256 key pair used for signing VHL CWTs. + * + * Key resolution order: + * 1. PEM file at VHL_PRIVATE_KEY_FILE (if set and exists). + * 2. Ephemeral key pair generated at startup (logged as warning). + * + * The kid (Key ID) is derived as the first 8 bytes of SHA-256 over the + * DER-encoded public key, encoded as base64url. This matches the GDHCN + * convention for referencing public keys in the trust registry. + */ +function loadKeys() { + if (_privateKey) return; + + const keyFile = process.env.VHL_PRIVATE_KEY_FILE; + if (keyFile) { + if (fs.existsSync(keyFile)) { + const pem = fs.readFileSync(keyFile); + _privateKey = crypto.createPrivateKey({ key: pem, format: 'pem' }); + _publicKey = crypto.createPublicKey(_privateKey); + } else { + console.warn(`[VHL] VHL_PRIVATE_KEY_FILE not found: ${keyFile}. Generating ephemeral key pair.`); + } + } else { + console.warn('[VHL] VHL_PRIVATE_KEY_FILE not set. Generating ephemeral key pair (lost on restart).'); + } + + if (!_privateKey) { + const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' }); + _privateKey = privateKey; + _publicKey = publicKey; + } + + // kid = base64url(SHA-256(DER public key)[0..7]) + const pubDer = _publicKey.export({ type: 'spki', format: 'der' }); + _kid = crypto.createHash('sha256').update(pubDer).digest().slice(0, 8).toString('base64url'); +} + +/** Returns the EC P-256 private KeyObject for signing. */ +function getPrivateKey() { + loadKeys(); + return _privateKey; +} + +/** Returns the EC P-256 public KeyObject for verification. */ +function getPublicKey() { + loadKeys(); + return _publicKey; +} + +/** + * Returns the Key ID (kid) for use in CWT protected headers. + * Derived from the public key fingerprint so verifiers can look up the key + * in the GDHCN trust registry. + */ +function getKid() { + loadKeys(); + return _kid; +} + +/** + * Returns the public key in JWK format for publishing to a trust registry. + * @returns {object} JWK representation of the public key. + */ +function getPublicJWK() { + loadKeys(); + return _publicKey.export({ format: 'jwk' }); +} + +module.exports = { getPrivateKey, getPublicKey, getKid, getPublicJWK }; diff --git a/bus-gateway/utils/vhlStorage.js b/bus-gateway/utils/vhlStorage.js new file mode 100644 index 0000000..492d1c1 --- /dev/null +++ b/bus-gateway/utils/vhlStorage.js @@ -0,0 +1,141 @@ +'use strict'; + +const crypto = require('crypto'); + +/** + * In-memory store for VHL manifests and encrypted documents. + * + * Each entry has a TTL; expired entries are cleaned up lazily. + * + * WARNING: state is lost on process restart. Replace with Redis or a + * persistent store for production deployments. + */ + +const manifests = new Map(); // manifestId → { manifest, pinHash: string|null, expiresAt: number } +const documents = new Map(); // documentId → { payload: Buffer, expiresAt: number } + +/** Generates a cryptographically random URL-safe ID (22 chars). */ +function generateId() { + return crypto.randomBytes(16).toString('base64url'); +} + +/** + * Derives a SHA-256 hash of a PIN. + * Stored instead of the plaintext PIN. + * @param {string} pin + * @returns {string} base64url hash + */ +function hashPin(pin) { + return crypto.createHash('sha256').update(String(pin)).digest('base64url'); +} + +// --- Documents --- + +/** + * Stores a serialized encrypted document and returns its ID. + * @param {Buffer} payload - Output of vhlCrypto.serializeEncrypted(). + * @param {number} ttlSeconds + * @returns {string} documentId + */ +function storeDocument(payload, ttlSeconds) { + const id = generateId(); + documents.set(id, { + payload, + expiresAt: Date.now() + ttlSeconds * 1000, + }); + scheduleCleanup(); + return id; +} + +/** + * Retrieves an encrypted document by ID. + * Returns null if not found or expired. + * @param {string} id + * @returns {Buffer|null} + */ +function getDocument(id) { + const entry = documents.get(id); + if (!entry) return null; + if (Date.now() > entry.expiresAt) { + documents.delete(id); + return null; + } + return entry.payload; +} + +// --- Manifests --- + +/** + * Stores a manifest and returns its ID. + * @param {object} manifest - Plain manifest object (e.g. { files: [...] }). + * @param {number} ttlSeconds + * @param {string} [pin] - If provided, access requires this PIN. + * @returns {string} manifestId + */ +function storeManifest(manifest, ttlSeconds, pin) { + const id = generateId(); + manifests.set(id, { + manifest, + pinHash: pin ? hashPin(pin) : null, + expiresAt: Date.now() + ttlSeconds * 1000, + }); + scheduleCleanup(); + return id; +} + +/** + * Retrieves a manifest by ID, optionally validating a PIN. + * + * @param {string} id + * @param {string} [pin] - Required when the manifest was stored with a PIN. + * @returns {object|null} The manifest, or null if not found / expired. + * @throws {Error} err.status = 401 if PIN is required but not provided. + * @throws {Error} err.status = 403 if PIN is incorrect. + */ +function getManifest(id, pin) { + const entry = manifests.get(id); + if (!entry) return null; + if (Date.now() > entry.expiresAt) { + manifests.delete(id); + return null; + } + if (entry.pinHash) { + if (!pin) { + const err = new Error('PIN requerido para acceder a este manifest'); + err.status = 401; + throw err; + } + if (hashPin(pin) !== entry.pinHash) { + const err = new Error('PIN incorrecto'); + err.status = 403; + throw err; + } + } + return entry.manifest; +} + +// --- Cleanup --- + +let _cleanupTimer = null; + +/** + * Schedules a one-shot cleanup run 60 seconds from now (if not already scheduled). + * Removes all entries whose TTL has expired. + */ +function scheduleCleanup() { + if (_cleanupTimer) return; + _cleanupTimer = setTimeout(() => { + _cleanupTimer = null; + const now = Date.now(); + for (const [id, entry] of manifests) { + if (now > entry.expiresAt) manifests.delete(id); + } + for (const [id, entry] of documents) { + if (now > entry.expiresAt) documents.delete(id); + } + }, 60_000); + // Allow the process to exit even if cleanup hasn't fired + if (_cleanupTimer.unref) _cleanupTimer.unref(); +} + +module.exports = { storeDocument, getDocument, storeManifest, getManifest }; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6f8e161 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,86 @@ +services: + hapi-fhir: + image: "hapiproject/hapi:latest" + depends_on: + - hapi-db + volumes: + - ./hapi-config:/data/hapi + environment: + SPRING_CONFIG_LOCATION: ${SPRING_CONFIG_LOCATION} + SPRING_DATASOURCE_URL: ${SPRING_DATASOURCE_URL} + SPRING_DATASOURCE_USERNAME: ${SPRING_DATASOURCE_USERNAME} + SPRING_DATASOURCE_PASSWORD: ${SPRING_DATASOURCE_PASSWORD} + SPRING_DATASOURCE_DRIVERCLASSNAME: ${SPRING_DATASOURCE_DRIVERCLASSNAME} + SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT: ${SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT} + networks: + - hapi-network + + hapi-db: + image: "postgres:14.6" + restart: always + user: root + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + volumes: + - hapi-data:/var/lib/postgresql/data + networks: + - hapi-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -p 5433"] + interval: 20s + timeout: 10s + retries: 5 + command: -p 5433 + + bus-gateway: + build: + context: ./bus-gateway + environment: + BUS_URL: ${BUS_URL} + BUS_JWT_SECRET: ${BUS_JWT_SECRET} + BUS_ISSUER: ${BUS_ISSUER} + MPI_URL: ${MPI_URL} + DOCUMENT_REGISTRY_URL: ${DOCUMENT_REGISTRY_URL} + MPI_SCOPE: ${MPI_SCOPE} + DOCUMENT_REGISTRY_SCOPE: ${DOCUMENT_REGISTRY_SCOPE} + FHIR_URL: http://hapi-fhir:8080/fhir + BUS_DEBUG: ${BUS_DEBUG} + ports: + - 9229:9229 + networks: + - hapi-network + command: node --inspect=0.0.0.0:9229 ./bin/www + + nginx: + image: nginx:alpine + depends_on: + - hapi-fhir + - bus-gateway + ports: + - "80:80" + - "443:443" + volumes: + # Selecciona la config con NGINX_CONF=http (default) o NGINX_CONF=https en el .env + - ./nginx/${NGINX_CONF:-http}.conf:/etc/nginx/nginx.conf:ro + secrets: + - ssl_cert + - ssl_key + networks: + - hapi-network + +networks: + hapi-network: + name: hapi-network + +volumes: + hapi-data: + name: hapi-data + driver: local + +secrets: + ssl_cert: + file: ${SSL_CERT_PATH:-./certs/server.crt} + ssl_key: + file: ${SSL_KEY_PATH:-./certs/server.key} diff --git a/hapi-config/application.yaml b/hapi-config/application.yaml new file mode 100644 index 0000000..0c85438 --- /dev/null +++ b/hapi-config/application.yaml @@ -0,0 +1,207 @@ +#Adds the option to go to eg. http://localhost:8080/actuator/health for seeing the running configuration +#see https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints +management: + endpoints: + web: + exposure: + include: "health,prometheus" +spring: + main: + allow-circular-references: true + #allow-bean-definition-overriding: true + flyway: + enabled: false + check-location: false + baselineOnMigrate: true + datasource: + url: 'jdbc:postgresql://hapi-db:5432/root' + username: root + password: hapifhir2023 + driverClassName: org.postgresql.Driver + max-active: 15 + + # database connection pool size + hikari: + maximum-pool-size: 10 + jpa: + properties: + hibernate.format_sql: false + hibernate.show_sql: false + #Hibernate dialect is automatically detected except Postgres and H2. + #If using H2, then supply the value of ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect + #If using postgres, then supply the value of ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect + + hibernate.dialect: ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect + # hibernate.hbm2ddl.auto: update + # hibernate.jdbc.batch_size: 20 + # hibernate.cache.use_query_cache: false + # hibernate.cache.use_second_level_cache: false + # hibernate.cache.use_structured_entries: false + # hibernate.cache.use_minimal_puts: false + ### These settings will enable fulltext search with lucene or elastic + hibernate.search.enabled: true + hibernate.search.backend.type: lucene + hibernate.search.backend.analysis.configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiLuceneAnalysisConfigurer + hibernate.search.backend.directory.type: local-filesystem + hibernate.search.backend.directory.root: target/lucenefiles + hibernate.search.backend.lucene_version: lucene_current +hapi: + fhir: + + ### This enables the swagger-ui at /fhir/swagger-ui/index.html as well as the /fhir/api-docs (see https://hapifhir.io/hapi-fhir/docs/server_plain/openapi.html) + openapi_enabled: true + ### This is the FHIR version. Choose between, DSTU2, DSTU3, R4 or R5 + fhir_version: R4 + ### enable to use the ApacheProxyAddressStrategy which uses X-Forwarded-* headers + ### to determine the FHIR server address + # use_apache_address_strategy: false + ### forces the use of the https:// protocol for the returned server address. + ### alternatively, it may be set using the X-Forwarded-Proto header. + # use_apache_address_strategy_https: false + ### enables the server to host content like HTML, css, etc. under the url pattern of /static/** + ### the deepest folder level will be used. E.g. - if you put file:/foo/bar/bazz as value then the files are resolved under /static/bazz/** + #staticLocation: file:/foo/bar/bazz + ### enable to set the Server URL + # server_address: http://hapi.fhir.org/baseR4 + # defer_indexing_for_codesystems_of_size: 101 + # install_transitive_ig_dependencies: true + # implementationguides: + ### example from registry (packages.fhir.org) + # swiss: + # name: swiss.mednet.fhir + # version: 0.8.0 + # example not from registry + # ips_1_0_0: + # url: https://build.fhir.org/ig/HL7/fhir-ips/package.tgz + # name: hl7.fhir.uv.ips + # version: 1.0.0 + # supported_resource_types: + # - Patient + # - Observation + ################################################## + # Allowed Bundle Types for persistence (defaults are: COLLECTION,DOCUMENT,MESSAGE) + ################################################## + # allowed_bundle_types: COLLECTION,DOCUMENT,MESSAGE,TRANSACTION,TRANSACTIONRESPONSE,BATCH,BATCHRESPONSE,HISTORY,SEARCHSET + # allow_cascading_deletes: true + # allow_contains_searches: true + # allow_external_references: true + # allow_multiple_delete: true + # allow_override_default_search_params: true + # auto_create_placeholder_reference_targets: false + # cr_enabled: true + # ips_enabled: false + # default_encoding: JSON + # default_pretty_print: true + # default_page_size: 20 + # delete_expunge_enabled: true + # enable_repository_validating_interceptor: true + # enable_index_missing_fields: false + # enable_index_of_type: true + # enable_index_contained_resource: false + ### !!Extended Lucene/Elasticsearch Indexing is still a experimental feature, expect some features (e.g. _total=accurate) to not work as expected!! + ### more information here: https://hapifhir.io/hapi-fhir/docs/server_jpa/elastic.html + advanced_lucene_indexing: false + bulk_export_enabled: false + bulk_import_enabled: false + # enforce_referential_integrity_on_delete: false + # This is an experimental feature, and does not fully support _total and other FHIR features. + # enforce_referential_integrity_on_delete: false + # enforce_referential_integrity_on_write: false + # etag_support_enabled: true + # expunge_enabled: true + # client_id_strategy: ALPHANUMERIC + # fhirpath_interceptor_enabled: false + # filter_search_enabled: true + # graphql_enabled: true + narrative_enabled: false + # mdm_enabled: true + # local_base_urls: + # - https://hapi.fhir.org/baseR4 + mdm_enabled: false + # partitioning: + # allow_references_across_partitions: false + # partitioning_include_in_search_hashes: false + cors: + allow_Credentials: true + # These are allowed_origin patterns, see: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/cors/CorsConfiguration.html#setAllowedOriginPatterns-java.util.List- + allowed_origin: + - '*' + + # Search coordinator thread pool sizes + search-coord-core-pool-size: 20 + search-coord-max-pool-size: 100 + search-coord-queue-capacity: 200 + + # comma-separated package names, will be @ComponentScan'ed by Spring to allow for creating custom Spring beans + #custom-bean-packages: + + # comma-separated list of fully qualified interceptor classes. + # classes listed here will be fetched from the Spring context when combined with 'custom-bean-packages', + # or will be instantiated via reflection using an no-arg contructor; then registered with the server + #custom-interceptor-classes: + + # Threadpool size for BATCH'ed GETs in a bundle. + # bundle_batch_pool_size: 10 + # bundle_batch_pool_max_size: 50 + + # logger: + # error_format: 'ERROR - ${requestVerb} ${requestUrl}' + # format: >- + # Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] + # Operation[${operationType} ${operationName} ${idOrResourceName}] + # UA[${requestHeader.user-agent}] Params[${requestParameters}] + # ResponseEncoding[${responseEncodingNoDefault}] + # log_exceptions: true + # name: fhirtest.access + # max_binary_size: 104857600 + # max_page_size: 200 + # retain_cached_searches_mins: 60 + # reuse_cached_search_results_millis: 60000 + tester: + home: + name: Local Tester + server_address: 'http://localhost:8080/fhir' + refuse_to_fetch_third_party_urls: false + fhir_version: R4 + global: + name: Global Tester + server_address: "http://hapi.fhir.org/baseR4" + refuse_to_fetch_third_party_urls: false + fhir_version: R4 + # validation: + # requests_enabled: true + # responses_enabled: true + # binary_storage_enabled: true + inline_resource_storage_below_size: 4000 +# bulk_export_enabled: true +# subscription: +# resthook_enabled: true +# websocket_enabled: false +# email: +# from: some@test.com +# host: google.com +# port: +# username: +# password: +# auth: +# startTlsEnable: +# startTlsRequired: +# quitWait: +# lastn_enabled: true +# store_resource_in_lucene_index_enabled: true +### This is configuration for normalized quantity serach level default is 0 +### 0: NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED - default +### 1: NORMALIZED_QUANTITY_STORAGE_SUPPORTED +### 2: NORMALIZED_QUANTITY_SEARCH_SUPPORTED +# normalized_quantity_search_level: 2 +#elasticsearch: +# debug: +# pretty_print_json_log: false +# refresh_after_write: false +# enabled: false +# password: SomePassword +# required_index_status: YELLOW +# rest_url: 'localhost:9200' +# protocol: 'http' +# schema_management_strategy: CREATE +# username: SomeUsername \ No newline at end of file diff --git a/init_snowstorm.sh b/init_snowstorm.sh new file mode 100644 index 0000000..f989f22 --- /dev/null +++ b/init_snowstorm.sh @@ -0,0 +1,29 @@ +if [ ! -f /status/script_executed ]; then + echo "Waiting for Elasticsearch to be healthy..." + sleep 30 + echo "Executing script..." + echo "1. Setup backup repository" + curl -X PUT "elasticsearch:9200/_snapshot/snowstorm_preload_server" -H 'Content-Type: application/json' -d' +{ + "type": "url", + "settings": { + "url": "https://storage.googleapis.com/snowstorm-preload/" + } +} +' + sleep 15 + echo "2. Delete existing data" + curl -X DELETE elasticsearch:9200/_all + sleep 30 + echo "3. Download and restore data" + curl -X POST "elasticsearch:9200/_snapshot/snowstorm_preload_server/snowstorm_10.3.1_spain_20240331/_restore?wait_for_completion=true" -H 'Content-Type: application/json' -d' +{ + "indices": "*", + "ignore_unavailable": true +} +' + echo "4. Saving status" + echo "1" >> /status/script_executed +else + echo "Initialization skipped" +fi \ No newline at end of file diff --git a/nginx/http.conf b/nginx/http.conf new file mode 100644 index 0000000..b9d56b4 --- /dev/null +++ b/nginx/http.conf @@ -0,0 +1,79 @@ +events {} + +http { + upstream hapi_fhir { + server hapi-fhir:8080; + } + + upstream bus_gateway { + server bus-gateway:3000; + } + + server { + listen 80; + server_name _; + + # Rutas del bus-gateway (prefijo más específico gana sobre /fhir/) + location /fhir/iti65 { + proxy_pass http://bus_gateway; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 90s; + } + + location /fhir/iti67 { + proxy_pass http://bus_gateway; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 90s; + } + + location /fhir/iti78 { + proxy_pass http://bus_gateway; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 90s; + } + location /fhir/iti104 { + proxy_pass http://bus_gateway; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 90s; + } + location /vhl/ { + proxy_pass http://bus_gateway; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 90s; + } + # Resto de /fhir/* va a hapi-fhir + location /fhir/ { + proxy_pass http://hapi_fhir; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 90s; + } + + # Todo lo demás va a hapi-fhir + location / { + proxy_pass http://hapi_fhir; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 90s; + } + } +} diff --git a/nginx/https.conf b/nginx/https.conf new file mode 100644 index 0000000..66eaa68 --- /dev/null +++ b/nginx/https.conf @@ -0,0 +1,95 @@ +events {} + +http { + upstream hapi_fhir { + server hapi-fhir:8080; + } + + upstream bus_gateway { + server bus-gateway:3000; + } + + # Redirige HTTP → HTTPS + server { + listen 80; + server_name _; + return 301 https://$host$request_uri; + } + + server { + listen 443 ssl; + server_name _; + + # Certificados montados como Docker secrets + ssl_certificate /run/secrets/ssl_cert; + ssl_certificate_key /run/secrets/ssl_key; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + # Rutas del bus-gateway (prefijo más específico gana sobre /fhir/) + # Rutas del bus-gateway (prefijo más específico gana sobre /fhir/) + location /fhir/iti65 { + proxy_pass http://bus_gateway; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 90s; + } + + location /fhir/iti67 { + proxy_pass http://bus_gateway; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 90s; + } + + location /fhir/iti78 { + proxy_pass http://bus_gateway; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 90s; + } + location /fhir/iti104 { + proxy_pass http://bus_gateway; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 90s; + } + location /vhl/ { + proxy_pass http://bus_gateway; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 90s; + } + # Resto de /fhir/* va a hapi-fhir + location /fhir/ { + proxy_pass http://hapi_fhir; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 90s; + } + # Todo lo demás va a hapi-fhir + location / { + proxy_pass http://hapi_fhir; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 90s; + } + } +} diff --git a/tests/fixtures/iti65/bundle-document-ips.json b/tests/fixtures/iti65/bundle-document-ips.json new file mode 100644 index 0000000..075fa7e --- /dev/null +++ b/tests/fixtures/iti65/bundle-document-ips.json @@ -0,0 +1,363 @@ +{ + "resourceType": "Bundle", + "meta": { + "profile": [ + "http://conectataton.msal.gov.ar/StructureDefinition/BundleDocAr" + ] + }, + "identifier": { + "system": "urn:ietf:rfc:3986", + "value": "urn:uuid:3a3de9f8-1111-4b6e-8c3f-aabbcc112233" + }, + "type": "document", + "timestamp": "2026-04-21T10:00:00-03:00", + "entry": [ + { + "fullUrl": "urn:uuid:64861145-8711-4410-8c25-74a06e45a627", + "resource": { + "resourceType": "Composition", + "status": "final", + "type": { + "coding": [ + { + "system": "http://loinc.org", + "code": "60591-5", + "display": "Patient summary Document" + } + ] + }, + "subject": { + "reference": "urn:uuid:a1df3ba7-ddc1-40c3-823f-46d4a48e2eb8" + }, + "date": "2026-04-21T10:00:00-03:00", + "author": [ + { + "reference": "urn:uuid:aec4943e-66f9-4ae4-86b3-870011449959" + } + ], + "title": "Resumen de Salud del Paciente", + "section": [ + { + "title": "Medicamentos Activos", + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "10160-0", + "display": "History of Medication use Narrative" + } + ] + }, + "text": { + "status": "generated", + "div": "
Enalapril 10mg/día, Metformina 850mg cada 12 hs.
" + }, + "entry": [ + { + "reference": "urn:uuid:d5a3b669-32de-48ca-a146-823a89ef76c1" + }, + { + "reference": "urn:uuid:7007b547-b3c8-4624-99ca-b27adc9f935e" + } + ] + }, + { + "title": "Alergias e Intolerancias", + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "48765-2", + "display": "Allergies and adverse reactions Document" + } + ] + }, + "text": { + "status": "generated", + "div": "
Alergia a Penicilina (urticaria).
" + }, + "entry": [ + { + "reference": "urn:uuid:a590d1b1-5725-477c-9cf8-5b722f17bcae" + } + ] + }, + { + "title": "Problemas de Salud", + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "11450-4", + "display": "Problem list - Reported" + } + ] + }, + "text": { + "status": "generated", + "div": "
Hipertensión arterial, Diabetes Mellitus tipo 2.
" + }, + "entry": [ + { + "reference": "urn:uuid:0a904e86-eb41-4516-9bc6-5bd405a64984" + }, + { + "reference": "urn:uuid:21fe000b-9388-45b1-9255-a0c0a212ca68" + } + ] + } + ] + } + }, + { + "fullUrl": "urn:uuid:a1df3ba7-ddc1-40c3-823f-46d4a48e2eb8", + "resource": { + "resourceType": "Patient", + "id": "paciente-001", + "meta": { + "profile": [ + "http://conectataton.msal.gov.ar/StructureDefinition/PacienteAR" + ] + }, + "identifier": [ + { + "system": "http://www.renaper.gob.ar/dni", + "value": "30945027" + }, + { + "system": "https://federador.msal.gob.ar/patient-id", + "value": "5037097" + } + ], + "active": true, + "name": [ + { + "use": "official", + "family": "García", + "given": [ + "Juan", + "Carlos" + ] + } + ], + "gender": "male", + "birthDate": "1985-03-14", + "address": [ + { + "use": "home", + "line": [ + "Av. Corrientes 1234" + ], + "city": "Ciudad Autónoma de Buenos Aires", + "state": "Buenos Aires", + "postalCode": "C1043AAZ", + "country": "AR" + } + ] + } + }, + { + "fullUrl": "urn:uuid:urn:uuid:aec4943e-66f9-4ae4-86b3-870011449959", + "resource": { + "resourceType": "Practitioner", + "id": "medico-001", + "identifier": [ + { + "system": "http://www.msal.gov.ar/rm", + "value": "MP-12345" + } + ], + "name": [ + { + "use": "official", + "family": "Rodríguez", + "given": [ + "María", + "Laura" + ] + } + ] + } + }, + { + "fullUrl": "urn:uuid:d5a3b669-32de-48ca-a146-823a89ef76c1", + "resource": { + "resourceType": "MedicationStatement", + "id": "medicacion-001", + "status": "active", + "medicationCodeableConcept": { + "coding": [ + { + "system": "http://www.whocc.no/atc", + "code": "C09AA02", + "display": "Enalapril" + } + ], + "text": "Enalapril 10 mg" + }, + "subject": { + "reference": "urn:uuid:a1df3ba7-ddc1-40c3-823f-46d4a48e2eb8" + }, + "dosage": [ + { + "text": "10 mg por día vía oral" + } + ] + } + }, + { + "fullUrl": "urn:uuid:7007b547-b3c8-4624-99ca-b27adc9f935e", + "resource": { + "resourceType": "MedicationStatement", + "id": "medicacion-002", + "status": "active", + "medicationCodeableConcept": { + "coding": [ + { + "system": "http://www.whocc.no/atc", + "code": "A10BA02", + "display": "Metformin" + } + ], + "text": "Metformina 850 mg" + }, + "subject": { + "reference": "urn:uuid:a1df3ba7-ddc1-40c3-823f-46d4a48e2eb8" + }, + "dosage": [ + { + "text": "850 mg cada 12 horas vía oral" + } + ] + } + }, + { + "fullUrl": "urn:uuid:a590d1b1-5725-477c-9cf8-5b722f17bcae", + "resource": { + "resourceType": "AllergyIntolerance", + "id": "alergia-001", + "clinicalStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical", + "code": "active" + } + ] + }, + "verificationStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/allergyintolerance-verification", + "code": "confirmed" + } + ] + }, + "type": "allergy", + "category": [ + "medication" + ], + "code": { + "coding": [ + { + "system": "http://www.whocc.no/atc", + "code": "J01CA01", + "display": "Ampicillin" + } + ], + "text": "Penicilina" + }, + "patient": { + "reference": "urn:uuid:a1df3ba7-ddc1-40c3-823f-46d4a48e2eb8" + }, + "reaction": [ + { + "manifestation": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "126485001", + "display": "Urticaria" + } + ] + } + ], + "severity": "moderate" + } + ] + } + }, + { + "fullUrl": "urn:uuid:0a904e86-eb41-4516-9bc6-5bd405a64984", + "resource": { + "resourceType": "Condition", + "id": "condicion-001", + "clinicalStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/condition-clinical", + "code": "active" + } + ] + }, + "verificationStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/condition-ver-status", + "code": "confirmed" + } + ] + }, + "code": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "38341003", + "display": "Hypertensive disorder" + } + ], + "text": "Hipertensión arterial" + }, + "subject": { + "reference": "urn:uuid:a1df3ba7-ddc1-40c3-823f-46d4a48e2eb8" + } + } + }, + { + "fullUrl": "urn:uuid:21fe000b-9388-45b1-9255-a0c0a212ca68", + "resource": { + "resourceType": "Condition", + "id": "condicion-002", + "clinicalStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/condition-clinical", + "code": "active" + } + ] + }, + "verificationStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/condition-ver-status", + "code": "confirmed" + } + ] + }, + "code": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "44054006", + "display": "Diabetes mellitus type 2" + } + ], + "text": "Diabetes Mellitus tipo 2" + }, + "subject": { + "reference": "urn:uuid:a1df3ba7-ddc1-40c3-823f-46d4a48e2eb8" + } + } + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/iti65/bundle-transaction-iti65.json b/tests/fixtures/iti65/bundle-transaction-iti65.json new file mode 100644 index 0000000..2e46648 --- /dev/null +++ b/tests/fixtures/iti65/bundle-transaction-iti65.json @@ -0,0 +1,573 @@ +{ + "resourceType": "Bundle", + "meta": { + "profile": [ + "http://conectataton.msal.gov.ar/StructureDefinition/BundleTransaccAR" + ] + }, + "type": "transaction", + "entry": [ + { + "fullUrl": "urn:uuid:297cf925-20ac-4667-a881-284560b67206", + "resource": { + "resourceType": "List", + "id": "submissionset-001", + "meta": { + "profile": [ + "http://conectataton.msal.gov.ar/StructureDefinition/ListAR" + ] + }, + "status": "current", + "mode": "working", + "code": { + "coding": [ + { + "system": "https://profiles.ihe.net/ITI/MHD/CodeSystem/MHDlistTypes", + "code": "submissionset" + } + ] + }, + "subject": { + "identifier": { + "system": "https://federador.msal.gob.ar/patient-id", + "value": "5037097" + } + }, + "date": "2026-04-21T10:00:00-03:00", + "source": { + "identifier": { + "system": "https://federador.msal.gob.ar/uri", + "value": "efector-001" + } + }, + "entry": [ + { + "item": { + "reference": "urn:uuid:1344c85d-4043-434c-b149-e81e5e735965" + } + } + ] + }, + "request": { + "method": "POST", + "url": "List" + } + }, + { + "fullUrl": "urn:uuid:1344c85d-4043-434c-b149-e81e5e735965", + "resource": { + "resourceType": "DocumentReference", + "id": "docref-001", + "meta": { + "profile": [ + "http://conectataton.msal.gov.ar/StructureDefinition/DocumentReferenceAR" + ] + }, + "status": "current", + "type": { + "coding": [ + { + "system": "http://loinc.org", + "code": "60591-5", + "display": "Patient Summary Document" + } + ] + }, + "subject": { + "identifier": { + "system": "https://federador.msal.gob.ar/patient-id", + "value": "5037097" + } + }, + "date": "2026-04-21T10:00:00-03:00", + "author": [ + { + "identifier": { + "system": "http://www.msal.gov.ar/rm", + "value": "MP-12345" + } + } + ], + "custodian": { + "identifier": { + "system": "https://federador.msal.gob.ar/uri", + "value": "efector-001" + } + }, + "content": [ + { + "attachment": { + "contentType": "application/fhir+json", + "url": "urn:uuid:bundle-doc-ips-001" + }, + "format": { + "system": "https://profiles.ihe.net/ITI/MHD/CodeSystem/MHDlistTypes", + "code": "urn:ihe:iti:xds-sd:pdf:2008" + } + } + ], + "context": { + "sourcePatientInfo": { + "identifier": { + "system": "http://www.renaper.gob.ar/dni", + "value": "30945027" + } + } + } + }, + "request": { + "method": "POST", + "url": "DocumentReference" + } + }, + { + "fullUrl": "urn:uuid:b29f0cd8-c72d-478e-8e2c-fa9c8085f0ee", + "resource": { + "resourceType": "Bundle", + "meta": { + "profile": [ + "http://conectataton.msal.gov.ar/StructureDefinition/BundleDocAr" + ] + }, + "identifier": { + "system": "urn:ietf:rfc:3986", + "value": "urn:uuid:3a3de9f8-1111-4b6e-8c3f-aabbcc112233" + }, + "type": "document", + "timestamp": "2026-04-21T10:00:00-03:00", + "entry": [ + { + "fullUrl": "urn:uuid:64861145-8711-4410-8c25-74a06e45a627", + "resource": { + "resourceType": "Composition", + "status": "final", + "type": { + "coding": [ + { + "system": "http://loinc.org", + "code": "60591-5", + "display": "Patient summary Document" + } + ] + }, + "subject": { + "reference": "urn:uuid:a1df3ba7-ddc1-40c3-823f-46d4a48e2eb8" + }, + "date": "2026-04-21T10:00:00-03:00", + "author": [ + { + "reference": "urn:uuid:aec4943e-66f9-4ae4-86b3-870011449959" + } + ], + "title": "Resumen de Salud del Paciente", + "section": [ + { + "title": "Medicamentos Activos", + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "10160-0", + "display": "History of Medication use Narrative" + } + ] + }, + "text": { + "status": "generated", + "div": "
Enalapril 10mg/día, Metformina 850mg cada 12 hs.
" + }, + "entry": [ + { + "reference": "urn:uuid:d5a3b669-32de-48ca-a146-823a89ef76c1" + }, + { + "reference": "urn:uuid:7007b547-b3c8-4624-99ca-b27adc9f935e" + } + ] + }, + { + "title": "Alergias e Intolerancias", + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "48765-2", + "display": "Allergies and adverse reactions Document" + } + ] + }, + "text": { + "status": "generated", + "div": "
Alergia a Penicilina (urticaria).
" + }, + "entry": [ + { + "reference": "urn:uuid:a590d1b1-5725-477c-9cf8-5b722f17bcae" + } + ] + }, + { + "title": "Problemas de Salud", + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "11450-4", + "display": "Problem list - Reported" + } + ] + }, + "text": { + "status": "generated", + "div": "
Hipertensión arterial, Diabetes Mellitus tipo 2.
" + }, + "entry": [ + { + "reference": "urn:uuid:0a904e86-eb41-4516-9bc6-5bd405a64984" + }, + { + "reference": "urn:uuid:21fe000b-9388-45b1-9255-a0c0a212ca68" + } + ] + } + ] + } + }, + { + "fullUrl": "urn:uuid:a1df3ba7-ddc1-40c3-823f-46d4a48e2eb8", + "resource": { + "resourceType": "Patient", + "id": "5037097", + "identifier": [ + { + "use": "official", + "system": "https://federador.msal.gob.ar/patient-id", + "value": "5037097", + "period": { + "start": "2026-03-12T14:48:59-03:00" + } + }, + { + "use": "usual", + "system": "http://www.renaper.gob.ar/dni", + "value": "30945027" + }, + { + "use": "official", + "system": "https://agomez.pythonanywhere.com/hsi", + "value": "c1234567", + "period": { + "start": "2026-03-12T14:48:59-03:00" + } + } + ], + "active": true, + "name": [ + { + "use": "official", + "text": "Alejandro Lopez Auad", + "family": "Lopez Auad", + "_family": { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/humanname-fathers-family", + "valueString": "Lopez" + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/humanname-mothers-family", + "valueString": "Auad" + } + ] + }, + "given": [ + "Alejandro" + ] + } + ], + "telecom": [ + { + "system": "phone", + "value": "1141233100" + } + ], + "gender": "male", + "birthDate": "1983-04-17" + } + }, + { + "fullUrl": "urn:uuid:urn:uuid:aec4943e-66f9-4ae4-86b3-870011449959", + "resource": { + "resourceType": "Practitioner", + "id": "medico-001", + "identifier": [ + { + "system": "http://www.msal.gov.ar/rm", + "value": "MP-12345" + } + ], + "name": [ + { + "use": "official", + "family": "Rodríguez", + "given": [ + "María", + "Laura" + ] + } + ] + } + }, + { + "fullUrl": "urn:uuid:d5a3b669-32de-48ca-a146-823a89ef76c1", + "resource": { + "resourceType": "MedicationStatement", + "id": "medicacion-001", + "status": "active", + "medicationCodeableConcept": { + "coding": [ + { + "system": "http://www.whocc.no/atc", + "code": "C09AA02", + "display": "Enalapril" + } + ], + "text": "Enalapril 10 mg" + }, + "subject": { + "reference": "urn:uuid:a1df3ba7-ddc1-40c3-823f-46d4a48e2eb8" + }, + "dosage": [ + { + "text": "10 mg por día vía oral" + } + ] + } + }, + { + "fullUrl": "urn:uuid:7007b547-b3c8-4624-99ca-b27adc9f935e", + "resource": { + "resourceType": "MedicationStatement", + "id": "medicacion-002", + "status": "active", + "medicationCodeableConcept": { + "coding": [ + { + "system": "http://www.whocc.no/atc", + "code": "A10BA02", + "display": "Metformin" + } + ], + "text": "Metformina 850 mg" + }, + "subject": { + "reference": "urn:uuid:a1df3ba7-ddc1-40c3-823f-46d4a48e2eb8" + }, + "dosage": [ + { + "text": "850 mg cada 12 horas vía oral" + } + ] + } + }, + { + "fullUrl": "urn:uuid:a590d1b1-5725-477c-9cf8-5b722f17bcae", + "resource": { + "resourceType": "AllergyIntolerance", + "id": "alergia-001", + "clinicalStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical", + "code": "active" + } + ] + }, + "verificationStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/allergyintolerance-verification", + "code": "confirmed" + } + ] + }, + "type": "allergy", + "category": [ + "medication" + ], + "code": { + "coding": [ + { + "system": "http://www.whocc.no/atc", + "code": "J01CA01", + "display": "Ampicillin" + } + ], + "text": "Penicilina" + }, + "patient": { + "reference": "urn:uuid:a1df3ba7-ddc1-40c3-823f-46d4a48e2eb8" + }, + "reaction": [ + { + "manifestation": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "126485001", + "display": "Urticaria" + } + ] + } + ], + "severity": "moderate" + } + ] + } + }, + { + "fullUrl": "urn:uuid:0a904e86-eb41-4516-9bc6-5bd405a64984", + "resource": { + "resourceType": "Condition", + "id": "condicion-001", + "clinicalStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/condition-clinical", + "code": "active" + } + ] + }, + "verificationStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/condition-ver-status", + "code": "confirmed" + } + ] + }, + "code": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "38341003", + "display": "Hypertensive disorder" + } + ], + "text": "Hipertensión arterial" + }, + "subject": { + "reference": "urn:uuid:a1df3ba7-ddc1-40c3-823f-46d4a48e2eb8" + } + } + }, + { + "fullUrl": "urn:uuid:21fe000b-9388-45b1-9255-a0c0a212ca68", + "resource": { + "resourceType": "Condition", + "id": "condicion-002", + "clinicalStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/condition-clinical", + "code": "active" + } + ] + }, + "verificationStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/condition-ver-status", + "code": "confirmed" + } + ] + }, + "code": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "44054006", + "display": "Diabetes mellitus type 2" + } + ], + "text": "Diabetes Mellitus tipo 2" + }, + "subject": { + "reference": "urn:uuid:a1df3ba7-ddc1-40c3-823f-46d4a48e2eb8" + } + } + } + ] + }, + "request": { + "method": "POST", + "url": "Bundle" + } + }, + { + "fullUrl": "urn:uuid:a1df3ba7-ddc1-40c3-823f-46d4a48e2eb8", + "resource": { + "resourceType": "Patient", + "id": "5037097", + "identifier": [ + { + "use": "official", + "system": "https://federador.msal.gob.ar/patient-id", + "value": "5037097", + "period": { + "start": "2026-03-12T14:48:59-03:00" + } + }, + { + "use": "usual", + "system": "http://www.renaper.gob.ar/dni", + "value": "30945027" + }, + { + "use": "official", + "system": "https://agomez.pythonanywhere.com/hsi", + "value": "c1234567", + "period": { + "start": "2026-03-12T14:48:59-03:00" + } + } + ], + "active": true, + "name": [ + { + "use": "official", + "text": "Alejandro Lopez Auad", + "family": "Lopez Auad", + "_family": { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/humanname-fathers-family", + "valueString": "Lopez" + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/humanname-mothers-family", + "valueString": "Auad" + } + ] + }, + "given": [ + "Alejandro" + ] + } + ], + "telecom": [ + { + "system": "phone", + "value": "1141233100" + } + ], + "gender": "male", + "birthDate": "1983-04-17" + }, + "request": { + "method": "POST", + "url": "Patient" + } + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/iti65/patient.json b/tests/fixtures/iti65/patient.json new file mode 100644 index 0000000..467ff06 --- /dev/null +++ b/tests/fixtures/iti65/patient.json @@ -0,0 +1,43 @@ +{ + "resourceType": "Patient", + "meta": { + "profile": [ + "http://conectataton.msal.gov.ar/StructureDefinition/PacienteAR" + ] + }, + "identifier": [ + { + "system": "http://www.renaper.gob.ar/dni", + "value": "30945027" + }, + { + "system": "https://federador.msal.gob.ar/patient-id", + "value": "5037097" + } + ], + "active": true, + "name": [ + { + "use": "official", + "family": "García", + "given": [ + "Juan", + "Carlos" + ] + } + ], + "gender": "male", + "birthDate": "1985-03-14", + "address": [ + { + "use": "home", + "line": [ + "Av. Corrientes 1234" + ], + "city": "Ciudad Autónoma de Buenos Aires", + "state": "Buenos Aires", + "postalCode": "C1043AAZ", + "country": "AR" + } + ] +} \ No newline at end of file