refactor: documents interfaces

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-07-09 19:32:27 -04:00
parent ac393843f1
commit 050467919b
23 changed files with 1731 additions and 1581 deletions
+21 -1
View File
@@ -4,6 +4,19 @@
import type { CollectionContentTypes, CollectionInterface, CollectionModelInterface, CollectionPropertiesInterface } from "@/types/collection";
/**
* Reduce a serialized resource identifier ("provider:service:collection")
* to its own (last) segment; plain values pass through unchanged
*/
function plainIdentifier(value: string | number | null | undefined): string | number | null {
if (value === null || value === undefined || value === '') {
return value ?? null;
}
const text = String(value);
const index = text.lastIndexOf(':');
return index >= 0 ? text.slice(index + 1) : value;
}
export class CollectionObject implements CollectionModelInterface {
_data!: CollectionInterface;
@@ -24,7 +37,14 @@ export class CollectionObject implements CollectionModelInterface {
}
fromJson(data: CollectionInterface): CollectionObject {
this._data = data;
// the wire format carries full resource identifiers (provider:service:collection);
// reduce them to plain ids, which is what the stores and UI operate on
this._data = {
...data,
service: plainIdentifier(data.service) ?? '',
collection: plainIdentifier(data.collection),
identifier: plainIdentifier(data.identifier) ?? '',
};
if (data.properties) {
this._data.properties = new CollectionPropertiesObject().fromJson(data.properties as CollectionPropertiesInterface);
}
+21 -1
View File
@@ -5,6 +5,19 @@ import type { EntityInterface, EntityModelInterface } from "@/types/entity";
import type { DocumentInterface, DocumentModelInterface } from "@/types/document";
import { DocumentObject } from "./document";
/**
* Reduce a serialized resource identifier ("provider:service:collection:entity")
* to its own (last) segment; plain values pass through unchanged
*/
function plainIdentifier(value: string | number | null | undefined): string | number | null {
if (value === null || value === undefined || value === '') {
return value ?? null;
}
const text = String(value);
const index = text.lastIndexOf(':');
return index >= 0 ? text.slice(index + 1) : value;
}
export class EntityObject implements EntityModelInterface {
_data!: EntityInterface<DocumentInterface|DocumentModelInterface>;
@@ -25,7 +38,14 @@ export class EntityObject implements EntityModelInterface {
}
fromJson(data: EntityInterface): EntityObject {
this._data = data
// the wire format carries full resource identifiers (provider:service:collection:entity);
// reduce them to plain ids, which is what the stores and UI operate on
this._data = {
...data,
service: String(plainIdentifier(data.service) ?? ''),
collection: plainIdentifier(data.collection) ?? '',
identifier: plainIdentifier(data.identifier) ?? '',
}
if (data.properties) {
this._data.properties = new DocumentObject().fromJson(data.properties as DocumentInterface);
}
+30 -2
View File
@@ -16,6 +16,10 @@ import type {
CollectionUpdateRequest,
CollectionDeleteResponse,
CollectionDeleteRequest,
CollectionCopyRequest,
CollectionCopyResponse,
CollectionMoveRequest,
CollectionMoveResponse,
CollectionInterface,
} from '../types/collection';
import { useIntegrationStore } from '@KTXC/stores/integrationStore';
@@ -141,15 +145,39 @@ export const collectionService = {
/**
* Delete a collection
*
*
* @param request - delete request parameters
*
*
* @returns Promise with deletion result
*/
async delete(request: CollectionDeleteRequest): Promise<CollectionDeleteResponse> {
return await transceivePost<CollectionDeleteRequest, CollectionDeleteResponse>('collection.delete', request);
},
/**
* Copy a collection to a new parent
*
* @param request - copy request parameters
*
* @returns Promise with copied collection object
*/
async copy(request: CollectionCopyRequest): Promise<CollectionObject> {
const response = await transceivePost<CollectionCopyRequest, CollectionCopyResponse>('collection.copy', request);
return createCollectionObject(response);
},
/**
* Move a collection to a new parent
*
* @param request - move request parameters
*
* @returns Promise with moved collection object
*/
async move(request: CollectionMoveRequest): Promise<CollectionObject> {
const response = await transceivePost<CollectionMoveRequest, CollectionMoveResponse>('collection.move', request);
return createCollectionObject(response);
},
};
export default collectionService;
+52 -12
View File
@@ -2,7 +2,7 @@
* Entity management service
*/
import { transceivePost } from './transceive';
import { transceivePost, transceiveStream } from './transceive';
import type {
EntityListRequest,
EntityListResponse,
@@ -18,6 +18,10 @@ import type {
EntityDeleteResponse,
EntityDeltaRequest,
EntityDeltaResponse,
EntityCopyRequest,
EntityCopyResponse,
EntityMoveRequest,
EntityMoveResponse,
EntityReadRequest,
EntityReadResponse,
EntityWriteRequest,
@@ -43,14 +47,14 @@ function createEntityObject(data: EntityInterface): EntityObject {
export const entityService = {
/**
* Retrieve list of entities, optionally filtered by source selector
*
* Retrieve list of entities, optionally filtered by source identifiers
*
* @param request - list request parameters
*
*
* @returns Promise with entity object list grouped by provider, service, collection, and entity identifier
*/
async list(request: EntityListRequest = {}): Promise<Record<string, Record<string, Record<string, Record<string, EntityObject>>>>> {
const response = await transceivePost<EntityListRequest, EntityListResponse>('entity.list', request);
async listBulk(request: EntityListRequest = {}): Promise<Record<string, Record<string, Record<string, Record<string, EntityObject>>>>> {
const response = await transceivePost<EntityListRequest, EntityListResponse>('entity.listBulk', request);
// Convert nested response to EntityObject instances
const providerList: Record<string, Record<string, Record<string, Record<string, EntityObject>>>> = {};
@@ -73,11 +77,25 @@ export const entityService = {
return providerList;
},
/**
* Stream entities one by one, invoking the callback for each entity
*
* @param request - list request parameters
* @param onEntity - callback invoked for each streamed entity
*
* @returns Promise with the total number of streamed entities
*/
async listStream(request: EntityListRequest, onEntity: (entity: EntityObject) => void): Promise<{ total: number }> {
return await transceiveStream<EntityListRequest, EntityInterface>('entity.listStream', request, (entityData) => {
onEntity(createEntityObject(entityData));
});
},
/**
* Retrieve a specific entity by provider and identifier
*
*
* @param request - fetch request parameters
*
*
* @returns Promise with entity objects keyed by identifier
*/
async fetch(request: EntityFetchRequest): Promise<Record<string, EntityObject>> {
@@ -128,16 +146,38 @@ export const entityService = {
},
/**
* Delete an entity
*
* Delete entities
*
* @param request - delete request parameters
*
* @returns Promise with deletion result
*
* @returns Promise with per-entity disposition results
*/
async delete(request: EntityDeleteRequest): Promise<EntityDeleteResponse> {
return await transceivePost<EntityDeleteRequest, EntityDeleteResponse>('entity.delete', request);
},
/**
* Copy entities to another collection
*
* @param request - copy request parameters
*
* @returns Promise with per-entity disposition results
*/
async copy(request: EntityCopyRequest): Promise<EntityCopyResponse> {
return await transceivePost<EntityCopyRequest, EntityCopyResponse>('entity.copy', request);
},
/**
* Move entities to another collection
*
* @param request - move request parameters
*
* @returns Promise with per-entity disposition results
*/
async move(request: EntityMoveRequest): Promise<EntityMoveResponse> {
return await transceivePost<EntityMoveRequest, EntityMoveResponse>('entity.move', request);
},
/**
* Retrieve delta changes for entities
*
-1
View File
@@ -6,4 +6,3 @@ export { providerService } from './providerService';
export { serviceService } from './serviceService';
export { collectionService } from './collectionService';
export { entityService } from './entityService';
export { nodeService } from './nodeService';
-68
View File
@@ -1,68 +0,0 @@
/**
* Node (unified collection/entity) management service
*/
import { transceivePost } from './transceive'
import type { ListFilter, ListSort, ListRange } from '../types/common'
import type { CollectionInterface } from '../types/collection'
import type { EntityInterface } from '../types/entity'
export type NodeItem = CollectionInterface | EntityInterface
export interface NodeListRequest {
provider: string
service: string | number
location?: string | number | null
recursive?: boolean
filter?: ListFilter | null
sort?: ListSort | null
range?: ListRange | null
}
export type NodeListResponse = Record<string, NodeItem>
export interface NodeDeltaRequest {
provider: string
service: string | number
location?: string | number | null
signature: string
recursive?: boolean
detail?: 'ids' | 'full'
}
export interface NodeDeltaResult {
added: Array<string | number | NodeItem>
modified: Array<string | number | NodeItem>
removed: Array<string | number>
signature: string
}
export const nodeService = {
async list(request: NodeListRequest): Promise<NodeItem[]> {
const response = await transceivePost<NodeListRequest, NodeListResponse>('node.list', {
provider: request.provider,
service: request.service,
location: request.location ?? null,
recursive: request.recursive ?? false,
filter: request.filter ?? null,
sort: request.sort ?? null,
range: request.range ?? null,
})
return Object.values(response)
},
async delta(request: NodeDeltaRequest): Promise<NodeDeltaResult> {
return await transceivePost<NodeDeltaRequest, NodeDeltaResult>('node.delta', {
provider: request.provider,
service: request.service,
location: request.location ?? null,
signature: request.signature,
recursive: request.recursive ?? false,
detail: request.detail ?? 'ids',
})
},
}
export default nodeService
+82 -3
View File
@@ -4,7 +4,7 @@
*/
import { createFetchWrapper } from '@KTXC';
import type { ApiRequest, ApiResponse } from '../types/common';
import type { ApiRequest, ApiResponse, ApiStreamResponse } from '../types/common';
const fetchWrapper = createFetchWrapper();
const API_URL = '/m/documents_manager/v1';
@@ -40,11 +40,90 @@ export async function transceivePost<TRequest, TResponse>(
};
const response: ApiResponse<TResponse> = await fetchWrapper.post(API_URL, request);
if (response.status === 'error') {
const errorMessage = `[${operation}] ${response.data.message}${response.data.code ? ` (code: ${response.data.code})` : ''}`;
throw new Error(errorMessage);
}
return response.data;
}
/**
* Stream an NDJSON API response, unwrapping data frames for the caller.
*
* @param operation - Operation name, e.g. 'entity.listStream'
* @param data - Operation-specific request data
* @param onData - Synchronous callback invoked for every unwrapped data payload.
* @param options - Optional `user` override and an `onStart` hook.
* @returns Promise resolving to the final stream total from the control/end frame
*/
export async function transceiveStream<TRequest, TData>(
operation: string,
data: TRequest,
onData: (data: TData) => void,
options?: { user?: string; onStart?: (expected?: number) => void }
): Promise<{ total: number }> {
const request: ApiRequest<TRequest> = {
version: API_VERSION,
transaction: generateTransactionId(),
operation,
data,
user: options?.user,
};
let total = 0;
const dispatch = (line: string): void => {
const message = JSON.parse(line) as ApiStreamResponse<TData>;
if (message.type === 'control') {
if (message.status === 'start') {
options?.onStart?.(message.total);
} else if (message.status === 'end') {
total = message.total;
}
return;
}
if (message.type === 'error') {
throw new Error(`[${operation}] ${message.message}`);
}
onData(message.data);
};
await fetchWrapper.post(API_URL, request, {
headers: { 'Accept': 'application/json' },
onStream: async (response: Response) => {
if (!response.body) {
throw new Error(`[${operation}] Response body is not readable`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop()!;
for (const line of lines) {
if (line.trim()) dispatch(line);
}
}
if (buffer.trim()) dispatch(buffer);
} finally {
reader.releaseLock();
}
},
});
return { total };
}
+16 -12
View File
@@ -6,11 +6,11 @@ import { ref, computed, readonly } from 'vue'
import { defineStore } from 'pinia'
import { collectionService } from '../services/collectionService'
import type {
SourceSelector,
CollectionIdentifier,
ServiceIdentifier,
ListFilter,
ListSort,
CollectionMutableProperties,
CollectionDeleteResponse,
} from '../types'
import { CollectionObject } from '../models/collection'
@@ -71,7 +71,7 @@ export const useCollectionsStore = defineStore('documentsCollectionsStore', () =
}
// Actions
async function list(sources?: SourceSelector, filter?: ListFilter, sort?: ListSort): Promise<Record<string, CollectionObject>> {
async function list(sources?: (ServiceIdentifier | CollectionIdentifier)[], filter?: ListFilter, sort?: ListSort): Promise<Record<string, CollectionObject>> {
transceiving.value = true
try {
const response = await collectionService.list({ sources, filter, sort })
@@ -101,7 +101,7 @@ export const useCollectionsStore = defineStore('documentsCollectionsStore', () =
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 response = await collectionService.fetch({ targets: [`${provider}:${service}:${identifier}`] })
const key = identifierKey(response.provider, response.service, response.identifier)
_collections.value[key] = response
@@ -115,10 +115,10 @@ export const useCollectionsStore = defineStore('documentsCollectionsStore', () =
}
}
async function extant(sources: SourceSelector) {
async function extant(targets: CollectionIdentifier[]) {
transceiving.value = true
try {
const response = await collectionService.extant({ sources })
const response = await collectionService.extant({ targets })
console.debug('[Documents Manager][Store] - Successfully checked collection availability')
return response
} catch (error: any) {
@@ -137,7 +137,10 @@ export const useCollectionsStore = defineStore('documentsCollectionsStore', () =
): Promise<CollectionObject> {
transceiving.value = true
try {
const response = await collectionService.create({ provider, service, collection, properties })
const target: CollectionIdentifier | undefined = collection !== null && collection !== undefined && collection !== ''
? `${provider}:${service}:${collection}`
: undefined
const response = await collectionService.create({ provider, service: String(service), target, properties })
const key = identifierKey(response.provider, response.service, response.identifier)
_collections.value[key] = response
@@ -159,7 +162,7 @@ export const useCollectionsStore = defineStore('documentsCollectionsStore', () =
): Promise<CollectionObject> {
transceiving.value = true
try {
const response = await collectionService.update({ provider, service, identifier, properties })
const response = await collectionService.update({ target: `${provider}:${service}:${identifier}`, properties })
const key = identifierKey(response.provider, response.service, response.identifier)
_collections.value[key] = response
@@ -173,17 +176,18 @@ export const useCollectionsStore = defineStore('documentsCollectionsStore', () =
}
}
async function remove(provider: string, service: string | number, identifier: string | number): Promise<CollectionDeleteResponse> {
async function remove(provider: string, service: string | number, identifier: string | number): Promise<{ success: boolean }> {
transceiving.value = true
try {
const response = await collectionService.delete({ provider, service, identifier })
if (response.success) {
const response = await collectionService.delete({ target: `${provider}:${service}:${identifier}` })
const success = response.disposition === 'deleted' || response.disposition === 'moved'
if (success) {
const key = identifierKey(provider, service, identifier)
delete _collections.value[key]
}
console.debug('[Documents Manager][Store] - Successfully deleted collection:', `${provider}:${service}:${identifier}`)
return response
return { success }
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to delete collection:', error)
throw error
+22 -18
View File
@@ -7,12 +7,13 @@ import { defineStore } from 'pinia'
import { entityService } from '../services/entityService'
import { EntityObject } from '../models'
import type {
SourceSelector,
CollectionIdentifier,
EntityIdentifier,
ServiceIdentifier,
ListFilter,
ListSort,
ListRange,
DocumentInterface,
EntityDeleteResponse,
EntityDeltaResponse,
} from '../types'
@@ -100,10 +101,10 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
}
// Actions
async function list(sources?: SourceSelector, filter?: ListFilter, sort?: ListSort, range?: ListRange): Promise<Record<string, EntityObject>> {
async function list(sources?: (ServiceIdentifier | CollectionIdentifier)[], filter?: ListFilter, sort?: ListSort, range?: ListRange): Promise<Record<string, EntityObject>> {
transceiving.value = true
try {
const response = await entityService.list({ sources, filter, sort, range })
const response = await entityService.listBulk({ sources, filter, sort, range })
const hydrated: Record<string, EntityObject> = {}
Object.entries(response).forEach(([providerId, providerServices]) => {
@@ -137,7 +138,8 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
): Promise<Record<string, EntityObject>> {
transceiving.value = true
try {
const response = await entityService.fetch({ provider, service, collection, identifiers })
const targets: EntityIdentifier[] = identifiers.map((identifier) => `${provider}:${service}:${collection}:${identifier}` as EntityIdentifier)
const response = await entityService.fetch({ targets })
const hydrated: Record<string, EntityObject> = {}
Object.entries(response).forEach(([identifier, entityObj]) => {
@@ -156,10 +158,10 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
}
}
async function extant(sources: SourceSelector) {
async function extant(targets: (CollectionIdentifier | EntityIdentifier)[]) {
transceiving.value = true
try {
const response = await entityService.extant({ sources })
const response = await entityService.extant({ targets })
console.debug('[Documents Manager][Store] - Successfully checked entity availability')
return response
} catch (error: any) {
@@ -179,7 +181,7 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
): Promise<EntityObject> {
transceiving.value = true
try {
const response = await entityService.create({ provider, service, collection, properties, options })
const response = await entityService.create({ target: `${provider}:${service}:${collection}`, properties, options })
const key = identifierKey(response.provider, response.service, response.collection, response.identifier)
_entities.value[key] = response
@@ -202,7 +204,7 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
): Promise<EntityObject> {
transceiving.value = true
try {
const response = await entityService.update({ provider, service, collection, identifier, properties })
const response = await entityService.update({ target: `${provider}:${service}:${collection}:${identifier}`, properties })
const key = identifierKey(response.provider, response.service, response.collection, response.identifier)
_entities.value[key] = response
@@ -221,17 +223,19 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
service: string | number,
collection: string | number,
identifier: string | number,
): Promise<EntityDeleteResponse> {
): Promise<{ success: boolean }> {
transceiving.value = true
try {
const response = await entityService.delete({ provider, service, collection, identifier })
if (response.success) {
const target: EntityIdentifier = `${provider}:${service}:${collection}:${identifier}`
const response = await entityService.delete({ targets: [target] })
const success = response[target]?.disposition === 'deleted'
if (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
console.debug('[Documents Manager][Store] - Successfully deleted entity:', target)
return { success }
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to delete entity:', error)
throw error
@@ -240,10 +244,10 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
}
}
async function delta(sources: SourceSelector): Promise<EntityDeltaResponse> {
async function delta(targets: (CollectionIdentifier | EntityIdentifier)[]): Promise<EntityDeltaResponse> {
transceiving.value = true
try {
const response = await entityService.delta({ sources })
const response = await entityService.delta({ targets })
Object.entries(response).forEach(([provider, providerData]) => {
if (providerData === false) return
@@ -280,7 +284,7 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
): Promise<string | null> {
transceiving.value = true
try {
const response = await entityService.read({ provider, service, collection, identifier })
const response = await entityService.read({ target: `${provider}:${service}:${collection}:${identifier}` })
return response.content
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to read entity:', error)
@@ -299,7 +303,7 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
): Promise<number> {
transceiving.value = true
try {
const response = await entityService.write({ provider, service, collection, identifier, content, encoding: 'base64' })
const response = await entityService.write({ target: `${provider}:${service}:${collection}:${identifier}`, content, encoding: 'base64' })
return response.bytesWritten
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to write entity:', error)
+8 -15
View File
@@ -5,7 +5,8 @@
import { computed, ref, readonly } from 'vue'
import { defineStore } from 'pinia'
import type {
SourceSelector,
CollectionIdentifier,
ServiceIdentifier,
ListFilter,
ListSort,
ListRange,
@@ -112,13 +113,9 @@ export const useNodesStore = defineStore('documentsNodesStore', () => {
): Promise<CollectionObject[]> {
error.value = null
try {
const sources: SourceSelector = {
[providerId]: {
[String(serviceId)]: collectionId === null
? true
: { [String(collectionId)]: true },
},
}
const sources: (ServiceIdentifier | CollectionIdentifier)[] = collectionId === null
? [`${providerId}:${serviceId}`]
: [`${providerId}:${serviceId}:${collectionId}`]
await collectionsStore.list(sources, filter, sort)
return collectionsStore.collectionsForService(providerId, serviceId)
@@ -138,13 +135,9 @@ export const useNodesStore = defineStore('documentsNodesStore', () => {
): Promise<EntityObject[]> {
error.value = null
try {
const sources: SourceSelector = {
[providerId]: {
[String(serviceId)]: collectionId === null
? true
: { [String(collectionId)]: true },
},
}
const sources: (ServiceIdentifier | CollectionIdentifier)[] = collectionId === null
? [`${providerId}:${serviceId}`]
: [`${providerId}:${serviceId}:${collectionId}`]
await entitiesStore.list(sources, filter, sort, range)
return entitiesStore.entitiesForCollection(providerId, serviceId, collectionId)
+8 -8
View File
@@ -6,7 +6,7 @@ import { ref, computed, readonly } from 'vue'
import { defineStore } from 'pinia'
import { providerService } from '../services'
import { ProviderObject } from '../models/provider'
import type { SourceSelector } from '../types'
import type { ProviderIdentifier } from '../types'
export const useProvidersStore = defineStore('documentsProvidersStore', () => {
// State
@@ -54,10 +54,10 @@ export const useProvidersStore = defineStore('documentsProvidersStore', () => {
*
* @returns Promise with provider object list keyed by provider identifier
*/
async function list(sources?: SourceSelector): Promise<Record<string, ProviderObject>> {
async function list(targets?: ProviderIdentifier[]): Promise<Record<string, ProviderObject>> {
transceiving.value = true
try {
const providers = await providerService.list({ sources })
const providers = await providerService.list({ targets })
// Merge retrieved providers into state
_providers.value = { ..._providers.value, ...providers }
@@ -82,7 +82,7 @@ export const useProvidersStore = defineStore('documentsProvidersStore', () => {
async function fetch(identifier: string): Promise<ProviderObject> {
transceiving.value = true
try {
const provider = await providerService.fetch({ identifier })
const provider = await providerService.fetch({ target: identifier })
// Merge fetched provider into state
_providers.value[provider.identifier] = provider
@@ -100,14 +100,14 @@ export const useProvidersStore = defineStore('documentsProvidersStore', () => {
/**
* Retrieve provider availability status for a given source selector
*
* @param sources - source selector to check availability for
* @param targets - provider identifiers to check availability for
*
* @returns Promise with provider availability status
*/
async function extant(sources: SourceSelector) {
async function extant(targets: ProviderIdentifier[]) {
transceiving.value = true
try {
const response = await providerService.extant({ sources })
const response = await providerService.extant({ targets })
Object.entries(response).forEach(([providerId, providerStatus]) => {
if (providerStatus === false) {
@@ -115,7 +115,7 @@ export const useProvidersStore = defineStore('documentsProvidersStore', () => {
}
})
console.debug('[Documents Manager][Store] - Successfully checked', sources ? Object.keys(sources).length : 0, 'providers')
console.debug('[Documents Manager][Store] - Successfully checked', targets ? targets.length : 0, 'providers')
return response
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to check providers:', error)
+8 -8
View File
@@ -7,7 +7,7 @@ import { defineStore } from 'pinia'
import { serviceService } from '../services'
import { ServiceObject } from '../models/service'
import type {
SourceSelector,
ServiceIdentifier,
ServiceInterface,
} from '../types'
@@ -76,14 +76,14 @@ export const useServicesStore = defineStore('documentsServicesStore', () => {
/**
* Retrieve all or specific services, optionally filtered by source selector
*
* @param sources - optional source selector
* @param targets - optional service identifiers
*
* @returns Promise with service object list keyed by provider and service identifier
*/
async function list(sources?: SourceSelector): Promise<Record<string, ServiceObject>> {
async function list(targets?: ServiceIdentifier[]): Promise<Record<string, ServiceObject>> {
transceiving.value = true
try {
const response = await serviceService.list({ sources })
const response = await serviceService.list({ targets })
// Flatten nested structure: provider-id: { service-id: object } -> "provider-id:service-id": object
const services: Record<string, ServiceObject> = {}
@@ -137,16 +137,16 @@ export const useServicesStore = defineStore('documentsServicesStore', () => {
/**
* Retrieve service availability status for a given source selector
*
* @param sources - source selector to check availability for
* @param targets - service identifiers to check availability for
*
* @returns Promise with service availability status
*/
async function extant(sources: SourceSelector) {
async function extant(targets: ServiceIdentifier[]) {
transceiving.value = true
try {
const response = await serviceService.extant({ sources })
const response = await serviceService.extant({ targets })
console.debug('[Documents Manager][Store] - Successfully checked', sources ? Object.keys(sources).length : 0, 'services')
console.debug('[Documents Manager][Store] - Successfully checked', targets ? targets.length : 0, 'services')
return response
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to check services:', error)
+14 -22
View File
@@ -1,7 +1,7 @@
/**
* Collection type definitions
*/
import type { ListFilter, ListSort, SourceSelector } from './common';
import type { CollectionIdentifier, ListFilter, ListSort, ServiceIdentifier } from './common';
export interface CollectionModelInterface extends Omit<CollectionInterface, '@type' | 'created' | 'modified'> {
@@ -46,7 +46,7 @@ export interface CollectionPropertiesInterface extends CollectionMutableProperti
* Collection list
*/
export interface CollectionListRequest {
sources?: SourceSelector;
sources?: (ServiceIdentifier | CollectionIdentifier)[];
filter?: ListFilter;
sort?: ListSort;
}
@@ -63,9 +63,7 @@ export interface CollectionListResponse {
* Collection fetch
*/
export interface CollectionFetchRequest {
provider: string;
service: string | number;
collection: string | number;
targets: CollectionIdentifier[];
}
export interface CollectionFetchResponse extends CollectionInterface {}
@@ -74,7 +72,7 @@ export interface CollectionFetchResponse extends CollectionInterface {}
* Collection extant
*/
export interface CollectionExtantRequest {
sources: SourceSelector;
targets: CollectionIdentifier[];
}
export interface CollectionExtantResponse {
@@ -91,8 +89,9 @@ export interface CollectionExtantResponse {
export interface CollectionCreateRequest {
provider: string;
service: string | number;
collection?: string | number | null; // Parent Collection Identifier
target?: CollectionIdentifier | null; // Parent collection identifier (absent for root)
properties: CollectionMutableProperties;
options?: Record<string, unknown>;
}
export interface CollectionCreateResponse extends CollectionInterface {}
@@ -101,9 +100,7 @@ export interface CollectionCreateResponse extends CollectionInterface {}
* Collection modify
*/
export interface CollectionUpdateRequest {
provider: string;
service: string | number;
identifier: string | number;
target: CollectionIdentifier;
properties: CollectionMutableProperties;
}
@@ -113,26 +110,23 @@ export interface CollectionUpdateResponse extends CollectionInterface {}
* Collection delete
*/
export interface CollectionDeleteRequest {
provider: string;
service: string | number;
identifier: string | number;
target: CollectionIdentifier;
options?: {
force?: boolean; // Whether to force delete even if collection is not empty
};
}
export interface CollectionDeleteResponse {
success: boolean;
disposition: 'deleted' | 'moved';
mutation?: CollectionInterface;
}
/**
* Collection copy
*/
export interface CollectionCopyRequest {
provider: string;
service: string;
identifier: string;
location?: string | null;
source: CollectionIdentifier;
target?: CollectionIdentifier | null; // Destination parent (absent for root)
}
export interface CollectionCopyResponse extends CollectionInterface {}
@@ -141,10 +135,8 @@ export interface CollectionCopyResponse extends CollectionInterface {}
* Collection move
*/
export interface CollectionMoveRequest {
provider: string;
service: string;
identifier: string;
location?: string | null;
source: CollectionIdentifier;
target?: CollectionIdentifier | null; // Destination parent (absent for root)
}
export interface CollectionMoveResponse extends CollectionInterface {}
+47 -23
View File
@@ -44,33 +44,57 @@ export interface ApiErrorResponse {
export type ApiResponse<T = any> = ApiSuccessResponse<T> | ApiErrorResponse;
/**
* Selector for targeting specific providers, services, collections, or entities in list or extant operations.
*
* Example usage:
* {
* "provider1": true, // Select all services/collections/entities under provider1
* "provider2": {
* "serviceA": true, // Select all collections/entities under serviceA of provider2
* "serviceB": {
* "collectionX": true, // Select all entities under collectionX of serviceB of provider2
* "collectionY": [1, 2, 3] // Select entities with identifiers 1, 2, and 3 under collectionY of serviceB of provider2
* }
* }
* }
* Stream control start line.
*/
export type SourceSelector = {
[provider: string]: boolean | ServiceSelector;
};
export interface ApiStreamStartResponse {
type: 'control';
status: 'start';
version: number;
transaction: string;
total?: number;
}
export type ServiceSelector = {
[service: string]: boolean | CollectionSelector;
};
/**
* Stream control end line
*/
export interface ApiStreamEndResponse {
type: 'control';
status: 'end';
total: number;
}
export type CollectionSelector = {
[collection: string | number]: boolean | EntitySelector;
};
/**
* Stream error line
*/
export interface ApiStreamErrorResponse {
type: 'error';
message: string;
}
export type EntitySelector = (string | number)[];
export interface ApiStreamDataResponse<T = any> {
type: 'data';
data: T;
}
/**
* Shared stream control lines
*/
export type ApiStreamResponse<T = any> =
| ApiStreamStartResponse
| ApiStreamEndResponse
| ApiStreamErrorResponse
| ApiStreamDataResponse<T>;
/**
* Identifiers for targeting specific providers, services, collections, or entities in list or extant operations.
*
* Operations accept flat arrays of colon-separated identifier strings, e.g.
* ["default:personal:00000000-0000-0000-0000-000000000000", "default:personal:folder1:file1"].
*/
export type ProviderIdentifier = `${string}`;
export type ServiceIdentifier = `${string}:${string}`;
export type CollectionIdentifier = `${string}:${string}:${string | number}`;
export type EntityIdentifier = `${string}:${string}:${string}:${string | number}`;
/**
+31 -40
View File
@@ -1,7 +1,7 @@
/**
* Entity type definitions
*/
import type { ListFilter, ListRange, ListSort, SourceSelector } from './common';
import type { CollectionIdentifier, EntityIdentifier, ListFilter, ListRange, ListSort, ServiceIdentifier } from './common';
import type { DocumentInterface, DocumentModelInterface } from './document';
/**
@@ -25,11 +25,21 @@ export interface EntityInterface<T = DocumentInterface> {
properties: T;
}
/**
* Entity mutation result (delete/move/copy operations)
*/
export interface EntityMutationResult {
disposition: 'deleted' | 'moved' | 'copied' | 'error';
destination?: CollectionIdentifier | null;
mutation?: EntityIdentifier;
error?: string;
}
/**
* Entity list
*/
export interface EntityListRequest {
sources?: SourceSelector;
sources?: (ServiceIdentifier | CollectionIdentifier)[];
filter?: ListFilter;
sort?: ListSort;
range?: ListRange;
@@ -49,10 +59,7 @@ export interface EntityListResponse {
* Entity fetch
*/
export interface EntityFetchRequest {
provider: string;
service: string | number;
collection: string | number;
identifiers: (string | number)[];
targets: EntityIdentifier[];
}
export interface EntityFetchResponse {
@@ -63,7 +70,7 @@ export interface EntityFetchResponse {
* Entity extant
*/
export interface EntityExtantRequest {
sources: SourceSelector;
targets: (CollectionIdentifier | EntityIdentifier)[];
}
export interface EntityExtantResponse {
@@ -80,9 +87,7 @@ export interface EntityExtantResponse {
* Entity create
*/
export interface EntityCreateRequest<T = DocumentInterface> {
provider: string;
service: string | number;
collection: string | number;
target: CollectionIdentifier;
properties: T;
options?: Record<string, unknown>;
}
@@ -93,10 +98,7 @@ export interface EntityCreateResponse<T = DocumentInterface> extends EntityInter
* Entity update
*/
export interface EntityUpdateRequest<T = DocumentInterface> {
provider: string;
service: string | number;
collection: string | number;
identifier: string | number;
target: EntityIdentifier;
properties: T;
}
@@ -106,21 +108,18 @@ export interface EntityUpdateResponse<T = DocumentInterface> extends EntityInter
* Entity delete
*/
export interface EntityDeleteRequest {
provider: string;
service: string | number;
collection: string | number;
identifier: string | number;
targets: EntityIdentifier[];
}
export interface EntityDeleteResponse {
success: boolean;
[identifier: string]: EntityMutationResult;
}
/**
* Entity delta
*/
export interface EntityDeltaRequest {
sources: SourceSelector;
targets: (CollectionIdentifier | EntityIdentifier)[];
}
export interface EntityDeltaResponse {
@@ -140,36 +139,31 @@ export interface EntityDeltaResponse {
* Entity copy
*/
export interface EntityCopyRequest {
provider: string;
service: string | number;
collection: string | number;
identifier: string | number;
destination?: string | null;
target: CollectionIdentifier;
sources: EntityIdentifier[];
}
export interface EntityCopyResponse<T = DocumentInterface> extends EntityInterface<T> {}
export interface EntityCopyResponse {
[identifier: string]: EntityMutationResult;
}
/**
* Entity move
*/
export interface EntityMoveRequest {
provider: string;
service: string | number;
collection: string | number;
identifier: string | number;
destination?: string | null;
target: CollectionIdentifier;
sources: EntityIdentifier[];
}
export interface EntityMoveResponse<T = DocumentInterface> extends EntityInterface<T> {}
export interface EntityMoveResponse {
[identifier: string]: EntityMutationResult;
}
/**
* Entity read content
*/
export interface EntityReadRequest {
provider: string;
service: string | number;
collection: string | number;
identifier: string | number;
target: EntityIdentifier;
}
export interface EntityReadResult {
@@ -183,10 +177,7 @@ export type EntityReadResponse = EntityReadResult;
* Entity write content
*/
export interface EntityWriteRequest {
provider: string;
service: string | number;
collection: string | number;
identifier: string | number;
target: EntityIdentifier;
content: string;
encoding?: 'base64';
}
-1
View File
@@ -4,4 +4,3 @@ export type * from './service';
export type * from './collection';
export type * from './entity';
export type * from './document';
export type * from './node';
-37
View File
@@ -1,37 +0,0 @@
/**
* Node types for combined operations
*/
import type { CollectionInterface } from "./collection";
import type { ApiResponse, ListFilterCondition, ListRange, ListSort } from "./common";
import type { EntityInterface } from "./entity";
export interface NodeListRequest {
provider: string;
service: string;
location?: string | null;
recursive?: boolean;
filter?: ListFilterCondition | null;
sort?: ListSort | null;
range?: ListRange | null;
}
export type NodeListResponse = ApiResponse<CollectionInterface | EntityInterface>;
export interface NodeDeltaRequest {
provider: string;
service: string;
location?: string | null;
signature: string;
recursive?: boolean;
detail?: 'ids' | 'full';
}
export interface NodeDeltaResult {
added: string[];
modified: string[];
removed: string[];
signature: string;
}
export type NodeDeltaResponse = ApiResponse<NodeDeltaResult>;
+6 -6
View File
@@ -1,7 +1,7 @@
/**
* Provider type definitions
*/
import type { SourceSelector } from "./common";
import type { ProviderIdentifier } from "./common";
/**
* Provider capabilities
@@ -32,7 +32,7 @@ export interface ProviderInterface {
* Provider list
*/
export interface ProviderListRequest {
sources?: SourceSelector;
targets?: ProviderIdentifier[];
}
export interface ProviderListResponse {
@@ -40,19 +40,19 @@ export interface ProviderListResponse {
}
/**
* Provider fetch
* Provider fetch
*/
export interface ProviderFetchRequest {
identifier: string;
target: ProviderIdentifier;
}
export interface ProviderFetchResponse extends ProviderInterface {}
/**
* Provider extant
* Provider extant
*/
export interface ProviderExtantRequest {
sources: SourceSelector;
targets: ProviderIdentifier[];
}
export interface ProviderExtantResponse {
+3 -3
View File
@@ -1,7 +1,7 @@
/**
* Service type definitions
*/
import type { SourceSelector, ListFilterComparisonOperator } from './common';
import type { ServiceIdentifier as ServiceIdentifierString, ListFilterComparisonOperator } from './common';
/**
* Service capabilities
@@ -66,7 +66,7 @@ export interface ServiceInterface {
* Service list
*/
export interface ServiceListRequest {
sources?: SourceSelector;
targets?: ServiceIdentifierString[];
}
export interface ServiceListResponse {
@@ -89,7 +89,7 @@ export interface ServiceFetchResponse extends ServiceInterface {}
* Service extant
*/
export interface ServiceExtantRequest {
sources: SourceSelector;
targets: ServiceIdentifierString[];
}
export interface ServiceExtantResponse {