diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..dfc07cc --- /dev/null +++ b/examples/README.md @@ -0,0 +1,140 @@ +# Ejemplos de uso del Cliente Tryton + +Esta carpeta contiene ejemplos prácticos de cómo utilizar el cliente RPC de Tryton en Node.js. + +## Preparación + +Antes de ejecutar cualquier ejemplo, asegúrate de tener: + +1. **Node.js instalado** (versión 14 o superior) +2. **Servidor Tryton ejecutándose** y accesible +3. **Credenciales válidas** para conectarse al servidor +4. **El proyecto compilado**: + ```bash + npm install + npm run build + ``` + +## Ejemplos disponibles + +### 1. Conexión Básica (`basic-connection.ts`) + +**Propósito**: Demuestra cómo conectarse a Tryton y realizar operaciones básicas. + +**Características**: + +- ✅ Conexión al servidor Tryton +- ✅ Autenticación de usuario +- ✅ Obtención de información del usuario y token de sesión +- ✅ Búsqueda de terceros (party.party) +- ✅ Manejo de errores y validación de configuración + +**Configuración requerida**: +Edita el archivo `basic-connection.ts` y completa estos campos: + +```typescript +const config = { + hostname: "localhost", // IP/dominio del servidor Tryton + port: 8000, // Puerto (generalmente 8000) + database: "tu_base_datos", // Nombre de la base de datos + username: "tu_usuario", // Usuario de Tryton + password: "tu_contraseña", // Contraseña + language: "es", // Idioma preferido + // ... resto de opciones +}; +``` + +**Ejecución**: + +```bash +# Opción 1: Usando el script npm (recomendado) +npm run example:basic + +# Opción 2: Directamente con Node.js +node dist/examples/basic-connection.js +``` + +**Salida esperada**: + +``` +🚀 Iniciando ejemplo de conexión con Tryton... + +📡 Creando cliente Tryton... +🔗 Conectando al servidor... +✅ Conexión exitosa! + +👤 INFORMACIÓN DEL USUARIO +======================================== +Usuario: Juan Pérez (ID: 123) +Idioma: es +Token de sesión: usuario:123:abc123xyz789 + +🏢 LISTA DE TERCEROS +======================================== +Buscando terceros... +✅ Se encontraron 15 terceros: + + 1. ACME Corporation (ACME001) + 2. Beta Industries + 3. Gamma Solutions (GAM001) +... + +🎉 ¡Ejemplo completado exitosamente! +``` + +## Solución de problemas comunes + +### Error de conexión + +- **Síntoma**: `Error durante la ejecución: Connection refused` +- **Solución**: Verifica que el servidor Tryton esté ejecutándose en la IP y puerto correctos + +### Error de autenticación + +- **Síntoma**: `Error durante la ejecución: Invalid login` +- **Solución**: Confirma que el usuario, contraseña y base de datos sean correctos + +### Error de base de datos + +- **Síntoma**: `Error durante la ejecución: Database not found` +- **Solución**: Asegúrate de que el nombre de la base de datos sea exacto (sensible a mayúsculas) + +### Error de permisos + +- **Síntoma**: `Access denied for model party.party` +- **Solución**: El usuario necesita permisos de lectura para el modelo de terceros + +### Error de compilación TypeScript + +- **Síntoma**: Errores durante `npm run build` +- **Solución**: Ejecuta `npm run clean` y luego `npm run build` + +## Estructura de archivos + +``` +examples/ +├── README.md # Esta documentación +├── basic-connection.ts # Ejemplo básico de conexión +└── [futuros ejemplos] # Ejemplos adicionales +``` + +## Próximos ejemplos + +Se planean agregar más ejemplos que cubran: + +- 📝 Creación de registros +- ✏️ Actualización de datos +- 🗑️ Eliminación de registros +- 🔍 Búsquedas avanzadas con dominios complejos +- 📊 Trabajo con diferentes modelos de Tryton +- 🔧 Configuración avanzada del cliente + +## Soporte + +Si encuentras problemas con los ejemplos: + +1. Verifica que hayas seguido todos los pasos de preparación +2. Revisa la sección de solución de problemas +3. Asegúrate de usar la versión más reciente del cliente + +Para más información sobre la API de Tryton, consulta la [documentación oficial de Tryton](https://docs.tryton.org/). diff --git a/examples/advanced.js b/examples/advanced.js deleted file mode 100644 index 8758c92..0000000 --- a/examples/advanced.js +++ /dev/null @@ -1,200 +0,0 @@ -/** - * Advanced usage examples for Tryton RPC Client - */ - -const { TrytonClient, Fault, ProtocolError } = require("../src"); - -async function advancedExamples() { - console.log("🔬 Advanced Tryton RPC Client Examples"); - console.log("=====================================\n"); - - const client = new TrytonClient({ - hostname: "localhost", - database: "tryton", - username: "admin", - password: "admin", - options: { - verbose: false, - cache: true, - keepMax: 6, - timeout: 45000, - }, - }); - - try { - await client.connect(); - console.log("✅ Connected to Tryton server\n"); - - // Example 1: Batch operations - console.log("📦 Example 1: Batch Operations"); - console.log("------------------------------"); - - const batchData = [ - { name: "Company A", code: "COMP_A" }, - { name: "Company B", code: "COMP_B" }, - { name: "Company C", code: "COMP_C" }, - ]; - - const createdIds = await client.create("party.party", batchData); - console.log("Created parties:", createdIds); - - // Read them back - const createdParties = await client.read("party.party", createdIds, [ - "id", - "name", - "code", - ]); - console.log("Created party details:", createdParties); - console.log(); - - // Example 2: Complex search with domain - console.log("🔍 Example 2: Complex Search"); - console.log("---------------------------"); - - const complexDomain = [ - "OR", - [["name", "like", "Company%"]], - [["code", "like", "COMP_%"]], - ]; - - const foundIds = await client.search("party.party", complexDomain); - console.log("Found IDs with complex domain:", foundIds); - - const foundParties = await client.searchRead( - "party.party", - complexDomain, - ["id", "name", "code"], - 0, // offset - 10 // limit - ); - console.log("Found parties:", foundParties); - console.log(); - - // Example 3: Update operations - console.log("✏️ Example 3: Update Operations"); - console.log("------------------------------"); - - if (createdIds.length > 0) { - await client.write("party.party", [createdIds[0]], { - name: "Updated Company Name", - }); - console.log(`Updated party ${createdIds[0]}`); - - // Verify update - const updated = await client.read( - "party.party", - [createdIds[0]], - ["id", "name", "code"] - ); - console.log("Updated party:", updated[0]); - } - console.log(); - - // Example 4: Parallel operations - console.log("⚡ Example 4: Parallel Operations"); - console.log("--------------------------------"); - - const parallelCalls = [ - { method: "model.party.party.search_count", args: [[]] }, - { method: "model.company.company.search_count", args: [[]] }, - { method: "common.version", args: [] }, - ]; - - const parallelResults = await client.callParallel(parallelCalls); - console.log("Parallel results:"); - console.log("- Total parties:", parallelResults[0]); - console.log("- Total companies:", parallelResults[1]); - console.log("- Server version:", parallelResults[2]); - console.log(); - - // Example 5: Working with dates - console.log("📅 Example 5: Working with Dates"); - console.log("-------------------------------"); - - const today = new Date(); - const tomorrow = new Date(today); - tomorrow.setDate(tomorrow.getDate() + 1); - - console.log("Today:", today.toISOString()); - console.log("Tomorrow:", tomorrow.toISOString()); - - // Note: Date handling depends on your specific Tryton models - // This is just to show how dates are serialized - console.log(); - - // Example 6: Error handling patterns - console.log("⚠️ Example 6: Error Handling"); - console.log("---------------------------"); - - try { - await client.call("nonexistent.model.method", []); - } catch (error) { - if (error instanceof Fault) { - console.log("✅ Caught RPC Fault:", error.faultCode); - } else if (error instanceof ProtocolError) { - console.log("✅ Caught Protocol Error:", error.errcode); - } else { - console.log("✅ Caught Generic Error:", error.message); - } - } - - try { - await client.read("party.party", [999999], ["name"]); - } catch (error) { - console.log( - "✅ Caught read error (likely record not found):", - error.message - ); - } - console.log(); - - // Example 7: Cache usage - console.log("💾 Example 7: Cache Usage"); - console.log("------------------------"); - - // First call - will hit server - console.time("First call"); - const result1 = await client.read("party.party", [1], ["name"]); - console.timeEnd("First call"); - - // Second call - should be faster due to cache - console.time("Second call (cached)"); - const result2 = await client.read("party.party", [1], ["name"]); - console.timeEnd("Second call (cached)"); - - console.log( - "Results are equal:", - JSON.stringify(result1) === JSON.stringify(result2) - ); - - // Clear cache - client.clearCache(); - console.log("Cache cleared"); - console.log(); - - // Cleanup: Delete created test records - console.log("🧹 Cleanup: Deleting test records"); - console.log("---------------------------------"); - - if (createdIds.length > 0) { - try { - await client.delete("party.party", createdIds); - console.log("✅ Deleted test records:", createdIds); - } catch (error) { - console.log("⚠️ Could not delete test records:", error.message); - } - } - } catch (error) { - console.error("❌ Advanced examples failed:", error.message); - console.error(error.stack); - } finally { - client.close(); - console.log("\n👋 Advanced examples completed"); - } -} - -if (require.main === module) { - advancedExamples().catch(console.error); -} - -module.exports = { advancedExamples }; diff --git a/examples/basic-connection.ts b/examples/basic-connection.ts new file mode 100644 index 0000000..b865acb --- /dev/null +++ b/examples/basic-connection.ts @@ -0,0 +1,172 @@ +/** + * Ejemplo de conexión básica con Tryton + * + * Este ejemplo demuestra cómo: + * 1. Conectarse al servidor Tryton + * 2. Obtener información del usuario y el token de sesión + * 3. Buscar terceros (party.party) y mostrar sus nombres + * + * Instrucciones de uso: + * 1. Instala las dependencias: npm install + * 2. Compila el proyecto: npm run build + * 3. Edita las variables de configuración abajo con tus datos de conexión + * 4. Ejecuta: node dist/examples/basic-connection.js + */ + +import { TrytonClient } from "../src/client"; + +// ==================================================== +// CONFIGURACIÓN - EDITA ESTOS VALORES CON TUS DATOS +// ==================================================== +const config = { + hostname: "https://demo7.6.tryton.org", // Servidor demo con HTTPS + port: 8000, // Puerto del servidor Tryton (generalmente 8000) + database: "demo7.6", // Base de datos demo + username: "admin", // Usuario demo + password: "admin", // Contraseña demo + language: "es", // Idioma (es, en, etc.) + options: { + verbose: true, // Mostrar información detallada de conexión + timeout: 30000, // Timeout en milisegundos + }, +}; + +// Verificar que se han completado los datos de configuración +function validateConfig() { + const requiredFields: (keyof typeof config)[] = [ + "hostname", + "database", + "username", + "password", + ]; + const missingFields = requiredFields.filter((field) => !config[field]); + + if (missingFields.length > 0) { + console.error( + "❌ Error: Faltan los siguientes campos de configuración:" + ); + missingFields.forEach((field) => console.error(` - ${field}`)); + console.error( + "\n💡 Edita este archivo y completa la configuración antes de ejecutar." + ); + process.exit(1); + } +} + +// Interfaz para los terceros (party.party) +interface Party { + id: number; + name: string; + code?: string; + active: boolean; + lang?: string; +} + +async function main() { + console.log("🚀 Iniciando ejemplo de conexión con Tryton...\n"); + + // Validar configuración + validateConfig(); + + // Crear cliente + console.log("📡 Creando cliente Tryton..."); + const client = new TrytonClient(config); + + try { + // Conectar al servidor + console.log("🔗 Conectando al servidor..."); + const connected = await client.connect(); + + if (!connected) { + throw new Error("No se pudo establecer la conexión"); + } + + console.log("✅ Conexión exitosa!\n"); + + // ==================================================== + // INFORMACIÓN DEL USUARIO Y SESIÓN + // ==================================================== + console.log("👤 INFORMACIÓN DEL USUARIO"); + console.log("=".repeat(40)); + + // Mostrar información básica del usuario + console.log(`Usuario: ${config.username}`); + console.log(`Base de datos: ${config.database}`); + console.log(`Servidor: ${config.hostname}:${config.port}`); + + // Mostrar token de sesión + const sessionToken = client.getSession(); + console.log(`Token de sesión: ${sessionToken}`); + console.log(""); + + // ==================================================== + // BÚSQUEDA DE TERCEROS + // ==================================================== + console.log("🏢 LISTA DE TERCEROS"); + console.log("=".repeat(40)); + + // Buscar todos los terceros activos (limitado a 20 para el ejemplo) + console.log("Buscando terceros..."); + const partiesData = await client.searchRead( + "party.party", // Modelo de terceros + [], // Dominio: todos los terceros (sin filtros) + ["id", "name", "code"], // Campos a obtener + 0, // Offset + 20 // Límite de resultados + // Sin parámetro order + ); + + if (partiesData.length === 0) { + console.log("❌ No se encontraron terceros."); + } else { + console.log(`✅ Se encontraron ${partiesData.length} terceros:\n`); + + // Mostrar lista de terceros + partiesData.forEach((party, index) => { + const code = party.code ? ` (${party.code})` : ""; + console.log( + `${(index + 1).toString().padStart(2)}. ${ + party.name + }${code}` + ); + }); + } + + console.log("\n🎉 ¡Ejemplo completado exitosamente!"); + } catch (error) { + console.error("❌ Error durante la ejecución:"); + const errorMessage = + error instanceof Error ? error.message : String(error); + console.error(errorMessage); + + if (config.options.verbose && error instanceof Error && error.stack) { + console.error("\nStack trace:"); + console.error(error.stack); + } + + // Sugerencias de solución de problemas + console.error("\n💡 Posibles soluciones:"); + console.error(" - Verifica que el servidor Tryton esté ejecutándose"); + console.error(" - Confirma que los datos de conexión sean correctos"); + console.error( + " - Asegúrate de que el usuario tenga permisos adecuados" + ); + console.error(" - Verifica la conectividad de red al servidor"); + + process.exit(1); + } finally { + // Cerrar la conexión si está abierta + if (client && client.isConnected) { + console.log("\n🔌 Cerrando conexión..."); + client.close(); + console.log("\n✅ Conexión cerrada."); + } + } +} + +// Ejecutar el ejemplo si se llama directamente +if (require.main === module) { + main().catch(console.error); +} + +export { main }; diff --git a/examples/basic.js b/examples/basic.js deleted file mode 100644 index e095314..0000000 --- a/examples/basic.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Basic usage example of Tryton RPC Client - * Simple example similar to the README - */ - -const { TrytonClient } = require("../src/client"); - -async function basicExample() { - console.log("📖 Basic Tryton RPC Client Example"); - console.log("==================================\n"); - - // Create client - const client = new TrytonClient({ - hostname: "https://demo7.6.tryton.org", // Using HTTPS demo server - database: "demo7.6", - username: "admin", - password: "admin", - port: 8000, - language: "en", - }); - - try { - // Connect - console.log("Connecting..."); - await client.connect(); - console.log("✅ Connected!\n"); - - // Read a party - console.log("Reading party with ID 1..."); - const party = await client.read( - "party.party", - [1], - ["id", "name", "code"] - ); - console.log("Party:", party[0]); - console.log(); - - // Create a new party - console.log("Creating new party..."); - const newIds = await client.create("party.party", [ - { name: "Test Party from JS" }, - ]); - console.log("Created party with ID:", newIds[0]); - console.log(); - - // Search for parties - console.log("Searching for parties..."); - const searchResults = await client.searchRead( - "party.party", - [["name", "like", "Test%"]], - ["id", "name"], - 0, // offset - 5 // limit - ); - console.log("Found parties:", searchResults); - } catch (error) { - console.error("❌ Error:", error.message); - } finally { - client.close(); - console.log("\n👋 Connection closed"); - } -} - -if (require.main === module) { - basicExample().catch(console.error); -} - -module.exports = { basicExample }; diff --git a/examples/test-parties.js b/examples/test-parties.js deleted file mode 100644 index adf9b03..0000000 --- a/examples/test-parties.js +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Test para obtener terceros (parties) de Tryton - * Muestra el nombre de cada tercero y el total de terceros - */ - -const { TrytonClient } = require("../src/client"); - -async function testParties() { - console.log("🏢 Test de Terceros (Parties) en Tryton"); - console.log("=======================================\n"); - - // Configuración del servidor Tryton - // Modifica estos valores según tu servidor - // - // Ejemplos de configuración: - // - // Para servidor local: - // hostname: "localhost" o "127.0.0.1" - // - // Para servidor remoto HTTP: - // hostname: "mi-servidor.com" - // - // Para servidor remoto HTTPS: - // hostname: "https://mi-servidor.com" - // - // Para servidor demo: - // hostname: "https://demo7.6.tryton.org", database: "demo7.6" - const config = { - hostname: "https://naliia.onecluster.com.co", // Sin barra final - database: "tryton", // Cambia por el nombre de tu base de datos - username: "admin", // Tu usuario de Tryton - password: "admin", // Tu contraseña - port: 8000, // Puerto del servidor (8000 para HTTP, 8443 para HTTPS) - language: "es", // Idioma (es, en, fr, etc.) - options: { - verbose: true, // Activar para ver qué está pasando - }, - }; - - // Crear cliente - const client = new TrytonClient(config); - - try { - console.log("📡 Conectando al servidor Tryton..."); - await client.connect(); - console.log("✅ Conexión exitosa!\n"); - - // 1. Obtener el total de terceros - console.log("🔢 Obteniendo cantidad total de terceros..."); - const totalParties = await client.searchCount("party.party", []); - console.log(`📊 Total de terceros en el sistema: ${totalParties}\n`); - - // 2. Obtener todos los terceros con información básica - console.log("👥 Obteniendo lista de todos los terceros..."); - const parties = await client.searchRead( - "party.party", - [], // Sin filtros, obtener todos - ["id", "name", "code"], // Campos que queremos - 0, // offset - null, // sin límite para obtener todos - null, // sin orden específico - {} // contexto vacío - ); - - console.log( - `\n📋 Lista completa de terceros (${parties.length} encontrados):` - ); - console.log("=" + "=".repeat(60)); - - // Mostrar cada tercero - parties.forEach((party, index) => { - const number = (index + 1).toString().padStart(3, " "); - const id = party.id.toString().padStart(4, " "); - const code = party.code ? `[${party.code}]` : "[Sin código]"; - const name = party.name || "Sin nombre"; - - console.log(`${number}. ID:${id} ${code.padEnd(12)} ${name}`); - }); - - console.log("=" + "=".repeat(60)); - console.log(`\n✨ Resumen:`); - console.log(` • Total de terceros: ${totalParties}`); - console.log(` • Terceros mostrados: ${parties.length}`); - console.log( - ` • Terceros con nombre: ${ - parties.filter((p) => p.name && p.name.trim()).length - }` - ); - console.log( - ` • Terceros con código: ${ - parties.filter((p) => p.code && p.code.trim()).length - }` - ); - - // Mostrar algunos terceros destacados si los hay - const partiesWithNames = parties.filter( - (p) => p.name && p.name.trim().length > 0 - ); - if (partiesWithNames.length > 0) { - console.log(`\n🌟 Algunos terceros destacados:`); - partiesWithNames.slice(0, 5).forEach((party) => { - console.log(` • ${party.name} (ID: ${party.id})`); - }); - if (partiesWithNames.length > 5) { - console.log(` • ... y ${partiesWithNames.length - 5} más`); - } - } - } catch (error) { - console.error("\n❌ Error durante la ejecución:", error.message); - if (error.stack) { - console.error("\n📍 Stack trace:"); - console.error(error.stack); - } - } finally { - console.log("\n🔌 Cerrando conexión..."); - client.close(); - console.log("👋 ¡Adiós!"); - } -} - -// Ejecutar si se llama directamente -if (require.main === module) { - testParties().catch(console.error); -} - -module.exports = { testParties }; diff --git a/examples/test.js b/examples/test.js deleted file mode 100644 index 971593a..0000000 --- a/examples/test.js +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Test script for Tryton RPC Client - * Equivalent to the Python test_client.py - */ - -const { TrytonClient } = require("../src/client"); - -async function main() { - console.log("🚀 Testing Tryton RPC Client for JavaScript"); - console.log("============================================\n"); - - // Create client instance (equivalent to Python version) - const client = new TrytonClient({ - hostname: "https://demo7.6.tryton.org", // Explicitly use HTTPS - database: "demo7.6", - username: "admin", - password: "admin", - port: 8000, // Keep original port but force HTTPS - language: "en", - options: { - verbose: true, // Enable logging to see requests/responses - cache: true, // Enable caching - keepMax: 4, // Maximum pooled connections - }, - }); - - try { - console.log("📡 Connecting to Tryton server..."); - await client.connect(); - console.log("✅ Connected successfully!\n"); - - console.log("📋 Client configuration:"); - console.log(JSON.stringify(client.getConfig(), null, 2)); - console.log(); - - // Test 1: Read party record (equivalent to Python test) - console.log("🔍 Test 1: Reading party record..."); - const readResult = await client.call("model.party.party.read", [ - [1], - ["id", "name", "code"], - {}, - ]); - console.log("📄 Read result:", JSON.stringify(readResult, null, 2)); - console.log(); - - // Test 2: Create party record (equivalent to Python test) - console.log("➕ Test 2: Creating new party record..."); - const createResult = await client.call("model.party.party.create", [ - [{ name: "Desde JavaScript" }], - {}, - ]); - console.log("🆕 Create result (new IDs):", createResult); - console.log(); - - // Test 3: Using helper methods - console.log("🛠️ Test 3: Using helper methods..."); - - // Read using helper method - const parties = await client.read( - "party.party", - [1], - ["id", "name", "code"] - ); - console.log("👥 Parties (helper method):", parties); - - // Search for parties - const partyIds = await client.search( - "party.party", - [["name", "like", "%"]], - 0, - 5 - ); - console.log("🔎 Found party IDs:", partyIds); - - // Search and read in one call - const partyRecords = await client.searchRead( - "party.party", - [["id", "in", partyIds.slice(0, 3)]], - ["id", "name", "code"] - ); - console.log("📋 Party records:", partyRecords); - - // Count parties - const partyCount = await client.searchCount("party.party", []); - console.log("📊 Total parties count:", partyCount); - console.log(); - - // Test 4: Server information - console.log("ℹ️ Test 4: Server information..."); - - try { - const version = await client.getVersion(); - console.log("🏷️ Server version:", version); - } catch (error) { - console.log("⚠️ Could not get version:", error.message); - } - - try { - const databases = await client.listDatabases(); - console.log("🗄️ Available databases:", databases); - } catch (error) { - console.log("⚠️ Could not list databases:", error.message); - } - console.log(); - - // Test 5: Error handling - console.log("❌ Test 5: Error handling..."); - try { - await client.call("invalid.method.name", []); - } catch (error) { - console.log("✅ Correctly caught error:", error.message); - } - console.log(); - - // Test 6: Multiple calls - console.log("🔄 Test 6: Multiple calls..."); - const multipleCalls = [ - { method: "model.party.party.read", args: [[1], ["name"]] }, - { method: "model.party.party.search_count", args: [[]] }, - ]; - - const multiResults = await client.callMultiple(multipleCalls); - console.log("🔢 Multiple call results:", multiResults); - console.log(); - - console.log("✅ All tests completed successfully!"); - } catch (error) { - console.error("❌ Test failed with error:", error.message); - console.error("Stack trace:", error.stack); - } finally { - console.log("\n🔌 Closing connection..."); - client.close(); - console.log("👋 Goodbye!"); - } -} - -// Handle unhandled promise rejections -process.on("unhandledRejection", (reason, promise) => { - console.error("Unhandled Rejection at:", promise, "reason:", reason); - process.exit(1); -}); - -// Handle uncaught exceptions -process.on("uncaughtException", (error) => { - console.error("Uncaught Exception:", error); - process.exit(1); -}); - -// Run the test -if (require.main === module) { - main().catch(console.error); -} - -module.exports = { main }; diff --git a/package-lock.json b/package-lock.json index e0f99bc..aa1ec18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,239 +1,87 @@ { - "name": "tryton-rpc-client-js", + "name": "tryton-rpc-client", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "tryton-rpc-client-js", + "name": "tryton-rpc-client", "version": "1.0.0", - "license": "GPL-3.0", + "license": "MIT", "devDependencies": { - "eslint": "^8.0.0" + "@types/node": "^20.0.0", + "rimraf": "^5.0.0", + "typescript": "^5.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "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/@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/@types/node": { + "version": "20.19.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.20.tgz", + "integrity": "sha512-2Q7WS25j4pS1cS8yw3d6buNCVJukOTeQ39bAnwR6sOJbaxvyCGebzTMypDFN82CxBLnl+lSWVdCCWbRY6y9yZQ==", "dev": true, "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "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/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "undici-types": "~6.21.0" } }, "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==", + "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": ">=8" + "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==", + "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", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -242,41 +90,13 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "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/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" + "balanced-match": "^1.0.0" } }, "node_modules/color-convert": { @@ -299,13 +119,6 @@ "dev": true, "license": "MIT" }, - "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/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -321,449 +134,62 @@ "node": ">= 8" } }, - "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/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "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/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "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/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/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "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": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "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/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "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": "*" + "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", "dependencies": { - "is-glob": "^4.0.3" + "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" }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "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/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "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.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "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": { @@ -777,206 +203,61 @@ "dev": true, "license": "ISC" }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "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": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" + "@isaacs/cliui": "^8.0.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "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": "MIT" + "license": "ISC" }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "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/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/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, "engines": { - "node": ">= 0.8.0" + "node": ">=16 || 14 >=14.17" } }, - "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": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { + "node_modules/package-json-from-dist": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "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": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "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" - } + "license": "BlueOak-1.0.0" }, "node_modules/path-key": { "version": "3.1.1", @@ -988,107 +269,37 @@ "node": ">=8" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "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": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "glob": "^7.1.3" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", + "license": "ISC", "dependencies": { - "queue-microtask": "^1.2.2" + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/shebang-command": { @@ -1114,7 +325,71 @@ "node": ">=8" } }, - "node_modules/strip-ansi": { + "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/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==", @@ -1127,75 +402,67 @@ "node": ">=8" } }, - "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==", + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "ansi-regex": "^6.0.1" + }, + "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/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "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/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1212,34 +479,102 @@ "node": ">= 8" } }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "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": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?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/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==", + "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/sponsors/sindresorhus" + "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/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/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" } } } diff --git a/package.json b/package.json index 0aab5e5..41ca34f 100644 --- a/package.json +++ b/package.json @@ -1,47 +1,49 @@ { - "name": "tryton-rpc-client-js", + "name": "tryton-rpc-client", "version": "1.0.0", - "description": "JavaScript RPC Client for Tryton ERP Server", - "main": "src/client.js", + "description": "Cliente RPC TypeScript para Tryton ERP - Compatible con Node.js", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "type": "commonjs", "scripts": { - "test": "node examples/test.js", - "example": "node examples/basic.js", - "lint": "eslint src/ examples/", - "start": "node examples/test.js" + "build": "tsc", + "build:watch": "tsc --watch", + "clean": "rimraf dist", + "prepare": "npm run build", + "test": "echo \"Error: no test specified\" && exit 1", + "example:basic": "node dist/examples/basic-connection.js" }, "keywords": [ "tryton", + "erp", "rpc", "client", - "javascript", + "typescript", "nodejs", - "erp", - "json-rpc" + "jsonrpc" ], - "author": "Your Name", - "license": "GPL-3.0", - "repository": { - "type": "git", - "url": "https://github.com/your-username/tryton-rpc-client-js.git" - }, - "bugs": { - "url": "https://github.com/your-username/tryton-rpc-client-js/issues" - }, - "homepage": "https://github.com/your-username/tryton-rpc-client-js#readme", + "author": "", + "license": "MIT", "engines": { - "node": ">=12.0.0" - }, - "files": [ - "src/", - "examples/", - "README.md", - "LICENSE", - "CHANGELOG.md" - ], - "devDependencies": { - "eslint": "^8.0.0" + "node": ">=14.0.0" }, "dependencies": {}, - "optionalDependencies": {}, - "peerDependencies": {} + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.0.0", + "rimraf": "^5.0.0" + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ], + "repository": { + "type": "git", + "url": "" + }, + "bugs": { + "url": "" + }, + "homepage": "" } diff --git a/src/cache.js b/src/cache.ts similarity index 53% rename from src/cache.js rename to src/cache.ts index 9b28d17..8187e1b 100644 --- a/src/cache.js +++ b/src/cache.ts @@ -1,15 +1,22 @@ /** * Cache system similar to Python's CacheDict from Tryton * Implements LRU (Least Recently Used) cache using JavaScript Map + * TypeScript version */ -class CacheDict extends Map { +export type CacheFactory = () => T; + +export class CacheDict extends Map { + private cacheLen: number; + private defaultFactory: CacheFactory | null; + /** * Create a new CacheDict - * @param {number} cacheLen - Maximum number of items to cache - * @param {Function} defaultFactory - Factory function for missing keys */ - constructor(cacheLen = 10, defaultFactory = null) { + constructor( + cacheLen: number = 10, + defaultFactory: CacheFactory | null = null + ) { super(); this.cacheLen = cacheLen; this.defaultFactory = defaultFactory; @@ -17,11 +24,8 @@ class CacheDict extends Map { /** * Set a key-value pair and maintain LRU order - * @param {*} key - The key - * @param {*} value - The value - * @returns {CacheDict} - This instance for chaining */ - set(key, value) { + override set(key: K, value: V): this { // If key exists, delete it first to move to end if (this.has(key)) { this.delete(key); @@ -32,7 +36,9 @@ class CacheDict extends Map { // Remove oldest entries if cache is full while (this.size > this.cacheLen) { const firstKey = this.keys().next().value; - this.delete(firstKey); + if (firstKey !== undefined) { + this.delete(firstKey); + } } return this; @@ -40,12 +46,10 @@ class CacheDict extends Map { /** * Get a value and move it to end (most recently used) - * @param {*} key - The key to retrieve - * @returns {*} - The value */ - get(key) { + override get(key: K): V | undefined { if (this.has(key)) { - const value = super.get(key); + const value = super.get(key)!; // Move to end by re-setting this.delete(key); super.set(key, value); @@ -64,14 +68,12 @@ class CacheDict extends Map { /** * Override has() to update LRU order on access - * @param {*} key - The key to check - * @returns {boolean} - Whether the key exists */ - has(key) { + override has(key: K): boolean { const exists = super.has(key); if (exists) { // Move to end on access - const value = super.get(key); + const value = super.get(key)!; this.delete(key); super.set(key, value); } @@ -80,54 +82,64 @@ class CacheDict extends Map { /** * Get current cache size - * @returns {number} - Number of items in cache */ - get length() { + get length(): number { return this.size; } /** * Clear all items from cache */ - clear() { + override clear(): void { super.clear(); } /** * Convert cache to array for debugging - * @returns {Array} - Array of [key, value] pairs */ - toArray() { + toArray(): Array<[K, V]> { return Array.from(this.entries()); } } +export interface CacheEntry { + expire: Date; + value: T; +} + +export interface CacheStats { + totalPrefixes: number; + totalEntries: number; + prefixes: Record; +} + /** * Advanced cache for Tryton RPC with expiration support */ -class TrytonCache { - constructor(cacheLen = 1024) { - this.store = new CacheDict(cacheLen, () => new CacheDict(cacheLen)); +export class TrytonCache { + private store: CacheDict>; + private cacheLen: number; + + constructor(cacheLen: number = 1024) { + this.cacheLen = cacheLen; + this.store = new CacheDict>( + cacheLen, + () => new CacheDict(cacheLen) + ); } /** * Check if a prefix is cached - * @param {string} prefix - Method prefix - * @returns {boolean} - Whether prefix exists */ - cached(prefix) { + cached(prefix: string): boolean { return this.store.has(prefix); } /** * Set cache entry with expiration - * @param {string} prefix - Method prefix - * @param {string} key - Cache key - * @param {number|Date} expire - Expiration time - * @param {*} value - Value to cache */ - set(prefix, key, expire, value) { - let expiration; + set(prefix: string, key: string, expire: number | Date, value: any): void { + let expiration: Date; if (typeof expire === "number") { // Assume seconds, convert to Date @@ -141,7 +153,7 @@ class TrytonCache { // Deep copy value to avoid mutations const cachedValue = this._deepCopy(value); - this.store.get(prefix).set(key, { + this.store.get(prefix)!.set(key, { expire: expiration, value: cachedValue, }); @@ -149,24 +161,20 @@ class TrytonCache { /** * Get cached value if not expired - * @param {string} prefix - Method prefix - * @param {string} key - Cache key - * @returns {*} - Cached value - * @throws {Error} - If key not found or expired */ - get(prefix, key) { + get(prefix: string, key: string): any { const now = new Date(); if (!this.store.has(prefix)) { throw new Error("Key not found"); } - const prefixCache = this.store.get(prefix); + const prefixCache = this.store.get(prefix)!; if (!prefixCache.has(key)) { throw new Error("Key not found"); } - const entry = prefixCache.get(key); + const entry = prefixCache.get(key)!; if (entry.expire < now) { prefixCache.delete(key); @@ -179,12 +187,11 @@ class TrytonCache { /** * Clear cache for a specific prefix or all - * @param {string} [prefix] - Optional prefix to clear */ - clear(prefix = null) { + clear(prefix?: string): void { if (prefix) { if (this.store.has(prefix)) { - this.store.get(prefix).clear(); + this.store.get(prefix)!.clear(); } } else { this.store.clear(); @@ -193,35 +200,33 @@ class TrytonCache { /** * Deep copy objects to prevent mutations - * @param {*} obj - Object to copy - * @returns {*} - Deep copied object - * @private */ - _deepCopy(obj) { + private _deepCopy(obj: T): T { if (obj === null || typeof obj !== "object") { return obj; } if (obj instanceof Date) { - return new Date(obj.getTime()); + return new Date(obj.getTime()) as T; } if (obj instanceof Array) { - return obj.map((item) => this._deepCopy(item)); + return obj.map((item) => this._deepCopy(item)) as T; } - if (obj instanceof Buffer) { - return Buffer.from(obj); + // Handle Buffer in Node.js environment + if (typeof Buffer !== "undefined" && obj instanceof Buffer) { + return Buffer.from(obj) as T; } if (typeof obj === "object") { - const copy = {}; + const copy: any = {}; for (const key in obj) { - if (obj.hasOwnProperty(key)) { - copy[key] = this._deepCopy(obj[key]); + if (Object.prototype.hasOwnProperty.call(obj, key)) { + copy[key] = this._deepCopy((obj as any)[key]); } } - return copy; + return copy as T; } return obj; @@ -229,10 +234,9 @@ class TrytonCache { /** * Get cache statistics - * @returns {Object} - Cache statistics */ - getStats() { - const stats = { + getStats(): CacheStats { + const stats: CacheStats = { totalPrefixes: this.store.size, totalEntries: 0, prefixes: {}, @@ -246,9 +250,48 @@ class TrytonCache { return stats; } -} -module.exports = { - CacheDict, - TrytonCache, -}; + /** + * Remove expired entries + */ + cleanupExpired(): number { + const now = new Date(); + let removedCount = 0; + + for (const [prefix, prefixCache] of this.store.entries()) { + const keysToRemove: string[] = []; + + for (const [key, entry] of prefixCache.entries()) { + if (entry.expire < now) { + keysToRemove.push(key); + } + } + + keysToRemove.forEach((key) => { + prefixCache.delete(key); + removedCount++; + }); + } + + return removedCount; + } + + /** + * Get cache size in bytes (approximate) + */ + getSizeEstimate(): number { + let size = 0; + + for (const [prefix, prefixCache] of this.store.entries()) { + size += prefix.length * 2; // UTF-16 encoding + + for (const [key, entry] of prefixCache.entries()) { + size += key.length * 2; + size += JSON.stringify(entry.value).length * 2; + size += 24; // Date object overhead + } + } + + return size; + } +} diff --git a/src/client.js b/src/client.ts similarity index 52% rename from src/client.js rename to src/client.ts index 69b3417..6a17418 100644 --- a/src/client.js +++ b/src/client.ts @@ -1,34 +1,62 @@ /** - * Main Tryton RPC Client - * JavaScript implementation of sabatron-tryton-rpc-client + * Tryton RPC Client for Node.js + * TypeScript implementation of sabatron-tryton-rpc-client */ -const { ServerProxy, ServerPool } = require("./jsonrpc"); +import { ServerProxy, ServerPool } from "./jsonrpc"; + +import { ServerProxyOptions, ServerPoolOptions } from "../types"; + +// Constants +const CONNECT_TIMEOUT = 5000; // 5 seconds +const DEFAULT_TIMEOUT = 30000; // 30 seconds +import { + TrytonClientConfig, + TrytonClientOptions, + SearchDomain, + TrytonContext, + TrytonRecord, + TrytonUser, + UserPreferences, + LoginResult, + RecordId, + RecordIds, + FieldName, + ModelName, + ClientConfig, + TrytonMethodCall, + TypedModelOperations, +} from "../types"; /** * Main client class for connecting to Tryton server via RPC */ -class TrytonClient { +export class TrytonClient { + private hostname: string; + private database: string; + private username: string; + private password: string; + private port: number; + private language: string; + private options: TrytonClientOptions; + private useHttps: boolean; + private connection: ServerPool | null; + private session: string | null; + /** * Create a new Tryton client - * @param {Object} config - Configuration object - * @param {string} config.hostname - Server hostname - * @param {string} config.database - Database name - * @param {string} config.username - Username - * @param {string} config.password - Password - * @param {number} [config.port=8000] - Server port - * @param {string} [config.language='en'] - Language code - * @param {Object} [config.options={}] - Additional options */ - constructor({ - hostname, - database, - username, - password, - port = 8000, - language = "en", - options = {}, - }) { + constructor(config: TrytonClientConfig) { + const { + hostname, + database, + username, + password, + port = 8000, + language = "en", + options = {}, + } = config; + // Extract protocol from hostname if present if (hostname.startsWith("https://")) { this.hostname = hostname.replace("https://", ""); @@ -53,22 +81,15 @@ class TrytonClient { /** * Alternative constructor for backward compatibility - * @param {string} hostname - Server hostname - * @param {string} database - Database name - * @param {string} username - Username - * @param {string} password - Password - * @param {number} [port=8000] - Server port - * @param {string} [language='en'] - Language code - * @returns {TrytonClient} - New client instance */ static create( - hostname, - database, - username, - password, - port = 8000, - language = "en" - ) { + hostname: string, + database: string, + username: string, + password: string, + port: number = 8000, + language: string = "en" + ): TrytonClient { return new TrytonClient({ hostname, database, @@ -84,17 +105,17 @@ class TrytonClient { * @returns {Promise} - True if connection successful * @throws {Error} - If connection or authentication fails */ - async connect() { + async connect(): Promise { try { - // Create proxy for login const proxy = new ServerProxy( this.hostname, this.port, this.database, { verbose: this.options.verbose || false, - connectTimeout: this.options.connectTimeout, - timeout: this.options.timeout, + connectTimeout: + this.options.connectTimeout || CONNECT_TIMEOUT, + timeout: this.options.timeout || DEFAULT_TIMEOUT, useHttps: this.useHttps, } ); @@ -104,16 +125,14 @@ class TrytonClient { password: this.password, }; - const result = await proxy.request("common.db.login", [ + const result = await proxy.request("common.db.login", [ this.username, parameters, this.language, ]); - // Close temporary proxy proxy.close(); - // Create session string this.session = [this.username, ...result].join(":"); // Create connection pool with session @@ -125,8 +144,9 @@ class TrytonClient { session: this.session, cache: this.options.cache !== false ? [] : null, // Enable cache by default verbose: this.options.verbose || false, - connectTimeout: this.options.connectTimeout, - timeout: this.options.timeout, + connectTimeout: + this.options.connectTimeout || CONNECT_TIMEOUT, + timeout: this.options.timeout || DEFAULT_TIMEOUT, keepMax: this.options.keepMax || 4, useHttps: this.useHttps, } @@ -134,7 +154,7 @@ class TrytonClient { return true; } catch (error) { - throw new Error(`Connection failed: ${error.message}`); + throw new Error(`Connection failed: ${(error as Error).message}`); } } @@ -145,13 +165,13 @@ class TrytonClient { * @returns {Promise<*>} - Method result * @throws {Error} - If not connected or method call fails */ - async call(methodName, args = []) { + async call(methodName: string, args: any[] = []): Promise { if (!this.connection) { throw new Error("Not connected. Call connect() first."); } return this.connection.withConnection(async (conn) => { - return conn.request(methodName, args); + return conn.request(methodName, args); }); } @@ -160,8 +180,8 @@ class TrytonClient { * @param {Array<{method: string, args: Array}>} calls - Array of method calls * @returns {Promise} - Array of results */ - async callMultiple(calls) { - const results = []; + async callMultiple(calls: TrytonMethodCall[]): Promise { + const results: any[] = []; for (const call of calls) { const result = await this.call(call.method, call.args); results.push(result); @@ -174,7 +194,7 @@ class TrytonClient { * @param {Array<{method: string, args: Array}>} calls - Array of method calls * @returns {Promise} - Array of results */ - async callParallel(calls) { + async callParallel(calls: TrytonMethodCall[]): Promise { const promises = calls.map((call) => this.call(call.method, call.args)); return Promise.all(promises); } @@ -187,8 +207,13 @@ class TrytonClient { * @param {Object} [context={}] - Context dictionary * @returns {Promise} - Array of records */ - async read(model, ids, fields, context = {}) { - return this.call(`model.${model}.read`, [ids, fields, context]); + async read( + model: ModelName, + ids: RecordIds, + fields: FieldName[], + context: TrytonContext = {} + ): Promise { + return this.call(`model.${model}.read`, [ids, fields, context]); } /** @@ -198,8 +223,15 @@ class TrytonClient { * @param {Object} [context={}] - Context dictionary * @returns {Promise>} - Array of created record IDs */ - async create(model, records, context = {}) { - return this.call(`model.${model}.create`, [records, context]); + async create( + model: ModelName, + records: Record[], + context: TrytonContext = {} + ): Promise { + return this.call(`model.${model}.create`, [ + records, + context, + ]); } /** @@ -210,8 +242,13 @@ class TrytonClient { * @param {Object} [context={}] - Context dictionary * @returns {Promise} */ - async write(model, ids, values, context = {}) { - return this.call(`model.${model}.write`, [ids, values, context]); + async write( + model: ModelName, + ids: RecordIds, + values: Record, + context: TrytonContext = {} + ): Promise { + return this.call(`model.${model}.write`, [ids, values, context]); } /** @@ -221,8 +258,12 @@ class TrytonClient { * @param {Object} [context={}] - Context dictionary * @returns {Promise} */ - async delete(model, ids, context = {}) { - return this.call(`model.${model}.delete`, [ids, context]); + async delete( + model: ModelName, + ids: RecordIds, + context: TrytonContext = {} + ): Promise { + return this.call(`model.${model}.delete`, [ids, context]); } /** @@ -236,14 +277,14 @@ class TrytonClient { * @returns {Promise>} - Array of record IDs */ async search( - model, - domain, - offset = 0, - limit = null, - order = null, - context = {} - ) { - return this.call(`model.${model}.search`, [ + model: ModelName, + domain: SearchDomain, + offset: number = 0, + limit: number | null = null, + order: string[] | null = null, + context: TrytonContext = {} + ): Promise { + return this.call(`model.${model}.search`, [ domain, offset, limit, @@ -263,16 +304,16 @@ class TrytonClient { * @param {Object} [context={}] - Context dictionary * @returns {Promise} - Array of records */ - async searchRead( - model, - domain, - fields, - offset = 0, - limit = null, - order = null, - context = {} - ) { - return this.call(`model.${model}.search_read`, [ + async searchRead( + model: ModelName, + domain: SearchDomain, + fields: FieldName[], + offset: number = 0, + limit: number | null = null, + order: string[] | null = null, + context: TrytonContext = {} + ): Promise { + return this.call(`model.${model}.search_read`, [ domain, offset, limit, @@ -289,15 +330,22 @@ class TrytonClient { * @param {Object} [context={}] - Context dictionary * @returns {Promise} - Number of records */ - async searchCount(model, domain, context = {}) { - return this.call(`model.${model}.search_count`, [domain, context]); + async searchCount( + model: ModelName, + domain: SearchDomain, + context: TrytonContext = {} + ): Promise { + return this.call(`model.${model}.search_count`, [ + domain, + context, + ]); } /** * Get database information * @returns {Promise} - Database info */ - async getDatabaseInfo() { + async getDatabaseInfo(): Promise { return this.call("common.db.get_info", []); } @@ -305,23 +353,49 @@ class TrytonClient { * List available databases * @returns {Promise>} - Database names */ - async listDatabases() { - return this.call("common.db.list", []); + async listDatabases(): Promise { + return this.call("common.db.list", []); } /** * Get server version * @returns {Promise} - Server version */ - async getVersion() { - return this.call("common.version", []); + async getVersion(): Promise { + return this.call("common.version", []); + } + + /** + * Get user preferences + */ + async getUserPreferences(): Promise { + return this.call("model.res.user.get_preferences", [ + true, + {}, + ]); + } + + /** + * Get current user information + */ + async getCurrentUser(): Promise { + const preferences = await this.getUserPreferences(); + const users = await this.read( + "res.user", + [preferences.id], + ["id", "name", "login", "language", "company", "email"] + ); + if (!users || users.length === 0) { + throw new Error("User not found"); + } + return users[0]!; } /** * Clear cache for specific prefix or all * @param {string} [prefix] - Optional prefix to clear */ - clearCache(prefix = null) { + clearCache(prefix?: string): void { if (this.connection) { this.connection.clearCache(prefix); } @@ -331,7 +405,7 @@ class TrytonClient { * Get connection SSL status * @returns {boolean|null} - SSL status or null if not connected */ - get ssl() { + get ssl(): boolean | null { return this.connection ? this.connection.ssl : null; } @@ -339,7 +413,7 @@ class TrytonClient { * Get connection URL * @returns {string|null} - Connection URL or null if not connected */ - get url() { + get url(): string | null { return this.connection ? this.connection.url : null; } @@ -347,7 +421,7 @@ class TrytonClient { * Check if client is connected * @returns {boolean} - True if connected */ - get isConnected() { + get isConnected(): boolean { return this.connection !== null; } @@ -355,14 +429,39 @@ class TrytonClient { * Get current session * @returns {string|null} - Session string or null if not connected */ - getSession() { + getSession(): string | null { return this.session; } + /** + * Get current user ID from session + */ + getUserId(): number | null { + if (!this.session) return null; + const parts = this.session.split(":"); + return parts.length >= 2 ? parseInt(parts[1], 10) : null; + } + + /** + * Get session token from session + */ + getSessionToken(): string | null { + if (!this.session) return null; + const parts = this.session.split(":"); + return parts.length >= 3 ? parts[2] : null; + } + + /** + * Get username + */ + getUsername(): string { + return this.username; + } + /** * Close connection and cleanup resources */ - close() { + close(): void { if (this.connection) { this.connection.close(); this.connection = null; @@ -374,7 +473,7 @@ class TrytonClient { * Create a new client instance with the same configuration * @returns {TrytonClient} - New client instance */ - clone() { + clone(): TrytonClient { return new TrytonClient({ hostname: this.hostname, database: this.database, @@ -390,7 +489,7 @@ class TrytonClient { * Get client configuration (without sensitive data) * @returns {Object} - Client configuration */ - getConfig() { + getConfig(): ClientConfig { return { hostname: this.hostname, database: this.database, @@ -402,8 +501,77 @@ class TrytonClient { url: this.url, }; } + + /** + * Type-safe model operations factory + * Creates a typed interface for specific models + */ + model( + modelName: ModelName + ): TypedModelOperations { + return { + read: ( + ids: RecordIds, + fields: FieldName[], + context?: TrytonContext + ) => this.read(modelName, ids, fields, context), + + create: ( + records: Partial>[], + context?: TrytonContext + ) => + this.create( + modelName, + records as Record[], + context + ), + + write: ( + ids: RecordIds, + values: Partial>, + context?: TrytonContext + ) => + this.write( + modelName, + ids, + values as Record, + context + ), + + delete: (ids: RecordIds, context?: TrytonContext) => + this.delete(modelName, ids, context), + + search: ( + domain: SearchDomain, + offset?: number, + limit?: number, + order?: string[], + context?: TrytonContext + ) => this.search(modelName, domain, offset, limit, order, context), + + searchRead: ( + domain: SearchDomain, + fields: FieldName[], + offset?: number, + limit?: number, + order?: string[], + context?: TrytonContext + ) => + this.searchRead( + modelName, + domain, + fields, + offset, + limit, + order, + context + ), + + searchCount: (domain: SearchDomain, context?: TrytonContext) => + this.searchCount(modelName, domain, context), + }; + } } -module.exports = { - TrytonClient, -}; +// Export for CommonJS compatibility +export default TrytonClient; diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 819d3fa..0000000 --- a/src/index.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Main module exports - * Entry point for the Tryton RPC Client package - */ - -const { TrytonClient } = require("./client"); -const { - ServerProxy, - ServerPool, - ResponseError, - Fault, - ProtocolError, - TrytonJSONEncoder, -} = require("./jsonrpc"); -const { CacheDict, TrytonCache } = require("./cache"); - -module.exports = { - // Main client class - TrytonClient, - - // Low-level RPC classes - ServerProxy, - ServerPool, - - // Error classes - ResponseError, - Fault, - ProtocolError, - - // Utility classes - TrytonJSONEncoder, - CacheDict, - TrytonCache, - - // Convenience export for backward compatibility - Client: TrytonClient, -}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..c81482b --- /dev/null +++ b/src/index.ts @@ -0,0 +1,170 @@ +/** + * Tryton RPC Client for Node.js - TypeScript Entry Point + * Main exports for the Tryton RPC Client library + */ + +// Import types and classes for internal use +import { TrytonClient } from "./client"; +import type { + TrytonClientConfig, + TrytonClientOptions, + TrytonRecord, + TrytonUser, + TrytonError, +} from "../types"; + +// Main client class +export { TrytonClient } from "./client"; +export { default as TrytonClientDefault } from "./client"; + +// RPC and transport classes (Node.js only) +export { + ServerProxy, + ServerPool, + Transport, + TrytonJSONEncoder, + ResponseError, + Fault, + ProtocolError, +} from "./jsonrpc"; + +// Cache classes +export { + TrytonCache, + CacheDict, + type CacheEntry, + type CacheStats, + type CacheFactory, +} from "./cache"; + +// All type definitions +export type { + // Configuration types + TrytonClientConfig, + TrytonClientOptions, + ServerProxyOptions, + ServerPoolOptions, + + // Data types + TrytonRecord, + TrytonUser, + TrytonCompany, + TrytonParty, + + // Search and domain types + SearchDomain, + DomainClause, + DomainOperator, + DomainLogicalOperator, + + // RPC types + TrytonMethodCall, + TrytonBatchCall, + + // Authentication types + LoginParameters, + LoginResult, + UserPreferences, + + // Database types + DatabaseInfo, + + // Error types + RpcError, + + // Context types + TrytonContext, + + // CRUD operation types + CreateOptions, + ReadOptions, + WriteOptions, + DeleteOptions, + SearchOptions, + SearchReadOptions, + + // Client state types + ClientConfig, + + // Utility types + CacheEntry as CacheEntryType, + ModelName, + FieldName, + RecordId, + RecordIds, + TypedModelOperations, +} from "../types"; + +// Re-export error class for convenience +export { TrytonError } from "../types"; + +// Version information +export const VERSION = "1.1.0"; + +// Default export is the main client +export default TrytonClient; + +// Convenience factory functions +export function createClient(config: TrytonClientConfig): TrytonClient { + return new TrytonClient(config); +} + +export function createTrytonClient( + hostname: string, + database: string, + username: string, + password: string, + port?: number, + language?: string +): TrytonClient { + return TrytonClient.create( + hostname, + database, + username, + password, + port, + language + ); +} + +// Type guard functions +export function isTrytonRecord(obj: any): obj is TrytonRecord { + return obj && typeof obj === "object" && typeof obj.id === "number"; +} + +export function isTrytonUser(obj: any): obj is TrytonUser { + return ( + isTrytonRecord(obj) && + typeof obj.login === "string" && + typeof obj.name === "string" + ); +} + +export function isTrytonError(error: any): error is TrytonError { + return error instanceof Error && error.name === "TrytonError"; +} + +// Helper functions for React Context +export interface ReactTrytonContextConfig { + hostname?: string; + database?: string; + port?: number; + language?: string; + options?: TrytonClientOptions; +} + +export function createReactTrytonConfig( + baseConfig: ReactTrytonContextConfig, + username: string, + password: string +): TrytonClientConfig { + return { + hostname: baseConfig.hostname || "localhost", + database: baseConfig.database || "tryton", + username, + password, + port: baseConfig.port || 8000, + language: baseConfig.language || "en", + options: baseConfig.options || {}, + }; +} diff --git a/src/jsonrpc.js b/src/jsonrpc.ts similarity index 58% rename from src/jsonrpc.js rename to src/jsonrpc.ts index 7367647..4186849 100644 --- a/src/jsonrpc.js +++ b/src/jsonrpc.ts @@ -1,13 +1,15 @@ /** * JSON-RPC implementation for Tryton server communication * Based on the Python implementation from sabatron-tryton-rpc-client + * TypeScript version - Node.js only */ -const https = require("https"); -const http = require("http"); -const zlib = require("zlib"); -const { URL } = require("url"); -const { TrytonCache } = require("./cache"); +import https from "https"; +import http from "http"; +import zlib from "zlib"; +import { URL } from "url"; +import { TrytonCache } from "./cache"; +import type { ServerProxyOptions, ServerPoolOptions } from "../types"; // Constants const CONNECT_TIMEOUT = 5000; // 5 seconds @@ -16,29 +18,45 @@ const DEFAULT_TIMEOUT = 30000; // 30 seconds /** * Custom error classes */ -class ResponseError extends Error { - constructor(message) { +export class ResponseError extends Error { + constructor(message: string) { super(message); this.name = "ResponseError"; } } -class Fault extends Error { - constructor(faultCode, faultString = "", extra = {}) { +export class Fault extends Error { + public readonly faultCode: string | number; + public readonly faultString: string; + public readonly extra: Record; + + constructor( + faultCode: string | number, + faultString: string = "", + extra: Record = {} + ) { super(faultString); this.name = "Fault"; this.faultCode = faultCode; this.faultString = faultString; + this.extra = extra; Object.assign(this, extra); } - toString() { + override toString(): string { return String(this.faultCode); } } -class ProtocolError extends Error { - constructor(message, errcode = null, errmsg = null) { +export class ProtocolError extends Error { + public readonly errcode: string | number | null; + public readonly errmsg: string | null; + + constructor( + message: string, + errcode: string | number | null = null, + errmsg: string | null = null + ) { super(message); this.name = "ProtocolError"; this.errcode = errcode; @@ -46,17 +64,66 @@ class ProtocolError extends Error { } } +interface TrytonDateTime { + __class__: "datetime"; + year: number; + month: number; + day: number; + hour?: number; + minute?: number; + second?: number; + microsecond?: number; +} + +interface TrytonDate { + __class__: "date"; + year: number; + month: number; + day: number; +} + +interface TrytonTime { + __class__: "time"; + hour?: number; + minute?: number; + second?: number; + microsecond?: number; +} + +interface TrytonTimeDelta { + __class__: "timedelta"; + seconds?: number; +} + +interface TrytonBytes { + __class__: "bytes"; + base64: string; +} + +interface TrytonDecimal { + __class__: "Decimal"; + decimal: string; +} + +type TrytonSpecialType = + | TrytonDateTime + | TrytonDate + | TrytonTime + | TrytonTimeDelta + | TrytonBytes + | TrytonDecimal; + /** * JSON encoder/decoder for Tryton specific types */ -class TrytonJSONEncoder { +export class TrytonJSONEncoder { /** * Serialize JavaScript objects to JSON with Tryton type handling * @param {*} obj - Object to serialize * @returns {string} - JSON string */ - static serialize(obj) { - return JSON.stringify(obj, (key, value) => { + static serialize(obj: any): string { + return JSON.stringify(obj, (key: string, value: any) => { if (value instanceof Date) { return { __class__: "datetime", @@ -67,14 +134,14 @@ class TrytonJSONEncoder { minute: value.getMinutes(), second: value.getSeconds(), microsecond: value.getMilliseconds() * 1000, - }; + } as TrytonDateTime; } - if (value instanceof Buffer) { + if (typeof Buffer !== "undefined" && value instanceof Buffer) { return { __class__: "bytes", base64: value.toString("base64"), - }; + } as TrytonBytes; } // Handle BigInt as Decimal @@ -82,7 +149,7 @@ class TrytonJSONEncoder { return { __class__: "Decimal", decimal: value.toString(), - }; + } as TrytonDecimal; } return value; @@ -94,46 +161,67 @@ class TrytonJSONEncoder { * @param {string} str - JSON string * @returns {*} - Parsed object */ - static deserialize(str) { - return JSON.parse(str, (key, value) => { + static deserialize(str: string): any { + return JSON.parse(str, (key: string, value: any) => { if (value && typeof value === "object" && value.__class__) { - switch (value.__class__) { - case "datetime": + const specialValue = value as TrytonSpecialType; + switch (specialValue.__class__) { + case "datetime": { + const dt = specialValue as TrytonDateTime; return new Date( - value.year, - value.month - 1, - value.day, - value.hour || 0, - value.minute || 0, - value.second || 0, - Math.floor((value.microsecond || 0) / 1000) + dt.year, + dt.month - 1, + dt.day, + dt.hour || 0, + dt.minute || 0, + dt.second || 0, + Math.floor((dt.microsecond || 0) / 1000) ); + } - case "date": - return new Date(value.year, value.month - 1, value.day); + case "date": { + const d = specialValue as TrytonDate; + return new Date(d.year, d.month - 1, d.day); + } - case "time": + case "time": { + const t = specialValue as TrytonTime; const today = new Date(); return new Date( today.getFullYear(), today.getMonth(), today.getDate(), - value.hour || 0, - value.minute || 0, - value.second || 0, - Math.floor((value.microsecond || 0) / 1000) + t.hour || 0, + t.minute || 0, + t.second || 0, + Math.floor((t.microsecond || 0) / 1000) ); + } - case "timedelta": + case "timedelta": { + const td = specialValue as TrytonTimeDelta; // Return seconds as number - return value.seconds || 0; + return td.seconds || 0; + } - case "bytes": - return Buffer.from(value.base64, "base64"); + case "bytes": { + const b = specialValue as TrytonBytes; + if (typeof Buffer !== "undefined") { + return Buffer.from(b.base64, "base64"); + } + // Fallback for browser environment + return new Uint8Array( + atob(b.base64) + .split("") + .map((c) => c.charCodeAt(0)) + ); + } - case "Decimal": + case "Decimal": { + const dec = specialValue as TrytonDecimal; // Convert to number or keep as string for precision - return parseFloat(value.decimal); + return parseFloat(dec.decimal); + } default: return value; @@ -144,11 +232,41 @@ class TrytonJSONEncoder { } } +interface TransportOptions { + fingerprints?: string[] | null | undefined; + caCerts?: string[] | null | undefined; + session?: string | null | undefined; + connectTimeout?: number | undefined; + timeout?: number | undefined; + useHttps?: boolean; +} + +interface JsonRpcRequest { + id: number; + method: string; + params: any[]; +} + +interface JsonRpcResponse { + id: number; + result?: any; + error?: [string | number, string]; + cache?: number; +} + /** * HTTP Transport for JSON-RPC requests */ -class Transport { - constructor(options = {}) { +export class Transport { + private fingerprints: string[] | null; + private caCerts: string[] | null; + private session: string | null; + private connection: http.ClientRequest | null; + private connectTimeout: number; + private timeout: number; + private useHttps: boolean; + + constructor(options: TransportOptions = {}) { this.fingerprints = options.fingerprints || null; this.caCerts = options.caCerts || null; this.session = options.session || null; @@ -166,7 +284,12 @@ class Transport { * @param {boolean} verbose - Enable verbose logging * @returns {Promise} - Response object */ - async request(host, handler, requestData, verbose = false) { + async request( + host: string, + handler: string, + requestData: string, + verbose: boolean = false + ): Promise { // Detect protocol based on port or explicit protocol const hostParts = host.split(":"); const port = hostParts[1] ? parseInt(hostParts[1]) : 80; @@ -183,14 +306,17 @@ class Transport { const url = new URL(`${protocol}://${host}${handler}`); const isHttps = url.protocol === "https:"; - const options = { + const options: https.RequestOptions = { hostname: url.hostname, port: url.port || (isHttps ? 443 : 80), path: url.pathname + url.search, method: "POST", headers: { "Content-Type": "application/json", - "Content-Length": Buffer.byteLength(requestData), + "Content-Length": + typeof Buffer !== "undefined" + ? Buffer.byteLength(requestData) + : new TextEncoder().encode(requestData).length, Connection: "keep-alive", "Accept-Encoding": "gzip, deflate", }, @@ -201,25 +327,44 @@ class Transport { // Add session authentication if (this.session) { - const auth = Buffer.from(this.session).toString("base64"); - options.headers["Authorization"] = `Session ${auth}`; + const auth = + typeof Buffer !== "undefined" + ? Buffer.from(this.session).toString("base64") + : btoa(this.session); + options.headers = { + ...options.headers, + Authorization: `Session ${auth}`, + }; } - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const client = isHttps ? https : http; - const req = client.request(options, (res) => { - let data = Buffer.alloc(0); + const req = client.request(options, (res: http.IncomingMessage) => { + let data = + typeof Buffer !== "undefined" + ? Buffer.alloc(0) + : new Uint8Array(0); - res.on("data", (chunk) => { - data = Buffer.concat([data, chunk]); + res.on("data", (chunk: any) => { + if (typeof Buffer !== "undefined") { + data = Buffer.concat([data as Buffer, chunk]); + } else { + // Browser fallback + const newData = new Uint8Array( + (data as Uint8Array).length + chunk.length + ); + newData.set(data as Uint8Array); + newData.set(chunk, (data as Uint8Array).length); + data = newData; + } }); res.on("end", () => { try { // Handle compression const encoding = res.headers["content-encoding"]; - let responseText; + let responseText: string; if (encoding === "gzip") { responseText = zlib @@ -237,12 +382,13 @@ class Transport { console.log("Response:", responseText); } - const response = - TrytonJSONEncoder.deserialize(responseText); + const response = TrytonJSONEncoder.deserialize( + responseText + ) as JsonRpcResponse; // Add cache header if present const cacheHeader = res.headers["x-tryton-cache"]; - if (cacheHeader) { + if (cacheHeader && typeof cacheHeader === "string") { try { response.cache = parseInt(cacheHeader); } catch (e) { @@ -254,14 +400,16 @@ class Transport { } catch (error) { reject( new ResponseError( - `Failed to parse response: ${error.message}` + `Failed to parse response: ${ + (error as Error).message + }` ) ); } }); }); - req.on("error", (error) => { + req.on("error", (error: any) => { if (error.code === "ECONNRESET" || error.code === "EPIPE") { // Retry once on connection reset reject( @@ -290,7 +438,7 @@ class Transport { /** * Close transport connection */ - close() { + close(): void { if (this.connection) { this.connection.destroy(); this.connection = null; @@ -301,8 +449,24 @@ class Transport { /** * Server proxy for making RPC calls */ -class ServerProxy { - constructor(host, port, database = "", options = {}) { +export class ServerProxy { + private host: string; + private port: number; + private database: string; + private verbose: boolean; + private handler: string; + private hostUrl: string; + private requestId: number; + private cache: TrytonCache | null; + private useHttps: boolean; + private transport: Transport; + + constructor( + host: string, + port: number, + database: string = "", + options: ServerProxyOptions = {} + ) { this.host = host; this.port = port; this.database = database; @@ -310,7 +474,10 @@ class ServerProxy { this.handler = database ? `/${encodeURIComponent(database)}/` : "/"; this.hostUrl = `${host}:${port}`; this.requestId = 0; - this.cache = options.cache || null; + this.cache = + options.cache && !Array.isArray(options.cache) + ? (options.cache as TrytonCache) + : null; this.useHttps = options.useHttps || false; this.transport = new Transport({ @@ -325,11 +492,8 @@ class ServerProxy { /** * Make RPC request with retry logic - * @param {string} methodName - RPC method name - * @param {Array} params - Method parameters - * @returns {Promise<*>} - Method result */ - async request(methodName, params) { + async request(methodName: string, params: any[]): Promise { this.requestId += 1; const id = this.requestId; @@ -337,7 +501,7 @@ class ServerProxy { id: id, method: methodName, params: params, - }); + } as JsonRpcRequest); // Check cache first if (this.cache && this.cache.cached(methodName)) { @@ -348,7 +512,7 @@ class ServerProxy { } } - let lastError = null; + let lastError: Error | null = null; // Retry logic (up to 5 attempts) for (let attempt = 0; attempt < 5; attempt++) { @@ -385,9 +549,9 @@ class ServerProxy { ); } - return response.result; + return response.result as T; } catch (error) { - lastError = error; + lastError = error as Error; // Check if we should retry if (error instanceof ProtocolError && error.errcode === 503) { @@ -400,8 +564,8 @@ class ServerProxy { // For connection errors, try once more if ( attempt === 0 && - (error.code === "ECONNRESET" || - error.code === "EPIPE" || + ((error as any).code === "ECONNRESET" || + (error as any).code === "EPIPE" || error instanceof ProtocolError) ) { this.transport.close(); @@ -419,7 +583,7 @@ class ServerProxy { /** * Close server proxy */ - close() { + close(): void { this.transport.close(); } @@ -427,7 +591,7 @@ class ServerProxy { * Get SSL status * @returns {boolean} - Whether connection uses SSL */ - get ssl() { + get ssl(): boolean { return this.port === 443 || this.hostUrl.startsWith("https"); } @@ -435,7 +599,7 @@ class ServerProxy { * Get full URL * @returns {string} - Full server URL */ - get url() { + get url(): string { const scheme = this.ssl ? "https" : "http"; return `${scheme}://${this.hostUrl}${this.handler}`; } @@ -444,8 +608,23 @@ class ServerProxy { /** * Connection pool for reusing ServerProxy instances */ -class ServerPool { - constructor(host, port, database, options = {}) { +export class ServerPool { + private host: string; + private port: number; + private database: string; + private options: ServerPoolOptions; + private keepMax: number; + private session: string | null; + private pool: ServerProxy[]; + private used: Set; + private cache: TrytonCache | null; + + constructor( + host: string, + port: number, + database: string, + options: ServerPoolOptions = {} + ) { this.host = host; this.port = port; this.database = database; @@ -454,13 +633,16 @@ class ServerPool { this.session = options.session || null; this.pool = []; - this.used = new Set(); + this.used = new Set(); this.cache = null; // Initialize cache if requested if (options.cache) { - this.cache = new TrytonCache(); - this.options.cache = this.cache; + if (Array.isArray(options.cache)) { + this.cache = new TrytonCache(); + } else { + this.cache = options.cache as TrytonCache; + } } } @@ -468,15 +650,15 @@ class ServerPool { * Get connection from pool or create new one * @returns {ServerProxy} - Server proxy instance */ - getConnection() { - let conn; + getConnection(): ServerProxy { + let conn: ServerProxy; if (this.pool.length > 0) { - conn = this.pool.pop(); + conn = this.pool.pop()!; } else { conn = new ServerProxy(this.host, this.port, this.database, { ...this.options, - cache: this.cache, + cache: this.cache as any, }); } @@ -488,14 +670,16 @@ class ServerPool { * Return connection to pool * @param {ServerProxy} conn - Connection to return */ - putConnection(conn) { + putConnection(conn: ServerProxy): void { this.used.delete(conn); this.pool.push(conn); // Remove excess connections while (this.pool.length > this.keepMax) { const oldConn = this.pool.shift(); - oldConn.close(); + if (oldConn) { + oldConn.close(); + } } } @@ -504,7 +688,9 @@ class ServerPool { * @param {Function} callback - Async function to execute * @returns {Promise<*>} - Callback result */ - async withConnection(callback) { + async withConnection( + callback: (conn: ServerProxy) => Promise + ): Promise { const conn = this.getConnection(); try { return await callback(conn); @@ -516,7 +702,7 @@ class ServerPool { /** * Close all connections in pool */ - close() { + close(): void { // Close all pooled connections for (const conn of this.pool) { conn.close(); @@ -535,7 +721,7 @@ class ServerPool { * Clear cache * @param {string} [prefix] - Optional prefix to clear */ - clearCache(prefix = null) { + clearCache(prefix?: string): void { if (this.cache) { this.cache.clear(prefix); } @@ -545,10 +731,10 @@ class ServerPool { * Get SSL status from any connection * @returns {boolean|null} - SSL status or null if no connections */ - get ssl() { + get ssl(): boolean | null { const allConns = [...this.pool, ...this.used]; if (allConns.length > 0) { - return allConns[0].ssl; + return allConns[0]?.ssl || null; } return null; } @@ -557,21 +743,11 @@ class ServerPool { * Get URL from any connection * @returns {string|null} - URL or null if no connections */ - get url() { + get url(): string | null { const allConns = [...this.pool, ...this.used]; if (allConns.length > 0) { - return allConns[0].url; + return allConns[0]?.url || null; } return null; } } - -module.exports = { - ResponseError, - Fault, - ProtocolError, - TrytonJSONEncoder, - Transport, - ServerProxy, - ServerPool, -}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7bfe999 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./", + "removeComments": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "moduleResolution": "node", + "types": ["node"] + }, + "include": [ + "*.ts", + "examples/**/*.ts", + "src/**/*.ts" + ], + "exclude": [ + "node_modules", + "dist", + "**/*.test.ts", + "**/*.spec.ts" + ] +} diff --git a/types.ts b/types.ts new file mode 100644 index 0000000..6069447 --- /dev/null +++ b/types.ts @@ -0,0 +1,284 @@ +/** + * TypeScript type definitions for Tryton RPC Client + */ + +// Forward declarations +declare class TrytonCache { + cached(prefix: string): boolean; + get(prefix: string, key: string): any; + set(prefix: string, key: string, expire: number | Date, value: any): void; + clear(prefix?: string): void; +} + +// ===== CONFIGURATION TYPES ===== + +export interface TrytonClientConfig { + hostname: string; + database: string; + username: string; + password: string; + port?: number; + language?: string; + options?: TrytonClientOptions; +} + +export interface TrytonClientOptions { + verbose?: boolean; + connectTimeout?: number; + timeout?: number; + keepMax?: number; + cache?: boolean | any[]; + useHttps?: boolean; +} + +// ===== SERVER PROXY TYPES ===== + +export interface ServerProxyOptions { + verbose?: boolean; + connectTimeout?: number; + timeout?: number; + useHttps?: boolean; + fingerprints?: string[] | null; + caCerts?: string[] | null; + session?: string | null; + cache?: TrytonCache | any[] | null; +} +export interface ServerPoolOptions extends ServerProxyOptions { + session?: string; + cache?: any[] | null; + keepMax?: number; +} + +// ===== TRYTON DATA TYPES ===== + +export interface TrytonRecord { + id: number; + [key: string]: any; +} + +export interface TrytonUser extends TrytonRecord { + id: number; + name: string; + login: string; + language: string; + company: number; + email?: string; + active?: boolean; +} + +export interface TrytonCompany extends TrytonRecord { + id: number; + name: string; + code?: string; + currency?: number; +} + +export interface TrytonParty extends TrytonRecord { + id: number; + name: string; + code?: string; + active?: boolean; + categories?: number[]; + addresses?: number[]; +} + +// ===== SEARCH AND DOMAIN TYPES ===== + +export type DomainOperator = + | "=" + | "!=" + | "<" + | "<=" + | ">" + | ">=" + | "like" + | "ilike" + | "not like" + | "not ilike" + | "in" + | "not in" + | "child_of" + | "parent_of"; + +export type DomainClause = [string, DomainOperator, any]; +export type DomainLogicalOperator = "AND" | "OR" | "NOT"; +export type SearchDomain = ( + | DomainClause + | DomainLogicalOperator + | SearchDomain +)[]; + +// ===== RPC METHOD CALL TYPES ===== + +export interface TrytonMethodCall { + method: string; + args: any[]; +} + +export interface TrytonBatchCall extends TrytonMethodCall { + id?: string | number; +} + +// ===== AUTHENTICATION TYPES ===== + +export interface LoginParameters { + password: string; + [key: string]: any; +} + +export interface LoginResult extends Array { + 0: number; // user id + 1: string; // session token + [key: number]: any; +} + +export interface UserPreferences { + user: number; + language: string; + timezone?: string; + company?: number; + [key: string]: any; +} + +// ===== DATABASE TYPES ===== + +export interface DatabaseInfo { + name: string; + version?: string; + [key: string]: any; +} + +// ===== ERROR TYPES ===== + +export class TrytonError extends Error { + public readonly code?: string | number | undefined; + public readonly type?: string | undefined; + + constructor( + message: string, + code?: string | number | undefined, + type?: string | undefined + ) { + super(message); + this.name = "TrytonError"; + this.code = code; + this.type = type; + Object.setPrototypeOf(this, TrytonError.prototype); + } +} + +export interface RpcError { + message: string; + code?: string | number; + type?: string; + args?: any[]; +} + +// ===== CONTEXT TYPES ===== + +export interface TrytonContext { + language?: string; + user?: number; + company?: number; + date?: string; + timezone?: string; + groups?: number[]; + [key: string]: any; +} + +// ===== CRUD OPERATION TYPES ===== + +export interface CreateOptions { + context?: TrytonContext; +} + +export interface ReadOptions { + context?: TrytonContext; +} + +export interface WriteOptions { + context?: TrytonContext; +} + +export interface DeleteOptions { + context?: TrytonContext; +} + +export interface SearchOptions { + offset?: number; + limit?: number | null; + order?: string[] | null; + context?: TrytonContext; +} + +export interface SearchReadOptions extends SearchOptions { + fields: string[]; +} + +// ===== CLIENT STATE TYPES ===== + +export interface ClientConfig { + hostname: string; + database: string; + username: string; + port: number; + language: string; + isConnected: boolean; + ssl: boolean | null; + url: string | null; +} + +// ===== UTILITY TYPES ===== + +export type Awaitable = T | Promise; + +export interface CacheEntry { + key: string; + value: any; + timestamp: number; +} + +// ===== GENERIC MODEL TYPES ===== + +export type ModelName = string; +export type FieldName = string; +export type RecordId = number; +export type RecordIds = number[]; + +// Helper type for typed model operations +export interface TypedModelOperations { + read( + ids: RecordIds, + fields: FieldName[], + context?: TrytonContext + ): Promise; + create( + records: Partial>[], + context?: TrytonContext + ): Promise; + write( + ids: RecordIds, + values: Partial>, + context?: TrytonContext + ): Promise; + delete(ids: RecordIds, context?: TrytonContext): Promise; + search( + domain: SearchDomain, + offset?: number, + limit?: number, + order?: string[], + context?: TrytonContext + ): Promise; + searchRead( + domain: SearchDomain, + fields: FieldName[], + offset?: number, + limit?: number, + order?: string[], + context?: TrytonContext + ): Promise; + searchCount(domain: SearchDomain, context?: TrytonContext): Promise; +} + +// ===== EXPORT ALL TYPES ===== +// Note: All types are already exported above individually