Integrationsfiles
Files SDK files
Use any Files SDK provider, such as Supabase, Dropbox, OneDrive, Box, SFTP or WebDAV, as a Mayura file store with @mayurajs/filestorage-files-sdk.
- Package
- @mayurajs/filestorage-files-sdk
- Install
- npm install mayura @mayurajs/filestorage-files-sdk
- Vendor
- Files SDK ↗
Files SDK is an open-source TypeScript library with one API over many storage providers:
S3-compatible services, Google Cloud Storage, Azure, Supabase, Netlify Blobs, Dropbox, OneDrive, Box, SFTP, WebDAV and
more. @mayurajs/filestorage-files-sdk turns a Files SDK client into a mayura/files backend, so
any of those providers gets the same createFileStore API, limits, per-tenant views and file tools as the built-in S3
backend.
The package has no dependencies: it calls the client you give it, typed by shape. files-sdk and the packages its
adapters need are yours to install and configure; the tests use files-sdk 2.6.2. It runs wherever your Files SDK
adapter runs.
Install
npm install mayura files-sdk @mayurajs/filestorage-files-sdkInstall what your adapter needs as well, for example @supabase/storage-js for Supabase.
Credentials
Each provider has its own credentials, and you give them to its Files SDK adapter. For Supabase, that is your project URL and a key from the project's API settings in the Supabase dashboard: the service role key writes to buckets protected by row-level security.
This package reads nothing from the environment. Some Files SDK adapters fall back to environment variables when an
option is missing (the Supabase adapter reads SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY, among others), so pass
every option explicitly when you want the configuration in one place.
Use it
import { Files } from 'files-sdk';
import { supabase } from 'files-sdk/supabase';
import { createFileStore } from 'mayura/files';
import { filesSdkFiles } from '@mayurajs/filestorage-files-sdk';
const client = new Files({
adapter: supabase({ bucket: 'uploads', url: process.env.SUPABASE_URL ?? '', key: process.env.SUPABASE_SERVICE_ROLE_KEY ?? '' }),
});
const files = createFileStore(filesSdkFiles({ files: client }), { maxFileBytes: 10 * 1024 * 1024 });
// What this provider keeps decides which options the store accepts.
console.log(files.conditionalWrites, files.conditionalDelete);
await files.put('invoices/inv-1001.pdf', new Uint8Array([0x25, 0x50, 0x44, 0x46]), { contentType: 'application/pdf' });See Files for every store method, the store's own options and the file tools.
Options
| Option | Default | Meaning |
|---|---|---|
files |
required | A Files SDK client, new Files({ adapter }), over any of its providers. |
What the store keeps
The backend reads the adapter's declared capabilities and keeps what the provider can keep. What it cannot keep is
refused rather than ignored:
| Capability | When the provider has it | When it does not |
|---|---|---|
| Conditional writes | conditional.create, conditional.replace and conditional.exactRead all set: ifNoneMatch: '*' and ifMatch become the adapter's native create and replace conditions, and a conditional read its exact read. conditionalWrites is true. |
ifMatch and ifNoneMatch are refused with INVALID_INPUT. |
| Conditional delete | conditional.delete: delete(key, { ifMatch }) passes the etag to the adapter. |
A conditional delete is refused with INVALID_INPUT. |
| Metadata | metadata: metadata is written and read back. |
A write with metadata fails with INVALID_INPUT. |
| Ranged reads | rangeRead: ranges are read natively. |
The whole file is read, within the limit, and the range taken from it. |
A file's etag is whatever the provider reports. A provider that lists without etags is asked for each listed file's
information, one head per file. A read with a range or ifMatch first reads the file's information, then checks that
the bytes it downloads carry the same etag.
Files are as large as the store's maxFileBytes, up to 5 GiB, and the provider's own limits apply. Each file is held in
memory while it is written or read.
Errors
Files SDK errors are mapped to the store's, without their messages:
| Files SDK error | Becomes |
|---|---|
Conflict |
CONFLICT. A conditional replace, read or delete of a missing file is a CONFLICT too. |
NotFound |
undefined from get and head; a plain delete of a missing file succeeds. |
Unauthorized |
FileStoreError, reason: 'authentication'. |
ReadOnly |
FileStoreError, reason: 'rejected'. |
Provider |
FileStoreError, reason: 'unavailable', or 'timeout' when the provider timed out. |
| anything else | FileStoreError, reason: 'unavailable'. |
A call that runs past the store's timeoutMs fails with reason: 'timeout', and one cancelled through its signal
with CANCELLED. Giving something other than a Files SDK client fails with INVALID_CONFIG when you call
filesSdkFiles().
How it is tested
- Unit tests run the file store conformance suite over Files SDK's own memory adapter: 10 cases pass, and the three precondition cases are skipped because that adapter declares no native conditional operations.
- Other tests use a fake client to check that preconditions and metadata are kept only where the adapter declares them,
that conditions reach the adapter and its
ConflictbecomesCONFLICT, that a range is cut from the whole file within the limit, and that errors carry no provider text. - In CI, the suite runs through Files SDK's RustFS adapter against a real S3-compatible server (
MAYURA_TEST_S3_URL): every case that does not need preconditions passes, the precondition cases pass or skip as the adapter declares, and the store'sconditionalWritesandconditionalDeletematch the adapter's capabilities. - It has not been run against a hosted provider account yet.
Related
- Files: stores, views, preconditions and file tools.
- Google Cloud Storage files and Azure Blob Storage files: direct backends with preconditions.
- Vercel Blob files: Vercel Blob without extra dependencies.
- Artifacts: artifacts need a store with conditional writes.