@payloadcms/storage-vercel-blob 4.0.0-internal.b8e7c89 → 4.0.0-internal.bf4bcce
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -8
- package/dist/adapter.d.ts +3 -1
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +68 -26
- package/dist/adapter.js.map +1 -1
- package/dist/authorizeFileOverwrite.d.ts +16 -0
- package/dist/authorizeFileOverwrite.d.ts.map +1 -0
- package/dist/authorizeFileOverwrite.js +117 -0
- package/dist/authorizeFileOverwrite.js.map +1 -0
- package/dist/client/VercelBlobClientUploadHandler.d.ts.map +1 -1
- package/dist/client/VercelBlobClientUploadHandler.js +3 -9
- package/dist/client/VercelBlobClientUploadHandler.js.map +1 -1
- package/dist/copyFile.d.ts +12 -0
- package/dist/copyFile.d.ts.map +1 -0
- package/dist/copyFile.js +86 -0
- package/dist/copyFile.js.map +1 -0
- package/dist/deleteFile.d.ts +2 -5
- package/dist/deleteFile.d.ts.map +1 -1
- package/dist/deleteFile.js +3 -10
- package/dist/deleteFile.js.map +1 -1
- package/dist/generateURL.d.ts +1 -0
- package/dist/generateURL.d.ts.map +1 -1
- package/dist/generateURL.js +13 -9
- package/dist/generateURL.js.map +1 -1
- package/dist/getCollectionSources.d.ts +20 -0
- package/dist/getCollectionSources.d.ts.map +1 -0
- package/dist/getCollectionSources.js +27 -0
- package/dist/getCollectionSources.js.map +1 -0
- package/dist/getFile.d.ts +4 -3
- package/dist/getFile.d.ts.map +1 -1
- package/dist/getFile.js +20 -12
- package/dist/getFile.js.map +1 -1
- package/dist/index.d.ts +10 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -22
- package/dist/index.js.map +1 -1
- package/dist/uploadFile.d.ts +2 -5
- package/dist/uploadFile.d.ts.map +1 -1
- package/dist/uploadFile.js +2 -9
- package/dist/uploadFile.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -41,11 +41,13 @@ export default buildConfig({
|
|
|
41
41
|
})
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
| Option | Description
|
|
45
|
-
| -------------------- |
|
|
46
|
-
| `enabled` | Whether or not to enable the plugin
|
|
47
|
-
| `collections` | Collections to apply the Vercel Blob adapter to
|
|
48
|
-
| `addRandomSuffix` |
|
|
49
|
-
| `cacheControlMaxAge` | Cache-Control max-age in seconds
|
|
50
|
-
| `token` | Vercel Blob storage read/write token
|
|
51
|
-
| `clientUploads` | Upload directly to Vercel Blob instead of through Payload.
|
|
44
|
+
| Option | Description | Default |
|
|
45
|
+
| -------------------- | --------------------------------------------------------------------------------------- | ----------------------------- |
|
|
46
|
+
| `enabled` | Whether or not to enable the plugin | `true` |
|
|
47
|
+
| `collections` | Collections to apply the Vercel Blob adapter to | |
|
|
48
|
+
| `addRandomSuffix` | Must be `false` or omitted; enabled adapters with configured collections reject `true`. | `false` |
|
|
49
|
+
| `cacheControlMaxAge` | Cache-Control max-age in seconds | `365 * 24 * 60 * 60` (1 Year) |
|
|
50
|
+
| `token` | Vercel Blob storage read/write token | `''` |
|
|
51
|
+
| `clientUploads` | Upload directly to Vercel Blob instead of through Payload. | |
|
|
52
|
+
|
|
53
|
+
Set `addRandomSuffix` to `false` or omit it. Payload requires exact storage paths for reads, rollback, and deletion, and already allocates unique `_objectKey` folders for managed uploads. Enabled adapters with configured collections reject `true` during initialization. Disabled adapters retain their existing behavior. Changing this option does not rename existing blobs; migrate any mismatched stored paths and saved metadata together.
|
package/dist/adapter.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import type { Adapter, ClientUploadsConfig } from '@payloadcms/plugin-cloud-storage/types';
|
|
2
|
+
import type { VercelBlobCollectionSource } from './authorizeFileOverwrite.js';
|
|
2
3
|
interface CreateVercelBlobAdapterArgs {
|
|
3
4
|
access: 'public';
|
|
4
5
|
addRandomSuffix?: boolean;
|
|
5
6
|
baseUrl: string;
|
|
6
7
|
cacheControlMaxAge: number;
|
|
7
8
|
clientUploads?: ClientUploadsConfig;
|
|
9
|
+
collectionSources: VercelBlobCollectionSource[];
|
|
8
10
|
token: string;
|
|
9
11
|
useCompositePrefixes?: boolean;
|
|
10
12
|
}
|
|
11
|
-
export declare function createVercelBlobAdapter({ access, addRandomSuffix, baseUrl, cacheControlMaxAge, clientUploads, token, useCompositePrefixes, }: CreateVercelBlobAdapterArgs): Adapter;
|
|
13
|
+
export declare function createVercelBlobAdapter({ access, addRandomSuffix, baseUrl, cacheControlMaxAge, clientUploads, collectionSources, token, useCompositePrefixes, }: CreateVercelBlobAdapterArgs): Adapter;
|
|
12
14
|
export {};
|
|
13
15
|
//# sourceMappingURL=adapter.d.ts.map
|
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,EACV,OAAO,EACP,mBAAmB,
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,mBAAmB,EAGpB,MAAM,wCAAwC,CAAA;AAU/C,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAA;AAS7E,UAAU,2BAA2B;IACnC,MAAM,EAAE,QAAQ,CAAA;IAChB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,kBAAkB,EAAE,MAAM,CAAA;IAC1B,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC,iBAAiB,EAAE,0BAA0B,EAAE,CAAA;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,wBAAgB,uBAAuB,CAAC,EACtC,MAAM,EACN,eAAe,EACf,OAAO,EACP,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,KAAK,EACL,oBAA4B,GAC7B,EAAE,2BAA2B,GAAG,OAAO,CAoJvC"}
|
package/dist/adapter.js
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
|
-
import { resolveSignedURLKey } from '@payloadcms/plugin-cloud-storage/utilities';
|
|
1
|
+
import { buildUploadStoragePathData, resolveSignedURLKey } from '@payloadcms/plugin-cloud-storage/utilities';
|
|
2
2
|
import { generateClientTokenFromReadWriteToken } from '@vercel/blob/client';
|
|
3
3
|
import { Forbidden } from 'payload';
|
|
4
|
+
import { assertClientUploadAllowed, createClientUploadReceipt } from 'payload/internal';
|
|
5
|
+
import { authorizeClientOverwrite } from './authorizeFileOverwrite.js';
|
|
6
|
+
import { copyVercelBlobFile } from './copyFile.js';
|
|
4
7
|
import { deleteFile } from './deleteFile.js';
|
|
5
8
|
import { generateURL } from './generateURL.js';
|
|
6
9
|
import { getFile } from './getFile.js';
|
|
7
10
|
import { uploadFile } from './uploadFile.js';
|
|
8
|
-
export function createVercelBlobAdapter({ access, addRandomSuffix, baseUrl, cacheControlMaxAge, clientUploads, token, useCompositePrefixes = false }) {
|
|
11
|
+
export function createVercelBlobAdapter({ access, addRandomSuffix, baseUrl, cacheControlMaxAge, clientUploads, collectionSources, token, useCompositePrefixes = false }) {
|
|
9
12
|
const clientUploadsAccess = typeof clientUploads === 'object' ? clientUploads.access : undefined;
|
|
13
|
+
const deleteStoredFile = ({ storageFilePath })=>deleteFile({
|
|
14
|
+
baseUrl,
|
|
15
|
+
storageFilePath,
|
|
16
|
+
token
|
|
17
|
+
});
|
|
10
18
|
return ({ collection, prefix = '' })=>({
|
|
11
19
|
name: 'vercel-blob',
|
|
20
|
+
copyFile: ({ from, req, to })=>copyVercelBlobFile({
|
|
21
|
+
access,
|
|
22
|
+
cacheControlMaxAge,
|
|
23
|
+
from,
|
|
24
|
+
req,
|
|
25
|
+
to,
|
|
26
|
+
token
|
|
27
|
+
}),
|
|
28
|
+
deleteFile: deleteStoredFile,
|
|
12
29
|
uploadInstructions: {
|
|
13
30
|
adminHandler: {
|
|
14
31
|
path: '@payloadcms/storage-vercel-blob/client#VercelBlobClientUploadHandler'
|
|
@@ -21,7 +38,40 @@ export function createVercelBlobAdapter({ access, addRandomSuffix, baseUrl, cach
|
|
|
21
38
|
}) : !req.user)) {
|
|
22
39
|
throw new Forbidden(req.t);
|
|
23
40
|
}
|
|
24
|
-
|
|
41
|
+
assertClientUploadAllowed({
|
|
42
|
+
collection,
|
|
43
|
+
filename,
|
|
44
|
+
mimeType
|
|
45
|
+
});
|
|
46
|
+
const requested = buildUploadStoragePathData({
|
|
47
|
+
collectionPrefix: prefix,
|
|
48
|
+
docPrefix,
|
|
49
|
+
filename,
|
|
50
|
+
useCompositePrefixes
|
|
51
|
+
});
|
|
52
|
+
const allowOverwrite = collection.versions ? false : await authorizeClientOverwrite({
|
|
53
|
+
collectionPrefix: prefix,
|
|
54
|
+
collectionSources,
|
|
55
|
+
overrideAccess,
|
|
56
|
+
req,
|
|
57
|
+
requestedCollectionSlug: collectionSlug,
|
|
58
|
+
requestedFilename: requested.sanitizedFilename,
|
|
59
|
+
requestedStorageFilePath: requested.storageFilePath
|
|
60
|
+
});
|
|
61
|
+
const resolved = allowOverwrite ? {
|
|
62
|
+
...requested,
|
|
63
|
+
uploadReference: {
|
|
64
|
+
prefix: requested.sanitizedDocPrefix,
|
|
65
|
+
signedReceipt: createClientUploadReceipt({
|
|
66
|
+
allowOverwrite: true,
|
|
67
|
+
collectionSlug,
|
|
68
|
+
filename: requested.sanitizedFilename,
|
|
69
|
+
filePrefix: requested.sanitizedDocPrefix,
|
|
70
|
+
req,
|
|
71
|
+
storageFilePath: requested.storageFilePath
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
} : await resolveSignedURLKey({
|
|
25
75
|
collectionPrefix: prefix,
|
|
26
76
|
collectionSlug,
|
|
27
77
|
docPrefix,
|
|
@@ -33,16 +83,18 @@ export function createVercelBlobAdapter({ access, addRandomSuffix, baseUrl, cach
|
|
|
33
83
|
name: 'uploadToVercelBlob',
|
|
34
84
|
type: 'dispatch',
|
|
35
85
|
data: {
|
|
36
|
-
pathname: resolved.
|
|
86
|
+
pathname: resolved.storageFilePath,
|
|
37
87
|
token: await generateClientTokenFromReadWriteToken({
|
|
38
|
-
addRandomSuffix,
|
|
88
|
+
addRandomSuffix: false,
|
|
39
89
|
allowedContentTypes: mimeType ? [
|
|
40
90
|
mimeType
|
|
41
91
|
] : undefined,
|
|
42
|
-
allowOverwrite
|
|
92
|
+
...allowOverwrite && {
|
|
93
|
+
allowOverwrite: true
|
|
94
|
+
},
|
|
43
95
|
cacheControlMaxAge,
|
|
44
96
|
maximumSizeInBytes: filesize,
|
|
45
|
-
pathname: resolved.
|
|
97
|
+
pathname: resolved.storageFilePath,
|
|
46
98
|
token
|
|
47
99
|
})
|
|
48
100
|
},
|
|
@@ -50,12 +102,11 @@ export function createVercelBlobAdapter({ access, addRandomSuffix, baseUrl, cach
|
|
|
50
102
|
filename: resolved.sanitizedFilename,
|
|
51
103
|
mimeType,
|
|
52
104
|
size: filesize,
|
|
53
|
-
uploadReference:
|
|
54
|
-
prefix: resolved.sanitizedDocPrefix
|
|
55
|
-
}
|
|
105
|
+
uploadReference: resolved.uploadReference
|
|
56
106
|
}
|
|
57
107
|
};
|
|
58
108
|
},
|
|
109
|
+
requiresUploadReceipt: true,
|
|
59
110
|
useInAdmin: true
|
|
60
111
|
},
|
|
61
112
|
generateURL: ({ filename, prefix: urlPrefix = '' })=>generateURL({
|
|
@@ -65,40 +116,31 @@ export function createVercelBlobAdapter({ access, addRandomSuffix, baseUrl, cach
|
|
|
65
116
|
prefix: urlPrefix,
|
|
66
117
|
useCompositePrefixes
|
|
67
118
|
}),
|
|
68
|
-
handleDelete:
|
|
69
|
-
|
|
70
|
-
collectionPrefix: prefix,
|
|
71
|
-
docPrefix,
|
|
72
|
-
filename,
|
|
73
|
-
token,
|
|
74
|
-
useCompositePrefixes
|
|
75
|
-
}),
|
|
76
|
-
handleUpload: async ({ data, file: { buffer, filename, mimeType } })=>{
|
|
119
|
+
handleDelete: deleteStoredFile,
|
|
120
|
+
handleUpload: async ({ data, file: { buffer, mimeType }, storageFilePath })=>{
|
|
77
121
|
const result = await uploadFile({
|
|
78
122
|
access,
|
|
79
123
|
addRandomSuffix,
|
|
80
124
|
buffer,
|
|
81
125
|
cacheControlMaxAge,
|
|
82
|
-
collectionPrefix: prefix,
|
|
83
|
-
docPrefix: data.prefix,
|
|
84
|
-
filename,
|
|
85
126
|
mimeType,
|
|
86
|
-
|
|
87
|
-
|
|
127
|
+
storageFilePath,
|
|
128
|
+
token
|
|
88
129
|
});
|
|
89
130
|
if (result.filename) {
|
|
90
131
|
data.filename = result.filename;
|
|
91
132
|
}
|
|
92
133
|
return data;
|
|
93
134
|
},
|
|
94
|
-
staticHandler: (req, { headers, params: { filename,
|
|
135
|
+
staticHandler: (req, { doc, headers, params: { filename, operation, uploadReference } })=>getFile({
|
|
95
136
|
baseUrl,
|
|
96
137
|
cacheControlMaxAge,
|
|
97
138
|
collection,
|
|
98
139
|
collectionPrefix: prefix,
|
|
140
|
+
doc,
|
|
99
141
|
filename,
|
|
100
142
|
incomingHeaders: headers,
|
|
101
|
-
|
|
143
|
+
operation,
|
|
102
144
|
req,
|
|
103
145
|
token,
|
|
104
146
|
uploadReference,
|
package/dist/adapter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapter.ts"],"sourcesContent":["import type {\n Adapter,\n ClientUploadsConfig,\n GeneratedAdapter,\n} from '@payloadcms/plugin-cloud-storage/types'\n\nimport {
|
|
1
|
+
{"version":3,"sources":["../src/adapter.ts"],"sourcesContent":["import type {\n Adapter,\n ClientUploadsConfig,\n DeleteFile,\n GeneratedAdapter,\n} from '@payloadcms/plugin-cloud-storage/types'\n\nimport {\n buildUploadStoragePathData,\n resolveSignedURLKey,\n} from '@payloadcms/plugin-cloud-storage/utilities'\nimport { generateClientTokenFromReadWriteToken } from '@vercel/blob/client'\nimport { Forbidden } from 'payload'\nimport { assertClientUploadAllowed, createClientUploadReceipt } from 'payload/internal'\n\nimport type { VercelBlobCollectionSource } from './authorizeFileOverwrite.js'\n\nimport { authorizeClientOverwrite } from './authorizeFileOverwrite.js'\nimport { copyVercelBlobFile } from './copyFile.js'\nimport { deleteFile } from './deleteFile.js'\nimport { generateURL } from './generateURL.js'\nimport { getFile } from './getFile.js'\nimport { uploadFile } from './uploadFile.js'\n\ninterface CreateVercelBlobAdapterArgs {\n access: 'public'\n addRandomSuffix?: boolean\n baseUrl: string\n cacheControlMaxAge: number\n clientUploads?: ClientUploadsConfig\n collectionSources: VercelBlobCollectionSource[]\n token: string\n useCompositePrefixes?: boolean\n}\n\nexport function createVercelBlobAdapter({\n access,\n addRandomSuffix,\n baseUrl,\n cacheControlMaxAge,\n clientUploads,\n collectionSources,\n token,\n useCompositePrefixes = false,\n}: CreateVercelBlobAdapterArgs): Adapter {\n const clientUploadsAccess = typeof clientUploads === 'object' ? clientUploads.access : undefined\n const deleteStoredFile: DeleteFile = ({ storageFilePath }) =>\n deleteFile({ baseUrl, storageFilePath, token })\n\n return ({ collection, prefix = '' }): GeneratedAdapter => ({\n name: 'vercel-blob',\n\n copyFile: ({ from, req, to }) =>\n copyVercelBlobFile({ access, cacheControlMaxAge, from, req, to, token }),\n deleteFile: deleteStoredFile,\n\n uploadInstructions: {\n adminHandler: {\n path: '@payloadcms/storage-vercel-blob/client#VercelBlobClientUploadHandler',\n },\n enabled: Boolean(clientUploads),\n generate: async ({\n collectionSlug,\n docPrefix,\n filename,\n filesize,\n mimeType,\n overrideAccess,\n req,\n }) => {\n if (\n !overrideAccess &&\n (clientUploadsAccess ? !(await clientUploadsAccess({ collectionSlug, req })) : !req.user)\n ) {\n throw new Forbidden(req.t)\n }\n\n assertClientUploadAllowed({ collection, filename, mimeType })\n\n const requested = buildUploadStoragePathData({\n collectionPrefix: prefix,\n docPrefix,\n filename,\n useCompositePrefixes,\n })\n const allowOverwrite = collection.versions\n ? false\n : await authorizeClientOverwrite({\n collectionPrefix: prefix,\n collectionSources,\n overrideAccess,\n req,\n requestedCollectionSlug: collectionSlug,\n requestedFilename: requested.sanitizedFilename,\n requestedStorageFilePath: requested.storageFilePath,\n })\n const resolved = allowOverwrite\n ? {\n ...requested,\n uploadReference: {\n prefix: requested.sanitizedDocPrefix,\n signedReceipt: createClientUploadReceipt({\n allowOverwrite: true,\n collectionSlug,\n filename: requested.sanitizedFilename,\n filePrefix: requested.sanitizedDocPrefix,\n req,\n storageFilePath: requested.storageFilePath,\n }),\n },\n }\n : await resolveSignedURLKey({\n collectionPrefix: prefix,\n collectionSlug,\n docPrefix,\n filename,\n req,\n useCompositePrefixes,\n })\n\n return {\n name: 'uploadToVercelBlob',\n type: 'dispatch',\n data: {\n pathname: resolved.storageFilePath,\n token: await generateClientTokenFromReadWriteToken({\n addRandomSuffix: false,\n allowedContentTypes: mimeType ? [mimeType] : undefined,\n ...(allowOverwrite && { allowOverwrite: true }),\n cacheControlMaxAge,\n maximumSizeInBytes: filesize,\n pathname: resolved.storageFilePath,\n token,\n }),\n },\n file: {\n filename: resolved.sanitizedFilename,\n mimeType,\n size: filesize,\n uploadReference: resolved.uploadReference,\n },\n }\n },\n requiresUploadReceipt: true,\n useInAdmin: true,\n },\n\n generateURL: ({ filename, prefix: urlPrefix = '' }) =>\n generateURL({\n baseUrl,\n collectionPrefix: prefix,\n filename,\n prefix: urlPrefix,\n useCompositePrefixes,\n }),\n\n handleDelete: deleteStoredFile,\n\n handleUpload: async ({ data, file: { buffer, mimeType }, storageFilePath }) => {\n const result = await uploadFile({\n access,\n addRandomSuffix,\n buffer,\n cacheControlMaxAge,\n mimeType,\n storageFilePath,\n token,\n })\n\n if (result.filename) {\n data.filename = result.filename\n }\n\n return data\n },\n\n staticHandler: (req, { doc, headers, params: { filename, operation, uploadReference } }) =>\n getFile({\n baseUrl,\n cacheControlMaxAge,\n collection,\n collectionPrefix: prefix,\n doc,\n filename,\n incomingHeaders: headers,\n operation,\n req,\n token,\n uploadReference,\n useCompositePrefixes,\n }),\n })\n}\n"],"names":["buildUploadStoragePathData","resolveSignedURLKey","generateClientTokenFromReadWriteToken","Forbidden","assertClientUploadAllowed","createClientUploadReceipt","authorizeClientOverwrite","copyVercelBlobFile","deleteFile","generateURL","getFile","uploadFile","createVercelBlobAdapter","access","addRandomSuffix","baseUrl","cacheControlMaxAge","clientUploads","collectionSources","token","useCompositePrefixes","clientUploadsAccess","undefined","deleteStoredFile","storageFilePath","collection","prefix","name","copyFile","from","req","to","uploadInstructions","adminHandler","path","enabled","Boolean","generate","collectionSlug","docPrefix","filename","filesize","mimeType","overrideAccess","user","t","requested","collectionPrefix","allowOverwrite","versions","requestedCollectionSlug","requestedFilename","sanitizedFilename","requestedStorageFilePath","resolved","uploadReference","sanitizedDocPrefix","signedReceipt","filePrefix","type","data","pathname","allowedContentTypes","maximumSizeInBytes","file","size","requiresUploadReceipt","useInAdmin","urlPrefix","handleDelete","handleUpload","buffer","result","staticHandler","doc","headers","params","operation","incomingHeaders"],"mappings":"AAOA,SACEA,0BAA0B,EAC1BC,mBAAmB,QACd,6CAA4C;AACnD,SAASC,qCAAqC,QAAQ,sBAAqB;AAC3E,SAASC,SAAS,QAAQ,UAAS;AACnC,SAASC,yBAAyB,EAAEC,yBAAyB,QAAQ,mBAAkB;AAIvF,SAASC,wBAAwB,QAAQ,8BAA6B;AACtE,SAASC,kBAAkB,QAAQ,gBAAe;AAClD,SAASC,UAAU,QAAQ,kBAAiB;AAC5C,SAASC,WAAW,QAAQ,mBAAkB;AAC9C,SAASC,OAAO,QAAQ,eAAc;AACtC,SAASC,UAAU,QAAQ,kBAAiB;AAa5C,OAAO,SAASC,wBAAwB,EACtCC,MAAM,EACNC,eAAe,EACfC,OAAO,EACPC,kBAAkB,EAClBC,aAAa,EACbC,iBAAiB,EACjBC,KAAK,EACLC,uBAAuB,KAAK,EACA;IAC5B,MAAMC,sBAAsB,OAAOJ,kBAAkB,WAAWA,cAAcJ,MAAM,GAAGS;IACvF,MAAMC,mBAA+B,CAAC,EAAEC,eAAe,EAAE,GACvDhB,WAAW;YAAEO;YAASS;YAAiBL;QAAM;IAE/C,OAAO,CAAC,EAAEM,UAAU,EAAEC,SAAS,EAAE,EAAE,GAAwB,CAAA;YACzDC,MAAM;YAENC,UAAU,CAAC,EAAEC,IAAI,EAAEC,GAAG,EAAEC,EAAE,EAAE,GAC1BxB,mBAAmB;oBAAEM;oBAAQG;oBAAoBa;oBAAMC;oBAAKC;oBAAIZ;gBAAM;YACxEX,YAAYe;YAEZS,oBAAoB;gBAClBC,cAAc;oBACZC,MAAM;gBACR;gBACAC,SAASC,QAAQnB;gBACjBoB,UAAU,OAAO,EACfC,cAAc,EACdC,SAAS,EACTC,QAAQ,EACRC,QAAQ,EACRC,QAAQ,EACRC,cAAc,EACdb,GAAG,EACJ;oBACC,IACE,CAACa,kBACAtB,CAAAA,sBAAsB,CAAE,MAAMA,oBAAoB;wBAAEiB;wBAAgBR;oBAAI,KAAM,CAACA,IAAIc,IAAI,AAAD,GACvF;wBACA,MAAM,IAAIzC,UAAU2B,IAAIe,CAAC;oBAC3B;oBAEAzC,0BAA0B;wBAAEqB;wBAAYe;wBAAUE;oBAAS;oBAE3D,MAAMI,YAAY9C,2BAA2B;wBAC3C+C,kBAAkBrB;wBAClBa;wBACAC;wBACApB;oBACF;oBACA,MAAM4B,iBAAiBvB,WAAWwB,QAAQ,GACtC,QACA,MAAM3C,yBAAyB;wBAC7ByC,kBAAkBrB;wBAClBR;wBACAyB;wBACAb;wBACAoB,yBAAyBZ;wBACzBa,mBAAmBL,UAAUM,iBAAiB;wBAC9CC,0BAA0BP,UAAUtB,eAAe;oBACrD;oBACJ,MAAM8B,WAAWN,iBACb;wBACE,GAAGF,SAAS;wBACZS,iBAAiB;4BACf7B,QAAQoB,UAAUU,kBAAkB;4BACpCC,eAAepD,0BAA0B;gCACvC2C,gBAAgB;gCAChBV;gCACAE,UAAUM,UAAUM,iBAAiB;gCACrCM,YAAYZ,UAAUU,kBAAkB;gCACxC1B;gCACAN,iBAAiBsB,UAAUtB,eAAe;4BAC5C;wBACF;oBACF,IACA,MAAMvB,oBAAoB;wBACxB8C,kBAAkBrB;wBAClBY;wBACAC;wBACAC;wBACAV;wBACAV;oBACF;oBAEJ,OAAO;wBACLO,MAAM;wBACNgC,MAAM;wBACNC,MAAM;4BACJC,UAAUP,SAAS9B,eAAe;4BAClCL,OAAO,MAAMjB,sCAAsC;gCACjDY,iBAAiB;gCACjBgD,qBAAqBpB,WAAW;oCAACA;iCAAS,GAAGpB;gCAC7C,GAAI0B,kBAAkB;oCAAEA,gBAAgB;gCAAK,CAAC;gCAC9ChC;gCACA+C,oBAAoBtB;gCACpBoB,UAAUP,SAAS9B,eAAe;gCAClCL;4BACF;wBACF;wBACA6C,MAAM;4BACJxB,UAAUc,SAASF,iBAAiB;4BACpCV;4BACAuB,MAAMxB;4BACNc,iBAAiBD,SAASC,eAAe;wBAC3C;oBACF;gBACF;gBACAW,uBAAuB;gBACvBC,YAAY;YACd;YAEA1D,aAAa,CAAC,EAAE+B,QAAQ,EAAEd,QAAQ0C,YAAY,EAAE,EAAE,GAChD3D,YAAY;oBACVM;oBACAgC,kBAAkBrB;oBAClBc;oBACAd,QAAQ0C;oBACRhD;gBACF;YAEFiD,cAAc9C;YAEd+C,cAAc,OAAO,EAAEV,IAAI,EAAEI,MAAM,EAAEO,MAAM,EAAE7B,QAAQ,EAAE,EAAElB,eAAe,EAAE;gBACxE,MAAMgD,SAAS,MAAM7D,WAAW;oBAC9BE;oBACAC;oBACAyD;oBACAvD;oBACA0B;oBACAlB;oBACAL;gBACF;gBAEA,IAAIqD,OAAOhC,QAAQ,EAAE;oBACnBoB,KAAKpB,QAAQ,GAAGgC,OAAOhC,QAAQ;gBACjC;gBAEA,OAAOoB;YACT;YAEAa,eAAe,CAAC3C,KAAK,EAAE4C,GAAG,EAAEC,OAAO,EAAEC,QAAQ,EAAEpC,QAAQ,EAAEqC,SAAS,EAAEtB,eAAe,EAAE,EAAE,GACrF7C,QAAQ;oBACNK;oBACAC;oBACAS;oBACAsB,kBAAkBrB;oBAClBgD;oBACAlC;oBACAsC,iBAAiBH;oBACjBE;oBACA/C;oBACAX;oBACAoC;oBACAnC;gBACF;QACJ,CAAA;AACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
export type VercelBlobCollectionSource = {
|
|
3
|
+
collectionPrefix: string;
|
|
4
|
+
collectionSlug: string;
|
|
5
|
+
useCompositePrefixes: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare function authorizeClientOverwrite({ collectionPrefix: collectionPrefixArg, collectionSources, overrideAccess, req, requestedCollectionSlug, requestedFilename, requestedStorageFilePath, }: {
|
|
8
|
+
collectionPrefix: string;
|
|
9
|
+
collectionSources: VercelBlobCollectionSource[];
|
|
10
|
+
overrideAccess?: boolean;
|
|
11
|
+
req: PayloadRequest;
|
|
12
|
+
requestedCollectionSlug: string;
|
|
13
|
+
requestedFilename: string;
|
|
14
|
+
requestedStorageFilePath: string;
|
|
15
|
+
}): Promise<boolean>;
|
|
16
|
+
//# sourceMappingURL=authorizeFileOverwrite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorizeFileOverwrite.d.ts","sourceRoot":"","sources":["../src/authorizeFileOverwrite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,cAAc,EAAqB,MAAM,SAAS,CAAA;AAU1E,MAAM,MAAM,0BAA0B,GAAG;IACvC,gBAAgB,EAAE,MAAM,CAAA;IACxB,cAAc,EAAE,MAAM,CAAA;IACtB,oBAAoB,EAAE,OAAO,CAAA;CAC9B,CAAA;AAmHD,wBAAsB,wBAAwB,CAAC,EAC7C,gBAAgB,EAAE,mBAAmB,EACrC,iBAAiB,EACjB,cAAsB,EACtB,GAAG,EACH,uBAAuB,EACvB,iBAAiB,EACjB,wBAAwB,GACzB,EAAE;IACD,gBAAgB,EAAE,MAAM,CAAA;IACxB,iBAAiB,EAAE,0BAA0B,EAAE,CAAA;IAC/C,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,GAAG,EAAE,cAAc,CAAA;IACnB,uBAAuB,EAAE,MAAM,CAAA;IAC/B,iBAAiB,EAAE,MAAM,CAAA;IACzB,wBAAwB,EAAE,MAAM,CAAA;CACjC,GAAG,OAAO,CAAC,OAAO,CAAC,CA8CnB"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { buildStoragePathData, isStoragePathWithinCollectionPrefix } from '@payloadcms/plugin-cloud-storage/utilities';
|
|
2
|
+
import { combineQueries, executeAccess, Forbidden } from 'payload';
|
|
3
|
+
function getDocumentFileKeys({ collectionPrefix, doc, useCompositePrefixes }) {
|
|
4
|
+
return [
|
|
5
|
+
doc.filename,
|
|
6
|
+
...Object.values(doc.variants || {}).map((size)=>size?.filename)
|
|
7
|
+
].filter((filename)=>typeof filename === 'string').map((filename)=>buildStoragePathData({
|
|
8
|
+
collectionPrefix,
|
|
9
|
+
docPrefix: doc.prefix,
|
|
10
|
+
filename,
|
|
11
|
+
useCompositePrefixes
|
|
12
|
+
}).storageFilePath);
|
|
13
|
+
}
|
|
14
|
+
async function findMatchingDocs({ collectionPrefix, collectionSlug, req, requestedFilename, requestedStorageFilePath, useCompositePrefixes }) {
|
|
15
|
+
const collection = req.payload.collections[collectionSlug];
|
|
16
|
+
if (!collection) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
const variants = collection.config.upload && typeof collection.config.upload === 'object' ? collection.config.upload.variants || [] : [];
|
|
20
|
+
const filenameQueries = [
|
|
21
|
+
{
|
|
22
|
+
filename: {
|
|
23
|
+
equals: requestedFilename
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
...variants.map(({ name })=>({
|
|
27
|
+
[`variants.${name}.filename`]: {
|
|
28
|
+
equals: requestedFilename
|
|
29
|
+
}
|
|
30
|
+
}))
|
|
31
|
+
];
|
|
32
|
+
const result = await req.payload.find({
|
|
33
|
+
collection: collectionSlug,
|
|
34
|
+
depth: 0,
|
|
35
|
+
draft: true,
|
|
36
|
+
overrideAccess: true,
|
|
37
|
+
pagination: false,
|
|
38
|
+
req,
|
|
39
|
+
where: {
|
|
40
|
+
or: filenameQueries
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return result.docs.filter((doc)=>getDocumentFileKeys({
|
|
44
|
+
collectionPrefix,
|
|
45
|
+
doc,
|
|
46
|
+
useCompositePrefixes
|
|
47
|
+
}).includes(requestedStorageFilePath));
|
|
48
|
+
}
|
|
49
|
+
async function canUpdateDoc({ collectionSlug, doc, req }) {
|
|
50
|
+
const collection = req.payload.collections[collectionSlug];
|
|
51
|
+
if (!collection) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const accessResult = await executeAccess({
|
|
55
|
+
id: doc.id,
|
|
56
|
+
slug: collectionSlug,
|
|
57
|
+
disableErrors: true,
|
|
58
|
+
req
|
|
59
|
+
}, collection.config.access.update);
|
|
60
|
+
if (typeof accessResult === 'boolean') {
|
|
61
|
+
return accessResult;
|
|
62
|
+
}
|
|
63
|
+
const result = await req.payload.find({
|
|
64
|
+
collection: collectionSlug,
|
|
65
|
+
depth: 0,
|
|
66
|
+
draft: true,
|
|
67
|
+
limit: 1,
|
|
68
|
+
overrideAccess: true,
|
|
69
|
+
pagination: false,
|
|
70
|
+
req,
|
|
71
|
+
where: combineQueries({
|
|
72
|
+
id: {
|
|
73
|
+
equals: doc.id
|
|
74
|
+
}
|
|
75
|
+
}, accessResult)
|
|
76
|
+
});
|
|
77
|
+
return result.docs.length === 1;
|
|
78
|
+
}
|
|
79
|
+
export async function authorizeClientOverwrite({ collectionPrefix: collectionPrefixArg, collectionSources, overrideAccess = false, req, requestedCollectionSlug, requestedFilename, requestedStorageFilePath }) {
|
|
80
|
+
if (!isStoragePathWithinCollectionPrefix({
|
|
81
|
+
collectionPrefix: collectionPrefixArg,
|
|
82
|
+
docPrefix: requestedStorageFilePath
|
|
83
|
+
})) {
|
|
84
|
+
throw new Forbidden(req.t);
|
|
85
|
+
}
|
|
86
|
+
const matchedDocs = (await Promise.all(collectionSources.map(async ({ collectionPrefix, collectionSlug, useCompositePrefixes })=>{
|
|
87
|
+
const docs = await findMatchingDocs({
|
|
88
|
+
collectionPrefix,
|
|
89
|
+
collectionSlug,
|
|
90
|
+
req,
|
|
91
|
+
requestedFilename,
|
|
92
|
+
requestedStorageFilePath,
|
|
93
|
+
useCompositePrefixes
|
|
94
|
+
});
|
|
95
|
+
return docs.map((doc)=>({
|
|
96
|
+
collectionSlug,
|
|
97
|
+
doc
|
|
98
|
+
}));
|
|
99
|
+
}))).flat();
|
|
100
|
+
if (matchedDocs.length === 0) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
const matchedDoc = matchedDocs[0];
|
|
104
|
+
if (matchedDocs.length !== 1 || matchedDoc.collectionSlug !== requestedCollectionSlug) {
|
|
105
|
+
throw new Forbidden(req.t);
|
|
106
|
+
}
|
|
107
|
+
if (!overrideAccess && !await canUpdateDoc({
|
|
108
|
+
collectionSlug: matchedDoc.collectionSlug,
|
|
109
|
+
doc: matchedDoc.doc,
|
|
110
|
+
req
|
|
111
|
+
})) {
|
|
112
|
+
throw new Forbidden(req.t);
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
//# sourceMappingURL=authorizeFileOverwrite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/authorizeFileOverwrite.ts"],"sourcesContent":["import type { FileData, PayloadRequest, TypeWithID, Where } from 'payload'\n\nimport {\n buildStoragePathData,\n isStoragePathWithinCollectionPrefix,\n} from '@payloadcms/plugin-cloud-storage/utilities'\nimport { combineQueries, executeAccess, Forbidden } from 'payload'\n\ntype OwnerDocument = { prefix?: string } & FileData & TypeWithID\n\nexport type VercelBlobCollectionSource = {\n collectionPrefix: string\n collectionSlug: string\n useCompositePrefixes: boolean\n}\n\ntype Owner = {\n collectionSlug: string\n doc: OwnerDocument\n}\n\nfunction getDocumentFileKeys({\n collectionPrefix,\n doc,\n useCompositePrefixes,\n}: {\n collectionPrefix: string\n doc: OwnerDocument\n useCompositePrefixes: boolean\n}): string[] {\n return [doc.filename, ...Object.values(doc.variants || {}).map((size) => size?.filename)]\n .filter((filename): filename is string => typeof filename === 'string')\n .map(\n (filename) =>\n buildStoragePathData({\n collectionPrefix,\n docPrefix: doc.prefix,\n filename,\n useCompositePrefixes,\n }).storageFilePath,\n )\n}\n\nasync function findMatchingDocs({\n collectionPrefix,\n collectionSlug,\n req,\n requestedFilename,\n requestedStorageFilePath,\n useCompositePrefixes,\n}: {\n collectionPrefix: string\n collectionSlug: string\n req: PayloadRequest\n requestedFilename: string\n requestedStorageFilePath: string\n useCompositePrefixes: boolean\n}): Promise<OwnerDocument[]> {\n const collection = req.payload.collections[collectionSlug]\n\n if (!collection) {\n return []\n }\n\n const variants =\n collection.config.upload && typeof collection.config.upload === 'object'\n ? collection.config.upload.variants || []\n : []\n const filenameQueries: Where[] = [\n { filename: { equals: requestedFilename } },\n ...variants.map(({ name }) => ({\n [`variants.${name}.filename`]: { equals: requestedFilename },\n })),\n ]\n const result = await req.payload.find({\n collection: collectionSlug,\n depth: 0,\n draft: true,\n overrideAccess: true,\n pagination: false,\n req,\n where: { or: filenameQueries },\n })\n\n return (result.docs as OwnerDocument[]).filter((doc) =>\n getDocumentFileKeys({ collectionPrefix, doc, useCompositePrefixes }).includes(\n requestedStorageFilePath,\n ),\n )\n}\n\nasync function canUpdateDoc({\n collectionSlug,\n doc,\n req,\n}: {\n collectionSlug: string\n doc: OwnerDocument\n req: PayloadRequest\n}): Promise<boolean> {\n const collection = req.payload.collections[collectionSlug]\n\n if (!collection) {\n return false\n }\n\n const accessResult = await executeAccess(\n { id: doc.id, slug: collectionSlug, disableErrors: true, req },\n collection.config.access.update,\n )\n\n if (typeof accessResult === 'boolean') {\n return accessResult\n }\n\n const result = await req.payload.find({\n collection: collectionSlug,\n depth: 0,\n draft: true,\n limit: 1,\n overrideAccess: true,\n pagination: false,\n req,\n where: combineQueries({ id: { equals: doc.id } }, accessResult),\n })\n\n return result.docs.length === 1\n}\n\nexport async function authorizeClientOverwrite({\n collectionPrefix: collectionPrefixArg,\n collectionSources,\n overrideAccess = false,\n req,\n requestedCollectionSlug,\n requestedFilename,\n requestedStorageFilePath,\n}: {\n collectionPrefix: string\n collectionSources: VercelBlobCollectionSource[]\n overrideAccess?: boolean\n req: PayloadRequest\n requestedCollectionSlug: string\n requestedFilename: string\n requestedStorageFilePath: string\n}): Promise<boolean> {\n if (\n !isStoragePathWithinCollectionPrefix({\n collectionPrefix: collectionPrefixArg,\n docPrefix: requestedStorageFilePath,\n })\n ) {\n throw new Forbidden(req.t)\n }\n\n const matchedDocs = (\n await Promise.all(\n collectionSources.map(\n async ({ collectionPrefix, collectionSlug, useCompositePrefixes }): Promise<Owner[]> => {\n const docs = await findMatchingDocs({\n collectionPrefix,\n collectionSlug,\n req,\n requestedFilename,\n requestedStorageFilePath,\n useCompositePrefixes,\n })\n\n return docs.map((doc) => ({ collectionSlug, doc }))\n },\n ),\n )\n ).flat()\n\n if (matchedDocs.length === 0) {\n return false\n }\n\n const matchedDoc = matchedDocs[0]!\n if (matchedDocs.length !== 1 || matchedDoc.collectionSlug !== requestedCollectionSlug) {\n throw new Forbidden(req.t)\n }\n\n if (\n !overrideAccess &&\n !(await canUpdateDoc({ collectionSlug: matchedDoc.collectionSlug, doc: matchedDoc.doc, req }))\n ) {\n throw new Forbidden(req.t)\n }\n\n return true\n}\n"],"names":["buildStoragePathData","isStoragePathWithinCollectionPrefix","combineQueries","executeAccess","Forbidden","getDocumentFileKeys","collectionPrefix","doc","useCompositePrefixes","filename","Object","values","variants","map","size","filter","docPrefix","prefix","storageFilePath","findMatchingDocs","collectionSlug","req","requestedFilename","requestedStorageFilePath","collection","payload","collections","config","upload","filenameQueries","equals","name","result","find","depth","draft","overrideAccess","pagination","where","or","docs","includes","canUpdateDoc","accessResult","id","slug","disableErrors","access","update","limit","length","authorizeClientOverwrite","collectionPrefixArg","collectionSources","requestedCollectionSlug","t","matchedDocs","Promise","all","flat","matchedDoc"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VercelBlobClientUploadHandler.d.ts","sourceRoot":"","sources":["../../src/client/VercelBlobClientUploadHandler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VercelBlobClientUploadHandler.d.ts","sourceRoot":"","sources":["../../src/client/VercelBlobClientUploadHandler.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,6BAA6B;;;;;;iCAexC,CAAA"}
|
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { createClientUploadHandler } from '@payloadcms/plugin-cloud-storage/client';
|
|
3
3
|
import { put } from '@vercel/blob/client';
|
|
4
|
-
/** Last path segment only (POSIX), for keys like `folder/sub/file.png`. */ function posixBasename(key) {
|
|
5
|
-
const normalized = key.replace(/^\/+/, '');
|
|
6
|
-
const lastSlash = normalized.lastIndexOf('/');
|
|
7
|
-
return lastSlash === -1 ? normalized : normalized.slice(lastSlash + 1);
|
|
8
|
-
}
|
|
9
4
|
export const VercelBlobClientUploadHandler = createClientUploadHandler({
|
|
10
5
|
name: 'uploadToVercelBlob',
|
|
11
|
-
handler: async ({ data, file
|
|
6
|
+
handler: async ({ data, file })=>{
|
|
12
7
|
const { pathname, token } = data;
|
|
13
8
|
const result = await put(pathname, file, {
|
|
14
9
|
access: 'public',
|
|
15
10
|
contentType: file.type,
|
|
16
11
|
token
|
|
17
12
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
updateFilename(filename);
|
|
13
|
+
if (decodeURIComponent(result.pathname) !== decodeURIComponent(pathname)) {
|
|
14
|
+
throw new Error('The uploaded file was stored at an unexpected location');
|
|
21
15
|
}
|
|
22
16
|
}
|
|
23
17
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/client/VercelBlobClientUploadHandler.ts"],"sourcesContent":["'use client'\nimport { createClientUploadHandler } from '@payloadcms/plugin-cloud-storage/client'\nimport { put } from '@vercel/blob/client'\n\
|
|
1
|
+
{"version":3,"sources":["../../src/client/VercelBlobClientUploadHandler.ts"],"sourcesContent":["'use client'\nimport { createClientUploadHandler } from '@payloadcms/plugin-cloud-storage/client'\nimport { put } from '@vercel/blob/client'\n\nexport const VercelBlobClientUploadHandler = createClientUploadHandler({\n name: 'uploadToVercelBlob',\n handler: async ({ data, file }) => {\n const { pathname, token } = data as { pathname: string; token: string }\n\n const result = await put(pathname, file, {\n access: 'public',\n contentType: file.type,\n token,\n })\n\n if (decodeURIComponent(result.pathname) !== decodeURIComponent(pathname)) {\n throw new Error('The uploaded file was stored at an unexpected location')\n }\n },\n})\n"],"names":["createClientUploadHandler","put","VercelBlobClientUploadHandler","name","handler","data","file","pathname","token","result","access","contentType","type","decodeURIComponent","Error"],"mappings":"AAAA;AACA,SAASA,yBAAyB,QAAQ,0CAAyC;AACnF,SAASC,GAAG,QAAQ,sBAAqB;AAEzC,OAAO,MAAMC,gCAAgCF,0BAA0B;IACrEG,MAAM;IACNC,SAAS,OAAO,EAAEC,IAAI,EAAEC,IAAI,EAAE;QAC5B,MAAM,EAAEC,QAAQ,EAAEC,KAAK,EAAE,GAAGH;QAE5B,MAAMI,SAAS,MAAMR,IAAIM,UAAUD,MAAM;YACvCI,QAAQ;YACRC,aAAaL,KAAKM,IAAI;YACtBJ;QACF;QAEA,IAAIK,mBAAmBJ,OAAOF,QAAQ,MAAMM,mBAAmBN,WAAW;YACxE,MAAM,IAAIO,MAAM;QAClB;IACF;AACF,GAAE"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
type Args = {
|
|
3
|
+
access: 'public';
|
|
4
|
+
cacheControlMaxAge: number;
|
|
5
|
+
from: string;
|
|
6
|
+
req?: PayloadRequest;
|
|
7
|
+
to: string;
|
|
8
|
+
token: string;
|
|
9
|
+
};
|
|
10
|
+
export declare const copyVercelBlobFile: ({ access, cacheControlMaxAge, from, req, to, token, }: Args) => Promise<void>;
|
|
11
|
+
export {};
|
|
12
|
+
//# 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,cAAc,EAAE,MAAM,SAAS,CAAA;AAI7C,KAAK,IAAI,GAAG;IACV,MAAM,EAAE,QAAQ,CAAA;IAChB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,eAAO,MAAM,kBAAkB,0DAO5B,IAAI,KAAG,OAAO,CAAC,IAAI,CA+ErB,CAAA"}
|
package/dist/copyFile.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { BlobNotFoundError, copy, del, head, put } from '@vercel/blob';
|
|
2
|
+
export const copyVercelBlobFile = async ({ access, cacheControlMaxAge, from, req, to, token })=>{
|
|
3
|
+
if (from === to) {
|
|
4
|
+
throw new Error('Storage copy requires different source and destination keys');
|
|
5
|
+
}
|
|
6
|
+
const source = await head(from, {
|
|
7
|
+
token
|
|
8
|
+
});
|
|
9
|
+
try {
|
|
10
|
+
await head(to, {
|
|
11
|
+
token
|
|
12
|
+
});
|
|
13
|
+
throw new Error(`Storage destination already exists: ${to}`);
|
|
14
|
+
} catch (err) {
|
|
15
|
+
if (!isBlobNotFound(err)) {
|
|
16
|
+
throw err;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const sourceMaxAge = /(?:^|,)\s*max-age=(\d+)/.exec(source.cacheControl);
|
|
20
|
+
let copied = await copy(from, to, {
|
|
21
|
+
access,
|
|
22
|
+
addRandomSuffix: false,
|
|
23
|
+
allowOverwrite: false,
|
|
24
|
+
cacheControlMaxAge: sourceMaxAge ? Number(sourceMaxAge[1]) : cacheControlMaxAge,
|
|
25
|
+
contentType: source.contentType,
|
|
26
|
+
token
|
|
27
|
+
});
|
|
28
|
+
try {
|
|
29
|
+
if (copied.pathname !== to) {
|
|
30
|
+
throw new Error(`Vercel Blob assigned an unexpected destination key: ${copied.pathname}`);
|
|
31
|
+
}
|
|
32
|
+
let destination = await head(to, {
|
|
33
|
+
token
|
|
34
|
+
});
|
|
35
|
+
if (destination.size !== source.size) {
|
|
36
|
+
await del(to, {
|
|
37
|
+
ifMatch: copied.etag,
|
|
38
|
+
token
|
|
39
|
+
});
|
|
40
|
+
// The first copy is gone; a rejected fallback must not delete another upload's object.
|
|
41
|
+
copied = undefined;
|
|
42
|
+
const sourceResponse = await fetch(source.url);
|
|
43
|
+
if (!sourceResponse.ok || !sourceResponse.body) {
|
|
44
|
+
throw new Error(`Vercel Blob source is not readable: ${from}`);
|
|
45
|
+
}
|
|
46
|
+
copied = await put(to, sourceResponse.body, {
|
|
47
|
+
access,
|
|
48
|
+
addRandomSuffix: false,
|
|
49
|
+
allowOverwrite: false,
|
|
50
|
+
cacheControlMaxAge: sourceMaxAge ? Number(sourceMaxAge[1]) : cacheControlMaxAge,
|
|
51
|
+
contentType: source.contentType,
|
|
52
|
+
token
|
|
53
|
+
});
|
|
54
|
+
if (copied.pathname !== to) {
|
|
55
|
+
throw new Error(`Vercel Blob assigned an unexpected destination key: ${copied.pathname}`);
|
|
56
|
+
}
|
|
57
|
+
destination = await head(to, {
|
|
58
|
+
token
|
|
59
|
+
});
|
|
60
|
+
if (destination.size !== source.size) {
|
|
61
|
+
throw new Error(`Copied Vercel Blob object is not readable at its expected length: ${to}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch (err) {
|
|
65
|
+
if (copied) {
|
|
66
|
+
try {
|
|
67
|
+
if (copied.pathname === from || !copied.etag) {
|
|
68
|
+
throw new Error('Vercel Blob copy did not return a safe destination for cleanup');
|
|
69
|
+
}
|
|
70
|
+
await del(copied.pathname, {
|
|
71
|
+
ifMatch: copied.etag,
|
|
72
|
+
token
|
|
73
|
+
});
|
|
74
|
+
} catch (cleanupError) {
|
|
75
|
+
req?.payload.logger.error({
|
|
76
|
+
err: cleanupError,
|
|
77
|
+
msg: `Failed to remove unsuccessful Vercel Blob copy at ${copied.pathname}`
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const isBlobNotFound = (err)=>err instanceof BlobNotFoundError || err !== null && typeof err === 'object' && 'name' in err && err.name === 'BlobNotFoundError';
|
|
85
|
+
|
|
86
|
+
//# sourceMappingURL=copyFile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/copyFile.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { BlobNotFoundError, copy, del, head, put } from '@vercel/blob'\n\ntype Args = {\n access: 'public'\n cacheControlMaxAge: number\n from: string\n req?: PayloadRequest\n to: string\n token: string\n}\n\nexport const copyVercelBlobFile = async ({\n access,\n cacheControlMaxAge,\n from,\n req,\n to,\n token,\n}: Args): Promise<void> => {\n if (from === to) {\n throw new Error('Storage copy requires different source and destination keys')\n }\n\n const source = await head(from, { token })\n\n try {\n await head(to, { token })\n throw new Error(`Storage destination already exists: ${to}`)\n } catch (err) {\n if (!isBlobNotFound(err)) {\n throw err\n }\n }\n\n const sourceMaxAge = /(?:^|,)\\s*max-age=(\\d+)/.exec(source.cacheControl)\n let copied: { etag: string; pathname: string } | undefined = await copy(from, to, {\n access,\n addRandomSuffix: false,\n allowOverwrite: false,\n cacheControlMaxAge: sourceMaxAge ? Number(sourceMaxAge[1]) : cacheControlMaxAge,\n contentType: source.contentType,\n token,\n })\n\n try {\n if (copied.pathname !== to) {\n throw new Error(`Vercel Blob assigned an unexpected destination key: ${copied.pathname}`)\n }\n\n let destination = await head(to, { token })\n\n if (destination.size !== source.size) {\n await del(to, { ifMatch: copied.etag, token })\n // The first copy is gone; a rejected fallback must not delete another upload's object.\n copied = undefined\n\n const sourceResponse = await fetch(source.url)\n\n if (!sourceResponse.ok || !sourceResponse.body) {\n throw new Error(`Vercel Blob source is not readable: ${from}`)\n }\n\n copied = await put(to, sourceResponse.body, {\n access,\n addRandomSuffix: false,\n allowOverwrite: false,\n cacheControlMaxAge: sourceMaxAge ? Number(sourceMaxAge[1]) : cacheControlMaxAge,\n contentType: source.contentType,\n token,\n })\n\n if (copied.pathname !== to) {\n throw new Error(`Vercel Blob assigned an unexpected destination key: ${copied.pathname}`)\n }\n\n destination = await head(to, { token })\n\n if (destination.size !== source.size) {\n throw new Error(`Copied Vercel Blob object is not readable at its expected length: ${to}`)\n }\n }\n } catch (err) {\n if (copied) {\n try {\n if (copied.pathname === from || !copied.etag) {\n throw new Error('Vercel Blob copy did not return a safe destination for cleanup')\n }\n await del(copied.pathname, { ifMatch: copied.etag, token })\n } catch (cleanupError) {\n req?.payload.logger.error({\n err: cleanupError,\n msg: `Failed to remove unsuccessful Vercel Blob copy at ${copied.pathname}`,\n })\n }\n }\n throw err\n }\n}\n\nconst isBlobNotFound = (err: unknown): boolean =>\n err instanceof BlobNotFoundError ||\n (err !== null && typeof err === 'object' && 'name' in err && err.name === 'BlobNotFoundError')\n"],"names":["BlobNotFoundError","copy","del","head","put","copyVercelBlobFile","access","cacheControlMaxAge","from","req","to","token","Error","source","err","isBlobNotFound","sourceMaxAge","exec","cacheControl","copied","addRandomSuffix","allowOverwrite","Number","contentType","pathname","destination","size","ifMatch","etag","undefined","sourceResponse","fetch","url","ok","body","cleanupError","payload","logger","error","msg","name"],"mappings":"AAEA,SAASA,iBAAiB,EAAEC,IAAI,EAAEC,GAAG,EAAEC,IAAI,EAAEC,GAAG,QAAQ,eAAc;AAWtE,OAAO,MAAMC,qBAAqB,OAAO,EACvCC,MAAM,EACNC,kBAAkB,EAClBC,IAAI,EACJC,GAAG,EACHC,EAAE,EACFC,KAAK,EACA;IACL,IAAIH,SAASE,IAAI;QACf,MAAM,IAAIE,MAAM;IAClB;IAEA,MAAMC,SAAS,MAAMV,KAAKK,MAAM;QAAEG;IAAM;IAExC,IAAI;QACF,MAAMR,KAAKO,IAAI;YAAEC;QAAM;QACvB,MAAM,IAAIC,MAAM,CAAC,oCAAoC,EAAEF,IAAI;IAC7D,EAAE,OAAOI,KAAK;QACZ,IAAI,CAACC,eAAeD,MAAM;YACxB,MAAMA;QACR;IACF;IAEA,MAAME,eAAe,0BAA0BC,IAAI,CAACJ,OAAOK,YAAY;IACvE,IAAIC,SAAyD,MAAMlB,KAAKO,MAAME,IAAI;QAChFJ;QACAc,iBAAiB;QACjBC,gBAAgB;QAChBd,oBAAoBS,eAAeM,OAAON,YAAY,CAAC,EAAE,IAAIT;QAC7DgB,aAAaV,OAAOU,WAAW;QAC/BZ;IACF;IAEA,IAAI;QACF,IAAIQ,OAAOK,QAAQ,KAAKd,IAAI;YAC1B,MAAM,IAAIE,MAAM,CAAC,oDAAoD,EAAEO,OAAOK,QAAQ,EAAE;QAC1F;QAEA,IAAIC,cAAc,MAAMtB,KAAKO,IAAI;YAAEC;QAAM;QAEzC,IAAIc,YAAYC,IAAI,KAAKb,OAAOa,IAAI,EAAE;YACpC,MAAMxB,IAAIQ,IAAI;gBAAEiB,SAASR,OAAOS,IAAI;gBAAEjB;YAAM;YAC5C,uFAAuF;YACvFQ,SAASU;YAET,MAAMC,iBAAiB,MAAMC,MAAMlB,OAAOmB,GAAG;YAE7C,IAAI,CAACF,eAAeG,EAAE,IAAI,CAACH,eAAeI,IAAI,EAAE;gBAC9C,MAAM,IAAItB,MAAM,CAAC,oCAAoC,EAAEJ,MAAM;YAC/D;YAEAW,SAAS,MAAMf,IAAIM,IAAIoB,eAAeI,IAAI,EAAE;gBAC1C5B;gBACAc,iBAAiB;gBACjBC,gBAAgB;gBAChBd,oBAAoBS,eAAeM,OAAON,YAAY,CAAC,EAAE,IAAIT;gBAC7DgB,aAAaV,OAAOU,WAAW;gBAC/BZ;YACF;YAEA,IAAIQ,OAAOK,QAAQ,KAAKd,IAAI;gBAC1B,MAAM,IAAIE,MAAM,CAAC,oDAAoD,EAAEO,OAAOK,QAAQ,EAAE;YAC1F;YAEAC,cAAc,MAAMtB,KAAKO,IAAI;gBAAEC;YAAM;YAErC,IAAIc,YAAYC,IAAI,KAAKb,OAAOa,IAAI,EAAE;gBACpC,MAAM,IAAId,MAAM,CAAC,kEAAkE,EAAEF,IAAI;YAC3F;QACF;IACF,EAAE,OAAOI,KAAK;QACZ,IAAIK,QAAQ;YACV,IAAI;gBACF,IAAIA,OAAOK,QAAQ,KAAKhB,QAAQ,CAACW,OAAOS,IAAI,EAAE;oBAC5C,MAAM,IAAIhB,MAAM;gBAClB;gBACA,MAAMV,IAAIiB,OAAOK,QAAQ,EAAE;oBAAEG,SAASR,OAAOS,IAAI;oBAAEjB;gBAAM;YAC3D,EAAE,OAAOwB,cAAc;gBACrB1B,KAAK2B,QAAQC,OAAOC,MAAM;oBACxBxB,KAAKqB;oBACLI,KAAK,CAAC,kDAAkD,EAAEpB,OAAOK,QAAQ,EAAE;gBAC7E;YACF;QACF;QACA,MAAMV;IACR;AACF,EAAC;AAED,MAAMC,iBAAiB,CAACD,MACtBA,eAAed,qBACdc,QAAQ,QAAQ,OAAOA,QAAQ,YAAY,UAAUA,OAAOA,IAAI0B,IAAI,KAAK"}
|
package/dist/deleteFile.d.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
interface DeleteFileArgs {
|
|
2
2
|
baseUrl: string;
|
|
3
|
-
|
|
4
|
-
docPrefix: string;
|
|
5
|
-
filename: string;
|
|
3
|
+
storageFilePath: string;
|
|
6
4
|
token: string;
|
|
7
|
-
useCompositePrefixes?: boolean;
|
|
8
5
|
}
|
|
9
|
-
export declare function deleteFile({ baseUrl,
|
|
6
|
+
export declare function deleteFile({ baseUrl, storageFilePath, token, }: DeleteFileArgs): Promise<void>;
|
|
10
7
|
export {};
|
|
11
8
|
//# sourceMappingURL=deleteFile.d.ts.map
|
package/dist/deleteFile.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deleteFile.d.ts","sourceRoot":"","sources":["../src/deleteFile.ts"],"names":[],"mappings":"AAIA,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,
|
|
1
|
+
{"version":3,"file":"deleteFile.d.ts","sourceRoot":"","sources":["../src/deleteFile.ts"],"names":[],"mappings":"AAIA,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,eAAe,EAAE,MAAM,CAAA;IACvB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,wBAAsB,UAAU,CAAC,EAC/B,OAAO,EACP,eAAe,EACf,KAAK,GACN,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhC"}
|
package/dist/deleteFile.js
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import { del } from '@vercel/blob';
|
|
2
|
-
import {
|
|
3
|
-
export async function deleteFile({ baseUrl,
|
|
4
|
-
|
|
5
|
-
baseUrl,
|
|
6
|
-
collectionPrefix,
|
|
7
|
-
filename,
|
|
8
|
-
prefix: docPrefix,
|
|
9
|
-
useCompositePrefixes
|
|
10
|
-
});
|
|
11
|
-
await del(fileUrl, {
|
|
2
|
+
import { buildBlobUrl } from './generateURL.js';
|
|
3
|
+
export async function deleteFile({ baseUrl, storageFilePath, token }) {
|
|
4
|
+
await del(buildBlobUrl(baseUrl, storageFilePath), {
|
|
12
5
|
token
|
|
13
6
|
});
|
|
14
7
|
}
|