UNPKG

userface

Version:

Universal Data-Driven UI Engine with live data, validation, and multi-platform support

358 lines (357 loc) 12.4 kB
"use strict"; 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();