@payloadcms/storage-r2 4.0.0-internal.d21c039 → 4.0.0-internal.d3909a9

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.d.ts CHANGED
@@ -5,8 +5,9 @@ interface CreateR2AdapterArgs {
5
5
  bucket: R2Bucket;
6
6
  clientUploads?: ClientUploadsConfig;
7
7
  collections: R2StorageOptions['collections'];
8
+ copyCredentials?: R2StorageOptions['copyCredentials'];
8
9
  useCompositePrefixes?: boolean;
9
10
  }
10
- export declare function createR2Adapter({ bucket, clientUploads, collections, useCompositePrefixes, }: CreateR2AdapterArgs): Adapter;
11
+ export declare function createR2Adapter({ bucket, clientUploads, collections, copyCredentials, useCompositePrefixes, }: CreateR2AdapterArgs): Adapter;
11
12
  export {};
12
13
  //# sourceMappingURL=adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,mBAAmB,EAEpB,MAAM,wCAAwC,CAAA;AAE/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAO1C,UAAU,mBAAmB;IAC3B,MAAM,EAAE,QAAQ,CAAA;IAChB,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IAC5C,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,aAAa,EACb,WAAW,EACX,oBAA4B,GAC7B,EAAE,mBAAmB,GAAG,OAAO,CA6D/B"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,mBAAmB,EAGpB,MAAM,wCAAwC,CAAA;AAE/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAO1C,UAAU,mBAAmB;IAC3B,MAAM,EAAE,QAAQ,CAAA;IAChB,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IAC5C,eAAe,CAAC,EAAE,gBAAgB,CAAC,iBAAiB,CAAC,CAAA;IACrD,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,aAAa,EACb,WAAW,EACX,eAAe,EACf,oBAA4B,GAC7B,EAAE,mBAAmB,GAAG,OAAO,CAuF/B"}
package/dist/adapter.js CHANGED
@@ -2,7 +2,7 @@ import { deleteFile } from './deleteFile.js';
2
2
  import { getFile } from './getFile.js';
3
3
  import { getHandleMultiPartUpload } from './handleMultiPartUpload.js';
4
4
  import { uploadFile } from './uploadFile.js';
5
- export function createR2Adapter({ bucket, clientUploads, collections, useCompositePrefixes = false }) {
5
+ export function createR2Adapter({ bucket, clientUploads, collections, copyCredentials, useCompositePrefixes = false }) {
6
6
  const access = typeof clientUploads === 'object' ? clientUploads.access : undefined;
7
7
  const uploadInstructions = {
8
8
  adminHandler: {
@@ -31,25 +31,56 @@ export function createR2Adapter({ bucket, clientUploads, collections, useComposi
31
31
  requiresUploadReceipt: true,
32
32
  useInAdmin: true
33
33
  };
34
+ const deleteStoredFile = ({ storageFilePath })=>deleteFile({
35
+ bucket,
36
+ storageFilePath
37
+ });
34
38
  return ({ collection, prefix = '' })=>({
35
39
  name: 'r2',
36
40
  uploadInstructions,
37
- handleDelete: ({ storageFilePath })=>deleteFile({
38
- bucket,
39
- storageFilePath
40
- }),
41
+ copyFile: async ({ from, req, to })=>{
42
+ if (!copyCredentials) {
43
+ throw new Error('R2 file copy requires S3 API credentials');
44
+ }
45
+ const [{ S3 }, { copyS3File }] = await Promise.all([
46
+ import('@aws-sdk/client-s3'),
47
+ import('@payloadcms/storage-s3/copy-file')
48
+ ]);
49
+ const client = new S3({
50
+ credentials: {
51
+ accessKeyId: copyCredentials.accessKeyId,
52
+ secretAccessKey: copyCredentials.secretAccessKey
53
+ },
54
+ endpoint: `https://${copyCredentials.accountId}.r2.cloudflarestorage.com`,
55
+ region: 'auto'
56
+ });
57
+ try {
58
+ await copyS3File({
59
+ bucket: copyCredentials.bucket,
60
+ client,
61
+ from,
62
+ req,
63
+ to
64
+ });
65
+ } finally{
66
+ client.destroy();
67
+ }
68
+ },
69
+ deleteFile: deleteStoredFile,
70
+ handleDelete: deleteStoredFile,
41
71
  handleUpload: ({ file, storageFilePath })=>uploadFile({
42
72
  bucket,
43
73
  buffer: file.buffer,
44
74
  mimeType: file.mimeType,
45
75
  storageFilePath
46
76
  }),
47
- staticHandler: (req, { doc, headers, params: { filename, uploadReference } })=>getFile({
77
+ staticHandler: (req, { doc, headers, params: { filename, operation, uploadReference } })=>getFile({
48
78
  bucket,
49
79
  collection,
50
80
  doc,
51
81
  filename,
52
82
  incomingHeaders: headers,
83
+ operation,
53
84
  prefix,
54
85
  req,
55
86
  uploadReference,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapter.ts"],"sourcesContent":["import type {\n Adapter,\n ClientUploadsConfig,\n GeneratedAdapter,\n} from '@payloadcms/plugin-cloud-storage/types'\n\nimport type { R2StorageOptions } from './index.js'\nimport type { R2Bucket } from './types.js'\n\nimport { deleteFile } from './deleteFile.js'\nimport { getFile } from './getFile.js'\nimport { getHandleMultiPartUpload } from './handleMultiPartUpload.js'\nimport { uploadFile } from './uploadFile.js'\n\ninterface CreateR2AdapterArgs {\n bucket: R2Bucket\n clientUploads?: ClientUploadsConfig\n collections: R2StorageOptions['collections']\n useCompositePrefixes?: boolean\n}\n\nexport function createR2Adapter({\n bucket,\n clientUploads,\n collections,\n useCompositePrefixes = false,\n}: CreateR2AdapterArgs): Adapter {\n const access = typeof clientUploads === 'object' ? clientUploads.access : undefined\n const uploadInstructions: GeneratedAdapter['uploadInstructions'] = {\n adminHandler: {\n path: '@payloadcms/storage-r2/client#R2ClientUploadHandler',\n },\n enabled: Boolean(clientUploads),\n endpoint: {\n handler: getHandleMultiPartUpload({\n access,\n bucket,\n collections,\n useCompositePrefixes,\n }),\n path: '/storage-r2-multi-part-upload',\n },\n generate: ({ filename, filesize, mimeType }) => ({\n name: 'uploadToR2',\n type: 'dispatch',\n file: {\n filename,\n mimeType,\n size: filesize,\n uploadReference: {},\n },\n }),\n requiresUploadReceipt: true,\n useInAdmin: true,\n }\n\n return ({ collection, prefix = '' }): GeneratedAdapter => ({\n name: 'r2',\n uploadInstructions,\n\n handleDelete: ({ storageFilePath }) =>\n deleteFile({\n bucket,\n storageFilePath,\n }),\n\n handleUpload: ({ file, storageFilePath }) =>\n uploadFile({\n bucket,\n buffer: file.buffer,\n mimeType: file.mimeType,\n storageFilePath,\n }),\n\n staticHandler: (req, { doc, headers, params: { filename, uploadReference } }) =>\n getFile({\n bucket,\n collection,\n doc,\n filename,\n incomingHeaders: headers,\n prefix,\n req,\n uploadReference,\n useCompositePrefixes,\n }),\n })\n}\n"],"names":["deleteFile","getFile","getHandleMultiPartUpload","uploadFile","createR2Adapter","bucket","clientUploads","collections","useCompositePrefixes","access","undefined","uploadInstructions","adminHandler","path","enabled","Boolean","endpoint","handler","generate","filename","filesize","mimeType","name","type","file","size","uploadReference","requiresUploadReceipt","useInAdmin","collection","prefix","handleDelete","storageFilePath","handleUpload","buffer","staticHandler","req","doc","headers","params","incomingHeaders"],"mappings":"AASA,SAASA,UAAU,QAAQ,kBAAiB;AAC5C,SAASC,OAAO,QAAQ,eAAc;AACtC,SAASC,wBAAwB,QAAQ,6BAA4B;AACrE,SAASC,UAAU,QAAQ,kBAAiB;AAS5C,OAAO,SAASC,gBAAgB,EAC9BC,MAAM,EACNC,aAAa,EACbC,WAAW,EACXC,uBAAuB,KAAK,EACR;IACpB,MAAMC,SAAS,OAAOH,kBAAkB,WAAWA,cAAcG,MAAM,GAAGC;IAC1E,MAAMC,qBAA6D;QACjEC,cAAc;YACZC,MAAM;QACR;QACAC,SAASC,QAAQT;QACjBU,UAAU;YACRC,SAASf,yBAAyB;gBAChCO;gBACAJ;gBACAE;gBACAC;YACF;YACAK,MAAM;QACR;QACAK,UAAU,CAAC,EAAEC,QAAQ,EAAEC,QAAQ,EAAEC,QAAQ,EAAE,GAAM,CAAA;gBAC/CC,MAAM;gBACNC,MAAM;gBACNC,MAAM;oBACJL;oBACAE;oBACAI,MAAML;oBACNM,iBAAiB,CAAC;gBACpB;YACF,CAAA;QACAC,uBAAuB;QACvBC,YAAY;IACd;IAEA,OAAO,CAAC,EAAEC,UAAU,EAAEC,SAAS,EAAE,EAAE,GAAwB,CAAA;YACzDR,MAAM;YACNX;YAEAoB,cAAc,CAAC,EAAEC,eAAe,EAAE,GAChChC,WAAW;oBACTK;oBACA2B;gBACF;YAEFC,cAAc,CAAC,EAAET,IAAI,EAAEQ,eAAe,EAAE,GACtC7B,WAAW;oBACTE;oBACA6B,QAAQV,KAAKU,MAAM;oBACnBb,UAAUG,KAAKH,QAAQ;oBACvBW;gBACF;YAEFG,eAAe,CAACC,KAAK,EAAEC,GAAG,EAAEC,OAAO,EAAEC,QAAQ,EAAEpB,QAAQ,EAAEO,eAAe,EAAE,EAAE,GAC1EzB,QAAQ;oBACNI;oBACAwB;oBACAQ;oBACAlB;oBACAqB,iBAAiBF;oBACjBR;oBACAM;oBACAV;oBACAlB;gBACF;QACJ,CAAA;AACF"}
1
+ {"version":3,"sources":["../src/adapter.ts"],"sourcesContent":["import type {\n Adapter,\n ClientUploadsConfig,\n DeleteFile,\n GeneratedAdapter,\n} from '@payloadcms/plugin-cloud-storage/types'\n\nimport type { R2StorageOptions } from './index.js'\nimport type { R2Bucket } from './types.js'\n\nimport { deleteFile } from './deleteFile.js'\nimport { getFile } from './getFile.js'\nimport { getHandleMultiPartUpload } from './handleMultiPartUpload.js'\nimport { uploadFile } from './uploadFile.js'\n\ninterface CreateR2AdapterArgs {\n bucket: R2Bucket\n clientUploads?: ClientUploadsConfig\n collections: R2StorageOptions['collections']\n copyCredentials?: R2StorageOptions['copyCredentials']\n useCompositePrefixes?: boolean\n}\n\nexport function createR2Adapter({\n bucket,\n clientUploads,\n collections,\n copyCredentials,\n useCompositePrefixes = false,\n}: CreateR2AdapterArgs): Adapter {\n const access = typeof clientUploads === 'object' ? clientUploads.access : undefined\n const uploadInstructions: GeneratedAdapter['uploadInstructions'] = {\n adminHandler: {\n path: '@payloadcms/storage-r2/client#R2ClientUploadHandler',\n },\n enabled: Boolean(clientUploads),\n endpoint: {\n handler: getHandleMultiPartUpload({\n access,\n bucket,\n collections,\n useCompositePrefixes,\n }),\n path: '/storage-r2-multi-part-upload',\n },\n generate: ({ filename, filesize, mimeType }) => ({\n name: 'uploadToR2',\n type: 'dispatch',\n file: {\n filename,\n mimeType,\n size: filesize,\n uploadReference: {},\n },\n }),\n requiresUploadReceipt: true,\n useInAdmin: true,\n }\n\n const deleteStoredFile: DeleteFile = ({ storageFilePath }) =>\n deleteFile({ bucket, storageFilePath })\n\n return ({ collection, prefix = '' }): GeneratedAdapter => ({\n name: 'r2',\n uploadInstructions,\n\n copyFile: async ({ from, req, to }) => {\n if (!copyCredentials) {\n throw new Error('R2 file copy requires S3 API credentials')\n }\n\n const [{ S3 }, { copyS3File }] = await Promise.all([\n import('@aws-sdk/client-s3'),\n import('@payloadcms/storage-s3/copy-file'),\n ])\n const client = new S3({\n credentials: {\n accessKeyId: copyCredentials.accessKeyId,\n secretAccessKey: copyCredentials.secretAccessKey,\n },\n endpoint: `https://${copyCredentials.accountId}.r2.cloudflarestorage.com`,\n region: 'auto',\n })\n\n try {\n await copyS3File({ bucket: copyCredentials.bucket, client, from, req, to })\n } finally {\n client.destroy()\n }\n },\n\n deleteFile: deleteStoredFile,\n handleDelete: deleteStoredFile,\n\n handleUpload: ({ file, storageFilePath }) =>\n uploadFile({\n bucket,\n buffer: file.buffer,\n mimeType: file.mimeType,\n storageFilePath,\n }),\n\n staticHandler: (req, { doc, headers, params: { filename, operation, uploadReference } }) =>\n getFile({\n bucket,\n collection,\n doc,\n filename,\n incomingHeaders: headers,\n operation,\n prefix,\n req,\n uploadReference,\n useCompositePrefixes,\n }),\n })\n}\n"],"names":["deleteFile","getFile","getHandleMultiPartUpload","uploadFile","createR2Adapter","bucket","clientUploads","collections","copyCredentials","useCompositePrefixes","access","undefined","uploadInstructions","adminHandler","path","enabled","Boolean","endpoint","handler","generate","filename","filesize","mimeType","name","type","file","size","uploadReference","requiresUploadReceipt","useInAdmin","deleteStoredFile","storageFilePath","collection","prefix","copyFile","from","req","to","Error","S3","copyS3File","Promise","all","client","credentials","accessKeyId","secretAccessKey","accountId","region","destroy","handleDelete","handleUpload","buffer","staticHandler","doc","headers","params","operation","incomingHeaders"],"mappings":"AAUA,SAASA,UAAU,QAAQ,kBAAiB;AAC5C,SAASC,OAAO,QAAQ,eAAc;AACtC,SAASC,wBAAwB,QAAQ,6BAA4B;AACrE,SAASC,UAAU,QAAQ,kBAAiB;AAU5C,OAAO,SAASC,gBAAgB,EAC9BC,MAAM,EACNC,aAAa,EACbC,WAAW,EACXC,eAAe,EACfC,uBAAuB,KAAK,EACR;IACpB,MAAMC,SAAS,OAAOJ,kBAAkB,WAAWA,cAAcI,MAAM,GAAGC;IAC1E,MAAMC,qBAA6D;QACjEC,cAAc;YACZC,MAAM;QACR;QACAC,SAASC,QAAQV;QACjBW,UAAU;YACRC,SAAShB,yBAAyB;gBAChCQ;gBACAL;gBACAE;gBACAE;YACF;YACAK,MAAM;QACR;QACAK,UAAU,CAAC,EAAEC,QAAQ,EAAEC,QAAQ,EAAEC,QAAQ,EAAE,GAAM,CAAA;gBAC/CC,MAAM;gBACNC,MAAM;gBACNC,MAAM;oBACJL;oBACAE;oBACAI,MAAML;oBACNM,iBAAiB,CAAC;gBACpB;YACF,CAAA;QACAC,uBAAuB;QACvBC,YAAY;IACd;IAEA,MAAMC,mBAA+B,CAAC,EAAEC,eAAe,EAAE,GACvD/B,WAAW;YAAEK;YAAQ0B;QAAgB;IAEvC,OAAO,CAAC,EAAEC,UAAU,EAAEC,SAAS,EAAE,EAAE,GAAwB,CAAA;YACzDV,MAAM;YACNX;YAEAsB,UAAU,OAAO,EAAEC,IAAI,EAAEC,GAAG,EAAEC,EAAE,EAAE;gBAChC,IAAI,CAAC7B,iBAAiB;oBACpB,MAAM,IAAI8B,MAAM;gBAClB;gBAEA,MAAM,CAAC,EAAEC,EAAE,EAAE,EAAE,EAAEC,UAAU,EAAE,CAAC,GAAG,MAAMC,QAAQC,GAAG,CAAC;oBACjD,MAAM,CAAC;oBACP,MAAM,CAAC;iBACR;gBACD,MAAMC,SAAS,IAAIJ,GAAG;oBACpBK,aAAa;wBACXC,aAAarC,gBAAgBqC,WAAW;wBACxCC,iBAAiBtC,gBAAgBsC,eAAe;oBAClD;oBACA7B,UAAU,CAAC,QAAQ,EAAET,gBAAgBuC,SAAS,CAAC,yBAAyB,CAAC;oBACzEC,QAAQ;gBACV;gBAEA,IAAI;oBACF,MAAMR,WAAW;wBAAEnC,QAAQG,gBAAgBH,MAAM;wBAAEsC;wBAAQR;wBAAMC;wBAAKC;oBAAG;gBAC3E,SAAU;oBACRM,OAAOM,OAAO;gBAChB;YACF;YAEAjD,YAAY8B;YACZoB,cAAcpB;YAEdqB,cAAc,CAAC,EAAE1B,IAAI,EAAEM,eAAe,EAAE,GACtC5B,WAAW;oBACTE;oBACA+C,QAAQ3B,KAAK2B,MAAM;oBACnB9B,UAAUG,KAAKH,QAAQ;oBACvBS;gBACF;YAEFsB,eAAe,CAACjB,KAAK,EAAEkB,GAAG,EAAEC,OAAO,EAAEC,QAAQ,EAAEpC,QAAQ,EAAEqC,SAAS,EAAE9B,eAAe,EAAE,EAAE,GACrF1B,QAAQ;oBACNI;oBACA2B;oBACAsB;oBACAlC;oBACAsC,iBAAiBH;oBACjBE;oBACAxB;oBACAG;oBACAT;oBACAlB;gBACF;QACJ,CAAA;AACF"}
package/dist/getFile.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CollectionConfig, PayloadRequest, TypeWithID } from 'payload';
1
+ import type { CollectionConfig, FileHandlerOperation, PayloadRequest, TypeWithID } from 'payload';
2
2
  import type { R2Bucket } from './types.js';
3
3
  interface GetFileArgs {
4
4
  bucket: R2Bucket;
@@ -6,11 +6,12 @@ interface GetFileArgs {
6
6
  doc?: TypeWithID;
7
7
  filename: string;
8
8
  incomingHeaders?: Headers;
9
+ operation?: FileHandlerOperation;
9
10
  prefix: string;
10
11
  req: PayloadRequest;
11
12
  uploadReference?: unknown;
12
13
  useCompositePrefixes?: boolean;
13
14
  }
14
- export declare function getFile({ bucket, collection, doc, filename, incomingHeaders, prefix, req, uploadReference, useCompositePrefixes, }: GetFileArgs): Promise<Response>;
15
+ export declare function getFile({ bucket, collection, doc, filename, incomingHeaders, operation, prefix, req, uploadReference, useCompositePrefixes, }: GetFileArgs): Promise<Response>;
15
16
  export {};
16
17
  //# sourceMappingURL=getFile.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"getFile.d.ts","sourceRoot":"","sources":["../src/getFile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAQ3E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAE1C,UAAU,WAAW;IACnB,MAAM,EAAE,QAAQ,CAAA;IAChB,UAAU,EAAE,gBAAgB,CAAA;IAC5B,GAAG,CAAC,EAAE,UAAU,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,cAAc,CAAA;IACnB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAID,wBAAsB,OAAO,CAAC,EAC5B,MAAM,EACN,UAAU,EACV,GAAG,EACH,QAAQ,EACR,eAAe,EACf,MAAW,EACX,GAAG,EACH,eAAe,EACf,oBAA4B,GAC7B,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwHjC"}
1
+ {"version":3,"file":"getFile.d.ts","sourceRoot":"","sources":["../src/getFile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAQjG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAE1C,UAAU,WAAW;IACnB,MAAM,EAAE,QAAQ,CAAA;IAChB,UAAU,EAAE,gBAAgB,CAAA;IAC5B,GAAG,CAAC,EAAE,UAAU,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,SAAS,CAAC,EAAE,oBAAoB,CAAA;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,cAAc,CAAA;IACnB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAID,wBAAsB,OAAO,CAAC,EAC5B,MAAM,EACN,UAAU,EACV,GAAG,EACH,QAAQ,EACR,eAAe,EACf,SAAkB,EAClB,MAAW,EACX,GAAG,EACH,eAAe,EACf,oBAA4B,GAC7B,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwHjC"}
package/dist/getFile.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { buildStoragePathData, getFilePrefix as getDocPrefix } from '@payloadcms/plugin-cloud-storage/utilities';
2
2
  import { getRangeRequestInfo, isXmlMimeType, uploadContentSecurityPolicy } from 'payload/internal';
3
3
  const isMiniflare = process.env.NODE_ENV === 'development';
4
- export async function getFile({ bucket, collection, doc, filename, incomingHeaders, prefix = '', req, uploadReference, useCompositePrefixes = false }) {
4
+ export async function getFile({ bucket, collection, doc, filename, incomingHeaders, operation = 'read', prefix = '', req, uploadReference, useCompositePrefixes = false }) {
5
+ const isTransformSource = operation === 'transform';
5
6
  try {
6
7
  const docPrefix = await getDocPrefix({
7
8
  collection,
@@ -27,14 +28,14 @@ export async function getFile({ bucket, collection, doc, filename, incomingHeade
27
28
  });
28
29
  }
29
30
  const fileSize = headObj.size;
30
- // Don't return large file uploads back to the client, or the Worker will run out of memory
31
- if (fileSize > 50 * 1024 * 1024 && uploadReference) {
31
+ // Don't return large file uploads back to the client, or the Worker will run out of memory.
32
+ // Skipped for `transform`, which needs the real bytes and would otherwise silently corrupt.
33
+ if (fileSize > 50 * 1024 * 1024 && uploadReference && !isTransformSource) {
32
34
  return new Response(null, {
33
35
  status: 200
34
36
  });
35
37
  }
36
- // Handle range request
37
- const rangeHeader = req.headers.get('range');
38
+ const rangeHeader = isTransformSource ? null : req.headers.get('range');
38
39
  const rangeResult = getRangeRequestInfo({
39
40
  fileSize,
40
41
  rangeHeader
@@ -45,7 +46,6 @@ export async function getFile({ bucket, collection, doc, filename, incomingHeade
45
46
  status: rangeResult.status
46
47
  });
47
48
  }
48
- // Get object with range if needed
49
49
  // Due to https://github.com/cloudflare/workers-sdk/issues/6047
50
50
  // We cannot send a Headers instance to Miniflare
51
51
  const obj = rangeResult.type === 'partial' && !isMiniflare ? await bucket?.get(storageFilePath, {
@@ -61,13 +61,10 @@ export async function getFile({ bucket, collection, doc, filename, incomingHeade
61
61
  });
62
62
  }
63
63
  let headers = new Headers(incomingHeaders);
64
- // Add range-related headers from the result
65
64
  for (const [headerKey, value] of Object.entries(rangeResult.headers)){
66
65
  headers.append(headerKey, value);
67
66
  }
68
- // Add R2-specific headers
69
67
  if (isMiniflare) {
70
- // In development with Miniflare, manually set headers from httpMetadata
71
68
  const metadata = obj.httpMetadata;
72
69
  if (metadata?.cacheControl) {
73
70
  headers.set('Cache-Control', metadata.cacheControl);
@@ -87,18 +84,18 @@ export async function getFile({ bucket, collection, doc, filename, incomingHeade
87
84
  } else {
88
85
  obj.writeHttpMetadata(headers);
89
86
  }
90
- // Apply a restrictive policy to XML-family responses served through Payload.
91
87
  const contentType = headers.get('Content-Type');
88
+ // Apply a restrictive policy to XML-family responses served through Payload.
92
89
  if (isXmlMimeType(contentType)) {
93
90
  headers.set('Content-Security-Policy', uploadContentSecurityPolicy);
94
91
  }
95
92
  const etagFromHeaders = req.headers.get('etag') || req.headers.get('if-none-match');
96
- if (collection.upload && typeof collection.upload === 'object' && typeof collection.upload.modifyResponseHeaders === 'function') {
93
+ if (!isTransformSource && collection.upload && typeof collection.upload === 'object' && typeof collection.upload.modifyResponseHeaders === 'function') {
97
94
  headers = collection.upload.modifyResponseHeaders({
98
95
  headers
99
96
  }) || headers;
100
97
  }
101
- if (etagFromHeaders && etagFromHeaders === obj.etag) {
98
+ if (!isTransformSource && etagFromHeaders && etagFromHeaders === obj.etag) {
102
99
  return new Response(null, {
103
100
  headers,
104
101
  status: 304
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/getFile.ts"],"sourcesContent":["import type { CollectionConfig, PayloadRequest, TypeWithID } from 'payload'\n\nimport {\n buildStoragePathData,\n getFilePrefix as getDocPrefix,\n} from '@payloadcms/plugin-cloud-storage/utilities'\nimport { getRangeRequestInfo, isXmlMimeType, uploadContentSecurityPolicy } from 'payload/internal'\n\nimport type { R2Bucket } from './types.js'\n\ninterface GetFileArgs {\n bucket: R2Bucket\n collection: CollectionConfig\n doc?: TypeWithID\n filename: string\n incomingHeaders?: Headers\n prefix: string\n req: PayloadRequest\n uploadReference?: unknown\n useCompositePrefixes?: boolean\n}\n\nconst isMiniflare = process.env.NODE_ENV === 'development'\n\nexport async function getFile({\n bucket,\n collection,\n doc,\n filename,\n incomingHeaders,\n prefix = '',\n req,\n uploadReference,\n useCompositePrefixes = false,\n}: GetFileArgs): Promise<Response> {\n try {\n const docPrefix = await getDocPrefix({\n collection,\n collectionPrefix: prefix,\n doc,\n filename,\n req,\n uploadReference,\n useCompositePrefixes,\n })\n\n const { storageFilePath } = buildStoragePathData({\n collectionPrefix: prefix,\n docPrefix,\n filename,\n useCompositePrefixes,\n })\n\n // Get file size for range validation\n const headObj = await bucket?.head(storageFilePath)\n if (!headObj) {\n return new Response(null, { status: 404, statusText: 'Not Found' })\n }\n\n const fileSize = headObj.size\n\n // Don't return large file uploads back to the client, or the Worker will run out of memory\n if (fileSize > 50 * 1024 * 1024 && uploadReference) {\n return new Response(null, { status: 200 })\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 // Get object with range if needed\n // Due to https://github.com/cloudflare/workers-sdk/issues/6047\n // We cannot send a Headers instance to Miniflare\n const obj =\n rangeResult.type === 'partial' && !isMiniflare\n ? await bucket?.get(storageFilePath, {\n range: {\n length: rangeResult.rangeEnd - rangeResult.rangeStart + 1,\n offset: rangeResult.rangeStart,\n },\n })\n : await bucket?.get(storageFilePath)\n\n if (!obj || obj.body == undefined) {\n return new Response(null, { status: 404, statusText: 'Not Found' })\n }\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 // Add R2-specific headers\n if (isMiniflare) {\n // In development with Miniflare, manually set headers from httpMetadata\n const metadata = obj.httpMetadata\n if (metadata?.cacheControl) {\n headers.set('Cache-Control', metadata.cacheControl)\n }\n if (metadata?.contentDisposition) {\n headers.set('Content-Disposition', metadata.contentDisposition)\n }\n if (metadata?.contentEncoding) {\n headers.set('Content-Encoding', metadata.contentEncoding)\n }\n if (metadata?.contentLanguage) {\n headers.set('Content-Language', metadata.contentLanguage)\n }\n if (metadata?.contentType) {\n headers.set('Content-Type', metadata.contentType)\n }\n } else {\n obj.writeHttpMetadata(headers)\n }\n\n // Apply a restrictive policy to XML-family responses served through Payload.\n const contentType = headers.get('Content-Type')\n if (isXmlMimeType(contentType)) {\n headers.set('Content-Security-Policy', uploadContentSecurityPolicy)\n }\n\n const etagFromHeaders = req.headers.get('etag') || req.headers.get('if-none-match')\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 === obj.etag) {\n return new Response(null, {\n headers,\n status: 304,\n })\n }\n\n return new Response(obj.body, {\n headers,\n status: rangeResult.status,\n })\n } catch (_err: unknown) {\n return new Response('Internal Server Error', { status: 500 })\n }\n}\n"],"names":["buildStoragePathData","getFilePrefix","getDocPrefix","getRangeRequestInfo","isXmlMimeType","uploadContentSecurityPolicy","isMiniflare","process","env","NODE_ENV","getFile","bucket","collection","doc","filename","incomingHeaders","prefix","req","uploadReference","useCompositePrefixes","docPrefix","collectionPrefix","storageFilePath","headObj","head","Response","status","statusText","fileSize","size","rangeHeader","headers","get","rangeResult","type","Headers","obj","range","length","rangeEnd","rangeStart","offset","body","undefined","headerKey","value","Object","entries","append","metadata","httpMetadata","cacheControl","set","contentDisposition","contentEncoding","contentLanguage","contentType","writeHttpMetadata","etagFromHeaders","upload","modifyResponseHeaders","etag","_err"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/getFile.ts"],"sourcesContent":["import type { CollectionConfig, FileHandlerOperation, PayloadRequest, TypeWithID } from 'payload'\n\nimport {\n buildStoragePathData,\n getFilePrefix as getDocPrefix,\n} from '@payloadcms/plugin-cloud-storage/utilities'\nimport { getRangeRequestInfo, isXmlMimeType, uploadContentSecurityPolicy } from 'payload/internal'\n\nimport type { R2Bucket } from './types.js'\n\ninterface GetFileArgs {\n bucket: R2Bucket\n collection: CollectionConfig\n doc?: TypeWithID\n filename: string\n incomingHeaders?: Headers\n operation?: FileHandlerOperation\n prefix: string\n req: PayloadRequest\n uploadReference?: unknown\n useCompositePrefixes?: boolean\n}\n\nconst isMiniflare = process.env.NODE_ENV === 'development'\n\nexport async function getFile({\n bucket,\n collection,\n doc,\n filename,\n incomingHeaders,\n operation = 'read',\n prefix = '',\n req,\n uploadReference,\n useCompositePrefixes = false,\n}: GetFileArgs): Promise<Response> {\n const isTransformSource = operation === 'transform'\n\n try {\n const docPrefix = await getDocPrefix({\n collection,\n collectionPrefix: prefix,\n doc,\n filename,\n req,\n uploadReference,\n useCompositePrefixes,\n })\n\n const { storageFilePath } = buildStoragePathData({\n collectionPrefix: prefix,\n docPrefix,\n filename,\n useCompositePrefixes,\n })\n\n // Get file size for range validation\n const headObj = await bucket?.head(storageFilePath)\n if (!headObj) {\n return new Response(null, { status: 404, statusText: 'Not Found' })\n }\n\n const fileSize = headObj.size\n\n // Don't return large file uploads back to the client, or the Worker will run out of memory.\n // Skipped for `transform`, which needs the real bytes and would otherwise silently corrupt.\n if (fileSize > 50 * 1024 * 1024 && uploadReference && !isTransformSource) {\n return new Response(null, { status: 200 })\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 // Due to https://github.com/cloudflare/workers-sdk/issues/6047\n // We cannot send a Headers instance to Miniflare\n const obj =\n rangeResult.type === 'partial' && !isMiniflare\n ? await bucket?.get(storageFilePath, {\n range: {\n length: rangeResult.rangeEnd - rangeResult.rangeStart + 1,\n offset: rangeResult.rangeStart,\n },\n })\n : await bucket?.get(storageFilePath)\n\n if (!obj || obj.body == undefined) {\n return new Response(null, { status: 404, statusText: 'Not Found' })\n }\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 if (isMiniflare) {\n const metadata = obj.httpMetadata\n if (metadata?.cacheControl) {\n headers.set('Cache-Control', metadata.cacheControl)\n }\n if (metadata?.contentDisposition) {\n headers.set('Content-Disposition', metadata.contentDisposition)\n }\n if (metadata?.contentEncoding) {\n headers.set('Content-Encoding', metadata.contentEncoding)\n }\n if (metadata?.contentLanguage) {\n headers.set('Content-Language', metadata.contentLanguage)\n }\n if (metadata?.contentType) {\n headers.set('Content-Type', metadata.contentType)\n }\n } else {\n obj.writeHttpMetadata(headers)\n }\n\n const contentType = headers.get('Content-Type')\n\n // Apply a restrictive policy to XML-family responses served through Payload.\n if (isXmlMimeType(contentType)) {\n headers.set('Content-Security-Policy', uploadContentSecurityPolicy)\n }\n\n const etagFromHeaders = req.headers.get('etag') || req.headers.get('if-none-match')\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 === obj.etag) {\n return new Response(null, {\n headers,\n status: 304,\n })\n }\n\n return new Response(obj.body, {\n headers,\n status: rangeResult.status,\n })\n } catch (_err: unknown) {\n return new Response('Internal Server Error', { status: 500 })\n }\n}\n"],"names":["buildStoragePathData","getFilePrefix","getDocPrefix","getRangeRequestInfo","isXmlMimeType","uploadContentSecurityPolicy","isMiniflare","process","env","NODE_ENV","getFile","bucket","collection","doc","filename","incomingHeaders","operation","prefix","req","uploadReference","useCompositePrefixes","isTransformSource","docPrefix","collectionPrefix","storageFilePath","headObj","head","Response","status","statusText","fileSize","size","rangeHeader","headers","get","rangeResult","type","Headers","obj","range","length","rangeEnd","rangeStart","offset","body","undefined","headerKey","value","Object","entries","append","metadata","httpMetadata","cacheControl","set","contentDisposition","contentEncoding","contentLanguage","contentType","writeHttpMetadata","etagFromHeaders","upload","modifyResponseHeaders","etag","_err"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"handleMultiPartUpload.d.ts","sourceRoot":"","sources":["../src/handleMultiPartUpload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAUvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAyD,MAAM,YAAY,CAAA;AAEjG,KAAK,IAAI,GAAG;IACV,MAAM,CAAC,EAAE,wBAAwB,CAAA;IACjC,MAAM,EAAE,QAAQ,CAAA;IAChB,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IAC5C,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B,CAAA;AAGD,eAAO,MAAM,wBAAwB,0DAC6B,IAAI,KAAG,cAkFtE,CAAA"}
1
+ {"version":3,"file":"handleMultiPartUpload.d.ts","sourceRoot":"","sources":["../src/handleMultiPartUpload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAUvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAyD,MAAM,YAAY,CAAA;AAEjG,KAAK,IAAI,GAAG;IACV,MAAM,CAAC,EAAE,wBAAwB,CAAA;IACjC,MAAM,EAAE,QAAQ,CAAA;IAChB,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IAC5C,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B,CAAA;AAGD,eAAO,MAAM,wBAAwB,0DAC6B,IAAI,KAAG,cAqFtE,CAAA"}
@@ -45,6 +45,9 @@ export const getHandleMultiPartUpload = ({ access, bucket, collections, useCompo
45
45
  if (isNaN(multipartNumber)) {
46
46
  // Upload complete
47
47
  const object = await multipartUpload.complete(await request.json());
48
+ if (object.key !== multipartKey) {
49
+ throw new APIError('Uploaded source was stored at an unexpected location.', 400);
50
+ }
48
51
  return new Response(object.key, {
49
52
  status: 200
50
53
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/handleMultiPartUpload.ts"],"sourcesContent":["import type { PayloadHandler, UploadInstructionsAccess } from 'payload'\n\nimport { resolveSignedURLKey } from '@payloadcms/plugin-cloud-storage/utilities'\nimport { APIError, Forbidden } from 'payload'\nimport {\n assertClientUploadAccess,\n assertClientUploadAllowed,\n verifyClientUploadReceipt,\n} from 'payload/internal'\n\nimport type { R2StorageOptions } from './index.js'\nimport type { R2Bucket, R2StorageMultipartUploadHandlerParams, R2UploadedPart } from './types.js'\n\ntype Args = {\n access?: UploadInstructionsAccess\n bucket: R2Bucket\n collections: R2StorageOptions['collections']\n useCompositePrefixes?: boolean\n}\n\n// Adapted from https://developers.cloudflare.com/r2/api/workers/workers-multipart-usage/\nexport const getHandleMultiPartUpload =\n ({ access, bucket, collections, useCompositePrefixes = false }: Args): PayloadHandler =>\n async (req) => {\n const params = Object.fromEntries(req.searchParams) as R2StorageMultipartUploadHandlerParams\n const collectionSlug = params.collection\n const filetype = params.fileType\n\n await assertClientUploadAccess({ collectionSlug, req })\n\n const collectionConfig = collections[collectionSlug]\n if (!collectionConfig) {\n throw new APIError(`Collection ${collectionSlug} was not found in R2 Storage options`)\n }\n\n if (access && !(await access({ collectionSlug, req }))) {\n throw new Forbidden(req.t)\n }\n\n assertClientUploadAllowed({\n collection: req.payload.collections[collectionSlug]?.config,\n filename: params.fileName,\n mimeType: filetype,\n })\n\n const multipartId = params.multipartId\n const multipartKey = params.multipartKey\n const multipartNumber = parseInt(params.multipartNumber || '')\n const collectionPrefix = (typeof collectionConfig === 'object' && collectionConfig.prefix) || ''\n\n if (multipartId && multipartKey) {\n const receipt = verifyClientUploadReceipt({\n collectionSlug,\n req,\n signedReceipt: params.signedReceipt,\n })\n // The receipt binds the full storage key (including the per-upload _objectKey segment), so\n // compare against it directly rather than recomputing from prefix + filename.\n if (receipt.storageFilePath !== multipartKey) {\n throw new APIError('Invalid upload reference.', 400)\n }\n const multipartUpload = bucket.resumeMultipartUpload(multipartKey, multipartId)\n const request = req as Request\n\n if (isNaN(multipartNumber)) {\n // Upload complete\n const object = await multipartUpload.complete((await request.json()) as R2UploadedPart[])\n return new Response(object.key, { status: 200 })\n } else {\n // Upload part\n const uploadedPart = await multipartUpload.uploadPart(\n multipartNumber,\n await request.arrayBuffer(),\n )\n return Response.json(uploadedPart)\n }\n } else {\n const { sanitizedFilename, storageFilePath, uploadReference } = await resolveSignedURLKey({\n collectionPrefix,\n collectionSlug,\n docPrefix: params.docPrefix ?? undefined,\n filename: params.fileName,\n req,\n useCompositePrefixes,\n })\n const existing = await bucket.head(storageFilePath)\n if (existing) {\n return new Response('Object already exists', { status: 412 })\n }\n\n // Create multipart upload\n const multipartUpload = await bucket.createMultipartUpload(storageFilePath, {\n httpMetadata: {\n contentType: filetype,\n },\n })\n\n return Response.json({\n filename: sanitizedFilename,\n key: multipartUpload.key,\n uploadId: multipartUpload.uploadId,\n uploadReference,\n })\n }\n }\n"],"names":["resolveSignedURLKey","APIError","Forbidden","assertClientUploadAccess","assertClientUploadAllowed","verifyClientUploadReceipt","getHandleMultiPartUpload","access","bucket","collections","useCompositePrefixes","req","params","Object","fromEntries","searchParams","collectionSlug","collection","filetype","fileType","collectionConfig","t","payload","config","filename","fileName","mimeType","multipartId","multipartKey","multipartNumber","parseInt","collectionPrefix","prefix","receipt","signedReceipt","storageFilePath","multipartUpload","resumeMultipartUpload","request","isNaN","object","complete","json","Response","key","status","uploadedPart","uploadPart","arrayBuffer","sanitizedFilename","uploadReference","docPrefix","undefined","existing","head","createMultipartUpload","httpMetadata","contentType","uploadId"],"mappings":"AAEA,SAASA,mBAAmB,QAAQ,6CAA4C;AAChF,SAASC,QAAQ,EAAEC,SAAS,QAAQ,UAAS;AAC7C,SACEC,wBAAwB,EACxBC,yBAAyB,EACzBC,yBAAyB,QACpB,mBAAkB;AAYzB,yFAAyF;AACzF,OAAO,MAAMC,2BACX,CAAC,EAAEC,MAAM,EAAEC,MAAM,EAAEC,WAAW,EAAEC,uBAAuB,KAAK,EAAQ,GACpE,OAAOC;QACL,MAAMC,SAASC,OAAOC,WAAW,CAACH,IAAII,YAAY;QAClD,MAAMC,iBAAiBJ,OAAOK,UAAU;QACxC,MAAMC,WAAWN,OAAOO,QAAQ;QAEhC,MAAMhB,yBAAyB;YAAEa;YAAgBL;QAAI;QAErD,MAAMS,mBAAmBX,WAAW,CAACO,eAAe;QACpD,IAAI,CAACI,kBAAkB;YACrB,MAAM,IAAInB,SAAS,CAAC,WAAW,EAAEe,eAAe,oCAAoC,CAAC;QACvF;QAEA,IAAIT,UAAU,CAAE,MAAMA,OAAO;YAAES;YAAgBL;QAAI,IAAK;YACtD,MAAM,IAAIT,UAAUS,IAAIU,CAAC;QAC3B;QAEAjB,0BAA0B;YACxBa,YAAYN,IAAIW,OAAO,CAACb,WAAW,CAACO,eAAe,EAAEO;YACrDC,UAAUZ,OAAOa,QAAQ;YACzBC,UAAUR;QACZ;QAEA,MAAMS,cAAcf,OAAOe,WAAW;QACtC,MAAMC,eAAehB,OAAOgB,YAAY;QACxC,MAAMC,kBAAkBC,SAASlB,OAAOiB,eAAe,IAAI;QAC3D,MAAME,mBAAmB,AAAC,OAAOX,qBAAqB,YAAYA,iBAAiBY,MAAM,IAAK;QAE9F,IAAIL,eAAeC,cAAc;YAC/B,MAAMK,UAAU5B,0BAA0B;gBACxCW;gBACAL;gBACAuB,eAAetB,OAAOsB,aAAa;YACrC;YACA,2FAA2F;YAC3F,8EAA8E;YAC9E,IAAID,QAAQE,eAAe,KAAKP,cAAc;gBAC5C,MAAM,IAAI3B,SAAS,6BAA6B;YAClD;YACA,MAAMmC,kBAAkB5B,OAAO6B,qBAAqB,CAACT,cAAcD;YACnE,MAAMW,UAAU3B;YAEhB,IAAI4B,MAAMV,kBAAkB;gBAC1B,kBAAkB;gBAClB,MAAMW,SAAS,MAAMJ,gBAAgBK,QAAQ,CAAE,MAAMH,QAAQI,IAAI;gBACjE,OAAO,IAAIC,SAASH,OAAOI,GAAG,EAAE;oBAAEC,QAAQ;gBAAI;YAChD,OAAO;gBACL,cAAc;gBACd,MAAMC,eAAe,MAAMV,gBAAgBW,UAAU,CACnDlB,iBACA,MAAMS,QAAQU,WAAW;gBAE3B,OAAOL,SAASD,IAAI,CAACI;YACvB;QACF,OAAO;YACL,MAAM,EAAEG,iBAAiB,EAAEd,eAAe,EAAEe,eAAe,EAAE,GAAG,MAAMlD,oBAAoB;gBACxF+B;gBACAf;gBACAmC,WAAWvC,OAAOuC,SAAS,IAAIC;gBAC/B5B,UAAUZ,OAAOa,QAAQ;gBACzBd;gBACAD;YACF;YACA,MAAM2C,WAAW,MAAM7C,OAAO8C,IAAI,CAACnB;YACnC,IAAIkB,UAAU;gBACZ,OAAO,IAAIV,SAAS,yBAAyB;oBAAEE,QAAQ;gBAAI;YAC7D;YAEA,0BAA0B;YAC1B,MAAMT,kBAAkB,MAAM5B,OAAO+C,qBAAqB,CAACpB,iBAAiB;gBAC1EqB,cAAc;oBACZC,aAAavC;gBACf;YACF;YAEA,OAAOyB,SAASD,IAAI,CAAC;gBACnBlB,UAAUyB;gBACVL,KAAKR,gBAAgBQ,GAAG;gBACxBc,UAAUtB,gBAAgBsB,QAAQ;gBAClCR;YACF;QACF;IACF,EAAC"}
1
+ {"version":3,"sources":["../src/handleMultiPartUpload.ts"],"sourcesContent":["import type { PayloadHandler, UploadInstructionsAccess } from 'payload'\n\nimport { resolveSignedURLKey } from '@payloadcms/plugin-cloud-storage/utilities'\nimport { APIError, Forbidden } from 'payload'\nimport {\n assertClientUploadAccess,\n assertClientUploadAllowed,\n verifyClientUploadReceipt,\n} from 'payload/internal'\n\nimport type { R2StorageOptions } from './index.js'\nimport type { R2Bucket, R2StorageMultipartUploadHandlerParams, R2UploadedPart } from './types.js'\n\ntype Args = {\n access?: UploadInstructionsAccess\n bucket: R2Bucket\n collections: R2StorageOptions['collections']\n useCompositePrefixes?: boolean\n}\n\n// Adapted from https://developers.cloudflare.com/r2/api/workers/workers-multipart-usage/\nexport const getHandleMultiPartUpload =\n ({ access, bucket, collections, useCompositePrefixes = false }: Args): PayloadHandler =>\n async (req) => {\n const params = Object.fromEntries(req.searchParams) as R2StorageMultipartUploadHandlerParams\n const collectionSlug = params.collection\n const filetype = params.fileType\n\n await assertClientUploadAccess({ collectionSlug, req })\n\n const collectionConfig = collections[collectionSlug]\n if (!collectionConfig) {\n throw new APIError(`Collection ${collectionSlug} was not found in R2 Storage options`)\n }\n\n if (access && !(await access({ collectionSlug, req }))) {\n throw new Forbidden(req.t)\n }\n\n assertClientUploadAllowed({\n collection: req.payload.collections[collectionSlug]?.config,\n filename: params.fileName,\n mimeType: filetype,\n })\n\n const multipartId = params.multipartId\n const multipartKey = params.multipartKey\n const multipartNumber = parseInt(params.multipartNumber || '')\n const collectionPrefix = (typeof collectionConfig === 'object' && collectionConfig.prefix) || ''\n\n if (multipartId && multipartKey) {\n const receipt = verifyClientUploadReceipt({\n collectionSlug,\n req,\n signedReceipt: params.signedReceipt,\n })\n // The receipt binds the full storage key (including the per-upload _objectKey segment), so\n // compare against it directly rather than recomputing from prefix + filename.\n if (receipt.storageFilePath !== multipartKey) {\n throw new APIError('Invalid upload reference.', 400)\n }\n const multipartUpload = bucket.resumeMultipartUpload(multipartKey, multipartId)\n const request = req as Request\n\n if (isNaN(multipartNumber)) {\n // Upload complete\n const object = await multipartUpload.complete((await request.json()) as R2UploadedPart[])\n if (object.key !== multipartKey) {\n throw new APIError('Uploaded source was stored at an unexpected location.', 400)\n }\n return new Response(object.key, { status: 200 })\n } else {\n // Upload part\n const uploadedPart = await multipartUpload.uploadPart(\n multipartNumber,\n await request.arrayBuffer(),\n )\n return Response.json(uploadedPart)\n }\n } else {\n const { sanitizedFilename, storageFilePath, uploadReference } = await resolveSignedURLKey({\n collectionPrefix,\n collectionSlug,\n docPrefix: params.docPrefix ?? undefined,\n filename: params.fileName,\n req,\n useCompositePrefixes,\n })\n const existing = await bucket.head(storageFilePath)\n if (existing) {\n return new Response('Object already exists', { status: 412 })\n }\n\n // Create multipart upload\n const multipartUpload = await bucket.createMultipartUpload(storageFilePath, {\n httpMetadata: {\n contentType: filetype,\n },\n })\n\n return Response.json({\n filename: sanitizedFilename,\n key: multipartUpload.key,\n uploadId: multipartUpload.uploadId,\n uploadReference,\n })\n }\n }\n"],"names":["resolveSignedURLKey","APIError","Forbidden","assertClientUploadAccess","assertClientUploadAllowed","verifyClientUploadReceipt","getHandleMultiPartUpload","access","bucket","collections","useCompositePrefixes","req","params","Object","fromEntries","searchParams","collectionSlug","collection","filetype","fileType","collectionConfig","t","payload","config","filename","fileName","mimeType","multipartId","multipartKey","multipartNumber","parseInt","collectionPrefix","prefix","receipt","signedReceipt","storageFilePath","multipartUpload","resumeMultipartUpload","request","isNaN","object","complete","json","key","Response","status","uploadedPart","uploadPart","arrayBuffer","sanitizedFilename","uploadReference","docPrefix","undefined","existing","head","createMultipartUpload","httpMetadata","contentType","uploadId"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -11,6 +11,13 @@ export interface R2StorageOptions {
11
11
  * Collection options to apply the R2 adapter to.
12
12
  */
13
13
  collections: Partial<Record<UploadCollectionSlug, Omit<CollectionOptions, 'adapter'> | true>>;
14
+ /** S3 API credentials required only for copy-dependent file versioning operations. */
15
+ copyCredentials?: {
16
+ accessKeyId: string;
17
+ accountId: string;
18
+ bucket: string;
19
+ secretAccessKey: string;
20
+ };
14
21
  enabled?: boolean;
15
22
  /**
16
23
  * When true, the collection-level prefix and document-level prefix are combined
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EAEnB,iBAAiB,EAClB,MAAM,wCAAwC,CAAA;AAC/C,OAAO,KAAK,EAAU,cAAc,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAI3E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAI1C,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,QAAQ,CAAA;IAChB;;OAEG;IACH,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC;;OAEG;IACH,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC,CAAA;IAC7F,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;;;;;;;;;OAaG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,KAAK,gBAAgB,GAAG,CAAC,aAAa,EAAE,gBAAgB,KAAK,cAAc,CAAA;AAE3E,eAAO,MAAM,SAAS,EAAE,gBAwEtB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EAEnB,iBAAiB,EAClB,MAAM,wCAAwC,CAAA;AAC/C,OAAO,KAAK,EAAU,cAAc,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAI3E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAI1C,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,QAAQ,CAAA;IAChB;;OAEG;IACH,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC;;OAEG;IACH,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC,CAAA;IAC7F,sFAAsF;IACtF,eAAe,CAAC,EAAE;QAChB,WAAW,EAAE,MAAM,CAAA;QACnB,SAAS,EAAE,MAAM,CAAA;QACjB,MAAM,EAAE,MAAM,CAAA;QACd,eAAe,EAAE,MAAM,CAAA;KACxB,CAAA;IACD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;;;;;;;;;OAaG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,KAAK,gBAAgB,GAAG,CAAC,aAAa,EAAE,gBAAgB,KAAK,cAAc,CAAA;AAE3E,eAAO,MAAM,SAAS,EAAE,gBAyEtB,CAAA"}
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ export const r2Storage = (r2StorageOptions)=>({
8
8
  bucket: r2StorageOptions.bucket,
9
9
  clientUploads: r2StorageOptions.clientUploads,
10
10
  collections: r2StorageOptions.collections,
11
+ copyCredentials: r2StorageOptions.copyCredentials,
11
12
  useCompositePrefixes: r2StorageOptions.useCompositePrefixes
12
13
  });
13
14
  const isPluginDisabled = r2StorageOptions.enabled === false;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n ClientUploadsConfig,\n PluginOptions as CloudStoragePluginOptions,\n CollectionOptions,\n} from '@payloadcms/plugin-cloud-storage/types'\nimport type { Config, StorageAdapter, UploadCollectionSlug } from 'payload'\n\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\n\nimport type { R2Bucket } from './types.js'\n\nimport { createR2Adapter } from './adapter.js'\n\nexport interface R2StorageOptions {\n bucket: R2Bucket\n /**\n * Upload files in chunks through Payload before document creation.\n */\n clientUploads?: ClientUploadsConfig\n /**\n * Collection options to apply the R2 adapter to.\n */\n collections: Partial<Record<UploadCollectionSlug, Omit<CollectionOptions, 'adapter'> | true>>\n enabled?: boolean\n /**\n * When true, the collection-level prefix and document-level prefix are combined\n * (compositional). When false (default), a document prefix already within the\n * collection prefix is used as-is for new uploads; otherwise it is nested beneath it.\n * Existing files retain their stored prefixes for reads, URLs, and cleanup.\n *\n * Example with a document prefix already contained by the collection prefix:\n * - collection prefix: `uploads/`\n * - document prefix: `uploads/documents/`\n * - resulting prefix with useCompositePrefixes=true: `uploads/uploads/documents/`\n * - resulting prefix with useCompositePrefixes=false: `uploads/documents/`\n *\n * @default false\n */\n useCompositePrefixes?: boolean\n}\n\ntype R2StorageFactory = (r2StorageArgs: R2StorageOptions) => StorageAdapter\n\nexport const r2Storage: R2StorageFactory = (\n r2StorageOptions: R2StorageOptions,\n): StorageAdapter => ({\n name: 'r2',\n collections: Object.keys(r2StorageOptions.collections),\n init: (incomingConfig: Config): Config => {\n const adapter = createR2Adapter({\n bucket: r2StorageOptions.bucket,\n clientUploads: r2StorageOptions.clientUploads,\n collections: r2StorageOptions.collections,\n useCompositePrefixes: r2StorageOptions.useCompositePrefixes,\n })\n\n const isPluginDisabled = r2StorageOptions.enabled === false\n\n if (isPluginDisabled) {\n // Still call cloudStoragePlugin with adapter: null so fields (like prefix) are\n // inserted into the schema, keeping it consistent across environments.\n const collectionsWithoutAdapter: CloudStoragePluginOptions['collections'] = Object.entries(\n r2StorageOptions.collections,\n ).reduce(\n (acc, [slug, collOptions]) => ({\n ...acc,\n [slug]: { ...(collOptions === true ? {} : collOptions), adapter: null },\n }),\n {} as Record<string, CollectionOptions>,\n )\n\n return cloudStoragePlugin({\n collections: collectionsWithoutAdapter,\n enabled: false,\n useCompositePrefixes: r2StorageOptions.useCompositePrefixes,\n })(incomingConfig)\n }\n\n // Add adapter to each collection option object\n const collectionsWithAdapter: CloudStoragePluginOptions['collections'] = Object.entries(\n r2StorageOptions.collections,\n ).reduce(\n (acc, [slug, collOptions]) => ({\n ...acc,\n [slug]: {\n ...(collOptions === true ? {} : collOptions),\n adapter,\n },\n }),\n {} as Record<string, CollectionOptions>,\n )\n\n // Set disableLocalStorage: true for collections specified in the plugin options\n const config = {\n ...incomingConfig,\n collections: (incomingConfig.collections || []).map((collection) => {\n if (!collectionsWithAdapter[collection.slug]) {\n return collection\n }\n\n return {\n ...collection,\n upload: {\n ...(typeof collection.upload === 'object' ? collection.upload : {}),\n disableLocalStorage: true,\n },\n }\n }),\n }\n\n return cloudStoragePlugin({\n collections: collectionsWithAdapter,\n useCompositePrefixes: r2StorageOptions.useCompositePrefixes,\n })(config)\n },\n})\n"],"names":["cloudStoragePlugin","createR2Adapter","r2Storage","r2StorageOptions","name","collections","Object","keys","init","incomingConfig","adapter","bucket","clientUploads","useCompositePrefixes","isPluginDisabled","enabled","collectionsWithoutAdapter","entries","reduce","acc","slug","collOptions","collectionsWithAdapter","config","map","collection","upload","disableLocalStorage"],"mappings":"AAOA,SAASA,kBAAkB,QAAQ,mCAAkC;AAIrE,SAASC,eAAe,QAAQ,eAAc;AAgC9C,OAAO,MAAMC,YAA8B,CACzCC,mBACoB,CAAA;QACpBC,MAAM;QACNC,aAAaC,OAAOC,IAAI,CAACJ,iBAAiBE,WAAW;QACrDG,MAAM,CAACC;YACL,MAAMC,UAAUT,gBAAgB;gBAC9BU,QAAQR,iBAAiBQ,MAAM;gBAC/BC,eAAeT,iBAAiBS,aAAa;gBAC7CP,aAAaF,iBAAiBE,WAAW;gBACzCQ,sBAAsBV,iBAAiBU,oBAAoB;YAC7D;YAEA,MAAMC,mBAAmBX,iBAAiBY,OAAO,KAAK;YAEtD,IAAID,kBAAkB;gBACpB,+EAA+E;gBAC/E,uEAAuE;gBACvE,MAAME,4BAAsEV,OAAOW,OAAO,CACxFd,iBAAiBE,WAAW,EAC5Ba,MAAM,CACN,CAACC,KAAK,CAACC,MAAMC,YAAY,GAAM,CAAA;wBAC7B,GAAGF,GAAG;wBACN,CAACC,KAAK,EAAE;4BAAE,GAAIC,gBAAgB,OAAO,CAAC,IAAIA,WAAW;4BAAGX,SAAS;wBAAK;oBACxE,CAAA,GACA,CAAC;gBAGH,OAAOV,mBAAmB;oBACxBK,aAAaW;oBACbD,SAAS;oBACTF,sBAAsBV,iBAAiBU,oBAAoB;gBAC7D,GAAGJ;YACL;YAEA,+CAA+C;YAC/C,MAAMa,yBAAmEhB,OAAOW,OAAO,CACrFd,iBAAiBE,WAAW,EAC5Ba,MAAM,CACN,CAACC,KAAK,CAACC,MAAMC,YAAY,GAAM,CAAA;oBAC7B,GAAGF,GAAG;oBACN,CAACC,KAAK,EAAE;wBACN,GAAIC,gBAAgB,OAAO,CAAC,IAAIA,WAAW;wBAC3CX;oBACF;gBACF,CAAA,GACA,CAAC;YAGH,gFAAgF;YAChF,MAAMa,SAAS;gBACb,GAAGd,cAAc;gBACjBJ,aAAa,AAACI,CAAAA,eAAeJ,WAAW,IAAI,EAAE,AAAD,EAAGmB,GAAG,CAAC,CAACC;oBACnD,IAAI,CAACH,sBAAsB,CAACG,WAAWL,IAAI,CAAC,EAAE;wBAC5C,OAAOK;oBACT;oBAEA,OAAO;wBACL,GAAGA,UAAU;wBACbC,QAAQ;4BACN,GAAI,OAAOD,WAAWC,MAAM,KAAK,WAAWD,WAAWC,MAAM,GAAG,CAAC,CAAC;4BAClEC,qBAAqB;wBACvB;oBACF;gBACF;YACF;YAEA,OAAO3B,mBAAmB;gBACxBK,aAAaiB;gBACbT,sBAAsBV,iBAAiBU,oBAAoB;YAC7D,GAAGU;QACL;IACF,CAAA,EAAE"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n ClientUploadsConfig,\n PluginOptions as CloudStoragePluginOptions,\n CollectionOptions,\n} from '@payloadcms/plugin-cloud-storage/types'\nimport type { Config, StorageAdapter, UploadCollectionSlug } from 'payload'\n\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\n\nimport type { R2Bucket } from './types.js'\n\nimport { createR2Adapter } from './adapter.js'\n\nexport interface R2StorageOptions {\n bucket: R2Bucket\n /**\n * Upload files in chunks through Payload before document creation.\n */\n clientUploads?: ClientUploadsConfig\n /**\n * Collection options to apply the R2 adapter to.\n */\n collections: Partial<Record<UploadCollectionSlug, Omit<CollectionOptions, 'adapter'> | true>>\n /** S3 API credentials required only for copy-dependent file versioning operations. */\n copyCredentials?: {\n accessKeyId: string\n accountId: string\n bucket: string\n secretAccessKey: string\n }\n enabled?: boolean\n /**\n * When true, the collection-level prefix and document-level prefix are combined\n * (compositional). When false (default), a document prefix already within the\n * collection prefix is used as-is for new uploads; otherwise it is nested beneath it.\n * Existing files retain their stored prefixes for reads, URLs, and cleanup.\n *\n * Example with a document prefix already contained by the collection prefix:\n * - collection prefix: `uploads/`\n * - document prefix: `uploads/documents/`\n * - resulting prefix with useCompositePrefixes=true: `uploads/uploads/documents/`\n * - resulting prefix with useCompositePrefixes=false: `uploads/documents/`\n *\n * @default false\n */\n useCompositePrefixes?: boolean\n}\n\ntype R2StorageFactory = (r2StorageArgs: R2StorageOptions) => StorageAdapter\n\nexport const r2Storage: R2StorageFactory = (\n r2StorageOptions: R2StorageOptions,\n): StorageAdapter => ({\n name: 'r2',\n collections: Object.keys(r2StorageOptions.collections),\n init: (incomingConfig: Config): Config => {\n const adapter = createR2Adapter({\n bucket: r2StorageOptions.bucket,\n clientUploads: r2StorageOptions.clientUploads,\n collections: r2StorageOptions.collections,\n copyCredentials: r2StorageOptions.copyCredentials,\n useCompositePrefixes: r2StorageOptions.useCompositePrefixes,\n })\n\n const isPluginDisabled = r2StorageOptions.enabled === false\n\n if (isPluginDisabled) {\n // Still call cloudStoragePlugin with adapter: null so fields (like prefix) are\n // inserted into the schema, keeping it consistent across environments.\n const collectionsWithoutAdapter: CloudStoragePluginOptions['collections'] = Object.entries(\n r2StorageOptions.collections,\n ).reduce(\n (acc, [slug, collOptions]) => ({\n ...acc,\n [slug]: { ...(collOptions === true ? {} : collOptions), adapter: null },\n }),\n {} as Record<string, CollectionOptions>,\n )\n\n return cloudStoragePlugin({\n collections: collectionsWithoutAdapter,\n enabled: false,\n useCompositePrefixes: r2StorageOptions.useCompositePrefixes,\n })(incomingConfig)\n }\n\n // Add adapter to each collection option object\n const collectionsWithAdapter: CloudStoragePluginOptions['collections'] = Object.entries(\n r2StorageOptions.collections,\n ).reduce(\n (acc, [slug, collOptions]) => ({\n ...acc,\n [slug]: {\n ...(collOptions === true ? {} : collOptions),\n adapter,\n },\n }),\n {} as Record<string, CollectionOptions>,\n )\n\n // Set disableLocalStorage: true for collections specified in the plugin options\n const config = {\n ...incomingConfig,\n collections: (incomingConfig.collections || []).map((collection) => {\n if (!collectionsWithAdapter[collection.slug]) {\n return collection\n }\n\n return {\n ...collection,\n upload: {\n ...(typeof collection.upload === 'object' ? collection.upload : {}),\n disableLocalStorage: true,\n },\n }\n }),\n }\n\n return cloudStoragePlugin({\n collections: collectionsWithAdapter,\n useCompositePrefixes: r2StorageOptions.useCompositePrefixes,\n })(config)\n },\n})\n"],"names":["cloudStoragePlugin","createR2Adapter","r2Storage","r2StorageOptions","name","collections","Object","keys","init","incomingConfig","adapter","bucket","clientUploads","copyCredentials","useCompositePrefixes","isPluginDisabled","enabled","collectionsWithoutAdapter","entries","reduce","acc","slug","collOptions","collectionsWithAdapter","config","map","collection","upload","disableLocalStorage"],"mappings":"AAOA,SAASA,kBAAkB,QAAQ,mCAAkC;AAIrE,SAASC,eAAe,QAAQ,eAAc;AAuC9C,OAAO,MAAMC,YAA8B,CACzCC,mBACoB,CAAA;QACpBC,MAAM;QACNC,aAAaC,OAAOC,IAAI,CAACJ,iBAAiBE,WAAW;QACrDG,MAAM,CAACC;YACL,MAAMC,UAAUT,gBAAgB;gBAC9BU,QAAQR,iBAAiBQ,MAAM;gBAC/BC,eAAeT,iBAAiBS,aAAa;gBAC7CP,aAAaF,iBAAiBE,WAAW;gBACzCQ,iBAAiBV,iBAAiBU,eAAe;gBACjDC,sBAAsBX,iBAAiBW,oBAAoB;YAC7D;YAEA,MAAMC,mBAAmBZ,iBAAiBa,OAAO,KAAK;YAEtD,IAAID,kBAAkB;gBACpB,+EAA+E;gBAC/E,uEAAuE;gBACvE,MAAME,4BAAsEX,OAAOY,OAAO,CACxFf,iBAAiBE,WAAW,EAC5Bc,MAAM,CACN,CAACC,KAAK,CAACC,MAAMC,YAAY,GAAM,CAAA;wBAC7B,GAAGF,GAAG;wBACN,CAACC,KAAK,EAAE;4BAAE,GAAIC,gBAAgB,OAAO,CAAC,IAAIA,WAAW;4BAAGZ,SAAS;wBAAK;oBACxE,CAAA,GACA,CAAC;gBAGH,OAAOV,mBAAmB;oBACxBK,aAAaY;oBACbD,SAAS;oBACTF,sBAAsBX,iBAAiBW,oBAAoB;gBAC7D,GAAGL;YACL;YAEA,+CAA+C;YAC/C,MAAMc,yBAAmEjB,OAAOY,OAAO,CACrFf,iBAAiBE,WAAW,EAC5Bc,MAAM,CACN,CAACC,KAAK,CAACC,MAAMC,YAAY,GAAM,CAAA;oBAC7B,GAAGF,GAAG;oBACN,CAACC,KAAK,EAAE;wBACN,GAAIC,gBAAgB,OAAO,CAAC,IAAIA,WAAW;wBAC3CZ;oBACF;gBACF,CAAA,GACA,CAAC;YAGH,gFAAgF;YAChF,MAAMc,SAAS;gBACb,GAAGf,cAAc;gBACjBJ,aAAa,AAACI,CAAAA,eAAeJ,WAAW,IAAI,EAAE,AAAD,EAAGoB,GAAG,CAAC,CAACC;oBACnD,IAAI,CAACH,sBAAsB,CAACG,WAAWL,IAAI,CAAC,EAAE;wBAC5C,OAAOK;oBACT;oBAEA,OAAO;wBACL,GAAGA,UAAU;wBACbC,QAAQ;4BACN,GAAI,OAAOD,WAAWC,MAAM,KAAK,WAAWD,WAAWC,MAAM,GAAG,CAAC,CAAC;4BAClEC,qBAAqB;wBACvB;oBACF;gBACF;YACF;YAEA,OAAO5B,mBAAmB;gBACxBK,aAAakB;gBACbT,sBAAsBX,iBAAiBW,oBAAoB;YAC7D,GAAGU;QACL;IACF,CAAA,EAAE"}
package/package.json CHANGED
@@ -1,13 +1,8 @@
1
1
  {
2
2
  "name": "@payloadcms/storage-r2",
3
- "version": "4.0.0-internal.d21c039",
3
+ "version": "4.0.0-internal.d3909a9",
4
4
  "description": "Payload storage adapter for Cloudflare R2",
5
5
  "homepage": "https://payloadcms.com",
6
- "repository": {
7
- "type": "git",
8
- "url": "https://github.com/payloadcms/payload.git",
9
- "directory": "packages/storage-r2"
10
- },
11
6
  "license": "MIT",
12
7
  "author": "Payload <dev@payloadcms.com> (https://payloadcms.com)",
13
8
  "maintainers": [
@@ -17,8 +12,18 @@
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-r2"
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",
@@ -31,20 +36,17 @@
31
36
  "default": "./dist/exports/client.js"
32
37
  }
33
38
  },
34
- "main": "./dist/index.js",
35
- "types": "./dist/index.d.ts",
36
- "files": [
37
- "dist"
38
- ],
39
39
  "dependencies": {
40
- "@payloadcms/plugin-cloud-storage": "4.0.0-internal.d21c039"
40
+ "@aws-sdk/client-s3": "^3.614.0",
41
+ "@payloadcms/plugin-cloud-storage": "4.0.0-internal.d3909a9",
42
+ "@payloadcms/storage-s3": "4.0.0-internal.d3909a9"
41
43
  },
42
44
  "devDependencies": {
43
45
  "@cloudflare/workers-types": "4.20260218.0",
44
- "payload": "4.0.0-internal.d21c039"
46
+ "payload": "4.0.0-internal.d3909a9"
45
47
  },
46
48
  "peerDependencies": {
47
- "payload": "4.0.0-internal.d21c039"
49
+ "payload": "4.0.0-internal.d3909a9"
48
50
  },
49
51
  "engines": {
50
52
  "node": ">=24.15.0"
@@ -55,8 +57,6 @@
55
57
  "build:debug": "pnpm build",
56
58
  "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
57
59
  "build:types": "tsc --emitDeclarationOnly --outDir dist",
58
- "clean": "rimraf -g {dist,*.tsbuildinfo}",
59
- "lint": "eslint .",
60
- "lint:fix": "eslint . --fix"
60
+ "clean": "rimraf -g {dist,*.tsbuildinfo}"
61
61
  }
62
62
  }