@payloadcms/plugin-cloud-storage 4.0.0-canary.33 → 4.0.0-canary.35

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 (75) hide show
  1. package/README.md +2 -0
  2. package/dist/exports/client.d.ts +1 -1
  3. package/dist/exports/client.d.ts.map +1 -1
  4. package/dist/exports/client.js +1 -1
  5. package/dist/exports/client.js.map +1 -1
  6. package/dist/exports/utilities.d.ts +1 -1
  7. package/dist/exports/utilities.d.ts.map +1 -1
  8. package/dist/exports/utilities.js +1 -1
  9. package/dist/exports/utilities.js.map +1 -1
  10. package/dist/fields/getFields.d.ts.map +1 -1
  11. package/dist/fields/getFields.js +25 -1
  12. package/dist/fields/getFields.js.map +1 -1
  13. package/dist/hooks/afterChange.d.ts +3 -1
  14. package/dist/hooks/afterChange.d.ts.map +1 -1
  15. package/dist/hooks/afterChange.js +51 -8
  16. package/dist/hooks/afterChange.js.map +1 -1
  17. package/dist/hooks/afterChange.spec.js +95 -0
  18. package/dist/hooks/afterChange.spec.js.map +1 -0
  19. package/dist/hooks/afterDelete.d.ts +3 -1
  20. package/dist/hooks/afterDelete.d.ts.map +1 -1
  21. package/dist/hooks/afterDelete.js +16 -2
  22. package/dist/hooks/afterDelete.js.map +1 -1
  23. package/dist/hooks/afterRead.d.ts.map +1 -1
  24. package/dist/hooks/afterRead.js +15 -2
  25. package/dist/hooks/afterRead.js.map +1 -1
  26. package/dist/hooks/beforeChange.d.ts.map +1 -1
  27. package/dist/hooks/beforeChange.js +12 -1
  28. package/dist/hooks/beforeChange.js.map +1 -1
  29. package/dist/hooks/normalizeUploadPrefix.d.ts +11 -0
  30. package/dist/hooks/normalizeUploadPrefix.d.ts.map +1 -0
  31. package/dist/hooks/normalizeUploadPrefix.js +42 -0
  32. package/dist/hooks/normalizeUploadPrefix.js.map +1 -0
  33. package/dist/hooks/normalizeUploadPrefix.spec.js +154 -0
  34. package/dist/hooks/normalizeUploadPrefix.spec.js.map +1 -0
  35. package/dist/plugin.d.ts.map +1 -1
  36. package/dist/plugin.js +14 -6
  37. package/dist/plugin.js.map +1 -1
  38. package/dist/plugin.spec.js +166 -0
  39. package/dist/plugin.spec.js.map +1 -0
  40. package/dist/types.d.ts +19 -1
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/types.js.map +1 -1
  43. package/dist/utilities/buildPrefixWithObjectKey.d.ts +8 -0
  44. package/dist/utilities/buildPrefixWithObjectKey.d.ts.map +1 -0
  45. package/dist/utilities/buildPrefixWithObjectKey.js +13 -0
  46. package/dist/utilities/buildPrefixWithObjectKey.js.map +1 -0
  47. package/dist/utilities/buildStoragePathData.d.ts +49 -0
  48. package/dist/utilities/buildStoragePathData.d.ts.map +1 -0
  49. package/dist/utilities/buildStoragePathData.js +67 -0
  50. package/dist/utilities/buildStoragePathData.js.map +1 -0
  51. package/dist/utilities/buildStoragePathData.spec.js +241 -0
  52. package/dist/utilities/buildStoragePathData.spec.js.map +1 -0
  53. package/dist/utilities/getFilePrefix.d.ts +18 -10
  54. package/dist/utilities/getFilePrefix.d.ts.map +1 -1
  55. package/dist/utilities/getFilePrefix.js +69 -29
  56. package/dist/utilities/getFilePrefix.js.map +1 -1
  57. package/dist/utilities/getFilePrefix.spec.js +174 -31
  58. package/dist/utilities/getFilePrefix.spec.js.map +1 -1
  59. package/dist/utilities/resolveSignedURLKey.d.ts +12 -1
  60. package/dist/utilities/resolveSignedURLKey.d.ts.map +1 -1
  61. package/dist/utilities/resolveSignedURLKey.js +42 -8
  62. package/dist/utilities/resolveSignedURLKey.js.map +1 -1
  63. package/dist/utilities/resolveSignedURLKey.spec.js +74 -0
  64. package/dist/utilities/resolveSignedURLKey.spec.js.map +1 -0
  65. package/dist/utilities/sanitizePrefix.d.ts +1 -9
  66. package/dist/utilities/sanitizePrefix.d.ts.map +1 -1
  67. package/dist/utilities/sanitizePrefix.js +1 -21
  68. package/dist/utilities/sanitizePrefix.js.map +1 -1
  69. package/package.json +4 -4
  70. package/dist/utilities/getFileKey.d.ts +0 -22
  71. package/dist/utilities/getFileKey.d.ts.map +0 -1
  72. package/dist/utilities/getFileKey.js +0 -23
  73. package/dist/utilities/getFileKey.js.map +0 -1
  74. package/dist/utilities/getFileKey.spec.js +0 -174
  75. package/dist/utilities/getFileKey.spec.js.map +0 -1
package/README.md CHANGED
@@ -98,6 +98,8 @@ Instead, all uploads will still be reached from the default `/:collectionSlug/fi
98
98
 
99
99
  If this does not apply to you (your upload collection has `read: () => true` or similar) you can disable this functionality by setting `disablePayloadAccessControl` to `true`. When this setting is in place, this plugin will update your file URLs to point directly to your cloud host.
100
100
 
101
+ `disablePayloadAccessControl` does not disable safe outbound fetching. If Payload needs to fetch files from a private or internal storage endpoint, configure the upload collection's [`skipSafeFetch`](https://payloadcms.com/docs/upload/overview#skip-safe-fetch) option with an explicit allowlist for the trusted destination. Set `skipSafeFetch: true` only when every URL accepted by the collection is trusted.
102
+
101
103
  ## Credit
102
104
 
103
105
  This plugin was created with significant help, and code, from [Alex Bechmann](https://github.com/alexbechmann) and [Richard VanBergen](https://github.com/richardvanbergen). Thank you!!
@@ -1,4 +1,4 @@
1
1
  export { createClientUploadHandler } from '../client/createClientUploadHandler.js';
2
- export { getFileKey } from '../utilities/getFileKey.js';
2
+ export { buildStoragePathData, buildUploadStoragePathData, } from '../utilities/buildStoragePathData.js';
3
3
  export { sanitizePrefix } from '../utilities/sanitizePrefix.js';
4
4
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/exports/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAA;AAClF,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/exports/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAA;AAClF,OAAO,EACL,oBAAoB,EACpB,0BAA0B,GAC3B,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA"}
@@ -1,5 +1,5 @@
1
1
  export { createClientUploadHandler } from '../client/createClientUploadHandler.js';
2
- export { getFileKey } from '../utilities/getFileKey.js';
2
+ export { buildStoragePathData, buildUploadStoragePathData } from '../utilities/buildStoragePathData.js';
3
3
  export { sanitizePrefix } from '../utilities/sanitizePrefix.js';
4
4
 
5
5
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["export { createClientUploadHandler } from '../client/createClientUploadHandler.js'\nexport { getFileKey } from '../utilities/getFileKey.js'\nexport { sanitizePrefix } from '../utilities/sanitizePrefix.js'\n"],"names":["createClientUploadHandler","getFileKey","sanitizePrefix"],"mappings":"AAAA,SAASA,yBAAyB,QAAQ,yCAAwC;AAClF,SAASC,UAAU,QAAQ,6BAA4B;AACvD,SAASC,cAAc,QAAQ,iCAAgC"}
1
+ {"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["export { createClientUploadHandler } from '../client/createClientUploadHandler.js'\nexport {\n buildStoragePathData,\n buildUploadStoragePathData,\n} from '../utilities/buildStoragePathData.js'\nexport { sanitizePrefix } from '../utilities/sanitizePrefix.js'\n"],"names":["createClientUploadHandler","buildStoragePathData","buildUploadStoragePathData","sanitizePrefix"],"mappings":"AAAA,SAASA,yBAAyB,QAAQ,yCAAwC;AAClF,SACEC,oBAAoB,EACpBC,0BAA0B,QACrB,uCAAsC;AAC7C,SAASC,cAAc,QAAQ,iCAAgC"}
@@ -1,4 +1,4 @@
1
- export { getFileKey } from '../utilities/getFileKey.js';
1
+ export { buildStoragePathData, buildUploadStoragePathData, isStoragePathWithinCollectionPrefix, } from '../utilities/buildStoragePathData.js';
2
2
  export { getFilePrefix } from '../utilities/getFilePrefix.js';
3
3
  export { resolveSignedURLKey } from '../utilities/resolveSignedURLKey.js';
4
4
  export { sanitizePrefix } from '../utilities/sanitizePrefix.js';
@@ -1 +1 @@
1
- {"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../../src/exports/utilities.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAA;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA"}
1
+ {"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../../src/exports/utilities.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,0BAA0B,EAC1B,mCAAmC,GACpC,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAA;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA"}
@@ -1,4 +1,4 @@
1
- export { getFileKey } from '../utilities/getFileKey.js';
1
+ export { buildStoragePathData, buildUploadStoragePathData, isStoragePathWithinCollectionPrefix } from '../utilities/buildStoragePathData.js';
2
2
  export { getFilePrefix } from '../utilities/getFilePrefix.js';
3
3
  export { resolveSignedURLKey } from '../utilities/resolveSignedURLKey.js';
4
4
  export { sanitizePrefix } from '../utilities/sanitizePrefix.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/exports/utilities.ts"],"sourcesContent":["export { getFileKey } from '../utilities/getFileKey.js'\nexport { getFilePrefix } from '../utilities/getFilePrefix.js'\nexport { resolveSignedURLKey } from '../utilities/resolveSignedURLKey.js'\nexport { sanitizePrefix } from '../utilities/sanitizePrefix.js'\n"],"names":["getFileKey","getFilePrefix","resolveSignedURLKey","sanitizePrefix"],"mappings":"AAAA,SAASA,UAAU,QAAQ,6BAA4B;AACvD,SAASC,aAAa,QAAQ,gCAA+B;AAC7D,SAASC,mBAAmB,QAAQ,sCAAqC;AACzE,SAASC,cAAc,QAAQ,iCAAgC"}
1
+ {"version":3,"sources":["../../src/exports/utilities.ts"],"sourcesContent":["export {\n buildStoragePathData,\n buildUploadStoragePathData,\n isStoragePathWithinCollectionPrefix,\n} from '../utilities/buildStoragePathData.js'\nexport { getFilePrefix } from '../utilities/getFilePrefix.js'\nexport { resolveSignedURLKey } from '../utilities/resolveSignedURLKey.js'\nexport { sanitizePrefix } from '../utilities/sanitizePrefix.js'\n"],"names":["buildStoragePathData","buildUploadStoragePathData","isStoragePathWithinCollectionPrefix","getFilePrefix","resolveSignedURLKey","sanitizePrefix"],"mappings":"AAAA,SACEA,oBAAoB,EACpBC,0BAA0B,EAC1BC,mCAAmC,QAC9B,uCAAsC;AAC7C,SAASC,aAAa,QAAQ,gCAA+B;AAC7D,SAASC,mBAAmB,QAAQ,sCAAqC;AACzE,SAASC,cAAc,QAAQ,iCAAgC"}
@@ -1 +1 @@
1
- {"version":3,"file":"getFields.d.ts","sourceRoot":"","sources":["../../src/fields/getFields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAyB,MAAM,SAAS,CAAA;AAI7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAKpE,UAAU,IAAI;IACZ,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B;;OAEG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,UAAU,EAAE,gBAAgB,CAAA;IAC5B,2BAA2B,CAAC,EAAE,IAAI,CAAA;IAClC,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,eAAO,MAAM,SAAS,6HAQnB,IAAI,KAAG,KAAK,EA+Kd,CAAA"}
1
+ {"version":3,"file":"getFields.d.ts","sourceRoot":"","sources":["../../src/fields/getFields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAyB,MAAM,SAAS,CAAA;AAI7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAMpE,UAAU,IAAI;IACZ,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B;;OAEG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,UAAU,EAAE,gBAAgB,CAAA;IAC5B,2BAA2B,CAAC,EAAE,IAAI,CAAA;IAClC,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,eAAO,MAAM,SAAS,6HAQnB,IAAI,KAAG,KAAK,EAsMd,CAAA"}
@@ -1,6 +1,7 @@
1
1
  import path from 'path';
2
2
  import { getAfterReadHook } from '../hooks/afterRead.js';
3
3
  import { getBeforeChangeHook } from '../hooks/beforeChange.js';
4
+ import { getNormalizeUploadPrefixFieldHook } from '../hooks/normalizeUploadPrefix.js';
4
5
  export const getFields = ({ adapter, alwaysInsertFields, collection, disablePayloadAccessControl, generateFileURL, prefix, useCompositePrefixes = false })=>{
5
6
  const baseURLField = {
6
7
  name: 'url',
@@ -19,6 +20,12 @@ export const getFields = ({ adapter, alwaysInsertFields, collection, disablePayl
19
20
  readOnly: true
20
21
  }
21
22
  };
23
+ // Server-owned key segment; hidden from the API and admin, read internally via showHiddenFields.
24
+ const baseObjectKeyField = {
25
+ name: '_objectKey',
26
+ type: 'text',
27
+ hidden: true
28
+ };
22
29
  const fields = [
23
30
  ...collection.fields,
24
31
  ...adapter?.fields || []
@@ -148,9 +155,26 @@ export const getFields = ({ adapter, alwaysInsertFields, collection, disablePayl
148
155
  fields.push({
149
156
  ...basePrefixField,
150
157
  ...existingPrefixField || {},
151
- defaultValue: existingPrefixField?.defaultValue ?? (useCompositePrefixes ? '' : prefix ? path.posix.join(prefix) : '')
158
+ defaultValue: existingPrefixField?.defaultValue ?? (useCompositePrefixes ? '' : prefix ? path.posix.join(prefix) : ''),
159
+ hooks: {
160
+ ...existingPrefixField?.hooks,
161
+ beforeChange: [
162
+ ...existingPrefixField?.hooks?.beforeChange || [],
163
+ getNormalizeUploadPrefixFieldHook({
164
+ collectionPrefix: prefix,
165
+ useCompositePrefixes
166
+ })
167
+ ]
168
+ }
152
169
  });
153
170
  }
171
+ const existingObjectKeyFieldIndex = fields.findIndex((existingField)=>'name' in existingField && existingField.name === '_objectKey');
172
+ if (existingObjectKeyFieldIndex > -1) {
173
+ fields.splice(existingObjectKeyFieldIndex, 1);
174
+ }
175
+ fields.push({
176
+ ...baseObjectKeyField
177
+ });
154
178
  return fields;
155
179
  };
156
180
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/fields/getFields.ts"],"sourcesContent":["import type { CollectionConfig, Field, GroupField, TextField } from 'payload'\n\nimport path from 'path'\n\nimport type { GeneratedAdapter, GenerateFileURL } from '../types.js'\n\nimport { getAfterReadHook } from '../hooks/afterRead.js'\nimport { getBeforeChangeHook } from '../hooks/beforeChange.js'\n\ninterface Args {\n adapter?: GeneratedAdapter\n /**\n * When true, always insert the prefix field regardless of whether a prefix is configured.\n */\n alwaysInsertFields?: boolean\n collection: CollectionConfig\n disablePayloadAccessControl?: true\n generateFileURL?: GenerateFileURL\n prefix?: string\n /**\n * When true, do not default the `prefix` field to the collection prefix; the\n * document field holds only the document-level segment.\n */\n useCompositePrefixes?: boolean\n}\n\nexport const getFields = ({\n adapter,\n alwaysInsertFields,\n collection,\n disablePayloadAccessControl,\n generateFileURL,\n prefix,\n useCompositePrefixes = false,\n}: Args): Field[] => {\n const baseURLField: TextField = {\n name: 'url',\n type: 'text',\n admin: {\n hidden: true,\n readOnly: true,\n },\n label: 'URL',\n }\n\n const basePrefixField: TextField = {\n name: 'prefix',\n type: 'text',\n admin: {\n hidden: true,\n readOnly: true,\n },\n }\n\n const fields = [...collection.fields, ...(adapter?.fields || [])]\n\n // Inject a hook into all URL fields to generate URLs\n\n let existingURLFieldIndex = -1\n\n const existingURLField = fields.find((existingField, i) => {\n if ('name' in existingField && existingField.name === 'url') {\n existingURLFieldIndex = i\n return true\n }\n return false\n }) as TextField\n\n if (existingURLFieldIndex > -1) {\n fields.splice(existingURLFieldIndex, 1)\n }\n\n // Only add afterRead hook if adapter is provided\n if (adapter) {\n fields.push({\n ...baseURLField,\n ...(existingURLField || {}),\n hooks: {\n afterRead: [\n getAfterReadHook({ adapter, collection, disablePayloadAccessControl, generateFileURL }),\n ...(existingURLField?.hooks?.afterRead || []),\n ],\n beforeChange: [\n getBeforeChangeHook({\n adapter,\n collection,\n disablePayloadAccessControl,\n generateFileURL,\n }),\n ...(existingURLField?.hooks?.beforeChange || []),\n ],\n },\n } as TextField)\n } else {\n fields.push({\n ...baseURLField,\n ...(existingURLField || {}),\n } as TextField)\n }\n\n if (typeof collection.upload === 'object' && collection.upload.imageSizes) {\n let existingSizesFieldIndex = -1\n\n const existingSizesField = fields.find((existingField, i) => {\n if ('name' in existingField && existingField.name === 'sizes') {\n existingSizesFieldIndex = i\n return true\n }\n\n return false\n }) as GroupField\n\n if (existingSizesFieldIndex > -1) {\n fields.splice(existingSizesFieldIndex, 1)\n }\n\n const sizesField: Field = {\n ...(existingSizesField || {}),\n name: 'sizes',\n type: 'group',\n admin: {\n hidden: true,\n },\n fields: collection.upload.imageSizes.map((size) => {\n const existingSizeField = existingSizesField?.fields.find(\n (existingField) => 'name' in existingField && existingField.name === size.name,\n ) as GroupField\n\n const existingSizeURLField = existingSizeField?.fields.find(\n (existingField) => 'name' in existingField && existingField.name === 'url',\n ) as TextField\n\n // Only add afterRead hook if adapter is provided\n const sizeURLField: TextField = adapter\n ? ({\n ...(existingSizeURLField || {}),\n ...baseURLField,\n hooks: {\n afterRead: [\n getAfterReadHook({\n adapter,\n collection,\n disablePayloadAccessControl,\n generateFileURL,\n size,\n }),\n ...((typeof existingSizeURLField === 'object' &&\n 'hooks' in existingSizeURLField &&\n existingSizeURLField?.hooks?.afterRead) ||\n []),\n ],\n beforeChange: [\n getBeforeChangeHook({\n adapter,\n collection,\n disablePayloadAccessControl,\n generateFileURL,\n size,\n }),\n ...((typeof existingSizeURLField === 'object' &&\n 'hooks' in existingSizeURLField &&\n existingSizeURLField?.hooks?.beforeChange) ||\n []),\n ],\n },\n } as TextField)\n : ({\n ...(existingSizeURLField || {}),\n ...baseURLField,\n } as TextField)\n\n return {\n ...existingSizeField,\n name: size.name,\n type: 'group',\n fields: [...(adapter?.fields || []), sizeURLField],\n } as Field\n }),\n }\n\n fields.push(sizesField)\n }\n\n // If prefix is enabled or alwaysInsertFields is true, save it to db\n if (typeof prefix !== 'undefined' || alwaysInsertFields) {\n let existingPrefixFieldIndex = -1\n\n const existingPrefixField = fields.find((existingField, i) => {\n if ('name' in existingField && existingField.name === 'prefix') {\n existingPrefixFieldIndex = i\n return true\n }\n return false\n }) as TextField\n\n if (existingPrefixFieldIndex > -1) {\n fields.splice(existingPrefixFieldIndex, 1)\n }\n\n fields.push({\n ...basePrefixField,\n ...(existingPrefixField || {}),\n defaultValue:\n existingPrefixField?.defaultValue ??\n (useCompositePrefixes ? '' : prefix ? path.posix.join(prefix) : ''),\n } as TextField)\n }\n\n return fields\n}\n"],"names":["path","getAfterReadHook","getBeforeChangeHook","getFields","adapter","alwaysInsertFields","collection","disablePayloadAccessControl","generateFileURL","prefix","useCompositePrefixes","baseURLField","name","type","admin","hidden","readOnly","label","basePrefixField","fields","existingURLFieldIndex","existingURLField","find","existingField","i","splice","push","hooks","afterRead","beforeChange","upload","imageSizes","existingSizesFieldIndex","existingSizesField","sizesField","map","size","existingSizeField","existingSizeURLField","sizeURLField","existingPrefixFieldIndex","existingPrefixField","defaultValue","posix","join"],"mappings":"AAEA,OAAOA,UAAU,OAAM;AAIvB,SAASC,gBAAgB,QAAQ,wBAAuB;AACxD,SAASC,mBAAmB,QAAQ,2BAA0B;AAmB9D,OAAO,MAAMC,YAAY,CAAC,EACxBC,OAAO,EACPC,kBAAkB,EAClBC,UAAU,EACVC,2BAA2B,EAC3BC,eAAe,EACfC,MAAM,EACNC,uBAAuB,KAAK,EACvB;IACL,MAAMC,eAA0B;QAC9BC,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,QAAQ;YACRC,UAAU;QACZ;QACAC,OAAO;IACT;IAEA,MAAMC,kBAA6B;QACjCN,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,QAAQ;YACRC,UAAU;QACZ;IACF;IAEA,MAAMG,SAAS;WAAIb,WAAWa,MAAM;WAAMf,SAASe,UAAU,EAAE;KAAE;IAEjE,qDAAqD;IAErD,IAAIC,wBAAwB,CAAC;IAE7B,MAAMC,mBAAmBF,OAAOG,IAAI,CAAC,CAACC,eAAeC;QACnD,IAAI,UAAUD,iBAAiBA,cAAcX,IAAI,KAAK,OAAO;YAC3DQ,wBAAwBI;YACxB,OAAO;QACT;QACA,OAAO;IACT;IAEA,IAAIJ,wBAAwB,CAAC,GAAG;QAC9BD,OAAOM,MAAM,CAACL,uBAAuB;IACvC;IAEA,iDAAiD;IACjD,IAAIhB,SAAS;QACXe,OAAOO,IAAI,CAAC;YACV,GAAGf,YAAY;YACf,GAAIU,oBAAoB,CAAC,CAAC;YAC1BM,OAAO;gBACLC,WAAW;oBACT3B,iBAAiB;wBAAEG;wBAASE;wBAAYC;wBAA6BC;oBAAgB;uBACjFa,kBAAkBM,OAAOC,aAAa,EAAE;iBAC7C;gBACDC,cAAc;oBACZ3B,oBAAoB;wBAClBE;wBACAE;wBACAC;wBACAC;oBACF;uBACIa,kBAAkBM,OAAOE,gBAAgB,EAAE;iBAChD;YACH;QACF;IACF,OAAO;QACLV,OAAOO,IAAI,CAAC;YACV,GAAGf,YAAY;YACf,GAAIU,oBAAoB,CAAC,CAAC;QAC5B;IACF;IAEA,IAAI,OAAOf,WAAWwB,MAAM,KAAK,YAAYxB,WAAWwB,MAAM,CAACC,UAAU,EAAE;QACzE,IAAIC,0BAA0B,CAAC;QAE/B,MAAMC,qBAAqBd,OAAOG,IAAI,CAAC,CAACC,eAAeC;YACrD,IAAI,UAAUD,iBAAiBA,cAAcX,IAAI,KAAK,SAAS;gBAC7DoB,0BAA0BR;gBAC1B,OAAO;YACT;YAEA,OAAO;QACT;QAEA,IAAIQ,0BAA0B,CAAC,GAAG;YAChCb,OAAOM,MAAM,CAACO,yBAAyB;QACzC;QAEA,MAAME,aAAoB;YACxB,GAAID,sBAAsB,CAAC,CAAC;YAC5BrB,MAAM;YACNC,MAAM;YACNC,OAAO;gBACLC,QAAQ;YACV;YACAI,QAAQb,WAAWwB,MAAM,CAACC,UAAU,CAACI,GAAG,CAAC,CAACC;gBACxC,MAAMC,oBAAoBJ,oBAAoBd,OAAOG,KACnD,CAACC,gBAAkB,UAAUA,iBAAiBA,cAAcX,IAAI,KAAKwB,KAAKxB,IAAI;gBAGhF,MAAM0B,uBAAuBD,mBAAmBlB,OAAOG,KACrD,CAACC,gBAAkB,UAAUA,iBAAiBA,cAAcX,IAAI,KAAK;gBAGvE,iDAAiD;gBACjD,MAAM2B,eAA0BnC,UAC3B;oBACC,GAAIkC,wBAAwB,CAAC,CAAC;oBAC9B,GAAG3B,YAAY;oBACfgB,OAAO;wBACLC,WAAW;4BACT3B,iBAAiB;gCACfG;gCACAE;gCACAC;gCACAC;gCACA4B;4BACF;+BACI,AAAC,OAAOE,yBAAyB,YACnC,WAAWA,wBACXA,sBAAsBX,OAAOC,aAC7B,EAAE;yBACL;wBACDC,cAAc;4BACZ3B,oBAAoB;gCAClBE;gCACAE;gCACAC;gCACAC;gCACA4B;4BACF;+BACI,AAAC,OAAOE,yBAAyB,YACnC,WAAWA,wBACXA,sBAAsBX,OAAOE,gBAC7B,EAAE;yBACL;oBACH;gBACF,IACC;oBACC,GAAIS,wBAAwB,CAAC,CAAC;oBAC9B,GAAG3B,YAAY;gBACjB;gBAEJ,OAAO;oBACL,GAAG0B,iBAAiB;oBACpBzB,MAAMwB,KAAKxB,IAAI;oBACfC,MAAM;oBACNM,QAAQ;2BAAKf,SAASe,UAAU,EAAE;wBAAGoB;qBAAa;gBACpD;YACF;QACF;QAEApB,OAAOO,IAAI,CAACQ;IACd;IAEA,oEAAoE;IACpE,IAAI,OAAOzB,WAAW,eAAeJ,oBAAoB;QACvD,IAAImC,2BAA2B,CAAC;QAEhC,MAAMC,sBAAsBtB,OAAOG,IAAI,CAAC,CAACC,eAAeC;YACtD,IAAI,UAAUD,iBAAiBA,cAAcX,IAAI,KAAK,UAAU;gBAC9D4B,2BAA2BhB;gBAC3B,OAAO;YACT;YACA,OAAO;QACT;QAEA,IAAIgB,2BAA2B,CAAC,GAAG;YACjCrB,OAAOM,MAAM,CAACe,0BAA0B;QAC1C;QAEArB,OAAOO,IAAI,CAAC;YACV,GAAGR,eAAe;YAClB,GAAIuB,uBAAuB,CAAC,CAAC;YAC7BC,cACED,qBAAqBC,gBACpBhC,CAAAA,uBAAuB,KAAKD,SAAST,KAAK2C,KAAK,CAACC,IAAI,CAACnC,UAAU,EAAC;QACrE;IACF;IAEA,OAAOU;AACT,EAAC"}
1
+ {"version":3,"sources":["../../src/fields/getFields.ts"],"sourcesContent":["import type { CollectionConfig, Field, GroupField, TextField } from 'payload'\n\nimport path from 'path'\n\nimport type { GeneratedAdapter, GenerateFileURL } from '../types.js'\n\nimport { getAfterReadHook } from '../hooks/afterRead.js'\nimport { getBeforeChangeHook } from '../hooks/beforeChange.js'\nimport { getNormalizeUploadPrefixFieldHook } from '../hooks/normalizeUploadPrefix.js'\n\ninterface Args {\n adapter?: GeneratedAdapter\n /**\n * When true, always insert the prefix field regardless of whether a prefix is configured.\n */\n alwaysInsertFields?: boolean\n collection: CollectionConfig\n disablePayloadAccessControl?: true\n generateFileURL?: GenerateFileURL\n prefix?: string\n /**\n * When true, do not default the `prefix` field to the collection prefix; the\n * document field holds only the document-level segment.\n */\n useCompositePrefixes?: boolean\n}\n\nexport const getFields = ({\n adapter,\n alwaysInsertFields,\n collection,\n disablePayloadAccessControl,\n generateFileURL,\n prefix,\n useCompositePrefixes = false,\n}: Args): Field[] => {\n const baseURLField: TextField = {\n name: 'url',\n type: 'text',\n admin: {\n hidden: true,\n readOnly: true,\n },\n label: 'URL',\n }\n\n const basePrefixField: TextField = {\n name: 'prefix',\n type: 'text',\n admin: {\n hidden: true,\n readOnly: true,\n },\n }\n\n // Server-owned key segment; hidden from the API and admin, read internally via showHiddenFields.\n const baseObjectKeyField: TextField = {\n name: '_objectKey',\n type: 'text',\n hidden: true,\n }\n\n const fields = [...collection.fields, ...(adapter?.fields || [])]\n\n // Inject a hook into all URL fields to generate URLs\n\n let existingURLFieldIndex = -1\n\n const existingURLField = fields.find((existingField, i) => {\n if ('name' in existingField && existingField.name === 'url') {\n existingURLFieldIndex = i\n return true\n }\n return false\n }) as TextField\n\n if (existingURLFieldIndex > -1) {\n fields.splice(existingURLFieldIndex, 1)\n }\n\n // Only add afterRead hook if adapter is provided\n if (adapter) {\n fields.push({\n ...baseURLField,\n ...(existingURLField || {}),\n hooks: {\n afterRead: [\n getAfterReadHook({ adapter, collection, disablePayloadAccessControl, generateFileURL }),\n ...(existingURLField?.hooks?.afterRead || []),\n ],\n beforeChange: [\n getBeforeChangeHook({\n adapter,\n collection,\n disablePayloadAccessControl,\n generateFileURL,\n }),\n ...(existingURLField?.hooks?.beforeChange || []),\n ],\n },\n } as TextField)\n } else {\n fields.push({\n ...baseURLField,\n ...(existingURLField || {}),\n } as TextField)\n }\n\n if (typeof collection.upload === 'object' && collection.upload.imageSizes) {\n let existingSizesFieldIndex = -1\n\n const existingSizesField = fields.find((existingField, i) => {\n if ('name' in existingField && existingField.name === 'sizes') {\n existingSizesFieldIndex = i\n return true\n }\n\n return false\n }) as GroupField\n\n if (existingSizesFieldIndex > -1) {\n fields.splice(existingSizesFieldIndex, 1)\n }\n\n const sizesField: Field = {\n ...(existingSizesField || {}),\n name: 'sizes',\n type: 'group',\n admin: {\n hidden: true,\n },\n fields: collection.upload.imageSizes.map((size) => {\n const existingSizeField = existingSizesField?.fields.find(\n (existingField) => 'name' in existingField && existingField.name === size.name,\n ) as GroupField\n\n const existingSizeURLField = existingSizeField?.fields.find(\n (existingField) => 'name' in existingField && existingField.name === 'url',\n ) as TextField\n\n // Only add afterRead hook if adapter is provided\n const sizeURLField: TextField = adapter\n ? ({\n ...(existingSizeURLField || {}),\n ...baseURLField,\n hooks: {\n afterRead: [\n getAfterReadHook({\n adapter,\n collection,\n disablePayloadAccessControl,\n generateFileURL,\n size,\n }),\n ...((typeof existingSizeURLField === 'object' &&\n 'hooks' in existingSizeURLField &&\n existingSizeURLField?.hooks?.afterRead) ||\n []),\n ],\n beforeChange: [\n getBeforeChangeHook({\n adapter,\n collection,\n disablePayloadAccessControl,\n generateFileURL,\n size,\n }),\n ...((typeof existingSizeURLField === 'object' &&\n 'hooks' in existingSizeURLField &&\n existingSizeURLField?.hooks?.beforeChange) ||\n []),\n ],\n },\n } as TextField)\n : ({\n ...(existingSizeURLField || {}),\n ...baseURLField,\n } as TextField)\n\n return {\n ...existingSizeField,\n name: size.name,\n type: 'group',\n fields: [...(adapter?.fields || []), sizeURLField],\n } as Field\n }),\n }\n\n fields.push(sizesField)\n }\n\n // If prefix is enabled or alwaysInsertFields is true, save it to db\n if (typeof prefix !== 'undefined' || alwaysInsertFields) {\n let existingPrefixFieldIndex = -1\n\n const existingPrefixField = fields.find((existingField, i) => {\n if ('name' in existingField && existingField.name === 'prefix') {\n existingPrefixFieldIndex = i\n return true\n }\n return false\n }) as TextField\n\n if (existingPrefixFieldIndex > -1) {\n fields.splice(existingPrefixFieldIndex, 1)\n }\n\n fields.push({\n ...basePrefixField,\n ...(existingPrefixField || {}),\n defaultValue:\n existingPrefixField?.defaultValue ??\n (useCompositePrefixes ? '' : prefix ? path.posix.join(prefix) : ''),\n hooks: {\n ...existingPrefixField?.hooks,\n beforeChange: [\n ...(existingPrefixField?.hooks?.beforeChange || []),\n getNormalizeUploadPrefixFieldHook({ collectionPrefix: prefix, useCompositePrefixes }),\n ],\n },\n } as TextField)\n }\n\n const existingObjectKeyFieldIndex = fields.findIndex(\n (existingField) => 'name' in existingField && existingField.name === '_objectKey',\n )\n if (existingObjectKeyFieldIndex > -1) {\n fields.splice(existingObjectKeyFieldIndex, 1)\n }\n\n fields.push({ ...baseObjectKeyField } as TextField)\n\n return fields\n}\n"],"names":["path","getAfterReadHook","getBeforeChangeHook","getNormalizeUploadPrefixFieldHook","getFields","adapter","alwaysInsertFields","collection","disablePayloadAccessControl","generateFileURL","prefix","useCompositePrefixes","baseURLField","name","type","admin","hidden","readOnly","label","basePrefixField","baseObjectKeyField","fields","existingURLFieldIndex","existingURLField","find","existingField","i","splice","push","hooks","afterRead","beforeChange","upload","imageSizes","existingSizesFieldIndex","existingSizesField","sizesField","map","size","existingSizeField","existingSizeURLField","sizeURLField","existingPrefixFieldIndex","existingPrefixField","defaultValue","posix","join","collectionPrefix","existingObjectKeyFieldIndex","findIndex"],"mappings":"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"}
@@ -3,7 +3,9 @@ import type { GeneratedAdapter } from '../types.js';
3
3
  interface Args {
4
4
  adapter: GeneratedAdapter;
5
5
  collection: CollectionConfig;
6
+ collectionPrefix?: string;
7
+ useCompositePrefixes?: boolean;
6
8
  }
7
- export declare const getAfterChangeHook: ({ adapter, collection }: Args) => CollectionAfterChangeHook<FileData & TypeWithID>;
9
+ export declare const getAfterChangeHook: ({ adapter, collection, collectionPrefix, useCompositePrefixes, }: Args) => CollectionAfterChangeHook<FileData & TypeWithID>;
8
10
  export {};
9
11
  //# sourceMappingURL=afterChange.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"afterChange.d.ts","sourceRoot":"","sources":["../../src/hooks/afterChange.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAIhG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAInD,UAAU,IAAI;IACZ,OAAO,EAAE,gBAAgB,CAAA;IACzB,UAAU,EAAE,gBAAgB,CAAA;CAC7B;AAED,eAAO,MAAM,kBAAkB,4BACH,IAAI,KAAG,yBAAyB,CAAC,QAAQ,GAAG,UAAU,CA8H/E,CAAA"}
1
+ {"version":3,"file":"afterChange.d.ts","sourceRoot":"","sources":["../../src/hooks/afterChange.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAIhG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AASnD,UAAU,IAAI;IACZ,OAAO,EAAE,gBAAgB,CAAA;IACzB,UAAU,EAAE,gBAAgB,CAAA;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAQD,eAAO,MAAM,kBAAkB,qEAM1B,IAAI,KAAG,yBAAyB,CAAC,QAAQ,GAAG,UAAU,CA6KxD,CAAA"}
@@ -1,6 +1,16 @@
1
1
  import { deepMergeWithSourceArrays } from 'payload';
2
+ import { buildPrefixWithObjectKey } from '../utilities/buildPrefixWithObjectKey.js';
3
+ import { buildStoragePathData, buildUploadStoragePathData } from '../utilities/buildStoragePathData.js';
2
4
  import { getIncomingFiles } from '../utilities/getIncomingFiles.js';
3
- export const getAfterChangeHook = ({ adapter, collection })=>async ({ data, doc, operation, previousDoc, req, select })=>{
5
+ // The object's folder: semantic prefix + `_objectKey` segment.
6
+ const getObjectFolder = (data)=>{
7
+ const record = data ?? {};
8
+ return buildPrefixWithObjectKey({
9
+ objectKey: record._objectKey,
10
+ prefix: record.prefix
11
+ });
12
+ };
13
+ export const getAfterChangeHook = ({ adapter, collection, collectionPrefix, useCompositePrefixes })=>async ({ data, doc, operation, previousDoc, req, select })=>{
4
14
  // Skip if this is an internal update to prevent infinite loop
5
15
  if (req.context?.skipCloudStorage) {
6
16
  return doc;
@@ -15,16 +25,30 @@ export const getAfterChangeHook = ({ adapter, collection })=>async ({ data, doc,
15
25
  req
16
26
  });
17
27
  if (files.length > 0) {
28
+ // Fold `_objectKey` so generated sizes land in the same folder as the original.
29
+ const dataForUpload = {
30
+ ...uploadData,
31
+ prefix: getObjectFolder(uploadData)
32
+ };
18
33
  const uploadResults = await Promise.all(files.filter((file)=>!file.uploadReference).map((file)=>adapter.handleUpload({
19
34
  collection,
20
- data: uploadData,
35
+ data: dataForUpload,
21
36
  file,
22
- req
37
+ req,
38
+ storageFilePath: buildUploadStoragePathData({
39
+ collectionPrefix,
40
+ docPrefix: dataForUpload.prefix,
41
+ filename: file.filename,
42
+ useCompositePrefixes
43
+ }).storageFilePath
23
44
  })));
24
45
  const uploadMetadata = uploadResults.filter((result)=>result != null && typeof result === 'object').reduce((acc, metadata)=>({
25
46
  ...acc,
26
47
  ...metadata
27
48
  }), {});
49
+ // Adapters may echo `data` back as metadata; keep the document's own `prefix`/`_objectKey`.
50
+ delete uploadMetadata.prefix;
51
+ delete uploadMetadata._objectKey;
28
52
  let docWithMetadata = doc;
29
53
  if (Object.keys(uploadMetadata).length > 0) {
30
54
  if (!req.context) {
@@ -68,9 +92,8 @@ export const getAfterChangeHook = ({ adapter, collection })=>async ({ data, doc,
68
92
  if (typeof previousDoc.sizes === 'object') {
69
93
  filesToDelete = filesToDelete.concat(Object.values(previousDoc?.sizes || []).map((resizedFileData)=>resizedFileData?.filename));
70
94
  }
71
- // Collect new filenames (main + sizes) so we don't delete a
72
- // file that the new upload reused (e.g. same filename on reupload
73
- // where Payload overwrites in place).
95
+ // Compare full locations: a replacement can reuse a filename while moving
96
+ // a legacy object beneath the collection prefix.
74
97
  const newFileData = {
75
98
  ...uploadData,
76
99
  ...uploadMetadata
@@ -86,13 +109,33 @@ export const getAfterChangeHook = ({ adapter, collection })=>async ({ data, doc,
86
109
  }
87
110
  }
88
111
  }
112
+ const resolveKey = ({ data, filename })=>buildStoragePathData({
113
+ collectionPrefix,
114
+ docPrefix: getObjectFolder(data),
115
+ filename,
116
+ useCompositePrefixes
117
+ }).storageFilePath;
118
+ const newKeys = new Set([
119
+ ...newFilenames
120
+ ].map((filename)=>resolveKey({
121
+ data: newFileData,
122
+ filename
123
+ })));
89
124
  const deletionPromises = filesToDelete.map(async (filename)=>{
90
- if (filename && !newFilenames.has(filename)) {
125
+ if (!filename) {
126
+ return;
127
+ }
128
+ const storageFilePath = resolveKey({
129
+ data: previousDoc,
130
+ filename
131
+ });
132
+ if (!newKeys.has(storageFilePath)) {
91
133
  await adapter.handleDelete({
92
134
  collection,
93
135
  doc: previousDoc,
94
136
  filename,
95
- req
137
+ req,
138
+ storageFilePath
96
139
  });
97
140
  }
98
141
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/afterChange.ts"],"sourcesContent":["import type { CollectionAfterChangeHook, CollectionConfig, FileData, TypeWithID } from 'payload'\n\nimport { deepMergeWithSourceArrays } from 'payload'\n\nimport type { GeneratedAdapter } from '../types.js'\n\nimport { getIncomingFiles } from '../utilities/getIncomingFiles.js'\n\ninterface Args {\n adapter: GeneratedAdapter\n collection: CollectionConfig\n}\n\nexport const getAfterChangeHook =\n ({ adapter, collection }: Args): CollectionAfterChangeHook<FileData & TypeWithID> =>\n async ({ data, doc, operation, previousDoc, req, select }) => {\n // Skip if this is an internal update to prevent infinite loop\n if (req.context?.skipCloudStorage) {\n return doc\n }\n\n // Restore upload metadata removed by select, including partially selected image sizes.\n const uploadData = select ? deepMergeWithSourceArrays<FileData & TypeWithID>(data, doc) : doc\n const isDraftSave = (uploadData as { _status?: string })._status === 'draft'\n const isDraftOverPublished =\n isDraftSave && (previousDoc as { _status?: string } | undefined)?._status === 'published'\n\n try {\n const files = getIncomingFiles({ data: uploadData, req })\n\n if (files.length > 0) {\n const uploadResults = await Promise.all(\n files\n .filter((file) => !file.uploadReference)\n .map((file) =>\n adapter.handleUpload({\n collection,\n data: uploadData,\n file,\n req,\n }),\n ),\n )\n\n const uploadMetadata = uploadResults\n .filter(\n (result): result is Partial<FileData & TypeWithID> =>\n result != null && typeof result === 'object',\n )\n .reduce(\n (acc, metadata) => ({ ...acc, ...metadata }),\n {} as Partial<FileData & TypeWithID>,\n )\n\n let docWithMetadata = doc\n\n if (Object.keys(uploadMetadata).length > 0) {\n if (!req.context) {\n req.context = {}\n }\n req.context.skipCloudStorage = true\n\n // Clear to prevent re-processing\n req.file = undefined\n req.payloadUploadSizes = undefined\n\n try {\n const updatedDoc = await req.payload.update({\n id: doc.id,\n collection: collection.slug,\n data: uploadMetadata,\n depth: 0,\n draft: isDraftSave,\n req,\n select,\n })\n\n // Persist all adapter metadata, but do not add unselected fields to the response.\n docWithMetadata = select ? { ...doc, ...updatedDoc } : { ...doc, ...uploadMetadata }\n } finally {\n delete req.context.skipCloudStorage\n }\n }\n\n // Delete previous files only after the new upload and metadata\n // persistence have succeeded. Deleting earlier would orphan the\n // record if a later step throws (e.g. a user-defined afterChange\n // hook on the same collection).\n if (previousDoc && operation === 'update' && !isDraftOverPublished) {\n let filesToDelete: string[] = []\n\n if (typeof previousDoc?.filename === 'string') {\n filesToDelete.push(previousDoc.filename)\n }\n\n if (typeof previousDoc.sizes === 'object') {\n filesToDelete = filesToDelete.concat(\n Object.values(previousDoc?.sizes || []).map(\n (resizedFileData) => resizedFileData?.filename as string,\n ),\n )\n }\n\n // Collect new filenames (main + sizes) so we don't delete a\n // file that the new upload reused (e.g. same filename on reupload\n // where Payload overwrites in place).\n const newFileData = { ...uploadData, ...uploadMetadata }\n const newFilenames = new Set<string>()\n if (typeof newFileData.filename === 'string') {\n newFilenames.add(newFileData.filename)\n }\n if (typeof newFileData.sizes === 'object') {\n for (const size of Object.values(newFileData.sizes || {})) {\n if (size?.filename && typeof size.filename === 'string') {\n newFilenames.add(size.filename)\n }\n }\n }\n\n const deletionPromises = filesToDelete.map(async (filename) => {\n if (filename && !newFilenames.has(filename)) {\n await adapter.handleDelete({ collection, doc: previousDoc, filename, req })\n }\n })\n\n await Promise.all(deletionPromises)\n }\n\n if (docWithMetadata !== doc) {\n return docWithMetadata\n }\n }\n } catch (err: unknown) {\n req.payload.logger.error(\n `There was an error while uploading files corresponding to the collection ${collection.slug} with filename ${doc.filename}:`,\n )\n req.payload.logger.error({ err })\n throw err\n }\n return doc\n }\n"],"names":["deepMergeWithSourceArrays","getIncomingFiles","getAfterChangeHook","adapter","collection","data","doc","operation","previousDoc","req","select","context","skipCloudStorage","uploadData","isDraftSave","_status","isDraftOverPublished","files","length","uploadResults","Promise","all","filter","file","uploadReference","map","handleUpload","uploadMetadata","result","reduce","acc","metadata","docWithMetadata","Object","keys","undefined","payloadUploadSizes","updatedDoc","payload","update","id","slug","depth","draft","filesToDelete","filename","push","sizes","concat","values","resizedFileData","newFileData","newFilenames","Set","add","size","deletionPromises","has","handleDelete","err","logger","error"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/hooks/afterChange.ts"],"sourcesContent":["import type { CollectionAfterChangeHook, CollectionConfig, FileData, TypeWithID } from 'payload'\n\nimport { deepMergeWithSourceArrays } from 'payload'\n\nimport type { GeneratedAdapter } from '../types.js'\n\nimport { buildPrefixWithObjectKey } from '../utilities/buildPrefixWithObjectKey.js'\nimport {\n buildStoragePathData,\n buildUploadStoragePathData,\n} from '../utilities/buildStoragePathData.js'\nimport { getIncomingFiles } from '../utilities/getIncomingFiles.js'\n\ninterface Args {\n adapter: GeneratedAdapter\n collection: CollectionConfig\n collectionPrefix?: string\n useCompositePrefixes?: boolean\n}\n\n// The object's folder: semantic prefix + `_objectKey` segment.\nconst getObjectFolder = (data: unknown): string => {\n const record = (data ?? {}) as { _objectKey?: string; prefix?: string }\n return buildPrefixWithObjectKey({ objectKey: record._objectKey, prefix: record.prefix })\n}\n\nexport const getAfterChangeHook =\n ({\n adapter,\n collection,\n collectionPrefix,\n useCompositePrefixes,\n }: Args): CollectionAfterChangeHook<FileData & TypeWithID> =>\n async ({ data, doc, operation, previousDoc, req, select }) => {\n // Skip if this is an internal update to prevent infinite loop\n if (req.context?.skipCloudStorage) {\n return doc\n }\n\n // Restore upload metadata removed by select, including partially selected image sizes.\n const uploadData = select ? deepMergeWithSourceArrays<FileData & TypeWithID>(data, doc) : doc\n const isDraftSave = (uploadData as { _status?: string })._status === 'draft'\n const isDraftOverPublished =\n isDraftSave && (previousDoc as { _status?: string } | undefined)?._status === 'published'\n\n try {\n const files = getIncomingFiles({ data: uploadData, req })\n\n if (files.length > 0) {\n // Fold `_objectKey` so generated sizes land in the same folder as the original.\n const dataForUpload = { ...uploadData, prefix: getObjectFolder(uploadData) }\n\n const uploadResults = await Promise.all(\n files\n .filter((file) => !file.uploadReference)\n .map((file) =>\n adapter.handleUpload({\n collection,\n data: dataForUpload,\n file,\n req,\n storageFilePath: buildUploadStoragePathData({\n collectionPrefix,\n docPrefix: dataForUpload.prefix,\n filename: file.filename,\n useCompositePrefixes,\n }).storageFilePath,\n }),\n ),\n )\n\n const uploadMetadata = uploadResults\n .filter(\n (result): result is Partial<FileData & TypeWithID> =>\n result != null && typeof result === 'object',\n )\n .reduce(\n (acc, metadata) => ({ ...acc, ...metadata }),\n {} as Partial<FileData & TypeWithID>,\n )\n\n // Adapters may echo `data` back as metadata; keep the document's own `prefix`/`_objectKey`.\n delete (uploadMetadata as Record<string, unknown>).prefix\n delete (uploadMetadata as Record<string, unknown>)._objectKey\n\n let docWithMetadata = doc\n\n if (Object.keys(uploadMetadata).length > 0) {\n if (!req.context) {\n req.context = {}\n }\n req.context.skipCloudStorage = true\n\n // Clear to prevent re-processing\n req.file = undefined\n req.payloadUploadSizes = undefined\n\n try {\n const updatedDoc = await req.payload.update({\n id: doc.id,\n collection: collection.slug,\n data: uploadMetadata,\n depth: 0,\n draft: isDraftSave,\n req,\n select,\n })\n\n // Persist all adapter metadata, but do not add unselected fields to the response.\n docWithMetadata = select ? { ...doc, ...updatedDoc } : { ...doc, ...uploadMetadata }\n } finally {\n delete req.context.skipCloudStorage\n }\n }\n\n // Delete previous files only after the new upload and metadata\n // persistence have succeeded. Deleting earlier would orphan the\n // record if a later step throws (e.g. a user-defined afterChange\n // hook on the same collection).\n if (previousDoc && operation === 'update' && !isDraftOverPublished) {\n let filesToDelete: string[] = []\n\n if (typeof previousDoc?.filename === 'string') {\n filesToDelete.push(previousDoc.filename)\n }\n\n if (typeof previousDoc.sizes === 'object') {\n filesToDelete = filesToDelete.concat(\n Object.values(previousDoc?.sizes || []).map(\n (resizedFileData) => resizedFileData?.filename as string,\n ),\n )\n }\n\n // Compare full locations: a replacement can reuse a filename while moving\n // a legacy object beneath the collection prefix.\n const newFileData = { ...uploadData, ...uploadMetadata }\n const newFilenames = new Set<string>()\n if (typeof newFileData.filename === 'string') {\n newFilenames.add(newFileData.filename)\n }\n if (typeof newFileData.sizes === 'object') {\n for (const size of Object.values(newFileData.sizes || {})) {\n if (size?.filename && typeof size.filename === 'string') {\n newFilenames.add(size.filename)\n }\n }\n }\n\n const resolveKey = ({\n data,\n filename,\n }: {\n data: { _objectKey?: string; prefix?: string }\n filename: string\n }) =>\n buildStoragePathData({\n collectionPrefix,\n docPrefix: getObjectFolder(data),\n filename,\n useCompositePrefixes,\n }).storageFilePath\n const newKeys = new Set(\n [...newFilenames].map((filename) =>\n resolveKey({\n data: newFileData as { _objectKey?: string; prefix?: string },\n filename,\n }),\n ),\n )\n\n const deletionPromises = filesToDelete.map(async (filename) => {\n if (!filename) {\n return\n }\n const storageFilePath = resolveKey({\n data: previousDoc as { _objectKey?: string; prefix?: string },\n filename,\n })\n if (!newKeys.has(storageFilePath)) {\n await adapter.handleDelete({\n collection,\n doc: previousDoc,\n filename,\n req,\n storageFilePath,\n })\n }\n })\n\n await Promise.all(deletionPromises)\n }\n\n if (docWithMetadata !== doc) {\n return docWithMetadata\n }\n }\n } catch (err: unknown) {\n req.payload.logger.error(\n `There was an error while uploading files corresponding to the collection ${collection.slug} with filename ${doc.filename}:`,\n )\n req.payload.logger.error({ err })\n throw err\n }\n return doc\n }\n"],"names":["deepMergeWithSourceArrays","buildPrefixWithObjectKey","buildStoragePathData","buildUploadStoragePathData","getIncomingFiles","getObjectFolder","data","record","objectKey","_objectKey","prefix","getAfterChangeHook","adapter","collection","collectionPrefix","useCompositePrefixes","doc","operation","previousDoc","req","select","context","skipCloudStorage","uploadData","isDraftSave","_status","isDraftOverPublished","files","length","dataForUpload","uploadResults","Promise","all","filter","file","uploadReference","map","handleUpload","storageFilePath","docPrefix","filename","uploadMetadata","result","reduce","acc","metadata","docWithMetadata","Object","keys","undefined","payloadUploadSizes","updatedDoc","payload","update","id","slug","depth","draft","filesToDelete","push","sizes","concat","values","resizedFileData","newFileData","newFilenames","Set","add","size","resolveKey","newKeys","deletionPromises","has","handleDelete","err","logger","error"],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { getAfterChangeHook } from './afterChange.js';
3
+ describe('upload replacement cleanup', ()=>{
4
+ it.each([
5
+ [
6
+ 'invoices',
7
+ 'media/invoices',
8
+ true,
9
+ false
10
+ ],
11
+ [
12
+ 'invoices',
13
+ 'media/invoices',
14
+ true,
15
+ true
16
+ ],
17
+ [
18
+ '',
19
+ 'media',
20
+ false,
21
+ false
22
+ ],
23
+ [
24
+ 'media/invoices',
25
+ 'media/invoices',
26
+ false,
27
+ false
28
+ ]
29
+ ])('should compare object locations when replacing %s with %s (delete: %s, selected: %s)', async (oldPrefix, newPrefix, shouldDelete, isSelected)=>{
30
+ const handleDelete = vi.fn();
31
+ const hook = getAfterChangeHook({
32
+ adapter: {
33
+ handleUpload: vi.fn(),
34
+ handleDelete
35
+ },
36
+ collection: {
37
+ slug: 'media'
38
+ },
39
+ collectionPrefix: 'media'
40
+ });
41
+ const uploadData = {
42
+ id: 1,
43
+ filename: 'file.png',
44
+ mimeType: 'image/png',
45
+ sizes: {
46
+ thumbnail: {
47
+ filename: 'thumb.png'
48
+ }
49
+ },
50
+ prefix: newPrefix
51
+ };
52
+ await hook({
53
+ data: uploadData,
54
+ doc: isSelected ? {
55
+ id: 1
56
+ } : uploadData,
57
+ select: isSelected ? {} : undefined,
58
+ previousDoc: {
59
+ id: 1,
60
+ filename: 'file.png',
61
+ mimeType: 'image/png',
62
+ sizes: {
63
+ thumbnail: {
64
+ filename: 'thumb.png'
65
+ }
66
+ },
67
+ prefix: oldPrefix
68
+ },
69
+ operation: 'update',
70
+ req: {
71
+ context: {},
72
+ file: {
73
+ data: Buffer.from('file'),
74
+ size: 4
75
+ },
76
+ payload: {
77
+ logger: {
78
+ error: vi.fn()
79
+ }
80
+ }
81
+ }
82
+ });
83
+ expect(handleDelete).toHaveBeenCalledTimes(shouldDelete ? 2 : 0);
84
+ if (shouldDelete) {
85
+ expect(handleDelete).toHaveBeenCalledWith(expect.objectContaining({
86
+ doc: expect.objectContaining({
87
+ prefix: oldPrefix
88
+ }),
89
+ filename: 'file.png'
90
+ }));
91
+ }
92
+ });
93
+ });
94
+
95
+ //# sourceMappingURL=afterChange.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/afterChange.spec.ts"],"sourcesContent":["import { describe, expect, it, vi } from 'vitest'\n\nimport { getAfterChangeHook } from './afterChange.js'\n\ndescribe('upload replacement cleanup', () => {\n it.each([\n ['invoices', 'media/invoices', true, false],\n ['invoices', 'media/invoices', true, true],\n ['', 'media', false, false],\n ['media/invoices', 'media/invoices', false, false],\n ] as const)(\n 'should compare object locations when replacing %s with %s (delete: %s, selected: %s)',\n async (oldPrefix, newPrefix, shouldDelete, isSelected) => {\n const handleDelete = vi.fn()\n const hook = getAfterChangeHook({\n adapter: { handleUpload: vi.fn(), handleDelete } as never,\n collection: { slug: 'media' } as never,\n collectionPrefix: 'media',\n })\n\n const uploadData = {\n id: 1,\n filename: 'file.png',\n mimeType: 'image/png',\n sizes: { thumbnail: { filename: 'thumb.png' } },\n prefix: newPrefix,\n }\n\n await hook({\n data: uploadData,\n doc: isSelected ? { id: 1 } : uploadData,\n select: isSelected ? {} : undefined,\n previousDoc: {\n id: 1,\n filename: 'file.png',\n mimeType: 'image/png',\n sizes: { thumbnail: { filename: 'thumb.png' } },\n prefix: oldPrefix,\n },\n operation: 'update',\n req: {\n context: {},\n file: { data: Buffer.from('file'), size: 4 },\n payload: { logger: { error: vi.fn() } },\n },\n } as never)\n\n expect(handleDelete).toHaveBeenCalledTimes(shouldDelete ? 2 : 0)\n if (shouldDelete) {\n expect(handleDelete).toHaveBeenCalledWith(\n expect.objectContaining({\n doc: expect.objectContaining({ prefix: oldPrefix }),\n filename: 'file.png',\n }),\n )\n }\n },\n )\n})\n"],"names":["describe","expect","it","vi","getAfterChangeHook","each","oldPrefix","newPrefix","shouldDelete","isSelected","handleDelete","fn","hook","adapter","handleUpload","collection","slug","collectionPrefix","uploadData","id","filename","mimeType","sizes","thumbnail","prefix","data","doc","select","undefined","previousDoc","operation","req","context","file","Buffer","from","size","payload","logger","error","toHaveBeenCalledTimes","toHaveBeenCalledWith","objectContaining"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAQ;AAEjD,SAASC,kBAAkB,QAAQ,mBAAkB;AAErDJ,SAAS,8BAA8B;IACrCE,GAAGG,IAAI,CAAC;QACN;YAAC;YAAY;YAAkB;YAAM;SAAM;QAC3C;YAAC;YAAY;YAAkB;YAAM;SAAK;QAC1C;YAAC;YAAI;YAAS;YAAO;SAAM;QAC3B;YAAC;YAAkB;YAAkB;YAAO;SAAM;KACnD,EACC,wFACA,OAAOC,WAAWC,WAAWC,cAAcC;QACzC,MAAMC,eAAeP,GAAGQ,EAAE;QAC1B,MAAMC,OAAOR,mBAAmB;YAC9BS,SAAS;gBAAEC,cAAcX,GAAGQ,EAAE;gBAAID;YAAa;YAC/CK,YAAY;gBAAEC,MAAM;YAAQ;YAC5BC,kBAAkB;QACpB;QAEA,MAAMC,aAAa;YACjBC,IAAI;YACJC,UAAU;YACVC,UAAU;YACVC,OAAO;gBAAEC,WAAW;oBAAEH,UAAU;gBAAY;YAAE;YAC9CI,QAAQjB;QACV;QAEA,MAAMK,KAAK;YACTa,MAAMP;YACNQ,KAAKjB,aAAa;gBAAEU,IAAI;YAAE,IAAID;YAC9BS,QAAQlB,aAAa,CAAC,IAAImB;YAC1BC,aAAa;gBACXV,IAAI;gBACJC,UAAU;gBACVC,UAAU;gBACVC,OAAO;oBAAEC,WAAW;wBAAEH,UAAU;oBAAY;gBAAE;gBAC9CI,QAAQlB;YACV;YACAwB,WAAW;YACXC,KAAK;gBACHC,SAAS,CAAC;gBACVC,MAAM;oBAAER,MAAMS,OAAOC,IAAI,CAAC;oBAASC,MAAM;gBAAE;gBAC3CC,SAAS;oBAAEC,QAAQ;wBAAEC,OAAOpC,GAAGQ,EAAE;oBAAG;gBAAE;YACxC;QACF;QAEAV,OAAOS,cAAc8B,qBAAqB,CAAChC,eAAe,IAAI;QAC9D,IAAIA,cAAc;YAChBP,OAAOS,cAAc+B,oBAAoB,CACvCxC,OAAOyC,gBAAgB,CAAC;gBACtBhB,KAAKzB,OAAOyC,gBAAgB,CAAC;oBAAElB,QAAQlB;gBAAU;gBACjDc,UAAU;YACZ;QAEJ;IACF;AAEJ"}
@@ -3,7 +3,9 @@ import type { GeneratedAdapter, TypeWithPrefix } from '../types.js';
3
3
  interface Args {
4
4
  adapter: GeneratedAdapter;
5
5
  collection: CollectionConfig;
6
+ collectionPrefix?: string;
7
+ useCompositePrefixes?: boolean;
6
8
  }
7
- export declare const getAfterDeleteHook: ({ adapter, collection, }: Args) => CollectionAfterDeleteHook<FileData & TypeWithID & TypeWithPrefix>;
9
+ export declare const getAfterDeleteHook: ({ adapter, collection, collectionPrefix, useCompositePrefixes, }: Args) => CollectionAfterDeleteHook<FileData & TypeWithID & TypeWithPrefix>;
8
10
  export {};
9
11
  //# sourceMappingURL=afterDelete.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"afterDelete.d.ts","sourceRoot":"","sources":["../../src/hooks/afterDelete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEhG,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEnE,UAAU,IAAI;IACZ,OAAO,EAAE,gBAAgB,CAAA;IACzB,UAAU,EAAE,gBAAgB,CAAA;CAC7B;AAED,eAAO,MAAM,kBAAkB,6BAG5B,IAAI,KAAG,yBAAyB,CAAC,QAAQ,GAAG,UAAU,GAAG,cAAc,CAyBzE,CAAA"}
1
+ {"version":3,"file":"afterDelete.d.ts","sourceRoot":"","sources":["../../src/hooks/afterDelete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEhG,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAKnE,UAAU,IAAI;IACZ,OAAO,EAAE,gBAAgB,CAAA;IACzB,UAAU,EAAE,gBAAgB,CAAA;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,eAAO,MAAM,kBAAkB,qEAK5B,IAAI,KAAG,yBAAyB,CAAC,QAAQ,GAAG,UAAU,GAAG,cAAc,CAqCzE,CAAA"}
@@ -1,17 +1,31 @@
1
- export const getAfterDeleteHook = ({ adapter, collection })=>{
1
+ import { buildPrefixWithObjectKey } from '../utilities/buildPrefixWithObjectKey.js';
2
+ import { buildStoragePathData } from '../utilities/buildStoragePathData.js';
3
+ export const getAfterDeleteHook = ({ adapter, collection, collectionPrefix, useCompositePrefixes })=>{
2
4
  return async ({ doc, req })=>{
3
5
  try {
6
+ // Fold `_objectKey` so deletes target the real object folder.
7
+ const docPrefix = buildPrefixWithObjectKey({
8
+ objectKey: doc._objectKey,
9
+ prefix: doc.prefix
10
+ });
4
11
  const filesToDelete = [
5
12
  doc.filename,
6
13
  ...Object.values(doc?.sizes || []).map((resizedFileData)=>resizedFileData?.filename)
7
14
  ];
8
15
  const promises = filesToDelete.map(async (filename)=>{
9
16
  if (filename) {
17
+ const { storageFilePath } = buildStoragePathData({
18
+ collectionPrefix,
19
+ docPrefix,
20
+ filename,
21
+ useCompositePrefixes
22
+ });
10
23
  await adapter.handleDelete({
11
24
  collection,
12
25
  doc,
13
26
  filename,
14
- req
27
+ req,
28
+ storageFilePath
15
29
  });
16
30
  }
17
31
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/afterDelete.ts"],"sourcesContent":["import type { CollectionAfterDeleteHook, CollectionConfig, FileData, TypeWithID } from 'payload'\n\nimport type { GeneratedAdapter, TypeWithPrefix } from '../types.js'\n\ninterface Args {\n adapter: GeneratedAdapter\n collection: CollectionConfig\n}\n\nexport const getAfterDeleteHook = ({\n adapter,\n collection,\n}: Args): CollectionAfterDeleteHook<FileData & TypeWithID & TypeWithPrefix> => {\n return async ({ doc, req }) => {\n try {\n const filesToDelete: string[] = [\n doc.filename,\n ...Object.values(doc?.sizes || []).map(\n (resizedFileData) => resizedFileData?.filename as string,\n ),\n ]\n\n const promises = filesToDelete.map(async (filename) => {\n if (filename) {\n await adapter.handleDelete({ collection, doc, filename, req })\n }\n })\n\n await Promise.all(promises)\n } catch (err: unknown) {\n req.payload.logger.error({\n err,\n msg: `There was an error while deleting files corresponding to the ${collection.labels?.singular} with ID ${doc.id}.`,\n })\n }\n return doc\n }\n}\n"],"names":["getAfterDeleteHook","adapter","collection","doc","req","filesToDelete","filename","Object","values","sizes","map","resizedFileData","promises","handleDelete","Promise","all","err","payload","logger","error","msg","labels","singular","id"],"mappings":"AASA,OAAO,MAAMA,qBAAqB,CAAC,EACjCC,OAAO,EACPC,UAAU,EACL;IACL,OAAO,OAAO,EAAEC,GAAG,EAAEC,GAAG,EAAE;QACxB,IAAI;YACF,MAAMC,gBAA0B;gBAC9BF,IAAIG,QAAQ;mBACTC,OAAOC,MAAM,CAACL,KAAKM,SAAS,EAAE,EAAEC,GAAG,CACpC,CAACC,kBAAoBA,iBAAiBL;aAEzC;YAED,MAAMM,WAAWP,cAAcK,GAAG,CAAC,OAAOJ;gBACxC,IAAIA,UAAU;oBACZ,MAAML,QAAQY,YAAY,CAAC;wBAAEX;wBAAYC;wBAAKG;wBAAUF;oBAAI;gBAC9D;YACF;YAEA,MAAMU,QAAQC,GAAG,CAACH;QACpB,EAAE,OAAOI,KAAc;YACrBZ,IAAIa,OAAO,CAACC,MAAM,CAACC,KAAK,CAAC;gBACvBH;gBACAI,KAAK,CAAC,6DAA6D,EAAElB,WAAWmB,MAAM,EAAEC,SAAS,SAAS,EAAEnB,IAAIoB,EAAE,CAAC,CAAC,CAAC;YACvH;QACF;QACA,OAAOpB;IACT;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/hooks/afterDelete.ts"],"sourcesContent":["import type { CollectionAfterDeleteHook, CollectionConfig, FileData, TypeWithID } from 'payload'\n\nimport type { GeneratedAdapter, TypeWithPrefix } from '../types.js'\n\nimport { buildPrefixWithObjectKey } from '../utilities/buildPrefixWithObjectKey.js'\nimport { buildStoragePathData } from '../utilities/buildStoragePathData.js'\n\ninterface Args {\n adapter: GeneratedAdapter\n collection: CollectionConfig\n collectionPrefix?: string\n useCompositePrefixes?: boolean\n}\n\nexport const getAfterDeleteHook = ({\n adapter,\n collection,\n collectionPrefix,\n useCompositePrefixes,\n}: Args): CollectionAfterDeleteHook<FileData & TypeWithID & TypeWithPrefix> => {\n return async ({ doc, req }) => {\n try {\n // Fold `_objectKey` so deletes target the real object folder.\n const docPrefix = buildPrefixWithObjectKey({\n objectKey: (doc as { _objectKey?: string })._objectKey,\n prefix: doc.prefix,\n })\n\n const filesToDelete: string[] = [\n doc.filename,\n ...Object.values(doc?.sizes || []).map(\n (resizedFileData) => resizedFileData?.filename as string,\n ),\n ]\n\n const promises = filesToDelete.map(async (filename) => {\n if (filename) {\n const { storageFilePath } = buildStoragePathData({\n collectionPrefix,\n docPrefix,\n filename,\n useCompositePrefixes,\n })\n await adapter.handleDelete({ collection, doc, filename, req, storageFilePath })\n }\n })\n\n await Promise.all(promises)\n } catch (err: unknown) {\n req.payload.logger.error({\n err,\n msg: `There was an error while deleting files corresponding to the ${collection.labels?.singular} with ID ${doc.id}.`,\n })\n }\n return doc\n }\n}\n"],"names":["buildPrefixWithObjectKey","buildStoragePathData","getAfterDeleteHook","adapter","collection","collectionPrefix","useCompositePrefixes","doc","req","docPrefix","objectKey","_objectKey","prefix","filesToDelete","filename","Object","values","sizes","map","resizedFileData","promises","storageFilePath","handleDelete","Promise","all","err","payload","logger","error","msg","labels","singular","id"],"mappings":"AAIA,SAASA,wBAAwB,QAAQ,2CAA0C;AACnF,SAASC,oBAAoB,QAAQ,uCAAsC;AAS3E,OAAO,MAAMC,qBAAqB,CAAC,EACjCC,OAAO,EACPC,UAAU,EACVC,gBAAgB,EAChBC,oBAAoB,EACf;IACL,OAAO,OAAO,EAAEC,GAAG,EAAEC,GAAG,EAAE;QACxB,IAAI;YACF,8DAA8D;YAC9D,MAAMC,YAAYT,yBAAyB;gBACzCU,WAAW,AAACH,IAAgCI,UAAU;gBACtDC,QAAQL,IAAIK,MAAM;YACpB;YAEA,MAAMC,gBAA0B;gBAC9BN,IAAIO,QAAQ;mBACTC,OAAOC,MAAM,CAACT,KAAKU,SAAS,EAAE,EAAEC,GAAG,CACpC,CAACC,kBAAoBA,iBAAiBL;aAEzC;YAED,MAAMM,WAAWP,cAAcK,GAAG,CAAC,OAAOJ;gBACxC,IAAIA,UAAU;oBACZ,MAAM,EAAEO,eAAe,EAAE,GAAGpB,qBAAqB;wBAC/CI;wBACAI;wBACAK;wBACAR;oBACF;oBACA,MAAMH,QAAQmB,YAAY,CAAC;wBAAElB;wBAAYG;wBAAKO;wBAAUN;wBAAKa;oBAAgB;gBAC/E;YACF;YAEA,MAAME,QAAQC,GAAG,CAACJ;QACpB,EAAE,OAAOK,KAAc;YACrBjB,IAAIkB,OAAO,CAACC,MAAM,CAACC,KAAK,CAAC;gBACvBH;gBACAI,KAAK,CAAC,6DAA6D,EAAEzB,WAAW0B,MAAM,EAAEC,SAAS,SAAS,EAAExB,IAAIyB,EAAE,CAAC,CAAC,CAAC;YACvH;QACF;QACA,OAAOzB;IACT;AACF,EAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"afterRead.d.ts","sourceRoot":"","sources":["../../src/hooks/afterRead.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAErE,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAEpE,UAAU,IAAI;IACZ,OAAO,EAAE,gBAAgB,CAAA;IACzB,UAAU,EAAE,gBAAgB,CAAA;IAC5B,2BAA2B,CAAC,EAAE,OAAO,CAAA;IACrC,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB;AAED,eAAO,MAAM,gBAAgB,gFACmD,IAAI,KAAG,SA4BpF,CAAA"}
1
+ {"version":3,"file":"afterRead.d.ts","sourceRoot":"","sources":["../../src/hooks/afterRead.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAErE,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAIpE,UAAU,IAAI;IACZ,OAAO,EAAE,gBAAgB,CAAA;IACzB,UAAU,EAAE,gBAAgB,CAAA;IAC5B,2BAA2B,CAAC,EAAE,OAAO,CAAA;IACrC,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB;AAiBD,eAAO,MAAM,gBAAgB,gFACmD,IAAI,KAAG,SA8BpF,CAAA"}
@@ -1,13 +1,26 @@
1
+ import { sanitizePrefix } from '../utilities/sanitizePrefix.js';
2
+ // The object's folder: semantic prefix + `_objectKey` segment.
3
+ const getObjectFolder = (data)=>{
4
+ const record = data ?? {};
5
+ const safePrefix = sanitizePrefix(typeof record.prefix === 'string' ? record.prefix : '');
6
+ const safeObjectKey = sanitizePrefix(typeof record._objectKey === 'string' ? record._objectKey : '');
7
+ if (safePrefix && safeObjectKey) {
8
+ return `${safePrefix}/${safeObjectKey}`;
9
+ }
10
+ return safePrefix || safeObjectKey;
11
+ };
1
12
  export const getAfterReadHook = ({ adapter, collection, disablePayloadAccessControl, generateFileURL, size })=>async ({ data, value })=>{
2
13
  const filename = size ? data?.sizes?.[size.name]?.filename : data?.filename;
3
14
  const prefix = data?.prefix;
15
+ // Direct-serve URLs encode the full location; the proxy resolves `_objectKey` server-side.
16
+ const objectFolder = getObjectFolder(data);
4
17
  let url = value;
5
18
  if (filename) {
6
19
  if (generateFileURL) {
7
20
  url = await generateFileURL({
8
21
  collection,
9
22
  filename,
10
- prefix,
23
+ prefix: objectFolder,
11
24
  size
12
25
  });
13
26
  } else if (disablePayloadAccessControl && adapter.generateURL) {
@@ -15,7 +28,7 @@ export const getAfterReadHook = ({ adapter, collection, disablePayloadAccessCont
15
28
  collection,
16
29
  data,
17
30
  filename,
18
- prefix
31
+ prefix: objectFolder
19
32
  });
20
33
  } else if (url && prefix) {
21
34
  const separator = url.includes('?') ? '&' : '?';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/afterRead.ts"],"sourcesContent":["import type { CollectionConfig, FieldHook, ImageSize } from 'payload'\n\nimport type { GeneratedAdapter, GenerateFileURL } from '../types.js'\n\ninterface Args {\n adapter: GeneratedAdapter\n collection: CollectionConfig\n disablePayloadAccessControl?: boolean\n generateFileURL?: GenerateFileURL\n size?: ImageSize\n}\n\nexport const getAfterReadHook =\n ({ adapter, collection, disablePayloadAccessControl, generateFileURL, size }: Args): FieldHook =>\n async ({ data, value }) => {\n const filename = size ? data?.sizes?.[size.name]?.filename : data?.filename\n const prefix = data?.prefix\n let url = value\n\n if (filename) {\n if (generateFileURL) {\n url = await generateFileURL({\n collection,\n filename,\n prefix,\n size,\n })\n } else if (disablePayloadAccessControl && adapter.generateURL) {\n url = await adapter.generateURL({\n collection,\n data,\n filename,\n prefix,\n })\n } else if (url && prefix) {\n const separator = url.includes('?') ? '&' : '?'\n url = `${url}${separator}prefix=${encodeURIComponent(prefix)}`\n }\n }\n\n return url\n }\n"],"names":["getAfterReadHook","adapter","collection","disablePayloadAccessControl","generateFileURL","size","data","value","filename","sizes","name","prefix","url","generateURL","separator","includes","encodeURIComponent"],"mappings":"AAYA,OAAO,MAAMA,mBACX,CAAC,EAAEC,OAAO,EAAEC,UAAU,EAAEC,2BAA2B,EAAEC,eAAe,EAAEC,IAAI,EAAQ,GAClF,OAAO,EAAEC,IAAI,EAAEC,KAAK,EAAE;QACpB,MAAMC,WAAWH,OAAOC,MAAMG,OAAO,CAACJ,KAAKK,IAAI,CAAC,EAAEF,WAAWF,MAAME;QACnE,MAAMG,SAASL,MAAMK;QACrB,IAAIC,MAAML;QAEV,IAAIC,UAAU;YACZ,IAAIJ,iBAAiB;gBACnBQ,MAAM,MAAMR,gBAAgB;oBAC1BF;oBACAM;oBACAG;oBACAN;gBACF;YACF,OAAO,IAAIF,+BAA+BF,QAAQY,WAAW,EAAE;gBAC7DD,MAAM,MAAMX,QAAQY,WAAW,CAAC;oBAC9BX;oBACAI;oBACAE;oBACAG;gBACF;YACF,OAAO,IAAIC,OAAOD,QAAQ;gBACxB,MAAMG,YAAYF,IAAIG,QAAQ,CAAC,OAAO,MAAM;gBAC5CH,MAAM,GAAGA,MAAME,UAAU,OAAO,EAAEE,mBAAmBL,SAAS;YAChE;QACF;QAEA,OAAOC;IACT,EAAC"}
1
+ {"version":3,"sources":["../../src/hooks/afterRead.ts"],"sourcesContent":["import type { CollectionConfig, FieldHook, ImageSize } from 'payload'\n\nimport type { GeneratedAdapter, GenerateFileURL } from '../types.js'\n\nimport { sanitizePrefix } from '../utilities/sanitizePrefix.js'\n\ninterface Args {\n adapter: GeneratedAdapter\n collection: CollectionConfig\n disablePayloadAccessControl?: boolean\n generateFileURL?: GenerateFileURL\n size?: ImageSize\n}\n\n// The object's folder: semantic prefix + `_objectKey` segment.\nconst getObjectFolder = (data: unknown): string => {\n const record = (data ?? {}) as Record<string, unknown>\n const safePrefix = sanitizePrefix(typeof record.prefix === 'string' ? record.prefix : '')\n const safeObjectKey = sanitizePrefix(\n typeof record._objectKey === 'string' ? record._objectKey : '',\n )\n\n if (safePrefix && safeObjectKey) {\n return `${safePrefix}/${safeObjectKey}`\n }\n\n return safePrefix || safeObjectKey\n}\n\nexport const getAfterReadHook =\n ({ adapter, collection, disablePayloadAccessControl, generateFileURL, size }: Args): FieldHook =>\n async ({ data, value }) => {\n const filename = size ? data?.sizes?.[size.name]?.filename : data?.filename\n const prefix = data?.prefix\n // Direct-serve URLs encode the full location; the proxy resolves `_objectKey` server-side.\n const objectFolder = getObjectFolder(data)\n let url = value\n\n if (filename) {\n if (generateFileURL) {\n url = await generateFileURL({\n collection,\n filename,\n prefix: objectFolder,\n size,\n })\n } else if (disablePayloadAccessControl && adapter.generateURL) {\n url = await adapter.generateURL({\n collection,\n data,\n filename,\n prefix: objectFolder,\n })\n } else if (url && prefix) {\n const separator = url.includes('?') ? '&' : '?'\n url = `${url}${separator}prefix=${encodeURIComponent(prefix)}`\n }\n }\n\n return url\n }\n"],"names":["sanitizePrefix","getObjectFolder","data","record","safePrefix","prefix","safeObjectKey","_objectKey","getAfterReadHook","adapter","collection","disablePayloadAccessControl","generateFileURL","size","value","filename","sizes","name","objectFolder","url","generateURL","separator","includes","encodeURIComponent"],"mappings":"AAIA,SAASA,cAAc,QAAQ,iCAAgC;AAU/D,+DAA+D;AAC/D,MAAMC,kBAAkB,CAACC;IACvB,MAAMC,SAAUD,QAAQ,CAAC;IACzB,MAAME,aAAaJ,eAAe,OAAOG,OAAOE,MAAM,KAAK,WAAWF,OAAOE,MAAM,GAAG;IACtF,MAAMC,gBAAgBN,eACpB,OAAOG,OAAOI,UAAU,KAAK,WAAWJ,OAAOI,UAAU,GAAG;IAG9D,IAAIH,cAAcE,eAAe;QAC/B,OAAO,GAAGF,WAAW,CAAC,EAAEE,eAAe;IACzC;IAEA,OAAOF,cAAcE;AACvB;AAEA,OAAO,MAAME,mBACX,CAAC,EAAEC,OAAO,EAAEC,UAAU,EAAEC,2BAA2B,EAAEC,eAAe,EAAEC,IAAI,EAAQ,GAClF,OAAO,EAAEX,IAAI,EAAEY,KAAK,EAAE;QACpB,MAAMC,WAAWF,OAAOX,MAAMc,OAAO,CAACH,KAAKI,IAAI,CAAC,EAAEF,WAAWb,MAAMa;QACnE,MAAMV,SAASH,MAAMG;QACrB,2FAA2F;QAC3F,MAAMa,eAAejB,gBAAgBC;QACrC,IAAIiB,MAAML;QAEV,IAAIC,UAAU;YACZ,IAAIH,iBAAiB;gBACnBO,MAAM,MAAMP,gBAAgB;oBAC1BF;oBACAK;oBACAV,QAAQa;oBACRL;gBACF;YACF,OAAO,IAAIF,+BAA+BF,QAAQW,WAAW,EAAE;gBAC7DD,MAAM,MAAMV,QAAQW,WAAW,CAAC;oBAC9BV;oBACAR;oBACAa;oBACAV,QAAQa;gBACV;YACF,OAAO,IAAIC,OAAOd,QAAQ;gBACxB,MAAMgB,YAAYF,IAAIG,QAAQ,CAAC,OAAO,MAAM;gBAC5CH,MAAM,GAAGA,MAAME,UAAU,OAAO,EAAEE,mBAAmBlB,SAAS;YAChE;QACF;QAEA,OAAOc;IACT,EAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"beforeChange.d.ts","sourceRoot":"","sources":["../../src/hooks/beforeChange.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAErE,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAEpE,UAAU,IAAI;IACZ,OAAO,EAAE,gBAAgB,CAAA;IACzB,UAAU,EAAE,gBAAgB,CAAA;IAC5B,2BAA2B,CAAC,EAAE,OAAO,CAAA;IACrC,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB;AAED,eAAO,MAAM,mBAAmB,gFACgD,IAAI,KAAG,SA2BpF,CAAA"}
1
+ {"version":3,"file":"beforeChange.d.ts","sourceRoot":"","sources":["../../src/hooks/beforeChange.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAErE,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAIpE,UAAU,IAAI;IACZ,OAAO,EAAE,gBAAgB,CAAA;IACzB,UAAU,EAAE,gBAAgB,CAAA;IAC5B,2BAA2B,CAAC,EAAE,OAAO,CAAA;IACrC,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB;AAiBD,eAAO,MAAM,mBAAmB,gFACgD,IAAI,KAAG,SA2BpF,CAAA"}