@tus/s3-store: complete empty multipart uploads on create - #876
Conversation
When Upload-Length is 0, @tus/server treats the upload as final and calls onUploadFinish without write(). Finish the empty multipart in create so the object exists before finish hooks run.
🦋 Changeset detectedLatest commit: 3d511f7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
| if (upload.size === 0 && !upload.sizeIsDeferred) { | ||
| const metadata = await this.getMetadata(upload.id) | ||
| await this.finishMultipartUpload(metadata, []) |
There was a problem hiding this comment.
🟡 Empty creation requests now fail
With zero length and creation-with-upload, create completes the upload before the server invokes write. The valid request then fails because its upload is already closed.
Prompt for agents
Preserve valid creation-with-upload requests whose Upload-Length is zero. PostHandler always calls DataStore.write when the POST carries application/offset+octet-stream, but S3Store.create now completes the multipart upload first. The subsequent S3Store.write calls retrieveParts and receives NoSuchUpload. Coordinate the server and store behavior, or make the S3 store's zero-byte completion idempotent, so both create-only empty uploads and empty creation-with-upload requests succeed. Add coverage for a zero-length POST carrying the creation-with-upload content type.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Good catch — creation-with-upload does call write() after create even when Upload-Length is 0.
Addressed by making write idempotent: if retrieveParts returns NoSuchUpload and offset === size, drain the body and return the final offset. Added a regression test for zero-byte creation-with-upload.
creation-with-upload still calls write() after create completes an empty multipart; treat NoSuchUpload as a no-op when offset already equals size.
Problem
@tus/servertreatsUpload-Length: 0as immediately final and callsonUploadFinishwithoutwrite()(PostHandlersetsisFinalwhensize === 0 && !sizeIsDeferred).S3Store.createonly starts a multipart upload and writes the.infoobject. Consumers thatHeadObject/getObjectthe key inonUploadFinish(e.g. Supabase Storage) then see a bare 404 because the multipart was never completed.finishMultipartUploadalready handles emptypartsby uploading a zero-byte part — it just was not reached on the create-only path.Reported downstream: supabase/storage#1300
(Supabase maintainer asked to fix upstream: supabase/storage#1365 (comment))
Fix
When
upload.size === 0 && !upload.sizeIsDeferred, complete the empty multipart increate, thencompleteMetadataand clear cache — same sequence as the end ofwrite().Update the existing zero-byte integration test to match server behavior (create only; no
write()).Test plan
should successfully upload a zero byte fileto assert object exists aftercreatealone@tus/s3-store