UNPKG

@hf-chimera/store

Version:

Cross-end reactivity API

599 lines (490 loc) 16.7 kB
# Chimera Store A cross-platform, reactive cache management library with intelligent deduplication and efficient data synchronization. Chimera Store provides a powerful API for managing cached data with built-in querying, filtering, ordering, and real-time updates. ## Features - **🔄 Cross-Platform Reactivity**: Works seamlessly across web, mobile, and desktop platforms - **💾 Intelligent Cache Management**: Automatic deduplication and memory-efficient caching - **🔍 Advanced Querying**: Built-in filtering, and sorting support (Pagination in development) - **⚡ Real-Time Updates**: Event-driven architecture with automatic cache invalidation - **🎯 Type Safety**: Full TypeScript support with comprehensive type definitions - **🛡️ Error Handling**: Robust error handling with detailed error messages - **📦 Modular Architecture**: Composable components for flexible integration - **🌐 Universal Compatibility**: Works with any data source (REST APIs, GraphQL, WebSockets, etc.) ## Installation ```bash npm install @hf-chimera/store # or yarn add @hf-chimera/store # or pnpm add @hf-chimera/store ``` ## Quick Start ### Basic Setup ```typescript import { ChimeraStore } from '@hf-chimera/store'; // Define your entity types type User = { id: string; name: string; email: string; age: number; }; type Post = { id: string; title: string; content: string; authorId: string; createdAt: Date; }; // Define your entity map type EntityMap = { users: User; posts: Post; }; // Create store configuration const store = new ChimeraStore<EntityMap>({ query: { defaults: { trustQuery: true, // idGetter can be a string (object key) or a function // String: Uses the specified property as the ID (e.g., 'id', 'uuid', 'key') // Function: Custom ID extraction logic (entityName, value) => string | number idGetter: 'id', }, entities: { // Define configuration for each entity in your EntityMap // Each entity must have its own fetchers, updaters, deleters, and creators users: { collectionFetcher: async (params, requestParams) => { const response = await fetch(`/api/users`, { signal: requestParams.signal, }); return { data: await response.json() }; }, itemFetcher: async (params, requestParams) => { const response = await fetch(`/api/users/${params.id}`, { signal: requestParams.signal, }); return { data: await response.json() }; }, itemUpdater: async (item, requestParams) => { const response = await fetch(`/api/users/${item.id}`, { method: 'PUT', body: JSON.stringify(item), signal: requestParams.signal, }); return { data: await response.json() }; }, itemDeleter: async (id, requestParams) => { await fetch(`/api/users/${id}`, { method: 'DELETE', signal: requestParams.signal, }); return { result: { id, success: true } }; }, itemCreator: async (item, requestParams) => { const response = await fetch(`/api/users`, { method: 'POST', body: JSON.stringify(item), signal: requestParams.signal, }); return { data: await response.json() }; }, }, posts: { collectionFetcher: async (params, requestParams) => { const response = await fetch(`/api/posts`, { signal: requestParams.signal, }); return { data: await response.json() }; }, itemFetcher: async (params, requestParams) => { const response = await fetch(`/api/posts/${params.id}`, { signal: requestParams.signal, }); return { data: await response.json() }; }, itemUpdater: async (item, requestParams) => { const response = await fetch(`/api/posts/${item.id}`, { method: 'PUT', body: JSON.stringify(item), signal: requestParams.signal, }); return { data: await response.json() }; }, itemDeleter: async (id, requestParams) => { await fetch(`/api/posts/${id}`, { method: 'DELETE', signal: requestParams.signal, }); return { result: { id, success: true } }; }, itemCreator: async (item, requestParams) => { const response = await fetch(`/api/posts`, { method: 'POST', body: JSON.stringify(item), signal: requestParams.signal, }); return { data: await response.json() }; }, }, }, }, }); ``` ### Working with Data ```typescript import { ChimeraStore, chimeraCreateConjunction, chimeraCreateOperator, chimeraCreateOrderBy, ChimeraOrderNulls } from '@hf-chimera/store'; // Get a repository for a specific entity const userRepo = store.from('users'); const postRepo = store.from('posts'); // Create a new user const newUserQuery = userRepo.createItem({ name: 'John Doe', email: 'john@example.com', age: 30, }); // Listen for creation events newUserQuery.on('created', (query) => { console.log('User created:', query.data); }); // Get a specific user const userQuery = userRepo.getItem('user-123'); // Listen for data updates userQuery.on('ready', (query) => { console.log('User loaded:', query.data); }); // Update user data userQuery.mutable.name = 'Jane Doe'; await userQuery.commit(); // Get a collection with filtering and sorting const activeUsersQuery = userRepo.getCollection({ filter: chimeraCreateConjunction('and', [ chimeraCreateOperator('gte', 'age', 18), ]), order: [ chimeraCreateOrderBy('name', false, ChimeraOrderNulls.Last), ], }); // Listen for collection updates activeUsersQuery.on('updated', (query, items, oldItems) => { console.log('Active users updated:', items); }); // External updates (e.g., from WebSocket) store.updateOne('users', { id: 'user-123', name: 'Updated Name', email: 'updated@example.com', age: 31, }); ``` ## Core Concepts ### Store Architecture Chimera Store is built around several key concepts: 1. **Store**: The main entry point that manages all entities 2. **Repository**: Entity-specific data management with query capabilities 3. **Query**: Represents a specific data request (single item or collection) 4. **Cache**: Intelligent deduplication and memory management 5. **Events**: Real-time updates and state synchronization ### Query Types - **Item Query**: Manages a single entity instance - **Collection Query**: Manages a filtered/sorted collection of entities ### Filtering System Chimera Store provides a powerful filtering system with support for: - **Operators**: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `contains`, `startsWith`, `endsWith`, `in`, `notIn` - **Conjunctions**: `and`, `or`, `not` - **Custom Operators**: Extensible operator system for custom logic - **Utility Functions**: Use `chimeraCreateOperator` and `chimeraCreateConjunction` to build filters ### Ordering System Flexible ordering with support for: - **Multiple Fields**: Sort by multiple properties - **Direction**: Ascending/descending order - **Null Handling**: Configurable null value positioning using `ChimeraOrderNulls.First` or `ChimeraOrderNulls.Last` - **Utility Functions**: Use `chimeraCreateOrderBy` to build order descriptors ## API Reference ### ChimeraStore The main store class that manages all entities and provides cross-entity operations. #### Constructor ```typescript const store = new ChimeraStore<EntityMap>(config) ``` #### Methods - `from<EntityName>(entityName: EntityName)`: Get repository for specific entity - `updateOne<EntityName>(entityName: EntityName, item: EntityMap[EntityName])`: Update single item - `updateMany<EntityName>(entityName: EntityName, items: Iterable<EntityMap[EntityName]>)`: Update multiple items - `deleteOne<EntityName>(entityName: EntityName, id: ChimeraEntityId)`: Delete single item - `deleteMany<EntityName>(entityName: EntityName, ids: Iterable<ChimeraEntityId>)`: Delete multiple items ### ChimeraEntityRepository Entity-specific repository with query capabilities. #### Methods - `createItem(item: DeepPartial<Item>, meta?: any)`: Create new item - `getItem(id: ChimeraEntityId, meta?: any)`: Get single item - `getCollection(params: ChimeraCollectionParams)`: Get filtered/sorted collection ### ChimeraItemQuery Represents a single item query with full CRUD operations. #### Properties - `data`: Current item data (throws if not ready) - `mutable`: Mutable reference for updates - `state`: Current query state - `ready`: Whether data is available - `inProgress`: Whether operation is in progress #### Methods - `refetch(force?: boolean)`: Refetch data - `update(item: Item, force?: boolean)`: Update item - `mutate(mutator: (draft: Item) => Item, force?: boolean)`: Update using mutator function - `commit(force?: boolean)`: Commit mutable changes - `delete(force?: boolean)`: Delete item #### Events - `initialized`: Query initialized - `created`: Item created - `ready`: Data ready - `updated`: Data updated - `selfUpdated`: Self-initiated update - `deleted`: Item deleted - `selfDeleted`: Self-initiated deletion - `error`: Error occurred ### ChimeraCollectionQuery Represents a collection query with filtering and sorting. #### Properties - `length`: Number of items - `state`: Current query state - `ready`: Whether data is available - `inProgress`: Whether operation is in progress #### Methods - `refetch(force?: boolean)`: Refetch data - `update(item: Item)`: Update single item - `batchedUpdate(items: Iterable<Item>)`: Update multiple items - `delete(id: ChimeraEntityId)`: Delete single item - `batchedDelete(ids: Iterable<ChimeraEntityId>)`: Delete multiple items - `create(item: DeepPartial<Item>)`: Create new item - `batchedCreate(items: Iterable<DeepPartial<Item>>)`: Create multiple items #### Array-like Methods Collection queries implement the standard Array interface: - `at(index: number)`: Get item at index - `find(predicate)`: Find item by predicate - `filter(predicate)`: Filter items - `map(transform)`: Transform items - `forEach(callback)`: Iterate over items - `slice(start?, end?)`: Get subset of items - And many more... #### Events - `initialized`: Query initialized - `ready`: Data ready - `updated`: Data updated - `selfUpdated`: Self-initiated update - `itemAdded`: Item added - `itemUpdated`: Item updated - `selfItemUpdated`: Self-initiated item update - `itemDeleted`: Item deleted - `selfItemDeleted`: Self-initiated item deletion - `error`: Error occurred ## Advanced Usage ### Custom Filter Operators ```typescript const customFilterConfig = { ...chimeraDefaultFilterConfig, operators: { ...chimeraDefaultFilterConfig.operators, // Custom operator for text search textSearch: (text: string, searchTerm: string) => text.toLowerCase().includes(searchTerm.toLowerCase()), }, }; const store = new ChimeraStore<EntityMap, typeof customFilterConfig.operators>({ filter: customFilterConfig, // ... other config }); ``` ### Complex Filtering Examples ```typescript import { chimeraCreateConjunction, chimeraCreateOperator, chimeraCreateOrderBy, ChimeraOrderNulls } from '@hf-chimera/store'; // Simple filter: users with age >= 18 const adultUsers = userRepo.getCollection({ filter: chimeraCreateOperator('gte', 'age', 18), }); // Complex filter: active users with specific age range and email domain const activeUsers = userRepo.getCollection({ filter: chimeraCreateConjunction('and', [ chimeraCreateOperator('gte', 'age', 18), chimeraCreateOperator('lte', 'age', 65), chimeraCreateOperator('endsWith', 'email', '@company.com'), chimeraCreateOperator('eq', 'isActive', true), ]), }); // OR filter: users with specific names or email domains const specificUsers = userRepo.getCollection({ filter: chimeraCreateConjunction('or', [ chimeraCreateOperator('in', 'name', ['John', 'Jane', 'Bob']), chimeraCreateOperator('endsWith', 'email', '@gmail.com'), ]), }); // Nested filters: complex business logic const premiumUsers = userRepo.getCollection({ filter: chimeraCreateConjunction('and', [ chimeraCreateOperator('gte', 'age', 21), chimeraCreateConjunction('or', [ chimeraCreateOperator('gte', 'subscriptionLevel', 'premium'), chimeraCreateOperator('gte', 'totalSpent', 1000), ]), chimeraCreateOperator('neq', 'status', 'suspended'), ]), }); // Text search with multiple conditions const searchResults = userRepo.getCollection({ filter: chimeraCreateConjunction('and', [ chimeraCreateConjunction('or', [ chimeraCreateOperator('contains', 'name', searchTerm), chimeraCreateOperator('contains', 'email', searchTerm), ]), chimeraCreateOperator('eq', 'isActive', true), ]), order: [ chimeraCreateOrderBy('name', false, ChimeraOrderNulls.Last), chimeraCreateOrderBy('createdAt', true, ChimeraOrderNulls.Last), // newest first ], }); ``` ### Custom Order Comparators ```typescript const customOrderConfig = { ...chimeraDefaultOrderConfig, primitiveComparator: (a: unknown, b: unknown, nulls: ChimeraOrderNulls) => { // Custom comparison logic if (typeof a === 'string' && typeof b === 'string') { return a.localeCompare(b, undefined, { numeric: true }); } return chimeraDefaultOrderConfig.primitiveComparator(a, b, nulls); }, }; ``` ### Event Handling ```typescript // Listen to store-level events store.on('itemAdded', (repository, item) => { console.log('Item added to', repository, item); }); // Listen to repository events const userRepo = store.from('users'); userRepo.on('itemUpdated', (repo, item, oldItem) => { console.log('User updated:', item, 'was:', oldItem); }); // Listen to query events const userQuery = userRepo.getItem('user-123'); userQuery.on('updated', (query, item, oldItem) => { console.log('Query updated:', item); }); ``` ### Optimistic Updates ```typescript // Optimistic update with rollback const userQuery = userRepo.getItem('user-123'); // Update optimistically userQuery.mutable.name = 'New Name'; try { await userQuery.commit(); console.log('Update successful'); } catch (error) { // Rollback on error await userQuery.refetch(); console.log('Update failed, rolled back'); } ``` ## Configuration Options ### Query Configuration ```typescript type ConfigExample = { query: { defaults: { trustQuery: boolean; // Trust external data providers idGetter: ((entityName: string, value: unknown) => string | number) | string; // Default ID getter collectionFetcher?: (params: any, request: { signal?: AbortSignal }) => Promise<{ data: any[] }>; itemFetcher?: (params: any, request: { signal?: AbortSignal }) => Promise<{ data: any }>; itemUpdater?: (item: any, request: { signal?: AbortSignal }) => Promise<{ data: any }>; itemDeleter?: (id: string | number, request: { signal?: AbortSignal }) => Promise<{ result?: any }>; itemCreator?: (item: any, request: { signal?: AbortSignal }) => Promise<{ data: any }>; // ... batched operations }; entities: { [entityName: string]: { // Entity-specific overrides }; }; }; }; ``` ### Filter Configuration ```typescript type FilterConfigExample = { filter: { operators: { eq: <T>(a: T, b: T) => boolean; neq: <T>(a: T, b: T) => boolean; gt?: (a: number, b: number) => boolean; // ... custom operators }; getFilterKey?: (input: unknown) => string; // Cache key generator for filters getOperatorKey?: (input: unknown) => string; // Cache key generator for operators }; }; ``` ### Order Configuration ```typescript type OrderConfigExample = { order: { primitiveComparator: (a: unknown, b: unknown, nulls: ChimeraOrderNulls) => number; // Custom comparator getKey: (input: unknown) => string; // Cache key generator }; }; ``` ## Performance Considerations ### Memory Management - Chimera Store uses weak references for automatic memory cleanup - Queries are automatically cached and deduplicated - Stale references are cleaned up automatically ### Caching Strategy - Collection queries are cached by a filter/order combination - Item queries are cached by ID - Cache keys are generated automatically for optimal performance ### Update Optimization - Batch operations for multiple updates - Optimistic updates for better UX ## Browser Support - **Modern Browsers**: Full support for ES2021+ features - **Node.js**: 14.17.0+ with `--harmony-weak-refs` flag, 16.0.0+ stable - **TypeScript**: 4.5+ recommended ## Contributing 1. Fork the repository 2. Create a feature branch 3. Make your changes 4. Add tests for new functionality 5. Submit a pull request ## License MIT License — see [LICENSE](LICENSE) file for details. ## Support - **Issues**: [GitHub Issues](https://github.com/hf-chimera/store/issues) - **Documentation**: [GitHub Wiki](https://github.com/hf-chimera/store/wiki) - **Discussions**: [GitHub Discussions](https://github.com/hf-chimera/store/discussions)