refactor: front end

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-03-28 12:47:21 -04:00
parent 28e5cce23a
commit ccb781f933
38 changed files with 4909 additions and 2897 deletions
+212
View File
@@ -0,0 +1,212 @@
/**
* Collections Store
*/
import { ref, computed, readonly } from 'vue'
import { defineStore } from 'pinia'
import { collectionService } from '../services/collectionService'
import type {
SourceSelector,
ListFilter,
ListSort,
CollectionMutableProperties,
CollectionDeleteResponse,
} from '../types'
import { CollectionObject } from '../models/collection'
export const useCollectionsStore = defineStore('documentsCollectionsStore', () => {
// State
const _collections = ref<Record<string, CollectionObject>>({})
const transceiving = ref(false)
// Getters
const count = computed(() => Object.keys(_collections.value).length)
const has = computed(() => count.value > 0)
const collections = computed(() => Object.values(_collections.value))
const collectionsByService = computed(() => {
const groups: Record<string, CollectionObject[]> = {}
Object.values(_collections.value).forEach((collection) => {
const serviceKey = `${collection.provider}:${collection.service}`
const serviceCollections = (groups[serviceKey] ??= [])
serviceCollections.push(collection)
})
return groups
})
function identifierKey(provider: string, service: string | number | null, identifier: string | number | null): string {
return `${provider}:${service ?? ''}:${identifier ?? ''}`
}
function collection(provider: string, service: string | number, identifier: string | number, retrieve: boolean = false): CollectionObject | null {
const key = identifierKey(provider, service, identifier)
if (retrieve === true && !_collections.value[key]) {
console.debug(`[Documents Manager][Store] - Force fetching collection "${key}"`)
fetch(provider, service, identifier)
}
return _collections.value[key] || null
}
function collectionsForService(provider: string, service: string | number): CollectionObject[] {
const serviceKeyPrefix = `${provider}:${service}:`
return Object.entries(_collections.value)
.filter(([key]) => key.startsWith(serviceKeyPrefix))
.map(([_, collectionObj]) => collectionObj)
}
function clearService(provider: string, service: string | number): void {
const serviceKeyPrefix = `${provider}:${service}:`
Object.keys(_collections.value)
.filter((key) => key.startsWith(serviceKeyPrefix))
.forEach((key) => {
delete _collections.value[key]
})
}
function clearAll(): void {
_collections.value = {}
}
// Actions
async function list(sources?: SourceSelector, filter?: ListFilter, sort?: ListSort): Promise<Record<string, CollectionObject>> {
transceiving.value = true
try {
const response = await collectionService.list({ sources, filter, sort })
const hydrated: Record<string, CollectionObject> = {}
Object.entries(response).forEach(([providerId, providerServices]) => {
Object.entries(providerServices).forEach(([serviceId, serviceCollections]) => {
Object.entries(serviceCollections).forEach(([collectionId, collectionObj]) => {
const key = identifierKey(providerId, serviceId, collectionId)
hydrated[key] = collectionObj
})
})
})
_collections.value = { ..._collections.value, ...hydrated }
console.debug('[Documents Manager][Store] - Successfully retrieved', Object.keys(hydrated).length, 'collections')
return hydrated
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to retrieve collections:', error)
throw error
} finally {
transceiving.value = false
}
}
async function fetch(provider: string, service: string | number, identifier: string | number): Promise<CollectionObject> {
transceiving.value = true
try {
const response = await collectionService.fetch({ provider, service, collection: identifier })
const key = identifierKey(response.provider, response.service, response.identifier)
_collections.value[key] = response
console.debug('[Documents Manager][Store] - Successfully fetched collection:', key)
return response
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to fetch collection:', error)
throw error
} finally {
transceiving.value = false
}
}
async function extant(sources: SourceSelector) {
transceiving.value = true
try {
const response = await collectionService.extant({ sources })
console.debug('[Documents Manager][Store] - Successfully checked collection availability')
return response
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to check collection availability:', error)
throw error
} finally {
transceiving.value = false
}
}
async function create(
provider: string,
service: string | number,
collection: string | number | null,
properties: CollectionMutableProperties,
): Promise<CollectionObject> {
transceiving.value = true
try {
const response = await collectionService.create({ provider, service, collection, properties })
const key = identifierKey(response.provider, response.service, response.identifier)
_collections.value[key] = response
console.debug('[Documents Manager][Store] - Successfully created collection:', key)
return response
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to create collection:', error)
throw error
} finally {
transceiving.value = false
}
}
async function update(
provider: string,
service: string | number,
identifier: string | number,
properties: CollectionMutableProperties,
): Promise<CollectionObject> {
transceiving.value = true
try {
const response = await collectionService.update({ provider, service, identifier, properties })
const key = identifierKey(response.provider, response.service, response.identifier)
_collections.value[key] = response
console.debug('[Documents Manager][Store] - Successfully updated collection:', key)
return response
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to update collection:', error)
throw error
} finally {
transceiving.value = false
}
}
async function remove(provider: string, service: string | number, identifier: string | number): Promise<CollectionDeleteResponse> {
transceiving.value = true
try {
const response = await collectionService.delete({ provider, service, identifier })
if (response.success) {
const key = identifierKey(provider, service, identifier)
delete _collections.value[key]
}
console.debug('[Documents Manager][Store] - Successfully deleted collection:', `${provider}:${service}:${identifier}`)
return response
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to delete collection:', error)
throw error
} finally {
transceiving.value = false
}
}
return {
transceiving: readonly(transceiving),
count,
has,
collections,
collectionsByService,
collection,
collectionsForService,
clearService,
clearAll,
list,
fetch,
extant,
create,
update,
delete: remove,
}
})
+334
View File
@@ -0,0 +1,334 @@
/**
* Entities Store
*/
import { ref, computed, readonly } from 'vue'
import { defineStore } from 'pinia'
import { entityService } from '../services/entityService'
import { EntityObject } from '../models'
import type {
SourceSelector,
ListFilter,
ListSort,
ListRange,
DocumentInterface,
EntityDeleteResponse,
EntityDeltaResponse,
} from '../types'
export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
// State
const _entities = ref<Record<string, EntityObject>>({})
const transceiving = ref(false)
// Getters
const count = computed(() => Object.keys(_entities.value).length)
const has = computed(() => count.value > 0)
const entities = computed(() => Object.values(_entities.value))
const entitiesByService = computed(() => {
const groups: Record<string, EntityObject[]> = {}
Object.values(_entities.value).forEach((entity) => {
const serviceKey = `${entity.provider}:${entity.service}`
const serviceEntities = (groups[serviceKey] ??= [])
serviceEntities.push(entity)
})
return groups
})
function identifierKey(
provider: string,
service: string | number,
collection: string | number,
identifier: string | number,
): string {
return `${provider}:${service}:${collection}:${identifier}`
}
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(`[Documents Manager][Store] - Force fetching entity "${key}"`)
fetch(provider, service, collection, [identifier])
}
return _entities.value[key] || null
}
function entitiesForService(provider: string, service: string | number): EntityObject[] {
const serviceKeyPrefix = `${provider}:${service}:`
return Object.entries(_entities.value)
.filter(([key]) => key.startsWith(serviceKeyPrefix))
.map(([_, entityObj]) => entityObj)
}
function entitiesForCollection(provider: string, service: string | number, collection: string | number): EntityObject[] {
const collectionKeyPrefix = `${provider}:${service}:${collection}:`
return Object.entries(_entities.value)
.filter(([key]) => key.startsWith(collectionKeyPrefix))
.map(([_, entityObj]) => entityObj)
}
function clearService(provider: string, service: string | number): void {
const serviceKeyPrefix = `${provider}:${service}:`
Object.keys(_entities.value)
.filter((key) => key.startsWith(serviceKeyPrefix))
.forEach((key) => {
delete _entities.value[key]
})
}
function clearCollection(provider: string, service: string | number, collection: string | number): void {
const collectionKeyPrefix = `${provider}:${service}:${collection}:`
Object.keys(_entities.value)
.filter((key) => key.startsWith(collectionKeyPrefix))
.forEach((key) => {
delete _entities.value[key]
})
}
function clearAll(): void {
_entities.value = {}
}
// Actions
async function list(sources?: SourceSelector, filter?: ListFilter, sort?: ListSort, range?: ListRange): Promise<Record<string, EntityObject>> {
transceiving.value = true
try {
const response = await entityService.list({ sources, filter, sort, range })
const hydrated: Record<string, EntityObject> = {}
Object.entries(response).forEach(([providerId, providerServices]) => {
Object.entries(providerServices).forEach(([serviceId, serviceCollections]) => {
Object.entries(serviceCollections).forEach(([collectionId, collectionEntities]) => {
Object.entries(collectionEntities).forEach(([entityId, entityObj]) => {
const key = identifierKey(providerId, serviceId, collectionId, entityId)
hydrated[key] = entityObj
})
})
})
})
_entities.value = { ..._entities.value, ...hydrated }
console.debug('[Documents Manager][Store] - Successfully retrieved', Object.keys(hydrated).length, 'entities')
return hydrated
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to retrieve entities:', error)
throw error
} finally {
transceiving.value = false
}
}
async function fetch(
provider: string,
service: string | number,
collection: string | number,
identifiers: (string | number)[],
): Promise<Record<string, EntityObject>> {
transceiving.value = true
try {
const response = await entityService.fetch({ provider, service, collection, identifiers })
const hydrated: Record<string, EntityObject> = {}
Object.entries(response).forEach(([identifier, entityObj]) => {
const key = identifierKey(provider, service, collection, identifier)
hydrated[key] = entityObj
_entities.value[key] = entityObj
})
console.debug('[Documents Manager][Store] - Successfully fetched', Object.keys(hydrated).length, 'entities')
return hydrated
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to fetch entities:', error)
throw error
} finally {
transceiving.value = false
}
}
async function extant(sources: SourceSelector) {
transceiving.value = true
try {
const response = await entityService.extant({ sources })
console.debug('[Documents Manager][Store] - Successfully checked entity availability')
return response
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to check entity availability:', error)
throw error
} finally {
transceiving.value = false
}
}
async function create(
provider: string,
service: string | number,
collection: string | number,
properties: DocumentInterface,
options?: Record<string, unknown>,
): Promise<EntityObject> {
transceiving.value = true
try {
const response = await entityService.create({ provider, service, collection, properties, options })
const key = identifierKey(response.provider, response.service, response.collection, response.identifier)
_entities.value[key] = response
console.debug('[Documents Manager][Store] - Successfully created entity:', key)
return response
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to create entity:', error)
throw error
} finally {
transceiving.value = false
}
}
async function update(
provider: string,
service: string | number,
collection: string | number,
identifier: string | number,
properties: DocumentInterface,
): Promise<EntityObject> {
transceiving.value = true
try {
const response = await entityService.update({ provider, service, collection, identifier, properties })
const key = identifierKey(response.provider, response.service, response.collection, response.identifier)
_entities.value[key] = response
console.debug('[Documents Manager][Store] - Successfully updated entity:', key)
return response
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to update entity:', error)
throw error
} finally {
transceiving.value = false
}
}
async function remove(
provider: string,
service: string | number,
collection: string | number,
identifier: string | number,
): Promise<EntityDeleteResponse> {
transceiving.value = true
try {
const response = await entityService.delete({ provider, service, collection, identifier })
if (response.success) {
const key = identifierKey(provider, service, collection, identifier)
delete _entities.value[key]
}
console.debug('[Documents Manager][Store] - Successfully deleted entity:', `${provider}:${service}:${collection}:${identifier}`)
return response
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to delete entity:', error)
throw error
} finally {
transceiving.value = false
}
}
async function delta(sources: SourceSelector): Promise<EntityDeltaResponse> {
transceiving.value = true
try {
const response = await entityService.delta({ sources })
Object.entries(response).forEach(([provider, providerData]) => {
if (providerData === false) return
Object.entries(providerData).forEach(([service, serviceData]) => {
if (serviceData === false) return
Object.entries(serviceData).forEach(([collection, collectionData]) => {
if (collectionData === false) return
collectionData.deletions.forEach((identifier) => {
const key = identifierKey(provider, service, collection, identifier)
delete _entities.value[key]
})
})
})
})
console.debug('[Documents Manager][Store] - Successfully processed entity delta')
return response
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to process entity delta:', error)
throw error
} finally {
transceiving.value = false
}
}
async function read(
provider: string,
service: string | number,
collection: string | number,
identifier: string | number,
): Promise<string | null> {
transceiving.value = true
try {
const response = await entityService.read({ provider, service, collection, identifier })
return response.content
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to read entity:', error)
throw error
} finally {
transceiving.value = false
}
}
async function write(
provider: string,
service: string | number,
collection: string | number,
identifier: string | number,
content: string,
): Promise<number> {
transceiving.value = true
try {
const response = await entityService.write({ provider, service, collection, identifier, content, encoding: 'base64' })
return response.bytesWritten
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to write entity:', error)
throw error
} finally {
transceiving.value = false
}
}
return {
transceiving: readonly(transceiving),
count,
has,
entities,
entitiesByService,
entity,
entitiesForService,
entitiesForCollection,
clearService,
clearCollection,
clearAll,
list,
fetch,
extant,
create,
update,
delete: remove,
delta,
read,
write,
}
})
+3 -1
View File
@@ -1,3 +1,5 @@
export { useProvidersStore } from './providersStore';
export { useServicesStore } from './servicesStore';
export { useNodesStore, ROOT_ID } from './nodesStore';
export { useCollectionsStore } from './collectionsStore';
export { useEntitiesStore } from './entitiesStore';
export { useNodesStore, ROOT_ID } from './nodesStore.ts';
+228 -574
View File
@@ -1,646 +1,322 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { Ref, ComputedRef } from 'vue'
import type { FileNode, FileCollection, FileEntity } from '../types/node'
import type { FilterCondition, SortCondition, RangeCondition } from '../types/common'
import { isFileCollection } from '../types/node'
import { collectionService } from '../services/collectionService'
import { entityService } from '../services/entityService'
import { nodeService } from '../services/nodeService'
import { FileCollectionObject } from '../models/collection'
import { FileEntityObject } from '../models/entity'
/**
* Nodes Store (thin wrapper over collections/entities stores)
*/
import { computed, ref, readonly } from 'vue'
import { defineStore } from 'pinia'
import type {
SourceSelector,
ListFilter,
ListSort,
ListRange,
CollectionMutableProperties,
DocumentInterface,
} from '../types'
import { CollectionObject, EntityObject } from '../models'
import { useCollectionsStore } from './collectionsStore'
import { useEntitiesStore } from './entitiesStore'
// Root collection constant
export const ROOT_ID = '00000000-0000-0000-0000-000000000000'
// Store structure: provider -> service -> nodeId -> node (either collection or entity object)
type NodeRecord = FileCollectionObject | FileEntityObject
type ServiceNodeStore = Record<string, NodeRecord>
type ProviderNodeStore = Record<string, ServiceNodeStore>
type NodeStore = Record<string, ProviderNodeStore>
type NodeRecord = CollectionObject | EntityObject
export const useNodesStore = defineStore('fileNodes', () => {
const nodes: Ref<NodeStore> = ref({})
const syncTokens: Ref<Record<string, Record<string, string>>> = ref({}) // provider -> service -> token
const loading = ref(false)
const error: Ref<string | null> = ref(null)
export const useNodesStore = defineStore('documentsNodesStore', () => {
const collectionsStore = useCollectionsStore()
const entitiesStore = useEntitiesStore()
// Computed: flat list of all nodes
const nodeList: ComputedRef<NodeRecord[]> = computed(() => {
const result: NodeRecord[] = []
Object.values(nodes.value).forEach(providerNodes => {
Object.values(providerNodes).forEach(serviceNodes => {
result.push(...Object.values(serviceNodes))
const error = ref<string | null>(null)
const transceiving = computed(() => collectionsStore.transceiving || entitiesStore.transceiving)
const nodeList = computed<NodeRecord[]>(() => {
return [...collectionsStore.collections, ...entitiesStore.entities]
})
const collectionList = computed<CollectionObject[]>(() => collectionsStore.collections)
const entityList = computed<EntityObject[]>(() => entitiesStore.entities)
function toId(value: string | number | null | undefined): string | null {
if (value === null || value === undefined || value === '') {
return null
}
return String(value)
}
function getServiceNodes(providerId: string, serviceId: string | number): NodeRecord[] {
return [
...collectionsStore.collectionsForService(providerId, serviceId),
...entitiesStore.entitiesForService(providerId, serviceId),
]
}
function getNode(providerId: string, serviceId: string | number, nodeId: string | number): NodeRecord | undefined {
const targetId = String(nodeId)
return getServiceNodes(providerId, serviceId)
.find((node) => String(node.identifier) === targetId)
}
function isRoot(nodeId: string | number | null | undefined): boolean {
const normalized = toId(nodeId)
return normalized === null || normalized === ROOT_ID
}
function getChildren(providerId: string, serviceId: string | number, parentId: string | number | null): NodeRecord[] {
const serviceNodes = getServiceNodes(providerId, serviceId)
if (isRoot(parentId)) {
return serviceNodes.filter((node) => {
const parent = toId(node.collection)
return parent === null || parent === ROOT_ID
})
})
return result
})
}
// Computed: all collections (folders)
const collectionList: ComputedRef<FileCollectionObject[]> = computed(() => {
return nodeList.value.filter(
(node): node is FileCollectionObject => node['@type'] === 'files.collection'
)
})
// Computed: all entities (files)
const entityList: ComputedRef<FileEntityObject[]> = computed(() => {
return nodeList.value.filter(
(node): node is FileEntityObject => node['@type'] === 'files.entity'
)
})
// Get a specific node
const getNode = (
providerId: string,
serviceId: string,
nodeId: string
): NodeRecord | undefined => {
return nodes.value[providerId]?.[serviceId]?.[nodeId]
const targetParent = String(parentId)
return serviceNodes.filter((node) => toId(node.collection) === targetParent)
}
// Get all nodes for a service
const getServiceNodes = (
providerId: string,
serviceId: string
): NodeRecord[] => {
return Object.values(nodes.value[providerId]?.[serviceId] || {})
function getChildCollections(providerId: string, serviceId: string | number, parentId: string | number | null): CollectionObject[] {
return getChildren(providerId, serviceId, parentId)
.filter((node): node is CollectionObject => node instanceof CollectionObject)
}
// Get children of a parent node (or root nodes if parentId is null/ROOT_ID)
const getChildren = (
providerId: string,
serviceId: string,
parentId: string | null
): NodeRecord[] => {
const serviceNodes = nodes.value[providerId]?.[serviceId] || {}
const targetParent = parentId === ROOT_ID ? ROOT_ID : parentId
return Object.values(serviceNodes).filter(node => node.in === targetParent)
function getChildEntities(providerId: string, serviceId: string | number, parentId: string | number | null): EntityObject[] {
return getChildren(providerId, serviceId, parentId)
.filter((node): node is EntityObject => node instanceof EntityObject)
}
// Get child collections (folders)
const getChildCollections = (
providerId: string,
serviceId: string,
parentId: string | null
): FileCollectionObject[] => {
return getChildren(providerId, serviceId, parentId).filter(
(node): node is FileCollectionObject => node['@type'] === 'files.collection'
)
}
// Get child entities (files)
const getChildEntities = (
providerId: string,
serviceId: string,
parentId: string | null
): FileEntityObject[] => {
return getChildren(providerId, serviceId, parentId).filter(
(node): node is FileEntityObject => node['@type'] === 'files.entity'
)
}
// Get path to root (ancestors)
const getPath = (
providerId: string,
serviceId: string,
nodeId: string
): NodeRecord[] => {
function getPath(providerId: string, serviceId: string | number, nodeId: string | number): NodeRecord[] {
const path: NodeRecord[] = []
let currentNode = getNode(providerId, serviceId, nodeId)
while (currentNode) {
path.unshift(currentNode)
if (currentNode.in === null || currentNode.in === ROOT_ID || currentNode.id === ROOT_ID) {
let current = getNode(providerId, serviceId, nodeId)
while (current) {
path.unshift(current)
const parentId = toId(current.collection)
if (parentId === null || parentId === ROOT_ID) {
break
}
currentNode = getNode(providerId, serviceId, currentNode.in)
current = getNode(providerId, serviceId, parentId)
}
return path
}
// Check if a node is the root
const isRoot = (nodeId: string): boolean => {
return nodeId === ROOT_ID
}
// Helper to hydrate a node based on its type
const hydrateNode = (data: FileNode): NodeRecord => {
if (isFileCollection(data)) {
return new FileCollectionObject().fromJson(data)
} else {
return new FileEntityObject().fromJson(data as FileEntity)
}
}
// Set all nodes for a provider/service
const setNodes = (
async function fetchCollections(
providerId: string,
serviceId: string,
data: FileNode[]
) => {
if (!nodes.value[providerId]) {
nodes.value[providerId] = {}
}
const hydrated: ServiceNodeStore = {}
for (const nodeData of data) {
hydrated[nodeData.id] = hydrateNode(nodeData)
}
nodes.value[providerId][serviceId] = hydrated
}
// Add/update a single node
const addNode = (
providerId: string,
serviceId: string,
node: FileNode
) => {
if (!nodes.value[providerId]) {
nodes.value[providerId] = {}
}
if (!nodes.value[providerId][serviceId]) {
nodes.value[providerId][serviceId] = {}
}
nodes.value[providerId][serviceId][node.id] = hydrateNode(node)
}
// Add multiple nodes (handles both array and object formats from API)
const addNodes = (
providerId: string,
serviceId: string,
data: FileNode[] | Record<string, FileNode>
) => {
if (!nodes.value[providerId]) {
nodes.value[providerId] = {}
}
if (!nodes.value[providerId][serviceId]) {
nodes.value[providerId][serviceId] = {}
}
// Handle both array and object (keyed by ID) formats
const nodeArray = Array.isArray(data) ? data : Object.values(data)
for (const nodeData of nodeArray) {
nodes.value[providerId][serviceId][nodeData.id] = hydrateNode(nodeData)
}
}
// Remove a node
const removeNode = (
providerId: string,
serviceId: string,
nodeId: string
) => {
if (nodes.value[providerId]?.[serviceId]) {
delete nodes.value[providerId][serviceId][nodeId]
}
}
// Remove multiple nodes
const removeNodes = (
providerId: string,
serviceId: string,
nodeIds: string[]
) => {
if (nodes.value[providerId]?.[serviceId]) {
for (const id of nodeIds) {
delete nodes.value[providerId][serviceId][id]
serviceId: string | number,
collectionId: string | number | null,
filter?: ListFilter,
sort?: ListSort,
): Promise<CollectionObject[]> {
error.value = null
try {
const sources: SourceSelector = {
[providerId]: {
[String(serviceId)]: collectionId === null
? true
: { [String(collectionId)]: true },
},
}
}
}
// Clear all nodes for a service
const clearServiceNodes = (
providerId: string,
serviceId: string
) => {
if (nodes.value[providerId]) {
delete nodes.value[providerId][serviceId]
}
}
// Clear all nodes
const clearNodes = () => {
nodes.value = {}
}
// Sync token management
const getSyncToken = (
providerId: string,
serviceId: string
): string | undefined => {
return syncTokens.value[providerId]?.[serviceId]
}
const setSyncToken = (
providerId: string,
serviceId: string,
token: string
) => {
if (!syncTokens.value[providerId]) {
syncTokens.value[providerId] = {}
}
syncTokens.value[providerId][serviceId] = token
}
// ==================== API Actions ====================
// Fetch nodes (collections and entities) for a location
const fetchNodes = async (
providerId: string,
serviceId: string,
location?: string | null,
recursive: boolean = false,
filter?: FilterCondition[] | null,
sort?: SortCondition[] | null,
range?: RangeCondition | null
): Promise<NodeRecord[]> => {
loading.value = true
error.value = null
try {
const data = await nodeService.list(
providerId,
serviceId,
location,
recursive,
filter,
sort,
range
)
// API returns object keyed by ID, convert to array
const nodeArray = Array.isArray(data) ? data : Object.values(data)
addNodes(providerId, serviceId, nodeArray)
return nodeArray.map(hydrateNode)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to fetch nodes'
throw e
} finally {
loading.value = false
}
}
// Fetch collections (folders) for a location
const fetchCollections = async (
providerId: string,
serviceId: string,
location?: string | null,
filter?: FilterCondition[] | null,
sort?: SortCondition[] | null
): Promise<FileCollectionObject[]> => {
loading.value = true
error.value = null
try {
const data = await collectionService.list(providerId, serviceId, location, filter, sort)
// API returns object keyed by ID, convert to array
const collectionArray = Array.isArray(data) ? data : Object.values(data)
addNodes(providerId, serviceId, collectionArray)
return collectionArray.map(c => new FileCollectionObject().fromJson(c))
await collectionsStore.list(sources, filter, sort)
return collectionsStore.collectionsForService(providerId, serviceId)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to fetch collections'
throw e
} finally {
loading.value = false
}
}
// Fetch entities (files) for a collection
const fetchEntities = async (
async function fetchEntities(
providerId: string,
serviceId: string,
collection: string,
filter?: FilterCondition[] | null,
sort?: SortCondition[] | null,
range?: RangeCondition | null
): Promise<FileEntityObject[]> => {
loading.value = true
serviceId: string | number,
collectionId: string | number | null,
filter?: ListFilter,
sort?: ListSort,
range?: ListRange,
): Promise<EntityObject[]> {
error.value = null
try {
const data = await entityService.list(providerId, serviceId, collection, filter, sort, range)
// API returns object keyed by ID, convert to array
const entityArray = Array.isArray(data) ? data : Object.values(data)
addNodes(providerId, serviceId, entityArray)
return entityArray.map(e => new FileEntityObject().fromJson(e))
const sources: SourceSelector = {
[providerId]: {
[String(serviceId)]: collectionId === null
? true
: { [String(collectionId)]: true },
},
}
await entitiesStore.list(sources, filter, sort, range)
return entitiesStore.entitiesForCollection(providerId, serviceId, collectionId)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to fetch entities'
throw e
} finally {
loading.value = false
}
}
// Create a collection (folder)
const createCollection = async (
async function fetchNodes(
providerId: string,
serviceId: string,
location: string | null,
data: Partial<FileCollection>,
options?: Record<string, unknown>
): Promise<FileCollectionObject> => {
loading.value = true
serviceId: string | number,
parentId: string | number | null = ROOT_ID,
filter?: ListFilter,
sort?: ListSort,
range?: ListRange,
): Promise<NodeRecord[]> {
error.value = null
try {
const created = await collectionService.create(providerId, serviceId, location, data, options)
addNode(providerId, serviceId, created)
return new FileCollectionObject().fromJson(created)
await Promise.all([
fetchCollections(providerId, serviceId, parentId, filter, sort),
fetchEntities(providerId, serviceId, parentId, filter, sort, range),
])
return getChildren(providerId, serviceId, parentId)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to fetch nodes'
throw e
}
}
async function createCollection(
providerId: string,
serviceId: string | number,
parentCollectionId: string | number | null,
properties: CollectionMutableProperties,
): Promise<CollectionObject> {
error.value = null
try {
return await collectionsStore.create(providerId, serviceId, parentCollectionId, properties)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to create collection'
throw e
} finally {
loading.value = false
}
}
// Create an entity (file)
const createEntity = async (
async function updateCollection(
providerId: string,
serviceId: string,
collection: string | null,
data: Partial<FileEntity>,
options?: Record<string, unknown>
): Promise<FileEntityObject> => {
loading.value = true
serviceId: string | number,
identifier: string | number,
properties: CollectionMutableProperties,
): Promise<CollectionObject> {
error.value = null
try {
const created = await entityService.create(providerId, serviceId, collection, data, options)
addNode(providerId, serviceId, created)
return new FileEntityObject().fromJson(created)
return await collectionsStore.update(providerId, serviceId, identifier, properties)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to update collection'
throw e
}
}
async function deleteCollection(
providerId: string,
serviceId: string | number,
identifier: string | number,
): Promise<boolean> {
error.value = null
try {
const response = await collectionsStore.delete(providerId, serviceId, identifier)
return response.success
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to delete collection'
throw e
}
}
async function createEntity(
providerId: string,
serviceId: string | number,
collectionId: string | number,
properties: DocumentInterface,
options?: Record<string, unknown>,
): Promise<EntityObject> {
error.value = null
try {
return await entitiesStore.create(providerId, serviceId, collectionId, properties, options)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to create entity'
throw e
} finally {
loading.value = false
}
}
// Modify a collection
const modifyCollection = async (
async function updateEntity(
providerId: string,
serviceId: string,
identifier: string,
data: Partial<FileCollection>
): Promise<FileCollectionObject> => {
loading.value = true
serviceId: string | number,
collectionId: string | number,
identifier: string | number,
properties: DocumentInterface,
): Promise<EntityObject> {
error.value = null
try {
const modified = await collectionService.modify(providerId, serviceId, identifier, data)
addNode(providerId, serviceId, modified)
return new FileCollectionObject().fromJson(modified)
return await entitiesStore.update(providerId, serviceId, collectionId, identifier, properties)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to modify collection'
error.value = e instanceof Error ? e.message : 'Failed to update entity'
throw e
} finally {
loading.value = false
}
}
// Modify an entity
const modifyEntity = async (
async function deleteEntity(
providerId: string,
serviceId: string,
collection: string | null,
identifier: string,
data: Partial<FileEntity>
): Promise<FileEntityObject> => {
loading.value = true
serviceId: string | number,
collectionId: string | number,
identifier: string | number,
): Promise<boolean> {
error.value = null
try {
const modified = await entityService.modify(providerId, serviceId, collection, identifier, data)
addNode(providerId, serviceId, modified)
return new FileEntityObject().fromJson(modified)
const response = await entitiesStore.delete(providerId, serviceId, collectionId, identifier)
return response.success
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to modify entity'
error.value = e instanceof Error ? e.message : 'Failed to delete entity'
throw e
} finally {
loading.value = false
}
}
// Destroy a collection
const destroyCollection = async (
async function readEntity(
providerId: string,
serviceId: string,
identifier: string
): Promise<boolean> => {
loading.value = true
serviceId: string | number,
collectionId: string | number,
identifier: string | number,
): Promise<string | null> {
error.value = null
try {
const success = await collectionService.destroy(providerId, serviceId, identifier)
if (success) {
removeNode(providerId, serviceId, identifier)
}
return success
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to destroy collection'
throw e
} finally {
loading.value = false
}
}
// Destroy an entity
const destroyEntity = async (
providerId: string,
serviceId: string,
collection: string | null,
identifier: string
): Promise<boolean> => {
loading.value = true
error.value = null
try {
const success = await entityService.destroy(providerId, serviceId, collection, identifier)
if (success) {
removeNode(providerId, serviceId, identifier)
}
return success
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to destroy entity'
throw e
} finally {
loading.value = false
}
}
// Copy a collection
const copyCollection = async (
providerId: string,
serviceId: string,
identifier: string,
location?: string | null
): Promise<FileCollectionObject> => {
loading.value = true
error.value = null
try {
const copied = await collectionService.copy(providerId, serviceId, identifier, location)
addNode(providerId, serviceId, copied)
return new FileCollectionObject().fromJson(copied)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to copy collection'
throw e
} finally {
loading.value = false
}
}
// Copy an entity
const copyEntity = async (
providerId: string,
serviceId: string,
collection: string | null,
identifier: string,
destination?: string | null
): Promise<FileEntityObject> => {
loading.value = true
error.value = null
try {
const copied = await entityService.copy(providerId, serviceId, collection, identifier, destination)
addNode(providerId, serviceId, copied)
return new FileEntityObject().fromJson(copied)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to copy entity'
throw e
} finally {
loading.value = false
}
}
// Move a collection
const moveCollection = async (
providerId: string,
serviceId: string,
identifier: string,
location?: string | null
): Promise<FileCollectionObject> => {
loading.value = true
error.value = null
try {
const moved = await collectionService.move(providerId, serviceId, identifier, location)
addNode(providerId, serviceId, moved)
return new FileCollectionObject().fromJson(moved)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to move collection'
throw e
} finally {
loading.value = false
}
}
// Move an entity
const moveEntity = async (
providerId: string,
serviceId: string,
collection: string | null,
identifier: string,
destination?: string | null
): Promise<FileEntityObject> => {
loading.value = true
error.value = null
try {
const moved = await entityService.move(providerId, serviceId, collection, identifier, destination)
addNode(providerId, serviceId, moved)
return new FileEntityObject().fromJson(moved)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to move entity'
throw e
} finally {
loading.value = false
}
}
// Read entity content
const readEntity = async (
providerId: string,
serviceId: string,
collection: string,
identifier: string
): Promise<string | null> => {
loading.value = true
error.value = null
try {
const result = await entityService.read(providerId, serviceId, collection, identifier)
return result.content
return await entitiesStore.read(providerId, serviceId, collectionId, identifier)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to read entity'
throw e
} finally {
loading.value = false
}
}
// Write entity content
const writeEntity = async (
async function writeEntity(
providerId: string,
serviceId: string,
collection: string | null,
identifier: string,
content: string
): Promise<number> => {
loading.value = true
serviceId: string | number,
collectionId: string | number,
identifier: string | number,
content: string,
): Promise<number> {
error.value = null
try {
return await entityService.write(providerId, serviceId, collection, identifier, content)
return await entitiesStore.write(providerId, serviceId, collectionId, identifier, content)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to write entity'
throw e
} finally {
loading.value = false
}
}
// Sync delta changes
const syncDelta = async (
providerId: string,
serviceId: string,
location: string | null,
signature: string,
recursive: boolean = false,
detail: 'ids' | 'full' = 'full'
): Promise<void> => {
loading.value = true
error.value = null
try {
const delta = await nodeService.delta(providerId, serviceId, location, signature, recursive, detail)
// Handle removed nodes
if (delta.removed.length > 0) {
removeNodes(providerId, serviceId, delta.removed)
}
// Handle added/modified nodes
if (detail === 'full') {
const addedNodes = delta.added as FileNode[]
const modifiedNodes = delta.modified as FileNode[]
if (addedNodes.length > 0) {
addNodes(providerId, serviceId, addedNodes)
}
if (modifiedNodes.length > 0) {
addNodes(providerId, serviceId, modifiedNodes)
}
}
// Update sync token
if (delta.signature) {
setSyncToken(providerId, serviceId, delta.signature)
}
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to sync delta'
throw e
} finally {
loading.value = false
}
function clearServiceNodes(providerId: string, serviceId: string | number): void {
collectionsStore.clearService(providerId, serviceId)
entitiesStore.clearService(providerId, serviceId)
}
function clearNodes(): void {
collectionsStore.clearAll()
entitiesStore.clearAll()
}
return {
// State
nodes,
syncTokens,
loading,
error,
// Constants
transceiving: readonly(transceiving),
error: readonly(error),
ROOT_ID,
// Computed
nodeList,
collectionList,
entityList,
// Getters
getNode,
getServiceNodes,
getChildren,
@@ -648,40 +324,18 @@ export const useNodesStore = defineStore('fileNodes', () => {
getChildEntities,
getPath,
isRoot,
// Setters
setNodes,
addNode,
addNodes,
removeNode,
removeNodes,
clearServiceNodes,
clearNodes,
// Sync
getSyncToken,
setSyncToken,
// API Actions - Fetch
fetchNodes,
fetchCollections,
fetchEntities,
// API Actions - Create
createCollection,
updateCollection,
deleteCollection,
createEntity,
// API Actions - Modify
modifyCollection,
modifyEntity,
// API Actions - Destroy
destroyCollection,
destroyEntity,
// API Actions - Copy
copyCollection,
copyEntity,
// API Actions - Move
moveCollection,
moveEntity,
// API Actions - Content
updateEntity,
deleteEntity,
readEntity,
writeEntity,
// API Actions - Sync
syncDelta,
clearServiceNodes,
clearNodes,
}
})
+117 -79
View File
@@ -1,104 +1,142 @@
/**
* Providers Store
*/
import { ref, computed, readonly } from 'vue'
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { Ref, ComputedRef } from 'vue'
import type { ProviderInterface, ProviderRecord, ProviderCapabilitiesInterface } from '../types/provider'
import type { SourceSelector } from '../types/common'
import { providerService } from '../services/providerService'
import { providerService } from '../services'
import { ProviderObject } from '../models/provider'
import type { SourceSelector } from '../types'
export const useProvidersStore = defineStore('fileProviders', () => {
const providers: Ref<Record<string, ProviderObject>> = ref({})
const loading = ref(false)
const error: Ref<string | null> = ref(null)
const initialized = ref(false)
export const useProvidersStore = defineStore('documentsProvidersStore', () => {
// State
const _providers = ref<Record<string, ProviderObject>>({})
const transceiving = ref(false)
const providerList: ComputedRef<ProviderObject[]> = computed(() =>
Object.values(providers.value)
)
/**
* Get count of providers in store
*/
const count = computed(() => Object.keys(_providers.value).length)
const providerIds: ComputedRef<string[]> = computed(() =>
Object.keys(providers.value)
)
/**
* Check if any providers are present in store
*/
const has = computed(() => count.value > 0)
const getProvider = (id: string): ProviderObject | undefined => {
return providers.value[id]
}
/**
* Get all providers present in store
*/
const providers = computed(() => Object.values(_providers.value))
const hasProvider = (id: string): boolean => {
return id in providers.value
}
const isCapable = (providerId: string, capability: keyof ProviderCapabilitiesInterface): boolean => {
const provider = providers.value[providerId]
return provider ? provider.capable(capability) : false
}
const setProviders = (data: ProviderRecord) => {
const hydrated: Record<string, ProviderObject> = {}
for (const [id, providerData] of Object.entries(data)) {
hydrated[id] = new ProviderObject().fromJson(providerData)
/**
* Get a specific provider from store, with optional retrieval
*
* @param identifier - Provider identifier
* @param retrieve - Retrieve behavior: true = fetch if missing or refresh, false = cache only
*
* @returns Provider object or null
*/
function provider(identifier: string, retrieve: boolean = false): ProviderObject | null {
if (retrieve === true && !_providers.value[identifier]) {
console.debug(`[Documents Manager][Store] - Force fetching provider "${identifier}"`)
fetch(identifier)
}
providers.value = hydrated
initialized.value = true
return _providers.value[identifier] || null
}
const addProvider = (id: string, provider: ProviderInterface) => {
providers.value[id] = new ProviderObject().fromJson(provider)
}
// Actions
const removeProvider = (id: string) => {
delete providers.value[id]
}
const clearProviders = () => {
providers.value = {}
initialized.value = false
}
// API actions
const fetchProviders = async (sources?: SourceSelector): Promise<void> => {
loading.value = true
error.value = null
/**
* Retrieve all or specific providers, optionally filtered by source selector
*
* @param request - list request parameters
*
* @returns Promise with provider object list keyed by provider identifier
*/
async function list(sources?: SourceSelector): Promise<Record<string, ProviderObject>> {
transceiving.value = true
try {
const data = await providerService.list(sources)
setProviders(data)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to fetch providers'
throw e
const providers = await providerService.list({ sources })
// Merge retrieved providers into state
_providers.value = { ..._providers.value, ...providers }
console.debug('[Documents Manager][Store] - Successfully retrieved', Object.keys(providers).length, 'providers')
return providers
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to retrieve providers:', error)
throw error
} finally {
loading.value = false
transceiving.value = false
}
}
const checkProviderExtant = async (sources: SourceSelector): Promise<Record<string, boolean>> => {
/**
* Retrieve a specific provider by identifier
*
* @param identifier - provider identifier
*
* @returns Promise with provider object
*/
async function fetch(identifier: string): Promise<ProviderObject> {
transceiving.value = true
try {
return await providerService.extant(sources)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to check providers'
throw e
const provider = await providerService.fetch({ identifier })
// Merge fetched provider into state
_providers.value[provider.identifier] = provider
console.debug('[Documents Manager][Store] - Successfully fetched provider:', provider.identifier)
return provider
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to fetch provider:', error)
throw error
} finally {
transceiving.value = false
}
}
/**
* Retrieve provider availability status for a given source selector
*
* @param sources - source selector to check availability for
*
* @returns Promise with provider availability status
*/
async function extant(sources: SourceSelector) {
transceiving.value = true
try {
const response = await providerService.extant({ sources })
Object.entries(response).forEach(([providerId, providerStatus]) => {
if (providerStatus === false) {
delete _providers.value[providerId]
}
})
console.debug('[Documents Manager][Store] - Successfully checked', sources ? Object.keys(sources).length : 0, 'providers')
return response
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to check providers:', error)
throw error
} finally {
transceiving.value = false
}
}
// Return public API
return {
// State
transceiving: readonly(transceiving),
// computed
count,
has,
providers,
loading,
error,
initialized,
// Computed
providerList,
providerIds,
// Getters
getProvider,
hasProvider,
isCapable,
// Setters
setProviders,
addProvider,
removeProvider,
clearProviders,
// Actions
fetchProviders,
checkProviderExtant,
provider,
// functions
list,
fetch,
extant,
}
})
+229 -101
View File
@@ -1,131 +1,259 @@
/**
* Services Store
*/
import { ref, computed, readonly } from 'vue'
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { Ref, ComputedRef } from 'vue'
import type { ServiceInterface, ServiceRecord } from '../types/service'
import type { SourceSelector } from '../types/common'
import { serviceService } from '../services/serviceService'
import { serviceService } from '../services'
import { ServiceObject } from '../models/service'
import type {
SourceSelector,
ServiceInterface,
} from '../types'
// Nested structure: provider -> service -> ServiceObject
type ServiceStore = Record<string, Record<string, ServiceObject>>
export const useServicesStore = defineStore('documentsServicesStore', () => {
// State
const _services = ref<Record<string, ServiceObject>>({})
const transceiving = ref(false)
export const useServicesStore = defineStore('fileServices', () => {
const services: Ref<ServiceStore> = ref({})
const loading = ref(false)
const error: Ref<string | null> = ref(null)
const initialized = ref(false)
/**
* Get count of services in store
*/
const count = computed(() => Object.keys(_services.value).length)
const serviceList: ComputedRef<ServiceObject[]> = computed(() => {
const result: ServiceObject[] = []
Object.values(services.value).forEach(providerServices => {
result.push(...Object.values(providerServices))
/**
* Check if any services are present in store
*/
const has = computed(() => count.value > 0)
/**
* Get all services present in store
*/
const services = computed(() => Object.values(_services.value))
/**
* Get all services present in store grouped by provider
*/
const servicesByProvider = computed(() => {
const groups: Record<string, ServiceObject[]> = {}
Object.values(_services.value).forEach((service) => {
const providerServices = (groups[service.provider] ??= [])
providerServices.push(service)
})
return result
return groups
})
const getService = (providerId: string, serviceId: string): ServiceObject | undefined => {
return services.value[providerId]?.[serviceId]
}
const hasService = (providerId: string, serviceId: string): boolean => {
return !!services.value[providerId]?.[serviceId]
}
const getProviderServices = (providerId: string): ServiceObject[] => {
return Object.values(services.value[providerId] || {})
}
const getRootId = (providerId: string, serviceId: string): string | undefined => {
return services.value[providerId]?.[serviceId]?.rootId
}
const setServices = (data: ServiceRecord) => {
const hydrated: ServiceStore = {}
for (const [id, serviceData] of Object.entries(data)) {
const providerId = serviceData.provider
if (!hydrated[providerId]) {
hydrated[providerId] = {}
}
hydrated[providerId][id] = new ServiceObject().fromJson(serviceData)
/**
* Get a specific service from store, with optional retrieval
*
* @param provider - provider identifier
* @param identifier - service identifier
* @param retrieve - Retrieve behavior: true = fetch if missing or refresh, false = cache only
*
* @returns Service object or null
*/
function service(provider: string, identifier: string | number, retrieve: boolean = false): ServiceObject | null {
const key = identifierKey(provider, identifier)
if (retrieve === true && !_services.value[key]) {
console.debug(`[Documents Manager][Store] - Force fetching service "${key}"`)
fetch(provider, identifier)
}
services.value = hydrated
initialized.value = true
return _services.value[key] || null
}
const addService = (providerId: string, serviceId: string, service: ServiceInterface) => {
if (!services.value[providerId]) {
services.value[providerId] = {}
}
services.value[providerId][serviceId] = new ServiceObject().fromJson(service)
/**
* Unique key for a service
*/
function identifierKey(provider: string, identifier: string | number | null): string {
return `${provider}:${identifier ?? ''}`
}
const removeService = (providerId: string, serviceId: string) => {
if (services.value[providerId]) {
delete services.value[providerId][serviceId]
}
}
const clearServices = () => {
services.value = {}
initialized.value = false
}
// API actions
const fetchServices = async (sources?: SourceSelector): Promise<void> => {
loading.value = true
error.value = null
// Actions
/**
* Retrieve all or specific services, optionally filtered by source selector
*
* @param sources - optional source selector
*
* @returns Promise with service object list keyed by provider and service identifier
*/
async function list(sources?: SourceSelector): Promise<Record<string, ServiceObject>> {
transceiving.value = true
try {
const data = await serviceService.list(sources)
setServices(data)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to fetch services'
throw e
const response = await serviceService.list({ sources })
// Flatten nested structure: provider-id: { service-id: object } -> "provider-id:service-id": object
const services: Record<string, ServiceObject> = {}
Object.entries(response).forEach(([_providerId, providerServices]) => {
Object.entries(providerServices).forEach(([_serviceId, serviceObj]) => {
const key = identifierKey(serviceObj.provider, serviceObj.identifier)
services[key] = serviceObj
})
})
// Merge retrieved services into state
_services.value = { ..._services.value, ...services }
console.debug('[Documents Manager][Store] - Successfully retrieved', Object.keys(services).length, 'services')
return services
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to retrieve services:', error)
throw error
} finally {
loading.value = false
transceiving.value = false
}
}
const checkServiceExtant = async (sources: SourceSelector): Promise<Record<string, boolean>> => {
/**
* Retrieve a specific service by provider and identifier
*
* @param provider - provider identifier
* @param identifier - service identifier
*
* @returns Promise with service object
*/
async function fetch(provider: string, identifier: string | number): Promise<ServiceObject> {
transceiving.value = true
try {
return await serviceService.extant(sources)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to check services'
throw e
const service = await serviceService.fetch({ provider, identifier })
// Merge fetched service into state
const key = identifierKey(service.provider, service.identifier)
_services.value[key] = service
console.debug('[Documents Manager][Store] - Successfully fetched service:', key)
return service
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to fetch service:', error)
throw error
} finally {
transceiving.value = false
}
}
const fetchService = async (providerId: string, serviceId: string): Promise<ServiceObject> => {
/**
* Retrieve service availability status for a given source selector
*
* @param sources - source selector to check availability for
*
* @returns Promise with service availability status
*/
async function extant(sources: SourceSelector) {
transceiving.value = true
try {
const data = await serviceService.fetch(providerId, serviceId)
addService(providerId, serviceId, data)
return services.value[providerId][serviceId]
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to fetch service'
throw e
const response = await serviceService.extant({ sources })
console.debug('[Documents Manager][Store] - Successfully checked', sources ? Object.keys(sources).length : 0, 'services')
return response
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to check services:', error)
throw error
} finally {
transceiving.value = false
}
}
/**
* Create a new service with given provider and data
*
* @param provider - provider identifier for the new service
* @param data - partial service data for creation
*
* @returns Promise with created service object
*/
async function create(provider: string, data: Partial<ServiceInterface>): Promise<ServiceObject> {
transceiving.value = true
try {
const service = await serviceService.create({ provider, data })
// Merge created service into state
const key = identifierKey(service.provider, service.identifier)
_services.value[key] = service
console.debug('[Documents Manager][Store] - Successfully created service:', key)
return service
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to create service:', error)
throw error
} finally {
transceiving.value = false
}
}
/**
* Update an existing service with given provider, identifier, and data
*
* @param provider - provider identifier for the service to update
* @param identifier - service identifier for the service to update
* @param data - partial service data for update
*
* @returns Promise with updated service object
*/
async function update(provider: string, identifier: string | number, data: Partial<ServiceInterface>): Promise<ServiceObject> {
transceiving.value = true
try {
const service = await serviceService.update({ provider, identifier, data })
// Merge updated service into state
const key = identifierKey(service.provider, service.identifier)
_services.value[key] = service
console.debug('[Documents Manager][Store] - Successfully updated service:', key)
return service
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to update service:', error)
throw error
} finally {
transceiving.value = false
}
}
/**
* Delete a service by provider and identifier
*
* @param provider - provider identifier for the service to delete
* @param identifier - service identifier for the service to delete
*
* @returns Promise with deletion result
*/
async function remove(provider: string, identifier: string | number): Promise<any> {
transceiving.value = true
try {
await serviceService.delete({ provider, identifier })
// Remove deleted service from state
const key = identifierKey(provider, identifier)
delete _services.value[key]
console.debug('[Documents Manager][Store] - Successfully deleted service:', key)
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to delete service:', error)
throw error
} finally {
transceiving.value = false
}
}
// Return public API
return {
// State
services,
loading,
error,
initialized,
// Computed
serviceList,
// State (readonly)
transceiving: readonly(transceiving),
// Getters
getService,
hasService,
getProviderServices,
getRootId,
// Setters
setServices,
addService,
removeService,
clearServices,
count,
has,
services,
servicesByProvider,
// Actions
fetchServices,
checkServiceExtant,
fetchService,
service,
list,
fetch,
extant,
create,
update,
delete: remove,
}
})