/** * Entities Store */ import { ref, computed, readonly } from 'vue' import { defineStore } from 'pinia' import { entityService } from '../services' import { EntityObject } from '../models' import type { SourceSelector, ListFilter, ListSort, ListRange } from '../types/common' export const useEntitiesStore = defineStore('chronoEntitiesStore', () => { // State const _entities = ref>({}) const transceiving = ref(false) /** * Get count of entities in store */ const count = computed(() => Object.keys(_entities.value).length) /** * Check if any entities are present in store */ const has = computed(() => count.value > 0) /** * Get all entities present in store */ const entities = computed(() => Object.values(_entities.value)) /** * Get a specific entity from store, with optional retrieval * * @param provider - provider identifier * @param service - service identifier * @param collection - collection identifier * @param identifier - entity identifier * @param retrieve - Retrieve behavior: true = fetch if missing or refresh, false = cache only * * @returns Entity object or null */ function entity(provider: string, service: string | number, collection: string | number, identifier: string | number, retrieve: boolean = false): EntityObject | null { const key = identifierKey(provider, service, collection, identifier) if (retrieve === true && !_entities.value[key]) { console.debug(`[Chrono Manager][Store] - Force fetching entity "${key}"`) fetch(provider, service, collection, [identifier]) } return _entities.value[key] || null } /** * Get all entities for a specific collection * * @param provider - provider identifier * @param service - service identifier * @param collection - collection identifier * @param retrieve - Retrieve behavior: true = fetch if missing or refresh, false = cache only * * @returns Array of entity objects */ function entitiesForCollection(provider: string, service: string | number, collection: string | number, retrieve: boolean = false): EntityObject[] { const collectionKeyPrefix = `${provider}:${service}:${collection}:` const collectionEntities = Object.entries(_entities.value) .filter(([key]) => key.startsWith(collectionKeyPrefix)) .map(([_, entity]) => entity) if (retrieve === true && collectionEntities.length === 0) { console.debug(`[Chrono Manager][Store] - Force fetching entities for collection "${provider}:${service}:${collection}"`) const sources: SourceSelector = { [provider]: { [String(service)]: { [String(collection)]: true } } } list(sources) } return collectionEntities } /** * Create unique key for an entity */ function identifierKey(provider: string, service: string | number, collection: string | number, identifier: string | number): string { return `${provider}:${service}:${collection}:${identifier}` } // Actions /** * Retrieve all or specific entities, optionally filtered by source selector * * @param sources - optional source selector * @param filter - optional list filter * @param sort - optional list sort * @param range - optional list range * * @returns Promise with entity object list keyed by identifier */ async function list(sources?: SourceSelector, filter?: ListFilter, sort?: ListSort, range?: ListRange): Promise> { transceiving.value = true try { const response = await entityService.list({ sources, filter, sort, range }) // Flatten nested structure: provider:service:collection:entity -> "provider:service:collection:entity": object const entities: Record = {} Object.entries(response).forEach(([providerId, providerServices]) => { Object.entries(providerServices).forEach(([serviceId, serviceCollections]) => { Object.entries(serviceCollections).forEach(([collectionId, collectionEntities]) => { Object.entries(collectionEntities).forEach(([entityId, entityData]) => { const key = identifierKey(providerId, serviceId, collectionId, entityId) entities[key] = entityData }) }) }) }) // Merge retrieved entities into state _entities.value = { ..._entities.value, ...entities } console.debug('[Chrono Manager][Store] - Successfully retrieved', Object.keys(entities).length, 'entities') return entities } catch (error: any) { console.error('[Chrono Manager][Store] - Failed to retrieve entities:', error) throw error } finally { transceiving.value = false } } /** * Retrieve specific entities by provider, service, collection, and identifiers * * @param provider - provider identifier * @param service - service identifier * @param collection - collection identifier * @param identifiers - array of entity identifiers to fetch * * @returns Promise with entity objects keyed by identifier */ async function fetch(provider: string, service: string | number, collection: string | number, identifiers: (string | number)[]): Promise> { transceiving.value = true try { const response = await entityService.fetch({ provider, service, collection, identifiers }) // Merge fetched entities into state const entities: Record = {} Object.entries(response).forEach(([identifier, entityData]) => { const key = identifierKey(provider, service, collection, identifier) entities[key] = entityData _entities.value[key] = entityData }) console.debug('[Chrono Manager][Store] - Successfully fetched', Object.keys(entities).length, 'entities') return entities } catch (error: any) { console.error('[Chrono Manager][Store] - Failed to fetch entities:', error) throw error } finally { transceiving.value = false } } /** * Retrieve entity availability status for a given source selector * * @param sources - source selector to check availability for * * @returns Promise with entity availability status */ async function extant(sources: SourceSelector) { transceiving.value = true try { const response = await entityService.extant({ sources }) console.debug('[Chrono Manager][Store] - Successfully checked entity availability') return response } catch (error: any) { console.error('[Chrono Manager][Store] - Failed to check entity availability:', error) throw error } finally { transceiving.value = false } } /** * Create a new entity with given provider, service, collection, and data * * @param provider - provider identifier for the new entity * @param service - service identifier for the new entity * @param collection - collection identifier for the new entity * @param data - entity properties for creation * * @returns Promise with created entity object */ async function create(provider: string, service: string | number, collection: string | number, data: any): Promise { transceiving.value = true try { const response = await entityService.create({ provider, service, collection, properties: data }) // Add created entity to state const key = identifierKey(response.provider, response.service, response.collection, response.identifier) _entities.value[key] = response console.debug('[Chrono Manager][Store] - Successfully created entity:', key) return response } catch (error: any) { console.error('[Chrono Manager][Store] - Failed to create entity:', error) throw error } finally { transceiving.value = false } } /** * Update an existing entity with given provider, service, collection, identifier, and data * * @param provider - provider identifier for the entity to update * @param service - service identifier for the entity to update * @param collection - collection identifier for the entity to update * @param identifier - entity identifier for the entity to update * @param data - entity properties for update * * @returns Promise with updated entity object */ async function update(provider: string, service: string | number, collection: string | number, identifier: string | number, data: any): Promise { transceiving.value = true try { const response = await entityService.update({ provider, service, collection, identifier, properties: data }) // Update entity in state const key = identifierKey(response.provider, response.service, response.collection, response.identifier) _entities.value[key] = response console.debug('[Chrono Manager][Store] - Successfully updated entity:', key) return response } catch (error: any) { console.error('[Chrono Manager][Store] - Failed to update entity:', error) throw error } finally { transceiving.value = false } } /** * Delete an entity by provider, service, collection, and identifier * * @param provider - provider identifier for the entity to delete * @param service - service identifier for the entity to delete * @param collection - collection identifier for the entity to delete * @param identifier - entity identifier for the entity to delete * * @returns Promise with deletion result */ async function remove(provider: string, service: string | number, collection: string | number, identifier: string | number): Promise { transceiving.value = true try { const response = await entityService.delete({ provider, service, collection, identifier }) // Remove entity from state const key = identifierKey(provider, service, collection, identifier) delete _entities.value[key] console.debug('[Chrono Manager][Store] - Successfully deleted entity:', key) return response } catch (error: any) { console.error('[Chrono Manager][Store] - Failed to delete entity:', error) throw error } finally { transceiving.value = false } } /** * Retrieve delta changes for entities * * @param sources - source selector for delta check * * @returns Promise with delta changes (additions, modifications, deletions) * * Note: Delta returns only identifiers, not full entities. * Caller should fetch full entities for additions/modifications separately. */ async function delta(sources: SourceSelector) { transceiving.value = true try { const response = await entityService.delta({ sources }) // Process delta and update store Object.entries(response).forEach(([provider, providerData]) => { // Skip if no changes for provider if (providerData === false) return Object.entries(providerData).forEach(([service, serviceData]) => { // Skip if no changes for service if (serviceData === false) return Object.entries(serviceData).forEach(([collection, collectionData]) => { // Skip if no changes for collection if (collectionData === false) return // Process deletions (remove from store) if (collectionData.deletions && collectionData.deletions.length > 0) { collectionData.deletions.forEach((identifier) => { const key = identifierKey(provider, service, collection, identifier) delete _entities.value[key] }) } // Note: additions and modifications contain only identifiers // The caller should fetch full entities using the fetch() method }) }) }) console.debug('[Chrono Manager][Store] - Successfully processed delta changes') return response } catch (error: any) { console.error('[Chrono Manager][Store] - Failed to process delta:', error) throw error } finally { transceiving.value = false } } // Return public API return { // State (readonly) transceiving: readonly(transceiving), // Getters count, has, entities, entitiesForCollection, // Actions entity, list, fetch, extant, create, update, delete: remove, delta, } })