# Changelog All notable changes to this project will be documented in this file. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- ## [1.1.0] — 2026-07-02 ### Added - **Buffered upload with lazy multipart creation** — supports any TUS chunk size from any client: - Each `PATCH` accumulates bytes into a temporary R2 buffer object (`__tus/buf-{uuid}`). - The R2 multipart upload is created **lazily** on the first `PATCH` where buffer + incoming chunk reaches 5 MB. If the upload completes before that threshold, the file is written with a single `bucket.put()` and the multipart API is never used. - Once multipart is active, complete 5 MB blocks are flushed and the remainder goes back to the buffer. Every non-trailing part is exactly 5 MB. - **`state.bufferSize`** field in upload state JSON — tracks bytes currently in the buffer object. ### Fixed - **R2 error 10048** (`All non-trailing parts must have the same length`): previously, uploading with unequal-sized TUS chunks caused `completeMultipartUpload` to fail. The buffer-flush design guarantees every non-trailing R2 part is exactly 5 MB regardless of TUS chunk size. - **R2 error 10011** (`Your proposed upload is smaller than the minimum allowed object size`): uploads that complete under 5 MB total use `bucket.put()` instead of the multipart API, so neither size constraint applies. - **Deferred-length uploads:** an upload created with `Upload-Defer-Length: 1` that completes under 5 MB is handled correctly — no multipart upload is ever started, so no abort is needed. ### Changed - **Upload state schema** — the `state.mode` field (`'put' | 'multipart'`) has been removed. The R2 multipart upload ID is now in `state.uploadId` (`string | null`), which is `null` until multipart is first needed and set on the fly. If you have custom cron cleanup code that checks `state.mode`, update it to check `state.uploadId` instead: ```js // before (1.0.0) if (state.mode === 'multipart') { ... } // after (1.1.0) if (state.uploadId) { ... } ``` --- ## [1.0.0] — 2026-06-01 Initial release. ### Features - TUS 1.0.0 protocol support on Cloudflare Workers + R2 - Zero dependencies — no KV, no Durable Objects, just an R2 bucket - **TUS extensions:** `creation`, `creation-with-upload`, `creation-defer-length`, `termination`, `expiration` - **CORS** — all responses include CORS headers; configurable per-origin allowlist via `corsAllowOrigin` / `CORS_ALLOW_ORIGIN` - **Webhook** — POST notification on upload completion with key and decoded metadata; configurable via `webhookUrl` / `WEBHOOK_URL` and `webhookBearerToken` / `WEBHOOK_BEARER_TOKEN` - **`onComplete` callback** — `async (key, metadata, bucket) => void` for custom post-upload logic - **`basePath`** — mount TUS at a sub-path within an existing Worker - **`maxSize`** — enforce a maximum upload size; advertised in TUS `OPTIONS` response - **`uploadTTL`** — configurable incomplete-upload expiry (default 24 h); refreshed on every `PATCH` so slow-but-steady uploads do not expire mid-flight - **TUS metadata → R2 metadata mapping:** `type` → `httpMetadata.contentType`; `filename` → `httpMetadata.contentDisposition`; other keys → `customMetadata` - `createTusHandler()` with no arguments reads `env.BUCKET`, `env.WEBHOOK_URL`, `env.WEBHOOK_BEARER_TOKEN`, and `env.CORS_ALLOW_ORIGIN` automatically [1.1.0]: https://github.com/aiodintsov/tus-server-r2/compare/v1.0.0...v1.1.0 [1.0.0]: https://github.com/aiodintsov/tus-server-r2/tree/b61bc8f423c0f6ae85c1641a1b5cc07117414af7