@payloadcms/plugin-cloud-storage 4.0.0-internal.b8e7c89 → 4.0.0-internal.bf4bcce

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) 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 +1 -5
  11. package/dist/fields/getFields.d.ts.map +1 -1
  12. package/dist/fields/getFields.js +118 -24
  13. package/dist/fields/getFields.js.map +1 -1
  14. package/dist/hooks/afterChange.d.ts +5 -1
  15. package/dist/hooks/afterChange.d.ts.map +1 -1
  16. package/dist/hooks/afterChange.js +115 -13
  17. package/dist/hooks/afterChange.js.map +1 -1
  18. package/dist/hooks/afterDelete.d.ts +3 -1
  19. package/dist/hooks/afterDelete.d.ts.map +1 -1
  20. package/dist/hooks/afterDelete.js +26 -4
  21. package/dist/hooks/afterDelete.js.map +1 -1
  22. package/dist/hooks/afterRead.d.ts +6 -1
  23. package/dist/hooks/afterRead.d.ts.map +1 -1
  24. package/dist/hooks/afterRead.js +34 -7
  25. package/dist/hooks/afterRead.js.map +1 -1
  26. package/dist/hooks/beforeChange.d.ts +2 -1
  27. package/dist/hooks/beforeChange.d.ts.map +1 -1
  28. package/dist/hooks/beforeChange.js +18 -4
  29. package/dist/hooks/beforeChange.js.map +1 -1
  30. package/dist/hooks/normalizeUploadPrefix.d.ts +11 -0
  31. package/dist/hooks/normalizeUploadPrefix.d.ts.map +1 -0
  32. package/dist/hooks/normalizeUploadPrefix.js +42 -0
  33. package/dist/hooks/normalizeUploadPrefix.js.map +1 -0
  34. package/dist/hooks/publicOriginalURL.d.ts +8 -0
  35. package/dist/hooks/publicOriginalURL.d.ts.map +1 -0
  36. package/dist/hooks/publicOriginalURL.js +34 -0
  37. package/dist/hooks/publicOriginalURL.js.map +1 -0
  38. package/dist/plugin.d.ts.map +1 -1
  39. package/dist/plugin.js +72 -42
  40. package/dist/plugin.js.map +1 -1
  41. package/dist/types.d.ts +45 -12
  42. package/dist/types.d.ts.map +1 -1
  43. package/dist/types.js.map +1 -1
  44. package/dist/utilities/buildPrefixWithObjectKey.d.ts +8 -0
  45. package/dist/utilities/buildPrefixWithObjectKey.d.ts.map +1 -0
  46. package/dist/utilities/buildPrefixWithObjectKey.js +13 -0
  47. package/dist/utilities/buildPrefixWithObjectKey.js.map +1 -0
  48. package/dist/utilities/buildStoragePathData.d.ts +49 -0
  49. package/dist/utilities/buildStoragePathData.d.ts.map +1 -0
  50. package/dist/utilities/buildStoragePathData.js +67 -0
  51. package/dist/utilities/buildStoragePathData.js.map +1 -0
  52. package/dist/utilities/createFileOperations.d.ts +13 -0
  53. package/dist/utilities/createFileOperations.d.ts.map +1 -0
  54. package/dist/utilities/createFileOperations.js +258 -0
  55. package/dist/utilities/createFileOperations.js.map +1 -0
  56. package/dist/utilities/getFilePrefix.d.ts +15 -10
  57. package/dist/utilities/getFilePrefix.d.ts.map +1 -1
  58. package/dist/utilities/getFilePrefix.js +85 -30
  59. package/dist/utilities/getFilePrefix.js.map +1 -1
  60. package/dist/utilities/getIncomingFiles.js +2 -2
  61. package/dist/utilities/getIncomingFiles.js.map +1 -1
  62. package/dist/utilities/getStorageLocationSelect.d.ts +9 -0
  63. package/dist/utilities/getStorageLocationSelect.d.ts.map +1 -0
  64. package/dist/utilities/getStorageLocationSelect.js +54 -0
  65. package/dist/utilities/getStorageLocationSelect.js.map +1 -0
  66. package/dist/utilities/resolveSignedURLKey.d.ts +12 -1
  67. package/dist/utilities/resolveSignedURLKey.d.ts.map +1 -1
  68. package/dist/utilities/resolveSignedURLKey.js +52 -10
  69. package/dist/utilities/resolveSignedURLKey.js.map +1 -1
  70. package/dist/utilities/sanitizePrefix.d.ts +1 -9
  71. package/dist/utilities/sanitizePrefix.d.ts.map +1 -1
  72. package/dist/utilities/sanitizePrefix.js +1 -21
  73. package/dist/utilities/sanitizePrefix.js.map +1 -1
  74. package/package.json +6 -6
  75. package/dist/admin/fields/getFields.d.ts +0 -17
  76. package/dist/admin/fields/getFields.d.ts.map +0 -1
  77. package/dist/admin/fields/getFields.js +0 -98
  78. package/dist/admin/fields/getFields.js.map +0 -1
  79. package/dist/admin/index.d.ts +0 -4
  80. package/dist/admin/index.d.ts.map +0 -1
  81. package/dist/admin/index.js +0 -34
  82. package/dist/admin/index.js.map +0 -1
  83. package/dist/utilities/getFileKey.d.ts +0 -22
  84. package/dist/utilities/getFileKey.d.ts.map +0 -1
  85. package/dist/utilities/getFileKey.js +0 -23
  86. package/dist/utilities/getFileKey.js.map +0 -1
  87. package/dist/utilities/getFileKey.spec.js +0 -174
  88. package/dist/utilities/getFileKey.spec.js.map +0 -1
  89. package/dist/utilities/getFilePrefix.spec.js +0 -105
  90. package/dist/utilities/getFilePrefix.spec.js.map +0 -1
@@ -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,CAiDzE,CAAA"}
@@ -1,17 +1,39 @@
1
- export const getAfterDeleteHook = ({ adapter, collection })=>{
2
- return async ({ doc, req })=>{
1
+ import { buildPrefixWithObjectKey } from '../utilities/buildPrefixWithObjectKey.js';
2
+ import { buildStoragePathData } from '../utilities/buildStoragePathData.js';
3
+ export const getAfterDeleteHook = ({ adapter, collection, collectionPrefix, useCompositePrefixes })=>{
4
+ return async ({ id, doc, req })=>{
5
+ // Managed objects are deleted after the operation commits and all references are checked.
6
+ const managedDeletedUploads = req.context?._payloadManagedDeletedUploads;
7
+ if (collection.versions || Boolean(doc.original) && typeof doc.original?.filename === 'string' || managedDeletedUploads?.has(JSON.stringify([
8
+ collection.slug,
9
+ String(id)
10
+ ]))) {
11
+ return doc;
12
+ }
3
13
  try {
14
+ // Fold `_objectKey` so deletes target the real object folder.
15
+ const docPrefix = buildPrefixWithObjectKey({
16
+ objectKey: doc._objectKey,
17
+ prefix: doc.prefix
18
+ });
4
19
  const filesToDelete = [
5
20
  doc.filename,
6
- ...Object.values(doc?.sizes || []).map((resizedFileData)=>resizedFileData?.filename)
21
+ ...Object.values(doc?.variants || []).map((resizedFileData)=>resizedFileData?.filename)
7
22
  ];
8
23
  const promises = filesToDelete.map(async (filename)=>{
9
24
  if (filename) {
25
+ const { storageFilePath } = buildStoragePathData({
26
+ collectionPrefix,
27
+ docPrefix,
28
+ filename,
29
+ useCompositePrefixes
30
+ });
10
31
  await adapter.handleDelete({
11
32
  collection,
12
33
  doc,
13
34
  filename,
14
- req
35
+ req,
36
+ storageFilePath
15
37
  });
16
38
  }
17
39
  });
@@ -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 ({ id, doc, req }) => {\n // Managed objects are deleted after the operation commits and all references are checked.\n const managedDeletedUploads = req.context?._payloadManagedDeletedUploads as\n | Set<string>\n | undefined\n if (\n collection.versions ||\n (Boolean(doc.original) && typeof doc.original?.filename === 'string') ||\n managedDeletedUploads?.has(JSON.stringify([collection.slug, String(id)]))\n ) {\n return doc\n }\n\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?.variants || []).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","id","doc","req","managedDeletedUploads","context","_payloadManagedDeletedUploads","versions","Boolean","original","filename","has","JSON","stringify","slug","String","docPrefix","objectKey","_objectKey","prefix","filesToDelete","Object","values","variants","map","resizedFileData","promises","storageFilePath","handleDelete","Promise","all","err","payload","logger","error","msg","labels","singular"],"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,EAAE,EAAEC,GAAG,EAAEC,GAAG,EAAE;QAC5B,0FAA0F;QAC1F,MAAMC,wBAAwBD,IAAIE,OAAO,EAAEC;QAG3C,IACER,WAAWS,QAAQ,IAClBC,QAAQN,IAAIO,QAAQ,KAAK,OAAOP,IAAIO,QAAQ,EAAEC,aAAa,YAC5DN,uBAAuBO,IAAIC,KAAKC,SAAS,CAAC;YAACf,WAAWgB,IAAI;YAAEC,OAAOd;SAAI,IACvE;YACA,OAAOC;QACT;QAEA,IAAI;YACF,8DAA8D;YAC9D,MAAMc,YAAYtB,yBAAyB;gBACzCuB,WAAW,AAACf,IAAgCgB,UAAU;gBACtDC,QAAQjB,IAAIiB,MAAM;YACpB;YAEA,MAAMC,gBAA0B;gBAC9BlB,IAAIQ,QAAQ;mBACTW,OAAOC,MAAM,CAACpB,KAAKqB,YAAY,EAAE,EAAEC,GAAG,CACvC,CAACC,kBAAoBA,iBAAiBf;aAEzC;YAED,MAAMgB,WAAWN,cAAcI,GAAG,CAAC,OAAOd;gBACxC,IAAIA,UAAU;oBACZ,MAAM,EAAEiB,eAAe,EAAE,GAAGhC,qBAAqB;wBAC/CI;wBACAiB;wBACAN;wBACAV;oBACF;oBACA,MAAMH,QAAQ+B,YAAY,CAAC;wBAAE9B;wBAAYI;wBAAKQ;wBAAUP;wBAAKwB;oBAAgB;gBAC/E;YACF;YAEA,MAAME,QAAQC,GAAG,CAACJ;QACpB,EAAE,OAAOK,KAAc;YACrB5B,IAAI6B,OAAO,CAACC,MAAM,CAACC,KAAK,CAAC;gBACvBH;gBACAI,KAAK,CAAC,6DAA6D,EAAErC,WAAWsC,MAAM,EAAEC,SAAS,SAAS,EAAEnC,IAAID,EAAE,CAAC,CAAC,CAAC;YACvH;QACF;QACA,OAAOC;IACT;AACF,EAAC"}
@@ -5,8 +5,13 @@ interface Args {
5
5
  collection: CollectionConfig;
6
6
  disablePayloadAccessControl?: boolean;
7
7
  generateFileURL?: GenerateFileURL;
8
+ isOriginal?: boolean;
8
9
  size?: ImageSize;
9
10
  }
10
- export declare const getAfterReadHook: ({ adapter, collection, disablePayloadAccessControl, generateFileURL, size }: Args) => FieldHook;
11
+ export declare const getAfterReadHook: ({ adapter, collection, disablePayloadAccessControl, generateFileURL, isOriginal, size, }: Args) => FieldHook;
12
+ export declare const appendProxyPrefix: ({ prefix, url }: {
13
+ prefix: string;
14
+ url: string;
15
+ }) => string;
11
16
  export {};
12
17
  //# sourceMappingURL=afterRead.d.ts.map
@@ -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,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB;AAiBD,eAAO,MAAM,gBAAgB,6FAQxB,IAAI,KAAG,SAkCT,CAAA;AAEH,eAAO,MAAM,iBAAiB,oBAAqB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,KAAG,MAMpF,CAAA"}
@@ -1,13 +1,31 @@
1
- export const getAfterReadHook = ({ adapter, collection, disablePayloadAccessControl, generateFileURL, size })=>async ({ data, value })=>{
2
- const filename = size ? data?.sizes?.[size.name]?.filename : data?.filename;
3
- const prefix = data?.prefix;
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
+ };
12
+ export const getAfterReadHook = ({ adapter, collection, disablePayloadAccessControl, generateFileURL, isOriginal, size })=>async ({ data, value })=>{
13
+ const representation = isOriginal ? data?.original : size ? data?.variants?.[size.name] : data;
14
+ const filename = representation?.filename;
15
+ const hasLegacyVariant = Boolean(size && !data?.original?.filename);
16
+ const prefix = representation?.prefix ?? (hasLegacyVariant ? data?.prefix : undefined);
17
+ // Direct-serve URLs encode the full location; the proxy resolves `_objectKey` server-side.
18
+ const objectFolder = getObjectFolder({
19
+ _objectKey: representation?._objectKey ?? (hasLegacyVariant ? data?._objectKey : undefined),
20
+ prefix
21
+ });
4
22
  let url = value;
5
23
  if (filename) {
6
24
  if (generateFileURL) {
7
25
  url = await generateFileURL({
8
26
  collection,
9
27
  filename,
10
- prefix,
28
+ prefix: objectFolder,
11
29
  size
12
30
  });
13
31
  } else if (disablePayloadAccessControl && adapter.generateURL) {
@@ -15,14 +33,23 @@ export const getAfterReadHook = ({ adapter, collection, disablePayloadAccessCont
15
33
  collection,
16
34
  data,
17
35
  filename,
18
- prefix
36
+ prefix: objectFolder
19
37
  });
20
38
  } else if (url && prefix) {
21
- const separator = url.includes('?') ? '&' : '?';
22
- url = `${url}${separator}prefix=${encodeURIComponent(prefix)}`;
39
+ url = appendProxyPrefix({
40
+ prefix,
41
+ url
42
+ });
23
43
  }
24
44
  }
25
45
  return url;
26
46
  };
47
+ export const appendProxyPrefix = ({ prefix, url })=>{
48
+ if (new URL(url, 'http://payload.local').searchParams.has('prefix')) {
49
+ return url;
50
+ }
51
+ const separator = url.includes('?') ? '&' : '?';
52
+ return `${url}${separator}prefix=${encodeURIComponent(prefix)}`;
53
+ };
27
54
 
28
55
  //# sourceMappingURL=afterRead.js.map
@@ -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 isOriginal?: boolean\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 ({\n adapter,\n collection,\n disablePayloadAccessControl,\n generateFileURL,\n isOriginal,\n size,\n }: Args): FieldHook =>\n async ({ data, value }) => {\n const representation = isOriginal ? data?.original : size ? data?.variants?.[size.name] : data\n const filename = representation?.filename\n const hasLegacyVariant = Boolean(size && !data?.original?.filename)\n const prefix = representation?.prefix ?? (hasLegacyVariant ? data?.prefix : undefined)\n // Direct-serve URLs encode the full location; the proxy resolves `_objectKey` server-side.\n const objectFolder = getObjectFolder({\n _objectKey: representation?._objectKey ?? (hasLegacyVariant ? data?._objectKey : undefined),\n prefix,\n })\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 url = appendProxyPrefix({ prefix, url })\n }\n }\n\n return url\n }\n\nexport const appendProxyPrefix = ({ prefix, url }: { prefix: string; url: string }): string => {\n if (new URL(url, 'http://payload.local').searchParams.has('prefix')) {\n return url\n }\n const separator = url.includes('?') ? '&' : '?'\n return `${url}${separator}prefix=${encodeURIComponent(prefix)}`\n}\n"],"names":["sanitizePrefix","getObjectFolder","data","record","safePrefix","prefix","safeObjectKey","_objectKey","getAfterReadHook","adapter","collection","disablePayloadAccessControl","generateFileURL","isOriginal","size","value","representation","original","variants","name","filename","hasLegacyVariant","Boolean","undefined","objectFolder","url","generateURL","appendProxyPrefix","URL","searchParams","has","separator","includes","encodeURIComponent"],"mappings":"AAIA,SAASA,cAAc,QAAQ,iCAAgC;AAW/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,EACCC,OAAO,EACPC,UAAU,EACVC,2BAA2B,EAC3BC,eAAe,EACfC,UAAU,EACVC,IAAI,EACC,GACP,OAAO,EAAEZ,IAAI,EAAEa,KAAK,EAAE;QACpB,MAAMC,iBAAiBH,aAAaX,MAAMe,WAAWH,OAAOZ,MAAMgB,UAAU,CAACJ,KAAKK,IAAI,CAAC,GAAGjB;QAC1F,MAAMkB,WAAWJ,gBAAgBI;QACjC,MAAMC,mBAAmBC,QAAQR,QAAQ,CAACZ,MAAMe,UAAUG;QAC1D,MAAMf,SAASW,gBAAgBX,UAAWgB,CAAAA,mBAAmBnB,MAAMG,SAASkB,SAAQ;QACpF,2FAA2F;QAC3F,MAAMC,eAAevB,gBAAgB;YACnCM,YAAYS,gBAAgBT,cAAec,CAAAA,mBAAmBnB,MAAMK,aAAagB,SAAQ;YACzFlB;QACF;QACA,IAAIoB,MAAMV;QAEV,IAAIK,UAAU;YACZ,IAAIR,iBAAiB;gBACnBa,MAAM,MAAMb,gBAAgB;oBAC1BF;oBACAU;oBACAf,QAAQmB;oBACRV;gBACF;YACF,OAAO,IAAIH,+BAA+BF,QAAQiB,WAAW,EAAE;gBAC7DD,MAAM,MAAMhB,QAAQiB,WAAW,CAAC;oBAC9BhB;oBACAR;oBACAkB;oBACAf,QAAQmB;gBACV;YACF,OAAO,IAAIC,OAAOpB,QAAQ;gBACxBoB,MAAME,kBAAkB;oBAAEtB;oBAAQoB;gBAAI;YACxC;QACF;QAEA,OAAOA;IACT,EAAC;AAEH,OAAO,MAAME,oBAAoB,CAAC,EAAEtB,MAAM,EAAEoB,GAAG,EAAmC;IAChF,IAAI,IAAIG,IAAIH,KAAK,wBAAwBI,YAAY,CAACC,GAAG,CAAC,WAAW;QACnE,OAAOL;IACT;IACA,MAAMM,YAAYN,IAAIO,QAAQ,CAAC,OAAO,MAAM;IAC5C,OAAO,GAAGP,MAAMM,UAAU,OAAO,EAAEE,mBAAmB5B,SAAS;AACjE,EAAC"}
@@ -5,8 +5,9 @@ interface Args {
5
5
  collection: CollectionConfig;
6
6
  disablePayloadAccessControl?: boolean;
7
7
  generateFileURL?: GenerateFileURL;
8
+ isOriginal?: boolean;
8
9
  size?: ImageSize;
9
10
  }
10
- export declare const getBeforeChangeHook: ({ adapter, collection, disablePayloadAccessControl, generateFileURL, size }: Args) => FieldHook;
11
+ export declare const getBeforeChangeHook: ({ adapter, collection, disablePayloadAccessControl, generateFileURL, isOriginal, size, }: Args) => FieldHook;
11
12
  export {};
12
13
  //# sourceMappingURL=beforeChange.d.ts.map
@@ -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,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB;AAiBD,eAAO,MAAM,mBAAmB,6FAQ3B,IAAI,KAAG,SAoCT,CAAA"}
@@ -1,8 +1,22 @@
1
- export const getBeforeChangeHook = ({ adapter, collection, disablePayloadAccessControl, generateFileURL, size })=>async ({ data, originalDoc, value })=>{
2
- const newFilename = size ? data?.sizes?.[size.name]?.filename : data?.filename;
3
- const originalFilename = size ? originalDoc?.sizes?.[size.name]?.filename : originalDoc?.filename;
1
+ import { sanitizePrefix } from '../utilities/sanitizePrefix.js';
2
+ // The object's folder: semantic prefix + `_objectKey` segment.
3
+ const getObjectFolder = (data, originalDoc)=>{
4
+ const source = data ?? originalDoc ?? {};
5
+ const safePrefix = sanitizePrefix(typeof source.prefix === 'string' ? source.prefix : '');
6
+ const safeObjectKey = sanitizePrefix(typeof source._objectKey === 'string' ? source._objectKey : '');
7
+ if (safePrefix && safeObjectKey) {
8
+ return `${safePrefix}/${safeObjectKey}`;
9
+ }
10
+ return safePrefix || safeObjectKey;
11
+ };
12
+ export const getBeforeChangeHook = ({ adapter, collection, disablePayloadAccessControl, generateFileURL, isOriginal, size })=>async ({ data, originalDoc, value })=>{
13
+ const newRepresentation = isOriginal ? data?.original : size ? data?.variants?.[size.name] : data;
14
+ const originalRepresentation = isOriginal ? originalDoc?.original : size ? originalDoc?.variants?.[size.name] : originalDoc;
15
+ const newFilename = newRepresentation?.filename;
16
+ const originalFilename = originalRepresentation?.filename;
4
17
  const filename = newFilename || originalFilename;
5
- const prefix = data?.prefix;
18
+ const representation = newRepresentation ?? originalRepresentation;
19
+ const prefix = getObjectFolder(representation, data ?? originalDoc);
6
20
  let url = value;
7
21
  if (generateFileURL && filename) {
8
22
  url = await generateFileURL({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/beforeChange.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 getBeforeChangeHook =\n ({ adapter, collection, disablePayloadAccessControl, generateFileURL, size }: Args): FieldHook =>\n async ({ data, originalDoc, value }) => {\n const newFilename = size ? data?.sizes?.[size.name]?.filename : data?.filename\n const originalFilename = size\n ? originalDoc?.sizes?.[size.name]?.filename\n : originalDoc?.filename\n const filename = newFilename || originalFilename\n const prefix = data?.prefix\n let url = value\n\n if (generateFileURL && filename) {\n url = await generateFileURL({\n collection,\n filename,\n prefix,\n size,\n })\n } else if (disablePayloadAccessControl && filename && adapter.generateURL) {\n url = await adapter.generateURL({\n collection,\n data: data || originalDoc,\n filename,\n prefix,\n })\n }\n\n return url\n }\n"],"names":["getBeforeChangeHook","adapter","collection","disablePayloadAccessControl","generateFileURL","size","data","originalDoc","value","newFilename","sizes","name","filename","originalFilename","prefix","url","generateURL"],"mappings":"AAYA,OAAO,MAAMA,sBACX,CAAC,EAAEC,OAAO,EAAEC,UAAU,EAAEC,2BAA2B,EAAEC,eAAe,EAAEC,IAAI,EAAQ,GAClF,OAAO,EAAEC,IAAI,EAAEC,WAAW,EAAEC,KAAK,EAAE;QACjC,MAAMC,cAAcJ,OAAOC,MAAMI,OAAO,CAACL,KAAKM,IAAI,CAAC,EAAEC,WAAWN,MAAMM;QACtE,MAAMC,mBAAmBR,OACrBE,aAAaG,OAAO,CAACL,KAAKM,IAAI,CAAC,EAAEC,WACjCL,aAAaK;QACjB,MAAMA,WAAWH,eAAeI;QAChC,MAAMC,SAASR,MAAMQ;QACrB,IAAIC,MAAMP;QAEV,IAAIJ,mBAAmBQ,UAAU;YAC/BG,MAAM,MAAMX,gBAAgB;gBAC1BF;gBACAU;gBACAE;gBACAT;YACF;QACF,OAAO,IAAIF,+BAA+BS,YAAYX,QAAQe,WAAW,EAAE;YACzED,MAAM,MAAMd,QAAQe,WAAW,CAAC;gBAC9Bd;gBACAI,MAAMA,QAAQC;gBACdK;gBACAE;YACF;QACF;QAEA,OAAOC;IACT,EAAC"}
1
+ {"version":3,"sources":["../../src/hooks/beforeChange.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 isOriginal?: boolean\n size?: ImageSize\n}\n\n// The object's folder: semantic prefix + `_objectKey` segment.\nconst getObjectFolder = (data: unknown, originalDoc: unknown): string => {\n const source = (data ?? originalDoc ?? {}) as Record<string, unknown>\n const safePrefix = sanitizePrefix(typeof source.prefix === 'string' ? source.prefix : '')\n const safeObjectKey = sanitizePrefix(\n typeof source._objectKey === 'string' ? source._objectKey : '',\n )\n\n if (safePrefix && safeObjectKey) {\n return `${safePrefix}/${safeObjectKey}`\n }\n\n return safePrefix || safeObjectKey\n}\n\nexport const getBeforeChangeHook =\n ({\n adapter,\n collection,\n disablePayloadAccessControl,\n generateFileURL,\n isOriginal,\n size,\n }: Args): FieldHook =>\n async ({ data, originalDoc, value }) => {\n const newRepresentation = isOriginal\n ? data?.original\n : size\n ? data?.variants?.[size.name]\n : data\n const originalRepresentation = isOriginal\n ? originalDoc?.original\n : size\n ? originalDoc?.variants?.[size.name]\n : originalDoc\n const newFilename = newRepresentation?.filename\n const originalFilename = originalRepresentation?.filename\n const filename = newFilename || originalFilename\n const representation = newRepresentation ?? originalRepresentation\n const prefix = getObjectFolder(representation, data ?? originalDoc)\n let url = value\n\n if (generateFileURL && filename) {\n url = await generateFileURL({\n collection,\n filename,\n prefix,\n size,\n })\n } else if (disablePayloadAccessControl && filename && adapter.generateURL) {\n url = await adapter.generateURL({\n collection,\n data: data || originalDoc,\n filename,\n prefix,\n })\n }\n\n return url\n }\n"],"names":["sanitizePrefix","getObjectFolder","data","originalDoc","source","safePrefix","prefix","safeObjectKey","_objectKey","getBeforeChangeHook","adapter","collection","disablePayloadAccessControl","generateFileURL","isOriginal","size","value","newRepresentation","original","variants","name","originalRepresentation","newFilename","filename","originalFilename","representation","url","generateURL"],"mappings":"AAIA,SAASA,cAAc,QAAQ,iCAAgC;AAW/D,+DAA+D;AAC/D,MAAMC,kBAAkB,CAACC,MAAeC;IACtC,MAAMC,SAAUF,QAAQC,eAAe,CAAC;IACxC,MAAME,aAAaL,eAAe,OAAOI,OAAOE,MAAM,KAAK,WAAWF,OAAOE,MAAM,GAAG;IACtF,MAAMC,gBAAgBP,eACpB,OAAOI,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,sBACX,CAAC,EACCC,OAAO,EACPC,UAAU,EACVC,2BAA2B,EAC3BC,eAAe,EACfC,UAAU,EACVC,IAAI,EACC,GACP,OAAO,EAAEb,IAAI,EAAEC,WAAW,EAAEa,KAAK,EAAE;QACjC,MAAMC,oBAAoBH,aACtBZ,MAAMgB,WACNH,OACEb,MAAMiB,UAAU,CAACJ,KAAKK,IAAI,CAAC,GAC3BlB;QACN,MAAMmB,yBAAyBP,aAC3BX,aAAae,WACbH,OACEZ,aAAagB,UAAU,CAACJ,KAAKK,IAAI,CAAC,GAClCjB;QACN,MAAMmB,cAAcL,mBAAmBM;QACvC,MAAMC,mBAAmBH,wBAAwBE;QACjD,MAAMA,WAAWD,eAAeE;QAChC,MAAMC,iBAAiBR,qBAAqBI;QAC5C,MAAMf,SAASL,gBAAgBwB,gBAAgBvB,QAAQC;QACvD,IAAIuB,MAAMV;QAEV,IAAIH,mBAAmBU,UAAU;YAC/BG,MAAM,MAAMb,gBAAgB;gBAC1BF;gBACAY;gBACAjB;gBACAS;YACF;QACF,OAAO,IAAIH,+BAA+BW,YAAYb,QAAQiB,WAAW,EAAE;YACzED,MAAM,MAAMhB,QAAQiB,WAAW,CAAC;gBAC9BhB;gBACAT,MAAMA,QAAQC;gBACdoB;gBACAjB;YACF;QACF;QAEA,OAAOoB;IACT,EAAC"}
@@ -0,0 +1,11 @@
1
+ import type { CollectionBeforeChangeHook, FieldHook } from 'payload';
2
+ type Args = {
3
+ collectionPrefix?: string;
4
+ useCompositePrefixes?: boolean;
5
+ };
6
+ /** Persist new upload destinations without reinterpreting metadata-only updates. */
7
+ export declare const getNormalizeUploadPrefixHook: ({ collectionPrefix, useCompositePrefixes }: Args) => CollectionBeforeChangeHook;
8
+ /** Run last on the prefix field so user hooks cannot leave stale storage coordinates. */
9
+ export declare const getNormalizeUploadPrefixFieldHook: ({ collectionPrefix, useCompositePrefixes }: Args) => FieldHook;
10
+ export {};
11
+ //# sourceMappingURL=normalizeUploadPrefix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizeUploadPrefix.d.ts","sourceRoot":"","sources":["../../src/hooks/normalizeUploadPrefix.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAIpE,KAAK,IAAI,GAAG;IACV,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B,CAAA;AAiBD,oFAAoF;AACpF,eAAO,MAAM,4BAA4B,+CACM,IAAI,KAAG,0BAiBnD,CAAA;AAEH,yFAAyF;AACzF,eAAO,MAAM,iCAAiC,+CACC,IAAI,KAAG,SAenD,CAAA"}
@@ -0,0 +1,42 @@
1
+ import { buildUploadPrefix } from '../utilities/buildStoragePathData.js';
2
+ /**
3
+ * A prefix the caller did not supply, or supplied unchanged, is left as stored so
4
+ * pre-upgrade files keep resolving to their original object keys. Anything the caller
5
+ * chooses is contained, because `prefix` is writable through the REST, GraphQL and
6
+ * Local APIs and the read path trusts whatever is persisted.
7
+ */ const isPrefixSupplied = ({ storedPrefix, value })=>typeof value === 'string' && value !== (typeof storedPrefix === 'string' ? storedPrefix : '');
8
+ /** Persist new upload destinations without reinterpreting metadata-only updates. */ export const getNormalizeUploadPrefixHook = ({ collectionPrefix, useCompositePrefixes })=>({ data, originalDoc, req })=>{
9
+ if (req.context?.skipCloudStorage) {
10
+ return data;
11
+ }
12
+ if (!req.file && !isPrefixSupplied({
13
+ storedPrefix: originalDoc?.prefix,
14
+ value: data.prefix
15
+ })) {
16
+ return data;
17
+ }
18
+ data.prefix = buildUploadPrefix({
19
+ collectionPrefix,
20
+ docPrefix: typeof data.prefix === 'string' ? data.prefix : '',
21
+ useCompositePrefixes
22
+ }).sanitizedDocPrefix;
23
+ return data;
24
+ };
25
+ /** Run last on the prefix field so user hooks cannot leave stale storage coordinates. */ export const getNormalizeUploadPrefixFieldHook = ({ collectionPrefix, useCompositePrefixes })=>({ previousValue, req, value })=>{
26
+ if (req.context?.skipCloudStorage) {
27
+ return value;
28
+ }
29
+ if (!req.file && !isPrefixSupplied({
30
+ storedPrefix: previousValue,
31
+ value
32
+ })) {
33
+ return value;
34
+ }
35
+ return buildUploadPrefix({
36
+ collectionPrefix,
37
+ docPrefix: typeof value === 'string' ? value : '',
38
+ useCompositePrefixes
39
+ }).sanitizedDocPrefix;
40
+ };
41
+
42
+ //# sourceMappingURL=normalizeUploadPrefix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/normalizeUploadPrefix.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook, FieldHook } from 'payload'\n\nimport { buildUploadPrefix } from '../utilities/buildStoragePathData.js'\n\ntype Args = {\n collectionPrefix?: string\n useCompositePrefixes?: boolean\n}\n\n/**\n * A prefix the caller did not supply, or supplied unchanged, is left as stored so\n * pre-upgrade files keep resolving to their original object keys. Anything the caller\n * chooses is contained, because `prefix` is writable through the REST, GraphQL and\n * Local APIs and the read path trusts whatever is persisted.\n */\nconst isPrefixSupplied = ({\n storedPrefix,\n value,\n}: {\n storedPrefix: unknown\n value: unknown\n}): boolean =>\n typeof value === 'string' && value !== (typeof storedPrefix === 'string' ? storedPrefix : '')\n\n/** Persist new upload destinations without reinterpreting metadata-only updates. */\nexport const getNormalizeUploadPrefixHook =\n ({ collectionPrefix, useCompositePrefixes }: Args): CollectionBeforeChangeHook =>\n ({ data, originalDoc, req }) => {\n if (req.context?.skipCloudStorage) {\n return data\n }\n\n if (!req.file && !isPrefixSupplied({ storedPrefix: originalDoc?.prefix, value: data.prefix })) {\n return data\n }\n\n data.prefix = buildUploadPrefix({\n collectionPrefix,\n docPrefix: typeof data.prefix === 'string' ? data.prefix : '',\n useCompositePrefixes,\n }).sanitizedDocPrefix\n\n return data\n }\n\n/** Run last on the prefix field so user hooks cannot leave stale storage coordinates. */\nexport const getNormalizeUploadPrefixFieldHook =\n ({ collectionPrefix, useCompositePrefixes }: Args): FieldHook =>\n ({ previousValue, req, value }) => {\n if (req.context?.skipCloudStorage) {\n return value\n }\n\n if (!req.file && !isPrefixSupplied({ storedPrefix: previousValue, value })) {\n return value\n }\n\n return buildUploadPrefix({\n collectionPrefix,\n docPrefix: typeof value === 'string' ? value : '',\n useCompositePrefixes,\n }).sanitizedDocPrefix\n }\n"],"names":["buildUploadPrefix","isPrefixSupplied","storedPrefix","value","getNormalizeUploadPrefixHook","collectionPrefix","useCompositePrefixes","data","originalDoc","req","context","skipCloudStorage","file","prefix","docPrefix","sanitizedDocPrefix","getNormalizeUploadPrefixFieldHook","previousValue"],"mappings":"AAEA,SAASA,iBAAiB,QAAQ,uCAAsC;AAOxE;;;;;CAKC,GACD,MAAMC,mBAAmB,CAAC,EACxBC,YAAY,EACZC,KAAK,EAIN,GACC,OAAOA,UAAU,YAAYA,UAAW,CAAA,OAAOD,iBAAiB,WAAWA,eAAe,EAAC;AAE7F,kFAAkF,GAClF,OAAO,MAAME,+BACX,CAAC,EAAEC,gBAAgB,EAAEC,oBAAoB,EAAQ,GACjD,CAAC,EAAEC,IAAI,EAAEC,WAAW,EAAEC,GAAG,EAAE;QACzB,IAAIA,IAAIC,OAAO,EAAEC,kBAAkB;YACjC,OAAOJ;QACT;QAEA,IAAI,CAACE,IAAIG,IAAI,IAAI,CAACX,iBAAiB;YAAEC,cAAcM,aAAaK;YAAQV,OAAOI,KAAKM,MAAM;QAAC,IAAI;YAC7F,OAAON;QACT;QAEAA,KAAKM,MAAM,GAAGb,kBAAkB;YAC9BK;YACAS,WAAW,OAAOP,KAAKM,MAAM,KAAK,WAAWN,KAAKM,MAAM,GAAG;YAC3DP;QACF,GAAGS,kBAAkB;QAErB,OAAOR;IACT,EAAC;AAEH,uFAAuF,GACvF,OAAO,MAAMS,oCACX,CAAC,EAAEX,gBAAgB,EAAEC,oBAAoB,EAAQ,GACjD,CAAC,EAAEW,aAAa,EAAER,GAAG,EAAEN,KAAK,EAAE;QAC5B,IAAIM,IAAIC,OAAO,EAAEC,kBAAkB;YACjC,OAAOR;QACT;QAEA,IAAI,CAACM,IAAIG,IAAI,IAAI,CAACX,iBAAiB;YAAEC,cAAce;YAAed;QAAM,IAAI;YAC1E,OAAOA;QACT;QAEA,OAAOH,kBAAkB;YACvBK;YACAS,WAAW,OAAOX,UAAU,WAAWA,QAAQ;YAC/CG;QACF,GAAGS,kBAAkB;IACvB,EAAC"}
@@ -0,0 +1,8 @@
1
+ import type { CollectionAfterReadHook, CollectionConfig } from 'payload';
2
+ import type { GeneratedAdapter, GenerateFileURL } from '../types.js';
3
+ export declare const getPublicOriginalURLHook: ({ adapter, collection, generateFileURL, }: {
4
+ adapter: GeneratedAdapter;
5
+ collection: CollectionConfig;
6
+ generateFileURL?: GenerateFileURL;
7
+ }) => CollectionAfterReadHook;
8
+ //# sourceMappingURL=publicOriginalURL.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publicOriginalURL.d.ts","sourceRoot":"","sources":["../../src/hooks/publicOriginalURL.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,gBAAgB,EAAwB,MAAM,SAAS,CAAA;AAE9F,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAIpE,eAAO,MAAM,wBAAwB,8CAKhC;IACD,OAAO,EAAE,gBAAgB,CAAA;IACzB,UAAU,EAAE,gBAAgB,CAAA;IAC5B,eAAe,CAAC,EAAE,eAAe,CAAA;CAClC,KAAG,uBAsBH,CAAA"}
@@ -0,0 +1,34 @@
1
+ import { buildPrefixWithObjectKey } from '../utilities/buildPrefixWithObjectKey.js';
2
+ export const getPublicOriginalURLHook = ({ adapter, collection, generateFileURL })=>async ({ doc })=>{
3
+ const upload = doc;
4
+ const filename = upload.original?.filename;
5
+ if (!filename) {
6
+ return doc;
7
+ }
8
+ if (upload.original?.url && !upload.original.url.startsWith('/')) {
9
+ return doc;
10
+ }
11
+ const prefix = buildPrefixWithObjectKey({
12
+ objectKey: upload.original?._objectKey,
13
+ prefix: upload.original?.prefix
14
+ });
15
+ const url = generateFileURL ? await generateFileURL({
16
+ collection,
17
+ filename,
18
+ prefix
19
+ }) : await adapter.generateURL?.({
20
+ collection,
21
+ data: upload,
22
+ filename,
23
+ prefix
24
+ });
25
+ return url ? {
26
+ ...doc,
27
+ original: {
28
+ ...upload.original,
29
+ url
30
+ }
31
+ } : doc;
32
+ };
33
+
34
+ //# sourceMappingURL=publicOriginalURL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/publicOriginalURL.ts"],"sourcesContent":["import type { CollectionAfterReadHook, CollectionConfig, FileData, TypeWithID } from 'payload'\n\nimport type { GeneratedAdapter, GenerateFileURL } from '../types.js'\n\nimport { buildPrefixWithObjectKey } from '../utilities/buildPrefixWithObjectKey.js'\n\nexport const getPublicOriginalURLHook =\n ({\n adapter,\n collection,\n generateFileURL,\n }: {\n adapter: GeneratedAdapter\n collection: CollectionConfig\n generateFileURL?: GenerateFileURL\n }): CollectionAfterReadHook =>\n async ({ doc }) => {\n const upload = doc as { _objectKey?: string; prefix?: string } & FileData & TypeWithID\n const filename = upload.original?.filename\n\n if (!filename) {\n return doc\n }\n\n if (upload.original?.url && !upload.original.url.startsWith('/')) {\n return doc\n }\n\n const prefix = buildPrefixWithObjectKey({\n objectKey: upload.original?._objectKey,\n prefix: upload.original?.prefix,\n })\n const url = generateFileURL\n ? await generateFileURL({ collection, filename, prefix })\n : await adapter.generateURL?.({ collection, data: upload, filename, prefix })\n\n return url ? { ...doc, original: { ...upload.original, url } } : doc\n }\n"],"names":["buildPrefixWithObjectKey","getPublicOriginalURLHook","adapter","collection","generateFileURL","doc","upload","filename","original","url","startsWith","prefix","objectKey","_objectKey","generateURL","data"],"mappings":"AAIA,SAASA,wBAAwB,QAAQ,2CAA0C;AAEnF,OAAO,MAAMC,2BACX,CAAC,EACCC,OAAO,EACPC,UAAU,EACVC,eAAe,EAKhB,GACD,OAAO,EAAEC,GAAG,EAAE;QACZ,MAAMC,SAASD;QACf,MAAME,WAAWD,OAAOE,QAAQ,EAAED;QAElC,IAAI,CAACA,UAAU;YACb,OAAOF;QACT;QAEA,IAAIC,OAAOE,QAAQ,EAAEC,OAAO,CAACH,OAAOE,QAAQ,CAACC,GAAG,CAACC,UAAU,CAAC,MAAM;YAChE,OAAOL;QACT;QAEA,MAAMM,SAASX,yBAAyB;YACtCY,WAAWN,OAAOE,QAAQ,EAAEK;YAC5BF,QAAQL,OAAOE,QAAQ,EAAEG;QAC3B;QACA,MAAMF,MAAML,kBACR,MAAMA,gBAAgB;YAAED;YAAYI;YAAUI;QAAO,KACrD,MAAMT,QAAQY,WAAW,GAAG;YAAEX;YAAYY,MAAMT;YAAQC;YAAUI;QAAO;QAE7E,OAAOF,MAAM;YAAE,GAAGJ,GAAG;YAAEG,UAAU;gBAAE,GAAGF,OAAOE,QAAQ;gBAAEC;YAAI;QAAE,IAAIJ;IACnE,EAAC"}
@@ -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,EAAsB,aAAa,EAAE,MAAM,YAAY,CAAA;AAgBnE,eAAO,MAAM,kBAAkB,kBACb,aAAa,sBACZ,MAAM,KAAG,MAoOzB,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,EAAsB,aAAa,EAAE,MAAM,YAAY,CAAA;AAoBnE,eAAO,MAAM,kBAAkB,kBACb,aAAa,sBACZ,MAAM,KAAG,MA+PzB,CAAA"}
package/dist/plugin.js CHANGED
@@ -1,7 +1,11 @@
1
1
  import { getFields } from './fields/getFields.js';
2
2
  import { getAfterChangeHook } from './hooks/afterChange.js';
3
3
  import { getAfterDeleteHook } from './hooks/afterDelete.js';
4
+ import { getNormalizeUploadPrefixHook } from './hooks/normalizeUploadPrefix.js';
4
5
  import { getPreserveFileDataHook } from './hooks/preserveFileData.js';
6
+ import { getPublicOriginalURLHook } from './hooks/publicOriginalURL.js';
7
+ import { createFileOperations } from './utilities/createFileOperations.js';
8
+ import { getStorageLocationSelect } from './utilities/getStorageLocationSelect.js';
5
9
  // This plugin extends all targeted collections by offloading uploaded files
6
10
  // to cloud storage instead of solely storing files locally.
7
11
  // It is based on an adapter approach, where adapters can be written for any cloud provider.
@@ -9,42 +13,39 @@ import { getPreserveFileDataHook } from './hooks/preserveFileData.js';
9
13
  // 1. handleUpload, 2. handleDelete, 3. generateURL, 4. staticHandler
10
14
  // Optionally, the adapter can specify any Webpack config overrides if they are necessary.
11
15
  export const cloudStoragePlugin = (pluginOptions)=>(incomingConfig)=>{
12
- const { alwaysInsertFields, collections: allCollectionOptions, enabled, useCompositePrefixes } = pluginOptions;
16
+ const { collections: allCollectionOptions, enabled, useCompositePrefixes } = pluginOptions;
13
17
  const config = {
14
18
  ...incomingConfig
15
19
  };
16
- // If disabled but alwaysInsertFields is true, only insert fields without full plugin functionality
20
+ // If disabled, only insert fields (e.g. prefix) without full plugin functionality,
21
+ // so the collection schema stays consistent across environments.
17
22
  if (enabled === false) {
18
- if (alwaysInsertFields) {
19
- return {
20
- ...config,
21
- collections: (config.collections || []).map((existingCollection)=>{
22
- const options = allCollectionOptions[existingCollection.slug];
23
- if (options) {
24
- // If adapter is provided, use it to get fields
25
- const adapter = options.adapter ? options.adapter({
26
- collection: existingCollection,
27
- prefix: options.prefix
28
- }) : undefined;
29
- const fields = getFields({
30
- adapter,
31
- alwaysInsertFields: true,
32
- collection: existingCollection,
33
- disablePayloadAccessControl: options.disablePayloadAccessControl,
34
- generateFileURL: options.generateFileURL,
35
- prefix: options.prefix,
36
- useCompositePrefixes
37
- });
38
- return {
39
- ...existingCollection,
40
- fields
41
- };
42
- }
43
- return existingCollection;
44
- })
45
- };
46
- }
47
- return config;
23
+ return {
24
+ ...config,
25
+ collections: (config.collections || []).map((existingCollection)=>{
26
+ const options = allCollectionOptions[existingCollection.slug];
27
+ if (options) {
28
+ // If adapter is provided, use it to get fields
29
+ const adapter = options.adapter ? options.adapter({
30
+ collection: existingCollection,
31
+ prefix: options.prefix
32
+ }) : undefined;
33
+ const fields = getFields({
34
+ adapter,
35
+ collection: existingCollection,
36
+ disablePayloadAccessControl: options.disablePayloadAccessControl,
37
+ generateFileURL: options.generateFileURL,
38
+ prefix: options.prefix,
39
+ useCompositePrefixes
40
+ });
41
+ return {
42
+ ...existingCollection,
43
+ fields
44
+ };
45
+ }
46
+ return existingCollection;
47
+ })
48
+ };
48
49
  }
49
50
  const initFunctions = [];
50
51
  const endpointPaths = new Map();
@@ -111,19 +112,15 @@ export const cloudStoragePlugin = (pluginOptions)=>(incomingConfig)=>{
111
112
  ];
112
113
  if (!options.disablePayloadAccessControl) {
113
114
  handlers.push(adapter.staticHandler);
114
- // Else if disablePayloadAccessControl: true and upload instructions are used
115
- // Build the "proxied" handler that responds only when addDataAndFileToRequest fetches the uploaded file
116
- } else if (uploadInstructions) {
115
+ // Public files still need a server path when Payload reads them for a transform.
116
+ } else {
117
117
  handlers.push((req, args)=>{
118
- if ('uploadReference' in args.params) {
118
+ if ('uploadReference' in args.params || args.params.operation === 'transform') {
119
119
  return adapter.staticHandler(req, args);
120
120
  }
121
121
  });
122
122
  }
123
123
  const getSkipSafeFetchSetting = ()=>{
124
- if (options.disablePayloadAccessControl) {
125
- return true;
126
- }
127
124
  const isBooleanTrueSkipSafeFetch = typeof existingCollection.upload === 'object' && existingCollection.upload.skipSafeFetch === true;
128
125
  const isAllowListSkipSafeFetch = typeof existingCollection.upload === 'object' && Array.isArray(existingCollection.upload.skipSafeFetch);
129
126
  if (isBooleanTrueSkipSafeFetch) {
@@ -162,24 +159,55 @@ export const cloudStoragePlugin = (pluginOptions)=>(incomingConfig)=>{
162
159
  ...existingCollection.hooks?.afterChange || [],
163
160
  getAfterChangeHook({
164
161
  adapter,
165
- collection: existingCollection
162
+ collection: existingCollection,
163
+ collectionPrefix: options.prefix,
164
+ disablePayloadAccessControl: options.disablePayloadAccessControl,
165
+ hasCustomFileURL: Boolean(options.generateFileURL),
166
+ useCompositePrefixes
166
167
  })
167
168
  ],
168
169
  afterDelete: [
169
170
  ...existingCollection.hooks?.afterDelete || [],
170
171
  getAfterDeleteHook({
171
172
  adapter,
172
- collection: existingCollection
173
+ collection: existingCollection,
174
+ collectionPrefix: options.prefix,
175
+ useCompositePrefixes
173
176
  })
174
177
  ],
178
+ afterRead: [
179
+ ...existingCollection.hooks?.afterRead || [],
180
+ ...options.disablePayloadAccessControl && (options.generateFileURL || adapter.generateURL) ? [
181
+ getPublicOriginalURLHook({
182
+ adapter,
183
+ collection: existingCollection,
184
+ generateFileURL: options.generateFileURL
185
+ })
186
+ ] : []
187
+ ],
175
188
  beforeChange: [
176
189
  ...existingCollection.hooks?.beforeChange || [],
190
+ getNormalizeUploadPrefixHook({
191
+ collectionPrefix: options.prefix,
192
+ useCompositePrefixes
193
+ }),
177
194
  getPreserveFileDataHook()
178
195
  ]
179
196
  },
197
+ select: getStorageLocationSelect({
198
+ select: existingCollection.select
199
+ }),
180
200
  upload: {
181
201
  ...typeof existingCollection.upload === 'object' ? existingCollection.upload : {},
182
202
  adapter: adapter.name,
203
+ fileOperations: createFileOperations({
204
+ adapter,
205
+ collection: existingCollection,
206
+ collectionPrefix: options.prefix,
207
+ disablePayloadAccessControl: options.disablePayloadAccessControl,
208
+ generateFileURL: options.generateFileURL,
209
+ useCompositePrefixes
210
+ }),
183
211
  ...uploadInstructions && {
184
212
  uploadInstructions
185
213
  },
@@ -195,7 +223,9 @@ export const cloudStoragePlugin = (pluginOptions)=>(incomingConfig)=>{
195
223
  ...config,
196
224
  collections,
197
225
  onInit: async (payload)=>{
198
- initFunctions.forEach((fn)=>fn());
226
+ // Await each init so a provisioning failure fails Payload startup
227
+ // instead of becoming an unhandled rejection.
228
+ await Promise.all(initFunctions.map((fn)=>fn()));
199
229
  if (config.onInit) {
200
230
  await config.onInit(payload);
201
231
  }