UNPKG

synapse-storage

Version:

Набор инструментов для управления состоянием и апи-запросами

216 lines (211 loc) 8.38 kB
import { ConfigMergeStrategy } from "./models.js"; class SingletonManager { static instances = new Map(); static configs = new Map(); static logger; static setLogger(logger) { this.logger = logger; } // Основной метод для создания/получения singleton static getOrCreate(key, config, factory) { const existingConfig = this.configs.get(key); const existingInstance = this.instances.get(key); // Если экземпляр уже существует if (existingInstance && existingConfig) { const mergeResult = this.mergeConfigurations(existingConfig, config); return { instance: existingInstance, isNewInstance: false, appliedConfig: mergeResult.finalConfig, conflicts: mergeResult.conflicts }; } // Создаем новый экземпляр const instance = factory(config); this.instances.set(key, instance); this.configs.set(key, { ...config }); this.logger?.debug(`Created new singleton instance: ${key}`); return { instance, isNewInstance: true, appliedConfig: config, conflicts: [] }; } // Проверка существования singleton static exists(key) { return this.instances.has(key); } // Получение конфигурации singleton static getConfig(key) { return this.configs.get(key); } // Удаление singleton из реестра (без вызова destroy) static remove(key) { const existed = this.instances.has(key); if (existed) { this.instances.delete(key); this.configs.delete(key); this.logger?.debug(`Removed singleton instance: ${key}`); return true; } return false; } // Очистка реестра singleton (без вызова destroy) static clear() { this.instances.clear(); this.configs.clear(); this.logger?.debug('Cleared all singleton instances'); } // Получение статистики static getStats() { return { instanceCount: this.instances.size, instances: Array.from(this.instances.keys()) }; } static mergeConfigurations(existing, incoming) { const strategy = incoming.singleton?.mergeStrategy || ConfigMergeStrategy.FIRST_WINS; const warnOnConflict = incoming.singleton?.warnOnConflict ?? true; switch(strategy){ case ConfigMergeStrategy.STRICT: return this.strictMerge(existing, incoming); case ConfigMergeStrategy.FIRST_WINS: return this.firstWinsMerge(existing, incoming, warnOnConflict); case ConfigMergeStrategy.DEEP_MERGE: return this.deepMerge(existing, incoming); case ConfigMergeStrategy.OVERRIDE: return this.overrideMerge(existing, incoming); case ConfigMergeStrategy.WARN_AND_USE_FIRST: return this.warnAndUseFirst(existing, incoming); default: return this.firstWinsMerge(existing, incoming, warnOnConflict); } } static strictMerge(existing, incoming) { const conflicts = this.findConflicts(existing, incoming); if (conflicts.length > 0) { throw new Error(`Strict singleton validation failed for "${existing.name}". Conflicts: ${conflicts.join(', ')}`); } return { finalConfig: existing, conflicts: [] }; } static firstWinsMerge(existing, incoming, warn) { const conflicts = this.findConflicts(existing, incoming); if (warn && conflicts.length > 0) { this.logger?.warn(`Singleton config conflicts for "${existing.name}":`, conflicts); this.logger?.warn('Using existing configuration'); } return { finalConfig: existing, conflicts }; } static deepMerge(existing, incoming) { const conflicts = this.findConflicts(existing, incoming); const mergedConfig = { ...existing, initialState: this.deepMergeObjects(existing.initialState || {}, incoming.initialState || {}) }; this.logger?.debug(`Deep merged singleton config for "${existing.name}"`); return { finalConfig: mergedConfig, conflicts }; } static overrideMerge(existing, incoming) { const conflicts = this.findConflicts(existing, incoming); const mergedConfig = { ...incoming, name: existing.name }; this.logger?.warn(`Overriding singleton config for "${existing.name}"`); return { finalConfig: mergedConfig, conflicts }; } static warnAndUseFirst(existing, incoming) { const conflicts = this.findConflicts(existing, incoming); if (conflicts.length > 0) { this.logger?.warn(`Configuration conflicts detected for singleton "${existing.name}":`); this.logger?.warn('Existing config:', existing); this.logger?.warn('Incoming config:', incoming); this.logger?.warn('Using existing configuration'); } return { finalConfig: existing, conflicts }; } static findConflicts(config1, config2) { const conflicts = []; // Проверяем initialState if (config1.initialState && config2.initialState) { const stateConflicts = this.findObjectConflicts(config1.initialState, config2.initialState, 'initialState'); conflicts.push(...stateConflicts); } else if (config1.initialState !== config2.initialState) { conflicts.push('initialState: one is undefined'); } // Проверяем middlewares (базовая проверка — поле живёт в SyncStorageConfig/AsyncStorageConfig) const c1 = config1; const c2 = config2; if (!!c1.middlewares !== !!c2.middlewares) { conflicts.push('middlewares: configuration differs'); } return conflicts; } static findObjectConflicts(obj1, obj2, path) { const conflicts = []; const allKeys = new Set([ ...Object.keys(obj1 || {}), ...Object.keys(obj2 || {}) ]); for (const key of allKeys){ const currentPath = `${path}.${key}`; const val1 = obj1?.[key]; const val2 = obj2?.[key]; if (val1 === val2) continue; if (val1 === undefined || val2 === undefined) { conflicts.push(`${currentPath}: missing in one config`); } else if (typeof val1 !== typeof val2) { conflicts.push(`${currentPath}: type mismatch (${typeof val1} vs ${typeof val2})`); } else if (typeof val1 === 'object' && val1 !== null && val2 !== null) { conflicts.push(...this.findObjectConflicts(val1, val2, currentPath)); } else { conflicts.push(`${currentPath}: value mismatch (${val1} vs ${val2})`); } } return conflicts; } static deepMergeObjects(target, source) { const result = { ...target }; for(const key in source){ if (source[key] !== null && typeof source[key] === 'object' && !Array.isArray(source[key])) { result[key] = this.deepMergeObjects(target[key] || {}, source[key]); } else { result[key] = source[key]; } } return result; } } // Хелпер для генерации ключа singleton class SingletonKeyGenerator { static generate(config, storageType) { // Если указан кастомный ключ if (config.singleton?.key) { return config.singleton.key; } // Стандартный ключ: тип + имя return `${storageType}_${config.name}`; } } export { SingletonKeyGenerator, SingletonManager }; //# sourceMappingURL=singleton.util.js.map