cs-element
Version:
Advanced reactive data management library with state machines, blueprints, persistence, compression, networking, and multithreading support
865 lines (708 loc) • 26.9 kB
Markdown
# 📋 Управление коллекциями в CSElement
CSElement превосходно подходит для работы с коллекциями данных. В этом разделе мы изучим все аспекты создания, управления и оптимизации списков элементов.
## 🎯 Основы работы с коллекциями
### Создание коллекций
```typescript
import { CSElement } from 'cs-element';
// Создаем корневую коллекцию
const library = CSElement.create('Library')
.withData('name', 'Городская библиотека')
.withData('founded', 1945);
// Добавляем коллекции книг
const books = await library.addElement('books');
const authors = await library.addElement('authors');
const categories = await library.addElement('categories');
console.log(`Создана библиотека с ${library.elementsCount()} разделами`);
```
### Добавление элементов в коллекции
```typescript
// Простое добавление
await books.addElement('book1', {
data: new Map([
['title', 'Война и мир'],
['author', 'Лев Толстой'],
['year', 1869],
['pages', 1225],
['genre', 'роман'],
['available', true]
])
});
// Добавление с индексом (для сортировки)
await books.addElement('book2', {
index: 0, // Будет первой книгой
data: new Map([
['title', 'Преступление и наказание'],
['author', 'Федор Достоевский'],
['year', 1866],
['pages', 671],
['genre', 'роман'],
['available', false]
])
});
// Fluent API для быстрого создания
books.withChild('book3')
.withData('title', 'Мастер и Маргарита')
.withData('author', 'Михаил Булгаков')
.withData('year', 1967)
.withData('pages', 384)
.withData('genre', 'фантастика')
.withData('available', true);
```
## 🔍 Мощная система поиска
### Базовые селекторы
```typescript
// Поиск по атрибутам
const availableBooks = books.query('[available=true]');
const novels = books.query('[genre=роман]');
const oldBooks = books.query('[year<1900]');
// Комбинированные селекторы
const availableNovels = books.query('[available=true][genre=роман]');
const modernBooks = books.query('[year>=1950]');
console.log(`Доступных романов: ${availableNovels.length}`);
```
### Продвинутые запросы
```typescript
// Поиск по частичному совпадению
const tolstoyBooks = books.findElements(book =>
book.getData('author')?.includes('Толстой')
);
// Поиск с множественными условиями
const longBooks = books.findElements(book => {
const pages = book.getData('pages');
const available = book.getData('available');
return pages > 500 && available;
});
// Поиск с сортировкой
const booksByYear = books.getAllElements()
.sort((a, b) => a.getData('year') - b.getData('year'));
// Поиск с пагинацией
function getBooks(page: number, pageSize: number = 10) {
const start = page * pageSize;
const end = start + pageSize;
return books.getAllElements().slice(start, end);
}
const firstPage = getBooks(0);
console.log(`Первая страница: ${firstPage.length} книг`);
```
### Сложные запросы с QueryEngine
```typescript
// Создаем селектор с помощью QueryEngine
const complexQuery = CSElement.createSelector()
.where('available', true)
.and('pages', '>', 300)
.and('year', '>=', 1950)
.orderBy('year', 'desc')
.limit(5);
const selectedBooks = books.query(complexQuery);
console.log(`Найдено книг: ${selectedBooks.length}`);
// Группировка по жанрам
const booksByGenre = new Map();
books.getAllElements().forEach(book => {
const genre = book.getData('genre');
if (!booksByGenre.has(genre)) {
booksByGenre.set(genre, []);
}
booksByGenre.get(genre).push(book);
});
console.log('Книги по жанрам:', Object.fromEntries(booksByGenre));
```
## 🎨 Продвинутые техники управления коллекциями
### Создание умных коллекций с автоматической сортировкой
```typescript
class SmartCollection extends CSElement {
private sortKey: string;
private sortOrder: 'asc' | 'desc';
constructor(name: string, sortKey: string = 'name', sortOrder: 'asc' | 'desc' = 'asc') {
super(name);
this.sortKey = sortKey;
this.sortOrder = sortOrder;
this.setupAutoSort();
}
private setupAutoSort() {
// Автоматическая сортировка при добавлении элементов
this.on('element:added', () => {
this.sort();
});
this.on('data:changed', (element, key) => {
if (key === this.sortKey) {
this.sort();
}
});
}
private sort() {
const elements = this.getAllElements();
elements.sort((a, b) => {
const aValue = a.getData(this.sortKey);
const bValue = b.getData(this.sortKey);
if (this.sortOrder === 'asc') {
return aValue < bValue ? -1 : aValue > bValue ? 1 : 0;
} else {
return aValue > bValue ? -1 : aValue < bValue ? 1 : 0;
}
});
// Обновляем индексы
elements.forEach((element, index) => {
element._index = index;
});
}
setSortOrder(key: string, order: 'asc' | 'desc' = 'asc') {
this.sortKey = key;
this.sortOrder = order;
this.sort();
}
}
// Использование
const sortedBooks = new SmartCollection('SortedBooks', 'title', 'asc');
```
### Коллекции с фильтрацией в реальном времени
```typescript
class FilteredCollection extends CSElement {
private filters: Map<string, (element: CSElement) => boolean> = new Map();
private filteredElements: CSElement[] = [];
addFilter(name: string, predicate: (element: CSElement) => boolean) {
this.filters.set(name, predicate);
this.updateFiltered();
}
removeFilter(name: string) {
this.filters.delete(name);
this.updateFiltered();
}
private updateFiltered() {
this.filteredElements = this.getAllElements().filter(element => {
return Array.from(this.filters.values()).every(filter => filter(element));
});
this.emit('filtered:updated', this.filteredElements);
}
getFiltered(): readonly CSElement[] {
return this.filteredElements;
}
getFilteredCount(): number {
return this.filteredElements.length;
}
// Переопределяем методы для обновления фильтра
async addElement(value: any, options: any = {}) {
const result = await super.addElement(value, options);
this.updateFiltered();
return result;
}
async removeElement(nameOrIndex: any) {
const result = await super.removeElement(nameOrIndex);
this.updateFiltered();
return result;
}
}
// Использование
const filteredBooks = new FilteredCollection('FilteredBooks');
// Добавляем фильтры
filteredBooks.addFilter('available', book => book.getData('available') === true);
filteredBooks.addFilter('modern', book => book.getData('year') >= 1950);
filteredBooks.addFilter('fiction', book =>
['роман', 'фантастика', 'детектив'].includes(book.getData('genre'))
);
// Подписываемся на обновления
filteredBooks.on('filtered:updated', (filtered) => {
console.log(`Отфильтровано книг: ${filtered.length}`);
});
```
## 📊 Работа с большими коллекциями
### Виртуализация и ленивая загрузка
```typescript
class VirtualizedCollection extends CSElement {
private pageSize: number;
private loadedPages: Set<number> = new Set();
private totalItems: number = 0;
constructor(name: string, pageSize: number = 50) {
super(name);
this.pageSize = pageSize;
}
async loadPage(pageNumber: number): Promise<CSElement[]> {
if (this.loadedPages.has(pageNumber)) {
return this.getPage(pageNumber);
}
// Симуляция загрузки данных
const pageData = await this.fetchPageData(pageNumber);
for (const item of pageData) {
await this.addElement(`item_${pageNumber}_${item.id}`, {
data: new Map(Object.entries(item))
});
}
this.loadedPages.add(pageNumber);
return this.getPage(pageNumber);
}
getPage(pageNumber: number): CSElement[] {
const start = pageNumber * this.pageSize;
const end = start + this.pageSize;
return this.getAllElements().slice(start, end);
}
async getVisibleRange(startIndex: number, endIndex: number): Promise<CSElement[]> {
const startPage = Math.floor(startIndex / this.pageSize);
const endPage = Math.floor(endIndex / this.pageSize);
// Загружаем необходимые страницы
for (let page = startPage; page <= endPage; page++) {
if (!this.loadedPages.has(page)) {
await this.loadPage(page);
}
}
return this.getAllElements().slice(startIndex, endIndex + 1);
}
private async fetchPageData(pageNumber: number): Promise<any[]> {
// Здесь был бы реальный API вызов
return Array.from({ length: this.pageSize }, (_, i) => ({
id: pageNumber * this.pageSize + i,
title: `Book ${pageNumber * this.pageSize + i}`,
author: `Author ${Math.floor(Math.random() * 100)}`,
year: 1900 + Math.floor(Math.random() * 124)
}));
}
}
```
### Индексирование для быстрого поиска
```typescript
class IndexedCollection extends CSElement {
private indexes: Map<string, Map<any, CSElement[]>> = new Map();
createIndex(fieldName: string) {
const index = new Map();
this.getAllElements().forEach(element => {
const value = element.getData(fieldName);
if (value !== undefined) {
if (!index.has(value)) {
index.set(value, []);
}
index.get(value).push(element);
}
});
this.indexes.set(fieldName, index);
// Обновляем индекс при изменении данных
this.on('element:added', (element) => {
this.updateIndex(fieldName, element);
});
this.on('data:changed', (element, key, newValue, oldValue) => {
if (key === fieldName) {
this.removeFromIndex(fieldName, element, oldValue);
this.updateIndex(fieldName, element);
}
});
}
private updateIndex(fieldName: string, element: CSElement) {
const index = this.indexes.get(fieldName);
if (!index) return;
const value = element.getData(fieldName);
if (value !== undefined) {
if (!index.has(value)) {
index.set(value, []);
}
index.get(value).push(element);
}
}
private removeFromIndex(fieldName: string, element: CSElement, value: any) {
const index = this.indexes.get(fieldName);
if (!index || !index.has(value)) return;
const elements = index.get(value);
const elementIndex = elements.indexOf(element);
if (elementIndex > -1) {
elements.splice(elementIndex, 1);
if (elements.length === 0) {
index.delete(value);
}
}
}
findByIndex(fieldName: string, value: any): CSElement[] {
const index = this.indexes.get(fieldName);
return index?.get(value) || [];
}
getIndexStats(fieldName: string): { uniqueValues: number, totalElements: number } {
const index = this.indexes.get(fieldName);
if (!index) return { uniqueValues: 0, totalElements: 0 };
const uniqueValues = index.size;
const totalElements = Array.from(index.values())
.reduce((sum, arr) => sum + arr.length, 0);
return { uniqueValues, totalElements };
}
}
// Использование
const indexedBooks = new IndexedCollection('IndexedBooks');
indexedBooks.createIndex('author');
indexedBooks.createIndex('genre');
indexedBooks.createIndex('year');
// Быстрый поиск по индексу
const tolstoyBooks = indexedBooks.findByIndex('author', 'Лев Толстой');
const novels = indexedBooks.findByIndex('genre', 'роман');
console.log('Статистика индексов:');
console.log('Авторы:', indexedBooks.getIndexStats('author'));
console.log('Жанры:', indexedBooks.getIndexStats('genre'));
```
## 🔄 Реактивные коллекции
### Live Collections с автообновлением
```typescript
class LiveCollection extends CSElement {
private liveQuery: any;
private selector: string;
constructor(name: string, sourceCollection: CSElement, selector: string) {
super(name);
this.selector = selector;
this.setupLiveQuery(sourceCollection);
}
private setupLiveQuery(sourceCollection: CSElement) {
this.liveQuery = CSElement.createLiveQuery(this.selector, {
root: sourceCollection,
debounce: 50
});
this.liveQuery.onResults((results: CSElement[]) => {
// Очищаем текущие элементы
this.clear();
// Добавляем новые результаты
results.forEach(async (element, index) => {
await this.addElement(`item_${index}`, {
data: element.data
});
});
this.emit('collection:updated', results);
});
CSElement.liveQueries.start(this.liveQuery.id);
}
private clear() {
const elements = [...this.getAllElements()];
elements.forEach(element => {
this.removeElement(element);
});
}
updateSelector(newSelector: string) {
this.selector = newSelector;
CSElement.liveQueries.stop(this.liveQuery.id);
// Создаем новый live query с обновленным селектором
// this.setupLiveQuery(sourceCollection); // Нужна ссылка на источник
}
destroy() {
if (this.liveQuery) {
CSElement.liveQueries.stop(this.liveQuery.id);
}
super.destroy();
}
}
```
### Computed коллекции
```typescript
class ComputedCollection extends CSElement {
private computedProperty: any;
private computeFunction: () => CSElement[];
constructor(name: string, computeFunction: () => CSElement[]) {
super(name);
this.computeFunction = computeFunction;
this.setupComputed();
}
private setupComputed() {
this.computedProperty = CSElement.computed(() => {
return this.computeFunction();
});
// Обновляем коллекцию при изменении computed свойства
CSElement.watch([this.computedProperty.id], (newValue) => {
this.updateCollection(newValue);
});
}
private updateCollection(newElements: CSElement[]) {
// Очищаем текущие элементы
this.clear();
// Добавляем новые элементы
newElements.forEach(async (element, index) => {
await this.addElement(`computed_${index}`, {
data: element.data
});
});
this.emit('computed:updated', newElements);
}
private clear() {
const elements = [...this.getAllElements()];
elements.forEach(element => {
this.removeElement(element);
});
}
get value(): CSElement[] {
return this.computedProperty.value;
}
}
// Использование
const availableBooks = new ComputedCollection('AvailableBooks', () => {
return books.query('[available=true]');
});
const popularBooks = new ComputedCollection('PopularBooks', () => {
return books.getAllElements()
.filter(book => book.getData('rating') >= 4.5)
.sort((a, b) => b.getData('rating') - a.getData('rating'))
.slice(0, 10);
});
```
## 🎯 Полный пример - Система управления библиотекой
```typescript
import { CSElement } from 'cs-element';
class LibrarySystem {
private library: CSElement;
private books: IndexedCollection;
private authors: CSElement;
private members: CSElement;
private loans: CSElement;
// Реактивные коллекции
private availableBooks: LiveCollection;
private overdueLoans: LiveCollection;
private popularBooks: ComputedCollection;
constructor() {
this.initializeLibrary();
this.setupCollections();
this.setupReactivity();
this.setupEventHandlers();
}
private initializeLibrary() {
this.library = CSElement.create('LibrarySystem')
.withData('name', 'Центральная библиотека')
.withData('established', 1925)
.withData('totalBooks', 0)
.withData('totalMembers', 0);
}
private setupCollections() {
// Основные коллекции
this.books = new IndexedCollection('Books');
this.authors = new CSElement('Authors');
this.members = new CSElement('Members');
this.loans = new CSElement('Loans');
// Добавляем в библиотеку
this.library.withChild(this.books);
this.library.withChild(this.authors);
this.library.withChild(this.members);
this.library.withChild(this.loans);
// Создаем индексы для быстрого поиска
this.books.createIndex('author');
this.books.createIndex('genre');
this.books.createIndex('isbn');
this.books.createIndex('available');
}
private setupReactivity() {
// Доступные книги
this.availableBooks = new LiveCollection(
'AvailableBooks',
this.books,
'[available=true]'
);
// Просроченные займы
this.overdueLoans = new LiveCollection(
'OverdueLoans',
this.loans,
`[dueDate<${Date.now()}][returned=false]`
);
// Популярные книги (computed)
this.popularBooks = new ComputedCollection('PopularBooks', () => {
return this.books.getAllElements()
.filter(book => book.getData('loanCount') > 0)
.sort((a, b) => b.getData('loanCount') - a.getData('loanCount'))
.slice(0, 20);
});
}
private setupEventHandlers() {
// Обновляем статистику при добавлении книг
this.books.on('element:added', () => {
this.library.setData('totalBooks', this.books.elementsCount());
});
// Обновляем статистику при добавлении читателей
this.members.on('element:added', () => {
this.library.setData('totalMembers', this.members.elementsCount());
});
// Обрабатываем займы книг
this.loans.on('element:added', (loan) => {
const bookId = loan.getData('bookId');
const book = this.books.getElementById(bookId);
if (book) {
book.setData('available', false);
const loanCount = book.getData('loanCount') || 0;
book.setData('loanCount', loanCount + 1);
}
});
// Обрабатываем возврат книг
this.loans.on('data:changed', (loan, key, newValue) => {
if (key === 'returned' && newValue === true) {
const bookId = loan.getData('bookId');
const book = this.books.getElementById(bookId);
if (book) {
book.setData('available', true);
}
}
});
}
// Методы управления книгами
async addBook(bookData: {
title: string;
author: string;
isbn: string;
genre: string;
publishYear: number;
pages: number;
}) {
const bookId = `book_${bookData.isbn}`;
const book = await this.books.addElement(bookId, {
data: new Map([
['title', bookData.title],
['author', bookData.author],
['isbn', bookData.isbn],
['genre', bookData.genre],
['publishYear', bookData.publishYear],
['pages', bookData.pages],
['available', true],
['loanCount', 0],
['addedDate', new Date().toISOString()]
])
});
// Добавляем автора если его нет
await this.ensureAuthorExists(bookData.author);
return book;
}
private async ensureAuthorExists(authorName: string) {
const existingAuthor = this.authors.findElements(author =>
author.getData('name') === authorName
);
if (existingAuthor.length === 0) {
await this.authors.addElement(`author_${Date.now()}`, {
data: new Map([
['name', authorName],
['bookCount', 1]
])
});
} else {
const author = existingAuthor[0];
const bookCount = author.getData('bookCount') || 0;
author.setData('bookCount', bookCount + 1);
}
}
// Методы поиска
searchBooks(query: {
title?: string;
author?: string;
genre?: string;
available?: boolean;
yearFrom?: number;
yearTo?: number;
}): CSElement[] {
let results = this.books.getAllElements();
if (query.title) {
results = results.filter(book =>
book.getData('title')?.toLowerCase().includes(query.title.toLowerCase())
);
}
if (query.author) {
results = this.books.findByIndex('author', query.author);
}
if (query.genre) {
results = this.books.findByIndex('genre', query.genre);
}
if (query.available !== undefined) {
results = this.books.findByIndex('available', query.available);
}
if (query.yearFrom || query.yearTo) {
results = results.filter(book => {
const year = book.getData('publishYear');
if (query.yearFrom && year < query.yearFrom) return false;
if (query.yearTo && year > query.yearTo) return false;
return true;
});
}
return results;
}
// Управление займами
async loanBook(bookId: string, memberId: string, durationDays: number = 14): Promise<boolean> {
const book = this.books.getElementById(bookId);
const member = this.members.getElementById(memberId);
if (!book || !member || !book.getData('available')) {
return false;
}
const loanId = `loan_${Date.now()}`;
const dueDate = new Date();
dueDate.setDate(dueDate.getDate() + durationDays);
await this.loans.addElement(loanId, {
data: new Map([
['bookId', bookId],
['memberId', memberId],
['loanDate', new Date().toISOString()],
['dueDate', dueDate.getTime()],
['returned', false],
['renewalCount', 0]
])
});
return true;
}
async returnBook(loanId: string): Promise<boolean> {
const loan = this.loans.getElementById(loanId);
if (!loan || loan.getData('returned')) {
return false;
}
await loan.setData('returned', true);
await loan.setData('returnDate', new Date().toISOString());
return true;
}
// Получение статистики
getLibraryStats() {
return {
totalBooks: this.books.elementsCount(),
availableBooks: this.availableBooks.elementsCount(),
totalAuthors: this.authors.elementsCount(),
totalMembers: this.members.elementsCount(),
activeLoans: this.loans.query('[returned=false]').length,
overdueLoans: this.overdueLoans.elementsCount(),
popularBooks: this.popularBooks.value.slice(0, 5).map(book => ({
title: book.getData('title'),
author: book.getData('author'),
loanCount: book.getData('loanCount')
}))
};
}
// Получение аналитики по жанрам
getGenreAnalytics() {
const genreStats = new Map();
this.books.getAllElements().forEach(book => {
const genre = book.getData('genre');
if (!genreStats.has(genre)) {
genreStats.set(genre, {
totalBooks: 0,
availableBooks: 0,
totalLoans: 0
});
}
const stats = genreStats.get(genre);
stats.totalBooks++;
if (book.getData('available')) {
stats.availableBooks++;
}
stats.totalLoans += book.getData('loanCount') || 0;
});
return Object.fromEntries(genreStats);
}
}
// Использование системы
const library = new LibrarySystem();
// Добавляем книги
await library.addBook({
title: 'Война и мир',
author: 'Лев Толстой',
isbn: '978-5-17-082417-1',
genre: 'роман',
publishYear: 1869,
pages: 1225
});
await library.addBook({
title: '1984',
author: 'Джордж Оруэлл',
isbn: '978-5-17-082418-8',
genre: 'антиутопия',
publishYear: 1949,
pages: 328
});
// Поиск книг
const novels = library.searchBooks({ genre: 'роман' });
const availableBooks = library.searchBooks({ available: true });
const modernBooks = library.searchBooks({ yearFrom: 1950 });
console.log('Статистика библиотеки:', library.getLibraryStats());
console.log('Аналитика по жанрам:', library.getGenreAnalytics());
```
## 🎉 Заключение
Управление коллекциями в CSElement предоставляет невероятную гибкость и мощь:
- **🔍 Мощный поиск** - от простых селекторов до сложных запросов
- **⚡ Реактивность** - автоматическое обновление при изменениях
- **📊 Индексирование** - быстрый доступ к данным
- **🎯 Виртуализация** - работа с большими объемами данных
- **🔄 Live Collections** - коллекции, обновляющиеся в реальном времени
**Готовы к изучению реактивности?** Переходите к [следующему разделу](03-reactivity.md)! 🚀