@payloadcms/storage-vercel-blob 4.0.0-internal.bc5cf8c → 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.
Files changed (47) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +11 -9
  3. package/dist/adapter.d.ts +3 -1
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/adapter.js +113 -19
  6. package/dist/adapter.js.map +1 -1
  7. package/dist/authorizeFileOverwrite.d.ts +16 -0
  8. package/dist/authorizeFileOverwrite.d.ts.map +1 -0
  9. package/dist/authorizeFileOverwrite.js +117 -0
  10. package/dist/authorizeFileOverwrite.js.map +1 -0
  11. package/dist/client/VercelBlobClientUploadHandler.d.ts +3 -8
  12. package/dist/client/VercelBlobClientUploadHandler.d.ts.map +1 -1
  13. package/dist/client/VercelBlobClientUploadHandler.js +8 -31
  14. package/dist/client/VercelBlobClientUploadHandler.js.map +1 -1
  15. package/dist/copyFile.d.ts +12 -0
  16. package/dist/copyFile.d.ts.map +1 -0
  17. package/dist/copyFile.js +86 -0
  18. package/dist/copyFile.js.map +1 -0
  19. package/dist/deleteFile.d.ts +2 -5
  20. package/dist/deleteFile.d.ts.map +1 -1
  21. package/dist/deleteFile.js +3 -10
  22. package/dist/deleteFile.js.map +1 -1
  23. package/dist/generateURL.d.ts +1 -0
  24. package/dist/generateURL.d.ts.map +1 -1
  25. package/dist/generateURL.js +13 -9
  26. package/dist/generateURL.js.map +1 -1
  27. package/dist/getCollectionSources.d.ts +20 -0
  28. package/dist/getCollectionSources.d.ts.map +1 -0
  29. package/dist/getCollectionSources.js +27 -0
  30. package/dist/getCollectionSources.js.map +1 -0
  31. package/dist/getFile.d.ts +5 -4
  32. package/dist/getFile.d.ts.map +1 -1
  33. package/dist/getFile.js +21 -13
  34. package/dist/getFile.js.map +1 -1
  35. package/dist/index.d.ts +11 -19
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +33 -41
  38. package/dist/index.js.map +1 -1
  39. package/dist/uploadFile.d.ts +2 -5
  40. package/dist/uploadFile.d.ts.map +1 -1
  41. package/dist/uploadFile.js +3 -9
  42. package/dist/uploadFile.js.map +1 -1
  43. package/package.json +4 -4
  44. package/dist/getClientUploadRoute.d.ts +0 -13
  45. package/dist/getClientUploadRoute.d.ts.map +0 -1
  46. package/dist/getClientUploadRoute.js +0 -35
  47. package/dist/getClientUploadRoute.js.map +0 -1
package/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2018-2025 Payload CMS, Inc. <info@payloadcms.com>
3
+ Copyright (c) 2018-2026 Payload CMS, LLC <info@payloadcms.com>
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining
6
6
  a copy of this software and associated documentation files (the
package/README.md CHANGED
@@ -15,7 +15,7 @@ pnpm add @payloadcms/storage-vercel-blob
15
15
  - Configure the `collections` object to specify which collections should use the Vercel Blob adapter. The slug _must_ match one of your existing collection slugs.
16
16
  - Ensure you have `BLOB_READ_WRITE_TOKEN` set in your Vercel environment variables. This is usually set by Vercel automatically after adding blob storage to your project.
17
17
  - When enabled, this package will automatically set `disableLocalStorage` to `true` for each collection.
18
- - When deploying to Vercel, server uploads are limited with 4.5MB. Set `clientUploads` to `true` to do uploads directly on the client.
18
+ - When deploying to Vercel, server uploads are limited to 4.5MB. Set `clientUploads` to `true` to use upload instructions and send files directly to Vercel Blob.
19
19
 
20
20
  ```ts
21
21
  import { vercelBlobStorage } from '@payloadcms/storage-vercel-blob'
@@ -41,11 +41,13 @@ export default buildConfig({
41
41
  })
42
42
  ```
43
43
 
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` | Add a random suffix to the uploaded file name in Vercel Blob storage | `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` | Do uploads directly on the client to bypass limits on Vercel | |
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
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,mBAAmB,EAEpB,MAAM,wCAAwC,CAAA;AAO/C,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,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,KAAK,EACL,oBAA4B,GAC7B,EAAE,2BAA2B,GAAG,OAAO,CA+DvC"}
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,11 +1,114 @@
1
+ import { buildUploadStoragePathData, resolveSignedURLKey } from '@payloadcms/plugin-cloud-storage/utilities';
2
+ import { generateClientTokenFromReadWriteToken } from '@vercel/blob/client';
3
+ import { Forbidden } from 'payload';
4
+ import { assertClientUploadAllowed, createClientUploadReceipt } from 'payload/internal';
5
+ import { authorizeClientOverwrite } from './authorizeFileOverwrite.js';
6
+ import { copyVercelBlobFile } from './copyFile.js';
1
7
  import { deleteFile } from './deleteFile.js';
2
8
  import { generateURL } from './generateURL.js';
3
9
  import { getFile } from './getFile.js';
4
10
  import { uploadFile } from './uploadFile.js';
5
- export function createVercelBlobAdapter({ access, addRandomSuffix, baseUrl, cacheControlMaxAge, clientUploads, token, useCompositePrefixes = false }) {
11
+ export function createVercelBlobAdapter({ access, addRandomSuffix, baseUrl, cacheControlMaxAge, clientUploads, collectionSources, token, useCompositePrefixes = false }) {
12
+ const clientUploadsAccess = typeof clientUploads === 'object' ? clientUploads.access : undefined;
13
+ const deleteStoredFile = ({ storageFilePath })=>deleteFile({
14
+ baseUrl,
15
+ storageFilePath,
16
+ token
17
+ });
6
18
  return ({ collection, prefix = '' })=>({
7
19
  name: 'vercel-blob',
8
- clientUploads,
20
+ copyFile: ({ from, req, to })=>copyVercelBlobFile({
21
+ access,
22
+ cacheControlMaxAge,
23
+ from,
24
+ req,
25
+ to,
26
+ token
27
+ }),
28
+ deleteFile: deleteStoredFile,
29
+ uploadInstructions: {
30
+ adminHandler: {
31
+ path: '@payloadcms/storage-vercel-blob/client#VercelBlobClientUploadHandler'
32
+ },
33
+ enabled: Boolean(clientUploads),
34
+ generate: async ({ collectionSlug, docPrefix, filename, filesize, mimeType, overrideAccess, req })=>{
35
+ if (!overrideAccess && (clientUploadsAccess ? !await clientUploadsAccess({
36
+ collectionSlug,
37
+ req
38
+ }) : !req.user)) {
39
+ throw new Forbidden(req.t);
40
+ }
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({
75
+ collectionPrefix: prefix,
76
+ collectionSlug,
77
+ docPrefix,
78
+ filename,
79
+ req,
80
+ useCompositePrefixes
81
+ });
82
+ return {
83
+ name: 'uploadToVercelBlob',
84
+ type: 'dispatch',
85
+ data: {
86
+ pathname: resolved.storageFilePath,
87
+ token: await generateClientTokenFromReadWriteToken({
88
+ addRandomSuffix: false,
89
+ allowedContentTypes: mimeType ? [
90
+ mimeType
91
+ ] : undefined,
92
+ ...allowOverwrite && {
93
+ allowOverwrite: true
94
+ },
95
+ cacheControlMaxAge,
96
+ maximumSizeInBytes: filesize,
97
+ pathname: resolved.storageFilePath,
98
+ token
99
+ })
100
+ },
101
+ file: {
102
+ filename: resolved.sanitizedFilename,
103
+ mimeType,
104
+ size: filesize,
105
+ uploadReference: resolved.uploadReference
106
+ }
107
+ };
108
+ },
109
+ requiresUploadReceipt: true,
110
+ useInAdmin: true
111
+ },
9
112
  generateURL: ({ filename, prefix: urlPrefix = '' })=>generateURL({
10
113
  baseUrl,
11
114
  collectionPrefix: prefix,
@@ -13,43 +116,34 @@ export function createVercelBlobAdapter({ access, addRandomSuffix, baseUrl, cach
13
116
  prefix: urlPrefix,
14
117
  useCompositePrefixes
15
118
  }),
16
- handleDelete: ({ doc: { prefix: docPrefix = '' }, filename })=>deleteFile({
17
- baseUrl,
18
- collectionPrefix: prefix,
19
- docPrefix,
20
- filename,
21
- token,
22
- useCompositePrefixes
23
- }),
24
- handleUpload: async ({ data, file: { buffer, filename, mimeType } })=>{
119
+ handleDelete: deleteStoredFile,
120
+ handleUpload: async ({ data, file: { buffer, mimeType }, storageFilePath })=>{
25
121
  const result = await uploadFile({
26
122
  access,
27
123
  addRandomSuffix,
28
124
  buffer,
29
125
  cacheControlMaxAge,
30
- collectionPrefix: prefix,
31
- docPrefix: data.prefix,
32
- filename,
33
126
  mimeType,
34
- token,
35
- useCompositePrefixes
127
+ storageFilePath,
128
+ token
36
129
  });
37
130
  if (result.filename) {
38
131
  data.filename = result.filename;
39
132
  }
40
133
  return data;
41
134
  },
42
- staticHandler: (req, { headers, params: { clientUploadContext, filename, prefix: prefixQueryParam } })=>getFile({
135
+ staticHandler: (req, { doc, headers, params: { filename, operation, uploadReference } })=>getFile({
43
136
  baseUrl,
44
137
  cacheControlMaxAge,
45
- clientUploadContext,
46
138
  collection,
47
139
  collectionPrefix: prefix,
140
+ doc,
48
141
  filename,
49
142
  incomingHeaders: headers,
50
- prefixQueryParam,
143
+ operation,
51
144
  req,
52
145
  token,
146
+ uploadReference,
53
147
  useCompositePrefixes
54
148
  })
55
149
  });
@@ -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 { 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 token: string\n useCompositePrefixes?: boolean\n}\n\nexport function createVercelBlobAdapter({\n access,\n addRandomSuffix,\n baseUrl,\n cacheControlMaxAge,\n clientUploads,\n token,\n useCompositePrefixes = false,\n}: CreateVercelBlobAdapterArgs): Adapter {\n return ({ collection, prefix = '' }): GeneratedAdapter => ({\n name: 'vercel-blob',\n clientUploads,\n\n generateURL: ({ filename, prefix: urlPrefix = '' }) =>\n generateURL({\n baseUrl,\n collectionPrefix: prefix,\n filename,\n prefix: urlPrefix,\n useCompositePrefixes,\n }),\n\n handleDelete: ({ doc: { prefix: docPrefix = '' }, filename }) =>\n deleteFile({\n baseUrl,\n collectionPrefix: prefix,\n docPrefix,\n filename,\n token,\n useCompositePrefixes,\n }),\n\n handleUpload: async ({ data, file: { buffer, filename, mimeType } }) => {\n const result = await uploadFile({\n access,\n addRandomSuffix,\n buffer,\n cacheControlMaxAge,\n collectionPrefix: prefix,\n docPrefix: data.prefix,\n filename,\n mimeType,\n token,\n useCompositePrefixes,\n })\n\n if (result.filename) {\n data.filename = result.filename\n }\n\n return data\n },\n\n staticHandler: (\n req,\n { headers, params: { clientUploadContext, filename, prefix: prefixQueryParam } },\n ) =>\n getFile({\n baseUrl,\n cacheControlMaxAge,\n clientUploadContext,\n collection,\n collectionPrefix: prefix,\n filename,\n incomingHeaders: headers,\n prefixQueryParam,\n req,\n token,\n useCompositePrefixes,\n }),\n })\n}\n"],"names":["deleteFile","generateURL","getFile","uploadFile","createVercelBlobAdapter","access","addRandomSuffix","baseUrl","cacheControlMaxAge","clientUploads","token","useCompositePrefixes","collection","prefix","name","filename","urlPrefix","collectionPrefix","handleDelete","doc","docPrefix","handleUpload","data","file","buffer","mimeType","result","staticHandler","req","headers","params","clientUploadContext","prefixQueryParam","incomingHeaders"],"mappings":"AAMA,SAASA,UAAU,QAAQ,kBAAiB;AAC5C,SAASC,WAAW,QAAQ,mBAAkB;AAC9C,SAASC,OAAO,QAAQ,eAAc;AACtC,SAASC,UAAU,QAAQ,kBAAiB;AAY5C,OAAO,SAASC,wBAAwB,EACtCC,MAAM,EACNC,eAAe,EACfC,OAAO,EACPC,kBAAkB,EAClBC,aAAa,EACbC,KAAK,EACLC,uBAAuB,KAAK,EACA;IAC5B,OAAO,CAAC,EAAEC,UAAU,EAAEC,SAAS,EAAE,EAAE,GAAwB,CAAA;YACzDC,MAAM;YACNL;YAEAR,aAAa,CAAC,EAAEc,QAAQ,EAAEF,QAAQG,YAAY,EAAE,EAAE,GAChDf,YAAY;oBACVM;oBACAU,kBAAkBJ;oBAClBE;oBACAF,QAAQG;oBACRL;gBACF;YAEFO,cAAc,CAAC,EAAEC,KAAK,EAAEN,QAAQO,YAAY,EAAE,EAAE,EAAEL,QAAQ,EAAE,GAC1Df,WAAW;oBACTO;oBACAU,kBAAkBJ;oBAClBO;oBACAL;oBACAL;oBACAC;gBACF;YAEFU,cAAc,OAAO,EAAEC,IAAI,EAAEC,MAAM,EAAEC,MAAM,EAAET,QAAQ,EAAEU,QAAQ,EAAE,EAAE;gBACjE,MAAMC,SAAS,MAAMvB,WAAW;oBAC9BE;oBACAC;oBACAkB;oBACAhB;oBACAS,kBAAkBJ;oBAClBO,WAAWE,KAAKT,MAAM;oBACtBE;oBACAU;oBACAf;oBACAC;gBACF;gBAEA,IAAIe,OAAOX,QAAQ,EAAE;oBACnBO,KAAKP,QAAQ,GAAGW,OAAOX,QAAQ;gBACjC;gBAEA,OAAOO;YACT;YAEAK,eAAe,CACbC,KACA,EAAEC,OAAO,EAAEC,QAAQ,EAAEC,mBAAmB,EAAEhB,QAAQ,EAAEF,QAAQmB,gBAAgB,EAAE,EAAE,GAEhF9B,QAAQ;oBACNK;oBACAC;oBACAuB;oBACAnB;oBACAK,kBAAkBJ;oBAClBE;oBACAkB,iBAAiBJ;oBACjBG;oBACAJ;oBACAlB;oBACAC;gBACF;QACJ,CAAA;AACF"}
1
+ {"version":3,"sources":["../src/adapter.ts"],"sourcesContent":["import type {\n Adapter,\n ClientUploadsConfig,\n DeleteFile,\n GeneratedAdapter,\n} from '@payloadcms/plugin-cloud-storage/types'\n\nimport {\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":"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"}
@@ -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":"AAEA,SACEA,oBAAoB,EACpBC,mCAAmC,QAC9B,6CAA4C;AACnD,SAASC,cAAc,EAAEC,aAAa,EAAEC,SAAS,QAAQ,UAAS;AAelE,SAASC,oBAAoB,EAC3BC,gBAAgB,EAChBC,GAAG,EACHC,oBAAoB,EAKrB;IACC,OAAO;QAACD,IAAIE,QAAQ;WAAKC,OAAOC,MAAM,CAACJ,IAAIK,QAAQ,IAAI,CAAC,GAAGC,GAAG,CAAC,CAACC,OAASA,MAAML;KAAU,CACtFM,MAAM,CAAC,CAACN,WAAiC,OAAOA,aAAa,UAC7DI,GAAG,CACF,CAACJ,WACCT,qBAAqB;YACnBM;YACAU,WAAWT,IAAIU,MAAM;YACrBR;YACAD;QACF,GAAGU,eAAe;AAE1B;AAEA,eAAeC,iBAAiB,EAC9Bb,gBAAgB,EAChBc,cAAc,EACdC,GAAG,EACHC,iBAAiB,EACjBC,wBAAwB,EACxBf,oBAAoB,EAQrB;IACC,MAAMgB,aAAaH,IAAII,OAAO,CAACC,WAAW,CAACN,eAAe;IAE1D,IAAI,CAACI,YAAY;QACf,OAAO,EAAE;IACX;IAEA,MAAMZ,WACJY,WAAWG,MAAM,CAACC,MAAM,IAAI,OAAOJ,WAAWG,MAAM,CAACC,MAAM,KAAK,WAC5DJ,WAAWG,MAAM,CAACC,MAAM,CAAChB,QAAQ,IAAI,EAAE,GACvC,EAAE;IACR,MAAMiB,kBAA2B;QAC/B;YAAEpB,UAAU;gBAAEqB,QAAQR;YAAkB;QAAE;WACvCV,SAASC,GAAG,CAAC,CAAC,EAAEkB,IAAI,EAAE,GAAM,CAAA;gBAC7B,CAAC,CAAC,SAAS,EAAEA,KAAK,SAAS,CAAC,CAAC,EAAE;oBAAED,QAAQR;gBAAkB;YAC7D,CAAA;KACD;IACD,MAAMU,SAAS,MAAMX,IAAII,OAAO,CAACQ,IAAI,CAAC;QACpCT,YAAYJ;QACZc,OAAO;QACPC,OAAO;QACPC,gBAAgB;QAChBC,YAAY;QACZhB;QACAiB,OAAO;YAAEC,IAAIV;QAAgB;IAC/B;IAEA,OAAO,AAACG,OAAOQ,IAAI,CAAqBzB,MAAM,CAAC,CAACR,MAC9CF,oBAAoB;YAAEC;YAAkBC;YAAKC;QAAqB,GAAGiC,QAAQ,CAC3ElB;AAGN;AAEA,eAAemB,aAAa,EAC1BtB,cAAc,EACdb,GAAG,EACHc,GAAG,EAKJ;IACC,MAAMG,aAAaH,IAAII,OAAO,CAACC,WAAW,CAACN,eAAe;IAE1D,IAAI,CAACI,YAAY;QACf,OAAO;IACT;IAEA,MAAMmB,eAAe,MAAMxC,cACzB;QAAEyC,IAAIrC,IAAIqC,EAAE;QAAEC,MAAMzB;QAAgB0B,eAAe;QAAMzB;IAAI,GAC7DG,WAAWG,MAAM,CAACoB,MAAM,CAACC,MAAM;IAGjC,IAAI,OAAOL,iBAAiB,WAAW;QACrC,OAAOA;IACT;IAEA,MAAMX,SAAS,MAAMX,IAAII,OAAO,CAACQ,IAAI,CAAC;QACpCT,YAAYJ;QACZc,OAAO;QACPC,OAAO;QACPc,OAAO;QACPb,gBAAgB;QAChBC,YAAY;QACZhB;QACAiB,OAAOpC,eAAe;YAAE0C,IAAI;gBAAEd,QAAQvB,IAAIqC,EAAE;YAAC;QAAE,GAAGD;IACpD;IAEA,OAAOX,OAAOQ,IAAI,CAACU,MAAM,KAAK;AAChC;AAEA,OAAO,eAAeC,yBAAyB,EAC7C7C,kBAAkB8C,mBAAmB,EACrCC,iBAAiB,EACjBjB,iBAAiB,KAAK,EACtBf,GAAG,EACHiC,uBAAuB,EACvBhC,iBAAiB,EACjBC,wBAAwB,EASzB;IACC,IACE,CAACtB,oCAAoC;QACnCK,kBAAkB8C;QAClBpC,WAAWO;IACb,IACA;QACA,MAAM,IAAInB,UAAUiB,IAAIkC,CAAC;IAC3B;IAEA,MAAMC,cAAc,AAClB,CAAA,MAAMC,QAAQC,GAAG,CACfL,kBAAkBxC,GAAG,CACnB,OAAO,EAAEP,gBAAgB,EAAEc,cAAc,EAAEZ,oBAAoB,EAAE;QAC/D,MAAMgC,OAAO,MAAMrB,iBAAiB;YAClCb;YACAc;YACAC;YACAC;YACAC;YACAf;QACF;QAEA,OAAOgC,KAAK3B,GAAG,CAAC,CAACN,MAAS,CAAA;gBAAEa;gBAAgBb;YAAI,CAAA;IAClD,GAEJ,EACAoD,IAAI;IAEN,IAAIH,YAAYN,MAAM,KAAK,GAAG;QAC5B,OAAO;IACT;IAEA,MAAMU,aAAaJ,WAAW,CAAC,EAAE;IACjC,IAAIA,YAAYN,MAAM,KAAK,KAAKU,WAAWxC,cAAc,KAAKkC,yBAAyB;QACrF,MAAM,IAAIlD,UAAUiB,IAAIkC,CAAC;IAC3B;IAEA,IACE,CAACnB,kBACD,CAAE,MAAMM,aAAa;QAAEtB,gBAAgBwC,WAAWxC,cAAc;QAAEb,KAAKqD,WAAWrD,GAAG;QAAEc;IAAI,IAC3F;QACA,MAAM,IAAIjB,UAAUiB,IAAIkC,CAAC;IAC3B;IAEA,OAAO;AACT"}
@@ -1,13 +1,8 @@
1
- export type VercelBlobClientUploadHandlerExtra = {
2
- addRandomSuffix: boolean;
3
- useCompositePrefixes: boolean;
4
- };
5
- export declare const VercelBlobClientUploadHandler: ({ children, collectionSlug, enabled, extra, prefix, serverHandlerPath, }: {
1
+ export declare const VercelBlobClientUploadHandler: ({ children, collectionSlug, endpointPath, prefix, props, }: {
6
2
  children: import("react").ReactNode;
7
3
  collectionSlug: import("payload").UploadCollectionSlug;
8
- enabled?: boolean;
9
- extra: VercelBlobClientUploadHandlerExtra;
4
+ endpointPath?: `/${string}`;
10
5
  prefix?: string;
11
- serverHandlerPath: `/${string}`;
6
+ props: Record<string, unknown>;
12
7
  }) => import("react").JSX.Element;
13
8
  //# sourceMappingURL=VercelBlobClientUploadHandler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"VercelBlobClientUploadHandler.d.ts","sourceRoot":"","sources":["../../src/client/VercelBlobClientUploadHandler.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,kCAAkC,GAAG;IAC/C,eAAe,EAAE,OAAO,CAAA;IACxB,oBAAoB,EAAE,OAAO,CAAA;CAC9B,CAAA;AASD,eAAO,MAAM,6BAA6B;;;;;;;iCAyCtC,CAAA"}
1
+ {"version":3,"file":"VercelBlobClientUploadHandler.d.ts","sourceRoot":"","sources":["../../src/client/VercelBlobClientUploadHandler.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,6BAA6B;;;;;;iCAexC,CAAA"}
@@ -1,41 +1,18 @@
1
1
  'use client';
2
2
  import { createClientUploadHandler } from '@payloadcms/plugin-cloud-storage/client';
3
- import { getFileKey } from '@payloadcms/plugin-cloud-storage/utilities';
4
- import { upload } from '@vercel/blob/client';
5
- import { formatAdminURL } from 'payload/shared';
6
- /** Last path segment only (POSIX), for keys like `folder/sub/file.png`. */ function posixBasename(key) {
7
- const normalized = key.replace(/^\/+/, '');
8
- const lastSlash = normalized.lastIndexOf('/');
9
- return lastSlash === -1 ? normalized : normalized.slice(lastSlash + 1);
10
- }
3
+ import { put } from '@vercel/blob/client';
11
4
  export const VercelBlobClientUploadHandler = createClientUploadHandler({
12
- handler: async ({ apiRoute, collectionSlug, docPrefix, extra: { addRandomSuffix, useCompositePrefixes = false }, file, prefix, serverHandlerPath, serverURL, updateFilename })=>{
13
- const endpointRoute = formatAdminURL({
14
- apiRoute,
15
- path: serverHandlerPath,
16
- serverURL
17
- });
18
- const { fileKey: pathname, sanitizedDocPrefix } = getFileKey({
19
- collectionPrefix: prefix,
20
- docPrefix,
21
- filename: file.name,
22
- useCompositePrefixes
23
- });
24
- // upload the file directly to Vercel Blob using the signed URL
25
- const result = await upload(pathname, file, {
5
+ name: 'uploadToVercelBlob',
6
+ handler: async ({ data, file })=>{
7
+ const { pathname, token } = data;
8
+ const result = await put(pathname, file, {
26
9
  access: 'public',
27
- clientPayload: collectionSlug,
28
10
  contentType: file.type,
29
- handleUploadUrl: endpointRoute
11
+ token
30
12
  });
31
- // Match server uploadFile: document `filename` is basename only; prefixes are separate.
32
- if (addRandomSuffix) {
33
- const pathname = result.pathname.replace(/^\/+/, '');
34
- updateFilename(decodeURIComponent(posixBasename(pathname)));
13
+ if (decodeURIComponent(result.pathname) !== decodeURIComponent(pathname)) {
14
+ throw new Error('The uploaded file was stored at an unexpected location');
35
15
  }
36
- return {
37
- prefix: sanitizedDocPrefix
38
- };
39
16
  }
40
17
  });
41
18
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/client/VercelBlobClientUploadHandler.ts"],"sourcesContent":["'use client'\nimport { createClientUploadHandler } from '@payloadcms/plugin-cloud-storage/client'\nimport { getFileKey } from '@payloadcms/plugin-cloud-storage/utilities'\nimport { upload } from '@vercel/blob/client'\nimport { formatAdminURL } from 'payload/shared'\n\nexport type VercelBlobClientUploadHandlerExtra = {\n addRandomSuffix: boolean\n useCompositePrefixes: boolean\n}\n\n/** Last path segment only (POSIX), for keys like `folder/sub/file.png`. */\nfunction posixBasename(key: string): string {\n const normalized = key.replace(/^\\/+/, '')\n const lastSlash = normalized.lastIndexOf('/')\n return lastSlash === -1 ? normalized : normalized.slice(lastSlash + 1)\n}\n\nexport const VercelBlobClientUploadHandler =\n createClientUploadHandler<VercelBlobClientUploadHandlerExtra>({\n handler: async ({\n apiRoute,\n collectionSlug,\n docPrefix,\n extra: { addRandomSuffix, useCompositePrefixes = false },\n file,\n prefix,\n serverHandlerPath,\n serverURL,\n updateFilename,\n }) => {\n const endpointRoute = formatAdminURL({\n apiRoute,\n path: serverHandlerPath,\n serverURL,\n })\n const { fileKey: pathname, sanitizedDocPrefix } = getFileKey({\n collectionPrefix: prefix,\n docPrefix,\n filename: file.name,\n useCompositePrefixes,\n })\n\n // upload the file directly to Vercel Blob using the signed URL\n const result = await upload(pathname, file, {\n access: 'public',\n clientPayload: collectionSlug,\n contentType: file.type,\n handleUploadUrl: endpointRoute,\n })\n\n // Match server uploadFile: document `filename` is basename only; prefixes are separate.\n if (addRandomSuffix) {\n const pathname = result.pathname.replace(/^\\/+/, '')\n updateFilename(decodeURIComponent(posixBasename(pathname)))\n }\n\n return { prefix: sanitizedDocPrefix }\n },\n })\n"],"names":["createClientUploadHandler","getFileKey","upload","formatAdminURL","posixBasename","key","normalized","replace","lastSlash","lastIndexOf","slice","VercelBlobClientUploadHandler","handler","apiRoute","collectionSlug","docPrefix","extra","addRandomSuffix","useCompositePrefixes","file","prefix","serverHandlerPath","serverURL","updateFilename","endpointRoute","path","fileKey","pathname","sanitizedDocPrefix","collectionPrefix","filename","name","result","access","clientPayload","contentType","type","handleUploadUrl","decodeURIComponent"],"mappings":"AAAA;AACA,SAASA,yBAAyB,QAAQ,0CAAyC;AACnF,SAASC,UAAU,QAAQ,6CAA4C;AACvE,SAASC,MAAM,QAAQ,sBAAqB;AAC5C,SAASC,cAAc,QAAQ,iBAAgB;AAO/C,yEAAyE,GACzE,SAASC,cAAcC,GAAW;IAChC,MAAMC,aAAaD,IAAIE,OAAO,CAAC,QAAQ;IACvC,MAAMC,YAAYF,WAAWG,WAAW,CAAC;IACzC,OAAOD,cAAc,CAAC,IAAIF,aAAaA,WAAWI,KAAK,CAACF,YAAY;AACtE;AAEA,OAAO,MAAMG,gCACXX,0BAA8D;IAC5DY,SAAS,OAAO,EACdC,QAAQ,EACRC,cAAc,EACdC,SAAS,EACTC,OAAO,EAAEC,eAAe,EAAEC,uBAAuB,KAAK,EAAE,EACxDC,IAAI,EACJC,MAAM,EACNC,iBAAiB,EACjBC,SAAS,EACTC,cAAc,EACf;QACC,MAAMC,gBAAgBrB,eAAe;YACnCU;YACAY,MAAMJ;YACNC;QACF;QACA,MAAM,EAAEI,SAASC,QAAQ,EAAEC,kBAAkB,EAAE,GAAG3B,WAAW;YAC3D4B,kBAAkBT;YAClBL;YACAe,UAAUX,KAAKY,IAAI;YACnBb;QACF;QAEA,+DAA+D;QAC/D,MAAMc,SAAS,MAAM9B,OAAOyB,UAAUR,MAAM;YAC1Cc,QAAQ;YACRC,eAAepB;YACfqB,aAAahB,KAAKiB,IAAI;YACtBC,iBAAiBb;QACnB;QAEA,wFAAwF;QACxF,IAAIP,iBAAiB;YACnB,MAAMU,WAAWK,OAAOL,QAAQ,CAACpB,OAAO,CAAC,QAAQ;YACjDgB,eAAee,mBAAmBlC,cAAcuB;QAClD;QAEA,OAAO;YAAEP,QAAQQ;QAAmB;IACtC;AACF,GAAE"}
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"}
@@ -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