@maas/payload-plugin-media-cloud 0.0.30 → 0.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/handleDelete.d.mts +3 -3
- package/dist/adapter/handleDelete.mjs +6 -6
- package/dist/adapter/handleDelete.mjs.map +1 -1
- package/dist/adapter/staticHandler.d.mts +5 -3
- package/dist/adapter/staticHandler.mjs +8 -7
- package/dist/adapter/staticHandler.mjs.map +1 -1
- package/dist/adapter/storageAdapter.d.mts +3 -3
- package/dist/adapter/storageAdapter.mjs +4 -4
- package/dist/adapter/storageAdapter.mjs.map +1 -1
- package/dist/collectionHooks/afterChange.d.mts +7 -0
- package/dist/collectionHooks/afterChange.mjs +39 -0
- package/dist/collectionHooks/afterChange.mjs.map +1 -0
- package/dist/collectionHooks/beforeChange.d.mts +7 -0
- package/dist/collectionHooks/beforeChange.mjs +33 -0
- package/dist/collectionHooks/beforeChange.mjs.map +1 -0
- package/dist/collections/mediaCollection.d.mts +3 -2
- package/dist/collections/mediaCollection.mjs +46 -198
- package/dist/collections/mediaCollection.mjs.map +1 -1
- package/dist/components/folderFileCard/folderFileCard.d.mts +13 -0
- package/dist/components/folderFileCard/folderFileCard.mjs +30 -0
- package/dist/components/folderFileCard/folderFileCard.mjs.map +1 -0
- package/dist/components/gridContext/gridContext.d.mts +51 -0
- package/dist/components/gridContext/gridContext.mjs +227 -0
- package/dist/components/gridContext/gridContext.mjs.map +1 -0
- package/dist/components/gridView/gridView.css +50 -0
- package/dist/components/gridView/gridView.d.mts +16 -0
- package/dist/components/gridView/gridView.mjs +124 -0
- package/dist/components/gridView/gridView.mjs.map +1 -0
- package/dist/components/gridView/index.d.mts +2 -0
- package/dist/components/gridView/index.mjs +3 -0
- package/dist/components/index.d.mts +9 -7
- package/dist/components/index.mjs +5 -4
- package/dist/components/itemCardGrid/itemCardGrid.css +12 -0
- package/dist/components/itemCardGrid/itemCardGrid.d.mts +18 -0
- package/dist/components/itemCardGrid/itemCardGrid.mjs +22 -0
- package/dist/components/itemCardGrid/itemCardGrid.mjs.map +1 -0
- package/dist/components/muxPreview/index.d.mts +2 -0
- package/dist/components/muxPreview/index.mjs +3 -0
- package/dist/components/{mux-preview/mux-preview.d.mts → muxPreview/muxPreview.d.mts} +2 -2
- package/dist/components/{mux-preview/mux-preview.mjs → muxPreview/muxPreview.mjs} +2 -2
- package/dist/components/muxPreview/muxPreview.mjs.map +1 -0
- package/dist/components/uploadHandler/index.d.mts +2 -0
- package/dist/components/uploadHandler/index.mjs +3 -0
- package/dist/components/uploadHandler/uploadHandler.d.mts +20 -0
- package/dist/components/{upload-handler/upload-handler.mjs → uploadHandler/uploadHandler.mjs} +82 -52
- package/dist/components/uploadHandler/uploadHandler.mjs.map +1 -0
- package/dist/components/uploadManager/index.d.mts +2 -0
- package/dist/components/uploadManager/index.mjs +3 -0
- package/dist/components/{upload-manager/upload-manager.d.mts → uploadManager/uploadManager.d.mts} +3 -3
- package/dist/components/{upload-manager/upload-manager.mjs → uploadManager/uploadManager.mjs} +3 -3
- package/dist/components/uploadManager/uploadManager.mjs.map +1 -0
- package/dist/endpoints/fileExistsHandler.d.mts +12 -0
- package/dist/endpoints/{tusFileExistsHandler.mjs → fileExistsHandler.mjs} +7 -7
- package/dist/endpoints/fileExistsHandler.mjs.map +1 -0
- package/dist/endpoints/muxAssetHandler.d.mts +1 -0
- package/dist/endpoints/muxAssetHandler.mjs +3 -3
- package/dist/endpoints/muxAssetHandler.mjs.map +1 -1
- package/dist/endpoints/muxWebhookHandler.d.mts +1 -0
- package/dist/endpoints/muxWebhookHandler.mjs +6 -5
- package/dist/endpoints/muxWebhookHandler.mjs.map +1 -1
- package/dist/endpoints/tusCleanupHandler.d.mts +3 -2
- package/dist/endpoints/tusCleanupHandler.mjs +4 -4
- package/dist/endpoints/tusCleanupHandler.mjs.map +1 -1
- package/dist/endpoints/tusFolderHandler.d.mts +12 -0
- package/dist/endpoints/tusFolderHandler.mjs +39 -0
- package/dist/endpoints/tusFolderHandler.mjs.map +1 -0
- package/dist/endpoints/tusPostProcessorHandler.d.mts +3 -2
- package/dist/endpoints/tusPostProcessorHandler.mjs +6 -5
- package/dist/endpoints/tusPostProcessorHandler.mjs.map +1 -1
- package/dist/fields/alt.d.mts +7 -0
- package/dist/fields/alt.mjs +10 -0
- package/dist/fields/alt.mjs.map +1 -0
- package/dist/fields/filename.d.mts +7 -0
- package/dist/fields/filename.mjs +14 -0
- package/dist/fields/filename.mjs.map +1 -0
- package/dist/fields/height.d.mts +7 -0
- package/dist/fields/height.mjs +15 -0
- package/dist/fields/height.mjs.map +1 -0
- package/dist/fields/mux.d.mts +7 -0
- package/dist/fields/mux.mjs +149 -0
- package/dist/fields/mux.mjs.map +1 -0
- package/dist/fields/path.d.mts +7 -0
- package/dist/fields/path.mjs +14 -0
- package/dist/fields/path.mjs.map +1 -0
- package/dist/fields/storage.d.mts +7 -0
- package/dist/fields/storage.mjs +18 -0
- package/dist/fields/storage.mjs.map +1 -0
- package/dist/fields/thumbnail.d.mts +7 -0
- package/dist/fields/thumbnail.mjs +17 -0
- package/dist/fields/thumbnail.mjs.map +1 -0
- package/dist/fields/width.d.mts +7 -0
- package/dist/fields/width.mjs +15 -0
- package/dist/fields/width.mjs.map +1 -0
- package/dist/hooks/useErrorHandler.d.mts +1 -1
- package/dist/hooks/useErrorHandler.mjs +4 -2
- package/dist/hooks/useErrorHandler.mjs.map +1 -1
- package/dist/plugin.d.mts +5 -4
- package/dist/plugin.mjs +53 -29
- package/dist/plugin.mjs.map +1 -1
- package/dist/tus/stores/s3/{expiration-manager.d.mts → expirationManager.d.mts} +4 -3
- package/dist/tus/stores/s3/{expiration-manager.mjs → expirationManager.mjs} +6 -3
- package/dist/tus/stores/s3/expirationManager.mjs.map +1 -0
- package/dist/tus/stores/s3/{file-operations.d.mts → fileOperations.d.mts} +2 -2
- package/dist/tus/stores/s3/{file-operations.mjs → fileOperations.mjs} +2 -2
- package/dist/tus/stores/s3/fileOperations.mjs.map +1 -0
- package/dist/tus/stores/s3/index.d.mts +1 -1
- package/dist/tus/stores/s3/index.mjs +20 -9
- package/dist/tus/stores/s3/index.mjs.map +1 -1
- package/dist/tus/stores/s3/{metadata-manager.d.mts → metadataManager.d.mts} +4 -2
- package/dist/tus/stores/s3/{metadata-manager.mjs → metadataManager.mjs} +6 -5
- package/dist/tus/stores/s3/metadataManager.mjs.map +1 -0
- package/dist/tus/stores/s3/{parts-manager.d.mts → partsManager.d.mts} +4 -4
- package/dist/tus/stores/s3/{parts-manager.mjs → partsManager.mjs} +67 -29
- package/dist/tus/stores/s3/partsManager.mjs.map +1 -0
- package/dist/tus/stores/s3/{s3-store.d.mts → s3Store.d.mts} +38 -32
- package/dist/tus/stores/s3/{s3-store.mjs → s3Store.mjs} +102 -57
- package/dist/tus/stores/s3/s3Store.mjs.map +1 -0
- package/dist/types/errors.d.mts +32 -0
- package/dist/types/errors.mjs +32 -0
- package/dist/types/errors.mjs.map +1 -1
- package/dist/types/index.d.mts +42 -4
- package/dist/utils/buildS3Path.d.mts +10 -0
- package/dist/utils/buildS3Path.mjs +16 -0
- package/dist/utils/buildS3Path.mjs.map +1 -0
- package/dist/utils/buildThumbnailURL.d.mts +14 -0
- package/dist/utils/buildThumbnailURL.mjs +10 -0
- package/dist/utils/buildThumbnailURL.mjs.map +1 -0
- package/dist/utils/defaultOptions.d.mts +7 -0
- package/dist/utils/defaultOptions.mjs +12 -0
- package/dist/utils/defaultOptions.mjs.map +1 -0
- package/dist/utils/file.d.mts +16 -2
- package/dist/utils/file.mjs +58 -6
- package/dist/utils/file.mjs.map +1 -1
- package/dist/utils/mux.mjs +19 -8
- package/dist/utils/mux.mjs.map +1 -1
- package/dist/utils/tus.d.mts +9 -6
- package/dist/utils/tus.mjs +31 -11
- package/dist/utils/tus.mjs.map +1 -1
- package/package.json +9 -4
- package/dist/components/mux-preview/index.d.mts +0 -2
- package/dist/components/mux-preview/index.mjs +0 -3
- package/dist/components/mux-preview/mux-preview.mjs.map +0 -1
- package/dist/components/upload-handler/index.d.mts +0 -2
- package/dist/components/upload-handler/index.mjs +0 -3
- package/dist/components/upload-handler/upload-handler.d.mts +0 -22
- package/dist/components/upload-handler/upload-handler.mjs.map +0 -1
- package/dist/components/upload-manager/index.d.mts +0 -2
- package/dist/components/upload-manager/index.mjs +0 -3
- package/dist/components/upload-manager/upload-manager.mjs.map +0 -1
- package/dist/endpoints/tusFileExistsHandler.d.mts +0 -11
- package/dist/endpoints/tusFileExistsHandler.mjs.map +0 -1
- package/dist/tus/stores/s3/expiration-manager.mjs.map +0 -1
- package/dist/tus/stores/s3/file-operations.mjs.map +0 -1
- package/dist/tus/stores/s3/metadata-manager.mjs.map +0 -1
- package/dist/tus/stores/s3/parts-manager.mjs.map +0 -1
- package/dist/tus/stores/s3/s3-store.mjs.map +0 -1
- /package/dist/components/{upload-manager/upload-manager.css → uploadManager/uploadManager.css} +0 -0
|
@@ -6,7 +6,7 @@ import { StreamSplitter } from "@tus/utils";
|
|
|
6
6
|
import fs from "node:fs";
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
|
|
9
|
-
//#region src/tus/stores/s3/
|
|
9
|
+
//#region src/tus/stores/s3/partsManager.ts
|
|
10
10
|
const { log, throwError } = useErrorHandler();
|
|
11
11
|
var S3PartsManager = class {
|
|
12
12
|
constructor(client, bucket, minPartSize, partUploadSemaphore, metadataManager, fileOperations, generateCompleteTag) {
|
|
@@ -35,7 +35,10 @@ var S3PartsManager = class {
|
|
|
35
35
|
}
|
|
36
36
|
const params = {
|
|
37
37
|
Bucket: this.bucket,
|
|
38
|
-
Key:
|
|
38
|
+
Key: this.metadataManager.generatePartKey({
|
|
39
|
+
id,
|
|
40
|
+
prefix: metadata.file.metadata?.prefix ?? void 0
|
|
41
|
+
}),
|
|
39
42
|
PartNumberMarker: partNumberMarker,
|
|
40
43
|
UploadId: metadata["upload-id"]
|
|
41
44
|
};
|
|
@@ -62,8 +65,11 @@ var S3PartsManager = class {
|
|
|
62
65
|
async finishMultipartUpload(args) {
|
|
63
66
|
const { metadata, parts } = args;
|
|
64
67
|
const params = {
|
|
68
|
+
Key: this.metadataManager.generatePartKey({
|
|
69
|
+
id: metadata.file.id,
|
|
70
|
+
prefix: metadata.file.metadata?.prefix ?? void 0
|
|
71
|
+
}),
|
|
65
72
|
Bucket: this.bucket,
|
|
66
|
-
Key: metadata.file.id,
|
|
67
73
|
MultipartUpload: { Parts: parts.map((part) => {
|
|
68
74
|
return {
|
|
69
75
|
ETag: part.ETag,
|
|
@@ -74,8 +80,11 @@ var S3PartsManager = class {
|
|
|
74
80
|
};
|
|
75
81
|
try {
|
|
76
82
|
return (await this.client.completeMultipartUpload(params)).Location;
|
|
77
|
-
} catch (
|
|
78
|
-
throwError(
|
|
83
|
+
} catch (error) {
|
|
84
|
+
throwError({
|
|
85
|
+
...MediaCloudErrors.TUS_UPLOAD_ERROR,
|
|
86
|
+
cause: error
|
|
87
|
+
});
|
|
79
88
|
throw new Error();
|
|
80
89
|
}
|
|
81
90
|
}
|
|
@@ -88,10 +97,12 @@ var S3PartsManager = class {
|
|
|
88
97
|
async getIncompletePart(args) {
|
|
89
98
|
const { id } = args;
|
|
90
99
|
try {
|
|
100
|
+
const { file } = await this.metadataManager.getMetadata({ id });
|
|
91
101
|
return (await this.client.getObject({
|
|
92
102
|
Bucket: this.bucket,
|
|
93
103
|
Key: this.metadataManager.generatePartKey({
|
|
94
104
|
id,
|
|
105
|
+
prefix: file?.metadata?.prefix ?? void 0,
|
|
95
106
|
isIncomplete: true
|
|
96
107
|
})
|
|
97
108
|
})).Body;
|
|
@@ -109,11 +120,13 @@ var S3PartsManager = class {
|
|
|
109
120
|
async getIncompletePartSize(args) {
|
|
110
121
|
const { id } = args;
|
|
111
122
|
try {
|
|
123
|
+
const { file } = await this.metadataManager.getMetadata({ id });
|
|
112
124
|
return (await this.client.headObject({
|
|
113
125
|
Bucket: this.bucket,
|
|
114
126
|
Key: this.metadataManager.generatePartKey({
|
|
115
127
|
id,
|
|
116
|
-
isIncomplete: true
|
|
128
|
+
isIncomplete: true,
|
|
129
|
+
prefix: file?.metadata?.prefix ?? void 0
|
|
117
130
|
})
|
|
118
131
|
})).ContentLength;
|
|
119
132
|
} catch (error) {
|
|
@@ -129,13 +142,22 @@ var S3PartsManager = class {
|
|
|
129
142
|
*/
|
|
130
143
|
async deleteIncompletePart(args) {
|
|
131
144
|
const { id } = args;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
145
|
+
try {
|
|
146
|
+
const { file } = await this.metadataManager.getMetadata({ id });
|
|
147
|
+
await this.client.deleteObject({
|
|
148
|
+
Bucket: this.bucket,
|
|
149
|
+
Key: this.metadataManager.generatePartKey({
|
|
150
|
+
id,
|
|
151
|
+
isIncomplete: true,
|
|
152
|
+
prefix: file?.metadata?.prefix ?? void 0
|
|
153
|
+
})
|
|
154
|
+
});
|
|
155
|
+
} catch (error) {
|
|
156
|
+
throwError({
|
|
157
|
+
...MediaCloudErrors.S3_DELETE_ERROR,
|
|
158
|
+
cause: error
|
|
159
|
+
});
|
|
160
|
+
}
|
|
139
161
|
}
|
|
140
162
|
/**
|
|
141
163
|
* Downloads incomplete part to temporary file
|
|
@@ -187,17 +209,27 @@ var S3PartsManager = class {
|
|
|
187
209
|
*/
|
|
188
210
|
async uploadIncompletePart(args) {
|
|
189
211
|
const { id, readStream } = args;
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
212
|
+
try {
|
|
213
|
+
const { file } = await this.metadataManager.getMetadata({ id });
|
|
214
|
+
const data = await this.client.putObject({
|
|
215
|
+
Body: readStream,
|
|
216
|
+
Bucket: this.bucket,
|
|
217
|
+
Key: this.metadataManager.generatePartKey({
|
|
218
|
+
id,
|
|
219
|
+
isIncomplete: true,
|
|
220
|
+
prefix: file?.metadata?.prefix ?? void 0
|
|
221
|
+
}),
|
|
222
|
+
Tagging: this.generateCompleteTag("false")
|
|
223
|
+
});
|
|
224
|
+
log(MediaCloudLogs.S3_STORE_INCOMPLETE_PART_UPLOADED);
|
|
225
|
+
return data.ETag;
|
|
226
|
+
} catch (error) {
|
|
227
|
+
throwError({
|
|
228
|
+
...MediaCloudErrors.TUS_UPLOAD_ERROR,
|
|
229
|
+
cause: error
|
|
230
|
+
});
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
201
233
|
}
|
|
202
234
|
/**
|
|
203
235
|
* Uploads a single part
|
|
@@ -217,7 +249,10 @@ var S3PartsManager = class {
|
|
|
217
249
|
const params = {
|
|
218
250
|
Body: readStream,
|
|
219
251
|
Bucket: this.bucket,
|
|
220
|
-
Key:
|
|
252
|
+
Key: this.metadataManager.generatePartKey({
|
|
253
|
+
id: metadata.file.id,
|
|
254
|
+
prefix: metadata.file.metadata?.prefix ?? void 0
|
|
255
|
+
}),
|
|
221
256
|
PartNumber: partNumber,
|
|
222
257
|
UploadId: metadata["upload-id"]
|
|
223
258
|
};
|
|
@@ -226,9 +261,12 @@ var S3PartsManager = class {
|
|
|
226
261
|
ETag: (await this.client.uploadPart(params)).ETag,
|
|
227
262
|
PartNumber: partNumber
|
|
228
263
|
};
|
|
229
|
-
} catch (
|
|
230
|
-
throwError(
|
|
231
|
-
|
|
264
|
+
} catch (error) {
|
|
265
|
+
throwError({
|
|
266
|
+
...MediaCloudErrors.TUS_UPLOAD_ERROR,
|
|
267
|
+
cause: error
|
|
268
|
+
});
|
|
269
|
+
throw error;
|
|
232
270
|
} finally {
|
|
233
271
|
permit();
|
|
234
272
|
}
|
|
@@ -320,4 +358,4 @@ var S3PartsManager = class {
|
|
|
320
358
|
|
|
321
359
|
//#endregion
|
|
322
360
|
export { S3PartsManager };
|
|
323
|
-
//# sourceMappingURL=
|
|
361
|
+
//# sourceMappingURL=partsManager.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partsManager.mjs","names":["client: S3","bucket: string","minPartSize: number","partUploadSemaphore: Semaphore","metadataManager: S3MetadataManager","fileOperations: S3FileOperations","generateCompleteTag: (value: 'false' | 'true') => string | undefined","params: AWS.ListPartsCommandInput","params: AWS.UploadPartCommandInput","promises: Promise<void>[]","pendingChunkFilepath: null | string","permit: SemaphorePermit | undefined"],"sources":["../../../../src/tus/stores/s3/partsManager.ts"],"sourcesContent":["import fs from 'node:fs'\nimport os from 'node:os'\nimport stream from 'node:stream'\n\nimport { NoSuchKey, NotFound, type S3 } from '@aws-sdk/client-s3'\nimport { StreamSplitter } from '@tus/utils'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { MediaCloudErrors, MediaCloudLogs } from '../../../types/errors'\n\nimport type AWS from '@aws-sdk/client-s3'\nimport type { Readable } from 'node:stream'\nimport type { IncompletePartInfo, TusUploadMetadata } from '../../../types'\nimport type { S3FileOperations } from './fileOperations'\nimport type { S3MetadataManager } from './metadataManager'\nimport type { Semaphore, SemaphorePermit } from './semaphore'\n\ntype RetrievePartsArgs = {\n id: string\n partNumberMarker?: string\n}\n\ntype FinishMultipartUploadArgs = {\n metadata: TusUploadMetadata\n parts: Array<AWS.Part>\n}\n\ntype GetIncompletePartArgs = {\n id: string\n}\n\ntype GetIncompletePartSizeArgs = {\n id: string\n}\n\ntype DeleteIncompletePartArgs = {\n id: string\n}\n\ntype DownloadIncompletePartArgs = {\n id: string\n}\n\ntype UploadIncompletePartArgs = {\n id: string\n readStream: fs.ReadStream | Readable\n}\n\ntype UploadPartArgs = {\n metadata: TusUploadMetadata\n readStream: fs.ReadStream | Readable\n partNumber: number\n}\n\ntype UploadPartsArgs = {\n metadata: TusUploadMetadata\n readStream: stream.Readable\n currentPartNumber: number\n offset: number\n}\n\nconst { log, throwError } = useErrorHandler()\n\nexport class S3PartsManager {\n constructor(\n private client: S3,\n private bucket: string,\n private minPartSize: number,\n private partUploadSemaphore: Semaphore,\n private metadataManager: S3MetadataManager,\n private fileOperations: S3FileOperations,\n private generateCompleteTag: (value: 'false' | 'true') => string | undefined\n ) {}\n\n /**\n * Gets the number of complete parts/chunks already uploaded to S3.\n * Retrieves only consecutive parts.\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @param args.partNumberMarker - Marker for pagination (optional)\n * @returns Promise that resolves to array of uploaded parts\n */\n async retrieveParts(args: RetrievePartsArgs): Promise<Array<AWS.Part>> {\n const { id, partNumberMarker } = args\n const metadata = await this.metadataManager.getMetadata({ id })\n\n if (!metadata['upload-id']) {\n throwError(MediaCloudErrors.MUX_UPLOAD_ID_MISSING)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n\n const params: AWS.ListPartsCommandInput = {\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: metadata.file.metadata?.prefix ?? undefined,\n }),\n PartNumberMarker: partNumberMarker,\n UploadId: metadata['upload-id'],\n }\n\n const data = await this.client.listParts(params)\n\n let parts = data.Parts ?? []\n\n if (data.IsTruncated) {\n const rest = await this.retrieveParts({\n id,\n partNumberMarker: data.NextPartNumberMarker,\n })\n parts = [...parts, ...rest]\n }\n\n if (!partNumberMarker) {\n parts.sort((a, b) => (a.PartNumber || 0) - (b.PartNumber || 0))\n }\n\n return parts\n }\n\n /**\n * Completes a multipart upload on S3.\n * This is where S3 concatenates all the uploaded parts.\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.parts - Array of uploaded parts to complete\n * @returns Promise that resolves to the location URL (optional)\n */\n async finishMultipartUpload(\n args: FinishMultipartUploadArgs\n ): Promise<string | undefined> {\n const { metadata, parts } = args\n\n const params = {\n Key: this.metadataManager.generatePartKey({\n id: metadata.file.id,\n prefix: metadata.file.metadata?.prefix ?? undefined,\n }),\n Bucket: this.bucket,\n MultipartUpload: {\n Parts: parts.map((part) => {\n return {\n ETag: part.ETag,\n PartNumber: part.PartNumber,\n }\n }),\n },\n UploadId: metadata['upload-id'],\n }\n\n try {\n const result = await this.client.completeMultipartUpload(params)\n return result.Location\n } catch (error) {\n throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n throw new Error() // This will never execute but satisfies TypeScript\n }\n }\n\n /**\n * Gets incomplete part from S3\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to readable stream or undefined if not found\n */\n async getIncompletePart(\n args: GetIncompletePartArgs\n ): Promise<Readable | undefined> {\n const { id } = args\n\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n const data = await this.client.getObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: file?.metadata?.prefix ?? undefined,\n isIncomplete: true,\n }),\n })\n return data.Body as Readable\n } catch (error) {\n if (error instanceof NoSuchKey) {\n return undefined\n }\n throw error\n }\n }\n\n /**\n * Gets the size of an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to part size or undefined if not found\n */\n async getIncompletePartSize(\n args: GetIncompletePartSizeArgs\n ): Promise<number | undefined> {\n const { id } = args\n\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n const data = await this.client.headObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n isIncomplete: true,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n })\n return data.ContentLength\n } catch (error) {\n if (error instanceof NotFound) {\n return undefined\n }\n throw error\n }\n }\n\n /**\n * Deletes an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves when deletion is complete\n */\n async deleteIncompletePart(args: DeleteIncompletePartArgs): Promise<void> {\n const { id } = args\n\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n await this.client.deleteObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n isIncomplete: true,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n })\n } catch (error) {\n throwError({\n ...MediaCloudErrors.S3_DELETE_ERROR,\n cause: error,\n })\n }\n }\n\n /**\n * Downloads incomplete part to temporary file\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to incomplete part info or undefined if not found\n */\n async downloadIncompletePart(\n args: DownloadIncompletePartArgs\n ): Promise<IncompletePartInfo | undefined> {\n const { id } = args\n const incompletePart = await this.getIncompletePart({ id })\n\n if (!incompletePart) {\n return\n }\n const filePath = await this.fileOperations.generateUniqueTmpFileName({\n template: 'tus-s3-incomplete-part-',\n })\n\n try {\n let incompletePartSize = 0\n\n const byteCounterTransform = new stream.Transform({\n transform(chunk, _, callback) {\n incompletePartSize += chunk.length\n callback(null, chunk)\n },\n })\n\n // Write to temporary file\n await stream.promises.pipeline(\n incompletePart,\n byteCounterTransform,\n fs.createWriteStream(filePath)\n )\n\n const createReadStream = (options: { cleanUpOnEnd: boolean }) => {\n const fileReader = fs.createReadStream(filePath)\n\n if (options.cleanUpOnEnd) {\n fileReader.on('end', () => {\n fs.unlink(filePath, () => {})\n })\n\n fileReader.on('error', (err) => {\n fileReader.destroy(err)\n fs.unlink(filePath, () => {})\n })\n }\n\n return fileReader\n }\n\n return {\n createReader: createReadStream,\n path: filePath,\n size: incompletePartSize,\n }\n } catch (err) {\n fs.promises.rm(filePath).catch(() => {})\n throw err\n }\n }\n\n /**\n * Uploads an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @param args.readStream - The stream to read data from\n * @returns Promise that resolves to the ETag of the uploaded part\n */\n async uploadIncompletePart(args: UploadIncompletePartArgs): Promise<string> {\n const { id, readStream } = args\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n const data = await this.client.putObject({\n Body: readStream,\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n isIncomplete: true,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n Tagging: this.generateCompleteTag('false'),\n })\n log(MediaCloudLogs.S3_STORE_INCOMPLETE_PART_UPLOADED)\n return data.ETag as string\n } catch (error) {\n throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n throw error\n }\n }\n\n /**\n * Uploads a single part\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.readStream - The stream to read data from\n * @param args.partNumber - The part number to upload\n * @returns Promise that resolves to the ETag of the uploaded part\n */\n async uploadPart(args: UploadPartArgs): Promise<AWS.Part> {\n const { metadata, readStream, partNumber } = args\n const permit = await this.partUploadSemaphore.acquire()\n\n if (!metadata['upload-id']) {\n throwError(MediaCloudErrors.MUX_UPLOAD_ID_MISSING)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n\n const params: AWS.UploadPartCommandInput = {\n Body: readStream,\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id: metadata.file.id,\n prefix: metadata.file.metadata?.prefix ?? undefined,\n }),\n PartNumber: partNumber,\n UploadId: metadata['upload-id'],\n }\n\n try {\n const data = await this.client.uploadPart(params)\n return { ETag: data.ETag, PartNumber: partNumber }\n } catch (error) {\n throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n throw error\n } finally {\n permit()\n }\n }\n\n /**\n * Uploads a stream to s3 using multiple parts\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.readStream - The stream to read data from\n * @param args.currentPartNumber - The current part number to start from\n * @param args.offset - The byte offset to start from\n * @returns Promise that resolves to the number of bytes uploaded\n */\n async uploadParts(args: UploadPartsArgs): Promise<number> {\n const { metadata, readStream, offset: initialOffset } = args\n let { currentPartNumber } = args\n let offset = initialOffset\n const size = metadata.file.size\n const promises: Promise<void>[] = []\n let pendingChunkFilepath: null | string = null\n let bytesUploaded = 0\n let permit: SemaphorePermit | undefined = undefined\n\n const splitterStream = new StreamSplitter({\n chunkSize: this.fileOperations.calculateOptimalPartSize({ size }),\n directory: os.tmpdir(),\n })\n .on('beforeChunkStarted', async () => {\n permit = await this.partUploadSemaphore.acquire()\n })\n .on('chunkStarted', (filepath) => {\n pendingChunkFilepath = filepath\n })\n .on('chunkFinished', ({ path, size: partSize }) => {\n pendingChunkFilepath = null\n\n const acquiredPermit = permit\n const partNumber = currentPartNumber++\n\n offset += partSize\n\n const isFinalPart = size === offset\n\n const uploadChunk = async () => {\n try {\n // Only the first chunk of each PATCH request can prepend\n // an incomplete part (last chunk) from the previous request.\n const readable = fs.createReadStream(path)\n readable.on('error', function (error) {\n throw error\n })\n\n switch (true) {\n case partSize >= this.minPartSize || isFinalPart:\n await this.uploadPart({\n metadata,\n readStream: readable,\n partNumber,\n })\n break\n default:\n await this.uploadIncompletePart({\n id: metadata.file.id,\n readStream: readable,\n })\n break\n }\n\n bytesUploaded += partSize\n } catch (error) {\n // Destroy the splitter to stop processing more chunks\n const mappedError =\n error instanceof Error ? error : new Error(String(error))\n splitterStream.destroy(mappedError)\n throw mappedError\n } finally {\n fs.promises.rm(path).catch(function () {})\n acquiredPermit?.()\n }\n }\n\n const deferred = uploadChunk()\n\n promises.push(deferred)\n })\n .on('chunkError', () => {\n permit?.()\n })\n\n try {\n await stream.promises.pipeline(readStream, splitterStream)\n } catch (error) {\n if (pendingChunkFilepath !== null) {\n try {\n await fs.promises.rm(pendingChunkFilepath)\n } catch {\n log(MediaCloudLogs.S3_STORE_CHUNK_REMOVAL_FAILED)\n }\n }\n const mappedError =\n error instanceof Error ? error : new Error(String(error))\n promises.push(Promise.reject(mappedError))\n } finally {\n // Wait for all promises\n await Promise.allSettled(promises)\n // Reject the promise if any of the promises reject\n await Promise.all(promises)\n }\n\n return bytesUploaded\n }\n}\n"],"mappings":";;;;;;;;;AA6DA,MAAM,EAAE,KAAK,eAAe,iBAAiB;AAE7C,IAAa,iBAAb,MAA4B;CAC1B,YACE,AAAQA,QACR,AAAQC,QACR,AAAQC,aACR,AAAQC,qBACR,AAAQC,iBACR,AAAQC,gBACR,AAAQC,qBACR;EAPQ;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;;;;CAWV,MAAM,cAAc,MAAmD;EACrE,MAAM,EAAE,IAAI,qBAAqB;EACjC,MAAM,WAAW,MAAM,KAAK,gBAAgB,YAAY,EAAE,IAAI,CAAC;AAE/D,MAAI,CAAC,SAAS,cAAc;AAC1B,cAAW,iBAAiB,sBAAsB;AAClD,SAAM,IAAI,OAAO;;EAGnB,MAAMC,SAAoC;GACxC,QAAQ,KAAK;GACb,KAAK,KAAK,gBAAgB,gBAAgB;IACxC;IACA,QAAQ,SAAS,KAAK,UAAU,UAAU;IAC3C,CAAC;GACF,kBAAkB;GAClB,UAAU,SAAS;GACpB;EAED,MAAM,OAAO,MAAM,KAAK,OAAO,UAAU,OAAO;EAEhD,IAAI,QAAQ,KAAK,SAAS,EAAE;AAE5B,MAAI,KAAK,aAAa;GACpB,MAAM,OAAO,MAAM,KAAK,cAAc;IACpC;IACA,kBAAkB,KAAK;IACxB,CAAC;AACF,WAAQ,CAAC,GAAG,OAAO,GAAG,KAAK;;AAG7B,MAAI,CAAC,iBACH,OAAM,MAAM,GAAG,OAAO,EAAE,cAAc,MAAM,EAAE,cAAc,GAAG;AAGjE,SAAO;;;;;;;;;;CAWT,MAAM,sBACJ,MAC6B;EAC7B,MAAM,EAAE,UAAU,UAAU;EAE5B,MAAM,SAAS;GACb,KAAK,KAAK,gBAAgB,gBAAgB;IACxC,IAAI,SAAS,KAAK;IAClB,QAAQ,SAAS,KAAK,UAAU,UAAU;IAC3C,CAAC;GACF,QAAQ,KAAK;GACb,iBAAiB,EACf,OAAO,MAAM,KAAK,SAAS;AACzB,WAAO;KACL,MAAM,KAAK;KACX,YAAY,KAAK;KAClB;KACD,EACH;GACD,UAAU,SAAS;GACpB;AAED,MAAI;AAEF,WADe,MAAM,KAAK,OAAO,wBAAwB,OAAO,EAClD;WACP,OAAO;AACd,cAAW;IAAE,GAAG,iBAAiB;IAAkB,OAAO;IAAO,CAAC;AAClE,SAAM,IAAI,OAAO;;;;;;;;;CAUrB,MAAM,kBACJ,MAC+B;EAC/B,MAAM,EAAE,OAAO;AAEf,MAAI;GACF,MAAM,EAAE,SAAS,MAAM,KAAK,gBAAgB,YAAY,EAAE,IAAI,CAAC;AAU/D,WARa,MAAM,KAAK,OAAO,UAAU;IACvC,QAAQ,KAAK;IACb,KAAK,KAAK,gBAAgB,gBAAgB;KACxC;KACA,QAAQ,MAAM,UAAU,UAAU;KAClC,cAAc;KACf,CAAC;IACH,CAAC,EACU;WACL,OAAO;AACd,OAAI,iBAAiB,UACnB;AAEF,SAAM;;;;;;;;;CAUV,MAAM,sBACJ,MAC6B;EAC7B,MAAM,EAAE,OAAO;AAEf,MAAI;GACF,MAAM,EAAE,SAAS,MAAM,KAAK,gBAAgB,YAAY,EAAE,IAAI,CAAC;AAU/D,WARa,MAAM,KAAK,OAAO,WAAW;IACxC,QAAQ,KAAK;IACb,KAAK,KAAK,gBAAgB,gBAAgB;KACxC;KACA,cAAc;KACd,QAAQ,MAAM,UAAU,UAAU;KACnC,CAAC;IACH,CAAC,EACU;WACL,OAAO;AACd,OAAI,iBAAiB,SACnB;AAEF,SAAM;;;;;;;;;CAUV,MAAM,qBAAqB,MAA+C;EACxE,MAAM,EAAE,OAAO;AAEf,MAAI;GACF,MAAM,EAAE,SAAS,MAAM,KAAK,gBAAgB,YAAY,EAAE,IAAI,CAAC;AAE/D,SAAM,KAAK,OAAO,aAAa;IAC7B,QAAQ,KAAK;IACb,KAAK,KAAK,gBAAgB,gBAAgB;KACxC;KACA,cAAc;KACd,QAAQ,MAAM,UAAU,UAAU;KACnC,CAAC;IACH,CAAC;WACK,OAAO;AACd,cAAW;IACT,GAAG,iBAAiB;IACpB,OAAO;IACR,CAAC;;;;;;;;;CAUN,MAAM,uBACJ,MACyC;EACzC,MAAM,EAAE,OAAO;EACf,MAAM,iBAAiB,MAAM,KAAK,kBAAkB,EAAE,IAAI,CAAC;AAE3D,MAAI,CAAC,eACH;EAEF,MAAM,WAAW,MAAM,KAAK,eAAe,0BAA0B,EACnE,UAAU,2BACX,CAAC;AAEF,MAAI;GACF,IAAI,qBAAqB;GAEzB,MAAM,uBAAuB,IAAI,OAAO,UAAU,EAChD,UAAU,OAAO,GAAG,UAAU;AAC5B,0BAAsB,MAAM;AAC5B,aAAS,MAAM,MAAM;MAExB,CAAC;AAGF,SAAM,OAAO,SAAS,SACpB,gBACA,sBACA,GAAG,kBAAkB,SAAS,CAC/B;GAED,MAAM,oBAAoB,YAAuC;IAC/D,MAAM,aAAa,GAAG,iBAAiB,SAAS;AAEhD,QAAI,QAAQ,cAAc;AACxB,gBAAW,GAAG,aAAa;AACzB,SAAG,OAAO,gBAAgB,GAAG;OAC7B;AAEF,gBAAW,GAAG,UAAU,QAAQ;AAC9B,iBAAW,QAAQ,IAAI;AACvB,SAAG,OAAO,gBAAgB,GAAG;OAC7B;;AAGJ,WAAO;;AAGT,UAAO;IACL,cAAc;IACd,MAAM;IACN,MAAM;IACP;WACM,KAAK;AACZ,MAAG,SAAS,GAAG,SAAS,CAAC,YAAY,GAAG;AACxC,SAAM;;;;;;;;;;CAWV,MAAM,qBAAqB,MAAiD;EAC1E,MAAM,EAAE,IAAI,eAAe;AAC3B,MAAI;GACF,MAAM,EAAE,SAAS,MAAM,KAAK,gBAAgB,YAAY,EAAE,IAAI,CAAC;GAE/D,MAAM,OAAO,MAAM,KAAK,OAAO,UAAU;IACvC,MAAM;IACN,QAAQ,KAAK;IACb,KAAK,KAAK,gBAAgB,gBAAgB;KACxC;KACA,cAAc;KACd,QAAQ,MAAM,UAAU,UAAU;KACnC,CAAC;IACF,SAAS,KAAK,oBAAoB,QAAQ;IAC3C,CAAC;AACF,OAAI,eAAe,kCAAkC;AACrD,UAAO,KAAK;WACL,OAAO;AACd,cAAW;IAAE,GAAG,iBAAiB;IAAkB,OAAO;IAAO,CAAC;AAClE,SAAM;;;;;;;;;;;CAYV,MAAM,WAAW,MAAyC;EACxD,MAAM,EAAE,UAAU,YAAY,eAAe;EAC7C,MAAM,SAAS,MAAM,KAAK,oBAAoB,SAAS;AAEvD,MAAI,CAAC,SAAS,cAAc;AAC1B,cAAW,iBAAiB,sBAAsB;AAClD,SAAM,IAAI,OAAO;;EAGnB,MAAMC,SAAqC;GACzC,MAAM;GACN,QAAQ,KAAK;GACb,KAAK,KAAK,gBAAgB,gBAAgB;IACxC,IAAI,SAAS,KAAK;IAClB,QAAQ,SAAS,KAAK,UAAU,UAAU;IAC3C,CAAC;GACF,YAAY;GACZ,UAAU,SAAS;GACpB;AAED,MAAI;AAEF,UAAO;IAAE,OADI,MAAM,KAAK,OAAO,WAAW,OAAO,EAC7B;IAAM,YAAY;IAAY;WAC3C,OAAO;AACd,cAAW;IAAE,GAAG,iBAAiB;IAAkB,OAAO;IAAO,CAAC;AAClE,SAAM;YACE;AACR,WAAQ;;;;;;;;;;;;CAaZ,MAAM,YAAY,MAAwC;EACxD,MAAM,EAAE,UAAU,YAAY,QAAQ,kBAAkB;EACxD,IAAI,EAAE,sBAAsB;EAC5B,IAAI,SAAS;EACb,MAAM,OAAO,SAAS,KAAK;EAC3B,MAAMC,WAA4B,EAAE;EACpC,IAAIC,uBAAsC;EAC1C,IAAI,gBAAgB;EACpB,IAAIC,SAAsC;EAE1C,MAAM,iBAAiB,IAAI,eAAe;GACxC,WAAW,KAAK,eAAe,yBAAyB,EAAE,MAAM,CAAC;GACjE,WAAW,GAAG,QAAQ;GACvB,CAAC,CACC,GAAG,sBAAsB,YAAY;AACpC,YAAS,MAAM,KAAK,oBAAoB,SAAS;IACjD,CACD,GAAG,iBAAiB,aAAa;AAChC,0BAAuB;IACvB,CACD,GAAG,kBAAkB,EAAE,MAAM,MAAM,eAAe;AACjD,0BAAuB;GAEvB,MAAM,iBAAiB;GACvB,MAAM,aAAa;AAEnB,aAAU;GAEV,MAAM,cAAc,SAAS;GAE7B,MAAM,cAAc,YAAY;AAC9B,QAAI;KAGF,MAAM,WAAW,GAAG,iBAAiB,KAAK;AAC1C,cAAS,GAAG,SAAS,SAAU,OAAO;AACpC,YAAM;OACN;AAEF,aAAQ,MAAR;MACE,KAAK,YAAY,KAAK,eAAe;AACnC,aAAM,KAAK,WAAW;QACpB;QACA,YAAY;QACZ;QACD,CAAC;AACF;MACF;AACE,aAAM,KAAK,qBAAqB;QAC9B,IAAI,SAAS,KAAK;QAClB,YAAY;QACb,CAAC;AACF;;AAGJ,sBAAiB;aACV,OAAO;KAEd,MAAM,cACJ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AAC3D,oBAAe,QAAQ,YAAY;AACnC,WAAM;cACE;AACR,QAAG,SAAS,GAAG,KAAK,CAAC,MAAM,WAAY,GAAG;AAC1C,uBAAkB;;;GAItB,MAAM,WAAW,aAAa;AAE9B,YAAS,KAAK,SAAS;IACvB,CACD,GAAG,oBAAoB;AACtB,aAAU;IACV;AAEJ,MAAI;AACF,SAAM,OAAO,SAAS,SAAS,YAAY,eAAe;WACnD,OAAO;AACd,OAAI,yBAAyB,KAC3B,KAAI;AACF,UAAM,GAAG,SAAS,GAAG,qBAAqB;WACpC;AACN,QAAI,eAAe,8BAA8B;;GAGrD,MAAM,cACJ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AAC3D,YAAS,KAAK,QAAQ,OAAO,YAAY,CAAC;YAClC;AAER,SAAM,QAAQ,WAAW,SAAS;AAElC,SAAM,QAAQ,IAAI,SAAS;;AAG7B,SAAO"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Semaphore } from "./semaphore.mjs";
|
|
2
|
-
import { S3ExpirationManager } from "./expiration-manager.mjs";
|
|
3
|
-
import { S3FileOperations } from "./file-operations.mjs";
|
|
4
2
|
import { S3StoreConfig } from "../../../types/index.mjs";
|
|
5
|
-
import { S3MetadataManager } from "./
|
|
6
|
-
import {
|
|
3
|
+
import { S3MetadataManager } from "./metadataManager.mjs";
|
|
4
|
+
import { S3ExpirationManager } from "./expirationManager.mjs";
|
|
5
|
+
import { S3FileOperations } from "./fileOperations.mjs";
|
|
6
|
+
import { S3PartsManager } from "./partsManager.mjs";
|
|
7
7
|
import stream, { Readable } from "node:stream";
|
|
8
8
|
import { S3 } from "@aws-sdk/client-s3";
|
|
9
9
|
import { DataStore, Upload } from "@tus/utils";
|
|
10
10
|
|
|
11
|
-
//#region src/tus/stores/s3/
|
|
11
|
+
//#region src/tus/stores/s3/s3Store.d.ts
|
|
12
12
|
declare class S3Store extends DataStore {
|
|
13
13
|
client: S3;
|
|
14
14
|
bucket: string;
|
|
@@ -26,19 +26,6 @@ declare class S3Store extends DataStore {
|
|
|
26
26
|
protected expirationManager: S3ExpirationManager;
|
|
27
27
|
protected customEndpoint: string;
|
|
28
28
|
constructor(options: S3StoreConfig);
|
|
29
|
-
/**
|
|
30
|
-
* Generate the key name for the info file
|
|
31
|
-
* @param id - The upload ID
|
|
32
|
-
* @returns The info file key
|
|
33
|
-
*/
|
|
34
|
-
protected generateInfoKey(id: string): string;
|
|
35
|
-
/**
|
|
36
|
-
* Generate the key name for a part file
|
|
37
|
-
* @param id - The upload ID
|
|
38
|
-
* @param isIncompletePart - Whether this is an incomplete part (default: false)
|
|
39
|
-
* @returns The part file key
|
|
40
|
-
*/
|
|
41
|
-
protected generatePartKey(id: string, isIncompletePart?: boolean): string;
|
|
42
29
|
/**
|
|
43
30
|
* Helper method to check if expiration tags should be used
|
|
44
31
|
* @returns True if expiration tags should be used
|
|
@@ -47,7 +34,7 @@ declare class S3Store extends DataStore {
|
|
|
47
34
|
/**
|
|
48
35
|
* Generates a tag for marking complete/incomplete uploads
|
|
49
36
|
* @param value - Either 'false' or 'true' to mark completion status
|
|
50
|
-
* @returns The tag string or undefined if tags shouldn
|
|
37
|
+
* @returns The tag string or undefined if tags shouldn’t be used
|
|
51
38
|
*/
|
|
52
39
|
protected generateCompleteTag(value: 'false' | 'true'): string | undefined;
|
|
53
40
|
/**
|
|
@@ -55,55 +42,74 @@ declare class S3Store extends DataStore {
|
|
|
55
42
|
* Also, a `${file_id}.info` file is created which holds some information
|
|
56
43
|
* about the upload itself like: `upload-id`, `upload-length`, etc.
|
|
57
44
|
* @param upload - The upload object to create
|
|
58
|
-
* @
|
|
45
|
+
* @return Promise that resolves to the created upload object with storage information
|
|
59
46
|
*/
|
|
60
47
|
create(upload: Upload): Promise<Upload>;
|
|
61
48
|
/**
|
|
62
|
-
*
|
|
63
|
-
* @param file_id - The file ID
|
|
64
|
-
* @param upload_length - The length of the upload
|
|
65
|
-
* @returns Promise that resolves when length is declared
|
|
66
|
-
*/
|
|
67
|
-
declareUploadLength(file_id: string, upload_length: number): Promise<void>;
|
|
68
|
-
/**
|
|
69
|
-
* Writes `buffer` to the file specified by the upload's `id` at `offset`
|
|
49
|
+
* Writes `buffer` to the file specified by the upload’s `id` at `offset`
|
|
70
50
|
* @param readable - The readable stream to write
|
|
71
51
|
* @param id - The upload ID
|
|
72
52
|
* @param offset - The byte offset to write at
|
|
73
|
-
* @
|
|
53
|
+
* @return Promise that resolves to the new offset after writing the data
|
|
74
54
|
*/
|
|
75
55
|
write(readable: stream.Readable, id: string, offset: number): Promise<number>;
|
|
76
56
|
/**
|
|
77
57
|
* Returns the current state of the upload, i.e how much data has been
|
|
78
58
|
* uploaded and if the upload is complete.
|
|
59
|
+
* @param id - The upload ID to retrieve
|
|
60
|
+
* @returns Promise that resolves to the upload object with current offset and storage information
|
|
79
61
|
*/
|
|
80
62
|
getUpload(id: string): Promise<Upload>;
|
|
81
63
|
/**
|
|
82
|
-
* Reads the file specified by the upload
|
|
64
|
+
* Reads the file specified by the upload’s `id` and returns a readable stream
|
|
65
|
+
* @param id - The upload ID to read
|
|
66
|
+
* @returns Promise that resolves to a readable stream of the file's contents
|
|
83
67
|
*/
|
|
84
68
|
read(id: string): Promise<Readable>;
|
|
85
69
|
/**
|
|
86
|
-
*
|
|
70
|
+
*
|
|
71
|
+
* Moves the file specified by its `oldKey` to `newKey`.
|
|
72
|
+
* @param oldKey - The current S3 key of the file to be moved
|
|
73
|
+
* @param newKey - The new S3 key to move the file to
|
|
74
|
+
* @return Promise that resolves when the file has been successfully moved, or rejects with an error if the move operation fails
|
|
75
|
+
*/
|
|
76
|
+
copy(oldKey: string, newKey: string): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Removes files specified by the upload’s `id`
|
|
79
|
+
* @param id - The upload ID to remove
|
|
80
|
+
* @returns Promise that resolves when the file and its metadata have been removed
|
|
87
81
|
*/
|
|
88
82
|
remove(id: string): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Removes the .info file specified by the upload’s `id`
|
|
85
|
+
* @param id - The upload ID to clean up
|
|
86
|
+
* @returns Promise that resolves when the metadata file has been removed
|
|
87
|
+
*/
|
|
88
|
+
cleanup(id: string): Promise<void>;
|
|
89
89
|
/**
|
|
90
90
|
* Combine all multipart uploads into a single object
|
|
91
|
+
* @param id - The upload ID to complete
|
|
92
|
+
* @returns Promise that resolves to the completed upload object with storage information
|
|
91
93
|
*/
|
|
92
94
|
completeMultipartUpload(id: string): Promise<Upload>;
|
|
93
95
|
/**
|
|
94
96
|
* Get the full S3 URL for an uploaded file
|
|
97
|
+
* @param id - The upload ID to get the URL for
|
|
98
|
+
* @returns The full URL to access the uploaded file on S3
|
|
95
99
|
*/
|
|
96
100
|
getUrl(id: string): string;
|
|
97
101
|
/**
|
|
98
102
|
* Deletes expired incomplete uploads.
|
|
99
103
|
* Returns the number of deleted uploads.
|
|
104
|
+
* @returns Promise that resolves to the number of deleted uploads
|
|
100
105
|
*/
|
|
101
106
|
deleteExpired(): Promise<number>;
|
|
102
107
|
/**
|
|
103
108
|
* Returns the expiration period in milliseconds
|
|
109
|
+
* @return The expiration period in milliseconds
|
|
104
110
|
*/
|
|
105
111
|
getExpiration(): number;
|
|
106
112
|
}
|
|
107
113
|
//#endregion
|
|
108
114
|
export { S3Store };
|
|
109
|
-
//# sourceMappingURL=
|
|
115
|
+
//# sourceMappingURL=s3Store.d.mts.map
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { MediaCloudLogs } from "../../../types/errors.mjs";
|
|
2
2
|
import { useErrorHandler } from "../../../hooks/useErrorHandler.mjs";
|
|
3
3
|
import { Semaphore } from "./semaphore.mjs";
|
|
4
|
-
import { S3ExpirationManager } from "./
|
|
5
|
-
import { S3FileOperations } from "./
|
|
6
|
-
import { S3MetadataManager } from "./
|
|
7
|
-
import { S3PartsManager } from "./
|
|
4
|
+
import { S3ExpirationManager } from "./expirationManager.mjs";
|
|
5
|
+
import { S3FileOperations } from "./fileOperations.mjs";
|
|
6
|
+
import { S3MetadataManager } from "./metadataManager.mjs";
|
|
7
|
+
import { S3PartsManager } from "./partsManager.mjs";
|
|
8
8
|
import stream from "node:stream";
|
|
9
9
|
import { NoSuchKey, NotFound, S3 } from "@aws-sdk/client-s3";
|
|
10
10
|
import { DataStore, ERRORS, TUS_RESUMABLE, Upload } from "@tus/utils";
|
|
11
11
|
|
|
12
|
-
//#region src/tus/stores/s3/
|
|
12
|
+
//#region src/tus/stores/s3/s3Store.ts
|
|
13
13
|
const { log } = useErrorHandler();
|
|
14
14
|
var S3Store = class extends DataStore {
|
|
15
15
|
constructor(options) {
|
|
@@ -42,27 +42,10 @@ var S3Store = class extends DataStore {
|
|
|
42
42
|
this.metadataManager = new S3MetadataManager(this.client, this.bucket, this.shouldUseExpirationTags.bind(this), this.generateCompleteTag.bind(this));
|
|
43
43
|
this.fileOperations = new S3FileOperations(this.maxMultipartParts, this.maxUploadSize, this.minPartSize, this.partSize);
|
|
44
44
|
this.partsManager = new S3PartsManager(this.client, this.bucket, this.minPartSize, this.partUploadSemaphore, this.metadataManager, this.fileOperations, this.generateCompleteTag.bind(this));
|
|
45
|
-
this.expirationManager = new S3ExpirationManager(this.client, this.bucket, this.expirationPeriodInMilliseconds, this.generateInfoKey.bind(this), this.generatePartKey.bind(this));
|
|
45
|
+
this.expirationManager = new S3ExpirationManager(this.client, this.bucket, this.expirationPeriodInMilliseconds, this.metadataManager.generateInfoKey.bind(this.metadataManager), this.metadataManager.generatePartKey.bind(this.metadataManager));
|
|
46
46
|
this.deleteExpired();
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
* Generate the key name for the info file
|
|
50
|
-
* @param id - The upload ID
|
|
51
|
-
* @returns The info file key
|
|
52
|
-
*/
|
|
53
|
-
generateInfoKey(id) {
|
|
54
|
-
return `${id}.info`;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Generate the key name for a part file
|
|
58
|
-
* @param id - The upload ID
|
|
59
|
-
* @param isIncompletePart - Whether this is an incomplete part (default: false)
|
|
60
|
-
* @returns The part file key
|
|
61
|
-
*/
|
|
62
|
-
generatePartKey(id, isIncompletePart = false) {
|
|
63
|
-
return isIncompletePart ? `${id}.part` : id;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
49
|
* Helper method to check if expiration tags should be used
|
|
67
50
|
* @returns True if expiration tags should be used
|
|
68
51
|
*/
|
|
@@ -72,7 +55,7 @@ var S3Store = class extends DataStore {
|
|
|
72
55
|
/**
|
|
73
56
|
* Generates a tag for marking complete/incomplete uploads
|
|
74
57
|
* @param value - Either 'false' or 'true' to mark completion status
|
|
75
|
-
* @returns The tag string or undefined if tags shouldn
|
|
58
|
+
* @returns The tag string or undefined if tags shouldn’t be used
|
|
76
59
|
*/
|
|
77
60
|
generateCompleteTag(value) {
|
|
78
61
|
if (!this.shouldUseExpirationTags()) return;
|
|
@@ -83,13 +66,16 @@ var S3Store = class extends DataStore {
|
|
|
83
66
|
* Also, a `${file_id}.info` file is created which holds some information
|
|
84
67
|
* about the upload itself like: `upload-id`, `upload-length`, etc.
|
|
85
68
|
* @param upload - The upload object to create
|
|
86
|
-
* @
|
|
69
|
+
* @return Promise that resolves to the created upload object with storage information
|
|
87
70
|
*/
|
|
88
71
|
async create(upload) {
|
|
89
72
|
log(MediaCloudLogs.S3_STORE_MULTIPART_INIT);
|
|
90
73
|
const request = {
|
|
91
74
|
Bucket: this.bucket,
|
|
92
|
-
Key:
|
|
75
|
+
Key: this.metadataManager.generatePartKey({
|
|
76
|
+
id: upload.id,
|
|
77
|
+
prefix: upload.metadata?.prefix ?? void 0
|
|
78
|
+
}),
|
|
93
79
|
Metadata: { "tus-version": TUS_RESUMABLE }
|
|
94
80
|
};
|
|
95
81
|
if (upload.metadata?.contentType) request.ContentType = upload.metadata.contentType;
|
|
@@ -110,26 +96,11 @@ var S3Store = class extends DataStore {
|
|
|
110
96
|
return upload;
|
|
111
97
|
}
|
|
112
98
|
/**
|
|
113
|
-
*
|
|
114
|
-
* @param file_id - The file ID
|
|
115
|
-
* @param upload_length - The length of the upload
|
|
116
|
-
* @returns Promise that resolves when length is declared
|
|
117
|
-
*/
|
|
118
|
-
async declareUploadLength(file_id, upload_length) {
|
|
119
|
-
const { file, "upload-id": uploadId } = await this.metadataManager.getMetadata({ id: file_id });
|
|
120
|
-
if (!file) throw ERRORS.FILE_NOT_FOUND;
|
|
121
|
-
file.size = upload_length;
|
|
122
|
-
await this.metadataManager.saveMetadata({
|
|
123
|
-
upload: file,
|
|
124
|
-
uploadId
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Writes `buffer` to the file specified by the upload's `id` at `offset`
|
|
99
|
+
* Writes `buffer` to the file specified by the upload’s `id` at `offset`
|
|
129
100
|
* @param readable - The readable stream to write
|
|
130
101
|
* @param id - The upload ID
|
|
131
102
|
* @param offset - The byte offset to write at
|
|
132
|
-
* @
|
|
103
|
+
* @return Promise that resolves to the new offset after writing the data
|
|
133
104
|
*/
|
|
134
105
|
async write(readable, id, offset) {
|
|
135
106
|
const metadata = await this.metadataManager.getMetadata({ id });
|
|
@@ -178,6 +149,8 @@ var S3Store = class extends DataStore {
|
|
|
178
149
|
/**
|
|
179
150
|
* Returns the current state of the upload, i.e how much data has been
|
|
180
151
|
* uploaded and if the upload is complete.
|
|
152
|
+
* @param id - The upload ID to retrieve
|
|
153
|
+
* @returns Promise that resolves to the upload object with current offset and storage information
|
|
181
154
|
*/
|
|
182
155
|
async getUpload(id) {
|
|
183
156
|
let metadata;
|
|
@@ -211,7 +184,9 @@ var S3Store = class extends DataStore {
|
|
|
211
184
|
});
|
|
212
185
|
}
|
|
213
186
|
/**
|
|
214
|
-
* Reads the file specified by the upload
|
|
187
|
+
* Reads the file specified by the upload’s `id` and returns a readable stream
|
|
188
|
+
* @param id - The upload ID to read
|
|
189
|
+
* @returns Promise that resolves to a readable stream of the file's contents
|
|
215
190
|
*/
|
|
216
191
|
async read(id) {
|
|
217
192
|
log(MediaCloudLogs.S3_STORE_READ_ATTEMPT);
|
|
@@ -231,19 +206,66 @@ var S3Store = class extends DataStore {
|
|
|
231
206
|
if (retries > 0) await new Promise((resolve) => setTimeout(resolve, 100));
|
|
232
207
|
}
|
|
233
208
|
log(MediaCloudLogs.S3_STORE_READ_FAILED);
|
|
234
|
-
throw lastError
|
|
209
|
+
throw lastError ?? /* @__PURE__ */ new Error(`Failed to read file ${id}`);
|
|
235
210
|
}
|
|
236
211
|
/**
|
|
237
|
-
*
|
|
212
|
+
*
|
|
213
|
+
* Moves the file specified by its `oldKey` to `newKey`.
|
|
214
|
+
* @param oldKey - The current S3 key of the file to be moved
|
|
215
|
+
* @param newKey - The new S3 key to move the file to
|
|
216
|
+
* @return Promise that resolves when the file has been successfully moved, or rejects with an error if the move operation fails
|
|
217
|
+
*/
|
|
218
|
+
async copy(oldKey, newKey) {
|
|
219
|
+
try {
|
|
220
|
+
await this.client.copyObject({
|
|
221
|
+
Bucket: this.bucket,
|
|
222
|
+
CopySource: encodeURI(`${this.bucket}/${oldKey}`),
|
|
223
|
+
Key: newKey
|
|
224
|
+
});
|
|
225
|
+
await this.client.deleteObject({
|
|
226
|
+
Bucket: this.bucket,
|
|
227
|
+
Key: oldKey
|
|
228
|
+
});
|
|
229
|
+
} catch (error) {
|
|
230
|
+
if (error?.code && [
|
|
231
|
+
"NoSuchKey",
|
|
232
|
+
"NoSuchUpload",
|
|
233
|
+
"NotFound"
|
|
234
|
+
].includes(error.Code || "")) {
|
|
235
|
+
log(MediaCloudLogs.S3_STORE_FILE_NOT_FOUND);
|
|
236
|
+
throw ERRORS.FILE_NOT_FOUND;
|
|
237
|
+
}
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Removes files specified by the upload’s `id`
|
|
243
|
+
* @param id - The upload ID to remove
|
|
244
|
+
* @returns Promise that resolves when the file and its metadata have been removed
|
|
238
245
|
*/
|
|
239
246
|
async remove(id) {
|
|
240
247
|
try {
|
|
241
|
-
const { "upload-id": uploadId } = await this.metadataManager.getMetadata({ id });
|
|
248
|
+
const { "upload-id": uploadId, file } = await this.metadataManager.getMetadata({ id });
|
|
242
249
|
if (uploadId) await this.client.abortMultipartUpload({
|
|
243
250
|
Bucket: this.bucket,
|
|
244
251
|
Key: id,
|
|
245
252
|
UploadId: uploadId
|
|
246
253
|
});
|
|
254
|
+
await this.client.deleteObjects({
|
|
255
|
+
Bucket: this.bucket,
|
|
256
|
+
Delete: { Objects: [
|
|
257
|
+
{ Key: this.metadataManager.generatePartKey({
|
|
258
|
+
id,
|
|
259
|
+
prefix: file?.metadata?.prefix ?? void 0
|
|
260
|
+
}) },
|
|
261
|
+
{ Key: this.metadataManager.generatePartKey({
|
|
262
|
+
id,
|
|
263
|
+
prefix: file?.metadata?.prefix ?? void 0,
|
|
264
|
+
isIncomplete: true
|
|
265
|
+
}) },
|
|
266
|
+
{ Key: this.metadataManager.generateInfoKey({ id }) }
|
|
267
|
+
] }
|
|
268
|
+
});
|
|
247
269
|
} catch (error) {
|
|
248
270
|
if (error?.code && [
|
|
249
271
|
"NoSuchKey",
|
|
@@ -255,20 +277,39 @@ var S3Store = class extends DataStore {
|
|
|
255
277
|
}
|
|
256
278
|
throw error;
|
|
257
279
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Removes the .info file specified by the upload’s `id`
|
|
283
|
+
* @param id - The upload ID to clean up
|
|
284
|
+
* @returns Promise that resolves when the metadata file has been removed
|
|
285
|
+
*/
|
|
286
|
+
async cleanup(id) {
|
|
287
|
+
try {
|
|
288
|
+
const { file } = await this.metadataManager.getMetadata({ id });
|
|
289
|
+
await this.client.deleteObjects({
|
|
290
|
+
Bucket: this.bucket,
|
|
291
|
+
Delete: { Objects: [{ Key: this.metadataManager.generatePartKey({
|
|
264
292
|
id,
|
|
293
|
+
prefix: file?.metadata?.prefix ?? void 0,
|
|
265
294
|
isIncomplete: true
|
|
266
|
-
}) }
|
|
267
|
-
|
|
268
|
-
})
|
|
295
|
+
}) }, { Key: this.metadataManager.generateInfoKey({ id }) }] }
|
|
296
|
+
});
|
|
297
|
+
} catch (error) {
|
|
298
|
+
if (error?.code && [
|
|
299
|
+
"NoSuchKey",
|
|
300
|
+
"NoSuchUpload",
|
|
301
|
+
"NotFound"
|
|
302
|
+
].includes(error.Code || "")) {
|
|
303
|
+
log(MediaCloudLogs.S3_STORE_FILE_NOT_FOUND);
|
|
304
|
+
throw ERRORS.FILE_NOT_FOUND;
|
|
305
|
+
}
|
|
306
|
+
throw error;
|
|
307
|
+
}
|
|
269
308
|
}
|
|
270
309
|
/**
|
|
271
310
|
* Combine all multipart uploads into a single object
|
|
311
|
+
* @param id - The upload ID to complete
|
|
312
|
+
* @returns Promise that resolves to the completed upload object with storage information
|
|
272
313
|
*/
|
|
273
314
|
async completeMultipartUpload(id) {
|
|
274
315
|
const metadata = await this.metadataManager.getMetadata({ id });
|
|
@@ -301,6 +342,8 @@ var S3Store = class extends DataStore {
|
|
|
301
342
|
}
|
|
302
343
|
/**
|
|
303
344
|
* Get the full S3 URL for an uploaded file
|
|
345
|
+
* @param id - The upload ID to get the URL for
|
|
346
|
+
* @returns The full URL to access the uploaded file on S3
|
|
304
347
|
*/
|
|
305
348
|
getUrl(id) {
|
|
306
349
|
if (this.customEndpoint) return `${this.customEndpoint}/${this.bucket}/${id}`;
|
|
@@ -314,12 +357,14 @@ var S3Store = class extends DataStore {
|
|
|
314
357
|
/**
|
|
315
358
|
* Deletes expired incomplete uploads.
|
|
316
359
|
* Returns the number of deleted uploads.
|
|
360
|
+
* @returns Promise that resolves to the number of deleted uploads
|
|
317
361
|
*/
|
|
318
362
|
async deleteExpired() {
|
|
319
363
|
return this.expirationManager.deleteExpired();
|
|
320
364
|
}
|
|
321
365
|
/**
|
|
322
366
|
* Returns the expiration period in milliseconds
|
|
367
|
+
* @return The expiration period in milliseconds
|
|
323
368
|
*/
|
|
324
369
|
getExpiration() {
|
|
325
370
|
return this.expirationManager.getExpiration();
|
|
@@ -328,4 +373,4 @@ var S3Store = class extends DataStore {
|
|
|
328
373
|
|
|
329
374
|
//#endregion
|
|
330
375
|
export { S3Store };
|
|
331
|
-
//# sourceMappingURL=
|
|
376
|
+
//# sourceMappingURL=s3Store.mjs.map
|