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
Markdown
# 🎯 Типизированные элементы в 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)! 🚀