Skip to content

Commit 9e71451

Browse files
committed
Merge branch 'main' into node-version
* main: [ci] release (#721) @tus/s3-store: fix unhandled promise rejection (#728) Bump @aws-sdk/client-s3 from 3.717.0 to 3.758.0 (#733) Bump @types/node from 22.10.1 to 22.13.7 (#734) Bump typescript from 5.6.2 to 5.8.2 (#731) @tus/azure-store: fix non-ASCII characters error on metadata (#725) Fix package-lock.json
2 parents 063ccf6 + ba571db commit 9e71451

File tree

16 files changed

+944
-817
lines changed

16 files changed

+944
-817
lines changed

.changeset/thin-keys-whisper.md

-5
This file was deleted.

package-lock.json

+851-796
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@
1717
"@biomejs/biome": "1.9.4",
1818
"@changesets/changelog-github": "^0.5.0",
1919
"@changesets/cli": "^2.27.10",
20-
"typescript": "^5.6.2"
20+
"typescript": "^5.8.2"
2121
}
2222
}

packages/azure-store/CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# @tus/azure-store
22

3+
## 0.1.3
4+
5+
### Patch Changes
6+
7+
- f47f371: Fix error on saving metadata when it contains non-ASCII characters
8+
39
## 0.1.2
410

511
### Patch Changes

packages/azure-store/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"devDependencies": {
2727
"@types/debug": "^4.1.12",
2828
"@types/mocha": "^10.0.6",
29-
"@types/node": "^22.10.1",
29+
"@types/node": "^22.13.7",
3030
"mocha": "^11.0.1",
3131
"should": "^13.2.3"
3232
},

packages/azure-store/src/index.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
type KvStore,
88
MemoryKvStore,
99
TUS_RESUMABLE,
10+
Metadata,
1011
} from '@tus/utils'
1112
import {
1213
type AppendBlobClient,
@@ -78,7 +79,11 @@ export class AzureStore extends DataStore {
7879
await appendBlobClient.setMetadata(
7980
{
8081
tus_version: TUS_RESUMABLE,
81-
upload: JSON.stringify(upload),
82+
upload: JSON.stringify({
83+
...upload,
84+
// Base64 encode the metadata to avoid errors for non-ASCII characters
85+
metadata: Metadata.stringify(upload.metadata ?? {}),
86+
}),
8287
},
8388
{}
8489
)
@@ -110,6 +115,9 @@ export class AzureStore extends DataStore {
110115
throw ERRORS.FILE_NOT_FOUND
111116
}
112117
const upload = JSON.parse(propertyData.metadata.upload) as Upload
118+
// Metadata is base64 encoded to avoid errors for non-ASCII characters
119+
// so we need to decode it separately
120+
upload.metadata = Metadata.parse(JSON.stringify(upload.metadata ?? {}))
113121

114122
await this.cache.set(appendBlobClient.url, upload)
115123

packages/file-store/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"devDependencies": {
2121
"@types/debug": "^4.1.12",
2222
"@types/mocha": "^10.0.6",
23-
"@types/node": "^22.10.1",
23+
"@types/node": "^22.13.7",
2424
"mocha": "^11.0.1",
2525
"should": "^13.2.3"
2626
},

packages/gcs-store/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"@tus/server": "^1.10.0",
2828
"@types/debug": "^4.1.12",
2929
"@types/mocha": "^10.0.6",
30-
"@types/node": "^22.10.1",
30+
"@types/node": "^22.13.7",
3131
"mocha": "^11.0.1",
3232
"should": "^13.2.3"
3333
},

packages/s3-store/CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# @tus/s3-store
22

3+
## 1.9.1
4+
5+
### Patch Changes
6+
7+
- 274a0d1: Bump @aws-sdk/client-s3 from 3.717.0 to 3.758.0
8+
- 81eb03a: Add missing documentation for `maxMultipartParts` option added in #712
9+
- c8e78bd: Fix unhandled promise rejection when uploading a part fails, in which case we returned too early, leaving other parts running in the background.
10+
311
## 1.9.0
412

513
### Minor Changes

packages/s3-store/package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://json.schemastore.org/package.json",
33
"name": "@tus/s3-store",
4-
"version": "1.9.0",
4+
"version": "1.9.1",
55
"description": "AWS S3 store for @tus/server",
66
"main": "dist/index.js",
77
"homepage": "https://github.com/tus/tus-node-server#readme",
@@ -16,10 +16,11 @@
1616
],
1717
"scripts": {
1818
"build": "tsc --build",
19+
"pretest": "tsc --build",
1920
"test": "mocha --timeout 40000 --exit --extension ts --require ts-node/register"
2021
},
2122
"dependencies": {
22-
"@aws-sdk/client-s3": "^3.717.0",
23+
"@aws-sdk/client-s3": "^3.758.0",
2324
"@shopify/semaphore": "^3.1.0",
2425
"@tus/utils": "^0.5.1",
2526
"debug": "^4.3.4",
@@ -29,7 +30,7 @@
2930
"@types/debug": "^4.1.12",
3031
"@types/mocha": "^10.0.6",
3132
"@types/multistream": "^4.1.3",
32-
"@types/node": "^22.10.1",
33+
"@types/node": "^22.13.7",
3334
"mocha": "^11.0.1",
3435
"should": "^13.2.3"
3536
},

packages/s3-store/src/index.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -409,19 +409,25 @@ export class S3Store extends DataStore {
409409
bytesUploaded += partSize
410410
resolve()
411411
} catch (error) {
412+
// If there's an error, destroy the splitter to stop processing more chunks
413+
splitterStream.destroy(error)
412414
reject(error)
413415
} finally {
414416
fsProm.rm(path).catch(() => {
415417
/* ignore */
416418
})
417-
acquiredPermit?.release()
419+
acquiredPermit?.release().catch(() => {
420+
/* ignore */
421+
})
418422
}
419423
})
420424

421425
promises.push(deferred)
422426
})
423427
.on('chunkError', () => {
424-
permit?.release()
428+
permit?.release().catch(() => {
429+
/* ignore */
430+
})
425431
})
426432

427433
try {
@@ -437,6 +443,10 @@ export class S3Store extends DataStore {
437443

438444
promises.push(Promise.reject(error))
439445
} finally {
446+
// Wait for all promises. We don't want to return
447+
// early have have promises running in the background.
448+
await Promise.allSettled(promises)
449+
// But we do want to reject the promise if any of the promises reject.
440450
await Promise.all(promises)
441451
}
442452

packages/s3-store/test/index.ts

+34-2
Original file line numberDiff line numberDiff line change
@@ -278,14 +278,14 @@ describe('S3DataStore', () => {
278278
}
279279
})
280280

281-
it('should use default maxMultipartParts when not specified', function () {
281+
it('should use default maxMultipartParts when not specified', () => {
282282
const store = new S3Store({
283283
s3ClientConfig,
284284
})
285285
assert.equal(store.maxMultipartParts, 10000)
286286
})
287287

288-
it('should use custom maxMultipartParts when specified', function () {
288+
it('should use custom maxMultipartParts when specified', () => {
289289
const customMaxParts = 5000
290290
const store = new S3Store({
291291
s3ClientConfig,
@@ -294,6 +294,38 @@ describe('S3DataStore', () => {
294294
assert.equal(store.maxMultipartParts, customMaxParts)
295295
})
296296

297+
it('should handle multiple concurrent part uploads with some failures', async () => {
298+
const store = new S3Store({
299+
partSize: 5 * 1024 * 1024,
300+
maxConcurrentPartUploads: 3, // Allow 3 concurrent uploads
301+
s3ClientConfig,
302+
})
303+
304+
const size = 15 * 1024 * 1024 // 15MB will be split into 3 parts
305+
const upload = new Upload({
306+
id: shared.testId('concurrent-failures'),
307+
size,
308+
offset: 0,
309+
})
310+
311+
// @ts-expect-error private method
312+
const uploadPartStub = sinon.stub(store, 'uploadPart')
313+
314+
// Make the second part upload fail
315+
uploadPartStub.onCall(0).resolves('etag1')
316+
uploadPartStub.onCall(1).rejects(new Error('Part 2 upload failed'))
317+
uploadPartStub.onCall(2).resolves('etag3')
318+
319+
await store.create(upload)
320+
321+
try {
322+
await store.write(Readable.from(Buffer.alloc(size)), upload.id, upload.offset)
323+
assert.fail('Expected write to fail but it succeeded')
324+
} catch (error) {
325+
assert.equal(uploadPartStub.callCount, 2, '2 parts should have been attempted')
326+
}
327+
})
328+
297329
shared.shouldHaveStoreMethods()
298330
shared.shouldCreateUploads()
299331
shared.shouldRemoveUploads() // Termination extension

packages/server/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"@types/debug": "^4.1.12",
2828
"@types/lodash.throttle": "^4.1.9",
2929
"@types/mocha": "^10.0.6",
30-
"@types/node": "^22.10.1",
30+
"@types/node": "^22.13.7",
3131
"@types/sinon": "^17.0.3",
3232
"@types/supertest": "^2.0.16",
3333
"mocha": "^11.0.1",

packages/utils/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"devDependencies": {
2222
"@types/debug": "^4.1.12",
2323
"@types/mocha": "^10.0.6",
24-
"@types/node": "^22.10.1",
24+
"@types/node": "^22.13.7",
2525
"ioredis": "^5.4.1",
2626
"mocha": "^11.0.1",
2727
"should": "^13.2.3",

test/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
"dependencies": {
1313
"@tus/file-store": "^1.5.1",
1414
"@tus/gcs-store": "^1.4.2",
15-
"@tus/s3-store": "^1.9.0",
15+
"@tus/s3-store": "^1.9.1",
1616
"@tus/server": "^1.10.2"
1717
},
1818
"devDependencies": {
1919
"@types/mocha": "^10.0.6",
20-
"@types/node": "^22.10.1",
20+
"@types/node": "^22.13.7",
2121
"@types/rimraf": "^3.0.2",
2222
"@types/sinon": "^17.0.3",
2323
"@types/supertest": "^2.0.16",

test/src/stores.test.ts

+12
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,18 @@ export const shouldCreateUploads = () => {
7171
const upload = await this.datastore.getUpload(file.id)
7272
assert.deepStrictEqual(upload.metadata, file.metadata)
7373
})
74+
75+
it('should store `upload_metadata` with non-ASCII characters', async function () {
76+
const file = new Upload({
77+
id: testId('create-test-non-ascii'),
78+
size: 1000,
79+
offset: 0,
80+
metadata: {filename: '世界_domination_plan.pdf', is_confidential: null},
81+
})
82+
await this.datastore.create(file)
83+
const upload = await this.datastore.getUpload(file.id)
84+
assert.deepStrictEqual(upload.metadata, file.metadata)
85+
})
7486
})
7587
}
7688

0 commit comments

Comments
 (0)