Initial commit

This commit is contained in:
root
2025-12-21 09:57:43 -05:00
committed by Sebastian Krupinski
commit db42b6699c
35 changed files with 6458 additions and 0 deletions

17
src/main.ts Normal file
View File

@@ -0,0 +1,17 @@
import { useNodesStore } from '@/stores/nodesStore'
import { useProvidersStore } from '@/stores/providersStore'
import { useServicesStore } from '@/stores/servicesStore'
/**
* File Manager Module Boot Script
*
* This script is executed when the file_manager module is loaded.
* It initializes the stores which manage file nodes (files and folders) state.
*/
console.log('[FileManager] Booting File Manager module...')
console.log('[FileManager] File Manager module booted successfully')
// Export stores for external use if needed
export { useNodesStore, useProvidersStore, useServicesStore }

132
src/models/collection.ts Normal file
View File

@@ -0,0 +1,132 @@
/**
* Class model for FileCollection Interface
*/
import type { FileCollection } from "@/types/node";
export class FileCollectionObject implements FileCollection {
_data!: FileCollection;
constructor() {
this._data = {
'@type': 'files.collection',
in: null,
id: '',
createdBy: '',
createdOn: '',
modifiedBy: '',
modifiedOn: '',
owner: '',
signature: '',
label: '',
};
}
fromJson(data: FileCollection): FileCollectionObject {
this._data = data;
return this;
}
toJson(): FileCollection {
return this._data;
}
clone(): FileCollectionObject {
const cloned = new FileCollectionObject();
cloned._data = JSON.parse(JSON.stringify(this._data));
return cloned;
}
/** Properties */
get '@type'(): 'files.collection' {
return this._data['@type'];
}
get in(): string | null {
return this._data.in;
}
set in(value: string | null) {
this._data.in = value;
}
get id(): string {
return this._data.id;
}
set id(value: string) {
this._data.id = value;
}
get createdBy(): string {
return this._data.createdBy;
}
set createdBy(value: string) {
this._data.createdBy = value;
}
get createdOn(): string {
return this._data.createdOn;
}
set createdOn(value: string) {
this._data.createdOn = value;
}
get modifiedBy(): string {
return this._data.modifiedBy;
}
set modifiedBy(value: string) {
this._data.modifiedBy = value;
}
get modifiedOn(): string {
return this._data.modifiedOn;
}
set modifiedOn(value: string) {
this._data.modifiedOn = value;
}
get owner(): string {
return this._data.owner;
}
set owner(value: string) {
this._data.owner = value;
}
get signature(): string {
return this._data.signature;
}
set signature(value: string) {
this._data.signature = value;
}
get label(): string {
return this._data.label;
}
set label(value: string) {
this._data.label = value;
}
/** Helper methods */
get isRoot(): boolean {
return this._data.id === '00000000-0000-0000-0000-000000000000';
}
get createdOnDate(): Date | null {
return this._data.createdOn ? new Date(this._data.createdOn) : null;
}
get modifiedOnDate(): Date | null {
return this._data.modifiedOn ? new Date(this._data.modifiedOn) : null;
}
}

200
src/models/entity.ts Normal file
View File

@@ -0,0 +1,200 @@
/**
* Class model for FileEntity Interface
*/
import type { FileEntity } from "@/types/node";
export class FileEntityObject implements FileEntity {
_data!: FileEntity;
constructor() {
this._data = {
'@type': 'files.entity',
in: null,
id: '',
createdBy: '',
createdOn: '',
modifiedBy: '',
modifiedOn: '',
owner: '',
signature: '',
label: '',
size: 0,
mime: '',
format: '',
encoding: '',
};
}
fromJson(data: FileEntity): FileEntityObject {
this._data = data;
return this;
}
toJson(): FileEntity {
return this._data;
}
clone(): FileEntityObject {
const cloned = new FileEntityObject();
cloned._data = JSON.parse(JSON.stringify(this._data));
return cloned;
}
/** Properties */
get '@type'(): 'files.entity' {
return this._data['@type'];
}
get in(): string | null {
return this._data.in;
}
set in(value: string | null) {
this._data.in = value;
}
get id(): string {
return this._data.id;
}
set id(value: string) {
this._data.id = value;
}
get createdBy(): string {
return this._data.createdBy;
}
set createdBy(value: string) {
this._data.createdBy = value;
}
get createdOn(): string {
return this._data.createdOn;
}
set createdOn(value: string) {
this._data.createdOn = value;
}
get modifiedBy(): string {
return this._data.modifiedBy;
}
set modifiedBy(value: string) {
this._data.modifiedBy = value;
}
get modifiedOn(): string {
return this._data.modifiedOn;
}
set modifiedOn(value: string) {
this._data.modifiedOn = value;
}
get owner(): string {
return this._data.owner;
}
set owner(value: string) {
this._data.owner = value;
}
get signature(): string {
return this._data.signature;
}
set signature(value: string) {
this._data.signature = value;
}
get label(): string {
return this._data.label;
}
set label(value: string) {
this._data.label = value;
}
get size(): number {
return this._data.size;
}
set size(value: number) {
this._data.size = value;
}
get mime(): string {
return this._data.mime;
}
set mime(value: string) {
this._data.mime = value;
}
get format(): string {
return this._data.format;
}
set format(value: string) {
this._data.format = value;
}
get encoding(): string {
return this._data.encoding;
}
set encoding(value: string) {
this._data.encoding = value;
}
/** Helper methods */
get createdOnDate(): Date | null {
return this._data.createdOn ? new Date(this._data.createdOn) : null;
}
get modifiedOnDate(): Date | null {
return this._data.modifiedOn ? new Date(this._data.modifiedOn) : null;
}
get extension(): string {
const parts = this._data.label.split('.');
return parts.length > 1 ? parts[parts.length - 1] : '';
}
get sizeFormatted(): string {
const bytes = this._data.size;
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
get isImage(): boolean {
return this._data.mime.startsWith('image/');
}
get isVideo(): boolean {
return this._data.mime.startsWith('video/');
}
get isAudio(): boolean {
return this._data.mime.startsWith('audio/');
}
get isText(): boolean {
return this._data.mime.startsWith('text/') ||
this._data.mime === 'application/json' ||
this._data.mime === 'application/xml';
}
get isPdf(): boolean {
return this._data.mime === 'application/pdf';
}
}

8
src/models/index.ts Normal file
View File

@@ -0,0 +1,8 @@
/**
* Central export point for all File Manager models
*/
export { FileCollectionObject } from './collection';
export { FileEntityObject } from './entity';
export { ProviderObject } from './provider';
export { ServiceObject } from './service';

63
src/models/provider.ts Normal file
View File

@@ -0,0 +1,63 @@
/**
* Class model for Provider Interface
*/
import type { ProviderCapabilitiesInterface, ProviderInterface } from "@/types/provider";
export class ProviderObject implements ProviderInterface {
_data!: ProviderInterface;
constructor() {
this._data = {
'@type': 'files:provider',
id: '',
label: '',
capabilities: {},
};
}
fromJson(data: ProviderInterface): ProviderObject {
this._data = data;
return this;
}
toJson(): ProviderInterface {
return this._data;
}
clone(): ProviderObject {
const cloned = new ProviderObject();
cloned._data = JSON.parse(JSON.stringify(this._data));
return cloned;
}
capable(capability: keyof ProviderCapabilitiesInterface): boolean {
return !!(this._data.capabilities && this._data.capabilities[capability]);
}
capability(capability: keyof ProviderCapabilitiesInterface): boolean | string[] | Record<string, string> | Record<string, string[]> | undefined {
if (this._data.capabilities) {
return this._data.capabilities[capability];
}
return undefined;
}
/** Immutable Properties */
get '@type'(): string {
return this._data['@type'];
}
get id(): string {
return this._data.id;
}
get label(): string {
return this._data.label;
}
get capabilities(): ProviderCapabilitiesInterface {
return this._data.capabilities;
}
}

57
src/models/service.ts Normal file
View File

@@ -0,0 +1,57 @@
/**
* Class model for Service Interface
*/
import type { ServiceInterface } from "@/types/service";
export class ServiceObject implements ServiceInterface {
_data!: ServiceInterface;
constructor() {
this._data = {
'@type': 'files:service',
id: '',
provider: '',
label: '',
rootId: '',
};
}
fromJson(data: ServiceInterface): ServiceObject {
this._data = data;
return this;
}
toJson(): ServiceInterface {
return this._data;
}
clone(): ServiceObject {
const cloned = new ServiceObject();
cloned._data = JSON.parse(JSON.stringify(this._data));
return cloned;
}
/** Immutable Properties */
get '@type'(): string {
return this._data['@type'];
}
get id(): string {
return this._data.id;
}
get provider(): string {
return this._data.provider;
}
get label(): string {
return this._data.label;
}
get rootId(): string {
return this._data.rootId;
}
}

72
src/services/api.ts Normal file
View File

@@ -0,0 +1,72 @@
/**
* File Manager API Service
* Central service for making API calls to the file manager backend
*/
import { createFetchWrapper } from '@KTXC/utils/helpers/fetch-wrapper-core';
const fetchWrapper = createFetchWrapper();
const BASE_URL = '/m/file_manager/v1';
interface ApiRequest {
version: number;
transaction: string;
operation: string;
data?: Record<string, unknown>;
}
interface ApiSuccessResponse<T> {
version: number;
transaction: string;
operation: string;
status: 'success';
data: T;
}
interface ApiErrorResponse {
version: number;
transaction: string;
operation: string;
status: 'error';
error: {
code: number;
message: string;
};
}
type ApiResponseRaw<T> = ApiSuccessResponse<T> | ApiErrorResponse;
/**
* Generate a unique transaction ID
*/
function generateTransactionId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
/**
* Execute an API operation
*/
async function execute<T>(operation: string, data: Record<string, unknown> = {}): Promise<T> {
const request: ApiRequest = {
version: 1,
transaction: generateTransactionId(),
operation,
data,
};
const response: ApiResponseRaw<T> = await fetchWrapper.post(BASE_URL, request);
if (response.status === 'error') {
throw new Error(response.error.message);
}
return response.data;
}
export const fileManagerApi = {
execute,
generateTransactionId,
};
export default fileManagerApi;

View File

@@ -0,0 +1,195 @@
/**
* Collection management service
*/
import { fileManagerApi } from './api';
import type { FilterCondition, SortCondition } from '@/types/common';
import type { FileCollection } from '@/types/node';
export const collectionService = {
/**
* List collections within a location
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param location - Parent collection ID (null for root)
* @param filter - Optional filter conditions
* @param sort - Optional sort conditions
* @returns Promise with collection list
*/
async list(
provider: string,
service: string,
location?: string | null,
filter?: FilterCondition[] | null,
sort?: SortCondition[] | null
): Promise<FileCollection[]> {
return await fileManagerApi.execute<FileCollection[]>('collection.list', {
provider,
service,
location: location ?? null,
filter: filter ?? null,
sort: sort ?? null,
});
},
/**
* Check if a collection exists
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param identifier - Collection identifier
* @returns Promise with extant status
*/
async extant(
provider: string,
service: string,
identifier: string
): Promise<boolean> {
const result = await fileManagerApi.execute<{ extant: boolean }>('collection.extant', {
provider,
service,
identifier,
});
return result.extant;
},
/**
* Fetch a specific collection
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param identifier - Collection identifier
* @returns Promise with collection details
*/
async fetch(
provider: string,
service: string,
identifier: string
): Promise<FileCollection> {
return await fileManagerApi.execute<FileCollection>('collection.fetch', {
provider,
service,
identifier,
});
},
/**
* Create a new collection (folder)
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param location - Parent collection ID (null for root)
* @param data - Collection data (label, etc.)
* @param options - Additional options
* @returns Promise with created collection
*/
async create(
provider: string,
service: string,
location: string | null,
data: Partial<FileCollection>,
options?: Record<string, unknown>
): Promise<FileCollection> {
return await fileManagerApi.execute<FileCollection>('collection.create', {
provider,
service,
location,
data,
options: options ?? {},
});
},
/**
* Modify an existing collection
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param identifier - Collection identifier
* @param data - Data to modify
* @returns Promise with modified collection
*/
async modify(
provider: string,
service: string,
identifier: string,
data: Partial<FileCollection>
): Promise<FileCollection> {
return await fileManagerApi.execute<FileCollection>('collection.modify', {
provider,
service,
identifier,
data,
});
},
/**
* Delete a collection
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param identifier - Collection identifier
* @returns Promise with success status
*/
async destroy(
provider: string,
service: string,
identifier: string
): Promise<boolean> {
const result = await fileManagerApi.execute<{ success: boolean }>('collection.destroy', {
provider,
service,
identifier,
});
return result.success;
},
/**
* Copy a collection to a new location
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param identifier - Collection identifier to copy
* @param location - Destination parent collection ID (null for root)
* @returns Promise with copied collection
*/
async copy(
provider: string,
service: string,
identifier: string,
location?: string | null
): Promise<FileCollection> {
return await fileManagerApi.execute<FileCollection>('collection.copy', {
provider,
service,
identifier,
location: location ?? null,
});
},
/**
* Move a collection to a new location
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param identifier - Collection identifier to move
* @param location - Destination parent collection ID (null for root)
* @returns Promise with moved collection
*/
async move(
provider: string,
service: string,
identifier: string,
location?: string | null
): Promise<FileCollection> {
return await fileManagerApi.execute<FileCollection>('collection.move', {
provider,
service,
identifier,
location: location ?? null,
});
},
};
export default collectionService;

View File

@@ -0,0 +1,293 @@
/**
* Entity (file) management service
*/
import { fileManagerApi } from './api';
import type { FilterCondition, SortCondition, RangeCondition } from '@/types/common';
import type { FileEntity } from '@/types/node';
import type { EntityDeltaResult } from '@/types/api';
export const entityService = {
/**
* List entities within a collection
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param collection - Collection identifier
* @param filter - Optional filter conditions
* @param sort - Optional sort conditions
* @param range - Optional range/pagination conditions
* @returns Promise with entity list
*/
async list(
provider: string,
service: string,
collection: string,
filter?: FilterCondition[] | null,
sort?: SortCondition[] | null,
range?: RangeCondition | null
): Promise<FileEntity[]> {
return await fileManagerApi.execute<FileEntity[]>('entity.list', {
provider,
service,
collection,
filter: filter ?? null,
sort: sort ?? null,
range: range ?? null,
});
},
/**
* Get delta changes for entities since a signature
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param collection - Collection identifier
* @param signature - Previous sync signature
* @param detail - Detail level ('ids' or 'full')
* @returns Promise with delta changes
*/
async delta(
provider: string,
service: string,
collection: string,
signature: string,
detail: 'ids' | 'full' = 'ids'
): Promise<EntityDeltaResult> {
return await fileManagerApi.execute<EntityDeltaResult>('entity.delta', {
provider,
service,
collection,
signature,
detail,
});
},
/**
* Check which entities exist
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param collection - Collection identifier
* @param identifiers - Entity identifiers to check
* @returns Promise with existence map
*/
async extant(
provider: string,
service: string,
collection: string,
identifiers: string[]
): Promise<Record<string, boolean>> {
return await fileManagerApi.execute<Record<string, boolean>>('entity.extant', {
provider,
service,
collection,
identifiers,
});
},
/**
* Fetch specific entities
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param collection - Collection identifier
* @param identifiers - Entity identifiers to fetch
* @returns Promise with entity list
*/
async fetch(
provider: string,
service: string,
collection: string,
identifiers: string[]
): Promise<FileEntity[]> {
return await fileManagerApi.execute<FileEntity[]>('entity.fetch', {
provider,
service,
collection,
identifiers,
});
},
/**
* Read entity content
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param collection - Collection identifier
* @param identifier - Entity identifier
* @returns Promise with base64 encoded content
*/
async read(
provider: string,
service: string,
collection: string,
identifier: string
): Promise<{ content: string | null; encoding: 'base64' }> {
return await fileManagerApi.execute<{ content: string | null; encoding: 'base64' }>('entity.read', {
provider,
service,
collection,
identifier,
});
},
/**
* Create a new entity (file)
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param collection - Collection identifier (null for root)
* @param data - Entity data (label, mime, etc.)
* @param options - Additional options
* @returns Promise with created entity
*/
async create(
provider: string,
service: string,
collection: string | null,
data: Partial<FileEntity>,
options?: Record<string, unknown>
): Promise<FileEntity> {
return await fileManagerApi.execute<FileEntity>('entity.create', {
provider,
service,
collection,
data,
options: options ?? {},
});
},
/**
* Modify an existing entity
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param collection - Collection identifier (can be null)
* @param identifier - Entity identifier
* @param data - Data to modify
* @returns Promise with modified entity
*/
async modify(
provider: string,
service: string,
collection: string | null,
identifier: string,
data: Partial<FileEntity>
): Promise<FileEntity> {
return await fileManagerApi.execute<FileEntity>('entity.modify', {
provider,
service,
collection,
identifier,
data,
});
},
/**
* Delete an entity
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param collection - Collection identifier (can be null)
* @param identifier - Entity identifier
* @returns Promise with success status
*/
async destroy(
provider: string,
service: string,
collection: string | null,
identifier: string
): Promise<boolean> {
const result = await fileManagerApi.execute<{ success: boolean }>('entity.destroy', {
provider,
service,
collection,
identifier,
});
return result.success;
},
/**
* Copy an entity to a new location
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param collection - Source collection identifier (can be null)
* @param identifier - Entity identifier to copy
* @param destination - Destination collection ID (null for root)
* @returns Promise with copied entity
*/
async copy(
provider: string,
service: string,
collection: string | null,
identifier: string,
destination?: string | null
): Promise<FileEntity> {
return await fileManagerApi.execute<FileEntity>('entity.copy', {
provider,
service,
collection,
identifier,
destination: destination ?? null,
});
},
/**
* Move an entity to a new location
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param collection - Source collection identifier (can be null)
* @param identifier - Entity identifier to move
* @param destination - Destination collection ID (null for root)
* @returns Promise with moved entity
*/
async move(
provider: string,
service: string,
collection: string | null,
identifier: string,
destination?: string | null
): Promise<FileEntity> {
return await fileManagerApi.execute<FileEntity>('entity.move', {
provider,
service,
collection,
identifier,
destination: destination ?? null,
});
},
/**
* Write content to an entity
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param collection - Collection identifier (can be null)
* @param identifier - Entity identifier
* @param content - Content to write (base64 encoded)
* @returns Promise with bytes written
*/
async write(
provider: string,
service: string,
collection: string | null,
identifier: string,
content: string
): Promise<number> {
const result = await fileManagerApi.execute<{ bytesWritten: number }>('entity.write', {
provider,
service,
collection,
identifier,
content,
encoding: 'base64',
});
return result.bytesWritten;
},
};
export default entityService;

10
src/services/index.ts Normal file
View File

@@ -0,0 +1,10 @@
/**
* Central export point for all File Manager services
*/
export { fileManagerApi } from './api';
export { providerService } from './providerService';
export { serviceService } from './serviceService';
export { collectionService } from './collectionService';
export { entityService } from './entityService';
export { nodeService } from './nodeService';

View File

@@ -0,0 +1,74 @@
/**
* Node (unified collection/entity) management service
*/
import { fileManagerApi } from './api';
import type { FilterCondition, SortCondition, RangeCondition } from '@/types/common';
import type { FileNode } from '@/types/node';
import type { NodeDeltaResult } from '@/types/api';
export const nodeService = {
/**
* List all nodes (collections and entities) within a location
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param location - Parent collection ID (null for root)
* @param recursive - Whether to list recursively
* @param filter - Optional filter conditions
* @param sort - Optional sort conditions
* @param range - Optional range/pagination conditions
* @returns Promise with node list
*/
async list(
provider: string,
service: string,
location?: string | null,
recursive: boolean = false,
filter?: FilterCondition[] | null,
sort?: SortCondition[] | null,
range?: RangeCondition | null
): Promise<FileNode[]> {
return await fileManagerApi.execute<FileNode[]>('node.list', {
provider,
service,
location: location ?? null,
recursive,
filter: filter ?? null,
sort: sort ?? null,
range: range ?? null,
});
},
/**
* Get delta changes for nodes since a signature
*
* @param provider - Provider identifier
* @param service - Service identifier
* @param location - Parent collection ID (null for root)
* @param signature - Previous sync signature
* @param recursive - Whether to get delta recursively
* @param detail - Detail level ('ids' or 'full')
* @returns Promise with delta changes
*/
async delta(
provider: string,
service: string,
location: string | null,
signature: string,
recursive: boolean = false,
detail: 'ids' | 'full' = 'ids'
): Promise<NodeDeltaResult> {
return await fileManagerApi.execute<NodeDeltaResult>('node.delta', {
provider,
service,
location,
signature,
recursive,
detail,
});
},
};
export default nodeService;

View File

@@ -0,0 +1,34 @@
/**
* Provider management service
*/
import { fileManagerApi } from './api';
import type { SourceSelector } from '@/types/common';
import type { ProviderRecord } from '@/types/provider';
export const providerService = {
/**
* List all available providers
*
* @param sources - Optional source selector to filter providers
* @returns Promise with provider list keyed by provider ID
*/
async list(sources?: SourceSelector): Promise<ProviderRecord> {
return await fileManagerApi.execute<ProviderRecord>('provider.list', {
sources: sources || null
});
},
/**
* Check which providers exist/are available
*
* @param sources - Source selector with provider IDs to check
* @returns Promise with provider availability status
*/
async extant(sources: SourceSelector): Promise<Record<string, boolean>> {
return await fileManagerApi.execute<Record<string, boolean>>('provider.extant', { sources });
},
};
export default providerService;

View File

@@ -0,0 +1,48 @@
/**
* Service management service
*/
import { fileManagerApi } from './api';
import type { SourceSelector } from '@/types/common';
import type { ServiceInterface, ServiceRecord } from '@/types/service';
export const serviceService = {
/**
* List all available services
*
* @param sources - Optional source selector to filter services
* @returns Promise with service list grouped by provider
*/
async list(sources?: SourceSelector): Promise<ServiceRecord> {
return await fileManagerApi.execute<ServiceRecord>('service.list', {
sources: sources || null
});
},
/**
* Check which services exist/are available
*
* @param sources - Source selector with service IDs to check
* @returns Promise with service availability status
*/
async extant(sources: SourceSelector): Promise<Record<string, boolean>> {
return await fileManagerApi.execute<Record<string, boolean>>('service.extant', { sources });
},
/**
* Fetch a specific service
*
* @param provider - Provider identifier
* @param identifier - Service identifier
* @returns Promise with service details
*/
async fetch(provider: string, identifier: string): Promise<ServiceInterface> {
return await fileManagerApi.execute<ServiceInterface>('service.fetch', {
provider,
identifier
});
},
};
export default serviceService;

687
src/stores/nodesStore.ts Normal file
View File

@@ -0,0 +1,687 @@
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'
// 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>
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)
// 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))
})
})
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]
}
// Get all nodes for a service
const getServiceNodes = (
providerId: string,
serviceId: string
): NodeRecord[] => {
return Object.values(nodes.value[providerId]?.[serviceId] || {})
}
// 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)
}
// 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[] => {
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) {
break
}
currentNode = getNode(providerId, serviceId, currentNode.in)
}
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 = (
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]
}
}
}
// 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))
} 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 (
providerId: string,
serviceId: string,
collection: string,
filter?: FilterCondition[] | null,
sort?: SortCondition[] | null,
range?: RangeCondition | null
): Promise<FileEntityObject[]> => {
loading.value = true
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))
} 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 (
providerId: string,
serviceId: string,
location: string | null,
data: Partial<FileCollection>,
options?: Record<string, unknown>
): Promise<FileCollectionObject> => {
loading.value = true
error.value = null
try {
const created = await collectionService.create(providerId, serviceId, location, data, options)
addNode(providerId, serviceId, created)
return new FileCollectionObject().fromJson(created)
} 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 (
providerId: string,
serviceId: string,
collection: string | null,
data: Partial<FileEntity>,
options?: Record<string, unknown>
): Promise<FileEntityObject> => {
loading.value = true
error.value = null
try {
const created = await entityService.create(providerId, serviceId, collection, data, options)
addNode(providerId, serviceId, created)
return new FileEntityObject().fromJson(created)
} 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 (
providerId: string,
serviceId: string,
identifier: string,
data: Partial<FileCollection>
): Promise<FileCollectionObject> => {
loading.value = true
error.value = null
try {
const modified = await collectionService.modify(providerId, serviceId, identifier, data)
addNode(providerId, serviceId, modified)
return new FileCollectionObject().fromJson(modified)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to modify collection'
throw e
} finally {
loading.value = false
}
}
// Modify an entity
const modifyEntity = async (
providerId: string,
serviceId: string,
collection: string | null,
identifier: string,
data: Partial<FileEntity>
): Promise<FileEntityObject> => {
loading.value = true
error.value = null
try {
const modified = await entityService.modify(providerId, serviceId, collection, identifier, data)
addNode(providerId, serviceId, modified)
return new FileEntityObject().fromJson(modified)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to modify entity'
throw e
} finally {
loading.value = false
}
}
// Destroy a collection
const destroyCollection = async (
providerId: string,
serviceId: string,
identifier: string
): Promise<boolean> => {
loading.value = true
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
} 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 (
providerId: string,
serviceId: string,
collection: string | null,
identifier: string,
content: string
): Promise<number> => {
loading.value = true
error.value = null
try {
return await entityService.write(providerId, serviceId, collection, 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
}
}
return {
// State
nodes,
syncTokens,
loading,
error,
// Constants
ROOT_ID,
// Computed
nodeList,
collectionList,
entityList,
// Getters
getNode,
getServiceNodes,
getChildren,
getChildCollections,
getChildEntities,
getPath,
isRoot,
// Setters
setNodes,
addNode,
addNodes,
removeNode,
removeNodes,
clearServiceNodes,
clearNodes,
// Sync
getSyncToken,
setSyncToken,
// API Actions - Fetch
fetchNodes,
fetchCollections,
fetchEntities,
// API Actions - Create
createCollection,
createEntity,
// API Actions - Modify
modifyCollection,
modifyEntity,
// API Actions - Destroy
destroyCollection,
destroyEntity,
// API Actions - Copy
copyCollection,
copyEntity,
// API Actions - Move
moveCollection,
moveEntity,
// API Actions - Content
readEntity,
writeEntity,
// API Actions - Sync
syncDelta,
}
})

View File

@@ -0,0 +1,104 @@
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 { ProviderObject } from '../models/provider'
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)
const providerList: ComputedRef<ProviderObject[]> = computed(() =>
Object.values(providers.value)
)
const providerIds: ComputedRef<string[]> = computed(() =>
Object.keys(providers.value)
)
const getProvider = (id: string): ProviderObject | undefined => {
return providers.value[id]
}
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)
}
providers.value = hydrated
initialized.value = true
}
const addProvider = (id: string, provider: ProviderInterface) => {
providers.value[id] = new ProviderObject().fromJson(provider)
}
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
try {
const data = await providerService.list(sources)
setProviders(data)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to fetch providers'
throw e
} finally {
loading.value = false
}
}
const checkProviderExtant = async (sources: SourceSelector): Promise<Record<string, boolean>> => {
try {
return await providerService.extant(sources)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to check providers'
throw e
}
}
return {
// State
providers,
loading,
error,
initialized,
// Computed
providerList,
providerIds,
// Getters
getProvider,
hasProvider,
isCapable,
// Setters
setProviders,
addProvider,
removeProvider,
clearProviders,
// Actions
fetchProviders,
checkProviderExtant,
}
})

131
src/stores/servicesStore.ts Normal file
View File

@@ -0,0 +1,131 @@
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 { ServiceObject } from '../models/service'
// Nested structure: provider -> service -> ServiceObject
type ServiceStore = Record<string, Record<string, ServiceObject>>
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)
const serviceList: ComputedRef<ServiceObject[]> = computed(() => {
const result: ServiceObject[] = []
Object.values(services.value).forEach(providerServices => {
result.push(...Object.values(providerServices))
})
return result
})
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)
}
services.value = hydrated
initialized.value = true
}
const addService = (providerId: string, serviceId: string, service: ServiceInterface) => {
if (!services.value[providerId]) {
services.value[providerId] = {}
}
services.value[providerId][serviceId] = new ServiceObject().fromJson(service)
}
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
try {
const data = await serviceService.list(sources)
setServices(data)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to fetch services'
throw e
} finally {
loading.value = false
}
}
const checkServiceExtant = async (sources: SourceSelector): Promise<Record<string, boolean>> => {
try {
return await serviceService.extant(sources)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to check services'
throw e
}
}
const fetchService = async (providerId: string, serviceId: string): Promise<ServiceObject> => {
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
}
}
return {
// State
services,
loading,
error,
initialized,
// Computed
serviceList,
// Getters
getService,
hasService,
getProviderServices,
getRootId,
// Setters
setServices,
addService,
removeService,
clearServices,
// Actions
fetchServices,
checkServiceExtant,
fetchService,
}
})

262
src/types/api.ts Normal file
View File

@@ -0,0 +1,262 @@
/**
* File Manager API Types - Request and Response interfaces
*/
import type { SourceSelector, FilterCondition, SortCondition, RangeCondition, ApiResponse } from '@/types/common';
import type { ProviderRecord } from '@/types/provider';
import type { ServiceInterface, ServiceRecord } from '@/types/service';
import type { FileCollection, FileEntity, FileNode } from '@/types/node';
// ==================== Provider Types ====================
export type ProviderListResponse = ApiResponse<ProviderRecord>;
export interface ProviderExtantRequest {
sources: SourceSelector;
}
export type ProviderExtantResponse = ApiResponse<Record<string, boolean>>;
// ==================== Service Types ====================
export type ServiceListResponse = ApiResponse<ServiceRecord>;
export interface ServiceExtantRequest {
sources: SourceSelector;
}
export type ServiceExtantResponse = ApiResponse<Record<string, boolean>>;
export interface ServiceFetchRequest {
provider: string;
identifier: string;
}
export type ServiceFetchResponse = ApiResponse<ServiceInterface>;
// ==================== Collection Types ====================
export interface CollectionListRequest {
provider: string;
service: string;
location?: string | null;
filter?: FilterCondition[] | null;
sort?: SortCondition[] | null;
}
export type CollectionListResponse = ApiResponse<FileCollection[]>;
export interface CollectionExtantRequest {
provider: string;
service: string;
identifier: string;
}
export type CollectionExtantResponse = ApiResponse<{ extant: boolean }>;
export interface CollectionFetchRequest {
provider: string;
service: string;
identifier: string;
}
export type CollectionFetchResponse = ApiResponse<FileCollection>;
export interface CollectionCreateRequest {
provider: string;
service: string;
location?: string | null;
data: Partial<FileCollection>;
options?: Record<string, unknown>;
}
export type CollectionCreateResponse = ApiResponse<FileCollection>;
export interface CollectionModifyRequest {
provider: string;
service: string;
identifier: string;
data: Partial<FileCollection>;
}
export type CollectionModifyResponse = ApiResponse<FileCollection>;
export interface CollectionDestroyRequest {
provider: string;
service: string;
identifier: string;
}
export type CollectionDestroyResponse = ApiResponse<{ success: boolean }>;
export interface CollectionCopyRequest {
provider: string;
service: string;
identifier: string;
location?: string | null;
}
export type CollectionCopyResponse = ApiResponse<FileCollection>;
export interface CollectionMoveRequest {
provider: string;
service: string;
identifier: string;
location?: string | null;
}
export type CollectionMoveResponse = ApiResponse<FileCollection>;
// ==================== Entity Types ====================
export interface EntityListRequest {
provider: string;
service: string;
collection: string;
filter?: FilterCondition[] | null;
sort?: SortCondition[] | null;
range?: RangeCondition | null;
}
export type EntityListResponse = ApiResponse<FileEntity[]>;
export interface EntityDeltaRequest {
provider: string;
service: string;
collection: string;
signature: string;
detail?: 'ids' | 'full';
}
export interface EntityDeltaResult {
added: string[] | FileEntity[];
modified: string[] | FileEntity[];
removed: string[];
signature: string;
}
export type EntityDeltaResponse = ApiResponse<EntityDeltaResult>;
export interface EntityExtantRequest {
provider: string;
service: string;
collection: string;
identifiers: string[];
}
export type EntityExtantResponse = ApiResponse<Record<string, boolean>>;
export interface EntityFetchRequest {
provider: string;
service: string;
collection: string;
identifiers: string[];
}
export type EntityFetchResponse = ApiResponse<FileEntity[]>;
export interface EntityReadRequest {
provider: string;
service: string;
collection: string;
identifier: string;
}
export interface EntityReadResult {
content: string | null;
encoding: 'base64';
}
export type EntityReadResponse = ApiResponse<EntityReadResult>;
export interface EntityCreateRequest {
provider: string;
service: string;
collection?: string | null;
data: Partial<FileEntity>;
options?: Record<string, unknown>;
}
export type EntityCreateResponse = ApiResponse<FileEntity>;
export interface EntityModifyRequest {
provider: string;
service: string;
collection?: string | null;
identifier: string;
data: Partial<FileEntity>;
}
export type EntityModifyResponse = ApiResponse<FileEntity>;
export interface EntityDestroyRequest {
provider: string;
service: string;
collection?: string | null;
identifier: string;
}
export type EntityDestroyResponse = ApiResponse<{ success: boolean }>;
export interface EntityCopyRequest {
provider: string;
service: string;
collection?: string | null;
identifier: string;
destination?: string | null;
}
export type EntityCopyResponse = ApiResponse<FileEntity>;
export interface EntityMoveRequest {
provider: string;
service: string;
collection?: string | null;
identifier: string;
destination?: string | null;
}
export type EntityMoveResponse = ApiResponse<FileEntity>;
export interface EntityWriteRequest {
provider: string;
service: string;
collection?: string | null;
identifier: string;
content: string;
encoding?: 'base64';
}
export type EntityWriteResponse = ApiResponse<{ bytesWritten: number }>;
// ==================== Node Types (Unified/Recursive) ====================
export interface NodeListRequest {
provider: string;
service: string;
location?: string | null;
recursive?: boolean;
filter?: FilterCondition[] | null;
sort?: SortCondition[] | null;
range?: RangeCondition | null;
}
export type NodeListResponse = ApiResponse<FileNode[]>;
export interface NodeDeltaRequest {
provider: string;
service: string;
location?: string | null;
signature: string;
recursive?: boolean;
detail?: 'ids' | 'full';
}
export interface NodeDeltaResult {
added: string[] | FileNode[];
modified: string[] | FileNode[];
removed: string[];
signature: string;
}
export type NodeDeltaResponse = ApiResponse<NodeDeltaResult>;

85
src/types/common.ts Normal file
View File

@@ -0,0 +1,85 @@
/**
* Common types for file manager
*/
export type SourceSelector = {
[provider: string]: boolean | ServiceSelector;
};
export type ServiceSelector = {
[service: string]: boolean;
};
export const SortDirection = {
Ascending: 'asc',
Descending: 'desc'
} as const;
export type SortDirection = typeof SortDirection[keyof typeof SortDirection];
export const RangeType = {
Tally: 'tally',
Date: 'date'
} as const;
export type RangeType = typeof RangeType[keyof typeof RangeType];
export const RangeAnchorType = {
Absolute: 'absolute',
Relative: 'relative'
} as const;
export type RangeAnchorType = typeof RangeAnchorType[keyof typeof RangeAnchorType];
export const FilterOperator = {
Equals: 'eq',
NotEquals: 'ne',
GreaterThan: 'gt',
LessThan: 'lt',
GreaterThanOrEquals: 'gte',
LessThanOrEquals: 'lte',
Contains: 'contains',
StartsWith: 'startsWith',
EndsWith: 'endsWith',
In: 'in',
NotIn: 'notIn'
} as const;
export type FilterOperator = typeof FilterOperator[keyof typeof FilterOperator];
export interface FilterCondition {
attribute: string;
value: unknown;
operator?: FilterOperator;
}
export interface SortCondition {
attribute: string;
direction: SortDirection;
}
export interface RangeCondition {
type: RangeType;
anchor?: RangeAnchorType;
position?: string | number;
tally?: number;
}
export interface ApiRequest {
version: number;
transaction: string;
operation: string;
data?: Record<string, unknown>;
}
export interface ApiResponse<T = unknown> {
version: number;
transaction: string;
operation: string;
status: 'success' | 'error';
data?: T;
error?: {
code: number;
message: string;
};
}

9
src/types/index.ts Normal file
View File

@@ -0,0 +1,9 @@
/**
* File manager types barrel export
*/
export * from './common';
export * from './provider';
export * from './service';
export * from './node';
export * from './api';

65
src/types/node.ts Normal file
View File

@@ -0,0 +1,65 @@
/**
* Node types for file manager (collections and entities)
*/
export type NodeType = 'files.collection' | 'files.entity';
export interface NodeBase {
'@type': NodeType;
in: string | null;
id: string;
createdBy: string;
createdOn: string;
modifiedBy: string;
modifiedOn: string;
owner: string;
signature: string;
label: string;
}
export interface FileCollection extends NodeBase {
'@type': 'files.collection';
}
export interface FileEntity extends NodeBase {
'@type': 'files.entity';
size: number;
mime: string;
format: string;
encoding: string;
}
export type FileNode = FileCollection | FileEntity;
export interface NodeListResult {
items: FileNode[];
total: number;
hasMore?: boolean;
}
export interface EntityListResult {
items: FileEntity[];
total: number;
hasMore?: boolean;
}
export interface CollectionListResult {
items: FileCollection[];
total: number;
hasMore?: boolean;
}
export interface DeltaResult {
added: FileNode[];
modified: FileNode[];
removed: string[];
signature: string;
}
export function isFileCollection(node: FileNode): node is FileCollection {
return node['@type'] === 'files.collection';
}
export function isFileEntity(node: FileNode): node is FileEntity {
return node['@type'] === 'files.entity';
}

49
src/types/provider.ts Normal file
View File

@@ -0,0 +1,49 @@
/**
* Provider types for file manager
*/
export interface ProviderCapabilitiesInterface {
CollectionList?: boolean;
CollectionListFilter?: boolean | Record<string, string>;
CollectionListSort?: boolean | string[];
CollectionExtant?: boolean;
CollectionFetch?: boolean;
CollectionCreate?: boolean;
CollectionModify?: boolean;
CollectionDestroy?: boolean;
CollectionCopy?: boolean;
CollectionMove?: boolean;
EntityList?: boolean;
EntityListFilter?: boolean | Record<string, string>;
EntityListSort?: boolean | string[];
EntityListRange?: boolean | Record<string, string[]>;
EntityDelta?: boolean;
EntityExtant?: boolean;
EntityFetch?: boolean;
EntityRead?: boolean;
EntityReadStream?: boolean;
EntityReadChunk?: boolean;
EntityCreate?: boolean;
EntityModify?: boolean;
EntityDestroy?: boolean;
EntityCopy?: boolean;
EntityMove?: boolean;
EntityWrite?: boolean;
EntityWriteStream?: boolean;
EntityWriteChunk?: boolean;
NodeList?: boolean;
NodeListFilter?: boolean | Record<string, string>;
NodeListSort?: boolean | string[];
NodeListRange?: boolean | Record<string, string[]>;
NodeDelta?: boolean;
[key: string]: boolean | string[] | Record<string, string> | Record<string, string[]> | undefined;
}
export interface ProviderInterface {
'@type': string;
id: string;
label: string;
capabilities: ProviderCapabilitiesInterface;
}
export type ProviderRecord = Record<string, ProviderInterface>;

13
src/types/service.ts Normal file
View File

@@ -0,0 +1,13 @@
/**
* Service types for file manager
*/
export interface ServiceInterface {
'@type': string;
id: string;
provider: string;
label: string;
rootId: string;
}
export type ServiceRecord = Record<string, ServiceInterface>;