@payloadcms/plugin-cloud-storage 3.83.0-canary.0 → 3.83.0-canary.2

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 (37) hide show
  1. package/dist/admin/fields/getFields.d.ts +6 -1
  2. package/dist/admin/fields/getFields.d.ts.map +1 -1
  3. package/dist/admin/fields/getFields.js +2 -2
  4. package/dist/admin/fields/getFields.js.map +1 -1
  5. package/dist/client/createClientUploadHandler.d.ts +1 -0
  6. package/dist/client/createClientUploadHandler.d.ts.map +1 -1
  7. package/dist/client/createClientUploadHandler.js +2 -1
  8. package/dist/client/createClientUploadHandler.js.map +1 -1
  9. package/dist/exports/utilities.d.ts +2 -0
  10. package/dist/exports/utilities.d.ts.map +1 -1
  11. package/dist/exports/utilities.js +2 -0
  12. package/dist/exports/utilities.js.map +1 -1
  13. package/dist/fields/getFields.d.ts +6 -1
  14. package/dist/fields/getFields.d.ts.map +1 -1
  15. package/dist/fields/getFields.js +2 -2
  16. package/dist/fields/getFields.js.map +1 -1
  17. package/dist/plugin.d.ts.map +1 -1
  18. package/dist/plugin.js +5 -3
  19. package/dist/plugin.js.map +1 -1
  20. package/dist/types.d.ts +10 -0
  21. package/dist/types.d.ts.map +1 -1
  22. package/dist/types.js.map +1 -1
  23. package/dist/utilities/getFileKey.d.ts +16 -0
  24. package/dist/utilities/getFileKey.d.ts.map +1 -0
  25. package/dist/utilities/getFileKey.js +18 -0
  26. package/dist/utilities/getFileKey.js.map +1 -0
  27. package/dist/utilities/getFileKey.spec.js +114 -0
  28. package/dist/utilities/getFileKey.spec.js.map +1 -0
  29. package/dist/utilities/getFilePrefix.d.ts +1 -3
  30. package/dist/utilities/getFilePrefix.d.ts.map +1 -1
  31. package/dist/utilities/getFilePrefix.js +2 -19
  32. package/dist/utilities/getFilePrefix.js.map +1 -1
  33. package/dist/utilities/sanitizePrefix.d.ts +10 -0
  34. package/dist/utilities/sanitizePrefix.d.ts.map +1 -0
  35. package/dist/utilities/sanitizePrefix.js +23 -0
  36. package/dist/utilities/sanitizePrefix.js.map +1 -0
  37. package/package.json +4 -4
@@ -6,7 +6,12 @@ interface Args {
6
6
  alwaysInsertFields?: boolean;
7
7
  collection: CollectionConfig;
8
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;
9
14
  }
10
- export declare const getFields: ({ alwaysInsertFields, collection, prefix }: Args) => Field[];
15
+ export declare const getFields: ({ alwaysInsertFields, collection, prefix, useCompositePrefixes, }: Args) => Field[];
11
16
  export {};
12
17
  //# sourceMappingURL=getFields.d.ts.map
@@ -1 +1 @@
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;CAChB;AAED,eAAO,MAAM,SAAS,+CAAgD,IAAI,KAAG,KAAK,EAoHjF,CAAA"}
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,sEAKnB,IAAI,KAAG,KAAK,EAoHd,CAAA"}
@@ -1,5 +1,5 @@
1
1
  import path from 'path';
2
- export const getFields = ({ alwaysInsertFields, collection, prefix })=>{
2
+ export const getFields = ({ alwaysInsertFields, collection, prefix, useCompositePrefixes = false })=>{
3
3
  const baseURLField = {
4
4
  name: 'url',
5
5
  type: 'text',
@@ -89,7 +89,7 @@ export const getFields = ({ alwaysInsertFields, collection, prefix })=>{
89
89
  fields.push({
90
90
  ...basePrefixField,
91
91
  ...existingPrefixField || {},
92
- defaultValue: prefix ? path.posix.join(prefix) : ''
92
+ defaultValue: useCompositePrefixes ? '' : prefix ? path.posix.join(prefix) : ''
93
93
  });
94
94
  }
95
95
  return fields;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/fields/getFields.ts"],"sourcesContent":["import type { CollectionConfig, Field, GroupField, TextField } from 'payload'\n\nimport path from 'path'\n\ninterface Args {\n /**\n * When true, always insert the prefix field regardless of whether a prefix is configured.\n */\n alwaysInsertFields?: boolean\n collection: CollectionConfig\n prefix?: string\n}\n\nexport const getFields = ({ alwaysInsertFields, collection, prefix }: 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]\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 fields.push({\n ...baseURLField,\n ...(existingURLField || {}),\n } as TextField)\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 GroupField\n\n return {\n ...existingSizeField,\n name: size.name,\n type: 'group',\n fields: [\n {\n ...(existingSizeURLField || {}),\n ...baseURLField,\n },\n ],\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: prefix ? path.posix.join(prefix) : '',\n } as TextField)\n }\n\n return fields\n}\n"],"names":["path","getFields","alwaysInsertFields","collection","prefix","baseURLField","name","type","admin","hidden","readOnly","label","basePrefixField","fields","existingURLFieldIndex","existingURLField","find","existingField","i","splice","push","upload","imageSizes","existingSizesFieldIndex","existingSizesField","sizesField","map","size","existingSizeField","existingSizeURLField","existingPrefixFieldIndex","existingPrefixField","defaultValue","posix","join"],"mappings":"AAEA,OAAOA,UAAU,OAAM;AAWvB,OAAO,MAAMC,YAAY,CAAC,EAAEC,kBAAkB,EAAEC,UAAU,EAAEC,MAAM,EAAQ;IACxE,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;WAAIV,WAAWU,MAAM;KAAC;IAErC,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;IAEAD,OAAOO,IAAI,CAAC;QACV,GAAGf,YAAY;QACf,GAAIU,oBAAoB,CAAC,CAAC;IAC5B;IAEA,IAAI,OAAOZ,WAAWkB,MAAM,KAAK,YAAYlB,WAAWkB,MAAM,CAACC,UAAU,EAAE;QACzE,IAAIC,0BAA0B,CAAC;QAE/B,MAAMC,qBAAqBX,OAAOG,IAAI,CAAC,CAACC,eAAeC;YACrD,IAAI,UAAUD,iBAAiBA,cAAcX,IAAI,KAAK,SAAS;gBAC7DiB,0BAA0BL;gBAC1B,OAAO;YACT;YAEA,OAAO;QACT;QAEA,IAAIK,0BAA0B,CAAC,GAAG;YAChCV,OAAOM,MAAM,CAACI,yBAAyB;QACzC;QAEA,MAAME,aAAoB;YACxB,GAAID,sBAAsB,CAAC,CAAC;YAC5BlB,MAAM;YACNC,MAAM;YACNC,OAAO;gBACLC,QAAQ;YACV;YACAI,QAAQV,WAAWkB,MAAM,CAACC,UAAU,CAACI,GAAG,CAAC,CAACC;gBACxC,MAAMC,oBAAoBJ,oBAAoBX,OAAOG,KACnD,CAACC,gBAAkB,UAAUA,iBAAiBA,cAAcX,IAAI,KAAKqB,KAAKrB,IAAI;gBAGhF,MAAMuB,uBAAuBD,mBAAmBf,OAAOG,KACrD,CAACC,gBAAkB,UAAUA,iBAAiBA,cAAcX,IAAI,KAAK;gBAGvE,OAAO;oBACL,GAAGsB,iBAAiB;oBACpBtB,MAAMqB,KAAKrB,IAAI;oBACfC,MAAM;oBACNM,QAAQ;wBACN;4BACE,GAAIgB,wBAAwB,CAAC,CAAC;4BAC9B,GAAGxB,YAAY;wBACjB;qBACD;gBACH;YACF;QACF;QAEAQ,OAAOO,IAAI,CAACK;IACd;IAEA,oEAAoE;IACpE,IAAI,OAAOrB,WAAW,eAAeF,oBAAoB;QACvD,IAAI4B,2BAA2B,CAAC;QAEhC,MAAMC,sBAAsBlB,OAAOG,IAAI,CAAC,CAACC,eAAeC;YACtD,IAAI,UAAUD,iBAAiBA,cAAcX,IAAI,KAAK,UAAU;gBAC9DwB,2BAA2BZ;gBAC3B,OAAO;YACT;YACA,OAAO;QACT;QAEA,IAAIY,2BAA2B,CAAC,GAAG;YACjCjB,OAAOM,MAAM,CAACW,0BAA0B;QAC1C;QAEAjB,OAAOO,IAAI,CAAC;YACV,GAAGR,eAAe;YAClB,GAAImB,uBAAuB,CAAC,CAAC;YAC7BC,cAAc5B,SAASJ,KAAKiC,KAAK,CAACC,IAAI,CAAC9B,UAAU;QACnD;IACF;IAEA,OAAOS;AACT,EAAC"}
1
+ {"version":3,"sources":["../../../src/admin/fields/getFields.ts"],"sourcesContent":["import type { CollectionConfig, Field, GroupField, TextField } from 'payload'\n\nimport path from 'path'\n\ninterface Args {\n /**\n * When true, always insert the prefix field regardless of whether a prefix is configured.\n */\n alwaysInsertFields?: boolean\n collection: CollectionConfig\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 alwaysInsertFields,\n collection,\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]\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 fields.push({\n ...baseURLField,\n ...(existingURLField || {}),\n } as TextField)\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 GroupField\n\n return {\n ...existingSizeField,\n name: size.name,\n type: 'group',\n fields: [\n {\n ...(existingSizeURLField || {}),\n ...baseURLField,\n },\n ],\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: useCompositePrefixes ? '' : prefix ? path.posix.join(prefix) : '',\n } as TextField)\n }\n\n return fields\n}\n"],"names":["path","getFields","alwaysInsertFields","collection","prefix","useCompositePrefixes","baseURLField","name","type","admin","hidden","readOnly","label","basePrefixField","fields","existingURLFieldIndex","existingURLField","find","existingField","i","splice","push","upload","imageSizes","existingSizesFieldIndex","existingSizesField","sizesField","map","size","existingSizeField","existingSizeURLField","existingPrefixFieldIndex","existingPrefixField","defaultValue","posix","join"],"mappings":"AAEA,OAAOA,UAAU,OAAM;AAgBvB,OAAO,MAAMC,YAAY,CAAC,EACxBC,kBAAkB,EAClBC,UAAU,EACVC,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;WAAIX,WAAWW,MAAM;KAAC;IAErC,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;IAEAD,OAAOO,IAAI,CAAC;QACV,GAAGf,YAAY;QACf,GAAIU,oBAAoB,CAAC,CAAC;IAC5B;IAEA,IAAI,OAAOb,WAAWmB,MAAM,KAAK,YAAYnB,WAAWmB,MAAM,CAACC,UAAU,EAAE;QACzE,IAAIC,0BAA0B,CAAC;QAE/B,MAAMC,qBAAqBX,OAAOG,IAAI,CAAC,CAACC,eAAeC;YACrD,IAAI,UAAUD,iBAAiBA,cAAcX,IAAI,KAAK,SAAS;gBAC7DiB,0BAA0BL;gBAC1B,OAAO;YACT;YAEA,OAAO;QACT;QAEA,IAAIK,0BAA0B,CAAC,GAAG;YAChCV,OAAOM,MAAM,CAACI,yBAAyB;QACzC;QAEA,MAAME,aAAoB;YACxB,GAAID,sBAAsB,CAAC,CAAC;YAC5BlB,MAAM;YACNC,MAAM;YACNC,OAAO;gBACLC,QAAQ;YACV;YACAI,QAAQX,WAAWmB,MAAM,CAACC,UAAU,CAACI,GAAG,CAAC,CAACC;gBACxC,MAAMC,oBAAoBJ,oBAAoBX,OAAOG,KACnD,CAACC,gBAAkB,UAAUA,iBAAiBA,cAAcX,IAAI,KAAKqB,KAAKrB,IAAI;gBAGhF,MAAMuB,uBAAuBD,mBAAmBf,OAAOG,KACrD,CAACC,gBAAkB,UAAUA,iBAAiBA,cAAcX,IAAI,KAAK;gBAGvE,OAAO;oBACL,GAAGsB,iBAAiB;oBACpBtB,MAAMqB,KAAKrB,IAAI;oBACfC,MAAM;oBACNM,QAAQ;wBACN;4BACE,GAAIgB,wBAAwB,CAAC,CAAC;4BAC9B,GAAGxB,YAAY;wBACjB;qBACD;gBACH;YACF;QACF;QAEAQ,OAAOO,IAAI,CAACK;IACd;IAEA,oEAAoE;IACpE,IAAI,OAAOtB,WAAW,eAAeF,oBAAoB;QACvD,IAAI6B,2BAA2B,CAAC;QAEhC,MAAMC,sBAAsBlB,OAAOG,IAAI,CAAC,CAACC,eAAeC;YACtD,IAAI,UAAUD,iBAAiBA,cAAcX,IAAI,KAAK,UAAU;gBAC9DwB,2BAA2BZ;gBAC3B,OAAO;YACT;YACA,OAAO;QACT;QAEA,IAAIY,2BAA2B,CAAC,GAAG;YACjCjB,OAAOM,MAAM,CAACW,0BAA0B;QAC1C;QAEAjB,OAAOO,IAAI,CAAC;YACV,GAAGR,eAAe;YAClB,GAAImB,uBAAuB,CAAC,CAAC;YAC7BC,cAAc5B,uBAAuB,KAAKD,SAASJ,KAAKkC,KAAK,CAACC,IAAI,CAAC/B,UAAU;QAC/E;IACF;IAEA,OAAOU;AACT,EAAC"}
@@ -12,6 +12,7 @@ export declare const createClientUploadHandler: <T extends Record<string, unknow
12
12
  handler: (args: {
13
13
  apiRoute: string;
14
14
  collectionSlug: UploadCollectionSlug;
15
+ docPrefix?: string;
15
16
  extra: T;
16
17
  file: File;
17
18
  prefix?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"createClientUploadHandler.d.ts","sourceRoot":"","sources":["../../src/client/createClientUploadHandler.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAGnD,OAAO,EAAY,KAAK,SAAS,EAAa,MAAM,OAAO,CAAA;AAE3D,KAAK,wBAAwB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACjE,QAAQ,EAAE,SAAS,CAAA;IACnB,cAAc,EAAE,oBAAoB,CAAA;IACpC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,EAAE,CAAC,CAAA;IACR,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,iBAAiB,EAAE,IAAI,MAAM,EAAE,CAAA;CAChC,CAAA;AAED,eAAO,MAAM,yBAAyB,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,gBAExE;IACD,OAAO,EAAE,CAAC,IAAI,EAAE;QACd,QAAQ,EAAE,MAAM,CAAA;QAChB,cAAc,EAAE,oBAAoB,CAAA;QACpC,KAAK,EAAE,CAAC,CAAA;QACR,IAAI,EAAE,IAAI,CAAA;QACV,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,iBAAiB,EAAE,IAAI,MAAM,EAAE,CAAA;QAC/B,SAAS,EAAE,MAAM,CAAA;QACjB,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;KACxC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CACvB,gFAQI,wBAAwB,CAAC,CAAC,CAAC,gCAmC/B,CAAA"}
1
+ {"version":3,"file":"createClientUploadHandler.d.ts","sourceRoot":"","sources":["../../src/client/createClientUploadHandler.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAGnD,OAAO,EAAY,KAAK,SAAS,EAAa,MAAM,OAAO,CAAA;AAE3D,KAAK,wBAAwB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACjE,QAAQ,EAAE,SAAS,CAAA;IACnB,cAAc,EAAE,oBAAoB,CAAA;IACpC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,EAAE,CAAC,CAAA;IACR,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,iBAAiB,EAAE,IAAI,MAAM,EAAE,CAAA;CAChC,CAAA;AAED,eAAO,MAAM,yBAAyB,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,gBAExE;IACD,OAAO,EAAE,CAAC,IAAI,EAAE;QACd,QAAQ,EAAE,MAAM,CAAA;QAChB,cAAc,EAAE,oBAAoB,CAAA;QACpC,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,KAAK,EAAE,CAAC,CAAA;QACR,IAAI,EAAE,IAAI,CAAA;QACV,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,iBAAiB,EAAE,IAAI,MAAM,EAAE,CAAA;QAC/B,SAAS,EAAE,MAAM,CAAA;QACjB,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;KACxC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CACvB,gFAQI,wBAAwB,CAAC,CAAC,CAAC,gCAoC/B,CAAA"}
@@ -10,10 +10,11 @@ export const createClientUploadHandler = ({ handler })=>{
10
10
  if (enabled) {
11
11
  setUploadHandler({
12
12
  collectionSlug,
13
- handler: ({ file, updateFilename })=>{
13
+ handler: ({ docPrefix, file, updateFilename })=>{
14
14
  return handler({
15
15
  apiRoute,
16
16
  collectionSlug,
17
+ docPrefix,
17
18
  extra,
18
19
  file,
19
20
  prefix,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/client/createClientUploadHandler.tsx"],"sourcesContent":["'use client'\n\nimport type { UploadCollectionSlug } from 'payload'\n\nimport { useConfig, useEffectEvent, useUploadHandlers } from '@payloadcms/ui'\nimport { Fragment, type ReactNode, useEffect } from 'react'\n\ntype ClientUploadHandlerProps<T extends Record<string, unknown>> = {\n children: ReactNode\n collectionSlug: UploadCollectionSlug\n enabled?: boolean\n extra: T\n prefix?: string\n serverHandlerPath: `/${string}`\n}\n\nexport const createClientUploadHandler = <T extends Record<string, unknown>>({\n handler,\n}: {\n handler: (args: {\n apiRoute: string\n collectionSlug: UploadCollectionSlug\n extra: T\n file: File\n prefix?: string\n serverHandlerPath: `/${string}`\n serverURL: string\n updateFilename: (value: string) => void\n }) => Promise<unknown>\n}) => {\n return function ClientUploadHandler({\n children,\n collectionSlug,\n enabled,\n extra,\n prefix,\n serverHandlerPath,\n }: ClientUploadHandlerProps<T>) {\n const { setUploadHandler } = useUploadHandlers()\n const {\n config: {\n routes: { api: apiRoute },\n serverURL,\n },\n } = useConfig()\n\n const initializeHandler = useEffectEvent(() => {\n if (enabled) {\n setUploadHandler({\n collectionSlug,\n handler: ({ file, updateFilename }) => {\n return handler({\n apiRoute,\n collectionSlug,\n extra,\n file,\n prefix,\n serverHandlerPath,\n serverURL,\n updateFilename,\n })\n },\n })\n }\n })\n\n useEffect(() => {\n initializeHandler()\n }, [])\n\n return <Fragment>{children}</Fragment>\n }\n}\n"],"names":["useConfig","useEffectEvent","useUploadHandlers","Fragment","useEffect","createClientUploadHandler","handler","ClientUploadHandler","children","collectionSlug","enabled","extra","prefix","serverHandlerPath","setUploadHandler","config","routes","api","apiRoute","serverURL","initializeHandler","file","updateFilename"],"mappings":"AAAA;;AAIA,SAASA,SAAS,EAAEC,cAAc,EAAEC,iBAAiB,QAAQ,iBAAgB;AAC7E,SAASC,QAAQ,EAAkBC,SAAS,QAAQ,QAAO;AAW3D,OAAO,MAAMC,4BAA4B,CAAoC,EAC3EC,OAAO,EAYR;IACC,OAAO,SAASC,oBAAoB,EAClCC,QAAQ,EACRC,cAAc,EACdC,OAAO,EACPC,KAAK,EACLC,MAAM,EACNC,iBAAiB,EACW;QAC5B,MAAM,EAAEC,gBAAgB,EAAE,GAAGZ;QAC7B,MAAM,EACJa,QAAQ,EACNC,QAAQ,EAAEC,KAAKC,QAAQ,EAAE,EACzBC,SAAS,EACV,EACF,GAAGnB;QAEJ,MAAMoB,oBAAoBnB,eAAe;YACvC,IAAIS,SAAS;gBACXI,iBAAiB;oBACfL;oBACAH,SAAS,CAAC,EAAEe,IAAI,EAAEC,cAAc,EAAE;wBAChC,OAAOhB,QAAQ;4BACbY;4BACAT;4BACAE;4BACAU;4BACAT;4BACAC;4BACAM;4BACAG;wBACF;oBACF;gBACF;YACF;QACF;QAEAlB,UAAU;YACRgB;QACF,GAAG,EAAE;QAEL,qBAAO,KAACjB;sBAAUK;;IACpB;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/client/createClientUploadHandler.tsx"],"sourcesContent":["'use client'\n\nimport type { UploadCollectionSlug } from 'payload'\n\nimport { useConfig, useEffectEvent, useUploadHandlers } from '@payloadcms/ui'\nimport { Fragment, type ReactNode, useEffect } from 'react'\n\ntype ClientUploadHandlerProps<T extends Record<string, unknown>> = {\n children: ReactNode\n collectionSlug: UploadCollectionSlug\n enabled?: boolean\n extra: T\n prefix?: string\n serverHandlerPath: `/${string}`\n}\n\nexport const createClientUploadHandler = <T extends Record<string, unknown>>({\n handler,\n}: {\n handler: (args: {\n apiRoute: string\n collectionSlug: UploadCollectionSlug\n docPrefix?: string\n extra: T\n file: File\n prefix?: string\n serverHandlerPath: `/${string}`\n serverURL: string\n updateFilename: (value: string) => void\n }) => Promise<unknown>\n}) => {\n return function ClientUploadHandler({\n children,\n collectionSlug,\n enabled,\n extra,\n prefix,\n serverHandlerPath,\n }: ClientUploadHandlerProps<T>) {\n const { setUploadHandler } = useUploadHandlers()\n const {\n config: {\n routes: { api: apiRoute },\n serverURL,\n },\n } = useConfig()\n\n const initializeHandler = useEffectEvent(() => {\n if (enabled) {\n setUploadHandler({\n collectionSlug,\n handler: ({ docPrefix, file, updateFilename }) => {\n return handler({\n apiRoute,\n collectionSlug,\n docPrefix,\n extra,\n file,\n prefix,\n serverHandlerPath,\n serverURL,\n updateFilename,\n })\n },\n })\n }\n })\n\n useEffect(() => {\n initializeHandler()\n }, [])\n\n return <Fragment>{children}</Fragment>\n }\n}\n"],"names":["useConfig","useEffectEvent","useUploadHandlers","Fragment","useEffect","createClientUploadHandler","handler","ClientUploadHandler","children","collectionSlug","enabled","extra","prefix","serverHandlerPath","setUploadHandler","config","routes","api","apiRoute","serverURL","initializeHandler","docPrefix","file","updateFilename"],"mappings":"AAAA;;AAIA,SAASA,SAAS,EAAEC,cAAc,EAAEC,iBAAiB,QAAQ,iBAAgB;AAC7E,SAASC,QAAQ,EAAkBC,SAAS,QAAQ,QAAO;AAW3D,OAAO,MAAMC,4BAA4B,CAAoC,EAC3EC,OAAO,EAaR;IACC,OAAO,SAASC,oBAAoB,EAClCC,QAAQ,EACRC,cAAc,EACdC,OAAO,EACPC,KAAK,EACLC,MAAM,EACNC,iBAAiB,EACW;QAC5B,MAAM,EAAEC,gBAAgB,EAAE,GAAGZ;QAC7B,MAAM,EACJa,QAAQ,EACNC,QAAQ,EAAEC,KAAKC,QAAQ,EAAE,EACzBC,SAAS,EACV,EACF,GAAGnB;QAEJ,MAAMoB,oBAAoBnB,eAAe;YACvC,IAAIS,SAAS;gBACXI,iBAAiB;oBACfL;oBACAH,SAAS,CAAC,EAAEe,SAAS,EAAEC,IAAI,EAAEC,cAAc,EAAE;wBAC3C,OAAOjB,QAAQ;4BACbY;4BACAT;4BACAY;4BACAV;4BACAW;4BACAV;4BACAC;4BACAM;4BACAI;wBACF;oBACF;gBACF;YACF;QACF;QAEAnB,UAAU;YACRgB;QACF,GAAG,EAAE;QAEL,qBAAO,KAACjB;sBAAUK;;IACpB;AACF,EAAC"}
@@ -1,3 +1,5 @@
1
+ export { getFileKey } from '../utilities/getFileKey.js';
1
2
  export { getFilePrefix } from '../utilities/getFilePrefix.js';
2
3
  export { initClientUploads } from '../utilities/initClientUploads.js';
4
+ export { sanitizePrefix } from '../utilities/sanitizePrefix.js';
3
5
  //# sourceMappingURL=utilities.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../../src/exports/utilities.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA"}
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,iBAAiB,EAAE,MAAM,mCAAmC,CAAA;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA"}
@@ -1,4 +1,6 @@
1
+ export { getFileKey } from '../utilities/getFileKey.js';
1
2
  export { getFilePrefix } from '../utilities/getFilePrefix.js';
2
3
  export { initClientUploads } from '../utilities/initClientUploads.js';
4
+ export { sanitizePrefix } from '../utilities/sanitizePrefix.js';
3
5
 
4
6
  //# sourceMappingURL=utilities.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/exports/utilities.ts"],"sourcesContent":["export { getFilePrefix } from '../utilities/getFilePrefix.js'\nexport { initClientUploads } from '../utilities/initClientUploads.js'\n"],"names":["getFilePrefix","initClientUploads"],"mappings":"AAAA,SAASA,aAAa,QAAQ,gCAA+B;AAC7D,SAASC,iBAAiB,QAAQ,oCAAmC"}
1
+ {"version":3,"sources":["../../src/exports/utilities.ts"],"sourcesContent":["export { getFileKey } from '../utilities/getFileKey.js'\nexport { getFilePrefix } from '../utilities/getFilePrefix.js'\nexport { initClientUploads } from '../utilities/initClientUploads.js'\nexport { sanitizePrefix } from '../utilities/sanitizePrefix.js'\n"],"names":["getFileKey","getFilePrefix","initClientUploads","sanitizePrefix"],"mappings":"AAAA,SAASA,UAAU,QAAQ,6BAA4B;AACvD,SAASC,aAAa,QAAQ,gCAA+B;AAC7D,SAASC,iBAAiB,QAAQ,oCAAmC;AACrE,SAASC,cAAc,QAAQ,iCAAgC"}
@@ -10,7 +10,12 @@ interface Args {
10
10
  disablePayloadAccessControl?: true;
11
11
  generateFileURL?: GenerateFileURL;
12
12
  prefix?: string;
13
+ /**
14
+ * When true, do not default the `prefix` field to the collection prefix; the
15
+ * document field holds only the document-level segment.
16
+ */
17
+ useCompositePrefixes?: boolean;
13
18
  }
14
- export declare const getFields: ({ adapter, alwaysInsertFields, collection, disablePayloadAccessControl, generateFileURL, prefix, }: Args) => Field[];
19
+ export declare const getFields: ({ adapter, alwaysInsertFields, collection, disablePayloadAccessControl, generateFileURL, prefix, useCompositePrefixes, }: Args) => Field[];
15
20
  export {};
16
21
  //# sourceMappingURL=getFields.d.ts.map
@@ -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;CAChB;AAED,eAAO,MAAM,SAAS,uGAOnB,IAAI,KAAG,KAAK,EA6Kd,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;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,EA6Kd,CAAA"}
@@ -1,7 +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
- export const getFields = ({ adapter, alwaysInsertFields, collection, disablePayloadAccessControl, generateFileURL, prefix })=>{
4
+ export const getFields = ({ adapter, alwaysInsertFields, collection, disablePayloadAccessControl, generateFileURL, prefix, useCompositePrefixes = false })=>{
5
5
  const baseURLField = {
6
6
  name: 'url',
7
7
  type: 'text',
@@ -148,7 +148,7 @@ export const getFields = ({ adapter, alwaysInsertFields, collection, disablePayl
148
148
  fields.push({
149
149
  ...basePrefixField,
150
150
  ...existingPrefixField || {},
151
- defaultValue: prefix ? path.posix.join(prefix) : ''
151
+ defaultValue: useCompositePrefixes ? '' : prefix ? path.posix.join(prefix) : ''
152
152
  });
153
153
  }
154
154
  return fields;
@@ -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\nexport const getFields = ({\n adapter,\n alwaysInsertFields,\n collection,\n disablePayloadAccessControl,\n generateFileURL,\n prefix,\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: 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","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":"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"}
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: 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":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,YAAY,CAAA;AAgB1D,eAAO,MAAM,kBAAkB,kBACb,aAAa,sBACZ,MAAM,KAAG,MAyKzB,CAAA"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,YAAY,CAAA;AAgB1D,eAAO,MAAM,kBAAkB,kBACb,aAAa,sBACZ,MAAM,KAAG,MAgLzB,CAAA"}
package/dist/plugin.js CHANGED
@@ -9,7 +9,7 @@ import { getPreserveFileDataHook } from './hooks/preserveFileData.js';
9
9
  // 1. handleUpload, 2. handleDelete, 3. generateURL, 4. staticHandler
10
10
  // Optionally, the adapter can specify any Webpack config overrides if they are necessary.
11
11
  export const cloudStoragePlugin = (pluginOptions)=>(incomingConfig)=>{
12
- const { alwaysInsertFields, collections: allCollectionOptions, enabled } = pluginOptions;
12
+ const { alwaysInsertFields, collections: allCollectionOptions, enabled, useCompositePrefixes } = pluginOptions;
13
13
  const config = {
14
14
  ...incomingConfig
15
15
  };
@@ -32,7 +32,8 @@ export const cloudStoragePlugin = (pluginOptions)=>(incomingConfig)=>{
32
32
  collection: existingCollection,
33
33
  disablePayloadAccessControl: options.disablePayloadAccessControl,
34
34
  generateFileURL: options.generateFileURL,
35
- prefix: options.prefix
35
+ prefix: options.prefix,
36
+ useCompositePrefixes
36
37
  });
37
38
  return {
38
39
  ...existingCollection,
@@ -63,7 +64,8 @@ export const cloudStoragePlugin = (pluginOptions)=>(incomingConfig)=>{
63
64
  collection: existingCollection,
64
65
  disablePayloadAccessControl: options.disablePayloadAccessControl,
65
66
  generateFileURL: options.generateFileURL,
66
- prefix: options.prefix
67
+ prefix: options.prefix,
68
+ useCompositePrefixes
67
69
  });
68
70
  const handlers = [
69
71
  ...typeof existingCollection.upload === 'object' && Array.isArray(existingCollection.upload.handlers) ? existingCollection.upload.handlers : []
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { Config } from 'payload'\n\nimport type { AllowList, PluginOptions } from './types.js'\n\nimport { getFields } from './fields/getFields.js'\nimport { getAfterChangeHook } from './hooks/afterChange.js'\nimport { getAfterDeleteHook } from './hooks/afterDelete.js'\nimport { getPreserveFileDataHook } from './hooks/preserveFileData.js'\n\n// This plugin extends all targeted collections by offloading uploaded files\n// to cloud storage instead of solely storing files locally.\n\n// It is based on an adapter approach, where adapters can be written for any cloud provider.\n// Adapters are responsible for providing four actions that this plugin will use:\n// 1. handleUpload, 2. handleDelete, 3. generateURL, 4. staticHandler\n\n// Optionally, the adapter can specify any Webpack config overrides if they are necessary.\n\nexport const cloudStoragePlugin =\n (pluginOptions: PluginOptions) =>\n (incomingConfig: Config): Config => {\n const { alwaysInsertFields, collections: allCollectionOptions, enabled } = pluginOptions\n const config = { ...incomingConfig }\n\n // If disabled but alwaysInsertFields is true, only insert fields without full plugin functionality\n if (enabled === false) {\n if (alwaysInsertFields) {\n return {\n ...config,\n collections: (config.collections || []).map((existingCollection) => {\n const options = allCollectionOptions[existingCollection.slug]\n\n if (options) {\n // If adapter is provided, use it to get fields\n const adapter = options.adapter\n ? options.adapter({\n collection: existingCollection,\n prefix: options.prefix,\n })\n : undefined\n\n const fields = getFields({\n adapter,\n alwaysInsertFields: true,\n collection: existingCollection,\n disablePayloadAccessControl: options.disablePayloadAccessControl,\n generateFileURL: options.generateFileURL,\n prefix: options.prefix,\n })\n\n return {\n ...existingCollection,\n fields,\n }\n }\n\n return existingCollection\n }),\n }\n }\n\n return config\n }\n\n const initFunctions: Array<() => void> = []\n\n return {\n ...config,\n collections: (config.collections || []).map((existingCollection) => {\n const options = allCollectionOptions[existingCollection.slug]\n\n if (options?.adapter) {\n const adapter = options.adapter({\n collection: existingCollection,\n prefix: options.prefix,\n })\n\n if (adapter.onInit) {\n initFunctions.push(adapter.onInit)\n }\n\n const fields = getFields({\n adapter,\n collection: existingCollection,\n disablePayloadAccessControl: options.disablePayloadAccessControl,\n generateFileURL: options.generateFileURL,\n prefix: options.prefix,\n })\n\n const handlers = [\n ...(typeof existingCollection.upload === 'object' &&\n Array.isArray(existingCollection.upload.handlers)\n ? existingCollection.upload.handlers\n : []),\n ]\n\n if (!options.disablePayloadAccessControl) {\n handlers.push(adapter.staticHandler)\n // Else if disablePayloadAccessControl: true and clientUploads is used\n // Build the \"proxied\" handler that responses only when the file was requested by client upload in addDataAndFileToRequest\n } else if (adapter.clientUploads) {\n handlers.push((req, args) => {\n if ('clientUploadContext' in args.params) {\n return adapter.staticHandler(req, args)\n }\n })\n }\n\n const getSkipSafeFetchSetting = (): AllowList | boolean => {\n if (options.disablePayloadAccessControl) {\n return true\n }\n const isBooleanTrueSkipSafeFetch =\n typeof existingCollection.upload === 'object' &&\n existingCollection.upload.skipSafeFetch === true\n\n const isAllowListSkipSafeFetch =\n typeof existingCollection.upload === 'object' &&\n Array.isArray(existingCollection.upload.skipSafeFetch)\n\n if (isBooleanTrueSkipSafeFetch) {\n return true\n } else if (isAllowListSkipSafeFetch) {\n const existingSkipSafeFetch =\n typeof existingCollection.upload === 'object' &&\n Array.isArray(existingCollection.upload.skipSafeFetch)\n ? existingCollection.upload.skipSafeFetch\n : []\n\n const hasExactLocalhostMatch = existingSkipSafeFetch.some((entry) => {\n const entryKeys = Object.keys(entry)\n return entryKeys.length === 1 && entry.hostname === 'localhost'\n })\n\n const localhostEntry =\n process.env.NODE_ENV !== 'production' && !hasExactLocalhostMatch\n ? [{ hostname: 'localhost' }]\n : []\n\n return [...existingSkipSafeFetch, ...localhostEntry]\n }\n\n if (process.env.NODE_ENV !== 'production') {\n return [{ hostname: 'localhost' }]\n }\n\n return false\n }\n\n return {\n ...existingCollection,\n fields,\n hooks: {\n ...(existingCollection.hooks || {}),\n afterChange: [\n ...(existingCollection.hooks?.afterChange || []),\n getAfterChangeHook({ adapter, collection: existingCollection }),\n ],\n afterDelete: [\n ...(existingCollection.hooks?.afterDelete || []),\n getAfterDeleteHook({ adapter, collection: existingCollection }),\n ],\n beforeChange: [\n ...(existingCollection.hooks?.beforeChange || []),\n getPreserveFileDataHook(),\n ],\n },\n upload: {\n ...(typeof existingCollection.upload === 'object' ? existingCollection.upload : {}),\n adapter: adapter.name,\n disableLocalStorage:\n typeof options.disableLocalStorage === 'boolean'\n ? options.disableLocalStorage\n : true,\n handlers,\n skipSafeFetch: getSkipSafeFetchSetting(),\n },\n }\n }\n\n return existingCollection\n }),\n onInit: async (payload) => {\n initFunctions.forEach((fn) => fn())\n if (config.onInit) {\n await config.onInit(payload)\n }\n },\n }\n }\n"],"names":["getFields","getAfterChangeHook","getAfterDeleteHook","getPreserveFileDataHook","cloudStoragePlugin","pluginOptions","incomingConfig","alwaysInsertFields","collections","allCollectionOptions","enabled","config","map","existingCollection","options","slug","adapter","collection","prefix","undefined","fields","disablePayloadAccessControl","generateFileURL","initFunctions","onInit","push","handlers","upload","Array","isArray","staticHandler","clientUploads","req","args","params","getSkipSafeFetchSetting","isBooleanTrueSkipSafeFetch","skipSafeFetch","isAllowListSkipSafeFetch","existingSkipSafeFetch","hasExactLocalhostMatch","some","entry","entryKeys","Object","keys","length","hostname","localhostEntry","process","env","NODE_ENV","hooks","afterChange","afterDelete","beforeChange","name","disableLocalStorage","payload","forEach","fn"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { Config } from 'payload'\n\nimport type { AllowList, PluginOptions } from './types.js'\n\nimport { getFields } from './fields/getFields.js'\nimport { getAfterChangeHook } from './hooks/afterChange.js'\nimport { getAfterDeleteHook } from './hooks/afterDelete.js'\nimport { getPreserveFileDataHook } from './hooks/preserveFileData.js'\n\n// This plugin extends all targeted collections by offloading uploaded files\n// to cloud storage instead of solely storing files locally.\n\n// It is based on an adapter approach, where adapters can be written for any cloud provider.\n// Adapters are responsible for providing four actions that this plugin will use:\n// 1. handleUpload, 2. handleDelete, 3. generateURL, 4. staticHandler\n\n// Optionally, the adapter can specify any Webpack config overrides if they are necessary.\n\nexport const cloudStoragePlugin =\n (pluginOptions: PluginOptions) =>\n (incomingConfig: Config): Config => {\n const {\n alwaysInsertFields,\n collections: allCollectionOptions,\n enabled,\n useCompositePrefixes,\n } = pluginOptions\n const config = { ...incomingConfig }\n\n // If disabled but alwaysInsertFields is true, only insert fields without full plugin functionality\n if (enabled === false) {\n if (alwaysInsertFields) {\n return {\n ...config,\n collections: (config.collections || []).map((existingCollection) => {\n const options = allCollectionOptions[existingCollection.slug]\n\n if (options) {\n // If adapter is provided, use it to get fields\n const adapter = options.adapter\n ? options.adapter({\n collection: existingCollection,\n prefix: options.prefix,\n })\n : undefined\n\n const fields = getFields({\n adapter,\n alwaysInsertFields: true,\n collection: existingCollection,\n disablePayloadAccessControl: options.disablePayloadAccessControl,\n generateFileURL: options.generateFileURL,\n prefix: options.prefix,\n useCompositePrefixes,\n })\n\n return {\n ...existingCollection,\n fields,\n }\n }\n\n return existingCollection\n }),\n }\n }\n\n return config\n }\n\n const initFunctions: Array<() => void> = []\n\n return {\n ...config,\n collections: (config.collections || []).map((existingCollection) => {\n const options = allCollectionOptions[existingCollection.slug]\n\n if (options?.adapter) {\n const adapter = options.adapter({\n collection: existingCollection,\n prefix: options.prefix,\n })\n\n if (adapter.onInit) {\n initFunctions.push(adapter.onInit)\n }\n\n const fields = getFields({\n adapter,\n collection: existingCollection,\n disablePayloadAccessControl: options.disablePayloadAccessControl,\n generateFileURL: options.generateFileURL,\n prefix: options.prefix,\n useCompositePrefixes,\n })\n\n const handlers = [\n ...(typeof existingCollection.upload === 'object' &&\n Array.isArray(existingCollection.upload.handlers)\n ? existingCollection.upload.handlers\n : []),\n ]\n\n if (!options.disablePayloadAccessControl) {\n handlers.push(adapter.staticHandler)\n // Else if disablePayloadAccessControl: true and clientUploads is used\n // Build the \"proxied\" handler that responses only when the file was requested by client upload in addDataAndFileToRequest\n } else if (adapter.clientUploads) {\n handlers.push((req, args) => {\n if ('clientUploadContext' in args.params) {\n return adapter.staticHandler(req, args)\n }\n })\n }\n\n const getSkipSafeFetchSetting = (): AllowList | boolean => {\n if (options.disablePayloadAccessControl) {\n return true\n }\n const isBooleanTrueSkipSafeFetch =\n typeof existingCollection.upload === 'object' &&\n existingCollection.upload.skipSafeFetch === true\n\n const isAllowListSkipSafeFetch =\n typeof existingCollection.upload === 'object' &&\n Array.isArray(existingCollection.upload.skipSafeFetch)\n\n if (isBooleanTrueSkipSafeFetch) {\n return true\n } else if (isAllowListSkipSafeFetch) {\n const existingSkipSafeFetch =\n typeof existingCollection.upload === 'object' &&\n Array.isArray(existingCollection.upload.skipSafeFetch)\n ? existingCollection.upload.skipSafeFetch\n : []\n\n const hasExactLocalhostMatch = existingSkipSafeFetch.some((entry) => {\n const entryKeys = Object.keys(entry)\n return entryKeys.length === 1 && entry.hostname === 'localhost'\n })\n\n const localhostEntry =\n process.env.NODE_ENV !== 'production' && !hasExactLocalhostMatch\n ? [{ hostname: 'localhost' }]\n : []\n\n return [...existingSkipSafeFetch, ...localhostEntry]\n }\n\n if (process.env.NODE_ENV !== 'production') {\n return [{ hostname: 'localhost' }]\n }\n\n return false\n }\n\n return {\n ...existingCollection,\n fields,\n hooks: {\n ...(existingCollection.hooks || {}),\n afterChange: [\n ...(existingCollection.hooks?.afterChange || []),\n getAfterChangeHook({ adapter, collection: existingCollection }),\n ],\n afterDelete: [\n ...(existingCollection.hooks?.afterDelete || []),\n getAfterDeleteHook({ adapter, collection: existingCollection }),\n ],\n beforeChange: [\n ...(existingCollection.hooks?.beforeChange || []),\n getPreserveFileDataHook(),\n ],\n },\n upload: {\n ...(typeof existingCollection.upload === 'object' ? existingCollection.upload : {}),\n adapter: adapter.name,\n disableLocalStorage:\n typeof options.disableLocalStorage === 'boolean'\n ? options.disableLocalStorage\n : true,\n handlers,\n skipSafeFetch: getSkipSafeFetchSetting(),\n },\n }\n }\n\n return existingCollection\n }),\n onInit: async (payload) => {\n initFunctions.forEach((fn) => fn())\n if (config.onInit) {\n await config.onInit(payload)\n }\n },\n }\n }\n"],"names":["getFields","getAfterChangeHook","getAfterDeleteHook","getPreserveFileDataHook","cloudStoragePlugin","pluginOptions","incomingConfig","alwaysInsertFields","collections","allCollectionOptions","enabled","useCompositePrefixes","config","map","existingCollection","options","slug","adapter","collection","prefix","undefined","fields","disablePayloadAccessControl","generateFileURL","initFunctions","onInit","push","handlers","upload","Array","isArray","staticHandler","clientUploads","req","args","params","getSkipSafeFetchSetting","isBooleanTrueSkipSafeFetch","skipSafeFetch","isAllowListSkipSafeFetch","existingSkipSafeFetch","hasExactLocalhostMatch","some","entry","entryKeys","Object","keys","length","hostname","localhostEntry","process","env","NODE_ENV","hooks","afterChange","afterDelete","beforeChange","name","disableLocalStorage","payload","forEach","fn"],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -104,5 +104,15 @@ export interface PluginOptions {
104
104
  * Default: true
105
105
  */
106
106
  enabled?: boolean;
107
+ /**
108
+ * When true (compositional prefixes), the stored `prefix` field is only the
109
+ * document-level segment; the collection prefix comes from plugin options and
110
+ * must not be pre-filled as the field default.
111
+ *
112
+ * Set by storage adapters that support compositional prefixes (e.g. S3, Azure, R2, Vercel Blob, GCS).
113
+ *
114
+ * @default false
115
+ */
116
+ useCompositePrefixes?: boolean;
107
117
  }
108
118
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type {\n CollectionConfig,\n Field,\n FileData,\n ImageSize,\n PayloadRequest,\n TypeWithID,\n UploadCollectionSlug,\n} from 'payload'\n\nexport interface File {\n buffer: Buffer\n clientUploadContext?: unknown\n filename: string\n filesize: number\n mimeType: string\n tempFilePath?: string\n}\n\nexport type ClientUploadsAccess = (args: {\n collectionSlug: UploadCollectionSlug\n req: PayloadRequest\n}) => boolean | Promise<boolean>\n\nexport type ClientUploadsConfig =\n | {\n access?: ClientUploadsAccess\n }\n | boolean\n\nexport type HandleUpload = (args: {\n clientUploadContext: unknown\n collection: CollectionConfig\n data: any\n file: File\n req: PayloadRequest\n}) =>\n | Partial<FileData & TypeWithID>\n | Promise<Partial<FileData & TypeWithID>>\n | Promise<void>\n | void\n\nexport interface TypeWithPrefix {\n prefix?: string\n}\n\nexport type HandleDelete = (args: {\n collection: CollectionConfig\n doc: FileData & TypeWithID & TypeWithPrefix\n filename: string\n req: PayloadRequest\n}) => Promise<void> | void\n\nexport type GenerateURL = (args: {\n collection: CollectionConfig\n data: any\n filename: string\n prefix?: string\n}) => Promise<string> | string\n\nexport type StaticHandler = (\n req: PayloadRequest,\n args: {\n doc?: TypeWithID\n headers?: Headers\n params: { clientUploadContext?: unknown; collection: string; filename: string; prefix?: string }\n },\n) => Promise<Response> | Response\n\nexport interface GeneratedAdapter {\n clientUploads?: ClientUploadsConfig\n /**\n * Additional fields to be injected into the base collection and image sizes\n */\n fields?: Field[]\n /**\n * Generates the public URL for a file\n */\n generateURL?: GenerateURL\n handleDelete: HandleDelete\n handleUpload: HandleUpload\n name: string\n onInit?: () => void\n staticHandler: StaticHandler\n}\n\nexport type Adapter = (args: { collection: CollectionConfig; prefix?: string }) => GeneratedAdapter\n\nexport type AllowList = Array<{\n hostname: string\n pathname?: string\n port?: string\n protocol?: 'http' | 'https'\n search?: string\n}>\n\nexport type GenerateFileURL = (args: {\n collection: CollectionConfig\n filename: string\n prefix?: string\n size?: ImageSize\n}) => Promise<string> | string\n\nexport interface CollectionOptions {\n adapter: Adapter | null\n disableLocalStorage?: boolean\n disablePayloadAccessControl?: true\n generateFileURL?: GenerateFileURL\n prefix?: string\n}\n\nexport interface PluginOptions {\n /**\n * When enabled, fields (like the prefix field) will always be inserted into\n * the collection schema regardless of whether the plugin is enabled. This\n * ensures a consistent schema across all environments.\n *\n * This will be enabled by default in Payload v4.\n *\n * @default false\n */\n alwaysInsertFields?: boolean\n collections: Partial<Record<UploadCollectionSlug, CollectionOptions>>\n /**\n * Whether or not to enable the plugin\n *\n * Default: true\n */\n enabled?: boolean\n}\n"],"names":[],"mappings":"AA+GA,WAkBC"}
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type {\n CollectionConfig,\n Field,\n FileData,\n ImageSize,\n PayloadRequest,\n TypeWithID,\n UploadCollectionSlug,\n} from 'payload'\n\nexport interface File {\n buffer: Buffer\n clientUploadContext?: unknown\n filename: string\n filesize: number\n mimeType: string\n tempFilePath?: string\n}\n\nexport type ClientUploadsAccess = (args: {\n collectionSlug: UploadCollectionSlug\n req: PayloadRequest\n}) => boolean | Promise<boolean>\n\nexport type ClientUploadsConfig =\n | {\n access?: ClientUploadsAccess\n }\n | boolean\n\nexport type HandleUpload = (args: {\n clientUploadContext: unknown\n collection: CollectionConfig\n data: any\n file: File\n req: PayloadRequest\n}) =>\n | Partial<FileData & TypeWithID>\n | Promise<Partial<FileData & TypeWithID>>\n | Promise<void>\n | void\n\nexport interface TypeWithPrefix {\n prefix?: string\n}\n\nexport type HandleDelete = (args: {\n collection: CollectionConfig\n doc: FileData & TypeWithID & TypeWithPrefix\n filename: string\n req: PayloadRequest\n}) => Promise<void> | void\n\nexport type GenerateURL = (args: {\n collection: CollectionConfig\n data: any\n filename: string\n prefix?: string\n}) => Promise<string> | string\n\nexport type StaticHandler = (\n req: PayloadRequest,\n args: {\n doc?: TypeWithID\n headers?: Headers\n params: { clientUploadContext?: unknown; collection: string; filename: string; prefix?: string }\n },\n) => Promise<Response> | Response\n\nexport interface GeneratedAdapter {\n clientUploads?: ClientUploadsConfig\n /**\n * Additional fields to be injected into the base collection and image sizes\n */\n fields?: Field[]\n /**\n * Generates the public URL for a file\n */\n generateURL?: GenerateURL\n handleDelete: HandleDelete\n handleUpload: HandleUpload\n name: string\n onInit?: () => void\n staticHandler: StaticHandler\n}\n\nexport type Adapter = (args: { collection: CollectionConfig; prefix?: string }) => GeneratedAdapter\n\nexport type AllowList = Array<{\n hostname: string\n pathname?: string\n port?: string\n protocol?: 'http' | 'https'\n search?: string\n}>\n\nexport type GenerateFileURL = (args: {\n collection: CollectionConfig\n filename: string\n prefix?: string\n size?: ImageSize\n}) => Promise<string> | string\n\nexport interface CollectionOptions {\n adapter: Adapter | null\n disableLocalStorage?: boolean\n disablePayloadAccessControl?: true\n generateFileURL?: GenerateFileURL\n prefix?: string\n}\n\nexport interface PluginOptions {\n /**\n * When enabled, fields (like the prefix field) will always be inserted into\n * the collection schema regardless of whether the plugin is enabled. This\n * ensures a consistent schema across all environments.\n *\n * This will be enabled by default in Payload v4.\n *\n * @default false\n */\n alwaysInsertFields?: boolean\n collections: Partial<Record<UploadCollectionSlug, CollectionOptions>>\n /**\n * Whether or not to enable the plugin\n *\n * Default: true\n */\n enabled?: boolean\n /**\n * When true (compositional prefixes), the stored `prefix` field is only the\n * document-level segment; the collection prefix comes from plugin options and\n * must not be pre-filled as the field default.\n *\n * Set by storage adapters that support compositional prefixes (e.g. S3, Azure, R2, Vercel Blob, GCS).\n *\n * @default false\n */\n useCompositePrefixes?: boolean\n}\n"],"names":[],"mappings":"AA+GA,WA4BC"}
@@ -0,0 +1,16 @@
1
+ type GetFileKeyArgs = {
2
+ collectionPrefix?: string;
3
+ docPrefix?: string;
4
+ filename: string;
5
+ useCompositePrefixes?: boolean;
6
+ };
7
+ /**
8
+ * Computes the file key (path) for storage.
9
+ *
10
+ * In non-composite mode (useCompositePrefixes: false), docPrefix overrides collectionPrefix.
11
+ * In composite mode (useCompositePrefixes: true), both are combined.
12
+ * Both prefixes are passed through {@link sanitizePrefix} so keys stay normalized.
13
+ */
14
+ export declare function getFileKey({ collectionPrefix, docPrefix, filename, useCompositePrefixes, }: GetFileKeyArgs): string;
15
+ export {};
16
+ //# sourceMappingURL=getFileKey.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getFileKey.d.ts","sourceRoot":"","sources":["../../src/utilities/getFileKey.ts"],"names":[],"mappings":"AAIA,KAAK,cAAc,GAAG;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EACzB,gBAAgB,EAChB,SAAS,EACT,QAAQ,EACR,oBAA4B,GAC7B,EAAE,cAAc,GAAG,MAAM,CASzB"}
@@ -0,0 +1,18 @@
1
+ import path from 'path';
2
+ import { sanitizePrefix } from './sanitizePrefix.js';
3
+ /**
4
+ * Computes the file key (path) for storage.
5
+ *
6
+ * In non-composite mode (useCompositePrefixes: false), docPrefix overrides collectionPrefix.
7
+ * In composite mode (useCompositePrefixes: true), both are combined.
8
+ * Both prefixes are passed through {@link sanitizePrefix} so keys stay normalized.
9
+ */ export function getFileKey({ collectionPrefix, docPrefix, filename, useCompositePrefixes = false }) {
10
+ const safeCollectionPrefix = sanitizePrefix(collectionPrefix || '');
11
+ const safeDocPrefix = sanitizePrefix(docPrefix || '');
12
+ if (useCompositePrefixes) {
13
+ return path.posix.join(safeCollectionPrefix, safeDocPrefix, filename);
14
+ }
15
+ return path.posix.join(safeDocPrefix || safeCollectionPrefix, filename);
16
+ }
17
+
18
+ //# sourceMappingURL=getFileKey.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/getFileKey.ts"],"sourcesContent":["import path from 'path'\n\nimport { sanitizePrefix } from './sanitizePrefix.js'\n\ntype GetFileKeyArgs = {\n collectionPrefix?: string\n docPrefix?: string\n filename: string\n useCompositePrefixes?: boolean\n}\n\n/**\n * Computes the file key (path) for storage.\n *\n * In non-composite mode (useCompositePrefixes: false), docPrefix overrides collectionPrefix.\n * In composite mode (useCompositePrefixes: true), both are combined.\n * Both prefixes are passed through {@link sanitizePrefix} so keys stay normalized.\n */\nexport function getFileKey({\n collectionPrefix,\n docPrefix,\n filename,\n useCompositePrefixes = false,\n}: GetFileKeyArgs): string {\n const safeCollectionPrefix = sanitizePrefix(collectionPrefix || '')\n const safeDocPrefix = sanitizePrefix(docPrefix || '')\n\n if (useCompositePrefixes) {\n return path.posix.join(safeCollectionPrefix, safeDocPrefix, filename)\n }\n\n return path.posix.join(safeDocPrefix || safeCollectionPrefix, filename)\n}\n"],"names":["path","sanitizePrefix","getFileKey","collectionPrefix","docPrefix","filename","useCompositePrefixes","safeCollectionPrefix","safeDocPrefix","posix","join"],"mappings":"AAAA,OAAOA,UAAU,OAAM;AAEvB,SAASC,cAAc,QAAQ,sBAAqB;AASpD;;;;;;CAMC,GACD,OAAO,SAASC,WAAW,EACzBC,gBAAgB,EAChBC,SAAS,EACTC,QAAQ,EACRC,uBAAuB,KAAK,EACb;IACf,MAAMC,uBAAuBN,eAAeE,oBAAoB;IAChE,MAAMK,gBAAgBP,eAAeG,aAAa;IAElD,IAAIE,sBAAsB;QACxB,OAAON,KAAKS,KAAK,CAACC,IAAI,CAACH,sBAAsBC,eAAeH;IAC9D;IAEA,OAAOL,KAAKS,KAAK,CAACC,IAAI,CAACF,iBAAiBD,sBAAsBF;AAChE"}
@@ -0,0 +1,114 @@
1
+ import { getFileKey } from './getFileKey.js';
2
+ import { describe, expect, it } from 'vitest';
3
+ describe('getFileKey', ()=>{
4
+ describe('non-composite mode (useCompositePrefixes: false)', ()=>{
5
+ it('should use docPrefix when provided, ignoring collectionPrefix', ()=>{
6
+ const result = getFileKey({
7
+ collectionPrefix: 'collection',
8
+ docPrefix: 'document',
9
+ filename: 'test.png',
10
+ useCompositePrefixes: false
11
+ });
12
+ expect(result).toBe('document/test.png');
13
+ });
14
+ it('should fallback to collectionPrefix when docPrefix is empty', ()=>{
15
+ const result = getFileKey({
16
+ collectionPrefix: 'collection',
17
+ docPrefix: '',
18
+ filename: 'test.png',
19
+ useCompositePrefixes: false
20
+ });
21
+ expect(result).toBe('collection/test.png');
22
+ });
23
+ it('should fallback to collectionPrefix when docPrefix is undefined', ()=>{
24
+ const result = getFileKey({
25
+ collectionPrefix: 'collection',
26
+ filename: 'test.png',
27
+ useCompositePrefixes: false
28
+ });
29
+ expect(result).toBe('collection/test.png');
30
+ });
31
+ it('should return only filename when both prefixes are empty', ()=>{
32
+ const result = getFileKey({
33
+ filename: 'test.png',
34
+ useCompositePrefixes: false
35
+ });
36
+ expect(result).toBe('test.png');
37
+ });
38
+ });
39
+ describe('composite mode (useCompositePrefixes: true)', ()=>{
40
+ it('should combine collectionPrefix and docPrefix', ()=>{
41
+ const result = getFileKey({
42
+ collectionPrefix: 'collection',
43
+ docPrefix: 'document',
44
+ filename: 'test.png',
45
+ useCompositePrefixes: true
46
+ });
47
+ expect(result).toBe('collection/document/test.png');
48
+ });
49
+ it('should work with only collectionPrefix', ()=>{
50
+ const result = getFileKey({
51
+ collectionPrefix: 'collection',
52
+ filename: 'test.png',
53
+ useCompositePrefixes: true
54
+ });
55
+ expect(result).toBe('collection/test.png');
56
+ });
57
+ it('should work with only docPrefix', ()=>{
58
+ const result = getFileKey({
59
+ docPrefix: 'document',
60
+ filename: 'test.png',
61
+ useCompositePrefixes: true
62
+ });
63
+ expect(result).toBe('document/test.png');
64
+ });
65
+ it('should return only filename when both prefixes are empty', ()=>{
66
+ const result = getFileKey({
67
+ filename: 'test.png',
68
+ useCompositePrefixes: true
69
+ });
70
+ expect(result).toBe('test.png');
71
+ });
72
+ });
73
+ describe('sanitization', ()=>{
74
+ it('should remove path traversal segments from collectionPrefix', ()=>{
75
+ const result = getFileKey({
76
+ collectionPrefix: '../../../etc',
77
+ filename: 'test.png',
78
+ useCompositePrefixes: false
79
+ });
80
+ expect(result).toBe('etc/test.png');
81
+ expect(result).not.toContain('..');
82
+ });
83
+ it('should remove path traversal segments from docPrefix', ()=>{
84
+ const result = getFileKey({
85
+ docPrefix: 'a/../../outside',
86
+ filename: 'test.png',
87
+ useCompositePrefixes: false
88
+ });
89
+ expect(result).toBe('a/outside/test.png');
90
+ expect(result).not.toContain('..');
91
+ });
92
+ it('should remove control characters from prefixes', ()=>{
93
+ const result = getFileKey({
94
+ collectionPrefix: 'test\x00\x1fprefix',
95
+ filename: 'test.png',
96
+ useCompositePrefixes: false
97
+ });
98
+ expect(result).toBe('testprefix/test.png');
99
+ expect(result).not.toMatch(/[\x00-\x1f]/);
100
+ });
101
+ it('should sanitize both prefixes in composite mode', ()=>{
102
+ const result = getFileKey({
103
+ collectionPrefix: '../collection',
104
+ docPrefix: '../../doc',
105
+ filename: 'test.png',
106
+ useCompositePrefixes: true
107
+ });
108
+ expect(result).toBe('collection/doc/test.png');
109
+ expect(result).not.toContain('..');
110
+ });
111
+ });
112
+ });
113
+
114
+ //# sourceMappingURL=getFileKey.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/getFileKey.spec.ts"],"sourcesContent":["import { getFileKey } from './getFileKey.js'\nimport { describe, expect, it } from 'vitest'\n\ndescribe('getFileKey', () => {\n describe('non-composite mode (useCompositePrefixes: false)', () => {\n it('should use docPrefix when provided, ignoring collectionPrefix', () => {\n const result = getFileKey({\n collectionPrefix: 'collection',\n docPrefix: 'document',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toBe('document/test.png')\n })\n\n it('should fallback to collectionPrefix when docPrefix is empty', () => {\n const result = getFileKey({\n collectionPrefix: 'collection',\n docPrefix: '',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toBe('collection/test.png')\n })\n\n it('should fallback to collectionPrefix when docPrefix is undefined', () => {\n const result = getFileKey({\n collectionPrefix: 'collection',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toBe('collection/test.png')\n })\n\n it('should return only filename when both prefixes are empty', () => {\n const result = getFileKey({\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toBe('test.png')\n })\n })\n\n describe('composite mode (useCompositePrefixes: true)', () => {\n it('should combine collectionPrefix and docPrefix', () => {\n const result = getFileKey({\n collectionPrefix: 'collection',\n docPrefix: 'document',\n filename: 'test.png',\n useCompositePrefixes: true,\n })\n expect(result).toBe('collection/document/test.png')\n })\n\n it('should work with only collectionPrefix', () => {\n const result = getFileKey({\n collectionPrefix: 'collection',\n filename: 'test.png',\n useCompositePrefixes: true,\n })\n expect(result).toBe('collection/test.png')\n })\n\n it('should work with only docPrefix', () => {\n const result = getFileKey({\n docPrefix: 'document',\n filename: 'test.png',\n useCompositePrefixes: true,\n })\n expect(result).toBe('document/test.png')\n })\n\n it('should return only filename when both prefixes are empty', () => {\n const result = getFileKey({\n filename: 'test.png',\n useCompositePrefixes: true,\n })\n expect(result).toBe('test.png')\n })\n })\n\n describe('sanitization', () => {\n it('should remove path traversal segments from collectionPrefix', () => {\n const result = getFileKey({\n collectionPrefix: '../../../etc',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toBe('etc/test.png')\n expect(result).not.toContain('..')\n })\n\n it('should remove path traversal segments from docPrefix', () => {\n const result = getFileKey({\n docPrefix: 'a/../../outside',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toBe('a/outside/test.png')\n expect(result).not.toContain('..')\n })\n\n it('should remove control characters from prefixes', () => {\n const result = getFileKey({\n collectionPrefix: 'test\\x00\\x1fprefix',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toBe('testprefix/test.png')\n expect(result).not.toMatch(/[\\x00-\\x1f]/)\n })\n\n it('should sanitize both prefixes in composite mode', () => {\n const result = getFileKey({\n collectionPrefix: '../collection',\n docPrefix: '../../doc',\n filename: 'test.png',\n useCompositePrefixes: true,\n })\n expect(result).toBe('collection/doc/test.png')\n expect(result).not.toContain('..')\n })\n })\n})\n"],"names":["getFileKey","describe","expect","it","result","collectionPrefix","docPrefix","filename","useCompositePrefixes","toBe","not","toContain","toMatch"],"mappings":"AAAA,SAASA,UAAU,QAAQ,kBAAiB;AAC5C,SAASC,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAQ;AAE7CF,SAAS,cAAc;IACrBA,SAAS,oDAAoD;QAC3DE,GAAG,iEAAiE;YAClE,MAAMC,SAASJ,WAAW;gBACxBK,kBAAkB;gBAClBC,WAAW;gBACXC,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,IAAI,CAAC;QACtB;QAEAN,GAAG,+DAA+D;YAChE,MAAMC,SAASJ,WAAW;gBACxBK,kBAAkB;gBAClBC,WAAW;gBACXC,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,IAAI,CAAC;QACtB;QAEAN,GAAG,mEAAmE;YACpE,MAAMC,SAASJ,WAAW;gBACxBK,kBAAkB;gBAClBE,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,IAAI,CAAC;QACtB;QAEAN,GAAG,4DAA4D;YAC7D,MAAMC,SAASJ,WAAW;gBACxBO,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,IAAI,CAAC;QACtB;IACF;IAEAR,SAAS,+CAA+C;QACtDE,GAAG,iDAAiD;YAClD,MAAMC,SAASJ,WAAW;gBACxBK,kBAAkB;gBAClBC,WAAW;gBACXC,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,IAAI,CAAC;QACtB;QAEAN,GAAG,0CAA0C;YAC3C,MAAMC,SAASJ,WAAW;gBACxBK,kBAAkB;gBAClBE,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,IAAI,CAAC;QACtB;QAEAN,GAAG,mCAAmC;YACpC,MAAMC,SAASJ,WAAW;gBACxBM,WAAW;gBACXC,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,IAAI,CAAC;QACtB;QAEAN,GAAG,4DAA4D;YAC7D,MAAMC,SAASJ,WAAW;gBACxBO,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,IAAI,CAAC;QACtB;IACF;IAEAR,SAAS,gBAAgB;QACvBE,GAAG,+DAA+D;YAChE,MAAMC,SAASJ,WAAW;gBACxBK,kBAAkB;gBAClBE,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,IAAI,CAAC;YACpBP,OAAOE,QAAQM,GAAG,CAACC,SAAS,CAAC;QAC/B;QAEAR,GAAG,wDAAwD;YACzD,MAAMC,SAASJ,WAAW;gBACxBM,WAAW;gBACXC,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,IAAI,CAAC;YACpBP,OAAOE,QAAQM,GAAG,CAACC,SAAS,CAAC;QAC/B;QAEAR,GAAG,kDAAkD;YACnD,MAAMC,SAASJ,WAAW;gBACxBK,kBAAkB;gBAClBE,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,IAAI,CAAC;YACpBP,OAAOE,QAAQM,GAAG,CAACE,OAAO,CAAC;QAC7B;QAEAT,GAAG,mDAAmD;YACpD,MAAMC,SAASJ,WAAW;gBACxBK,kBAAkB;gBAClBC,WAAW;gBACXC,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,IAAI,CAAC;YACpBP,OAAOE,QAAQM,GAAG,CAACC,SAAS,CAAC;QAC/B;IACF;AACF"}
@@ -8,9 +8,7 @@ import type { CollectionConfig, PayloadRequest } from 'payload';
8
8
  * 2. `clientUploadContext.prefix`
9
9
  * 3. Stored document `prefix` from the database
10
10
  *
11
- * Query / client input is decoded once; malformed and multi-encoded values are
12
- * rejected. Sanitization then normalizes slashes, removes `.` / `..` path
13
- * traversal segments, strips leading slashes, and removes control characters.
11
+ * Resolved values are passed through `sanitizePrefix`.
14
12
  */
15
13
  export declare function getFilePrefix({ clientUploadContext, collection, filename, prefixQueryParam, req, }: {
16
14
  clientUploadContext?: unknown;
@@ -1 +1 @@
1
- {"version":3,"file":"getFilePrefix.d.ts","sourceRoot":"","sources":["../../src/utilities/getFilePrefix.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAgB,MAAM,SAAS,CAAA;AA+B7E;;;;;;;;;;;;GAYG;AACH,wBAAsB,aAAa,CAAC,EAClC,mBAAmB,EACnB,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,GAAG,GACJ,EAAE;IACD,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,UAAU,EAAE,gBAAgB,CAAA;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,GAAG,EAAE,cAAc,CAAA;CACpB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoClB"}
1
+ {"version":3,"file":"getFilePrefix.d.ts","sourceRoot":"","sources":["../../src/utilities/getFilePrefix.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAgB,MAAM,SAAS,CAAA;AAI7E;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CAAC,EAClC,mBAAmB,EACnB,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,GAAG,GACJ,EAAE;IACD,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,UAAU,EAAE,gBAAgB,CAAA;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,GAAG,EAAE,cAAc,CAAA;CACpB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoClB"}
@@ -1,19 +1,4 @@
1
- /**
2
- * Normalizes a storage prefix to ensure only valid path segments are included.
3
- */ function sanitizePrefix(prefix) {
4
- let decodedPrefix;
5
- try {
6
- decodedPrefix = decodeURIComponent(prefix);
7
- } catch {
8
- return '';
9
- }
10
- // Reject multi-encoded values (e.g. `%252f`) by allowing only a single decode pass.
11
- if (/%[0-9a-f]{2}/i.test(decodedPrefix)) {
12
- return '';
13
- }
14
- return decodedPrefix.replace(/\\/g, '/').split('/').filter((segment)=>segment !== '..' && segment !== '.').join('/').replace(/^\/+/, '')// eslint-disable-next-line no-control-regex
15
- .replace(/[\x00-\x1f\x80-\x9f]/g, '');
16
- }
1
+ import { sanitizePrefix } from './sanitizePrefix.js';
17
2
  /**
18
3
  * Resolves the file prefix from the highest-priority available source and
19
4
  * always returns a sanitized value.
@@ -23,9 +8,7 @@
23
8
  * 2. `clientUploadContext.prefix`
24
9
  * 3. Stored document `prefix` from the database
25
10
  *
26
- * Query / client input is decoded once; malformed and multi-encoded values are
27
- * rejected. Sanitization then normalizes slashes, removes `.` / `..` path
28
- * traversal segments, strips leading slashes, and removes control characters.
11
+ * Resolved values are passed through `sanitizePrefix`.
29
12
  */ export async function getFilePrefix({ clientUploadContext, collection, filename, prefixQueryParam, req }) {
30
13
  if (typeof prefixQueryParam === 'string') {
31
14
  return sanitizePrefix(prefixQueryParam);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/getFilePrefix.ts"],"sourcesContent":["import type { CollectionConfig, PayloadRequest, UploadConfig } from 'payload'\n\n/**\n * Normalizes a storage prefix to ensure only valid path segments are included.\n */\nfunction 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\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 * Query / client input is decoded once; malformed and multi-encoded values are\n * rejected. Sanitization then normalizes slashes, removes `.` / `..` path\n * traversal segments, strips leading slashes, and removes control characters.\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","prefix","decodedPrefix","decodeURIComponent","test","replace","split","filter","segment","join","getFilePrefix","clientUploadContext","collection","filename","prefixQueryParam","req","imageSizes","upload","files","payload","find","slug","depth","draft","limit","pagination","where","or","equals","map","imageSize","name","docs"],"mappings":"AAEA;;CAEC,GACD,SAASA,eAAeC,MAAc;IACpC,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;AAEA;;;;;;;;;;;;CAYC,GACD,OAAO,eAAeK,cAAc,EAClCC,mBAAmB,EACnBC,UAAU,EACVC,QAAQ,EACRC,gBAAgB,EAChBC,GAAG,EAOJ;IACC,IAAI,OAAOD,qBAAqB,UAAU;QACxC,OAAOd,eAAec;IACxB;IAEA,mDAAmD;IACnD,IACEH,uBACA,OAAOA,wBAAwB,YAC/B,YAAYA,uBACZ,OAAOA,oBAAoBV,MAAM,KAAK,UACtC;QACA,OAAOD,eAAeW,oBAAoBV,MAAM;IAClD;IAEA,MAAMe,aAAa,AAACJ,YAAYK,QAAyBD,cAAc,EAAE;IAEzE,MAAME,QAAQ,MAAMH,IAAII,OAAO,CAACC,IAAI,CAAC;QACnCR,YAAYA,WAAWS,IAAI;QAC3BC,OAAO;QACPC,OAAO;QACPC,OAAO;QACPC,YAAY;QACZC,OAAO;YACLC,IAAI;gBACF;oBACEd,UAAU;wBAAEe,QAAQf;oBAAS;gBAC/B;mBACGG,WAAWa,GAAG,CAAC,CAACC,YAAe,CAAA;wBAChC,CAAC,CAAC,MAAM,EAAEA,UAAUC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE;4BAAEH,QAAQf;wBAAS;oBAC3D,CAAA;aACD;QACH;IACF;IACA,MAAMZ,SAASiB,OAAOc,MAAM,CAAC,EAAE,EAAE/B;IACjC,OAAOA,SAASD,eAAeC,UAAoB;AACrD"}
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"}
@@ -0,0 +1,10 @@
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;
10
+ //# sourceMappingURL=sanitizePrefix.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,23 @@
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
+ }
22
+
23
+ //# sourceMappingURL=sanitizePrefix.js.map
@@ -0,0 +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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payloadcms/plugin-cloud-storage",
3
- "version": "3.83.0-canary.0",
3
+ "version": "3.83.0-canary.2",
4
4
  "description": "The official cloud storage plugin for Payload CMS",
5
5
  "homepage": "https://payloadcms.com",
6
6
  "repository": {
@@ -51,18 +51,18 @@
51
51
  "dependencies": {
52
52
  "find-node-modules": "^2.1.3",
53
53
  "range-parser": "^1.2.1",
54
- "@payloadcms/ui": "3.83.0-canary.0"
54
+ "@payloadcms/ui": "3.83.0-canary.2"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/find-node-modules": "^2.1.2",
58
58
  "@types/react": "19.2.9",
59
59
  "@types/react-dom": "19.2.3",
60
- "payload": "3.83.0-canary.0"
60
+ "payload": "3.83.0-canary.2"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "react": "^19.0.1 || ^19.1.2 || ^19.2.1",
64
64
  "react-dom": "^19.0.1 || ^19.1.2 || ^19.2.1",
65
- "payload": "3.83.0-canary.0"
65
+ "payload": "3.83.0-canary.2"
66
66
  },
67
67
  "publishConfig": {
68
68
  "registry": "https://registry.npmjs.org/"