@powersync/attachments-storage-react-native
v0.1.1
Published
React Native file system storage adapters for PowerSync attachments
Readme
@powersync/attachments-storage-react-native
[!NOTE] Attachment helpers are currently in an alpha state, intended strictly for testing. Expect breaking changes and instability as development continues.
Do not rely on this package for production use.
React Native storage and transport adapters for PowerSync attachments.
This package provides:
- Local storage adapters (
LocalStorageAdapter) — persist attachment files on device. - Streaming transport adapters (
AttachmentTransportAdapter) — move attachment bytes directly between the local file and remote storage using native APIs, without buffering the whole file in JS memory. Recommended for large files (recordings, videos) on lower-end devices.
Installation
npm install @powersync/attachments-storage-react-native
# or
pnpm add @powersync/attachments-storage-react-native
# or
yarn add @powersync/attachments-storage-react-nativeYou'll also need to install one of the supported file system libraries. The same library powers both the local storage adapter and the native transport adapter for that platform.
For Expo projects
[!IMPORTANT] Requires Expo 54+
npx expo install expo-file-systemFor bare React Native projects
npm install @dr.pogodin/react-native-fsLocal storage adapters
The local storage adapter handles file persistence on the device. Pass it to the queue as localStorage.
With Expo File System
import { ExpoFileSystemStorageAdapter } from '@powersync/attachments-storage-react-native';
import { AttachmentQueue } from '@powersync/react-native';
const localStorage = new ExpoFileSystemStorageAdapter();
const attachmentQueue = new AttachmentQueue({
db,
localStorage,
remoteStorage, // your RemoteStorageAdapter (buffered upload/download/delete)
watchAttachments
});With React Native FS
import { ReactNativeFileSystemStorageAdapter } from '@powersync/attachments-storage-react-native';
import { AttachmentQueue } from '@powersync/react-native';
const localStorage = new ReactNativeFileSystemStorageAdapter();
const attachmentQueue = new AttachmentQueue({
db,
localStorage,
remoteStorage,
watchAttachments
});Custom storage directory
Both local adapters accept an optional storageDirectory parameter:
const localStorage = new ExpoFileSystemStorageAdapter('/custom/path/to/attachments/');Streaming transport adapters
A transport adapter owns all remote operations (upload / download / delete) and transfers bytes natively — the file never enters the JS heap. Implement remote delete via the deleteFile callback.
The queue takes exactly one remote mechanism: either remoteStorage or transportAdapter. A transportAdapter replaces remoteStorage entirely, so none is needed alongside it.
Both transports are backend-agnostic: you supply resolver callbacks that map an attachment to a request (typically a presigned URL from your backend).
With Expo File System (File.upload / File.downloadFileAsync)
[!NOTE] The Expo streaming transport requires Expo SDK 56+ (
expo-file-system >=56). TheExpoFileSystemStorageAdapteritself still works on Expo SDK 54+.
import { ExpoFileSystemStorageAdapter } from '@powersync/attachments-storage-react-native';
import { AttachmentQueue } from '@powersync/react-native';
const localStorage = new ExpoFileSystemStorageAdapter();
const transportAdapter = localStorage.createTransportAdapter({
resolveUpload: async (attachment) => ({
url: await getSignedUploadUrl(attachment.filename), // from your backend
httpMethod: 'PUT',
mimeType: attachment.mediaType ?? 'application/octet-stream'
}),
resolveDownload: async (attachment) => ({
url: await getSignedDownloadUrl(attachment.filename)
}),
deleteFile: async (attachment) => {
await deleteFromRemoteStorage(attachment.filename); // your SDK / DELETE call
}
});
const attachmentQueue = new AttachmentQueue({
db,
localStorage,
transportAdapter, // owns upload/download/delete — no remoteStorage needed
watchAttachments
});With React Native FS (uploadFiles / downloadFile)
Identical options shape. The upload is sent as a raw binary PUT (binaryStreamOnly), suitable for presigned S3/Supabase URLs.
import { ReactNativeFileSystemStorageAdapter } from '@powersync/attachments-storage-react-native';
const localStorage = new ReactNativeFileSystemStorageAdapter();
const transportAdapter = localStorage.createTransportAdapter({
resolveUpload: async (attachment) => ({
url: await getSignedUploadUrl(attachment.filename),
httpMethod: 'PUT',
mimeType: attachment.mediaType ?? 'application/octet-stream'
}),
resolveDownload: async (attachment) => ({
url: await getSignedDownloadUrl(attachment.filename)
}),
deleteFile: async (attachment) => {
await deleteFromRemoteStorage(attachment.filename);
}
});Registering an on-disk file (buffer-free)
For files already written to disk (recordings, camera/picker output), use AttachmentQueue.saveFileFromUri to register them without reading the bytes into memory — the local adapter's moveFile relocates the file into managed storage.
await attachmentQueue.saveFileFromUri({
localUri, // path to the existing file
fileExtension: 'm4a',
mediaType: 'audio/m4a'
});API
Local storage adapters
Implement the LocalStorageAdapter interface from @powersync/common:
initialize()- Create the storage directory if it doesn't existclear()- Remove all files from the storage directorygetLocalUri(filename)- Get the full path for a filenamesaveFile(filePath, data, options?)- Save data to a filereadFile(filePath, options?)- Read a file as ArrayBuffermoveFile(sourceUri, targetUri)- Move a file into managed storage without buffering (enablessaveFileFromUri)deleteFile(filePath)- Delete a filefileExists(filePath)- Check if a file existsmakeDir(path)- Create a directoryrmDir(path)- Remove a directory
Transport adapters
Implement the AttachmentTransportAdapter interface from @powersync/common:
upload(attachment)- Transfer the local file to remote storagedownload(attachment)- Transfer the remote file intoattachment.localUridelete(attachment)- Delete the file from remote storage
Supported Versions
| Adapter | Library | Supported Versions |
| ------------------------------------- | ----------------------------- | -------------------------------- |
| ExpoFileSystemStorageAdapter | expo-file-system | >=19.0.0 (Expo 54+) |
| ReactNativeFileSystemStorageAdapter | @dr.pogodin/react-native-fs | ^2.25.0 |
Streaming transports are created via localStorage.createTransportAdapter(...). The Expo streaming transport additionally requires expo-file-system >=56 (Expo SDK 56+).
License
Apache-2.0
