First commit: Tryton client using TypeScript
This commit is contained in:
28
.gitignore
vendored
Normal file
28
.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# Node.js
|
||||
node_modules/
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
|
||||
# Logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# IDE/Editor
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
|
||||
# Env files
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# Others
|
||||
*.log
|
||||
31
.npmignore
Normal file
31
.npmignore
Normal file
@@ -0,0 +1,31 @@
|
||||
# Archivos de desarrollo
|
||||
*.ts
|
||||
tsconfig.json
|
||||
.vscode
|
||||
.idea
|
||||
.DS_Store
|
||||
|
||||
# Archivos de Node.js
|
||||
node_modules
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Tests
|
||||
*.test.ts
|
||||
*.spec.ts
|
||||
test/
|
||||
tests/
|
||||
__tests__/
|
||||
coverage/
|
||||
|
||||
# Build temporales
|
||||
*.tsbuildinfo
|
||||
.cache
|
||||
|
||||
# Otros
|
||||
.git
|
||||
.gitignore
|
||||
.npmignore
|
||||
*.md.backup
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
390
README.md
Normal file
390
README.md
Normal file
@@ -0,0 +1,390 @@
|
||||
# Tryton RPC Client para Node.js
|
||||
|
||||
Cliente TypeScript completo para conectar con servidores Tryton ERP a través de JSON-RPC.
|
||||
|
||||
## 🚀 Características
|
||||
|
||||
- ✅ **100% TypeScript** - Tipado completo y seguro
|
||||
- ✅ **Compatible con Node.js** - Versión 14 o superior
|
||||
- ✅ **Operaciones CRUD** - Create, Read, Update, Delete
|
||||
- ✅ **Búsquedas avanzadas** - Search, SearchRead, SearchCount
|
||||
- ✅ **Cache integrado** - Sistema LRU cache para optimización
|
||||
- ✅ **Pool de conexiones** - Manejo eficiente de múltiples conexiones
|
||||
- ✅ **Soporte HTTPS/HTTP** - Conexiones seguras
|
||||
- ✅ **Operaciones tipadas** - Factory para modelos con tipos específicos
|
||||
|
||||
## 📦 Instalación
|
||||
|
||||
### Opción 1: Instalación local (sin npm)
|
||||
|
||||
Simplemente copia la carpeta completa a tu proyecto y construye:
|
||||
|
||||
```bash
|
||||
cd trytonClientNode
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Opción 2: Usar directamente en otro proyecto
|
||||
|
||||
Desde la carpeta de tu proyecto:
|
||||
|
||||
```bash
|
||||
npm install /ruta/a/trytonClientNode
|
||||
```
|
||||
|
||||
## 🔧 Uso
|
||||
|
||||
### Conexión básica
|
||||
|
||||
```typescript
|
||||
import { TrytonClient } from '@tryton/client-node';
|
||||
|
||||
// Crear cliente
|
||||
const client = new TrytonClient({
|
||||
hostname: 'localhost',
|
||||
port: 8000,
|
||||
database: 'tryton_db',
|
||||
username: 'admin',
|
||||
password: 'admin',
|
||||
language: 'es'
|
||||
});
|
||||
|
||||
// Conectar
|
||||
await client.connect();
|
||||
```
|
||||
|
||||
### Operaciones CRUD
|
||||
|
||||
#### Leer registros
|
||||
|
||||
```typescript
|
||||
// Leer registros específicos
|
||||
const parties = await client.read(
|
||||
'party.party',
|
||||
[1, 2, 3],
|
||||
['name', 'code', 'email']
|
||||
);
|
||||
|
||||
console.log(parties);
|
||||
// [{ id: 1, name: 'Company A', code: 'C001', email: 'a@example.com' }, ...]
|
||||
```
|
||||
|
||||
#### Crear registros
|
||||
|
||||
```typescript
|
||||
// Crear nuevos registros
|
||||
const ids = await client.create('party.party', [
|
||||
{ name: 'New Company', code: 'NC001' },
|
||||
{ name: 'Another Company', code: 'AC001' }
|
||||
]);
|
||||
|
||||
console.log(ids); // [4, 5]
|
||||
```
|
||||
|
||||
#### Actualizar registros
|
||||
|
||||
```typescript
|
||||
// Actualizar registros existentes
|
||||
await client.write(
|
||||
'party.party',
|
||||
[1, 2],
|
||||
{ active: false }
|
||||
);
|
||||
```
|
||||
|
||||
#### Eliminar registros
|
||||
|
||||
```typescript
|
||||
// Eliminar registros
|
||||
await client.delete('party.party', [4, 5]);
|
||||
```
|
||||
|
||||
### Búsquedas
|
||||
|
||||
#### Search - Buscar IDs
|
||||
|
||||
```typescript
|
||||
// Buscar IDs que cumplan criterios
|
||||
const ids = await client.search(
|
||||
'party.party',
|
||||
[['name', 'like', '%Company%']],
|
||||
0, // offset
|
||||
10, // limit
|
||||
['name'] // order
|
||||
);
|
||||
|
||||
console.log(ids); // [1, 2, 3]
|
||||
```
|
||||
|
||||
#### SearchRead - Buscar y leer en una sola operación
|
||||
|
||||
```typescript
|
||||
// Buscar y leer directamente
|
||||
const parties = await client.searchRead(
|
||||
'party.party',
|
||||
[['active', '=', true]],
|
||||
['name', 'code', 'email'],
|
||||
0,
|
||||
100
|
||||
);
|
||||
```
|
||||
|
||||
#### SearchCount - Contar registros
|
||||
|
||||
```typescript
|
||||
// Contar registros que cumplen criterios
|
||||
const count = await client.searchCount(
|
||||
'party.party',
|
||||
[['active', '=', true]]
|
||||
);
|
||||
|
||||
console.log(count); // 150
|
||||
```
|
||||
|
||||
### Operaciones tipadas (Type-safe)
|
||||
|
||||
```typescript
|
||||
// Definir interfaz del modelo
|
||||
interface Party {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
email?: string;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
// Usar operaciones tipadas
|
||||
const partyModel = client.model<Party>('party.party');
|
||||
|
||||
// Todas las operaciones están tipadas
|
||||
const parties = await partyModel.searchRead(
|
||||
[['active', '=', true]],
|
||||
['name', 'code', 'email']
|
||||
);
|
||||
|
||||
// TypeScript sabe que 'parties' es Party[]
|
||||
parties.forEach(party => {
|
||||
console.log(party.name); // ✅ Autocompletado
|
||||
});
|
||||
```
|
||||
|
||||
### Métodos de utilidad
|
||||
|
||||
```typescript
|
||||
// Obtener información del usuario actual
|
||||
const user = await client.getCurrentUser();
|
||||
console.log(user.name, user.login);
|
||||
|
||||
// Obtener preferencias del usuario
|
||||
const prefs = await client.getUserPreferences();
|
||||
console.log(prefs.language);
|
||||
|
||||
// Obtener versión del servidor
|
||||
const version = await client.getVersion();
|
||||
console.log(version);
|
||||
|
||||
// Listar bases de datos disponibles
|
||||
const databases = await client.listDatabases();
|
||||
console.log(databases);
|
||||
```
|
||||
|
||||
### Llamadas RPC personalizadas
|
||||
|
||||
```typescript
|
||||
// Llamar a cualquier método RPC
|
||||
const result = await client.call('model.party.party.custom_method', [
|
||||
arg1,
|
||||
arg2,
|
||||
{ context: 'value' }
|
||||
]);
|
||||
```
|
||||
|
||||
### Manejo de cache
|
||||
|
||||
```typescript
|
||||
// Limpiar cache completo
|
||||
client.clearCache();
|
||||
|
||||
// Limpiar cache con prefijo específico
|
||||
client.clearCache('model.party.party');
|
||||
```
|
||||
|
||||
## 📋 API Principal
|
||||
|
||||
### Constructor
|
||||
|
||||
```typescript
|
||||
new TrytonClient(config: TrytonClientConfig)
|
||||
```
|
||||
|
||||
**TrytonClientConfig:**
|
||||
- `hostname` - Host del servidor (con o sin `http://`/`https://`)
|
||||
- `database` - Nombre de la base de datos
|
||||
- `username` - Usuario de Tryton
|
||||
- `password` - Contraseña
|
||||
- `port` - Puerto del servidor (default: 8000)
|
||||
- `language` - Idioma (default: 'en')
|
||||
- `options` - Opciones adicionales (cache, timeouts, etc.)
|
||||
|
||||
### Métodos principales
|
||||
|
||||
| Método | Descripción |
|
||||
|--------|-------------|
|
||||
| `connect()` | Conecta y autentica con el servidor |
|
||||
| `read(model, ids, fields, context?)` | Lee registros específicos |
|
||||
| `create(model, records, context?)` | Crea nuevos registros |
|
||||
| `write(model, ids, values, context?)` | Actualiza registros |
|
||||
| `delete(model, ids, context?)` | Elimina registros |
|
||||
| `search(model, domain, offset?, limit?, order?, context?)` | Busca IDs |
|
||||
| `searchRead(model, domain, fields, offset?, limit?, order?, context?)` | Busca y lee |
|
||||
| `searchCount(model, domain, context?)` | Cuenta registros |
|
||||
| `call(method, args?)` | Llamada RPC genérica |
|
||||
| `model(modelName)` | Factory de operaciones tipadas |
|
||||
|
||||
### Propiedades
|
||||
|
||||
| Propiedad | Descripción |
|
||||
|-----------|-------------|
|
||||
| `isConnected` | Indica si está conectado |
|
||||
| `ssl` | Indica si usa HTTPS |
|
||||
| `url` | URL de conexión |
|
||||
|
||||
## 🔒 Dominios de búsqueda
|
||||
|
||||
Los dominios en Tryton siguen la sintaxis de Python:
|
||||
|
||||
```typescript
|
||||
// Operadores básicos
|
||||
[['field', '=', value]] // Igual
|
||||
[['field', '!=', value]] // Diferente
|
||||
[['field', '>', value]] // Mayor que
|
||||
[['field', '<', value]] // Menor que
|
||||
[['field', '>=', value]] // Mayor o igual
|
||||
[['field', '<=', value]] // Menor o igual
|
||||
[['field', 'like', '%pattern%']] // Like (SQL)
|
||||
[['field', 'in', [1, 2, 3]]] // En lista
|
||||
|
||||
// Operadores lógicos
|
||||
['OR', [['f1', '=', 'a']], [['f2', '=', 'b']]] // OR
|
||||
['AND', [['f1', '=', 'a']], [['f2', '=', 'b']]] // AND (default)
|
||||
|
||||
// Ejemplo complejo
|
||||
[
|
||||
'OR',
|
||||
['AND', [['name', 'like', '%Inc%']], [['active', '=', true]]],
|
||||
[['code', 'in', ['A001', 'B002']]]
|
||||
]
|
||||
```
|
||||
|
||||
## 🛠️ Scripts de desarrollo
|
||||
|
||||
```bash
|
||||
# Compilar el código TypeScript
|
||||
npm run build
|
||||
|
||||
# Compilar en modo watch
|
||||
npm run build:watch
|
||||
|
||||
# Limpiar archivos compilados
|
||||
npm run clean
|
||||
```
|
||||
|
||||
## 📁 Estructura de archivos
|
||||
|
||||
```
|
||||
trytonClientNode/
|
||||
├── cache.ts # Sistema de cache LRU
|
||||
├── client.ts # Cliente principal
|
||||
├── index.ts # Exports públicos
|
||||
├── jsonrpc.ts # Implementación JSON-RPC
|
||||
├── types.ts # Definiciones de tipos
|
||||
├── package.json # Configuración del paquete
|
||||
├── tsconfig.json # Configuración TypeScript
|
||||
└── README.md # Este archivo
|
||||
```
|
||||
|
||||
## 🔍 Ejemplo completo
|
||||
|
||||
```typescript
|
||||
import { TrytonClient } from '@tryton/client-node';
|
||||
|
||||
async function main() {
|
||||
// 1. Crear y conectar
|
||||
const client = new TrytonClient({
|
||||
hostname: 'http://localhost',
|
||||
port: 8000,
|
||||
database: 'tryton',
|
||||
username: 'admin',
|
||||
password: 'admin'
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
console.log('✅ Conectado');
|
||||
|
||||
// 2. Buscar empresas activas
|
||||
const activeParties = await client.searchRead(
|
||||
'party.party',
|
||||
[['active', '=', true]],
|
||||
['name', 'code'],
|
||||
0,
|
||||
10
|
||||
);
|
||||
|
||||
console.log('Empresas activas:', activeParties);
|
||||
|
||||
// 3. Crear una nueva empresa
|
||||
const [newId] = await client.create('party.party', [{
|
||||
name: 'Mi Nueva Empresa',
|
||||
code: 'MNE001'
|
||||
}]);
|
||||
|
||||
console.log('✅ Empresa creada con ID:', newId);
|
||||
|
||||
// 4. Leer la empresa creada
|
||||
const [newParty] = await client.read(
|
||||
'party.party',
|
||||
[newId],
|
||||
['name', 'code', 'create_date']
|
||||
);
|
||||
|
||||
console.log('Empresa creada:', newParty);
|
||||
|
||||
// 5. Actualizar la empresa
|
||||
await client.write(
|
||||
'party.party',
|
||||
[newId],
|
||||
{ name: 'Empresa Actualizada' }
|
||||
);
|
||||
|
||||
console.log('✅ Empresa actualizada');
|
||||
|
||||
// 6. Verificar actualización
|
||||
const [updated] = await client.read(
|
||||
'party.party',
|
||||
[newId],
|
||||
['name']
|
||||
);
|
||||
|
||||
console.log('Nuevo nombre:', updated.name);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
```
|
||||
|
||||
## ⚠️ Requisitos
|
||||
|
||||
- Node.js >= 14.0.0
|
||||
- Servidor Tryton activo y accesible
|
||||
|
||||
## 📝 Licencia
|
||||
|
||||
MIT
|
||||
|
||||
## 🤝 Contribuciones
|
||||
|
||||
Este es un paquete independiente. Puedes modificarlo y adaptarlo según tus necesidades.
|
||||
|
||||
## 📧 Soporte
|
||||
|
||||
Para problemas o preguntas, revisa la documentación oficial de Tryton en https://www.tryton.org/
|
||||
297
cache.ts
Normal file
297
cache.ts
Normal file
@@ -0,0 +1,297 @@
|
||||
/**
|
||||
* Cache system similar to Python's CacheDict from Tryton
|
||||
* Implements LRU (Least Recently Used) cache using JavaScript Map
|
||||
* TypeScript version
|
||||
*/
|
||||
|
||||
export type CacheFactory<T> = () => T;
|
||||
|
||||
export class CacheDict<K = any, V = any> extends Map<K, V> {
|
||||
private cacheLen: number;
|
||||
private defaultFactory: CacheFactory<V> | null;
|
||||
|
||||
/**
|
||||
* Create a new CacheDict
|
||||
*/
|
||||
constructor(
|
||||
cacheLen: number = 10,
|
||||
defaultFactory: CacheFactory<V> | null = null
|
||||
) {
|
||||
super();
|
||||
this.cacheLen = cacheLen;
|
||||
this.defaultFactory = defaultFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a key-value pair and maintain LRU order
|
||||
*/
|
||||
override set(key: K, value: V): this {
|
||||
// If key exists, delete it first to move to end
|
||||
if (this.has(key)) {
|
||||
this.delete(key);
|
||||
}
|
||||
|
||||
super.set(key, value);
|
||||
|
||||
// Remove oldest entries if cache is full
|
||||
while (this.size > this.cacheLen) {
|
||||
const firstKey = this.keys().next().value;
|
||||
if (firstKey !== undefined) {
|
||||
this.delete(firstKey);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value and move it to end (most recently used)
|
||||
*/
|
||||
override get(key: K): V | undefined {
|
||||
if (this.has(key)) {
|
||||
const value = super.get(key)!;
|
||||
// Move to end by re-setting
|
||||
this.delete(key);
|
||||
super.set(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Handle missing key with default factory
|
||||
if (this.defaultFactory) {
|
||||
const value = this.defaultFactory();
|
||||
this.set(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override has() to update LRU order on access
|
||||
*/
|
||||
override has(key: K): boolean {
|
||||
const exists = super.has(key);
|
||||
if (exists) {
|
||||
// Move to end on access
|
||||
const value = super.get(key)!;
|
||||
this.delete(key);
|
||||
super.set(key, value);
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current cache size
|
||||
*/
|
||||
get length(): number {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all items from cache
|
||||
*/
|
||||
override clear(): void {
|
||||
super.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert cache to array for debugging
|
||||
*/
|
||||
toArray(): Array<[K, V]> {
|
||||
return Array.from(this.entries());
|
||||
}
|
||||
}
|
||||
|
||||
export interface CacheEntry<T = any> {
|
||||
expire: Date;
|
||||
value: T;
|
||||
}
|
||||
|
||||
export interface CacheStats {
|
||||
totalPrefixes: number;
|
||||
totalEntries: number;
|
||||
prefixes: Record<string, number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advanced cache for Tryton RPC with expiration support
|
||||
*/
|
||||
export class TrytonCache {
|
||||
private store: CacheDict<string, CacheDict<string, CacheEntry>>;
|
||||
private cacheLen: number;
|
||||
|
||||
constructor(cacheLen: number = 1024) {
|
||||
this.cacheLen = cacheLen;
|
||||
this.store = new CacheDict<string, CacheDict<string, CacheEntry>>(
|
||||
cacheLen,
|
||||
() => new CacheDict<string, CacheEntry>(cacheLen)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a prefix is cached
|
||||
*/
|
||||
cached(prefix: string): boolean {
|
||||
return this.store.has(prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cache entry with expiration
|
||||
*/
|
||||
set(prefix: string, key: string, expire: number | Date, value: any): void {
|
||||
let expiration: Date;
|
||||
|
||||
if (typeof expire === "number") {
|
||||
// Assume seconds, convert to Date
|
||||
expiration = new Date(Date.now() + expire * 1000);
|
||||
} else if (expire instanceof Date) {
|
||||
expiration = expire;
|
||||
} else {
|
||||
throw new Error("Invalid expiration type");
|
||||
}
|
||||
|
||||
// Deep copy value to avoid mutations
|
||||
const cachedValue = this._deepCopy(value);
|
||||
|
||||
this.store.get(prefix)!.set(key, {
|
||||
expire: expiration,
|
||||
value: cachedValue,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached value if not expired
|
||||
*/
|
||||
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)!;
|
||||
if (!prefixCache.has(key)) {
|
||||
throw new Error("Key not found");
|
||||
}
|
||||
|
||||
const entry = prefixCache.get(key)!;
|
||||
|
||||
if (entry.expire < now) {
|
||||
prefixCache.delete(key);
|
||||
throw new Error("Key expired");
|
||||
}
|
||||
|
||||
console.log(`(cached) ${prefix} ${key}`);
|
||||
return this._deepCopy(entry.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache for a specific prefix or all
|
||||
*/
|
||||
clear(prefix?: string): void {
|
||||
if (prefix) {
|
||||
if (this.store.has(prefix)) {
|
||||
this.store.get(prefix)!.clear();
|
||||
}
|
||||
} else {
|
||||
this.store.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep copy objects to prevent mutations
|
||||
*/
|
||||
private _deepCopy<T>(obj: T): T {
|
||||
if (obj === null || typeof obj !== "object") {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (obj instanceof Date) {
|
||||
return new Date(obj.getTime()) as T;
|
||||
}
|
||||
|
||||
if (obj instanceof Array) {
|
||||
return obj.map((item) => this._deepCopy(item)) as T;
|
||||
}
|
||||
|
||||
// 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: any = {};
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
copy[key] = this._deepCopy((obj as any)[key]);
|
||||
}
|
||||
}
|
||||
return copy as T;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache statistics
|
||||
*/
|
||||
getStats(): CacheStats {
|
||||
const stats: CacheStats = {
|
||||
totalPrefixes: this.store.size,
|
||||
totalEntries: 0,
|
||||
prefixes: {},
|
||||
};
|
||||
|
||||
for (const [prefix, prefixCache] of this.store.entries()) {
|
||||
const count = prefixCache.size;
|
||||
stats.totalEntries += count;
|
||||
stats.prefixes[prefix] = count;
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
577
client.ts
Normal file
577
client.ts
Normal file
@@ -0,0 +1,577 @@
|
||||
/**
|
||||
* Tryton RPC Client for Node.js
|
||||
* TypeScript implementation of sabatron-tryton-rpc-client
|
||||
*/
|
||||
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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://", "");
|
||||
this.useHttps = true;
|
||||
} else if (hostname.startsWith("http://")) {
|
||||
this.hostname = hostname.replace("http://", "");
|
||||
this.useHttps = false;
|
||||
} else {
|
||||
this.hostname = hostname;
|
||||
this.useHttps = port === 443 || port === 8443;
|
||||
}
|
||||
|
||||
this.database = database;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.port = port;
|
||||
this.language = language;
|
||||
this.options = options;
|
||||
this.connection = null;
|
||||
this.session = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative constructor for backward compatibility
|
||||
*/
|
||||
static create(
|
||||
hostname: string,
|
||||
database: string,
|
||||
username: string,
|
||||
password: string,
|
||||
port: number = 8000,
|
||||
language: string = "en"
|
||||
): TrytonClient {
|
||||
return new TrytonClient({
|
||||
hostname,
|
||||
database,
|
||||
username,
|
||||
password,
|
||||
port,
|
||||
language,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to Tryton server and authenticate
|
||||
* @returns {Promise<boolean>} - True if connection successful
|
||||
* @throws {Error} - If connection or authentication fails
|
||||
*/
|
||||
async connect(): Promise<boolean> {
|
||||
try {
|
||||
const proxy = new ServerProxy(
|
||||
this.hostname,
|
||||
this.port,
|
||||
this.database,
|
||||
{
|
||||
verbose: this.options.verbose || false,
|
||||
connectTimeout:
|
||||
this.options.connectTimeout || CONNECT_TIMEOUT,
|
||||
timeout: this.options.timeout || DEFAULT_TIMEOUT,
|
||||
useHttps: this.useHttps,
|
||||
}
|
||||
);
|
||||
|
||||
// Perform login
|
||||
const parameters = {
|
||||
password: this.password,
|
||||
};
|
||||
|
||||
const result = await proxy.request<LoginResult>("common.db.login", [
|
||||
this.username,
|
||||
parameters,
|
||||
this.language,
|
||||
]);
|
||||
|
||||
proxy.close();
|
||||
|
||||
this.session = [this.username, ...result].join(":");
|
||||
|
||||
// Create connection pool with session
|
||||
this.connection = new ServerPool(
|
||||
this.hostname,
|
||||
this.port,
|
||||
this.database,
|
||||
{
|
||||
session: this.session,
|
||||
cache: this.options.cache !== false ? [] : null, // Enable cache by default
|
||||
verbose: this.options.verbose || false,
|
||||
connectTimeout:
|
||||
this.options.connectTimeout || CONNECT_TIMEOUT,
|
||||
timeout: this.options.timeout || DEFAULT_TIMEOUT,
|
||||
keepMax: this.options.keepMax || 4,
|
||||
useHttps: this.useHttps,
|
||||
}
|
||||
);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw new Error(`Connection failed: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call RPC method on server
|
||||
* @param {string} methodName - RPC method name (e.g., 'model.party.party.read')
|
||||
* @param {Array} args - Method arguments
|
||||
* @returns {Promise<*>} - Method result
|
||||
* @throws {Error} - If not connected or method call fails
|
||||
*/
|
||||
async call<T = any>(methodName: string, args: any[] = []): Promise<T> {
|
||||
if (!this.connection) {
|
||||
throw new Error("Not connected. Call connect() first.");
|
||||
}
|
||||
|
||||
return this.connection.withConnection(async (conn) => {
|
||||
return conn.request<T>(methodName, args);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Call multiple RPC methods in sequence
|
||||
* @param {Array<{method: string, args: Array}>} calls - Array of method calls
|
||||
* @returns {Promise<Array>} - Array of results
|
||||
*/
|
||||
async callMultiple(calls: TrytonMethodCall[]): Promise<any[]> {
|
||||
const results: any[] = [];
|
||||
for (const call of calls) {
|
||||
const result = await this.call(call.method, call.args);
|
||||
results.push(result);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call multiple RPC methods in parallel
|
||||
* @param {Array<{method: string, args: Array}>} calls - Array of method calls
|
||||
* @returns {Promise<Array>} - Array of results
|
||||
*/
|
||||
async callParallel(calls: TrytonMethodCall[]): Promise<any[]> {
|
||||
const promises = calls.map((call) => this.call(call.method, call.args));
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to read records from a model
|
||||
* @param {string} model - Model name (e.g., 'party.party')
|
||||
* @param {Array<number>} ids - Record IDs to read
|
||||
* @param {Array<string>} fields - Fields to read
|
||||
* @param {Object} [context={}] - Context dictionary
|
||||
* @returns {Promise<Array>} - Array of records
|
||||
*/
|
||||
async read<T extends TrytonRecord = TrytonRecord>(
|
||||
model: ModelName,
|
||||
ids: RecordIds,
|
||||
fields: FieldName[],
|
||||
context: TrytonContext = {}
|
||||
): Promise<T[]> {
|
||||
return this.call<T[]>(`model.${model}.read`, [ids, fields, context]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create records in a model
|
||||
* @param {string} model - Model name (e.g., 'party.party')
|
||||
* @param {Array<Object>} records - Records to create
|
||||
* @param {Object} [context={}] - Context dictionary
|
||||
* @returns {Promise<Array<number>>} - Array of created record IDs
|
||||
*/
|
||||
async create(
|
||||
model: ModelName,
|
||||
records: Record<string, any>[],
|
||||
context: TrytonContext = {}
|
||||
): Promise<RecordIds> {
|
||||
return this.call<RecordIds>(`model.${model}.create`, [
|
||||
records,
|
||||
context,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to write/update records in a model
|
||||
* @param {string} model - Model name (e.g., 'party.party')
|
||||
* @param {Array<number>} ids - Record IDs to update
|
||||
* @param {Object} values - Values to update
|
||||
* @param {Object} [context={}] - Context dictionary
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async write(
|
||||
model: ModelName,
|
||||
ids: RecordIds,
|
||||
values: Record<string, any>,
|
||||
context: TrytonContext = {}
|
||||
): Promise<void> {
|
||||
return this.call<void>(`model.${model}.write`, [ids, values, context]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to delete records from a model
|
||||
* @param {string} model - Model name (e.g., 'party.party')
|
||||
* @param {Array<number>} ids - Record IDs to delete
|
||||
* @param {Object} [context={}] - Context dictionary
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async delete(
|
||||
model: ModelName,
|
||||
ids: RecordIds,
|
||||
context: TrytonContext = {}
|
||||
): Promise<void> {
|
||||
return this.call<void>(`model.${model}.delete`, [ids, context]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to search for records
|
||||
* @param {string} model - Model name (e.g., 'party.party')
|
||||
* @param {Array} domain - Search domain
|
||||
* @param {number} [offset=0] - Offset for pagination
|
||||
* @param {number} [limit=null] - Limit for pagination
|
||||
* @param {Array<string>} [order=null] - Order specification
|
||||
* @param {Object} [context={}] - Context dictionary
|
||||
* @returns {Promise<Array<number>>} - Array of record IDs
|
||||
*/
|
||||
async search(
|
||||
model: ModelName,
|
||||
domain: SearchDomain,
|
||||
offset: number = 0,
|
||||
limit: number | null = null,
|
||||
order: string[] | null = null,
|
||||
context: TrytonContext = {}
|
||||
): Promise<RecordIds> {
|
||||
return this.call<RecordIds>(`model.${model}.search`, [
|
||||
domain,
|
||||
offset,
|
||||
limit,
|
||||
order,
|
||||
context,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to search and read records in one call
|
||||
* @param {string} model - Model name (e.g., 'party.party')
|
||||
* @param {Array} domain - Search domain
|
||||
* @param {Array<string>} fields - Fields to read
|
||||
* @param {number} [offset=0] - Offset for pagination
|
||||
* @param {number} [limit=null] - Limit for pagination
|
||||
* @param {Array<string>} [order=null] - Order specification
|
||||
* @param {Object} [context={}] - Context dictionary
|
||||
* @returns {Promise<Array>} - Array of records
|
||||
*/
|
||||
async searchRead<T extends TrytonRecord = TrytonRecord>(
|
||||
model: ModelName,
|
||||
domain: SearchDomain,
|
||||
fields: FieldName[],
|
||||
offset: number = 0,
|
||||
limit: number | null = null,
|
||||
order: string[] | null = null,
|
||||
context: TrytonContext = {}
|
||||
): Promise<T[]> {
|
||||
return this.call<T[]>(`model.${model}.search_read`, [
|
||||
domain,
|
||||
offset,
|
||||
limit,
|
||||
order,
|
||||
fields,
|
||||
context,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to count records
|
||||
* @param {string} model - Model name (e.g., 'party.party')
|
||||
* @param {Array} domain - Search domain
|
||||
* @param {Object} [context={}] - Context dictionary
|
||||
* @returns {Promise<number>} - Number of records
|
||||
*/
|
||||
async searchCount(
|
||||
model: ModelName,
|
||||
domain: SearchDomain,
|
||||
context: TrytonContext = {}
|
||||
): Promise<number> {
|
||||
return this.call<number>(`model.${model}.search_count`, [
|
||||
domain,
|
||||
context,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database information
|
||||
* @returns {Promise<Object>} - Database info
|
||||
*/
|
||||
async getDatabaseInfo(): Promise<any> {
|
||||
return this.call("common.db.get_info", []);
|
||||
}
|
||||
|
||||
/**
|
||||
* List available databases
|
||||
* @returns {Promise<Array<string>>} - Database names
|
||||
*/
|
||||
async listDatabases(): Promise<string[]> {
|
||||
return this.call<string[]>("common.db.list", []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get server version
|
||||
* @returns {Promise<string>} - Server version
|
||||
*/
|
||||
async getVersion(): Promise<string> {
|
||||
return this.call<string>("common.version", []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user preferences
|
||||
*/
|
||||
async getUserPreferences(): Promise<UserPreferences> {
|
||||
return this.call<UserPreferences>("model.res.user.get_preferences", [
|
||||
true,
|
||||
{},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current user information
|
||||
*/
|
||||
async getCurrentUser(): Promise<TrytonUser> {
|
||||
const preferences = await this.getUserPreferences();
|
||||
const users = await this.read<TrytonUser>(
|
||||
"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?: string): void {
|
||||
if (this.connection) {
|
||||
this.connection.clearCache(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection SSL status
|
||||
* @returns {boolean|null} - SSL status or null if not connected
|
||||
*/
|
||||
get ssl(): boolean | null {
|
||||
return this.connection ? this.connection.ssl : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection URL
|
||||
* @returns {string|null} - Connection URL or null if not connected
|
||||
*/
|
||||
get url(): string | null {
|
||||
return this.connection ? this.connection.url : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if client is connected
|
||||
* @returns {boolean} - True if connected
|
||||
*/
|
||||
get isConnected(): boolean {
|
||||
return this.connection !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current session
|
||||
* @returns {string|null} - Session string or null if not connected
|
||||
*/
|
||||
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(): void {
|
||||
if (this.connection) {
|
||||
this.connection.close();
|
||||
this.connection = null;
|
||||
this.session = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new client instance with the same configuration
|
||||
* @returns {TrytonClient} - New client instance
|
||||
*/
|
||||
clone(): TrytonClient {
|
||||
return new TrytonClient({
|
||||
hostname: this.hostname,
|
||||
database: this.database,
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
port: this.port,
|
||||
language: this.language,
|
||||
options: { ...this.options },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get client configuration (without sensitive data)
|
||||
* @returns {Object} - Client configuration
|
||||
*/
|
||||
getConfig(): ClientConfig {
|
||||
return {
|
||||
hostname: this.hostname,
|
||||
database: this.database,
|
||||
username: this.username,
|
||||
port: this.port,
|
||||
language: this.language,
|
||||
isConnected: this.isConnected,
|
||||
ssl: this.ssl,
|
||||
url: this.url,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-safe model operations factory
|
||||
* Creates a typed interface for specific models
|
||||
*/
|
||||
model<T extends TrytonRecord = TrytonRecord>(
|
||||
modelName: ModelName
|
||||
): TypedModelOperations<T> {
|
||||
return {
|
||||
read: (
|
||||
ids: RecordIds,
|
||||
fields: FieldName[],
|
||||
context?: TrytonContext
|
||||
) => this.read<T>(modelName, ids, fields, context),
|
||||
|
||||
create: (
|
||||
records: Partial<Omit<T, "id">>[],
|
||||
context?: TrytonContext
|
||||
) =>
|
||||
this.create(
|
||||
modelName,
|
||||
records as Record<string, any>[],
|
||||
context
|
||||
),
|
||||
|
||||
write: (
|
||||
ids: RecordIds,
|
||||
values: Partial<Omit<T, "id">>,
|
||||
context?: TrytonContext
|
||||
) =>
|
||||
this.write(
|
||||
modelName,
|
||||
ids,
|
||||
values as Record<string, any>,
|
||||
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<T>(
|
||||
modelName,
|
||||
domain,
|
||||
fields,
|
||||
offset,
|
||||
limit,
|
||||
order,
|
||||
context
|
||||
),
|
||||
|
||||
searchCount: (domain: SearchDomain, context?: TrytonContext) =>
|
||||
this.searchCount(modelName, domain, context),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export for CommonJS compatibility
|
||||
export default TrytonClient;
|
||||
140
examples/README.md
Normal file
140
examples/README.md
Normal file
@@ -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/).
|
||||
172
examples/basic-connection.ts
Normal file
172
examples/basic-connection.ts
Normal file
@@ -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 "../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.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 };
|
||||
170
index.ts
Normal file
170
index.ts
Normal file
@@ -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 || {},
|
||||
};
|
||||
}
|
||||
753
jsonrpc.ts
Normal file
753
jsonrpc.ts
Normal file
@@ -0,0 +1,753 @@
|
||||
/**
|
||||
* JSON-RPC implementation for Tryton server communication
|
||||
* Based on the Python implementation from sabatron-tryton-rpc-client
|
||||
* TypeScript version - Node.js only
|
||||
*/
|
||||
|
||||
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
|
||||
const DEFAULT_TIMEOUT = 30000; // 30 seconds
|
||||
|
||||
/**
|
||||
* Custom error classes
|
||||
*/
|
||||
export class ResponseError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "ResponseError";
|
||||
}
|
||||
}
|
||||
|
||||
export class Fault extends Error {
|
||||
public readonly faultCode: string | number;
|
||||
public readonly faultString: string;
|
||||
public readonly extra: Record<string, any>;
|
||||
|
||||
constructor(
|
||||
faultCode: string | number,
|
||||
faultString: string = "",
|
||||
extra: Record<string, any> = {}
|
||||
) {
|
||||
super(faultString);
|
||||
this.name = "Fault";
|
||||
this.faultCode = faultCode;
|
||||
this.faultString = faultString;
|
||||
this.extra = extra;
|
||||
Object.assign(this, extra);
|
||||
}
|
||||
|
||||
override toString(): string {
|
||||
return String(this.faultCode);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
this.errmsg = errmsg;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
export class TrytonJSONEncoder {
|
||||
/**
|
||||
* Serialize JavaScript objects to JSON with Tryton type handling
|
||||
* @param {*} obj - Object to serialize
|
||||
* @returns {string} - JSON string
|
||||
*/
|
||||
static serialize(obj: any): string {
|
||||
return JSON.stringify(obj, (key: string, value: any) => {
|
||||
if (value instanceof Date) {
|
||||
return {
|
||||
__class__: "datetime",
|
||||
year: value.getFullYear(),
|
||||
month: value.getMonth() + 1,
|
||||
day: value.getDate(),
|
||||
hour: value.getHours(),
|
||||
minute: value.getMinutes(),
|
||||
second: value.getSeconds(),
|
||||
microsecond: value.getMilliseconds() * 1000,
|
||||
} as TrytonDateTime;
|
||||
}
|
||||
|
||||
if (typeof Buffer !== "undefined" && value instanceof Buffer) {
|
||||
return {
|
||||
__class__: "bytes",
|
||||
base64: value.toString("base64"),
|
||||
} as TrytonBytes;
|
||||
}
|
||||
|
||||
// Handle BigInt as Decimal
|
||||
if (typeof value === "bigint") {
|
||||
return {
|
||||
__class__: "Decimal",
|
||||
decimal: value.toString(),
|
||||
} as TrytonDecimal;
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize JSON with Tryton type handling
|
||||
* @param {string} str - JSON string
|
||||
* @returns {*} - Parsed object
|
||||
*/
|
||||
static deserialize(str: string): any {
|
||||
return JSON.parse(str, (key: string, value: any) => {
|
||||
if (value && typeof value === "object" && value.__class__) {
|
||||
const specialValue = value as TrytonSpecialType;
|
||||
switch (specialValue.__class__) {
|
||||
case "datetime": {
|
||||
const dt = specialValue as TrytonDateTime;
|
||||
return new Date(
|
||||
dt.year,
|
||||
dt.month - 1,
|
||||
dt.day,
|
||||
dt.hour || 0,
|
||||
dt.minute || 0,
|
||||
dt.second || 0,
|
||||
Math.floor((dt.microsecond || 0) / 1000)
|
||||
);
|
||||
}
|
||||
|
||||
case "date": {
|
||||
const d = specialValue as TrytonDate;
|
||||
return new Date(d.year, d.month - 1, d.day);
|
||||
}
|
||||
|
||||
case "time": {
|
||||
const t = specialValue as TrytonTime;
|
||||
const today = new Date();
|
||||
return new Date(
|
||||
today.getFullYear(),
|
||||
today.getMonth(),
|
||||
today.getDate(),
|
||||
t.hour || 0,
|
||||
t.minute || 0,
|
||||
t.second || 0,
|
||||
Math.floor((t.microsecond || 0) / 1000)
|
||||
);
|
||||
}
|
||||
|
||||
case "timedelta": {
|
||||
const td = specialValue as TrytonTimeDelta;
|
||||
// Return seconds as number
|
||||
return td.seconds || 0;
|
||||
}
|
||||
|
||||
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": {
|
||||
const dec = specialValue as TrytonDecimal;
|
||||
// Convert to number or keep as string for precision
|
||||
return parseFloat(dec.decimal);
|
||||
}
|
||||
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
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;
|
||||
this.connection = null;
|
||||
this.connectTimeout = options.connectTimeout || CONNECT_TIMEOUT;
|
||||
this.timeout = options.timeout || DEFAULT_TIMEOUT;
|
||||
this.useHttps = options.useHttps || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make HTTP request to server
|
||||
* @param {string} host - Server host
|
||||
* @param {string} handler - URL path
|
||||
* @param {string} requestData - JSON request data
|
||||
* @param {boolean} verbose - Enable verbose logging
|
||||
* @returns {Promise<Object>} - Response object
|
||||
*/
|
||||
async request(
|
||||
host: string,
|
||||
handler: string,
|
||||
requestData: string,
|
||||
verbose: boolean = false
|
||||
): Promise<JsonRpcResponse> {
|
||||
// Detect protocol based on port or explicit protocol
|
||||
const hostParts = host.split(":");
|
||||
const port = hostParts[1] ? parseInt(hostParts[1]) : 80;
|
||||
const hostname = hostParts[0];
|
||||
|
||||
// Use HTTPS if explicitly configured, or for standard HTTPS ports
|
||||
const shouldUseHttps =
|
||||
this.useHttps ||
|
||||
port === 443 ||
|
||||
port === 8443 ||
|
||||
host.startsWith("https://");
|
||||
const protocol = shouldUseHttps ? "https" : "http";
|
||||
|
||||
const url = new URL(`${protocol}://${host}${handler}`);
|
||||
const isHttps = url.protocol === "https:";
|
||||
|
||||
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":
|
||||
typeof Buffer !== "undefined"
|
||||
? Buffer.byteLength(requestData)
|
||||
: new TextEncoder().encode(requestData).length,
|
||||
Connection: "keep-alive",
|
||||
"Accept-Encoding": "gzip, deflate",
|
||||
},
|
||||
timeout: this.connectTimeout,
|
||||
// Allow self-signed certificates for testing
|
||||
rejectUnauthorized: false,
|
||||
};
|
||||
|
||||
// Add session authentication
|
||||
if (this.session) {
|
||||
const auth =
|
||||
typeof Buffer !== "undefined"
|
||||
? Buffer.from(this.session).toString("base64")
|
||||
: btoa(this.session);
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
Authorization: `Session ${auth}`,
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise<JsonRpcResponse>((resolve, reject) => {
|
||||
const client = isHttps ? https : http;
|
||||
|
||||
const req = client.request(options, (res: http.IncomingMessage) => {
|
||||
let data =
|
||||
typeof Buffer !== "undefined"
|
||||
? Buffer.alloc(0)
|
||||
: new Uint8Array(0);
|
||||
|
||||
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: string;
|
||||
|
||||
if (encoding === "gzip") {
|
||||
responseText = zlib
|
||||
.gunzipSync(data)
|
||||
.toString("utf-8");
|
||||
} else if (encoding === "deflate") {
|
||||
responseText = zlib
|
||||
.inflateSync(data)
|
||||
.toString("utf-8");
|
||||
} else {
|
||||
responseText = data.toString("utf-8");
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
console.log("Response:", responseText);
|
||||
}
|
||||
|
||||
const response = TrytonJSONEncoder.deserialize(
|
||||
responseText
|
||||
) as JsonRpcResponse;
|
||||
|
||||
// Add cache header if present
|
||||
const cacheHeader = res.headers["x-tryton-cache"];
|
||||
if (cacheHeader && typeof cacheHeader === "string") {
|
||||
try {
|
||||
response.cache = parseInt(cacheHeader);
|
||||
} catch (e) {
|
||||
// Ignore invalid cache header
|
||||
}
|
||||
}
|
||||
|
||||
resolve(response);
|
||||
} catch (error) {
|
||||
reject(
|
||||
new ResponseError(
|
||||
`Failed to parse response: ${
|
||||
(error as Error).message
|
||||
}`
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on("error", (error: any) => {
|
||||
if (error.code === "ECONNRESET" || error.code === "EPIPE") {
|
||||
// Retry once on connection reset
|
||||
reject(
|
||||
new ProtocolError(
|
||||
"Connection reset",
|
||||
error.code,
|
||||
error.message
|
||||
)
|
||||
);
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
req.on("timeout", () => {
|
||||
req.destroy();
|
||||
reject(new Error("Request timeout"));
|
||||
});
|
||||
|
||||
req.setTimeout(this.timeout);
|
||||
req.write(requestData);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Close transport connection
|
||||
*/
|
||||
close(): void {
|
||||
if (this.connection) {
|
||||
this.connection.destroy();
|
||||
this.connection = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Server proxy for making RPC calls
|
||||
*/
|
||||
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;
|
||||
this.verbose = options.verbose || false;
|
||||
this.handler = database ? `/${encodeURIComponent(database)}/` : "/";
|
||||
this.hostUrl = `${host}:${port}`;
|
||||
this.requestId = 0;
|
||||
this.cache =
|
||||
options.cache && !Array.isArray(options.cache)
|
||||
? (options.cache as TrytonCache)
|
||||
: null;
|
||||
this.useHttps = options.useHttps || false;
|
||||
|
||||
this.transport = new Transport({
|
||||
fingerprints: options.fingerprints,
|
||||
caCerts: options.caCerts,
|
||||
session: options.session,
|
||||
connectTimeout: options.connectTimeout,
|
||||
timeout: options.timeout,
|
||||
useHttps: this.useHttps,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Make RPC request with retry logic
|
||||
*/
|
||||
async request<T = any>(methodName: string, params: any[]): Promise<T> {
|
||||
this.requestId += 1;
|
||||
const id = this.requestId;
|
||||
|
||||
const requestData = TrytonJSONEncoder.serialize({
|
||||
id: id,
|
||||
method: methodName,
|
||||
params: params,
|
||||
} as JsonRpcRequest);
|
||||
|
||||
// Check cache first
|
||||
if (this.cache && this.cache.cached(methodName)) {
|
||||
try {
|
||||
return this.cache.get(methodName, requestData);
|
||||
} catch (error) {
|
||||
// Cache miss or expired, continue with request
|
||||
}
|
||||
}
|
||||
|
||||
let lastError: Error | null = null;
|
||||
|
||||
// Retry logic (up to 5 attempts)
|
||||
for (let attempt = 0; attempt < 5; attempt++) {
|
||||
try {
|
||||
const response = await this.transport.request(
|
||||
this.hostUrl,
|
||||
this.handler,
|
||||
requestData,
|
||||
this.verbose
|
||||
);
|
||||
|
||||
// Validate response
|
||||
if (response.id !== id) {
|
||||
throw new ResponseError(
|
||||
`Invalid response id (${response.id}) expected ${id}`
|
||||
);
|
||||
}
|
||||
|
||||
// Handle RPC errors
|
||||
if (response.error) {
|
||||
if (this.verbose) {
|
||||
console.error("RPC Error:", response);
|
||||
}
|
||||
throw new Fault(response.error[0], response.error[1] || "");
|
||||
}
|
||||
|
||||
// Cache successful response
|
||||
if (this.cache && response.cache) {
|
||||
this.cache.set(
|
||||
methodName,
|
||||
requestData,
|
||||
response.cache,
|
||||
response.result
|
||||
);
|
||||
}
|
||||
|
||||
return response.result as T;
|
||||
} catch (error) {
|
||||
lastError = error as Error;
|
||||
|
||||
// Check if we should retry
|
||||
if (error instanceof ProtocolError && error.errcode === 503) {
|
||||
// Service unavailable, wait and retry
|
||||
const delay = Math.min(attempt + 1, 10) * 1000;
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
continue;
|
||||
}
|
||||
|
||||
// For connection errors, try once more
|
||||
if (
|
||||
attempt === 0 &&
|
||||
((error as any).code === "ECONNRESET" ||
|
||||
(error as any).code === "EPIPE" ||
|
||||
error instanceof ProtocolError)
|
||||
) {
|
||||
this.transport.close();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't retry other errors
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close server proxy
|
||||
*/
|
||||
close(): void {
|
||||
this.transport.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SSL status
|
||||
* @returns {boolean} - Whether connection uses SSL
|
||||
*/
|
||||
get ssl(): boolean {
|
||||
return this.port === 443 || this.hostUrl.startsWith("https");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full URL
|
||||
* @returns {string} - Full server URL
|
||||
*/
|
||||
get url(): string {
|
||||
const scheme = this.ssl ? "https" : "http";
|
||||
return `${scheme}://${this.hostUrl}${this.handler}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection pool for reusing ServerProxy instances
|
||||
*/
|
||||
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<ServerProxy>;
|
||||
private cache: TrytonCache | null;
|
||||
|
||||
constructor(
|
||||
host: string,
|
||||
port: number,
|
||||
database: string,
|
||||
options: ServerPoolOptions = {}
|
||||
) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.database = database;
|
||||
this.options = options;
|
||||
this.keepMax = options.keepMax || 4;
|
||||
this.session = options.session || null;
|
||||
|
||||
this.pool = [];
|
||||
this.used = new Set<ServerProxy>();
|
||||
this.cache = null;
|
||||
|
||||
// Initialize cache if requested
|
||||
if (options.cache) {
|
||||
if (Array.isArray(options.cache)) {
|
||||
this.cache = new TrytonCache();
|
||||
} else {
|
||||
this.cache = options.cache as TrytonCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection from pool or create new one
|
||||
* @returns {ServerProxy} - Server proxy instance
|
||||
*/
|
||||
getConnection(): ServerProxy {
|
||||
let conn: ServerProxy;
|
||||
|
||||
if (this.pool.length > 0) {
|
||||
conn = this.pool.pop()!;
|
||||
} else {
|
||||
conn = new ServerProxy(this.host, this.port, this.database, {
|
||||
...this.options,
|
||||
cache: this.cache as any,
|
||||
});
|
||||
}
|
||||
|
||||
this.used.add(conn);
|
||||
return conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return connection to pool
|
||||
* @param {ServerProxy} conn - Connection to return
|
||||
*/
|
||||
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();
|
||||
if (oldConn) {
|
||||
oldConn.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute callback with a pooled connection
|
||||
* @param {Function} callback - Async function to execute
|
||||
* @returns {Promise<*>} - Callback result
|
||||
*/
|
||||
async withConnection<T>(
|
||||
callback: (conn: ServerProxy) => Promise<T>
|
||||
): Promise<T> {
|
||||
const conn = this.getConnection();
|
||||
try {
|
||||
return await callback(conn);
|
||||
} finally {
|
||||
this.putConnection(conn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all connections in pool
|
||||
*/
|
||||
close(): void {
|
||||
// Close all pooled connections
|
||||
for (const conn of this.pool) {
|
||||
conn.close();
|
||||
}
|
||||
|
||||
// Close all used connections
|
||||
for (const conn of this.used) {
|
||||
conn.close();
|
||||
}
|
||||
|
||||
this.pool = [];
|
||||
this.used.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache
|
||||
* @param {string} [prefix] - Optional prefix to clear
|
||||
*/
|
||||
clearCache(prefix?: string): void {
|
||||
if (this.cache) {
|
||||
this.cache.clear(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SSL status from any connection
|
||||
* @returns {boolean|null} - SSL status or null if no connections
|
||||
*/
|
||||
get ssl(): boolean | null {
|
||||
const allConns = [...this.pool, ...this.used];
|
||||
if (allConns.length > 0) {
|
||||
return allConns[0]?.ssl || null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URL from any connection
|
||||
* @returns {string|null} - URL or null if no connections
|
||||
*/
|
||||
get url(): string | null {
|
||||
const allConns = [...this.pool, ...this.used];
|
||||
if (allConns.length > 0) {
|
||||
return allConns[0]?.url || null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
581
package-lock.json
generated
Normal file
581
package-lock.json
generated
Normal file
@@ -0,0 +1,581 @@
|
||||
{
|
||||
"name": "@tryton/client-node",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@tryton/client-node",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"rimraf": "^5.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^5.1.2",
|
||||
"string-width-cjs": "npm:string-width@^4.2.0",
|
||||
"strip-ansi": "^7.0.1",
|
||||
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
|
||||
"wrap-ansi": "^8.1.0",
|
||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@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": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
|
||||
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/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/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/foreground-child": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.6",
|
||||
"signal-exit": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/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": {
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^3.1.2",
|
||||
"minimatch": "^9.0.4",
|
||||
"minipass": "^7.1.2",
|
||||
"package-json-from-dist": "^1.0.0",
|
||||
"path-scurry": "^1.11.1"
|
||||
},
|
||||
"bin": {
|
||||
"glob": "dist/esm/bin.mjs"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^8.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"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": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/package-json-from-dist": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0"
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"lru-cache": "^10.2.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "5.0.10",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
|
||||
"integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"glob": "^10.3.7"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "dist/esm/bin.mjs"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eastasianwidth": "^0.2.0",
|
||||
"emoji-regex": "^9.2.2",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs": {
|
||||
"name": "string-width",
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string-width-cjs/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "7.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": {
|
||||
"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/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/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.1.0",
|
||||
"string-width": "^5.0.1",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs": {
|
||||
"name": "wrap-ansi",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
49
package.json
Normal file
49
package.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "@tryton/client-node",
|
||||
"version": "1.0.0",
|
||||
"description": "Cliente RPC TypeScript para Tryton ERP - Compatible con Node.js",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
"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",
|
||||
"typescript",
|
||||
"nodejs",
|
||||
"jsonrpc"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"dependencies": {},
|
||||
"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": ""
|
||||
}
|
||||
30
tsconfig.json
Normal file
30
tsconfig.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"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"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
284
types.ts
Normal file
284
types.ts
Normal file
@@ -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<any> {
|
||||
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> = T | Promise<T>;
|
||||
|
||||
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<T extends TrytonRecord = TrytonRecord> {
|
||||
read(
|
||||
ids: RecordIds,
|
||||
fields: FieldName[],
|
||||
context?: TrytonContext
|
||||
): Promise<T[]>;
|
||||
create(
|
||||
records: Partial<Omit<T, "id">>[],
|
||||
context?: TrytonContext
|
||||
): Promise<RecordIds>;
|
||||
write(
|
||||
ids: RecordIds,
|
||||
values: Partial<Omit<T, "id">>,
|
||||
context?: TrytonContext
|
||||
): Promise<void>;
|
||||
delete(ids: RecordIds, context?: TrytonContext): Promise<void>;
|
||||
search(
|
||||
domain: SearchDomain,
|
||||
offset?: number,
|
||||
limit?: number,
|
||||
order?: string[],
|
||||
context?: TrytonContext
|
||||
): Promise<RecordIds>;
|
||||
searchRead(
|
||||
domain: SearchDomain,
|
||||
fields: FieldName[],
|
||||
offset?: number,
|
||||
limit?: number,
|
||||
order?: string[],
|
||||
context?: TrytonContext
|
||||
): Promise<T[]>;
|
||||
searchCount(domain: SearchDomain, context?: TrytonContext): Promise<number>;
|
||||
}
|
||||
|
||||
// ===== EXPORT ALL TYPES =====
|
||||
// Note: All types are already exported above individually
|
||||
Reference in New Issue
Block a user