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.
- package/dist/collections/operations/update.js +5 -5
- package/dist/collections/operations/update.js.map +1 -1
- package/dist/index.bundled.d.ts +49 -29
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/queues/config/collection.d.ts.map +1 -1
- package/dist/queues/config/collection.js +6 -0
- package/dist/queues/config/collection.js.map +1 -1
- package/dist/queues/config/types/index.d.ts +2 -0
- package/dist/queues/config/types/index.d.ts.map +1 -1
- package/dist/queues/config/types/index.js.map +1 -1
- package/dist/queues/endpoints/handleSchedules.d.ts.map +1 -1
- package/dist/queues/endpoints/handleSchedules.js +2 -1
- package/dist/queues/endpoints/handleSchedules.js.map +1 -1
- package/dist/queues/endpoints/run.d.ts.map +1 -1
- package/dist/queues/endpoints/run.js +2 -1
- package/dist/queues/endpoints/run.js.map +1 -1
- package/dist/uploads/getFileFromClientUpload.d.ts +2 -2
- package/dist/uploads/getFileFromClientUpload.d.ts.map +1 -1
- package/dist/uploads/getFileFromClientUpload.js +4 -4
- package/dist/uploads/getFileFromClientUpload.js.map +1 -1
- package/dist/uploads/getFileFromClientUpload.spec.js +62 -0
- package/dist/uploads/getFileFromClientUpload.spec.js.map +1 -1
- package/dist/uploads/unlinkClientUploadTempFile.d.ts +20 -0
- package/dist/uploads/unlinkClientUploadTempFile.d.ts.map +1 -0
- package/dist/uploads/unlinkClientUploadTempFile.js +30 -0
- package/dist/uploads/unlinkClientUploadTempFile.js.map +1 -0
- package/dist/uploads/unlinkTempFiles.d.ts.map +1 -1
- package/dist/uploads/unlinkTempFiles.js +7 -9
- package/dist/uploads/unlinkTempFiles.js.map +1 -1
- package/dist/uploads/unlinkTempFiles.spec.js +61 -1
- package/dist/uploads/unlinkTempFiles.spec.js.map +1 -1
- package/dist/utilities/wrapInternalEndpoints.d.ts.map +1 -1
- package/dist/utilities/wrapInternalEndpoints.js +10 -3
- package/dist/utilities/wrapInternalEndpoints.js.map +1 -1
- package/dist/utilities/wrapInternalEndpoints.spec.js +124 -0
- package/dist/utilities/wrapInternalEndpoints.spec.js.map +1 -0
- 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 {
|
|
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":"AAIA,OAAOA,QAAQ,cAAa;AAC5B,OAAOC,QAAQ,KAAI;AACnB,OAAOC,UAAU,OAAM;AACvB,SAASC,SAAS,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAQ;AAE5D,SAASC,wCAAwC,QAAQ,+BAA8B;AACvF,SAASC,eAAe,QAAQ,uBAAsB;AAEtD,MAAMC,iBAAiB,OAAOC,WAAW,oBAAoB;IAC3D,MAAMC,eAAeV,KAAKW,IAAI,CAC5BZ,GAAGa,MAAM,IACT,CAAC,uBAAuB,EAAEC,KAAKC,GAAG,GAAG,CAAC,EAAEC,KAAKC,MAAM,IAAI;IAEzD,MAAMlB,GAAGmB,SAAS,CAACP,cAAcD;IACjC,OAAOC;AACT;AAEA,MAAMQ,aAAa,OAAOC;IACxB,IAAI;QACF,MAAMrB,GAAGsB,MAAM,CAACD;QAChB,OAAO;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA,MAAME,mBAAmB;IACvBC,QAAQ;QAAEC,qBAAqB;IAAK;AACtC;AAEA,MAAMC,4BAA4B,CAAC;AAEnCtB,SAAS,mBAAmB;IAC1B,MAAMuB,oBAA8B,EAAE;IAEtCxB,UAAU;QACR,KAAK,MAAMS,gBAAgBe,kBAAmB;YAC5C,MAAM3B,GAAG4B,EAAE,CAAChB,cAAc;gBAAEiB,OAAO;YAAK;QAC1C;QACAF,kBAAkBG,MAAM,GAAG;IAC7B;IAEAxB,GAAG,kFAAkF;QACnF,MAAMM,eAAe,MAAMF;QAC3BiB,kBAAkBI,IAAI,CAACnB;QAEvB,MAAMoB,MAAM;YACVC,MAAM;gBACJC,qBAAqBC;gBACrBC,MAAMC,OAAOC,KAAK,CAAC;gBACnBC,UAAU;gBACVC,MAAM;gBACNC,MAAM;gBACN7B;YACF;QACF;QAEA,MAAMH,gBAAgB;YACpBc;YACAmB,QAAQ;gBAAElB,QAAQ;oBAAEmB,cAAc;gBAAM;YAAE;YAC1CX;QACF;QAEA3B,OAAO,MAAMe,WAAWR,eAAegC,IAAI,CAAC;IAC9C;IAEAtC,GAAG,iEAAiE;QAClE,MAAMM,eAAe,MAAMF;QAC3BiB,kBAAkBI,IAAI,CAACnB;QAEvB,MAAMoB,MAAM;YACVC,MAAM;gBACJG,MAAMC,OAAOC,KAAK,CAAC;gBACnBC,UAAU;gBACVC,MAAM;gBACNC,MAAM;gBACN7B;YACF;QACF;QAEA,MAAMH,gBAAgB;YACpBc;YACAmB,QAAQ;gBAAElB,QAAQ;oBAAEmB,cAAc;gBAAM;YAAE;YAC1CX;QACF;QAEA3B,OAAO,MAAMe,WAAWR,eAAegC,IAAI,CAAC;IAC9C;IAEAtC,GAAG,2DAA2D;QAC5D,MAAMM,eAAe,MAAMF;QAC3BiB,kBAAkBI,IAAI,CAACnB;QAEvB,MAAMoB,MAAM;YACVC,MAAM;gBACJG,MAAMC,OAAOC,KAAK,CAAC;gBACnBC,UAAU;gBACVC,MAAM;gBACNC,MAAM;gBACN7B;YACF;QACF;QAEA,MAAMH,gBAAgB;YACpBc;YACAmB,QAAQ;gBAAElB,QAAQ;oBAAEmB,cAAc;gBAAK;YAAE;YACzCX;QACF;QAEA3B,OAAO,MAAMe,WAAWR,eAAegC,IAAI,CAAC;IAC9C;IAEAtC,GAAG,uFAAuF;QACxF,MAAMM,eAAe,MAAMF;QAC3BiB,kBAAkBI,IAAI,CAACnB;QAEvB,yFAAyF;QACzF,0DAA0D;QAC1D,MAAMoB,MAAM;YACVa,SAAS;gBAAE,CAACrC,yCAAyC,EAAEI;YAAa;YACpEqB,MAAME;QACR;QAEA,MAAM1B,gBAAgB;YACpBc;YACAmB,QAAQ;gBAAElB,QAAQ;oBAAEmB,cAAc;gBAAM;YAAE;YAC1CX;QACF;QAEA3B,OAAO,MAAMe,WAAWR,eAAegC,IAAI,CAAC;QAC5CvC,OAAO2B,IAAIa,OAAO,CAACrC,yCAAyC,EAAEsC,aAAa;IAC7E;IAEAxC,GAAG,gGAAgG;QACjG,MAAMM,eAAe,MAAMF;QAC3BiB,kBAAkBI,IAAI,CAACnB;QAEvB,MAAMoB,MAAM;YACVa,SAAS;gBAAE,CAACrC,yCAAyC,EAAEI;YAAa;YACpEqB,MAAM;gBACJC,qBAAqBC;gBACrBC,MAAMC,OAAOC,KAAK,CAAC;gBACnBC,UAAU;gBACVC,MAAM;gBACNC,MAAM;gBACN7B;YACF;QACF;QAEA,MAAMP,OACJI,gBAAgB;YACdc;YACAmB,QAAQ;gBAAElB,QAAQ;oBAAEmB,cAAc;gBAAM;YAAE;YAC1CX;QACF,IACAe,QAAQ,CAACC,GAAG,CAACC,OAAO;QAEtB5C,OAAO,MAAMe,WAAWR,eAAegC,IAAI,CAAC;IAC9C;IAEAtC,GAAG,qFAAqF;QACtF,MAAMM,eAAe,MAAMF;QAC3BiB,kBAAkBI,IAAI,CAACnB;QAEvB,uFAAuF;QACvF,qFAAqF;QACrF,MAAMoB,MAAM;YACVa,SAAS;gBAAE,CAACrC,yCAAyC,EAAEI;YAAa;YACpEqB,MAAM;gBACJC,qBAAqB;oBAAEgB,QAAQ;gBAAG;gBAClCd,MAAMC,OAAOC,KAAK,CAAC;gBACnBC,UAAU;gBACVC,MAAM;gBACNC,MAAM;gBACN7B;YACF;QACF;QAEA,MAAMH,gBAAgB;YACpBc,kBAAkBG;YAClBgB,QAAQ;gBAAElB,QAAQ;oBAAEmB,cAAc;gBAAM;YAAE;YAC1CX;QACF;QAEA3B,OAAO,MAAMe,WAAWR,eAAegC,IAAI,CAAC;QAC5CvC,OAAO2B,IAAIa,OAAO,CAACrC,yCAAyC,EAAEsC,aAAa;IAC7E;IAEAxC,GAAG,oFAAoF;QACrF,MAAM0B,MAAM;YACVa,SAAS;gBACP,CAACrC,yCAAyC,EAAEN,KAAKW,IAAI,CACnDZ,GAAGa,MAAM,IACT;YAEJ;YACAqC,SAAS;gBAAEC,QAAQ;oBAAEC,OAAO9C,GAAG+C,EAAE;gBAAG;YAAE;QACxC;QAEA,MAAMjD,OACJI,gBAAgB;YACdc;YACAmB,QAAQ;gBAAElB,QAAQ;oBAAEmB,cAAc;gBAAM;YAAE;YAC1CX;QACF,IACAe,QAAQ,CAACD,aAAa;QAExBzC,OAAO2B,IAAImB,OAAO,CAACC,MAAM,CAACC,KAAK,EAAEE,oBAAoB,CAAC;YACpDC,KAAKnD,OAAOoD,gBAAgB,CAAC;gBAAEC,MAAM;YAAS;YAC9CC,KAAK;QACP;IACF;AACF"}
|
|
@@ -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;
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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":"AAGA,OAAOA,QAAQ,cAAa;AAC5B,OAAOC,QAAQ,KAAI;AACnB,OAAOC,UAAU,OAAM;AACvB,SAASC,SAAS,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAQ;AAE5D,SAASC,wCAAwC,QAAQ,wCAAuC;AAChG,SAASC,qBAAqB,QAAQ,6BAA4B;AAElE,MAAMC,iBAAiB;IACrB,MAAMC,eAAeT,KAAKU,IAAI,CAC5BX,GAAGY,MAAM,IACT,CAAC,6BAA6B,EAAEC,KAAKC,GAAG,GAAG,CAAC,EAAEC,KAAKC,MAAM,IAAI;IAE/D,MAAMjB,GAAGkB,SAAS,CAACP,cAAc;IACjC,OAAOA;AACT;AAEA,MAAMQ,aAAa,OAAOC;IACxB,IAAI;QACF,MAAMpB,GAAGqB,MAAM,CAACD;QAChB,OAAO;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA,MAAME,YAAY,CAACX,eAChB,CAAA;QACCY,MAAM;QACNC,SAASb,eAAe;YAAE,CAACH,yCAAyC,EAAEG;QAAa,IAAI,CAAC;QACxFc,SAAS,IAAIC;QACbC,QAAQ;QACRC,SAAS;YACPC,QAAQ,CAAC;YACTC,QAAQ;gBAAEC,OAAOxB,GAAGyB,EAAE;YAAG;QAC3B;IACF,CAAA;AAEF,MAAMC,eAAe,CAACC;IACpB,MAAM,CAACC,SAAS,GAAG1B,sBAAsB;QAAC;YAAEyB;YAASP,QAAQ;YAAQzB,MAAM;QAAQ;KAAc;IAEjG,OAAOiC,SAAUD,OAAO;AAC1B;AAEA9B,SAAS,yBAAyB;IAChC,MAAMgC,oBAA8B,EAAE;IAEtCjC,UAAU;QACR,KAAK,MAAMQ,gBAAgByB,kBAAmB;YAC5C,MAAMpC,GAAGqC,EAAE,CAAC1B,cAAc;gBAAE2B,OAAO;YAAK;QAC1C;QACAF,kBAAkBG,MAAM,GAAG;IAC7B;IAEAjC,GAAG,0DAA0D;QAC3D,MAAMK,eAAe,MAAMD;QAC3B0B,kBAAkBI,IAAI,CAAC7B;QAEvB,4FAA4F;QAC5F,wDAAwD;QACxD,MAAMuB,UAAU3B,GAAGyB,EAAE,CAAC,IAAMS,QAAQC,OAAO,CAACC,SAASC,IAAI,CAAC,CAAC;QAC3D,MAAMC,MAAMvB,UAAUX;QAEtB,MAAMsB,aAAaC,SAASW;QAE5BxC,OAAO6B,SAASY,qBAAqB,CAAC;QACtCzC,OAAO,MAAMc,WAAWR,eAAeoC,IAAI,CAAC;QAC5C1C,OAAOwC,IAAIrB,OAAO,CAAChB,yCAAyC,EAAEwC,aAAa;IAC7E;IAEA1C,GAAG,6DAA6D;QAC9D,MAAMK,eAAe,MAAMD;QAC3B0B,kBAAkBI,IAAI,CAAC7B;QAEvB,MAAMuB,UAAU3B,GAAGyB,EAAE,CAAC,IAAMS,QAAQQ,MAAM,CAAC,IAAIC,MAAM;QACrD,MAAML,MAAMvB,UAAUX;QAEtB,MAAMN,OAAO4B,aAAaC,SAASW,MAAMM,OAAO,CAACC,OAAO,CAAC;QAEzD/C,OAAO,MAAMc,WAAWR,eAAeoC,IAAI,CAAC;IAC9C;IAEAzC,GAAG,mEAAmE;QACpE,MAAMK,eAAe,MAAMD;QAC3B0B,kBAAkBI,IAAI,CAAC7B;QAEvB,MAAMuB,UAAU3B,GAAGyB,EAAE,CAAC,OAAOa;YAC3B,OAAOA,IAAIrB,OAAO,CAAChB,yCAAyC;YAC5D,MAAMR,GAAGqD,MAAM,CAAC1C;YAChB,OAAOgC,SAASC,IAAI,CAAC,CAAC;QACxB;QACA,MAAMC,MAAMvB,UAAUX;QAEtB,MAAMN,OAAO4B,aAAaC,SAASW,MAAMS,QAAQ,CAACC,cAAc,CAACZ;IACnE;IAEArC,GAAG,8DAA8D;QAC/D,MAAM4B,UAAU3B,GAAGyB,EAAE,CAAC,IAAMS,QAAQC,OAAO,CAACC,SAASC,IAAI,CAAC,CAAC;QAC3D,MAAMC,MAAMvB;QAEZ,MAAMW,aAAaC,SAASW;QAE5BxC,OAAO6B,SAASY,qBAAqB,CAAC;QACtCzC,OAAOwC,IAAIjB,OAAO,CAACE,MAAM,CAACC,KAAK,EAAEyB,GAAG,CAACC,gBAAgB;IACvD;IAEAnD,GAAG,qEAAqE;QACtE,MAAMK,eAAe,MAAMD;QAC3B0B,kBAAkBI,IAAI,CAAC7B;QAEvB,IAAI+C,sBAAsB;QAC1B,MAAMxB,UAAU3B,GAAGyB,EAAE,CAAC;YACpB,MAAM,IAAIS,QAAQ,CAACC,UAAYiB,WAAWjB,SAAS;YACnDgB,sBAAsB,MAAMvC,WAAWR;YACvC,OAAOgC,SAASC,IAAI,CAAC,CAAC;QACxB;QACA,MAAMC,MAAMvB,UAAUX;QAEtB,MAAMsB,aAAaC,SAASW;QAE5BxC,OAAOqD,qBAAqBX,IAAI,CAAC;QACjC1C,OAAO,MAAMc,WAAWR,eAAeoC,IAAI,CAAC;IAC9C;IAEAzC,GAAG,4EAA4E;QAC7E,MAAMuC,MAAMvB,UAAUpB,KAAKU,IAAI,CAACX,GAAGY,MAAM,IAAI,CAAC,yCAAyC,CAAC;QACxF,MAAMqB,UAAU3B,GAAGyB,EAAE,CAAC,IAAMS,QAAQC,OAAO,CAACC,SAASC,IAAI,CAAC,CAAC;QAE3D,MAAMvC,OAAO4B,aAAaC,SAASW,MAAMS,QAAQ,CAACC,cAAc,CAACZ;QAEjEtC,OAAOwC,IAAIjB,OAAO,CAACE,MAAM,CAACC,KAAK,EAAE6B,oBAAoB,CAAC;YACpDC,KAAKxD,OAAOyD,gBAAgB,CAAC;gBAAEC,MAAM;YAAS;YAC9CC,KAAK;QACP;IACF;IAEA1D,GAAG,wDAAwD;QACzD,MAAMuC,MAAMvB,UAAUpB,KAAKU,IAAI,CAACX,GAAGY,MAAM,IAAI,CAAC,yCAAyC,CAAC;QACxF,MAAMqB,UAAU3B,GAAGyB,EAAE,CAAC,IAAMS,QAAQQ,MAAM,CAAC,IAAIC,MAAM;QAErD,MAAM7C,OAAO4B,aAAaC,SAASW,MAAMM,OAAO,CAACC,OAAO,CAAC;IAC3D;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payload",
|
|
3
|
-
"version": "3.89.0
|
|
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
|
|
118
|
+
"@payloadcms/translations": "3.89.0"
|
|
119
119
|
},
|
|
120
120
|
"devDependencies": {
|
|
121
121
|
"@hyrious/esbuild-plugin-commonjs": "0.2.6",
|