Skip to content

Document the streaming attachment transport (JS SDK) - #563

Open
khawarizmus wants to merge 11 commits into
mainfrom
attachment-transport
Open

Document the streaming attachment transport (JS SDK)#563
khawarizmus wants to merge 11 commits into
mainfrom
attachment-transport

Conversation

@khawarizmus

@khawarizmus khawarizmus commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Documents the streaming attachment transport shipped in powersync-js powersync-ja/powersync-js#1039 (@powersync/common@2.1.0), and fixes a few pre-existing issues in the JS examples found along the way.

New content (all JavaScript/TypeScript only; other SDK tabs untouched):

  • "Attachment Transport" section under Core Components: the default buffered path, the AttachmentTransportAdapter interface, and the either-remoteStorage-or-transportAdapter configuration rule
  • "Transferring Large Files Without Buffering" under Advanced Topics: the three streaming transports (Expo, React Native FS, Node.js) created via createTransportAdapter, with a full Expo example
  • "Custom Transport Adapters": why and how to build your own (resumable transfers, encryption), with a skeleton
  • saveFileFromUri example for registering on-disk files without buffering, plus StreamingLocalStorageAdapter coverage in the Local Storage Adapter section
  • Version requirements: web v3.0.0, React Native v2.0.3, Node v0.21.0, attachments-storage-react-native v0.1.0

Fixes:

  • JS examples compared state === 'SYNCED', but AttachmentState is a numeric enum, so the checks never matched
  • The ProfilePhoto web example passed local_uri (an indexeddb:// reference) straight to <img src>, which fails; it now converts through the storage adapter to an object URL, with a note explaining the platform difference

The content in this PR was AI-assisted using Claude Code.

…t SDK

- Clarified the format of `localUri` for local storage references.
- Added details about the `Attachment Transport` and its role in managing remote operations.
- Introduced the concept of a streaming transport for large files, including configuration examples.
- Updated notes on the React Native local storage adapter requirements.
- Provided additional context on the `Attachment Queue` and its lifecycle management.

This update aims to improve clarity and usability for developers working with attachments.
@mintlify

mintlify Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
powersync 🟢 Ready View Preview Aug 13, 2026, 4:58 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Comment thread client-sdks/advanced/attachments.mdx Outdated
Comment thread client-sdks/advanced/attachments.mdx Outdated
Comment thread client-sdks/advanced/attachments.mdx Outdated
Comment thread client-sdks/advanced/attachments.mdx Outdated
Comment thread client-sdks/advanced/attachments.mdx Outdated
Comment thread client-sdks/advanced/attachments.mdx Outdated
Comment thread client-sdks/advanced/attachments.mdx
Comment thread client-sdks/advanced/attachments.mdx Outdated
Comment thread client-sdks/advanced/attachments.mdx Outdated
Comment thread client-sdks/advanced/attachments.mdx Outdated
Comment thread client-sdks/advanced/attachments.mdx Outdated
- Updated the description of the remote storage adapter to emphasize the use of streaming transports for large file transfers.
- Removed outdated sections on the Attachment Transport, consolidating information for clarity.
- Enhanced the explanation of the Attachment Queue and its role in managing attachment lifecycles.
- Clarified the handling of `localUri` in the web SDK and its implications for image loading.

These changes aim to improve the documentation's clarity and usability for developers working with attachments.
Comment thread client-sdks/advanced/attachments.mdx Outdated
- `downloadFile(attachment)` - Download file from cloud storage
- `deleteFile(attachment)` - Delete file from cloud storage

In the JavaScript/TypeScript SDK, apps that transfer large files can replace this adapter with a streaming transport; see [Transferring Large Files Without Buffering](#transferring-large-files-without-buffering).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we calling it TypeScript SDK anywhere else?

Suggested change
In the JavaScript/TypeScript SDK, apps that transfer large files can replace this adapter with a streaming transport; see [Transferring Large Files Without Buffering](#transferring-large-files-without-buffering).
In the JavaScript SDK, apps that transfer large files can replace this adapter with a streaming transport; see [Transferring Large Files Without Buffering](#transferring-large-files-without-buffering).

This also still makes it sound like all other SDKs necessarily buffer the file, when that is not true for Kotlin and Dart.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have fixed this by changing every reference on the "JavaScript/TypeScript SDK" to "JavaScript SDKs" as it is consistent with the rest of the docs.

I have also made this specific part more explicit about it being JS only.

Comment thread client-sdks/advanced/attachments.mdx Outdated
- **Performs cleanup** - Removes archived files that are no longer needed
- **Verifies integrity** - Checks local files exist and repairs inconsistencies

In the JavaScript/TypeScript SDK, remote transfers buffer each file through JS memory by default. Apps that handle large files can swap in a streaming transport adapter; see [Transferring Large Files Without Buffering](#transferring-large-files-without-buffering).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also only mention this once (it's also mentioned under attachment table, I don't think it belongs there).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have kept both references but reworded them to their actual context so they don't appear like repetition.

Comment thread client-sdks/advanced/attachments.mdx Outdated
- **Resumable transfers** - The queue retries a failed operation by calling the transport again on the next sync interval. A transport built on a resumable protocol such as [tus](https://tus.io) or S3 multipart upload can continue from the last confirmed offset instead of restarting from zero. Downloads can resume a partial file with HTTP `Range` requests
- **Encryption** - Encrypt files before upload and decrypt them after download for end-to-end encrypted attachments, without holding the whole file in memory

```typescript

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this empty snippet, linking to the relevant interface in our tsdoc reference might be better.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was addressed as per the comment.

Comment thread client-sdks/advanced/attachments.mdx Outdated
Comment on lines +2502 to +2503

Throwing from any method marks the operation as failed; the queue retries it on the next sync interval, subject to your [error handler](#error-handling).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this is obvious enough that it doesn't need to be mentioned.

Suggested change
Throwing from any method marks the operation as failed; the queue retries it on the next sync interval, subject to your [error handler](#error-handling).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have reworded this around retries and improved the wording. Let me know if you still think it should be removed

Comment thread client-sdks/advanced/attachments.mdx Outdated
// saveFileFromUri queues the upload without reading the file into memory.
// Requires a streaming-capable local storage adapter (StreamingLocalStorageAdapter:
// Node.js, Expo, or React Native FS; not available on web).
async function attachRecording(localUri: string, recordingId: string) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should add a second snippet for this (to have one with a blob and one streaming example) instead of combining it into one?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understood you well, but I have created a new section just below, dedicated to streaming and scoped to JS. This, however, takes it out of the <CodeGroup> and into the main content.

@benitav

benitav commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

@khawarizmus in 7503b48 I added some general polish based on our docs writing standards - hopefully nothing too contentious, mainly:

  • Removed the "Pattern 2 / Pattern 3" labels from headings - there was no Pattern 1, so the numbering was confusing.
  • Moved the UNION query guidance out of the early concepts section to the implementation examples further down - the same content appeared twice on the page, and it seemed quite detailed for an early overview section.
  • Replaced the "Coming soon" placeholder code tabs with a Note - those read as internal notes “need to…”.
  • Reworded "offline-first" phrasing across the page so it’s more explanatory and not just jargon.

I think all of these were existing things, nothing new from this PR, just noticed it now.

khawarizmus and others added 4 commits August 24, 2026 17:47
…t SDK

- Clarified the format of `localUri` for local storage references.
- Added details about the `Attachment Transport` and its role in managing remote operations.
- Introduced the concept of a streaming transport for large files, including configuration examples.
- Updated notes on the React Native local storage adapter requirements.
- Provided additional context on the `Attachment Queue` and its lifecycle management.

This update aims to improve clarity and usability for developers working with attachments.
- Updated the description of the remote storage adapter to emphasize the use of streaming transports for large file transfers.
- Removed outdated sections on the Attachment Transport, consolidating information for clarity.
- Enhanced the explanation of the Attachment Queue and its role in managing attachment lifecycles.
- Clarified the handling of `localUri` in the web SDK and its implications for image loading.

These changes aim to improve the documentation's clarity and usability for developers working with attachments.
These changes aim to improve clarity and provide developers with better guidance on managing attachments.
@khawarizmus
khawarizmus force-pushed the attachment-transport branch from 2514df6 to 459d1e4 Compare August 24, 2026 09:47
@benitav

benitav commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

@khawarizmus In commit 2539369, I further updated the "flow" of this page, especially in regard to this new streaming feature for large files. Overall, the problem still was, that this feature was mentioned multiple times in early sections and it was unnecessarily noisy IMO. It's of course still up for discussion, so here's my reasoning (I asked Claude for some specifics, so please tell me if any of my current understanding is incorrect):

  • This feature has a relatively small audience, since it is an advanced use case and the buffer-free benefit is limited to React Native and Node.js apparently (a custom transport is technically possible on web, but it can't avoid buffering since IndexedDB has no file paths). Mentioning it too early seems like an unnecessary interruption for most users who just want to get started.
  • According to Claude: Adopting it later is cheap: you swap remoteStorage for a transportAdapter, and the signed-URL endpoints you already built get reused by resolveUpload/resolveDownload. There's no lock-in that would justify telling everyone upfront.
  • It only matters for apps handling large media (videos, recordings), and those developers should find "Transferring Large Files Without Buffering" in the table of contents by name.

My main changes (mostly summarized by Claude):

  • Removed the repeated forward references from the concepts sections and the commented-out transportAdapter option from the init example.
  • The two remaining references (remote storage adapter, upload section) now say "React Native and Node.js" explicitly, so the right readers self-select.
  • Moved the saveFileFromUri example into a new "Saving Files Already on Disk" subsection under the large-files section — it's part of the same streaming story, and this keeps "Upload an Attachment" as the clean cross-SDK path.
  • Moved "Custom Transport Adapters" to sit directly after the large-files section, since they reference each other.
  • Merged the web local_uri note into the JS code comment - it only applies to readers on that tab. I know you preferred the Note for visibility, but since it's only actionable for web readers, the JS tab seems like the better place.
  • The SDK availability statement is now a Note at the top of the section to stand out a bit more.
  • Removed the Expo 56 sentence from the Core Components warning — that requirement now lives only in the large-files section where the transport is introduced.

You implemented feedback from Simon as I pushed this so just some notes on how I merged those with the above updates:

  • Kept your "JavaScript SDKs" phrasing and your buffer explanation in the remote storage adapter reference and merged it with my "React Native and Node.js only” addition I mentioned above.
  • Kept your "Custom Transport Adapters" rewrite - it just lives in the new location now.
  • Your "Uploading a File Already on Disk" section became the "Saving Files Already on Disk" subsection mentioned above - so also just a different location.

A remaining question I have is where we say "JavaScript SDKs" shouldn't we technically say just React Native and Node.js? I don't fully grok what this new feature adds for web users.

@khawarizmus

Copy link
Copy Markdown
Contributor Author

@benitav the reason I initially added the section that has now been removed and why I still think the two short references at the beginning of the document are worth keeping is mainly about helping the reader build a mental model as they go.

My thinking was:

  • Creating relationships between concepts. Having sections reference each other helps developers understand how the different concepts fit together, rather than treating each section as completely isolated.
  • Introducing and exhausting concepts before they are needed. The deleted section was mostly intended to expose some of the concepts early, so that readers have the context needed to understand the sections that follow.
  • Signposting without going into detail. A short reference earlier in the document can tell the reader, "there's more to this," without interrupting the current flow or requiring them to dive into the advanced concept immediately.

From my personal experience reading docs, my brain naturally starts asking questions as I go. Knowing that another concept exists, or that the thing I'm reading is related to something else, helps me keep track of those questions even if I don't follow the reference immediately. It essentially creates a shelf and a pointer that I can come back to when I need it.

I think this is useful because it lets the reader understand what they don't know yet without forcing them to learn it upfront. They can stop at the level they need, follow a reference when it becomes relevant, or jump directly to an advanced use case if that's what they're looking for.

@benitav

benitav commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

@khawarizmus I agree with that sentiment when it comes to common/core concepts that most/all readers should know/care about. But my reasoning is that with this feature those concepts is not useful to what 80/90%? (please correct me if you think I'm wrong!) of readers, hence unnecessary mental overhead and a distraction for most readers in this case. Please let me know if you think this feature has a much wider audience and then more practically, why you think it's useful to introduce those readers to the different transport setup for these large files early on - since the use case itself also feels advanced and like I tried to explain, it seems safe to just plug that in, and learn about it later, when users run into this use case? I migth just be misunderstanding the feature, or be missing something so I'm also happy to jump on a quick call to discuss if easier.

- Added a tip for React Native users to utilize streaming transport instead of a remote storage adapter to prevent memory issues on low-end devices.
- Clarified the implications of buffering large files and emphasized the need for a transport adapter for efficient file transfers.

These updates aim to improve guidance for developers managing attachments in the SDK.
### Transferring Large Files Without Buffering

<Note>
This section applies to the JavaScript SDKs only. In the Dart and Kotlin SDKs, the remote storage interface is already stream-based (`Stream`/`Flow`), so transfers can avoid buffering. The Swift SDK currently receives files as `Data` and has no streaming equivalent yet.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This section applies to the JavaScript SDKs only. In the Dart and Kotlin SDKs, the remote storage interface is already stream-based (`Stream`/`Flow`), so transfers can avoid buffering. The Swift SDK currently receives files as `Data` and has no streaming equivalent yet.
This section applies to React Native, Expo and Node.js platforms only. In the Dart and Kotlin SDKs, the remote storage interface is already stream-based (`Stream`/`Flow`), so transfers can avoid buffering. The Swift SDK currently receives files as `Data` and has no streaming equivalent yet.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we say something about .Net and Kotlin here as well since they are also referenced on this page.

- `ReactNativeFileSystemStorageAdapter` (`@powersync/attachments-storage-react-native`) - The transport streams with `uploadFiles`/`downloadFile` from `@dr.pogodin/react-native-fs`, uploading as a raw binary `PUT` by default
- `NodeFileSystemAdapter` (`@powersync/node`) - The transport streams with `fetch` and Node.js filesystem streams

The web IndexedDB adapter is not streaming-capable and cannot create a transport.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The web IndexedDB adapter is not streaming-capable and cannot create a transport.

All three take the same options. `resolveUpload` and `resolveDownload` map an attachment to the HTTP request that transfers its bytes, typically a signed URL from your backend. `deleteFile` performs the remote delete, which is a plain remote call rather than a byte transfer.

<Note>
The transport API requires `@powersync/web` v2.2.0, `@powersync/react-native` v2.0.3, or `@powersync/node` v0.21.0 or later. React Native also requires `@powersync/attachments-storage-react-native` v0.1.0 or later.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what cases would @powersync/web be relevant, since I thought this is not usable/available on web.


### Custom Transport Adapters

In the JavaScript SDKs, you can also write your own [transport adapter](#transferring-large-files-without-buffering). A custom remote storage adapter always receives the file as one full in-memory buffer. A custom transport receives the file's path instead. This makes the following possible:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this also just apply to RN and Node.js or web as well?

```

#### Saving Files Already on Disk

@benitav benitav Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<Note>
This section applies to React Native, Expo and Node.js platforms only.
</Note>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants