userface
Version:
Universal Data-Driven UI Engine with live data, validation, and multi-platform support
358 lines (357 loc) • 12.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.dataLayer = exports.DataLayer = void 0;
const logger_1 = require("./logger");
class DataLayer {
constructor() {
Object.defineProperty(this, "dataSources", {
enumerable: true,
configurable: true,
writable: true,
value: new Map()
});
Object.defineProperty(this, "dataStates", {
enumerable: true,
configurable: true,
writable: true,
value: new Map()
});
Object.defineProperty(this, "subscriptions", {
enumerable: true,
configurable: true,
writable: true,
value: new Map()
});
Object.defineProperty(this, "cache", {
enumerable: true,
configurable: true,
writable: true,
value: new Map()
});
Object.defineProperty(this, "apiClient", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "stateManager", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "reactivityEngine", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "dataValidator", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.apiClient = this.createAPIClient();
this.stateManager = this.createStateManager();
this.reactivityEngine = this.createReactivityEngine();
this.dataValidator = this.createDataValidator();
logger_1.logger.debug('DataLayer initialized');
}
registerDataSource(path, config) {
this.dataSources.set(path, config);
logger_1.logger.debug(`Data source registered: ${path} (type: ${config.type})`);
}
unregisterDataSource(path) {
this.dataSources.delete(path);
this.dataStates.delete(path);
this.clearCache(path);
logger_1.logger.debug(`Data source unregistered: ${path}`);
}
async getData(path, options) {
const config = this.dataSources.get(path);
if (!config) {
throw new Error(`Data source not found: ${path}`);
}
const mergedConfig = { ...config, ...options };
// Проверяем кэш
if (mergedConfig.cache) {
const cached = this.getFromCache(path);
if (cached) {
return cached;
}
}
try {
let data;
switch (mergedConfig.type) {
case 'api':
data = await this.fetchFromAPI(path, mergedConfig);
break;
case 'local':
data = await this.getFromLocal(path, mergedConfig);
break;
case 'websocket':
data = await this.getFromWebSocket(path, mergedConfig);
break;
case 'file':
data = await this.getFromFile(path, mergedConfig);
break;
default:
throw new Error(`Unsupported data source type: ${mergedConfig.type}`);
}
// Трансформация данных
if (mergedConfig.transform) {
data = mergedConfig.transform(data);
}
// Кэширование
if (mergedConfig.cache) {
this.setCache(path, data, 300000); // 5 минут по умолчанию
}
// Обновление состояния
this.updateDataState(path, {
loading: false,
data,
lastUpdated: Date.now(),
error: null,
source: config.type
});
// Уведомление подписчиков
this.notifySubscribers(path, data);
return data;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.updateDataState(path, {
loading: false,
error: errorMessage,
data: null,
lastUpdated: Date.now(),
source: config.type
});
logger_1.logger.error(`Failed to get data: ${path} - ${error instanceof Error ? error.message : String(error)}`);
throw error;
}
}
subscribe(path, callback) {
const subscriptionId = `${path}-${Date.now()}-${Math.random()}`;
const subscription = {
id: subscriptionId,
path,
callback,
active: true
};
if (!this.subscriptions.has(path)) {
this.subscriptions.set(path, []);
}
this.subscriptions.get(path).push(subscription);
logger_1.logger.debug(`Subscription created: ${subscriptionId} for ${path}`);
return subscription;
}
unsubscribe(subscriptionId) {
for (const [path, subs] of this.subscriptions.entries()) {
const index = subs.findIndex(sub => sub.id === subscriptionId);
if (index !== -1) {
subs.splice(index, 1);
if (subs.length === 0) {
this.subscriptions.delete(path);
}
logger_1.logger.debug(`Subscription removed: ${subscriptionId}`);
break;
}
}
}
getFromCache(path) {
const cached = this.cache.get(path);
if (cached && Date.now() - cached.timestamp < cached.ttl) {
return cached.data;
}
if (cached) {
this.cache.delete(path);
}
return null;
}
setCache(path, data, ttl) {
this.cache.set(path, {
data,
timestamp: Date.now(),
ttl
});
}
clearCache(path) {
if (path) {
this.cache.delete(path);
}
else {
this.cache.clear();
}
}
createAPIClient() {
return {
get: async (url) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
},
post: async (url, data) => {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
},
put: async (url, data) => {
const response = await fetch(url, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
},
delete: async (url) => {
const response = await fetch(url, { method: 'DELETE' });
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
};
}
createStateManager() {
return {
getState: (path) => this.dataStates.get(path),
setState: (path, state) => {
const current = this.dataStates.get(path) || {
loading: false,
error: null,
data: null,
lastUpdated: Date.now(),
source: 'api'
};
const newState = { ...current, ...state };
this.dataStates.set(path, newState);
},
getAllStates: () => new Map(this.dataStates)
};
}
createReactivityEngine() {
return {
subscribe: (path, callback) => {
const id = `${path}-reactive-${Date.now()}`;
this.subscribe(path, callback);
return id;
},
unsubscribe: (id) => this.unsubscribe(id),
notify: (path, data) => this.notifySubscribers(path, data)
};
}
createDataValidator() {
return {
validate: (data) => data !== null && data !== undefined,
sanitize: (data) => data
};
}
async fetchFromAPI(path, config) {
if (!config.url) {
throw new Error('URL is required for API data source');
}
return this.apiClient.get(config.url);
}
async getFromLocal(path, config) {
// Локальные данные - возвращаем пустой объект
return {};
}
async getFromWebSocket(path, config) {
// WebSocket - возвращаем пустой объект
return {};
}
async getFromFile(path, config) {
// Файловые данные - возвращаем пустой объект
return {};
}
updateDataState(path, updates) {
const current = this.dataStates.get(path) || {
loading: false,
error: null,
data: null,
lastUpdated: Date.now(),
source: 'api'
};
const newState = { ...current, ...updates };
this.dataStates.set(path, newState);
}
notifySubscribers(path, data) {
const subs = this.subscriptions.get(path);
if (subs) {
const state = this.dataStates.get(path);
subs.forEach(sub => {
if (sub.active) {
try {
sub.callback(data, state || {
loading: false,
error: null,
data: null,
lastUpdated: Date.now(),
source: 'api'
});
}
catch (error) {
logger_1.logger.error(`Error in subscription callback: ${sub.id} - ${error instanceof Error ? error.message : String(error)}`);
}
}
});
}
}
getState(path) {
return this.dataStates.get(path);
}
getAllStates() {
return new Map(this.dataStates);
}
clearAllData() {
this.dataStates.clear();
this.cache.clear();
this.subscriptions.clear();
}
getStats() {
return {
dataSources: this.dataSources.size,
states: this.dataStates.size,
subscriptions: Array.from(this.subscriptions.values()).flat().length,
cache: this.cache.size
};
}
// === МЕТОДЫ ИНТЕРФЕЙСА IDataLayer ===
subscribeToData(path, callback) {
return this.subscribe(path, callback);
}
getDataState(path) {
return this.getState(path);
}
getDataStats() {
return this.getStats();
}
async renderWithData(spec, adapterId) {
// Простая реализация - возвращаем компонент с данными
const component = spec.component;
const data = spec.data || {};
// Здесь должна быть логика рендеринга с данными
// Пока возвращаем объект с компонентом и данными
return {
component,
data,
adapterId,
timestamp: Date.now()
};
}
}
exports.DataLayer = DataLayer;
exports.dataLayer = new DataLayer();