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
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:
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:
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.
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:
typeDriveItemUnion=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:
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.
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. It does not replace the runtime service registry automatically.
Example: listing a folder
A component asks a hook or context for items using a provider and account selection.
The browser calls a Next.js API route; the route authenticates the user and validates the account/resource input.
Server orchestration resolves the configured service through ServiceRegistry or an injected provider port.
The provider service calls its native API and handles paging, roots, namespaces, and provider errors.
The service returns CloudStorageResponse<ListFilesResponse> containing DriveItemUnion[].
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
The request names source and destination providers, accounts, and resources explicitly.
Next.js checks authentication, ownership, transfer quota, container restrictions, and conflicts before execution.
Provider behavior supplies only the information needed to build safe rclone inputs.
The rclone boundary starts the operation in the worker; provider bytes do not transit through a React component.
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
Extend ServiceType in src/types/services.ts only when introducing a real provider.
Add one centralized src/config/services/<provider>.config.ts declaration for capabilities, OAuth metadata, rate limits, and rclone behavior.
Implement the full runtime CloudStorageService contract, normally through BaseCloudStorageService.
Add the server-only loader in src/config/services/registration.ts.
Implement or adapt CloudStoragePort only for application slices that use the narrower port.
Normalize native items, errors, paths, paging, and delete outcomes before they cross the provider boundary.
Prove (provider, account, resource) isolation and cover auth failure, refresh failure, throttling, timeout, quota, unsupported capabilities, and provider-specific edge cases.
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:
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.