basalt / drives/src / DriveProvider
Interface: DriveProvider
Defined in: drives/src/provider.ts:405
A file-storage provider adapter.
Only name, allowedHosts, authorization, list and download are required. Everything optional degrades honestly: the engine reports DriveUnsupportedError rather than silently doing nothing, and falls back to a full listing when delta is absent.
Properties
allowedHosts
> readonly allowedHosts: readonly string[]
Defined in: drives/src/provider.ts:418
Every host this adapter is allowed to open a connection to.
The allowlist is the load-bearing SSRF control. Provider responses carry URLs the framework then fetches (Graph's @microsoft.graph.downloadUrl, Google's redirect to googleusercontent.com), and those are attacker- influenced data. An entry is either an exact host (api.dropboxapi.com) or a leading-dot suffix (.googleusercontent.com) that matches subdomains only — never the bare parent.
authorization
> readonly authorization: DriveAuthorization
Defined in: drives/src/provider.ts:419
deltaIncludesExisting?
> readonly optional deltaIncludesExisting?: boolean
Defined in: drives/src/provider.ts:469
Whether the cursor startDelta returns replays the items that already exist, or only changes from that moment on.
This is the difference the neutral cursor was flattening, and it decides whether a first sync imports a tenant's drive or silently imports nothing:
- Dropbox —
true.files/list_folderis the head of the feed: its first page enumerates the folder and/continuecarries on into changes. Backfill and delta are one continuum. - Google Drive —
false.changes.getStartPageTokenis explicitly "from now"; the existing corpus never appears inchanges.list. - Microsoft Graph —
true./deltawith no token enumerates the drive first, then hands over adeltaLink.
Defaults to false, the safe direction: the engine runs one full listing pass before the first delta run, so an adapter that forgets to declare it costs extra metadata reads instead of losing a tenant's files.
name
> readonly name: string
Defined in: drives/src/provider.ts:407
Stable identifier, stored on every connection: google, microsoft, dropbox.
Methods
delta()?
> optional delta(session, cursor): Promise<DriveDelta>
Defined in: drives/src/provider.ts:449
Reads one page of changes since cursor.
Parameters
session
cursor
string
Returns
Promise<DriveDelta>
download()
> download(session, item): Promise<DriveContent>
Defined in: drives/src/provider.ts:425
Opens the item's bytes. Must not buffer them.
Parameters
session
item
Returns
Promise<DriveContent>
get()?
> optional get(session, externalId): Promise<DriveItem | null>
Defined in: drives/src/provider.ts:423
Fetches one item's metadata. null when it no longer exists.
Parameters
session
externalId
string
Returns
Promise<DriveItem | null>
list()
> list(session, options): Promise<DrivePage<DriveItem>>
Defined in: drives/src/provider.ts:421
Lists one page of a folder.
Parameters
session
options
Returns
Promise<DrivePage<DriveItem>>
retryAfterFromBody()?
> optional retryAfterFromBody(body): number | undefined
Defined in: drives/src/provider.ts:519
Reads a vendor-specific "slow down" hint out of a 429/503 body.
Retry-After is the interoperable answer and always wins, but Dropbox frequently answers 429 with no header at all and puts the number in the body instead ({"error":{".tag":"too_many_requests","retry_after":300}}). The guarded fetch destroys a rate-limited body before an adapter can see it — deliberately, so nothing unbounded is read on an error path — so the hint has to be declared here, where the engine can apply it under its own cap.
Returns milliseconds, or undefined when the body carries no hint. It must be pure: it runs on a hostile-ish path and gets at most a few kilobytes.
Parameters
body
string
Returns
number | undefined
startDelta()?
> optional startDelta(session, options): Promise<string>
Defined in: drives/src/provider.ts:447
Establishes the initial delta cursor. See deltaIncludesExisting.
Parameters
session
options
folderId?
string
Returns
Promise<string>
unwatch()?
> optional unwatch(session, watch): Promise<void>
Defined in: drives/src/provider.ts:473
Cancels a subscription.
Parameters
session
watch
Returns
Promise<void>
upload()?
> optional upload(session, input): Promise<DriveItem>
Defined in: drives/src/provider.ts:445
Writes a file back to the provider. Optional — most apps only read.
Every adapter has a hard size ceiling, and it is low. A single-request upload is all any of them implements, because the alternative on all three vendors is a multi-call resumable session with its own chunking, its own 308-based resumption and its own failure modes:
| Adapter | Ceiling | What a larger file would need |
|---|---|---|
@basaltkit/drives-microsoft | 4 MB | createUploadSession |
@basaltkit/drives-google | 5 MB | uploadType=resumable |
@basaltkit/drives-dropbox | 150 MB | files/upload_session/* |
Anything larger is refused with DriveContentTooLargeError — up front when DriveUploadInput.size is given, and mid-stream otherwise. Nothing here silently truncates, and nothing here promises large files; an app that needs them should upload to the provider itself for now.
Parameters
session
input
Returns
Promise<DriveItem>
verifyNotification()?
> optional verifyNotification(input): DriveNotificationResult
Defined in: drives/src/provider.ts:504
Verifies an inbound notification. Pure and synchronous: it gets no session and no network, so verification cannot be turned into a request amplifier by an unauthenticated caller hammering the webhook route.
"Verified" is not one guarantee
The engine treats every result the same way, and an app that reads shouldSync: true cannot tell which of these produced it. They are not equivalent, and the difference is the vendors', not this contract's:
- Dropbox — a real signature.
X-Dropbox-Signatureis an HMAC-SHA256 over the raw body under the app secret. It authenticates the message itself, which is what makes it safe for the engine to act on an DriveNotificationResult.accountIds lookup that necessarily spans tenants. - Google and Microsoft — a secret we chose, echoed back (
X-Goog-Channel-Token, Graph'sclientState). Neither vendor signs anything. This authenticates the subscription, not the bytes: anyone holding the secret can send any body, and a body is not covered at all. That is why the engine matches such a result only against a connection that holds the subscription, and never across tenants.
What makes the weaker one acceptable is not the secret, it is the blast radius: no vendor sends the changed data. A verified notification only ever causes the engine to go and ask the provider, with its own credentials, for its own tenant. A perfect forgery costs a wasted sync. Anything that changed that — a notification whose content was trusted — would need the guarantees to be equalised first.
Parameters
input
Returns
watch()?
> optional watch(session, input): Promise<DriveWatch>
Defined in: drives/src/provider.ts:471
Subscribes to push notifications.
Parameters
session
input
Returns
Promise<DriveWatch>