JavaScript SDK Documentation
Requirements
- Node.js 16+ (for server-side use)
- Modern browsers (Chrome 90+, Firefox 88+, Safari 14+, Edge 90+)
Installation
NPM / Yarn
# NPM
npm install @unikhorn/sdk
# Yarn
yarn add @unikhorn/sdk
# PNPM
pnpm add @unikhorn/sdk
CDN (Browser)
<!-- 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
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
const { UnikhornClient } = require('@unikhorn/sdk');
const client = new UnikhornClient({
apiKey: 'votre-cle-api'
});
Browser (Global)
// After loading the SDK via CDN
const client = new Unikhorn.UnikhornClient({
apiKey: 'votre-cle-api'
});
Usage Examples
Calculate a Simple Coat
// 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
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
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
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
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
<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
UnikhornClient
Method | Description | Return |
---|---|---|
calculateCoat(data) |
Calculate a horse's coat | Promise<CoatResult> |
generateOffspring(data) |
Generate offspring possibilities | Promise<OffspringResult> |
getApiKeyInfo() |
Retrieve API key information | Promise<ApiKeyInfo> |
setLanguage(lang) |
Set default language | void |
TypeScript Types
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
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
Request Interceptors
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
const client = new UnikhornClient({
apiKey: 'votre-cle-api',
retry: {
enabled: true,
maxAttempts: 3,
delay: 1000, // ms
backoff: 2 // Delay multiplier
}
});
Result Caching
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
// 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
- Keep your API key secure
- Never expose your API key on the client side in production
- Use environment variables to store keys
- Implement a backend proxy for public applications
- Respect API rate limits