8 Commits

Author SHA1 Message Date
Sebastian d9e30d7653 chore(deps): update dependency vite to v8.3.0
Build Test / test (pull_request) Successful in 1m46s
JS Unit Tests / test (pull_request) Successful in 1m46s
PHP Unit Tests / test (pull_request) Successful in 2m13s
PHP Integration Tests / Integration Tests (pull_request) Failing after 3m15s
2026-09-11 03:03:32 +00:00
Sebastian 4b37be3e7a feat: use resource identifiers
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
2026-09-05 20:12:27 -04:00
Sebastian 26ed0c8aad feat: use resource identifier
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
2026-09-05 20:10:08 -04:00
Sebastian 41de70da75 refactor: use common validation
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
2026-09-05 20:05:08 -04:00
Sebastian e802c8fc0f feat: use ResourceIdentifiers
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
2026-09-05 17:27:55 -04:00
Sebastian 7d18bcab1a feat: node list back end
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
2026-09-04 22:12:27 -04:00
Sebastian 93aa1b0f24 refactor(module): register browser integration through context
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
2026-08-31 22:55:11 -04:00
Sebastian cbcf2e5acb fix(preview): return no content when preview is unavailable
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
2026-08-31 22:55:11 -04:00
16 changed files with 1184 additions and 1049 deletions
File diff suppressed because it is too large Load Diff
+7 -9
View File
@@ -62,14 +62,14 @@ class TransferController extends ControllerAbstract
/** @var EntityBaseInterface[] $entities */ /** @var EntityBaseInterface[] $entities */
$entities = $this->manager->entityFetchBulk($tenantId, $userId, $target); $entities = $this->manager->entityFetchBulk($tenantId, $userId, $target);
if (empty($entities) || !isset($entities[$identifier])) { if (!isset($entities[(string) $target])) {
return new JsonResponse([ return new JsonResponse([
'status' => 'error', 'status' => 'error',
'error' => ['code' => 404, 'message' => 'File not found'] 'error' => ['code' => 404, 'message' => 'File not found']
], Response::HTTP_NOT_FOUND); ], Response::HTTP_NOT_FOUND);
} }
$entity = $entities[$identifier]; $entity = $entities[(string) $target];
// Get the stream // Get the stream
$stream = $this->manager->entityReadStream($tenantId, $userId, $target); $stream = $this->manager->entityReadStream($tenantId, $userId, $target);
@@ -130,10 +130,7 @@ class TransferController extends ControllerAbstract
$variant, $variant,
); );
if ($preview === null) { if ($preview === null) {
return new JsonResponse([ return new Response('', Response::HTTP_NO_CONTENT);
'status' => 'error',
'error' => ['code' => 404, 'message' => 'Preview unavailable'],
], Response::HTTP_NOT_FOUND);
} }
$response = new StreamedResponse($preview->resource->stream()); $response = new StreamedResponse($preview->resource->stream());
@@ -330,15 +327,16 @@ class TransferController extends ControllerAbstract
foreach ($ids as $id) { foreach ($ids as $id) {
// Try as entity first // Try as entity first
if ($collection !== null) { if ($collection !== null) {
$target = new EntityIdentifier($provider, $service, $collection, (string) $id);
/** @var EntityBaseInterface[] $entities */ /** @var EntityBaseInterface[] $entities */
$entities = $this->manager->entityFetchBulk( $entities = $this->manager->entityFetchBulk(
$tenantId, $tenantId,
$userId, $userId,
new EntityIdentifier($provider, $service, $collection, (string)$id) $target
); );
if (!empty($entities) && isset($entities[$id])) { if (isset($entities[(string) $target])) {
$entity = $entities[$id]; $entity = $entities[(string) $target];
$files[] = [ $files[] = [
'type' => 'file', 'type' => 'file',
'id' => $id, 'id' => $id,
+98 -12
View File
@@ -20,10 +20,12 @@ use KTXF\Documents\Service\ServiceBaseInterface;
use KTXF\Documents\Service\ServiceCollectionMutableInterface; use KTXF\Documents\Service\ServiceCollectionMutableInterface;
use KTXF\Documents\Service\ServiceEntityMutableInterface; use KTXF\Documents\Service\ServiceEntityMutableInterface;
use KTXF\Documents\Service\ServiceMutableInterface; use KTXF\Documents\Service\ServiceMutableInterface;
use KTXF\Documents\Service\ServiceNodeListInterface;
use KTXF\Resource\Filter\IFilter; use KTXF\Resource\Filter\IFilter;
use KTXF\Preview\PreviewSource; use KTXF\Preview\PreviewSource;
use KTXF\Resource\BinaryResource; use KTXF\Resource\BinaryResource;
use KTXF\Resource\Identifier\CollectionIdentifier; use KTXF\Resource\Identifier\CollectionIdentifier;
use KTXF\Resource\Identifier\ServiceIdentifier;
use KTXF\Resource\Identifier\EntityIdentifier; use KTXF\Resource\Identifier\EntityIdentifier;
use KTXF\Resource\Identifier\ResourceIdentifiers; use KTXF\Resource\Identifier\ResourceIdentifiers;
use KTXF\Resource\Provider\ResourceServiceIdentityInterface; use KTXF\Resource\Provider\ResourceServiceIdentityInterface;
@@ -506,18 +508,16 @@ class Manager {
* *
* @param string $tenantId tenant identifier * @param string $tenantId tenant identifier
* @param string $userId user identifier * @param string $userId user identifier
* @param string $provider provider identifier * @param ServiceIdentifier|CollectionIdentifier $target service root or parent collection
* @param string|int $service service identifier
* @param CollectionIdentifier|null $target target parent collection identifier (null for root)
* @param CollectionPropertiesMutableInterface|array $properties properties for the new collection * @param CollectionPropertiesMutableInterface|array $properties properties for the new collection
* @param array $options additional options for creation * @param array $options additional options for creation
* *
* @return CollectionBaseInterface * @return CollectionBaseInterface
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
public function collectionCreate(string $tenantId, string $userId, string $provider, string|int $service, CollectionIdentifier|null $target, CollectionPropertiesMutableInterface|array $properties, array $options = []): CollectionBaseInterface { public function collectionCreate(string $tenantId, string $userId, ServiceIdentifier|CollectionIdentifier $target, CollectionPropertiesMutableInterface|array $properties, array $options = []): CollectionBaseInterface {
// retrieve service // retrieve service
$service = $this->serviceFetch($tenantId, $userId, $provider, $service); $service = $this->serviceFetch($tenantId, $userId, $target->provider(), $target->service());
// Check if service supports collection creation // Check if service supports collection creation
if ($service->getEnabled() === false) { if ($service->getEnabled() === false) {
throw new InvalidArgumentException("Service '{$service->identifier()}' not found or is disabled"); throw new InvalidArgumentException("Service '{$service->identifier()}' not found or is disabled");
@@ -533,7 +533,7 @@ class Manager {
$properties = $service->collectionFresh()->getProperties()->jsonDeserialize($properties); $properties = $service->collectionFresh()->getProperties()->jsonDeserialize($properties);
} }
// Create collection // Create collection
return $service->collectionCreate($target, $properties, $options); return $service->collectionCreate($target instanceof CollectionIdentifier ? $target : null, $properties, $options);
} }
/** /**
@@ -811,21 +811,28 @@ class Manager {
* @return array<string,EntityBaseInterface> * @return array<string,EntityBaseInterface>
*/ */
public function entityFetchBulk(string $tenantId, ?string $userId, EntityIdentifier ...$identifiers): array { public function entityFetchBulk(string $tenantId, ?string $userId, EntityIdentifier ...$identifiers): array {
// group identifiers by provider/service // Keep locations separate: providers may return maps keyed by local ID.
$groupedIdentifiers = []; $groupedIdentifiers = [];
foreach ($identifiers as $identifier) { foreach ($identifiers as $identifier) {
$groupedIdentifiers[$identifier->provider()][$identifier->service()][] = $identifier; $groupedIdentifiers[$identifier->provider()][$identifier->service()][$identifier->collection()][] = $identifier;
} }
// retrieve each service and fetch entities // retrieve each service and fetch entities
$list = []; $list = [];
foreach ($groupedIdentifiers as $providerId => $services) { foreach ($groupedIdentifiers as $providerId => $services) {
foreach ($services as $serviceId => $entities) { foreach ($services as $serviceId => $collections) {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
if ($service->getEnabled() === false) { if ($service->getEnabled() === false) {
throw new InvalidArgumentException("Service '{$providerId}:{$serviceId}' not found or is disabled"); throw new InvalidArgumentException("Service '{$providerId}:{$serviceId}' not found or is disabled");
} }
// retrieve entities and merge into list foreach ($collections as $entities) {
$list = array_merge($list, $service->entityFetchBulk(...$entities)); $fetched = $service->entityFetchBulk(...$entities);
foreach ($entities as $identifier) {
$entity = $fetched[(string) $identifier] ?? $fetched[$identifier->entity()] ?? null;
if ($entity !== null) {
$list[(string) $identifier] = $entity;
}
}
}
} }
} }
return $list; return $list;
@@ -1353,7 +1360,7 @@ class Manager {
*/ */
public function entityPreview(string $tenantId, string $userId, EntityIdentifier $target, string $variant = 'inline'): ?Preview { public function entityPreview(string $tenantId, string $userId, EntityIdentifier $target, string $variant = 'inline'): ?Preview {
$entities = $this->entityFetchBulk($tenantId, $userId, $target); $entities = $this->entityFetchBulk($tenantId, $userId, $target);
$entity = $entities[$target->entity()] ?? reset($entities); $entity = $entities[(string) $target] ?? null;
if (!$entity instanceof EntityBaseInterface) { if (!$entity instanceof EntityBaseInterface) {
return null; return null;
} }
@@ -1404,6 +1411,85 @@ class Manager {
return $this->previewManager->fetch($tenantId, $source, $variant); return $this->previewManager->fetch($tenantId, $source, $variant);
} }
/**
* List collections and entities within a location as one unified, paginated set
*
* Folders are ordered before files within the returned range. Unlike
* {@see collectionList} and {@see entityListBulk}, this requires the target
* service to natively implement {@see ServiceNodeListInterface} — there is
* intentionally no fallback for services that only support the separate
* collection/entity listing operations, since every current caller targets a
* single provider/service/location per request.
*
* @since 2026.09.01
*
* @param string $tenantId Tenant identifier
* @param string $userId User identifier
* @param ResourceIdentifiers|null $targets Node sources with collection identifiers
* @param array|null $filter Node filter
* @param array|null $sort Node sort
* @param array|null $range Node range/pagination
*
* @return \Generator<int,CollectionBaseInterface|EntityBaseInterface> Nodes in provider order
*
* @throws InvalidArgumentException If the resolved service does not support unified node listing
*/
public function nodeList(string $tenantId, string $userId, ?ResourceIdentifiers $targets = null, array|null $filter = null, array|null $sort = null, array|null $range = null): \Generator {
// confirm that sources are provided
if ($targets === null) {
$targets = new ResourceIdentifiers([]);
}
// retrieve services for each provider
$aggregateServices = $this->serviceList($tenantId, $userId, $targets);
// retrieve nodes for each service
foreach ($aggregateServices as $services) {
/** @var ServiceBaseInterface $service */
foreach ($services as $service) {
// omit disabled services
if ($service->getEnabled() === false) {
continue;
}
if ($service instanceof ServiceNodeListInterface === false) {
throw new InvalidArgumentException("Service '{$service->identifier()}' does not support paginated node listing");
}
// retrieve collections for each service
$collectionSelected = $targets->byProvider($service->provider())->byService($service->identifier())->collections();
if ($collectionSelected === []) {
// documents are hierarchical: service level selection lists the root collection
$collectionSelected = [''];
}
// construct filter for nodes
$nodeFilter = null;
if ($filter !== null && $filter !== []) {
$nodeFilter = $service->nodeListFilter();
foreach ($filter as $attribute => $value) {
$nodeFilter->condition($attribute, $value);
}
}
// construct sort for nodes
$nodeSort = null;
if ($sort !== null && $sort !== []) {
$nodeSort = $service->nodeListSort();
foreach ($sort as $attribute => $direction) {
$nodeSort->condition($attribute, $direction);
}
}
// construct range for nodes
$nodeRange = null;
if ($range !== null && $range !== [] && isset($range['type'])) {
$nodeRange = $service->nodeListRange(RangeType::from($range['type']))->jsonDeserialize($range);
}
// Preserve provider order without grouping or buffering nodes.
foreach ($collectionSelected as $collectionId) {
$nodes = $service->nodeList($collectionId, $nodeFilter, $nodeSort, $nodeRange);
foreach ($nodes as $node) {
yield $node;
}
}
}
}
}
} }
+8 -12
View File
@@ -2,13 +2,14 @@
namespace KTXM\DocumentsManager; namespace KTXM\DocumentsManager;
use KTXF\Module\ModuleBrowserInterface; use KTXF\Module\Configuration\BrowserModuleContextInterface;
use KTXF\Module\Configuration\ModuleContextInterface;
use KTXF\Module\ModuleInstanceAbstract; use KTXF\Module\ModuleInstanceAbstract;
/** /**
* File Manager Module * File Manager Module
*/ */
class Module extends ModuleInstanceAbstract implements ModuleBrowserInterface class Module extends ModuleInstanceAbstract
{ {
public function __construct() public function __construct()
@@ -50,16 +51,11 @@ class Module extends ModuleInstanceAbstract implements ModuleBrowserInterface
]; ];
} }
public function registerBI(): array { public function configure(ModuleContextInterface $context): void
return [ {
'handle' => $this->handle(), if ($context instanceof BrowserModuleContextInterface) {
'namespace' => 'DocumentsManager', $context->registerModule($this, 'DocumentsManager', 'static/module.mjs');
'version' => $this->version(), }
'label' => $this->label(),
'author' => $this->author(),
'description' => $this->description(),
'boot' => 'static/module.mjs',
];
} }
} }
+210 -284
View File
@@ -95,12 +95,12 @@
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.29.8", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
"integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.29.8" "@babel/types": "^7.29.7"
}, },
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@@ -110,9 +110,9 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.29.8", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
"integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.29.7", "@babel/helper-string-parser": "^7.29.7",
@@ -285,40 +285,6 @@
"node": ">=20.19.0" "node": ">=20.19.0"
} }
}, },
"node_modules/@emnapi/core": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
"integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.2",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
"integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
"integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@exodus/bytes": { "node_modules/@exodus/bytes": {
"version": "1.15.1", "version": "1.15.1",
"resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz",
@@ -364,39 +330,37 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@napi-rs/wasm-runtime": { "node_modules/@oxc-project/types": {
"version": "1.1.6", "version": "0.149.0",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.149.0.tgz",
"integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", "integrity": "sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/oxc-project"
}
},
"node_modules/@rolldown/binding-android-arm-eabi": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.8.tgz",
"integrity": "sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==",
"cpu": [
"arm"
],
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "os": [
"@tybys/wasm-util": "^0.10.3" "android"
}, ],
"funding": { "engines": {
"type": "github", "node": "^20.19.0 || >=22.12.0"
"url": "https://github.com/sponsors/Brooooooklyn"
},
"peerDependencies": {
"@emnapi/core": "^1.7.1",
"@emnapi/runtime": "^1.7.1"
}
},
"node_modules/@oxc-project/types": {
"version": "0.139.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz",
"integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
} }
}, },
"node_modules/@rolldown/binding-android-arm64": { "node_modules/@rolldown/binding-android-arm64": {
"version": "1.1.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.8.tgz",
"integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", "integrity": "sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -411,9 +375,9 @@
} }
}, },
"node_modules/@rolldown/binding-darwin-arm64": { "node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.1.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.8.tgz",
"integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", "integrity": "sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -428,9 +392,9 @@
} }
}, },
"node_modules/@rolldown/binding-darwin-x64": { "node_modules/@rolldown/binding-darwin-x64": {
"version": "1.1.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.8.tgz",
"integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", "integrity": "sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -445,9 +409,9 @@
} }
}, },
"node_modules/@rolldown/binding-freebsd-x64": { "node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.1.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.8.tgz",
"integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", "integrity": "sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -462,9 +426,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-arm-gnueabihf": { "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.1.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.8.tgz",
"integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", "integrity": "sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -479,9 +443,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-arm64-gnu": { "node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.1.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.8.tgz",
"integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", "integrity": "sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -496,9 +460,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-arm64-musl": { "node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.1.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.8.tgz",
"integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", "integrity": "sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -513,9 +477,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-ppc64-gnu": { "node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.1.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.8.tgz",
"integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", "integrity": "sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@@ -530,9 +494,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-s390x-gnu": { "node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.1.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.8.tgz",
"integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", "integrity": "sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -547,9 +511,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-x64-gnu": { "node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.1.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.8.tgz",
"integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", "integrity": "sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -564,9 +528,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-x64-musl": { "node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.1.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.8.tgz",
"integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", "integrity": "sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -581,9 +545,9 @@
} }
}, },
"node_modules/@rolldown/binding-openharmony-arm64": { "node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.1.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.8.tgz",
"integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", "integrity": "sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -597,29 +561,10 @@
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
} }
}, },
"node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz",
"integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==",
"cpu": [
"wasm32"
],
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "1.11.1",
"@emnapi/runtime": "1.11.1",
"@napi-rs/wasm-runtime": "^1.1.6"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": { "node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.1.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.8.tgz",
"integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", "integrity": "sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -634,9 +579,9 @@
} }
}, },
"node_modules/@rolldown/binding-win32-x64-msvc": { "node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.1.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.8.tgz",
"integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", "integrity": "sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -671,17 +616,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@tybys/wasm-util": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
"integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@types/chai": { "node_modules/@types/chai": {
"version": "5.2.3", "version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
@@ -918,39 +852,39 @@
} }
}, },
"node_modules/@vue/compiler-core": { "node_modules/@vue/compiler-core": {
"version": "3.5.42", "version": "3.5.40",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.42.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.40.tgz",
"integrity": "sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ==", "integrity": "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.29.8", "@babel/parser": "^7.29.7",
"@vue/shared": "3.5.42", "@vue/shared": "3.5.40",
"entities": "^7.0.1", "entities": "^7.0.1",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
} }
}, },
"node_modules/@vue/compiler-dom": { "node_modules/@vue/compiler-dom": {
"version": "3.5.42", "version": "3.5.40",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.42.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz",
"integrity": "sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA==", "integrity": "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-core": "3.5.42", "@vue/compiler-core": "3.5.40",
"@vue/shared": "3.5.42" "@vue/shared": "3.5.40"
} }
}, },
"node_modules/@vue/compiler-sfc": { "node_modules/@vue/compiler-sfc": {
"version": "3.5.42", "version": "3.5.40",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.42.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz",
"integrity": "sha512-fkCAFB4okcAANGMThboWnScp/gzWjU0ZSkVnjTIiplmMDq2uq0tIB3j+xVu4rhv5rvOgBySCysudmbMd6xRRqw==", "integrity": "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.29.8", "@babel/parser": "^7.29.7",
"@vue/compiler-core": "3.5.42", "@vue/compiler-core": "3.5.40",
"@vue/compiler-dom": "3.5.42", "@vue/compiler-dom": "3.5.40",
"@vue/compiler-ssr": "3.5.42", "@vue/compiler-ssr": "3.5.40",
"@vue/shared": "3.5.42", "@vue/shared": "3.5.40",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.30.21", "magic-string": "^0.30.21",
"postcss": "^8.5.19", "postcss": "^8.5.19",
@@ -958,13 +892,13 @@
} }
}, },
"node_modules/@vue/compiler-ssr": { "node_modules/@vue/compiler-ssr": {
"version": "3.5.42", "version": "3.5.40",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.42.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.40.tgz",
"integrity": "sha512-xmLk3wLkbizPAiLyomjgFFosf2ys9b5Ghb+oh/k2tnvipNz8OFrQOiTcWCzyK7MpBp9KkyGtfvgfLUivbmuGYA==", "integrity": "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.5.42", "@vue/compiler-dom": "3.5.40",
"@vue/shared": "3.5.42" "@vue/shared": "3.5.40"
} }
}, },
"node_modules/@vue/devtools-api": { "node_modules/@vue/devtools-api": {
@@ -1014,51 +948,51 @@
} }
}, },
"node_modules/@vue/reactivity": { "node_modules/@vue/reactivity": {
"version": "3.5.42", "version": "3.5.40",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.42.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.40.tgz",
"integrity": "sha512-TzNNfKpb7hDxbQltwAut8VDQA5YP+BuRlxntHUuRjyKwlMvmAPbs3unhCvieijifY6vFfVBwsS7wG/C7uq+bEQ==", "integrity": "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/shared": "3.5.42" "@vue/shared": "3.5.40"
} }
}, },
"node_modules/@vue/runtime-core": { "node_modules/@vue/runtime-core": {
"version": "3.5.42", "version": "3.5.40",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.42.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.40.tgz",
"integrity": "sha512-9uACtuHs7vJGkm5Bp3xu4xRDLFTIYy5DgxpToVjqGIAhAEKwQfsaLvKINhM6nFVp6bZPRFGdDqd1g52MqKsotA==", "integrity": "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/reactivity": "3.5.42", "@vue/reactivity": "3.5.40",
"@vue/shared": "3.5.42" "@vue/shared": "3.5.40"
} }
}, },
"node_modules/@vue/runtime-dom": { "node_modules/@vue/runtime-dom": {
"version": "3.5.42", "version": "3.5.40",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.42.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.40.tgz",
"integrity": "sha512-rsCmhiWLaRxGltLwhlCWyYkFn7WAbKRh0q17eZ1A6Dq6eqc2ACQ61IIryxz0LrsvCzHSilLA9JHovVwM8CNE2g==", "integrity": "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/reactivity": "3.5.42", "@vue/reactivity": "3.5.40",
"@vue/runtime-core": "3.5.42", "@vue/runtime-core": "3.5.40",
"@vue/shared": "3.5.42", "@vue/shared": "3.5.40",
"csstype": "^3.2.3" "csstype": "^3.2.3"
} }
}, },
"node_modules/@vue/server-renderer": { "node_modules/@vue/server-renderer": {
"version": "3.5.42", "version": "3.5.40",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.42.tgz", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.40.tgz",
"integrity": "sha512-2++5dUyYS4gvo7xQXSECUDhB7TS0aOl5SeVfC5qSq1Jgfhjvegw1zqhwTIR3imZ+QYPJQw9gfcFvXGAjGZ7ajQ==", "integrity": "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-ssr": "3.5.42", "@vue/compiler-ssr": "3.5.40",
"@vue/runtime-dom": "3.5.42", "@vue/runtime-dom": "3.5.40",
"@vue/shared": "3.5.42" "@vue/shared": "3.5.40"
} }
}, },
"node_modules/@vue/shared": { "node_modules/@vue/shared": {
"version": "3.5.42", "version": "3.5.40",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.42.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.40.tgz",
"integrity": "sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g==", "integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vue/tsconfig": { "node_modules/@vue/tsconfig": {
@@ -1407,9 +1341,9 @@
} }
}, },
"node_modules/lightningcss": { "node_modules/lightningcss": {
"version": "1.32.0", "version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
"dev": true, "dev": true,
"license": "MPL-2.0", "license": "MPL-2.0",
"dependencies": { "dependencies": {
@@ -1423,23 +1357,23 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
}, },
"optionalDependencies": { "optionalDependencies": {
"lightningcss-android-arm64": "1.32.0", "lightningcss-android-arm64": "1.33.0",
"lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.33.0",
"lightningcss-darwin-x64": "1.32.0", "lightningcss-darwin-x64": "1.33.0",
"lightningcss-freebsd-x64": "1.32.0", "lightningcss-freebsd-x64": "1.33.0",
"lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.33.0",
"lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-gnu": "1.33.0",
"lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-arm64-musl": "1.33.0",
"lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-gnu": "1.33.0",
"lightningcss-linux-x64-musl": "1.32.0", "lightningcss-linux-x64-musl": "1.33.0",
"lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-arm64-msvc": "1.33.0",
"lightningcss-win32-x64-msvc": "1.32.0" "lightningcss-win32-x64-msvc": "1.33.0"
} }
}, },
"node_modules/lightningcss-android-arm64": { "node_modules/lightningcss-android-arm64": {
"version": "1.32.0", "version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
"integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1458,9 +1392,9 @@
} }
}, },
"node_modules/lightningcss-darwin-arm64": { "node_modules/lightningcss-darwin-arm64": {
"version": "1.32.0", "version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
"integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1479,9 +1413,9 @@
} }
}, },
"node_modules/lightningcss-darwin-x64": { "node_modules/lightningcss-darwin-x64": {
"version": "1.32.0", "version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
"integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1500,9 +1434,9 @@
} }
}, },
"node_modules/lightningcss-freebsd-x64": { "node_modules/lightningcss-freebsd-x64": {
"version": "1.32.0", "version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
"integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1521,9 +1455,9 @@
} }
}, },
"node_modules/lightningcss-linux-arm-gnueabihf": { "node_modules/lightningcss-linux-arm-gnueabihf": {
"version": "1.32.0", "version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
"integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -1542,9 +1476,9 @@
} }
}, },
"node_modules/lightningcss-linux-arm64-gnu": { "node_modules/lightningcss-linux-arm64-gnu": {
"version": "1.32.0", "version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
"integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1563,9 +1497,9 @@
} }
}, },
"node_modules/lightningcss-linux-arm64-musl": { "node_modules/lightningcss-linux-arm64-musl": {
"version": "1.32.0", "version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
"integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1584,9 +1518,9 @@
} }
}, },
"node_modules/lightningcss-linux-x64-gnu": { "node_modules/lightningcss-linux-x64-gnu": {
"version": "1.32.0", "version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
"integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1605,9 +1539,9 @@
} }
}, },
"node_modules/lightningcss-linux-x64-musl": { "node_modules/lightningcss-linux-x64-musl": {
"version": "1.32.0", "version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
"integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1626,9 +1560,9 @@
} }
}, },
"node_modules/lightningcss-win32-arm64-msvc": { "node_modules/lightningcss-win32-arm64-msvc": {
"version": "1.32.0", "version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
"integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1647,9 +1581,9 @@
} }
}, },
"node_modules/lightningcss-win32-x64-msvc": { "node_modules/lightningcss-win32-x64-msvc": {
"version": "1.32.0", "version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
"integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1729,9 +1663,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.12", "version": "3.3.19",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -1820,9 +1754,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "4.0.5", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1858,9 +1792,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.19", "version": "8.5.28",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
"integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -1877,7 +1811,7 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"nanoid": "^3.3.12", "nanoid": "^3.3.18",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
}, },
@@ -1906,13 +1840,13 @@
} }
}, },
"node_modules/rolldown": { "node_modules/rolldown": {
"version": "1.1.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.8.tgz",
"integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", "integrity": "sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@oxc-project/types": "=0.139.0", "@oxc-project/types": "=0.149.0",
"@rolldown/pluginutils": "^1.0.0" "@rolldown/pluginutils": "^1.0.0"
}, },
"bin": { "bin": {
@@ -1922,21 +1856,21 @@
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rolldown/binding-android-arm64": "1.1.5", "@rolldown/binding-android-arm-eabi": "1.2.8",
"@rolldown/binding-darwin-arm64": "1.1.5", "@rolldown/binding-android-arm64": "1.2.8",
"@rolldown/binding-darwin-x64": "1.1.5", "@rolldown/binding-darwin-arm64": "1.2.8",
"@rolldown/binding-freebsd-x64": "1.1.5", "@rolldown/binding-darwin-x64": "1.2.8",
"@rolldown/binding-linux-arm-gnueabihf": "1.1.5", "@rolldown/binding-freebsd-x64": "1.2.8",
"@rolldown/binding-linux-arm64-gnu": "1.1.5", "@rolldown/binding-linux-arm-gnueabihf": "1.2.8",
"@rolldown/binding-linux-arm64-musl": "1.1.5", "@rolldown/binding-linux-arm64-gnu": "1.2.8",
"@rolldown/binding-linux-ppc64-gnu": "1.1.5", "@rolldown/binding-linux-arm64-musl": "1.2.8",
"@rolldown/binding-linux-s390x-gnu": "1.1.5", "@rolldown/binding-linux-ppc64-gnu": "1.2.8",
"@rolldown/binding-linux-x64-gnu": "1.1.5", "@rolldown/binding-linux-s390x-gnu": "1.2.8",
"@rolldown/binding-linux-x64-musl": "1.1.5", "@rolldown/binding-linux-x64-gnu": "1.2.8",
"@rolldown/binding-openharmony-arm64": "1.1.5", "@rolldown/binding-linux-x64-musl": "1.2.8",
"@rolldown/binding-wasm32-wasi": "1.1.5", "@rolldown/binding-openharmony-arm64": "1.2.8",
"@rolldown/binding-win32-arm64-msvc": "1.1.5", "@rolldown/binding-win32-arm64-msvc": "1.2.8",
"@rolldown/binding-win32-x64-msvc": "1.1.5" "@rolldown/binding-win32-x64-msvc": "1.2.8"
} }
}, },
"node_modules/saxes": { "node_modules/saxes": {
@@ -2105,14 +2039,6 @@
"node": ">=20" "node": ">=20"
} }
}, },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD",
"optional": true
},
"node_modules/typescript": { "node_modules/typescript": {
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
@@ -2145,16 +2071,16 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "8.1.5", "version": "8.3.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz",
"integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"lightningcss": "^1.32.0", "lightningcss": "^1.33.0",
"picomatch": "^4.0.5", "picomatch": "^4.0.7",
"postcss": "^8.5.17", "postcss": "^8.5.28",
"rolldown": "~1.1.5", "rolldown": "~1.2.6",
"tinyglobby": "^0.2.17" "tinyglobby": "^0.2.17"
}, },
"bin": { "bin": {
@@ -2171,7 +2097,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0", "@types/node": "^20.19.0 || >=22.12.0",
"@vitejs/devtools": "^0.3.0", "@vitejs/devtools": "^0.7.1",
"esbuild": "^0.27.0 || ^0.28.0", "esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0", "jiti": ">=1.21.0",
"less": "^4.0.0", "less": "^4.0.0",
@@ -2320,16 +2246,16 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/vue": { "node_modules/vue": {
"version": "3.5.42", "version": "3.5.40",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.42.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.40.tgz",
"integrity": "sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==", "integrity": "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.5.42", "@vue/compiler-dom": "3.5.40",
"@vue/compiler-sfc": "3.5.42", "@vue/compiler-sfc": "3.5.40",
"@vue/runtime-dom": "3.5.42", "@vue/runtime-dom": "3.5.40",
"@vue/server-renderer": "3.5.42", "@vue/server-renderer": "3.5.40",
"@vue/shared": "3.5.42" "@vue/shared": "3.5.40"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "*" "typescript": "*"
+71 -27
View File
@@ -8,7 +8,7 @@ import type { Ref, ComputedRef } from 'vue'
import { useProvidersStore } from '@/stores/providersStore' import { useProvidersStore } from '@/stores/providersStore'
import { useServicesStore } from '@/stores/servicesStore' import { useServicesStore } from '@/stores/servicesStore'
import { useNodesStore, ROOT_ID } from '@/stores/nodesStore' import { useNodesStore, ROOT_ID } from '@/stores/nodesStore'
import type { ListFilter, ListSort, ListRange } from '@/types/common' import type { ListFilter, ListSort, ListRange, ServiceIdentifier, CollectionIdentifier } from '@/types/common'
import type { DocumentInterface } from '@/types/document' import type { DocumentInterface } from '@/types/document'
import { CollectionObject } from '@/models/collection' import { CollectionObject } from '@/models/collection'
import { EntityObject } from '@/models/entity' import { EntityObject } from '@/models/entity'
@@ -16,6 +16,9 @@ import { EntityObject } from '@/models/entity'
// Base URL for file manager transfer endpoints // Base URL for file manager transfer endpoints
const TRANSFER_BASE_URL = '/m/documents_manager' const TRANSFER_BASE_URL = '/m/documents_manager'
// Number of collections+entities requested per page
const DEFAULT_PAGE_TALLY = 64
export interface UseDocumentManagerOptions { export interface UseDocumentManagerOptions {
providerId: string | Ref<string> | ComputedRef<string> providerId: string | Ref<string> | ComputedRef<string>
serviceId: string | Ref<string> | ComputedRef<string> serviceId: string | Ref<string> | ComputedRef<string>
@@ -32,6 +35,12 @@ export function useDocumentManager(options: UseDocumentManagerOptions) {
const currentProviderId = () => unref(providerId) const currentProviderId = () => unref(providerId)
const currentServiceId = () => unref(serviceId) const currentServiceId = () => unref(serviceId)
const currentSources = (): (ServiceIdentifier | CollectionIdentifier)[] => [
currentLocation.value === ROOT_ID
? `${currentProviderId()}:${currentServiceId()}`
: `${currentProviderId()}:${currentServiceId()}:${currentLocation.value}`,
]
// Current location (folder being viewed) // Current location (folder being viewed)
const currentLocation: Ref<string> = ref(ROOT_ID) const currentLocation: Ref<string> = ref(ROOT_ID)
@@ -39,6 +48,13 @@ export function useDocumentManager(options: UseDocumentManagerOptions) {
const isLoading = computed(() => nodesStore.transceiving) const isLoading = computed(() => nodesStore.transceiving)
const error = computed(() => nodesStore.error) const error = computed(() => nodesStore.error)
// Pagination state for the current location + filter + sort
const currentFilter: Ref<ListFilter | undefined> = ref(undefined)
const currentSort: Ref<ListSort | undefined> = ref(undefined)
const loadedCount = ref(0)
const hasMore = ref(false)
const isLoadingMore = ref(false)
// Provider and service // Provider and service
const provider = computed(() => providersStore.provider(currentProviderId())) const provider = computed(() => providersStore.provider(currentProviderId()))
const service = computed(() => servicesStore.service(currentProviderId(), currentServiceId())) const service = computed(() => servicesStore.service(currentProviderId(), currentServiceId()))
@@ -71,7 +87,7 @@ export function useDocumentManager(options: UseDocumentManagerOptions) {
// Navigate to a folder // Navigate to a folder
const navigateTo = async (collectionId: string | null) => { const navigateTo = async (collectionId: string | null) => {
currentLocation.value = collectionId || ROOT_ID currentLocation.value = collectionId || ROOT_ID
await refresh() await resetAndLoad(currentFilter.value, currentSort.value)
} }
// Navigate up one level // Navigate up one level
@@ -90,28 +106,55 @@ export function useDocumentManager(options: UseDocumentManagerOptions) {
await navigateTo(ROOT_ID) await navigateTo(ROOT_ID)
} }
// Refresh current location // Reset the current location's loaded items and (re)fetch the first page,
const refresh = async ( // optionally with a new filter/sort (e.g. search query or sort field change)
filter?: ListFilter, const resetAndLoad = async (filter?: ListFilter, sort?: ListSort) => {
sort?: ListSort, currentFilter.value = filter
range?: ListRange currentSort.value = sort
) => { loadedCount.value = 0
await nodesStore.fetchNodes( hasMore.value = false
currentProviderId(),
currentServiceId(), const range: ListRange = { type: 'tally', anchor: 'absolute', position: 0, tally: DEFAULT_PAGE_TALLY }
currentLocation.value === ROOT_ID ? ROOT_ID : currentLocation.value, const result = await nodesStore.fetchNodes(
currentSources(),
filter, filter,
sort, sort,
range range,
'reset'
) )
loadedCount.value = result.collections.length + result.entities.length
hasMore.value = !result.isLastPage
}
// Fetch the next page of the current location (infinite scroll "load more")
const loadMore = async () => {
if (!hasMore.value || isLoadingMore.value) {
return
}
isLoadingMore.value = true
try {
const range: ListRange = { type: 'tally', anchor: 'absolute', position: loadedCount.value, tally: DEFAULT_PAGE_TALLY }
const result = await nodesStore.fetchNodes(
currentSources(),
currentFilter.value,
currentSort.value,
range,
'append'
)
loadedCount.value += result.collections.length + result.entities.length
hasMore.value = !result.isLastPage
} finally {
isLoadingMore.value = false
}
} }
// Create a new folder // Create a new folder
const createFolder = async (label: string): Promise<CollectionObject> => { const createFolder = async (label: string): Promise<CollectionObject> => {
return await nodesStore.createCollection( return await nodesStore.createCollection(
currentProviderId(), `${currentProviderId()}:${currentServiceId()}:${currentLocation.value}`,
currentServiceId(),
currentLocation.value === ROOT_ID ? ROOT_ID : currentLocation.value,
{ label, owner: '' } { label, owner: '' }
) )
} }
@@ -132,9 +175,7 @@ export function useDocumentManager(options: UseDocumentManagerOptions) {
} }
return await nodesStore.createEntity( return await nodesStore.createEntity(
currentProviderId(), `${currentProviderId()}:${currentServiceId()}:${currentLocation.value}`,
currentServiceId(),
currentLocation.value === ROOT_ID ? ROOT_ID : currentLocation.value,
properties properties
) )
} }
@@ -147,7 +188,7 @@ export function useDocumentManager(options: UseDocumentManagerOptions) {
} }
if (node instanceof CollectionObject) { if (node instanceof CollectionObject) {
return await nodesStore.updateCollection(currentProviderId(), currentServiceId(), nodeId, { return await nodesStore.updateCollection(`${node.provider}:${node.service}:${nodeId}`, {
label: newLabel, label: newLabel,
owner: node.properties.owner, owner: node.properties.owner,
}) })
@@ -161,7 +202,7 @@ export function useDocumentManager(options: UseDocumentManagerOptions) {
format: node.properties.format, format: node.properties.format,
encoding: node.properties.encoding, encoding: node.properties.encoding,
} }
return await nodesStore.updateEntity(currentProviderId(), currentServiceId(), node.collection, nodeId, properties) return await nodesStore.updateEntity(`${node.provider}:${node.service}:${node.collection || ROOT_ID}:${nodeId}`, properties)
} }
} }
@@ -173,9 +214,9 @@ export function useDocumentManager(options: UseDocumentManagerOptions) {
} }
if (node instanceof CollectionObject) { if (node instanceof CollectionObject) {
return await nodesStore.deleteCollection(currentProviderId(), currentServiceId(), nodeId) return await nodesStore.deleteCollection(`${node.provider}:${node.service}:${nodeId}`)
} else { } else {
return await nodesStore.deleteEntity(currentProviderId(), currentServiceId(), node.collection, nodeId) return await nodesStore.deleteEntity(`${node.provider}:${node.service}:${node.collection || ROOT_ID}:${nodeId}`)
} }
} }
@@ -185,7 +226,7 @@ export function useDocumentManager(options: UseDocumentManagerOptions) {
if (!node || !(node instanceof EntityObject)) { if (!node || !(node instanceof EntityObject)) {
throw new Error('Entity not found') throw new Error('Entity not found')
} }
return await nodesStore.readEntity(currentProviderId(), currentServiceId(), node.collection || ROOT_ID, entityId) return await nodesStore.readEntity(`${node.provider}:${node.service}:${node.collection || ROOT_ID}:${entityId}`)
} }
// Write file content // Write file content
@@ -194,7 +235,7 @@ export function useDocumentManager(options: UseDocumentManagerOptions) {
if (!node || !(node instanceof EntityObject)) { if (!node || !(node instanceof EntityObject)) {
throw new Error('Entity not found') throw new Error('Entity not found')
} }
return await nodesStore.writeEntity(currentProviderId(), currentServiceId(), node.collection, entityId, content) return await nodesStore.writeEntity(`${node.provider}:${node.service}:${node.collection || ROOT_ID}:${entityId}`, content)
} }
// Get a URL suitable for inline viewing (img src / video src) // Get a URL suitable for inline viewing (img src / video src)
@@ -253,7 +294,7 @@ export function useDocumentManager(options: UseDocumentManagerOptions) {
await providersStore.list([currentProviderId()]) await providersStore.list([currentProviderId()])
await servicesStore.list() await servicesStore.list()
if (autoFetch) { if (autoFetch) {
await refresh() await resetAndLoad()
} }
} }
@@ -262,6 +303,8 @@ export function useDocumentManager(options: UseDocumentManagerOptions) {
currentLocation, currentLocation,
isLoading, isLoading,
error, error,
hasMore,
isLoadingMore,
// Provider/Service // Provider/Service
provider, provider,
@@ -279,7 +322,8 @@ export function useDocumentManager(options: UseDocumentManagerOptions) {
navigateTo, navigateTo,
navigateUp, navigateUp,
navigateToRoot, navigateToRoot,
refresh, resetAndLoad,
loadMore,
// Operations // Operations
createFolder, createFolder,
+8 -31
View File
@@ -25,23 +25,11 @@ import type {
import { useIntegrationStore } from '@KTXC/stores/integrationStore'; import { useIntegrationStore } from '@KTXC/stores/integrationStore';
import { CollectionObject, CollectionPropertiesObject } from '../models/collection'; import { CollectionObject, CollectionPropertiesObject } from '../models/collection';
function isCollectionPayload(value: unknown): value is CollectionInterface {
if (!value || typeof value !== 'object') {
return false;
}
const candidate = value as Record<string, unknown>;
return (
('identifier' in candidate || 'provider' in candidate || 'service' in candidate)
&& 'properties' in candidate
);
}
/** /**
* Helper to create the right collection model class based on provider identifier * Helper to create the right collection model class based on provider identifier
* Uses provider-specific factory if available, otherwise returns base CollectionObject * Uses provider-specific factory if available, otherwise returns base CollectionObject
*/ */
function createCollectionObject(data: CollectionInterface): CollectionObject { export function createCollectionObject(data: CollectionInterface): CollectionObject {
const integrationStore = useIntegrationStore(); const integrationStore = useIntegrationStore();
const factoryItem = integrationStore.getItemById('documents_collection_factory', data.provider) as any; const factoryItem = integrationStore.getItemById('documents_collection_factory', data.provider) as any;
const factory = factoryItem?.factory; const factory = factoryItem?.factory;
@@ -68,38 +56,27 @@ export const collectionService = {
const serviceList: Record<string, Record<string, CollectionObject>> = {}; const serviceList: Record<string, Record<string, CollectionObject>> = {};
Object.entries(providerServices).forEach(([serviceId, serviceCollections]) => { Object.entries(providerServices).forEach(([serviceId, serviceCollections]) => {
const collectionList: Record<string, CollectionObject> = {}; const collectionList: Record<string, CollectionObject> = {};
Object.entries(serviceCollections as Record<string, unknown>).forEach(([collectionId, collectionData]) => { Object.entries(serviceCollections).forEach(([collectionId, collectionData]) => {
if (isCollectionPayload(collectionData)) { collectionList[collectionId] = createCollectionObject(collectionData);
collectionList[collectionId] = createCollectionObject(collectionData);
return;
}
if (collectionData && typeof collectionData === 'object') {
Object.entries(collectionData as Record<string, unknown>).forEach(([nestedCollectionId, nestedCollectionData]) => {
if (isCollectionPayload(nestedCollectionData)) {
collectionList[nestedCollectionId] = createCollectionObject(nestedCollectionData);
}
});
}
}); });
serviceList[serviceId] = collectionList; serviceList[serviceId] = collectionList;
}); });
providerList[providerId] = serviceList; providerList[providerId] = serviceList;
}); });
return providerList; return providerList;
}, },
/** /**
* Retrieve a specific collection by provider and identifier * Retrieve collections by resource identifier
* *
* @param request - fetch request parameters * @param request - fetch request parameters
* *
* @returns Promise with collection object * @returns Promise with collections keyed by resource identifier
*/ */
async fetch(request: CollectionFetchRequest): Promise<CollectionObject> { async fetch(request: CollectionFetchRequest): Promise<Record<string, CollectionObject>> {
const response = await transceivePost<CollectionFetchRequest, CollectionFetchResponse>('collection.fetch', request); const response = await transceivePost<CollectionFetchRequest, CollectionFetchResponse>('collection.fetch', request);
return createCollectionObject(response); return Object.fromEntries(Object.entries(response).map(([target, data]) => [target, createCollectionObject(data)]));
}, },
/** /**
+1 -1
View File
@@ -35,7 +35,7 @@ import { EntityObject } from '../models';
* Helper to create the right entity model class based on provider identifier * Helper to create the right entity model class based on provider identifier
* Uses provider-specific factory if available, otherwise returns base EntityObject * Uses provider-specific factory if available, otherwise returns base EntityObject
*/ */
function createEntityObject(data: EntityInterface): EntityObject { export function createEntityObject(data: EntityInterface): EntityObject {
const integrationStore = useIntegrationStore(); const integrationStore = useIntegrationStore();
const factoryItem = integrationStore.getItemById('documents_entity_factory', data.provider) as any; const factoryItem = integrationStore.getItemById('documents_entity_factory', data.provider) as any;
const factory = factoryItem?.factory; const factory = factoryItem?.factory;
+34
View File
@@ -0,0 +1,34 @@
import { transceiveStream } from './transceive';
import type { NodeListRequest, NodeInterface } from '../types/node';
import { createCollectionObject } from './collectionService';
import { createEntityObject } from './entityService';
import type { CollectionObject } from '../models/collection';
import type { EntityObject } from '../models/entity';
export type NodeListResult = (CollectionObject | EntityObject)[];
export const nodeService = {
/** Hydrate a mixed page in the exact order returned by the provider. */
async list(request: NodeListRequest = {}): Promise<NodeListResult> {
const nodes: NodeListResult = [];
const { total } = await transceiveStream<NodeListRequest, NodeInterface>('node.list', request, node => {
switch (node['@type']) {
case 'document:collection':
nodes.push(createCollectionObject(node));
break;
case 'document:entity':
nodes.push(createEntityObject(node));
break;
default:
throw new Error('Unsupported node type in node.list response');
}
}, { requireEnd: true });
if (total !== nodes.length) {
throw new Error('Incomplete node.list stream');
}
// Publish only complete pages so a retry uses the same offset after failure.
return nodes;
},
};
export default nodeService;
+7 -1
View File
@@ -62,7 +62,7 @@ export async function transceiveStream<TRequest, TData>(
operation: string, operation: string,
data: TRequest, data: TRequest,
onData: (data: TData) => void, onData: (data: TData) => void,
options?: { user?: string; onStart?: (expected?: number) => void } options?: { user?: string; onStart?: (expected?: number) => void; requireEnd?: boolean }
): Promise<{ total: number }> { ): Promise<{ total: number }> {
const request: ApiRequest<TRequest> = { const request: ApiRequest<TRequest> = {
version: API_VERSION, version: API_VERSION,
@@ -73,6 +73,7 @@ export async function transceiveStream<TRequest, TData>(
}; };
let total = 0; let total = 0;
let ended = false;
const dispatch = (line: string): void => { const dispatch = (line: string): void => {
const message = JSON.parse(line) as ApiStreamResponse<TData>; const message = JSON.parse(line) as ApiStreamResponse<TData>;
@@ -82,6 +83,7 @@ export async function transceiveStream<TRequest, TData>(
options?.onStart?.(message.total); options?.onStart?.(message.total);
} else if (message.status === 'end') { } else if (message.status === 'end') {
total = message.total; total = message.total;
ended = true;
} }
return; return;
} }
@@ -118,6 +120,7 @@ export async function transceiveStream<TRequest, TData>(
} }
} }
buffer += decoder.decode();
if (buffer.trim()) dispatch(buffer); if (buffer.trim()) dispatch(buffer);
} finally { } finally {
reader.releaseLock(); reader.releaseLock();
@@ -125,5 +128,8 @@ export async function transceiveStream<TRequest, TData>(
}, },
}); });
if (options?.requireEnd && !ended) {
throw new Error(`[${operation}] Stream ended without a completion frame`);
}
return { total }; return { total };
} }
+41 -29
View File
@@ -40,25 +40,25 @@ export const useCollectionsStore = defineStore('documentsCollectionsStore', () =
return `${provider}:${service ?? ''}:${identifier ?? ''}` return `${provider}:${service ?? ''}:${identifier ?? ''}`
} }
function collection(provider: string, service: string | number, identifier: string | number, retrieve: boolean = false): CollectionObject | null { function collection(target: CollectionIdentifier, retrieve: boolean = false): CollectionObject | null {
const key = identifierKey(provider, service, identifier) const key = target
if (retrieve === true && !_collections.value[key]) { if (retrieve === true && !_collections.value[key]) {
console.debug(`[Documents Manager][Store] - Force fetching collection "${key}"`) console.debug(`[Documents Manager][Store] - Force fetching collection "${key}"`)
fetch(provider, service, identifier) void fetch([target])
} }
return _collections.value[key] || null return _collections.value[key] || null
} }
function collectionsForService(provider: string, service: string | number): CollectionObject[] { function collectionsForService(target: ServiceIdentifier): CollectionObject[] {
const serviceKeyPrefix = `${provider}:${service}:` const serviceKeyPrefix = `${target}:`
return Object.entries(_collections.value) return Object.entries(_collections.value)
.filter(([key]) => key.startsWith(serviceKeyPrefix)) .filter(([key]) => key.startsWith(serviceKeyPrefix))
.map(([_, collectionObj]) => collectionObj) .map(([_, collectionObj]) => collectionObj)
} }
function clearService(provider: string, service: string | number): void { function clearService(target: ServiceIdentifier): void {
const serviceKeyPrefix = `${provider}:${service}:` const serviceKeyPrefix = `${target}:`
Object.keys(_collections.value) Object.keys(_collections.value)
.filter((key) => key.startsWith(serviceKeyPrefix)) .filter((key) => key.startsWith(serviceKeyPrefix))
.forEach((key) => { .forEach((key) => {
@@ -70,6 +70,27 @@ export const useCollectionsStore = defineStore('documentsCollectionsStore', () =
_collections.value = {} _collections.value = {}
} }
/**
* Merge externally-hydrated collections into the store (used by paginated node fetches)
*/
function mergeMany(collections: CollectionObject[]): void {
const hydrated: Record<string, CollectionObject> = {}
collections.forEach((collectionObj) => {
const key = identifierKey(collectionObj.provider, collectionObj.service, collectionObj.identifier)
hydrated[key] = collectionObj
})
_collections.value = { ..._collections.value, ...hydrated }
}
/**
* Remove specific collections by their own identifiers (used to evict a stale page before a reset)
*/
function removeMany(targets: CollectionIdentifier[]): void {
targets.forEach((target) => {
delete _collections.value[target]
})
}
// Actions // Actions
async function list(sources?: (ServiceIdentifier | CollectionIdentifier)[], 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
@@ -98,14 +119,11 @@ export const useCollectionsStore = defineStore('documentsCollectionsStore', () =
} }
} }
async function fetch(provider: string, service: string | number, identifier: string | number): Promise<CollectionObject> { async function fetch(targets: CollectionIdentifier[]): Promise<Record<string, CollectionObject>> {
transceiving.value = true transceiving.value = true
try { try {
const response = await collectionService.fetch({ targets: [`${provider}:${service}:${identifier}`] }) const response = await collectionService.fetch({ targets })
const key = identifierKey(response.provider, response.service, response.identifier) _collections.value = { ..._collections.value, ...response }
_collections.value[key] = response
console.debug('[Documents Manager][Store] - Successfully fetched collection:', key)
return response return response
} catch (error: any) { } catch (error: any) {
console.error('[Documents Manager][Store] - Failed to fetch collection:', error) console.error('[Documents Manager][Store] - Failed to fetch collection:', error)
@@ -130,17 +148,12 @@ export const useCollectionsStore = defineStore('documentsCollectionsStore', () =
} }
async function create( async function create(
provider: string, target: ServiceIdentifier | CollectionIdentifier,
service: string | number,
collection: string | number | null,
properties: CollectionMutableProperties, properties: CollectionMutableProperties,
): Promise<CollectionObject> { ): Promise<CollectionObject> {
transceiving.value = true transceiving.value = true
try { try {
const target: CollectionIdentifier | undefined = collection !== null && collection !== undefined && collection !== '' const response = await collectionService.create({ target, properties })
? `${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
@@ -155,14 +168,12 @@ export const useCollectionsStore = defineStore('documentsCollectionsStore', () =
} }
async function update( async function update(
provider: string, target: CollectionIdentifier,
service: string | number,
identifier: string | number,
properties: CollectionMutableProperties, properties: CollectionMutableProperties,
): Promise<CollectionObject> { ): Promise<CollectionObject> {
transceiving.value = true transceiving.value = true
try { try {
const response = await collectionService.update({ target: `${provider}:${service}:${identifier}`, properties }) const response = await collectionService.update({ 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
@@ -176,17 +187,16 @@ export const useCollectionsStore = defineStore('documentsCollectionsStore', () =
} }
} }
async function remove(provider: string, service: string | number, identifier: string | number): Promise<{ success: boolean }> { async function remove(target: CollectionIdentifier): Promise<{ success: boolean }> {
transceiving.value = true transceiving.value = true
try { try {
const response = await collectionService.delete({ target: `${provider}:${service}:${identifier}` }) const response = await collectionService.delete({ target })
const success = response.disposition === 'deleted' || response.disposition === 'moved' const success = response.disposition === 'deleted' || response.disposition === 'moved'
if (success) { if (success) {
const key = identifierKey(provider, service, identifier) delete _collections.value[target]
delete _collections.value[key]
} }
console.debug('[Documents Manager][Store] - Successfully deleted collection:', `${provider}:${service}:${identifier}`) console.debug('[Documents Manager][Store] - Successfully deleted collection:', target)
return { success } 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)
@@ -206,6 +216,8 @@ export const useCollectionsStore = defineStore('documentsCollectionsStore', () =
collectionsForService, collectionsForService,
clearService, clearService,
clearAll, clearAll,
mergeMany,
removeMany,
list, list,
fetch, fetch,
extant, extant,
+48 -62
View File
@@ -49,37 +49,34 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
} }
function entity( function entity(
provider: string, target: EntityIdentifier,
service: string | number,
collection: string | number,
identifier: string | number,
retrieve: boolean = false, retrieve: boolean = false,
): EntityObject | null { ): EntityObject | null {
const key = identifierKey(provider, service, collection, identifier) const key = target
if (retrieve === true && !_entities.value[key]) { if (retrieve === true && !_entities.value[key]) {
console.debug(`[Documents Manager][Store] - Force fetching entity "${key}"`) console.debug(`[Documents Manager][Store] - Force fetching entity "${key}"`)
fetch(provider, service, collection, [identifier]) void fetch([target])
} }
return _entities.value[key] || null return _entities.value[key] || null
} }
function entitiesForService(provider: string, service: string | number): EntityObject[] { function entitiesForService(target: ServiceIdentifier): EntityObject[] {
const serviceKeyPrefix = `${provider}:${service}:` const serviceKeyPrefix = `${target}:`
return Object.entries(_entities.value) return Object.entries(_entities.value)
.filter(([key]) => key.startsWith(serviceKeyPrefix)) .filter(([key]) => key.startsWith(serviceKeyPrefix))
.map(([_, entityObj]) => entityObj) .map(([_, entityObj]) => entityObj)
} }
function entitiesForCollection(provider: string, service: string | number, collection: string | number): EntityObject[] { function entitiesForCollection(target: CollectionIdentifier): EntityObject[] {
const collectionKeyPrefix = `${provider}:${service}:${collection}:` const collectionKeyPrefix = `${target}:`
return Object.entries(_entities.value) return Object.entries(_entities.value)
.filter(([key]) => key.startsWith(collectionKeyPrefix)) .filter(([key]) => key.startsWith(collectionKeyPrefix))
.map(([_, entityObj]) => entityObj) .map(([_, entityObj]) => entityObj)
} }
function clearService(provider: string, service: string | number): void { function clearService(target: ServiceIdentifier): void {
const serviceKeyPrefix = `${provider}:${service}:` const serviceKeyPrefix = `${target}:`
Object.keys(_entities.value) Object.keys(_entities.value)
.filter((key) => key.startsWith(serviceKeyPrefix)) .filter((key) => key.startsWith(serviceKeyPrefix))
.forEach((key) => { .forEach((key) => {
@@ -87,8 +84,8 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
}) })
} }
function clearCollection(provider: string, service: string | number, collection: string | number): void { function clearCollection(target: CollectionIdentifier): void {
const collectionKeyPrefix = `${provider}:${service}:${collection}:` const collectionKeyPrefix = `${target}:`
Object.keys(_entities.value) Object.keys(_entities.value)
.filter((key) => key.startsWith(collectionKeyPrefix)) .filter((key) => key.startsWith(collectionKeyPrefix))
.forEach((key) => { .forEach((key) => {
@@ -100,6 +97,18 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
_entities.value = {} _entities.value = {}
} }
/**
* Merge externally-hydrated entities into the store (used by paginated node fetches)
*/
function mergeMany(entities: EntityObject[]): void {
const hydrated: Record<string, EntityObject> = {}
entities.forEach((entityObj) => {
const key = identifierKey(entityObj.provider, entityObj.service, entityObj.collection, entityObj.identifier)
hydrated[key] = entityObj
})
_entities.value = { ..._entities.value, ...hydrated }
}
// Actions // Actions
async function list(sources?: (ServiceIdentifier | CollectionIdentifier)[], 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
@@ -131,25 +140,13 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
} }
async function fetch( async function fetch(
provider: string, targets: EntityIdentifier[],
service: string | number,
collection: string | number,
identifiers: (string | number)[],
): Promise<Record<string, EntityObject>> { ): Promise<Record<string, EntityObject>> {
transceiving.value = true transceiving.value = true
try { try {
const targets: EntityIdentifier[] = identifiers.map((identifier) => `${provider}:${service}:${collection}:${identifier}` as EntityIdentifier)
const response = await entityService.fetch({ targets }) const response = await entityService.fetch({ targets })
_entities.value = { ..._entities.value, ...response }
const hydrated: Record<string, EntityObject> = {} return response
Object.entries(response).forEach(([identifier, entityObj]) => {
const key = identifierKey(provider, service, collection, identifier)
hydrated[key] = entityObj
_entities.value[key] = entityObj
})
console.debug('[Documents Manager][Store] - Successfully fetched', Object.keys(hydrated).length, 'entities')
return hydrated
} catch (error: any) { } catch (error: any) {
console.error('[Documents Manager][Store] - Failed to fetch entities:', error) console.error('[Documents Manager][Store] - Failed to fetch entities:', error)
throw error throw error
@@ -173,15 +170,13 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
} }
async function create( async function create(
provider: string, target: CollectionIdentifier,
service: string | number,
collection: string | number,
properties: DocumentInterface, properties: DocumentInterface,
options?: Record<string, unknown>, options?: Record<string, unknown>,
): Promise<EntityObject> { ): Promise<EntityObject> {
transceiving.value = true transceiving.value = true
try { try {
const response = await entityService.create({ target: `${provider}:${service}:${collection}`, properties, options }) const response = await entityService.create({ target, 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
@@ -196,15 +191,12 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
} }
async function update( async function update(
provider: string, target: EntityIdentifier,
service: string | number,
collection: string | number,
identifier: string | number,
properties: DocumentInterface, properties: DocumentInterface,
): Promise<EntityObject> { ): Promise<EntityObject> {
transceiving.value = true transceiving.value = true
try { try {
const response = await entityService.update({ target: `${provider}:${service}:${collection}:${identifier}`, properties }) const response = await entityService.update({ target, 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
@@ -219,23 +211,22 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
} }
async function remove( async function remove(
provider: string, targets: EntityIdentifier[],
service: string | number, ): Promise<{ successes: EntityIdentifier[]; failures: EntityIdentifier[] }> {
collection: string | number,
identifier: string | number,
): Promise<{ success: boolean }> {
transceiving.value = true transceiving.value = true
try { try {
const target: EntityIdentifier = `${provider}:${service}:${collection}:${identifier}` const response = await entityService.delete({ targets })
const response = await entityService.delete({ targets: [target] }) const successes: EntityIdentifier[] = []
const success = response[target]?.disposition === 'deleted' const failures: EntityIdentifier[] = []
if (success) { for (const target of targets) {
const key = identifierKey(provider, service, collection, identifier) if (response[target]?.disposition === 'deleted') {
delete _entities.value[key] delete _entities.value[target]
successes.push(target)
} else {
failures.push(target)
}
} }
return { successes, failures }
console.debug('[Documents Manager][Store] - Successfully deleted entity:', target)
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
@@ -277,14 +268,11 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
} }
async function read( async function read(
provider: string, target: EntityIdentifier,
service: string | number,
collection: string | number,
identifier: string | number,
): Promise<string | null> { ): Promise<string | null> {
transceiving.value = true transceiving.value = true
try { try {
const response = await entityService.read({ target: `${provider}:${service}:${collection}:${identifier}` }) const response = await entityService.read({ target })
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)
@@ -295,15 +283,12 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
} }
async function write( async function write(
provider: string, target: EntityIdentifier,
service: string | number,
collection: string | number,
identifier: string | number,
content: string, content: string,
): Promise<number> { ): Promise<number> {
transceiving.value = true transceiving.value = true
try { try {
const response = await entityService.write({ target: `${provider}:${service}:${collection}:${identifier}`, content, encoding: 'base64' }) const response = await entityService.write({ target, 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)
@@ -325,6 +310,7 @@ export const useEntitiesStore = defineStore('documentsEntitiesStore', () => {
clearService, clearService,
clearCollection, clearCollection,
clearAll, clearAll,
mergeMany,
list, list,
fetch, fetch,
extant, extant,
+76 -68
View File
@@ -6,6 +6,7 @@ import { computed, ref, readonly } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import type { import type {
CollectionIdentifier, CollectionIdentifier,
EntityIdentifier,
ServiceIdentifier, ServiceIdentifier,
ListFilter, ListFilter,
ListSort, ListSort,
@@ -16,6 +17,7 @@ import type {
import { CollectionObject, EntityObject } from '../models' import { CollectionObject, EntityObject } from '../models'
import { useCollectionsStore } from './collectionsStore' import { useCollectionsStore } from './collectionsStore'
import { useEntitiesStore } from './entitiesStore' import { useEntitiesStore } from './entitiesStore'
import { nodeService } from '../services/nodeService'
export const ROOT_ID = '00000000-0000-0000-0000-000000000000' export const ROOT_ID = '00000000-0000-0000-0000-000000000000'
@@ -46,8 +48,8 @@ export const useNodesStore = defineStore('documentsNodesStore', () => {
function getServiceNodes(providerId: string, serviceId: string | number): NodeRecord[] { function getServiceNodes(providerId: string, serviceId: string | number): NodeRecord[] {
return [ return [
...collectionsStore.collectionsForService(providerId, serviceId), ...collectionsStore.collectionsForService(`${providerId}:${serviceId}`),
...entitiesStore.entitiesForService(providerId, serviceId), ...entitiesStore.entitiesForService(`${providerId}:${serviceId}`),
] ]
} }
@@ -105,20 +107,13 @@ export const useNodesStore = defineStore('documentsNodesStore', () => {
} }
async function fetchCollections( async function fetchCollections(
providerId: string, sources?: (ServiceIdentifier | CollectionIdentifier)[],
serviceId: string | number,
collectionId: string | number | null,
filter?: ListFilter, filter?: ListFilter,
sort?: ListSort, sort?: ListSort,
): Promise<CollectionObject[]> { ): Promise<CollectionObject[]> {
error.value = null error.value = null
try { try {
const sources: (ServiceIdentifier | CollectionIdentifier)[] = collectionId === null return Object.values(await collectionsStore.list(sources, filter, sort))
? [`${providerId}:${serviceId}`]
: [`${providerId}:${serviceId}:${collectionId}`]
await collectionsStore.list(sources, filter, sort)
return collectionsStore.collectionsForService(providerId, serviceId)
} catch (e) { } catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to fetch collections' error.value = e instanceof Error ? e.message : 'Failed to fetch collections'
throw e throw e
@@ -126,43 +121,76 @@ export const useNodesStore = defineStore('documentsNodesStore', () => {
} }
async function fetchEntities( async function fetchEntities(
providerId: string, sources?: (ServiceIdentifier | CollectionIdentifier)[],
serviceId: string | number,
collectionId: string | number | null,
filter?: ListFilter, filter?: ListFilter,
sort?: ListSort, sort?: ListSort,
range?: ListRange, range?: ListRange,
): Promise<EntityObject[]> { ): Promise<EntityObject[]> {
error.value = null error.value = null
try { try {
const sources: (ServiceIdentifier | CollectionIdentifier)[] = collectionId === null return Object.values(await entitiesStore.list(sources, filter, sort, range))
? [`${providerId}:${serviceId}`]
: [`${providerId}:${serviceId}:${collectionId}`]
await entitiesStore.list(sources, filter, sort, range)
return entitiesStore.entitiesForCollection(providerId, serviceId, collectionId)
} catch (e) { } catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to fetch entities' error.value = e instanceof Error ? e.message : 'Failed to fetch entities'
throw e throw e
} }
} }
/**
* Fetch a unified node listing, optionally limited to a page by range.
*/
async function fetchNodes( async function fetchNodes(
providerId: string, sources?: (ServiceIdentifier | CollectionIdentifier)[],
serviceId: string | number,
parentId: string | number | null = ROOT_ID,
filter?: ListFilter, filter?: ListFilter,
sort?: ListSort, sort?: ListSort,
range?: ListRange, range?: ListRange,
): Promise<NodeRecord[]> { mode: 'reset' | 'append' = 'reset',
): Promise<{ collections: CollectionObject[]; entities: EntityObject[]; isLastPage: boolean }> {
error.value = null error.value = null
try { try {
await Promise.all([ const result = await nodeService.list({ sources, filter, sort, range })
fetchCollections(providerId, serviceId, parentId, filter, sort),
fetchEntities(providerId, serviceId, parentId, filter, sort, range),
])
return getChildren(providerId, serviceId, parentId) // Service selectors list the root; collection selectors list that folder.
// Omitted sources list roots across services, not every cached descendant.
const locations = new Map<string, { provider: string; service: string; parent: string }>()
if (sources?.length) {
for (const source of sources) {
const [provider, service, parent] = source.split(':')
const location = { provider: provider!, service: service!, parent: isRoot(parent) ? ROOT_ID : parent! }
locations.set(`${location.provider}:${location.service}:${location.parent}`, location)
}
} else {
for (const node of [...nodeList.value, ...result]) {
const location = { provider: node.provider, service: String(node.service), parent: ROOT_ID }
locations.set(`${location.provider}:${location.service}:${ROOT_ID}`, location)
}
}
// Replace only the selected locations, and only after the stream succeeds.
if (mode === 'reset') {
for (const { provider, service, parent } of locations.values()) {
const staleIds = getChildCollections(provider, service, parent)
.map((node): CollectionIdentifier => `${node.provider}:${node.service}:${node.identifier}`)
collectionsStore.removeMany(staleIds)
entitiesStore.clearCollection(`${provider}:${service}:${parent}`)
}
}
const pageCollections = result.filter((node): node is CollectionObject => node instanceof CollectionObject)
const pageEntities = result.filter((node): node is EntityObject => node instanceof EntityObject)
collectionsStore.mergeMany(pageCollections)
entitiesStore.mergeMany(pageEntities)
// The backend applies the range independently to each selected location.
const counts = new Map<string, number>()
for (const node of result) {
const parent = isRoot(node.collection) ? ROOT_ID : String(node.collection)
const key = `${node.provider}:${node.service}:${parent}`
counts.set(key, (counts.get(key) ?? 0) + 1)
}
const isLastPage = range === undefined || [...counts.values()].every(count => count < range.tally)
return { collections: pageCollections, entities: pageEntities, isLastPage }
} catch (e) { } catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to fetch nodes' error.value = e instanceof Error ? e.message : 'Failed to fetch nodes'
throw e throw e
@@ -170,14 +198,12 @@ export const useNodesStore = defineStore('documentsNodesStore', () => {
} }
async function createCollection( async function createCollection(
providerId: string, target: ServiceIdentifier | CollectionIdentifier,
serviceId: string | number,
parentCollectionId: string | number | null,
properties: CollectionMutableProperties, properties: CollectionMutableProperties,
): Promise<CollectionObject> { ): Promise<CollectionObject> {
error.value = null error.value = null
try { try {
return await collectionsStore.create(providerId, serviceId, parentCollectionId, properties) return await collectionsStore.create(target, properties)
} catch (e) { } catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to create collection' error.value = e instanceof Error ? e.message : 'Failed to create collection'
throw e throw e
@@ -185,14 +211,12 @@ export const useNodesStore = defineStore('documentsNodesStore', () => {
} }
async function updateCollection( async function updateCollection(
providerId: string, target: CollectionIdentifier,
serviceId: string | number,
identifier: string | number,
properties: CollectionMutableProperties, properties: CollectionMutableProperties,
): Promise<CollectionObject> { ): Promise<CollectionObject> {
error.value = null error.value = null
try { try {
return await collectionsStore.update(providerId, serviceId, identifier, properties) return await collectionsStore.update(target, properties)
} catch (e) { } catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to update collection' error.value = e instanceof Error ? e.message : 'Failed to update collection'
throw e throw e
@@ -200,13 +224,11 @@ export const useNodesStore = defineStore('documentsNodesStore', () => {
} }
async function deleteCollection( async function deleteCollection(
providerId: string, target: CollectionIdentifier,
serviceId: string | number,
identifier: string | number,
): Promise<boolean> { ): Promise<boolean> {
error.value = null error.value = null
try { try {
const response = await collectionsStore.delete(providerId, serviceId, identifier) const response = await collectionsStore.delete(target)
return response.success return response.success
} catch (e) { } catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to delete collection' error.value = e instanceof Error ? e.message : 'Failed to delete collection'
@@ -215,15 +237,13 @@ export const useNodesStore = defineStore('documentsNodesStore', () => {
} }
async function createEntity( async function createEntity(
providerId: string, target: CollectionIdentifier,
serviceId: string | number,
collectionId: string | number,
properties: DocumentInterface, properties: DocumentInterface,
options?: Record<string, unknown>, options?: Record<string, unknown>,
): Promise<EntityObject> { ): Promise<EntityObject> {
error.value = null error.value = null
try { try {
return await entitiesStore.create(providerId, serviceId, collectionId, properties, options) return await entitiesStore.create(target, properties, options)
} catch (e) { } catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to create entity' error.value = e instanceof Error ? e.message : 'Failed to create entity'
throw e throw e
@@ -231,15 +251,12 @@ export const useNodesStore = defineStore('documentsNodesStore', () => {
} }
async function updateEntity( async function updateEntity(
providerId: string, target: EntityIdentifier,
serviceId: string | number,
collectionId: string | number,
identifier: string | number,
properties: DocumentInterface, properties: DocumentInterface,
): Promise<EntityObject> { ): Promise<EntityObject> {
error.value = null error.value = null
try { try {
return await entitiesStore.update(providerId, serviceId, collectionId, identifier, properties) return await entitiesStore.update(target, properties)
} catch (e) { } catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to update entity' error.value = e instanceof Error ? e.message : 'Failed to update entity'
throw e throw e
@@ -247,15 +264,12 @@ export const useNodesStore = defineStore('documentsNodesStore', () => {
} }
async function deleteEntity( async function deleteEntity(
providerId: string, target: EntityIdentifier,
serviceId: string | number,
collectionId: string | number,
identifier: string | number,
): Promise<boolean> { ): Promise<boolean> {
error.value = null error.value = null
try { try {
const response = await entitiesStore.delete(providerId, serviceId, collectionId, identifier) const response = await entitiesStore.delete([target])
return response.success return response.successes.includes(target)
} catch (e) { } catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to delete entity' error.value = e instanceof Error ? e.message : 'Failed to delete entity'
throw e throw e
@@ -263,14 +277,11 @@ export const useNodesStore = defineStore('documentsNodesStore', () => {
} }
async function readEntity( async function readEntity(
providerId: string, target: EntityIdentifier,
serviceId: string | number,
collectionId: string | number,
identifier: string | number,
): Promise<string | null> { ): Promise<string | null> {
error.value = null error.value = null
try { try {
return await entitiesStore.read(providerId, serviceId, collectionId, identifier) return await entitiesStore.read(target)
} catch (e) { } catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to read entity' error.value = e instanceof Error ? e.message : 'Failed to read entity'
throw e throw e
@@ -278,15 +289,12 @@ export const useNodesStore = defineStore('documentsNodesStore', () => {
} }
async function writeEntity( async function writeEntity(
providerId: string, target: EntityIdentifier,
serviceId: string | number,
collectionId: string | number,
identifier: string | number,
content: string, content: string,
): Promise<number> { ): Promise<number> {
error.value = null error.value = null
try { try {
return await entitiesStore.write(providerId, serviceId, collectionId, identifier, content) return await entitiesStore.write(target, content)
} catch (e) { } catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to write entity' error.value = e instanceof Error ? e.message : 'Failed to write entity'
throw e throw e
@@ -294,8 +302,8 @@ export const useNodesStore = defineStore('documentsNodesStore', () => {
} }
function clearServiceNodes(providerId: string, serviceId: string | number): void { function clearServiceNodes(providerId: string, serviceId: string | number): void {
collectionsStore.clearService(providerId, serviceId) collectionsStore.clearService(`${providerId}:${serviceId}`)
entitiesStore.clearService(providerId, serviceId) entitiesStore.clearService(`${providerId}:${serviceId}`)
} }
function clearNodes(): void { function clearNodes(): void {
+3 -5
View File
@@ -66,7 +66,7 @@ export interface CollectionFetchRequest {
targets: CollectionIdentifier[]; targets: CollectionIdentifier[];
} }
export interface CollectionFetchResponse extends CollectionInterface {} export type CollectionFetchResponse = Record<CollectionIdentifier, CollectionInterface>;
/** /**
* Collection extant * Collection extant
@@ -87,9 +87,7 @@ export interface CollectionExtantResponse {
* Collection create * Collection create
*/ */
export interface CollectionCreateRequest { export interface CollectionCreateRequest {
provider: string; target: ServiceIdentifier | CollectionIdentifier; // Service root or parent collection
service: string | number;
target?: CollectionIdentifier | null; // Parent collection identifier (absent for root)
properties: CollectionMutableProperties; properties: CollectionMutableProperties;
options?: Record<string, unknown>; options?: Record<string, unknown>;
} }
@@ -139,4 +137,4 @@ export interface CollectionMoveRequest {
target?: CollectionIdentifier | null; // Destination parent (absent for root) target?: CollectionIdentifier | null; // Destination parent (absent for root)
} }
export interface CollectionMoveResponse extends CollectionInterface {} export interface CollectionMoveResponse extends CollectionInterface {}
+1
View File
@@ -3,4 +3,5 @@ export type * from './provider';
export type * from './service'; export type * from './service';
export type * from './collection'; export type * from './collection';
export type * from './entity'; export type * from './entity';
export type * from './node';
export type * from './document'; export type * from './document';
+23
View File
@@ -0,0 +1,23 @@
/**
* Node (unified collections + entities) type definitions
*/
import type { CollectionIdentifier, ListFilter, ListRange, ListSort, ServiceIdentifier } from './common';
import type { CollectionInterface } from './collection';
import type { EntityInterface } from './entity';
/**
* Node list
*/
export interface NodeListRequest {
sources?: (ServiceIdentifier | CollectionIdentifier)[];
filter?: ListFilter;
sort?: ListSort;
range?: ListRange;
}
export type NodeInterface =
| (Omit<CollectionInterface, '@type'> & { '@type': 'document:collection' })
| (Omit<EntityInterface, '@type'> & { '@type': 'document:entity' });
/** Payload of each data frame in the node.list NDJSON stream. */
export type NodeListResponse = NodeInterface;