payload 3.89.0-internal.4b0e9b6 → 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/getFileContentRequirement.d.ts +9 -0
- package/dist/uploads/getFileContentRequirement.d.ts.map +1 -0
- package/dist/uploads/getFileContentRequirement.js +24 -0
- package/dist/uploads/getFileContentRequirement.js.map +1 -0
- package/dist/uploads/getFileContentRequirement.spec.js +95 -0
- package/dist/uploads/getFileContentRequirement.spec.js.map +1 -0
- package/dist/uploads/getFileFromClientUpload.d.ts +20 -0
- package/dist/uploads/getFileFromClientUpload.d.ts.map +1 -0
- package/dist/uploads/getFileFromClientUpload.js +199 -0
- package/dist/uploads/getFileFromClientUpload.js.map +1 -0
- package/dist/uploads/getFileFromClientUpload.spec.js +590 -0
- package/dist/uploads/getFileFromClientUpload.spec.js.map +1 -0
- 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 +10 -2
- package/dist/uploads/unlinkTempFiles.js.map +1 -1
- package/dist/uploads/unlinkTempFiles.spec.js +217 -0
- package/dist/uploads/unlinkTempFiles.spec.js.map +1 -0
- package/dist/utilities/addDataAndFileToRequest.d.ts.map +1 -1
- package/dist/utilities/addDataAndFileToRequest.js +7 -46
- package/dist/utilities/addDataAndFileToRequest.js.map +1 -1
- package/dist/utilities/addDataAndFileToRequest.spec.js +131 -1
- package/dist/utilities/addDataAndFileToRequest.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
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import { CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY } from './getFileFromClientUpload.js';
|
|
3
|
+
/**
|
|
4
|
+
* Removes the temp file a client upload materialized, if it is still on disk. Logs rather than
|
|
5
|
+
* throws, so cleanup can never replace a response or mask the error that a caller is handling.
|
|
6
|
+
*
|
|
7
|
+
* The path is tracked on `req.context` rather than read back off `req.file` because the file
|
|
8
|
+
* can outlive both: plugin-cloud-storage's afterChange hook clears `req.file` before cleanup
|
|
9
|
+
* runs, and the request parser materializes the file for whatever `collectionSlug` the multipart
|
|
10
|
+
* field names, which does not have to be a collection the endpoint writes a document to.
|
|
11
|
+
*/ export const unlinkClientUploadTempFile = async ({ alreadyUnlinkedPath, req })=>{
|
|
12
|
+
const tempFilePath = req.context?.[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY];
|
|
13
|
+
if (typeof tempFilePath !== 'string') {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
delete req.context[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY];
|
|
17
|
+
if (tempFilePath === alreadyUnlinkedPath) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
await fs.unlink(tempFilePath);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
req.payload.logger.error({
|
|
24
|
+
err: error,
|
|
25
|
+
msg: 'Failed to remove client upload temp file'
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=unlinkClientUploadTempFile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/uploads/unlinkClientUploadTempFile.ts"],"sourcesContent":["import fs from 'fs/promises'\n\nimport type { PayloadRequest } from '../types/index.js'\n\nimport { CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY } from './getFileFromClientUpload.js'\n\ntype Args = {\n /**\n * A path the caller has already removed, so the redundant unlink is skipped.\n */\n alreadyUnlinkedPath?: string\n req: PayloadRequest\n}\n\n/**\n * Removes the temp file a client upload materialized, if it is still on disk. Logs rather than\n * throws, so cleanup can never replace a response or mask the error that a caller is handling.\n *\n * The path is tracked on `req.context` rather than read back off `req.file` because the file\n * can outlive both: plugin-cloud-storage's afterChange hook clears `req.file` before cleanup\n * runs, and the request parser materializes the file for whatever `collectionSlug` the multipart\n * field names, which does not have to be a collection the endpoint writes a document to.\n */\nexport const unlinkClientUploadTempFile: (args: Args) => Promise<void> = async ({\n alreadyUnlinkedPath,\n req,\n}) => {\n const tempFilePath = req.context?.[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]\n\n if (typeof tempFilePath !== 'string') {\n return\n }\n\n delete req.context[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]\n\n if (tempFilePath === alreadyUnlinkedPath) {\n return\n }\n\n try {\n await fs.unlink(tempFilePath)\n } catch (error) {\n req.payload.logger.error({ err: error, msg: 'Failed to remove client upload temp file' })\n }\n}\n"],"names":["fs","CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY","unlinkClientUploadTempFile","alreadyUnlinkedPath","req","tempFilePath","context","unlink","error","payload","logger","err","msg"],"mappings":"AAAA,OAAOA,QAAQ,cAAa;AAI5B,SAASC,wCAAwC,QAAQ,+BAA8B;AAUvF;;;;;;;;CAQC,GACD,OAAO,MAAMC,6BAA4D,OAAO,EAC9EC,mBAAmB,EACnBC,GAAG,EACJ;IACC,MAAMC,eAAeD,IAAIE,OAAO,EAAE,CAACL,yCAAyC;IAE5E,IAAI,OAAOI,iBAAiB,UAAU;QACpC;IACF;IAEA,OAAOD,IAAIE,OAAO,CAACL,yCAAyC;IAE5D,IAAII,iBAAiBF,qBAAqB;QACxC;IACF;IAEA,IAAI;QACF,MAAMH,GAAGO,MAAM,CAACF;IAClB,EAAE,OAAOG,OAAO;QACdJ,IAAIK,OAAO,CAACC,MAAM,CAACF,KAAK,CAAC;YAAEG,KAAKH;YAAOI,KAAK;QAA2C;IACzF;AACF,EAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unlinkTempFiles.d.ts","sourceRoot":"","sources":["../../src/uploads/unlinkTempFiles.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAA;AAC/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"unlinkTempFiles.d.ts","sourceRoot":"","sources":["../../src/uploads/unlinkTempFiles.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAA;AAC/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAKvD,KAAK,IAAI,GAAG;IACV,gBAAgB,EAAE,yBAAyB,CAAA;IAC3C,MAAM,EAAE,eAAe,CAAA;IACvB,GAAG,EAAE,cAAc,CAAA;CACpB,CAAA;AACD;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAuBzD,CAAA"}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import { mapAsync } from '../utilities/mapAsync.js';
|
|
3
|
+
import { unlinkClientUploadTempFile } from './unlinkClientUploadTempFile.js';
|
|
3
4
|
/**
|
|
4
5
|
* Cleanup temp files after operation lifecycle
|
|
5
6
|
*/ export const unlinkTempFiles = async ({ collectionConfig, config, req })=>{
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
const { file } = req;
|
|
8
|
+
const isClientUploadTempFile = Boolean(file?.tempFilePath && Object.prototype.hasOwnProperty.call(file, 'clientUploadContext'));
|
|
9
|
+
let unlinkedTempFilePath;
|
|
10
|
+
if (collectionConfig.upload && (config.upload?.useTempFiles || isClientUploadTempFile)) {
|
|
8
11
|
const fileArray = [
|
|
9
12
|
{
|
|
10
13
|
file
|
|
@@ -14,9 +17,14 @@ import { mapAsync } from '../utilities/mapAsync.js';
|
|
|
14
17
|
// Still need this check because this will not be populated if using local API
|
|
15
18
|
if (file?.tempFilePath) {
|
|
16
19
|
await fs.unlink(file.tempFilePath);
|
|
20
|
+
unlinkedTempFilePath = file.tempFilePath;
|
|
17
21
|
}
|
|
18
22
|
});
|
|
19
23
|
}
|
|
24
|
+
await unlinkClientUploadTempFile({
|
|
25
|
+
alreadyUnlinkedPath: unlinkedTempFilePath,
|
|
26
|
+
req
|
|
27
|
+
});
|
|
20
28
|
};
|
|
21
29
|
|
|
22
30
|
//# sourceMappingURL=unlinkTempFiles.js.map
|
|
@@ -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'\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
|
|
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"}
|
|
@@ -0,0 +1,217 @@
|
|
|
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 './getFileFromClientUpload.js';
|
|
6
|
+
import { unlinkTempFiles } from './unlinkTempFiles.js';
|
|
7
|
+
const createTempFile = async (contents = 'temp-file-contents')=>{
|
|
8
|
+
const tempFilePath = path.join(os.tmpdir(), `unlink-temp-files-spec-${Date.now()}-${Math.random()}`);
|
|
9
|
+
await fs.writeFile(tempFilePath, 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 collectionConfig = {
|
|
21
|
+
upload: {
|
|
22
|
+
disableLocalStorage: true
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const nonUploadCollectionConfig = {};
|
|
26
|
+
describe('unlinkTempFiles', ()=>{
|
|
27
|
+
const tempFilesToRemove = [];
|
|
28
|
+
afterEach(async ()=>{
|
|
29
|
+
for (const tempFilePath of tempFilesToRemove){
|
|
30
|
+
await fs.rm(tempFilePath, {
|
|
31
|
+
force: true
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
tempFilesToRemove.length = 0;
|
|
35
|
+
});
|
|
36
|
+
it('removes a client-upload materialized temp file even when useTempFiles is false', async ()=>{
|
|
37
|
+
const tempFilePath = await createTempFile();
|
|
38
|
+
tempFilesToRemove.push(tempFilePath);
|
|
39
|
+
const req = {
|
|
40
|
+
file: {
|
|
41
|
+
clientUploadContext: undefined,
|
|
42
|
+
data: Buffer.alloc(0),
|
|
43
|
+
mimetype: 'video/mp4',
|
|
44
|
+
name: 'clip.mp4',
|
|
45
|
+
size: 10,
|
|
46
|
+
tempFilePath
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
await unlinkTempFiles({
|
|
50
|
+
collectionConfig,
|
|
51
|
+
config: {
|
|
52
|
+
upload: {
|
|
53
|
+
useTempFiles: false
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
req
|
|
57
|
+
});
|
|
58
|
+
expect(await fileExists(tempFilePath)).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
it('leaves a local-API temp file alone when useTempFiles is false', async ()=>{
|
|
61
|
+
const tempFilePath = await createTempFile();
|
|
62
|
+
tempFilesToRemove.push(tempFilePath);
|
|
63
|
+
const req = {
|
|
64
|
+
file: {
|
|
65
|
+
data: Buffer.alloc(0),
|
|
66
|
+
mimetype: 'video/mp4',
|
|
67
|
+
name: 'clip.mp4',
|
|
68
|
+
size: 10,
|
|
69
|
+
tempFilePath
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
await unlinkTempFiles({
|
|
73
|
+
collectionConfig,
|
|
74
|
+
config: {
|
|
75
|
+
upload: {
|
|
76
|
+
useTempFiles: false
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
req
|
|
80
|
+
});
|
|
81
|
+
expect(await fileExists(tempFilePath)).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
it('removes a multipart temp file when useTempFiles is true', async ()=>{
|
|
84
|
+
const tempFilePath = await createTempFile();
|
|
85
|
+
tempFilesToRemove.push(tempFilePath);
|
|
86
|
+
const req = {
|
|
87
|
+
file: {
|
|
88
|
+
data: Buffer.alloc(0),
|
|
89
|
+
mimetype: 'video/mp4',
|
|
90
|
+
name: 'clip.mp4',
|
|
91
|
+
size: 10,
|
|
92
|
+
tempFilePath
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
await unlinkTempFiles({
|
|
96
|
+
collectionConfig,
|
|
97
|
+
config: {
|
|
98
|
+
upload: {
|
|
99
|
+
useTempFiles: true
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
req
|
|
103
|
+
});
|
|
104
|
+
expect(await fileExists(tempFilePath)).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
it('removes a client-upload temp file tracked on req.context after req.file was cleared', async ()=>{
|
|
107
|
+
const tempFilePath = await createTempFile();
|
|
108
|
+
tempFilesToRemove.push(tempFilePath);
|
|
109
|
+
// Mirrors plugin-cloud-storage's afterChange hook, which clears req.file after uploading
|
|
110
|
+
// generated image sizes but leaves req.context untouched.
|
|
111
|
+
const req = {
|
|
112
|
+
context: {
|
|
113
|
+
[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]: tempFilePath
|
|
114
|
+
},
|
|
115
|
+
file: undefined
|
|
116
|
+
};
|
|
117
|
+
await unlinkTempFiles({
|
|
118
|
+
collectionConfig,
|
|
119
|
+
config: {
|
|
120
|
+
upload: {
|
|
121
|
+
useTempFiles: false
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
req
|
|
125
|
+
});
|
|
126
|
+
expect(await fileExists(tempFilePath)).toBe(false);
|
|
127
|
+
expect(req.context[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]).toBeUndefined();
|
|
128
|
+
});
|
|
129
|
+
it('does not attempt a second unlink when the context-tracked path matches req.file.tempFilePath', async ()=>{
|
|
130
|
+
const tempFilePath = await createTempFile();
|
|
131
|
+
tempFilesToRemove.push(tempFilePath);
|
|
132
|
+
const req = {
|
|
133
|
+
context: {
|
|
134
|
+
[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY]: tempFilePath
|
|
135
|
+
},
|
|
136
|
+
file: {
|
|
137
|
+
clientUploadContext: undefined,
|
|
138
|
+
data: Buffer.alloc(0),
|
|
139
|
+
mimetype: 'video/mp4',
|
|
140
|
+
name: 'clip.mp4',
|
|
141
|
+
size: 10,
|
|
142
|
+
tempFilePath
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
await expect(unlinkTempFiles({
|
|
146
|
+
collectionConfig,
|
|
147
|
+
config: {
|
|
148
|
+
upload: {
|
|
149
|
+
useTempFiles: false
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
req
|
|
153
|
+
})).resolves.not.toThrow();
|
|
154
|
+
expect(await fileExists(tempFilePath)).toBe(false);
|
|
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
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
//# sourceMappingURL=unlinkTempFiles.spec.js.map
|
|
@@ -0,0 +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, 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":"addDataAndFileToRequest.d.ts","sourceRoot":"","sources":["../../src/utilities/addDataAndFileToRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"addDataAndFileToRequest.d.ts","sourceRoot":"","sources":["../../src/utilities/addDataAndFileToRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAOvD,KAAK,uBAAuB,GAAG,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAErE;;GAEG;AACH,eAAO,MAAM,uBAAuB,EAAE,uBAiErC,CAAA"}
|
|
@@ -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
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
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
|
|
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","
|
|
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":"AAIA,OAAOA,QAAQ,cAAa;AAC5B,SAASC,SAAS,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAQ;AAE5D,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;AAEA,MAAMC,wBAAwB,CAAC,EAC7BC,IAAI,EACJC,OAAO,EACPL,MAAM,EAKP;IACC,MAAMd,WAAW,IAAIC;IACrBD,SAASE,MAAM,CAAC,QAAQkB,KAAKC,SAAS,CAACH;IAEvC,MAAMb,UAAU,IAAIC,QAAQ,8BAA8B;QACxDC,MAAMP;QACNQ,QAAQ;IACV;IAEA,OAAO;QACLD,MAAMF,QAAQE,IAAI;QAClBE,SAASJ,QAAQI,OAAO;QACxBD,QAAQH,QAAQG,MAAM;QACtBE,SAAS;YACPC,aAAa;gBACXW,OAAO;oBACLV,QAAQ;wBACNE,QAAQ;4BACNS,qBAAqB;4BACrBC,UAAU;gCAACL;6BAAQ;4BACnB,GAAGL,MAAM;wBACX;oBACF;gBACF;YACF;YACAF,QAAQ;gBACNC,YAAY,CAAC;gBACbC,QAAQ,CAAC;YACX;YACAC,QAAQ;gBACNC,OAAO,KAAO;YAChB;QACF;IACF;AACF;AAEAtB,SAAS,2BAA2B;IAClCE,GAAG,uEAAuE;QACxE,MAAM6B,MAAM1B;QAEZJ,OAAO8B,IAAIhB,OAAO,CAACiB,GAAG,CAAC,mBAAmBC,QAAQ;QAElD,MAAM7B,wBAAwB2B;QAE9B9B,OAAO8B,IAAIP,IAAI,EAAEU,WAAW;QAC5BjC,OAAO8B,IAAIP,IAAI,EAAEW,MAAMC,IAAI,CAAC;QAC5BnC,OAAO8B,IAAIP,IAAI,EAAEa,UAAUD,IAAI,CAAC;IAClC;IAEApC,SAAS,kBAAkB;QACzB,MAAMsC,oBAA8B,EAAE;QAEtCvC,UAAU;YACR,KAAK,MAAMwC,gBAAgBD,kBAAmB;gBAC5C,MAAMxC,GAAG0C,EAAE,CAACD,cAAc;oBAAEE,OAAO;gBAAK;YAC1C;YACAH,kBAAkBI,MAAM,GAAG;QAC7B;QAEAxC,GAAG,8EAA8E;YAC/E,MAAMuB,UAAUtB,GAAGwC,EAAE,CAAC;gBACpB,MAAM,IAAIC,MAAM;YAClB;YACA,MAAMb,MAAMR,sBAAsB;gBAChCC,MAAM;oBACJqB,qBAAqB;wBAAEC,QAAQ;oBAAG;oBAClCC,gBAAgB;oBAChBC,UAAU;oBACVC,UAAU;oBACVC,MAAM;gBACR;gBACAzB;gBACAL,QAAQ;oBAAES,qBAAqB;gBAAK;YACtC;YAEA,MAAMzB,wBAAwB2B;YAE9B9B,OAAOwB,SAAS0B,GAAG,CAACC,gBAAgB;YACpCnD,OAAO8B,IAAIP,IAAI,EAAE6B,aAAa,CAAC;gBAC7BR,qBAAqB;oBAAEC,QAAQ;gBAAG;gBAClCT,UAAU;gBACVF,MAAM;gBACNe,MAAM;YACR;YACAjD,OAAO8B,IAAIP,IAAI,EAAE8B,KAAKZ,QAAQN,IAAI,CAAC;QACrC;QAEAlC,GAAG,wFAAwF;YACzF,MAAMqD,SAAS;gBAACC,OAAOC,IAAI,CAAC;gBAAeD,OAAOC,IAAI,CAAC;aAAa;YACpE,MAAMC,SAAS,IAAIC,eAAe;gBAChCC,OAAMC,UAAU;oBACd,KAAK,MAAMC,SAASP,OAAQ;wBAC1BM,WAAWE,OAAO,CAACD;oBACrB;oBACAD,WAAWG,KAAK;gBAClB;YACF;YACA,MAAMC,WAAW,IAAIC,SAASR,QAAQ;gBACpC3C,SAAS;oBAAE,gBAAgB;gBAAY;gBACvCoD,QAAQ;YACV;YACA,MAAMC,sBAAsBjE,GAAGwC,EAAE,CAAC;gBAChC,MAAM,IAAIC,MAAM;YAClB;YACAyB,OAAOC,cAAc,CAACL,UAAU,eAAe;gBAAEM,OAAOH;YAAoB;YAE5E,MAAM3C,UAAUtB,GAAGwC,EAAE,CAAC,UAAYsB;YAClC,MAAMlC,MAAMR,sBAAsB;gBAChCC,MAAM;oBACJqB,qBAAqB;wBAAEC,QAAQ;oBAAG;oBAClCC,gBAAgB;oBAChBC,UAAU;oBACVC,UAAU;oBACVC,MAAM;gBACR;gBACAzB;gBACAL,QAAQ;oBAAES,qBAAqB;oBAAM2C,WAAW;wBAAC;qBAAU;gBAAC;YAC9D;YAEA,MAAMpE,wBAAwB2B;YAC9BO,kBAAkBmC,IAAI,CAAC1C,IAAIP,IAAI,CAAEe,YAAY;YAE7CtC,OAAOmE,qBAAqBjB,GAAG,CAACC,gBAAgB;YAChDnD,OAAO8B,IAAIP,IAAI,EAAE8B,KAAKZ,QAAQN,IAAI,CAAC;YACnCnC,OAAO8B,IAAIP,IAAI,EAAEe,cAAcL,WAAW;YAC1C,MAAMwC,UAAU,MAAM5E,GAAG6E,QAAQ,CAAC5C,IAAIP,IAAI,CAAEe,YAAY;YACxDtC,OAAOyE,QAAQE,QAAQ,IAAIxC,IAAI,CAACmB,OAAOsB,GAAG,CAAC,CAACf,QAAUA,MAAMc,QAAQ,IAAIE,IAAI,CAAC;QAC/E;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"}
|