UNPKG

cs-element

Version:

Advanced reactive data management library with state machines, blueprints, persistence, compression, networking, and multithreading support

817 lines (711 loc) 25.6 kB
# 🎯 Типизированные элементы в CSElement Типизированные элементы обеспечивают строгую типизацию данных, валидацию и автодополнение. Они превращают динамические структуры в надежные, типобезопасные компоненты. ## 🎯 Основы типизации ### Создание схемы типа ```typescript import { CSElement, TypedElementSchema } from 'cs-element'; // Определяем схему пользователя const UserSchema: TypedElementSchema<User> = { name: 'User', version: '1.0.0', abstract: false, sealed: false, fields: { id: { name: 'id', type: 'string', required: true, unique: true, validation: { pattern: /^user_\d+$/ } }, email: { name: 'email', type: 'string', required: true, unique: true, validation: { pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ } }, name: { name: 'name', type: 'string', required: true, minLength: 2, maxLength: 50 }, age: { name: 'age', type: 'number', required: false, min: 0, max: 150 }, role: { name: 'role', type: 'string', required: true, enum: ['admin', 'user', 'moderator'], defaultValue: 'user' }, isActive: { name: 'isActive', type: 'boolean', required: true, defaultValue: true }, metadata: { name: 'metadata', type: 'object', required: false, defaultValue: {} } } }; interface User { id: string; email: string; name: string; age?: number; role: 'admin' | 'user' | 'moderator'; isActive: boolean; metadata?: Record<string, any>; } ``` ### Создание типизированных элементов ```typescript // Регистрируем схему в менеджере const typedManager = CSElement.typedElements; typedManager.registerSchema(UserSchema); // Создаем типизированный элемент const user = await typedManager.createElement<User>('User', { id: 'user_001', email: 'alice@example.com', name: 'Alice Johnson', age: 28, role: 'admin' }); console.log('Создан пользователь:', user.getTypedData()); // Типизированный доступ к данным console.log('Email:', user.getField('email')); // Автодополнение работает! console.log('Роль:', user.getField('role')); ``` ## 🏗️ Наследование типов ### Базовые и производные типы ```typescript // Базовая схема сотрудника const EmployeeSchema: TypedElementSchema<Employee> = { name: 'Employee', version: '1.0.0', abstract: true, // Абстрактный тип fields: { employeeId: { name: 'employeeId', type: 'string', required: true, unique: true }, firstName: { name: 'firstName', type: 'string', required: true }, lastName: { name: 'lastName', type: 'string', required: true }, department: { name: 'department', type: 'string', required: true }, salary: { name: 'salary', type: 'number', required: true, min: 0 } } }; // Схема разработчика (наследует от Employee) const DeveloperSchema: TypedElementSchema<Developer> = { name: 'Developer', version: '1.0.0', extends: 'Employee', // Наследование fields: { programmingLanguages: { name: 'programmingLanguages', type: 'array', required: true, defaultValue: [] }, experience: { name: 'experience', type: 'number', required: true, min: 0 }, seniority: { name: 'seniority', type: 'string', required: true, enum: ['junior', 'middle', 'senior', 'lead'] } } }; // Схема менеджера (тоже наследует от Employee) const ManagerSchema: TypedElementSchema<Manager> = { name: 'Manager', version: '1.0.0', extends: 'Employee', fields: { teamSize: { name: 'teamSize', type: 'number', required: true, min: 1 }, budget: { name: 'budget', type: 'number', required: false, min: 0 } } }; interface Employee { employeeId: string; firstName: string; lastName: string; department: string; salary: number; } interface Developer extends Employee { programmingLanguages: string[]; experience: number; seniority: 'junior' | 'middle' | 'senior' | 'lead'; } interface Manager extends Employee { teamSize: number; budget?: number; } // Регистрируем схемы typedManager.registerSchema(EmployeeSchema); typedManager.registerSchema(DeveloperSchema); typedManager.registerSchema(ManagerSchema); // Создаем типизированных сотрудников const developer = await typedManager.createElement<Developer>('Developer', { employeeId: 'DEV001', firstName: 'John', lastName: 'Doe', department: 'Engineering', salary: 80000, programmingLanguages: ['TypeScript', 'Python', 'Go'], experience: 5, seniority: 'senior' }); const manager = await typedManager.createElement<Manager>('Manager', { employeeId: 'MGR001', firstName: 'Jane', lastName: 'Smith', department: 'Engineering', salary: 120000, teamSize: 8, budget: 500000 }); // Проверка типов console.log('Разработчик:', developer.isInstanceOf('Developer')); // true console.log('Сотрудник:', developer.isInstanceOf('Employee')); // true console.log('Менеджер:', developer.isInstanceOf('Manager')); // false ``` ## 🔍 Валидация и проверки ### Автоматическая валидация ```typescript // Попытка создать невалидного пользователя try { const invalidUser = await typedManager.createElement<User>('User', { id: 'invalid_id', // Не соответствует паттерну email: 'not-an-email', // Невалидный email name: 'A', // Слишком короткое имя age: -5, // Отрицательный возраст role: 'superadmin' // Недопустимая роль }); } catch (error) { console.log('Ошибки валидации:', error.validationErrors); // Выведет подробные ошибки по каждому полю } // Валидация отдельных полей const user = await typedManager.createElement<User>('User', { id: 'user_002', email: 'bob@example.com', name: 'Bob' }); // Проверяем конкретное поле const emailValidation = await user.validateField('email', 'invalid-email'); console.log('Email валиден:', emailValidation.isValid); console.log('Ошибки email:', emailValidation.errors); // Полная валидация элемента const fullValidation = await user.validateType(); console.log('Элемент валиден:', fullValidation.isValid); ``` ### Кастомная валидация ```typescript // Схема с кастомной валидацией const ProductSchema: TypedElementSchema<Product> = { name: 'Product', version: '1.0.0', fields: { sku: { name: 'sku', type: 'string', required: true, validation: { custom: (value: string) => { // Кастомная логика валидации SKU if (!value.startsWith('PRD-')) { return { valid: false, message: 'SKU должен начинаться с PRD-' }; } if (value.length !== 10) { return { valid: false, message: 'SKU должен быть длиной 10 символов' }; } return { valid: true }; } } }, price: { name: 'price', type: 'number', required: true, validation: { custom: (value: number, element: any) => { const category = element.getField('category'); const minPrice = category === 'premium' ? 100 : 10; if (value < minPrice) { return { valid: false, message: `Цена для категории ${category} должна быть не менее ${minPrice}` }; } return { valid: true }; } } }, category: { name: 'category', type: 'string', required: true, enum: ['basic', 'standard', 'premium'] } } }; interface Product { sku: string; price: number; category: 'basic' | 'standard' | 'premium'; } ``` ## 🚀 Продвинутые возможности ### Типизированные коллекции ```typescript class TypedCollection<T> extends CSElement { private schema: TypedElementSchema<T>; private typedManager: any; constructor(name: string, schema: TypedElementSchema<T>) { super(name); this.schema = schema; this.typedManager = CSElement.typedElements; this.typedManager.registerSchema(schema); } async addTypedElement(data: Partial<T>): Promise<TypedElement<T>> { const element = await this.typedManager.createElement<T>(this.schema.name, data); await this.addElement(element.id, { element }); return element; } getTypedElements(): TypedElement<T>[] { return this.getAllElements() .filter(el => el instanceof TypedElement) .map(el => el as TypedElement<T>); } queryTyped(predicate: (item: T) => boolean): TypedElement<T>[] { return this.getTypedElements().filter(el => { const data = el.getTypedData(); return predicate(data as T); }); } async updateElement(id: string, updates: Partial<T>): Promise<boolean> { const element = this.getElement(id); if (element instanceof TypedElement) { await element.setTypedData(updates); return true; } return false; } validateAll(): Promise<ValidationResult[]> { const elements = this.getTypedElements(); return Promise.all(elements.map(el => el.validateType())); } } // Использование типизированной коллекции const userCollection = new TypedCollection<User>('Users', UserSchema); // Добавляем пользователей с автоматической валидацией const alice = await userCollection.addTypedElement({ id: 'user_003', email: 'alice@company.com', name: 'Alice Cooper', role: 'admin' }); const bob = await userCollection.addTypedElement({ id: 'user_004', email: 'bob@company.com', name: 'Bob Wilson', role: 'user', age: 35 }); // Типизированные запросы const admins = userCollection.queryTyped(user => user.role === 'admin'); const adults = userCollection.queryTyped(user => (user.age || 0) >= 18); console.log('Администраторы:', admins.length); console.log('Взрослые пользователи:', adults.length); ``` ### Миграции схем ```typescript // Версия 1.0.0 схемы пользователя const UserSchemaV1: TypedElementSchema<UserV1> = { name: 'User', version: '1.0.0', fields: { id: { name: 'id', type: 'string', required: true }, name: { name: 'name', type: 'string', required: true }, email: { name: 'email', type: 'string', required: true } } }; // Версия 2.0.0 - добавили новые поля const UserSchemaV2: TypedElementSchema<UserV2> = { name: 'User', version: '2.0.0', fields: { id: { name: 'id', type: 'string', required: true }, firstName: { name: 'firstName', type: 'string', required: true }, lastName: { name: 'lastName', type: 'string', required: true }, email: { name: 'email', type: 'string', required: true }, role: { name: 'role', type: 'string', required: true, defaultValue: 'user' }, createdAt: { name: 'createdAt', type: 'string', required: true } }, migrations: { '1.0.0': { up: (oldData: UserV1) => { const nameParts = oldData.name.split(' '); return { id: oldData.id, firstName: nameParts[0] || oldData.name, lastName: nameParts.slice(1).join(' ') || '', email: oldData.email, role: 'user', createdAt: new Date().toISOString() }; }, down: (newData: UserV2) => { return { id: newData.id, name: `${newData.firstName} ${newData.lastName}`.trim(), email: newData.email }; } } } }; interface UserV1 { id: string; name: string; email: string; } interface UserV2 { id: string; firstName: string; lastName: string; email: string; role: string; createdAt: string; } ``` ## 🎯 Полный пример - Система управления сотрудниками ```typescript import { CSElement, TypedElementSchema } from 'cs-element'; // Определяем все схемы const PersonSchema: TypedElementSchema<Person> = { name: 'Person', version: '1.0.0', abstract: true, fields: { id: { name: 'id', type: 'string', required: true, unique: true }, firstName: { name: 'firstName', type: 'string', required: true }, lastName: { name: 'lastName', type: 'string', required: true }, email: { name: 'email', type: 'string', required: true, unique: true }, phone: { name: 'phone', type: 'string', required: false }, birthDate: { name: 'birthDate', type: 'string', required: false } } }; const EmployeeSchema: TypedElementSchema<Employee> = { name: 'Employee', version: '1.0.0', extends: 'Person', fields: { employeeId: { name: 'employeeId', type: 'string', required: true, unique: true }, department: { name: 'department', type: 'string', required: true }, position: { name: 'position', type: 'string', required: true }, salary: { name: 'salary', type: 'number', required: true, min: 0 }, hireDate: { name: 'hireDate', type: 'string', required: true }, isActive: { name: 'isActive', type: 'boolean', required: true, defaultValue: true } } }; const DepartmentSchema: TypedElementSchema<Department> = { name: 'Department', version: '1.0.0', fields: { id: { name: 'id', type: 'string', required: true, unique: true }, name: { name: 'name', type: 'string', required: true }, budget: { name: 'budget', type: 'number', required: true, min: 0 }, managerId: { name: 'managerId', type: 'string', required: false }, employeeCount: { name: 'employeeCount', type: 'number', required: true, defaultValue: 0 } } }; interface Person { id: string; firstName: string; lastName: string; email: string; phone?: string; birthDate?: string; } interface Employee extends Person { employeeId: string; department: string; position: string; salary: number; hireDate: string; isActive: boolean; } interface Department { id: string; name: string; budget: number; managerId?: string; employeeCount: number; } class EmployeeManagementSystem extends CSElement { private typedManager: any; private employees: TypedCollection<Employee>; private departments: TypedCollection<Department>; constructor() { super('EmployeeManagementSystem'); this.typedManager = CSElement.typedElements; this.initializeSchemas(); this.initializeCollections(); this.setupReactivity(); } private initializeSchemas() { this.typedManager.registerSchema(PersonSchema); this.typedManager.registerSchema(EmployeeSchema); this.typedManager.registerSchema(DepartmentSchema); } private initializeCollections() { this.employees = new TypedCollection<Employee>('Employees', EmployeeSchema); this.departments = new TypedCollection<Department>('Departments', DepartmentSchema); this.withChild(this.employees); this.withChild(this.departments); } private setupReactivity() { // Автоматически обновляем количество сотрудников в отделах this.employees.on('element:added', () => this.updateDepartmentCounts()); this.employees.on('element:removed', () => this.updateDepartmentCounts()); this.employees.on('data:changed', (employee, key) => { if (key === 'department') { this.updateDepartmentCounts(); } }); } private async updateDepartmentCounts() { const allEmployees = this.employees.getTypedElements(); const departmentCounts = new Map<string, number>(); // Подсчитываем сотрудников по отделам allEmployees.forEach(emp => { const dept = emp.getField('department'); if (dept) { departmentCounts.set(dept, (departmentCounts.get(dept) || 0) + 1); } }); // Обновляем счетчики в отделах const allDepartments = this.departments.getTypedElements(); for (const dept of allDepartments) { const deptName = dept.getField('name'); const count = departmentCounts.get(deptName) || 0; await dept.setField('employeeCount', count); } } async addEmployee(employeeData: Omit<Employee, 'isActive'>): Promise<Employee> { const employee = await this.employees.addTypedElement({ ...employeeData, isActive: true }); console.log(`✅ Сотрудник добавлен: ${employee.getField('firstName')} ${employee.getField('lastName')}`); return employee; } async addDepartment(departmentData: Omit<Department, 'employeeCount'>): Promise<Department> { const department = await this.departments.addTypedElement({ ...departmentData, employeeCount: 0 }); console.log(`🏢 Отдел создан: ${department.getField('name')}`); return department; } async promoteEmployee(employeeId: string, newPosition: string, newSalary: number): Promise<boolean> { const employee = this.employees.getElement(employeeId); if (employee instanceof TypedElement) { const oldPosition = employee.getField('position'); const oldSalary = employee.getField('salary'); await employee.setField('position', newPosition); await employee.setField('salary', newSalary); console.log(`🚀 Повышение: ${employee.getField('firstName')} ${employee.getField('lastName')}`); console.log(` ${oldPosition} → ${newPosition}`); console.log(` ${oldSalary}₽ → ${newSalary}₽`); return true; } return false; } async transferEmployee(employeeId: string, newDepartment: string): Promise<boolean> { const employee = this.employees.getElement(employeeId); if (employee instanceof TypedElement) { const oldDepartment = employee.getField('department'); await employee.setField('department', newDepartment); console.log(`🔄 Перевод: ${employee.getField('firstName')} ${employee.getField('lastName')}`); console.log(` ${oldDepartment} → ${newDepartment}`); return true; } return false; } getEmployeesByDepartment(departmentName: string): Employee[] { return this.employees.queryTyped(emp => emp.department === departmentName) .map(emp => emp.getTypedData() as Employee); } getEmployeesByPosition(position: string): Employee[] { return this.employees.queryTyped(emp => emp.position === position) .map(emp => emp.getTypedData() as Employee); } getSalaryStatistics() { const employees = this.employees.getTypedElements(); const salaries = employees.map(emp => emp.getField('salary')).filter(Boolean); if (salaries.length === 0) return null; const total = salaries.reduce((sum, salary) => sum + salary, 0); const avg = total / salaries.length; const min = Math.min(...salaries); const max = Math.max(...salaries); return { total, average: avg, minimum: min, maximum: max, count: salaries.length }; } getDepartmentStatistics() { const departments = this.departments.getTypedElements(); return departments.map(dept => ({ name: dept.getField('name'), budget: dept.getField('budget'), employeeCount: dept.getField('employeeCount'), managerId: dept.getField('managerId') })); } async validateAllEmployees(): Promise<{ valid: Employee[], invalid: Array<{ employee: Employee, errors: string[] }> }> { const employees = this.employees.getTypedElements(); const valid: Employee[] = []; const invalid: Array<{ employee: Employee, errors: string[] }> = []; for (const emp of employees) { const validation = await emp.validateType(); if (validation.isValid) { valid.push(emp.getTypedData() as Employee); } else { invalid.push({ employee: emp.getTypedData() as Employee, errors: validation.errors.map(e => e.message) }); } } return { valid, invalid }; } generateReport(): string { const employeeCount = this.employees.elementsCount(); const departmentCount = this.departments.elementsCount(); const salaryStats = this.getSalaryStatistics(); const deptStats = this.getDepartmentStatistics(); let report = `📊 Отчет по системе управления сотрудниками\n`; report += `═══════════════════════════════════════════\n\n`; report += `👥 Общая информация:\n`; report += ` Всего сотрудников: ${employeeCount}\n`; report += ` Всего отделов: ${departmentCount}\n\n`; if (salaryStats) { report += `💰 Статистика зарплат:\n`; report += ` Общий фонд: ${salaryStats.total.toLocaleString()}₽\n`; report += ` Средняя зарплата: ${Math.round(salaryStats.average).toLocaleString()}₽\n`; report += ` Минимальная: ${salaryStats.minimum.toLocaleString()}₽\n`; report += ` Максимальная: ${salaryStats.maximum.toLocaleString()}₽\n\n`; } report += `🏢 Отделы:\n`; deptStats.forEach(dept => { report += ` ${dept.name}: ${dept.employeeCount} сотр., бюджет ${dept.budget.toLocaleString()}₽\n`; }); return report; } } // Использование системы const hrSystem = new EmployeeManagementSystem(); // Создаем отделы await hrSystem.addDepartment({ id: 'eng', name: 'Engineering', budget: 2000000, managerId: 'emp_001' }); await hrSystem.addDepartment({ id: 'sales', name: 'Sales', budget: 1500000 }); // Добавляем сотрудников await hrSystem.addEmployee({ id: 'person_001', employeeId: 'emp_001', firstName: 'John', lastName: 'Smith', email: 'john.smith@company.com', department: 'Engineering', position: 'Senior Developer', salary: 120000, hireDate: '2023-01-15' }); await hrSystem.addEmployee({ id: 'person_002', employeeId: 'emp_002', firstName: 'Jane', lastName: 'Doe', email: 'jane.doe@company.com', department: 'Sales', position: 'Sales Manager', salary: 100000, hireDate: '2023-03-01' }); // Операции с сотрудниками await hrSystem.promoteEmployee('emp_001', 'Tech Lead', 140000); await hrSystem.transferEmployee('emp_002', 'Engineering'); // Получаем статистику console.log('\n' + hrSystem.generateReport()); // Валидация const validation = await hrSystem.validateAllEmployees(); console.log(`\n✅ Валидных сотрудников: ${validation.valid.length}`); console.log(`❌ Невалидных сотрудников: ${validation.invalid.length}`); ``` ## 🎉 Заключение Типизированные элементы в CSElement обеспечивают: - **🛡️ Безопасность типов** - автодополнение и проверка во время компиляции - **✅ Автоматическая валидация** - проверка данных при создании и изменении - **🏗️ Наследование** - создание иерархий типов - **🔄 Миграции** - безопасное обновление схем данных - **📊 Типизированные коллекции** - строго типизированные списки **Готовы изучить batch операции?** Переходите к [следующему разделу](05-batching.md)! 🚀