@payloadcms/storage-gcs 4.0.0-internal.f11981a → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,KAAK,EACV,OAAO,EACP,mBAAmB,EAEpB,MAAM,wCAAwC,CAAA;AAQ/C,UAAU,oBAAoB;IAC5B,GAAG,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC,gBAAgB,EAAE,MAAM,OAAO,CAAA;IAC/B,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,GAAG,EACH,MAAM,EACN,aAAa,EACb,gBAAgB,EAChB,oBAA4B,GAC7B,EAAE,oBAAoB,GAAG,OAAO,CAqEhC"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,KAAK,EACV,OAAO,EACP,mBAAmB,EAGpB,MAAM,wCAAwC,CAAA;AAS/C,UAAU,oBAAoB;IAC5B,GAAG,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC,gBAAgB,EAAE,MAAM,OAAO,CAAA;IAC/B,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,GAAG,EACH,MAAM,EACN,aAAa,EACb,gBAAgB,EAChB,oBAA4B,GAC7B,EAAE,oBAAoB,GAAG,OAAO,CAgEhC"}
package/dist/adapter.js CHANGED
@@ -1,11 +1,26 @@
1
+ import { copyGcsFile } from './copyFile.js';
1
2
  import { deleteFile } from './deleteFile.js';
2
3
  import { generateUploadInstructions } from './generateUploadInstructions.js';
3
4
  import { generateURL } from './generateURL.js';
4
5
  import { getFile } from './getFile.js';
5
6
  import { uploadFile } from './uploadFile.js';
6
7
  export function createGcsAdapter({ acl, bucket, clientUploads, getStorageClient, useCompositePrefixes = false }) {
8
+ const deleteStoredFile = ({ storageFilePath })=>deleteFile({
9
+ bucket,
10
+ client: getStorageClient(),
11
+ storageFilePath
12
+ });
7
13
  return ({ collection, prefix = '' })=>({
8
14
  name: 'gcs',
15
+ copyFile: ({ from, req, to })=>copyGcsFile({
16
+ acl,
17
+ bucket,
18
+ client: getStorageClient(),
19
+ from,
20
+ req,
21
+ to
22
+ }),
23
+ deleteFile: deleteStoredFile,
9
24
  generateURL: ({ filename, prefix: urlPrefix = '' })=>generateURL({
10
25
  bucket,
11
26
  client: getStorageClient(),
@@ -23,38 +38,30 @@ export function createGcsAdapter({ acl, bucket, clientUploads, getStorageClient,
23
38
  getStorageClient,
24
39
  useCompositePrefixes
25
40
  }),
41
+ requiresUploadReceipt: true,
26
42
  useInAdmin: true
27
43
  },
28
- handleDelete: ({ doc: { prefix: docPrefix = '' }, filename })=>deleteFile({
29
- bucket,
30
- client: getStorageClient(),
31
- collectionPrefix: prefix,
32
- docPrefix,
33
- filename,
34
- useCompositePrefixes
35
- }),
36
- handleUpload: async ({ data, file })=>{
44
+ handleDelete: deleteStoredFile,
45
+ handleUpload: async ({ data, file, storageFilePath })=>{
37
46
  await uploadFile({
38
47
  acl,
39
48
  bucket,
40
49
  buffer: file.buffer,
41
50
  client: getStorageClient(),
42
- collectionPrefix: prefix,
43
- docPrefix: data.prefix,
44
- filename: file.filename,
45
51
  mimeType: file.mimeType,
46
- useCompositePrefixes
52
+ storageFilePath
47
53
  });
48
54
  return data;
49
55
  },
50
- staticHandler: (req, { headers, params: { filename, prefix: prefixQueryParam, uploadReference } })=>getFile({
56
+ staticHandler: (req, { doc, headers, params: { filename, operation, uploadReference } })=>getFile({
51
57
  bucket,
52
58
  client: getStorageClient(),
53
59
  collection,
54
60
  collectionPrefix: prefix,
61
+ doc,
55
62
  filename,
56
63
  incomingHeaders: headers,
57
- prefixQueryParam,
64
+ operation,
58
65
  req,
59
66
  uploadReference,
60
67
  useCompositePrefixes
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapter.ts"],"sourcesContent":["import type { Storage } from '@google-cloud/storage'\nimport type {\n Adapter,\n ClientUploadsConfig,\n GeneratedAdapter,\n} from '@payloadcms/plugin-cloud-storage/types'\n\nimport { deleteFile } from './deleteFile.js'\nimport { generateUploadInstructions } from './generateUploadInstructions.js'\nimport { generateURL } from './generateURL.js'\nimport { getFile } from './getFile.js'\nimport { uploadFile } from './uploadFile.js'\n\ninterface CreateGcsAdapterArgs {\n acl?: 'Private' | 'Public'\n bucket: string\n clientUploads?: ClientUploadsConfig\n getStorageClient: () => Storage\n useCompositePrefixes?: boolean\n}\n\nexport function createGcsAdapter({\n acl,\n bucket,\n clientUploads,\n getStorageClient,\n useCompositePrefixes = false,\n}: CreateGcsAdapterArgs): Adapter {\n return ({ collection, prefix = '' }): GeneratedAdapter => ({\n name: 'gcs',\n\n generateURL: ({ filename, prefix: urlPrefix = '' }) =>\n generateURL({\n bucket,\n client: getStorageClient(),\n collectionPrefix: prefix,\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 bucket,\n collectionPrefix: prefix,\n getStorageClient,\n useCompositePrefixes,\n }),\n useInAdmin: true,\n },\n\n handleDelete: ({ doc: { prefix: docPrefix = '' }, filename }) =>\n deleteFile({\n bucket,\n client: getStorageClient(),\n collectionPrefix: prefix,\n docPrefix,\n filename,\n useCompositePrefixes,\n }),\n\n handleUpload: async ({ data, file }) => {\n await uploadFile({\n acl,\n bucket,\n buffer: file.buffer,\n client: getStorageClient(),\n collectionPrefix: prefix,\n docPrefix: data.prefix,\n filename: file.filename,\n mimeType: file.mimeType,\n useCompositePrefixes,\n })\n\n return data\n },\n\n staticHandler: (\n req,\n { headers, params: { filename, prefix: prefixQueryParam, uploadReference } },\n ) =>\n getFile({\n bucket,\n client: getStorageClient(),\n collection,\n collectionPrefix: prefix,\n filename,\n incomingHeaders: headers,\n prefixQueryParam,\n req,\n uploadReference,\n useCompositePrefixes,\n }),\n })\n}\n"],"names":["deleteFile","generateUploadInstructions","generateURL","getFile","uploadFile","createGcsAdapter","acl","bucket","clientUploads","getStorageClient","useCompositePrefixes","collection","prefix","name","filename","urlPrefix","client","collectionPrefix","uploadInstructions","enabled","Boolean","generate","access","undefined","useInAdmin","handleDelete","doc","docPrefix","handleUpload","data","file","buffer","mimeType","staticHandler","req","headers","params","prefixQueryParam","uploadReference","incomingHeaders"],"mappings":"AAOA,SAASA,UAAU,QAAQ,kBAAiB;AAC5C,SAASC,0BAA0B,QAAQ,kCAAiC;AAC5E,SAASC,WAAW,QAAQ,mBAAkB;AAC9C,SAASC,OAAO,QAAQ,eAAc;AACtC,SAASC,UAAU,QAAQ,kBAAiB;AAU5C,OAAO,SAASC,iBAAiB,EAC/BC,GAAG,EACHC,MAAM,EACNC,aAAa,EACbC,gBAAgB,EAChBC,uBAAuB,KAAK,EACP;IACrB,OAAO,CAAC,EAAEC,UAAU,EAAEC,SAAS,EAAE,EAAE,GAAwB,CAAA;YACzDC,MAAM;YAENX,aAAa,CAAC,EAAEY,QAAQ,EAAEF,QAAQG,YAAY,EAAE,EAAE,GAChDb,YAAY;oBACVK;oBACAS,QAAQP;oBACRQ,kBAAkBL;oBAClBE;oBACAF,QAAQG;oBACRL;gBACF;YAEFQ,oBAAoB;gBAClBC,SAASC,QAAQZ;gBACjBa,UAAUpB,2BAA2B;oBACnCqB,QAAQ,OAAOd,kBAAkB,WAAWA,cAAcc,MAAM,GAAGC;oBACnEhB;oBACAU,kBAAkBL;oBAClBH;oBACAC;gBACF;gBACAc,YAAY;YACd;YAEAC,cAAc,CAAC,EAAEC,KAAK,EAAEd,QAAQe,YAAY,EAAE,EAAE,EAAEb,QAAQ,EAAE,GAC1Dd,WAAW;oBACTO;oBACAS,QAAQP;oBACRQ,kBAAkBL;oBAClBe;oBACAb;oBACAJ;gBACF;YAEFkB,cAAc,OAAO,EAAEC,IAAI,EAAEC,IAAI,EAAE;gBACjC,MAAM1B,WAAW;oBACfE;oBACAC;oBACAwB,QAAQD,KAAKC,MAAM;oBACnBf,QAAQP;oBACRQ,kBAAkBL;oBAClBe,WAAWE,KAAKjB,MAAM;oBACtBE,UAAUgB,KAAKhB,QAAQ;oBACvBkB,UAAUF,KAAKE,QAAQ;oBACvBtB;gBACF;gBAEA,OAAOmB;YACT;YAEAI,eAAe,CACbC,KACA,EAAEC,OAAO,EAAEC,QAAQ,EAAEtB,QAAQ,EAAEF,QAAQyB,gBAAgB,EAAEC,eAAe,EAAE,EAAE,GAE5EnC,QAAQ;oBACNI;oBACAS,QAAQP;oBACRE;oBACAM,kBAAkBL;oBAClBE;oBACAyB,iBAAiBJ;oBACjBE;oBACAH;oBACAI;oBACA5B;gBACF;QACJ,CAAA;AACF"}
1
+ {"version":3,"sources":["../src/adapter.ts"],"sourcesContent":["import type { Storage } from '@google-cloud/storage'\nimport type {\n Adapter,\n ClientUploadsConfig,\n DeleteFile,\n GeneratedAdapter,\n} from '@payloadcms/plugin-cloud-storage/types'\n\nimport { copyGcsFile } from './copyFile.js'\nimport { deleteFile } from './deleteFile.js'\nimport { generateUploadInstructions } from './generateUploadInstructions.js'\nimport { generateURL } from './generateURL.js'\nimport { getFile } from './getFile.js'\nimport { uploadFile } from './uploadFile.js'\n\ninterface CreateGcsAdapterArgs {\n acl?: 'Private' | 'Public'\n bucket: string\n clientUploads?: ClientUploadsConfig\n getStorageClient: () => Storage\n useCompositePrefixes?: boolean\n}\n\nexport function createGcsAdapter({\n acl,\n bucket,\n clientUploads,\n getStorageClient,\n useCompositePrefixes = false,\n}: CreateGcsAdapterArgs): Adapter {\n const deleteStoredFile: DeleteFile = ({ storageFilePath }) =>\n deleteFile({ bucket, client: getStorageClient(), storageFilePath })\n\n return ({ collection, prefix = '' }): GeneratedAdapter => ({\n name: 'gcs',\n\n copyFile: ({ from, req, to }) =>\n copyGcsFile({ acl, bucket, client: getStorageClient(), from, req, to }),\n deleteFile: deleteStoredFile,\n\n generateURL: ({ filename, prefix: urlPrefix = '' }) =>\n generateURL({\n bucket,\n client: getStorageClient(),\n collectionPrefix: prefix,\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 bucket,\n collectionPrefix: prefix,\n getStorageClient,\n useCompositePrefixes,\n }),\n requiresUploadReceipt: true,\n useInAdmin: true,\n },\n\n handleDelete: deleteStoredFile,\n\n handleUpload: async ({ data, file, storageFilePath }) => {\n await uploadFile({\n acl,\n bucket,\n buffer: file.buffer,\n client: getStorageClient(),\n mimeType: file.mimeType,\n storageFilePath,\n })\n\n return data\n },\n\n staticHandler: (req, { doc, headers, params: { filename, operation, uploadReference } }) =>\n getFile({\n bucket,\n client: getStorageClient(),\n collection,\n collectionPrefix: prefix,\n doc,\n filename,\n incomingHeaders: headers,\n operation,\n req,\n uploadReference,\n useCompositePrefixes,\n }),\n })\n}\n"],"names":["copyGcsFile","deleteFile","generateUploadInstructions","generateURL","getFile","uploadFile","createGcsAdapter","acl","bucket","clientUploads","getStorageClient","useCompositePrefixes","deleteStoredFile","storageFilePath","client","collection","prefix","name","copyFile","from","req","to","filename","urlPrefix","collectionPrefix","uploadInstructions","enabled","Boolean","generate","access","undefined","requiresUploadReceipt","useInAdmin","handleDelete","handleUpload","data","file","buffer","mimeType","staticHandler","doc","headers","params","operation","uploadReference","incomingHeaders"],"mappings":"AAQA,SAASA,WAAW,QAAQ,gBAAe;AAC3C,SAASC,UAAU,QAAQ,kBAAiB;AAC5C,SAASC,0BAA0B,QAAQ,kCAAiC;AAC5E,SAASC,WAAW,QAAQ,mBAAkB;AAC9C,SAASC,OAAO,QAAQ,eAAc;AACtC,SAASC,UAAU,QAAQ,kBAAiB;AAU5C,OAAO,SAASC,iBAAiB,EAC/BC,GAAG,EACHC,MAAM,EACNC,aAAa,EACbC,gBAAgB,EAChBC,uBAAuB,KAAK,EACP;IACrB,MAAMC,mBAA+B,CAAC,EAAEC,eAAe,EAAE,GACvDZ,WAAW;YAAEO;YAAQM,QAAQJ;YAAoBG;QAAgB;IAEnE,OAAO,CAAC,EAAEE,UAAU,EAAEC,SAAS,EAAE,EAAE,GAAwB,CAAA;YACzDC,MAAM;YAENC,UAAU,CAAC,EAAEC,IAAI,EAAEC,GAAG,EAAEC,EAAE,EAAE,GAC1BrB,YAAY;oBAAEO;oBAAKC;oBAAQM,QAAQJ;oBAAoBS;oBAAMC;oBAAKC;gBAAG;YACvEpB,YAAYW;YAEZT,aAAa,CAAC,EAAEmB,QAAQ,EAAEN,QAAQO,YAAY,EAAE,EAAE,GAChDpB,YAAY;oBACVK;oBACAM,QAAQJ;oBACRc,kBAAkBR;oBAClBM;oBACAN,QAAQO;oBACRZ;gBACF;YAEFc,oBAAoB;gBAClBC,SAASC,QAAQlB;gBACjBmB,UAAU1B,2BAA2B;oBACnC2B,QAAQ,OAAOpB,kBAAkB,WAAWA,cAAcoB,MAAM,GAAGC;oBACnEtB;oBACAgB,kBAAkBR;oBAClBN;oBACAC;gBACF;gBACAoB,uBAAuB;gBACvBC,YAAY;YACd;YAEAC,cAAcrB;YAEdsB,cAAc,OAAO,EAAEC,IAAI,EAAEC,IAAI,EAAEvB,eAAe,EAAE;gBAClD,MAAMR,WAAW;oBACfE;oBACAC;oBACA6B,QAAQD,KAAKC,MAAM;oBACnBvB,QAAQJ;oBACR4B,UAAUF,KAAKE,QAAQ;oBACvBzB;gBACF;gBAEA,OAAOsB;YACT;YAEAI,eAAe,CAACnB,KAAK,EAAEoB,GAAG,EAAEC,OAAO,EAAEC,QAAQ,EAAEpB,QAAQ,EAAEqB,SAAS,EAAEC,eAAe,EAAE,EAAE,GACrFxC,QAAQ;oBACNI;oBACAM,QAAQJ;oBACRK;oBACAS,kBAAkBR;oBAClBwB;oBACAlB;oBACAuB,iBAAiBJ;oBACjBE;oBACAvB;oBACAwB;oBACAjC;gBACF;QACJ,CAAA;AACF"}
@@ -0,0 +1,13 @@
1
+ import type { Storage } from '@google-cloud/storage';
2
+ import type { PayloadRequest } from 'payload';
3
+ type Args = {
4
+ acl?: 'Private' | 'Public';
5
+ bucket: string;
6
+ client: Storage;
7
+ from: string;
8
+ req?: PayloadRequest;
9
+ to: string;
10
+ };
11
+ export declare const copyGcsFile: ({ acl, bucket, client, from, req, to }: Args) => 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,OAAO,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C,KAAK,IAAI,GAAG;IACV,GAAG,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,EAAE,EAAE,MAAM,CAAA;CACX,CAAA;AAED,eAAO,MAAM,WAAW,2CAAkD,IAAI,KAAG,OAAO,CAAC,IAAI,CA6C5F,CAAA"}
@@ -0,0 +1,45 @@
1
+ export const copyGcsFile = async ({ acl, bucket, client, from, req, to })=>{
2
+ if (from === to) {
3
+ throw new Error('Storage copy requires different source and destination keys');
4
+ }
5
+ const storageBucket = client.bucket(bucket);
6
+ const source = storageBucket.file(from);
7
+ const destination = storageBucket.file(to);
8
+ const [sourceMetadata] = await source.getMetadata();
9
+ const [hasDestination] = await destination.exists();
10
+ if (hasDestination) {
11
+ throw new Error(`Storage destination already exists: ${to}`);
12
+ }
13
+ const [, response] = await source.copy(destination, {
14
+ preconditionOpts: {
15
+ ifGenerationMatch: 0
16
+ }
17
+ });
18
+ const generation = response.resource?.generation;
19
+ try {
20
+ if (acl) {
21
+ await destination[`make${acl}`]();
22
+ }
23
+ const [destinationMetadata] = await destination.getMetadata();
24
+ if (destinationMetadata.size !== sourceMetadata.size) {
25
+ throw new Error(`Copied GCS object is not readable at its expected length: ${to}`);
26
+ }
27
+ } catch (err) {
28
+ try {
29
+ if (generation === undefined) {
30
+ throw new Error('GCS copy did not return a generation for safe cleanup');
31
+ }
32
+ await storageBucket.file(to, {
33
+ generation
34
+ }).delete();
35
+ } catch (cleanupError) {
36
+ req?.payload.logger.error({
37
+ err: cleanupError,
38
+ msg: `Failed to remove unsuccessful GCS copy at ${bucket}/${to}`
39
+ });
40
+ }
41
+ throw err;
42
+ }
43
+ };
44
+
45
+ //# sourceMappingURL=copyFile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/copyFile.ts"],"sourcesContent":["import type { Storage } from '@google-cloud/storage'\nimport type { PayloadRequest } from 'payload'\n\ntype Args = {\n acl?: 'Private' | 'Public'\n bucket: string\n client: Storage\n from: string\n req?: PayloadRequest\n to: string\n}\n\nexport const copyGcsFile = async ({ acl, bucket, client, from, req, to }: Args): Promise<void> => {\n if (from === to) {\n throw new Error('Storage copy requires different source and destination keys')\n }\n\n const storageBucket = client.bucket(bucket)\n const source = storageBucket.file(from)\n const destination = storageBucket.file(to)\n const [sourceMetadata] = await source.getMetadata()\n const [hasDestination] = await destination.exists()\n\n if (hasDestination) {\n throw new Error(`Storage destination already exists: ${to}`)\n }\n\n const [, response] = await source.copy(destination, {\n preconditionOpts: { ifGenerationMatch: 0 },\n })\n const generation = (response as { resource?: { generation?: number | string } }).resource\n ?.generation\n\n try {\n if (acl) {\n await destination[`make${acl}`]()\n }\n\n const [destinationMetadata] = await destination.getMetadata()\n\n if (destinationMetadata.size !== sourceMetadata.size) {\n throw new Error(`Copied GCS object is not readable at its expected length: ${to}`)\n }\n } catch (err) {\n try {\n if (generation === undefined) {\n throw new Error('GCS copy did not return a generation for safe cleanup')\n }\n await storageBucket.file(to, { generation }).delete()\n } catch (cleanupError) {\n req?.payload.logger.error({\n err: cleanupError,\n msg: `Failed to remove unsuccessful GCS copy at ${bucket}/${to}`,\n })\n }\n throw err\n }\n}\n"],"names":["copyGcsFile","acl","bucket","client","from","req","to","Error","storageBucket","source","file","destination","sourceMetadata","getMetadata","hasDestination","exists","response","copy","preconditionOpts","ifGenerationMatch","generation","resource","destinationMetadata","size","err","undefined","delete","cleanupError","payload","logger","error","msg"],"mappings":"AAYA,OAAO,MAAMA,cAAc,OAAO,EAAEC,GAAG,EAAEC,MAAM,EAAEC,MAAM,EAAEC,IAAI,EAAEC,GAAG,EAAEC,EAAE,EAAQ;IAC5E,IAAIF,SAASE,IAAI;QACf,MAAM,IAAIC,MAAM;IAClB;IAEA,MAAMC,gBAAgBL,OAAOD,MAAM,CAACA;IACpC,MAAMO,SAASD,cAAcE,IAAI,CAACN;IAClC,MAAMO,cAAcH,cAAcE,IAAI,CAACJ;IACvC,MAAM,CAACM,eAAe,GAAG,MAAMH,OAAOI,WAAW;IACjD,MAAM,CAACC,eAAe,GAAG,MAAMH,YAAYI,MAAM;IAEjD,IAAID,gBAAgB;QAClB,MAAM,IAAIP,MAAM,CAAC,oCAAoC,EAAED,IAAI;IAC7D;IAEA,MAAM,GAAGU,SAAS,GAAG,MAAMP,OAAOQ,IAAI,CAACN,aAAa;QAClDO,kBAAkB;YAAEC,mBAAmB;QAAE;IAC3C;IACA,MAAMC,aAAa,AAACJ,SAA6DK,QAAQ,EACrFD;IAEJ,IAAI;QACF,IAAInB,KAAK;YACP,MAAMU,WAAW,CAAC,CAAC,IAAI,EAAEV,KAAK,CAAC;QACjC;QAEA,MAAM,CAACqB,oBAAoB,GAAG,MAAMX,YAAYE,WAAW;QAE3D,IAAIS,oBAAoBC,IAAI,KAAKX,eAAeW,IAAI,EAAE;YACpD,MAAM,IAAIhB,MAAM,CAAC,0DAA0D,EAAED,IAAI;QACnF;IACF,EAAE,OAAOkB,KAAK;QACZ,IAAI;YACF,IAAIJ,eAAeK,WAAW;gBAC5B,MAAM,IAAIlB,MAAM;YAClB;YACA,MAAMC,cAAcE,IAAI,CAACJ,IAAI;gBAAEc;YAAW,GAAGM,MAAM;QACrD,EAAE,OAAOC,cAAc;YACrBtB,KAAKuB,QAAQC,OAAOC,MAAM;gBACxBN,KAAKG;gBACLI,KAAK,CAAC,0CAA0C,EAAE7B,OAAO,CAAC,EAAEI,IAAI;YAClE;QACF;QACA,MAAMkB;IACR;AACF,EAAC"}
@@ -2,11 +2,8 @@ import type { Storage } from '@google-cloud/storage';
2
2
  interface DeleteFileArgs {
3
3
  bucket: string;
4
4
  client: Storage;
5
- collectionPrefix?: string;
6
- docPrefix: string;
7
- filename: string;
8
- useCompositePrefixes?: boolean;
5
+ storageFilePath: string;
9
6
  }
10
- export declare function deleteFile({ bucket, client, collectionPrefix, docPrefix, filename, useCompositePrefixes, }: DeleteFileArgs): Promise<void>;
7
+ export declare function deleteFile({ bucket, client, storageFilePath, }: DeleteFileArgs): Promise<void>;
11
8
  export {};
12
9
  //# sourceMappingURL=deleteFile.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deleteFile.d.ts","sourceRoot":"","sources":["../src/deleteFile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAIpD,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,wBAAsB,UAAU,CAAC,EAC/B,MAAM,EACN,MAAM,EACN,gBAAqB,EACrB,SAAS,EACT,QAAQ,EACR,oBAA4B,GAC7B,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAWhC"}
1
+ {"version":3,"file":"deleteFile.d.ts","sourceRoot":"","sources":["../src/deleteFile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAEpD,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;IACf,eAAe,EAAE,MAAM,CAAA;CACxB;AAED,wBAAsB,UAAU,CAAC,EAC/B,MAAM,EACN,MAAM,EACN,eAAe,GAChB,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhC"}
@@ -1,12 +1,5 @@
1
- import { getFileKey } from '@payloadcms/plugin-cloud-storage/utilities';
2
- export async function deleteFile({ bucket, client, collectionPrefix = '', docPrefix, filename, useCompositePrefixes = false }) {
3
- const { fileKey } = getFileKey({
4
- collectionPrefix,
5
- docPrefix,
6
- filename,
7
- useCompositePrefixes
8
- });
9
- await client.bucket(bucket).file(fileKey).delete({
1
+ export async function deleteFile({ bucket, client, storageFilePath }) {
2
+ await client.bucket(bucket).file(storageFilePath).delete({
10
3
  ignoreNotFound: true
11
4
  });
12
5
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/deleteFile.ts"],"sourcesContent":["import type { Storage } from '@google-cloud/storage'\n\nimport { getFileKey } from '@payloadcms/plugin-cloud-storage/utilities'\n\ninterface DeleteFileArgs {\n bucket: string\n client: Storage\n collectionPrefix?: string\n docPrefix: string\n filename: string\n useCompositePrefixes?: boolean\n}\n\nexport async function deleteFile({\n bucket,\n client,\n collectionPrefix = '',\n docPrefix,\n filename,\n useCompositePrefixes = false,\n}: DeleteFileArgs): Promise<void> {\n const { fileKey } = getFileKey({\n collectionPrefix,\n docPrefix,\n filename,\n useCompositePrefixes,\n })\n\n await client.bucket(bucket).file(fileKey).delete({\n ignoreNotFound: true,\n })\n}\n"],"names":["getFileKey","deleteFile","bucket","client","collectionPrefix","docPrefix","filename","useCompositePrefixes","fileKey","file","delete","ignoreNotFound"],"mappings":"AAEA,SAASA,UAAU,QAAQ,6CAA4C;AAWvE,OAAO,eAAeC,WAAW,EAC/BC,MAAM,EACNC,MAAM,EACNC,mBAAmB,EAAE,EACrBC,SAAS,EACTC,QAAQ,EACRC,uBAAuB,KAAK,EACb;IACf,MAAM,EAAEC,OAAO,EAAE,GAAGR,WAAW;QAC7BI;QACAC;QACAC;QACAC;IACF;IAEA,MAAMJ,OAAOD,MAAM,CAACA,QAAQO,IAAI,CAACD,SAASE,MAAM,CAAC;QAC/CC,gBAAgB;IAClB;AACF"}
1
+ {"version":3,"sources":["../src/deleteFile.ts"],"sourcesContent":["import type { Storage } from '@google-cloud/storage'\n\ninterface DeleteFileArgs {\n bucket: string\n client: Storage\n storageFilePath: string\n}\n\nexport async function deleteFile({\n bucket,\n client,\n storageFilePath,\n}: DeleteFileArgs): Promise<void> {\n await client.bucket(bucket).file(storageFilePath).delete({\n ignoreNotFound: true,\n })\n}\n"],"names":["deleteFile","bucket","client","storageFilePath","file","delete","ignoreNotFound"],"mappings":"AAQA,OAAO,eAAeA,WAAW,EAC/BC,MAAM,EACNC,MAAM,EACNC,eAAe,EACA;IACf,MAAMD,OAAOD,MAAM,CAACA,QAAQG,IAAI,CAACD,iBAAiBE,MAAM,CAAC;QACvDC,gBAAgB;IAClB;AACF"}
@@ -1,12 +1,12 @@
1
- import { getFileKey } from '@payloadcms/plugin-cloud-storage/utilities';
1
+ import { buildStoragePathData } from '@payloadcms/plugin-cloud-storage/utilities';
2
2
  export function generateURL({ bucket, client, collectionPrefix = '', filename, prefix, useCompositePrefixes = false }) {
3
- const { fileKey } = getFileKey({
3
+ const { storageFilePath } = buildStoragePathData({
4
4
  collectionPrefix,
5
5
  docPrefix: prefix,
6
6
  filename,
7
7
  useCompositePrefixes
8
8
  });
9
- return client.bucket(bucket).file(fileKey).publicUrl();
9
+ return client.bucket(bucket).file(storageFilePath).publicUrl();
10
10
  }
11
11
 
12
12
  //# sourceMappingURL=generateURL.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/generateURL.ts"],"sourcesContent":["import type { Storage } from '@google-cloud/storage'\n\nimport { getFileKey } from '@payloadcms/plugin-cloud-storage/utilities'\n\ninterface GenerateURLArgs {\n bucket: string\n client: Storage\n collectionPrefix?: string\n filename: string\n prefix: string\n useCompositePrefixes?: boolean\n}\n\nexport function generateURL({\n bucket,\n client,\n collectionPrefix = '',\n filename,\n prefix,\n useCompositePrefixes = false,\n}: GenerateURLArgs): string {\n const { fileKey } = getFileKey({\n collectionPrefix,\n docPrefix: prefix,\n filename,\n useCompositePrefixes,\n })\n\n return client.bucket(bucket).file(fileKey).publicUrl()\n}\n"],"names":["getFileKey","generateURL","bucket","client","collectionPrefix","filename","prefix","useCompositePrefixes","fileKey","docPrefix","file","publicUrl"],"mappings":"AAEA,SAASA,UAAU,QAAQ,6CAA4C;AAWvE,OAAO,SAASC,YAAY,EAC1BC,MAAM,EACNC,MAAM,EACNC,mBAAmB,EAAE,EACrBC,QAAQ,EACRC,MAAM,EACNC,uBAAuB,KAAK,EACZ;IAChB,MAAM,EAAEC,OAAO,EAAE,GAAGR,WAAW;QAC7BI;QACAK,WAAWH;QACXD;QACAE;IACF;IAEA,OAAOJ,OAAOD,MAAM,CAACA,QAAQQ,IAAI,CAACF,SAASG,SAAS;AACtD"}
1
+ {"version":3,"sources":["../src/generateURL.ts"],"sourcesContent":["import type { Storage } from '@google-cloud/storage'\n\nimport { buildStoragePathData } from '@payloadcms/plugin-cloud-storage/utilities'\n\ninterface GenerateURLArgs {\n bucket: string\n client: Storage\n collectionPrefix?: string\n filename: string\n prefix: string\n useCompositePrefixes?: boolean\n}\n\nexport function generateURL({\n bucket,\n client,\n collectionPrefix = '',\n filename,\n prefix,\n useCompositePrefixes = false,\n}: GenerateURLArgs): string {\n const { storageFilePath } = buildStoragePathData({\n collectionPrefix,\n docPrefix: prefix,\n filename,\n useCompositePrefixes,\n })\n\n return client.bucket(bucket).file(storageFilePath).publicUrl()\n}\n"],"names":["buildStoragePathData","generateURL","bucket","client","collectionPrefix","filename","prefix","useCompositePrefixes","storageFilePath","docPrefix","file","publicUrl"],"mappings":"AAEA,SAASA,oBAAoB,QAAQ,6CAA4C;AAWjF,OAAO,SAASC,YAAY,EAC1BC,MAAM,EACNC,MAAM,EACNC,mBAAmB,EAAE,EACrBC,QAAQ,EACRC,MAAM,EACNC,uBAAuB,KAAK,EACZ;IAChB,MAAM,EAAEC,eAAe,EAAE,GAAGR,qBAAqB;QAC/CI;QACAK,WAAWH;QACXD;QACAE;IACF;IAEA,OAAOJ,OAAOD,MAAM,CAACA,QAAQQ,IAAI,CAACF,iBAAiBG,SAAS;AAC9D"}
@@ -1 +1 @@
1
- {"version":3,"file":"generateUploadInstructions.d.ts","sourceRoot":"","sources":["../src/generateUploadInstructions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,KAAK,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAKnF,UAAU,IAAI;IACZ,MAAM,CAAC,EAAE,wBAAwB,CAAA;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,gBAAgB,EAAE,MAAM,CAAA;IACxB,gBAAgB,EAAE,MAAM,OAAO,CAAA;IAC/B,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,eAAO,MAAM,0BAA0B,kFAMpC,IAAI,KAAG,0BAmDT,CAAA"}
1
+ {"version":3,"file":"generateUploadInstructions.d.ts","sourceRoot":"","sources":["../src/generateUploadInstructions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,KAAK,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAMnF,UAAU,IAAI;IACZ,MAAM,CAAC,EAAE,wBAAwB,CAAA;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,gBAAgB,EAAE,MAAM,CAAA;IACxB,gBAAgB,EAAE,MAAM,OAAO,CAAA;IAC/B,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAMD,eAAO,MAAM,0BAA0B,kFAMpC,IAAI,KAAG,0BA0DT,CAAA"}
@@ -1,5 +1,9 @@
1
1
  import { resolveSignedURLKey } from '@payloadcms/plugin-cloud-storage/utilities';
2
2
  import { Forbidden } from 'payload';
3
+ import { assertClientUploadAllowed } from 'payload/internal';
4
+ const createOnlyHeaders = {
5
+ 'x-goog-if-generation-match': '0'
6
+ };
3
7
  export const generateUploadInstructions = ({ access, bucket, collectionPrefix, getStorageClient, useCompositePrefixes = false })=>{
4
8
  return async ({ collectionSlug, docPrefix, filename, filesize, mimeType, overrideAccess, req })=>{
5
9
  if (!overrideAccess && (access ? !await access({
@@ -8,7 +12,12 @@ export const generateUploadInstructions = ({ access, bucket, collectionPrefix, g
8
12
  }) : !req.user)) {
9
13
  throw new Forbidden(req.t);
10
14
  }
11
- const { fileKey, sanitizedDocPrefix, sanitizedFilename } = await resolveSignedURLKey({
15
+ assertClientUploadAllowed({
16
+ collection: req.payload.collections[collectionSlug]?.config,
17
+ filename,
18
+ mimeType
19
+ });
20
+ const { sanitizedFilename, storageFilePath, uploadReference } = await resolveSignedURLKey({
12
21
  collectionPrefix,
13
22
  collectionSlug,
14
23
  docPrefix,
@@ -16,10 +25,11 @@ export const generateUploadInstructions = ({ access, bucket, collectionPrefix, g
16
25
  req,
17
26
  useCompositePrefixes
18
27
  });
19
- const [url] = await getStorageClient().bucket(bucket).file(fileKey).getSignedUrl({
28
+ const [url] = await getStorageClient().bucket(bucket).file(storageFilePath).getSignedUrl({
20
29
  action: 'write',
21
30
  contentType: mimeType,
22
31
  expires: Date.now() + 60 * 60 * 5,
32
+ extensionHeaders: createOnlyHeaders,
23
33
  version: 'v4'
24
34
  });
25
35
  return {
@@ -28,14 +38,13 @@ export const generateUploadInstructions = ({ access, bucket, collectionPrefix, g
28
38
  filename: sanitizedFilename,
29
39
  mimeType,
30
40
  size: filesize,
31
- uploadReference: {
32
- prefix: sanitizedDocPrefix
33
- }
41
+ uploadReference
34
42
  },
35
43
  request: {
36
44
  headers: {
37
45
  'Content-Length': String(filesize),
38
- 'Content-Type': mimeType
46
+ 'Content-Type': mimeType,
47
+ ...createOnlyHeaders
39
48
  },
40
49
  method: 'PUT',
41
50
  url
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/generateUploadInstructions.ts"],"sourcesContent":["import type { Storage } from '@google-cloud/storage'\nimport type { GenerateUploadInstructions, UploadInstructionsAccess } from 'payload'\n\nimport { resolveSignedURLKey } from '@payloadcms/plugin-cloud-storage/utilities'\nimport { Forbidden } from 'payload'\n\ninterface Args {\n access?: UploadInstructionsAccess\n bucket: string\n collectionPrefix: string\n getStorageClient: () => Storage\n useCompositePrefixes?: boolean\n}\n\nexport const generateUploadInstructions = ({\n access,\n bucket,\n collectionPrefix,\n getStorageClient,\n useCompositePrefixes = false,\n}: Args): GenerateUploadInstructions => {\n return async ({\n collectionSlug,\n docPrefix,\n filename,\n filesize,\n mimeType,\n overrideAccess,\n req,\n }) => {\n if (!overrideAccess && (access ? !(await access({ collectionSlug, req })) : !req.user)) {\n throw new Forbidden(req.t)\n }\n\n const { fileKey, sanitizedDocPrefix, sanitizedFilename } = await resolveSignedURLKey({\n collectionPrefix,\n collectionSlug,\n docPrefix,\n filename,\n req,\n useCompositePrefixes,\n })\n\n const [url] = await getStorageClient()\n .bucket(bucket)\n .file(fileKey)\n .getSignedUrl({\n action: 'write',\n contentType: mimeType,\n expires: Date.now() + 60 * 60 * 5,\n version: 'v4',\n })\n\n return {\n type: 'http',\n file: {\n filename: sanitizedFilename,\n mimeType,\n size: filesize,\n uploadReference: { prefix: sanitizedDocPrefix },\n },\n request: {\n headers: {\n 'Content-Length': String(filesize),\n 'Content-Type': mimeType,\n },\n method: 'PUT',\n url,\n },\n }\n }\n}\n"],"names":["resolveSignedURLKey","Forbidden","generateUploadInstructions","access","bucket","collectionPrefix","getStorageClient","useCompositePrefixes","collectionSlug","docPrefix","filename","filesize","mimeType","overrideAccess","req","user","t","fileKey","sanitizedDocPrefix","sanitizedFilename","url","file","getSignedUrl","action","contentType","expires","Date","now","version","type","size","uploadReference","prefix","request","headers","String","method"],"mappings":"AAGA,SAASA,mBAAmB,QAAQ,6CAA4C;AAChF,SAASC,SAAS,QAAQ,UAAS;AAUnC,OAAO,MAAMC,6BAA6B,CAAC,EACzCC,MAAM,EACNC,MAAM,EACNC,gBAAgB,EAChBC,gBAAgB,EAChBC,uBAAuB,KAAK,EACvB;IACL,OAAO,OAAO,EACZC,cAAc,EACdC,SAAS,EACTC,QAAQ,EACRC,QAAQ,EACRC,QAAQ,EACRC,cAAc,EACdC,GAAG,EACJ;QACC,IAAI,CAACD,kBAAmBV,CAAAA,SAAS,CAAE,MAAMA,OAAO;YAAEK;YAAgBM;QAAI,KAAM,CAACA,IAAIC,IAAI,AAAD,GAAI;YACtF,MAAM,IAAId,UAAUa,IAAIE,CAAC;QAC3B;QAEA,MAAM,EAAEC,OAAO,EAAEC,kBAAkB,EAAEC,iBAAiB,EAAE,GAAG,MAAMnB,oBAAoB;YACnFK;YACAG;YACAC;YACAC;YACAI;YACAP;QACF;QAEA,MAAM,CAACa,IAAI,GAAG,MAAMd,mBACjBF,MAAM,CAACA,QACPiB,IAAI,CAACJ,SACLK,YAAY,CAAC;YACZC,QAAQ;YACRC,aAAaZ;YACba,SAASC,KAAKC,GAAG,KAAK,KAAK,KAAK;YAChCC,SAAS;QACX;QAEF,OAAO;YACLC,MAAM;YACNR,MAAM;gBACJX,UAAUS;gBACVP;gBACAkB,MAAMnB;gBACNoB,iBAAiB;oBAAEC,QAAQd;gBAAmB;YAChD;YACAe,SAAS;gBACPC,SAAS;oBACP,kBAAkBC,OAAOxB;oBACzB,gBAAgBC;gBAClB;gBACAwB,QAAQ;gBACRhB;YACF;QACF;IACF;AACF,EAAC"}
1
+ {"version":3,"sources":["../src/generateUploadInstructions.ts"],"sourcesContent":["import type { Storage } from '@google-cloud/storage'\nimport type { GenerateUploadInstructions, UploadInstructionsAccess } from 'payload'\n\nimport { resolveSignedURLKey } from '@payloadcms/plugin-cloud-storage/utilities'\nimport { Forbidden } from 'payload'\nimport { assertClientUploadAllowed } from 'payload/internal'\n\ninterface Args {\n access?: UploadInstructionsAccess\n bucket: string\n collectionPrefix: string\n getStorageClient: () => Storage\n useCompositePrefixes?: boolean\n}\n\nconst createOnlyHeaders = {\n 'x-goog-if-generation-match': '0',\n}\n\nexport const generateUploadInstructions = ({\n access,\n bucket,\n collectionPrefix,\n getStorageClient,\n useCompositePrefixes = false,\n}: Args): GenerateUploadInstructions => {\n return async ({\n collectionSlug,\n docPrefix,\n filename,\n filesize,\n mimeType,\n overrideAccess,\n req,\n }) => {\n if (!overrideAccess && (access ? !(await access({ collectionSlug, req })) : !req.user)) {\n throw new Forbidden(req.t)\n }\n\n assertClientUploadAllowed({\n collection: req.payload.collections[collectionSlug]?.config,\n filename,\n mimeType,\n })\n const { sanitizedFilename, storageFilePath, uploadReference } = await resolveSignedURLKey({\n collectionPrefix,\n collectionSlug,\n docPrefix,\n filename,\n req,\n useCompositePrefixes,\n })\n\n const [url] = await getStorageClient()\n .bucket(bucket)\n .file(storageFilePath)\n .getSignedUrl({\n action: 'write',\n contentType: mimeType,\n expires: Date.now() + 60 * 60 * 5,\n extensionHeaders: createOnlyHeaders,\n version: 'v4',\n })\n\n return {\n type: 'http',\n file: {\n filename: sanitizedFilename,\n mimeType,\n size: filesize,\n uploadReference,\n },\n request: {\n headers: {\n 'Content-Length': String(filesize),\n 'Content-Type': mimeType,\n ...createOnlyHeaders,\n },\n method: 'PUT',\n url,\n },\n }\n }\n}\n"],"names":["resolveSignedURLKey","Forbidden","assertClientUploadAllowed","createOnlyHeaders","generateUploadInstructions","access","bucket","collectionPrefix","getStorageClient","useCompositePrefixes","collectionSlug","docPrefix","filename","filesize","mimeType","overrideAccess","req","user","t","collection","payload","collections","config","sanitizedFilename","storageFilePath","uploadReference","url","file","getSignedUrl","action","contentType","expires","Date","now","extensionHeaders","version","type","size","request","headers","String","method"],"mappings":"AAGA,SAASA,mBAAmB,QAAQ,6CAA4C;AAChF,SAASC,SAAS,QAAQ,UAAS;AACnC,SAASC,yBAAyB,QAAQ,mBAAkB;AAU5D,MAAMC,oBAAoB;IACxB,8BAA8B;AAChC;AAEA,OAAO,MAAMC,6BAA6B,CAAC,EACzCC,MAAM,EACNC,MAAM,EACNC,gBAAgB,EAChBC,gBAAgB,EAChBC,uBAAuB,KAAK,EACvB;IACL,OAAO,OAAO,EACZC,cAAc,EACdC,SAAS,EACTC,QAAQ,EACRC,QAAQ,EACRC,QAAQ,EACRC,cAAc,EACdC,GAAG,EACJ;QACC,IAAI,CAACD,kBAAmBV,CAAAA,SAAS,CAAE,MAAMA,OAAO;YAAEK;YAAgBM;QAAI,KAAM,CAACA,IAAIC,IAAI,AAAD,GAAI;YACtF,MAAM,IAAIhB,UAAUe,IAAIE,CAAC;QAC3B;QAEAhB,0BAA0B;YACxBiB,YAAYH,IAAII,OAAO,CAACC,WAAW,CAACX,eAAe,EAAEY;YACrDV;YACAE;QACF;QACA,MAAM,EAAES,iBAAiB,EAAEC,eAAe,EAAEC,eAAe,EAAE,GAAG,MAAMzB,oBAAoB;YACxFO;YACAG;YACAC;YACAC;YACAI;YACAP;QACF;QAEA,MAAM,CAACiB,IAAI,GAAG,MAAMlB,mBACjBF,MAAM,CAACA,QACPqB,IAAI,CAACH,iBACLI,YAAY,CAAC;YACZC,QAAQ;YACRC,aAAahB;YACbiB,SAASC,KAAKC,GAAG,KAAK,KAAK,KAAK;YAChCC,kBAAkB/B;YAClBgC,SAAS;QACX;QAEF,OAAO;YACLC,MAAM;YACNT,MAAM;gBACJf,UAAUW;gBACVT;gBACAuB,MAAMxB;gBACNY;YACF;YACAa,SAAS;gBACPC,SAAS;oBACP,kBAAkBC,OAAO3B;oBACzB,gBAAgBC;oBAChB,GAAGX,iBAAiB;gBACtB;gBACAsC,QAAQ;gBACRf;YACF;QACF;IACF;AACF,EAAC"}
package/dist/getFile.d.ts CHANGED
@@ -1,17 +1,18 @@
1
1
  import type { Storage } from '@google-cloud/storage';
2
- import type { CollectionConfig, PayloadRequest } from 'payload';
2
+ import type { CollectionConfig, FileHandlerOperation, PayloadRequest, TypeWithID } from 'payload';
3
3
  interface GetFileArgs {
4
4
  bucket: string;
5
5
  client: Storage;
6
6
  collection: CollectionConfig;
7
7
  collectionPrefix?: string;
8
+ doc?: TypeWithID;
8
9
  filename: string;
9
10
  incomingHeaders?: Headers;
10
- prefixQueryParam?: string;
11
+ operation?: FileHandlerOperation;
11
12
  req: PayloadRequest;
12
13
  uploadReference?: unknown;
13
14
  useCompositePrefixes?: boolean;
14
15
  }
15
- export declare function getFile({ bucket, client, collection, collectionPrefix, filename, incomingHeaders, prefixQueryParam, req, uploadReference, useCompositePrefixes, }: GetFileArgs): Promise<Response>;
16
+ export declare function getFile({ bucket, client, collection, collectionPrefix, doc, filename, incomingHeaders, operation, req, uploadReference, useCompositePrefixes, }: GetFileArgs): Promise<Response>;
16
17
  export {};
17
18
  //# 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,OAAO,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAS/D,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;IACf,UAAU,EAAE,gBAAgB,CAAA;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,GAAG,EAAE,cAAc,CAAA;IACnB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,wBAAsB,OAAO,CAAC,EAC5B,MAAM,EACN,MAAM,EACN,UAAU,EACV,gBAAqB,EACrB,QAAQ,EACR,eAAe,EACf,gBAAgB,EAChB,GAAG,EACH,eAAe,EACf,oBAA4B,GAC7B,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiGjC"}
1
+ {"version":3,"file":"getFile.d.ts","sourceRoot":"","sources":["../src/getFile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AASjG,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;IACf,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,CAAC,EAAE,OAAO,CAAA;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,wBAAsB,OAAO,CAAC,EAC5B,MAAM,EACN,MAAM,EACN,UAAU,EACV,gBAAqB,EACrB,GAAG,EACH,QAAQ,EACR,eAAe,EACf,SAAkB,EAClB,GAAG,EACH,eAAe,EACf,oBAA4B,GAC7B,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAgIjC"}
package/dist/getFile.js CHANGED
@@ -1,25 +1,27 @@
1
1
  import { ApiError } from '@google-cloud/storage';
2
- import { getFilePrefix as getDocPrefix, getFileKey } from '@payloadcms/plugin-cloud-storage/utilities';
3
- import { getRangeRequestInfo } from 'payload/internal';
4
- export async function getFile({ bucket, client, collection, collectionPrefix = '', filename, incomingHeaders, prefixQueryParam, req, uploadReference, useCompositePrefixes = false }) {
2
+ import { buildStoragePathData, getFilePrefix as getDocPrefix } from '@payloadcms/plugin-cloud-storage/utilities';
3
+ import { getRangeRequestInfo, isXmlMimeType, uploadContentSecurityPolicy } from 'payload/internal';
4
+ export async function getFile({ bucket, client, collection, collectionPrefix = '', doc, filename, incomingHeaders, operation = 'read', req, uploadReference, useCompositePrefixes = false }) {
5
+ const isTransformSource = operation === 'transform';
5
6
  try {
6
7
  const docPrefix = await getDocPrefix({
7
8
  collection,
9
+ collectionPrefix,
10
+ doc,
8
11
  filename,
9
- prefixQueryParam,
10
12
  req,
11
- uploadReference
13
+ uploadReference,
14
+ useCompositePrefixes
12
15
  });
13
- const { fileKey } = getFileKey({
16
+ const { storageFilePath } = buildStoragePathData({
14
17
  collectionPrefix,
15
18
  docPrefix,
16
19
  filename,
17
20
  useCompositePrefixes
18
21
  });
19
- const file = client.bucket(bucket).file(fileKey);
22
+ const file = client.bucket(bucket).file(storageFilePath);
20
23
  const [metadata] = await file.getMetadata();
21
- // Handle range request
22
- const rangeHeader = req.headers.get('range');
24
+ const rangeHeader = isTransformSource ? null : req.headers.get('range');
23
25
  const fileSize = Number(metadata.size);
24
26
  const rangeResult = getRangeRequestInfo({
25
27
  fileSize,
@@ -34,42 +36,66 @@ export async function getFile({ bucket, client, collection, collectionPrefix = '
34
36
  const etagFromHeaders = req.headers.get('etag') || req.headers.get('if-none-match');
35
37
  const objectEtag = metadata.etag;
36
38
  let headers = new Headers(incomingHeaders);
37
- // Add range-related headers from the result
38
39
  for (const [key, value] of Object.entries(rangeResult.headers)){
39
40
  headers.append(key, value);
40
41
  }
41
42
  headers.append('Content-Type', String(metadata.contentType));
42
43
  headers.append('ETag', String(metadata.etag));
43
- // Add Content-Security-Policy header for SVG files to prevent executable code
44
- if (metadata.contentType === 'image/svg+xml') {
45
- headers.append('Content-Security-Policy', "script-src 'none'");
44
+ // Apply a restrictive policy to XML-family responses served through Payload.
45
+ if (isXmlMimeType(metadata.contentType)) {
46
+ headers.append('Content-Security-Policy', uploadContentSecurityPolicy);
46
47
  }
47
- if (collection.upload && typeof collection.upload === 'object' && typeof collection.upload.modifyResponseHeaders === 'function') {
48
+ if (!isTransformSource && collection.upload && typeof collection.upload === 'object' && typeof collection.upload.modifyResponseHeaders === 'function') {
48
49
  headers = collection.upload.modifyResponseHeaders({
49
50
  headers
50
51
  }) || headers;
51
52
  }
52
- if (etagFromHeaders && etagFromHeaders === objectEtag) {
53
+ if (!isTransformSource && etagFromHeaders && etagFromHeaders === objectEtag) {
53
54
  return new Response(null, {
54
55
  headers,
55
56
  status: 304
56
57
  });
57
58
  }
58
- // Manually create a ReadableStream for the web from a Node.js stream.
59
+ const streamOptions = rangeResult.type === 'partial' ? {
60
+ end: rangeResult.rangeEnd,
61
+ start: rangeResult.rangeStart
62
+ } : {};
63
+ const nodeStream = file.createReadStream(streamOptions);
64
+ // A consumer that stops reading (a failed transform, a disconnected client) must end the
65
+ // GCS download too, otherwise the underlying connection stays open until it drains.
66
+ let onAbort;
67
+ const removeAbortListener = ()=>{
68
+ if (onAbort) {
69
+ req.signal?.removeEventListener('abort', onAbort);
70
+ }
71
+ };
72
+ const stopReading = ()=>{
73
+ removeAbortListener();
74
+ nodeStream.destroy();
75
+ };
59
76
  const readableStream = new ReadableStream({
77
+ cancel: stopReading,
60
78
  start (controller) {
61
- const streamOptions = rangeResult.type === 'partial' ? {
62
- end: rangeResult.rangeEnd,
63
- start: rangeResult.rangeStart
64
- } : {};
65
- const nodeStream = file.createReadStream(streamOptions);
79
+ onAbort = ()=>{
80
+ stopReading();
81
+ controller.error(req.signal?.reason);
82
+ };
83
+ if (req.signal?.aborted) {
84
+ onAbort();
85
+ return;
86
+ }
87
+ req.signal?.addEventListener('abort', onAbort, {
88
+ once: true
89
+ });
66
90
  nodeStream.on('data', (chunk)=>{
67
91
  controller.enqueue(new Uint8Array(chunk));
68
92
  });
69
93
  nodeStream.on('end', ()=>{
94
+ removeAbortListener();
70
95
  controller.close();
71
96
  });
72
97
  nodeStream.on('error', (err)=>{
98
+ removeAbortListener();
73
99
  controller.error(err);
74
100
  });
75
101
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/getFile.ts"],"sourcesContent":["import type { Storage } from '@google-cloud/storage'\nimport type { CollectionConfig, PayloadRequest } from 'payload'\n\nimport { ApiError } from '@google-cloud/storage'\nimport {\n getFilePrefix as getDocPrefix,\n getFileKey,\n} from '@payloadcms/plugin-cloud-storage/utilities'\nimport { getRangeRequestInfo } from 'payload/internal'\n\ninterface GetFileArgs {\n bucket: string\n client: Storage\n collection: CollectionConfig\n collectionPrefix?: string\n filename: string\n incomingHeaders?: Headers\n prefixQueryParam?: string\n req: PayloadRequest\n uploadReference?: unknown\n useCompositePrefixes?: boolean\n}\n\nexport async function getFile({\n bucket,\n client,\n collection,\n collectionPrefix = '',\n filename,\n incomingHeaders,\n prefixQueryParam,\n req,\n uploadReference,\n useCompositePrefixes = false,\n}: GetFileArgs): Promise<Response> {\n try {\n const docPrefix = await getDocPrefix({\n collection,\n filename,\n prefixQueryParam,\n req,\n uploadReference,\n })\n\n const { fileKey } = getFileKey({\n collectionPrefix,\n docPrefix,\n filename,\n useCompositePrefixes,\n })\n\n const file = client.bucket(bucket).file(fileKey)\n\n const [metadata] = await file.getMetadata()\n\n // Handle range request\n const rangeHeader = req.headers.get('range')\n const fileSize = Number(metadata.size)\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 etagFromHeaders = req.headers.get('etag') || req.headers.get('if-none-match')\n const objectEtag = metadata.etag\n\n let headers = new Headers(incomingHeaders)\n\n // Add range-related headers from the result\n for (const [key, value] of Object.entries(rangeResult.headers)) {\n headers.append(key, value)\n }\n\n headers.append('Content-Type', String(metadata.contentType))\n headers.append('ETag', String(metadata.etag))\n\n // Add Content-Security-Policy header for SVG files to prevent executable code\n if (metadata.contentType === 'image/svg+xml') {\n headers.append('Content-Security-Policy', \"script-src 'none'\")\n }\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 // Manually create a ReadableStream for the web from a Node.js stream.\n const readableStream = new ReadableStream({\n start(controller) {\n const streamOptions =\n rangeResult.type === 'partial'\n ? { end: rangeResult.rangeEnd, start: rangeResult.rangeStart }\n : {}\n const nodeStream = file.createReadStream(streamOptions)\n nodeStream.on('data', (chunk) => {\n controller.enqueue(new Uint8Array(chunk))\n })\n nodeStream.on('end', () => {\n controller.close()\n })\n nodeStream.on('error', (err) => {\n controller.error(err)\n })\n },\n })\n\n return new Response(readableStream, {\n headers,\n status: rangeResult.status,\n })\n } catch (err: unknown) {\n if (err instanceof ApiError && err.code === 404) {\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 }\n}\n"],"names":["ApiError","getFilePrefix","getDocPrefix","getFileKey","getRangeRequestInfo","getFile","bucket","client","collection","collectionPrefix","filename","incomingHeaders","prefixQueryParam","req","uploadReference","useCompositePrefixes","docPrefix","fileKey","file","metadata","getMetadata","rangeHeader","headers","get","fileSize","Number","size","rangeResult","type","Response","Headers","status","etagFromHeaders","objectEtag","etag","key","value","Object","entries","append","String","contentType","upload","modifyResponseHeaders","readableStream","ReadableStream","start","controller","streamOptions","end","rangeEnd","rangeStart","nodeStream","createReadStream","on","chunk","enqueue","Uint8Array","close","err","error","code","statusText","payload","logger"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/getFile.ts"],"sourcesContent":["import type { Storage } from '@google-cloud/storage'\nimport type { CollectionConfig, FileHandlerOperation, PayloadRequest, TypeWithID } from 'payload'\n\nimport { ApiError } from '@google-cloud/storage'\nimport {\n buildStoragePathData,\n getFilePrefix as getDocPrefix,\n} from '@payloadcms/plugin-cloud-storage/utilities'\nimport { getRangeRequestInfo, isXmlMimeType, uploadContentSecurityPolicy } from 'payload/internal'\n\ninterface GetFileArgs {\n bucket: string\n client: Storage\n collection: CollectionConfig\n collectionPrefix?: string\n doc?: TypeWithID\n filename: string\n incomingHeaders?: Headers\n operation?: FileHandlerOperation\n req: PayloadRequest\n uploadReference?: unknown\n useCompositePrefixes?: boolean\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 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,\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 const file = client.bucket(bucket).file(storageFilePath)\n\n const [metadata] = await file.getMetadata()\n\n const rangeHeader = isTransformSource ? null : req.headers.get('range')\n const fileSize = Number(metadata.size)\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 etagFromHeaders = req.headers.get('etag') || req.headers.get('if-none-match')\n const objectEtag = metadata.etag\n\n let headers = new Headers(incomingHeaders)\n\n for (const [key, value] of Object.entries(rangeResult.headers)) {\n headers.append(key, value)\n }\n\n headers.append('Content-Type', String(metadata.contentType))\n headers.append('ETag', String(metadata.etag))\n\n // Apply a restrictive policy to XML-family responses served through Payload.\n if (isXmlMimeType(metadata.contentType)) {\n headers.append('Content-Security-Policy', uploadContentSecurityPolicy)\n }\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 const streamOptions =\n rangeResult.type === 'partial'\n ? { end: rangeResult.rangeEnd, start: rangeResult.rangeStart }\n : {}\n const nodeStream = file.createReadStream(streamOptions)\n\n // A consumer that stops reading (a failed transform, a disconnected client) must end the\n // GCS download too, otherwise the underlying connection stays open until it drains.\n let onAbort: (() => void) | undefined\n const removeAbortListener = () => {\n if (onAbort) {\n req.signal?.removeEventListener('abort', onAbort)\n }\n }\n const stopReading = () => {\n removeAbortListener()\n nodeStream.destroy()\n }\n\n const readableStream = new ReadableStream<Uint8Array>({\n cancel: stopReading,\n start(controller) {\n onAbort = () => {\n stopReading()\n controller.error(req.signal?.reason)\n }\n\n if (req.signal?.aborted) {\n onAbort()\n return\n }\n\n req.signal?.addEventListener('abort', onAbort, { once: true })\n\n nodeStream.on('data', (chunk) => {\n controller.enqueue(new Uint8Array(chunk))\n })\n nodeStream.on('end', () => {\n removeAbortListener()\n controller.close()\n })\n nodeStream.on('error', (err) => {\n removeAbortListener()\n controller.error(err)\n })\n },\n })\n\n return new Response(readableStream, {\n headers,\n status: rangeResult.status,\n })\n } catch (err: unknown) {\n if (err instanceof ApiError && err.code === 404) {\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 }\n}\n"],"names":["ApiError","buildStoragePathData","getFilePrefix","getDocPrefix","getRangeRequestInfo","isXmlMimeType","uploadContentSecurityPolicy","getFile","bucket","client","collection","collectionPrefix","doc","filename","incomingHeaders","operation","req","uploadReference","useCompositePrefixes","isTransformSource","docPrefix","storageFilePath","file","metadata","getMetadata","rangeHeader","headers","get","fileSize","Number","size","rangeResult","type","Response","Headers","status","etagFromHeaders","objectEtag","etag","key","value","Object","entries","append","String","contentType","upload","modifyResponseHeaders","streamOptions","end","rangeEnd","start","rangeStart","nodeStream","createReadStream","onAbort","removeAbortListener","signal","removeEventListener","stopReading","destroy","readableStream","ReadableStream","cancel","controller","error","reason","aborted","addEventListener","once","on","chunk","enqueue","Uint8Array","close","err","code","statusText","payload","logger"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -3,16 +3,6 @@ import type { ClientUploadsConfig, CollectionOptions } from '@payloadcms/plugin-
3
3
  import type { StorageAdapter, UploadCollectionSlug } from 'payload';
4
4
  export interface GcsStorageOptions {
5
5
  acl?: 'Private' | 'Public';
6
- /**
7
- * When enabled, fields (like the prefix field) will always be inserted into
8
- * the collection schema regardless of whether the plugin is enabled. This
9
- * ensures a consistent schema across all environments.
10
- *
11
- * This will be enabled by default in Payload v4.
12
- *
13
- * @default false
14
- */
15
- alwaysInsertFields?: boolean;
16
6
  /**
17
7
  * The name of the bucket to use.
18
8
  */
@@ -46,14 +36,15 @@ export interface GcsStorageOptions {
46
36
  options: StorageOptions;
47
37
  /**
48
38
  * When true, the collection-level prefix and document-level prefix are combined
49
- * (compositional). When false (default), document prefix overrides collection
50
- * prefix entirely.
39
+ * (compositional). When false (default), a document prefix already within the
40
+ * collection prefix is used as-is for new uploads; otherwise it is nested beneath it.
41
+ * Existing files retain their stored prefixes for reads, URLs, and cleanup.
51
42
  *
52
- * Example:
53
- * - collection prefix: `collection-prefix/`
54
- * - document prefix: `document-prefix/`
55
- * - resulting prefix with useCompositePrefixes=true: `collection-prefix/document-prefix/`
56
- * - resulting prefix with useCompositePrefixes=false: `document-prefix/`
43
+ * Example with a document prefix already contained by the collection prefix:
44
+ * - collection prefix: `uploads/`
45
+ * - document prefix: `uploads/documents/`
46
+ * - resulting prefix with useCompositePrefixes=true: `uploads/uploads/documents/`
47
+ * - resulting prefix with useCompositePrefixes=false: `uploads/documents/`
57
48
  *
58
49
  * @default false
59
50
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAC3D,OAAO,KAAK,EACV,mBAAmB,EAEnB,iBAAiB,EAClB,MAAM,wCAAwC,CAAA;AAC/C,OAAO,KAAK,EAAU,cAAc,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAO3E,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IAE1B;;;;;;;;OAQG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAE5B;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;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;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,OAAO,EAAE,cAAc,CAAA;IAEvB;;;;;;;;;;;;OAYG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,KAAK,iBAAiB,GAAG,CAAC,cAAc,EAAE,iBAAiB,KAAK,cAAc,CAAA;AAI9E,eAAO,MAAM,UAAU,EAAE,iBAqEvB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAC3D,OAAO,KAAK,EACV,mBAAmB,EAEnB,iBAAiB,EAClB,MAAM,wCAAwC,CAAA;AAC/C,OAAO,KAAK,EAAU,cAAc,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAO3E,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IAE1B;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;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;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,OAAO,EAAE,cAAc,CAAA;IAEvB;;;;;;;;;;;;;OAaG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,KAAK,iBAAiB,GAAG,CAAC,cAAc,EAAE,iBAAiB,KAAK,cAAc,CAAA;AAI9E,eAAO,MAAM,UAAU,EAAE,iBAoFvB,CAAA"}
package/dist/index.js CHANGED
@@ -23,7 +23,20 @@ export const gcsStorage = (gcsStorageOptions)=>({
23
23
  });
24
24
  const isPluginDisabled = gcsStorageOptions.enabled === false;
25
25
  if (isPluginDisabled) {
26
- return incomingConfig;
26
+ // Still call cloudStoragePlugin with adapter: null so fields (like prefix) are
27
+ // inserted into the schema, keeping it consistent across environments.
28
+ const collectionsWithoutAdapter = Object.entries(gcsStorageOptions.collections).reduce((acc, [slug, collOptions])=>({
29
+ ...acc,
30
+ [slug]: {
31
+ ...collOptions === true ? {} : collOptions,
32
+ adapter: null
33
+ }
34
+ }), {});
35
+ return cloudStoragePlugin({
36
+ collections: collectionsWithoutAdapter,
37
+ enabled: false,
38
+ useCompositePrefixes: gcsStorageOptions.useCompositePrefixes
39
+ })(incomingConfig);
27
40
  }
28
41
  // Add adapter to each collection option object
29
42
  const collectionsWithAdapter = Object.entries(gcsStorageOptions.collections).reduce((acc, [slug, collOptions])=>({
@@ -50,7 +63,6 @@ export const gcsStorage = (gcsStorageOptions)=>({
50
63
  })
51
64
  };
52
65
  return cloudStoragePlugin({
53
- alwaysInsertFields: gcsStorageOptions.alwaysInsertFields,
54
66
  collections: collectionsWithAdapter,
55
67
  useCompositePrefixes: gcsStorageOptions.useCompositePrefixes
56
68
  })(config);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { StorageOptions } from '@google-cloud/storage'\nimport 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 { Storage } from '@google-cloud/storage'\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\n\nimport { createGcsAdapter } from './adapter.js'\n\nexport interface GcsStorageOptions {\n acl?: 'Private' | 'Public'\n\n /**\n * When enabled, fields (like the prefix field) will always be inserted into\n * the collection schema regardless of whether the plugin is enabled. This\n * ensures a consistent schema across all environments.\n *\n * This will be enabled by default in Payload v4.\n *\n * @default false\n */\n alwaysInsertFields?: boolean\n\n /**\n * The name of the bucket to use.\n */\n bucket: string\n /**\n * Optional cache key to identify the GCS storage client instance.\n * If not provided, a default key will be used.\n *\n * @default `gcs:containerName`\n */\n clientCacheKey?: string\n /**\n * Upload directly to GCS instead of through Payload. You must allow CORS PUT requests from your website.\n */\n clientUploads?: ClientUploadsConfig\n /**\n * Collection options to apply the S3 adapter to.\n */\n collections: Partial<Record<UploadCollectionSlug, Omit<CollectionOptions, 'adapter'> | true>>\n /**\n * Whether or not to enable the plugin\n *\n * Default: true\n */\n enabled?: boolean\n /**\n * Google Cloud Storage client configuration.\n *\n * @see https://github.com/googleapis/nodejs-storage\n */\n options: StorageOptions\n\n /**\n * When true, the collection-level prefix and document-level prefix are combined\n * (compositional). When false (default), document prefix overrides collection\n * prefix entirely.\n *\n * Example:\n * - collection prefix: `collection-prefix/`\n * - document prefix: `document-prefix/`\n * - resulting prefix with useCompositePrefixes=true: `collection-prefix/document-prefix/`\n * - resulting prefix with useCompositePrefixes=false: `document-prefix/`\n *\n * @default false\n */\n useCompositePrefixes?: boolean\n}\n\ntype GcsStorageFactory = (gcsStorageArgs: GcsStorageOptions) => StorageAdapter\n\nconst gcsClients = new Map<string, Storage>()\n\nexport const gcsStorage: GcsStorageFactory = (\n gcsStorageOptions: GcsStorageOptions,\n): StorageAdapter => ({\n name: 'gcs',\n collections: Object.keys(gcsStorageOptions.collections),\n init: (incomingConfig: Config): Config => {\n const cacheKey = gcsStorageOptions.clientCacheKey || `gcs:${gcsStorageOptions.bucket}`\n\n const getStorageClient = (): Storage => {\n if (gcsClients.has(cacheKey)) {\n return gcsClients.get(cacheKey)!\n }\n gcsClients.set(cacheKey, new Storage(gcsStorageOptions.options))\n\n return gcsClients.get(cacheKey)!\n }\n\n const adapter = createGcsAdapter({\n acl: gcsStorageOptions.acl,\n bucket: gcsStorageOptions.bucket,\n clientUploads: gcsStorageOptions.clientUploads,\n getStorageClient,\n useCompositePrefixes: gcsStorageOptions.useCompositePrefixes,\n })\n\n const isPluginDisabled = gcsStorageOptions.enabled === false\n\n if (isPluginDisabled) {\n return incomingConfig\n }\n\n // Add adapter to each collection option object\n const collectionsWithAdapter: CloudStoragePluginOptions['collections'] = Object.entries(\n gcsStorageOptions.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 alwaysInsertFields: gcsStorageOptions.alwaysInsertFields,\n collections: collectionsWithAdapter,\n useCompositePrefixes: gcsStorageOptions.useCompositePrefixes,\n })(config)\n },\n})\n"],"names":["Storage","cloudStoragePlugin","createGcsAdapter","gcsClients","Map","gcsStorage","gcsStorageOptions","name","collections","Object","keys","init","incomingConfig","cacheKey","clientCacheKey","bucket","getStorageClient","has","get","set","options","adapter","acl","clientUploads","useCompositePrefixes","isPluginDisabled","enabled","collectionsWithAdapter","entries","reduce","acc","slug","collOptions","config","map","collection","upload","disableLocalStorage","alwaysInsertFields"],"mappings":"AAQA,SAASA,OAAO,QAAQ,wBAAuB;AAC/C,SAASC,kBAAkB,QAAQ,mCAAkC;AAErE,SAASC,gBAAgB,QAAQ,eAAc;AAkE/C,MAAMC,aAAa,IAAIC;AAEvB,OAAO,MAAMC,aAAgC,CAC3CC,oBACoB,CAAA;QACpBC,MAAM;QACNC,aAAaC,OAAOC,IAAI,CAACJ,kBAAkBE,WAAW;QACtDG,MAAM,CAACC;YACL,MAAMC,WAAWP,kBAAkBQ,cAAc,IAAI,CAAC,IAAI,EAAER,kBAAkBS,MAAM,EAAE;YAEtF,MAAMC,mBAAmB;gBACvB,IAAIb,WAAWc,GAAG,CAACJ,WAAW;oBAC5B,OAAOV,WAAWe,GAAG,CAACL;gBACxB;gBACAV,WAAWgB,GAAG,CAACN,UAAU,IAAIb,QAAQM,kBAAkBc,OAAO;gBAE9D,OAAOjB,WAAWe,GAAG,CAACL;YACxB;YAEA,MAAMQ,UAAUnB,iBAAiB;gBAC/BoB,KAAKhB,kBAAkBgB,GAAG;gBAC1BP,QAAQT,kBAAkBS,MAAM;gBAChCQ,eAAejB,kBAAkBiB,aAAa;gBAC9CP;gBACAQ,sBAAsBlB,kBAAkBkB,oBAAoB;YAC9D;YAEA,MAAMC,mBAAmBnB,kBAAkBoB,OAAO,KAAK;YAEvD,IAAID,kBAAkB;gBACpB,OAAOb;YACT;YAEA,+CAA+C;YAC/C,MAAMe,yBAAmElB,OAAOmB,OAAO,CACrFtB,kBAAkBE,WAAW,EAC7BqB,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,MAAMY,SAAS;gBACb,GAAGrB,cAAc;gBACjBJ,aAAa,AAACI,CAAAA,eAAeJ,WAAW,IAAI,EAAE,AAAD,EAAG0B,GAAG,CAAC,CAACC;oBACnD,IAAI,CAACR,sBAAsB,CAACQ,WAAWJ,IAAI,CAAC,EAAE;wBAC5C,OAAOI;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,OAAOpC,mBAAmB;gBACxBqC,oBAAoBhC,kBAAkBgC,kBAAkB;gBACxD9B,aAAamB;gBACbH,sBAAsBlB,kBAAkBkB,oBAAoB;YAC9D,GAAGS;QACL;IACF,CAAA,EAAE"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { StorageOptions } from '@google-cloud/storage'\nimport 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 { Storage } from '@google-cloud/storage'\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\n\nimport { createGcsAdapter } from './adapter.js'\n\nexport interface GcsStorageOptions {\n acl?: 'Private' | 'Public'\n\n /**\n * The name of the bucket to use.\n */\n bucket: string\n /**\n * Optional cache key to identify the GCS storage client instance.\n * If not provided, a default key will be used.\n *\n * @default `gcs:containerName`\n */\n clientCacheKey?: string\n /**\n * Upload directly to GCS instead of through Payload. You must allow CORS PUT requests from your website.\n */\n clientUploads?: ClientUploadsConfig\n /**\n * Collection options to apply the S3 adapter to.\n */\n collections: Partial<Record<UploadCollectionSlug, Omit<CollectionOptions, 'adapter'> | true>>\n /**\n * Whether or not to enable the plugin\n *\n * Default: true\n */\n enabled?: boolean\n /**\n * Google Cloud Storage client configuration.\n *\n * @see https://github.com/googleapis/nodejs-storage\n */\n options: StorageOptions\n\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 GcsStorageFactory = (gcsStorageArgs: GcsStorageOptions) => StorageAdapter\n\nconst gcsClients = new Map<string, Storage>()\n\nexport const gcsStorage: GcsStorageFactory = (\n gcsStorageOptions: GcsStorageOptions,\n): StorageAdapter => ({\n name: 'gcs',\n collections: Object.keys(gcsStorageOptions.collections),\n init: (incomingConfig: Config): Config => {\n const cacheKey = gcsStorageOptions.clientCacheKey || `gcs:${gcsStorageOptions.bucket}`\n\n const getStorageClient = (): Storage => {\n if (gcsClients.has(cacheKey)) {\n return gcsClients.get(cacheKey)!\n }\n gcsClients.set(cacheKey, new Storage(gcsStorageOptions.options))\n\n return gcsClients.get(cacheKey)!\n }\n\n const adapter = createGcsAdapter({\n acl: gcsStorageOptions.acl,\n bucket: gcsStorageOptions.bucket,\n clientUploads: gcsStorageOptions.clientUploads,\n getStorageClient,\n useCompositePrefixes: gcsStorageOptions.useCompositePrefixes,\n })\n\n const isPluginDisabled = gcsStorageOptions.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 gcsStorageOptions.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: gcsStorageOptions.useCompositePrefixes,\n })(incomingConfig)\n }\n\n // Add adapter to each collection option object\n const collectionsWithAdapter: CloudStoragePluginOptions['collections'] = Object.entries(\n gcsStorageOptions.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: gcsStorageOptions.useCompositePrefixes,\n })(config)\n },\n})\n"],"names":["Storage","cloudStoragePlugin","createGcsAdapter","gcsClients","Map","gcsStorage","gcsStorageOptions","name","collections","Object","keys","init","incomingConfig","cacheKey","clientCacheKey","bucket","getStorageClient","has","get","set","options","adapter","acl","clientUploads","useCompositePrefixes","isPluginDisabled","enabled","collectionsWithoutAdapter","entries","reduce","acc","slug","collOptions","collectionsWithAdapter","config","map","collection","upload","disableLocalStorage"],"mappings":"AAQA,SAASA,OAAO,QAAQ,wBAAuB;AAC/C,SAASC,kBAAkB,QAAQ,mCAAkC;AAErE,SAASC,gBAAgB,QAAQ,eAAc;AAwD/C,MAAMC,aAAa,IAAIC;AAEvB,OAAO,MAAMC,aAAgC,CAC3CC,oBACoB,CAAA;QACpBC,MAAM;QACNC,aAAaC,OAAOC,IAAI,CAACJ,kBAAkBE,WAAW;QACtDG,MAAM,CAACC;YACL,MAAMC,WAAWP,kBAAkBQ,cAAc,IAAI,CAAC,IAAI,EAAER,kBAAkBS,MAAM,EAAE;YAEtF,MAAMC,mBAAmB;gBACvB,IAAIb,WAAWc,GAAG,CAACJ,WAAW;oBAC5B,OAAOV,WAAWe,GAAG,CAACL;gBACxB;gBACAV,WAAWgB,GAAG,CAACN,UAAU,IAAIb,QAAQM,kBAAkBc,OAAO;gBAE9D,OAAOjB,WAAWe,GAAG,CAACL;YACxB;YAEA,MAAMQ,UAAUnB,iBAAiB;gBAC/BoB,KAAKhB,kBAAkBgB,GAAG;gBAC1BP,QAAQT,kBAAkBS,MAAM;gBAChCQ,eAAejB,kBAAkBiB,aAAa;gBAC9CP;gBACAQ,sBAAsBlB,kBAAkBkB,oBAAoB;YAC9D;YAEA,MAAMC,mBAAmBnB,kBAAkBoB,OAAO,KAAK;YAEvD,IAAID,kBAAkB;gBACpB,+EAA+E;gBAC/E,uEAAuE;gBACvE,MAAME,4BAAsElB,OAAOmB,OAAO,CACxFtB,kBAAkBE,WAAW,EAC7BqB,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,OAAOpB,mBAAmB;oBACxBO,aAAamB;oBACbD,SAAS;oBACTF,sBAAsBlB,kBAAkBkB,oBAAoB;gBAC9D,GAAGZ;YACL;YAEA,+CAA+C;YAC/C,MAAMqB,yBAAmExB,OAAOmB,OAAO,CACrFtB,kBAAkBE,WAAW,EAC7BqB,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,GAAGtB,cAAc;gBACjBJ,aAAa,AAACI,CAAAA,eAAeJ,WAAW,IAAI,EAAE,AAAD,EAAG2B,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,OAAOrC,mBAAmB;gBACxBO,aAAayB;gBACbT,sBAAsBlB,kBAAkBkB,oBAAoB;YAC9D,GAAGU;QACL;IACF,CAAA,EAAE"}
@@ -4,12 +4,9 @@ interface UploadFileArgs {
4
4
  bucket: string;
5
5
  buffer: Buffer;
6
6
  client: Storage;
7
- collectionPrefix?: string;
8
- docPrefix?: string;
9
- filename: string;
10
7
  mimeType: string;
11
- useCompositePrefixes?: boolean;
8
+ storageFilePath: string;
12
9
  }
13
- export declare function uploadFile({ acl, bucket, buffer, client, collectionPrefix, docPrefix, filename, mimeType, useCompositePrefixes, }: UploadFileArgs): Promise<void>;
10
+ export declare function uploadFile({ acl, bucket, buffer, client, mimeType, storageFilePath, }: UploadFileArgs): Promise<void>;
14
11
  export {};
15
12
  //# sourceMappingURL=uploadFile.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"uploadFile.d.ts","sourceRoot":"","sources":["../src/uploadFile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAIpD,UAAU,cAAc;IACtB,GAAG,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,wBAAsB,UAAU,CAAC,EAC/B,GAAG,EACH,MAAM,EACN,MAAM,EACN,MAAM,EACN,gBAAqB,EACrB,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,oBAA4B,GAC7B,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBhC"}
1
+ {"version":3,"file":"uploadFile.d.ts","sourceRoot":"","sources":["../src/uploadFile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAEpD,UAAU,cAAc;IACtB,GAAG,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;CACxB;AAED,wBAAsB,UAAU,CAAC,EAC/B,GAAG,EACH,MAAM,EACN,MAAM,EACN,MAAM,EACN,QAAQ,EACR,eAAe,GAChB,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAYhC"}
@@ -1,12 +1,5 @@
1
- import { getFileKey } from '@payloadcms/plugin-cloud-storage/utilities';
2
- export async function uploadFile({ acl, bucket, buffer, client, collectionPrefix = '', docPrefix, filename, mimeType, useCompositePrefixes = false }) {
3
- const { fileKey } = getFileKey({
4
- collectionPrefix,
5
- docPrefix: docPrefix || '',
6
- filename,
7
- useCompositePrefixes
8
- });
9
- const gcsFile = client.bucket(bucket).file(fileKey);
1
+ export async function uploadFile({ acl, bucket, buffer, client, mimeType, storageFilePath }) {
2
+ const gcsFile = client.bucket(bucket).file(storageFilePath);
10
3
  await gcsFile.save(buffer, {
11
4
  metadata: {
12
5
  contentType: mimeType
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/uploadFile.ts"],"sourcesContent":["import type { Storage } from '@google-cloud/storage'\n\nimport { getFileKey } from '@payloadcms/plugin-cloud-storage/utilities'\n\ninterface UploadFileArgs {\n acl?: 'Private' | 'Public'\n bucket: string\n buffer: Buffer\n client: Storage\n collectionPrefix?: string\n docPrefix?: string\n filename: string\n mimeType: string\n useCompositePrefixes?: boolean\n}\n\nexport async function uploadFile({\n acl,\n bucket,\n buffer,\n client,\n collectionPrefix = '',\n docPrefix,\n filename,\n mimeType,\n useCompositePrefixes = false,\n}: UploadFileArgs): Promise<void> {\n const { fileKey } = getFileKey({\n collectionPrefix,\n docPrefix: docPrefix || '',\n filename,\n useCompositePrefixes,\n })\n\n const gcsFile = client.bucket(bucket).file(fileKey)\n\n await gcsFile.save(buffer, {\n metadata: {\n contentType: mimeType,\n },\n })\n\n if (acl) {\n await gcsFile[`make${acl}`]()\n }\n}\n"],"names":["getFileKey","uploadFile","acl","bucket","buffer","client","collectionPrefix","docPrefix","filename","mimeType","useCompositePrefixes","fileKey","gcsFile","file","save","metadata","contentType"],"mappings":"AAEA,SAASA,UAAU,QAAQ,6CAA4C;AAcvE,OAAO,eAAeC,WAAW,EAC/BC,GAAG,EACHC,MAAM,EACNC,MAAM,EACNC,MAAM,EACNC,mBAAmB,EAAE,EACrBC,SAAS,EACTC,QAAQ,EACRC,QAAQ,EACRC,uBAAuB,KAAK,EACb;IACf,MAAM,EAAEC,OAAO,EAAE,GAAGX,WAAW;QAC7BM;QACAC,WAAWA,aAAa;QACxBC;QACAE;IACF;IAEA,MAAME,UAAUP,OAAOF,MAAM,CAACA,QAAQU,IAAI,CAACF;IAE3C,MAAMC,QAAQE,IAAI,CAACV,QAAQ;QACzBW,UAAU;YACRC,aAAaP;QACf;IACF;IAEA,IAAIP,KAAK;QACP,MAAMU,OAAO,CAAC,CAAC,IAAI,EAAEV,KAAK,CAAC;IAC7B;AACF"}
1
+ {"version":3,"sources":["../src/uploadFile.ts"],"sourcesContent":["import type { Storage } from '@google-cloud/storage'\n\ninterface UploadFileArgs {\n acl?: 'Private' | 'Public'\n bucket: string\n buffer: Buffer\n client: Storage\n mimeType: string\n storageFilePath: string\n}\n\nexport async function uploadFile({\n acl,\n bucket,\n buffer,\n client,\n mimeType,\n storageFilePath,\n}: UploadFileArgs): Promise<void> {\n const gcsFile = client.bucket(bucket).file(storageFilePath)\n\n await gcsFile.save(buffer, {\n metadata: {\n contentType: mimeType,\n },\n })\n\n if (acl) {\n await gcsFile[`make${acl}`]()\n }\n}\n"],"names":["uploadFile","acl","bucket","buffer","client","mimeType","storageFilePath","gcsFile","file","save","metadata","contentType"],"mappings":"AAWA,OAAO,eAAeA,WAAW,EAC/BC,GAAG,EACHC,MAAM,EACNC,MAAM,EACNC,MAAM,EACNC,QAAQ,EACRC,eAAe,EACA;IACf,MAAMC,UAAUH,OAAOF,MAAM,CAACA,QAAQM,IAAI,CAACF;IAE3C,MAAMC,QAAQE,IAAI,CAACN,QAAQ;QACzBO,UAAU;YACRC,aAAaN;QACf;IACF;IAEA,IAAIJ,KAAK;QACP,MAAMM,OAAO,CAAC,CAAC,IAAI,EAAEN,KAAK,CAAC;IAC7B;AACF"}
package/package.json CHANGED
@@ -1,13 +1,8 @@
1
1
  {
2
2
  "name": "@payloadcms/storage-gcs",
3
- "version": "4.0.0-internal.f11981a",
3
+ "version": "4.0.0-internal.f2a04f7",
4
4
  "description": "Payload storage adapter for Google Cloud Storage",
5
5
  "homepage": "https://payloadcms.com",
6
- "repository": {
7
- "type": "git",
8
- "url": "https://github.com/payloadcms/payload.git",
9
- "directory": "packages/storage-gcs"
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-gcs"
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",
@@ -26,20 +31,15 @@
26
31
  "default": "./dist/index.js"
27
32
  }
28
33
  },
29
- "main": "./dist/index.js",
30
- "types": "./dist/index.d.ts",
31
- "files": [
32
- "dist"
33
- ],
34
34
  "dependencies": {
35
35
  "@google-cloud/storage": "7.19.0",
36
- "@payloadcms/plugin-cloud-storage": "4.0.0-internal.f11981a"
36
+ "@payloadcms/plugin-cloud-storage": "4.0.0-internal.f2a04f7"
37
37
  },
38
38
  "devDependencies": {
39
- "payload": "4.0.0-internal.f11981a"
39
+ "payload": "4.0.0-internal.f2a04f7"
40
40
  },
41
41
  "peerDependencies": {
42
- "payload": "4.0.0-internal.f11981a"
42
+ "payload": "4.0.0-internal.f2a04f7"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=24.15.0"