payload 3.89.0-internal.95c9cba → 3.89.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/collections/operations/update.js +5 -5
  2. package/dist/collections/operations/update.js.map +1 -1
  3. package/dist/index.bundled.d.ts +49 -29
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/queues/config/collection.d.ts.map +1 -1
  9. package/dist/queues/config/collection.js +6 -0
  10. package/dist/queues/config/collection.js.map +1 -1
  11. package/dist/queues/config/types/index.d.ts +2 -0
  12. package/dist/queues/config/types/index.d.ts.map +1 -1
  13. package/dist/queues/config/types/index.js.map +1 -1
  14. package/dist/queues/endpoints/handleSchedules.d.ts.map +1 -1
  15. package/dist/queues/endpoints/handleSchedules.js +2 -1
  16. package/dist/queues/endpoints/handleSchedules.js.map +1 -1
  17. package/dist/queues/endpoints/run.d.ts.map +1 -1
  18. package/dist/queues/endpoints/run.js +2 -1
  19. package/dist/queues/endpoints/run.js.map +1 -1
  20. package/dist/uploads/getFileFromClientUpload.d.ts +2 -2
  21. package/dist/uploads/getFileFromClientUpload.d.ts.map +1 -1
  22. package/dist/uploads/getFileFromClientUpload.js +4 -4
  23. package/dist/uploads/getFileFromClientUpload.js.map +1 -1
  24. package/dist/uploads/getFileFromClientUpload.spec.js +62 -0
  25. package/dist/uploads/getFileFromClientUpload.spec.js.map +1 -1
  26. package/dist/uploads/unlinkClientUploadTempFile.d.ts +20 -0
  27. package/dist/uploads/unlinkClientUploadTempFile.d.ts.map +1 -0
  28. package/dist/uploads/unlinkClientUploadTempFile.js +30 -0
  29. package/dist/uploads/unlinkClientUploadTempFile.js.map +1 -0
  30. package/dist/uploads/unlinkTempFiles.d.ts.map +1 -1
  31. package/dist/uploads/unlinkTempFiles.js +7 -9
  32. package/dist/uploads/unlinkTempFiles.js.map +1 -1
  33. package/dist/uploads/unlinkTempFiles.spec.js +61 -1
  34. package/dist/uploads/unlinkTempFiles.spec.js.map +1 -1
  35. package/dist/utilities/wrapInternalEndpoints.d.ts.map +1 -1
  36. package/dist/utilities/wrapInternalEndpoints.js +10 -3
  37. package/dist/utilities/wrapInternalEndpoints.js.map +1 -1
  38. package/dist/utilities/wrapInternalEndpoints.spec.js +124 -0
  39. package/dist/utilities/wrapInternalEndpoints.spec.js.map +1 -0
  40. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/queues/config/types/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/queues/config/types/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/queues/config/types/index.ts"],"sourcesContent":["import type { CollectionConfig, Job } from '../../../index.js'\nimport type { MaybePromise, Payload, PayloadRequest, Sort } from '../../../types/index.js'\nimport type { RunJobsSilent } from '../../localAPI.js'\nimport type { RunJobsArgs } from '../../operations/runJobs/index.js'\nimport type { JobStats } from '../global.js'\nimport type { TaskConfig } from './taskTypes.js'\nimport type { WorkflowConfig } from './workflowTypes.js'\n\nexport type AutorunCronConfig = {\n /**\n * If you want to autoRUn jobs from all queues, set this to true.\n * If you set this to true, the `queue` property will be ignored.\n *\n * @default false\n */\n allQueues?: boolean\n /**\n * The cron schedule for the job.\n * @default '* * * * *' (every minute).\n *\n * @example\n * ┌───────────── (optional) second (0 - 59)\n * │ ┌───────────── minute (0 - 59)\n * │ │ ┌───────────── hour (0 - 23)\n * │ │ │ ┌───────────── day of the month (1 - 31)\n * │ │ │ │ ┌───────────── month (1 - 12)\n * │ │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday)\n * │ │ │ │ │ │\n * │ │ │ │ │ │\n * - '* 0 * * * *' every hour at minute 0\n * - '* 0 0 * * *' daily at midnight\n * - '* 0 0 * * 0' weekly at midnight on Sundays\n * - '* 0 0 1 * *' monthly at midnight on the 1st day of the month\n * - '* 0/5 * * * *' every 5 minutes\n * - '* * * * * *' every second\n */\n cron?: string\n /**\n * By default, the autorun will attempt to schedule jobs for tasks and workflows that have a `schedule` property, given\n * the queue name is the same.\n *\n * Set this to `true` to disable the scheduling of jobs automatically.\n *\n * @default false\n */\n disableScheduling?: boolean\n /**\n * The limit for the job. This can be overridden by the user. Defaults to 10.\n */\n limit?: number\n /**\n * The queue name for the job.\n *\n * @default 'default'\n */\n queue?: string\n /**\n * If set to true, the job system will not log any output to the console (for both info and error logs).\n * Can be an option for more granular control over logging.\n *\n * This will not automatically affect user-configured logs (e.g. if you call `console.log` or `payload.logger.info` in your job code).\n *\n * @default false\n */\n silent?: RunJobsSilent\n}\n\nexport type RunJobAccessArgs = {\n req: PayloadRequest\n}\n\nexport type RunJobAccess = (args: RunJobAccessArgs) => MaybePromise<boolean>\n\nexport type QueueJobAccessArgs = {\n req: PayloadRequest\n}\n\nexport type CancelJobAccessArgs = {\n req: PayloadRequest\n}\nexport type CancelJobAccess = (args: CancelJobAccessArgs) => MaybePromise<boolean>\nexport type QueueJobAccess = (args: QueueJobAccessArgs) => MaybePromise<boolean>\n\nexport type SanitizedJobsConfig = {\n /**\n * If set to `true`, the job system is enabled and a payload-jobs collection exists.\n * This property is automatically set during sanitization.\n */\n enabled?: boolean\n /**\n * If set to `true`, at least one task or workflow has scheduling enabled.\n * This property is automatically set during sanitization.\n */\n scheduling?: boolean\n /**\n * If set to `true`, a payload-job-stats global exists.\n * This property is automatically set during sanitization.\n */\n stats?: boolean\n} & JobsConfig\nexport type JobsConfig = {\n /**\n * Specify access control to determine who can interact with jobs.\n */\n access?: {\n /**\n * By default, all logged-in users can cancel jobs.\n */\n cancel?: CancelJobAccess\n /**\n * By default, all logged-in users can queue jobs.\n */\n queue?: QueueJobAccess\n /**\n * By default, all logged-in users can run jobs.\n */\n run?: RunJobAccess\n }\n /** Adds information about the parent job to the task log. This is useful for debugging and tracking the flow of tasks.\n *\n * In 4.0, this will default to `true`.\n *\n * @default false\n */\n addParentToTaskLog?: boolean\n /**\n * Allows you to configure cron jobs that automatically run queued jobs\n * at specified intervals. Note that this does not _queue_ new jobs - only\n * _runs_ jobs that are already in the specified queue.\n *\n * @remark this property should not be used on serverless platforms like Vercel\n */\n autoRun?: ((payload: Payload) => MaybePromise<AutorunCronConfig[]>) | AutorunCronConfig[]\n /**\n * Determine whether or not to delete a job after it has successfully completed.\n */\n deleteJobOnComplete?: boolean\n /**\n * Specify depth for retrieving jobs from the queue.\n * This should be as low as possible in order for job retrieval\n * to be as efficient as possible. Setting it to anything higher than\n * 0 will drastically affect performance, as less efficient database\n * queries will be used.\n *\n * @default 0\n * @deprecated - this will be removed in 4.0\n */\n depth?: number\n /**\n * Enable concurrency controls for workflows and tasks.\n * When enabled, adds a `concurrencyKey` field to the jobs collection schema.\n * This allows workflows and tasks to use the `concurrency` option to prevent race conditions.\n *\n * **Important:** Enabling this may require a database migration depending on your database adapter,\n * as it adds a new indexed field to the jobs collection schema.\n *\n * @default false\n * @todo In 4.0, this will default to `true`.\n */\n enableConcurrencyControl?: boolean\n /**\n * Override any settings on the default Jobs collection. Accepts the default collection and allows you to return\n * a new collection.\n */\n jobsCollectionOverrides?: (args: { defaultJobsCollection: CollectionConfig }) => CollectionConfig\n /**\n * Adjust the job processing order using a Payload sort string. This can be set globally or per queue.\n *\n * FIFO would equal `createdAt` and LIFO would equal `-createdAt`.\n *\n * @default all jobs for all queues will be executed in FIFO order.\n */\n processingOrder?:\n | ((args: RunJobsArgs) => Promise<Sort> | Sort)\n | {\n default?: Sort\n queues: {\n [queue: string]: Sort\n }\n }\n | Sort\n /**\n * By default, the job system uses direct database calls for optimal performance.\n * If you added custom hooks to your jobs collection, you can set this to true to\n * use the standard Payload API for all job operations. This is discouraged, as it will\n * drastically affect performance.\n *\n * @default false\n * @deprecated - this will be removed in 4.0\n */\n runHooks?: boolean\n /**\n * A function that will be executed before Payload picks up jobs which are configured by the `jobs.autorun` function.\n * If this function returns true, jobs will be queried and picked up. If it returns false, jobs will not be run.\n * @default undefined - if this function is not defined, jobs will be run - as if () => true was passed.\n * @param payload\n * @returns boolean\n */\n shouldAutoRun?: (payload: Payload) => MaybePromise<boolean>\n /**\n * Define all possible tasks here\n */\n tasks?: TaskConfig<any>[]\n /**\n * Define all the workflows here. Workflows orchestrate the flow of multiple tasks.\n */\n workflows?: WorkflowConfig<any>[]\n}\n\nexport type Queueable = {\n scheduleConfig: ScheduleConfig\n taskConfig?: TaskConfig\n // If not set, queue it immediately\n waitUntil?: Date\n workflowConfig?: WorkflowConfig\n}\n\nexport type BeforeScheduleFn = (args: {\n defaultBeforeSchedule: BeforeScheduleFn\n /**\n * payload-job-stats global data\n */\n jobStats: JobStats\n queueable: Queueable\n req: PayloadRequest\n}) => MaybePromise<{\n input?: object\n shouldSchedule: boolean\n waitUntil?: Date\n}>\n\nexport type AfterScheduleFn = (\n args: {\n defaultAfterSchedule: AfterScheduleFn\n /**\n * payload-job-stats global data. If the global does not exist, it will be null.\n */\n jobStats: JobStats | null\n queueable: Queueable\n req: PayloadRequest\n } & (\n | {\n error: Error\n job?: never\n status: 'error'\n }\n | {\n error?: never\n job: Job\n status: 'success'\n }\n | {\n error?: never\n job?: never\n /**\n * If the beforeSchedule hook returned `shouldSchedule: false`, this will be called with status `skipped`.\n */\n status: 'skipped'\n }\n ),\n) => MaybePromise<void>\n\nexport type ScheduleConfig = {\n /**\n * The cron for scheduling the job.\n *\n * @example\n * ┌───────────── (optional) second (0 - 59)\n * │ ┌───────────── minute (0 - 59)\n * │ │ ┌───────────── hour (0 - 23)\n * │ │ │ ┌───────────── day of the month (1 - 31)\n * │ │ │ │ ┌───────────── month (1 - 12)\n * │ │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday)\n * │ │ │ │ │ │\n * │ │ │ │ │ │\n * - '* 0 * * * *' every hour at minute 0\n * - '* 0 0 * * *' daily at midnight\n * - '* 0 0 * * 0' weekly at midnight on Sundays\n * - '* 0 0 1 * *' monthly at midnight on the 1st day of the month\n * - '* 0/5 * * * *' every 5 minutes\n * - '* * * * * *' every second\n */\n cron: string\n hooks?: {\n /**\n * Functions that will be executed after the job has been successfully scheduled.\n *\n * @default By default, global update?? Unless global update should happen before\n */\n afterSchedule?: AfterScheduleFn\n /**\n * Functions that will be executed before the job is scheduled.\n * You can use this to control whether or not the job should be scheduled, or what input\n * data should be passed to the job.\n *\n * @default By default, this has one function that returns { shouldSchedule: true } if the following conditions are met:\n * - There currently is no job of the same type in the specified queue that is currently running\n * - There currently is no job of the same type in the specified queue that is scheduled to run in the future\n * - There currently is no job of the same type in the specified queue that failed previously but can be retried\n */\n beforeSchedule?: BeforeScheduleFn\n }\n /**\n * Queue to which the scheduled job will be added.\n */\n queue: string\n}\n"],"names":[],"mappings":"AAsQA,WA4CC"}
1
+ {"version":3,"sources":["../../../../src/queues/config/types/index.ts"],"sourcesContent":["import type { CollectionConfig, Job } from '../../../index.js'\nimport type { MaybePromise, Payload, PayloadRequest, Sort } from '../../../types/index.js'\nimport type { RunJobsSilent } from '../../localAPI.js'\nimport type { RunJobsArgs } from '../../operations/runJobs/index.js'\nimport type { JobStats } from '../global.js'\nimport type { TaskConfig } from './taskTypes.js'\nimport type { WorkflowConfig } from './workflowTypes.js'\n\nexport type AutorunCronConfig = {\n /**\n * If you want to autoRUn jobs from all queues, set this to true.\n * If you set this to true, the `queue` property will be ignored.\n *\n * @default false\n */\n allQueues?: boolean\n /**\n * The cron schedule for the job.\n * @default '* * * * *' (every minute).\n *\n * @example\n * ┌───────────── (optional) second (0 - 59)\n * │ ┌───────────── minute (0 - 59)\n * │ │ ┌───────────── hour (0 - 23)\n * │ │ │ ┌───────────── day of the month (1 - 31)\n * │ │ │ │ ┌───────────── month (1 - 12)\n * │ │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday)\n * │ │ │ │ │ │\n * │ │ │ │ │ │\n * - '* 0 * * * *' every hour at minute 0\n * - '* 0 0 * * *' daily at midnight\n * - '* 0 0 * * 0' weekly at midnight on Sundays\n * - '* 0 0 1 * *' monthly at midnight on the 1st day of the month\n * - '* 0/5 * * * *' every 5 minutes\n * - '* * * * * *' every second\n */\n cron?: string\n /**\n * By default, the autorun will attempt to schedule jobs for tasks and workflows that have a `schedule` property, given\n * the queue name is the same.\n *\n * Set this to `true` to disable the scheduling of jobs automatically.\n *\n * @default false\n */\n disableScheduling?: boolean\n /**\n * The limit for the job. This can be overridden by the user. Defaults to 10.\n */\n limit?: number\n /**\n * The queue name for the job.\n *\n * @default 'default'\n */\n queue?: string\n /**\n * If set to true, the job system will not log any output to the console (for both info and error logs).\n * Can be an option for more granular control over logging.\n *\n * This will not automatically affect user-configured logs (e.g. if you call `console.log` or `payload.logger.info` in your job code).\n *\n * @default false\n */\n silent?: RunJobsSilent\n}\n\nexport type RunJobAccessArgs = {\n req: PayloadRequest\n}\n\nexport type RunJobAccess = (args: RunJobAccessArgs) => MaybePromise<boolean>\n\nexport type QueueJobAccessArgs = {\n req: PayloadRequest\n}\n\nexport type CancelJobAccessArgs = {\n req: PayloadRequest\n}\nexport type CancelJobAccess = (args: CancelJobAccessArgs) => MaybePromise<boolean>\nexport type QueueJobAccess = (args: QueueJobAccessArgs) => MaybePromise<boolean>\n\nexport type SanitizedJobsConfig = {\n /**\n * If set to `true`, the job system is enabled and a payload-jobs collection exists.\n * This property is automatically set during sanitization.\n */\n enabled?: boolean\n /**\n * If set to `true`, at least one task or workflow has scheduling enabled.\n * This property is automatically set during sanitization.\n */\n scheduling?: boolean\n /**\n * If set to `true`, a payload-job-stats global exists.\n * This property is automatically set during sanitization.\n */\n stats?: boolean\n} & JobsConfig\nexport type JobsConfig = {\n /**\n * Specify access control to determine who can interact with jobs.\n */\n access?: {\n /**\n * By default, all logged-in users can cancel jobs.\n */\n cancel?: CancelJobAccess\n /**\n * By default, all logged-in users can queue jobs.\n */\n queue?: QueueJobAccess\n /**\n * By default, all logged-in users can run jobs.\n */\n run?: RunJobAccess\n }\n /** Adds information about the parent job to the task log. This is useful for debugging and tracking the flow of tasks.\n *\n * In 4.0, this will default to `true`.\n *\n * @default false\n */\n addParentToTaskLog?: boolean\n /**\n * Allows you to configure cron jobs that automatically run queued jobs\n * at specified intervals. Note that this does not _queue_ new jobs - only\n * _runs_ jobs that are already in the specified queue.\n *\n * @remark this property should not be used on serverless platforms like Vercel\n */\n autoRun?: ((payload: Payload) => MaybePromise<AutorunCronConfig[]>) | AutorunCronConfig[]\n /**\n * Determine whether or not to delete a job after it has successfully completed.\n */\n deleteJobOnComplete?: boolean\n /**\n * Specify depth for retrieving jobs from the queue.\n * This should be as low as possible in order for job retrieval\n * to be as efficient as possible. Setting it to anything higher than\n * 0 will drastically affect performance, as less efficient database\n * queries will be used.\n *\n * @default 0\n * @deprecated - this will be removed in 4.0\n */\n depth?: number\n /**\n * Enable concurrency controls for workflows and tasks.\n * When enabled, adds a `concurrencyKey` field to the jobs collection schema.\n * This allows workflows and tasks to use the `concurrency` option to prevent race conditions.\n *\n * **Important:** Enabling this may require a database migration depending on your database adapter,\n * as it adds a new indexed field to the jobs collection schema.\n *\n * @default false\n * @todo In 4.0, this will default to `true`.\n */\n enableConcurrencyControl?: boolean\n /**\n * Override any settings on the default Jobs collection. Accepts the default collection and allows you to return\n * a new collection.\n *\n * @experimental\n */\n jobsCollectionOverrides?: (args: { defaultJobsCollection: CollectionConfig }) => CollectionConfig\n /**\n * Adjust the job processing order using a Payload sort string. This can be set globally or per queue.\n *\n * FIFO would equal `createdAt` and LIFO would equal `-createdAt`.\n *\n * @default all jobs for all queues will be executed in FIFO order.\n */\n processingOrder?:\n | ((args: RunJobsArgs) => Promise<Sort> | Sort)\n | {\n default?: Sort\n queues: {\n [queue: string]: Sort\n }\n }\n | Sort\n /**\n * By default, the job system uses direct database calls for optimal performance.\n * If you added custom hooks to your jobs collection, you can set this to true to\n * use the standard Payload API for all job operations. This is discouraged, as it will\n * drastically affect performance.\n *\n * @default false\n * @deprecated - this will be removed in 4.0\n */\n runHooks?: boolean\n /**\n * A function that will be executed before Payload picks up jobs which are configured by the `jobs.autorun` function.\n * If this function returns true, jobs will be queried and picked up. If it returns false, jobs will not be run.\n * @default undefined - if this function is not defined, jobs will be run - as if () => true was passed.\n * @param payload\n * @returns boolean\n */\n shouldAutoRun?: (payload: Payload) => MaybePromise<boolean>\n /**\n * Define all possible tasks here\n */\n tasks?: TaskConfig<any>[]\n /**\n * Define all the workflows here. Workflows orchestrate the flow of multiple tasks.\n */\n workflows?: WorkflowConfig<any>[]\n}\n\nexport type Queueable = {\n scheduleConfig: ScheduleConfig\n taskConfig?: TaskConfig\n // If not set, queue it immediately\n waitUntil?: Date\n workflowConfig?: WorkflowConfig\n}\n\nexport type BeforeScheduleFn = (args: {\n defaultBeforeSchedule: BeforeScheduleFn\n /**\n * payload-job-stats global data\n */\n jobStats: JobStats\n queueable: Queueable\n req: PayloadRequest\n}) => MaybePromise<{\n input?: object\n shouldSchedule: boolean\n waitUntil?: Date\n}>\n\nexport type AfterScheduleFn = (\n args: {\n defaultAfterSchedule: AfterScheduleFn\n /**\n * payload-job-stats global data. If the global does not exist, it will be null.\n */\n jobStats: JobStats | null\n queueable: Queueable\n req: PayloadRequest\n } & (\n | {\n error: Error\n job?: never\n status: 'error'\n }\n | {\n error?: never\n job: Job\n status: 'success'\n }\n | {\n error?: never\n job?: never\n /**\n * If the beforeSchedule hook returned `shouldSchedule: false`, this will be called with status `skipped`.\n */\n status: 'skipped'\n }\n ),\n) => MaybePromise<void>\n\nexport type ScheduleConfig = {\n /**\n * The cron for scheduling the job.\n *\n * @example\n * ┌───────────── (optional) second (0 - 59)\n * │ ┌───────────── minute (0 - 59)\n * │ │ ┌───────────── hour (0 - 23)\n * │ │ │ ┌───────────── day of the month (1 - 31)\n * │ │ │ │ ┌───────────── month (1 - 12)\n * │ │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday)\n * │ │ │ │ │ │\n * │ │ │ │ │ │\n * - '* 0 * * * *' every hour at minute 0\n * - '* 0 0 * * *' daily at midnight\n * - '* 0 0 * * 0' weekly at midnight on Sundays\n * - '* 0 0 1 * *' monthly at midnight on the 1st day of the month\n * - '* 0/5 * * * *' every 5 minutes\n * - '* * * * * *' every second\n */\n cron: string\n hooks?: {\n /**\n * Functions that will be executed after the job has been successfully scheduled.\n *\n * @default By default, global update?? Unless global update should happen before\n */\n afterSchedule?: AfterScheduleFn\n /**\n * Functions that will be executed before the job is scheduled.\n * You can use this to control whether or not the job should be scheduled, or what input\n * data should be passed to the job.\n *\n * @default By default, this has one function that returns { shouldSchedule: true } if the following conditions are met:\n * - There currently is no job of the same type in the specified queue that is currently running\n * - There currently is no job of the same type in the specified queue that is scheduled to run in the future\n * - There currently is no job of the same type in the specified queue that failed previously but can be retried\n */\n beforeSchedule?: BeforeScheduleFn\n }\n /**\n * Queue to which the scheduled job will be added.\n */\n queue: string\n}\n"],"names":[],"mappings":"AAwQA,WA4CC"}
@@ -1 +1 @@
1
- {"version":3,"file":"handleSchedules.d.ts","sourceRoot":"","sources":["../../../src/queues/endpoints/handleSchedules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAKrD;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,EAAE,QA8DzC,CAAA"}
1
+ {"version":3,"file":"handleSchedules.d.ts","sourceRoot":"","sources":["../../../src/queues/endpoints/handleSchedules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAMrD;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,EAAE,QA8DzC,CAAA"}
@@ -1,3 +1,4 @@
1
+ import { defaultAccess } from '../../auth/defaultAccess.js';
1
2
  import { handleSchedules } from '../operations/handleSchedules/index.js';
2
3
  import { configHasJobs } from './run.js';
3
4
  /**
@@ -14,7 +15,7 @@ import { configHasJobs } from './run.js';
14
15
  status: 200
15
16
  });
16
17
  }
17
- const accessFn = jobsConfig.access?.run ?? (()=>true);
18
+ const accessFn = jobsConfig.access?.run ?? defaultAccess;
18
19
  const hasAccess = await accessFn({
19
20
  req
20
21
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/queues/endpoints/handleSchedules.ts"],"sourcesContent":["import type { Endpoint } from '../../config/types.js'\n\nimport { handleSchedules } from '../operations/handleSchedules/index.js'\nimport { configHasJobs } from './run.js'\n\n/**\n * GET /api/payload-jobs/handle-schedules endpoint\n *\n * This endpoint is GET instead of POST to allow it to be used in a Vercel Cron.\n */\nexport const handleSchedulesJobsEndpoint: Endpoint = {\n handler: async (req) => {\n const jobsConfig = req.payload.config.jobs\n\n if (!configHasJobs(jobsConfig)) {\n return Response.json(\n {\n message: 'No jobs to schedule.',\n },\n { status: 200 },\n )\n }\n\n const accessFn = jobsConfig.access?.run ?? (() => true)\n\n const hasAccess = await accessFn({ req })\n\n if (!hasAccess) {\n return Response.json(\n {\n message: req.i18n.t('error:unauthorized'),\n },\n { status: 401 },\n )\n }\n\n if (!jobsConfig.scheduling) {\n // There is no reason to call the handleSchedules endpoint if the stats global is not enabled (= no schedules defined)\n return Response.json(\n {\n message:\n 'Cannot handle schedules because no tasks or workflows with schedules are defined.',\n },\n { status: 500 },\n )\n }\n\n const { allQueues, queue } = req.query as {\n allQueues?: 'false' | 'true'\n queue?: string\n }\n\n const runAllQueues = allQueues && !(typeof allQueues === 'string' && allQueues === 'false')\n\n const { errored, queued, skipped } = await handleSchedules({\n allQueues: runAllQueues,\n queue,\n req,\n })\n\n return Response.json(\n {\n errored,\n message: req.i18n.t('general:success'),\n queued,\n skipped,\n },\n { status: 200 },\n )\n },\n method: 'get',\n path: '/handle-schedules',\n}\n"],"names":["handleSchedules","configHasJobs","handleSchedulesJobsEndpoint","handler","req","jobsConfig","payload","config","jobs","Response","json","message","status","accessFn","access","run","hasAccess","i18n","t","scheduling","allQueues","queue","query","runAllQueues","errored","queued","skipped","method","path"],"mappings":"AAEA,SAASA,eAAe,QAAQ,yCAAwC;AACxE,SAASC,aAAa,QAAQ,WAAU;AAExC;;;;CAIC,GACD,OAAO,MAAMC,8BAAwC;IACnDC,SAAS,OAAOC;QACd,MAAMC,aAAaD,IAAIE,OAAO,CAACC,MAAM,CAACC,IAAI;QAE1C,IAAI,CAACP,cAAcI,aAAa;YAC9B,OAAOI,SAASC,IAAI,CAClB;gBACEC,SAAS;YACX,GACA;gBAAEC,QAAQ;YAAI;QAElB;QAEA,MAAMC,WAAWR,WAAWS,MAAM,EAAEC,OAAQ,CAAA,IAAM,IAAG;QAErD,MAAMC,YAAY,MAAMH,SAAS;YAAET;QAAI;QAEvC,IAAI,CAACY,WAAW;YACd,OAAOP,SAASC,IAAI,CAClB;gBACEC,SAASP,IAAIa,IAAI,CAACC,CAAC,CAAC;YACtB,GACA;gBAAEN,QAAQ;YAAI;QAElB;QAEA,IAAI,CAACP,WAAWc,UAAU,EAAE;YAC1B,sHAAsH;YACtH,OAAOV,SAASC,IAAI,CAClB;gBACEC,SACE;YACJ,GACA;gBAAEC,QAAQ;YAAI;QAElB;QAEA,MAAM,EAAEQ,SAAS,EAAEC,KAAK,EAAE,GAAGjB,IAAIkB,KAAK;QAKtC,MAAMC,eAAeH,aAAa,CAAE,CAAA,OAAOA,cAAc,YAAYA,cAAc,OAAM;QAEzF,MAAM,EAAEI,OAAO,EAAEC,MAAM,EAAEC,OAAO,EAAE,GAAG,MAAM1B,gBAAgB;YACzDoB,WAAWG;YACXF;YACAjB;QACF;QAEA,OAAOK,SAASC,IAAI,CAClB;YACEc;YACAb,SAASP,IAAIa,IAAI,CAACC,CAAC,CAAC;YACpBO;YACAC;QACF,GACA;YAAEd,QAAQ;QAAI;IAElB;IACAe,QAAQ;IACRC,MAAM;AACR,EAAC"}
1
+ {"version":3,"sources":["../../../src/queues/endpoints/handleSchedules.ts"],"sourcesContent":["import type { Endpoint } from '../../config/types.js'\n\nimport { defaultAccess } from '../../auth/defaultAccess.js'\nimport { handleSchedules } from '../operations/handleSchedules/index.js'\nimport { configHasJobs } from './run.js'\n\n/**\n * GET /api/payload-jobs/handle-schedules endpoint\n *\n * This endpoint is GET instead of POST to allow it to be used in a Vercel Cron.\n */\nexport const handleSchedulesJobsEndpoint: Endpoint = {\n handler: async (req) => {\n const jobsConfig = req.payload.config.jobs\n\n if (!configHasJobs(jobsConfig)) {\n return Response.json(\n {\n message: 'No jobs to schedule.',\n },\n { status: 200 },\n )\n }\n\n const accessFn = jobsConfig.access?.run ?? defaultAccess\n\n const hasAccess = await accessFn({ req })\n\n if (!hasAccess) {\n return Response.json(\n {\n message: req.i18n.t('error:unauthorized'),\n },\n { status: 401 },\n )\n }\n\n if (!jobsConfig.scheduling) {\n // There is no reason to call the handleSchedules endpoint if the stats global is not enabled (= no schedules defined)\n return Response.json(\n {\n message:\n 'Cannot handle schedules because no tasks or workflows with schedules are defined.',\n },\n { status: 500 },\n )\n }\n\n const { allQueues, queue } = req.query as {\n allQueues?: 'false' | 'true'\n queue?: string\n }\n\n const runAllQueues = allQueues && !(typeof allQueues === 'string' && allQueues === 'false')\n\n const { errored, queued, skipped } = await handleSchedules({\n allQueues: runAllQueues,\n queue,\n req,\n })\n\n return Response.json(\n {\n errored,\n message: req.i18n.t('general:success'),\n queued,\n skipped,\n },\n { status: 200 },\n )\n },\n method: 'get',\n path: '/handle-schedules',\n}\n"],"names":["defaultAccess","handleSchedules","configHasJobs","handleSchedulesJobsEndpoint","handler","req","jobsConfig","payload","config","jobs","Response","json","message","status","accessFn","access","run","hasAccess","i18n","t","scheduling","allQueues","queue","query","runAllQueues","errored","queued","skipped","method","path"],"mappings":"AAEA,SAASA,aAAa,QAAQ,8BAA6B;AAC3D,SAASC,eAAe,QAAQ,yCAAwC;AACxE,SAASC,aAAa,QAAQ,WAAU;AAExC;;;;CAIC,GACD,OAAO,MAAMC,8BAAwC;IACnDC,SAAS,OAAOC;QACd,MAAMC,aAAaD,IAAIE,OAAO,CAACC,MAAM,CAACC,IAAI;QAE1C,IAAI,CAACP,cAAcI,aAAa;YAC9B,OAAOI,SAASC,IAAI,CAClB;gBACEC,SAAS;YACX,GACA;gBAAEC,QAAQ;YAAI;QAElB;QAEA,MAAMC,WAAWR,WAAWS,MAAM,EAAEC,OAAOhB;QAE3C,MAAMiB,YAAY,MAAMH,SAAS;YAAET;QAAI;QAEvC,IAAI,CAACY,WAAW;YACd,OAAOP,SAASC,IAAI,CAClB;gBACEC,SAASP,IAAIa,IAAI,CAACC,CAAC,CAAC;YACtB,GACA;gBAAEN,QAAQ;YAAI;QAElB;QAEA,IAAI,CAACP,WAAWc,UAAU,EAAE;YAC1B,sHAAsH;YACtH,OAAOV,SAASC,IAAI,CAClB;gBACEC,SACE;YACJ,GACA;gBAAEC,QAAQ;YAAI;QAElB;QAEA,MAAM,EAAEQ,SAAS,EAAEC,KAAK,EAAE,GAAGjB,IAAIkB,KAAK;QAKtC,MAAMC,eAAeH,aAAa,CAAE,CAAA,OAAOA,cAAc,YAAYA,cAAc,OAAM;QAEzF,MAAM,EAAEI,OAAO,EAAEC,MAAM,EAAEC,OAAO,EAAE,GAAG,MAAM1B,gBAAgB;YACzDoB,WAAWG;YACXF;YACAjB;QACF;QAEA,OAAOK,SAASC,IAAI,CAClB;YACEc;YACAb,SAASP,IAAIa,IAAI,CAACC,CAAC,CAAC;YACpBO;YACAC;QACF,GACA;YAAEd,QAAQ;QAAI;IAElB;IACAe,QAAQ;IACRC,MAAM;AACR,EAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/queues/endpoints/run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAInE;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,QAmG7B,CAAA;AAED,eAAO,MAAM,aAAa,eAAgB,mBAAmB,KAAG,OAE/D,CAAA"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/queues/endpoints/run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAKnE;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,QAmG7B,CAAA;AAED,eAAO,MAAM,aAAa,eAAgB,mBAAmB,KAAG,OAE/D,CAAA"}
@@ -1,3 +1,4 @@
1
+ import { defaultAccess } from '../../auth/defaultAccess.js';
1
2
  import { runJobs } from '../operations/runJobs/index.js';
2
3
  /**
3
4
  * /api/payload-jobs/run endpoint
@@ -13,7 +14,7 @@ import { runJobs } from '../operations/runJobs/index.js';
13
14
  status: 200
14
15
  });
15
16
  }
16
- const accessFn = jobsConfig.access?.run ?? (()=>true);
17
+ const accessFn = jobsConfig.access?.run ?? defaultAccess;
17
18
  const hasAccess = await accessFn({
18
19
  req
19
20
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/queues/endpoints/run.ts"],"sourcesContent":["import type { Endpoint } from '../../config/types.js'\nimport type { SanitizedJobsConfig } from '../config/types/index.js'\n\nimport { runJobs, type RunJobsArgs } from '../operations/runJobs/index.js'\n\n/**\n * /api/payload-jobs/run endpoint\n *\n * This endpoint is GET instead of POST to allow it to be used in a Vercel Cron.\n */\nexport const runJobsEndpoint: Endpoint = {\n handler: async (req) => {\n const jobsConfig = req.payload.config.jobs\n\n if (!configHasJobs(jobsConfig)) {\n return Response.json(\n {\n message: 'No jobs to run.',\n },\n { status: 200 },\n )\n }\n\n const accessFn = jobsConfig.access?.run ?? (() => true)\n\n const hasAccess = await accessFn({ req })\n\n if (!hasAccess) {\n return Response.json(\n {\n message: req.i18n.t('error:unauthorized'),\n },\n { status: 401 },\n )\n }\n\n const {\n allQueues,\n disableScheduling: disableSchedulingParam,\n limit,\n queue,\n silent: silentParam,\n } = req.query as {\n allQueues?: 'false' | 'true'\n disableScheduling?: 'false' | 'true'\n limit?: number\n queue?: string\n silent?: string\n }\n\n const silent = silentParam === 'true'\n\n const shouldHandleSchedules = disableSchedulingParam !== 'true'\n\n const runAllQueues = allQueues && !(typeof allQueues === 'string' && allQueues === 'false')\n\n if (shouldHandleSchedules && jobsConfig.scheduling) {\n // If should handle schedules and schedules are defined\n await req.payload.jobs.handleSchedules({ allQueues: runAllQueues, queue, req })\n }\n\n const runJobsArgs: RunJobsArgs = {\n queue,\n req,\n // Access is validated above, so it's safe to override here\n allQueues: runAllQueues,\n overrideAccess: true,\n silent,\n }\n\n const parsedLimit = Number(limit)\n if (!isNaN(parsedLimit)) {\n runJobsArgs.limit = parsedLimit\n }\n\n let noJobsRemaining = false\n let remainingJobsFromQueried = 0\n try {\n const result = await runJobs(runJobsArgs)\n noJobsRemaining = !!result.noJobsRemaining\n remainingJobsFromQueried = result.remainingJobsFromQueried\n } catch (err) {\n req.payload.logger.error({\n err,\n msg: 'There was an error running jobs:',\n queue: runJobsArgs.queue,\n })\n\n return Response.json(\n {\n message: req.i18n.t('error:unknown'),\n noJobsRemaining: true,\n remainingJobsFromQueried,\n },\n { status: 500 },\n )\n }\n\n return Response.json(\n {\n message: req.i18n.t('general:success'),\n noJobsRemaining,\n remainingJobsFromQueried,\n },\n { status: 200 },\n )\n },\n method: 'get',\n path: '/run',\n}\n\nexport const configHasJobs = (jobsConfig: SanitizedJobsConfig): boolean => {\n return Boolean(jobsConfig.tasks?.length || jobsConfig.workflows?.length)\n}\n"],"names":["runJobs","runJobsEndpoint","handler","req","jobsConfig","payload","config","jobs","configHasJobs","Response","json","message","status","accessFn","access","run","hasAccess","i18n","t","allQueues","disableScheduling","disableSchedulingParam","limit","queue","silent","silentParam","query","shouldHandleSchedules","runAllQueues","scheduling","handleSchedules","runJobsArgs","overrideAccess","parsedLimit","Number","isNaN","noJobsRemaining","remainingJobsFromQueried","result","err","logger","error","msg","method","path","Boolean","tasks","length","workflows"],"mappings":"AAGA,SAASA,OAAO,QAA0B,iCAAgC;AAE1E;;;;CAIC,GACD,OAAO,MAAMC,kBAA4B;IACvCC,SAAS,OAAOC;QACd,MAAMC,aAAaD,IAAIE,OAAO,CAACC,MAAM,CAACC,IAAI;QAE1C,IAAI,CAACC,cAAcJ,aAAa;YAC9B,OAAOK,SAASC,IAAI,CAClB;gBACEC,SAAS;YACX,GACA;gBAAEC,QAAQ;YAAI;QAElB;QAEA,MAAMC,WAAWT,WAAWU,MAAM,EAAEC,OAAQ,CAAA,IAAM,IAAG;QAErD,MAAMC,YAAY,MAAMH,SAAS;YAAEV;QAAI;QAEvC,IAAI,CAACa,WAAW;YACd,OAAOP,SAASC,IAAI,CAClB;gBACEC,SAASR,IAAIc,IAAI,CAACC,CAAC,CAAC;YACtB,GACA;gBAAEN,QAAQ;YAAI;QAElB;QAEA,MAAM,EACJO,SAAS,EACTC,mBAAmBC,sBAAsB,EACzCC,KAAK,EACLC,KAAK,EACLC,QAAQC,WAAW,EACpB,GAAGtB,IAAIuB,KAAK;QAQb,MAAMF,SAASC,gBAAgB;QAE/B,MAAME,wBAAwBN,2BAA2B;QAEzD,MAAMO,eAAeT,aAAa,CAAE,CAAA,OAAOA,cAAc,YAAYA,cAAc,OAAM;QAEzF,IAAIQ,yBAAyBvB,WAAWyB,UAAU,EAAE;YAClD,uDAAuD;YACvD,MAAM1B,IAAIE,OAAO,CAACE,IAAI,CAACuB,eAAe,CAAC;gBAAEX,WAAWS;gBAAcL;gBAAOpB;YAAI;QAC/E;QAEA,MAAM4B,cAA2B;YAC/BR;YACApB;YACA,2DAA2D;YAC3DgB,WAAWS;YACXI,gBAAgB;YAChBR;QACF;QAEA,MAAMS,cAAcC,OAAOZ;QAC3B,IAAI,CAACa,MAAMF,cAAc;YACvBF,YAAYT,KAAK,GAAGW;QACtB;QAEA,IAAIG,kBAAkB;QACtB,IAAIC,2BAA2B;QAC/B,IAAI;YACF,MAAMC,SAAS,MAAMtC,QAAQ+B;YAC7BK,kBAAkB,CAAC,CAACE,OAAOF,eAAe;YAC1CC,2BAA2BC,OAAOD,wBAAwB;QAC5D,EAAE,OAAOE,KAAK;YACZpC,IAAIE,OAAO,CAACmC,MAAM,CAACC,KAAK,CAAC;gBACvBF;gBACAG,KAAK;gBACLnB,OAAOQ,YAAYR,KAAK;YAC1B;YAEA,OAAOd,SAASC,IAAI,CAClB;gBACEC,SAASR,IAAIc,IAAI,CAACC,CAAC,CAAC;gBACpBkB,iBAAiB;gBACjBC;YACF,GACA;gBAAEzB,QAAQ;YAAI;QAElB;QAEA,OAAOH,SAASC,IAAI,CAClB;YACEC,SAASR,IAAIc,IAAI,CAACC,CAAC,CAAC;YACpBkB;YACAC;QACF,GACA;YAAEzB,QAAQ;QAAI;IAElB;IACA+B,QAAQ;IACRC,MAAM;AACR,EAAC;AAED,OAAO,MAAMpC,gBAAgB,CAACJ;IAC5B,OAAOyC,QAAQzC,WAAW0C,KAAK,EAAEC,UAAU3C,WAAW4C,SAAS,EAAED;AACnE,EAAC"}
1
+ {"version":3,"sources":["../../../src/queues/endpoints/run.ts"],"sourcesContent":["import type { Endpoint } from '../../config/types.js'\nimport type { SanitizedJobsConfig } from '../config/types/index.js'\n\nimport { defaultAccess } from '../../auth/defaultAccess.js'\nimport { runJobs, type RunJobsArgs } from '../operations/runJobs/index.js'\n\n/**\n * /api/payload-jobs/run endpoint\n *\n * This endpoint is GET instead of POST to allow it to be used in a Vercel Cron.\n */\nexport const runJobsEndpoint: Endpoint = {\n handler: async (req) => {\n const jobsConfig = req.payload.config.jobs\n\n if (!configHasJobs(jobsConfig)) {\n return Response.json(\n {\n message: 'No jobs to run.',\n },\n { status: 200 },\n )\n }\n\n const accessFn = jobsConfig.access?.run ?? defaultAccess\n\n const hasAccess = await accessFn({ req })\n\n if (!hasAccess) {\n return Response.json(\n {\n message: req.i18n.t('error:unauthorized'),\n },\n { status: 401 },\n )\n }\n\n const {\n allQueues,\n disableScheduling: disableSchedulingParam,\n limit,\n queue,\n silent: silentParam,\n } = req.query as {\n allQueues?: 'false' | 'true'\n disableScheduling?: 'false' | 'true'\n limit?: number\n queue?: string\n silent?: string\n }\n\n const silent = silentParam === 'true'\n\n const shouldHandleSchedules = disableSchedulingParam !== 'true'\n\n const runAllQueues = allQueues && !(typeof allQueues === 'string' && allQueues === 'false')\n\n if (shouldHandleSchedules && jobsConfig.scheduling) {\n // If should handle schedules and schedules are defined\n await req.payload.jobs.handleSchedules({ allQueues: runAllQueues, queue, req })\n }\n\n const runJobsArgs: RunJobsArgs = {\n queue,\n req,\n // Access is validated above, so it's safe to override here\n allQueues: runAllQueues,\n overrideAccess: true,\n silent,\n }\n\n const parsedLimit = Number(limit)\n if (!isNaN(parsedLimit)) {\n runJobsArgs.limit = parsedLimit\n }\n\n let noJobsRemaining = false\n let remainingJobsFromQueried = 0\n try {\n const result = await runJobs(runJobsArgs)\n noJobsRemaining = !!result.noJobsRemaining\n remainingJobsFromQueried = result.remainingJobsFromQueried\n } catch (err) {\n req.payload.logger.error({\n err,\n msg: 'There was an error running jobs:',\n queue: runJobsArgs.queue,\n })\n\n return Response.json(\n {\n message: req.i18n.t('error:unknown'),\n noJobsRemaining: true,\n remainingJobsFromQueried,\n },\n { status: 500 },\n )\n }\n\n return Response.json(\n {\n message: req.i18n.t('general:success'),\n noJobsRemaining,\n remainingJobsFromQueried,\n },\n { status: 200 },\n )\n },\n method: 'get',\n path: '/run',\n}\n\nexport const configHasJobs = (jobsConfig: SanitizedJobsConfig): boolean => {\n return Boolean(jobsConfig.tasks?.length || jobsConfig.workflows?.length)\n}\n"],"names":["defaultAccess","runJobs","runJobsEndpoint","handler","req","jobsConfig","payload","config","jobs","configHasJobs","Response","json","message","status","accessFn","access","run","hasAccess","i18n","t","allQueues","disableScheduling","disableSchedulingParam","limit","queue","silent","silentParam","query","shouldHandleSchedules","runAllQueues","scheduling","handleSchedules","runJobsArgs","overrideAccess","parsedLimit","Number","isNaN","noJobsRemaining","remainingJobsFromQueried","result","err","logger","error","msg","method","path","Boolean","tasks","length","workflows"],"mappings":"AAGA,SAASA,aAAa,QAAQ,8BAA6B;AAC3D,SAASC,OAAO,QAA0B,iCAAgC;AAE1E;;;;CAIC,GACD,OAAO,MAAMC,kBAA4B;IACvCC,SAAS,OAAOC;QACd,MAAMC,aAAaD,IAAIE,OAAO,CAACC,MAAM,CAACC,IAAI;QAE1C,IAAI,CAACC,cAAcJ,aAAa;YAC9B,OAAOK,SAASC,IAAI,CAClB;gBACEC,SAAS;YACX,GACA;gBAAEC,QAAQ;YAAI;QAElB;QAEA,MAAMC,WAAWT,WAAWU,MAAM,EAAEC,OAAOhB;QAE3C,MAAMiB,YAAY,MAAMH,SAAS;YAAEV;QAAI;QAEvC,IAAI,CAACa,WAAW;YACd,OAAOP,SAASC,IAAI,CAClB;gBACEC,SAASR,IAAIc,IAAI,CAACC,CAAC,CAAC;YACtB,GACA;gBAAEN,QAAQ;YAAI;QAElB;QAEA,MAAM,EACJO,SAAS,EACTC,mBAAmBC,sBAAsB,EACzCC,KAAK,EACLC,KAAK,EACLC,QAAQC,WAAW,EACpB,GAAGtB,IAAIuB,KAAK;QAQb,MAAMF,SAASC,gBAAgB;QAE/B,MAAME,wBAAwBN,2BAA2B;QAEzD,MAAMO,eAAeT,aAAa,CAAE,CAAA,OAAOA,cAAc,YAAYA,cAAc,OAAM;QAEzF,IAAIQ,yBAAyBvB,WAAWyB,UAAU,EAAE;YAClD,uDAAuD;YACvD,MAAM1B,IAAIE,OAAO,CAACE,IAAI,CAACuB,eAAe,CAAC;gBAAEX,WAAWS;gBAAcL;gBAAOpB;YAAI;QAC/E;QAEA,MAAM4B,cAA2B;YAC/BR;YACApB;YACA,2DAA2D;YAC3DgB,WAAWS;YACXI,gBAAgB;YAChBR;QACF;QAEA,MAAMS,cAAcC,OAAOZ;QAC3B,IAAI,CAACa,MAAMF,cAAc;YACvBF,YAAYT,KAAK,GAAGW;QACtB;QAEA,IAAIG,kBAAkB;QACtB,IAAIC,2BAA2B;QAC/B,IAAI;YACF,MAAMC,SAAS,MAAMtC,QAAQ+B;YAC7BK,kBAAkB,CAAC,CAACE,OAAOF,eAAe;YAC1CC,2BAA2BC,OAAOD,wBAAwB;QAC5D,EAAE,OAAOE,KAAK;YACZpC,IAAIE,OAAO,CAACmC,MAAM,CAACC,KAAK,CAAC;gBACvBF;gBACAG,KAAK;gBACLnB,OAAOQ,YAAYR,KAAK;YAC1B;YAEA,OAAOd,SAASC,IAAI,CAClB;gBACEC,SAASR,IAAIc,IAAI,CAACC,CAAC,CAAC;gBACpBkB,iBAAiB;gBACjBC;YACF,GACA;gBAAEzB,QAAQ;YAAI;QAElB;QAEA,OAAOH,SAASC,IAAI,CAClB;YACEC,SAASR,IAAIc,IAAI,CAACC,CAAC,CAAC;YACpBkB;YACAC;QACF,GACA;YAAEzB,QAAQ;QAAI;IAElB;IACA+B,QAAQ;IACRC,MAAM;AACR,EAAC;AAED,OAAO,MAAMpC,gBAAgB,CAACJ;IAC5B,OAAOyC,QAAQzC,WAAW0C,KAAK,EAAEC,UAAU3C,WAAW4C,SAAS,EAAED;AACnE,EAAC"}
@@ -7,8 +7,8 @@ export type ClientUploadData = {
7
7
  size: number;
8
8
  };
9
9
  /**
10
- * `req.context` key `unlinkTempFiles` reads to find a materializer-created temp file once
11
- * `req.file` is gone - plugin-cloud-storage's afterChange hook clears `req.file` after
10
+ * `req.context` key `unlinkClientUploadTempFile` reads to find a materializer-created temp file
11
+ * once `req.file` is gone - plugin-cloud-storage's afterChange hook clears `req.file` after
12
12
  * uploading generated image sizes, before cleanup runs, so tracking it only on `req.file`
13
13
  * would leak the temp file.
14
14
  */
@@ -1 +1 @@
1
- {"version":3,"file":"getFileFromClientUpload.d.ts","sourceRoot":"","sources":["../../src/uploads/getFileFromClientUpload.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAQvD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,cAAc,EAAE,MAAM,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,oCAAoC,CAAA;AAEzF,wBAAsB,uBAAuB,CAAC,EAC5C,IAAI,EACJ,GAAG,GACJ,EAAE;IACD,IAAI,EAAE,gBAAgB,CAAA;IACtB,GAAG,EAAE,cAAc,CAAA;CACpB,GAAG,OAAO,CAAC,WAAW,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,CA8C/C"}
1
+ {"version":3,"file":"getFileFromClientUpload.d.ts","sourceRoot":"","sources":["../../src/uploads/getFileFromClientUpload.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAQvD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,cAAc,EAAE,MAAM,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,oCAAoC,CAAA;AAEzF,wBAAsB,uBAAuB,CAAC,EAC5C,IAAI,EACJ,GAAG,GACJ,EAAE;IACD,IAAI,EAAE,gBAAgB,CAAA;IACtB,GAAG,EAAE,cAAc,CAAA;CACpB,GAAG,OAAO,CAAC,WAAW,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,CAgD/C"}
@@ -10,8 +10,8 @@ import { isolateObjectProperty } from '../utilities/isolateObjectProperty.js';
10
10
  import { getFileContentRequirement, HEADER_PROBE_BYTE_LENGTH } from './getFileContentRequirement.js';
11
11
  import { getImageSize } from './getImageSize.js';
12
12
  /**
13
- * `req.context` key `unlinkTempFiles` reads to find a materializer-created temp file once
14
- * `req.file` is gone - plugin-cloud-storage's afterChange hook clears `req.file` after
13
+ * `req.context` key `unlinkClientUploadTempFile` reads to find a materializer-created temp file
14
+ * once `req.file` is gone - plugin-cloud-storage's afterChange hook clears `req.file` after
15
15
  * uploading generated image sizes, before cleanup runs, so tracking it only on `req.file`
16
16
  * would leak the temp file.
17
17
  */ export const CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY = 'payloadClientUploadTempFilePath';
@@ -20,11 +20,11 @@ export async function getFileFromClientUpload({ file, req }) {
20
20
  if (!uploadConfig || !uploadConfig.handlers) {
21
21
  throw new APIError(`uploadConfig.handlers is not present for ${file.collectionSlug}`);
22
22
  }
23
- const contentRequirement = getFileContentRequirement({
23
+ const contentRequirement = file.clientUploadContext ? getFileContentRequirement({
24
24
  hasSizeEdits: requestHasSizeEdits(req),
25
25
  mimeType: file.mimeType,
26
26
  uploadConfig
27
- });
27
+ }) : 'full';
28
28
  if (contentRequirement === 'none') {
29
29
  return {
30
30
  name: file.filename,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/uploads/getFileFromClientUpload.ts"],"sourcesContent":["import { randomUUID } from 'crypto'\nimport fs from 'fs'\nimport { mkdir, rm, writeFile } from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport { Readable } from 'stream'\nimport { pipeline } from 'stream/promises'\n\nimport type { PayloadRequest } from '../types/index.js'\nimport type { SanitizedUploadConfig, UploadEdits } from './types.js'\n\nimport { APIError } from '../errors/APIError.js'\nimport { isolateObjectProperty } from '../utilities/isolateObjectProperty.js'\nimport { getFileContentRequirement, HEADER_PROBE_BYTE_LENGTH } from './getFileContentRequirement.js'\nimport { getImageSize } from './getImageSize.js'\n\nexport type ClientUploadData = {\n clientUploadContext?: unknown\n collectionSlug: string\n filename: string\n mimeType: string\n size: number\n}\n\n/**\n * `req.context` key `unlinkTempFiles` reads to find a materializer-created temp file once\n * `req.file` is gone - plugin-cloud-storage's afterChange hook clears `req.file` after\n * uploading generated image sizes, before cleanup runs, so tracking it only on `req.file`\n * would leak the temp file.\n */\nexport const CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY = 'payloadClientUploadTempFilePath'\n\nexport async function getFileFromClientUpload({\n file,\n req,\n}: {\n file: ClientUploadData\n req: PayloadRequest\n}): Promise<NonNullable<PayloadRequest['file']>> {\n const uploadConfig = req.payload.collections[file.collectionSlug]?.config.upload\n\n if (!uploadConfig || !uploadConfig.handlers) {\n throw new APIError(`uploadConfig.handlers is not present for ${file.collectionSlug}`)\n }\n\n const contentRequirement = getFileContentRequirement({\n hasSizeEdits: requestHasSizeEdits(req),\n mimeType: file.mimeType,\n uploadConfig,\n })\n\n if (contentRequirement === 'none') {\n return {\n name: file.filename,\n clientUploadContext: file.clientUploadContext,\n data: Buffer.alloc(0),\n mimetype: file.mimeType,\n size: file.size,\n }\n }\n\n if (contentRequirement === 'header') {\n const headerFile = await fetchHeaderOnly({ file, req, uploadConfig })\n if (headerFile) {\n return headerFile\n }\n }\n\n const response = await fetchUploadResponse({ file, req, uploadConfig })\n const tempFilePath = await streamResponseToTempFile({ req, response })\n\n if (!req.context) {\n req.context = {}\n }\n req.context[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY] = tempFilePath\n\n return {\n name: file.filename,\n clientUploadContext: file.clientUploadContext,\n data: Buffer.alloc(0),\n mimetype: response.headers.get('Content-Type') || file.mimeType,\n size: file.size,\n tempFilePath,\n }\n}\n\n/**\n * Only crop and explicit pixel edits require the original bytes - a focal-point-only\n * change re-derives from data already on the document, not the incoming upload.\n */\nfunction requestHasSizeEdits(req: PayloadRequest): boolean {\n const uploadEdits =\n req.query?.uploadEdits && typeof req.query.uploadEdits === 'object'\n ? (req.query.uploadEdits as UploadEdits)\n : undefined\n\n return Boolean(uploadEdits?.crop || uploadEdits?.heightInPixels || uploadEdits?.widthInPixels)\n}\n\n/**\n * Runs every configured handler and returns the last one that responds, following a single\n * redirect if that response is one - matching the pre-existing v3 handler contract exactly\n * (v3 always ran every handler; only v4 stops at the first response, via an unrelated PR).\n */\nasync function fetchUploadResponse({\n file,\n req,\n uploadConfig,\n}: {\n file: ClientUploadData\n req: PayloadRequest\n uploadConfig: SanitizedUploadConfig\n}): Promise<Response> {\n let response: null | Response = null\n let error: unknown\n\n for (const handler of uploadConfig.handlers!) {\n try {\n const result = await handler(req, {\n doc: null!,\n params: {\n clientUploadContext: file.clientUploadContext,\n collection: file.collectionSlug,\n filename: file.filename,\n },\n })\n\n if (result) {\n response = result\n }\n // If we couldn't get the file from that handler, save the error and try other.\n } catch (err) {\n error = err\n }\n }\n\n if (!response) {\n if (error) {\n req.payload.logger.error(error)\n }\n\n throw new APIError('Expected response from the upload handler.')\n }\n\n if (response.status >= 300 && response.status < 400) {\n const redirectUrl = response.headers.get('Location')\n if (redirectUrl) {\n // Forward the Range header (if any) so a redirect from the header-probe path still\n // fetches only the bounded slice, instead of the whole object, from the redirect target.\n const rangeHeader = req.headers.get('Range')\n response = rangeHeader\n ? await fetch(redirectUrl, { headers: { Range: rangeHeader } })\n : await fetch(redirectUrl)\n }\n }\n\n return response\n}\n\n/**\n * Requests a bounded byte range from the handlers and probes it for image dimensions, without\n * ever reading past `HEADER_PROBE_BYTE_LENGTH`. Returns `null` when the bounded bytes cannot be\n * probed, so the caller falls back to a full streamed fetch.\n */\nasync function fetchHeaderOnly({\n file,\n req,\n uploadConfig,\n}: {\n file: ClientUploadData\n req: PayloadRequest\n uploadConfig: SanitizedUploadConfig\n}): Promise<NonNullable<PayloadRequest['file']> | null> {\n const rangedHeaders = new Headers(req.headers)\n rangedHeaders.set('Range', `bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}`)\n\n const scopedReq = isolateObjectProperty(req, 'headers')\n scopedReq.headers = rangedHeaders\n\n const response = await fetchUploadResponse({ file, req: scopedReq, uploadConfig })\n\n if (!response.body) {\n return null\n }\n\n const reader = response.body.getReader()\n const chunks: Uint8Array[] = []\n let bytesRead = 0\n\n try {\n while (bytesRead < HEADER_PROBE_BYTE_LENGTH) {\n const { done, value } = await reader.read()\n if (done || !value) {\n break\n }\n\n const remaining = HEADER_PROBE_BYTE_LENGTH - bytesRead\n const chunk = value.length > remaining ? value.slice(0, remaining) : value\n\n chunks.push(chunk)\n bytesRead += chunk.length\n }\n } finally {\n await reader.cancel().catch(() => undefined)\n }\n\n const probedFile = {\n name: file.filename,\n clientUploadContext: file.clientUploadContext,\n data: Buffer.concat(chunks),\n mimetype: response.headers.get('Content-Type') || file.mimeType,\n size: file.size,\n }\n\n try {\n await getImageSize({ file: probedFile, sharp: req.payload.config.sharp })\n } catch {\n return null\n }\n\n return probedFile\n}\n\n/**\n * Streams the full response body straight to disk so a cloud object of unbounded size never\n * becomes a single in-memory Buffer.\n */\nasync function streamResponseToTempFile({\n req,\n response,\n}: {\n req: PayloadRequest\n response: Response\n}): Promise<string> {\n const tempFileDir = req.payload.config.upload?.tempFileDir || os.tmpdir()\n await mkdir(tempFileDir, { recursive: true })\n const tempFilePath = path.join(tempFileDir, `payload-client-upload-${randomUUID()}`)\n\n // A null body is a legitimate zero-byte file (the Fetch API allows a Response to omit a\n // body entirely), not a failure - `response.arrayBuffer()` tolerated this the same way.\n if (!response.body) {\n await writeFile(tempFilePath, Buffer.alloc(0))\n return tempFilePath\n }\n\n try {\n await pipeline(\n Readable.fromWeb(response.body as ReadableStream<Uint8Array>),\n fs.createWriteStream(tempFilePath),\n )\n } catch (error) {\n await rm(tempFilePath, { force: true })\n throw error\n }\n\n return tempFilePath\n}\n"],"names":["randomUUID","fs","mkdir","rm","writeFile","os","path","Readable","pipeline","APIError","isolateObjectProperty","getFileContentRequirement","HEADER_PROBE_BYTE_LENGTH","getImageSize","CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY","getFileFromClientUpload","file","req","uploadConfig","payload","collections","collectionSlug","config","upload","handlers","contentRequirement","hasSizeEdits","requestHasSizeEdits","mimeType","name","filename","clientUploadContext","data","Buffer","alloc","mimetype","size","headerFile","fetchHeaderOnly","response","fetchUploadResponse","tempFilePath","streamResponseToTempFile","context","headers","get","uploadEdits","query","undefined","Boolean","crop","heightInPixels","widthInPixels","error","handler","result","doc","params","collection","err","logger","status","redirectUrl","rangeHeader","fetch","Range","rangedHeaders","Headers","set","scopedReq","body","reader","getReader","chunks","bytesRead","done","value","read","remaining","chunk","length","slice","push","cancel","catch","probedFile","concat","sharp","tempFileDir","tmpdir","recursive","join","fromWeb","createWriteStream","force"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/uploads/getFileFromClientUpload.ts"],"sourcesContent":["import { randomUUID } from 'crypto'\nimport fs from 'fs'\nimport { mkdir, rm, writeFile } from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport { Readable } from 'stream'\nimport { pipeline } from 'stream/promises'\n\nimport type { PayloadRequest } from '../types/index.js'\nimport type { SanitizedUploadConfig, UploadEdits } from './types.js'\n\nimport { APIError } from '../errors/APIError.js'\nimport { isolateObjectProperty } from '../utilities/isolateObjectProperty.js'\nimport { getFileContentRequirement, HEADER_PROBE_BYTE_LENGTH } from './getFileContentRequirement.js'\nimport { getImageSize } from './getImageSize.js'\n\nexport type ClientUploadData = {\n clientUploadContext?: unknown\n collectionSlug: string\n filename: string\n mimeType: string\n size: number\n}\n\n/**\n * `req.context` key `unlinkClientUploadTempFile` reads to find a materializer-created temp file\n * once `req.file` is gone - plugin-cloud-storage's afterChange hook clears `req.file` after\n * uploading generated image sizes, before cleanup runs, so tracking it only on `req.file`\n * would leak the temp file.\n */\nexport const CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY = 'payloadClientUploadTempFilePath'\n\nexport async function getFileFromClientUpload({\n file,\n req,\n}: {\n file: ClientUploadData\n req: PayloadRequest\n}): Promise<NonNullable<PayloadRequest['file']>> {\n const uploadConfig = req.payload.collections[file.collectionSlug]?.config.upload\n\n if (!uploadConfig || !uploadConfig.handlers) {\n throw new APIError(`uploadConfig.handlers is not present for ${file.collectionSlug}`)\n }\n\n const contentRequirement = file.clientUploadContext\n ? getFileContentRequirement({\n hasSizeEdits: requestHasSizeEdits(req),\n mimeType: file.mimeType,\n uploadConfig,\n })\n : 'full'\n\n if (contentRequirement === 'none') {\n return {\n name: file.filename,\n clientUploadContext: file.clientUploadContext,\n data: Buffer.alloc(0),\n mimetype: file.mimeType,\n size: file.size,\n }\n }\n\n if (contentRequirement === 'header') {\n const headerFile = await fetchHeaderOnly({ file, req, uploadConfig })\n if (headerFile) {\n return headerFile\n }\n }\n\n const response = await fetchUploadResponse({ file, req, uploadConfig })\n const tempFilePath = await streamResponseToTempFile({ req, response })\n\n if (!req.context) {\n req.context = {}\n }\n req.context[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY] = tempFilePath\n\n return {\n name: file.filename,\n clientUploadContext: file.clientUploadContext,\n data: Buffer.alloc(0),\n mimetype: response.headers.get('Content-Type') || file.mimeType,\n size: file.size,\n tempFilePath,\n }\n}\n\n/**\n * Only crop and explicit pixel edits require the original bytes - a focal-point-only\n * change re-derives from data already on the document, not the incoming upload.\n */\nfunction requestHasSizeEdits(req: PayloadRequest): boolean {\n const uploadEdits =\n req.query?.uploadEdits && typeof req.query.uploadEdits === 'object'\n ? (req.query.uploadEdits as UploadEdits)\n : undefined\n\n return Boolean(uploadEdits?.crop || uploadEdits?.heightInPixels || uploadEdits?.widthInPixels)\n}\n\n/**\n * Runs every configured handler and returns the last one that responds, following a single\n * redirect if that response is one - matching the pre-existing v3 handler contract exactly\n * (v3 always ran every handler; only v4 stops at the first response, via an unrelated PR).\n */\nasync function fetchUploadResponse({\n file,\n req,\n uploadConfig,\n}: {\n file: ClientUploadData\n req: PayloadRequest\n uploadConfig: SanitizedUploadConfig\n}): Promise<Response> {\n let response: null | Response = null\n let error: unknown\n\n for (const handler of uploadConfig.handlers!) {\n try {\n const result = await handler(req, {\n doc: null!,\n params: {\n clientUploadContext: file.clientUploadContext,\n collection: file.collectionSlug,\n filename: file.filename,\n },\n })\n\n if (result) {\n response = result\n }\n // If we couldn't get the file from that handler, save the error and try other.\n } catch (err) {\n error = err\n }\n }\n\n if (!response) {\n if (error) {\n req.payload.logger.error(error)\n }\n\n throw new APIError('Expected response from the upload handler.')\n }\n\n if (response.status >= 300 && response.status < 400) {\n const redirectUrl = response.headers.get('Location')\n if (redirectUrl) {\n // Forward the Range header (if any) so a redirect from the header-probe path still\n // fetches only the bounded slice, instead of the whole object, from the redirect target.\n const rangeHeader = req.headers.get('Range')\n response = rangeHeader\n ? await fetch(redirectUrl, { headers: { Range: rangeHeader } })\n : await fetch(redirectUrl)\n }\n }\n\n return response\n}\n\n/**\n * Requests a bounded byte range from the handlers and probes it for image dimensions, without\n * ever reading past `HEADER_PROBE_BYTE_LENGTH`. Returns `null` when the bounded bytes cannot be\n * probed, so the caller falls back to a full streamed fetch.\n */\nasync function fetchHeaderOnly({\n file,\n req,\n uploadConfig,\n}: {\n file: ClientUploadData\n req: PayloadRequest\n uploadConfig: SanitizedUploadConfig\n}): Promise<NonNullable<PayloadRequest['file']> | null> {\n const rangedHeaders = new Headers(req.headers)\n rangedHeaders.set('Range', `bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}`)\n\n const scopedReq = isolateObjectProperty(req, 'headers')\n scopedReq.headers = rangedHeaders\n\n const response = await fetchUploadResponse({ file, req: scopedReq, uploadConfig })\n\n if (!response.body) {\n return null\n }\n\n const reader = response.body.getReader()\n const chunks: Uint8Array[] = []\n let bytesRead = 0\n\n try {\n while (bytesRead < HEADER_PROBE_BYTE_LENGTH) {\n const { done, value } = await reader.read()\n if (done || !value) {\n break\n }\n\n const remaining = HEADER_PROBE_BYTE_LENGTH - bytesRead\n const chunk = value.length > remaining ? value.slice(0, remaining) : value\n\n chunks.push(chunk)\n bytesRead += chunk.length\n }\n } finally {\n await reader.cancel().catch(() => undefined)\n }\n\n const probedFile = {\n name: file.filename,\n clientUploadContext: file.clientUploadContext,\n data: Buffer.concat(chunks),\n mimetype: response.headers.get('Content-Type') || file.mimeType,\n size: file.size,\n }\n\n try {\n await getImageSize({ file: probedFile, sharp: req.payload.config.sharp })\n } catch {\n return null\n }\n\n return probedFile\n}\n\n/**\n * Streams the full response body straight to disk so a cloud object of unbounded size never\n * becomes a single in-memory Buffer.\n */\nasync function streamResponseToTempFile({\n req,\n response,\n}: {\n req: PayloadRequest\n response: Response\n}): Promise<string> {\n const tempFileDir = req.payload.config.upload?.tempFileDir || os.tmpdir()\n await mkdir(tempFileDir, { recursive: true })\n const tempFilePath = path.join(tempFileDir, `payload-client-upload-${randomUUID()}`)\n\n // A null body is a legitimate zero-byte file (the Fetch API allows a Response to omit a\n // body entirely), not a failure - `response.arrayBuffer()` tolerated this the same way.\n if (!response.body) {\n await writeFile(tempFilePath, Buffer.alloc(0))\n return tempFilePath\n }\n\n try {\n await pipeline(\n Readable.fromWeb(response.body as ReadableStream<Uint8Array>),\n fs.createWriteStream(tempFilePath),\n )\n } catch (error) {\n await rm(tempFilePath, { force: true })\n throw error\n }\n\n return tempFilePath\n}\n"],"names":["randomUUID","fs","mkdir","rm","writeFile","os","path","Readable","pipeline","APIError","isolateObjectProperty","getFileContentRequirement","HEADER_PROBE_BYTE_LENGTH","getImageSize","CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY","getFileFromClientUpload","file","req","uploadConfig","payload","collections","collectionSlug","config","upload","handlers","contentRequirement","clientUploadContext","hasSizeEdits","requestHasSizeEdits","mimeType","name","filename","data","Buffer","alloc","mimetype","size","headerFile","fetchHeaderOnly","response","fetchUploadResponse","tempFilePath","streamResponseToTempFile","context","headers","get","uploadEdits","query","undefined","Boolean","crop","heightInPixels","widthInPixels","error","handler","result","doc","params","collection","err","logger","status","redirectUrl","rangeHeader","fetch","Range","rangedHeaders","Headers","set","scopedReq","body","reader","getReader","chunks","bytesRead","done","value","read","remaining","chunk","length","slice","push","cancel","catch","probedFile","concat","sharp","tempFileDir","tmpdir","recursive","join","fromWeb","createWriteStream","force"],"mappings":"AAAA,SAASA,UAAU,QAAQ,SAAQ;AACnC,OAAOC,QAAQ,KAAI;AACnB,SAASC,KAAK,EAAEC,EAAE,EAAEC,SAAS,QAAQ,cAAa;AAClD,OAAOC,QAAQ,KAAI;AACnB,OAAOC,UAAU,OAAM;AACvB,SAASC,QAAQ,QAAQ,SAAQ;AACjC,SAASC,QAAQ,QAAQ,kBAAiB;AAK1C,SAASC,QAAQ,QAAQ,wBAAuB;AAChD,SAASC,qBAAqB,QAAQ,wCAAuC;AAC7E,SAASC,yBAAyB,EAAEC,wBAAwB,QAAQ,iCAAgC;AACpG,SAASC,YAAY,QAAQ,oBAAmB;AAUhD;;;;;CAKC,GACD,OAAO,MAAMC,2CAA2C,kCAAiC;AAEzF,OAAO,eAAeC,wBAAwB,EAC5CC,IAAI,EACJC,GAAG,EAIJ;IACC,MAAMC,eAAeD,IAAIE,OAAO,CAACC,WAAW,CAACJ,KAAKK,cAAc,CAAC,EAAEC,OAAOC;IAE1E,IAAI,CAACL,gBAAgB,CAACA,aAAaM,QAAQ,EAAE;QAC3C,MAAM,IAAIf,SAAS,CAAC,yCAAyC,EAAEO,KAAKK,cAAc,EAAE;IACtF;IAEA,MAAMI,qBAAqBT,KAAKU,mBAAmB,GAC/Cf,0BAA0B;QACxBgB,cAAcC,oBAAoBX;QAClCY,UAAUb,KAAKa,QAAQ;QACvBX;IACF,KACA;IAEJ,IAAIO,uBAAuB,QAAQ;QACjC,OAAO;YACLK,MAAMd,KAAKe,QAAQ;YACnBL,qBAAqBV,KAAKU,mBAAmB;YAC7CM,MAAMC,OAAOC,KAAK,CAAC;YACnBC,UAAUnB,KAAKa,QAAQ;YACvBO,MAAMpB,KAAKoB,IAAI;QACjB;IACF;IAEA,IAAIX,uBAAuB,UAAU;QACnC,MAAMY,aAAa,MAAMC,gBAAgB;YAAEtB;YAAMC;YAAKC;QAAa;QACnE,IAAImB,YAAY;YACd,OAAOA;QACT;IACF;IAEA,MAAME,WAAW,MAAMC,oBAAoB;QAAExB;QAAMC;QAAKC;IAAa;IACrE,MAAMuB,eAAe,MAAMC,yBAAyB;QAAEzB;QAAKsB;IAAS;IAEpE,IAAI,CAACtB,IAAI0B,OAAO,EAAE;QAChB1B,IAAI0B,OAAO,GAAG,CAAC;IACjB;IACA1B,IAAI0B,OAAO,CAAC7B,yCAAyC,GAAG2B;IAExD,OAAO;QACLX,MAAMd,KAAKe,QAAQ;QACnBL,qBAAqBV,KAAKU,mBAAmB;QAC7CM,MAAMC,OAAOC,KAAK,CAAC;QACnBC,UAAUI,SAASK,OAAO,CAACC,GAAG,CAAC,mBAAmB7B,KAAKa,QAAQ;QAC/DO,MAAMpB,KAAKoB,IAAI;QACfK;IACF;AACF;AAEA;;;CAGC,GACD,SAASb,oBAAoBX,GAAmB;IAC9C,MAAM6B,cACJ7B,IAAI8B,KAAK,EAAED,eAAe,OAAO7B,IAAI8B,KAAK,CAACD,WAAW,KAAK,WACtD7B,IAAI8B,KAAK,CAACD,WAAW,GACtBE;IAEN,OAAOC,QAAQH,aAAaI,QAAQJ,aAAaK,kBAAkBL,aAAaM;AAClF;AAEA;;;;CAIC,GACD,eAAeZ,oBAAoB,EACjCxB,IAAI,EACJC,GAAG,EACHC,YAAY,EAKb;IACC,IAAIqB,WAA4B;IAChC,IAAIc;IAEJ,KAAK,MAAMC,WAAWpC,aAAaM,QAAQ,CAAG;QAC5C,IAAI;YACF,MAAM+B,SAAS,MAAMD,QAAQrC,KAAK;gBAChCuC,KAAK;gBACLC,QAAQ;oBACN/B,qBAAqBV,KAAKU,mBAAmB;oBAC7CgC,YAAY1C,KAAKK,cAAc;oBAC/BU,UAAUf,KAAKe,QAAQ;gBACzB;YACF;YAEA,IAAIwB,QAAQ;gBACVhB,WAAWgB;YACb;QACA,+EAA+E;QACjF,EAAE,OAAOI,KAAK;YACZN,QAAQM;QACV;IACF;IAEA,IAAI,CAACpB,UAAU;QACb,IAAIc,OAAO;YACTpC,IAAIE,OAAO,CAACyC,MAAM,CAACP,KAAK,CAACA;QAC3B;QAEA,MAAM,IAAI5C,SAAS;IACrB;IAEA,IAAI8B,SAASsB,MAAM,IAAI,OAAOtB,SAASsB,MAAM,GAAG,KAAK;QACnD,MAAMC,cAAcvB,SAASK,OAAO,CAACC,GAAG,CAAC;QACzC,IAAIiB,aAAa;YACf,mFAAmF;YACnF,yFAAyF;YACzF,MAAMC,cAAc9C,IAAI2B,OAAO,CAACC,GAAG,CAAC;YACpCN,WAAWwB,cACP,MAAMC,MAAMF,aAAa;gBAAElB,SAAS;oBAAEqB,OAAOF;gBAAY;YAAE,KAC3D,MAAMC,MAAMF;QAClB;IACF;IAEA,OAAOvB;AACT;AAEA;;;;CAIC,GACD,eAAeD,gBAAgB,EAC7BtB,IAAI,EACJC,GAAG,EACHC,YAAY,EAKb;IACC,MAAMgD,gBAAgB,IAAIC,QAAQlD,IAAI2B,OAAO;IAC7CsB,cAAcE,GAAG,CAAC,SAAS,CAAC,QAAQ,EAAExD,2BAA2B,GAAG;IAEpE,MAAMyD,YAAY3D,sBAAsBO,KAAK;IAC7CoD,UAAUzB,OAAO,GAAGsB;IAEpB,MAAM3B,WAAW,MAAMC,oBAAoB;QAAExB;QAAMC,KAAKoD;QAAWnD;IAAa;IAEhF,IAAI,CAACqB,SAAS+B,IAAI,EAAE;QAClB,OAAO;IACT;IAEA,MAAMC,SAAShC,SAAS+B,IAAI,CAACE,SAAS;IACtC,MAAMC,SAAuB,EAAE;IAC/B,IAAIC,YAAY;IAEhB,IAAI;QACF,MAAOA,YAAY9D,yBAA0B;YAC3C,MAAM,EAAE+D,IAAI,EAAEC,KAAK,EAAE,GAAG,MAAML,OAAOM,IAAI;YACzC,IAAIF,QAAQ,CAACC,OAAO;gBAClB;YACF;YAEA,MAAME,YAAYlE,2BAA2B8D;YAC7C,MAAMK,QAAQH,MAAMI,MAAM,GAAGF,YAAYF,MAAMK,KAAK,CAAC,GAAGH,aAAaF;YAErEH,OAAOS,IAAI,CAACH;YACZL,aAAaK,MAAMC,MAAM;QAC3B;IACF,SAAU;QACR,MAAMT,OAAOY,MAAM,GAAGC,KAAK,CAAC,IAAMpC;IACpC;IAEA,MAAMqC,aAAa;QACjBvD,MAAMd,KAAKe,QAAQ;QACnBL,qBAAqBV,KAAKU,mBAAmB;QAC7CM,MAAMC,OAAOqD,MAAM,CAACb;QACpBtC,UAAUI,SAASK,OAAO,CAACC,GAAG,CAAC,mBAAmB7B,KAAKa,QAAQ;QAC/DO,MAAMpB,KAAKoB,IAAI;IACjB;IAEA,IAAI;QACF,MAAMvB,aAAa;YAAEG,MAAMqE;YAAYE,OAAOtE,IAAIE,OAAO,CAACG,MAAM,CAACiE,KAAK;QAAC;IACzE,EAAE,OAAM;QACN,OAAO;IACT;IAEA,OAAOF;AACT;AAEA;;;CAGC,GACD,eAAe3C,yBAAyB,EACtCzB,GAAG,EACHsB,QAAQ,EAIT;IACC,MAAMiD,cAAcvE,IAAIE,OAAO,CAACG,MAAM,CAACC,MAAM,EAAEiE,eAAenF,GAAGoF,MAAM;IACvE,MAAMvF,MAAMsF,aAAa;QAAEE,WAAW;IAAK;IAC3C,MAAMjD,eAAenC,KAAKqF,IAAI,CAACH,aAAa,CAAC,sBAAsB,EAAExF,cAAc;IAEnF,wFAAwF;IACxF,wFAAwF;IACxF,IAAI,CAACuC,SAAS+B,IAAI,EAAE;QAClB,MAAMlE,UAAUqC,cAAcR,OAAOC,KAAK,CAAC;QAC3C,OAAOO;IACT;IAEA,IAAI;QACF,MAAMjC,SACJD,SAASqF,OAAO,CAACrD,SAAS+B,IAAI,GAC9BrE,GAAG4F,iBAAiB,CAACpD;IAEzB,EAAE,OAAOY,OAAO;QACd,MAAMlD,GAAGsC,cAAc;YAAEqD,OAAO;QAAK;QACrC,MAAMzC;IACR;IAEA,OAAOZ;AACT"}
@@ -82,6 +82,9 @@ const videoFile = (overrides = {})=>({
82
82
  ...overrides
83
83
  });
84
84
  const imageFile = (overrides = {})=>({
85
+ clientUploadContext: {
86
+ prefix: 'abc'
87
+ },
85
88
  collectionSlug: 'media',
86
89
  filename: 'photo.png',
87
90
  mimeType: 'image/png',
@@ -523,6 +526,65 @@ describe('getFileFromClientUpload', ()=>{
523
526
  const written = await fs.readFile(result.tempFilePath);
524
527
  expect(written.length).toBe(0);
525
528
  });
529
+ it('streams the full file to disk when a non-image client upload carries no context', async ()=>{
530
+ const handler = vi.fn(async ()=>new Response(Buffer.from('full-video-bytes'), {
531
+ headers: {
532
+ 'Content-Type': 'video/mp4'
533
+ },
534
+ status: 200
535
+ }));
536
+ const req = createReq({
537
+ handlers: [
538
+ handler
539
+ ],
540
+ upload: {
541
+ disableLocalStorage: true
542
+ }
543
+ });
544
+ const file = videoFile({
545
+ clientUploadContext: undefined
546
+ });
547
+ const result = await getFileFromClientUpload({
548
+ file,
549
+ req
550
+ });
551
+ expect(handler).toHaveBeenCalledTimes(1);
552
+ expect(result.tempFilePath).toBeDefined();
553
+ tempFilesToRemove.push(result.tempFilePath);
554
+ const written = await fs.readFile(result.tempFilePath);
555
+ expect(written.toString()).toBe('full-video-bytes');
556
+ });
557
+ it('streams the full file to disk when an image client upload carries no context', async ()=>{
558
+ const handler = vi.fn(async (handlerReq)=>{
559
+ expect(handlerReq.headers.get('Range')).toBeNull();
560
+ return new Response(MINIMAL_PNG, {
561
+ headers: {
562
+ 'Content-Type': 'image/png'
563
+ },
564
+ status: 200
565
+ });
566
+ });
567
+ const req = createReq({
568
+ handlers: [
569
+ handler
570
+ ],
571
+ upload: {
572
+ disableLocalStorage: true
573
+ }
574
+ });
575
+ const file = imageFile({
576
+ clientUploadContext: undefined
577
+ });
578
+ const result = await getFileFromClientUpload({
579
+ file,
580
+ req
581
+ });
582
+ expect(handler).toHaveBeenCalledTimes(1);
583
+ expect(result.tempFilePath).toBeDefined();
584
+ tempFilesToRemove.push(result.tempFilePath);
585
+ const written = await fs.readFile(result.tempFilePath);
586
+ expect(written).toEqual(MINIMAL_PNG);
587
+ });
526
588
  });
527
589
 
528
590
  //# sourceMappingURL=getFileFromClientUpload.spec.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/uploads/getFileFromClientUpload.spec.ts"],"sourcesContent":["import type { PayloadRequest } from '../types/index.js'\nimport type { SanitizedUploadConfig } from './types.js'\n\nimport fs from 'fs/promises'\nimport os from 'os'\nimport { afterEach, describe, expect, it, vi } from 'vitest'\n\nimport { HEADER_PROBE_BYTE_LENGTH } from './getFileContentRequirement.js'\nimport { type ClientUploadData, getFileFromClientUpload } from './getFileFromClientUpload.js'\nimport { getImageSize } from './getImageSize.js'\n\nconst MINIMAL_PNG = (() => {\n const signature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])\n const ihdrData = Buffer.alloc(13)\n ihdrData.writeUInt32BE(400, 0) // width\n ihdrData.writeUInt32BE(300, 4) // height\n ihdrData[8] = 8 // bit depth\n ihdrData[9] = 2 // color type: RGB\n const ihdrChunk = Buffer.concat([\n Buffer.from([0, 0, 0, 13]),\n Buffer.from('IHDR'),\n ihdrData,\n Buffer.alloc(4), // crc (unchecked by dimension probing)\n ])\n return Buffer.concat([signature, ihdrChunk])\n})()\n\nconst tempFilesToRemove: string[] = []\n\nafterEach(async () => {\n vi.unstubAllGlobals()\n for (const tempFilePath of tempFilesToRemove) {\n await fs.rm(tempFilePath, { force: true })\n }\n tempFilesToRemove.length = 0\n})\n\nconst createReq = ({\n handlers,\n upload,\n}: {\n handlers: NonNullable<SanitizedUploadConfig['handlers']>\n upload?: Partial<SanitizedUploadConfig>\n}): PayloadRequest => {\n const request = new Request('http://localhost/api/media')\n const req = request as unknown as PayloadRequest\n\n req.query = {}\n req.payload = {\n collections: {\n media: {\n config: {\n upload: {\n disableLocalStorage: true,\n handlers,\n ...upload,\n },\n },\n },\n },\n config: {\n upload: {},\n },\n logger: { error: vi.fn() },\n } as unknown as PayloadRequest['payload']\n\n return req\n}\n\nconst videoFile = (overrides: Partial<ClientUploadData> = {}): ClientUploadData => ({\n clientUploadContext: { prefix: 'abc' },\n collectionSlug: 'media',\n filename: 'clip.mp4',\n mimeType: 'video/mp4',\n size: 10,\n ...overrides,\n})\n\nconst imageFile = (overrides: Partial<ClientUploadData> = {}): ClientUploadData => ({\n collectionSlug: 'media',\n filename: 'photo.png',\n mimeType: 'image/png',\n size: MINIMAL_PNG.length,\n ...overrides,\n})\n\ndescribe('getFileFromClientUpload', () => {\n it('does not call a handler and returns empty data with an own clientUploadContext property when no content is required', async () => {\n const handler = vi.fn()\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(handler).not.toHaveBeenCalled()\n expect(Object.prototype.hasOwnProperty.call(result, 'clientUploadContext')).toBe(true)\n expect(result.clientUploadContext).toEqual({ prefix: 'abc' })\n expect(result.data.length).toBe(0)\n expect(result.tempFilePath).toBeUndefined()\n expect(result.name).toBe('clip.mp4')\n expect(result.size).toBe(10)\n })\n\n it('requests a bounded byte range and returns dimensions for a header-only image', async () => {\n const handler = vi.fn(async (handlerReq: PayloadRequest) => {\n expect(handlerReq.headers.get('Range')).toBe(`bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}`)\n return new Response(MINIMAL_PNG, {\n headers: { 'Content-Type': 'image/png' },\n status: 206,\n })\n })\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(result.data.length).toBeLessThanOrEqual(HEADER_PROBE_BYTE_LENGTH)\n expect(result.tempFilePath).toBeUndefined()\n await expect(getImageSize({ file: result })).resolves.toEqual({ height: 300, width: 400 })\n })\n\n it('caps the header read at the boundary and cancels the stream without pulling past it', async () => {\n let hasCancelled = false\n let hasReadPastBoundary = false\n const firstChunk = Buffer.concat([\n MINIMAL_PNG,\n Buffer.alloc(HEADER_PROBE_BYTE_LENGTH - MINIMAL_PNG.length),\n ])\n const stream = new ReadableStream(\n {\n cancel() {\n hasCancelled = true\n },\n pull(controller) {\n hasReadPastBoundary = true\n controller.error(new Error('Read past header boundary'))\n },\n start(controller) {\n controller.enqueue(firstChunk)\n },\n },\n { highWaterMark: 0 },\n )\n\n const handler = vi.fn(\n async () => new Response(stream, { headers: { 'Content-Type': 'image/png' }, status: 206 }),\n )\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile({ size: HEADER_PROBE_BYTE_LENGTH })\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(result.data.length).toBe(HEADER_PROBE_BYTE_LENGTH)\n expect(hasCancelled).toBe(true)\n expect(hasReadPastBoundary).toBe(false)\n })\n\n it('lets a handler read native Request properties like signal through the Range-scoped proxy', async () => {\n const handler = vi.fn(async (handlerReq: PayloadRequest) => {\n expect(() => handlerReq.signal).not.toThrow()\n expect(handlerReq.signal).toBeInstanceOf(AbortSignal)\n return new Response(MINIMAL_PNG, {\n headers: { 'Content-Type': 'image/png' },\n status: 206,\n })\n })\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n await getFileFromClientUpload({ file, req })\n\n expect(handler).toHaveBeenCalledTimes(1)\n })\n\n it('falls back to a full fetch when the bounded header cannot be probed for dimensions', async () => {\n const handler = vi\n .fn()\n .mockResolvedValueOnce(\n new Response(Buffer.from('not an image'), {\n headers: { 'Content-Type': 'image/png' },\n status: 206,\n }),\n )\n .mockResolvedValueOnce(\n new Response(MINIMAL_PNG, { headers: { 'Content-Type': 'image/png' }, status: 200 }),\n )\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(handler).toHaveBeenCalledTimes(2)\n expect(result.tempFilePath).toBeDefined()\n expect(result.data.length).toBe(0)\n })\n\n it('streams a full-content response to a temp file without buffering the whole body', async () => {\n const chunks = [\n Buffer.from('chunk-one-'),\n Buffer.from('chunk-two-'),\n Buffer.from('chunk-three'),\n ]\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\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 = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile({ size: 30 })\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(result.data).toEqual(Buffer.alloc(0))\n expect(arrayBufferTripwire).not.toHaveBeenCalled()\n const written = await fs.readFile(result.tempFilePath!)\n expect(written.toString()).toBe(chunks.map((chunk) => chunk.toString()).join(''))\n })\n\n it('runs every handler and uses the last one that returns a response, matching v3', async () => {\n const firstHandler = vi.fn(async () => new Response(Buffer.from('first-response')))\n const secondHandler = vi.fn(async () => new Response(Buffer.from('second-response')))\n const req = createReq({\n handlers: [firstHandler, secondHandler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(firstHandler).toHaveBeenCalledTimes(1)\n expect(secondHandler).toHaveBeenCalledTimes(1)\n const written = await fs.readFile(result.tempFilePath!)\n expect(written.toString()).toBe('second-response')\n })\n\n it('falls through to the next handler when the first returns nothing', async () => {\n const firstHandler = vi.fn(async () => undefined)\n const secondHandler = vi.fn(async () => new Response(Buffer.alloc(10), { status: 200 }))\n const req = createReq({\n handlers: [firstHandler, secondHandler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(firstHandler).toHaveBeenCalledTimes(1)\n expect(secondHandler).toHaveBeenCalledTimes(1)\n })\n\n it('follows exactly one redirect from a handler response', async () => {\n const redirectTarget = 'http://storage.example.com/file.mp4'\n const handler = vi.fn(\n async () => new Response(null, { headers: { Location: redirectTarget }, status: 302 }),\n )\n const fetchMock = vi.fn(\n async () =>\n new Response(Buffer.alloc(10), { headers: { 'Content-Type': 'video/mp4' }, status: 200 }),\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(fetchMock).toHaveBeenCalledTimes(1)\n expect(fetchMock).toHaveBeenCalledWith(redirectTarget)\n })\n\n it('forwards the Range header through a redirect during the bounded header probe', async () => {\n const redirectTarget = 'http://storage.example.com/photo.png'\n const handler = vi.fn(\n async () => new Response(null, { headers: { Location: redirectTarget }, status: 302 }),\n )\n const fetchMock = vi.fn(\n async () =>\n new Response(MINIMAL_PNG, { headers: { 'Content-Type': 'image/png' }, status: 206 }),\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(result.tempFilePath).toBeUndefined()\n expect(fetchMock).toHaveBeenCalledTimes(1)\n expect(fetchMock).toHaveBeenCalledWith(redirectTarget, {\n headers: { Range: `bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}` },\n })\n })\n\n it('removes the partial temp file when the stream fails before completion', async () => {\n const stream = new ReadableStream({\n pull(controller) {\n controller.error(new Error('stream broke'))\n },\n start(controller) {\n controller.enqueue(Buffer.from('partial-chunk'))\n },\n })\n const handler = vi.fn(\n async () => new Response(stream, { headers: { 'Content-Type': 'video/mp4' }, status: 200 }),\n )\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const tempFileDir = os.tmpdir()\n const filesBefore = new Set(await fs.readdir(tempFileDir))\n\n await expect(getFileFromClientUpload({ file, req })).rejects.toThrow('stream broke')\n\n const filesAfter = await fs.readdir(tempFileDir)\n const leftoverTempFiles = filesAfter.filter(\n (entry) => !filesBefore.has(entry) && entry.startsWith('payload-client-upload-'),\n )\n expect(leftoverTempFiles).toEqual([])\n })\n\n it('requires full content and skips the Range header when the request carries crop or size edits', async () => {\n const handler = vi.fn(async (handlerReq: PayloadRequest) => {\n expect(handlerReq.headers.get('Range')).toBeNull()\n return new Response(MINIMAL_PNG, { headers: { 'Content-Type': 'image/png' }, status: 200 })\n })\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n req.query = { uploadEdits: { heightInPixels: 100, widthInPixels: 100 } }\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(handler).toHaveBeenCalledTimes(1)\n expect(result.tempFilePath).toBeDefined()\n expect(result.data.length).toBe(0)\n })\n\n it('treats a null response body as a legitimate zero-byte file', async () => {\n const handler = vi.fn(\n async () => new Response(null, { headers: { 'Content-Type': 'video/mp4' }, status: 200 }),\n )\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile({ size: 0 })\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(result.tempFilePath).toBeDefined()\n const written = await fs.readFile(result.tempFilePath!)\n expect(written.length).toBe(0)\n })\n})\n"],"names":["fs","os","afterEach","describe","expect","it","vi","HEADER_PROBE_BYTE_LENGTH","getFileFromClientUpload","getImageSize","MINIMAL_PNG","signature","Buffer","from","ihdrData","alloc","writeUInt32BE","ihdrChunk","concat","tempFilesToRemove","unstubAllGlobals","tempFilePath","rm","force","length","createReq","handlers","upload","request","Request","req","query","payload","collections","media","config","disableLocalStorage","logger","error","fn","videoFile","overrides","clientUploadContext","prefix","collectionSlug","filename","mimeType","size","imageFile","handler","file","result","not","toHaveBeenCalled","Object","prototype","hasOwnProperty","call","toBe","toEqual","data","toBeUndefined","name","handlerReq","headers","get","Response","status","toBeLessThanOrEqual","resolves","height","width","hasCancelled","hasReadPastBoundary","firstChunk","stream","ReadableStream","cancel","pull","controller","Error","start","enqueue","highWaterMark","signal","toThrow","toBeInstanceOf","AbortSignal","toHaveBeenCalledTimes","mockResolvedValueOnce","push","toBeDefined","chunks","chunk","close","response","arrayBufferTripwire","defineProperty","value","mimeTypes","written","readFile","toString","map","join","firstHandler","secondHandler","undefined","redirectTarget","Location","fetchMock","stubGlobal","toHaveBeenCalledWith","Range","tempFileDir","tmpdir","filesBefore","Set","readdir","rejects","filesAfter","leftoverTempFiles","filter","entry","has","startsWith","toBeNull","uploadEdits","heightInPixels","widthInPixels"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/uploads/getFileFromClientUpload.spec.ts"],"sourcesContent":["import type { PayloadRequest } from '../types/index.js'\nimport type { SanitizedUploadConfig } from './types.js'\n\nimport fs from 'fs/promises'\nimport os from 'os'\nimport { afterEach, describe, expect, it, vi } from 'vitest'\n\nimport { HEADER_PROBE_BYTE_LENGTH } from './getFileContentRequirement.js'\nimport { type ClientUploadData, getFileFromClientUpload } from './getFileFromClientUpload.js'\nimport { getImageSize } from './getImageSize.js'\n\nconst MINIMAL_PNG = (() => {\n const signature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])\n const ihdrData = Buffer.alloc(13)\n ihdrData.writeUInt32BE(400, 0) // width\n ihdrData.writeUInt32BE(300, 4) // height\n ihdrData[8] = 8 // bit depth\n ihdrData[9] = 2 // color type: RGB\n const ihdrChunk = Buffer.concat([\n Buffer.from([0, 0, 0, 13]),\n Buffer.from('IHDR'),\n ihdrData,\n Buffer.alloc(4), // crc (unchecked by dimension probing)\n ])\n return Buffer.concat([signature, ihdrChunk])\n})()\n\nconst tempFilesToRemove: string[] = []\n\nafterEach(async () => {\n vi.unstubAllGlobals()\n for (const tempFilePath of tempFilesToRemove) {\n await fs.rm(tempFilePath, { force: true })\n }\n tempFilesToRemove.length = 0\n})\n\nconst createReq = ({\n handlers,\n upload,\n}: {\n handlers: NonNullable<SanitizedUploadConfig['handlers']>\n upload?: Partial<SanitizedUploadConfig>\n}): PayloadRequest => {\n const request = new Request('http://localhost/api/media')\n const req = request as unknown as PayloadRequest\n\n req.query = {}\n req.payload = {\n collections: {\n media: {\n config: {\n upload: {\n disableLocalStorage: true,\n handlers,\n ...upload,\n },\n },\n },\n },\n config: {\n upload: {},\n },\n logger: { error: vi.fn() },\n } as unknown as PayloadRequest['payload']\n\n return req\n}\n\nconst videoFile = (overrides: Partial<ClientUploadData> = {}): ClientUploadData => ({\n clientUploadContext: { prefix: 'abc' },\n collectionSlug: 'media',\n filename: 'clip.mp4',\n mimeType: 'video/mp4',\n size: 10,\n ...overrides,\n})\n\nconst imageFile = (overrides: Partial<ClientUploadData> = {}): ClientUploadData => ({\n clientUploadContext: { prefix: 'abc' },\n collectionSlug: 'media',\n filename: 'photo.png',\n mimeType: 'image/png',\n size: MINIMAL_PNG.length,\n ...overrides,\n})\n\ndescribe('getFileFromClientUpload', () => {\n it('does not call a handler and returns empty data with an own clientUploadContext property when no content is required', async () => {\n const handler = vi.fn()\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(handler).not.toHaveBeenCalled()\n expect(Object.prototype.hasOwnProperty.call(result, 'clientUploadContext')).toBe(true)\n expect(result.clientUploadContext).toEqual({ prefix: 'abc' })\n expect(result.data.length).toBe(0)\n expect(result.tempFilePath).toBeUndefined()\n expect(result.name).toBe('clip.mp4')\n expect(result.size).toBe(10)\n })\n\n it('requests a bounded byte range and returns dimensions for a header-only image', async () => {\n const handler = vi.fn(async (handlerReq: PayloadRequest) => {\n expect(handlerReq.headers.get('Range')).toBe(`bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}`)\n return new Response(MINIMAL_PNG, {\n headers: { 'Content-Type': 'image/png' },\n status: 206,\n })\n })\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(result.data.length).toBeLessThanOrEqual(HEADER_PROBE_BYTE_LENGTH)\n expect(result.tempFilePath).toBeUndefined()\n await expect(getImageSize({ file: result })).resolves.toEqual({ height: 300, width: 400 })\n })\n\n it('caps the header read at the boundary and cancels the stream without pulling past it', async () => {\n let hasCancelled = false\n let hasReadPastBoundary = false\n const firstChunk = Buffer.concat([\n MINIMAL_PNG,\n Buffer.alloc(HEADER_PROBE_BYTE_LENGTH - MINIMAL_PNG.length),\n ])\n const stream = new ReadableStream(\n {\n cancel() {\n hasCancelled = true\n },\n pull(controller) {\n hasReadPastBoundary = true\n controller.error(new Error('Read past header boundary'))\n },\n start(controller) {\n controller.enqueue(firstChunk)\n },\n },\n { highWaterMark: 0 },\n )\n\n const handler = vi.fn(\n async () => new Response(stream, { headers: { 'Content-Type': 'image/png' }, status: 206 }),\n )\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile({ size: HEADER_PROBE_BYTE_LENGTH })\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(result.data.length).toBe(HEADER_PROBE_BYTE_LENGTH)\n expect(hasCancelled).toBe(true)\n expect(hasReadPastBoundary).toBe(false)\n })\n\n it('lets a handler read native Request properties like signal through the Range-scoped proxy', async () => {\n const handler = vi.fn(async (handlerReq: PayloadRequest) => {\n expect(() => handlerReq.signal).not.toThrow()\n expect(handlerReq.signal).toBeInstanceOf(AbortSignal)\n return new Response(MINIMAL_PNG, {\n headers: { 'Content-Type': 'image/png' },\n status: 206,\n })\n })\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n await getFileFromClientUpload({ file, req })\n\n expect(handler).toHaveBeenCalledTimes(1)\n })\n\n it('falls back to a full fetch when the bounded header cannot be probed for dimensions', async () => {\n const handler = vi\n .fn()\n .mockResolvedValueOnce(\n new Response(Buffer.from('not an image'), {\n headers: { 'Content-Type': 'image/png' },\n status: 206,\n }),\n )\n .mockResolvedValueOnce(\n new Response(MINIMAL_PNG, { headers: { 'Content-Type': 'image/png' }, status: 200 }),\n )\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(handler).toHaveBeenCalledTimes(2)\n expect(result.tempFilePath).toBeDefined()\n expect(result.data.length).toBe(0)\n })\n\n it('streams a full-content response to a temp file without buffering the whole body', async () => {\n const chunks = [\n Buffer.from('chunk-one-'),\n Buffer.from('chunk-two-'),\n Buffer.from('chunk-three'),\n ]\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\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 = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile({ size: 30 })\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(result.data).toEqual(Buffer.alloc(0))\n expect(arrayBufferTripwire).not.toHaveBeenCalled()\n const written = await fs.readFile(result.tempFilePath!)\n expect(written.toString()).toBe(chunks.map((chunk) => chunk.toString()).join(''))\n })\n\n it('runs every handler and uses the last one that returns a response, matching v3', async () => {\n const firstHandler = vi.fn(async () => new Response(Buffer.from('first-response')))\n const secondHandler = vi.fn(async () => new Response(Buffer.from('second-response')))\n const req = createReq({\n handlers: [firstHandler, secondHandler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(firstHandler).toHaveBeenCalledTimes(1)\n expect(secondHandler).toHaveBeenCalledTimes(1)\n const written = await fs.readFile(result.tempFilePath!)\n expect(written.toString()).toBe('second-response')\n })\n\n it('falls through to the next handler when the first returns nothing', async () => {\n const firstHandler = vi.fn(async () => undefined)\n const secondHandler = vi.fn(async () => new Response(Buffer.alloc(10), { status: 200 }))\n const req = createReq({\n handlers: [firstHandler, secondHandler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(firstHandler).toHaveBeenCalledTimes(1)\n expect(secondHandler).toHaveBeenCalledTimes(1)\n })\n\n it('follows exactly one redirect from a handler response', async () => {\n const redirectTarget = 'http://storage.example.com/file.mp4'\n const handler = vi.fn(\n async () => new Response(null, { headers: { Location: redirectTarget }, status: 302 }),\n )\n const fetchMock = vi.fn(\n async () =>\n new Response(Buffer.alloc(10), { headers: { 'Content-Type': 'video/mp4' }, status: 200 }),\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(fetchMock).toHaveBeenCalledTimes(1)\n expect(fetchMock).toHaveBeenCalledWith(redirectTarget)\n })\n\n it('forwards the Range header through a redirect during the bounded header probe', async () => {\n const redirectTarget = 'http://storage.example.com/photo.png'\n const handler = vi.fn(\n async () => new Response(null, { headers: { Location: redirectTarget }, status: 302 }),\n )\n const fetchMock = vi.fn(\n async () =>\n new Response(MINIMAL_PNG, { headers: { 'Content-Type': 'image/png' }, status: 206 }),\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(result.tempFilePath).toBeUndefined()\n expect(fetchMock).toHaveBeenCalledTimes(1)\n expect(fetchMock).toHaveBeenCalledWith(redirectTarget, {\n headers: { Range: `bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}` },\n })\n })\n\n it('removes the partial temp file when the stream fails before completion', async () => {\n const stream = new ReadableStream({\n pull(controller) {\n controller.error(new Error('stream broke'))\n },\n start(controller) {\n controller.enqueue(Buffer.from('partial-chunk'))\n },\n })\n const handler = vi.fn(\n async () => new Response(stream, { headers: { 'Content-Type': 'video/mp4' }, status: 200 }),\n )\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const tempFileDir = os.tmpdir()\n const filesBefore = new Set(await fs.readdir(tempFileDir))\n\n await expect(getFileFromClientUpload({ file, req })).rejects.toThrow('stream broke')\n\n const filesAfter = await fs.readdir(tempFileDir)\n const leftoverTempFiles = filesAfter.filter(\n (entry) => !filesBefore.has(entry) && entry.startsWith('payload-client-upload-'),\n )\n expect(leftoverTempFiles).toEqual([])\n })\n\n it('requires full content and skips the Range header when the request carries crop or size edits', async () => {\n const handler = vi.fn(async (handlerReq: PayloadRequest) => {\n expect(handlerReq.headers.get('Range')).toBeNull()\n return new Response(MINIMAL_PNG, { headers: { 'Content-Type': 'image/png' }, status: 200 })\n })\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n req.query = { uploadEdits: { heightInPixels: 100, widthInPixels: 100 } }\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(handler).toHaveBeenCalledTimes(1)\n expect(result.tempFilePath).toBeDefined()\n expect(result.data.length).toBe(0)\n })\n\n it('treats a null response body as a legitimate zero-byte file', async () => {\n const handler = vi.fn(\n async () => new Response(null, { headers: { 'Content-Type': 'video/mp4' }, status: 200 }),\n )\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile({ size: 0 })\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(result.tempFilePath).toBeDefined()\n const written = await fs.readFile(result.tempFilePath!)\n expect(written.length).toBe(0)\n })\n\n it('streams the full file to disk when a non-image client upload carries no context', async () => {\n const handler = vi.fn(\n async () =>\n new Response(Buffer.from('full-video-bytes'), {\n headers: { 'Content-Type': 'video/mp4' },\n status: 200,\n }),\n )\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = videoFile({ clientUploadContext: undefined })\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(handler).toHaveBeenCalledTimes(1)\n expect(result.tempFilePath).toBeDefined()\n tempFilesToRemove.push(result.tempFilePath!)\n\n const written = await fs.readFile(result.tempFilePath!)\n expect(written.toString()).toBe('full-video-bytes')\n })\n\n it('streams the full file to disk when an image client upload carries no context', async () => {\n const handler = vi.fn(async (handlerReq: PayloadRequest) => {\n expect(handlerReq.headers.get('Range')).toBeNull()\n return new Response(MINIMAL_PNG, { headers: { 'Content-Type': 'image/png' }, status: 200 })\n })\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile({ clientUploadContext: undefined })\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(handler).toHaveBeenCalledTimes(1)\n expect(result.tempFilePath).toBeDefined()\n tempFilesToRemove.push(result.tempFilePath!)\n\n const written = await fs.readFile(result.tempFilePath!)\n expect(written).toEqual(MINIMAL_PNG)\n })\n})\n"],"names":["fs","os","afterEach","describe","expect","it","vi","HEADER_PROBE_BYTE_LENGTH","getFileFromClientUpload","getImageSize","MINIMAL_PNG","signature","Buffer","from","ihdrData","alloc","writeUInt32BE","ihdrChunk","concat","tempFilesToRemove","unstubAllGlobals","tempFilePath","rm","force","length","createReq","handlers","upload","request","Request","req","query","payload","collections","media","config","disableLocalStorage","logger","error","fn","videoFile","overrides","clientUploadContext","prefix","collectionSlug","filename","mimeType","size","imageFile","handler","file","result","not","toHaveBeenCalled","Object","prototype","hasOwnProperty","call","toBe","toEqual","data","toBeUndefined","name","handlerReq","headers","get","Response","status","toBeLessThanOrEqual","resolves","height","width","hasCancelled","hasReadPastBoundary","firstChunk","stream","ReadableStream","cancel","pull","controller","Error","start","enqueue","highWaterMark","signal","toThrow","toBeInstanceOf","AbortSignal","toHaveBeenCalledTimes","mockResolvedValueOnce","push","toBeDefined","chunks","chunk","close","response","arrayBufferTripwire","defineProperty","value","mimeTypes","written","readFile","toString","map","join","firstHandler","secondHandler","undefined","redirectTarget","Location","fetchMock","stubGlobal","toHaveBeenCalledWith","Range","tempFileDir","tmpdir","filesBefore","Set","readdir","rejects","filesAfter","leftoverTempFiles","filter","entry","has","startsWith","toBeNull","uploadEdits","heightInPixels","widthInPixels"],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { PayloadRequest } from '../types/index.js';
2
+ type Args = {
3
+ /**
4
+ * A path the caller has already removed, so the redundant unlink is skipped.
5
+ */
6
+ alreadyUnlinkedPath?: string;
7
+ req: PayloadRequest;
8
+ };
9
+ /**
10
+ * Removes the temp file a client upload materialized, if it is still on disk. Logs rather than
11
+ * throws, so cleanup can never replace a response or mask the error that a caller is handling.
12
+ *
13
+ * The path is tracked on `req.context` rather than read back off `req.file` because the file
14
+ * can outlive both: plugin-cloud-storage's afterChange hook clears `req.file` before cleanup
15
+ * runs, and the request parser materializes the file for whatever `collectionSlug` the multipart
16
+ * field names, which does not have to be a collection the endpoint writes a document to.
17
+ */
18
+ export declare const unlinkClientUploadTempFile: (args: Args) => Promise<void>;
19
+ export {};
20
+ //# sourceMappingURL=unlinkClientUploadTempFile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unlinkClientUploadTempFile.d.ts","sourceRoot":"","sources":["../../src/uploads/unlinkClientUploadTempFile.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAIvD,KAAK,IAAI,GAAG;IACV;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,GAAG,EAAE,cAAc,CAAA;CACpB,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAqBpE,CAAA"}
@@ -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;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,CAgCzD,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,11 +1,12 @@
1
1
  import fs from 'fs/promises';
2
2
  import { mapAsync } from '../utilities/mapAsync.js';
3
- import { CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY } from './getFileFromClientUpload.js';
3
+ import { unlinkClientUploadTempFile } from './unlinkClientUploadTempFile.js';
4
4
  /**
5
5
  * Cleanup temp files after operation lifecycle
6
6
  */ export const unlinkTempFiles = async ({ collectionConfig, config, req })=>{
7
7
  const { file } = req;
8
8
  const isClientUploadTempFile = Boolean(file?.tempFilePath && Object.prototype.hasOwnProperty.call(file, 'clientUploadContext'));
9
+ let unlinkedTempFilePath;
9
10
  if (collectionConfig.upload && (config.upload?.useTempFiles || isClientUploadTempFile)) {
10
11
  const fileArray = [
11
12
  {
@@ -16,17 +17,14 @@ import { CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY } from './getFileFromClientUpl
16
17
  // Still need this check because this will not be populated if using local API
17
18
  if (file?.tempFilePath) {
18
19
  await fs.unlink(file.tempFilePath);
20
+ unlinkedTempFilePath = file.tempFilePath;
19
21
  }
20
22
  });
21
23
  }
22
- // plugin-cloud-storage's afterChange hook clears req.file after uploading generated image
23
- // sizes, before this cleanup runs, so the materializer's temp file is tracked separately and
24
- // may still need removing even though req.file no longer references it.
25
- const clientUploadTempFilePath = req.context?.[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY];
26
- if (typeof clientUploadTempFilePath === 'string' && clientUploadTempFilePath !== file?.tempFilePath) {
27
- delete req.context[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY];
28
- await fs.unlink(clientUploadTempFilePath);
29
- }
24
+ await unlinkClientUploadTempFile({
25
+ alreadyUnlinkedPath: unlinkedTempFilePath,
26
+ req
27
+ });
30
28
  };
31
29
 
32
30
  //# sourceMappingURL=unlinkTempFiles.js.map