payload 3.89.0-internal.95c9cba → 3.89.0

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 (40) hide show
  1. package/dist/collections/operations/update.js +5 -5
  2. package/dist/collections/operations/update.js.map +1 -1
  3. package/dist/index.bundled.d.ts +49 -29
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/queues/config/collection.d.ts.map +1 -1
  9. package/dist/queues/config/collection.js +6 -0
  10. package/dist/queues/config/collection.js.map +1 -1
  11. package/dist/queues/config/types/index.d.ts +2 -0
  12. package/dist/queues/config/types/index.d.ts.map +1 -1
  13. package/dist/queues/config/types/index.js.map +1 -1
  14. package/dist/queues/endpoints/handleSchedules.d.ts.map +1 -1
  15. package/dist/queues/endpoints/handleSchedules.js +2 -1
  16. package/dist/queues/endpoints/handleSchedules.js.map +1 -1
  17. package/dist/queues/endpoints/run.d.ts.map +1 -1
  18. package/dist/queues/endpoints/run.js +2 -1
  19. package/dist/queues/endpoints/run.js.map +1 -1
  20. package/dist/uploads/getFileFromClientUpload.d.ts +2 -2
  21. package/dist/uploads/getFileFromClientUpload.d.ts.map +1 -1
  22. package/dist/uploads/getFileFromClientUpload.js +4 -4
  23. package/dist/uploads/getFileFromClientUpload.js.map +1 -1
  24. package/dist/uploads/getFileFromClientUpload.spec.js +62 -0
  25. package/dist/uploads/getFileFromClientUpload.spec.js.map +1 -1
  26. package/dist/uploads/unlinkClientUploadTempFile.d.ts +20 -0
  27. package/dist/uploads/unlinkClientUploadTempFile.d.ts.map +1 -0
  28. package/dist/uploads/unlinkClientUploadTempFile.js +30 -0
  29. package/dist/uploads/unlinkClientUploadTempFile.js.map +1 -0
  30. package/dist/uploads/unlinkTempFiles.d.ts.map +1 -1
  31. package/dist/uploads/unlinkTempFiles.js +7 -9
  32. package/dist/uploads/unlinkTempFiles.js.map +1 -1
  33. package/dist/uploads/unlinkTempFiles.spec.js +61 -1
  34. package/dist/uploads/unlinkTempFiles.spec.js.map +1 -1
  35. package/dist/utilities/wrapInternalEndpoints.d.ts.map +1 -1
  36. package/dist/utilities/wrapInternalEndpoints.js +10 -3
  37. package/dist/utilities/wrapInternalEndpoints.js.map +1 -1
  38. package/dist/utilities/wrapInternalEndpoints.spec.js +124 -0
  39. package/dist/utilities/wrapInternalEndpoints.spec.js.map +1 -0
  40. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/uploads/unlinkTempFiles.ts"],"sourcesContent":["import fs from 'fs/promises'\n\nimport type { SanitizedCollectionConfig } from '../collections/config/types.js'\nimport type { SanitizedConfig } from '../config/types.js'\nimport type { PayloadRequest } from '../types/index.js'\n\nimport { mapAsync } from '../utilities/mapAsync.js'\nimport { CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY } from './getFileFromClientUpload.js'\n\ntype Args = {\n collectionConfig: SanitizedCollectionConfig\n config: SanitizedConfig\n req: PayloadRequest\n}\n/**\n * Cleanup temp files after operation lifecycle\n */\nexport const unlinkTempFiles: (args: Args) => Promise<void> = async ({\n collectionConfig,\n config,\n req,\n}) => {\n const { file } = req\n const isClientUploadTempFile = Boolean(\n file?.tempFilePath && Object.prototype.hasOwnProperty.call(file, 'clientUploadContext'),\n )\n\n if (collectionConfig.upload && (config.upload?.useTempFiles || isClientUploadTempFile)) {\n const fileArray = [{ file }]\n await mapAsync(fileArray, async ({ file }) => {\n // Still need this check because this will not be populated if using local API\n if (file?.tempFilePath) {\n await fs.unlink(file.tempFilePath)\n }\n })\n }\n\n // plugin-cloud-storage's afterChange hook clears req.file after uploading generated image\n // sizes, before this cleanup runs, so the materializer's temp file is tracked separately and\n // may still need removing even though req.file no longer references it.\n const clientUploadTempFilePath = req.context?.[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]\n\n if (\n typeof clientUploadTempFilePath === 'string' &&\n clientUploadTempFilePath !== file?.tempFilePath\n ) {\n delete req.context[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]\n await fs.unlink(clientUploadTempFilePath)\n }\n}\n"],"names":["fs","mapAsync","CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY","unlinkTempFiles","collectionConfig","config","req","file","isClientUploadTempFile","Boolean","tempFilePath","Object","prototype","hasOwnProperty","call","upload","useTempFiles","fileArray","unlink","clientUploadTempFilePath","context"],"mappings":"AAAA,OAAOA,QAAQ,cAAa;AAM5B,SAASC,QAAQ,QAAQ,2BAA0B;AACnD,SAASC,wCAAwC,QAAQ,+BAA8B;AAOvF;;CAEC,GACD,OAAO,MAAMC,kBAAiD,OAAO,EACnEC,gBAAgB,EAChBC,MAAM,EACNC,GAAG,EACJ;IACC,MAAM,EAAEC,IAAI,EAAE,GAAGD;IACjB,MAAME,yBAAyBC,QAC7BF,MAAMG,gBAAgBC,OAAOC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACP,MAAM;IAGnE,IAAIH,iBAAiBW,MAAM,IAAKV,CAAAA,OAAOU,MAAM,EAAEC,gBAAgBR,sBAAqB,GAAI;QACtF,MAAMS,YAAY;YAAC;gBAAEV;YAAK;SAAE;QAC5B,MAAMN,SAASgB,WAAW,OAAO,EAAEV,IAAI,EAAE;YACvC,8EAA8E;YAC9E,IAAIA,MAAMG,cAAc;gBACtB,MAAMV,GAAGkB,MAAM,CAACX,KAAKG,YAAY;YACnC;QACF;IACF;IAEA,0FAA0F;IAC1F,6FAA6F;IAC7F,wEAAwE;IACxE,MAAMS,2BAA2Bb,IAAIc,OAAO,EAAE,CAAClB,yCAAyC;IAExF,IACE,OAAOiB,6BAA6B,YACpCA,6BAA6BZ,MAAMG,cACnC;QACA,OAAOJ,IAAIc,OAAO,CAAClB,yCAAyC;QAC5D,MAAMF,GAAGkB,MAAM,CAACC;IAClB;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/uploads/unlinkTempFiles.ts"],"sourcesContent":["import fs from 'fs/promises'\n\nimport type { SanitizedCollectionConfig } from '../collections/config/types.js'\nimport type { SanitizedConfig } from '../config/types.js'\nimport type { PayloadRequest } from '../types/index.js'\n\nimport { mapAsync } from '../utilities/mapAsync.js'\nimport { unlinkClientUploadTempFile } from './unlinkClientUploadTempFile.js'\n\ntype Args = {\n collectionConfig: SanitizedCollectionConfig\n config: SanitizedConfig\n req: PayloadRequest\n}\n/**\n * Cleanup temp files after operation lifecycle\n */\nexport const unlinkTempFiles: (args: Args) => Promise<void> = async ({\n collectionConfig,\n config,\n req,\n}) => {\n const { file } = req\n const isClientUploadTempFile = Boolean(\n file?.tempFilePath && Object.prototype.hasOwnProperty.call(file, 'clientUploadContext'),\n )\n let unlinkedTempFilePath: string | undefined\n\n if (collectionConfig.upload && (config.upload?.useTempFiles || isClientUploadTempFile)) {\n const fileArray = [{ file }]\n await mapAsync(fileArray, async ({ file }) => {\n // Still need this check because this will not be populated if using local API\n if (file?.tempFilePath) {\n await fs.unlink(file.tempFilePath)\n unlinkedTempFilePath = file.tempFilePath\n }\n })\n }\n\n await unlinkClientUploadTempFile({ alreadyUnlinkedPath: unlinkedTempFilePath, req })\n}\n"],"names":["fs","mapAsync","unlinkClientUploadTempFile","unlinkTempFiles","collectionConfig","config","req","file","isClientUploadTempFile","Boolean","tempFilePath","Object","prototype","hasOwnProperty","call","unlinkedTempFilePath","upload","useTempFiles","fileArray","unlink","alreadyUnlinkedPath"],"mappings":"AAAA,OAAOA,QAAQ,cAAa;AAM5B,SAASC,QAAQ,QAAQ,2BAA0B;AACnD,SAASC,0BAA0B,QAAQ,kCAAiC;AAO5E;;CAEC,GACD,OAAO,MAAMC,kBAAiD,OAAO,EACnEC,gBAAgB,EAChBC,MAAM,EACNC,GAAG,EACJ;IACC,MAAM,EAAEC,IAAI,EAAE,GAAGD;IACjB,MAAME,yBAAyBC,QAC7BF,MAAMG,gBAAgBC,OAAOC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACP,MAAM;IAEnE,IAAIQ;IAEJ,IAAIX,iBAAiBY,MAAM,IAAKX,CAAAA,OAAOW,MAAM,EAAEC,gBAAgBT,sBAAqB,GAAI;QACtF,MAAMU,YAAY;YAAC;gBAAEX;YAAK;SAAE;QAC5B,MAAMN,SAASiB,WAAW,OAAO,EAAEX,IAAI,EAAE;YACvC,8EAA8E;YAC9E,IAAIA,MAAMG,cAAc;gBACtB,MAAMV,GAAGmB,MAAM,CAACZ,KAAKG,YAAY;gBACjCK,uBAAuBR,KAAKG,YAAY;YAC1C;QACF;IACF;IAEA,MAAMR,2BAA2B;QAAEkB,qBAAqBL;QAAsBT;IAAI;AACpF,EAAC"}
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs/promises';
2
2
  import os from 'os';
3
3
  import path from 'path';
4
- import { afterEach, describe, expect, it } from 'vitest';
4
+ import { afterEach, describe, expect, it, vi } from 'vitest';
5
5
  import { CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY } from './getFileFromClientUpload.js';
6
6
  import { unlinkTempFiles } from './unlinkTempFiles.js';
7
7
  const createTempFile = async (contents = 'temp-file-contents')=>{
@@ -22,6 +22,7 @@ const collectionConfig = {
22
22
  disableLocalStorage: true
23
23
  }
24
24
  };
25
+ const nonUploadCollectionConfig = {};
25
26
  describe('unlinkTempFiles', ()=>{
26
27
  const tempFilesToRemove = [];
27
28
  afterEach(async ()=>{
@@ -152,6 +153,65 @@ describe('unlinkTempFiles', ()=>{
152
153
  })).resolves.not.toThrow();
153
154
  expect(await fileExists(tempFilePath)).toBe(false);
154
155
  });
156
+ it('removes a client-upload temp file when the collection is not an upload collection', async ()=>{
157
+ const tempFilePath = await createTempFile();
158
+ tempFilesToRemove.push(tempFilePath);
159
+ // A forged `collectionSlug` in the multipart file field materializes a temp file for a
160
+ // collection that never accepts uploads, so nothing in the upload branch removes it.
161
+ const req = {
162
+ context: {
163
+ [CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]: tempFilePath
164
+ },
165
+ file: {
166
+ clientUploadContext: {
167
+ prefix: ''
168
+ },
169
+ data: Buffer.alloc(0),
170
+ mimetype: 'video/mp4',
171
+ name: 'clip.mp4',
172
+ size: 10,
173
+ tempFilePath
174
+ }
175
+ };
176
+ await unlinkTempFiles({
177
+ collectionConfig: nonUploadCollectionConfig,
178
+ config: {
179
+ upload: {
180
+ useTempFiles: false
181
+ }
182
+ },
183
+ req
184
+ });
185
+ expect(await fileExists(tempFilePath)).toBe(false);
186
+ expect(req.context[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]).toBeUndefined();
187
+ });
188
+ it('logs instead of throwing when a client-upload temp file can no longer be removed', async ()=>{
189
+ const req = {
190
+ context: {
191
+ [CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]: path.join(os.tmpdir(), 'unlink-temp-files-spec-missing-file')
192
+ },
193
+ payload: {
194
+ logger: {
195
+ error: vi.fn()
196
+ }
197
+ }
198
+ };
199
+ await expect(unlinkTempFiles({
200
+ collectionConfig,
201
+ config: {
202
+ upload: {
203
+ useTempFiles: false
204
+ }
205
+ },
206
+ req
207
+ })).resolves.toBeUndefined();
208
+ expect(req.payload.logger.error).toHaveBeenCalledWith({
209
+ err: expect.objectContaining({
210
+ code: 'ENOENT'
211
+ }),
212
+ msg: 'Failed to remove client upload temp file'
213
+ });
214
+ });
155
215
  });
156
216
 
157
217
  //# sourceMappingURL=unlinkTempFiles.spec.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/uploads/unlinkTempFiles.spec.ts"],"sourcesContent":["import type { SanitizedCollectionConfig } from '../collections/config/types.js'\nimport type { SanitizedConfig } from '../config/types.js'\nimport type { PayloadRequest } from '../types/index.js'\n\nimport fs from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport { afterEach, describe, expect, it } from 'vitest'\n\nimport { CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY } from './getFileFromClientUpload.js'\nimport { unlinkTempFiles } from './unlinkTempFiles.js'\n\nconst createTempFile = async (contents = 'temp-file-contents'): Promise<string> => {\n const tempFilePath = path.join(\n os.tmpdir(),\n `unlink-temp-files-spec-${Date.now()}-${Math.random()}`,\n )\n await fs.writeFile(tempFilePath, contents)\n return tempFilePath\n}\n\nconst fileExists = async (filePath: string): Promise<boolean> => {\n try {\n await fs.access(filePath)\n return true\n } catch {\n return false\n }\n}\n\nconst collectionConfig = {\n upload: { disableLocalStorage: true },\n} as unknown as SanitizedCollectionConfig\n\ndescribe('unlinkTempFiles', () => {\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('removes a client-upload materialized temp file even when useTempFiles is false', async () => {\n const tempFilePath = await createTempFile()\n tempFilesToRemove.push(tempFilePath)\n\n const req = {\n file: {\n clientUploadContext: undefined,\n data: Buffer.alloc(0),\n mimetype: 'video/mp4',\n name: 'clip.mp4',\n size: 10,\n tempFilePath,\n },\n } as unknown as PayloadRequest\n\n await unlinkTempFiles({\n collectionConfig,\n config: { upload: { useTempFiles: false } } as unknown as SanitizedConfig,\n req,\n })\n\n expect(await fileExists(tempFilePath)).toBe(false)\n })\n\n it('leaves a local-API temp file alone when useTempFiles is false', async () => {\n const tempFilePath = await createTempFile()\n tempFilesToRemove.push(tempFilePath)\n\n const req = {\n file: {\n data: Buffer.alloc(0),\n mimetype: 'video/mp4',\n name: 'clip.mp4',\n size: 10,\n tempFilePath,\n },\n } as unknown as PayloadRequest\n\n await unlinkTempFiles({\n collectionConfig,\n config: { upload: { useTempFiles: false } } as unknown as SanitizedConfig,\n req,\n })\n\n expect(await fileExists(tempFilePath)).toBe(true)\n })\n\n it('removes a multipart temp file when useTempFiles is true', async () => {\n const tempFilePath = await createTempFile()\n tempFilesToRemove.push(tempFilePath)\n\n const req = {\n file: {\n data: Buffer.alloc(0),\n mimetype: 'video/mp4',\n name: 'clip.mp4',\n size: 10,\n tempFilePath,\n },\n } as unknown as PayloadRequest\n\n await unlinkTempFiles({\n collectionConfig,\n config: { upload: { useTempFiles: true } } as unknown as SanitizedConfig,\n req,\n })\n\n expect(await fileExists(tempFilePath)).toBe(false)\n })\n\n it('removes a client-upload temp file tracked on req.context after req.file was cleared', async () => {\n const tempFilePath = await createTempFile()\n tempFilesToRemove.push(tempFilePath)\n\n // Mirrors plugin-cloud-storage's afterChange hook, which clears req.file after uploading\n // generated image sizes but leaves req.context untouched.\n const req = {\n context: { [CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]: tempFilePath },\n file: undefined,\n } as unknown as PayloadRequest\n\n await unlinkTempFiles({\n collectionConfig,\n config: { upload: { useTempFiles: false } } as unknown as SanitizedConfig,\n req,\n })\n\n expect(await fileExists(tempFilePath)).toBe(false)\n expect(req.context[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]).toBeUndefined()\n })\n\n it('does not attempt a second unlink when the context-tracked path matches req.file.tempFilePath', async () => {\n const tempFilePath = await createTempFile()\n tempFilesToRemove.push(tempFilePath)\n\n const req = {\n context: { [CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]: tempFilePath },\n file: {\n clientUploadContext: undefined,\n data: Buffer.alloc(0),\n mimetype: 'video/mp4',\n name: 'clip.mp4',\n size: 10,\n tempFilePath,\n },\n } as unknown as PayloadRequest\n\n await expect(\n unlinkTempFiles({\n collectionConfig,\n config: { upload: { useTempFiles: false } } as unknown as SanitizedConfig,\n req,\n }),\n ).resolves.not.toThrow()\n\n expect(await fileExists(tempFilePath)).toBe(false)\n })\n})\n"],"names":["fs","os","path","afterEach","describe","expect","it","CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY","unlinkTempFiles","createTempFile","contents","tempFilePath","join","tmpdir","Date","now","Math","random","writeFile","fileExists","filePath","access","collectionConfig","upload","disableLocalStorage","tempFilesToRemove","rm","force","length","push","req","file","clientUploadContext","undefined","data","Buffer","alloc","mimetype","name","size","config","useTempFiles","toBe","context","toBeUndefined","resolves","not","toThrow"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/uploads/unlinkTempFiles.spec.ts"],"sourcesContent":["import type { SanitizedCollectionConfig } from '../collections/config/types.js'\nimport type { SanitizedConfig } from '../config/types.js'\nimport type { PayloadRequest } from '../types/index.js'\n\nimport fs from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport { afterEach, describe, expect, it, vi } from 'vitest'\n\nimport { CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY } from './getFileFromClientUpload.js'\nimport { unlinkTempFiles } from './unlinkTempFiles.js'\n\nconst createTempFile = async (contents = 'temp-file-contents'): Promise<string> => {\n const tempFilePath = path.join(\n os.tmpdir(),\n `unlink-temp-files-spec-${Date.now()}-${Math.random()}`,\n )\n await fs.writeFile(tempFilePath, contents)\n return tempFilePath\n}\n\nconst fileExists = async (filePath: string): Promise<boolean> => {\n try {\n await fs.access(filePath)\n return true\n } catch {\n return false\n }\n}\n\nconst collectionConfig = {\n upload: { disableLocalStorage: true },\n} as unknown as SanitizedCollectionConfig\n\nconst nonUploadCollectionConfig = {} as unknown as SanitizedCollectionConfig\n\ndescribe('unlinkTempFiles', () => {\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('removes a client-upload materialized temp file even when useTempFiles is false', async () => {\n const tempFilePath = await createTempFile()\n tempFilesToRemove.push(tempFilePath)\n\n const req = {\n file: {\n clientUploadContext: undefined,\n data: Buffer.alloc(0),\n mimetype: 'video/mp4',\n name: 'clip.mp4',\n size: 10,\n tempFilePath,\n },\n } as unknown as PayloadRequest\n\n await unlinkTempFiles({\n collectionConfig,\n config: { upload: { useTempFiles: false } } as unknown as SanitizedConfig,\n req,\n })\n\n expect(await fileExists(tempFilePath)).toBe(false)\n })\n\n it('leaves a local-API temp file alone when useTempFiles is false', async () => {\n const tempFilePath = await createTempFile()\n tempFilesToRemove.push(tempFilePath)\n\n const req = {\n file: {\n data: Buffer.alloc(0),\n mimetype: 'video/mp4',\n name: 'clip.mp4',\n size: 10,\n tempFilePath,\n },\n } as unknown as PayloadRequest\n\n await unlinkTempFiles({\n collectionConfig,\n config: { upload: { useTempFiles: false } } as unknown as SanitizedConfig,\n req,\n })\n\n expect(await fileExists(tempFilePath)).toBe(true)\n })\n\n it('removes a multipart temp file when useTempFiles is true', async () => {\n const tempFilePath = await createTempFile()\n tempFilesToRemove.push(tempFilePath)\n\n const req = {\n file: {\n data: Buffer.alloc(0),\n mimetype: 'video/mp4',\n name: 'clip.mp4',\n size: 10,\n tempFilePath,\n },\n } as unknown as PayloadRequest\n\n await unlinkTempFiles({\n collectionConfig,\n config: { upload: { useTempFiles: true } } as unknown as SanitizedConfig,\n req,\n })\n\n expect(await fileExists(tempFilePath)).toBe(false)\n })\n\n it('removes a client-upload temp file tracked on req.context after req.file was cleared', async () => {\n const tempFilePath = await createTempFile()\n tempFilesToRemove.push(tempFilePath)\n\n // Mirrors plugin-cloud-storage's afterChange hook, which clears req.file after uploading\n // generated image sizes but leaves req.context untouched.\n const req = {\n context: { [CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]: tempFilePath },\n file: undefined,\n } as unknown as PayloadRequest\n\n await unlinkTempFiles({\n collectionConfig,\n config: { upload: { useTempFiles: false } } as unknown as SanitizedConfig,\n req,\n })\n\n expect(await fileExists(tempFilePath)).toBe(false)\n expect(req.context[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]).toBeUndefined()\n })\n\n it('does not attempt a second unlink when the context-tracked path matches req.file.tempFilePath', async () => {\n const tempFilePath = await createTempFile()\n tempFilesToRemove.push(tempFilePath)\n\n const req = {\n context: { [CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]: tempFilePath },\n file: {\n clientUploadContext: undefined,\n data: Buffer.alloc(0),\n mimetype: 'video/mp4',\n name: 'clip.mp4',\n size: 10,\n tempFilePath,\n },\n } as unknown as PayloadRequest\n\n await expect(\n unlinkTempFiles({\n collectionConfig,\n config: { upload: { useTempFiles: false } } as unknown as SanitizedConfig,\n req,\n }),\n ).resolves.not.toThrow()\n\n expect(await fileExists(tempFilePath)).toBe(false)\n })\n\n it('removes a client-upload temp file when the collection is not an upload collection', async () => {\n const tempFilePath = await createTempFile()\n tempFilesToRemove.push(tempFilePath)\n\n // A forged `collectionSlug` in the multipart file field materializes a temp file for a\n // collection that never accepts uploads, so nothing in the upload branch removes it.\n const req = {\n context: { [CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]: tempFilePath },\n file: {\n clientUploadContext: { prefix: '' },\n data: Buffer.alloc(0),\n mimetype: 'video/mp4',\n name: 'clip.mp4',\n size: 10,\n tempFilePath,\n },\n } as unknown as PayloadRequest\n\n await unlinkTempFiles({\n collectionConfig: nonUploadCollectionConfig,\n config: { upload: { useTempFiles: false } } as unknown as SanitizedConfig,\n req,\n })\n\n expect(await fileExists(tempFilePath)).toBe(false)\n expect(req.context[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]).toBeUndefined()\n })\n\n it('logs instead of throwing when a client-upload temp file can no longer be removed', async () => {\n const req = {\n context: {\n [CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]: path.join(\n os.tmpdir(),\n 'unlink-temp-files-spec-missing-file',\n ),\n },\n payload: { logger: { error: vi.fn() } },\n } as unknown as PayloadRequest\n\n await expect(\n unlinkTempFiles({\n collectionConfig,\n config: { upload: { useTempFiles: false } } as unknown as SanitizedConfig,\n req,\n }),\n ).resolves.toBeUndefined()\n\n expect(req.payload.logger.error).toHaveBeenCalledWith({\n err: expect.objectContaining({ code: 'ENOENT' }),\n msg: 'Failed to remove client upload temp file',\n })\n })\n})\n"],"names":["fs","os","path","afterEach","describe","expect","it","vi","CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY","unlinkTempFiles","createTempFile","contents","tempFilePath","join","tmpdir","Date","now","Math","random","writeFile","fileExists","filePath","access","collectionConfig","upload","disableLocalStorage","nonUploadCollectionConfig","tempFilesToRemove","rm","force","length","push","req","file","clientUploadContext","undefined","data","Buffer","alloc","mimetype","name","size","config","useTempFiles","toBe","context","toBeUndefined","resolves","not","toThrow","prefix","payload","logger","error","fn","toHaveBeenCalledWith","err","objectContaining","code","msg"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"wrapInternalEndpoints.d.ts","sourceRoot":"","sources":["../../src/utilities/wrapInternalEndpoints.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAKlD,eAAO,MAAM,qBAAqB,cAAe,QAAQ,EAAE,KAAG,QAAQ,EAcrE,CAAA"}
1
+ {"version":3,"file":"wrapInternalEndpoints.d.ts","sourceRoot":"","sources":["../../src/utilities/wrapInternalEndpoints.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAMlD,eAAO,MAAM,qBAAqB,cAAe,QAAQ,EAAE,KAAG,QAAQ,EAkBrE,CAAA"}
@@ -1,3 +1,4 @@
1
+ import { unlinkClientUploadTempFile } from '../uploads/unlinkClientUploadTempFile.js';
1
2
  import { addDataAndFileToRequest } from './addDataAndFileToRequest.js';
2
3
  import { addLocalesToRequestFromData } from './addLocalesToRequest.js';
3
4
  export const wrapInternalEndpoints = (endpoints)=>{
@@ -8,9 +9,15 @@ export const wrapInternalEndpoints = (endpoints)=>{
8
9
  'post'
9
10
  ].includes(endpoint.method)) {
10
11
  endpoint.handler = async (req)=>{
11
- await addDataAndFileToRequest(req);
12
- addLocalesToRequestFromData(req);
13
- return handler(req);
12
+ try {
13
+ await addDataAndFileToRequest(req);
14
+ addLocalesToRequestFromData(req);
15
+ return await handler(req);
16
+ } finally{
17
+ await unlinkClientUploadTempFile({
18
+ req
19
+ });
20
+ }
14
21
  };
15
22
  }
16
23
  return endpoint;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/wrapInternalEndpoints.ts"],"sourcesContent":["import type { Endpoint } from '../config/types.js'\n\nimport { addDataAndFileToRequest } from './addDataAndFileToRequest.js'\nimport { addLocalesToRequestFromData } from './addLocalesToRequest.js'\n\nexport const wrapInternalEndpoints = (endpoints: Endpoint[]): Endpoint[] => {\n return endpoints.map((endpoint) => {\n const handler = endpoint.handler\n\n if (['patch', 'post'].includes(endpoint.method)) {\n endpoint.handler = async (req) => {\n await addDataAndFileToRequest(req)\n addLocalesToRequestFromData(req)\n return handler(req)\n }\n }\n\n return endpoint\n })\n}\n"],"names":["addDataAndFileToRequest","addLocalesToRequestFromData","wrapInternalEndpoints","endpoints","map","endpoint","handler","includes","method","req"],"mappings":"AAEA,SAASA,uBAAuB,QAAQ,+BAA8B;AACtE,SAASC,2BAA2B,QAAQ,2BAA0B;AAEtE,OAAO,MAAMC,wBAAwB,CAACC;IACpC,OAAOA,UAAUC,GAAG,CAAC,CAACC;QACpB,MAAMC,UAAUD,SAASC,OAAO;QAEhC,IAAI;YAAC;YAAS;SAAO,CAACC,QAAQ,CAACF,SAASG,MAAM,GAAG;YAC/CH,SAASC,OAAO,GAAG,OAAOG;gBACxB,MAAMT,wBAAwBS;gBAC9BR,4BAA4BQ;gBAC5B,OAAOH,QAAQG;YACjB;QACF;QAEA,OAAOJ;IACT;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/utilities/wrapInternalEndpoints.ts"],"sourcesContent":["import type { Endpoint } from '../config/types.js'\n\nimport { unlinkClientUploadTempFile } from '../uploads/unlinkClientUploadTempFile.js'\nimport { addDataAndFileToRequest } from './addDataAndFileToRequest.js'\nimport { addLocalesToRequestFromData } from './addLocalesToRequest.js'\n\nexport const wrapInternalEndpoints = (endpoints: Endpoint[]): Endpoint[] => {\n return endpoints.map((endpoint) => {\n const handler = endpoint.handler\n\n if (['patch', 'post'].includes(endpoint.method)) {\n endpoint.handler = async (req) => {\n try {\n await addDataAndFileToRequest(req)\n addLocalesToRequestFromData(req)\n return await handler(req)\n } finally {\n await unlinkClientUploadTempFile({ req })\n }\n }\n }\n\n return endpoint\n })\n}\n"],"names":["unlinkClientUploadTempFile","addDataAndFileToRequest","addLocalesToRequestFromData","wrapInternalEndpoints","endpoints","map","endpoint","handler","includes","method","req"],"mappings":"AAEA,SAASA,0BAA0B,QAAQ,2CAA0C;AACrF,SAASC,uBAAuB,QAAQ,+BAA8B;AACtE,SAASC,2BAA2B,QAAQ,2BAA0B;AAEtE,OAAO,MAAMC,wBAAwB,CAACC;IACpC,OAAOA,UAAUC,GAAG,CAAC,CAACC;QACpB,MAAMC,UAAUD,SAASC,OAAO;QAEhC,IAAI;YAAC;YAAS;SAAO,CAACC,QAAQ,CAACF,SAASG,MAAM,GAAG;YAC/CH,SAASC,OAAO,GAAG,OAAOG;gBACxB,IAAI;oBACF,MAAMT,wBAAwBS;oBAC9BR,4BAA4BQ;oBAC5B,OAAO,MAAMH,QAAQG;gBACvB,SAAU;oBACR,MAAMV,2BAA2B;wBAAEU;oBAAI;gBACzC;YACF;QACF;QAEA,OAAOJ;IACT;AACF,EAAC"}
@@ -0,0 +1,124 @@
1
+ import fs from 'fs/promises';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { afterEach, describe, expect, it, vi } from 'vitest';
5
+ import { CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY } from '../uploads/getFileFromClientUpload.js';
6
+ import { wrapInternalEndpoints } from './wrapInternalEndpoints.js';
7
+ const createTempFile = async ()=>{
8
+ const tempFilePath = path.join(os.tmpdir(), `wrap-internal-endpoints-spec-${Date.now()}-${Math.random()}`);
9
+ await fs.writeFile(tempFilePath, 'temp-file-contents');
10
+ return tempFilePath;
11
+ };
12
+ const fileExists = async (filePath)=>{
13
+ try {
14
+ await fs.access(filePath);
15
+ return true;
16
+ } catch {
17
+ return false;
18
+ }
19
+ };
20
+ const createReq = (tempFilePath)=>({
21
+ body: null,
22
+ context: tempFilePath ? {
23
+ [CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]: tempFilePath
24
+ } : {},
25
+ headers: new Headers(),
26
+ method: 'POST',
27
+ payload: {
28
+ config: {},
29
+ logger: {
30
+ error: vi.fn()
31
+ }
32
+ }
33
+ });
34
+ const wrapEndpoint = (handler)=>{
35
+ const [endpoint] = wrapInternalEndpoints([
36
+ {
37
+ handler,
38
+ method: 'post',
39
+ path: '/test'
40
+ }
41
+ ]);
42
+ return endpoint.handler;
43
+ };
44
+ describe('wrapInternalEndpoints', ()=>{
45
+ const tempFilesToRemove = [];
46
+ afterEach(async ()=>{
47
+ for (const tempFilePath of tempFilesToRemove){
48
+ await fs.rm(tempFilePath, {
49
+ force: true
50
+ });
51
+ }
52
+ tempFilesToRemove.length = 0;
53
+ });
54
+ it('removes a client-upload temp file no operation claimed', async ()=>{
55
+ const tempFilePath = await createTempFile();
56
+ tempFilesToRemove.push(tempFilePath);
57
+ // An endpoint that runs no collection create or update - an auth endpoint, a global update,
58
+ // or a custom endpoint - never reaches unlinkTempFiles.
59
+ const handler = vi.fn(()=>Promise.resolve(Response.json({})));
60
+ const req = createReq(tempFilePath);
61
+ await wrapEndpoint(handler)(req);
62
+ expect(handler).toHaveBeenCalledTimes(1);
63
+ expect(await fileExists(tempFilePath)).toBe(false);
64
+ expect(req.context[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]).toBeUndefined();
65
+ });
66
+ it('removes a client-upload temp file when the handler throws', async ()=>{
67
+ const tempFilePath = await createTempFile();
68
+ tempFilesToRemove.push(tempFilePath);
69
+ const handler = vi.fn(()=>Promise.reject(new Error('handler failed')));
70
+ const req = createReq(tempFilePath);
71
+ await expect(wrapEndpoint(handler)(req)).rejects.toThrow('handler failed');
72
+ expect(await fileExists(tempFilePath)).toBe(false);
73
+ });
74
+ it('does not throw when the operation already removed the temp file', async ()=>{
75
+ const tempFilePath = await createTempFile();
76
+ tempFilesToRemove.push(tempFilePath);
77
+ const handler = vi.fn(async (req)=>{
78
+ delete req.context[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY];
79
+ await fs.unlink(tempFilePath);
80
+ return Response.json({});
81
+ });
82
+ const req = createReq(tempFilePath);
83
+ await expect(wrapEndpoint(handler)(req)).resolves.toBeInstanceOf(Response);
84
+ });
85
+ it('leaves requests without a materialized temp file untouched', async ()=>{
86
+ const handler = vi.fn(()=>Promise.resolve(Response.json({})));
87
+ const req = createReq();
88
+ await wrapEndpoint(handler)(req);
89
+ expect(handler).toHaveBeenCalledTimes(1);
90
+ expect(req.payload.logger.error).not.toHaveBeenCalled();
91
+ });
92
+ it('removes the temp file only after the handler has finished with it', async ()=>{
93
+ const tempFilePath = await createTempFile();
94
+ tempFilesToRemove.push(tempFilePath);
95
+ let existsDuringHandler = false;
96
+ const handler = vi.fn(async ()=>{
97
+ await new Promise((resolve)=>setTimeout(resolve, 10));
98
+ existsDuringHandler = await fileExists(tempFilePath);
99
+ return Response.json({});
100
+ });
101
+ const req = createReq(tempFilePath);
102
+ await wrapEndpoint(handler)(req);
103
+ expect(existsDuringHandler).toBe(true);
104
+ expect(await fileExists(tempFilePath)).toBe(false);
105
+ });
106
+ it('returns the handler response when the temp file can no longer be removed', async ()=>{
107
+ const req = createReq(path.join(os.tmpdir(), `wrap-internal-endpoints-spec-missing-file`));
108
+ const handler = vi.fn(()=>Promise.resolve(Response.json({})));
109
+ await expect(wrapEndpoint(handler)(req)).resolves.toBeInstanceOf(Response);
110
+ expect(req.payload.logger.error).toHaveBeenCalledWith({
111
+ err: expect.objectContaining({
112
+ code: 'ENOENT'
113
+ }),
114
+ msg: 'Failed to remove client upload temp file'
115
+ });
116
+ });
117
+ it('throws the handler error rather than a cleanup error', async ()=>{
118
+ const req = createReq(path.join(os.tmpdir(), `wrap-internal-endpoints-spec-missing-file`));
119
+ const handler = vi.fn(()=>Promise.reject(new Error('handler failed')));
120
+ await expect(wrapEndpoint(handler)(req)).rejects.toThrow('handler failed');
121
+ });
122
+ });
123
+
124
+ //# sourceMappingURL=wrapInternalEndpoints.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/wrapInternalEndpoints.spec.ts"],"sourcesContent":["import type { Endpoint } from '../config/types.js'\nimport type { PayloadRequest } from '../types/index.js'\n\nimport fs from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport { afterEach, describe, expect, it, vi } from 'vitest'\n\nimport { CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY } from '../uploads/getFileFromClientUpload.js'\nimport { wrapInternalEndpoints } from './wrapInternalEndpoints.js'\n\nconst createTempFile = async (): Promise<string> => {\n const tempFilePath = path.join(\n os.tmpdir(),\n `wrap-internal-endpoints-spec-${Date.now()}-${Math.random()}`,\n )\n await fs.writeFile(tempFilePath, 'temp-file-contents')\n return tempFilePath\n}\n\nconst fileExists = async (filePath: string): Promise<boolean> => {\n try {\n await fs.access(filePath)\n return true\n } catch {\n return false\n }\n}\n\nconst createReq = (tempFilePath?: string): PayloadRequest =>\n ({\n body: null,\n context: tempFilePath ? { [CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]: tempFilePath } : {},\n headers: new Headers(),\n method: 'POST',\n payload: {\n config: {},\n logger: { error: vi.fn() },\n },\n }) as unknown as PayloadRequest\n\nconst wrapEndpoint = (handler: Endpoint['handler']): Endpoint['handler'] => {\n const [endpoint] = wrapInternalEndpoints([{ handler, method: 'post', path: '/test' } as Endpoint])\n\n return endpoint!.handler\n}\n\ndescribe('wrapInternalEndpoints', () => {\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('removes a client-upload temp file no operation claimed', async () => {\n const tempFilePath = await createTempFile()\n tempFilesToRemove.push(tempFilePath)\n\n // An endpoint that runs no collection create or update - an auth endpoint, a global update,\n // or a custom endpoint - never reaches unlinkTempFiles.\n const handler = vi.fn(() => Promise.resolve(Response.json({})))\n const req = createReq(tempFilePath)\n\n await wrapEndpoint(handler)(req)\n\n expect(handler).toHaveBeenCalledTimes(1)\n expect(await fileExists(tempFilePath)).toBe(false)\n expect(req.context[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]).toBeUndefined()\n })\n\n it('removes a client-upload temp file when the handler throws', async () => {\n const tempFilePath = await createTempFile()\n tempFilesToRemove.push(tempFilePath)\n\n const handler = vi.fn(() => Promise.reject(new Error('handler failed')))\n const req = createReq(tempFilePath)\n\n await expect(wrapEndpoint(handler)(req)).rejects.toThrow('handler failed')\n\n expect(await fileExists(tempFilePath)).toBe(false)\n })\n\n it('does not throw when the operation already removed the temp file', async () => {\n const tempFilePath = await createTempFile()\n tempFilesToRemove.push(tempFilePath)\n\n const handler = vi.fn(async (req: PayloadRequest) => {\n delete req.context[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]\n await fs.unlink(tempFilePath)\n return Response.json({})\n })\n const req = createReq(tempFilePath)\n\n await expect(wrapEndpoint(handler)(req)).resolves.toBeInstanceOf(Response)\n })\n\n it('leaves requests without a materialized temp file untouched', async () => {\n const handler = vi.fn(() => Promise.resolve(Response.json({})))\n const req = createReq()\n\n await wrapEndpoint(handler)(req)\n\n expect(handler).toHaveBeenCalledTimes(1)\n expect(req.payload.logger.error).not.toHaveBeenCalled()\n })\n\n it('removes the temp file only after the handler has finished with it', async () => {\n const tempFilePath = await createTempFile()\n tempFilesToRemove.push(tempFilePath)\n\n let existsDuringHandler = false\n const handler = vi.fn(async () => {\n await new Promise((resolve) => setTimeout(resolve, 10))\n existsDuringHandler = await fileExists(tempFilePath)\n return Response.json({})\n })\n const req = createReq(tempFilePath)\n\n await wrapEndpoint(handler)(req)\n\n expect(existsDuringHandler).toBe(true)\n expect(await fileExists(tempFilePath)).toBe(false)\n })\n\n it('returns the handler response when the temp file can no longer be removed', async () => {\n const req = createReq(path.join(os.tmpdir(), `wrap-internal-endpoints-spec-missing-file`))\n const handler = vi.fn(() => Promise.resolve(Response.json({})))\n\n await expect(wrapEndpoint(handler)(req)).resolves.toBeInstanceOf(Response)\n\n expect(req.payload.logger.error).toHaveBeenCalledWith({\n err: expect.objectContaining({ code: 'ENOENT' }),\n msg: 'Failed to remove client upload temp file',\n })\n })\n\n it('throws the handler error rather than a cleanup error', async () => {\n const req = createReq(path.join(os.tmpdir(), `wrap-internal-endpoints-spec-missing-file`))\n const handler = vi.fn(() => Promise.reject(new Error('handler failed')))\n\n await expect(wrapEndpoint(handler)(req)).rejects.toThrow('handler failed')\n })\n})\n"],"names":["fs","os","path","afterEach","describe","expect","it","vi","CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY","wrapInternalEndpoints","createTempFile","tempFilePath","join","tmpdir","Date","now","Math","random","writeFile","fileExists","filePath","access","createReq","body","context","headers","Headers","method","payload","config","logger","error","fn","wrapEndpoint","handler","endpoint","tempFilesToRemove","rm","force","length","push","Promise","resolve","Response","json","req","toHaveBeenCalledTimes","toBe","toBeUndefined","reject","Error","rejects","toThrow","unlink","resolves","toBeInstanceOf","not","toHaveBeenCalled","existsDuringHandler","setTimeout","toHaveBeenCalledWith","err","objectContaining","code","msg"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payload",
3
- "version": "3.89.0-internal.95c9cba",
3
+ "version": "3.89.0",
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.89.0-internal.95c9cba"
118
+ "@payloadcms/translations": "3.89.0"
119
119
  },
120
120
  "devDependencies": {
121
121
  "@hyrious/esbuild-plugin-commonjs": "0.2.6",