diff --git a/.changeset/s3-store-remove-incomplete-part.md b/.changeset/s3-store-remove-incomplete-part.md new file mode 100644 index 00000000..6effe7d5 --- /dev/null +++ b/.changeset/s3-store-remove-incomplete-part.md @@ -0,0 +1,5 @@ +--- +"@tus/s3-store": patch +--- + +Delete the incomplete part object (`.part`) when removing an upload so terminating an upload that has a pending sub-minimum-size chunk no longer leaves an orphaned object in the bucket. diff --git a/packages/s3-store/src/index.ts b/packages/s3-store/src/index.ts index 6256a722..87e08ea8 100644 --- a/packages/s3-store/src/index.ts +++ b/packages/s3-store/src/index.ts @@ -743,7 +743,7 @@ export class S3Store extends DataStore { await this.client.deleteObjects({ Bucket: this.bucket, Delete: { - Objects: [{Key: id}, {Key: this.infoKey(id)}], + Objects: [{Key: id}, {Key: this.infoKey(id)}, {Key: this.partKey(id, true)}], }, }) diff --git a/packages/s3-store/src/test/index.ts b/packages/s3-store/src/test/index.ts index 5cd2fbb6..a0328c37 100644 --- a/packages/s3-store/src/test/index.ts +++ b/packages/s3-store/src/test/index.ts @@ -310,6 +310,26 @@ describe('S3DataStore', () => { await assert.rejects(store.remove(id), {status_code: 404}) }) + it('should delete the incomplete part when removing an upload', async function () { + const store = this.datastore as S3Store + const incompleteSize = 1024 + const upload = new Upload({ + id: shared.testId('remove-incomplete-part'), + size: incompleteSize * 2, + offset: 0, + }) + + await store.create(upload) + await store.write(Readable.from(Buffer.alloc(incompleteSize)), upload.id, 0) + // @ts-expect-error protected method + assert.equal(await store.getIncompletePartSize(upload.id), incompleteSize) + + await store.remove(upload.id) + + // @ts-expect-error protected method + assert.equal(await store.getIncompletePartSize(upload.id), undefined) + }) + for (const [S3Error, completed] of [ [NoSuchUpload, true], [NoSuchKey, true],