synapse-storage
Version:
Набор инструментов для управления состоянием и апи-запросами
216 lines (211 loc) • 8.38 kB
JavaScript
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