JavaScript SDK Documentation Documentation du SDK JavaScript

Requirements Prérequis

  • Node.js 16+ (for server-side use) Node.js 16+ (pour utilisation côté serveur)
  • Modern browsers (Chrome 90+, Firefox 88+, Safari 14+, Edge 90+) Navigateurs modernes (Chrome 90+, Firefox 88+, Safari 14+, Edge 90+)

Installation Installation

NPM / Yarn

# NPM
npm install @unikhorn/sdk

# Yarn
yarn add @unikhorn/sdk

# PNPM
pnpm add @unikhorn/sdk

CDN (Browser) CDN (Navigateur)

<!-- Version spécifique -->
<script src="https://cdn.jsdelivr.net/npm/@unikhorn/sdk@1.0.0/dist/unikhorn.min.js"></script>

<!-- Dernière version -->
<script src="https://cdn.jsdelivr.net/npm/@unikhorn/sdk/dist/unikhorn.min.js"></script>

Configuration Configuration

ES6 Modules

import { UnikhornClient } from '@unikhorn/sdk';

const client = new UnikhornClient({
    apiKey: 'votre-cle-api',
    baseURL: 'https://api.unikhorn.com', // Optionnel
    timeout: 30000, // Optionnel, timeout en ms
    language: 'fr' // Optional, default language
});

CommonJS CommonJS

const { UnikhornClient } = require('@unikhorn/sdk');

const client = new UnikhornClient({
    apiKey: 'votre-cle-api'
});

Browser (Global) Navigateur (Global)

// After loading the SDK via CDN
const client = new Unikhorn.UnikhornClient({
    apiKey: 'votre-cle-api'
});

Usage Examples Exemples d'utilisation

Calculate a Simple Coat Calculer une robe simple

// Async/Await
async function calculateCoat() {
    try {
        const result = await client.calculateCoat({
            base: {
                agouti: ['A','A'],
                extension: ['E','E']
            },
            lang: 'fr' // Optionnel
        });

        console.log('Calculated coat:', result.translate);
        console.log('Genotype:', result.base);
        
    } catch (error) {
        console.error('Error:', error.message);
    }
}

// Promises
client.calculateCoat({
    base: {
        agouti: ['A','A'],
        extension: ['E','E']
    }
})
.then(result => {
    console.log('Coat:', result.translate);
})
.catch(error => {
    console.error('Error:', error);
});

Calculation with Modifier Genes Calcul avec gènes modificateurs

async function calculateComplexCoat() {
    const result = await client.calculateCoat({
        base: {
            agouti: ['A','a'],
            extension: ['E','e']
        },
        others: {
            matp: ['Cr','n'],     // MATP gene (cream/pearl)
            dun: ['D','D'],       // Dun gene
            silver: ['Z','n'],    // Silver gene
            champagne: ['Ch','n'], // Champagne gene
            grey: ['G','n'],      // Grey gene
            kit: ['DW7','n'],     // KIT (dominant white)
            lp: ['Lp','n']        // Leopard complex
        },
        lang: 'fr'
    });

    console.log('Complete coat:', result.translate);
    console.log('Base:', result.base);
    console.log('Modificateurs:', result.others);
    
    // Formatted display
    console.log(`
        Coat: ${result.translate}
        Agouti: ${result.base.agouti}
        Extension: ${result.base.extension}
        MATP: ${result.others.matp || ['n','n']}
        Dun: ${result.others.dun || ['nd2','nd2']}
    `);
}

Generate Offspring Possibilities Générer les possibilités de descendance

async function generateOffspring() {
    const offspring = await client.generateOffspring({
        mother: {
            base: {
                agouti: ['A','a'],
                extension: ['E','e']
            },
            others: {
                matp: ['Cr','n'],
                dun: ['D','nd1']
            }
        },
        father: {
            base: {
                agouti: ['A','A'],
                extension: ['e','e']
            },
            others: {
                matp: ['n','n'],
                dun: ['D','D']
            }
        },
        lang: 'fr'
    });

    console.log(`Number of possibilities: ${offspring.results.length}`);
    
    // Display all possibilities
    offspring.results.forEach(possibility => {
        console.log(`${possibility.coat}: ${possibility.percentage.toFixed(2)}%`);
    });

    // Group by coat
    const grouped = offspring.getGroupedByCoat();
    console.log('\nSummary by coat:');
    Object.entries(grouped).forEach(([coat, percentage]) => {
        console.log(`${coat}: ${percentage.toFixed(2)}%`);
    });

    // Most probable possibilities
    const topResults = offspring.results
        .sort((a, b) => b.percentage - a.percentage)
        .slice(0, 5);
    
    console.log('\nTop 5 possibilities:');
    topResults.forEach(result => {
        console.log(`${result.coat}: ${result.percentage.toFixed(2)}%`);
    });
}

KIT Genetic Linkage Liaison génétique KIT

async function offspringWithKitLinkage() {
    const offspring = await client.generateOffspring({
        mother: {
            base: {
                agouti: ['A','a'],
                extension: ['E','e']
            },
            others: {
                kit: ['DW7','n']
            }
        },
        father: {
            base: {
                agouti: ['A','A'],
                extension: ['e','e']
            },
            others: {
                kit: ['n','n']
            }
        },
        kit_linkage: {
            mother: ['E', 'DW7'], // Extension E linked with DW7
            father: []
        },
        lang: 'fr'
    });

    if (offspring.hasKitLinkage) {
        console.log('⚠️ KIT genetic linkage detected');
        console.log('Results account for genetic linkage.');
    }

    offspring.results.forEach(result => {
        console.log(`${result.coat}: ${result.percentage.toFixed(2)}%`);
    });
}

Usage in React Utilisation dans React

import React, { useState, useEffect } from 'react';
import { UnikhornClient } from '@unikhorn/sdk';

const CoatCalculator = () => {
    const [client] = useState(new UnikhornClient({ 
        apiKey: process.env.REACT_APP_UNIKHORN_API_KEY 
    }));
    const [result, setResult] = useState(null);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(null);

    const [formData, setFormData] = useState({
        agouti: ['A','A'],
        extension: ['E','E'],
        matp: ['n','n'],
        dun: ['nd2','nd2']
    });

    const calculateCoat = async () => {
        setLoading(true);
        setError(null);
        
        try {
            const coatResult = await client.calculateCoat({
                base: {
                    agouti: formData.agouti,
                    extension: formData.extension
                },
                others: {
                    matp: formData.matp,
                    dun: formData.dun
                },
                lang: 'fr'
            });
            
            setResult(coatResult);
        } catch (err) {
            setError(err.message);
        } finally {
            setLoading(false);
        }
    };

    return (
        <div>
            <h2>Coat Calculator</h2>
            
            <div>
                <label>
                    Agouti:
                    <select 
                        value={formData.agouti} 
                        onChange={(e) => setFormData({...formData, agouti: JSON.parse(e.target.value)})}
                    >
                        <option value='["A","A"]'>AA</option>
                        <option value='["A","a"]'>Aa</option>
                        <option value='["a","a"]'>aa</option>
                    </select>
                </label>
            </div>

            <button onClick={calculateCoat} disabled={loading}>
                {loading ? 'Calculating...' : 'Calculate'}
            </button>

            {error && <div className="error">{error}</div>}
            
            {result && (
                <div>
                    <h3>Result: {result.translate}</h3>
                    <p>Génotype: {result.base.agouti} {result.base.extension}</p>
                </div>
            )}
        </div>
    );
};

Usage in Vue.js Utilisation dans Vue.js

<template>
  <div>
    <h2>Coat Calculator</h2>
    
    <div>
      <label>
        Agouti:
        <select v-model="formData.agouti">
          <option value="AA">AA</option>
          <option value="Aa">Aa</option>
          <option value="aa">aa</option>
        </select>
      </label>
    </div>

    <button @click="calculateCoat" :disabled="loading">
      {{ loading ? 'Calculating...' : 'Calculate' }}
    </button>

    <div v-if="error" class="error">{{ error }}</div>
    
    <div v-if="result">
      <h3>Result: {{ result.translate }}</h3>
      <p>Génotype: {{ result.base.agouti }} {{ result.base.extension }}</p>
    </div>
  </div>
</template>

<script>
import { UnikhornClient } from '@unikhorn/sdk';

export default {
  data() {
    return {
      client: new UnikhornClient({ 
        apiKey: import.meta.env.VITE_UNIKHORN_API_KEY 
      }),
      result: null,
      loading: false,
      error: null,
      formData: {
        agouti: ['A','A'],
        extension: ['E','E'],
        matp: ['n','n'],
        dun: ['nd2','nd2']
      }
    };
  },
  
  methods: {
    async calculateCoat() {
      this.loading = true;
      this.error = null;
      
      try {
        this.result = await this.client.calculateCoat({
          base: {
            agouti: this.formData.agouti,
            extension: this.formData.extension
          },
          others: {
            matp: this.formData.matp,
            dun: this.formData.dun
          },
          lang: 'fr'
        });
      } catch (err) {
        this.error = err.message;
      } finally {
        this.loading = false;
      }
    }
  }
};
</script>

API Reference Référence API

UnikhornClient

Method Méthode Description Description Return Retour
calculateCoat(data) Calculate a horse's coat Calcule la robe d'un cheval Promise<CoatResult>
generateOffspring(data) Generate offspring possibilities Génère les possibilités de descendance Promise<OffspringResult>
getApiKeyInfo() Retrieve API key information Récupère les informations de la clé API Promise<ApiKeyInfo>
setLanguage(lang) Set default language Définit la langue par défaut void

TypeScript Types Types TypeScript

interface CoatData {
    base: {
        agouti: [string, string]; // ['A','A'], ['A','a'], ['a','a']
        extension: [string, string]; // ['E','E'], ['E','e'], ['e','e']
    };
    others?: {
        // Dilution genes
        matp?: [string, string]; // ['Cr','Cr'], ['Cr','Prl'], ['Prl','Prl'], ['Cr','n'], ['Prl','n'], ['n','n']
        dun?: [string, string]; // ['D','D'], ['D','nd1'], ['D','nd2'], ['nd1','nd2'], ['nd2','nd2']
        silver?: [string, string]; // ['Z','Z'], ['Z','n'], ['n','n']
        champagne?: [string, string]; // ['Ch','Ch'], ['Ch','n'], ['n','n']
        grey?: [string, string]; // ['G','G'], ['G','n'], ['n','n']
        
        // Pattern/white genes
        kit?: [string, string]; // ['DW2','n'], ['DW6','n'], ['To','n'], ['R','n'], ['Sb1','n'], etc.
        lp?: [string, string]; // ['Lp','Lp'], ['Lp','n'], ['n','n'] (Leopard Complex)
        lwo?: [string, string]; // ['LWO','n'], ['n','n'] (Lethal White Overo)
        mushroom?: [string, string]; // ['Mu','Mu'], ['Mu','n'], ['n','n']
        
        // Splash genes
        splash_mift?: [string, string];
        splash_pax3?: [string, string];
        
        // Other available genes
        [key: string]: [string, string];
    };
    lang?: 'fr' | 'en';
}

// Example with all main genes
const exempleComplet: CoatData = {
    base: {
        agouti: ['A','a'],     // Bay heterozygous
        extension: ['E','E']   // Extension homozygote
    },
    others: {
        matp: ['Cr','n'],         // Cream heterozygous
        dun: ['D','nd2'],         // Dun heterozygous
        silver: ['Z','n'],        // Silver heterozygous
        champagne: ['n','n'],     // Pas de champagne
        grey: ['G','n'],          // Grey heterozygous
        kit: ['DW2','n'],         // Dominant White 2 heterozygous
        lp: ['Lp','n'],           // Leopard Complex heterozygous
        lwo: ['n','n'],           // Pas de Lethal White
        mushroom: ['Mu','n'],     // Mushroom heterozygous
        splash_mift: ['n','n'],   // Pas de Splash MIFT
        splash_pax3: ['SW1','n']  // Splash PAX3 variant 1
    },
    lang: 'fr'
};

interface CoatResult {
    translate: string;
    base: {
        agouti: [string, string];
        extension: [string, string];
    };
    others: Record<string, [string, string]>;
}

interface OffspringData {
    mother: CoatData;
    father: CoatData;
    kit_linkage?: {
        mother: string[];
        father: string[];
    };
    lang?: 'fr' | 'en';
}

interface OffspringResult {
    results: Array<{
        coat: string;
        percentage: number;
        genotype: {
            agouti: [string, string];
            extension: [string, string];
            [key: string]: [string, string];
        };
    }>;
    hasKitLinkage: boolean;
    hasIDK: boolean;
    getGroupedByCoat(): Record<string, number>;
}

Error Handling Gestion des erreurs

import { 
    UnikhornClient, 
    UnikhornError,
    AuthenticationError,
    ValidationError,
    RateLimitError 
} from '@unikhorn/sdk';

const client = new UnikhornClient({ apiKey: 'your-api-key' });

try {
    const result = await client.calculateCoat(data);
} catch (error) {
    if (error instanceof AuthenticationError) {
        console.error('Invalid API key:', error.message);
    } else if (error instanceof ValidationError) {
        console.error('Invalid data:', error.message);
        console.error('Details:', error.validationErrors);
    } else if (error instanceof RateLimitError) {
        console.error('Limit exceeded. Retry in:', error.retryAfter, 'seconds');
    } else if (error instanceof UnikhornError) {
        console.error('API Error:', error.message);
    } else {
        console.error('Unexpected error:', error);
    }
}

// With async/await and specific handling
async function safeCalculate(data) {
    try {
        return await client.calculateCoat(data);
    } catch (error) {
        if (error.code === 'AUTH_FAILED') {
            // Renew API key
            await renewApiKey();
            return await client.calculateCoat(data);
        }
        throw error;
    }
}

Advanced Configuration Configuration avancée

Request Interceptors Intercepteurs de requêtes

const client = new UnikhornClient({
    apiKey: 'votre-cle-api',
    onRequest: (config) => {
        console.log('Request:', config.url, config.data);
        // Add custom headers
        config.headers['X-Custom-Header'] = 'value';
        return config;
    },
    onResponse: (response) => {
        console.log('Response:', response.status, response.data);
        return response;
    },
    onError: (error) => {
        console.error('Intercepted error:', error);
        // Log error to external service
        logService.error(error);
        throw error;
    }
});

Automatic Retry Retry automatique

const client = new UnikhornClient({
    apiKey: 'votre-cle-api',
    retry: {
        enabled: true,
        maxAttempts: 3,
        delay: 1000, // ms
        backoff: 2 // Delay multiplier
    }
});

Result Caching Cache des résultats

const client = new UnikhornClient({
    apiKey: 'votre-cle-api',
    cache: {
        enabled: true,
        ttl: 3600000, // 1 heure en ms
        maxSize: 100 // Max cache entries
    }
});

// Forcer le refresh du cache
const result = await client.calculateCoat(data, { 
    cache: false 
});

Testing Tests

// Jest example
import { UnikhornClient } from '@unikhorn/sdk';

describe('UnikhornClient', () => {
    let client;
    
    beforeEach(() => {
        client = new UnikhornClient({ 
            apiKey: process.env.TEST_API_KEY 
        });
    });

    test('should calculate simple coat', async () => {
        const result = await client.calculateCoat({
            base: {
                agouti: 'AA',
                extension: 'EE'
            }
        });
        
        expect(result).toHaveProperty('translate');
        expect(result.base).toMatchObject({
            agouti: 'AA',
            extension: 'EE'
        });
    });

    test('should handle invalid data', async () => {
        await expect(client.calculateCoat({
            base: {
                agouti: 'invalid',
                extension: 'EE'
            }
        })).rejects.toThrow('Validation error');
    });
});

⚠️ Important Notes ⚠️ Notes importantes

  • Keep your API key secure Conservez votre clé API de manière sécurisée
  • Never expose your API key on the client side in production N'exposez jamais votre clé API côté client en production
  • Use environment variables to store keys Utilisez des variables d'environnement pour stocker les clés
  • Implement a backend proxy for public applications Implémentez un proxy backend pour les applications publiques
  • Respect API rate limits Respectez les limites de taux de l'API