@payloadcms/storage-s3 4.0.0-internal.77191e5 → 4.0.0-internal.7d5c665
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/README.md +1 -1
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +24 -9
- package/dist/adapter.js.map +1 -1
- package/dist/copyFile.d.ts +13 -0
- package/dist/copyFile.d.ts.map +1 -0
- package/dist/copyFile.js +145 -0
- package/dist/copyFile.js.map +1 -0
- package/dist/getFile.d.ts +3 -2
- package/dist/getFile.d.ts.map +1 -1
- package/dist/getFile.js +6 -8
- package/dist/getFile.js.map +1 -1
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -57,4 +57,4 @@ export default buildConfig({
|
|
|
57
57
|
|
|
58
58
|
### Configuration Options
|
|
59
59
|
|
|
60
|
-
See the
|
|
60
|
+
See the [AWS SDK Package](https://github.com/aws/aws-sdk-js-v3) and [`S3ClientConfig`](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3) object for guidance on AWS S3 configuration.
|
package/dist/adapter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,KAAK,EACV,OAAO,EACP,mBAAmB,
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,KAAK,EACV,OAAO,EACP,mBAAmB,EAGpB,MAAM,wCAAwC,CAAA;AAE/C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAKzD,UAAU,mBAAmB;IAC3B,GAAG,CAAC,EAAE,SAAS,GAAG,aAAa,CAAA;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC,MAAM,EAAE,cAAc,CAAA;IACtB,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,eAAe,EAAE,qBAAqB,CAAA;IACtC,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,wBAAgB,eAAe,CAAC,EAC9B,GAAG,EACH,MAAM,EACN,aAAa,EACb,MAAM,EACN,gBAAgB,EAChB,eAAe,EACf,oBAA4B,GAC7B,EAAE,mBAAmB,GAAG,OAAO,CAgF/B"}
|
package/dist/adapter.js
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
import { generateUploadInstructions } from './generateUploadInstructions.js';
|
|
2
2
|
import { generateURL } from './generateURL.js';
|
|
3
3
|
export function createS3Adapter({ acl, bucket, clientUploads, config, getStorageClient, signedDownloads, useCompositePrefixes = false }) {
|
|
4
|
+
const deleteStoredFile = async ({ storageFilePath })=>{
|
|
5
|
+
const { deleteFile } = await import('./deleteFile.js');
|
|
6
|
+
return deleteFile({
|
|
7
|
+
bucket,
|
|
8
|
+
client: getStorageClient(),
|
|
9
|
+
storageFilePath
|
|
10
|
+
});
|
|
11
|
+
};
|
|
4
12
|
return ({ collection, prefix = '' })=>({
|
|
5
13
|
name: 's3',
|
|
14
|
+
supportsTempFiles: true,
|
|
15
|
+
copyFile: async ({ from, req, to })=>{
|
|
16
|
+
const { copyS3File } = await import('./copyFile.js');
|
|
17
|
+
await copyS3File({
|
|
18
|
+
acl,
|
|
19
|
+
bucket,
|
|
20
|
+
client: getStorageClient(),
|
|
21
|
+
from,
|
|
22
|
+
req,
|
|
23
|
+
to
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
deleteFile: deleteStoredFile,
|
|
6
27
|
generateURL: ({ filename, prefix: urlPrefix = '' })=>generateURL({
|
|
7
28
|
bucket,
|
|
8
29
|
collectionPrefix: prefix,
|
|
@@ -26,14 +47,7 @@ export function createS3Adapter({ acl, bucket, clientUploads, config, getStorage
|
|
|
26
47
|
},
|
|
27
48
|
// Helpers below dynamic-import their @aws-sdk dependencies so the SDK only
|
|
28
49
|
// loads on the first request that actually needs it.
|
|
29
|
-
handleDelete:
|
|
30
|
-
const { deleteFile } = await import('./deleteFile.js');
|
|
31
|
-
return deleteFile({
|
|
32
|
-
bucket,
|
|
33
|
-
client: getStorageClient(),
|
|
34
|
-
storageFilePath
|
|
35
|
-
});
|
|
36
|
-
},
|
|
50
|
+
handleDelete: deleteStoredFile,
|
|
37
51
|
handleUpload: async ({ data, file, storageFilePath })=>{
|
|
38
52
|
const { uploadFile } = await import('./uploadFile.js');
|
|
39
53
|
await uploadFile({
|
|
@@ -47,7 +61,7 @@ export function createS3Adapter({ acl, bucket, clientUploads, config, getStorage
|
|
|
47
61
|
});
|
|
48
62
|
return data;
|
|
49
63
|
},
|
|
50
|
-
staticHandler: async (req, { doc, headers, params: { filename, uploadReference } })=>{
|
|
64
|
+
staticHandler: async (req, { doc, headers, params: { filename, operation, uploadReference } })=>{
|
|
51
65
|
const { getFile } = await import('./getFile.js');
|
|
52
66
|
return getFile({
|
|
53
67
|
bucket,
|
|
@@ -57,6 +71,7 @@ export function createS3Adapter({ acl, bucket, clientUploads, config, getStorage
|
|
|
57
71
|
doc,
|
|
58
72
|
filename,
|
|
59
73
|
incomingHeaders: headers,
|
|
74
|
+
operation,
|
|
60
75
|
req,
|
|
61
76
|
signedDownloads,
|
|
62
77
|
uploadReference,
|
package/dist/adapter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapter.ts"],"sourcesContent":["import type { S3, S3ClientConfig } from '@aws-sdk/client-s3'\nimport type {\n Adapter,\n ClientUploadsConfig,\n GeneratedAdapter,\n} from '@payloadcms/plugin-cloud-storage/types'\n\nimport type { SignedDownloadsConfig } from './getFile.js'\n\nimport { generateUploadInstructions } from './generateUploadInstructions.js'\nimport { generateURL } from './generateURL.js'\n\ninterface CreateS3AdapterArgs {\n acl?: 'private' | 'public-read'\n bucket: string\n clientUploads?: ClientUploadsConfig\n config: S3ClientConfig\n getStorageClient: () => S3\n signedDownloads: SignedDownloadsConfig\n useCompositePrefixes?: boolean\n}\n\nexport function createS3Adapter({\n acl,\n bucket,\n clientUploads,\n config,\n getStorageClient,\n signedDownloads,\n useCompositePrefixes = false,\n}: CreateS3AdapterArgs): Adapter {\n return ({ collection, prefix = '' }): GeneratedAdapter => ({\n name: 's3',\n\n generateURL: ({ filename, prefix: urlPrefix = '' }) =>\n generateURL({\n bucket,\n collectionPrefix: prefix,\n endpoint: config.endpoint,\n filename,\n prefix: urlPrefix,\n useCompositePrefixes,\n }),\n\n uploadInstructions: {\n enabled: Boolean(clientUploads),\n generate: generateUploadInstructions({\n access: typeof clientUploads === 'object' ? clientUploads.access : undefined,\n acl,\n bucket,\n collectionPrefix: prefix,\n getStorageClient,\n useCompositePrefixes,\n }),\n requiresUploadReceipt: true,\n useInAdmin: true,\n },\n\n // Helpers below dynamic-import their @aws-sdk dependencies so the SDK only\n // loads on the first request that actually needs it.\n handleDelete:
|
|
1
|
+
{"version":3,"sources":["../src/adapter.ts"],"sourcesContent":["import type { S3, S3ClientConfig } from '@aws-sdk/client-s3'\nimport type {\n Adapter,\n ClientUploadsConfig,\n DeleteFile,\n GeneratedAdapter,\n} from '@payloadcms/plugin-cloud-storage/types'\n\nimport type { SignedDownloadsConfig } from './getFile.js'\n\nimport { generateUploadInstructions } from './generateUploadInstructions.js'\nimport { generateURL } from './generateURL.js'\n\ninterface CreateS3AdapterArgs {\n acl?: 'private' | 'public-read'\n bucket: string\n clientUploads?: ClientUploadsConfig\n config: S3ClientConfig\n getStorageClient: () => S3\n signedDownloads: SignedDownloadsConfig\n useCompositePrefixes?: boolean\n}\n\nexport function createS3Adapter({\n acl,\n bucket,\n clientUploads,\n config,\n getStorageClient,\n signedDownloads,\n useCompositePrefixes = false,\n}: CreateS3AdapterArgs): Adapter {\n const deleteStoredFile: DeleteFile = async ({ storageFilePath }) => {\n const { deleteFile } = await import('./deleteFile.js')\n return deleteFile({ bucket, client: getStorageClient(), storageFilePath })\n }\n\n return ({ collection, prefix = '' }): GeneratedAdapter => ({\n name: 's3',\n supportsTempFiles: true,\n\n copyFile: async ({ from, req, to }) => {\n const { copyS3File } = await import('./copyFile.js')\n await copyS3File({ acl, bucket, client: getStorageClient(), from, req, to })\n },\n deleteFile: deleteStoredFile,\n\n generateURL: ({ filename, prefix: urlPrefix = '' }) =>\n generateURL({\n bucket,\n collectionPrefix: prefix,\n endpoint: config.endpoint,\n filename,\n prefix: urlPrefix,\n useCompositePrefixes,\n }),\n\n uploadInstructions: {\n enabled: Boolean(clientUploads),\n generate: generateUploadInstructions({\n access: typeof clientUploads === 'object' ? clientUploads.access : undefined,\n acl,\n bucket,\n collectionPrefix: prefix,\n getStorageClient,\n useCompositePrefixes,\n }),\n requiresUploadReceipt: true,\n useInAdmin: true,\n },\n\n // Helpers below dynamic-import their @aws-sdk dependencies so the SDK only\n // loads on the first request that actually needs it.\n handleDelete: deleteStoredFile,\n\n handleUpload: async ({ data, file, storageFilePath }) => {\n const { uploadFile } = await import('./uploadFile.js')\n await uploadFile({\n acl,\n bucket,\n buffer: file.buffer,\n client: getStorageClient(),\n mimeType: file.mimeType,\n storageFilePath,\n tempFilePath: file.tempFilePath,\n })\n\n return data\n },\n\n staticHandler: async (\n req,\n { doc, headers, params: { filename, operation, uploadReference } },\n ) => {\n const { getFile } = await import('./getFile.js')\n return getFile({\n bucket,\n client: getStorageClient(),\n collection,\n collectionPrefix: prefix,\n doc,\n filename,\n incomingHeaders: headers,\n operation,\n req,\n signedDownloads,\n uploadReference,\n useCompositePrefixes,\n })\n },\n })\n}\n"],"names":["generateUploadInstructions","generateURL","createS3Adapter","acl","bucket","clientUploads","config","getStorageClient","signedDownloads","useCompositePrefixes","deleteStoredFile","storageFilePath","deleteFile","client","collection","prefix","name","supportsTempFiles","copyFile","from","req","to","copyS3File","filename","urlPrefix","collectionPrefix","endpoint","uploadInstructions","enabled","Boolean","generate","access","undefined","requiresUploadReceipt","useInAdmin","handleDelete","handleUpload","data","file","uploadFile","buffer","mimeType","tempFilePath","staticHandler","doc","headers","params","operation","uploadReference","getFile","incomingHeaders"],"mappings":"AAUA,SAASA,0BAA0B,QAAQ,kCAAiC;AAC5E,SAASC,WAAW,QAAQ,mBAAkB;AAY9C,OAAO,SAASC,gBAAgB,EAC9BC,GAAG,EACHC,MAAM,EACNC,aAAa,EACbC,MAAM,EACNC,gBAAgB,EAChBC,eAAe,EACfC,uBAAuB,KAAK,EACR;IACpB,MAAMC,mBAA+B,OAAO,EAAEC,eAAe,EAAE;QAC7D,MAAM,EAAEC,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC;QACpC,OAAOA,WAAW;YAAER;YAAQS,QAAQN;YAAoBI;QAAgB;IAC1E;IAEA,OAAO,CAAC,EAAEG,UAAU,EAAEC,SAAS,EAAE,EAAE,GAAwB,CAAA;YACzDC,MAAM;YACNC,mBAAmB;YAEnBC,UAAU,OAAO,EAAEC,IAAI,EAAEC,GAAG,EAAEC,EAAE,EAAE;gBAChC,MAAM,EAAEC,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC;gBACpC,MAAMA,WAAW;oBAAEnB;oBAAKC;oBAAQS,QAAQN;oBAAoBY;oBAAMC;oBAAKC;gBAAG;YAC5E;YACAT,YAAYF;YAEZT,aAAa,CAAC,EAAEsB,QAAQ,EAAER,QAAQS,YAAY,EAAE,EAAE,GAChDvB,YAAY;oBACVG;oBACAqB,kBAAkBV;oBAClBW,UAAUpB,OAAOoB,QAAQ;oBACzBH;oBACAR,QAAQS;oBACRf;gBACF;YAEFkB,oBAAoB;gBAClBC,SAASC,QAAQxB;gBACjByB,UAAU9B,2BAA2B;oBACnC+B,QAAQ,OAAO1B,kBAAkB,WAAWA,cAAc0B,MAAM,GAAGC;oBACnE7B;oBACAC;oBACAqB,kBAAkBV;oBAClBR;oBACAE;gBACF;gBACAwB,uBAAuB;gBACvBC,YAAY;YACd;YAEA,2EAA2E;YAC3E,qDAAqD;YACrDC,cAAczB;YAEd0B,cAAc,OAAO,EAAEC,IAAI,EAAEC,IAAI,EAAE3B,eAAe,EAAE;gBAClD,MAAM,EAAE4B,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC;gBACpC,MAAMA,WAAW;oBACfpC;oBACAC;oBACAoC,QAAQF,KAAKE,MAAM;oBACnB3B,QAAQN;oBACRkC,UAAUH,KAAKG,QAAQ;oBACvB9B;oBACA+B,cAAcJ,KAAKI,YAAY;gBACjC;gBAEA,OAAOL;YACT;YAEAM,eAAe,OACbvB,KACA,EAAEwB,GAAG,EAAEC,OAAO,EAAEC,QAAQ,EAAEvB,QAAQ,EAAEwB,SAAS,EAAEC,eAAe,EAAE,EAAE;gBAElE,MAAM,EAAEC,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC;gBACjC,OAAOA,QAAQ;oBACb7C;oBACAS,QAAQN;oBACRO;oBACAW,kBAAkBV;oBAClB6B;oBACArB;oBACA2B,iBAAiBL;oBACjBE;oBACA3B;oBACAZ;oBACAwC;oBACAvC;gBACF;YACF;QACF,CAAA;AACF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { S3 } from '@aws-sdk/client-s3';
|
|
2
|
+
import type { PayloadRequest } from 'payload';
|
|
3
|
+
type CopyS3FileArgs = {
|
|
4
|
+
acl?: 'private' | 'public-read';
|
|
5
|
+
bucket: string;
|
|
6
|
+
client: S3;
|
|
7
|
+
from: string;
|
|
8
|
+
req?: PayloadRequest;
|
|
9
|
+
to: string;
|
|
10
|
+
};
|
|
11
|
+
export declare const copyS3File: ({ acl, bucket, client, from, req, to, }: CopyS3FileArgs) => Promise<void>;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=copyFile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copyFile.d.ts","sourceRoot":"","sources":["../src/copyFile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C,KAAK,cAAc,GAAG;IACpB,GAAG,CAAC,EAAE,SAAS,GAAG,aAAa,CAAA;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,EAAE,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,EAAE,EAAE,MAAM,CAAA;CACX,CAAA;AAKD,eAAO,MAAM,UAAU,4CAOpB,cAAc,KAAG,OAAO,CAAC,IAAI,CAyI/B,CAAA"}
|
package/dist/copyFile.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
const singleCopyLimit = 5 * 1024 ** 3;
|
|
2
|
+
const partSize = 256 * 1024 ** 2;
|
|
3
|
+
export const copyS3File = async ({ acl, bucket, client, from, req, to })=>{
|
|
4
|
+
if (from === to) {
|
|
5
|
+
throw new Error('Storage copy requires different source and destination keys');
|
|
6
|
+
}
|
|
7
|
+
const source = await client.headObject({
|
|
8
|
+
Bucket: bucket,
|
|
9
|
+
Key: from
|
|
10
|
+
});
|
|
11
|
+
const size = source.ContentLength;
|
|
12
|
+
if (size === undefined) {
|
|
13
|
+
throw new Error('Storage source has no known length');
|
|
14
|
+
}
|
|
15
|
+
const copySource = `${encodeURIComponent(bucket)}/${from.split('/').map(encodeURIComponent).join('/')}`;
|
|
16
|
+
let hasCopied = false;
|
|
17
|
+
let destinationETag;
|
|
18
|
+
let destinationVersion;
|
|
19
|
+
try {
|
|
20
|
+
if (size <= singleCopyLimit) {
|
|
21
|
+
const copied = await client.copyObject({
|
|
22
|
+
ACL: acl,
|
|
23
|
+
Bucket: bucket,
|
|
24
|
+
CopySource: copySource,
|
|
25
|
+
CopySourceIfMatch: source.ETag,
|
|
26
|
+
IfNoneMatch: '*',
|
|
27
|
+
Key: to,
|
|
28
|
+
MetadataDirective: 'COPY',
|
|
29
|
+
ServerSideEncryption: source.ServerSideEncryption,
|
|
30
|
+
SSEKMSKeyId: source.SSEKMSKeyId,
|
|
31
|
+
TaggingDirective: 'COPY'
|
|
32
|
+
});
|
|
33
|
+
hasCopied = true;
|
|
34
|
+
destinationETag = copied.CopyObjectResult?.ETag;
|
|
35
|
+
destinationVersion = copied.VersionId;
|
|
36
|
+
} else {
|
|
37
|
+
const { TagSet = [] } = await client.getObjectTagging({
|
|
38
|
+
Bucket: bucket,
|
|
39
|
+
Key: from
|
|
40
|
+
});
|
|
41
|
+
const tagging = new URLSearchParams();
|
|
42
|
+
for (const tag of TagSet){
|
|
43
|
+
if (tag.Key && tag.Value !== undefined) {
|
|
44
|
+
tagging.set(tag.Key, tag.Value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const upload = await client.createMultipartUpload({
|
|
48
|
+
ACL: acl,
|
|
49
|
+
Bucket: bucket,
|
|
50
|
+
CacheControl: source.CacheControl,
|
|
51
|
+
ContentDisposition: source.ContentDisposition,
|
|
52
|
+
ContentEncoding: source.ContentEncoding,
|
|
53
|
+
ContentLanguage: source.ContentLanguage,
|
|
54
|
+
ContentType: source.ContentType,
|
|
55
|
+
Expires: source.Expires,
|
|
56
|
+
Key: to,
|
|
57
|
+
Metadata: source.Metadata,
|
|
58
|
+
ServerSideEncryption: source.ServerSideEncryption,
|
|
59
|
+
SSEKMSKeyId: source.SSEKMSKeyId,
|
|
60
|
+
StorageClass: source.StorageClass,
|
|
61
|
+
Tagging: tagging.size ? tagging.toString() : undefined
|
|
62
|
+
});
|
|
63
|
+
if (!upload.UploadId) {
|
|
64
|
+
throw new Error('S3 did not return a multipart upload ID');
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const parts = [];
|
|
68
|
+
for(let partNumber = 1, start = 0; start < size; start += partSize, partNumber++){
|
|
69
|
+
const end = Math.min(start + partSize, size) - 1;
|
|
70
|
+
const part = await client.uploadPartCopy({
|
|
71
|
+
Bucket: bucket,
|
|
72
|
+
CopySource: copySource,
|
|
73
|
+
CopySourceIfMatch: source.ETag,
|
|
74
|
+
CopySourceRange: `bytes=${start}-${end}`,
|
|
75
|
+
Key: to,
|
|
76
|
+
PartNumber: partNumber,
|
|
77
|
+
UploadId: upload.UploadId
|
|
78
|
+
});
|
|
79
|
+
if (!part.CopyPartResult?.ETag) {
|
|
80
|
+
throw new Error(`S3 copy part ${partNumber} did not return an ETag`);
|
|
81
|
+
}
|
|
82
|
+
parts.push({
|
|
83
|
+
ETag: part.CopyPartResult.ETag,
|
|
84
|
+
PartNumber: partNumber
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const copied = await client.completeMultipartUpload({
|
|
88
|
+
Bucket: bucket,
|
|
89
|
+
IfNoneMatch: '*',
|
|
90
|
+
Key: to,
|
|
91
|
+
MultipartUpload: {
|
|
92
|
+
Parts: parts
|
|
93
|
+
},
|
|
94
|
+
UploadId: upload.UploadId
|
|
95
|
+
});
|
|
96
|
+
hasCopied = true;
|
|
97
|
+
destinationETag = copied.ETag;
|
|
98
|
+
destinationVersion = copied.VersionId;
|
|
99
|
+
} catch (err) {
|
|
100
|
+
try {
|
|
101
|
+
await client.abortMultipartUpload({
|
|
102
|
+
Bucket: bucket,
|
|
103
|
+
Key: to,
|
|
104
|
+
UploadId: upload.UploadId
|
|
105
|
+
});
|
|
106
|
+
} catch (cleanupError) {
|
|
107
|
+
req?.payload.logger.error({
|
|
108
|
+
err: cleanupError,
|
|
109
|
+
msg: `Failed to abort S3 multipart copy at ${bucket}/${to} (upload ${upload.UploadId})`
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
throw err;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const destination = await client.headObject({
|
|
116
|
+
Bucket: bucket,
|
|
117
|
+
Key: to
|
|
118
|
+
});
|
|
119
|
+
if (destination.ContentLength !== size) {
|
|
120
|
+
throw new Error(`Copied storage object is not readable at its expected length: ${to}`);
|
|
121
|
+
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
if (hasCopied) {
|
|
124
|
+
try {
|
|
125
|
+
if (!destinationETag && (!destinationVersion || destinationVersion === 'null')) {
|
|
126
|
+
throw new Error('S3 copy did not return an ETag or version for safe cleanup');
|
|
127
|
+
}
|
|
128
|
+
await client.deleteObject({
|
|
129
|
+
Bucket: bucket,
|
|
130
|
+
IfMatch: destinationETag,
|
|
131
|
+
Key: to,
|
|
132
|
+
VersionId: destinationVersion
|
|
133
|
+
});
|
|
134
|
+
} catch (cleanupError) {
|
|
135
|
+
req?.payload.logger.error({
|
|
136
|
+
err: cleanupError,
|
|
137
|
+
msg: `Failed to remove unsuccessful S3 copy at ${bucket}/${to}`
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
//# sourceMappingURL=copyFile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/copyFile.ts"],"sourcesContent":["import type { S3 } from '@aws-sdk/client-s3'\nimport type { PayloadRequest } from 'payload'\n\ntype CopyS3FileArgs = {\n acl?: 'private' | 'public-read'\n bucket: string\n client: S3\n from: string\n req?: PayloadRequest\n to: string\n}\n\nconst singleCopyLimit = 5 * 1024 ** 3\nconst partSize = 256 * 1024 ** 2\n\nexport const copyS3File = async ({\n acl,\n bucket,\n client,\n from,\n req,\n to,\n}: CopyS3FileArgs): Promise<void> => {\n if (from === to) {\n throw new Error('Storage copy requires different source and destination keys')\n }\n\n const source = await client.headObject({ Bucket: bucket, Key: from })\n const size = source.ContentLength\n\n if (size === undefined) {\n throw new Error('Storage source has no known length')\n }\n\n const copySource = `${encodeURIComponent(bucket)}/${from.split('/').map(encodeURIComponent).join('/')}`\n\n let hasCopied = false\n let destinationETag: string | undefined\n let destinationVersion: string | undefined\n\n try {\n if (size <= singleCopyLimit) {\n const copied = await client.copyObject({\n ACL: acl,\n Bucket: bucket,\n CopySource: copySource,\n CopySourceIfMatch: source.ETag,\n IfNoneMatch: '*',\n Key: to,\n MetadataDirective: 'COPY',\n ServerSideEncryption: source.ServerSideEncryption,\n SSEKMSKeyId: source.SSEKMSKeyId,\n TaggingDirective: 'COPY',\n })\n hasCopied = true\n destinationETag = copied.CopyObjectResult?.ETag\n destinationVersion = copied.VersionId\n } else {\n const { TagSet = [] } = await client.getObjectTagging({ Bucket: bucket, Key: from })\n const tagging = new URLSearchParams()\n\n for (const tag of TagSet) {\n if (tag.Key && tag.Value !== undefined) {\n tagging.set(tag.Key, tag.Value)\n }\n }\n\n const upload = await client.createMultipartUpload({\n ACL: acl,\n Bucket: bucket,\n CacheControl: source.CacheControl,\n ContentDisposition: source.ContentDisposition,\n ContentEncoding: source.ContentEncoding,\n ContentLanguage: source.ContentLanguage,\n ContentType: source.ContentType,\n Expires: source.Expires,\n Key: to,\n Metadata: source.Metadata,\n ServerSideEncryption: source.ServerSideEncryption,\n SSEKMSKeyId: source.SSEKMSKeyId,\n StorageClass: source.StorageClass,\n Tagging: tagging.size ? tagging.toString() : undefined,\n })\n\n if (!upload.UploadId) {\n throw new Error('S3 did not return a multipart upload ID')\n }\n\n try {\n const parts = []\n\n for (let partNumber = 1, start = 0; start < size; start += partSize, partNumber++) {\n const end = Math.min(start + partSize, size) - 1\n const part = await client.uploadPartCopy({\n Bucket: bucket,\n CopySource: copySource,\n CopySourceIfMatch: source.ETag,\n CopySourceRange: `bytes=${start}-${end}`,\n Key: to,\n PartNumber: partNumber,\n UploadId: upload.UploadId,\n })\n\n if (!part.CopyPartResult?.ETag) {\n throw new Error(`S3 copy part ${partNumber} did not return an ETag`)\n }\n\n parts.push({ ETag: part.CopyPartResult.ETag, PartNumber: partNumber })\n }\n\n const copied = await client.completeMultipartUpload({\n Bucket: bucket,\n IfNoneMatch: '*',\n Key: to,\n MultipartUpload: { Parts: parts },\n UploadId: upload.UploadId,\n })\n hasCopied = true\n destinationETag = copied.ETag\n destinationVersion = copied.VersionId\n } catch (err) {\n try {\n await client.abortMultipartUpload({ Bucket: bucket, Key: to, UploadId: upload.UploadId })\n } catch (cleanupError) {\n req?.payload.logger.error({\n err: cleanupError,\n msg: `Failed to abort S3 multipart copy at ${bucket}/${to} (upload ${upload.UploadId})`,\n })\n }\n throw err\n }\n }\n\n const destination = await client.headObject({ Bucket: bucket, Key: to })\n\n if (destination.ContentLength !== size) {\n throw new Error(`Copied storage object is not readable at its expected length: ${to}`)\n }\n } catch (err) {\n if (hasCopied) {\n try {\n if (!destinationETag && (!destinationVersion || destinationVersion === 'null')) {\n throw new Error('S3 copy did not return an ETag or version for safe cleanup')\n }\n await client.deleteObject({\n Bucket: bucket,\n IfMatch: destinationETag,\n Key: to,\n VersionId: destinationVersion,\n })\n } catch (cleanupError) {\n req?.payload.logger.error({\n err: cleanupError,\n msg: `Failed to remove unsuccessful S3 copy at ${bucket}/${to}`,\n })\n }\n }\n throw err\n }\n}\n"],"names":["singleCopyLimit","partSize","copyS3File","acl","bucket","client","from","req","to","Error","source","headObject","Bucket","Key","size","ContentLength","undefined","copySource","encodeURIComponent","split","map","join","hasCopied","destinationETag","destinationVersion","copied","copyObject","ACL","CopySource","CopySourceIfMatch","ETag","IfNoneMatch","MetadataDirective","ServerSideEncryption","SSEKMSKeyId","TaggingDirective","CopyObjectResult","VersionId","TagSet","getObjectTagging","tagging","URLSearchParams","tag","Value","set","upload","createMultipartUpload","CacheControl","ContentDisposition","ContentEncoding","ContentLanguage","ContentType","Expires","Metadata","StorageClass","Tagging","toString","UploadId","parts","partNumber","start","end","Math","min","part","uploadPartCopy","CopySourceRange","PartNumber","CopyPartResult","push","completeMultipartUpload","MultipartUpload","Parts","err","abortMultipartUpload","cleanupError","payload","logger","error","msg","destination","deleteObject","IfMatch"],"mappings":"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"}
|
package/dist/getFile.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type * as AWS from '@aws-sdk/client-s3';
|
|
2
|
-
import type { CollectionConfig, PayloadRequest, TypeWithID } from 'payload';
|
|
2
|
+
import type { CollectionConfig, FileHandlerOperation, PayloadRequest, TypeWithID } from 'payload';
|
|
3
3
|
export type SignedDownloadsConfig = {
|
|
4
4
|
/** @default 7200 */
|
|
5
5
|
expiresIn?: number;
|
|
@@ -17,11 +17,12 @@ interface GetFileArgs {
|
|
|
17
17
|
doc?: TypeWithID;
|
|
18
18
|
filename: string;
|
|
19
19
|
incomingHeaders?: Headers;
|
|
20
|
+
operation?: FileHandlerOperation;
|
|
20
21
|
req: PayloadRequest;
|
|
21
22
|
signedDownloads: SignedDownloadsConfig;
|
|
22
23
|
uploadReference?: unknown;
|
|
23
24
|
useCompositePrefixes?: boolean;
|
|
24
25
|
}
|
|
25
|
-
export declare function getFile({ bucket, client, collection, collectionPrefix, doc, filename, incomingHeaders, req, signedDownloads, uploadReference, useCompositePrefixes, }: GetFileArgs): Promise<Response>;
|
|
26
|
+
export declare function getFile({ bucket, client, collection, collectionPrefix, doc, filename, incomingHeaders, operation, req, signedDownloads, uploadReference, useCompositePrefixes, }: GetFileArgs): Promise<Response>;
|
|
26
27
|
export {};
|
|
27
28
|
//# sourceMappingURL=getFile.d.ts.map
|
package/dist/getFile.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getFile.d.ts","sourceRoot":"","sources":["../src/getFile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,oBAAoB,CAAA;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"getFile.d.ts","sourceRoot":"","sources":["../src/getFile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,oBAAoB,CAAA;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAWjG,MAAM,MAAM,qBAAqB,GAC7B;IACE,oBAAoB;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kBAAkB,CAAC,CAAC,IAAI,EAAE;QACxB,UAAU,EAAE,gBAAgB,CAAA;QAC5B,QAAQ,EAAE,MAAM,CAAA;QAChB,GAAG,EAAE,cAAc,CAAA;KACpB,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC/B,GACD,OAAO,CAAA;AAEX,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,GAAG,CAAC,EAAE,CAAA;IACd,UAAU,EAAE,gBAAgB,CAAA;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,GAAG,CAAC,EAAE,UAAU,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,SAAS,CAAC,EAAE,oBAAoB,CAAA;IAChC,GAAG,EAAE,cAAc,CAAA;IACnB,eAAe,EAAE,qBAAqB,CAAA;IACtC,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AA8BD,wBAAsB,OAAO,CAAC,EAC5B,MAAM,EACN,MAAM,EACN,UAAU,EACV,gBAAqB,EACrB,GAAG,EACH,QAAQ,EACR,eAAe,EACf,SAAkB,EAClB,GAAG,EACH,eAAe,EACf,eAAe,EACf,oBAA4B,GAC7B,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CA+JjC"}
|
package/dist/getFile.js
CHANGED
|
@@ -14,7 +14,8 @@ const abortRequestAndDestroyStream = ({ abortController, object })=>{
|
|
|
14
14
|
object.Body.destroy();
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
|
-
export async function getFile({ bucket, client, collection, collectionPrefix = '', doc, filename, incomingHeaders, req, signedDownloads, uploadReference, useCompositePrefixes = false }) {
|
|
17
|
+
export async function getFile({ bucket, client, collection, collectionPrefix = '', doc, filename, incomingHeaders, operation = 'read', req, signedDownloads, uploadReference, useCompositePrefixes = false }) {
|
|
18
|
+
const isTransformSource = operation === 'transform';
|
|
18
19
|
let object = undefined;
|
|
19
20
|
let streamed = false;
|
|
20
21
|
const abortController = new AbortController();
|
|
@@ -42,7 +43,7 @@ export async function getFile({ bucket, client, collection, collectionPrefix = '
|
|
|
42
43
|
filename,
|
|
43
44
|
useCompositePrefixes
|
|
44
45
|
});
|
|
45
|
-
if (signedDownloads && !uploadReference) {
|
|
46
|
+
if (signedDownloads && !uploadReference && !isTransformSource) {
|
|
46
47
|
let useSignedURL = true;
|
|
47
48
|
if (typeof signedDownloads === 'object' && typeof signedDownloads.shouldUseSignedURL === 'function') {
|
|
48
49
|
useSignedURL = await signedDownloads.shouldUseSignedURL({
|
|
@@ -62,7 +63,6 @@ export async function getFile({ bucket, client, collection, collectionPrefix = '
|
|
|
62
63
|
return Response.redirect(signedUrl, 302);
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
|
-
// Get file size first for range validation and to set Content-Length header before streaming
|
|
66
66
|
const headObject = await client.headObject({
|
|
67
67
|
Bucket: bucket,
|
|
68
68
|
Key: storageFilePath
|
|
@@ -73,8 +73,7 @@ export async function getFile({ bucket, client, collection, collectionPrefix = '
|
|
|
73
73
|
status: 500
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
|
-
|
|
77
|
-
const rangeHeader = req.headers.get('range');
|
|
76
|
+
const rangeHeader = isTransformSource ? null : req.headers.get('range');
|
|
78
77
|
const rangeResult = getRangeRequestInfo({
|
|
79
78
|
fileSize,
|
|
80
79
|
rangeHeader
|
|
@@ -87,7 +86,6 @@ export async function getFile({ bucket, client, collection, collectionPrefix = '
|
|
|
87
86
|
}
|
|
88
87
|
const rangeForS3 = rangeResult.type === 'partial' ? `bytes=${rangeResult.rangeStart}-${rangeResult.rangeEnd}` : undefined;
|
|
89
88
|
let headers = new Headers(incomingHeaders);
|
|
90
|
-
// Add range-related headers from the result
|
|
91
89
|
for (const [headerKey, value] of Object.entries(rangeResult.headers)){
|
|
92
90
|
headers.append(headerKey, value);
|
|
93
91
|
}
|
|
@@ -101,12 +99,12 @@ export async function getFile({ bucket, client, collection, collectionPrefix = '
|
|
|
101
99
|
}
|
|
102
100
|
const etagFromHeaders = req.headers.get('etag') || req.headers.get('if-none-match');
|
|
103
101
|
const objectEtag = headObject.ETag;
|
|
104
|
-
if (collection.upload && typeof collection.upload === 'object' && typeof collection.upload.modifyResponseHeaders === 'function') {
|
|
102
|
+
if (!isTransformSource && collection.upload && typeof collection.upload === 'object' && typeof collection.upload.modifyResponseHeaders === 'function') {
|
|
105
103
|
headers = collection.upload.modifyResponseHeaders({
|
|
106
104
|
headers
|
|
107
105
|
}) || headers;
|
|
108
106
|
}
|
|
109
|
-
if (etagFromHeaders && etagFromHeaders === objectEtag) {
|
|
107
|
+
if (!isTransformSource && etagFromHeaders && etagFromHeaders === objectEtag) {
|
|
110
108
|
return new Response(null, {
|
|
111
109
|
headers,
|
|
112
110
|
status: 304
|
package/dist/getFile.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/getFile.ts"],"sourcesContent":["import type * as AWS from '@aws-sdk/client-s3'\nimport type { CollectionConfig, PayloadRequest, TypeWithID } from 'payload'\nimport type { Readable } from 'stream'\n\nimport { GetObjectCommand } from '@aws-sdk/client-s3'\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner'\nimport {\n buildStoragePathData,\n getFilePrefix as getDocPrefix,\n} from '@payloadcms/plugin-cloud-storage/utilities'\nimport { getRangeRequestInfo, isXmlMimeType, uploadContentSecurityPolicy } from 'payload/internal'\n\nexport type SignedDownloadsConfig =\n | {\n /** @default 7200 */\n expiresIn?: number\n shouldUseSignedURL?(args: {\n collection: CollectionConfig\n filename: string\n req: PayloadRequest\n }): boolean | Promise<boolean>\n }\n | boolean\n\ninterface GetFileArgs {\n bucket: string\n client: AWS.S3\n collection: CollectionConfig\n collectionPrefix?: string\n doc?: TypeWithID\n filename: string\n incomingHeaders?: Headers\n req: PayloadRequest\n signedDownloads: SignedDownloadsConfig\n uploadReference?: unknown\n useCompositePrefixes?: boolean\n}\n\nconst isNodeReadableStream = (body: AWS.GetObjectOutput['Body']): body is Readable => {\n return (\n typeof body === 'object' &&\n body !== null &&\n 'pipe' in body &&\n typeof body.pipe === 'function' &&\n 'destroy' in body &&\n typeof body.destroy === 'function'\n )\n}\n\nconst abortRequestAndDestroyStream = ({\n abortController,\n object,\n}: {\n abortController: AbortController\n object?: AWS.GetObjectOutput\n}) => {\n try {\n abortController.abort()\n } catch {\n /* noop */\n }\n if (object?.Body && isNodeReadableStream(object.Body)) {\n object.Body.destroy()\n }\n}\n\nexport async function getFile({\n bucket,\n client,\n collection,\n collectionPrefix = '',\n doc,\n filename,\n incomingHeaders,\n req,\n signedDownloads,\n uploadReference,\n useCompositePrefixes = false,\n}: GetFileArgs): Promise<Response> {\n let object: AWS.GetObjectOutput | undefined = undefined\n let streamed = false\n\n const abortController = new AbortController()\n if (req.signal) {\n req.signal.addEventListener('abort', () => {\n abortRequestAndDestroyStream({ abortController, object })\n })\n }\n\n try {\n const docPrefix = await getDocPrefix({\n collection,\n collectionPrefix,\n doc,\n filename,\n req,\n uploadReference,\n useCompositePrefixes,\n })\n\n const { storageFilePath } = buildStoragePathData({\n collectionPrefix,\n docPrefix,\n filename,\n useCompositePrefixes,\n })\n\n if (signedDownloads && !uploadReference) {\n let useSignedURL = true\n if (\n typeof signedDownloads === 'object' &&\n typeof signedDownloads.shouldUseSignedURL === 'function'\n ) {\n useSignedURL = await signedDownloads.shouldUseSignedURL({ collection, filename, req })\n }\n\n if (useSignedURL) {\n const command = new GetObjectCommand({ Bucket: bucket, Key: storageFilePath })\n const signedUrl = await getSignedUrl(\n client,\n command,\n typeof signedDownloads === 'object' ? signedDownloads : { expiresIn: 7200 },\n )\n return Response.redirect(signedUrl, 302)\n }\n }\n\n // Get file size first for range validation and to set Content-Length header before streaming\n const headObject = await client.headObject({\n Bucket: bucket,\n Key: storageFilePath,\n })\n const fileSize = headObject.ContentLength\n\n if (!fileSize) {\n return new Response('Internal Server Error', { status: 500 })\n }\n\n // Handle range request\n const rangeHeader = req.headers.get('range')\n const rangeResult = getRangeRequestInfo({ fileSize, rangeHeader })\n\n if (rangeResult.type === 'invalid') {\n return new Response(null, {\n headers: new Headers(rangeResult.headers),\n status: rangeResult.status,\n })\n }\n\n const rangeForS3 =\n rangeResult.type === 'partial'\n ? `bytes=${rangeResult.rangeStart}-${rangeResult.rangeEnd}`\n : undefined\n\n let headers = new Headers(incomingHeaders)\n\n // Add range-related headers from the result\n for (const [headerKey, value] of Object.entries(rangeResult.headers)) {\n headers.append(headerKey, value)\n }\n\n headers.append('Content-Type', String(headObject.ContentType))\n if (headObject.ETag) {\n headers.append('ETag', headObject.ETag)\n }\n\n // Apply a restrictive policy to XML-family responses served through Payload.\n if (isXmlMimeType(headObject.ContentType)) {\n headers.append('Content-Security-Policy', uploadContentSecurityPolicy)\n }\n\n const etagFromHeaders = req.headers.get('etag') || req.headers.get('if-none-match')\n const objectEtag = headObject.ETag\n\n if (\n collection.upload &&\n typeof collection.upload === 'object' &&\n typeof collection.upload.modifyResponseHeaders === 'function'\n ) {\n headers = collection.upload.modifyResponseHeaders({ headers }) || headers\n }\n\n if (etagFromHeaders && etagFromHeaders === objectEtag) {\n return new Response(null, {\n headers,\n status: 304,\n })\n }\n\n object = await client.getObject(\n {\n Bucket: bucket,\n Key: storageFilePath,\n Range: rangeForS3,\n },\n { abortSignal: abortController.signal },\n )\n\n if (!object.Body) {\n return new Response(null, { status: 404, statusText: 'Not Found' })\n }\n\n if (!isNodeReadableStream(object.Body)) {\n req.payload.logger.error({\n key: storageFilePath,\n msg: 'S3 object body is not a readable stream',\n })\n return new Response('Internal Server Error', { status: 500 })\n }\n\n const stream = object.Body\n stream.on('error', (err: Error) => {\n req.payload.logger.error({\n err,\n key: storageFilePath,\n msg: 'Error while streaming S3 object (aborting)',\n })\n abortRequestAndDestroyStream({ abortController, object })\n })\n\n streamed = true\n return new Response(stream, { headers, status: rangeResult.status })\n } catch (err) {\n if (\n err &&\n typeof err === 'object' &&\n (('name' in err && (err.name === 'NoSuchKey' || err.name === 'NotFound')) ||\n ('httpStatusCode' in err && err.httpStatusCode === 404))\n ) {\n return new Response(null, { status: 404, statusText: 'Not Found' })\n }\n req.payload.logger.error(err)\n return new Response('Internal Server Error', { status: 500 })\n } finally {\n if (!streamed) {\n abortRequestAndDestroyStream({ abortController, object })\n }\n }\n}\n"],"names":["GetObjectCommand","getSignedUrl","buildStoragePathData","getFilePrefix","getDocPrefix","getRangeRequestInfo","isXmlMimeType","uploadContentSecurityPolicy","isNodeReadableStream","body","pipe","destroy","abortRequestAndDestroyStream","abortController","object","abort","Body","getFile","bucket","client","collection","collectionPrefix","doc","filename","incomingHeaders","req","signedDownloads","uploadReference","useCompositePrefixes","undefined","streamed","AbortController","signal","addEventListener","docPrefix","storageFilePath","useSignedURL","shouldUseSignedURL","command","Bucket","Key","signedUrl","expiresIn","Response","redirect","headObject","fileSize","ContentLength","status","rangeHeader","headers","get","rangeResult","type","Headers","rangeForS3","rangeStart","rangeEnd","headerKey","value","Object","entries","append","String","ContentType","ETag","etagFromHeaders","objectEtag","upload","modifyResponseHeaders","getObject","Range","abortSignal","statusText","payload","logger","error","key","msg","stream","on","err","name","httpStatusCode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/getFile.ts"],"sourcesContent":["import type * as AWS from '@aws-sdk/client-s3'\nimport type { CollectionConfig, FileHandlerOperation, PayloadRequest, TypeWithID } from 'payload'\nimport type { Readable } from 'stream'\n\nimport { GetObjectCommand } from '@aws-sdk/client-s3'\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner'\nimport {\n buildStoragePathData,\n getFilePrefix as getDocPrefix,\n} from '@payloadcms/plugin-cloud-storage/utilities'\nimport { getRangeRequestInfo, isXmlMimeType, uploadContentSecurityPolicy } from 'payload/internal'\n\nexport type SignedDownloadsConfig =\n | {\n /** @default 7200 */\n expiresIn?: number\n shouldUseSignedURL?(args: {\n collection: CollectionConfig\n filename: string\n req: PayloadRequest\n }): boolean | Promise<boolean>\n }\n | boolean\n\ninterface GetFileArgs {\n bucket: string\n client: AWS.S3\n collection: CollectionConfig\n collectionPrefix?: string\n doc?: TypeWithID\n filename: string\n incomingHeaders?: Headers\n operation?: FileHandlerOperation\n req: PayloadRequest\n signedDownloads: SignedDownloadsConfig\n uploadReference?: unknown\n useCompositePrefixes?: boolean\n}\n\nconst isNodeReadableStream = (body: AWS.GetObjectOutput['Body']): body is Readable => {\n return (\n typeof body === 'object' &&\n body !== null &&\n 'pipe' in body &&\n typeof body.pipe === 'function' &&\n 'destroy' in body &&\n typeof body.destroy === 'function'\n )\n}\n\nconst abortRequestAndDestroyStream = ({\n abortController,\n object,\n}: {\n abortController: AbortController\n object?: AWS.GetObjectOutput\n}) => {\n try {\n abortController.abort()\n } catch {\n /* noop */\n }\n if (object?.Body && isNodeReadableStream(object.Body)) {\n object.Body.destroy()\n }\n}\n\nexport async function getFile({\n bucket,\n client,\n collection,\n collectionPrefix = '',\n doc,\n filename,\n incomingHeaders,\n operation = 'read',\n req,\n signedDownloads,\n uploadReference,\n useCompositePrefixes = false,\n}: GetFileArgs): Promise<Response> {\n const isTransformSource = operation === 'transform'\n let object: AWS.GetObjectOutput | undefined = undefined\n let streamed = false\n\n const abortController = new AbortController()\n if (req.signal) {\n req.signal.addEventListener('abort', () => {\n abortRequestAndDestroyStream({ abortController, object })\n })\n }\n\n try {\n const docPrefix = await getDocPrefix({\n collection,\n collectionPrefix,\n doc,\n filename,\n req,\n uploadReference,\n useCompositePrefixes,\n })\n\n const { storageFilePath } = buildStoragePathData({\n collectionPrefix,\n docPrefix,\n filename,\n useCompositePrefixes,\n })\n\n if (signedDownloads && !uploadReference && !isTransformSource) {\n let useSignedURL = true\n if (\n typeof signedDownloads === 'object' &&\n typeof signedDownloads.shouldUseSignedURL === 'function'\n ) {\n useSignedURL = await signedDownloads.shouldUseSignedURL({ collection, filename, req })\n }\n\n if (useSignedURL) {\n const command = new GetObjectCommand({ Bucket: bucket, Key: storageFilePath })\n const signedUrl = await getSignedUrl(\n client,\n command,\n typeof signedDownloads === 'object' ? signedDownloads : { expiresIn: 7200 },\n )\n return Response.redirect(signedUrl, 302)\n }\n }\n\n const headObject = await client.headObject({\n Bucket: bucket,\n Key: storageFilePath,\n })\n const fileSize = headObject.ContentLength\n\n if (!fileSize) {\n return new Response('Internal Server Error', { status: 500 })\n }\n\n const rangeHeader = isTransformSource ? null : req.headers.get('range')\n const rangeResult = getRangeRequestInfo({ fileSize, rangeHeader })\n\n if (rangeResult.type === 'invalid') {\n return new Response(null, {\n headers: new Headers(rangeResult.headers),\n status: rangeResult.status,\n })\n }\n\n const rangeForS3 =\n rangeResult.type === 'partial'\n ? `bytes=${rangeResult.rangeStart}-${rangeResult.rangeEnd}`\n : undefined\n\n let headers = new Headers(incomingHeaders)\n\n for (const [headerKey, value] of Object.entries(rangeResult.headers)) {\n headers.append(headerKey, value)\n }\n\n headers.append('Content-Type', String(headObject.ContentType))\n if (headObject.ETag) {\n headers.append('ETag', headObject.ETag)\n }\n\n // Apply a restrictive policy to XML-family responses served through Payload.\n if (isXmlMimeType(headObject.ContentType)) {\n headers.append('Content-Security-Policy', uploadContentSecurityPolicy)\n }\n\n const etagFromHeaders = req.headers.get('etag') || req.headers.get('if-none-match')\n const objectEtag = headObject.ETag\n\n if (\n !isTransformSource &&\n collection.upload &&\n typeof collection.upload === 'object' &&\n typeof collection.upload.modifyResponseHeaders === 'function'\n ) {\n headers = collection.upload.modifyResponseHeaders({ headers }) || headers\n }\n\n if (!isTransformSource && etagFromHeaders && etagFromHeaders === objectEtag) {\n return new Response(null, {\n headers,\n status: 304,\n })\n }\n\n object = await client.getObject(\n {\n Bucket: bucket,\n Key: storageFilePath,\n Range: rangeForS3,\n },\n { abortSignal: abortController.signal },\n )\n\n if (!object.Body) {\n return new Response(null, { status: 404, statusText: 'Not Found' })\n }\n\n if (!isNodeReadableStream(object.Body)) {\n req.payload.logger.error({\n key: storageFilePath,\n msg: 'S3 object body is not a readable stream',\n })\n return new Response('Internal Server Error', { status: 500 })\n }\n\n const stream = object.Body\n stream.on('error', (err: Error) => {\n req.payload.logger.error({\n err,\n key: storageFilePath,\n msg: 'Error while streaming S3 object (aborting)',\n })\n abortRequestAndDestroyStream({ abortController, object })\n })\n\n streamed = true\n return new Response(stream, { headers, status: rangeResult.status })\n } catch (err) {\n if (\n err &&\n typeof err === 'object' &&\n (('name' in err && (err.name === 'NoSuchKey' || err.name === 'NotFound')) ||\n ('httpStatusCode' in err && err.httpStatusCode === 404))\n ) {\n return new Response(null, { status: 404, statusText: 'Not Found' })\n }\n req.payload.logger.error(err)\n return new Response('Internal Server Error', { status: 500 })\n } finally {\n if (!streamed) {\n abortRequestAndDestroyStream({ abortController, object })\n }\n }\n}\n"],"names":["GetObjectCommand","getSignedUrl","buildStoragePathData","getFilePrefix","getDocPrefix","getRangeRequestInfo","isXmlMimeType","uploadContentSecurityPolicy","isNodeReadableStream","body","pipe","destroy","abortRequestAndDestroyStream","abortController","object","abort","Body","getFile","bucket","client","collection","collectionPrefix","doc","filename","incomingHeaders","operation","req","signedDownloads","uploadReference","useCompositePrefixes","isTransformSource","undefined","streamed","AbortController","signal","addEventListener","docPrefix","storageFilePath","useSignedURL","shouldUseSignedURL","command","Bucket","Key","signedUrl","expiresIn","Response","redirect","headObject","fileSize","ContentLength","status","rangeHeader","headers","get","rangeResult","type","Headers","rangeForS3","rangeStart","rangeEnd","headerKey","value","Object","entries","append","String","ContentType","ETag","etagFromHeaders","objectEtag","upload","modifyResponseHeaders","getObject","Range","abortSignal","statusText","payload","logger","error","key","msg","stream","on","err","name","httpStatusCode"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payloadcms/storage-s3",
|
|
3
|
-
"version": "4.0.0-internal.
|
|
3
|
+
"version": "4.0.0-internal.7d5c665",
|
|
4
4
|
"description": "Payload storage adapter for Amazon S3",
|
|
5
5
|
"homepage": "https://payloadcms.com",
|
|
6
6
|
"repository": {
|
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
"import": "./dist/index.js",
|
|
25
25
|
"types": "./dist/index.d.ts",
|
|
26
26
|
"default": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./copy-file": {
|
|
29
|
+
"import": "./dist/copyFile.js",
|
|
30
|
+
"types": "./dist/copyFile.d.ts",
|
|
31
|
+
"default": "./dist/copyFile.js"
|
|
27
32
|
}
|
|
28
33
|
},
|
|
29
34
|
"main": "./dist/index.js",
|
|
@@ -35,14 +40,14 @@
|
|
|
35
40
|
"@aws-sdk/client-s3": "^3.614.0",
|
|
36
41
|
"@aws-sdk/lib-storage": "^3.614.0",
|
|
37
42
|
"@aws-sdk/s3-request-presigner": "^3.614.0",
|
|
38
|
-
"@payloadcms/plugin-cloud-storage": "4.0.0-internal.
|
|
43
|
+
"@payloadcms/plugin-cloud-storage": "4.0.0-internal.7d5c665"
|
|
39
44
|
},
|
|
40
45
|
"devDependencies": {
|
|
41
46
|
"@smithy/node-http-handler": "4.0.3",
|
|
42
|
-
"payload": "4.0.0-internal.
|
|
47
|
+
"payload": "4.0.0-internal.7d5c665"
|
|
43
48
|
},
|
|
44
49
|
"peerDependencies": {
|
|
45
|
-
"payload": "4.0.0-internal.
|
|
50
|
+
"payload": "4.0.0-internal.7d5c665"
|
|
46
51
|
},
|
|
47
52
|
"engines": {
|
|
48
53
|
"node": ">=24.15.0"
|