refactor: documents interfaces
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,11 @@ use KTXC\Http\Response\StreamedResponse;
|
|||||||
use KTXC\SessionIdentity;
|
use KTXC\SessionIdentity;
|
||||||
use KTXC\SessionTenant;
|
use KTXC\SessionTenant;
|
||||||
use KTXF\Controller\ControllerAbstract;
|
use KTXF\Controller\ControllerAbstract;
|
||||||
|
use KTXF\Documents\Collection\CollectionBaseInterface;
|
||||||
|
use KTXF\Documents\Entity\EntityBaseInterface;
|
||||||
|
use KTXF\Resource\Identifier\CollectionIdentifier;
|
||||||
|
use KTXF\Resource\Identifier\EntityIdentifier;
|
||||||
|
use KTXF\Resource\Identifier\ResourceIdentifiers;
|
||||||
use KTXF\Routing\Attributes\AuthenticatedRoute;
|
use KTXF\Routing\Attributes\AuthenticatedRoute;
|
||||||
use KTXM\DocumentsManager\Manager;
|
use KTXM\DocumentsManager\Manager;
|
||||||
use KTXM\DocumentsManager\Transfer\StreamingZip;
|
use KTXM\DocumentsManager\Transfer\StreamingZip;
|
||||||
@@ -23,7 +28,7 @@ use Throwable;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller for file transfers (downloads and uploads)
|
* Controller for file transfers (downloads and uploads)
|
||||||
*
|
*
|
||||||
* Handles binary file transfers that don't fit the JSON API pattern:
|
* Handles binary file transfers that don't fit the JSON API pattern:
|
||||||
* - Single file downloads (streamed)
|
* - Single file downloads (streamed)
|
||||||
* - Multi-file downloads as ZIP (streamed)
|
* - Multi-file downloads as ZIP (streamed)
|
||||||
@@ -42,7 +47,7 @@ class TransferController extends ControllerAbstract
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Download a single file
|
* Download a single file
|
||||||
*
|
*
|
||||||
* GET /download/entity/{provider}/{service}/{collection}/{identifier}
|
* GET /download/entity/{provider}/{service}/{collection}/{identifier}
|
||||||
*/
|
*/
|
||||||
#[AuthenticatedRoute(
|
#[AuthenticatedRoute(
|
||||||
@@ -55,15 +60,11 @@ class TransferController extends ControllerAbstract
|
|||||||
$userId = $this->userIdentity->identifier();
|
$userId = $this->userIdentity->identifier();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
$target = new EntityIdentifier($provider, $service, $collection, $identifier);
|
||||||
|
|
||||||
// Fetch entity metadata
|
// Fetch entity metadata
|
||||||
$entities = $this->manager->entityFetch(
|
/** @var EntityBaseInterface[] $entities */
|
||||||
$tenantId,
|
$entities = $this->manager->entityFetchBulk($tenantId, $userId, $target);
|
||||||
$userId,
|
|
||||||
$provider,
|
|
||||||
$service,
|
|
||||||
$collection,
|
|
||||||
[$identifier]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (empty($entities) || !isset($entities[$identifier])) {
|
if (empty($entities) || !isset($entities[$identifier])) {
|
||||||
return new JsonResponse([
|
return new JsonResponse([
|
||||||
@@ -73,16 +74,9 @@ class TransferController extends ControllerAbstract
|
|||||||
}
|
}
|
||||||
|
|
||||||
$entity = $entities[$identifier];
|
$entity = $entities[$identifier];
|
||||||
|
|
||||||
// Get the stream
|
// Get the stream
|
||||||
$stream = $this->manager->entityReadStream(
|
$stream = $this->manager->entityReadStream($tenantId, $userId, $target);
|
||||||
$tenantId,
|
|
||||||
$userId,
|
|
||||||
$provider,
|
|
||||||
$service,
|
|
||||||
$collection,
|
|
||||||
$identifier
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($stream === null) {
|
if ($stream === null) {
|
||||||
return new JsonResponse([
|
return new JsonResponse([
|
||||||
@@ -114,7 +108,7 @@ class TransferController extends ControllerAbstract
|
|||||||
if ($size > 0) {
|
if ($size > 0) {
|
||||||
$response->headers->set('Content-Length', (string) $size);
|
$response->headers->set('Content-Length', (string) $size);
|
||||||
}
|
}
|
||||||
$response->headers->set('Content-Disposition',
|
$response->headers->set('Content-Disposition',
|
||||||
$response->headers->makeDisposition('attachment', $filename, $this->asciiFallback($filename))
|
$response->headers->makeDisposition('attachment', $filename, $this->asciiFallback($filename))
|
||||||
);
|
);
|
||||||
$response->headers->set('Cache-Control', 'private, no-cache');
|
$response->headers->set('Cache-Control', 'private, no-cache');
|
||||||
@@ -132,7 +126,7 @@ class TransferController extends ControllerAbstract
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Download multiple files as a ZIP archive
|
* Download multiple files as a ZIP archive
|
||||||
*
|
*
|
||||||
* GET /download/archive?provider=...&service=...&ids[]=...&ids[]=...
|
* GET /download/archive?provider=...&service=...&ids[]=...&ids[]=...
|
||||||
*/
|
*/
|
||||||
#[AuthenticatedRoute(
|
#[AuthenticatedRoute(
|
||||||
@@ -179,10 +173,7 @@ class TransferController extends ControllerAbstract
|
|||||||
$stream = $this->manager->entityReadStream(
|
$stream = $this->manager->entityReadStream(
|
||||||
$tenantId,
|
$tenantId,
|
||||||
$userId,
|
$userId,
|
||||||
$provider,
|
new EntityIdentifier($provider, $service, (string)$file['collection'], (string)$file['id'])
|
||||||
$service,
|
|
||||||
$file['collection'],
|
|
||||||
$file['id']
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($stream !== null) {
|
if ($stream !== null) {
|
||||||
@@ -221,7 +212,7 @@ class TransferController extends ControllerAbstract
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Download a collection (folder) as a ZIP archive with structure preserved
|
* Download a collection (folder) as a ZIP archive with structure preserved
|
||||||
*
|
*
|
||||||
* GET /download/collection/{provider}/{service}/{identifier}
|
* GET /download/collection/{provider}/{service}/{identifier}
|
||||||
*/
|
*/
|
||||||
#[AuthenticatedRoute(
|
#[AuthenticatedRoute(
|
||||||
@@ -239,9 +230,7 @@ class TransferController extends ControllerAbstract
|
|||||||
$collection = $this->manager->collectionFetch(
|
$collection = $this->manager->collectionFetch(
|
||||||
$tenantId,
|
$tenantId,
|
||||||
$userId,
|
$userId,
|
||||||
$provider,
|
new CollectionIdentifier($provider, $service, $identifier)
|
||||||
$service,
|
|
||||||
$identifier
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($collection === null) {
|
if ($collection === null) {
|
||||||
@@ -251,7 +240,7 @@ class TransferController extends ControllerAbstract
|
|||||||
], Response::HTTP_NOT_FOUND);
|
], Response::HTTP_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
$folderName = $collection->getLabel() ?? 'folder';
|
$folderName = $collection->getProperties()->getLabel() ?? 'folder';
|
||||||
$archiveName = $this->sanitizeFilename($folderName) . '.zip';
|
$archiveName = $this->sanitizeFilename($folderName) . '.zip';
|
||||||
|
|
||||||
// Build recursive file list
|
// Build recursive file list
|
||||||
@@ -275,10 +264,7 @@ class TransferController extends ControllerAbstract
|
|||||||
$stream = $this->manager->entityReadStream(
|
$stream = $this->manager->entityReadStream(
|
||||||
$tenantId,
|
$tenantId,
|
||||||
$userId,
|
$userId,
|
||||||
$provider,
|
new EntityIdentifier($provider, $service, (string)$file['collection'], (string)$file['id'])
|
||||||
$service,
|
|
||||||
$file['collection'],
|
|
||||||
$file['id']
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($stream !== null) {
|
if ($stream !== null) {
|
||||||
@@ -325,13 +311,10 @@ class TransferController extends ControllerAbstract
|
|||||||
// Try as entity first
|
// Try as entity first
|
||||||
if ($collection !== null) {
|
if ($collection !== null) {
|
||||||
/** @var EntityBaseInterface[] $entities */
|
/** @var EntityBaseInterface[] $entities */
|
||||||
$entities = $this->manager->entityFetch(
|
$entities = $this->manager->entityFetchBulk(
|
||||||
$tenantId,
|
$tenantId,
|
||||||
$userId,
|
$userId,
|
||||||
$provider,
|
new EntityIdentifier($provider, $service, $collection, (string)$id)
|
||||||
$service,
|
|
||||||
$collection,
|
|
||||||
[$id]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!empty($entities) && isset($entities[$id])) {
|
if (!empty($entities) && isset($entities[$id])) {
|
||||||
@@ -340,8 +323,8 @@ class TransferController extends ControllerAbstract
|
|||||||
'type' => 'file',
|
'type' => 'file',
|
||||||
'id' => $id,
|
'id' => $id,
|
||||||
'collection' => $collection,
|
'collection' => $collection,
|
||||||
'path' => $entity->getLabel() ?? $id,
|
'path' => $entity->getProperties()->getLabel() ?? $id,
|
||||||
'modTime' => $entity->modifiedOn()?->getTimestamp(),
|
'modTime' => $entity->modified()?->getTimestamp(),
|
||||||
];
|
];
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -352,19 +335,17 @@ class TransferController extends ControllerAbstract
|
|||||||
$collectionNode = $this->manager->collectionFetch(
|
$collectionNode = $this->manager->collectionFetch(
|
||||||
$tenantId,
|
$tenantId,
|
||||||
$userId,
|
$userId,
|
||||||
$provider,
|
new CollectionIdentifier($provider, $service, (string)$id)
|
||||||
$service,
|
|
||||||
$id
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($collectionNode !== null) {
|
if ($collectionNode !== null) {
|
||||||
$folderName = $collectionNode->getLabel() ?? $id;
|
$folderName = $collectionNode->getProperties()->getLabel() ?? $id;
|
||||||
$subFiles = $this->resolveCollectionContents(
|
$subFiles = $this->resolveCollectionContents(
|
||||||
$tenantId,
|
$tenantId,
|
||||||
$userId,
|
$userId,
|
||||||
$provider,
|
$provider,
|
||||||
$service,
|
$service,
|
||||||
$id,
|
(string)$id,
|
||||||
$folderName
|
$folderName
|
||||||
);
|
);
|
||||||
$files = array_merge($files, $subFiles);
|
$files = array_merge($files, $subFiles);
|
||||||
@@ -407,17 +388,13 @@ class TransferController extends ControllerAbstract
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all nodes in this collection using nodeList with recursive=false
|
// List immediate child collections and entities of this collection;
|
||||||
// We handle recursion ourselves to build proper paths
|
// recursion is handled here to build proper archive paths
|
||||||
|
$sources = new ResourceIdentifiers();
|
||||||
|
$sources->add(new CollectionIdentifier($provider, $service, $collectionId));
|
||||||
try {
|
try {
|
||||||
$nodes = $this->manager->nodeList(
|
$collections = $this->manager->collectionList($tenantId, $userId, $sources)[$provider][$service] ?? [];
|
||||||
$tenantId,
|
$entities = $this->manager->entityListBulk($tenantId, $userId, $sources)[$provider][$service][$collectionId] ?? [];
|
||||||
$userId,
|
|
||||||
$provider,
|
|
||||||
$service,
|
|
||||||
$collectionId,
|
|
||||||
false // Not recursive - we handle it ourselves
|
|
||||||
);
|
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$this->logger->warning('Failed to list collection contents', [
|
$this->logger->warning('Failed to list collection contents', [
|
||||||
'collection' => $collectionId,
|
'collection' => $collectionId,
|
||||||
@@ -426,34 +403,35 @@ class TransferController extends ControllerAbstract
|
|||||||
return $files;
|
return $files;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($nodes as $node) {
|
/** @var CollectionBaseInterface $node */
|
||||||
$nodeName = $node->getLabel() ?? (string) $node->id();
|
foreach ($collections as $node) {
|
||||||
|
$nodeName = $node->getProperties()->getLabel() ?? (string) $node->identifier();
|
||||||
$nodePath = $basePath !== '' ? $basePath . '/' . $nodeName : $nodeName;
|
$nodePath = $basePath !== '' ? $basePath . '/' . $nodeName : $nodeName;
|
||||||
|
// Recursively get contents of sub-collection
|
||||||
|
$subFiles = $this->resolveCollectionContents(
|
||||||
|
$tenantId,
|
||||||
|
$userId,
|
||||||
|
$provider,
|
||||||
|
$service,
|
||||||
|
(string) $node->identifier(),
|
||||||
|
$nodePath,
|
||||||
|
$depth + 1,
|
||||||
|
$maxDepth
|
||||||
|
);
|
||||||
|
$files = array_merge($files, $subFiles);
|
||||||
|
}
|
||||||
|
|
||||||
if ($node->isCollection()) {
|
/** @var EntityBaseInterface $node */
|
||||||
// Recursively get contents of sub-collection
|
foreach ($entities as $node) {
|
||||||
$subFiles = $this->resolveCollectionContents(
|
$nodeName = $node->getProperties()->getLabel() ?? (string) $node->identifier();
|
||||||
$tenantId,
|
$nodePath = $basePath !== '' ? $basePath . '/' . $nodeName : $nodeName;
|
||||||
$userId,
|
$files[] = [
|
||||||
$provider,
|
'type' => 'file',
|
||||||
$service,
|
'id' => (string) $node->identifier(),
|
||||||
(string) $node->id(),
|
'collection' => $collectionId,
|
||||||
$nodePath,
|
'path' => $nodePath,
|
||||||
$depth + 1,
|
'modTime' => $node->modified()?->getTimestamp(),
|
||||||
$maxDepth
|
];
|
||||||
);
|
|
||||||
$files = array_merge($files, $subFiles);
|
|
||||||
} else {
|
|
||||||
// It's an entity (file)
|
|
||||||
/** @var INodeEntityBase $node */
|
|
||||||
$files[] = [
|
|
||||||
'type' => 'file',
|
|
||||||
'id' => (string) $node->id(),
|
|
||||||
'collection' => $collectionId,
|
|
||||||
'path' => $nodePath,
|
|
||||||
'modTime' => $node->modifiedOn()?->getTimestamp(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $files;
|
return $files;
|
||||||
@@ -467,11 +445,11 @@ class TransferController extends ControllerAbstract
|
|||||||
// Remove or replace problematic characters
|
// Remove or replace problematic characters
|
||||||
$filename = preg_replace('/[<>:"\/\\|?*\x00-\x1F]/', '_', $filename);
|
$filename = preg_replace('/[<>:"\/\\|?*\x00-\x1F]/', '_', $filename);
|
||||||
$filename = trim($filename, '. ');
|
$filename = trim($filename, '. ');
|
||||||
|
|
||||||
if ($filename === '') {
|
if ($filename === '') {
|
||||||
$filename = 'download';
|
$filename = 'download';
|
||||||
}
|
}
|
||||||
|
|
||||||
return $filename;
|
return $filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+779
-624
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace KTXM\DocumentsManager\Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implemented by a stream event that declares, up front, how many data frames
|
||||||
|
* are expected to follow. When such an event leads a stream generator, the
|
||||||
|
* envelope writer folds its value into the `control:start` frame's `total`
|
||||||
|
* (the progress denominator) instead of emitting it as a `data` frame.
|
||||||
|
*/
|
||||||
|
interface ExpectedTotal {
|
||||||
|
public function expectedTotal(): int;
|
||||||
|
}
|
||||||
@@ -4,6 +4,19 @@
|
|||||||
|
|
||||||
import type { CollectionContentTypes, CollectionInterface, CollectionModelInterface, CollectionPropertiesInterface } from "@/types/collection";
|
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 {
|
export class CollectionObject implements CollectionModelInterface {
|
||||||
|
|
||||||
_data!: CollectionInterface;
|
_data!: CollectionInterface;
|
||||||
@@ -24,7 +37,14 @@ export class CollectionObject implements CollectionModelInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fromJson(data: CollectionInterface): CollectionObject {
|
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) {
|
if (data.properties) {
|
||||||
this._data.properties = new CollectionPropertiesObject().fromJson(data.properties as CollectionPropertiesInterface);
|
this._data.properties = new CollectionPropertiesObject().fromJson(data.properties as CollectionPropertiesInterface);
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-1
@@ -5,6 +5,19 @@ import type { EntityInterface, EntityModelInterface } from "@/types/entity";
|
|||||||
import type { DocumentInterface, DocumentModelInterface } from "@/types/document";
|
import type { DocumentInterface, DocumentModelInterface } from "@/types/document";
|
||||||
import { DocumentObject } from "./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 {
|
export class EntityObject implements EntityModelInterface {
|
||||||
|
|
||||||
_data!: EntityInterface<DocumentInterface|DocumentModelInterface>;
|
_data!: EntityInterface<DocumentInterface|DocumentModelInterface>;
|
||||||
@@ -25,7 +38,14 @@ export class EntityObject implements EntityModelInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fromJson(data: EntityInterface): EntityObject {
|
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) {
|
if (data.properties) {
|
||||||
this._data.properties = new DocumentObject().fromJson(data.properties as DocumentInterface);
|
this._data.properties = new DocumentObject().fromJson(data.properties as DocumentInterface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ import type {
|
|||||||
CollectionUpdateRequest,
|
CollectionUpdateRequest,
|
||||||
CollectionDeleteResponse,
|
CollectionDeleteResponse,
|
||||||
CollectionDeleteRequest,
|
CollectionDeleteRequest,
|
||||||
|
CollectionCopyRequest,
|
||||||
|
CollectionCopyResponse,
|
||||||
|
CollectionMoveRequest,
|
||||||
|
CollectionMoveResponse,
|
||||||
CollectionInterface,
|
CollectionInterface,
|
||||||
} from '../types/collection';
|
} from '../types/collection';
|
||||||
import { useIntegrationStore } from '@KTXC/stores/integrationStore';
|
import { useIntegrationStore } from '@KTXC/stores/integrationStore';
|
||||||
@@ -141,15 +145,39 @@ export const collectionService = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a collection
|
* Delete a collection
|
||||||
*
|
*
|
||||||
* @param request - delete request parameters
|
* @param request - delete request parameters
|
||||||
*
|
*
|
||||||
* @returns Promise with deletion result
|
* @returns Promise with deletion result
|
||||||
*/
|
*/
|
||||||
async delete(request: CollectionDeleteRequest): Promise<CollectionDeleteResponse> {
|
async delete(request: CollectionDeleteRequest): Promise<CollectionDeleteResponse> {
|
||||||
return await transceivePost<CollectionDeleteRequest, CollectionDeleteResponse>('collection.delete', request);
|
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;
|
export default collectionService;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Entity management service
|
* Entity management service
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { transceivePost } from './transceive';
|
import { transceivePost, transceiveStream } from './transceive';
|
||||||
import type {
|
import type {
|
||||||
EntityListRequest,
|
EntityListRequest,
|
||||||
EntityListResponse,
|
EntityListResponse,
|
||||||
@@ -18,6 +18,10 @@ import type {
|
|||||||
EntityDeleteResponse,
|
EntityDeleteResponse,
|
||||||
EntityDeltaRequest,
|
EntityDeltaRequest,
|
||||||
EntityDeltaResponse,
|
EntityDeltaResponse,
|
||||||
|
EntityCopyRequest,
|
||||||
|
EntityCopyResponse,
|
||||||
|
EntityMoveRequest,
|
||||||
|
EntityMoveResponse,
|
||||||
EntityReadRequest,
|
EntityReadRequest,
|
||||||
EntityReadResponse,
|
EntityReadResponse,
|
||||||
EntityWriteRequest,
|
EntityWriteRequest,
|
||||||
@@ -43,14 +47,14 @@ function createEntityObject(data: EntityInterface): EntityObject {
|
|||||||
export const entityService = {
|
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
|
* @param request - list request parameters
|
||||||
*
|
*
|
||||||
* @returns Promise with entity object list grouped by provider, service, collection, and entity identifier
|
* @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>>>>> {
|
async listBulk(request: EntityListRequest = {}): Promise<Record<string, Record<string, Record<string, Record<string, EntityObject>>>>> {
|
||||||
const response = await transceivePost<EntityListRequest, EntityListResponse>('entity.list', request);
|
const response = await transceivePost<EntityListRequest, EntityListResponse>('entity.listBulk', request);
|
||||||
|
|
||||||
// Convert nested response to EntityObject instances
|
// Convert nested response to EntityObject instances
|
||||||
const providerList: Record<string, Record<string, Record<string, Record<string, EntityObject>>>> = {};
|
const providerList: Record<string, Record<string, Record<string, Record<string, EntityObject>>>> = {};
|
||||||
@@ -73,11 +77,25 @@ export const entityService = {
|
|||||||
return providerList;
|
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
|
* Retrieve a specific entity by provider and identifier
|
||||||
*
|
*
|
||||||
* @param request - fetch request parameters
|
* @param request - fetch request parameters
|
||||||
*
|
*
|
||||||
* @returns Promise with entity objects keyed by identifier
|
* @returns Promise with entity objects keyed by identifier
|
||||||
*/
|
*/
|
||||||
async fetch(request: EntityFetchRequest): Promise<Record<string, EntityObject>> {
|
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
|
* @param request - delete request parameters
|
||||||
*
|
*
|
||||||
* @returns Promise with deletion result
|
* @returns Promise with per-entity disposition results
|
||||||
*/
|
*/
|
||||||
async delete(request: EntityDeleteRequest): Promise<EntityDeleteResponse> {
|
async delete(request: EntityDeleteRequest): Promise<EntityDeleteResponse> {
|
||||||
return await transceivePost<EntityDeleteRequest, EntityDeleteResponse>('entity.delete', request);
|
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
|
* Retrieve delta changes for entities
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -6,4 +6,3 @@ export { providerService } from './providerService';
|
|||||||
export { serviceService } from './serviceService';
|
export { serviceService } from './serviceService';
|
||||||
export { collectionService } from './collectionService';
|
export { collectionService } from './collectionService';
|
||||||
export { entityService } from './entityService';
|
export { entityService } from './entityService';
|
||||||
export { nodeService } from './nodeService';
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { createFetchWrapper } from '@KTXC';
|
import { createFetchWrapper } from '@KTXC';
|
||||||
import type { ApiRequest, ApiResponse } from '../types/common';
|
import type { ApiRequest, ApiResponse, ApiStreamResponse } from '../types/common';
|
||||||
|
|
||||||
const fetchWrapper = createFetchWrapper();
|
const fetchWrapper = createFetchWrapper();
|
||||||
const API_URL = '/m/documents_manager/v1';
|
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);
|
const response: ApiResponse<TResponse> = await fetchWrapper.post(API_URL, request);
|
||||||
|
|
||||||
if (response.status === 'error') {
|
if (response.status === 'error') {
|
||||||
const errorMessage = `[${operation}] ${response.data.message}${response.data.code ? ` (code: ${response.data.code})` : ''}`;
|
const errorMessage = `[${operation}] ${response.data.message}${response.data.code ? ` (code: ${response.data.code})` : ''}`;
|
||||||
throw new Error(errorMessage);
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.data;
|
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 };
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import { ref, computed, readonly } from 'vue'
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { collectionService } from '../services/collectionService'
|
import { collectionService } from '../services/collectionService'
|
||||||
import type {
|
import type {
|
||||||
SourceSelector,
|
CollectionIdentifier,
|
||||||
|
ServiceIdentifier,
|
||||||
ListFilter,
|
ListFilter,
|
||||||
ListSort,
|
ListSort,
|
||||||
CollectionMutableProperties,
|
CollectionMutableProperties,
|
||||||
CollectionDeleteResponse,
|
|
||||||
} from '../types'
|
} from '../types'
|
||||||
import { CollectionObject } from '../models/collection'
|
import { CollectionObject } from '../models/collection'
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ export const useCollectionsStore = defineStore('documentsCollectionsStore', () =
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Actions
|
// 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
|
transceiving.value = true
|
||||||
try {
|
try {
|
||||||
const response = await collectionService.list({ sources, filter, sort })
|
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> {
|
async function fetch(provider: string, service: string | number, identifier: string | number): Promise<CollectionObject> {
|
||||||
transceiving.value = true
|
transceiving.value = true
|
||||||
try {
|
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)
|
const key = identifierKey(response.provider, response.service, response.identifier)
|
||||||
_collections.value[key] = response
|
_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
|
transceiving.value = true
|
||||||
try {
|
try {
|
||||||
const response = await collectionService.extant({ sources })
|
const response = await collectionService.extant({ targets })
|
||||||
console.debug('[Documents Manager][Store] - Successfully checked collection availability')
|
console.debug('[Documents Manager][Store] - Successfully checked collection availability')
|
||||||
return response
|
return response
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -137,7 +137,10 @@ export const useCollectionsStore = defineStore('documentsCollectionsStore', () =
|
|||||||
): Promise<CollectionObject> {
|
): Promise<CollectionObject> {
|
||||||
transceiving.value = true
|
transceiving.value = true
|
||||||
try {
|
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)
|
const key = identifierKey(response.provider, response.service, response.identifier)
|
||||||
_collections.value[key] = response
|
_collections.value[key] = response
|
||||||
|
|
||||||
@@ -159,7 +162,7 @@ export const useCollectionsStore = defineStore('documentsCollectionsStore', () =
|
|||||||
): Promise<CollectionObject> {
|
): Promise<CollectionObject> {
|
||||||
transceiving.value = true
|
transceiving.value = true
|
||||||
try {
|
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)
|
const key = identifierKey(response.provider, response.service, response.identifier)
|
||||||
_collections.value[key] = response
|
_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
|
transceiving.value = true
|
||||||
try {
|
try {
|
||||||
const response = await collectionService.delete({ provider, service, identifier })
|
const response = await collectionService.delete({ target: `${provider}:${service}:${identifier}` })
|
||||||
if (response.success) {
|
const success = response.disposition === 'deleted' || response.disposition === 'moved'
|
||||||
|
if (success) {
|
||||||
const key = identifierKey(provider, service, identifier)
|
const key = identifierKey(provider, service, identifier)
|
||||||
delete _collections.value[key]
|
delete _collections.value[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug('[Documents Manager][Store] - Successfully deleted collection:', `${provider}:${service}:${identifier}`)
|
console.debug('[Documents Manager][Store] - Successfully deleted collection:', `${provider}:${service}:${identifier}`)
|
||||||
return response
|
return { success }
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[Documents Manager][Store] - Failed to delete collection:', error)
|
console.error('[Documents Manager][Store] - Failed to delete collection:', error)
|
||||||
throw error
|
throw error
|
||||||
|
|||||||
+22
-18
@@ -7,12 +7,13 @@ import { defineStore } from 'pinia'
|
|||||||
import { entityService } from '../services/entityService'
|
import { entityService } from '../services/entityService'
|
||||||
import { EntityObject } from '../models'
|
import { EntityObject } from '../models'
|
||||||
import type {
|
import type {
|
||||||
SourceSelector,
|
CollectionIdentifier,
|
||||||
|
EntityIdentifier,
|
||||||
|
ServiceIdentifier,
|
||||||
ListFilter,
|
ListFilter,
|
||||||
ListSort,
|
ListSort,
|
||||||
ListRange,
|
ListRange,
|
||||||
DocumentInterface,
|
DocumentInterface,
|
||||||
EntityDeleteResponse,
|
|
||||||
EntityDeltaResponse,
|
EntityDeltaResponse,
|
||||||
} from '../types'
|
} from '../types'
|
||||||
|
|
||||||
@@ -100,10 +101,10 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Actions
|
// 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
|
transceiving.value = true
|
||||||
try {
|
try {
|
||||||
const response = await entityService.list({ sources, filter, sort, range })
|
const response = await entityService.listBulk({ sources, filter, sort, range })
|
||||||
|
|
||||||
const hydrated: Record<string, EntityObject> = {}
|
const hydrated: Record<string, EntityObject> = {}
|
||||||
Object.entries(response).forEach(([providerId, providerServices]) => {
|
Object.entries(response).forEach(([providerId, providerServices]) => {
|
||||||
@@ -137,7 +138,8 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
|
|||||||
): Promise<Record<string, EntityObject>> {
|
): Promise<Record<string, EntityObject>> {
|
||||||
transceiving.value = true
|
transceiving.value = true
|
||||||
try {
|
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> = {}
|
const hydrated: Record<string, EntityObject> = {}
|
||||||
Object.entries(response).forEach(([identifier, entityObj]) => {
|
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
|
transceiving.value = true
|
||||||
try {
|
try {
|
||||||
const response = await entityService.extant({ sources })
|
const response = await entityService.extant({ targets })
|
||||||
console.debug('[Documents Manager][Store] - Successfully checked entity availability')
|
console.debug('[Documents Manager][Store] - Successfully checked entity availability')
|
||||||
return response
|
return response
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -179,7 +181,7 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
|
|||||||
): Promise<EntityObject> {
|
): Promise<EntityObject> {
|
||||||
transceiving.value = true
|
transceiving.value = true
|
||||||
try {
|
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)
|
const key = identifierKey(response.provider, response.service, response.collection, response.identifier)
|
||||||
_entities.value[key] = response
|
_entities.value[key] = response
|
||||||
|
|
||||||
@@ -202,7 +204,7 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
|
|||||||
): Promise<EntityObject> {
|
): Promise<EntityObject> {
|
||||||
transceiving.value = true
|
transceiving.value = true
|
||||||
try {
|
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)
|
const key = identifierKey(response.provider, response.service, response.collection, response.identifier)
|
||||||
_entities.value[key] = response
|
_entities.value[key] = response
|
||||||
|
|
||||||
@@ -221,17 +223,19 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
|
|||||||
service: string | number,
|
service: string | number,
|
||||||
collection: string | number,
|
collection: string | number,
|
||||||
identifier: string | number,
|
identifier: string | number,
|
||||||
): Promise<EntityDeleteResponse> {
|
): Promise<{ success: boolean }> {
|
||||||
transceiving.value = true
|
transceiving.value = true
|
||||||
try {
|
try {
|
||||||
const response = await entityService.delete({ provider, service, collection, identifier })
|
const target: EntityIdentifier = `${provider}:${service}:${collection}:${identifier}`
|
||||||
if (response.success) {
|
const response = await entityService.delete({ targets: [target] })
|
||||||
|
const success = response[target]?.disposition === 'deleted'
|
||||||
|
if (success) {
|
||||||
const key = identifierKey(provider, service, collection, identifier)
|
const key = identifierKey(provider, service, collection, identifier)
|
||||||
delete _entities.value[key]
|
delete _entities.value[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug('[Documents Manager][Store] - Successfully deleted entity:', `${provider}:${service}:${collection}:${identifier}`)
|
console.debug('[Documents Manager][Store] - Successfully deleted entity:', target)
|
||||||
return response
|
return { success }
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[Documents Manager][Store] - Failed to delete entity:', error)
|
console.error('[Documents Manager][Store] - Failed to delete entity:', error)
|
||||||
throw 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
|
transceiving.value = true
|
||||||
try {
|
try {
|
||||||
const response = await entityService.delta({ sources })
|
const response = await entityService.delta({ targets })
|
||||||
|
|
||||||
Object.entries(response).forEach(([provider, providerData]) => {
|
Object.entries(response).forEach(([provider, providerData]) => {
|
||||||
if (providerData === false) return
|
if (providerData === false) return
|
||||||
@@ -280,7 +284,7 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
|
|||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
transceiving.value = true
|
transceiving.value = true
|
||||||
try {
|
try {
|
||||||
const response = await entityService.read({ provider, service, collection, identifier })
|
const response = await entityService.read({ target: `${provider}:${service}:${collection}:${identifier}` })
|
||||||
return response.content
|
return response.content
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[Documents Manager][Store] - Failed to read entity:', error)
|
console.error('[Documents Manager][Store] - Failed to read entity:', error)
|
||||||
@@ -299,7 +303,7 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
|
|||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
transceiving.value = true
|
transceiving.value = true
|
||||||
try {
|
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
|
return response.bytesWritten
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[Documents Manager][Store] - Failed to write entity:', error)
|
console.error('[Documents Manager][Store] - Failed to write entity:', error)
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
import { computed, ref, readonly } from 'vue'
|
import { computed, ref, readonly } from 'vue'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import type {
|
import type {
|
||||||
SourceSelector,
|
CollectionIdentifier,
|
||||||
|
ServiceIdentifier,
|
||||||
ListFilter,
|
ListFilter,
|
||||||
ListSort,
|
ListSort,
|
||||||
ListRange,
|
ListRange,
|
||||||
@@ -112,13 +113,9 @@ export const useNodesStore = defineStore('documentsNodesStore', () => {
|
|||||||
): Promise<CollectionObject[]> {
|
): Promise<CollectionObject[]> {
|
||||||
error.value = null
|
error.value = null
|
||||||
try {
|
try {
|
||||||
const sources: SourceSelector = {
|
const sources: (ServiceIdentifier | CollectionIdentifier)[] = collectionId === null
|
||||||
[providerId]: {
|
? [`${providerId}:${serviceId}`]
|
||||||
[String(serviceId)]: collectionId === null
|
: [`${providerId}:${serviceId}:${collectionId}`]
|
||||||
? true
|
|
||||||
: { [String(collectionId)]: true },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
await collectionsStore.list(sources, filter, sort)
|
await collectionsStore.list(sources, filter, sort)
|
||||||
return collectionsStore.collectionsForService(providerId, serviceId)
|
return collectionsStore.collectionsForService(providerId, serviceId)
|
||||||
@@ -138,13 +135,9 @@ export const useNodesStore = defineStore('documentsNodesStore', () => {
|
|||||||
): Promise<EntityObject[]> {
|
): Promise<EntityObject[]> {
|
||||||
error.value = null
|
error.value = null
|
||||||
try {
|
try {
|
||||||
const sources: SourceSelector = {
|
const sources: (ServiceIdentifier | CollectionIdentifier)[] = collectionId === null
|
||||||
[providerId]: {
|
? [`${providerId}:${serviceId}`]
|
||||||
[String(serviceId)]: collectionId === null
|
: [`${providerId}:${serviceId}:${collectionId}`]
|
||||||
? true
|
|
||||||
: { [String(collectionId)]: true },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
await entitiesStore.list(sources, filter, sort, range)
|
await entitiesStore.list(sources, filter, sort, range)
|
||||||
return entitiesStore.entitiesForCollection(providerId, serviceId, collectionId)
|
return entitiesStore.entitiesForCollection(providerId, serviceId, collectionId)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { ref, computed, readonly } from 'vue'
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { providerService } from '../services'
|
import { providerService } from '../services'
|
||||||
import { ProviderObject } from '../models/provider'
|
import { ProviderObject } from '../models/provider'
|
||||||
import type { SourceSelector } from '../types'
|
import type { ProviderIdentifier } from '../types'
|
||||||
|
|
||||||
export const useProvidersStore = defineStore('documentsProvidersStore', () => {
|
export const useProvidersStore = defineStore('documentsProvidersStore', () => {
|
||||||
// State
|
// State
|
||||||
@@ -54,10 +54,10 @@ export const useProvidersStore = defineStore('documentsProvidersStore', () => {
|
|||||||
*
|
*
|
||||||
* @returns Promise with provider object list keyed by provider identifier
|
* @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
|
transceiving.value = true
|
||||||
try {
|
try {
|
||||||
const providers = await providerService.list({ sources })
|
const providers = await providerService.list({ targets })
|
||||||
|
|
||||||
// Merge retrieved providers into state
|
// Merge retrieved providers into state
|
||||||
_providers.value = { ..._providers.value, ...providers }
|
_providers.value = { ..._providers.value, ...providers }
|
||||||
@@ -82,7 +82,7 @@ export const useProvidersStore = defineStore('documentsProvidersStore', () => {
|
|||||||
async function fetch(identifier: string): Promise<ProviderObject> {
|
async function fetch(identifier: string): Promise<ProviderObject> {
|
||||||
transceiving.value = true
|
transceiving.value = true
|
||||||
try {
|
try {
|
||||||
const provider = await providerService.fetch({ identifier })
|
const provider = await providerService.fetch({ target: identifier })
|
||||||
|
|
||||||
// Merge fetched provider into state
|
// Merge fetched provider into state
|
||||||
_providers.value[provider.identifier] = provider
|
_providers.value[provider.identifier] = provider
|
||||||
@@ -100,14 +100,14 @@ export const useProvidersStore = defineStore('documentsProvidersStore', () => {
|
|||||||
/**
|
/**
|
||||||
* Retrieve provider availability status for a given source selector
|
* 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
|
* @returns Promise with provider availability status
|
||||||
*/
|
*/
|
||||||
async function extant(sources: SourceSelector) {
|
async function extant(targets: ProviderIdentifier[]) {
|
||||||
transceiving.value = true
|
transceiving.value = true
|
||||||
try {
|
try {
|
||||||
const response = await providerService.extant({ sources })
|
const response = await providerService.extant({ targets })
|
||||||
|
|
||||||
Object.entries(response).forEach(([providerId, providerStatus]) => {
|
Object.entries(response).forEach(([providerId, providerStatus]) => {
|
||||||
if (providerStatus === false) {
|
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
|
return response
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[Documents Manager][Store] - Failed to check providers:', error)
|
console.error('[Documents Manager][Store] - Failed to check providers:', error)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { defineStore } from 'pinia'
|
|||||||
import { serviceService } from '../services'
|
import { serviceService } from '../services'
|
||||||
import { ServiceObject } from '../models/service'
|
import { ServiceObject } from '../models/service'
|
||||||
import type {
|
import type {
|
||||||
SourceSelector,
|
ServiceIdentifier,
|
||||||
ServiceInterface,
|
ServiceInterface,
|
||||||
} from '../types'
|
} from '../types'
|
||||||
|
|
||||||
@@ -76,14 +76,14 @@ export const useServicesStore = defineStore('documentsServicesStore', () => {
|
|||||||
/**
|
/**
|
||||||
* Retrieve all or specific services, optionally filtered by source selector
|
* 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
|
* @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
|
transceiving.value = true
|
||||||
try {
|
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
|
// Flatten nested structure: provider-id: { service-id: object } -> "provider-id:service-id": object
|
||||||
const services: Record<string, ServiceObject> = {}
|
const services: Record<string, ServiceObject> = {}
|
||||||
@@ -137,16 +137,16 @@ export const useServicesStore = defineStore('documentsServicesStore', () => {
|
|||||||
/**
|
/**
|
||||||
* Retrieve service availability status for a given source selector
|
* 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
|
* @returns Promise with service availability status
|
||||||
*/
|
*/
|
||||||
async function extant(sources: SourceSelector) {
|
async function extant(targets: ServiceIdentifier[]) {
|
||||||
transceiving.value = true
|
transceiving.value = true
|
||||||
try {
|
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
|
return response
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[Documents Manager][Store] - Failed to check services:', error)
|
console.error('[Documents Manager][Store] - Failed to check services:', error)
|
||||||
|
|||||||
+14
-22
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Collection type definitions
|
* 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'> {
|
export interface CollectionModelInterface extends Omit<CollectionInterface, '@type' | 'created' | 'modified'> {
|
||||||
@@ -46,7 +46,7 @@ export interface CollectionPropertiesInterface extends CollectionMutableProperti
|
|||||||
* Collection list
|
* Collection list
|
||||||
*/
|
*/
|
||||||
export interface CollectionListRequest {
|
export interface CollectionListRequest {
|
||||||
sources?: SourceSelector;
|
sources?: (ServiceIdentifier | CollectionIdentifier)[];
|
||||||
filter?: ListFilter;
|
filter?: ListFilter;
|
||||||
sort?: ListSort;
|
sort?: ListSort;
|
||||||
}
|
}
|
||||||
@@ -63,9 +63,7 @@ export interface CollectionListResponse {
|
|||||||
* Collection fetch
|
* Collection fetch
|
||||||
*/
|
*/
|
||||||
export interface CollectionFetchRequest {
|
export interface CollectionFetchRequest {
|
||||||
provider: string;
|
targets: CollectionIdentifier[];
|
||||||
service: string | number;
|
|
||||||
collection: string | number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionFetchResponse extends CollectionInterface {}
|
export interface CollectionFetchResponse extends CollectionInterface {}
|
||||||
@@ -74,7 +72,7 @@ export interface CollectionFetchResponse extends CollectionInterface {}
|
|||||||
* Collection extant
|
* Collection extant
|
||||||
*/
|
*/
|
||||||
export interface CollectionExtantRequest {
|
export interface CollectionExtantRequest {
|
||||||
sources: SourceSelector;
|
targets: CollectionIdentifier[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionExtantResponse {
|
export interface CollectionExtantResponse {
|
||||||
@@ -91,8 +89,9 @@ export interface CollectionExtantResponse {
|
|||||||
export interface CollectionCreateRequest {
|
export interface CollectionCreateRequest {
|
||||||
provider: string;
|
provider: string;
|
||||||
service: string | number;
|
service: string | number;
|
||||||
collection?: string | number | null; // Parent Collection Identifier
|
target?: CollectionIdentifier | null; // Parent collection identifier (absent for root)
|
||||||
properties: CollectionMutableProperties;
|
properties: CollectionMutableProperties;
|
||||||
|
options?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionCreateResponse extends CollectionInterface {}
|
export interface CollectionCreateResponse extends CollectionInterface {}
|
||||||
@@ -101,9 +100,7 @@ export interface CollectionCreateResponse extends CollectionInterface {}
|
|||||||
* Collection modify
|
* Collection modify
|
||||||
*/
|
*/
|
||||||
export interface CollectionUpdateRequest {
|
export interface CollectionUpdateRequest {
|
||||||
provider: string;
|
target: CollectionIdentifier;
|
||||||
service: string | number;
|
|
||||||
identifier: string | number;
|
|
||||||
properties: CollectionMutableProperties;
|
properties: CollectionMutableProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,26 +110,23 @@ export interface CollectionUpdateResponse extends CollectionInterface {}
|
|||||||
* Collection delete
|
* Collection delete
|
||||||
*/
|
*/
|
||||||
export interface CollectionDeleteRequest {
|
export interface CollectionDeleteRequest {
|
||||||
provider: string;
|
target: CollectionIdentifier;
|
||||||
service: string | number;
|
|
||||||
identifier: string | number;
|
|
||||||
options?: {
|
options?: {
|
||||||
force?: boolean; // Whether to force delete even if collection is not empty
|
force?: boolean; // Whether to force delete even if collection is not empty
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionDeleteResponse {
|
export interface CollectionDeleteResponse {
|
||||||
success: boolean;
|
disposition: 'deleted' | 'moved';
|
||||||
|
mutation?: CollectionInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collection copy
|
* Collection copy
|
||||||
*/
|
*/
|
||||||
export interface CollectionCopyRequest {
|
export interface CollectionCopyRequest {
|
||||||
provider: string;
|
source: CollectionIdentifier;
|
||||||
service: string;
|
target?: CollectionIdentifier | null; // Destination parent (absent for root)
|
||||||
identifier: string;
|
|
||||||
location?: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionCopyResponse extends CollectionInterface {}
|
export interface CollectionCopyResponse extends CollectionInterface {}
|
||||||
@@ -141,10 +135,8 @@ export interface CollectionCopyResponse extends CollectionInterface {}
|
|||||||
* Collection move
|
* Collection move
|
||||||
*/
|
*/
|
||||||
export interface CollectionMoveRequest {
|
export interface CollectionMoveRequest {
|
||||||
provider: string;
|
source: CollectionIdentifier;
|
||||||
service: string;
|
target?: CollectionIdentifier | null; // Destination parent (absent for root)
|
||||||
identifier: string;
|
|
||||||
location?: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionMoveResponse extends CollectionInterface {}
|
export interface CollectionMoveResponse extends CollectionInterface {}
|
||||||
+47
-23
@@ -44,33 +44,57 @@ export interface ApiErrorResponse {
|
|||||||
export type ApiResponse<T = any> = ApiSuccessResponse<T> | ApiErrorResponse;
|
export type ApiResponse<T = any> = ApiSuccessResponse<T> | ApiErrorResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selector for targeting specific providers, services, collections, or entities in list or extant operations.
|
* Stream control start line.
|
||||||
*
|
|
||||||
* 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
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
*/
|
||||||
export type SourceSelector = {
|
export interface ApiStreamStartResponse {
|
||||||
[provider: string]: boolean | ServiceSelector;
|
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
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Entity type definitions
|
* 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';
|
import type { DocumentInterface, DocumentModelInterface } from './document';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,11 +25,21 @@ export interface EntityInterface<T = DocumentInterface> {
|
|||||||
properties: T;
|
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
|
* Entity list
|
||||||
*/
|
*/
|
||||||
export interface EntityListRequest {
|
export interface EntityListRequest {
|
||||||
sources?: SourceSelector;
|
sources?: (ServiceIdentifier | CollectionIdentifier)[];
|
||||||
filter?: ListFilter;
|
filter?: ListFilter;
|
||||||
sort?: ListSort;
|
sort?: ListSort;
|
||||||
range?: ListRange;
|
range?: ListRange;
|
||||||
@@ -49,10 +59,7 @@ export interface EntityListResponse {
|
|||||||
* Entity fetch
|
* Entity fetch
|
||||||
*/
|
*/
|
||||||
export interface EntityFetchRequest {
|
export interface EntityFetchRequest {
|
||||||
provider: string;
|
targets: EntityIdentifier[];
|
||||||
service: string | number;
|
|
||||||
collection: string | number;
|
|
||||||
identifiers: (string | number)[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityFetchResponse {
|
export interface EntityFetchResponse {
|
||||||
@@ -63,7 +70,7 @@ export interface EntityFetchResponse {
|
|||||||
* Entity extant
|
* Entity extant
|
||||||
*/
|
*/
|
||||||
export interface EntityExtantRequest {
|
export interface EntityExtantRequest {
|
||||||
sources: SourceSelector;
|
targets: (CollectionIdentifier | EntityIdentifier)[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityExtantResponse {
|
export interface EntityExtantResponse {
|
||||||
@@ -80,9 +87,7 @@ export interface EntityExtantResponse {
|
|||||||
* Entity create
|
* Entity create
|
||||||
*/
|
*/
|
||||||
export interface EntityCreateRequest<T = DocumentInterface> {
|
export interface EntityCreateRequest<T = DocumentInterface> {
|
||||||
provider: string;
|
target: CollectionIdentifier;
|
||||||
service: string | number;
|
|
||||||
collection: string | number;
|
|
||||||
properties: T;
|
properties: T;
|
||||||
options?: Record<string, unknown>;
|
options?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
@@ -93,10 +98,7 @@ export interface EntityCreateResponse<T = DocumentInterface> extends EntityInter
|
|||||||
* Entity update
|
* Entity update
|
||||||
*/
|
*/
|
||||||
export interface EntityUpdateRequest<T = DocumentInterface> {
|
export interface EntityUpdateRequest<T = DocumentInterface> {
|
||||||
provider: string;
|
target: EntityIdentifier;
|
||||||
service: string | number;
|
|
||||||
collection: string | number;
|
|
||||||
identifier: string | number;
|
|
||||||
properties: T;
|
properties: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,21 +108,18 @@ export interface EntityUpdateResponse<T = DocumentInterface> extends EntityInter
|
|||||||
* Entity delete
|
* Entity delete
|
||||||
*/
|
*/
|
||||||
export interface EntityDeleteRequest {
|
export interface EntityDeleteRequest {
|
||||||
provider: string;
|
targets: EntityIdentifier[];
|
||||||
service: string | number;
|
|
||||||
collection: string | number;
|
|
||||||
identifier: string | number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityDeleteResponse {
|
export interface EntityDeleteResponse {
|
||||||
success: boolean;
|
[identifier: string]: EntityMutationResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entity delta
|
* Entity delta
|
||||||
*/
|
*/
|
||||||
export interface EntityDeltaRequest {
|
export interface EntityDeltaRequest {
|
||||||
sources: SourceSelector;
|
targets: (CollectionIdentifier | EntityIdentifier)[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityDeltaResponse {
|
export interface EntityDeltaResponse {
|
||||||
@@ -140,36 +139,31 @@ export interface EntityDeltaResponse {
|
|||||||
* Entity copy
|
* Entity copy
|
||||||
*/
|
*/
|
||||||
export interface EntityCopyRequest {
|
export interface EntityCopyRequest {
|
||||||
provider: string;
|
target: CollectionIdentifier;
|
||||||
service: string | number;
|
sources: EntityIdentifier[];
|
||||||
collection: string | number;
|
|
||||||
identifier: string | number;
|
|
||||||
destination?: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityCopyResponse<T = DocumentInterface> extends EntityInterface<T> {}
|
export interface EntityCopyResponse {
|
||||||
|
[identifier: string]: EntityMutationResult;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entity move
|
* Entity move
|
||||||
*/
|
*/
|
||||||
export interface EntityMoveRequest {
|
export interface EntityMoveRequest {
|
||||||
provider: string;
|
target: CollectionIdentifier;
|
||||||
service: string | number;
|
sources: EntityIdentifier[];
|
||||||
collection: string | number;
|
|
||||||
identifier: string | number;
|
|
||||||
destination?: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityMoveResponse<T = DocumentInterface> extends EntityInterface<T> {}
|
export interface EntityMoveResponse {
|
||||||
|
[identifier: string]: EntityMutationResult;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entity read content
|
* Entity read content
|
||||||
*/
|
*/
|
||||||
export interface EntityReadRequest {
|
export interface EntityReadRequest {
|
||||||
provider: string;
|
target: EntityIdentifier;
|
||||||
service: string | number;
|
|
||||||
collection: string | number;
|
|
||||||
identifier: string | number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityReadResult {
|
export interface EntityReadResult {
|
||||||
@@ -183,10 +177,7 @@ export type EntityReadResponse = EntityReadResult;
|
|||||||
* Entity write content
|
* Entity write content
|
||||||
*/
|
*/
|
||||||
export interface EntityWriteRequest {
|
export interface EntityWriteRequest {
|
||||||
provider: string;
|
target: EntityIdentifier;
|
||||||
service: string | number;
|
|
||||||
collection: string | number;
|
|
||||||
identifier: string | number;
|
|
||||||
content: string;
|
content: string;
|
||||||
encoding?: 'base64';
|
encoding?: 'base64';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,3 @@ export type * from './service';
|
|||||||
export type * from './collection';
|
export type * from './collection';
|
||||||
export type * from './entity';
|
export type * from './entity';
|
||||||
export type * from './document';
|
export type * from './document';
|
||||||
export type * from './node';
|
|
||||||
|
|||||||
@@ -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>;
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Provider type definitions
|
* Provider type definitions
|
||||||
*/
|
*/
|
||||||
import type { SourceSelector } from "./common";
|
import type { ProviderIdentifier } from "./common";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provider capabilities
|
* Provider capabilities
|
||||||
@@ -32,7 +32,7 @@ export interface ProviderInterface {
|
|||||||
* Provider list
|
* Provider list
|
||||||
*/
|
*/
|
||||||
export interface ProviderListRequest {
|
export interface ProviderListRequest {
|
||||||
sources?: SourceSelector;
|
targets?: ProviderIdentifier[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProviderListResponse {
|
export interface ProviderListResponse {
|
||||||
@@ -40,19 +40,19 @@ export interface ProviderListResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provider fetch
|
* Provider fetch
|
||||||
*/
|
*/
|
||||||
export interface ProviderFetchRequest {
|
export interface ProviderFetchRequest {
|
||||||
identifier: string;
|
target: ProviderIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProviderFetchResponse extends ProviderInterface {}
|
export interface ProviderFetchResponse extends ProviderInterface {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provider extant
|
* Provider extant
|
||||||
*/
|
*/
|
||||||
export interface ProviderExtantRequest {
|
export interface ProviderExtantRequest {
|
||||||
sources: SourceSelector;
|
targets: ProviderIdentifier[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProviderExtantResponse {
|
export interface ProviderExtantResponse {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Service type definitions
|
* Service type definitions
|
||||||
*/
|
*/
|
||||||
import type { SourceSelector, ListFilterComparisonOperator } from './common';
|
import type { ServiceIdentifier as ServiceIdentifierString, ListFilterComparisonOperator } from './common';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service capabilities
|
* Service capabilities
|
||||||
@@ -66,7 +66,7 @@ export interface ServiceInterface {
|
|||||||
* Service list
|
* Service list
|
||||||
*/
|
*/
|
||||||
export interface ServiceListRequest {
|
export interface ServiceListRequest {
|
||||||
sources?: SourceSelector;
|
targets?: ServiceIdentifierString[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServiceListResponse {
|
export interface ServiceListResponse {
|
||||||
@@ -89,7 +89,7 @@ export interface ServiceFetchResponse extends ServiceInterface {}
|
|||||||
* Service extant
|
* Service extant
|
||||||
*/
|
*/
|
||||||
export interface ServiceExtantRequest {
|
export interface ServiceExtantRequest {
|
||||||
sources: SourceSelector;
|
targets: ServiceIdentifierString[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServiceExtantResponse {
|
export interface ServiceExtantResponse {
|
||||||
|
|||||||
Reference in New Issue
Block a user