> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackra.figentra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 0413 — Streamed file writes: multipart on S3 and R2, buffered elsewhere, never a partial object

> Proposed · 2026-10-26 · amended 2026-10-27 (STA-1259)

> [!NOTE]
>
> Proposed (STA-828). It extends
> [0222](https://docs.stackra.figentra.com/adr/0222-files-and-media-storage-drivers-uploads-scanning-and-public-assets.md) decision 1
> and [0234](https://docs.stackra.figentra.com/adr/0234-module-assets-and-storage.md) decision 7. OS audit gate A, gap 10.

## Context

`FileSystem#write` takes one buffer, so an export for a large tenant is built whole in memory before it is stored.

## Decision

1. **`FileSystem#writeStream(key, source, options)`** takes a web `ReadableStream<Uint8Array>` or an
   `AsyncIterable<Uint8Array | string>` (strings are UTF-8).
2. **The port's own default buffers**: it reads the stream and calls `write`. The memory, local, GCS, Encore and Electron
   disks use it.
3. **The `s3` disk (S3, R2, MinIO) uploads in parts** of a fixed 8 MiB (above S3's 5 MiB floor, equal as R2 requires),
   holding one part in memory. A stream shorter than one part is one PUT.
4. **Never a partial object.** A stream that fails stores nothing: the buffered default never calls `write`, and the
   multipart upload is aborted (the abort's own failure never hides the stream's error).

## Consequences

* A large export streams on the managed buckets; a local or memory disk still holds it whole (development only).
* The part size is a constant, not configuration: it is a protocol limit, not a business answer.

## Enforcement

planned — `packages/data/filesystem/src/contracts/ports/__tests__/file-system.port.test.ts` and
`packages/data/filesystem/src/node/drivers/__tests__/s3-file-system.driver.test.ts`.

## Note 2026-10-27: streamed reads (STA-1259)

`FileSystem#readStream(key)` is the read side of decision 1. It resolves to a web `ReadableStream<Uint8Array>` once the
file is found, so a missing file rejects the call with `FileNotFoundError`, never the stream. Every disk has it: the
`local` and `electron` disks stream from an open file handle, `s3` streams the GET body and `gcs` the download; the
`memory` and `encore_bucket` disks use the port default, which reads the file whole and serves it as one chunk
(development or small files, as decision 2 does for writes).

Enforcement of this note: planned — `file-system.port.test.ts` and the driver tests under
`packages/data/filesystem/src/{node,desktop}/drivers/__tests__/` (written, run on ci-box).

## Supersedes and sources

Supersedes: none. Sources: services/audit SPEC (gap 10); STA-828.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.