# Provider Abstraction and Service Layer

StratoFusion presents Google Drive, OneDrive, and Dropbox through provider-neutral application and UI boundaries. Provider SDKs, native identifiers, namespace rules, and error shapes stay in server-side services or adapters. The durable identity boundary is always `(provider, account, resource)`.

This guide describes the current repository, including the transition between the established service layer and the newer ports-and-adapters modules. It does not imply that every class under `src/infra/providers/` is wired into the default runtime.

## Current architecture at a glance

```text
Browser component
  -> hook or ServiceManagerContext
  -> authenticated Next.js API route
  -> application/service operation
  -> ServiceRegistry -> CloudStorageService implementation
  -> Google Drive API | Microsoft Graph | Dropbox API
  -> CloudStorageResponse<DriveItemUnion ...>
  -> provider-neutral UI state
```

Large copy, move, backup, and sync operations take a separate data-plane path:

```text
Browser
  -> Next.js route/service: auth, ownership, quota, validation, preflight
  -> rclone gateway/port
  -> rclone worker in the production VM Compose stack
  -> source and destination provider remotes
```

Next.js owns control-plane decisions. The rclone worker owns byte-heavy transfer execution. Stopped Fly.io apps are recovery resources, not the authoritative production data plane.

## The two provider contracts

Two related contracts coexist while the architecture is being migrated. They serve different scopes and should not be described as one interchangeable interface.

### Runtime service contract: `CloudStorageService`

`src/types/cloud-storage.ts` defines the broad contract used by the service registry. It includes:

- authentication, refresh, and disconnect operations;
- list, upload, download, delete, and rename operations;
- folder creation, deletion, rename, and hierarchy;
- search and metadata;
- health/configuration access; and
- optional organizational-container operations.

`src/services/base/BaseCloudStorageService.ts` supplies shared service behavior. The live implementations for the three product providers are:

- `src/services/GoogleDriveService.ts`;
- `src/services/OneDriveService.ts`; and
- `src/services/DropboxService.ts`.

These classes also implement the narrower `CloudStoragePort`, allowing application use cases to depend on a smaller contract where appropriate.

### Narrow application port: `CloudStoragePort`

`src/domain/ports/CloudStoragePort.ts` exposes only the operations currently required by the newer application layer:

```typescript
interface CloudStoragePort {
  listFiles(
    accountId,
    options?,
  ): Promise<CloudStorageResponse<ListFilesResponse>>;
  getFile(
    fileId,
    accountId,
    userId?,
  ): Promise<CloudStorageResponse<DriveItemUnion>>;
  search(accountId, options): Promise<CloudStorageResponse<SearchResponse>>;
  getCapabilities(): ProviderCapabilities;
  getRcloneBehavior(): RcloneBehavior;
}
```

Transfer and sync use cases such as `CopyFilesUseCase`, `TransferFilesUseCase`, and `SyncFoldersUseCase` under `src/application/` accept this port instead of a concrete provider class.

The port keeps orchestration provider-neutral. It does not mean every full-service method has moved to the domain port.

## Runtime registration and capability lookup

`src/config/services/registration.ts` is the server-side implementation-loader map. `src/services/ServiceRegistry.ts` uses it to lazily construct configured services.

```text
ServiceType
  -> ServiceConfigRegistry checks configuration
  -> registration.ts resolves a loader
  -> CloudStorageServiceFactory creates the service
  -> ServiceRegistry caches the instance
```

Capability lookup is config-driven. The authoritative declarations live in:

- `src/config/services/google.config.ts`;
- `src/config/services/onedrive.config.ts`; and
- `src/config/services/dropbox.config.ts`.

A capability flag describes what the product currently exposes, not everything the upstream provider API could theoretically do. For example, organizational-container code may exist while Shared Drives, SharePoint sites, or Team Folders remain hidden by product policy.

## Canonical models and account boundaries

Provider responses are returned in a standard `CloudStorageResponse<T>` envelope. File and folder records are normalized to the discriminated `DriveItemUnion` from `src/types/items.ts`:

```typescript
type DriveItemUnion = FileItem | FolderItem;
```

Both variants share canonical fields such as `id`, `name`, `type`, `modifiedAt`, `parentId`, `service`, `accountId`, and an optional display `path`. Display paths use `/` separators, begin with `/`, and include the item name.

`PathRef` in `src/domain/models/PathRef.ts` carries the complete provider/account/resource reference for domain work:

```typescript
interface PathRef {
  provider: ProviderId;
  accountId: AccountId;
  resourceId: string;
  path?: string;
  isRoot: boolean;
  containerId?: string;
  containerType?: string;
}
```

The UI may carry an opaque canonical `id`, but it must not parse it, construct provider-native paths from it, or assume that two accounts share an identifier namespace. Services and adapters interpret provider-specific values.

## How the three providers differ

| Provider     | Live service boundary                | Native concerns kept behind it                                                                                                                   | Canonical outcome                                                               |
| ------------ | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
| Google Drive | `src/services/GoogleDriveService.ts` | Drive API v3 IDs, `root`, Google Workspace MIME types, parent-based path resolution, and removal of the `My Drive` display prefix                | `DriveItemUnion` with canonical paths and account context                       |
| OneDrive     | `src/services/OneDriveService.ts`    | Microsoft Graph clients/endpoints, personal versus business drives, composite `driveId!itemId` values, paging, and SharePoint/container metadata | The same item/response models; composite IDs remain opaque outside the boundary |
| Dropbox      | `src/services/DropboxService.ts`     | Path-based endpoints, `id:` references, business namespace headers, home-path stripping, cursors, and temporary download-link behavior           | The same item/response models with namespace details removed from display paths |

Provider-specific errors are translated to user-safe errors or standard response envelopes. Native responses must not be returned directly from routes to components.

## Newer provider facades and specialized adapters

`src/infra/providers/` contains `GoogleDriveProvider`, `OneDriveProvider`, and `DropboxProvider` facades. Each implements `CloudStoragePort` by composing file, folder, search, and transformer modules.

```text
GoogleDriveProvider
  -> GoogleDriveFileAdapter
  -> GoogleDriveFolderAdapter
  -> GoogleDriveSearchAdapter
  -> GoogleDriveFileTransformer / GoogleDriveErrorTransformer
```

OneDrive and Dropbox follow the same shape. These modules demonstrate the target deep-module boundary and have focused tests, but `src/config/services/registration.ts` currently loads the `src/services/*Service` classes instead. Do not claim the facades are the default production registry path, and do not introduce a third provider abstraction for search or indexing.

`src/services/adapters/` is another opt-in wrapper toolkit for request normalization, response transformation, authentication, retry policy, and in-process metrics. It is documented separately in [Service Adapter Pattern](./SERVICE_ADAPTER_PATTERN.md). It does not replace the runtime service registry automatically.

## Example: listing a folder

1. A component asks a hook or context for items using a provider and account selection.
2. The browser calls a Next.js API route; the route authenticates the user and validates the account/resource input.
3. Server orchestration resolves the configured service through `ServiceRegistry` or an injected provider port.
4. The provider service calls its native API and handles paging, roots, namespaces, and provider errors.
5. The service returns `CloudStorageResponse<ListFilesResponse>` containing `DriveItemUnion[]`.
6. The hook stores provider-neutral state; the component renders it without provider-specific branching.

For multi-account correctness, the request must retain all three identity parts. `resourceId` by itself is not a safe lookup key.

## Example: cross-provider transfer

1. The request names source and destination providers, accounts, and resources explicitly.
2. Next.js checks authentication, ownership, transfer quota, container restrictions, and conflicts before execution.
3. Provider behavior supplies only the information needed to build safe rclone inputs.
4. The rclone boundary starts the operation in the worker; provider bytes do not transit through a React component.
5. Operation state is persisted and returned in provider-neutral job/progress models.

Source and destination direction must never be inferred. Destructive or delete-enabled rclone work follows the repository's dry-run and approval rules.

## Adding or changing a provider

1. Extend `ServiceType` in `src/types/services.ts` only when introducing a real provider.
2. Add one centralized `src/config/services/<provider>.config.ts` declaration for capabilities, OAuth metadata, rate limits, and rclone behavior.
3. Implement the full runtime `CloudStorageService` contract, normally through `BaseCloudStorageService`.
4. Add the server-only loader in `src/config/services/registration.ts`.
5. Implement or adapt `CloudStoragePort` only for application slices that use the narrower port.
6. Normalize native items, errors, paths, paging, and delete outcomes before they cross the provider boundary.
7. Prove `(provider, account, resource)` isolation and cover auth failure, refresh failure, throttling, timeout, quota, unsupported capabilities, and provider-specific edge cases.
8. Update canonical documentation and capability statements when the supported product behavior changes.

Do not add a provider by putting SDK calls in UI components or API routes. Do not enable a capability merely because an upstream API advertises it.

## Verification map

Use the smallest relevant deterministic loop, then broaden according to risk:

```powershell
pnpm test src/config/services/__tests__/service-config.test.ts
pnpm test src/services/__tests__/GoogleDriveService.test.ts src/services/__tests__/OneDriveService.test.ts src/services/__tests__/DropboxService.test.ts
pnpm test src/infra/providers
pnpm check
```

Documentation-only changes require source/path inventory and documentation checks rather than authenticated provider calls. Provider behavior changes additionally require focused service/adapter tests and route tests with upstream APIs mocked behind the provider boundary.

## Related documentation

- [Architecture](../../ARCHITECTURE.md)
- [Service Adapter Pattern](./SERVICE_ADAPTER_PATTERN.md)
- [Service Configuration](./SERVICE_CONFIGURATION.md)
- [Type Definitions](./TYPE_DEFINITIONS.md)
- [Unified Rclone Module](./RCLONE_MODULE.md)
- [Testing](../../TESTING.md)
