v0.4.0
Protocol
- Added the Creation With Upload extension: clients may now send the first
part of an upload in the creation
POSTby settingContent-Type: application/offset+octet-stream, saving a round trip. The response carriesUpload-OffsetalongsideLocation, and a body that arrives complete finalises the upload immediately, firingon_complete_uploadfrom the creation request. Advertised ascreation-with-uploadinTus-Extension. - Fixed
Tus-Extensionadvertisement: the Expiration extension has been implemented since v0.2.0 but was never advertised, so clients had no way to discover it. It is now listed whenever an:expiration_periodis configured. - Fixed missing
Tus-Resumableon error responses: thePATCH404/409/413/400 branches and thePOST413 branch omitted the header, which strict clients may read as a response from a non-tus server. The 412 version-rejection responses remain without it, matching the reference implementation.
Behaviour Changes
- A request without
Tus-Resumablenow returns412 Precondition Failedwith aTus-Versionheader, instead of400 Bad Requestwith no header, as the protocol requires. Callers matching on 400 for a missing version header need to expect 412.
Dependencies
- Upgraded Plug:
1.5.0->1.20.3. The declared requirement moved from~> 1.3to>= 1.15.4 and < 2.0.0, which excludes the releases affected by CVE-2026-54892, CVE-2026-56813 and CVE-2026-56814 while staying permissive for downstream applications. Pulls inmime2.0.7 and addsplug_cryptoandtelemetryas transitive dependencies. - Removed the
uuiddependency: that package has not been published since- Upload ids are now generated by an internal Tussle.UID module, which
produces RFC 9562 version 4 UUIDs from
:crypto.strong_rand_bytes/1. The output format is unchanged (36-character lowercase hyphenated), so storage layouts andtus_storage_s3are unaffected.
- Upload ids are now generated by an internal Tussle.UID module, which
produces RFC 9562 version 4 UUIDs from
- Upgraded ex_doc (dev only):
0.40.1->0.40.3.
Improvements
- Toolchain: pinned to Erlang/OTP 29.0.5 and Elixir 1.20.3, with CI bumped to match.
- Docs: added
docs/UPSTREAM_COMPAT.mdrecording Tussle's standing against upstream tus clients and the IETF resumable upload draft, with an effort estimate for each remaining gap. - Docs: documented the CORS configuration browser clients need. An
incomplete
Access-Control-Expose-Headerslist is a quiet failure mode: browsers hideLocationandUpload-Offsetfrom JavaScript unless they are explicitly exposed, so uploads start but can never resume. - Replaced the deprecated
use Plug.Testin the test suite with direct imports.
v0.3.1
Bug Fixes
- Fixed upload-offset header: Previously returned the old
file.offsetinstead ofnew_offsetafter appending data, causing clients to retry uploading the same chunk repeatedly.
Improvements
Configurable read_body options: Added application config options for tuning request body reading:
read_body_length- max body size (default: 100MB)read_body_read_length- chunk size for throughput (default: 256KB)read_body_timeout- timeout per read (default: 30s)
Error logging: Added
Logger.errorcalls for failed operations to aid debugging.
v0.3.0
CloudFlare HEAD-to-GET Compatibility
Added support for CDNs (notably CloudFlare) that convert HEAD requests to GET requests.
- Added
Tussle.get/2function that mirrorsTussle.head/2behavior - Added
Tussle.Routesmodule withadd_tus_routes/1macro for easy route setup - Updated controller to include
get/2action - Fixed
read_body/2to properly accumulate chunked request bodies in PATCH handler - Added
CDN-Cache-Control: no-storeheader to HEAD/GET responses
Why this matters: CloudFlare's caching layer converts HEAD requests to GET,
which unexpectedly violates the expectations of the TUS protocol. The new GET
route and add_tus_routes macro restore compatibility.
Usage
# In your router:
import Tussle.Routes
scope "/files", MyAppWeb do
pipe_through :api
add_tus_routes UploadController
endv0.2.0
Fork of the original tus package, renamed to Tussle.
Changes from original tus package
- Renamed package from
tustotussleto allow publishing updates to Hex - Updated to Elixir 1.18+ / OTP 28
- Replaced deprecated
use Mix.Configwithimport Config - Replaced deprecated
Supervisor.Spec.worker/3with modern child spec syntax - Added
Tussle.Storagebehaviour module (from ringods) - Added
@type t()typespec toTussle.File(from ringods) - Fixed metadata parsing to return map instead of list (from ringods)
- Added
@behaviour Tussle.StoragetoTussle.Storage.Local
Merged community contributions
- Storage behaviour and File typespecs (ringods via bucha)
- Metadata as map fix (ringods via bucha)
- Expiration protocol (davec82)
- on_complete_upload result checking (davec82)
- Empty metadata handling (davec82)
- Location prefix support (zkessin)
- Storage provider offset control (Clause-Logic)
- init_file callback (marcinkoziej)
- Missing config handling (marcinkoziej)
- source_url fix (feng19)
- mix.exs formatting fix (joeljuca)