collection of available providers */ public function providerList(string $tenantId, string $userId, ?SourceSelector $sources = null): array { // determine filter from sources $filter = ($sources !== null && $sources->identifiers() !== []) ? $sources->identifiers() : null; // retrieve providers from provider manager return $this->providerManager->providers(ProviderInterface::TYPE_FILES, $filter); } /** * Confirm which providers are available * * @param SourceSelector $sources collection of provider identifiers to confirm * * @return array collection of providers and their availability status */ public function providerExtant(string $tenantId, string $userId, SourceSelector $sources): array { $providerFilter = $sources?->identifiers() ?? []; $providersResolved = $this->providerManager->providers(ProviderInterface::TYPE_FILES, $providerFilter); $providersAvailable = array_keys($providersResolved); $providersUnavailable = array_diff($providerFilter, $providersAvailable); $responseData = array_merge( array_fill_keys($providersAvailable, true), array_fill_keys($providersUnavailable, false) ); return $responseData; } /** * Retrieve specific provider for specific user * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param string $provider provider identifier * * @return IProviderBase * @throws InvalidArgumentException */ public function providerFetch(string $tenantId, string $userId, string $provider): IProviderBase { // retrieve provider $providers = $this->providerList($tenantId, $userId, new SourceSelector([$provider => true])); if (!isset($providers[$provider])) { throw new InvalidArgumentException("Provider '$provider' not found"); } return $providers[$provider]; } // ==================== Service Operations ==================== /** * Retrieve available services for specific user * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param SourceSelector|null $sources list of provider and service identifiers * * @return array> collections of available services */ public function serviceList(string $tenantId, string $userId, ?SourceSelector $sources = null): array { // retrieve providers $providers = $this->providerList($tenantId, $userId, $sources); // retrieve services for each provider $responseData = []; foreach ($providers as $provider) { $serviceFilter = $sources[$provider->id()] instanceof ServiceSelector ? $sources[$provider->id()]->identifiers() : []; $services = $provider->serviceList($tenantId, $userId, $serviceFilter); $responseData[$provider->id()] = $services; } return $responseData; } /** * Confirm which services are available * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param SourceSelector|null $sources collection of provider and service identifiers to confirm * * @return array collection of providers and their availability status */ public function serviceExtant(string $tenantId, string $userId, SourceSelector $sources): array { // retrieve providers $providers = $this->providerList($tenantId, $userId, $sources); $providersRequested = $sources->identifiers(); $providersUnavailable = array_diff($providersRequested, array_keys($providers)); // initialize response with unavailable providers $responseData = array_fill_keys($providersUnavailable, false); // retrieve services for each available provider foreach ($providers as $provider) { $serviceSelector = $sources[$provider->id()]; $serviceAvailability = $provider->serviceExtant($tenantId, $userId, ...$serviceSelector->identifiers()); $responseData[$provider->id()] = $serviceAvailability; } return $responseData; } /** * Retrieve service for specific user * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param string $providerId provider identifier * @param string|int $serviceId service identifier * * @return IServiceBase * @throws InvalidArgumentException */ public function serviceFetch(string $tenantId, string $userId, string $providerId, string|int $serviceId): IServiceBase { // retrieve provider and service $service = $this->providerFetch($tenantId, $userId, $providerId)->serviceFetch($tenantId, $userId, $serviceId); if ($service === null) { throw new InvalidArgumentException("Service '$serviceId' not found for provider '$providerId'"); } return $service; } // ==================== Collection Operations ==================== /** * List collections at a location * * @return array */ public function collectionList( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int|null $location = null, ?array $filter = null, ?array $sort = null ): array { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); // construct filter $collectionFilter = null; if ($filter !== null && $filter !== []) { $collectionFilter = $service->collectionListFilter(); foreach ($filter as $attribute => $value) { $collectionFilter->condition($attribute, $value); } } // construct sort $collectionSort = null; if ($sort !== null && $sort !== []) { $collectionSort = $service->collectionListSort(); foreach ($sort as $attribute => $direction) { $collectionSort->condition($attribute, $direction); } } return $service->collectionList($location, $collectionFilter, $collectionSort); } /** * Check if collection exists */ public function collectionExtant( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $identifier ): bool { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); return $service->collectionExtant($identifier); } /** * Fetch a specific collection */ public function collectionFetch( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $identifier ): ?INodeCollectionBase { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); return $service->collectionFetch($identifier); } /** * Create a new collection */ public function collectionCreate( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int|null $location, INodeCollectionMutable|array $collection, array $options = [] ): INodeCollectionBase { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!$service instanceof IServiceCollectionMutable) { throw new InvalidArgumentException('Service does not support collection creation'); } if (is_array($collection)) { $collectionObject = $service->collectionFresh(); $collectionObject->jsonDeserialize($collection); $collection = $collectionObject; } return $service->collectionCreate($location, $collection, $options); } /** * Modify an existing collection */ public function collectionModify( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $identifier, INodeCollectionMutable|array $collection ): INodeCollectionBase { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!$service instanceof IServiceCollectionMutable) { throw new InvalidArgumentException('Service does not support collection modification'); } if (is_array($collection)) { $collectionObject = $service->collectionFresh(); $collectionObject->jsonDeserialize($collection); $collection = $collectionObject; } return $service->collectionModify($identifier, $collection); } /** * Destroy a collection */ public function collectionDestroy( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $identifier ): bool { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!$service instanceof IServiceCollectionMutable) { throw new InvalidArgumentException('Service does not support collection deletion'); } return $service->collectionDestroy($identifier); } /** * Copy a collection */ public function collectionCopy( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $identifier, string|int|null $location ): INodeCollectionBase { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!$service instanceof IServiceCollectionMutable) { throw new InvalidArgumentException('Service does not support collection copy'); } return $service->collectionCopy($identifier, $location); } /** * Move a collection */ public function collectionMove( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $identifier, string|int|null $location ): INodeCollectionBase { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!$service instanceof IServiceCollectionMutable) { throw new InvalidArgumentException('Service does not support collection move'); } return $service->collectionMove($identifier, $location); } // ==================== Entity Operations ==================== /** * List entities in a collection * * @return array */ public function entityList( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collection, ?array $filter = null, ?array $sort = null, ?array $range = null ): array { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); // construct filter $entityFilter = null; if ($filter !== null && $filter !== []) { $entityFilter = $service->entityListFilter(); foreach ($filter as $attribute => $value) { $entityFilter->condition($attribute, $value); } } // construct sort $entitySort = null; if ($sort !== null && $sort !== []) { $entitySort = $service->entityListSort(); foreach ($sort as $attribute => $direction) { $entitySort->condition($attribute, $direction); } } // construct range $entityRange = null; if ($range !== null && $range !== [] && isset($range['type'])) { $entityRange = $service->entityListRange(RangeType::from($range['type'])); if ($entityRange instanceof IRangeTally) { if (isset($range['anchor'])) { $entityRange->setAnchor(RangeAnchorType::from($range['anchor'])); } if (isset($range['position'])) { $entityRange->setPosition($range['position']); } if (isset($range['tally'])) { $entityRange->setTally($range['tally']); } } } return $service->entityList($collection, $entityFilter, $entitySort, $entityRange); } /** * Get entity delta/changes since a signature */ public function entityDelta( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collection, string $signature, string $detail = 'ids' ): array { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); return $service->entityDelta($collection, $signature, $detail); } /** * Check if entities exist * * @return array */ public function entityExtant( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collection, array $identifiers ): array { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); return $service->entityExtant($collection, ...$identifiers); } /** * Fetch specific entities * * @return array */ public function entityFetch( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collection, array $identifiers ): array { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); return $service->entityFetch($collection, ...$identifiers); } /** * Read entity content */ public function entityRead( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collection, string|int $identifier ): ?string { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); return $service->entityRead($collection, $identifier); } /** * Read entity content as stream * * @return resource|null */ public function entityReadStream( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collection, string|int $identifier ) { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); return $service->entityReadStream($collection, $identifier); } /** * Read entity content chunk */ public function entityReadChunk( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collection, string|int $identifier, int $offset, int $length ): ?string { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); return $service->entityReadChunk($collection, $identifier, $offset, $length); } /** * Create a new entity */ public function entityCreate( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int|null $collection, INodeEntityMutable|array $entity, array $options = [] ): INodeEntityBase { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!$service instanceof IServiceEntityMutable) { throw new InvalidArgumentException('Service does not support entity creation'); } if (is_array($entity)) { $entityObject = $service->entityFresh(); $entityObject->jsonDeserialize($entity); $entity = $entityObject; } return $service->entityCreate($collection, $entity, $options); } /** * Modify an existing entity */ public function entityModify( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int|null $collection, string|int $identifier, INodeEntityMutable|array $entity ): INodeEntityBase { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!$service instanceof IServiceEntityMutable) { throw new InvalidArgumentException('Service does not support entity modification'); } if (is_array($entity)) { $entityObject = $service->entityFresh(); $entityObject->jsonDeserialize($entity); $entity = $entityObject; } return $service->entityModify($collection, $identifier, $entity); } /** * Destroy an entity */ public function entityDestroy( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int|null $collection, string|int $identifier ): bool { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!$service instanceof IServiceEntityMutable) { throw new InvalidArgumentException('Service does not support entity deletion'); } return $service->entityDestroy($collection, $identifier); } /** * Copy an entity */ public function entityCopy( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int|null $collection, string|int $identifier, string|int|null $destination ): INodeEntityBase { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!$service instanceof IServiceEntityMutable) { throw new InvalidArgumentException('Service does not support entity copy'); } return $service->entityCopy($collection, $identifier, $destination); } /** * Move an entity */ public function entityMove( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int|null $collection, string|int $identifier, string|int|null $destination ): INodeEntityBase { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!$service instanceof IServiceEntityMutable) { throw new InvalidArgumentException('Service does not support entity move'); } return $service->entityMove($collection, $identifier, $destination); } /** * Write entity content */ public function entityWrite( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int|null $collection, string|int $identifier, string $data ): int { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!$service instanceof IServiceEntityMutable) { throw new InvalidArgumentException('Service does not support entity write'); } return $service->entityWrite($collection, $identifier, $data); } /** * Write entity content from stream * * @return resource|null */ public function entityWriteStream( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collection, string|int $identifier ) { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!$service instanceof IServiceEntityMutable) { throw new InvalidArgumentException('Service does not support entity write stream'); } return $service->entityWriteStream($collection, $identifier); } /** * Write entity content chunk */ public function entityWriteChunk( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collection, string|int $identifier, int $offset, string $data ): int { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!$service instanceof IServiceEntityMutable) { throw new InvalidArgumentException('Service does not support entity write chunk'); } return $service->entityWriteChunk($collection, $identifier, $offset, $data); } // ==================== Node Operations (Unified/Recursive) ==================== /** * List nodes (collections and entities) at a location * * @return array */ public function nodeList( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int|null $location = null, bool $recursive = false, ?array $filter = null, ?array $sort = null, ?array $range = null ): array { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); // construct filter $nodeFilter = null; if ($filter !== null && $filter !== []) { $nodeFilter = $service->nodeListFilter(); foreach ($filter as $attribute => $value) { $nodeFilter->condition($attribute, $value); } } // construct sort $nodeSort = null; if ($sort !== null && $sort !== []) { $nodeSort = $service->nodeListSort(); foreach ($sort as $attribute => $direction) { $nodeSort->condition($attribute, $direction); } } // construct range $nodeRange = null; if ($range !== null && $range !== [] && isset($range['type'])) { $nodeRange = $service->nodeListRange(RangeType::from($range['type'])); if ($nodeRange instanceof IRangeTally) { if (isset($range['anchor'])) { $nodeRange->setAnchor(RangeAnchorType::from($range['anchor'])); } if (isset($range['position'])) { $nodeRange->setPosition($range['position']); } if (isset($range['tally'])) { $nodeRange->setTally($range['tally']); } } } return $service->nodeList($location, $recursive, $nodeFilter, $nodeSort, $nodeRange); } /** * Get node delta/changes since a signature */ public function nodeDelta( string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int|null $location, string $signature, bool $recursive = false, string $detail = 'ids' ): array { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); return $service->nodeDelta($location, $signature, $recursive, $detail); } }