@payloadcms/storage-s3 4.0.0-internal.f15737c → 4.0.0-internal.f2a04f7

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 CHANGED
@@ -57,4 +57,4 @@ export default buildConfig({
57
57
 
58
58
  ### Configuration Options
59
59
 
60
- See the 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.
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.
@@ -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,EAEpB,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,CA2E/B"}
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: async ({ storageFilePath })=>{
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({
@@ -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: async ({ storageFilePath }) => {\n const { deleteFile } = await import('./deleteFile.js')\n return deleteFile({\n bucket,\n client: getStorageClient(),\n storageFilePath,\n })\n },\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","collection","prefix","name","filename","urlPrefix","collectionPrefix","endpoint","uploadInstructions","enabled","Boolean","generate","access","undefined","requiresUploadReceipt","useInAdmin","handleDelete","storageFilePath","deleteFile","client","handleUpload","data","file","uploadFile","buffer","mimeType","tempFilePath","staticHandler","req","doc","headers","params","operation","uploadReference","getFile","incomingHeaders"],"mappings":"AASA,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,OAAO,CAAC,EAAEC,UAAU,EAAEC,SAAS,EAAE,EAAE,GAAwB,CAAA;YACzDC,MAAM;YAENX,aAAa,CAAC,EAAEY,QAAQ,EAAEF,QAAQG,YAAY,EAAE,EAAE,GAChDb,YAAY;oBACVG;oBACAW,kBAAkBJ;oBAClBK,UAAUV,OAAOU,QAAQ;oBACzBH;oBACAF,QAAQG;oBACRL;gBACF;YAEFQ,oBAAoB;gBAClBC,SAASC,QAAQd;gBACjBe,UAAUpB,2BAA2B;oBACnCqB,QAAQ,OAAOhB,kBAAkB,WAAWA,cAAcgB,MAAM,GAAGC;oBACnEnB;oBACAC;oBACAW,kBAAkBJ;oBAClBJ;oBACAE;gBACF;gBACAc,uBAAuB;gBACvBC,YAAY;YACd;YAEA,2EAA2E;YAC3E,qDAAqD;YACrDC,cAAc,OAAO,EAAEC,eAAe,EAAE;gBACtC,MAAM,EAAEC,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC;gBACpC,OAAOA,WAAW;oBAChBvB;oBACAwB,QAAQrB;oBACRmB;gBACF;YACF;YAEAG,cAAc,OAAO,EAAEC,IAAI,EAAEC,IAAI,EAAEL,eAAe,EAAE;gBAClD,MAAM,EAAEM,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC;gBACpC,MAAMA,WAAW;oBACf7B;oBACAC;oBACA6B,QAAQF,KAAKE,MAAM;oBACnBL,QAAQrB;oBACR2B,UAAUH,KAAKG,QAAQ;oBACvBR;oBACAS,cAAcJ,KAAKI,YAAY;gBACjC;gBAEA,OAAOL;YACT;YAEAM,eAAe,OACbC,KACA,EAAEC,GAAG,EAAEC,OAAO,EAAEC,QAAQ,EAAE3B,QAAQ,EAAE4B,SAAS,EAAEC,eAAe,EAAE,EAAE;gBAElE,MAAM,EAAEC,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC;gBACjC,OAAOA,QAAQ;oBACbvC;oBACAwB,QAAQrB;oBACRG;oBACAK,kBAAkBJ;oBAClB2B;oBACAzB;oBACA+B,iBAAiBL;oBACjBE;oBACAJ;oBACA7B;oBACAkC;oBACAjC;gBACF;YACF;QACF,CAAA;AACF"}
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"}
@@ -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/package.json CHANGED
@@ -1,13 +1,8 @@
1
1
  {
2
2
  "name": "@payloadcms/storage-s3",
3
- "version": "4.0.0-internal.f15737c",
3
+ "version": "4.0.0-internal.f2a04f7",
4
4
  "description": "Payload storage adapter for Amazon S3",
5
5
  "homepage": "https://payloadcms.com",
6
- "repository": {
7
- "type": "git",
8
- "url": "https://github.com/payloadcms/payload.git",
9
- "directory": "packages/storage-s3"
10
- },
11
6
  "license": "MIT",
12
7
  "author": "Payload <dev@payloadcms.com> (https://payloadcms.com)",
13
8
  "maintainers": [
@@ -17,32 +12,42 @@
17
12
  "url": "https://payloadcms.com"
18
13
  }
19
14
  ],
20
- "sideEffects": false,
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/payloadcms/payload.git",
18
+ "directory": "packages/storage-s3"
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
21
23
  "type": "module",
24
+ "sideEffects": false,
25
+ "main": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
22
27
  "exports": {
23
28
  ".": {
24
29
  "import": "./dist/index.js",
25
30
  "types": "./dist/index.d.ts",
26
31
  "default": "./dist/index.js"
32
+ },
33
+ "./copy-file": {
34
+ "import": "./dist/copyFile.js",
35
+ "types": "./dist/copyFile.d.ts",
36
+ "default": "./dist/copyFile.js"
27
37
  }
28
38
  },
29
- "main": "./dist/index.js",
30
- "types": "./dist/index.d.ts",
31
- "files": [
32
- "dist"
33
- ],
34
39
  "dependencies": {
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.f15737c"
43
+ "@payloadcms/plugin-cloud-storage": "4.0.0-internal.f2a04f7"
39
44
  },
40
45
  "devDependencies": {
41
46
  "@smithy/node-http-handler": "4.0.3",
42
- "payload": "4.0.0-internal.f15737c"
47
+ "payload": "4.0.0-internal.f2a04f7"
43
48
  },
44
49
  "peerDependencies": {
45
- "payload": "4.0.0-internal.f15737c"
50
+ "payload": "4.0.0-internal.f2a04f7"
46
51
  },
47
52
  "engines": {
48
53
  "node": ">=24.15.0"