@payloadcms/plugin-cloud-storage 4.0.0-internal.1f9ae9a → 4.0.0-internal.2071af3

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 (99) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +2 -0
  3. package/dist/client/createClientUploadHandler.d.ts +8 -7
  4. package/dist/client/createClientUploadHandler.d.ts.map +1 -1
  5. package/dist/client/createClientUploadHandler.js +26 -22
  6. package/dist/client/createClientUploadHandler.js.map +1 -1
  7. package/dist/exports/client.d.ts +2 -0
  8. package/dist/exports/client.d.ts.map +1 -1
  9. package/dist/exports/client.js +2 -0
  10. package/dist/exports/client.js.map +1 -1
  11. package/dist/exports/utilities.d.ts +2 -2
  12. package/dist/exports/utilities.d.ts.map +1 -1
  13. package/dist/exports/utilities.js +2 -2
  14. package/dist/exports/utilities.js.map +1 -1
  15. package/dist/fields/getFields.d.ts +1 -5
  16. package/dist/fields/getFields.d.ts.map +1 -1
  17. package/dist/fields/getFields.js +118 -24
  18. package/dist/fields/getFields.js.map +1 -1
  19. package/dist/hooks/afterChange.d.ts +5 -1
  20. package/dist/hooks/afterChange.d.ts.map +1 -1
  21. package/dist/hooks/afterChange.js +140 -24
  22. package/dist/hooks/afterChange.js.map +1 -1
  23. package/dist/hooks/afterDelete.d.ts +3 -1
  24. package/dist/hooks/afterDelete.d.ts.map +1 -1
  25. package/dist/hooks/afterDelete.js +27 -5
  26. package/dist/hooks/afterDelete.js.map +1 -1
  27. package/dist/hooks/afterRead.d.ts +6 -1
  28. package/dist/hooks/afterRead.d.ts.map +1 -1
  29. package/dist/hooks/afterRead.js +34 -7
  30. package/dist/hooks/afterRead.js.map +1 -1
  31. package/dist/hooks/beforeChange.d.ts +2 -1
  32. package/dist/hooks/beforeChange.d.ts.map +1 -1
  33. package/dist/hooks/beforeChange.js +18 -4
  34. package/dist/hooks/beforeChange.js.map +1 -1
  35. package/dist/hooks/normalizeUploadPrefix.d.ts +11 -0
  36. package/dist/hooks/normalizeUploadPrefix.d.ts.map +1 -0
  37. package/dist/hooks/normalizeUploadPrefix.js +42 -0
  38. package/dist/hooks/normalizeUploadPrefix.js.map +1 -0
  39. package/dist/hooks/publicOriginalURL.d.ts +8 -0
  40. package/dist/hooks/publicOriginalURL.d.ts.map +1 -0
  41. package/dist/hooks/publicOriginalURL.js +34 -0
  42. package/dist/hooks/publicOriginalURL.js.map +1 -0
  43. package/dist/plugin.d.ts.map +1 -1
  44. package/dist/plugin.js +203 -128
  45. package/dist/plugin.js.map +1 -1
  46. package/dist/types.d.ts +66 -21
  47. package/dist/types.d.ts.map +1 -1
  48. package/dist/types.js.map +1 -1
  49. package/dist/utilities/buildPrefixWithObjectKey.d.ts +8 -0
  50. package/dist/utilities/buildPrefixWithObjectKey.d.ts.map +1 -0
  51. package/dist/utilities/buildPrefixWithObjectKey.js +13 -0
  52. package/dist/utilities/buildPrefixWithObjectKey.js.map +1 -0
  53. package/dist/utilities/buildStoragePathData.d.ts +49 -0
  54. package/dist/utilities/buildStoragePathData.d.ts.map +1 -0
  55. package/dist/utilities/buildStoragePathData.js +67 -0
  56. package/dist/utilities/buildStoragePathData.js.map +1 -0
  57. package/dist/utilities/createFileOperations.d.ts +13 -0
  58. package/dist/utilities/createFileOperations.d.ts.map +1 -0
  59. package/dist/utilities/createFileOperations.js +293 -0
  60. package/dist/utilities/createFileOperations.js.map +1 -0
  61. package/dist/utilities/getFilePrefix.d.ts +18 -11
  62. package/dist/utilities/getFilePrefix.d.ts.map +1 -1
  63. package/dist/utilities/getFilePrefix.js +93 -31
  64. package/dist/utilities/getFilePrefix.js.map +1 -1
  65. package/dist/utilities/getIncomingFiles.js +4 -4
  66. package/dist/utilities/getIncomingFiles.js.map +1 -1
  67. package/dist/utilities/getStorageLocationSelect.d.ts +9 -0
  68. package/dist/utilities/getStorageLocationSelect.d.ts.map +1 -0
  69. package/dist/utilities/getStorageLocationSelect.js +60 -0
  70. package/dist/utilities/getStorageLocationSelect.js.map +1 -0
  71. package/dist/utilities/resolveSignedURLKey.d.ts +16 -3
  72. package/dist/utilities/resolveSignedURLKey.d.ts.map +1 -1
  73. package/dist/utilities/resolveSignedURLKey.js +45 -10
  74. package/dist/utilities/resolveSignedURLKey.js.map +1 -1
  75. package/dist/utilities/sanitizePrefix.d.ts +1 -9
  76. package/dist/utilities/sanitizePrefix.d.ts.map +1 -1
  77. package/dist/utilities/sanitizePrefix.js +1 -21
  78. package/dist/utilities/sanitizePrefix.js.map +1 -1
  79. package/package.json +24 -26
  80. package/dist/admin/fields/getFields.d.ts +0 -17
  81. package/dist/admin/fields/getFields.d.ts.map +0 -1
  82. package/dist/admin/fields/getFields.js +0 -98
  83. package/dist/admin/fields/getFields.js.map +0 -1
  84. package/dist/admin/index.d.ts +0 -4
  85. package/dist/admin/index.d.ts.map +0 -1
  86. package/dist/admin/index.js +0 -34
  87. package/dist/admin/index.js.map +0 -1
  88. package/dist/utilities/getFileKey.d.ts +0 -22
  89. package/dist/utilities/getFileKey.d.ts.map +0 -1
  90. package/dist/utilities/getFileKey.js +0 -23
  91. package/dist/utilities/getFileKey.js.map +0 -1
  92. package/dist/utilities/getFileKey.spec.js +0 -174
  93. package/dist/utilities/getFileKey.spec.js.map +0 -1
  94. package/dist/utilities/getFilePrefix.spec.js +0 -105
  95. package/dist/utilities/getFilePrefix.spec.js.map +0 -1
  96. package/dist/utilities/initClientUploads.d.ts +0 -13
  97. package/dist/utilities/initClientUploads.d.ts.map +0 -1
  98. package/dist/utilities/initClientUploads.js +0 -63
  99. package/dist/utilities/initClientUploads.js.map +0 -1
@@ -1,46 +1,108 @@
1
+ import { isOriginalFileRequest } from 'payload/internal';
2
+ import { buildPrefixWithObjectKey } from './buildPrefixWithObjectKey.js';
3
+ import { buildUploadStoragePathData } from './buildStoragePathData.js';
1
4
  import { sanitizePrefix } from './sanitizePrefix.js';
2
5
  /**
3
- * Resolves the file prefix from the highest-priority available source and
4
- * always returns a sanitized value.
6
+ * Resolves the folder a stored object lives under (semantic `prefix` plus `_objectKey`).
5
7
  *
6
- * Resolution order:
7
- * 1. `prefixQueryParam`
8
- * 2. `clientUploadContext.prefix`
9
- * 3. Stored document `prefix` from the database
10
- *
11
- * Resolved values are passed through `sanitizePrefix`.
12
- */ export async function getFilePrefix({ clientUploadContext, collection, filename, prefixQueryParam, req }) {
13
- if (typeof prefixQueryParam === 'string') {
14
- return sanitizePrefix(prefixQueryParam);
8
+ * An already-authorized `doc` wins outright. Otherwise an `uploadReference` is used (its
9
+ * `prefix` re-contained with {@link buildUploadStoragePathData}, since it hasn't been verified against
10
+ * a signed receipt by every adapter). Otherwise the document is resolved with
11
+ * `overrideAccess: false`, any query-param prefix only narrows that lookup, and `_objectKey`
12
+ * is read from the resolved document via `showHiddenFields`.
13
+ */ export async function getFilePrefix({ collection, collectionPrefix, doc, filename, isOriginal, prefixQueryParam, req, uploadReference, useCompositePrefixes = false }) {
14
+ // The serve path already loaded and authorized this document — trust it over any
15
+ // client-supplied upload reference or query prefix.
16
+ if (doc) {
17
+ return getRepresentationPrefix({
18
+ doc,
19
+ filename,
20
+ isOriginal
21
+ });
15
22
  }
16
- // Prioritize from clientUploadContext if there is:
17
- if (clientUploadContext && typeof clientUploadContext === 'object' && 'prefix' in clientUploadContext && typeof clientUploadContext.prefix === 'string') {
18
- return sanitizePrefix(clientUploadContext.prefix);
23
+ // Upload instructions call handlers without a document yet. Re-contain the claimed
24
+ // prefix — it hasn't been verified against a signed receipt by every adapter.
25
+ if (uploadReference && typeof uploadReference === 'object' && 'prefix' in uploadReference && typeof uploadReference.prefix === 'string') {
26
+ const referenceObjectKey = '_objectKey' in uploadReference ? uploadReference._objectKey : undefined;
27
+ const containedPrefix = buildUploadStoragePathData({
28
+ collectionPrefix,
29
+ docPrefix: uploadReference.prefix,
30
+ filename,
31
+ useCompositePrefixes
32
+ }).sanitizedDocPrefix;
33
+ return buildPrefixWithObjectKey({
34
+ objectKey: referenceObjectKey,
35
+ prefix: containedPrefix
36
+ });
19
37
  }
20
- const imageSizes = collection?.upload?.imageSizes || [];
38
+ // Reads without a query prefix or read-access constraints skip the endpoint's document lookup.
39
+ const variants = collection?.upload?.variants || [];
40
+ const filenameClause = {
41
+ or: [
42
+ {
43
+ filename: {
44
+ equals: filename
45
+ }
46
+ },
47
+ {
48
+ 'original.filename': {
49
+ equals: filename
50
+ }
51
+ },
52
+ ...variants.map((imageSize)=>({
53
+ [`variants.${imageSize.name}.filename`]: {
54
+ equals: filename
55
+ }
56
+ }))
57
+ ]
58
+ };
59
+ // Only filter by prefix when the collection persists a `prefix` field (querying a missing field throws).
60
+ const hasPrefixField = collection.fields?.some((field)=>'name' in field && field.name === 'prefix');
61
+ const where = typeof prefixQueryParam === 'string' && hasPrefixField ? {
62
+ and: [
63
+ filenameClause,
64
+ {
65
+ prefix: {
66
+ equals: sanitizePrefix(prefixQueryParam)
67
+ }
68
+ }
69
+ ]
70
+ } : filenameClause;
21
71
  const files = await req.payload.find({
22
72
  collection: collection.slug,
23
73
  depth: 0,
24
74
  draft: true,
25
75
  limit: 1,
76
+ overrideAccess: false,
26
77
  pagination: false,
27
- where: {
28
- or: [
29
- {
30
- filename: {
31
- equals: filename
32
- }
33
- },
34
- ...imageSizes.map((imageSize)=>({
35
- [`sizes.${imageSize.name}.filename`]: {
36
- equals: filename
37
- }
38
- }))
39
- ]
40
- }
78
+ req,
79
+ select: {
80
+ _objectKey: true,
81
+ filename: true,
82
+ original: true,
83
+ prefix: true,
84
+ variants: true
85
+ },
86
+ showHiddenFields: true,
87
+ where
41
88
  });
42
- const prefix = files?.docs?.[0]?.prefix;
43
- return prefix ? sanitizePrefix(prefix) : '';
89
+ const found = files?.docs?.[0];
90
+ return found ? getRepresentationPrefix({
91
+ doc: found,
92
+ filename,
93
+ isOriginal
94
+ }) : '';
44
95
  }
96
+ /** The main file wins a shared filename unless the original is asked for. */ const getRepresentationPrefix = ({ doc, filename, isOriginal })=>{
97
+ const representation = isOriginalFileRequest({
98
+ doc,
99
+ filename,
100
+ isOriginal
101
+ }) ? doc.original : Object.values(doc.variants ?? {}).find((variant)=>variant?.filename === filename) ?? doc;
102
+ return buildPrefixWithObjectKey({
103
+ objectKey: representation._objectKey ?? (!doc.original?.filename ? doc._objectKey : undefined),
104
+ prefix: representation.prefix ?? (!doc.original?.filename ? doc.prefix : undefined)
105
+ });
106
+ };
45
107
 
46
108
  //# sourceMappingURL=getFilePrefix.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/getFilePrefix.ts"],"sourcesContent":["import type { CollectionConfig, PayloadRequest, UploadConfig } from 'payload'\n\nimport { sanitizePrefix } from './sanitizePrefix.js'\n\n/**\n * Resolves the file prefix from the highest-priority available source and\n * always returns a sanitized value.\n *\n * Resolution order:\n * 1. `prefixQueryParam`\n * 2. `clientUploadContext.prefix`\n * 3. Stored document `prefix` from the database\n *\n * Resolved values are passed through `sanitizePrefix`.\n */\nexport async function getFilePrefix({\n clientUploadContext,\n collection,\n filename,\n prefixQueryParam,\n req,\n}: {\n clientUploadContext?: unknown\n collection: CollectionConfig\n filename: string\n prefixQueryParam?: string\n req: PayloadRequest\n}): Promise<string> {\n if (typeof prefixQueryParam === 'string') {\n return sanitizePrefix(prefixQueryParam)\n }\n\n // Prioritize from clientUploadContext if there is:\n if (\n clientUploadContext &&\n typeof clientUploadContext === 'object' &&\n 'prefix' in clientUploadContext &&\n typeof clientUploadContext.prefix === 'string'\n ) {\n return sanitizePrefix(clientUploadContext.prefix)\n }\n\n const imageSizes = (collection?.upload as UploadConfig)?.imageSizes || []\n\n const files = await req.payload.find({\n collection: collection.slug,\n depth: 0,\n draft: true,\n limit: 1,\n pagination: false,\n where: {\n or: [\n {\n filename: { equals: filename },\n },\n ...imageSizes.map((imageSize) => ({\n [`sizes.${imageSize.name}.filename`]: { equals: filename },\n })),\n ],\n },\n })\n const prefix = files?.docs?.[0]?.prefix\n return prefix ? sanitizePrefix(prefix as string) : ''\n}\n"],"names":["sanitizePrefix","getFilePrefix","clientUploadContext","collection","filename","prefixQueryParam","req","prefix","imageSizes","upload","files","payload","find","slug","depth","draft","limit","pagination","where","or","equals","map","imageSize","name","docs"],"mappings":"AAEA,SAASA,cAAc,QAAQ,sBAAqB;AAEpD;;;;;;;;;;CAUC,GACD,OAAO,eAAeC,cAAc,EAClCC,mBAAmB,EACnBC,UAAU,EACVC,QAAQ,EACRC,gBAAgB,EAChBC,GAAG,EAOJ;IACC,IAAI,OAAOD,qBAAqB,UAAU;QACxC,OAAOL,eAAeK;IACxB;IAEA,mDAAmD;IACnD,IACEH,uBACA,OAAOA,wBAAwB,YAC/B,YAAYA,uBACZ,OAAOA,oBAAoBK,MAAM,KAAK,UACtC;QACA,OAAOP,eAAeE,oBAAoBK,MAAM;IAClD;IAEA,MAAMC,aAAa,AAACL,YAAYM,QAAyBD,cAAc,EAAE;IAEzE,MAAME,QAAQ,MAAMJ,IAAIK,OAAO,CAACC,IAAI,CAAC;QACnCT,YAAYA,WAAWU,IAAI;QAC3BC,OAAO;QACPC,OAAO;QACPC,OAAO;QACPC,YAAY;QACZC,OAAO;YACLC,IAAI;gBACF;oBACEf,UAAU;wBAAEgB,QAAQhB;oBAAS;gBAC/B;mBACGI,WAAWa,GAAG,CAAC,CAACC,YAAe,CAAA;wBAChC,CAAC,CAAC,MAAM,EAAEA,UAAUC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE;4BAAEH,QAAQhB;wBAAS;oBAC3D,CAAA;aACD;QACH;IACF;IACA,MAAMG,SAASG,OAAOc,MAAM,CAAC,EAAE,EAAEjB;IACjC,OAAOA,SAASP,eAAeO,UAAoB;AACrD"}
1
+ {"version":3,"sources":["../../src/utilities/getFilePrefix.ts"],"sourcesContent":["import type {\n CollectionConfig,\n FileData,\n PayloadRequest,\n SanitizedUploadConfig,\n TypeWithID,\n} from 'payload'\n\nimport { isOriginalFileRequest } from 'payload/internal'\n\nimport { buildPrefixWithObjectKey } from './buildPrefixWithObjectKey.js'\nimport { buildUploadStoragePathData } from './buildStoragePathData.js'\nimport { sanitizePrefix } from './sanitizePrefix.js'\n\n/**\n * Resolves the folder a stored object lives under (semantic `prefix` plus `_objectKey`).\n *\n * An already-authorized `doc` wins outright. Otherwise an `uploadReference` is used (its\n * `prefix` re-contained with {@link buildUploadStoragePathData}, since it hasn't been verified against\n * a signed receipt by every adapter). Otherwise the document is resolved with\n * `overrideAccess: false`, any query-param prefix only narrows that lookup, and `_objectKey`\n * is read from the resolved document via `showHiddenFields`.\n */\nexport async function getFilePrefix({\n collection,\n collectionPrefix,\n doc,\n filename,\n isOriginal,\n prefixQueryParam,\n req,\n uploadReference,\n useCompositePrefixes = false,\n}: {\n collection: CollectionConfig\n collectionPrefix?: string\n doc?: Partial<FileData> & TypeWithID\n filename: string\n /** Serves the original when it shares the main file's name. */\n isOriginal?: boolean\n /**\n * Only narrows the access-controlled fallback lookup below; never trusted or\n * returned directly. Prefer passing the access-checked document as `doc` instead.\n */\n prefixQueryParam?: string\n req: PayloadRequest\n uploadReference?: unknown\n useCompositePrefixes?: boolean\n}): Promise<string> {\n // The serve path already loaded and authorized this document — trust it over any\n // client-supplied upload reference or query prefix.\n if (doc) {\n return getRepresentationPrefix({ doc, filename, isOriginal })\n }\n\n // Upload instructions call handlers without a document yet. Re-contain the claimed\n // prefix — it hasn't been verified against a signed receipt by every adapter.\n if (\n uploadReference &&\n typeof uploadReference === 'object' &&\n 'prefix' in uploadReference &&\n typeof uploadReference.prefix === 'string'\n ) {\n const referenceObjectKey =\n '_objectKey' in uploadReference\n ? (uploadReference as { _objectKey?: string })._objectKey\n : undefined\n const containedPrefix = buildUploadStoragePathData({\n collectionPrefix,\n docPrefix: uploadReference.prefix,\n filename,\n useCompositePrefixes,\n }).sanitizedDocPrefix\n return buildPrefixWithObjectKey({ objectKey: referenceObjectKey, prefix: containedPrefix })\n }\n\n // Reads without a query prefix or read-access constraints skip the endpoint's document lookup.\n const variants = (collection?.upload as SanitizedUploadConfig)?.variants || []\n\n const filenameClause = {\n or: [\n {\n filename: { equals: filename },\n },\n { 'original.filename': { equals: filename } },\n ...variants.map((imageSize) => ({\n [`variants.${imageSize.name}.filename`]: { equals: filename },\n })),\n ],\n }\n\n // Only filter by prefix when the collection persists a `prefix` field (querying a missing field throws).\n const hasPrefixField = collection.fields?.some(\n (field) => 'name' in field && field.name === 'prefix',\n )\n const where =\n typeof prefixQueryParam === 'string' && hasPrefixField\n ? { and: [filenameClause, { prefix: { equals: sanitizePrefix(prefixQueryParam) } }] }\n : filenameClause\n\n const files = await req.payload.find({\n collection: collection.slug,\n depth: 0,\n draft: true,\n limit: 1,\n overrideAccess: false,\n pagination: false,\n req,\n select: {\n _objectKey: true,\n filename: true,\n original: true,\n prefix: true,\n variants: true,\n },\n showHiddenFields: true,\n where,\n })\n\n const found = files?.docs?.[0] as\n | ({ _objectKey?: string; prefix?: string } & Partial<FileData>)\n | undefined\n return found ? getRepresentationPrefix({ doc: found, filename, isOriginal }) : ''\n}\n\n/** The main file wins a shared filename unless the original is asked for. */\nconst getRepresentationPrefix = ({\n doc,\n filename,\n isOriginal,\n}: {\n doc: Partial<FileData>\n filename: string\n isOriginal?: boolean\n}): string => {\n const representation = isOriginalFileRequest({ doc, filename, isOriginal })\n ? doc.original!\n : (Object.values(doc.variants ?? {}).find((variant) => variant?.filename === filename) ?? doc)\n return buildPrefixWithObjectKey({\n objectKey: representation._objectKey ?? (!doc.original?.filename ? doc._objectKey : undefined),\n prefix: representation.prefix ?? (!doc.original?.filename ? doc.prefix : undefined),\n })\n}\n"],"names":["isOriginalFileRequest","buildPrefixWithObjectKey","buildUploadStoragePathData","sanitizePrefix","getFilePrefix","collection","collectionPrefix","doc","filename","isOriginal","prefixQueryParam","req","uploadReference","useCompositePrefixes","getRepresentationPrefix","prefix","referenceObjectKey","_objectKey","undefined","containedPrefix","docPrefix","sanitizedDocPrefix","objectKey","variants","upload","filenameClause","or","equals","map","imageSize","name","hasPrefixField","fields","some","field","where","and","files","payload","find","slug","depth","draft","limit","overrideAccess","pagination","select","original","showHiddenFields","found","docs","representation","Object","values","variant"],"mappings":"AAQA,SAASA,qBAAqB,QAAQ,mBAAkB;AAExD,SAASC,wBAAwB,QAAQ,gCAA+B;AACxE,SAASC,0BAA0B,QAAQ,4BAA2B;AACtE,SAASC,cAAc,QAAQ,sBAAqB;AAEpD;;;;;;;;CAQC,GACD,OAAO,eAAeC,cAAc,EAClCC,UAAU,EACVC,gBAAgB,EAChBC,GAAG,EACHC,QAAQ,EACRC,UAAU,EACVC,gBAAgB,EAChBC,GAAG,EACHC,eAAe,EACfC,uBAAuB,KAAK,EAgB7B;IACC,iFAAiF;IACjF,oDAAoD;IACpD,IAAIN,KAAK;QACP,OAAOO,wBAAwB;YAAEP;YAAKC;YAAUC;QAAW;IAC7D;IAEA,mFAAmF;IACnF,8EAA8E;IAC9E,IACEG,mBACA,OAAOA,oBAAoB,YAC3B,YAAYA,mBACZ,OAAOA,gBAAgBG,MAAM,KAAK,UAClC;QACA,MAAMC,qBACJ,gBAAgBJ,kBACZ,AAACA,gBAA4CK,UAAU,GACvDC;QACN,MAAMC,kBAAkBjB,2BAA2B;YACjDI;YACAc,WAAWR,gBAAgBG,MAAM;YACjCP;YACAK;QACF,GAAGQ,kBAAkB;QACrB,OAAOpB,yBAAyB;YAAEqB,WAAWN;YAAoBD,QAAQI;QAAgB;IAC3F;IAEA,+FAA+F;IAC/F,MAAMI,WAAW,AAAClB,YAAYmB,QAAkCD,YAAY,EAAE;IAE9E,MAAME,iBAAiB;QACrBC,IAAI;YACF;gBACElB,UAAU;oBAAEmB,QAAQnB;gBAAS;YAC/B;YACA;gBAAE,qBAAqB;oBAAEmB,QAAQnB;gBAAS;YAAE;eACzCe,SAASK,GAAG,CAAC,CAACC,YAAe,CAAA;oBAC9B,CAAC,CAAC,SAAS,EAAEA,UAAUC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE;wBAAEH,QAAQnB;oBAAS;gBAC9D,CAAA;SACD;IACH;IAEA,yGAAyG;IACzG,MAAMuB,iBAAiB1B,WAAW2B,MAAM,EAAEC,KACxC,CAACC,QAAU,UAAUA,SAASA,MAAMJ,IAAI,KAAK;IAE/C,MAAMK,QACJ,OAAOzB,qBAAqB,YAAYqB,iBACpC;QAAEK,KAAK;YAACX;YAAgB;gBAAEV,QAAQ;oBAAEY,QAAQxB,eAAeO;gBAAkB;YAAE;SAAE;IAAC,IAClFe;IAEN,MAAMY,QAAQ,MAAM1B,IAAI2B,OAAO,CAACC,IAAI,CAAC;QACnClC,YAAYA,WAAWmC,IAAI;QAC3BC,OAAO;QACPC,OAAO;QACPC,OAAO;QACPC,gBAAgB;QAChBC,YAAY;QACZlC;QACAmC,QAAQ;YACN7B,YAAY;YACZT,UAAU;YACVuC,UAAU;YACVhC,QAAQ;YACRQ,UAAU;QACZ;QACAyB,kBAAkB;QAClBb;IACF;IAEA,MAAMc,QAAQZ,OAAOa,MAAM,CAAC,EAAE;IAG9B,OAAOD,QAAQnC,wBAAwB;QAAEP,KAAK0C;QAAOzC;QAAUC;IAAW,KAAK;AACjF;AAEA,2EAA2E,GAC3E,MAAMK,0BAA0B,CAAC,EAC/BP,GAAG,EACHC,QAAQ,EACRC,UAAU,EAKX;IACC,MAAM0C,iBAAiBnD,sBAAsB;QAAEO;QAAKC;QAAUC;IAAW,KACrEF,IAAIwC,QAAQ,GACXK,OAAOC,MAAM,CAAC9C,IAAIgB,QAAQ,IAAI,CAAC,GAAGgB,IAAI,CAAC,CAACe,UAAYA,SAAS9C,aAAaA,aAAaD;IAC5F,OAAON,yBAAyB;QAC9BqB,WAAW6B,eAAelC,UAAU,IAAK,CAAA,CAACV,IAAIwC,QAAQ,EAAEvC,WAAWD,IAAIU,UAAU,GAAGC,SAAQ;QAC5FH,QAAQoC,eAAepC,MAAM,IAAK,CAAA,CAACR,IAAIwC,QAAQ,EAAEvC,WAAWD,IAAIQ,MAAM,GAAGG,SAAQ;IACnF;AACF"}
@@ -7,17 +7,17 @@ export function getIncomingFiles({ data, req }) {
7
7
  if (file && data.filename && data.mimeType) {
8
8
  const mainFile = {
9
9
  buffer: file.data,
10
- clientUploadContext: file.clientUploadContext,
11
10
  filename: data.filename,
12
11
  filesize: file.size,
13
12
  mimeType: data.mimeType,
14
- tempFilePath: file.tempFilePath
13
+ tempFilePath: file.tempFilePath,
14
+ uploadReference: file.uploadReference
15
15
  };
16
16
  files = [
17
17
  mainFile
18
18
  ];
19
- if (data?.sizes) {
20
- Object.entries(data.sizes).forEach(([key, resizedFileData])=>{
19
+ if (data?.variants) {
20
+ Object.entries(data.variants).forEach(([key, resizedFileData])=>{
21
21
  if (payloadUploadSizes?.[key] && resizedFileData.mimeType) {
22
22
  files = files.concat([
23
23
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/getIncomingFiles.ts"],"sourcesContent":["import type { FileData, PayloadRequest } from 'payload'\n\nimport type { File } from '../types.js'\n\ninterface CloudStorageContext {\n file: PayloadRequest['file']\n uploadSizes: PayloadRequest['payloadUploadSizes']\n}\n\nexport function getIncomingFiles({\n data,\n req,\n}: {\n data: Partial<FileData>\n req: PayloadRequest\n}): File[] {\n // Fall back to context if req.file was cleared\n const ctx = req.context?._payloadCloudStorage as CloudStorageContext | undefined\n const file = req.file ?? ctx?.file\n const payloadUploadSizes = req.payloadUploadSizes ?? ctx?.uploadSizes\n\n let files: File[] = []\n\n if (file && data.filename && data.mimeType) {\n const mainFile: File = {\n buffer: file.data,\n clientUploadContext: file.clientUploadContext,\n filename: data.filename,\n filesize: file.size,\n mimeType: data.mimeType,\n tempFilePath: file.tempFilePath,\n }\n\n files = [mainFile]\n\n if (data?.sizes) {\n Object.entries(data.sizes).forEach(([key, resizedFileData]) => {\n if (payloadUploadSizes?.[key] && resizedFileData.mimeType) {\n files = files.concat([\n {\n buffer: payloadUploadSizes[key],\n filename: `${resizedFileData.filename}`,\n filesize: payloadUploadSizes[key].length,\n mimeType: resizedFileData.mimeType,\n },\n ])\n }\n })\n }\n }\n\n return files\n}\n"],"names":["getIncomingFiles","data","req","ctx","context","_payloadCloudStorage","file","payloadUploadSizes","uploadSizes","files","filename","mimeType","mainFile","buffer","clientUploadContext","filesize","size","tempFilePath","sizes","Object","entries","forEach","key","resizedFileData","concat","length"],"mappings":"AASA,OAAO,SAASA,iBAAiB,EAC/BC,IAAI,EACJC,GAAG,EAIJ;IACC,+CAA+C;IAC/C,MAAMC,MAAMD,IAAIE,OAAO,EAAEC;IACzB,MAAMC,OAAOJ,IAAII,IAAI,IAAIH,KAAKG;IAC9B,MAAMC,qBAAqBL,IAAIK,kBAAkB,IAAIJ,KAAKK;IAE1D,IAAIC,QAAgB,EAAE;IAEtB,IAAIH,QAAQL,KAAKS,QAAQ,IAAIT,KAAKU,QAAQ,EAAE;QAC1C,MAAMC,WAAiB;YACrBC,QAAQP,KAAKL,IAAI;YACjBa,qBAAqBR,KAAKQ,mBAAmB;YAC7CJ,UAAUT,KAAKS,QAAQ;YACvBK,UAAUT,KAAKU,IAAI;YACnBL,UAAUV,KAAKU,QAAQ;YACvBM,cAAcX,KAAKW,YAAY;QACjC;QAEAR,QAAQ;YAACG;SAAS;QAElB,IAAIX,MAAMiB,OAAO;YACfC,OAAOC,OAAO,CAACnB,KAAKiB,KAAK,EAAEG,OAAO,CAAC,CAAC,CAACC,KAAKC,gBAAgB;gBACxD,IAAIhB,oBAAoB,CAACe,IAAI,IAAIC,gBAAgBZ,QAAQ,EAAE;oBACzDF,QAAQA,MAAMe,MAAM,CAAC;wBACnB;4BACEX,QAAQN,kBAAkB,CAACe,IAAI;4BAC/BZ,UAAU,GAAGa,gBAAgBb,QAAQ,EAAE;4BACvCK,UAAUR,kBAAkB,CAACe,IAAI,CAACG,MAAM;4BACxCd,UAAUY,gBAAgBZ,QAAQ;wBACpC;qBACD;gBACH;YACF;QACF;IACF;IAEA,OAAOF;AACT"}
1
+ {"version":3,"sources":["../../src/utilities/getIncomingFiles.ts"],"sourcesContent":["import type { FileData, PayloadRequest } from 'payload'\n\nimport type { File } from '../types.js'\n\ninterface CloudStorageContext {\n file: PayloadRequest['file']\n uploadSizes: PayloadRequest['payloadUploadSizes']\n}\n\nexport function getIncomingFiles({\n data,\n req,\n}: {\n data: Partial<FileData>\n req: PayloadRequest\n}): File[] {\n // Fall back to context if req.file was cleared\n const ctx = req.context?._payloadCloudStorage as CloudStorageContext | undefined\n const file = req.file ?? ctx?.file\n const payloadUploadSizes = req.payloadUploadSizes ?? ctx?.uploadSizes\n\n let files: File[] = []\n\n if (file && data.filename && data.mimeType) {\n const mainFile: File = {\n buffer: file.data,\n filename: data.filename,\n filesize: file.size,\n mimeType: data.mimeType,\n tempFilePath: file.tempFilePath,\n uploadReference: file.uploadReference,\n }\n\n files = [mainFile]\n\n if (data?.variants) {\n Object.entries(data.variants).forEach(([key, resizedFileData]) => {\n if (payloadUploadSizes?.[key] && resizedFileData.mimeType) {\n files = files.concat([\n {\n buffer: payloadUploadSizes[key],\n filename: `${resizedFileData.filename}`,\n filesize: payloadUploadSizes[key].length,\n mimeType: resizedFileData.mimeType,\n },\n ])\n }\n })\n }\n }\n\n return files\n}\n"],"names":["getIncomingFiles","data","req","ctx","context","_payloadCloudStorage","file","payloadUploadSizes","uploadSizes","files","filename","mimeType","mainFile","buffer","filesize","size","tempFilePath","uploadReference","variants","Object","entries","forEach","key","resizedFileData","concat","length"],"mappings":"AASA,OAAO,SAASA,iBAAiB,EAC/BC,IAAI,EACJC,GAAG,EAIJ;IACC,+CAA+C;IAC/C,MAAMC,MAAMD,IAAIE,OAAO,EAAEC;IACzB,MAAMC,OAAOJ,IAAII,IAAI,IAAIH,KAAKG;IAC9B,MAAMC,qBAAqBL,IAAIK,kBAAkB,IAAIJ,KAAKK;IAE1D,IAAIC,QAAgB,EAAE;IAEtB,IAAIH,QAAQL,KAAKS,QAAQ,IAAIT,KAAKU,QAAQ,EAAE;QAC1C,MAAMC,WAAiB;YACrBC,QAAQP,KAAKL,IAAI;YACjBS,UAAUT,KAAKS,QAAQ;YACvBI,UAAUR,KAAKS,IAAI;YACnBJ,UAAUV,KAAKU,QAAQ;YACvBK,cAAcV,KAAKU,YAAY;YAC/BC,iBAAiBX,KAAKW,eAAe;QACvC;QAEAR,QAAQ;YAACG;SAAS;QAElB,IAAIX,MAAMiB,UAAU;YAClBC,OAAOC,OAAO,CAACnB,KAAKiB,QAAQ,EAAEG,OAAO,CAAC,CAAC,CAACC,KAAKC,gBAAgB;gBAC3D,IAAIhB,oBAAoB,CAACe,IAAI,IAAIC,gBAAgBZ,QAAQ,EAAE;oBACzDF,QAAQA,MAAMe,MAAM,CAAC;wBACnB;4BACEX,QAAQN,kBAAkB,CAACe,IAAI;4BAC/BZ,UAAU,GAAGa,gBAAgBb,QAAQ,EAAE;4BACvCI,UAAUP,kBAAkB,CAACe,IAAI,CAACG,MAAM;4BACxCd,UAAUY,gBAAgBZ,QAAQ;wBACpC;qBACD;gBACH;YACF;QACF;IACF;IAEA,OAAOF;AACT"}
@@ -0,0 +1,9 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ /**
3
+ * URL hooks rebuild direct URLs from each representation's stored location, so a positive
4
+ * selection of a URL must also read the fields that location is built from.
5
+ */
6
+ export declare const getStorageLocationSelect: ({ select: existingSelect, }: {
7
+ select: CollectionConfig['select'];
8
+ }) => NonNullable<CollectionConfig['select']>;
9
+ //# sourceMappingURL=getStorageLocationSelect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getStorageLocationSelect.d.ts","sourceRoot":"","sources":["../../src/utilities/getStorageLocationSelect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAc,MAAM,SAAS,CAAA;AAQ3D;;;GAGG;AACH,eAAO,MAAM,wBAAwB,gCAGhC;IACD,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAA;CACnC,KAAG,WAAW,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CASzC,CAAA"}
@@ -0,0 +1,60 @@
1
+ import { getSelectMode } from 'payload/shared';
2
+ const locationFieldNames = [
3
+ 'filename',
4
+ 'prefix',
5
+ '_objectKey'
6
+ ];
7
+ /**
8
+ * URL hooks rebuild direct URLs from each representation's stored location, so a positive
9
+ * selection of a URL must also read the fields that location is built from.
10
+ */ export const getStorageLocationSelect = ({ select: existingSelect })=>(args)=>{
11
+ const select = existingSelect?.(args) ?? args.select;
12
+ if (!select || getSelectMode(select) === 'exclude') {
13
+ return select;
14
+ }
15
+ return withLocationFields({
16
+ select
17
+ });
18
+ };
19
+ const withLocationFields = ({ select })=>{
20
+ const result = addLocationFields({
21
+ representation: {
22
+ ...select
23
+ }
24
+ });
25
+ if (isSelectRecord(result.original)) {
26
+ const original = addLocationFields({
27
+ representation: {
28
+ ...result.original
29
+ }
30
+ });
31
+ result.original = original;
32
+ // The original URL compares the original's location with the main file's location.
33
+ if (original.url === true) {
34
+ result.filename = true;
35
+ result._objectKey = true;
36
+ }
37
+ }
38
+ if (isSelectRecord(result.variants)) {
39
+ result.variants = Object.fromEntries(Object.entries(result.variants).map(([name, variant])=>[
40
+ name,
41
+ isSelectRecord(variant) ? addLocationFields({
42
+ representation: {
43
+ ...variant
44
+ }
45
+ }) : variant
46
+ ]));
47
+ }
48
+ return result;
49
+ };
50
+ const addLocationFields = ({ representation })=>{
51
+ if (representation.url === true) {
52
+ for (const fieldName of locationFieldNames){
53
+ representation[fieldName] = true;
54
+ }
55
+ }
56
+ return representation;
57
+ };
58
+ const isSelectRecord = (value)=>typeof value === 'object' && value !== null && !Array.isArray(value);
59
+
60
+ //# sourceMappingURL=getStorageLocationSelect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/getStorageLocationSelect.ts"],"sourcesContent":["import type { CollectionConfig, SelectType } from 'payload'\n\nimport { getSelectMode } from 'payload/shared'\n\ntype SelectRecord = Record<string, unknown>\n\nconst locationFieldNames = ['filename', 'prefix', '_objectKey'] as const\n\n/**\n * URL hooks rebuild direct URLs from each representation's stored location, so a positive\n * selection of a URL must also read the fields that location is built from.\n */\nexport const getStorageLocationSelect =\n ({\n select: existingSelect,\n }: {\n select: CollectionConfig['select']\n }): NonNullable<CollectionConfig['select']> =>\n (args) => {\n const select = existingSelect?.(args) ?? args.select\n\n if (!select || getSelectMode(select) === 'exclude') {\n return select\n }\n\n return withLocationFields({ select })\n }\n\nconst withLocationFields = ({ select }: { select: SelectType }): SelectType => {\n const result = addLocationFields({ representation: { ...select } })\n\n if (isSelectRecord(result.original)) {\n const original = addLocationFields({ representation: { ...result.original } })\n result.original = original\n\n // The original URL compares the original's location with the main file's location.\n if (original.url === true) {\n result.filename = true\n result._objectKey = true\n }\n }\n\n if (isSelectRecord(result.variants)) {\n result.variants = Object.fromEntries(\n Object.entries(result.variants).map(([name, variant]) => [\n name,\n isSelectRecord(variant) ? addLocationFields({ representation: { ...variant } }) : variant,\n ]),\n )\n }\n\n return result as SelectType\n}\n\nconst addLocationFields = ({ representation }: { representation: SelectRecord }): SelectRecord => {\n if (representation.url === true) {\n for (const fieldName of locationFieldNames) {\n representation[fieldName] = true\n }\n }\n\n return representation\n}\n\nconst isSelectRecord = (value: unknown): value is SelectRecord =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n"],"names":["getSelectMode","locationFieldNames","getStorageLocationSelect","select","existingSelect","args","withLocationFields","result","addLocationFields","representation","isSelectRecord","original","url","filename","_objectKey","variants","Object","fromEntries","entries","map","name","variant","fieldName","value","Array","isArray"],"mappings":"AAEA,SAASA,aAAa,QAAQ,iBAAgB;AAI9C,MAAMC,qBAAqB;IAAC;IAAY;IAAU;CAAa;AAE/D;;;CAGC,GACD,OAAO,MAAMC,2BACX,CAAC,EACCC,QAAQC,cAAc,EAGvB,GACD,CAACC;QACC,MAAMF,SAASC,iBAAiBC,SAASA,KAAKF,MAAM;QAEpD,IAAI,CAACA,UAAUH,cAAcG,YAAY,WAAW;YAClD,OAAOA;QACT;QAEA,OAAOG,mBAAmB;YAAEH;QAAO;IACrC,EAAC;AAEH,MAAMG,qBAAqB,CAAC,EAAEH,MAAM,EAA0B;IAC5D,MAAMI,SAASC,kBAAkB;QAAEC,gBAAgB;YAAE,GAAGN,MAAM;QAAC;IAAE;IAEjE,IAAIO,eAAeH,OAAOI,QAAQ,GAAG;QACnC,MAAMA,WAAWH,kBAAkB;YAAEC,gBAAgB;gBAAE,GAAGF,OAAOI,QAAQ;YAAC;QAAE;QAC5EJ,OAAOI,QAAQ,GAAGA;QAElB,mFAAmF;QACnF,IAAIA,SAASC,GAAG,KAAK,MAAM;YACzBL,OAAOM,QAAQ,GAAG;YAClBN,OAAOO,UAAU,GAAG;QACtB;IACF;IAEA,IAAIJ,eAAeH,OAAOQ,QAAQ,GAAG;QACnCR,OAAOQ,QAAQ,GAAGC,OAAOC,WAAW,CAClCD,OAAOE,OAAO,CAACX,OAAOQ,QAAQ,EAAEI,GAAG,CAAC,CAAC,CAACC,MAAMC,QAAQ,GAAK;gBACvDD;gBACAV,eAAeW,WAAWb,kBAAkB;oBAAEC,gBAAgB;wBAAE,GAAGY,OAAO;oBAAC;gBAAE,KAAKA;aACnF;IAEL;IAEA,OAAOd;AACT;AAEA,MAAMC,oBAAoB,CAAC,EAAEC,cAAc,EAAoC;IAC7E,IAAIA,eAAeG,GAAG,KAAK,MAAM;QAC/B,KAAK,MAAMU,aAAarB,mBAAoB;YAC1CQ,cAAc,CAACa,UAAU,GAAG;QAC9B;IACF;IAEA,OAAOb;AACT;AAEA,MAAMC,iBAAiB,CAACa,QACtB,OAAOA,UAAU,YAAYA,UAAU,QAAQ,CAACC,MAAMC,OAAO,CAACF"}
@@ -1,5 +1,8 @@
1
1
  import type { PayloadRequest } from 'payload';
2
+ import type { UploadReference } from '../types.js';
2
3
  type Args = {
4
+ /** Signed into the receipt and returned to the upload handler as `uploadReference.adapterData`. */
5
+ adapterData?: Record<string, unknown>;
3
6
  collectionPrefix?: string;
4
7
  collectionSlug: string;
5
8
  docPrefix?: string;
@@ -8,17 +11,27 @@ type Args = {
8
11
  useCompositePrefixes?: boolean;
9
12
  };
10
13
  /**
11
- * Resolves the storage key for a clientUploads signed-URL request, deduping
14
+ * Resolves the storage key for an upload-instructions request, deduping
12
15
  * the filename via {@link getSafeFileName} so a duplicate upload does not
13
16
  * overwrite an existing blob.
14
17
  *
18
+ * A unique per-upload segment is included in the storage key so each issued
19
+ * key is distinct. The stored `filename` stays clean and the semantic `prefix`
20
+ * stays exactly what the collection configured — the per-upload entropy is
21
+ * persisted separately in `_objectKey`.
22
+ *
15
23
  * The resolved `sanitizedFilename` is returned so the browser-side handler
16
24
  * can update the form via `updateFilename`.
25
+ *
26
+ * The semantic prefix is resolved with {@link buildUploadPrefix} and the final
27
+ * key with {@link buildUploadStoragePathData}, so the receipt only ever signs a key
28
+ * beneath the configured collection prefix.
17
29
  */
18
- export declare function resolveSignedURLKey({ collectionPrefix, collectionSlug, docPrefix, filename, req, useCompositePrefixes, }: Args): Promise<{
19
- fileKey: string;
30
+ export declare function resolveSignedURLKey({ adapterData, collectionPrefix, collectionSlug, docPrefix, filename, req, useCompositePrefixes, }: Args): Promise<{
20
31
  sanitizedDocPrefix: string;
21
32
  sanitizedFilename: string;
33
+ storageFilePath: string;
34
+ uploadReference: UploadReference;
22
35
  }>;
23
36
  export {};
24
37
  //# sourceMappingURL=resolveSignedURLKey.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolveSignedURLKey.d.ts","sourceRoot":"","sources":["../../src/utilities/resolveSignedURLKey.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAM7C,KAAK,IAAI,GAAG;IACV,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,cAAc,CAAA;IACnB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B,CAAA;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CAAC,EACxC,gBAAqB,EACrB,cAAc,EACd,SAAS,EACT,QAAQ,EACR,GAAG,EACH,oBAA4B,GAC7B,EAAE,IAAI;;;;GAgBN"}
1
+ {"version":3,"file":"resolveSignedURLKey.d.ts","sourceRoot":"","sources":["../../src/utilities/resolveSignedURLKey.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAM7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAIlD,KAAK,IAAI,GAAG;IACV,mGAAmG;IACnG,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACrC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,cAAc,CAAA;IACnB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B,CAAA;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,mBAAmB,CAAC,EACxC,WAAW,EACX,gBAAqB,EACrB,cAAc,EACd,SAAS,EACT,QAAQ,EACR,GAAG,EACH,oBAA4B,GAC7B,EAAE,IAAI,GAAG,OAAO,CAAC;IAChB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,iBAAiB,EAAE,MAAM,CAAA;IACzB,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,eAAe,CAAA;CACjC,CAAC,CAwCD"}
@@ -1,29 +1,64 @@
1
- import { getSafeFileName } from 'payload/internal';
2
- import { getFileKey } from './getFileKey.js';
1
+ import { randomUUID } from 'node:crypto';
2
+ import { createClientUploadReceipt, getSafeFileName } from 'payload/internal';
3
+ import { getSanitizedUploadFilename } from 'payload/shared';
4
+ import { buildUploadPrefix, buildUploadStoragePathData } from './buildStoragePathData.js';
3
5
  /**
4
- * Resolves the storage key for a clientUploads signed-URL request, deduping
6
+ * Resolves the storage key for an upload-instructions request, deduping
5
7
  * the filename via {@link getSafeFileName} so a duplicate upload does not
6
8
  * overwrite an existing blob.
7
9
  *
10
+ * A unique per-upload segment is included in the storage key so each issued
11
+ * key is distinct. The stored `filename` stays clean and the semantic `prefix`
12
+ * stays exactly what the collection configured — the per-upload entropy is
13
+ * persisted separately in `_objectKey`.
14
+ *
8
15
  * The resolved `sanitizedFilename` is returned so the browser-side handler
9
16
  * can update the form via `updateFilename`.
10
- */ export async function resolveSignedURLKey({ collectionPrefix = '', collectionSlug, docPrefix, filename, req, useCompositePrefixes = false }) {
17
+ *
18
+ * The semantic prefix is resolved with {@link buildUploadPrefix} and the final
19
+ * key with {@link buildUploadStoragePathData}, so the receipt only ever signs a key
20
+ * beneath the configured collection prefix.
21
+ */ export async function resolveSignedURLKey({ adapterData, collectionPrefix = '', collectionSlug, docPrefix, filename, req, useCompositePrefixes = false }) {
11
22
  const sanitizedFilename = await getSafeFileName({
12
23
  collectionSlug,
13
- desiredFilename: filename,
14
- prefix: docPrefix,
24
+ desiredFilename: getSanitizedUploadFilename(filename),
15
25
  req
16
26
  });
17
- const { fileKey, sanitizedDocPrefix } = getFileKey({
27
+ const rawBaseDocPrefix = useCompositePrefixes ? docPrefix : docPrefix || collectionPrefix;
28
+ const { sanitizedDocPrefix } = buildUploadPrefix({
29
+ collectionPrefix,
30
+ docPrefix: rawBaseDocPrefix,
31
+ useCompositePrefixes
32
+ });
33
+ const _objectKey = randomUUID();
34
+ // Per-upload segment lives in the key; it is persisted in `_objectKey`, keeping `prefix` semantic.
35
+ const keyedDocPrefix = sanitizedDocPrefix ? `${sanitizedDocPrefix}/${_objectKey}` : _objectKey;
36
+ const { storageFilePath } = buildUploadStoragePathData({
18
37
  collectionPrefix,
19
- docPrefix,
38
+ docPrefix: keyedDocPrefix,
20
39
  filename: sanitizedFilename,
21
40
  useCompositePrefixes
22
41
  });
42
+ const uploadReference = {
43
+ _objectKey,
44
+ prefix: sanitizedDocPrefix,
45
+ signedReceipt: createClientUploadReceipt({
46
+ _objectKey,
47
+ ...adapterData !== undefined && {
48
+ adapterData
49
+ },
50
+ collectionSlug,
51
+ filename: sanitizedFilename,
52
+ filePrefix: sanitizedDocPrefix,
53
+ req,
54
+ storageFilePath
55
+ })
56
+ };
23
57
  return {
24
- fileKey,
25
58
  sanitizedDocPrefix,
26
- sanitizedFilename
59
+ sanitizedFilename,
60
+ storageFilePath,
61
+ uploadReference
27
62
  };
28
63
  }
29
64
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/resolveSignedURLKey.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { getSafeFileName } from 'payload/internal'\n\nimport { getFileKey } from './getFileKey.js'\n\ntype Args = {\n collectionPrefix?: string\n collectionSlug: string\n docPrefix?: string\n filename: string\n req: PayloadRequest\n useCompositePrefixes?: boolean\n}\n\n/**\n * Resolves the storage key for a clientUploads signed-URL request, deduping\n * the filename via {@link getSafeFileName} so a duplicate upload does not\n * overwrite an existing blob.\n *\n * The resolved `sanitizedFilename` is returned so the browser-side handler\n * can update the form via `updateFilename`.\n */\nexport async function resolveSignedURLKey({\n collectionPrefix = '',\n collectionSlug,\n docPrefix,\n filename,\n req,\n useCompositePrefixes = false,\n}: Args) {\n const sanitizedFilename = await getSafeFileName({\n collectionSlug,\n desiredFilename: filename,\n prefix: docPrefix,\n req,\n })\n\n const { fileKey, sanitizedDocPrefix } = getFileKey({\n collectionPrefix,\n docPrefix,\n filename: sanitizedFilename,\n useCompositePrefixes,\n })\n\n return { fileKey, sanitizedDocPrefix, sanitizedFilename }\n}\n"],"names":["getSafeFileName","getFileKey","resolveSignedURLKey","collectionPrefix","collectionSlug","docPrefix","filename","req","useCompositePrefixes","sanitizedFilename","desiredFilename","prefix","fileKey","sanitizedDocPrefix"],"mappings":"AAEA,SAASA,eAAe,QAAQ,mBAAkB;AAElD,SAASC,UAAU,QAAQ,kBAAiB;AAW5C;;;;;;;CAOC,GACD,OAAO,eAAeC,oBAAoB,EACxCC,mBAAmB,EAAE,EACrBC,cAAc,EACdC,SAAS,EACTC,QAAQ,EACRC,GAAG,EACHC,uBAAuB,KAAK,EACvB;IACL,MAAMC,oBAAoB,MAAMT,gBAAgB;QAC9CI;QACAM,iBAAiBJ;QACjBK,QAAQN;QACRE;IACF;IAEA,MAAM,EAAEK,OAAO,EAAEC,kBAAkB,EAAE,GAAGZ,WAAW;QACjDE;QACAE;QACAC,UAAUG;QACVD;IACF;IAEA,OAAO;QAAEI;QAASC;QAAoBJ;IAAkB;AAC1D"}
1
+ {"version":3,"sources":["../../src/utilities/resolveSignedURLKey.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { randomUUID } from 'node:crypto'\nimport { createClientUploadReceipt, getSafeFileName } from 'payload/internal'\nimport { getSanitizedUploadFilename } from 'payload/shared'\n\nimport type { UploadReference } from '../types.js'\n\nimport { buildUploadPrefix, buildUploadStoragePathData } from './buildStoragePathData.js'\n\ntype Args = {\n /** Signed into the receipt and returned to the upload handler as `uploadReference.adapterData`. */\n adapterData?: Record<string, unknown>\n collectionPrefix?: string\n collectionSlug: string\n docPrefix?: string\n filename: string\n req: PayloadRequest\n useCompositePrefixes?: boolean\n}\n\n/**\n * Resolves the storage key for an upload-instructions request, deduping\n * the filename via {@link getSafeFileName} so a duplicate upload does not\n * overwrite an existing blob.\n *\n * A unique per-upload segment is included in the storage key so each issued\n * key is distinct. The stored `filename` stays clean and the semantic `prefix`\n * stays exactly what the collection configured — the per-upload entropy is\n * persisted separately in `_objectKey`.\n *\n * The resolved `sanitizedFilename` is returned so the browser-side handler\n * can update the form via `updateFilename`.\n *\n * The semantic prefix is resolved with {@link buildUploadPrefix} and the final\n * key with {@link buildUploadStoragePathData}, so the receipt only ever signs a key\n * beneath the configured collection prefix.\n */\nexport async function resolveSignedURLKey({\n adapterData,\n collectionPrefix = '',\n collectionSlug,\n docPrefix,\n filename,\n req,\n useCompositePrefixes = false,\n}: Args): Promise<{\n sanitizedDocPrefix: string\n sanitizedFilename: string\n storageFilePath: string\n uploadReference: UploadReference\n}> {\n const sanitizedFilename = await getSafeFileName({\n collectionSlug,\n desiredFilename: getSanitizedUploadFilename(filename),\n req,\n })\n\n const rawBaseDocPrefix = useCompositePrefixes ? docPrefix : docPrefix || collectionPrefix\n const { sanitizedDocPrefix } = buildUploadPrefix({\n collectionPrefix,\n docPrefix: rawBaseDocPrefix,\n useCompositePrefixes,\n })\n\n const _objectKey = randomUUID()\n // Per-upload segment lives in the key; it is persisted in `_objectKey`, keeping `prefix` semantic.\n const keyedDocPrefix = sanitizedDocPrefix ? `${sanitizedDocPrefix}/${_objectKey}` : _objectKey\n\n const { storageFilePath } = buildUploadStoragePathData({\n collectionPrefix,\n docPrefix: keyedDocPrefix,\n filename: sanitizedFilename,\n useCompositePrefixes,\n })\n\n const uploadReference = {\n _objectKey,\n prefix: sanitizedDocPrefix,\n signedReceipt: createClientUploadReceipt({\n _objectKey,\n ...(adapterData !== undefined && { adapterData }),\n collectionSlug,\n filename: sanitizedFilename,\n filePrefix: sanitizedDocPrefix,\n req,\n storageFilePath,\n }),\n }\n\n return { sanitizedDocPrefix, sanitizedFilename, storageFilePath, uploadReference }\n}\n"],"names":["randomUUID","createClientUploadReceipt","getSafeFileName","getSanitizedUploadFilename","buildUploadPrefix","buildUploadStoragePathData","resolveSignedURLKey","adapterData","collectionPrefix","collectionSlug","docPrefix","filename","req","useCompositePrefixes","sanitizedFilename","desiredFilename","rawBaseDocPrefix","sanitizedDocPrefix","_objectKey","keyedDocPrefix","storageFilePath","uploadReference","prefix","signedReceipt","undefined","filePrefix"],"mappings":"AAEA,SAASA,UAAU,QAAQ,cAAa;AACxC,SAASC,yBAAyB,EAAEC,eAAe,QAAQ,mBAAkB;AAC7E,SAASC,0BAA0B,QAAQ,iBAAgB;AAI3D,SAASC,iBAAiB,EAAEC,0BAA0B,QAAQ,4BAA2B;AAazF;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,eAAeC,oBAAoB,EACxCC,WAAW,EACXC,mBAAmB,EAAE,EACrBC,cAAc,EACdC,SAAS,EACTC,QAAQ,EACRC,GAAG,EACHC,uBAAuB,KAAK,EACvB;IAML,MAAMC,oBAAoB,MAAMZ,gBAAgB;QAC9CO;QACAM,iBAAiBZ,2BAA2BQ;QAC5CC;IACF;IAEA,MAAMI,mBAAmBH,uBAAuBH,YAAYA,aAAaF;IACzE,MAAM,EAAES,kBAAkB,EAAE,GAAGb,kBAAkB;QAC/CI;QACAE,WAAWM;QACXH;IACF;IAEA,MAAMK,aAAalB;IACnB,mGAAmG;IACnG,MAAMmB,iBAAiBF,qBAAqB,GAAGA,mBAAmB,CAAC,EAAEC,YAAY,GAAGA;IAEpF,MAAM,EAAEE,eAAe,EAAE,GAAGf,2BAA2B;QACrDG;QACAE,WAAWS;QACXR,UAAUG;QACVD;IACF;IAEA,MAAMQ,kBAAkB;QACtBH;QACAI,QAAQL;QACRM,eAAetB,0BAA0B;YACvCiB;YACA,GAAIX,gBAAgBiB,aAAa;gBAAEjB;YAAY,CAAC;YAChDE;YACAE,UAAUG;YACVW,YAAYR;YACZL;YACAQ;QACF;IACF;IAEA,OAAO;QAAEH;QAAoBH;QAAmBM;QAAiBC;IAAgB;AACnF"}
@@ -1,10 +1,2 @@
1
- /**
2
- * Normalizes a storage prefix for use in object keys and URLs.
3
- *
4
- * Decodes URI components once (so query-style `%2F` becomes `/`), rejects
5
- * values that still contain percent-encodings after decoding (e.g. `%252f`),
6
- * then normalizes slashes, drops `.` / `..` segments, strips leading slashes,
7
- * and removes control characters.
8
- */
9
- export declare function sanitizePrefix(prefix: string): string;
1
+ export { sanitizeUploadPrefix as sanitizePrefix } from 'payload/shared';
10
2
  //# sourceMappingURL=sanitizePrefix.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sanitizePrefix.d.ts","sourceRoot":"","sources":["../../src/utilities/sanitizePrefix.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAwBrD"}
1
+ {"version":3,"file":"sanitizePrefix.d.ts","sourceRoot":"","sources":["../../src/utilities/sanitizePrefix.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAA"}
@@ -1,23 +1,3 @@
1
- /**
2
- * Normalizes a storage prefix for use in object keys and URLs.
3
- *
4
- * Decodes URI components once (so query-style `%2F` becomes `/`), rejects
5
- * values that still contain percent-encodings after decoding (e.g. `%252f`),
6
- * then normalizes slashes, drops `.` / `..` segments, strips leading slashes,
7
- * and removes control characters.
8
- */ export function sanitizePrefix(prefix) {
9
- let decodedPrefix;
10
- try {
11
- decodedPrefix = decodeURIComponent(prefix);
12
- } catch {
13
- return '';
14
- }
15
- // Reject multi-encoded values (e.g. `%252f`) by allowing only a single decode pass.
16
- if (/%[0-9a-f]{2}/i.test(decodedPrefix)) {
17
- return '';
18
- }
19
- return decodedPrefix.replace(/\\/g, '/').split('/').filter((segment)=>segment !== '..' && segment !== '.').join('/').replace(/^\/+/, '')// eslint-disable-next-line no-control-regex
20
- .replace(/[\x00-\x1f\x80-\x9f]/g, '');
21
- }
1
+ export { sanitizeUploadPrefix as sanitizePrefix } from 'payload/shared';
22
2
 
23
3
  //# sourceMappingURL=sanitizePrefix.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/sanitizePrefix.ts"],"sourcesContent":["/**\n * Normalizes a storage prefix for use in object keys and URLs.\n *\n * Decodes URI components once (so query-style `%2F` becomes `/`), rejects\n * values that still contain percent-encodings after decoding (e.g. `%252f`),\n * then normalizes slashes, drops `.` / `..` segments, strips leading slashes,\n * and removes control characters.\n */\nexport function sanitizePrefix(prefix: string): string {\n let decodedPrefix: string\n\n try {\n decodedPrefix = decodeURIComponent(prefix)\n } catch {\n return ''\n }\n\n // Reject multi-encoded values (e.g. `%252f`) by allowing only a single decode pass.\n if (/%[0-9a-f]{2}/i.test(decodedPrefix)) {\n return ''\n }\n\n return (\n decodedPrefix\n .replace(/\\\\/g, '/')\n .split('/')\n .filter((segment) => segment !== '..' && segment !== '.')\n .join('/')\n .replace(/^\\/+/, '')\n // eslint-disable-next-line no-control-regex\n .replace(/[\\x00-\\x1f\\x80-\\x9f]/g, '')\n )\n}\n"],"names":["sanitizePrefix","prefix","decodedPrefix","decodeURIComponent","test","replace","split","filter","segment","join"],"mappings":"AAAA;;;;;;;CAOC,GACD,OAAO,SAASA,eAAeC,MAAc;IAC3C,IAAIC;IAEJ,IAAI;QACFA,gBAAgBC,mBAAmBF;IACrC,EAAE,OAAM;QACN,OAAO;IACT;IAEA,oFAAoF;IACpF,IAAI,gBAAgBG,IAAI,CAACF,gBAAgB;QACvC,OAAO;IACT;IAEA,OACEA,cACGG,OAAO,CAAC,OAAO,KACfC,KAAK,CAAC,KACNC,MAAM,CAAC,CAACC,UAAYA,YAAY,QAAQA,YAAY,KACpDC,IAAI,CAAC,KACLJ,OAAO,CAAC,QAAQ,GACjB,4CAA4C;KAC3CA,OAAO,CAAC,yBAAyB;AAExC"}
1
+ {"version":3,"sources":["../../src/utilities/sanitizePrefix.ts"],"sourcesContent":["export { sanitizeUploadPrefix as sanitizePrefix } from 'payload/shared'\n"],"names":["sanitizeUploadPrefix","sanitizePrefix"],"mappings":"AAAA,SAASA,wBAAwBC,cAAc,QAAQ,iBAAgB"}
package/package.json CHANGED
@@ -1,13 +1,8 @@
1
1
  {
2
2
  "name": "@payloadcms/plugin-cloud-storage",
3
- "version": "4.0.0-internal.1f9ae9a",
3
+ "version": "4.0.0-internal.2071af3",
4
4
  "description": "The official cloud storage plugin for Payload CMS",
5
5
  "homepage": "https://payloadcms.com",
6
- "repository": {
7
- "type": "git",
8
- "url": "https://github.com/payloadcms/payload.git",
9
- "directory": "packages/plugin-cloud-storage"
10
- },
11
6
  "license": "MIT",
12
7
  "author": "Payload <dev@payloadcms.com> (https://payloadcms.com)",
13
8
  "maintainers": [
@@ -17,8 +12,20 @@
17
12
  "url": "https://payloadcms.com"
18
13
  }
19
14
  ],
20
- "sideEffects": false,
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/payloadcms/payload.git",
18
+ "directory": "packages/plugin-cloud-storage"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "*.js",
23
+ "*.d.ts"
24
+ ],
21
25
  "type": "module",
26
+ "sideEffects": false,
27
+ "main": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
22
29
  "exports": {
23
30
  ".": {
24
31
  "import": "./dist/index.js",
@@ -41,31 +48,24 @@
41
48
  "default": "./dist/exports/client.js"
42
49
  }
43
50
  },
44
- "main": "./dist/index.js",
45
- "types": "./dist/index.d.ts",
46
- "files": [
47
- "dist",
48
- "*.js",
49
- "*.d.ts"
50
- ],
51
+ "publishConfig": {
52
+ "registry": "https://registry.npmjs.org/"
53
+ },
51
54
  "dependencies": {
55
+ "@payloadcms/ui": "4.0.0-internal.2071af3",
52
56
  "find-node-modules": "^2.1.3",
53
- "range-parser": "^1.2.1",
54
- "@payloadcms/ui": "4.0.0-internal.1f9ae9a"
57
+ "range-parser": "^1.2.1"
55
58
  },
56
59
  "devDependencies": {
57
60
  "@types/find-node-modules": "^2.1.2",
58
- "@types/react": "19.2.14",
59
- "@types/react-dom": "19.2.3",
60
- "payload": "4.0.0-internal.1f9ae9a"
61
+ "@types/react": "19.3.0",
62
+ "@types/react-dom": "19.3.0",
63
+ "payload": "4.0.0-internal.2071af3"
61
64
  },
62
65
  "peerDependencies": {
66
+ "payload": "4.0.0-internal.2071af3",
63
67
  "react": "^19.0.1 || ^19.1.2 || ^19.2.1",
64
- "react-dom": "^19.0.1 || ^19.1.2 || ^19.2.1",
65
- "payload": "4.0.0-internal.1f9ae9a"
66
- },
67
- "publishConfig": {
68
- "registry": "https://registry.npmjs.org/"
68
+ "react-dom": "^19.0.1 || ^19.1.2 || ^19.2.1"
69
69
  },
70
70
  "scripts": {
71
71
  "build": "pnpm build:types && pnpm build:swc ",
@@ -73,8 +73,6 @@
73
73
  "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
74
74
  "build:types": "tsc --emitDeclarationOnly --outDir dist",
75
75
  "clean": "rimraf -g {dist,*.tsbuildinfo}",
76
- "lint": "eslint .",
77
- "lint:fix": "eslint . --fix",
78
76
  "test": "echo \"No tests available.\""
79
77
  }
80
78
  }
@@ -1,17 +0,0 @@
1
- import type { CollectionConfig, Field } from 'payload';
2
- interface Args {
3
- /**
4
- * When true, always insert the prefix field regardless of whether a prefix is configured.
5
- */
6
- alwaysInsertFields?: boolean;
7
- collection: CollectionConfig;
8
- prefix?: string;
9
- /**
10
- * When true, do not default the `prefix` field to the collection prefix; the
11
- * document field holds only the document-level segment.
12
- */
13
- useCompositePrefixes?: boolean;
14
- }
15
- export declare const getFields: ({ alwaysInsertFields, collection, prefix, useCompositePrefixes, }: Args) => Field[];
16
- export {};
17
- //# sourceMappingURL=getFields.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"getFields.d.ts","sourceRoot":"","sources":["../../../src/admin/fields/getFields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAyB,MAAM,SAAS,CAAA;AAI7E,UAAU,IAAI;IACZ;;OAEG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,UAAU,EAAE,gBAAgB,CAAA;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,eAAO,MAAM,SAAS,GAAI,mEAKvB,IAAI,KAAG,KAAK,EAsHd,CAAA"}