/** * Traveller NPC Generator - Utilitaires * * Ce fichier contient les classes et fonctions utilitaires pour le générateur. */ const MODULE_ID = 'mgt2-compendium-amiral-denisov'; // ============================================================================ // Classe de gestion des erreurs // ============================================================================ /** * Erreur spécifique au générateur de PNJ Traveller */ export class TravellerNpcError extends Error { /** * @param {string} message - Message d'erreur * @param {string} code - Code d'erreur * @param {Object} [details={}] - Détails supplémentaires */ constructor(message, code, details = {}) { super(message); this.name = 'TravellerNpcError'; this.code = code; this.details = details; this.isTravellerNpcError = true; this.timestamp = new Date().toISOString(); } /** * Crée une erreur à partir d'une erreur existante * @param {Error} error - Erreur originale * @param {string} code - Code d'erreur * @returns {TravellerNpcError} */ static from(error, code = 'UNKNOWN') { if (error?.isTravellerNpcError) { return error; } return new TravellerNpcError( error?.message || 'Erreur inconnue', code, { originalError: error } ); } /** * Log l'erreur dans la console */ log() { console.error(`${MODULE_ID} | [${this.code}] ${this.message}`, { details: this.details, timestamp: this.timestamp }); } /** * Affiche une notification à l'utilisateur et log l'erreur */ notify() { ui.notifications?.error(`${this.code}: ${this.message}`); this.log(); } /** * Crée une notification sans lancer d'erreur * @param {string} message - Message * @param {string} code - Code */ static warn(message, code) { const error = new TravellerNpcError(message, code); error.log(); return error; } } // ============================================================================ // Classe de cache pour le module // ============================================================================ /** * Système de cache générique pour le module */ export class ModuleCache { /** * @param {string} moduleId - ID du module * @param {number} [defaultTTL=300000] - Durée de vie par défaut (5 min) */ constructor(moduleId, defaultTTL = 300000) { this.moduleId = moduleId; this.defaultTTL = defaultTTL; this.cache = new Map(); this.pending = new Map(); this.timestamps = new Map(); } /** * Récupère une valeur du cache ou la fetch * @template T * @param {string} key - Clé de cache * @param {Function} fetchFn - Fonction de récupération * @param {Object} [options={}] - Options * @param {boolean} [options.forceRefresh=false] - Forcer le rafraîchissement * @param {number} [options.ttl] - TTL spécifique * @returns {Promise} */ async getOrFetch(key, fetchFn, options = {}) { const { forceRefresh = false, ttl = this.defaultTTL } = options; // Si déjà en cache et non expiré if (!forceRefresh && this.cache.has(key)) { const timestamp = this.timestamps.get(key); if (Date.now() - timestamp < ttl) { return foundry.utils.deepClone(this.cache.get(key)); } // Expiré, on le supprime this.clear(key); } // Si déjà en cours de fetch pour cette clé if (this.pending.has(key)) { return this.pending.get(key); } // Nouveau fetch const promise = (async () => { try { const result = await fetchFn(); const cachedResult = result ? foundry.utils.deepClone(result) : null; this.cache.set(key, cachedResult); this.timestamps.set(key, Date.now()); this.pending.delete(key); return foundry.utils.deepClone(cachedResult); } catch (error) { console.warn(`${this.moduleId} | Erreur de cache pour ${key}:`, error); this.pending.delete(key); throw error; } })(); this.pending.set(key, promise); return promise; } /** * Vide une entrée du cache * @param {string} key - Clé à supprimer */ clear(key) { this.cache.delete(key); this.pending.delete(key); this.timestamps.delete(key); } /** * Vide tout le cache */ clearAll() { this.cache.clear(); this.pending.clear(); this.timestamps.clear(); } /** * Vérifie si une clé est en cache * @param {string} key - Clé à vérifier * @returns {boolean} */ has(key) { if (!this.cache.has(key)) return false; const timestamp = this.timestamps.get(key); return Date.now() - timestamp < this.defaultTTL; } /** * Récupère une valeur du cache sans vérification d'expiration * @template T * @param {string} key - Clé * @returns {T|null} */ get(key) { return this.cache.get(key) ?? null; } } // ============================================================================ // Instance de cache pour le module // ============================================================================ /** * Instance de cache partagée pour le générateur de PNJ Traveller * @type {ModuleCache} */ export const travellerNpcCache = new ModuleCache(MODULE_ID, 300000); // 5 min TTL // ============================================================================ // Codes d'erreur // ============================================================================ /** * Codes d'erreur standard pour le module */ export const ERROR_CODES = { INVALID_OPTIONS: 'INVALID_OPTIONS', INVALID_ROLE: 'INVALID_ROLE', INVALID_CATEGORY: 'INVALID_CATEGORY', INVALID_EXPERIENCE: 'INVALID_EXPERIENCE', INVALID_GENDER: 'INVALID_GENDER', ACTOR_CREATION_FAILED: 'ACTOR_CREATION_FAILED', MGT2E_NOT_ACTIVE: 'MGT2E_NOT_ACTIVE', BASE_ACTOR_NOT_FOUND: 'BASE_ACTOR_NOT_FOUND', CACHE_ERROR: 'CACHE_ERROR', GENERATION_ERROR: 'GENERATION_ERROR' }; // ============================================================================ // Fonctions utilitaires générales // ============================================================================ /** * Formate un message de debug * @param {string} message - Message * @param {Object} [data={}] - Données supplémentaires */ export function debug(message, data = {}) { if (game.settings.get(MODULE_ID, 'debug') || game.user?.isGM) { console.debug(`${MODULE_ID} | ${message}`, data); } } /** * Formate un message de log * @param {string} message - Message * @param {Object} [data={}] - Données supplémentaires */ export function log(message, data = {}) { console.log(`${MODULE_ID} | ${message}`, data); } /** * Formate un message d'avertissement * @param {string} message - Message * @param {Object} [data={}] - Données supplémentaires */ export function warn(message, data = {}) { console.warn(`${MODULE_ID} | ${message}`, data); } /** * Formate un message d'erreur * @param {string} message - Message * @param {Object} [data={}] - Données supplémentaires */ export function error(message, data = {}) { console.error(`${MODULE_ID} | ${message}`, data); } // ============================================================================ // Export par défaut // ============================================================================ export default { TravellerNpcError, ModuleCache, travellerNpcCache, ERROR_CODES, debug, log, warn, error };