payload 3.88.0 → 3.89.0-internal.95c9cba

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 (61) hide show
  1. package/dist/collections/operations/create.d.ts.map +1 -1
  2. package/dist/collections/operations/create.js +10 -0
  3. package/dist/collections/operations/create.js.map +1 -1
  4. package/dist/collections/operations/update.d.ts.map +1 -1
  5. package/dist/collections/operations/update.js +10 -0
  6. package/dist/collections/operations/update.js.map +1 -1
  7. package/dist/collections/operations/updateByID.d.ts.map +1 -1
  8. package/dist/collections/operations/updateByID.js +10 -0
  9. package/dist/collections/operations/updateByID.js.map +1 -1
  10. package/dist/index.bundled.d.ts +10 -0
  11. package/dist/uploads/cropImage.d.ts.map +1 -1
  12. package/dist/uploads/cropImage.js +2 -5
  13. package/dist/uploads/cropImage.js.map +1 -1
  14. package/dist/uploads/generateFileData.d.ts +8 -0
  15. package/dist/uploads/generateFileData.d.ts.map +1 -1
  16. package/dist/uploads/generateFileData.js +55 -29
  17. package/dist/uploads/generateFileData.js.map +1 -1
  18. package/dist/uploads/generateFileData.spec.js +144 -0
  19. package/dist/uploads/generateFileData.spec.js.map +1 -0
  20. package/dist/uploads/generateFileData.tempFileBuffering.spec.js +81 -0
  21. package/dist/uploads/generateFileData.tempFileBuffering.spec.js.map +1 -0
  22. package/dist/uploads/getFileContentRequirement.d.ts +9 -0
  23. package/dist/uploads/getFileContentRequirement.d.ts.map +1 -0
  24. package/dist/uploads/getFileContentRequirement.js +24 -0
  25. package/dist/uploads/getFileContentRequirement.js.map +1 -0
  26. package/dist/uploads/getFileContentRequirement.spec.js +95 -0
  27. package/dist/uploads/getFileContentRequirement.spec.js.map +1 -0
  28. package/dist/uploads/getFileFromClientUpload.d.ts +20 -0
  29. package/dist/uploads/getFileFromClientUpload.d.ts.map +1 -0
  30. package/dist/uploads/getFileFromClientUpload.js +199 -0
  31. package/dist/uploads/getFileFromClientUpload.js.map +1 -0
  32. package/dist/uploads/getFileFromClientUpload.spec.js +528 -0
  33. package/dist/uploads/getFileFromClientUpload.spec.js.map +1 -0
  34. package/dist/uploads/image-resizing/createImageSizes.d.ts.map +1 -1
  35. package/dist/uploads/image-resizing/createImageSizes.js +2 -5
  36. package/dist/uploads/image-resizing/createImageSizes.js.map +1 -1
  37. package/dist/uploads/isAnimatedImage.d.ts +6 -0
  38. package/dist/uploads/isAnimatedImage.d.ts.map +1 -0
  39. package/dist/uploads/isAnimatedImage.js +19 -0
  40. package/dist/uploads/isAnimatedImage.js.map +1 -0
  41. package/dist/uploads/isAnimatedImage.spec.js +19 -0
  42. package/dist/uploads/isAnimatedImage.spec.js.map +1 -0
  43. package/dist/uploads/types.d.ts +10 -0
  44. package/dist/uploads/types.d.ts.map +1 -1
  45. package/dist/uploads/types.js.map +1 -1
  46. package/dist/uploads/unlinkTempFiles.d.ts.map +1 -1
  47. package/dist/uploads/unlinkTempFiles.js +12 -2
  48. package/dist/uploads/unlinkTempFiles.js.map +1 -1
  49. package/dist/uploads/unlinkTempFiles.spec.js +157 -0
  50. package/dist/uploads/unlinkTempFiles.spec.js.map +1 -0
  51. package/dist/uploads/uploadFiles.d.ts.map +1 -1
  52. package/dist/uploads/uploadFiles.js +7 -2
  53. package/dist/uploads/uploadFiles.js.map +1 -1
  54. package/dist/uploads/uploadFiles.spec.js +44 -0
  55. package/dist/uploads/uploadFiles.spec.js.map +1 -0
  56. package/dist/utilities/addDataAndFileToRequest.d.ts.map +1 -1
  57. package/dist/utilities/addDataAndFileToRequest.js +7 -46
  58. package/dist/utilities/addDataAndFileToRequest.js.map +1 -1
  59. package/dist/utilities/addDataAndFileToRequest.spec.js +131 -1
  60. package/dist/utilities/addDataAndFileToRequest.spec.js.map +1 -1
  61. package/package.json +2 -2
@@ -1,5 +1,6 @@
1
1
  import { APIError } from '../errors/APIError.js';
2
2
  import { processMultipartFormdata } from '../uploads/fetchAPI-multipart/index.js';
3
+ import { getFileFromClientUpload } from '../uploads/getFileFromClientUpload.js';
3
4
  /**
4
5
  * Mutates the Request, appending 'data' and 'file' if found
5
6
  */ export const addDataAndFileToRequest = async (req)=>{
@@ -50,56 +51,16 @@ import { processMultipartFormdata } from '../uploads/fetchAPI-multipart/index.js
50
51
  req.data = JSON.parse(fields._payload);
51
52
  }
52
53
  if (!req.file && fields?.file && typeof fields?.file === 'string') {
53
- let clientUploadContext, collectionSlug, filename, mimeType, size;
54
+ let clientUploadFile;
54
55
  try {
55
- ;
56
- ({ clientUploadContext, collectionSlug, filename, mimeType, size } = JSON.parse(fields.file));
56
+ clientUploadFile = JSON.parse(fields.file);
57
57
  } catch {
58
58
  throw new APIError('A file name is required.', 400);
59
59
  }
60
- const uploadConfig = req.payload.collections[collectionSlug].config.upload;
61
- if (!uploadConfig.handlers) {
62
- throw new APIError('uploadConfig.handlers is not present for ' + collectionSlug);
63
- }
64
- let response = null;
65
- let error;
66
- for (const handler of uploadConfig.handlers){
67
- try {
68
- const result = await handler(req, {
69
- doc: null,
70
- params: {
71
- clientUploadContext,
72
- collection: collectionSlug,
73
- filename
74
- }
75
- });
76
- if (result) {
77
- response = result;
78
- }
79
- // If we couldn't get the file from that handler, save the error and try other.
80
- } catch (err) {
81
- error = err;
82
- }
83
- }
84
- if (!response) {
85
- if (error) {
86
- payload.logger.error(error);
87
- }
88
- throw new APIError('Expected response from the upload handler.');
89
- }
90
- if (response.status >= 300 && response.status < 400) {
91
- const redirectUrl = response.headers.get('Location');
92
- if (redirectUrl) {
93
- response = await fetch(redirectUrl);
94
- }
95
- }
96
- req.file = {
97
- name: filename,
98
- clientUploadContext,
99
- data: Buffer.from(await response.arrayBuffer()),
100
- mimetype: response.headers.get('Content-Type') || mimeType,
101
- size
102
- };
60
+ req.file = await getFileFromClientUpload({
61
+ file: clientUploadFile,
62
+ req
63
+ });
103
64
  }
104
65
  }
105
66
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/addDataAndFileToRequest.ts"],"sourcesContent":["import type { PayloadRequest } from '../types/index.js'\n\nimport { APIError } from '../errors/APIError.js'\nimport { processMultipartFormdata } from '../uploads/fetchAPI-multipart/index.js'\n\ntype AddDataAndFileToRequest = (req: PayloadRequest) => Promise<void>\n\n/**\n * Mutates the Request, appending 'data' and 'file' if found\n */\nexport const addDataAndFileToRequest: AddDataAndFileToRequest = async (req) => {\n const { body, headers, method, payload } = req\n\n if (method && ['PATCH', 'POST', 'PUT'].includes(method.toUpperCase()) && body) {\n const [contentType] = (headers.get('Content-Type') || '').split(';', 1)\n const bodyByteSize = parseInt(req.headers.get('Content-Length') || '0', 10)\n const hasBodyStream = req.body !== null\n\n if (contentType === 'application/json') {\n try {\n const text = await req.text?.()\n const data = text ? JSON.parse(text) : {}\n req.data = data\n // @ts-expect-error attach json method to request\n req.json = () => Promise.resolve(data)\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new APIError('Invalid JSON', 400)\n }\n req.payload.logger.error(error)\n throw error\n }\n } else if ((bodyByteSize || hasBodyStream) && contentType?.includes('multipart/')) {\n const { error, fields, files } = await processMultipartFormdata({\n options: {\n ...(payload.config.bodyParser || {}),\n ...(payload.config.upload || {}),\n },\n request: req as Request,\n })\n\n if (error) {\n throw new APIError(error.message)\n }\n\n // Set all files on req.files for access by hooks\n if (files) {\n req.files = files\n // Backwards compatibility: set req.file for standard upload collections\n // Guard: if multiple files share the field name \"file\", files.file is an array — skip\n if (files.file && !Array.isArray(files.file)) {\n req.file = files.file\n }\n }\n\n if (fields?._payload && typeof fields._payload === 'string') {\n req.data = JSON.parse(fields._payload)\n }\n\n if (!req.file && fields?.file && typeof fields?.file === 'string') {\n let clientUploadContext, collectionSlug, filename, mimeType, size\n try {\n ;({ clientUploadContext, collectionSlug, filename, mimeType, size } = JSON.parse(\n fields.file,\n ))\n } catch {\n throw new APIError('A file name is required.', 400)\n }\n const uploadConfig = req.payload.collections[collectionSlug]!.config.upload\n\n if (!uploadConfig.handlers) {\n throw new APIError('uploadConfig.handlers is not present for ' + collectionSlug)\n }\n\n let response: null | Response = null\n let error: unknown\n\n for (const handler of uploadConfig.handlers) {\n try {\n const result = await handler(req, {\n doc: null!,\n params: {\n clientUploadContext, // Pass additional specific to adapters context returned from UploadHandler, then staticHandler can use them.\n collection: collectionSlug,\n filename,\n },\n })\n if (result) {\n response = result\n }\n // If we couldn't get the file from that handler, save the error and try other.\n } catch (err) {\n error = err\n }\n }\n\n if (!response) {\n if (error) {\n payload.logger.error(error)\n }\n\n throw new APIError('Expected response from the upload handler.')\n }\n\n if (response.status >= 300 && response.status < 400) {\n const redirectUrl = response.headers.get('Location')\n if (redirectUrl) {\n response = await fetch(redirectUrl)\n }\n }\n\n req.file = {\n name: filename,\n clientUploadContext,\n data: Buffer.from(await response.arrayBuffer()),\n mimetype: response.headers.get('Content-Type') || mimeType,\n size,\n }\n }\n }\n }\n}\n"],"names":["APIError","processMultipartFormdata","addDataAndFileToRequest","req","body","headers","method","payload","includes","toUpperCase","contentType","get","split","bodyByteSize","parseInt","hasBodyStream","text","data","JSON","parse","json","Promise","resolve","error","SyntaxError","logger","fields","files","options","config","bodyParser","upload","request","message","file","Array","isArray","_payload","clientUploadContext","collectionSlug","filename","mimeType","size","uploadConfig","collections","handlers","response","handler","result","doc","params","collection","err","status","redirectUrl","fetch","name","Buffer","from","arrayBuffer","mimetype"],"mappings":"AAEA,SAASA,QAAQ,QAAQ,wBAAuB;AAChD,SAASC,wBAAwB,QAAQ,yCAAwC;AAIjF;;CAEC,GACD,OAAO,MAAMC,0BAAmD,OAAOC;IACrE,MAAM,EAAEC,IAAI,EAAEC,OAAO,EAAEC,MAAM,EAAEC,OAAO,EAAE,GAAGJ;IAE3C,IAAIG,UAAU;QAAC;QAAS;QAAQ;KAAM,CAACE,QAAQ,CAACF,OAAOG,WAAW,OAAOL,MAAM;QAC7E,MAAM,CAACM,YAAY,GAAG,AAACL,CAAAA,QAAQM,GAAG,CAAC,mBAAmB,EAAC,EAAGC,KAAK,CAAC,KAAK;QACrE,MAAMC,eAAeC,SAASX,IAAIE,OAAO,CAACM,GAAG,CAAC,qBAAqB,KAAK;QACxE,MAAMI,gBAAgBZ,IAAIC,IAAI,KAAK;QAEnC,IAAIM,gBAAgB,oBAAoB;YACtC,IAAI;gBACF,MAAMM,OAAO,MAAMb,IAAIa,IAAI;gBAC3B,MAAMC,OAAOD,OAAOE,KAAKC,KAAK,CAACH,QAAQ,CAAC;gBACxCb,IAAIc,IAAI,GAAGA;gBACX,iDAAiD;gBACjDd,IAAIiB,IAAI,GAAG,IAAMC,QAAQC,OAAO,CAACL;YACnC,EAAE,OAAOM,OAAO;gBACd,IAAIA,iBAAiBC,aAAa;oBAChC,MAAM,IAAIxB,SAAS,gBAAgB;gBACrC;gBACAG,IAAII,OAAO,CAACkB,MAAM,CAACF,KAAK,CAACA;gBACzB,MAAMA;YACR;QACF,OAAO,IAAI,AAACV,CAAAA,gBAAgBE,aAAY,KAAML,aAAaF,SAAS,eAAe;YACjF,MAAM,EAAEe,KAAK,EAAEG,MAAM,EAAEC,KAAK,EAAE,GAAG,MAAM1B,yBAAyB;gBAC9D2B,SAAS;oBACP,GAAIrB,QAAQsB,MAAM,CAACC,UAAU,IAAI,CAAC,CAAC;oBACnC,GAAIvB,QAAQsB,MAAM,CAACE,MAAM,IAAI,CAAC,CAAC;gBACjC;gBACAC,SAAS7B;YACX;YAEA,IAAIoB,OAAO;gBACT,MAAM,IAAIvB,SAASuB,MAAMU,OAAO;YAClC;YAEA,iDAAiD;YACjD,IAAIN,OAAO;gBACTxB,IAAIwB,KAAK,GAAGA;gBACZ,wEAAwE;gBACxE,sFAAsF;gBACtF,IAAIA,MAAMO,IAAI,IAAI,CAACC,MAAMC,OAAO,CAACT,MAAMO,IAAI,GAAG;oBAC5C/B,IAAI+B,IAAI,GAAGP,MAAMO,IAAI;gBACvB;YACF;YAEA,IAAIR,QAAQW,YAAY,OAAOX,OAAOW,QAAQ,KAAK,UAAU;gBAC3DlC,IAAIc,IAAI,GAAGC,KAAKC,KAAK,CAACO,OAAOW,QAAQ;YACvC;YAEA,IAAI,CAAClC,IAAI+B,IAAI,IAAIR,QAAQQ,QAAQ,OAAOR,QAAQQ,SAAS,UAAU;gBACjE,IAAII,qBAAqBC,gBAAgBC,UAAUC,UAAUC;gBAC7D,IAAI;;oBACA,CAAA,EAAEJ,mBAAmB,EAAEC,cAAc,EAAEC,QAAQ,EAAEC,QAAQ,EAAEC,IAAI,EAAE,GAAGxB,KAAKC,KAAK,CAC9EO,OAAOQ,IAAI,CACb;gBACF,EAAE,OAAM;oBACN,MAAM,IAAIlC,SAAS,4BAA4B;gBACjD;gBACA,MAAM2C,eAAexC,IAAII,OAAO,CAACqC,WAAW,CAACL,eAAe,CAAEV,MAAM,CAACE,MAAM;gBAE3E,IAAI,CAACY,aAAaE,QAAQ,EAAE;oBAC1B,MAAM,IAAI7C,SAAS,8CAA8CuC;gBACnE;gBAEA,IAAIO,WAA4B;gBAChC,IAAIvB;gBAEJ,KAAK,MAAMwB,WAAWJ,aAAaE,QAAQ,CAAE;oBAC3C,IAAI;wBACF,MAAMG,SAAS,MAAMD,QAAQ5C,KAAK;4BAChC8C,KAAK;4BACLC,QAAQ;gCACNZ;gCACAa,YAAYZ;gCACZC;4BACF;wBACF;wBACA,IAAIQ,QAAQ;4BACVF,WAAWE;wBACb;oBACA,+EAA+E;oBACjF,EAAE,OAAOI,KAAK;wBACZ7B,QAAQ6B;oBACV;gBACF;gBAEA,IAAI,CAACN,UAAU;oBACb,IAAIvB,OAAO;wBACThB,QAAQkB,MAAM,CAACF,KAAK,CAACA;oBACvB;oBAEA,MAAM,IAAIvB,SAAS;gBACrB;gBAEA,IAAI8C,SAASO,MAAM,IAAI,OAAOP,SAASO,MAAM,GAAG,KAAK;oBACnD,MAAMC,cAAcR,SAASzC,OAAO,CAACM,GAAG,CAAC;oBACzC,IAAI2C,aAAa;wBACfR,WAAW,MAAMS,MAAMD;oBACzB;gBACF;gBAEAnD,IAAI+B,IAAI,GAAG;oBACTsB,MAAMhB;oBACNF;oBACArB,MAAMwC,OAAOC,IAAI,CAAC,MAAMZ,SAASa,WAAW;oBAC5CC,UAAUd,SAASzC,OAAO,CAACM,GAAG,CAAC,mBAAmB8B;oBAClDC;gBACF;YACF;QACF;IACF;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/utilities/addDataAndFileToRequest.ts"],"sourcesContent":["import type { PayloadRequest } from '../types/index.js'\nimport type { ClientUploadData } from '../uploads/getFileFromClientUpload.js'\n\nimport { APIError } from '../errors/APIError.js'\nimport { processMultipartFormdata } from '../uploads/fetchAPI-multipart/index.js'\nimport { getFileFromClientUpload } from '../uploads/getFileFromClientUpload.js'\n\ntype AddDataAndFileToRequest = (req: PayloadRequest) => Promise<void>\n\n/**\n * Mutates the Request, appending 'data' and 'file' if found\n */\nexport const addDataAndFileToRequest: AddDataAndFileToRequest = async (req) => {\n const { body, headers, method, payload } = req\n\n if (method && ['PATCH', 'POST', 'PUT'].includes(method.toUpperCase()) && body) {\n const [contentType] = (headers.get('Content-Type') || '').split(';', 1)\n const bodyByteSize = parseInt(req.headers.get('Content-Length') || '0', 10)\n const hasBodyStream = req.body !== null\n\n if (contentType === 'application/json') {\n try {\n const text = await req.text?.()\n const data = text ? JSON.parse(text) : {}\n req.data = data\n // @ts-expect-error attach json method to request\n req.json = () => Promise.resolve(data)\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new APIError('Invalid JSON', 400)\n }\n req.payload.logger.error(error)\n throw error\n }\n } else if ((bodyByteSize || hasBodyStream) && contentType?.includes('multipart/')) {\n const { error, fields, files } = await processMultipartFormdata({\n options: {\n ...(payload.config.bodyParser || {}),\n ...(payload.config.upload || {}),\n },\n request: req as Request,\n })\n\n if (error) {\n throw new APIError(error.message)\n }\n\n // Set all files on req.files for access by hooks\n if (files) {\n req.files = files\n // Backwards compatibility: set req.file for standard upload collections\n // Guard: if multiple files share the field name \"file\", files.file is an array — skip\n if (files.file && !Array.isArray(files.file)) {\n req.file = files.file\n }\n }\n\n if (fields?._payload && typeof fields._payload === 'string') {\n req.data = JSON.parse(fields._payload)\n }\n\n if (!req.file && fields?.file && typeof fields?.file === 'string') {\n let clientUploadFile: ClientUploadData\n\n try {\n clientUploadFile = JSON.parse(fields.file) as ClientUploadData\n } catch {\n throw new APIError('A file name is required.', 400)\n }\n\n req.file = await getFileFromClientUpload({\n file: clientUploadFile,\n req,\n })\n }\n }\n }\n}\n"],"names":["APIError","processMultipartFormdata","getFileFromClientUpload","addDataAndFileToRequest","req","body","headers","method","payload","includes","toUpperCase","contentType","get","split","bodyByteSize","parseInt","hasBodyStream","text","data","JSON","parse","json","Promise","resolve","error","SyntaxError","logger","fields","files","options","config","bodyParser","upload","request","message","file","Array","isArray","_payload","clientUploadFile"],"mappings":"AAGA,SAASA,QAAQ,QAAQ,wBAAuB;AAChD,SAASC,wBAAwB,QAAQ,yCAAwC;AACjF,SAASC,uBAAuB,QAAQ,wCAAuC;AAI/E;;CAEC,GACD,OAAO,MAAMC,0BAAmD,OAAOC;IACrE,MAAM,EAAEC,IAAI,EAAEC,OAAO,EAAEC,MAAM,EAAEC,OAAO,EAAE,GAAGJ;IAE3C,IAAIG,UAAU;QAAC;QAAS;QAAQ;KAAM,CAACE,QAAQ,CAACF,OAAOG,WAAW,OAAOL,MAAM;QAC7E,MAAM,CAACM,YAAY,GAAG,AAACL,CAAAA,QAAQM,GAAG,CAAC,mBAAmB,EAAC,EAAGC,KAAK,CAAC,KAAK;QACrE,MAAMC,eAAeC,SAASX,IAAIE,OAAO,CAACM,GAAG,CAAC,qBAAqB,KAAK;QACxE,MAAMI,gBAAgBZ,IAAIC,IAAI,KAAK;QAEnC,IAAIM,gBAAgB,oBAAoB;YACtC,IAAI;gBACF,MAAMM,OAAO,MAAMb,IAAIa,IAAI;gBAC3B,MAAMC,OAAOD,OAAOE,KAAKC,KAAK,CAACH,QAAQ,CAAC;gBACxCb,IAAIc,IAAI,GAAGA;gBACX,iDAAiD;gBACjDd,IAAIiB,IAAI,GAAG,IAAMC,QAAQC,OAAO,CAACL;YACnC,EAAE,OAAOM,OAAO;gBACd,IAAIA,iBAAiBC,aAAa;oBAChC,MAAM,IAAIzB,SAAS,gBAAgB;gBACrC;gBACAI,IAAII,OAAO,CAACkB,MAAM,CAACF,KAAK,CAACA;gBACzB,MAAMA;YACR;QACF,OAAO,IAAI,AAACV,CAAAA,gBAAgBE,aAAY,KAAML,aAAaF,SAAS,eAAe;YACjF,MAAM,EAAEe,KAAK,EAAEG,MAAM,EAAEC,KAAK,EAAE,GAAG,MAAM3B,yBAAyB;gBAC9D4B,SAAS;oBACP,GAAIrB,QAAQsB,MAAM,CAACC,UAAU,IAAI,CAAC,CAAC;oBACnC,GAAIvB,QAAQsB,MAAM,CAACE,MAAM,IAAI,CAAC,CAAC;gBACjC;gBACAC,SAAS7B;YACX;YAEA,IAAIoB,OAAO;gBACT,MAAM,IAAIxB,SAASwB,MAAMU,OAAO;YAClC;YAEA,iDAAiD;YACjD,IAAIN,OAAO;gBACTxB,IAAIwB,KAAK,GAAGA;gBACZ,wEAAwE;gBACxE,sFAAsF;gBACtF,IAAIA,MAAMO,IAAI,IAAI,CAACC,MAAMC,OAAO,CAACT,MAAMO,IAAI,GAAG;oBAC5C/B,IAAI+B,IAAI,GAAGP,MAAMO,IAAI;gBACvB;YACF;YAEA,IAAIR,QAAQW,YAAY,OAAOX,OAAOW,QAAQ,KAAK,UAAU;gBAC3DlC,IAAIc,IAAI,GAAGC,KAAKC,KAAK,CAACO,OAAOW,QAAQ;YACvC;YAEA,IAAI,CAAClC,IAAI+B,IAAI,IAAIR,QAAQQ,QAAQ,OAAOR,QAAQQ,SAAS,UAAU;gBACjE,IAAII;gBAEJ,IAAI;oBACFA,mBAAmBpB,KAAKC,KAAK,CAACO,OAAOQ,IAAI;gBAC3C,EAAE,OAAM;oBACN,MAAM,IAAInC,SAAS,4BAA4B;gBACjD;gBAEAI,IAAI+B,IAAI,GAAG,MAAMjC,wBAAwB;oBACvCiC,MAAMI;oBACNnC;gBACF;YACF;QACF;IACF;AACF,EAAC"}
@@ -1,4 +1,5 @@
1
- import { describe, expect, it } from 'vitest';
1
+ import fs from 'fs/promises';
2
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
3
  import { addDataAndFileToRequest } from './addDataAndFileToRequest.js';
3
4
  const createReqWithMultipartBody = ()=>{
4
5
  const formData = new FormData();
@@ -27,6 +28,41 @@ const createReqWithMultipartBody = ()=>{
27
28
  }
28
29
  };
29
30
  };
31
+ const createClientUploadReq = ({ file, handler, upload })=>{
32
+ const formData = new FormData();
33
+ formData.append('file', JSON.stringify(file));
34
+ const request = new Request('http://localhost/api/media', {
35
+ body: formData,
36
+ method: 'POST'
37
+ });
38
+ return {
39
+ body: request.body,
40
+ headers: request.headers,
41
+ method: request.method,
42
+ payload: {
43
+ collections: {
44
+ media: {
45
+ config: {
46
+ upload: {
47
+ disableLocalStorage: true,
48
+ handlers: [
49
+ handler
50
+ ],
51
+ ...upload
52
+ }
53
+ }
54
+ }
55
+ },
56
+ config: {
57
+ bodyParser: {},
58
+ upload: {}
59
+ },
60
+ logger: {
61
+ error: ()=>{}
62
+ }
63
+ }
64
+ };
65
+ };
30
66
  describe('addDataAndFileToRequest', ()=>{
31
67
  it('should parse multipart form-data even when content-length is absent', async ()=>{
32
68
  const req = createReqWithMultipartBody();
@@ -36,6 +72,100 @@ describe('addDataAndFileToRequest', ()=>{
36
72
  expect(req.file?.name).toBe('hello.txt');
37
73
  expect(req.file?.mimetype).toBe('text/plain');
38
74
  });
75
+ describe('client uploads', ()=>{
76
+ const tempFilesToRemove = [];
77
+ afterEach(async ()=>{
78
+ for (const tempFilePath of tempFilesToRemove){
79
+ await fs.rm(tempFilePath, {
80
+ force: true
81
+ });
82
+ }
83
+ tempFilesToRemove.length = 0;
84
+ });
85
+ it('materializes client-upload metadata without buffering an unused cloud file', async ()=>{
86
+ const handler = vi.fn(()=>{
87
+ throw new Error('No-content handler was invoked');
88
+ });
89
+ const req = createClientUploadReq({
90
+ file: {
91
+ clientUploadContext: {
92
+ prefix: ''
93
+ },
94
+ collectionSlug: 'media',
95
+ filename: 'large.mp4',
96
+ mimeType: 'video/mp4',
97
+ size: 5_000_000_000
98
+ },
99
+ handler,
100
+ upload: {
101
+ disableLocalStorage: true
102
+ }
103
+ });
104
+ await addDataAndFileToRequest(req);
105
+ expect(handler).not.toHaveBeenCalled();
106
+ expect(req.file).toMatchObject({
107
+ clientUploadContext: {
108
+ prefix: ''
109
+ },
110
+ mimetype: 'video/mp4',
111
+ name: 'large.mp4',
112
+ size: 5_000_000_000
113
+ });
114
+ expect(req.file?.data.length).toBe(0);
115
+ });
116
+ it('streams a full-content client upload to a temp file without buffering the whole body', async ()=>{
117
+ const chunks = [
118
+ Buffer.from('chunk-one-'),
119
+ Buffer.from('chunk-two')
120
+ ];
121
+ const stream = new ReadableStream({
122
+ start (controller) {
123
+ for (const chunk of chunks){
124
+ controller.enqueue(chunk);
125
+ }
126
+ controller.close();
127
+ }
128
+ });
129
+ const response = new Response(stream, {
130
+ headers: {
131
+ 'Content-Type': 'video/mp4'
132
+ },
133
+ status: 200
134
+ });
135
+ const arrayBufferTripwire = vi.fn(async ()=>{
136
+ throw new Error('Unexpected whole-body buffering');
137
+ });
138
+ Object.defineProperty(response, 'arrayBuffer', {
139
+ value: arrayBufferTripwire
140
+ });
141
+ const handler = vi.fn(async ()=>response);
142
+ const req = createClientUploadReq({
143
+ file: {
144
+ clientUploadContext: {
145
+ prefix: ''
146
+ },
147
+ collectionSlug: 'media',
148
+ filename: 'clip.mp4',
149
+ mimeType: 'video/mp4',
150
+ size: 20
151
+ },
152
+ handler,
153
+ upload: {
154
+ disableLocalStorage: true,
155
+ mimeTypes: [
156
+ 'video/*'
157
+ ]
158
+ }
159
+ });
160
+ await addDataAndFileToRequest(req);
161
+ tempFilesToRemove.push(req.file.tempFilePath);
162
+ expect(arrayBufferTripwire).not.toHaveBeenCalled();
163
+ expect(req.file?.data.length).toBe(0);
164
+ expect(req.file?.tempFilePath).toBeDefined();
165
+ const written = await fs.readFile(req.file.tempFilePath);
166
+ expect(written.toString()).toBe(chunks.map((chunk)=>chunk.toString()).join(''));
167
+ });
168
+ });
39
169
  });
40
170
 
41
171
  //# sourceMappingURL=addDataAndFileToRequest.spec.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/addDataAndFileToRequest.spec.ts"],"sourcesContent":["import type { PayloadRequest } from '../types/index.js'\n\nimport { describe, expect, it } from 'vitest'\n\nimport { addDataAndFileToRequest } from './addDataAndFileToRequest.js'\n\ntype MinimalReq = Pick<PayloadRequest, 'body' | 'headers' | 'method' | 'payload'> & {\n file?: PayloadRequest['file']\n}\n\nconst createReqWithMultipartBody = (): MinimalReq => {\n const formData = new FormData()\n formData.append('file', new Blob(['hello world'], { type: 'text/plain' }), 'hello.txt')\n\n const request = new Request('http://localhost/api/upload', {\n body: formData,\n method: 'POST',\n })\n\n return {\n body: request.body,\n headers: request.headers,\n method: request.method,\n payload: {\n collections: {},\n config: {\n bodyParser: {},\n upload: {},\n },\n logger: {\n error: () => {},\n },\n } as unknown as PayloadRequest['payload'],\n }\n}\n\ndescribe('addDataAndFileToRequest', () => {\n it('should parse multipart form-data even when content-length is absent', async () => {\n const req = createReqWithMultipartBody()\n\n expect(req.headers.get('content-length')).toBeNull()\n\n await addDataAndFileToRequest(req as PayloadRequest)\n\n expect(req.file).toBeDefined()\n expect(req.file?.name).toBe('hello.txt')\n expect(req.file?.mimetype).toBe('text/plain')\n })\n})\n"],"names":["describe","expect","it","addDataAndFileToRequest","createReqWithMultipartBody","formData","FormData","append","Blob","type","request","Request","body","method","headers","payload","collections","config","bodyParser","upload","logger","error","req","get","toBeNull","file","toBeDefined","name","toBe","mimetype"],"mappings":"AAEA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAQ;AAE7C,SAASC,uBAAuB,QAAQ,+BAA8B;AAMtE,MAAMC,6BAA6B;IACjC,MAAMC,WAAW,IAAIC;IACrBD,SAASE,MAAM,CAAC,QAAQ,IAAIC,KAAK;QAAC;KAAc,EAAE;QAAEC,MAAM;IAAa,IAAI;IAE3E,MAAMC,UAAU,IAAIC,QAAQ,+BAA+B;QACzDC,MAAMP;QACNQ,QAAQ;IACV;IAEA,OAAO;QACLD,MAAMF,QAAQE,IAAI;QAClBE,SAASJ,QAAQI,OAAO;QACxBD,QAAQH,QAAQG,MAAM;QACtBE,SAAS;YACPC,aAAa,CAAC;YACdC,QAAQ;gBACNC,YAAY,CAAC;gBACbC,QAAQ,CAAC;YACX;YACAC,QAAQ;gBACNC,OAAO,KAAO;YAChB;QACF;IACF;AACF;AAEArB,SAAS,2BAA2B;IAClCE,GAAG,uEAAuE;QACxE,MAAMoB,MAAMlB;QAEZH,OAAOqB,IAAIR,OAAO,CAACS,GAAG,CAAC,mBAAmBC,QAAQ;QAElD,MAAMrB,wBAAwBmB;QAE9BrB,OAAOqB,IAAIG,IAAI,EAAEC,WAAW;QAC5BzB,OAAOqB,IAAIG,IAAI,EAAEE,MAAMC,IAAI,CAAC;QAC5B3B,OAAOqB,IAAIG,IAAI,EAAEI,UAAUD,IAAI,CAAC;IAClC;AACF"}
1
+ {"version":3,"sources":["../../src/utilities/addDataAndFileToRequest.spec.ts"],"sourcesContent":["import type { PayloadRequest } from '../types/index.js'\nimport type { ClientUploadData } from '../uploads/getFileFromClientUpload.js'\nimport type { SanitizedUploadConfig } from '../uploads/types.js'\n\nimport fs from 'fs/promises'\nimport { afterEach, describe, expect, it, vi } from 'vitest'\n\nimport { addDataAndFileToRequest } from './addDataAndFileToRequest.js'\n\ntype MinimalReq = Pick<PayloadRequest, 'body' | 'headers' | 'method' | 'payload'> & {\n file?: PayloadRequest['file']\n}\n\nconst createReqWithMultipartBody = (): MinimalReq => {\n const formData = new FormData()\n formData.append('file', new Blob(['hello world'], { type: 'text/plain' }), 'hello.txt')\n\n const request = new Request('http://localhost/api/upload', {\n body: formData,\n method: 'POST',\n })\n\n return {\n body: request.body,\n headers: request.headers,\n method: request.method,\n payload: {\n collections: {},\n config: {\n bodyParser: {},\n upload: {},\n },\n logger: {\n error: () => {},\n },\n } as unknown as PayloadRequest['payload'],\n }\n}\n\nconst createClientUploadReq = ({\n file,\n handler,\n upload,\n}: {\n file: ClientUploadData\n handler: NonNullable<SanitizedUploadConfig['handlers']>[number]\n upload?: Partial<SanitizedUploadConfig>\n}): MinimalReq => {\n const formData = new FormData()\n formData.append('file', JSON.stringify(file))\n\n const request = new Request('http://localhost/api/media', {\n body: formData,\n method: 'POST',\n })\n\n return {\n body: request.body,\n headers: request.headers,\n method: request.method,\n payload: {\n collections: {\n media: {\n config: {\n upload: {\n disableLocalStorage: true,\n handlers: [handler],\n ...upload,\n },\n },\n },\n },\n config: {\n bodyParser: {},\n upload: {},\n },\n logger: {\n error: () => {},\n },\n } as unknown as PayloadRequest['payload'],\n }\n}\n\ndescribe('addDataAndFileToRequest', () => {\n it('should parse multipart form-data even when content-length is absent', async () => {\n const req = createReqWithMultipartBody()\n\n expect(req.headers.get('content-length')).toBeNull()\n\n await addDataAndFileToRequest(req as PayloadRequest)\n\n expect(req.file).toBeDefined()\n expect(req.file?.name).toBe('hello.txt')\n expect(req.file?.mimetype).toBe('text/plain')\n })\n\n describe('client uploads', () => {\n const tempFilesToRemove: string[] = []\n\n afterEach(async () => {\n for (const tempFilePath of tempFilesToRemove) {\n await fs.rm(tempFilePath, { force: true })\n }\n tempFilesToRemove.length = 0\n })\n\n it('materializes client-upload metadata without buffering an unused cloud file', async () => {\n const handler = vi.fn(() => {\n throw new Error('No-content handler was invoked')\n })\n const req = createClientUploadReq({\n file: {\n clientUploadContext: { prefix: '' },\n collectionSlug: 'media',\n filename: 'large.mp4',\n mimeType: 'video/mp4',\n size: 5_000_000_000,\n },\n handler,\n upload: { disableLocalStorage: true },\n })\n\n await addDataAndFileToRequest(req as PayloadRequest)\n\n expect(handler).not.toHaveBeenCalled()\n expect(req.file).toMatchObject({\n clientUploadContext: { prefix: '' },\n mimetype: 'video/mp4',\n name: 'large.mp4',\n size: 5_000_000_000,\n })\n expect(req.file?.data.length).toBe(0)\n })\n\n it('streams a full-content client upload to a temp file without buffering the whole body', async () => {\n const chunks = [Buffer.from('chunk-one-'), Buffer.from('chunk-two')]\n const stream = new ReadableStream({\n start(controller) {\n for (const chunk of chunks) {\n controller.enqueue(chunk)\n }\n controller.close()\n },\n })\n const response = new Response(stream, {\n headers: { 'Content-Type': 'video/mp4' },\n status: 200,\n })\n const arrayBufferTripwire = vi.fn(async () => {\n throw new Error('Unexpected whole-body buffering')\n })\n Object.defineProperty(response, 'arrayBuffer', { value: arrayBufferTripwire })\n\n const handler = vi.fn(async () => response)\n const req = createClientUploadReq({\n file: {\n clientUploadContext: { prefix: '' },\n collectionSlug: 'media',\n filename: 'clip.mp4',\n mimeType: 'video/mp4',\n size: 20,\n },\n handler,\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n\n await addDataAndFileToRequest(req as PayloadRequest)\n tempFilesToRemove.push(req.file!.tempFilePath!)\n\n expect(arrayBufferTripwire).not.toHaveBeenCalled()\n expect(req.file?.data.length).toBe(0)\n expect(req.file?.tempFilePath).toBeDefined()\n const written = await fs.readFile(req.file!.tempFilePath!)\n expect(written.toString()).toBe(chunks.map((chunk) => chunk.toString()).join(''))\n })\n })\n})\n"],"names":["fs","afterEach","describe","expect","it","vi","addDataAndFileToRequest","createReqWithMultipartBody","formData","FormData","append","Blob","type","request","Request","body","method","headers","payload","collections","config","bodyParser","upload","logger","error","createClientUploadReq","file","handler","JSON","stringify","media","disableLocalStorage","handlers","req","get","toBeNull","toBeDefined","name","toBe","mimetype","tempFilesToRemove","tempFilePath","rm","force","length","fn","Error","clientUploadContext","prefix","collectionSlug","filename","mimeType","size","not","toHaveBeenCalled","toMatchObject","data","chunks","Buffer","from","stream","ReadableStream","start","controller","chunk","enqueue","close","response","Response","status","arrayBufferTripwire","Object","defineProperty","value","mimeTypes","push","written","readFile","toString","map","join"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payload",
3
- "version": "3.88.0",
3
+ "version": "3.89.0-internal.95c9cba",
4
4
  "description": "Node, React, Headless CMS and Application Framework built on Next.js",
5
5
  "keywords": [
6
6
  "admin panel",
@@ -115,7 +115,7 @@
115
115
  "undici": "7.29.0",
116
116
  "uuid": "13.0.2",
117
117
  "ws": "^8.16.0",
118
- "@payloadcms/translations": "3.88.0"
118
+ "@payloadcms/translations": "3.89.0-internal.95c9cba"
119
119
  },
120
120
  "devDependencies": {
121
121
  "@hyrious/esbuild-plugin-commonjs": "0.2.6",