@payloadcms/storage-gcs 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.map +1 -1
- package/dist/adapter.js +18 -6
- package/dist/adapter.js.map +1 -1
- package/dist/copyFile.d.ts +13 -0
- package/dist/copyFile.d.ts.map +1 -0
- package/dist/copyFile.js +45 -0
- package/dist/copyFile.js.map +1 -0
- package/dist/getFile.d.ts +3 -2
- package/dist/getFile.d.ts.map +1 -1
- package/dist/getFile.js +36 -12
- package/dist/getFile.js.map +1 -1
- package/package.json +16 -18
package/dist/adapter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,KAAK,EACV,OAAO,EACP,mBAAmB,
|
|
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(),
|
|
@@ -26,11 +41,7 @@ export function createGcsAdapter({ acl, bucket, clientUploads, getStorageClient,
|
|
|
26
41
|
requiresUploadReceipt: true,
|
|
27
42
|
useInAdmin: true
|
|
28
43
|
},
|
|
29
|
-
handleDelete:
|
|
30
|
-
bucket,
|
|
31
|
-
client: getStorageClient(),
|
|
32
|
-
storageFilePath
|
|
33
|
-
}),
|
|
44
|
+
handleDelete: deleteStoredFile,
|
|
34
45
|
handleUpload: async ({ data, file, storageFilePath })=>{
|
|
35
46
|
await uploadFile({
|
|
36
47
|
acl,
|
|
@@ -42,7 +53,7 @@ export function createGcsAdapter({ acl, bucket, clientUploads, getStorageClient,
|
|
|
42
53
|
});
|
|
43
54
|
return data;
|
|
44
55
|
},
|
|
45
|
-
staticHandler: (req, { doc, headers, params: { filename, uploadReference } })=>getFile({
|
|
56
|
+
staticHandler: (req, { doc, headers, params: { filename, operation, uploadReference } })=>getFile({
|
|
46
57
|
bucket,
|
|
47
58
|
client: getStorageClient(),
|
|
48
59
|
collection,
|
|
@@ -50,6 +61,7 @@ export function createGcsAdapter({ acl, bucket, clientUploads, getStorageClient,
|
|
|
50
61
|
doc,
|
|
51
62
|
filename,
|
|
52
63
|
incomingHeaders: headers,
|
|
64
|
+
operation,
|
|
53
65
|
req,
|
|
54
66
|
uploadReference,
|
|
55
67
|
useCompositePrefixes
|
package/dist/adapter.js.map
CHANGED
|
@@ -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 requiresUploadReceipt: true,\n useInAdmin: true,\n },\n\n handleDelete:
|
|
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"}
|
package/dist/copyFile.js
ADDED
|
@@ -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"}
|
package/dist/getFile.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Storage } from '@google-cloud/storage';
|
|
2
|
-
import type { CollectionConfig, PayloadRequest, TypeWithID } from 'payload';
|
|
2
|
+
import type { CollectionConfig, FileHandlerOperation, PayloadRequest, TypeWithID } from 'payload';
|
|
3
3
|
interface GetFileArgs {
|
|
4
4
|
bucket: string;
|
|
5
5
|
client: Storage;
|
|
@@ -8,10 +8,11 @@ interface GetFileArgs {
|
|
|
8
8
|
doc?: TypeWithID;
|
|
9
9
|
filename: string;
|
|
10
10
|
incomingHeaders?: Headers;
|
|
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, doc, filename, incomingHeaders, 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
|
package/dist/getFile.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getFile.d.ts","sourceRoot":"","sources":["../src/getFile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"getFile.d.ts","sourceRoot":"","sources":["../src/getFile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,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,7 +1,8 @@
|
|
|
1
1
|
import { ApiError } from '@google-cloud/storage';
|
|
2
2
|
import { buildStoragePathData, getFilePrefix as getDocPrefix } from '@payloadcms/plugin-cloud-storage/utilities';
|
|
3
3
|
import { getRangeRequestInfo, isXmlMimeType, uploadContentSecurityPolicy } from 'payload/internal';
|
|
4
|
-
export async function getFile({ bucket, client, collection, collectionPrefix = '', doc, filename, incomingHeaders, req, uploadReference, useCompositePrefixes = false }) {
|
|
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,
|
|
@@ -20,8 +21,7 @@ export async function getFile({ bucket, client, collection, collectionPrefix = '
|
|
|
20
21
|
});
|
|
21
22
|
const file = client.bucket(bucket).file(storageFilePath);
|
|
22
23
|
const [metadata] = await file.getMetadata();
|
|
23
|
-
|
|
24
|
-
const rangeHeader = req.headers.get('range');
|
|
24
|
+
const rangeHeader = isTransformSource ? null : req.headers.get('range');
|
|
25
25
|
const fileSize = Number(metadata.size);
|
|
26
26
|
const rangeResult = getRangeRequestInfo({
|
|
27
27
|
fileSize,
|
|
@@ -36,7 +36,6 @@ export async function getFile({ bucket, client, collection, collectionPrefix = '
|
|
|
36
36
|
const etagFromHeaders = req.headers.get('etag') || req.headers.get('if-none-match');
|
|
37
37
|
const objectEtag = metadata.etag;
|
|
38
38
|
let headers = new Headers(incomingHeaders);
|
|
39
|
-
// Add range-related headers from the result
|
|
40
39
|
for (const [key, value] of Object.entries(rangeResult.headers)){
|
|
41
40
|
headers.append(key, value);
|
|
42
41
|
}
|
|
@@ -46,32 +45,57 @@ export async function getFile({ bucket, client, collection, collectionPrefix = '
|
|
|
46
45
|
if (isXmlMimeType(metadata.contentType)) {
|
|
47
46
|
headers.append('Content-Security-Policy', uploadContentSecurityPolicy);
|
|
48
47
|
}
|
|
49
|
-
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') {
|
|
50
49
|
headers = collection.upload.modifyResponseHeaders({
|
|
51
50
|
headers
|
|
52
51
|
}) || headers;
|
|
53
52
|
}
|
|
54
|
-
if (etagFromHeaders && etagFromHeaders === objectEtag) {
|
|
53
|
+
if (!isTransformSource && etagFromHeaders && etagFromHeaders === objectEtag) {
|
|
55
54
|
return new Response(null, {
|
|
56
55
|
headers,
|
|
57
56
|
status: 304
|
|
58
57
|
});
|
|
59
58
|
}
|
|
60
|
-
|
|
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
|
+
};
|
|
61
76
|
const readableStream = new ReadableStream({
|
|
77
|
+
cancel: stopReading,
|
|
62
78
|
start (controller) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
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
|
+
});
|
|
68
90
|
nodeStream.on('data', (chunk)=>{
|
|
69
91
|
controller.enqueue(new Uint8Array(chunk));
|
|
70
92
|
});
|
|
71
93
|
nodeStream.on('end', ()=>{
|
|
94
|
+
removeAbortListener();
|
|
72
95
|
controller.close();
|
|
73
96
|
});
|
|
74
97
|
nodeStream.on('error', (err)=>{
|
|
98
|
+
removeAbortListener();
|
|
75
99
|
controller.error(err);
|
|
76
100
|
});
|
|
77
101
|
}
|
package/dist/getFile.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/getFile.ts"],"sourcesContent":["import type { Storage } from '@google-cloud/storage'\nimport type { CollectionConfig, 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 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 req,\n uploadReference,\n useCompositePrefixes = false,\n}: GetFileArgs): Promise<Response> {\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
|
|
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/package.json
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payloadcms/storage-gcs",
|
|
3
|
-
"version": "4.0.0-internal.
|
|
3
|
+
"version": "4.0.0-internal.d3909a9",
|
|
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
|
-
"
|
|
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.
|
|
36
|
+
"@payloadcms/plugin-cloud-storage": "4.0.0-internal.d3909a9"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"payload": "4.0.0-internal.
|
|
39
|
+
"payload": "4.0.0-internal.d3909a9"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"payload": "4.0.0-internal.
|
|
42
|
+
"payload": "4.0.0-internal.d3909a9"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
|
45
45
|
"node": ">=24.15.0"
|
|
@@ -50,8 +50,6 @@
|
|
|
50
50
|
"build:debug": "pnpm build",
|
|
51
51
|
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
|
52
52
|
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
|
53
|
-
"clean": "rimraf -g {dist,*.tsbuildinfo}"
|
|
54
|
-
"lint": "eslint .",
|
|
55
|
-
"lint:fix": "eslint . --fix"
|
|
53
|
+
"clean": "rimraf -g {dist,*.tsbuildinfo}"
|
|
56
54
|
}
|
|
57
55
|
}
|